use crate::fixer::{Fixer, FixerError};
use crate::rule::referent_rule::RuleRegistration;
use crate::rule::Rule;
use crate::rule_config::RuleConfigError;
use crate::rule_core::RuleCoreError;
use crate::transform::{Transform, TransformError};
use crate::RuleCore;
use std::collections::{HashMap, HashSet};
type RResult<T> = std::result::Result<T, RuleCoreError>;
pub enum CheckHint<'r> {
Global,
Normal,
Rewriter(&'r HashSet<&'r str>),
}
pub fn check_rule_with_hint<'r>(
rule: &'r Rule,
utils: &'r RuleRegistration,
constraints: &'r HashMap<String, Rule>,
transform: &'r Option<Transform>,
fixer: &Vec<Fixer>,
hint: CheckHint<'r>,
) -> RResult<()> {
match hint {
CheckHint::Global => {
check_vars(rule, utils, constraints, transform, fixer)?;
}
CheckHint::Normal => {
check_utils_defined(rule, constraints)?;
check_vars(rule, utils, constraints, transform, fixer)?;
}
CheckHint::Rewriter(upper_vars) => {
if fixer.is_empty() {
return Err(RuleCoreError::Fixer(FixerError::InvalidRewriter));
}
check_utils_defined(rule, constraints)?;
check_vars_in_rewriter(rule, utils, constraints, transform, fixer, upper_vars)?;
}
}
Ok(())
}
fn check_vars_in_rewriter<'r>(
rule: &'r Rule,
utils: &'r RuleRegistration,
constraints: &'r HashMap<String, Rule>,
transform: &'r Option<Transform>,
fixer: &Vec<Fixer>,
upper_var: &HashSet<&str>,
) -> RResult<()> {
let vars = get_vars_from_rules(rule, utils);
let vars = check_var_in_constraints(vars, constraints)?;
let mut vars = check_var_in_transform(vars, transform)?;
for v in upper_var {
vars.insert(v);
}
check_var_in_fix(vars, fixer)?;
Ok(())
}
fn check_utils_defined(rule: &Rule, constraints: &HashMap<String, Rule>) -> RResult<()> {
rule.verify_util()?;
for constraint in constraints.values() {
constraint.verify_util()?;
}
Ok(())
}
fn check_vars<'r>(
rule: &'r Rule,
utils: &'r RuleRegistration,
constraints: &'r HashMap<String, Rule>,
transform: &'r Option<Transform>,
fixer: &Vec<Fixer>,
) -> RResult<()> {
let vars = get_vars_from_rules(rule, utils);
let vars = check_var_in_constraints(vars, constraints)?;
let vars = check_var_in_transform(vars, transform)?;
check_var_in_fix(vars, fixer)?;
Ok(())
}
fn get_vars_from_rules<'r>(rule: &'r Rule, utils: &'r RuleRegistration) -> HashSet<&'r str> {
let mut vars = rule.defined_vars();
for var in utils.get_local_util_vars() {
vars.insert(var);
}
vars
}
fn check_var_in_constraints<'r>(
mut vars: HashSet<&'r str>,
constraints: &'r HashMap<String, Rule>,
) -> RResult<HashSet<&'r str>> {
for rule in constraints.values() {
for var in rule.defined_vars() {
vars.insert(var);
}
}
for var in constraints.keys() {
let var: &str = var;
if !vars.contains(var) {
return Err(RuleCoreError::UndefinedMetaVar(
var.to_owned(),
"constraints",
));
}
}
Ok(vars)
}
fn check_var_in_transform<'r>(
mut vars: HashSet<&'r str>,
transform: &'r Option<Transform>,
) -> RResult<HashSet<&'r str>> {
let Some(transform) = transform else {
return Ok(vars);
};
for var in transform.keys() {
if !vars.insert(var) {
return Err(RuleCoreError::Transform(TransformError::AlreadyDefined(
var.to_string(),
)));
}
}
for trans in transform.values() {
let needed = trans.used_vars();
if !vars.contains(needed) {
return Err(RuleCoreError::UndefinedMetaVar(
needed.to_string(),
"transform",
));
}
}
Ok(vars)
}
fn check_var_in_fix(vars: HashSet<&str>, fixers: &Vec<Fixer>) -> RResult<()> {
for fixer in fixers {
for var in fixer.used_vars() {
if !vars.contains(&var) {
return Err(RuleCoreError::UndefinedMetaVar(var.to_string(), "fix"));
}
}
}
Ok(())
}
pub fn check_rewriters_in_transform(
rule: &RuleCore,
rewriters: &HashMap<String, RuleCore>,
) -> Result<(), RuleConfigError> {
if let Some(err) = check_one_rewriter_in_rule(rule, rewriters) {
return Err(err);
}
let error = rewriters
.values()
.find_map(|rewriter| check_one_rewriter_in_rule(rewriter, rewriters));
if let Some(err) = error {
return Err(err);
}
Ok(())
}
fn check_one_rewriter_in_rule(
rule: &RuleCore,
rewriters: &HashMap<String, RuleCore>,
) -> Option<RuleConfigError> {
let transform = rule.transform.as_ref()?;
let mut used_rewriters = transform
.values()
.flat_map(|trans| trans.used_rewriters().iter());
let undefined_writers = used_rewriters.find(|r| !rewriters.contains_key(*r))?;
Some(RuleConfigError::UndefinedRewriter(
undefined_writers.to_string(),
))
}
#[cfg(test)]
mod test {
use super::*;
use crate::test::TypeScript;
use crate::{from_str, DeserializeEnv, SerializableGlobalRule, SerializableRuleCore};
#[test]
fn test_defined_vars() {
let env = DeserializeEnv::new(TypeScript::Tsx);
let ser_rule: SerializableRuleCore = from_str(
r"
rule: {pattern: $A = $B}
constraints:
A: { pattern: $C = $D }
transform:
E:
substring:
source: $B
startCar: 1",
)
.expect("should deser");
let matcher = ser_rule.get_matcher(env).expect("should parse");
assert_eq!(
matcher.defined_vars(),
["A", "B", "C", "D", "E"].into_iter().collect()
);
}
fn get_undefined(src: &str) -> (String, &str) {
let env = DeserializeEnv::new(TypeScript::Tsx);
let ser_rule: SerializableRuleCore = from_str(src).expect("should deser");
match ser_rule.get_matcher(env) {
Err(RuleCoreError::UndefinedMetaVar(name, section)) => (name, section),
_ => panic!("unexpected error"),
}
}
#[test]
fn test_undefined_vars_in_constraints() {
let (name, section) = get_undefined(
r"
rule: {pattern: $A}
constraints: {B: {pattern: bbb}}
",
);
assert_eq!(name, "B");
assert_eq!(section, "constraints");
}
#[test]
fn test_undefined_vars_in_transform() {
let (name, section) = get_undefined(
r"
rule: {pattern: $A}
constraints: {A: {pattern: $C}}
transform:
B:
replace: {source: $C, replace: a, by: b }
D:
replace: {source: $E, replace: a, by: b }
",
);
assert_eq!(name, "E");
assert_eq!(section, "transform");
}
#[test]
fn test_undefined_vars_in_fix() {
let (name, section) = get_undefined(
r"
rule: {pattern: $A}
constraints: {A: {pattern: $C}}
transform:
B:
replace: {source: $C, replace: a, by: b }
fix: $D
",
);
assert_eq!(name, "D");
assert_eq!(section, "fix");
}
#[test]
fn test_defined_vars_in_utils() {
let env = DeserializeEnv::new(TypeScript::Tsx);
let ser_rule: SerializableRuleCore = from_str(
r"
rule: {matches: test}
utils:
test: { pattern: $B}",
)
.expect("should deser");
let matcher = ser_rule.get_matcher(env).expect("should parse");
assert_eq!(matcher.defined_vars(), ["B"].into_iter().collect());
}
#[test]
fn test_parameterized_global_rule_only_exports_argument_vars() {
let globals: Vec<SerializableGlobalRule<TypeScript>> = from_str(
r"
- id: global-rule
arguments: [export-var]
language: Tsx
rule:
pattern: Some($A)
matches: export-var
",
)
.expect("should parse globals");
let globals = DeserializeEnv::parse_global_utils(globals).expect("should parse globals");
let env = DeserializeEnv::new(TypeScript::Tsx).with_globals(&globals);
let ser_rule: SerializableRuleCore = from_str(
r"
rule:
matches:
global-rule:
export-var:
pattern: $EXP
fix: $EXP
",
)
.expect("should deser");
let matcher = ser_rule.get_matcher(env).expect("should parse");
assert_eq!(matcher.defined_vars(), ["EXP"].into_iter().collect());
}
#[test]
fn test_parameterized_global_rule_internal_var_not_in_defined_vars() {
let globals: Vec<SerializableGlobalRule<TypeScript>> = from_str(
r"
- id: global-rule
arguments: [export-var]
language: Tsx
rule:
pattern: Some($A)
matches: export-var
",
)
.expect("should parse globals");
let globals = DeserializeEnv::parse_global_utils(globals).expect("should parse globals");
let env = DeserializeEnv::new(TypeScript::Tsx).with_globals(&globals);
let ser_rule: SerializableRuleCore = from_str(
r"
rule:
matches:
global-rule:
export-var:
pattern: $EXP
fix: $A
",
)
.expect("should deser");
match ser_rule.get_matcher(env) {
Err(RuleCoreError::UndefinedMetaVar(name, section)) => {
assert_eq!(name, "A");
assert_eq!(section, "fix");
}
_ => panic!("unexpected error"),
}
}
#[test]
fn test_use_vars_in_utils() {
let env = DeserializeEnv::new(TypeScript::Tsx);
let ser_rule: SerializableRuleCore = from_str(
r"
utils:
test: { pattern: $B }
rule: { matches: test }
fix: $B = 123",
)
.expect("should deser");
let matcher = ser_rule.get_matcher(env).expect("should parse");
assert_eq!(matcher.defined_vars(), ["B"].into_iter().collect());
}
#[test]
fn test_defined_vars_cyclic() {
let env = DeserializeEnv::new(TypeScript::Tsx);
let ser_rule: SerializableRuleCore = from_str(
r"
rule: { matches: test1 }
utils:
test1: { pattern: $B, inside: {matches: test2} }
test2: { pattern: $A, has: {matches: test1} }",
)
.expect("should deser");
let matcher = ser_rule.get_matcher(env).expect("should parse");
assert_eq!(matcher.defined_vars(), ["A", "B"].into_iter().collect());
}
#[test]
fn test_transform_already_defined() {
let env = DeserializeEnv::new(TypeScript::Tsx);
let ser_rule: SerializableRuleCore = from_str(
r"
rule: { pattern: $A = $B }
transform:
B: { substring: { source: $A } }",
)
.expect("should deser");
let matcher = ser_rule.get_matcher(env);
match matcher {
Err(RuleCoreError::Transform(TransformError::AlreadyDefined(b))) => {
assert_eq!(b, "B");
}
_ => panic!("unexpected error"),
}
}
}