Commit Graph

56 Commits

Author SHA1 Message Date
overlookmotel e94b6cde71 refactor(ast_macros): calculate which enums are fieldless in ast_tools (#23957)
Move work from `#[ast]` macro into `ast_tools`. Calculate whether an enum is fieldless or not in `ast_tools` and pass that info to `#[ast]` macro via an `EnumDetails` struct. This avoids having to iterate over all variants of each enum in the macro.

This is preferable because `ast_tools` runs only after changes to AST, whereas the macro code runs every time `oxc_ast` crate is compiled.
2026-06-30 15:45:38 +01:00
overlookmotel 382958afa8 feat(span)!: remove re-exports of string types from oxc_span crate (#21246)
For historical reasons, `oxc_span` crate was re-exporting string types which now live in `oxc_str` crate.

Remove these re-exports, and import these types directly from `oxc_str`.

Main motivation is just that it makes sense, but it should also have a minor positive effect on compile times - `oxc_span` and `oxc_str` crates can now be compiled in parallel.
2026-04-09 12:06:21 +00:00
overlookmotel b782a4b50d refactor(ast_tools): simplify StructsAndEnums iterator (#20941)
Refactor. Simplify and shorten the code for `StructsAndEnums` iterator in `ast_tools`.
2026-04-01 18:13:26 +00:00
overlookmotel 1be1ebe1ae refactor(ast_tools): search all crates which depend on oxc_ast_macros (#20577)
One annoyance with `ast_tools`, our codegen, has been that when you want it to generate code from types in some file, you had to manually add the file paths to a list in `ast_tools` itself.

This PR removes that list and instead finds crates in the monorepo which depend on `oxc_ast_macros` crate, via `cargo metadata`, and searches all files in those crates for types with `#[ast]` attributes etc - types that it need to act on.

There is no longer a list to keep updated, you just mark types `#[ast]` and the codegen will find them.

This produces some churn in generated files. None of the generated code changes, it just gets re-ordered in some places, due to the different order that files get found in now. This order is deterministic, and it should not change again.
2026-03-20 22:15:54 +00:00
Cameron 80e28a3055 feat(ast_tools): add Cargo.lock to CI watch list (#19170) 2026-02-09 19:41:37 +08:00
Boshen 2faf9be4b5 refactor(traverse): port traverse codegen from JS to Rust (#19032)
## Summary

- Add blank lines between generated items using `///@@line_break` in traverse trait, walk functions, ancestor types, Without structs, `is_*`/`is_parent_of_*` methods
- Fix match arm ordering in inherited enums (BFS instead of DFS) to match JS output
- Restore SAFETY comment block and "Compiler should reduce" comment using `///@ ` syntax

Remaining diff (~360 lines across 3 files) is exclusively intentional improvements:
- Header comment referencing new generator location
- V8 naming consistency (`v_8_` matching `convert_case` crate, consistent with other Rust-generated files)
- Type ordering (literals before JSX, following `SOURCE_PATHS` order)

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-02-06 07:20:53 +00:00
camc314 61091c1a77 chore(ast-tools): add Cargo.toml to ast tools watch paths list (#18009) 2026-01-14 21:12:13 +00:00
overlookmotel c9512a9469 refactor(linter/plugins): add environments definitions (#17292)
Generate a file defining the vars that each environment (`env`) provides.

This isn't used for anything yet, but it will be in next PR #17293.

Vars for each environment are stored in this form:

```js
{
  readonly: ["var1", "var2", "var3"],
  writable: ["var4", "var5"],
}
```

instead of the more obvious:

```js
{
  var1: false,
  var2: false,
  var3: false,
  var4: true,
  var5: true,
}
```

...because it's shorter code when there's lots of vars (which there usually are), and iterating through arrays I assume is faster than iterating through properties of objects (which are essentially hash maps when they're large).
2025-12-23 10:53:25 +00:00
camc314 5d211a8c69 refactor(napi/parser): move generated JS into src-js (#16957)
moves the generated code into src-js to keep the same pattern as oxlint
2025-12-17 11:43:42 +00:00
camc314 291b57bfb2 feat(ast_tools): generate TS declaration files for deserializer and walk files (#16912)
this fixes some ts-expect-errors in oxlint by generating declaration files.

We could also consider using oxc_isolated_declarations to generate these
2025-12-15 20:50:16 +00:00
Cameron d933cf0989 feat(ast_tools): add package.json and oxfmtrc.jsonc to CI watch list (#16097)
this change forces ast changes to run when package.json (oxfmt version)
OR oxfmtrc.jsonc (oxfmt config) changes, hopefully avoiding failing CI
due to this not running, and changes to either of those two happening
2025-11-25 20:35:57 +08:00
overlookmotel fc439ed43d ci(ast_tools): run "AST Changes" CI task when dependency crates change (#15603)
#15565 made `oxc_ast_tools` depend on local workspace versions of various crates. Add those crates to the watch list for "AST Changes" CI task, so if changes are made in them, `ast_tools` codegen will run to ensure generated code has been updated to reflect those changes.

In particular, this will prevent generated code getting out of sync when changes to minifier affect the generated code.
2025-11-11 17:49:26 +00:00
overlookmotel ec66eb4936 ci(ast_tools): run "AST Changes" CI task when CI workflow file changes (#15591)
Add path to CI workflow file to the watch list for "AST Changes" CI task, so the task runs if the workflow is altered.
2025-11-11 12:47:37 +00:00
Dunqing bae5f11325 refactor(formatter): improve AstNode and AstNodes (#14686)
Reorganize it so that we can impl some useful methods for them
2025-10-17 08:01:41 +00:00
overlookmotel f279f0b4a6 refactor(linter/plugins): do not lazy-load visitor keys (#14431)
Previously we lazy-loaded visitor keys file, because it would be rarely used. But it's required for selectors DSL, so statically import it, and include it in bundle.

Also, import it as a TS file, since it's valid TS, even without type annotations.
2025-10-07 23:35:31 +00:00
overlookmotel 06b0e9f9e8 refactor(linter/plugins): convert generated files to TS (#14385)
Pure refactor. Generated files `constants.js` and `type_ids.js` are JS files, but they only define `const`s, so are also valid TS, without adding any type annotations. So rename them to `.ts` to allow TS to infer the types of their exports.
2025-10-06 18:55:16 +00:00
overlookmotel 52f35c6dcd refactor(napi/parser, linter/plugins): rename types.js to type_ids.js (#14384)
Pure refactor. Just rename generated `types.js` files to `type_ids.js`. This name better describes what the files contain, and it avoids naming clash with `types.d.ts` in `apps/oxlint/src-js/generated`.
2025-10-06 18:55:15 +00:00
overlookmotel c8eeeb5d78 refactor(linter/plugins): remove build-time dependency on napi/parser (#14374)
Refactor. Previously build script for `oxlint` package copied files from `napi/parser`. Remove this dependency, by instead making `oxc_ast_tools` generate the files in `apps/oxlint/src-js/generated`, and then copy the files from there to `dist/generated` in the build script.

This ends up with the same result, but divorces `oxlint` package from `oxc-parser`, which is cleaner.
2025-10-06 12:08:07 +00:00
overlookmotel fb1a067637 refactor(linter/plugins): bundle walker and AST types map (#14373)
Refactor. Include generated files `types.js` and `walk.js` in bundle, instead of externalizing them. It's only the deserializer code that we want to keep unbundled to make it easier to debug without source maps. `types.js` and `walk.js` aren't too complicated.
2025-10-06 12:08:06 +00:00
overlookmotel a2914fe8d8 feat(linter/plugins): add loc field getter to all AST nodes (#14355)
Add `loc` to all AST nodes.

`loc` is computed lazily, as it's quite an expensive operation - because it requires searching the source text for line breaks and building a translation table.

We can consider making `loc` a "real" property once we have a faster translation table built on Rust side.
2025-10-05 14:02:12 +00:00
overlookmotel 93d81641b2 refactor(linter/plugins): export AST types direct from oxlint package (#14353)
Export AST types directly from `oxlint` package, instead of depending on `@oxc-project/types`. This enables altering the types to reflect that `range` field is always present on AST node in Oxlint's AST (#14354).
2025-10-05 13:47:32 +00:00
overlookmotel c8de6fe9cf feat(linter/plugins): add parent field to AST nodes (#14345)
Use the new raw transfer deserializer added in #14344 to add `parent` field to all AST node in Oxlint JS plugins.
2025-10-05 00:32:09 +00:00
overlookmotel 7e4d04fadf feat(napi/parser): add option to add parent prop to AST nodes with raw transfer (#14344)
Add an option to `oxc-parser` to add `parent` to all AST nodes. This option is only available with raw transfer (not really feasible with JSON transfer, as JSON doesn't support circular references).

This is really for the purposes of JS plugins in Oxlint, so have not included the new deserializers which add `parent` in the `oxc-parser` NPM package, to avoid bloating download size. But it's ideal home is in `napi/parser` so can add it to the conformance tests.

`parent` is correct for all nodes in all Test262 and TypeScript test cases.
2025-10-05 00:32:09 +00:00
overlookmotel e75d42d509 perf(napi/parser, linter/plugins): remove runtime preserveParens option from raw transfer deserializers (#14338)
We had a runtime `preserveParens` option in deserializers to cater for linter, but it was not used in parser. Convert it to a compile-time option instead.
2025-10-04 17:03:33 +00:00
overlookmotel 6374065095 feat(napi/parser): raw transfer support range field (#14319)
Raw transfer support `range` option, adding `range` field to every AST node.
2025-10-04 15:47:47 +00:00
overlookmotel bb040bc2d5 refactor(parser, linter): replace .mjs files with .js (#14045)
Now that `apps/oxlint` and `napi/parser` are both ESM packages (#13723 and #14042), we can use `.js` file extensions for all ESM files, rather than the mix of `.js` and `.mjs` file we had previously.

It's less confusing having to remember what's `.js` and what's `.mjs`, and avoids ugly workarounds like #14038 (`.d.mts`??).
2025-09-23 14:47:40 +00:00
Daniel Roe 1f4be382a8 fix(napi/parser): generate .d.mts extension for types (#14038)
the latest version of `oxc-parser` (on npm) does not have resolvable
types because the entry is `.mjs` and the types are `.d.ts` (these need
to match - `.d.mts`). you can see this in the failing CI for [this
renovate
PR](https://github.com/nuxt/nuxt/actions/runs/17940186619/job/51014702361?pr=33290)
but I'm happy to provide a more minimal reproduction if it helps

I also spotted that `napi/parser/test/visit.test.ts` was importing
`visitorKeys` which was not exported from the corresponding declaration,
so took the liberty to add that export as well.

let me know if this needs any changes 🙏 

I hope it helps

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: overlookmotel <theoverlookmotel@gmail.com>
2025-09-23 11:53:20 +01:00
overlookmotel 56a385fe94 feat(napi/parser): add AST walker (#13930)
Add an AST visitor to `oxc-parser`.

```js
import { parseSync, Visitor } from 'oxc-parser';

const { program } = parseSync('test.js', 'let x = { y: z };');

const idents = [];
const visitor = new Visitor({
  Identifier(node) {
    idents.push(node.name);
  }
});

visitor.visit(program);

// Logs [ 'x', 'y', 'z' ]
console.log(idents);
```

Primarily we need a visitor for Oxlint plugins, but it's easier to develop and test in `oxc-parser`, so I thought I'd add it there first. Oxlint build process will copy the generated files for tree-walking from `napi/parser` dir, as it does with other files.

Notes:

* The `walk.mjs` file is large, so it's lazy-loaded only when user first constructs a `Visitor`. Adding `Visitor` to the package will not affect start-up time where user only uses `parseSync` etc.
* TS type definition for `Visitor` and the visitor object it takes are provided.
* Visitor supports enter and exit visitors e.g. `Program` and `Program:exit`.
* `src-js/visit/visitor.mjs` is copied from `apps/oxlint` with only minor modifications, and conversion from TS to JS. It'd be better if we only had this impl in one place, but that would require converting `oxc-parser` to TS and building with TSDown, like `oxlint` is. Leaving that for later - #13935.
2025-09-22 11:08:46 +00:00
overlookmotel 4ee94a3308 feat(napi/parser): export visitor keys (#13927)
Add visitor keys export to `oxc-parser` NPM package. This is useful in itself, but also the first step towards an ESTree walker.

Visitor keys are based on `@typescript-eslint/visitor-keys`.

It'd be ideal to generate visitor keys direct from Oxc's own types, but that's proving difficult due to all the custom serializers we use to translate to ESTree. So using the shortcut of borrowing from TS-ESLint for now.

Oxc's AST is slightly different from TS-ESTree (notably Oxc's AST adds `ParenthesizedExpression`). But apart from those few differences, TS-ESLint's AST and Oxc's are identical, and I made a PR to TS-ESLint earlier this year (https://github.com/typescript-eslint/typescript-eslint/pull/11279) to ensure visitor key order is correct, and matches Oxc.

To avoid adding a runtime dependency on `@typescript-eslint/visitor-keys`, extract the data from TS-ESLint in `oxc_ast_tools`, amend for Oxc/TS-ESTree differences, and re-output it as a simple object literal.

This approach will be faster at runtime, as the object's shape is consistent, and also avoids a dependency.
2025-09-22 11:08:46 +00:00
overlookmotel 53d04dd1dc feat(linter): convert oxlint to NAPI app (#13723)
Convert `oxlint` to a NAPI package.

This PR:

1. Moves files from `napi/oxlint` into `apps/oxlint`.
2. Converts `oxlint` into a NAPI package.
3. Alters the [release workflow](https://github.com/oxc-project/oxc/pull/13723/files#diff-80cb497c859bbaf7756da6ced2bca3d31933affc96102983eee1ad7d4bcc60fd) and [script](https://github.com/oxc-project/oxc/pull/13723/files#diff-4c4b7045c68805aeeae7c0af94cf7cf7487cb0403496da974a6a23accdc457d0) for building `oxlint` packages.

Most of necessary code modifications and refactoring was done in previous PRs (#13701, #13712, #13722, #13745), so this PR only includes changes necessary for packaging as NAPI.

I've done my best to update release workflow, but am mostly "flying blind" without ability to test it, so very likely there are mistakes.

A few notes and oddities:

* I've kept the `oxlint` NPM package as CommonJS. We could probably switch to ESM, but I thought it better to minimize the changes in this PR.
* NAPI-RS creates `.node` files named with a `-msvc` postfix for Windows. The scripts here remove that postfix so that filename of `.node` files and NPM package names match for all platforms, for simplicity.
* `tasks/website` has a dependency on `oxlint` as a normal library. This resulted in linker errors, so have had to put all the NAPI stuff behind `napi` cargo feature. Tests for `tasks/website` are run separately in CI, to avoid feature unification turning on the `napi` feature for `website`.

I've made a few comments below for the parts which aren't fairly self-explanatory.
2025-09-17 09:20:21 +00:00
overlookmotel 1fd993f54f refactor(napi/oxlint): rename napi/oxlint2 to napi/oxlint (#13682)
Rename `napi/oxlint2` to `napi/oxlint`. This is in preparation for migrating code from `napi/oxlint` into `apps/oxlint`. This PR is incidental, but renaming everything in this PR will reduce diff when moving code over to `apps/oxlint`.
2025-09-12 03:44:25 +00:00
overlookmotel a0d7389884 refactor(allocator/pool): move AllocatorPool into own directory (#13619)
Pure refactor. Move all code related to `AllocatorPool` into a directory together, in preparation for further changes.
2025-09-10 04:03:00 +00:00
Boshen 4577b71ec8 feat(napi/parser)!: change oxc-parser to ESM (#13432)
closes #13329
2025-09-08 10:53:05 +00:00
Dunqing 7ebfb9143c refactor(formatter): implement Format::fmt directly for the enum AST instead of delegating to FormatWrite::write (#13314)
Note: I haven't started to refactor the generator files yet. I just found that implementing `FormatWrite::write` for enum AST is unnecessary, so I made it clearer so that I can develop the remaining features.
2025-08-29 08:43:50 +00:00
overlookmotel aaec279f5c refactor(napi/oxlint): split Rust and JS code into separate directories (#12453)
Pure refactor. Previously `src` directory contained a mix of Rust and JS code. Move the JS code into a separate `src-js` directory.
2025-07-22 14:32:53 +00:00
overlookmotel 6d2b549c59 feat(napi/oxlint): pass AST in buffer to JS (#12350)
First working prototype of JS linter plugins.

Rust now passes the AST to JS via buffer, and lint rule runner uses the JS rule's provided visitor to traverse the AST using lazy deserialization.

Only a single rule is supported at present.

Before anyone gets too excited, this is very much unfinished! It's incomplete, the AST is not compliant with ESTree, and it's not well tested. Really this is a proof of concept more than a usable feature at present. But we'll get there...
2025-07-18 09:32:29 +00:00
overlookmotel 5e3b415903 refactor(linter): duplicate RawTransferMetadata in oxc_linter crate (#12382)
`RawTransferMetadata` is used to store basic info about the data in the buffer which is used on JS side to locate `Program` in the buffer etc.

This struct lives in `napi/parser` crate but we're going to need it in `napi/oxlint2` too. So make a 2nd copy of it in `napi/oxlint2` and use `oxc_ast_tools` to enforce that the 2 copies are identical.

This is a bit of a hack, but it doesn't seem worthwhile creating a new crate just to hold this one struct, and it doesn't really fit in `oxc_allocator`, or any of our other existing crates.
2025-07-18 08:59:32 +00:00
overlookmotel 319fc3b817 refactor(allocator/fixed-size): store alloc_ptr in the memory block backing the allocator (#12380)
`FixedSizeAllocator`s allocate a block of memory larger than what the `Allocator` controls, and often with a different start pointer. So when `FixedSizeAllocator` is dropped, it needs to deallocate that block of memory using the original pointer.

That pointer way previously stored in `FixedSizeAllocator` itself. Instead store it in a `FixedSizeAllocatorMetadata` struct, which is written into the memory block itself, after the section controlled by the `Allocator`, and other metadata (`RawTransferMetadata`).

This allows getting the original `alloc_ptr` when you only have access to the `Allocator`, but not the `FixedSizeAllocator`.
2025-07-18 08:59:31 +00:00
overlookmotel 39ef911147 refactor(napi/parser, allocator): raw transfer: store buffer size and align as consts (#12275)
Similar to #12268. Instead of hard-coding the size and alignment of raw transfer buffers in multiple places, codegen add these to generated files, and everywhere else import the values from those files. This is more robust.
2025-07-14 19:36:49 +00:00
overlookmotel d009bdb886 refactor(napi/parser): raw transfer: store offsets as consts (#12268)
Pure refactor. Instead of hard-coding constants for fixed offsets of data in the buffer, generate these constants in one place and import them from `constants.js` everywhere else.
2025-07-14 18:56:27 +00:00
overlookmotel 9ae081590d refactor(napi/parser): move files (#12088)
Pure refactor. Move generated files for lazy deserialization into a separate directory from generated files for raw transfer.
2025-07-06 11:29:05 +00:00
overlookmotel 93069a5b6c feat(napi/parser): add experimental lazy visitor (#11837)
Add lazy visitor to `oxc-parser`.

This API is experimental and not ready for use yet. AST is not ESTree-compatible at present (same as lazy deserializer).

Usage:

```js
import { parseSync, experimentalGetLazyVisitor } from 'oxc-parser';

// Get `Visitor` class.
// `Visitor` is not exported directly from `oxc-parser` as all code
// related to lazy deserialization is lazy-loaded.
const Visitor = experimentalGetLazyVisitor();

// Define visitor.
// This visitor counts how many identifiers in AST.
let identCount = 0;
const visitor = new Visitor({
  BindingIdentifier(ident) {
    identCount++;
  },
  IdentifierReference(ident) {
    identCount++;
  },
  IdentifierName(ident) {
    identCount++;
  },
});

const code = 'let x, y; x = { z: 2 };';
const { visit, dispose } = parseSync('test.js', code, { experimentalLazy: true });

visit(visitor);

assert(identCount === 4);

// Dispose of AST now we're done with it.
// This returns buffer storing AST data to the pool for reuse.
dispose();
```
2025-06-20 18:23:56 +00:00
overlookmotel 5a55a5856e feat(napi/parser): add lazy deserialization (#11595)
First iteration of lazy deserialization. It works (does not throw an error on any of Test262 or TS test cases), but has some major shortcomings at present:

* Does not match ESTree - shape of AST is more like our Rust types.
* Some missing fields.
* `Vec`s are not lazily deserialized.
* No caching of nodes, so you get a different object each time you get a node e.g. `assert(binaryExpression.left == binaryExpression.left)` fails!

Note: This is a lazy deserializer, which is first step towards a lazy visitor. But there's no visitor implemented yet.

I will build on this starting point in future PRs.
2025-06-11 15:04:19 +00:00
Dunqing cf0b7d5a10 refactor(formatter): core revamp (#11511)
close: https://github.com/oxc-project/oxc/pull/11339 (Another approach is the memory address-based approach, but it is not ideal and has some problems.)

This PR introduces `AstNodes` and `AstNode` to replace the `ParentStack`, and changes that implement `Format` and `FormatWrite` traits from `AST`s to `AstNode` so that now we can access parent by `self.parent` during formatting. All other changes stem from the main one.

```rs
pub enum AstNodes<'a> {
    Dummy(),
    Program(&'a AstNode<'a, Program<'a>>),
    IdentifierName(&'a AstNode<'a, IdentifierName<'a>>),
    ...
}

pub struct AstNode<'a, T> {
    inner: &'a T,
    pub parent: &'a AstNodes<'a>,
    allocator: &'a Allocator,
}
```

### What is the purpose of `AstNode`?

The `AstNode` aims to get the parent node easily. Before we use `ParentStack` to get the parent node correctly, which has a significant problem, that is, we have to push ancestor nodes sequentially for a node, whereas the implementation of some ASTs can't meet this condition.

For example, in the `CallExpression`, we have special logic to print `Arguments` if the first argument is an `ArrowFunctionExpression`; if so, we will print `ArrowFunctionExpression` directly rather than printing a `Argument`, which causes us to lose a parent node `Argument`.

The above issue is exactly what we aim to address with `AstNode`.

###  How does `AstNode` solve the above issue?

As you can see from the `AstNode` struct, which is shown above, every AstNode has `inner` and `parent` fields. The `inner` field stores the original AST, which would be used to print the child of itself, and the `parent`, which is `AstNodes` and points to the parent of the current AST, so that we can know what the parent is.

Let's take a look at how we print the children of the `Program`.
 ```rs

impl<'a> AstNode<'a, Program<'a>> {
    ...
    #[inline]
    pub fn hashbang(&self) -> Option<&AstNode<'a, Hashbang<'a>>> {
        self.allocator
            .alloc(self.inner.hashbang.as_ref().map(|inner| AstNode {
                inner,
                allocator: self.allocator,
                parent: self.allocator.alloc(AstNodes::Program(transmute_self(self))),
            }))
            .as_ref()
    }

    #[inline]
    pub fn directives(&self) -> &AstNode<'a, Vec<'a, Directive<'a>>> {
        self.allocator.alloc(AstNode {
            inner: &self.inner.directives,
            allocator: self.allocator,
            parent: self.allocator.alloc(AstNodes::Program(transmute_self(self))),
        })
    }

    #[inline]
    pub fn body(&self) -> &AstNode<'a, Vec<'a, Statement<'a>>> {
        self.allocator.alloc(AstNode {
            inner: &self.inner.body,
            allocator: self.allocator,
            parent: self.allocator.alloc(AstNodes::Program(transmute_self(self))),
        })
    }
}

// Printing
impl<'a> FormatWrite<'a> for AstNode<'a, Program<'a>> {
    fn write(&self, f: &mut Formatter<'_, 'a>) -> FormatResult<()> {
        write!(
            f,
            [
                self.hashbang(),
                self.directives(),
                self.body(),
                hard_line_break()
            ]
        )
    }
}
```

As you can see, now we implement `FormatWrite` trait for the `AstNode<'a, Prgoram<'a>>` rather than `Program<'a>`, and we need to call the `AstNode`'s methods to get the child `AstNode` to print, which converts `inner`'s child nodes to `AstNode` and binds the parent. That is to say that we handle `AstNode` all along.

NOTE: All of the field-to-AstNode methods are generated by the ast tools and generated in [PLACEHOLDER].

### Worth noting things

#### As little as possible to get child nodes by those methods

Since getting a child node from those methods has some overhead, as we need to wrap an `AstNode` for the original AST. It would be better to only use those methods to get the child nodes when they need to be printed.

#### Efficiently check certain fields
Same as the above, to avoid unnecessary overhead, it would be better to get the original AST to check if you want to check something. For example, if you want to check the first statement of the `body` of the `Program` if it's an `EmptyStatement`, you can check by `self.as_ref().body.first()` rather than `self.body().first()`.

```rs
impl<'a, T> AsRef<T> for AstNode<'a, T> {
    fn as_ref(&self) -> &T {
        self.inner
    }
}
```

`as_ref()` is to get the original AST.

#### `Deref` trait for the `AstNode`

```rs
impl<'a, T> Deref for AstNode<'a, T> {
    type Target = T;
    fn deref(&self) -> &Self::Target {
        self.inner
    }
}
```

Since we implemented the `Deref` trait for the `AstNode`, now we can directly call the original AST methods by `self.method()` rather than `self.as_ref() method()` which can reduce a lot of `as_ref()` calls.

But there is a footgun, after `Deref` was implemented,  both `self.body` and `self.body()` are work, but only `self.body()` can be printed because only `AstNode` implements `Format` trait, so this may make the new contributor who unfamiliar with the underly design to struggle. I will keep an eye on this to see whether it should be removed.

#### `as_ast_nodes` method for enum AST, such as `Statement`, `Expression`, and so on.

In general, we don't need to call `as_ast_nodes` because in most cases we only need to call `fmt` for the inner AST of the enum AST. But in some cases, we need to deal with the specific thing for a certain inner node of the enum AST.

For example, if the `Expression` is a `ClassExpression`, you need to print the decorators of that class first, then handle the class somewhere. In that time, you need to use `as_ast_nodes` to get the inner node which is converted to the `AstNode`, the code lis ike:
```rs

impl<'a> AstNode<'a, Expression<'a>> {
    #[inline]
    pub fn as_ast_nodes(&self) -> &AstNodes<'a> {
        let parent = self.parent;
        let node = match self.inner {
            Expression::ClassExpression(s) => AstNodes::Class(self.allocator.alloc(AstNode {
                inner: s.as_ref(),
                parent,
                allocator: self.allocator,
            }))
            ...
        };
        self.allocator.alloc(node)
    }
}

if let AstNodes::Class(class) = expr.as_ast_nodes() {
  write!(f, class.decorators())
}
```

### Performance

The initial expectation was a performance improvement due to removing a large `Vec` (previously used for parent kinds) and eliminating indexed parent lookups. However, this has not been observed.

The performance regression might stem from the overhead of wrapping original ASTs into `AstNode` instances, which involves frequent `Allocator::alloc` calls. Additionally, the current implementation is incomplete, with limited parent-checking logic, so the benefits of `AstNode` may not be fully realized yet.

### Downsides

The extensive use of generic structs and the generation of a large file (over 8000 lines) have led to an increase in binary size and compilation time, though the increases are not excessive.

#### Binary Size
*   Before: 4.8 MB
*   After: 6.9 MB

#### Compilation Time (Release Profile)
*   Before: 6.67s
*   After: 7.41s
2025-06-11 02:34:27 +00:00
overlookmotel c2c02689b2 feat(syntax): introduce CommentNodeId (#11214)
First step towards #11213.

Introduce `CommentNodeId`, which we'll add to AST structs.

Note: `CommentNodeId` wraps a `u32`, not a `NonMaxU32`. `CommentNodeId`s will be created in parser, every node will have one, and they will never change thereafter. So we don't need to wrap in `Cell<Option<CommentNodeId>>`, the way we do with e.g. `ScopeId`. So `NonMaxU32` offers no benefit here, and would be slightly more costly.

Add a method `get_comment_node_id` to `AstBuilder` which other `AstBuilder` methods will call to populate a field with a `CommentNodeId` field. This method is currently a dummy.

`CommentNodeId` is marked `#[clone_in(default)]`, `#[content_eq(skip)]`, `#[estree(skip)]`, so adding this field to AST types will not disrupt any other code.
2025-05-24 02:44:48 +00:00
overlookmotel 2b0a69fb6b perf(ast): re-order struct fields to reduce padding (#11056)
All our AST structs are `#[repr(C)]`, which gives us guaranteed stable memory layouts. This is essential for raw transfer, and helpful for other purposes too.

However, the downside is that we lose out on the automatic field-reordering that default `#[repr(Rust)]` provides, where structs are packed to minimize padding bytes.

This PR implements a similar field-reordering scheme, to get the same effect.

* `oxc_ast_tools` determines optimal field order for compact memory layout.
* `oxc_ast_tools` generates a file (`structs.rs`) which details the desired field order for each type.
* `#[ast]` proc macro follows the instructions in `structs.rs` and re-orders the fields.

This reduces the size of various AST types by 8 bytes. No type now has more than 7 padding bytes (previously e.g. `Function` had 15 bytes of padding).

Possible future improvements:

1. Get Rust Analyser to display original field order.
2. Remove `syn` dependency from `oxc_ast_macros` crate, to improve compile times. It should be possible to do all the AST manipulation using just `proc_macro` crate, by generating more code in `oxc_ast_tools`, so the macro's logic can be simpler.

Note: This PR doesn't add `syn` dependency, it was already there. But it makes it easier to remove it.
2025-05-16 03:43:22 +00:00
overlookmotel 2a403cb49d refactor(ast_macros): rename generated file (#11047)
Pure refactor. Move generated file in `oxc_ast_macros` crate, to make room for more generated files to be added.
2025-05-14 21:47:51 +00:00
overlookmotel 567368efe3 refactor(span): remove source_type module (#11024)
It's unnecessary to have a separate `errors.rs` file for `SourceType`. Combine 2 files into 1.

Pure refactor. This PR doesn't alter any of the logic, just relocates it.
2025-05-14 09:11:22 +00:00
overlookmotel b8018b9864 refactor(ast/estree): split custom serializers into multiple files (#10967)
Pure refactor. `serialize.rs` had got too large. Split it up into multiple files.

Also make sure all custom serializers/converters have clear doc comments.
2025-05-12 13:58:45 +00:00
overlookmotel daba0a7c2a refactor(estree): remove regular expression types from ESTree AST (#10855)
`Pattern` and other types in `oxc_regular_expression` are not part of ESTree AST. Don't implement `ESTree`, and don't generate raw transfer deserializers for these types. None of that code is actually used.

This reduces the scope of ESTree serialization.
2025-05-07 13:46:32 +01:00