Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: move fixer to RuleCore #860

Merged
merged 6 commits into from Jan 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion crates/cli/src/print/colored_print/test.rs
Expand Up @@ -238,7 +238,7 @@ fix: '{rewrite}'"
.pop()
.unwrap();
let matcher = rule.get_matcher(&globals).expect("should parse");
let fixer = rule.fixer.as_ref().expect("should have fixer");
let fixer = matcher.fixer.as_ref().expect("should have fixer");
let matches = grep.root().find_all(&matcher);
let diffs = matches.map(|n| (Diff::generate(n, &pattern, fixer), &rule));
printer
Expand Down
4 changes: 2 additions & 2 deletions crates/cli/src/print/interactive_print.rs
Expand Up @@ -288,8 +288,8 @@ rule:
- pattern: $A++
fix: ($B, lifecycle.update(['$A']))",
);
let matcher = config.matcher;
let fixer = config.fixer.unwrap();
let mut matcher = config.matcher;
let fixer = matcher.fixer.take().unwrap();
let diffs = make_diffs(&root, matcher, &fixer);
let ret = apply_rewrite(diffs);
assert_eq!(ret, "let a = () => (c++, lifecycle.update(['c']))");
Expand Down
4 changes: 2 additions & 2 deletions crates/cli/src/scan.rs
Expand Up @@ -248,7 +248,7 @@
let diffs = matches
.into_iter()
.filter_map(|(m, rule)| {
let fix = rule.fixer.as_ref()?;
let fix = rule.matcher.fixer.as_ref()?;

Check warning on line 251 in crates/cli/src/scan.rs

View check run for this annotation

Codecov / codecov/patch

crates/cli/src/scan.rs#L251

Added line #L251 was not covered by tests
let diff = Diff::generate(m, &rule.matcher, fix);
Some((diff, rule))
})
Expand All @@ -266,7 +266,7 @@
) -> Result<()> {
let matches = matches.into_iter();
let file = SimpleFile::new(path.to_string_lossy(), file_content);
if let Some(fixer) = &rule.fixer {
if let Some(fixer) = &rule.matcher.fixer {

Check warning on line 269 in crates/cli/src/scan.rs

View check run for this annotation

Codecov / codecov/patch

crates/cli/src/scan.rs#L269

Added line #L269 was not covered by tests
let diffs = matches
.map(|m| (Diff::generate(m, &rule.matcher, fixer), rule))
.collect();
Expand Down
2 changes: 1 addition & 1 deletion crates/cli/src/verify/snapshot.rs
Expand Up @@ -91,7 +91,7 @@ impl TestSnapshot {
return Ok(None);
};
let labels = Label::from_matched(matched);
let Some(fix) = &rule_config.fixer else {
let Some(fix) = &rule_config.matcher.fixer else {
return Ok(Some(Self {
fixed: None,
labels,
Expand Down
4 changes: 2 additions & 2 deletions crates/config/src/combined.rs
Expand Up @@ -19,7 +19,7 @@
pub fn new(mut rules: Vec<&'r RuleConfig<L>>) -> Self {
// process fixable rule first, the order by id
// note, mapping.push will invert order so we sort fixable order in reverse
rules.sort_unstable_by_key(|r| (r.fixer.is_some(), &r.id));
rules.sort_unstable_by_key(|r| (r.fix.is_some(), &r.id));
let mut mapping = Vec::new();
for (idx, rule) in rules.iter().enumerate() {
for kind in &rule
Expand Down Expand Up @@ -85,7 +85,7 @@
continue;
}
let rule = &self.rules[idx];
if exclude_fix && rule.fixer.is_some() {
if exclude_fix && rule.fix.is_some() {

Check warning on line 88 in crates/config/src/combined.rs

View check run for this annotation

Codecov / codecov/patch

crates/config/src/combined.rs#L88

Added line #L88 was not covered by tests
continue;
}
let Some(ret) = rule.matcher.match_node(node.clone()) else {
Expand Down
5 changes: 2 additions & 3 deletions crates/config/src/lib.rs
@@ -1,10 +1,10 @@
mod combined;
mod constraints;
mod fixer;
mod maybe;
mod rule;
mod rule_collection;
mod rule_config;
mod rule_core;
mod transform;

use serde::Deserialize;
Expand All @@ -19,8 +19,7 @@ pub use rule::DeserializeEnv;
pub use rule::{Rule, RuleSerializeError, SerializableRule};
pub use rule_collection::RuleCollection;
pub use rule_config::{
RuleConfig, RuleConfigError, RuleWithConstraint, SerializableRuleConfig, SerializableRuleCore,
Severity,
RuleConfig, RuleConfigError, RuleCore, SerializableRuleConfig, SerializableRuleCore, Severity,
};
pub use transform::Transformation;

Expand Down
4 changes: 2 additions & 2 deletions crates/config/src/rule/deserialize_env.rs
Expand Up @@ -2,7 +2,7 @@ use super::referent_rule::{GlobalRules, ReferentRuleError, RuleRegistration};
use crate::maybe::Maybe;
use crate::rule::{self, Rule, RuleSerializeError, SerializableRule};
use crate::rule_config::{
into_map, RuleConfigError, SerializableRuleConfigCore, SerializableRuleCore,
into_map, RuleConfigError, SerializableRuleCore, SerializableRuleCoreWithId,
HerringtonDarkholme marked this conversation as resolved.
Show resolved Hide resolved
};

use ast_grep_core::language::Language;
Expand Down Expand Up @@ -144,7 +144,7 @@ impl<L: Language> DeserializeEnv<L> {

/// register global utils rule discovered in the config.
pub fn parse_global_utils(
utils: Vec<SerializableRuleConfigCore<L>>,
utils: Vec<SerializableRuleCoreWithId<L>>,
) -> Result<GlobalRules<L>, RuleConfigError> {
let registration = GlobalRules::default();
let utils = into_map(utils);
Expand Down
14 changes: 7 additions & 7 deletions crates/config/src/rule/referent_rule.rs
@@ -1,4 +1,4 @@
use crate::{Rule, RuleWithConstraint};
use crate::{Rule, RuleCore};

use ast_grep_core::language::Language;
use ast_grep_core::meta_var::MetaVarEnv;
Expand Down Expand Up @@ -27,10 +27,10 @@
self.0.write().unwrap()
}
}
pub type GlobalRules<L> = Registration<RuleWithConstraint<L>>;
pub type GlobalRules<L> = Registration<RuleCore<L>>;

impl<L: Language> GlobalRules<L> {
pub fn insert(&self, id: &str, rule: RuleWithConstraint<L>) -> Result<(), ReferentRuleError> {
pub fn insert(&self, id: &str, rule: RuleCore<L>) -> Result<(), ReferentRuleError> {

Check warning on line 33 in crates/config/src/rule/referent_rule.rs

View check run for this annotation

Codecov / codecov/patch

crates/config/src/rule/referent_rule.rs#L33

Added line #L33 was not covered by tests
let mut map = self.write();
if map.contains_key(id) {
return Err(ReferentRuleError::DuplicateRule(id.into()));
Expand All @@ -55,7 +55,7 @@
#[derive(Clone)]
pub struct RuleRegistration<L: Language> {
local: Registration<Rule<L>>,
global: Registration<RuleWithConstraint<L>>,
global: Registration<RuleCore<L>>,
}

// these are shit code
Expand All @@ -64,7 +64,7 @@
self.local.read()
}

fn get_global(&self) -> RwLockReadGuard<HashMap<String, RuleWithConstraint<L>>> {
fn get_global(&self) -> RwLockReadGuard<HashMap<String, RuleCore<L>>> {
self.global.read()
}

Expand Down Expand Up @@ -107,7 +107,7 @@

pub struct RegistrationRef<L: Language> {
local: Weak<RwLock<HashMap<String, Rule<L>>>>,
global: Weak<RwLock<HashMap<String, RuleWithConstraint<L>>>>,
global: Weak<RwLock<HashMap<String, RuleCore<L>>>>,
}
// these are shit code
impl<L: Language> RegistrationRef<L> {
Expand Down Expand Up @@ -156,7 +156,7 @@

fn eval_global<F, T>(&self, func: F) -> Option<T>
where
F: FnOnce(&RuleWithConstraint<L>) -> T,
F: FnOnce(&RuleCore<L>) -> T,
{
let registration = self.reg_ref.unref();
let rules = registration.get_global();
Expand Down
120 changes: 23 additions & 97 deletions crates/config/src/rule_config.rs
@@ -1,21 +1,15 @@
use crate::fixer::{Fixer, FixerError, SerializableFixer};
use crate::rule::{RuleSerializeError, SerializableRule};
use crate::transform::Transformation;
use crate::DeserializeEnv;
use crate::GlobalRules;

pub use crate::constraints::{
try_deserialize_matchers, RuleWithConstraint, SerializableMetaVarMatcher,
SerializeConstraintsError,
pub use crate::rule_core::{
try_deserialize_matchers, RuleConfigError, RuleCore, SerializableMetaVarMatcher,
SerializableRuleCore, SerializeConstraintsError,
};
use ast_grep_core::language::Language;
use ast_grep_core::meta_var::MetaVarMatchers;
use ast_grep_core::replacer::{IndentSensitive, Replacer};
use ast_grep_core::replacer::Replacer;
use ast_grep_core::{NodeMatch, StrDoc};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use serde_yaml::{with::singleton_map_recursive::deserialize, Deserializer, Error as YamlError};
use thiserror::Error;
use serde_yaml::{with::singleton_map_recursive::deserialize, Deserializer};

use std::collections::HashMap;
use std::ops::{Deref, DerefMut};
Expand All @@ -37,63 +31,15 @@ pub enum Severity {
}

#[derive(Serialize, Deserialize, Clone, JsonSchema)]
pub struct SerializableRuleCore<L: Language> {
/// Specify the language to parse and the file extension to include in matching.
pub language: L,
/// A rule object to find matching AST nodes
pub rule: SerializableRule,
/// Additional meta variables pattern to filter matching
pub constraints: Option<HashMap<String, SerializableMetaVarMatcher>>,
/// Utility rules that can be used in `matches`
pub utils: Option<HashMap<String, SerializableRule>>,
/// A dictionary for metavariable manipulation. Dict key is the new variable name.
/// Dict value is a [transformation] that specifies how meta var is processed.
/// Warning: this is experimental option. [`https://github.com/ast-grep/ast-grep/issues/436`]
pub transform: Option<HashMap<String, Transformation>>,
}

impl<L: Language> SerializableRuleCore<L> {
fn get_deserialize_env(&self, globals: &GlobalRules<L>) -> RResult<DeserializeEnv<L>> {
let env = DeserializeEnv::new(self.language.clone()).with_globals(globals);
if let Some(utils) = &self.utils {
let env = env.register_local_utils(utils)?;
Ok(env)
} else {
Ok(env)
}
}

fn get_meta_var_matchers(&self) -> RResult<MetaVarMatchers<StrDoc<L>>> {
Ok(if let Some(constraints) = self.constraints.clone() {
try_deserialize_matchers(constraints, self.language.clone())?
} else {
MetaVarMatchers::default()
})
}

pub fn get_matcher(&self, globals: &GlobalRules<L>) -> RResult<RuleWithConstraint<L>> {
let env = self.get_deserialize_env(globals)?;
let rule = env.deserialize_rule(self.rule.clone())?;
let matchers = self.get_meta_var_matchers()?;
let transform = self.transform.clone();
Ok(
RuleWithConstraint::new(rule)
.with_matchers(matchers)
.with_utils(env.registration)
.with_transform(transform),
)
}
}
#[derive(Serialize, Deserialize, Clone, JsonSchema)]
pub struct SerializableRuleConfigCore<L: Language> {
pub struct SerializableRuleCoreWithId<L: Language> {
#[serde(flatten)]
pub core: SerializableRuleCore<L>,
/// Unique, descriptive identifier, e.g., no-unused-variable
pub id: String,
}

pub fn into_map<L: Language>(
rules: Vec<SerializableRuleConfigCore<L>>,
rules: Vec<SerializableRuleCoreWithId<L>>,
) -> HashMap<String, SerializableRuleCore<L>> {
rules.into_iter().map(|r| (r.id, r.core)).collect()
}
Expand All @@ -113,8 +59,6 @@ pub struct SerializableRuleConfig<L: Language> {
/// One of: hint, info, warning, or error
#[serde(default)]
pub severity: Severity,
/// A pattern to auto fix the issue. It can reference metavariables appeared in rule.
pub fix: Option<SerializableFixer>,
/// Glob patterns to specify that the rule only applies to matching files
pub files: Option<Vec<String>>,
/// Glob patterns that exclude rules from applying to files
Expand All @@ -125,20 +69,7 @@ pub struct SerializableRuleConfig<L: Language> {
pub metadata: Option<HashMap<String, String>>,
}

type RResult<T> = std::result::Result<T, RuleConfigError>;

impl<L: Language> SerializableRuleConfig<L> {
pub fn get_fixer<C: IndentSensitive>(&self) -> RResult<Option<Fixer<C, L>>> {
let transform = &self.transform;
if let Some(fixer) = &self.fix {
let env = self.get_deserialize_env(&Default::default())?;
let parsed = Fixer::parse(fixer, &env, transform)?;
Ok(Some(parsed))
} else {
Ok(None)
}
}

fn get_message(&self, node_match: &NodeMatch<StrDoc<L>>) -> String {
let bytes = self.message.generate_replacement(node_match);
String::from_utf8(bytes).expect("replacement must be valid utf-8")
Expand All @@ -158,22 +89,9 @@ impl<L: Language> DerefMut for SerializableRuleConfig<L> {
}
}

#[derive(Debug, Error)]
pub enum RuleConfigError {
#[error("Fail to parse yaml as RuleConfig")]
Yaml(#[from] YamlError),
#[error("Rule is not configured correctly.")]
Rule(#[from] RuleSerializeError),
#[error("fix pattern is invalid.")]
Fixer(#[from] FixerError),
#[error("constraints is not configured correctly.")]
Constraints(#[from] SerializeConstraintsError),
}

pub struct RuleConfig<L: Language> {
inner: SerializableRuleConfig<L>,
pub matcher: RuleWithConstraint<L>,
pub fixer: Option<Fixer<String, L>>,
pub matcher: RuleCore<L>,
}

impl<L: Language> RuleConfig<L> {
Expand All @@ -182,12 +100,7 @@ impl<L: Language> RuleConfig<L> {
globals: &GlobalRules<L>,
) -> Result<Self, RuleConfigError> {
let matcher = inner.get_matcher(globals)?;
let fixer = inner.get_fixer()?;
Ok(Self {
inner,
matcher,
fixer,
})
Ok(Self { inner, matcher })
}

pub fn deserialize<'de>(
Expand Down Expand Up @@ -216,6 +129,7 @@ impl<L: Language> Deref for RuleConfig<L> {
mod test {
use super::*;
use crate::from_str;
use crate::rule::SerializableRule;
use crate::test::TypeScript;

fn ts_rule_config(rule: SerializableRule) -> SerializableRuleConfig<TypeScript> {
Expand All @@ -225,14 +139,14 @@ mod test {
constraints: None,
transform: None,
utils: None,
fix: None,
};
SerializableRuleConfig {
core,
id: "".into(),
message: "".into(),
note: None,
severity: Severity::Hint,
fix: None,
files: None,
ignores: None,
url: None,
Expand Down Expand Up @@ -381,4 +295,16 @@ test-rule:
let grep = TypeScript::Tsx.ast_grep("some()");
assert!(grep.root().find(&matcher).is_none());
}
#[test]
fn test_get_fixer() {
let globals = GlobalRules::default();
let mut config = get_matches_config();
config.fix = Some(from_str("string!!").unwrap());
let rule = RuleConfig::try_from(config, &globals).unwrap();
let fixer = rule.get_fixer::<String>(&globals).unwrap().unwrap();
let grep = TypeScript::Tsx.ast_grep("some(123)");
let nm = grep.root().find(&rule.matcher).unwrap();
let replacement = fixer.generate_replacement(&nm);
assert_eq!(String::from_utf8_lossy(&replacement), "string!!");
}
}