diff --git a/crates/oxc_ast/src/ast/js.rs b/crates/oxc_ast/src/ast/js.rs index 97b1fbbeced20..c5f28d5a48129 100644 --- a/crates/oxc_ast/src/ast/js.rs +++ b/crates/oxc_ast/src/ast/js.rs @@ -1228,7 +1228,7 @@ pub struct WithStatement<'a> { } /// Switch Statement -#[visited_node(scope(ScopeFlags::empty()), enter_scope_before(cases))] +#[visited_node(scope(ScopeFlags::empty()))] #[derive(Debug)] #[cfg_attr(feature = "serialize", derive(Serialize, Tsify))] #[cfg_attr(feature = "serialize", serde(tag = "type"))] @@ -1236,6 +1236,7 @@ pub struct SwitchStatement<'a> { #[cfg_attr(feature = "serialize", serde(flatten))] pub span: Span, pub discriminant: Expression<'a>, + #[scope(enter_before)] pub cases: Vec<'a, SwitchCase<'a>>, pub scope_id: Cell>, } @@ -1566,7 +1567,7 @@ pub struct YieldExpression<'a> { } /// Class Definitions -#[visited_node(scope(ScopeFlags::StrictMode), enter_scope_before(id))] +#[visited_node(scope(ScopeFlags::StrictMode))] #[derive(Debug)] #[cfg_attr(feature = "serialize", derive(Serialize, Tsify))] #[cfg_attr(feature = "serialize", serde(rename_all = "camelCase"))] @@ -1575,6 +1576,7 @@ pub struct Class<'a> { #[cfg_attr(feature = "serialize", serde(flatten))] pub span: Span, pub decorators: Vec<'a, Decorator<'a>>, + #[scope(enter_before)] pub id: Option>, pub super_class: Option>, pub body: Box<'a, ClassBody<'a>>, diff --git a/crates/oxc_ast/src/ast/ts.rs b/crates/oxc_ast/src/ast/ts.rs index fa25d343568b1..2bdfc25d3c814 100644 --- a/crates/oxc_ast/src/ast/ts.rs +++ b/crates/oxc_ast/src/ast/ts.rs @@ -46,7 +46,7 @@ pub struct TSThisParameter<'a> { /// Enum Declaration /// /// `const_opt` enum `BindingIdentifier` { `EnumBody_opt` } -#[visited_node(scope(ScopeFlags::empty()), enter_scope_before(members))] +#[visited_node(scope(ScopeFlags::empty()))] #[derive(Debug)] #[cfg_attr(feature = "serialize", derive(Serialize, Tsify))] #[cfg_attr(feature = "serialize", serde(tag = "type"))] @@ -54,6 +54,7 @@ pub struct TSEnumDeclaration<'a> { #[cfg_attr(feature = "serialize", serde(flatten))] pub span: Span, pub id: BindingIdentifier<'a>, + #[scope(enter_before)] pub members: Vec<'a, TSEnumMember<'a>>, pub r#const: bool, pub declare: bool, @@ -784,7 +785,6 @@ pub enum TSTypePredicateName<'a> { #[visited_node( scope(ScopeFlags::TsModuleBlock), - enter_scope_before(body), strict_if(self.body.as_ref().is_some_and(|body| body.is_strict())), )] #[derive(Debug)] @@ -794,6 +794,7 @@ pub struct TSModuleDeclaration<'a> { #[cfg_attr(feature = "serialize", serde(flatten))] pub span: Span, pub id: TSModuleDeclarationName<'a>, + #[scope(enter_before)] pub body: Option>, /// The keyword used to define this module declaration /// ```text diff --git a/crates/oxc_ast_macros/src/lib.rs b/crates/oxc_ast_macros/src/lib.rs index 92efceb9e3ca0..37154f9e53877 100644 --- a/crates/oxc_ast_macros/src/lib.rs +++ b/crates/oxc_ast_macros/src/lib.rs @@ -1,8 +1,28 @@ use proc_macro::TokenStream; +use std::str::FromStr; /// Attach to AST node type (struct or enum), to signal to codegen to create visitor for this type. -/// Macro itself does nothing - just passes through the token stream unchanged. +/// +/// Macro does not generate any code - it's purely a means to communicate information to the codegen. +/// +/// Only thing macro does is add `#[derive(VisitedNode)]` to the item. +/// Deriving `VisitedNode` does nothing, but supports the `#[scope]` attr on struct fields. +/// This is a workaround for Rust not supporting helper attributes for `proc_macro_attribute` macros, +/// so we need to use a derive macro to get that support. +/// +/// Use native Rust `TokenStream`, to avoid dependency on slow-compiling crates like `syn` and `quote`. #[proc_macro_attribute] +#[allow(clippy::missing_panics_doc)] pub fn visited_node(_args: TokenStream, input: TokenStream) -> TokenStream { - input + let mut stream = TokenStream::from_str("#[derive(::oxc_ast_macros::VisitedNode)]").unwrap(); + stream.extend(input); + stream +} + +/// Dummy derive macro for a non-existent trait `VisitedNode`. +/// +/// Does not generate any code, only purpose is to allow using `#[scope]` attr in the type def. +#[proc_macro_derive(VisitedNode, attributes(scope))] +pub fn visited_node_derive(_item: TokenStream) -> TokenStream { + TokenStream::new() } diff --git a/crates/oxc_traverse/scripts/lib/parse.mjs b/crates/oxc_traverse/scripts/lib/parse.mjs index 2c9665d12bce2..e7b661e04bd0f 100644 --- a/crates/oxc_traverse/scripts/lib/parse.mjs +++ b/crates/oxc_traverse/scripts/lib/parse.mjs @@ -68,8 +68,11 @@ function parseFile(code, filename, types) { function parseStruct(name, rawName, lines, scopeArgs, filename, startLineIndex) { const fields = []; for (let i = 0; i < lines.length; i++) { - const line = lines[i]; - if (line.startsWith('#[')) { + let line = lines[i]; + const isScopeEntry = line === '#[scope(enter_before)]'; + if (isScopeEntry) { + line = lines[++i]; + } else if (line.startsWith('#[')) { while (!lines[i].endsWith(']')) { i++; } @@ -86,6 +89,8 @@ function parseStruct(name, rawName, lines, scopeArgs, filename, startLineIndex) {name: innerTypeName, wrappers} = typeAndWrappers(typeName); fields.push({name, typeName, rawName, rawTypeName, innerTypeName, wrappers}); + + if (isScopeEntry) scopeArgs.enterScopeBefore = name; } return {kind: 'struct', name, rawName, fields, scopeArgs}; } @@ -128,7 +133,7 @@ function parseScopeArgs(argsStr, filename, lineIndex) { while (true) { const [key] = matchAndConsume(/^([a-z_]+)\(/); assert( - ['scope', 'scope_if', 'strict_if', 'enter_scope_before'].includes(key), + ['scope', 'scope_if', 'strict_if'].includes(key), `Unexpected visited_node macro arg: ${key}` );