mirror of
https://github.com/danbulant/oxc
synced 2026-05-24 12:21:58 +00:00
feat(macros): property generate declarations for nested modules (#212)
This commit is contained in:
parent
677060fc32
commit
88cd82b698
2 changed files with 253 additions and 161 deletions
|
|
@ -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<Self> {
|
||||
let path = input.parse::<syn::Path>()?;
|
||||
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<LintRuleMeta>,
|
||||
}
|
||||
|
||||
impl Parse for AllLintRulesMeta {
|
||||
fn parse(input: ParseStream<'_>) -> Result<Self> {
|
||||
let rules = input
|
||||
.parse_terminated::<LintRuleMeta, syn::Token![,]>(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::<Vec<_>>();
|
||||
|
||||
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<serde_json::Value>) -> 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<H: std::hash::Hasher>(&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<std::cmp::Ordering> {
|
||||
Some(self.cmp(&other))
|
||||
}
|
||||
}
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
pub static ref RULES: Vec<RuleEnum> = 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<Self> {
|
||||
let path = input.parse::<syn::Path>()?;
|
||||
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<LintRuleMeta>,
|
||||
}
|
||||
|
||||
impl Parse for AllLintRulesMeta {
|
||||
fn parse(input: ParseStream<'_>) -> Result<Self> {
|
||||
let rules = input
|
||||
.parse_terminated::<LintRuleMeta, syn::Token![,]>(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::<Vec<_>>();
|
||||
|
||||
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<serde_json::Value>) -> 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<H: std::hash::Hasher>(&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<std::cmp::Ordering> {
|
||||
Some(self.cmp(&other))
|
||||
}
|
||||
}
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
pub static ref RULES: Vec<RuleEnum> = vec![
|
||||
#(RuleEnum::#struct_names(#struct_names::default())),*
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
118
crates/oxc_macros/src/declare_all_lint_rules/trie.rs
Normal file
118
crates/oxc_macros/src/declare_all_lint_rules/trie.rs
Normal file
|
|
@ -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<RulePathTrieNode>),
|
||||
}
|
||||
|
||||
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<RulePathTrieNode> {
|
||||
let NodeKind::InternalNode(children) = self.root.kind else { unreachable!() };
|
||||
children
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue