[feat] add sgconfig yml

This commit is contained in:
HerringtonDarkholme
2022-07-31 01:31:23 -04:00
parent 6550b3ec8d
commit 1c1cdc4a7c
6 changed files with 105 additions and 42 deletions
+40 -11
View File
@@ -4,7 +4,7 @@ mod interaction;
use ansi_term::Color::{Cyan, Green, Red};
use ansi_term::Style;
use ast_grep_core::language::Language;
use ast_grep_core::{Node, Pattern};
use ast_grep_core::{Node, Pattern, Matcher};
use clap::Parser;
use guess_language::{SupportLang, file_types};
use ignore::{WalkBuilder, WalkParallel, WalkState};
@@ -39,6 +39,10 @@ struct Args {
#[clap(short, long)]
lang: Option<SupportLang>,
/// Path to ast-grep config, either YAML or folder of YAMLs
#[clap(short, long, conflicts_with("pattern"))]
config: Option<String>,
/// Include hidden files in search
#[clap(short, long, parse(from_flag))]
hidden: bool,
@@ -58,16 +62,15 @@ struct Args {
fn main() -> Result<()> {
let args = Args::parse();
if args.pattern.is_some() {
run_with_pattern(args);
run_with_pattern(args)
} else {
run_with_config(args);
run_with_config(args)
}
Ok(())
}
// Every run will include Search or Replace
// Search or Replace by arguments `pattern` and `rewrite` passed from CLI
fn run_with_pattern(args: Args) {
fn run_with_pattern(args: Args) -> Result<()> {
let pattern = args.pattern.unwrap();
let threads = num_cpus::get().min(12);
let lang = args.lang.unwrap();
@@ -130,16 +133,42 @@ fn run_with_pattern(args: Args) {
while let Ok((grep, path)) = rx.recv() {
interaction::clear();
let matches = grep.root().find_all(pattern.clone());
print_matches(matches, &path, &pattern, &rewrite);
print_matches(matches, &path, pattern.clone(), &rewrite);
interaction::prompt("Confirm", "yn", Some('y'))
.expect("Error happened during prompt");
}
}
Ok(())
}
fn run_with_config(_arg: Args) {
use ast_grep_config::from_yaml_string;
println!("TODO! add the code path after config");
fn run_with_config(args: Args) -> Result<()> {
use ast_grep_config::{from_yaml_string};
let config_file = args.config.unwrap_or_else(find_default_config);
let yaml = read_to_string(config_file)?;
let config = from_yaml_string(&yaml).unwrap();
let threads = num_cpus::get().min(12);
let walker = WalkBuilder::new(&args.path)
.hidden(args.hidden)
.threads(threads)
.build_parallel();
let lang = config.language;
run_walker(walker, |path| {
let file_content = match read_to_string(&path) {
Ok(content) => content,
_ => return,
};
let grep = lang.new(file_content);
let mut matches = grep.root().find_all(config.clone()).peekable();
if matches.peek().is_none() {
return;
}
print_matches(matches, path, config.clone(), &None);
});
Ok(())
}
fn find_default_config() -> String {
"sgconfig.yml".to_string()
}
fn run_walker(walker: WalkParallel, f: impl Fn(&Path) -> () + Sync) {
@@ -180,13 +209,13 @@ fn match_one_file(
if matches.peek().is_none() {
return;
}
print_matches(matches, path, pattern, rewrite);
print_matches(matches, path, pattern.clone(), rewrite);
}
fn print_matches<'a>(
matches: impl Iterator<Item = Node<'a, SupportLang>>,
path: &Path,
pattern: &Pattern<SupportLang>,
pattern: impl Matcher<SupportLang> + Clone,
rewrite: &Option<Pattern<SupportLang>>,
) {
let lock = std::io::stdout().lock(); // lock stdout to avoid interleaving output
+56 -30
View File
@@ -2,13 +2,13 @@ pub mod support_language;
use std::collections::HashMap;
use serde::{Serialize, Deserialize};
use ast_grep_core::{Rule, Matcher, PositiveMatcher};
use ast_grep_core::{Rule, Matcher, PositiveMatcher, meta_var::MetaVarEnv};
use ast_grep_core as core;
pub use support_language::SupportLang;
#[derive(Serialize, Deserialize)]
#[derive(Serialize, Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub enum Severity {
Info,
@@ -16,7 +16,7 @@ pub enum Severity {
Error,
}
#[derive(Serialize, Deserialize)]
#[derive(Serialize, Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub enum SerializableRule {
All(Vec<SerializableRule>),
@@ -28,28 +28,53 @@ pub enum SerializableRule {
Kind(String),
}
#[derive(Serialize, Deserialize)]
pub struct AstGrepRuleConfig {
/// Unique, descriptive identifier, e.g., no-unused-variable
id: String,
/// Message highlighting why this rule fired and how to remediate the issue
message: String,
/// One of: Info, Warning, or Error
severity: Severity,
/// Specify the language to parse and the file extension to includ in matching.
language: SupportLang,
/// Pattern rules to find matching AST nodes
rule: SerializableRule,
/// Addtionaly meta variables pattern to filter matching
#[serde(default)]
meta_variables: HashMap<String, String>,
impl Matcher<SupportLang> for AstGrepRuleConfig {
fn match_node_with_env<'tree>(&self, node: core::Node<'tree, SupportLang>, env: &mut MetaVarEnv<'tree, SupportLang>) -> Option<core::Node<'tree, SupportLang>> {
use SerializableRule::*;
let lang = self.language;
match &self.rule {
All(rules) => {
core::All::new(rules.iter().map(|r| convert_serializable_rule(r, lang))).match_node_with_env(node, env)
}
Any(rules) => {
core::Either::new(rules.into_iter().map(|r| convert_serializable_rule_to_positive(r, lang))).match_node_with_env(node, env)
}
Not(rule) => {
core::Rule::not(convert_serializable_rule(rule, lang)).match_node_with_env(node, env)
}
Inside(rule) => {
core::rule::Inside::new(convert_serializable_rule(rule, lang)).match_node_with_env(node, env)
}
Has(rule) => {
core::rule::Inside::new(convert_serializable_rule(rule, lang)).match_node_with_env(node, env)
}
Pattern(pattern) => core::Pattern::new(&pattern, lang).match_node_with_env(node, env),
Kind(kind_name) => core::KindMatcher::new(&kind_name, lang).match_node_with_env(node, env),
}
}
}
type Parsed = Rule<SupportLang, Box<dyn PositiveMatcher<SupportLang>>>;
pub fn from_yaml_string(yaml: &str) -> Result<Parsed, serde_yaml::Error> {
let ast_grep_rule: AstGrepRuleConfig = serde_yaml::from_str(yaml)?;
let matcher = convert_serializable_rule_to_positive(ast_grep_rule.rule, ast_grep_rule.language);
Ok(Rule::new(matcher))
#[derive(Serialize, Deserialize, Clone)]
pub struct AstGrepRuleConfig {
/// Unique, descriptive identifier, e.g., no-unused-variable
pub id: String,
/// Message highlighting why this rule fired and how to remediate the issue
pub message: String,
/// One of: Info, Warning, or Error
pub severity: Severity,
/// Specify the language to parse and the file extension to includ in matching.
pub language: SupportLang,
/// Pattern rules to find matching AST nodes
pub rule: SerializableRule,
/// Addtionaly meta variables pattern to filter matching
#[serde(default)]
pub meta_variables: HashMap<String, String>,
}
type Parsed = Rule<SupportLang, SerializableRule>;
pub fn from_yaml_string(yaml: &str) -> Result<AstGrepRuleConfig, serde_yaml::Error> {
serde_yaml::from_str(yaml)
}
enum SerializeError {
@@ -57,7 +82,7 @@ enum SerializeError {
MissPositiveMatcher,
}
fn convert_serializable_rule_to_positive(rule: SerializableRule, lang: SupportLang) -> Box<dyn PositiveMatcher<SupportLang>> {
fn convert_serializable_rule_to_positive(rule: &SerializableRule, lang: SupportLang) -> Box<dyn PositiveMatcher<SupportLang>> {
use SerializableRule::*;
match rule {
All(rules) => {
@@ -72,7 +97,7 @@ fn convert_serializable_rule_to_positive(rule: SerializableRule, lang: SupportLa
}
}
fn convert_serializable_rule(rule: SerializableRule, lang: SupportLang) -> Box<dyn Matcher<SupportLang>> {
fn convert_serializable_rule(rule: &SerializableRule, lang: SupportLang) -> Box<dyn Matcher<SupportLang>> {
use SerializableRule::*;
match rule {
All(rules) => {
@@ -82,13 +107,13 @@ fn convert_serializable_rule(rule: SerializableRule, lang: SupportLang) -> Box<d
Box::new(core::Either::new(rules.into_iter().map(|r| convert_serializable_rule_to_positive(r, lang))))
}
Not(rule) => {
Box::new(core::Rule::not(convert_serializable_rule(*rule, lang)))
Box::new(core::Rule::not(convert_serializable_rule(rule, lang)))
}
Inside(rule) => {
Box::new(core::rule::Inside::new(convert_serializable_rule(*rule, lang)))
Box::new(core::rule::Inside::new(convert_serializable_rule(rule, lang)))
}
Has(rule) => {
Box::new(core::rule::Inside::new(convert_serializable_rule(*rule, lang)))
Box::new(core::rule::Inside::new(convert_serializable_rule(rule, lang)))
}
Pattern(pattern) => Box::new(core::Pattern::new(&pattern, lang)),
Kind(kind_name) => Box::new(core::KindMatcher::new(&kind_name, lang)),
@@ -99,16 +124,17 @@ fn convert_serializable_rule(rule: SerializableRule, lang: SupportLang) -> Box<d
mod test {
use super::*;
use ast_grep_core::language::Language;
fn test_rule_match(yaml: &str, source: &str) {
let config = from_yaml_string(yaml).expect("rule should parse");
let grep = core::AstGrep::new(source, SupportLang::TypeScript);
let grep = config.language.new(source);
assert!(grep.root().find(config).is_some());
}
fn test_rule_unmatch(yaml: &str, source: &str) {
let config = from_yaml_string(yaml).expect("rule should parse");
let grep = core::AstGrep::new(source, SupportLang::TypeScript);
let grep = config.language.new(source);
assert!(grep.root().find(config).is_none());
}
+1 -1
View File
@@ -1,7 +1,7 @@
pub mod language;
mod match_tree;
mod matcher;
mod meta_var;
pub mod meta_var;
mod node;
mod pattern;
mod replacer;
+1
View File
@@ -210,6 +210,7 @@ where
}
}
#[derive(Clone)]
pub struct Rule<L: Language, M: Matcher<L>> {
inner: M,
meta_vars: MetaVarMatchers<L>,
+1
View File
@@ -0,0 +1 @@
let a = 123
+6
View File
@@ -0,0 +1,6 @@
id: test
message: test rule
severity: info
language: TypeScript
rule:
pattern: $A && $A()