feat(macros): property generate declarations for nested modules (#212)

This commit is contained in:
yangchenye 2023-03-26 21:30:47 -05:00 committed by GitHub
parent 677060fc32
commit 88cd82b698
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 253 additions and 161 deletions

View file

@ -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())),*
];
}
}
}

View 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
}
}