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`).
This commit is contained in:
overlookmotel 2024-11-05 02:25:27 +00:00
parent 843bce4d92
commit cc8a1917e5
7 changed files with 524 additions and 18 deletions

View file

@ -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'

View file

@ -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)

View file

@ -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));
}
}

View file

@ -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;
}

View file

@ -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<TokenStream> {
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<Option<ScopeId>>") => "ScopeId",
("symbol_id", "Cell<Option<SymbolId>>") => "SymbolId",
("reference_id", "Cell<Option<ReferenceId>>") => "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::<Vec<_>>();
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)*
}
})
}

View file

@ -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};

View file

@ -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<dyn std::error::Error>> {
.generate(AssertLayouts)
.generate(AstKindGenerator)
.generate(AstBuilderGenerator)
.generate(GetIdGenerator)
.generate(VisitGenerator)
.generate(VisitMutGenerator)
.generate(TypescriptGenerator)