use crate::RuleCore;
use crate::fixer::Fixer;
use crate::rewriter::Rewriter;
use crate::rule::Rule;
use crate::rule_config::RuleConfigError;
use crate::rule_core::RuleCoreError;
use crate::transform::{Transform, TransformError};
use std::collections::{HashMap, HashSet};
type RResult<T> = std::result::Result<T, RuleCoreError>;
pub enum CheckHint {
Global,
Normal,
}
pub fn check_rule_with_hint(rule: &RuleCore, hint: CheckHint) -> RResult<()> {
match hint {
CheckHint::Global => {
check_vars(rule)?;
}
CheckHint::Normal => {
check_utils_defined(rule)?;
check_vars(rule)?;
}
}
Ok(())
}
pub fn check_fix(rule: &RuleCore, fixer: &[Fixer]) -> RResult<()> {
let vars = check_vars(rule)?;
check_var_in_fix(vars, fixer)?;
Ok(())
}
pub fn check_rewriter_fix(
rule: &RuleCore,
fixer: &[Fixer],
upper_vars: &HashSet<&str>,
) -> RResult<()> {
let mut vars = check_vars(rule)?;
for v in upper_vars {
vars.insert(v);
}
check_var_in_fix(vars, fixer)?;
Ok(())
}
fn check_utils_defined(rule: &RuleCore) -> RResult<()> {
rule.rule.verify_util()?;
for constraint in rule.constraints.values() {
constraint.verify_util()?;
}
Ok(())
}
fn check_vars(rule: &RuleCore) -> RResult<HashSet<&str>> {
let vars = get_vars_from_rules(rule);
let vars = check_var_in_constraints(vars, &rule.constraints)?;
check_var_in_transform(vars, &rule.transform)
}
fn get_vars_from_rules(rule: &RuleCore) -> HashSet<&str> {
let mut vars = rule.rule.defined_vars();
for var in rule.registration.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: &[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, Rewriter>,
) -> 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.matcher, rewriters));
if let Some(err) = error {
return Err(err);
}
Ok(())
}
fn check_one_rewriter_in_rule(
rule: &RuleCore,
rewriters: &HashMap<String, Rewriter>,
) -> 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::{
DeserializeEnv, RuleConfig, SerializableGlobalRule, SerializableRuleConfig,
SerializableRuleCore, from_str,
};
#[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_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 ser_rule: SerializableRuleConfig<TypeScript> = from_str(
r"
language: Tsx
rule:
matches:
global-rule:
export-var:
pattern: $EXP
fix: $A
",
)
.expect("should deser");
let ret = RuleConfig::try_from(ser_rule, &globals);
match ret {
Err(RuleConfigError::Core(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"),
}
}
}