From cc8a1917e526e43de03b659876ac545e5928e272 Mon Sep 17 00:00:00 2001 From: overlookmotel <557937+overlookmotel@users.noreply.github.com> Date: Tue, 5 Nov 2024 02:25:27 +0000 Subject: [PATCH] feat(ast): methods on AST nodes to get `scope_id` etc (#7127) Add getter and setter methods to all AST types which have a `ScopeId`, `SymbolId` or `ReferenceId` field to get the contents of that field. Before: ```rs let symbol_id = ident.symbol_id.get().unwrap(); ``` After: ```rs let symbol_id = ident.symbol_id(); ``` This allows removing boilerplate code from the transformer, and discouraging the anti-pattern of treating these fields as if they may contain either `Some` or `None` (after semantic, they will always be `Some`). --- .github/.generated_ast_watch_list.yml | 1 + crates/oxc_ast/src/ast_impl/js.rs | 17 +- crates/oxc_ast/src/generated/get_id.rs | 405 +++++++++++++++++++++++ crates/oxc_ast/src/lib.rs | 1 + tasks/ast_tools/src/generators/get_id.rs | 111 +++++++ tasks/ast_tools/src/generators/mod.rs | 2 + tasks/ast_tools/src/main.rs | 5 +- 7 files changed, 524 insertions(+), 18 deletions(-) create mode 100644 crates/oxc_ast/src/generated/get_id.rs create mode 100644 tasks/ast_tools/src/generators/get_id.rs diff --git a/.github/.generated_ast_watch_list.yml b/.github/.generated_ast_watch_list.yml index eb89bcbe0b579..d848c4f95fcaf 100644 --- a/.github/.generated_ast_watch_list.yml +++ b/.github/.generated_ast_watch_list.yml @@ -30,6 +30,7 @@ src: - 'crates/oxc_ast/src/generated/assert_layouts.rs' - 'crates/oxc_ast/src/generated/ast_kind.rs' - 'crates/oxc_ast/src/generated/ast_builder.rs' + - 'crates/oxc_ast/src/generated/get_id.rs' - 'crates/oxc_ast/src/generated/visit.rs' - 'crates/oxc_ast/src/generated/visit_mut.rs' - 'npm/oxc-types/types.d.ts' diff --git a/crates/oxc_ast/src/ast_impl/js.rs b/crates/oxc_ast/src/ast_impl/js.rs index 4540ffb5925c1..e455c42f0e012 100644 --- a/crates/oxc_ast/src/ast_impl/js.rs +++ b/crates/oxc_ast/src/ast_impl/js.rs @@ -4,9 +4,7 @@ use std::{borrow::Cow, fmt}; use oxc_allocator::{Address, Box, FromIn, GetAddress, Vec}; use oxc_span::{Atom, GetSpan, Span}; -use oxc_syntax::{ - operator::UnaryOperator, reference::ReferenceId, scope::ScopeFlags, symbol::SymbolId, -}; +use oxc_syntax::{operator::UnaryOperator, scope::ScopeFlags, symbol::SymbolId}; use crate::ast::*; @@ -293,19 +291,6 @@ impl<'a> fmt::Display for IdentifierName<'a> { } } -impl<'a> IdentifierReference<'a> { - /// Get `ReferenceId` of `IdentifierReference`. - /// - /// Only use this method on a post-semantic AST where `ReferenceId`s are always defined. - /// - /// # Panics - /// Panics if `reference_id` is `None`. - #[inline] - pub fn reference_id(&self) -> ReferenceId { - self.reference_id.get().unwrap() - } -} - impl<'a> fmt::Display for IdentifierReference<'a> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.name.fmt(f) diff --git a/crates/oxc_ast/src/generated/get_id.rs b/crates/oxc_ast/src/generated/get_id.rs new file mode 100644 index 0000000000000..31562c007a87d --- /dev/null +++ b/crates/oxc_ast/src/generated/get_id.rs @@ -0,0 +1,405 @@ +// Auto-generated code, DO NOT EDIT DIRECTLY! +// To edit this generated file you have to edit `tasks/ast_tools/src/generators/get_id.rs` + +use oxc_syntax::{reference::ReferenceId, scope::ScopeId, symbol::SymbolId}; + +use crate::ast::*; + +impl<'a> Program<'a> { + /// Get [`ScopeId`] of [`Program`]. + /// + /// Only use this method on a post-semantic AST where [`ScopeId`]s are always defined. + /// + /// # Panics + /// Panics if `scope_id` is [`None`]. + #[inline] + pub fn scope_id(&self) -> ScopeId { + self.scope_id.get().unwrap() + } + + /// Set [`ScopeId`] of [`Program`]. + #[inline] + pub fn set_scope_id(&self, scope_id: ScopeId) { + self.scope_id.set(Some(scope_id)); + } +} + +impl<'a> IdentifierReference<'a> { + /// Get [`ReferenceId`] of [`IdentifierReference`]. + /// + /// Only use this method on a post-semantic AST where [`ReferenceId`]s are always defined. + /// + /// # Panics + /// Panics if `reference_id` is [`None`]. + #[inline] + pub fn reference_id(&self) -> ReferenceId { + self.reference_id.get().unwrap() + } + + /// Set [`ReferenceId`] of [`IdentifierReference`]. + #[inline] + pub fn set_reference_id(&self, reference_id: ReferenceId) { + self.reference_id.set(Some(reference_id)); + } +} + +impl<'a> BindingIdentifier<'a> { + /// Get [`SymbolId`] of [`BindingIdentifier`]. + /// + /// Only use this method on a post-semantic AST where [`SymbolId`]s are always defined. + /// + /// # Panics + /// Panics if `symbol_id` is [`None`]. + #[inline] + pub fn symbol_id(&self) -> SymbolId { + self.symbol_id.get().unwrap() + } + + /// Set [`SymbolId`] of [`BindingIdentifier`]. + #[inline] + pub fn set_symbol_id(&self, symbol_id: SymbolId) { + self.symbol_id.set(Some(symbol_id)); + } +} + +impl<'a> BlockStatement<'a> { + /// Get [`ScopeId`] of [`BlockStatement`]. + /// + /// Only use this method on a post-semantic AST where [`ScopeId`]s are always defined. + /// + /// # Panics + /// Panics if `scope_id` is [`None`]. + #[inline] + pub fn scope_id(&self) -> ScopeId { + self.scope_id.get().unwrap() + } + + /// Set [`ScopeId`] of [`BlockStatement`]. + #[inline] + pub fn set_scope_id(&self, scope_id: ScopeId) { + self.scope_id.set(Some(scope_id)); + } +} + +impl<'a> ForStatement<'a> { + /// Get [`ScopeId`] of [`ForStatement`]. + /// + /// Only use this method on a post-semantic AST where [`ScopeId`]s are always defined. + /// + /// # Panics + /// Panics if `scope_id` is [`None`]. + #[inline] + pub fn scope_id(&self) -> ScopeId { + self.scope_id.get().unwrap() + } + + /// Set [`ScopeId`] of [`ForStatement`]. + #[inline] + pub fn set_scope_id(&self, scope_id: ScopeId) { + self.scope_id.set(Some(scope_id)); + } +} + +impl<'a> ForInStatement<'a> { + /// Get [`ScopeId`] of [`ForInStatement`]. + /// + /// Only use this method on a post-semantic AST where [`ScopeId`]s are always defined. + /// + /// # Panics + /// Panics if `scope_id` is [`None`]. + #[inline] + pub fn scope_id(&self) -> ScopeId { + self.scope_id.get().unwrap() + } + + /// Set [`ScopeId`] of [`ForInStatement`]. + #[inline] + pub fn set_scope_id(&self, scope_id: ScopeId) { + self.scope_id.set(Some(scope_id)); + } +} + +impl<'a> ForOfStatement<'a> { + /// Get [`ScopeId`] of [`ForOfStatement`]. + /// + /// Only use this method on a post-semantic AST where [`ScopeId`]s are always defined. + /// + /// # Panics + /// Panics if `scope_id` is [`None`]. + #[inline] + pub fn scope_id(&self) -> ScopeId { + self.scope_id.get().unwrap() + } + + /// Set [`ScopeId`] of [`ForOfStatement`]. + #[inline] + pub fn set_scope_id(&self, scope_id: ScopeId) { + self.scope_id.set(Some(scope_id)); + } +} + +impl<'a> SwitchStatement<'a> { + /// Get [`ScopeId`] of [`SwitchStatement`]. + /// + /// Only use this method on a post-semantic AST where [`ScopeId`]s are always defined. + /// + /// # Panics + /// Panics if `scope_id` is [`None`]. + #[inline] + pub fn scope_id(&self) -> ScopeId { + self.scope_id.get().unwrap() + } + + /// Set [`ScopeId`] of [`SwitchStatement`]. + #[inline] + pub fn set_scope_id(&self, scope_id: ScopeId) { + self.scope_id.set(Some(scope_id)); + } +} + +impl<'a> CatchClause<'a> { + /// Get [`ScopeId`] of [`CatchClause`]. + /// + /// Only use this method on a post-semantic AST where [`ScopeId`]s are always defined. + /// + /// # Panics + /// Panics if `scope_id` is [`None`]. + #[inline] + pub fn scope_id(&self) -> ScopeId { + self.scope_id.get().unwrap() + } + + /// Set [`ScopeId`] of [`CatchClause`]. + #[inline] + pub fn set_scope_id(&self, scope_id: ScopeId) { + self.scope_id.set(Some(scope_id)); + } +} + +impl<'a> Function<'a> { + /// Get [`ScopeId`] of [`Function`]. + /// + /// Only use this method on a post-semantic AST where [`ScopeId`]s are always defined. + /// + /// # Panics + /// Panics if `scope_id` is [`None`]. + #[inline] + pub fn scope_id(&self) -> ScopeId { + self.scope_id.get().unwrap() + } + + /// Set [`ScopeId`] of [`Function`]. + #[inline] + pub fn set_scope_id(&self, scope_id: ScopeId) { + self.scope_id.set(Some(scope_id)); + } +} + +impl<'a> ArrowFunctionExpression<'a> { + /// Get [`ScopeId`] of [`ArrowFunctionExpression`]. + /// + /// Only use this method on a post-semantic AST where [`ScopeId`]s are always defined. + /// + /// # Panics + /// Panics if `scope_id` is [`None`]. + #[inline] + pub fn scope_id(&self) -> ScopeId { + self.scope_id.get().unwrap() + } + + /// Set [`ScopeId`] of [`ArrowFunctionExpression`]. + #[inline] + pub fn set_scope_id(&self, scope_id: ScopeId) { + self.scope_id.set(Some(scope_id)); + } +} + +impl<'a> Class<'a> { + /// Get [`ScopeId`] of [`Class`]. + /// + /// Only use this method on a post-semantic AST where [`ScopeId`]s are always defined. + /// + /// # Panics + /// Panics if `scope_id` is [`None`]. + #[inline] + pub fn scope_id(&self) -> ScopeId { + self.scope_id.get().unwrap() + } + + /// Set [`ScopeId`] of [`Class`]. + #[inline] + pub fn set_scope_id(&self, scope_id: ScopeId) { + self.scope_id.set(Some(scope_id)); + } +} + +impl<'a> StaticBlock<'a> { + /// Get [`ScopeId`] of [`StaticBlock`]. + /// + /// Only use this method on a post-semantic AST where [`ScopeId`]s are always defined. + /// + /// # Panics + /// Panics if `scope_id` is [`None`]. + #[inline] + pub fn scope_id(&self) -> ScopeId { + self.scope_id.get().unwrap() + } + + /// Set [`ScopeId`] of [`StaticBlock`]. + #[inline] + pub fn set_scope_id(&self, scope_id: ScopeId) { + self.scope_id.set(Some(scope_id)); + } +} + +impl<'a> TSEnumDeclaration<'a> { + /// Get [`ScopeId`] of [`TSEnumDeclaration`]. + /// + /// Only use this method on a post-semantic AST where [`ScopeId`]s are always defined. + /// + /// # Panics + /// Panics if `scope_id` is [`None`]. + #[inline] + pub fn scope_id(&self) -> ScopeId { + self.scope_id.get().unwrap() + } + + /// Set [`ScopeId`] of [`TSEnumDeclaration`]. + #[inline] + pub fn set_scope_id(&self, scope_id: ScopeId) { + self.scope_id.set(Some(scope_id)); + } +} + +impl<'a> TSConditionalType<'a> { + /// Get [`ScopeId`] of [`TSConditionalType`]. + /// + /// Only use this method on a post-semantic AST where [`ScopeId`]s are always defined. + /// + /// # Panics + /// Panics if `scope_id` is [`None`]. + #[inline] + pub fn scope_id(&self) -> ScopeId { + self.scope_id.get().unwrap() + } + + /// Set [`ScopeId`] of [`TSConditionalType`]. + #[inline] + pub fn set_scope_id(&self, scope_id: ScopeId) { + self.scope_id.set(Some(scope_id)); + } +} + +impl<'a> TSTypeAliasDeclaration<'a> { + /// Get [`ScopeId`] of [`TSTypeAliasDeclaration`]. + /// + /// Only use this method on a post-semantic AST where [`ScopeId`]s are always defined. + /// + /// # Panics + /// Panics if `scope_id` is [`None`]. + #[inline] + pub fn scope_id(&self) -> ScopeId { + self.scope_id.get().unwrap() + } + + /// Set [`ScopeId`] of [`TSTypeAliasDeclaration`]. + #[inline] + pub fn set_scope_id(&self, scope_id: ScopeId) { + self.scope_id.set(Some(scope_id)); + } +} + +impl<'a> TSInterfaceDeclaration<'a> { + /// Get [`ScopeId`] of [`TSInterfaceDeclaration`]. + /// + /// Only use this method on a post-semantic AST where [`ScopeId`]s are always defined. + /// + /// # Panics + /// Panics if `scope_id` is [`None`]. + #[inline] + pub fn scope_id(&self) -> ScopeId { + self.scope_id.get().unwrap() + } + + /// Set [`ScopeId`] of [`TSInterfaceDeclaration`]. + #[inline] + pub fn set_scope_id(&self, scope_id: ScopeId) { + self.scope_id.set(Some(scope_id)); + } +} + +impl<'a> TSMethodSignature<'a> { + /// Get [`ScopeId`] of [`TSMethodSignature`]. + /// + /// Only use this method on a post-semantic AST where [`ScopeId`]s are always defined. + /// + /// # Panics + /// Panics if `scope_id` is [`None`]. + #[inline] + pub fn scope_id(&self) -> ScopeId { + self.scope_id.get().unwrap() + } + + /// Set [`ScopeId`] of [`TSMethodSignature`]. + #[inline] + pub fn set_scope_id(&self, scope_id: ScopeId) { + self.scope_id.set(Some(scope_id)); + } +} + +impl<'a> TSConstructSignatureDeclaration<'a> { + /// Get [`ScopeId`] of [`TSConstructSignatureDeclaration`]. + /// + /// Only use this method on a post-semantic AST where [`ScopeId`]s are always defined. + /// + /// # Panics + /// Panics if `scope_id` is [`None`]. + #[inline] + pub fn scope_id(&self) -> ScopeId { + self.scope_id.get().unwrap() + } + + /// Set [`ScopeId`] of [`TSConstructSignatureDeclaration`]. + #[inline] + pub fn set_scope_id(&self, scope_id: ScopeId) { + self.scope_id.set(Some(scope_id)); + } +} + +impl<'a> TSModuleDeclaration<'a> { + /// Get [`ScopeId`] of [`TSModuleDeclaration`]. + /// + /// Only use this method on a post-semantic AST where [`ScopeId`]s are always defined. + /// + /// # Panics + /// Panics if `scope_id` is [`None`]. + #[inline] + pub fn scope_id(&self) -> ScopeId { + self.scope_id.get().unwrap() + } + + /// Set [`ScopeId`] of [`TSModuleDeclaration`]. + #[inline] + pub fn set_scope_id(&self, scope_id: ScopeId) { + self.scope_id.set(Some(scope_id)); + } +} + +impl<'a> TSMappedType<'a> { + /// Get [`ScopeId`] of [`TSMappedType`]. + /// + /// Only use this method on a post-semantic AST where [`ScopeId`]s are always defined. + /// + /// # Panics + /// Panics if `scope_id` is [`None`]. + #[inline] + pub fn scope_id(&self) -> ScopeId { + self.scope_id.get().unwrap() + } + + /// Set [`ScopeId`] of [`TSMappedType`]. + #[inline] + pub fn set_scope_id(&self, scope_id: ScopeId) { + self.scope_id.set(Some(scope_id)); + } +} diff --git a/crates/oxc_ast/src/lib.rs b/crates/oxc_ast/src/lib.rs index 608b680547a9b..4968f7a40a383 100644 --- a/crates/oxc_ast/src/lib.rs +++ b/crates/oxc_ast/src/lib.rs @@ -67,6 +67,7 @@ mod generated { pub mod derive_estree; pub mod derive_get_span; pub mod derive_get_span_mut; + pub mod get_id; pub mod visit; pub mod visit_mut; } diff --git a/tasks/ast_tools/src/generators/get_id.rs b/tasks/ast_tools/src/generators/get_id.rs new file mode 100644 index 0000000000000..b684c0aa89bbf --- /dev/null +++ b/tasks/ast_tools/src/generators/get_id.rs @@ -0,0 +1,111 @@ +//! Generator for ID getter/setter methods on all types with `scope_id`, `symbol_id`, `reference_id` +//! fields. +//! +//! e.g. Generates `scope_id` and `set_scope_id` methods on all types with a `scope_id` field. + +use proc_macro2::TokenStream; +use quote::{format_ident, quote}; + +use crate::{ + output::{output_path, Output}, + schema::{Schema, TypeDef}, + util::ToIdent, + Generator, +}; + +use super::define_generator; + +pub struct GetIdGenerator; + +define_generator!(GetIdGenerator); + +impl Generator for GetIdGenerator { + fn generate(&mut self, schema: &Schema) -> Output { + let impls = schema.defs.iter().filter_map(generate_for_type); + + let output = quote! { + use oxc_syntax::{reference::ReferenceId, scope::ScopeId, symbol::SymbolId}; + + ///@@line_break + use crate::ast::*; + + #(#impls)* + }; + + Output::Rust { path: output_path(crate::AST_CRATE, "get_id.rs"), tokens: output } + } +} + +fn generate_for_type(def: &TypeDef) -> Option { + let TypeDef::Struct(def) = def else { return None }; + + let struct_name = def.name.as_str(); + + let methods = def + .fields + .iter() + .filter_map(|field| { + let field_ident = field.ident().expect("expected named field"); + let field_name = field_ident.to_string(); + + let type_name = match (field_name.as_str(), field.typ.raw()) { + ("scope_id", "Cell>") => "ScopeId", + ("symbol_id", "Cell>") => "SymbolId", + ("reference_id", "Cell>") => "ReferenceId", + _ => return None, + }; + let type_ident = type_name.to_ident(); + + // Generate getter method + let get_doc1 = format!(" Get [`{type_name}`] of [`{struct_name}`]."); + let get_doc2 = format!(" Only use this method on a post-semantic AST where [`{type_name}`]s are always defined."); + let get_doc3 = format!(" Panics if `{field_name}` is [`None`]."); + + let get_method = quote! { + #[doc = #get_doc1] + /// + #[doc = #get_doc2] + /// + /// # Panics + #[doc = #get_doc3] + #[inline] + pub fn #field_ident(&self) -> #type_ident { + self.#field_ident.get().unwrap() + } + }; + + // Generate setter method + let set_method_ident = format_ident!("set_{field_name}"); + let set_doc = format!(" Set [`{type_name}`] of [`{struct_name}`]."); + let set_method = quote! { + #[doc = #set_doc] + #[inline] + pub fn #set_method_ident(&self, #field_ident: #type_ident) { + self.#field_ident.set(Some(#field_ident)); + } + }; + + Some(quote! { + ///@@line_break + #get_method + + ///@@line_break + #set_method + }) + }) + .collect::>(); + + if methods.is_empty() { + return None; + } + + let struct_name_ident = struct_name.to_ident(); + let lifetime = if def.has_lifetime { quote!(<'a>) } else { TokenStream::default() }; + + Some(quote! { + ///@@line_break + impl #lifetime #struct_name_ident #lifetime { + #(#methods)* + } + }) +} diff --git a/tasks/ast_tools/src/generators/mod.rs b/tasks/ast_tools/src/generators/mod.rs index 3b833ca9f65c7..fac408d2ff805 100644 --- a/tasks/ast_tools/src/generators/mod.rs +++ b/tasks/ast_tools/src/generators/mod.rs @@ -3,12 +3,14 @@ use crate::{output::Output, Result, Schema}; mod assert_layouts; mod ast_builder; mod ast_kind; +mod get_id; mod typescript; mod visit; pub use assert_layouts::AssertLayouts; pub use ast_builder::AstBuilderGenerator; pub use ast_kind::AstKindGenerator; +pub use get_id::GetIdGenerator; pub use typescript::TypescriptGenerator; pub use visit::{VisitGenerator, VisitMutGenerator}; diff --git a/tasks/ast_tools/src/main.rs b/tasks/ast_tools/src/main.rs index e2b66a5ccb2df..35fcbb215d83c 100644 --- a/tasks/ast_tools/src/main.rs +++ b/tasks/ast_tools/src/main.rs @@ -22,8 +22,8 @@ use derives::{ DeriveGetSpanMut, }; use generators::{ - AssertLayouts, AstBuilderGenerator, AstKindGenerator, Generator, TypescriptGenerator, - VisitGenerator, VisitMutGenerator, + AssertLayouts, AstBuilderGenerator, AstKindGenerator, Generator, GetIdGenerator, + TypescriptGenerator, VisitGenerator, VisitMutGenerator, }; use logger::{log, log_failed, log_result, log_success}; use output::{Output, RawOutput}; @@ -84,6 +84,7 @@ fn main() -> std::result::Result<(), Box> { .generate(AssertLayouts) .generate(AstKindGenerator) .generate(AstBuilderGenerator) + .generate(GetIdGenerator) .generate(VisitGenerator) .generate(VisitMutGenerator) .generate(TypescriptGenerator)