normalize_syntax_rules/lib.rs
1//! Syntax-based linting with tree-sitter queries.
2//!
3//! This crate provides:
4//! - Rule loading from multiple sources (builtins, user global, project)
5//! - Rule execution with combined query optimization
6//! - Pluggable data sources for rule conditionals
7//!
8//! # Rule File Format
9//!
10//! ```scm
11//! # ---
12//! # id = "no-unwrap"
13//! # severity = "warning"
14//! # message = "Avoid unwrap() on user input"
15//! # allow = ["**/tests/**"]
16//! # requires = { "rust.edition" = ">=2024" }
17//! # enabled = true # set to false to disable a builtin
18//! # fix = "" # empty = delete match, or use "$capture" to substitute
19//! # ---
20//!
21//! (call_expression
22//! function: (field_expression
23//! field: (field_identifier) @method)
24//! (#eq? @method "unwrap")) @match
25//! ```
26
27mod builtin;
28mod loader;
29mod runner;
30mod sources;
31
32pub use builtin::BUILTIN_RULES;
33pub use loader::{RuleOverride, RulesConfig, load_all_rules, parse_rule_content};
34pub use runner::{DebugFlags, Finding, apply_fixes, evaluate_predicates, run_rules};
35pub use sources::{
36 EnvSource, GitSource, GoSource, PathSource, PythonSource, RuleSource, RustSource,
37 SourceContext, SourceRegistry, TypeScriptSource, builtin_registry,
38};
39
40use glob::Pattern;
41use std::collections::HashMap;
42use std::path::PathBuf;
43
44/// Severity level for rule findings.
45#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
46pub enum Severity {
47 Error,
48 #[default]
49 Warning,
50 Info,
51}
52
53impl std::fmt::Display for Severity {
54 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
55 match self {
56 Severity::Error => write!(f, "error"),
57 Severity::Warning => write!(f, "warning"),
58 Severity::Info => write!(f, "info"),
59 }
60 }
61}
62
63impl std::str::FromStr for Severity {
64 type Err = String;
65
66 fn from_str(s: &str) -> Result<Self, Self::Err> {
67 match s.to_lowercase().as_str() {
68 "error" => Ok(Severity::Error),
69 "warning" | "warn" => Ok(Severity::Warning),
70 "info" | "note" => Ok(Severity::Info),
71 _ => Err(format!("unknown severity: {}", s)),
72 }
73 }
74}
75
76/// A syntax rule definition.
77#[derive(Debug)]
78pub struct Rule {
79 /// Unique identifier for this rule.
80 pub id: String,
81 /// The tree-sitter query pattern.
82 pub query_str: String,
83 /// Severity level.
84 pub severity: Severity,
85 /// Message to display when the rule matches.
86 pub message: String,
87 /// Glob patterns for files where matches are allowed.
88 pub allow: Vec<Pattern>,
89 /// Source file path of this rule (empty for builtins).
90 pub source_path: PathBuf,
91 /// Languages this rule applies to (inferred from query or explicit).
92 pub languages: Vec<String>,
93 /// Whether this rule is enabled.
94 pub enabled: bool,
95 /// Whether this is a builtin rule.
96 pub builtin: bool,
97 /// Conditions that must be met for this rule to apply.
98 /// Format: { "namespace.key" = "value" } or { "namespace.key" = ">=value" }
99 pub requires: HashMap<String, String>,
100 /// Auto-fix template using capture names from the query.
101 /// Use `$capture_name` to reference captures, `$match` for the full match.
102 /// Empty string means "delete the match".
103 pub fix: Option<String>,
104}
105
106/// A builtin rule definition (id, content).
107pub struct BuiltinRule {
108 pub id: &'static str,
109 pub content: &'static str,
110}