use std::path::Path;
use ast_grep_config::RuleConfig;
use ast_grep_language::SupportLang;
use globset::GlobSet;
use serde_yaml::Value;
use crate::rule::{FixSafety, Rule, RuleId};
use crate::scanner::{build_glob_set, compile_ast_grep_rule, AstGrepRule, ScanError};
use crate::source::RuleOrigin;
use super::FixerKind;
#[derive(Clone, Copy)]
pub(super) enum FixAction {
Template,
Builtin(FixerKind),
}
pub(super) struct CompiledFixRule {
pub(super) rule_id: RuleId,
pub(super) lang: SupportLang,
pub(super) skip_test_code: bool,
pub(super) action: FixAction,
pub(super) config: RuleConfig<SupportLang>,
pub(super) detect: Option<RuleConfig<SupportLang>>,
pub(super) files: Option<GlobSet>,
pub(super) ignores: Option<GlobSet>,
}
impl CompiledFixRule {
pub(super) fn applies_to(&self, path: &Path) -> bool {
self.files.as_ref().is_none_or(|g| g.is_match(path))
&& !self.ignores.as_ref().is_some_and(|g| g.is_match(path))
}
}
fn is_trusted_for_fix(rule: &Rule, allow_external: &[String], unsafe_fixes: bool) -> bool {
let level_allowed = match rule.fix_safety() {
Some(FixSafety::Safe) => true,
Some(FixSafety::Unsafe) => unsafe_fixes,
None => false,
};
level_allowed
&& (rule.origin == RuleOrigin::Builtin
|| allow_external.iter().any(|id| id == rule.id.as_str()))
}
fn compile_matcher(
rule: &Rule,
lang: SupportLang,
matcher: &Value,
constraints: Option<&Value>,
fix: Option<&str>,
) -> Result<RuleConfig<SupportLang>, ScanError> {
let ast_grep_rule = AstGrepRule {
id: rule.id.as_str(),
language: lang,
severity: &rule.severity,
message: &rule.message,
note: rule.note.as_deref(),
rule: matcher,
constraints,
fix,
files: rule.files.as_deref(),
ignores: rule.ignores.as_deref(),
};
compile_ast_grep_rule(&ast_grep_rule).map_err(|source| ScanError::RuleCompile {
id: rule.id.clone(),
source,
})
}
pub(super) fn compile_fix_rules(
rules: &[Rule],
allow_external: &[String],
unsafe_fixes: bool,
) -> Result<Vec<CompiledFixRule>, ScanError> {
let mut compiled = Vec::new();
for rule in rules
.iter()
.filter(|r| is_trusted_for_fix(r, allow_external, unsafe_fixes))
{
let (action, rewrite) = match (rule.fixer, rule.rewrite.as_deref()) {
(Some(kind), _) => (FixAction::Builtin(kind), None),
(None, Some(rewrite)) => (FixAction::Template, Some(rewrite)),
(None, None) => continue,
};
let uses_dedicated_matcher = !rule.autofix_rule.is_null();
let detection_constraints = (!rule.constraints.is_null()).then_some(&rule.constraints);
let constraints = detection_constraints.filter(|_| !uses_dedicated_matcher);
let files = rule.files.as_deref().map(build_glob_set).transpose()?;
let ignores = rule.ignores.as_deref().map(build_glob_set).transpose()?;
for &lang in rule.language.ast_grep_langs() {
let config = compile_matcher(rule, lang, rule.fix_matcher(), constraints, rewrite)?;
let detect = uses_dedicated_matcher
.then(|| compile_matcher(rule, lang, &rule.rule, detection_constraints, None))
.transpose()?;
compiled.push(CompiledFixRule {
rule_id: rule.id.clone(),
lang,
skip_test_code: rule.skip_test_code,
action,
config,
detect,
files: files.clone(),
ignores: ignores.clone(),
});
}
}
Ok(compiled)
}
pub(super) fn needs_strum(rules: &[CompiledFixRule]) -> bool {
rules
.iter()
.any(|r| matches!(r.action, FixAction::Builtin(FixerKind::StrumEnumStr)))
}