Commit Graph

14440 Commits

Author SHA1 Message Date
Cameron 7d09bf6312 feat(codegen): claim annotation comments by node id 2026-09-04 14:48:49 +01:00
Cameron 576218f5f1 feat(codegen): print comments inside empty containers 2026-09-04 14:48:49 +01:00
Cameron 1b2ddc7b27 feat(codegen): print comments inside structural bodies 2026-09-04 14:48:49 +01:00
Cameron ec6960c662 feat(codegen): print comments inside literals 2026-09-04 14:48:49 +01:00
Cameron 66ce3e6896 feat(codegen): print comments inside functions 2026-09-04 14:48:49 +01:00
Cameron 3acb77d25f feat(codegen): print comments 2026-09-04 14:48:49 +01:00
Cameron f38cbf3747 feat(semantic): rehome comments during node id rebuild 2026-09-04 14:48:49 +01:00
Cameron 5195a0588c feat(ast_visit): add comment attachment sidecar 2026-09-04 14:48:49 +01:00
Cameron 1130d46fa4 feat(ast_visit): add standalone node id assignment 2026-09-04 14:48:49 +01:00
overlookmotel c966acaac5 fix(parser): do not omit < token opening type argument list (#26328)
Fix tokens produced when `<<` opens a type argument list. e.g.:

```typescript
type Bar = ReturnType<<T>(x: T) => number>;
```

Previously only 1 `<` token was produced between `ReturnType` and `T`. This PR corrects that so 2 `<` tokens are produced.

TS-ESLint get this wrong too - hence why this wasn't noticed before, as our conformance tests compare to TS-ESLint.

PR submitted to fix TS-ESLint: https://github.com/typescript-eslint/typescript-eslint/pull/12821

In meantime, this PR skips the erroneously failing ESTree tokens conformance tests. We can can re-enable those tests once the fix lands in TS-ESLint.
2026-09-04 12:08:49 +00:00
Pearce Ropion 253cdb2f46 fix(linter/eslint/id-length): exempt TS interface/type-literal members with never (#26310)
## Summary

The `id-length` lint rule lets users opt out of checking property name
lengths via a `properties: "never"` setting. This exemption worked
correctly for plain JavaScript object properties, but was silently
ignored for TypeScript interface members and object type literal
members. As a result, users who explicitly disabled property length
checks would still see false-positive warnings on their TypeScript type
definitions.

## Why

TypeScript interfaces and type literals describe object shapes using
their own dedicated syntax, distinct from JavaScript object literals.
The rule's internal logic only recognized the JavaScript form when
deciding whether to skip the length check, so the TypeScript equivalents
were treated as ordinary identifiers instead of property names, even
though users intended the same exemption to apply to both.

## Changes

- The rule now recognizes TypeScript interface and type literal members
as property names, so the `properties: "never"` setting applies to them
consistently with plain JavaScript object properties.
- Added test coverage confirming both the exempted and non-exempted
behavior for TypeScript interface and type literal members.

## Test plan

- Added passing test cases showing the exemption now applies to
TypeScript interface and type literal members.
- Added failing test cases confirming the rule still flags these members
under its default settings.
- Verified the fix manually against the exact reproduction case reported
in the issue, confirming the false-positive warning no longer occurs.
- Full linter test suite passes with no regressions.

## AI disclosure

Per the repo's AI usage policy: this fix was drafted with AI assistance
(Claude), and reviewed, tested, and understood by me before submission.
The linked issue itself was also AI-drafted and reviewed by me.

Resolves #26309

---------

Signed-off-by: Cameron <cameron.clark@hey.com>
Co-authored-by: Cameron <cameron.clark@hey.com>
2026-09-04 09:54:59 +01:00
leaysgur 0f4af5d7df test(formatter_tests): assert fingerprint survives formatting (#26322)
Historically, only JS formatter has had the `detect_code_removal` feature, which uses CI to check for missing nodes or comments.

While this would be useful in other languages (especially CSS), but setting up the mechanism is difficult. For now, just start with something simple that just guarantees the number of comments.

That said, even with a mechanism in place, it can't detect anything unless the cases are fully covered by fixtures, so this only tells us that the current state is problem-free.
2026-09-04 07:14:53 +00:00
leaysgur 172ddb67dc fix(formatter_css): keep a same-line line comment on its line (#26321)
Consistent output with other language formatters.
2026-09-04 07:14:53 +00:00
leaysgur cafaf32de1 fix(formatter_css): never drop comments inside an at-rule prelude (#26320)
Fix missing comment bug.
2026-09-04 07:14:52 +00:00
leaysgur 534a0fe838 fix(formatter_css): keep a line comment on its comma's line (#26319)
e.g.

```scss
$my-map: (
  "foo": 1, // Comment
  "bar": 2, // Comment
);
```

should not be formatted:

```scss
$my-map: (
  "foo": 1,
  // Comment
  "bar": 2, // Comment
);
```
2026-09-04 07:14:52 +00:00
leaysgur 99cfd7556e fix(formatter_css): hide raw comment printing API, use strcut instead (#26316)
#26313 for CSS formatter. This also fixes some bugs.
2026-09-04 07:14:52 +00:00
leaysgur 223eb9dcb8 refactor(formatter_yaml): hide raw comment printing API, use struct instead (#26315)
#26313 for YAML formatter.
2026-09-04 07:14:51 +00:00
leaysgur 54316b239f refactor(formatter_json): hide raw comment printing API, use struct instead (#26314)
#26313 for JSON formatter.
2026-09-04 07:14:51 +00:00
leaysgur fa493a735e refactor(formatter): remove impl Format for Comment for safety (#26313)
If line-comment is printed with this and forget to add `hardline` after it, content rides on line-comment and disappear.

To prevent, hide raw API and always use safer struct based one.
2026-09-04 07:14:50 +00:00
leaysgur 32084d466a docs(formatter_core): clarify printer hard_line compression (#26312)
`[hardline, hardline]` does not produce 2 line breaks. This is printer philosophy.
2026-09-04 07:14:50 +00:00
overlookmotel b4f6593c4f ci(lexer): run lint, tests and conformance on both lexer implementations (#26298)
#26263 added the ability to run tests and conformance for both versions of `oxc_lexer` (SIMD and scalar fallback) on any platform.

Run tests and conformance on CI (both SIMD and scalar) on any PR which touches `oxc_lexer` crate or other files which affect conformance runner.

Additionally, run Clippy on the SIMD version (main Lint CI task doesn't lint it, due to the `target_feature = "avx2"` gate on the code).

All of this sets up `oxc_lexer` crate for further development, catching any regressions in CI.
2026-09-03 16:40:21 +00:00
Armano a17de58e30 feat(minifier): process of return/throw in non last position and fix async * inlining (#26214)
This Pr brings couple optimizations and refactor,

- Disable merging: `if (a) return x; return;` and `if (a) return; return
x;` when is descendant of `async* function`
- Handle empty chains: `if (a) return; return;` → `a; return;`
- Process returns/throw anywhere in block via handler merge `if (a)
return; return; var b` → `a; return; var b`

fixes #26255
ref #24331
2026-09-03 23:05:40 +08:00
camc314 f6b75b4a6b test(parser): group annotation application cases (#26294)
## Summary

- group independent `PURE` and `NO_SIDE_EFFECTS` application checks
- clarify test names for unapplied annotation cases
2026-09-03 14:50:56 +00:00
camc314 03cdfdbf17 test(codegen): group unapplied annotation comment cases (#26295)
## Summary

- group unapplied `PURE` and `NO_SIDE_EFFECTS` codegen coverage
- keep the mixed-annotation preservation regression with related cases
2026-09-03 14:43:37 +00:00
overlookmotel 7e75a5aab6 test(ast_visit, linter/plugins): increase test coverage for UTF-16 offset conversion (#26287)
Follow-on after #26173 and #26223.

Expand the test coverage added in both PRs to cover more cases.
2026-09-03 14:33:15 +00:00
overlookmotel da77cc0ae7 test(ast_visit): re-order tests for Utf8ToUtf16 (#26286)
Pure refactor of tests. Move the new tests added in #26173 to lower down below the existing unit tests.
2026-09-03 14:14:34 +00:00
overlookmotel 67dd054273 docs(ast_visit): correct out-of-date comments on Utf8ToUtf16 converter (#26285)
Comments-only change.

While reviewing #26173 and #26223, I noticed that various comments on UTF-8 to UTF-16 offset conversion code were out of date or unclear. Bring them up to date, and add some clarifications.
2026-09-03 14:07:55 +00:00
Bharadwaj Pendyala f07dca52c4 fix(ast_visit): add the trimmed prefix length to translation table offsets (#26173)
Closes #26162.

`Utf8ToUtf16::new_with_offset` seeds the translation table with an
absolute BOM entry, `{ utf8_offset: 3, utf16_difference: 3 }`, but
`build_translations` records later entries relative to the trimmed
source text. From the first non-ASCII character on, the table is 3 bytes
short and offsets land on the wrong side of a boundary.

For the issue repro, a JS plugin reading
`context.sourceCode.getAllComments()[0]` gets `0..4`, so
`text.slice(start, end)` is `"/*x*"` instead of `"/*x*/"`. Parsing the
BOM'd file and converting comment spans the way `parse.rs` does gives
`comment span = 0..4`, `"/*x*"` before the change and `comment span =
0..5`, `"/*x*/"` after.

Add `offset` back on when pushing the entry so the table stays absolute.
This is one line in `build_translations`.

`Utf8ToUtf16::new` passes `offset: 0`, so the parser, coverage, and
benchmarks are unchanged. The four non-zero-offset callers are in the
linter's JS-plugin path: `crates/oxc_linter/src/lib.rs:752` (BOM) and
`:754` (leading newline of a Vue/Astro/Svelte fragment),
`apps/oxlint/src/js_plugins/parse.rs:249` and `fix.rs:34`. Each trims
the prefix from `source_text` before constructing this type. I left the
other two spots the reporter flagged alone because they look like
separate bugs.

Both new tests fail on `db66f58` before the change and pass after:

- `translate_offsets_after_trimmed_bom`: comment end `8` converts to
`4`, expected `5`.
- `translate_offsets_with_trimmed_prefix`: sweeps every byte offset of
three sources against the same source with nothing trimmed, for a 1-byte
and a 3-byte prefix. One source puts the non-ASCII characters in the
SIMD aligned-chunk path rather than the short-source path.

Ran on `db66f58`, macOS arm64, rustc 1.98.0:

- `cargo test -p oxc_ast_visit --lib --features serialize`: 5 passed, 0
failed (3 passed, 2 failed without the fix)
- `cargo test -p oxc_linter`: 1256 passed, 0 failed
- `cargo clippy -p oxc_ast_visit --all-features --all-targets -- -D
warnings`: clean
- `cargo fmt --all --check`: clean

Per the AI usage policy: I used an AI assistant on this. The diagnosis,
the tests and the numbers above are all mine and I ran every command
quoted here.
2026-09-03 13:55:02 +01:00
碳苯 Carbon bfb4c57a5d feat(parser): distinguish unapplied no-side-effects comments (#26120)
## Summary 

Fixes #26088

`NO_SIDE_EFFECTS` can either be applied to a function:

```rs
/* #__NO_SIDE_EFFECTS__ */ function foo() {}
```

Or be meaningless for its position, since it only works on function
declaration:
```rs
/* #__NO_SIDE_EFFECTS__ */ value;
```

But oxc could not distinguish these two cases. 

This PR adds `CommentContent::NoSideEffectsNotApplied`, mirroring the
existing `PureNotApplied`.

---------

Co-authored-by: Cameron <cameron.clark@hey.com>
2026-09-03 11:14:24 +01:00
camc314 5f9cffc753 fix(linter/typescript/no-explicit-any): fix rest parameter to unknown[] (#26279)
## Summary

- replace bare any rest parameter annotations with unknown[] when fixToUnknown is enabled
- preserve existing element-wise fixes for any[] and Array<any>
- add regression coverage for function declarations, arrow functions, call signatures, and method signatures reported in https://github.com/typescript-eslint/typescript-eslint/issues/12813
2026-09-03 09:46:00 +00:00
camc314 4de9fa3d44 fix(linter/react/jsx-no-literals): check nested JSX expression literals (#26253)
## Summary

- report text literals in JSX nested inside expression containers
- preserve upstream element override inheritance through nested JSX and fragments
- add regression coverage for logical, conditional, and plain JSX expressions

Fixes #26208
2026-09-03 09:01:50 +00:00
leaysgur 2028896ab9 fix(formatter_css): preserve comments around SCSS/Less variable values (#26276)
```less
// input
@a: /* c */ 1;

// before
@a: 1;

// after
@a: /* c */ 1;
```

Comment should be preserved.

```scss
// input
$x: // c
  1;

// before
$x:
  // c1;

// after
$x: // c
  1;
```

Content should be preserved.
2026-09-03 08:17:36 +00:00
leaysgur 6752abc1a2 fix(formatter_css): keep leading comments before (#26272)
Apply comment printing policy.
2026-09-03 08:17:36 +00:00
leaysgur 3b054de895 fix(formatter_graphql): align comment printing after open { (#26271)
Aligned the handling of line comments immediately following an opening `{`.

(For GraphQL only,) previously kept their own line, now matches other languages by inserting a line break and treating them as leading comments for the next element.

This brings it in line with Prettier's style. The only drawback is that the behavior of `-line` style comments. However, since most of these can be replaced with `-next-line` style comments, we are going to standardize on this consistent behavior for now.

prettier/prettier#1267 (won't fix)
2026-09-03 08:17:35 +00:00
leaysgur b3c287a47d fix(formatter_css): fix SCSS bracket list printing (#26270)
`$basic: [1, 2];` and `$basic: (1, 2);` should be formatted in the same way.
2026-09-03 08:17:35 +00:00
leaysgur bac770adaa fix(formatter_css): preserve comment in SCSS paren comma list (#26269)
Keep comment position for consistency.
2026-09-03 08:17:35 +00:00
leaysgur c7b57efc38 fix(formatter_css): print SCSS map consisntent with and without comment (#26268)
```scss
$no-comment: (
  a: 1,
);
$with-block: (
  /* c */ a: 1,
);
```

These should be formatted in the same way.
2026-09-03 08:17:34 +00:00
leaysgur 4fabdcca11 docs(formatter,formatter_css,formatter_graphql,formatter_yaml): update DIVERGENCES.md (#26267)
Update each document following #26266 policy.
2026-09-03 08:17:34 +00:00
leaysgur 26a1fa4d92 docs(formatter_core): refine FORMATTER_POLICY (#26266)
Clarify divergence policy.
2026-09-03 08:17:34 +00:00
leaysgur 063e2b6249 fix(formatter_css): preserve trailing comma in single-item scss lists (#26245)
Keep these as-is.

```scss
foo: ("a",);

$x: "a",;
```

Even if they consist of a single element, these are still lists, and if the commas are removed, their type changes.

And also fixed inconsistent comment printing.
2026-09-03 08:17:33 +00:00
overlookmotel 3ab9bd122c fix(parser): do not re-lex template substitution tail after fatal error (#26230)
When the parser records a fatal error it fast-forwards the lexer to end of file. `re_lex_template_substitution_tail` did not check for that, so an incomplete template substitution would still re-lex the `}` and derive the token's start from a cursor which had already jumped to the end.

With tokens enabled, that fails the debug assertion in `finish_next_inner`, because the token being replaced is no longer the one on top of the collected token stream. Release builds silently overwrite an unrelated token instead. Tokens are only collected for Oxlint's JS plugins, which bail out if any parsing error, which is why this hadn't been noticed before.

Fix by adding the same `fatal_error` guard, added to other re-lex methods in #11023.

This does make a difference to diagnostics output for invalid code matching this pattern. TypeScript snapshot loses an "Unterminated string" diagnostic on 2 fixtures. Those errors came from the bogus re-lex reading past end of file. Both these fixtures already have an "Unexpected token" error. The additional "Unterminated string" errors were incorrect, and this fix has the side effect of removing them.
2026-09-02 21:41:11 +00:00
overlookmotel 07851b9c5d fix(parser): fix debug assert failure when lexer error with tokens enabled (#26229)
`Lexer::finalize_tokens` discards the last collected token - the `Eof` that terminates every token stream - and debug asserted that it was `Eof`.

Turns out that assumption does not always hold. Where the lexer cannot lex any further it emits `Kind::Undetermined`, and no `Eof` ever follows, so the last token is `Undetermined` and the debug assertion fails. This can be triggered by an unterminated string, for example:

```js
foo();
'unterminated
```

Like `Eof`, `Undetermined` is not a "real" token, so either way it's fine to remove it from the token stream. The fix is just to relax the debug assertion to accept either.

Release behaviour is unchanged - it was only a debug assert. Only came across this while running the conformance tests which compare current lexer to the new one (`oxc_lexer`).
2026-09-02 21:41:10 +00:00
camc314 a662c40576 fix(linter/eslint/no-useless-assignment): handle destructuring assignments (#25925)
## Summary
- recognize destructuring assignment targets when ordering reads and writes
- record the target write after the assignment right side and target defaults
- add array, object, and defaulted destructuring regression coverage

Fixes #25923 Closes https://github.com/oxc-project/oxc/pull/25924
2026-09-02 15:56:17 +00:00
camc314 a8ed2a7837 docs(minifier): fix stale validation instructions (#26251)
Updates the minifier documentation to use supported validation commands and fixes broken relative links. Also refreshes the coverage task README so it no longer advertises removed suite selectors.
2026-09-02 15:49:32 +00:00
Hamody We 304bd9aef7 fix(linter/eslint/no-array-constructor): handle ASI hazards in fixer (#26166)
## What

`eslint/no-array-constructor`'s fixer replaced the call with `[...]`
unconditionally, so a `[` could land at the start of a line whose
predecessor has no semicolon:

```js
foo
Array()
```

`oxlint --fix` rewrote that to

```js
foo
[]
```

which is a member access on `foo` and does not parse — `Unexpected
token`. The fix is applied to the file, so the result is a source file
the user can no longer build.

This is not a hypothetical: the linter's own fixer harness catches it.
Adding `("foo\nArray()", …)` to the fix table on `main` fails with

```
Linter fixer produced invalid syntax.
Input code:  foo\nArray()
Fixed code:  foo\n[]
Parse errors: "Unexpected token"
```

The two cases at the bottom of that table were commented out with
*"TODO: These currently produce invalid syntax, need to fix the fixer."*
rather than fixed, so nothing in CI was exercising it.

## Why this shape

`ast_util::could_be_asi_hazard` already answers exactly this question,
and every other fixer that can emit a statement-initial `[` calls it:

| rule | uses helper |
|---|---|
| `unicorn/no-new-array` | ✅ |
| `unicorn/prefer-spread` | ✅ |
| `unicorn/no-negation-in-equality-check` | ✅ |
| `unicorn/prefer-response-static-json` | ✅ |
| `shared/no-negated-condition` | ✅ |
| **`eslint/no-array-constructor`** | ❌ — this PR |

I checked the other fixers that emit a leading `[` (`no-div-regex`,
`no-iterator`, `consistent-empty-array-spread`,
`no-single-promise-in-promise-methods`); none of them can be at
statement start, so this rule was the only gap. That also means no new
machinery — one call to the helper the codebase already trusts.

---

*Found by sweeping the linter for fixers that can emit a
statement-initial `[`, after noticing the `no-array-constructor` TODO.*

**AI disclosure:** this change was prepared with the assistance of
Claude Code. I reviewed the change and ran the verification above before
opening, and I stand behind them as my own.

*Branch note: based on an older `main` because the token used here lacks
the `workflow` scope needed to push newer upstream commits touching
`.github/workflows/` (the fork cannot be synced for the same reason).
`no_array_constructor.rs` is identical on both bases and
`could_be_asi_hazard` is unchanged, so it applies to current `main`
as-is — happy to rebase.*

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-02 16:48:11 +01:00
camc314 1d9b9d372b feat(ast): add GetNodeId trait (#26145)
## Summary

- add `GetNodeId` alongside `NodeId` in `oxc_syntax`
- add a dedicated `DeriveGetNodeId` following the existing `GetSpan` derive architecture
- generate implementations into `derive_get_node_id.rs`, delegating uniformly through `self.node_id()`
2026-09-02 15:35:26 +00:00
camc314 b41bb35bca fix(linter): qualify rules in rules output (#26250)
## Summary

- preserve plugin names when tracking default and enabled rules
- prevent same-named rules from inheriting another plugin’s state in Markdown and JSON output
- add regression coverage for all known rule-name collisions

Fixes #26209.
2026-09-02 13:29:56 +00:00
camc314 6a0e19cfaa feat(linter/eslint/no-unmodified-loop-condition): support checkConditionalExpressions option (#26249)
## Summary

- add the ESLint-compatible `checkConditionalExpressions` option to `no-unmodified-loop-condition`, defaulting to `false`
- port tests added by eslint/eslint#21175 and add regression coverage for repeated references within and across binary groups

Closes #26182.
2026-09-02 10:39:45 +00:00
camc314 00dea7ab73 fix(allocator): gate Allocator::data_end_ptr behind fixed_size feature (#26248)
## Summary

- compile Allocator::data_end_ptr only in supported fixed-size allocator builds
- remove the dead-code warning from coverage builds while preserving fixed-size pool usage
2026-09-02 09:45:15 +00:00
camc314 853ffabf34 fix(ecmascript): avoid charAt panic on 32-bit (#26244)
## Summary

- Return `OutOfRange` when a `StringCharAt` position cannot fit in `usize`, avoiding a panic on 32-bit targets.
- Cover the `u32` boundary in direct evaluation and minifier constant folding, alongside existing conversion and UTF-16 edge cases.
2026-09-02 09:07:00 +00:00