From 88cd82b6986b929f43fcd93885515fb4f564a6e4 Mon Sep 17 00:00:00 2001 From: yangchenye Date: Sun, 26 Mar 2023 21:30:47 -0500 Subject: [PATCH] feat(macros): property generate declarations for nested modules (#212) --- .../mod.rs} | 296 ++++++++---------- .../src/declare_all_lint_rules/trie.rs | 118 +++++++ 2 files changed, 253 insertions(+), 161 deletions(-) rename crates/oxc_macros/src/{declare_all_lint_rules.rs => declare_all_lint_rules/mod.rs} (75%) create mode 100644 crates/oxc_macros/src/declare_all_lint_rules/trie.rs diff --git a/crates/oxc_macros/src/declare_all_lint_rules.rs b/crates/oxc_macros/src/declare_all_lint_rules/mod.rs similarity index 75% rename from crates/oxc_macros/src/declare_all_lint_rules.rs rename to crates/oxc_macros/src/declare_all_lint_rules/mod.rs index 3a2af5c16..5d283b89e 100644 --- a/crates/oxc_macros/src/declare_all_lint_rules.rs +++ b/crates/oxc_macros/src/declare_all_lint_rules/mod.rs @@ -1,161 +1,135 @@ -use convert_case::{Case, Casing}; -use proc_macro2::TokenStream; -use quote::quote; -use syn::parse::{Parse, ParseStream}; -use syn::Result; - -pub struct LintRuleMeta { - name: syn::Ident, - path: syn::Path, -} - -impl LintRuleMeta { - pub fn mod_stmt(&self) -> TokenStream { - let mut segments = self.path.segments.iter().rev().peekable(); - let first = &segments.next().unwrap().ident; - let mut stmts = quote! {mod #first;}; - if segments.peek().is_some() { - stmts = quote! {pub #stmts}; - } - - while let Some(segment) = segments.next() { - let ident = &segment.ident; - - stmts = quote! { - mod #ident { #stmts } - }; - - if segments.peek().is_some() { - stmts = quote! { - pub #stmts - }; - } - } - - stmts - } - - pub fn use_stmt(&self) -> TokenStream { - let mut path = self.path.clone(); - path.segments.push(self.name.clone().into()); - - quote! { - pub use #path; - } - } -} - -impl Parse for LintRuleMeta { - fn parse(input: ParseStream<'_>) -> Result { - let path = input.parse::()?; - let name = syn::parse_str( - &path.segments.iter().last().unwrap().ident.to_string().to_case(Case::Pascal), - ) - .unwrap(); - Ok(Self { name, path }) - } -} - -pub struct AllLintRulesMeta { - rules: Vec, -} - -impl Parse for AllLintRulesMeta { - fn parse(input: ParseStream<'_>) -> Result { - let rules = input - .parse_terminated::(LintRuleMeta::parse)? - .into_iter() - .collect(); - - Ok(Self { rules }) - } -} - -#[allow(clippy::cognitive_complexity)] -pub fn declare_all_lint_rules(metadata: AllLintRulesMeta) -> TokenStream { - let AllLintRulesMeta { rules } = metadata; - - let mod_stmts = rules.iter().map(LintRuleMeta::mod_stmt); - let use_stmts = rules.iter().map(LintRuleMeta::use_stmt); - let struct_names = rules.iter().map(|rule| &rule.name).collect::>(); - - quote! { - #(#mod_stmts)* - #(#use_stmts)* - - use crate::{context::LintContext, rule::{Rule, RuleCategory}, rule::RuleMeta, AstNode}; - use oxc_semantic::Symbol; - - #[derive(Debug, Clone)] - #[allow(clippy::enum_variant_names)] - pub enum RuleEnum { - #(#struct_names(#struct_names)),* - } - - impl RuleEnum { - pub fn name(&self) -> &'static str { - match self { - #(Self::#struct_names(_) => #struct_names::NAME),* - } - } - - pub fn category(&self) -> RuleCategory { - match self { - #(Self::#struct_names(_) => #struct_names::CATEGORY),* - } - } - - pub fn read_json(&self, maybe_value: Option) -> Self { - match self { - #(Self::#struct_names(_) => Self::#struct_names( - maybe_value.map(#struct_names::from_configuration).unwrap_or_default(), - )),* - } - } - - pub fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) { - match self { - #(Self::#struct_names(rule) => rule.run(node, ctx)),* - } - } - - pub fn run_on_symbol<'a>(&self, symbol: &Symbol, ctx: &LintContext<'a>) { - match self { - #(Self::#struct_names(rule) => rule.run_on_symbol(symbol, ctx)),* - } - } - } - - impl std::hash::Hash for RuleEnum { - fn hash(&self, state: &mut H) { - self.name().hash(state); - } - } - - impl PartialEq for RuleEnum { - fn eq(&self, other: &Self) -> bool { - self.name() == other.name() - } - } - - impl Eq for RuleEnum {} - - impl Ord for RuleEnum { - fn cmp(&self, other: &Self) -> std::cmp::Ordering { - self.name().cmp(&other.name()) - } - } - - impl PartialOrd for RuleEnum { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(&other)) - } - } - - lazy_static::lazy_static! { - pub static ref RULES: Vec = vec![ - #(RuleEnum::#struct_names(#struct_names::default())),* - ]; - } - } -} +mod trie; + +use convert_case::{Case, Casing}; +use proc_macro2::TokenStream; +use quote::quote; +use syn::parse::{Parse, ParseStream}; +use syn::Result; +use trie::RulePathTrieBuilder; + +pub struct LintRuleMeta { + name: syn::Ident, + path: syn::Path, +} + +impl Parse for LintRuleMeta { + fn parse(input: ParseStream<'_>) -> Result { + let path = input.parse::()?; + let name = syn::parse_str( + &path.segments.iter().last().unwrap().ident.to_string().to_case(Case::Pascal), + ) + .unwrap(); + Ok(Self { name, path }) + } +} + +pub struct AllLintRulesMeta { + rules: Vec, +} + +impl Parse for AllLintRulesMeta { + fn parse(input: ParseStream<'_>) -> Result { + let rules = input + .parse_terminated::(LintRuleMeta::parse)? + .into_iter() + .collect(); + + Ok(Self { rules }) + } +} + +#[allow(clippy::cognitive_complexity)] +pub fn declare_all_lint_rules(metadata: AllLintRulesMeta) -> TokenStream { + let AllLintRulesMeta { rules } = metadata; + // all the top-level module trees + let module_tries = { + let mut builder = RulePathTrieBuilder::new(); + for rule in &rules { + builder.push(rule); + } + builder.finish() + }; + let mod_stmts = module_tries.iter().map(|node| node.mod_stmt(true)); + let use_stmts = module_tries.iter().map(|node| node.use_stmt(true)); + let struct_names = rules.iter().map(|rule| &rule.name).collect::>(); + + quote! { + #(#mod_stmts)* + #(#use_stmts)* + + use crate::{context::LintContext, rule::{Rule, RuleCategory}, rule::RuleMeta, AstNode}; + use oxc_semantic::Symbol; + + #[derive(Debug, Clone)] + #[allow(clippy::enum_variant_names)] + pub enum RuleEnum { + #(#struct_names(#struct_names)),* + } + + impl RuleEnum { + pub fn name(&self) -> &'static str { + match self { + #(Self::#struct_names(_) => #struct_names::NAME),* + } + } + + pub fn category(&self) -> RuleCategory { + match self { + #(Self::#struct_names(_) => #struct_names::CATEGORY),* + } + } + + pub fn read_json(&self, maybe_value: Option) -> Self { + match self { + #(Self::#struct_names(_) => Self::#struct_names( + maybe_value.map(#struct_names::from_configuration).unwrap_or_default(), + )),* + } + } + + pub fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) { + match self { + #(Self::#struct_names(rule) => rule.run(node, ctx)),* + } + } + + pub fn run_on_symbol<'a>(&self, symbol: &Symbol, ctx: &LintContext<'a>) { + match self { + #(Self::#struct_names(rule) => rule.run_on_symbol(symbol, ctx)),* + } + } + } + + impl std::hash::Hash for RuleEnum { + fn hash(&self, state: &mut H) { + self.name().hash(state); + } + } + + impl PartialEq for RuleEnum { + fn eq(&self, other: &Self) -> bool { + self.name() == other.name() + } + } + + impl Eq for RuleEnum {} + + impl Ord for RuleEnum { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.name().cmp(&other.name()) + } + } + + impl PartialOrd for RuleEnum { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(&other)) + } + } + + lazy_static::lazy_static! { + pub static ref RULES: Vec = vec![ + #(RuleEnum::#struct_names(#struct_names::default())),* + ]; + } + } +} diff --git a/crates/oxc_macros/src/declare_all_lint_rules/trie.rs b/crates/oxc_macros/src/declare_all_lint_rules/trie.rs new file mode 100644 index 000000000..4c4dc8e62 --- /dev/null +++ b/crates/oxc_macros/src/declare_all_lint_rules/trie.rs @@ -0,0 +1,118 @@ +use proc_macro2::{Ident, Span, TokenStream}; +use quote::quote; + +use super::LintRuleMeta; + +pub struct RulePathTrieNode { + /// Name of module + name: Ident, + kind: NodeKind, +} + +enum NodeKind { + /// This node is a leaf node, stores its rule structure name + LeafNode(Ident), + /// This node is internal node, stores its children + InternalNode(Vec), +} + +impl RulePathTrieNode { + pub fn leaf_node(mod_name: Ident, struct_name: Ident) -> Self { + Self { name: mod_name, kind: NodeKind::LeafNode(struct_name) } + } + + pub fn internal_node(name: Ident) -> Self { + Self { name, kind: NodeKind::InternalNode(vec![]) } + } + + // mod root { + // pub mod inner1; + // pub mod inner2; + // } + pub fn mod_stmt(&self, is_root: bool) -> TokenStream { + let name = &self.name; + let mut stmts = quote! { mod #name }; + + if !is_root { + stmts = quote! { pub #stmts }; + } + + match &self.kind { + NodeKind::InternalNode(children) => { + let child_mods = children.iter().map(|node| node.mod_stmt(false)); + quote! { + #stmts { + #(#child_mods)* + } + } + } + NodeKind::LeafNode(_) => { + quote! { #stmts; } + } + } + } + + // pub use root::{ + // inner1::Rule1, + // inner2::Rule2, + // }; + pub fn use_stmt(&self, is_root: bool) -> TokenStream { + let name = &self.name; + let mut stmts = quote! { #name }; + stmts = match &self.kind { + NodeKind::LeafNode(struct_name) => { + quote! { #stmts::#struct_name } + } + NodeKind::InternalNode(children) => { + let child_uses = children.iter().map(|node| node.use_stmt(false)); + quote! { + #stmts::{ + #(#child_uses),* + } + } + } + }; + + if is_root { + stmts = quote! { + pub use #stmts; + } + } + stmts + } +} + +pub struct RulePathTrieBuilder { + root: RulePathTrieNode, +} + +impl RulePathTrieBuilder { + pub fn new() -> Self { + Self { root: RulePathTrieNode::internal_node(Ident::new("root", Span::call_site())) } + } + + pub fn push(&mut self, rule_meta: &LintRuleMeta) { + let mut cur = &mut self.root; + let mut segments = rule_meta.path.segments.iter().peekable(); + // Sanity check: We don't expect empty path + assert!(segments.peek().is_some()); + + for segment in segments { + let name = &segment.ident; + let NodeKind::InternalNode(children) = &mut cur.kind else { unreachable!() }; + let contains_node = children.iter().any(|node| &node.name == name); + if !contains_node { + children.push(RulePathTrieNode::internal_node(name.clone())); + } + let child = children.iter_mut().find(|node| &node.name == name).unwrap(); + cur = child; + } + // The last path is a leaf node + *cur = RulePathTrieNode::leaf_node(cur.name.clone(), rule_meta.name.clone()); + } + + pub fn finish(self) -> Vec { + let NodeKind::InternalNode(children) = self.root.kind else { unreachable!() }; + children + } +}