The three new Annex B declaration fixtures in #28057 parse successfully
but fail after formatting. A comment between `let` and its binding moves
onto the keyword's line: `let --> comment`. A closing HTML comment is
recognized only at the start of a line, so this changes the program or
produces invalid syntax.
Force a hard line break before closing HTML comments. Add a formatter
snapshot covering `let`, `const`, and destructuring declarations, and
document the divergence from Prettier 3.9.10, whose output is rejected
by `node --check`.
Based on the merged submodule update. `just submodules`, `just
coverage`, full `just test -p oxc_formatter` (including Prettier
conformance), `just lint -p oxc_formatter`, and the default-feature
check completed. All three upstream failures disappear: formatter Babel
positives improve from 2271/2280 to 2274/2280. Updated the coverage and
fixture snapshots; output is stable on a second formatting pass.
AI disclosure: prepared and verified with Codex assistance.
---------
Co-authored-by: leaysgur <6259812+leaysgur@users.noreply.github.com>
With `disallowedWords` configured, `valid-title` returned before
checking patterns, spacing, and duplicate prefixes even when no word
matched. It now continues those checks unless a banned word matches,
matching the upstream Jest and Vitest rules. Regression cases cover both
plugins; `String.raw` titles use the raw quasi and template literal span
so validation matches runtime text and autofixes preserve valid syntax.
Fixes#27137
GPT-6 Astra needs less step-by-step scaffolding, while explicit
completion criteria help it carry work through verification. Following
OpenAI's [guidance on rethinking skills and prompts for GPT-6
Astra](https://developers.openai.com/blog/rethinking-skills-and-prompts-for-gpt-6-astra),
this trims the Insta snapshot skill from 136 lines to 21 and removes
repeated workflows and blanket accept/reject examples.
The shorter skill keeps the important constraints: review expected
output before accepting, use the non-interactive CLI, accept only
snapshots from the current task, and rerun the affected test. It also
makes completion explicit without requiring a commit for every snapshot
update.
## Summary
- keep UTF-8 translation offsets absolute when the converter is built
from source with a trimmed BOM or partial-source prefix
- extend the Oxlint BOM fixture with the reported comment-before-Unicode
case
- assert the JS plugin API returns the complete comment range and source
slice
Without the fix, the integration snapshot reports an end offset of 4 and
slices `"/*x*"`. With the fix, it reports an end offset of 5 and slices
`"/*x*/"`.
Fixes#26162
*Update*: #26173 fixed the same bug in the same way, and has been
merged. This PR adds more tests, so merging it on top.
Co-authored-by: camc314 <18101008+camc314@users.noreply.github.com>
Preserves inline `type` modifiers when `no-useless-rename` removes a
redundant import or export alias, avoiding semantic changes under
`verbatimModuleSyntax`.
fixes#26004
## Summary
- parse ordinary class field initializers as `AssignmentExpression`
- reject unparenthesized comma expressions while preserving
parenthesized sequence expressions
- add paired misc conformance fixtures and update the generated
snapshots
## Root cause
Ordinary class field initializers used `parse_expr`, which accepts a
top-level comma and constructs a sequence expression. The ECMAScript
`Initializer` grammar requires an `AssignmentExpression`, so the
unparenthesized comma must remain for the class-element terminator check
to reject.
## Summary
- Remove `NodeFlags::HasYield` and its accessor now that `require-yield`
detects yields locally.
- Delete the semantic-builder state that existed only to populate the
flag.
- Remove `AstNodeStore::current_node_flags`, which has no remaining
writers or non-empty values.
## Summary
- Replace the semantic `NodeFlags::HasYield` check in `require-yield`
with a rule-local AST visitor.
- Stop traversal at nested functions and arrow functions so only yields
belonging to the current generator count.
- Preserve syntactic handling of delegated, unreachable, and
computed-key yields.
## Summary
- add focused formatter coverage for declaration-bearing exports, local
named exports, and source re-exports
- cover comment attachment and prettier-ignore behavior for each export
shape
- include TypeScript type exports and re-export attributes
- covers edge cases to prep for #25095
AI Disclosure: generated using Claude Code w/ Opus 5. It has been
reviewed and tested manually to ensure parity and that the improvement
is real and makes sense.
## What this does
`should_traverse_module` collected both matching entry lists into `Vec`s
only to test `!is_empty()` and `all(is_type)`. `ignoreTypes` defaults to
`true`, so this ran on every graph edge considered — and since the
acyclic prefilter in #25003 traverses each file's whole reachable
subgraph, it is now the hottest allocation site in the rule. Chaining
the two `is_type` iterators and peeking is the same predicate with
nothing materialized.
## Measurements
vscode `src`, 7,368 files, `import/no-cycle` as the only enabled rule,
release build. Note that the baseline is the target PR's HEAD, not
`main`.
| metric | before | after | delta |
| ----------------------- | --------- | -------- | ---------- |
| rule allocations | 86.78M | 38.61M | **-55.5%** |
| rule bytes | 6.00 GB | 4.29 GB | -28.6% |
| wall clock | 1182.8 ms | 1063.0 ms | **-119.8 ms** |
| rule-attributable time | 1083.0 ms | 963.8 ms | **-11.0%** |
Wall clock is paired interleaved runs with order alternation, n=20:
paired sd 14.7 ms, t=+36.4, faster in 20/20 rounds. The parse-only
control is flat (+0.6 ms, t=+1.45), so the delta is attributable to the
rule rather than to binary layout. Allocation counts come from a
counting global allocator, 2 reps, run-to-run spread 0.003%; both
metrics subtract the parse-only baseline.
## Correctness
- 1,566 `no-cycle` diagnostics on vscode, identical before and after
when sorted.
- All 41 `import::*` tests, the three `no_cycle` tests, and
`oxc/no_barrel_file` pass. Clippy clean.
Short-circuiting differs from the original — `all` stops at the first
`false` where the old code built both `Vec`s first — but only the return
value is observable, and the predicate is unchanged: the old code
required `all(is_type)` over each list separately, which is the same as
over the concatenation.
This change was taken out of #24961 as it was the most promising/best
change in that PR and was the most likely to still have an impact after
the acyclic fix in the target PR.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
This is the change from #24963, but as its own PR to avoid dependency on
the larger refactor PR it was targeted at. Benchmarks have been re-run
to ensure that the info is correct, but I'm pretty confident in this
change now.
AI Disclosure: Generated with Claude Code, Opus 5. Reviewed and tested
manually be me.
## What
`run_once` walks the module graph once per direct import, and each walk
explores the module's whole reachable subgraph before concluding "no
cycle". On `vscode/src` that is 78,760 walks producing 1,566
diagnostics, so **98% of them are pure overhead**.
A module is in a cycle **if and only if** some module reachable from it
imports it back. So one traversal of that subgraph answers the question
for every direct import at once. When it says no, skip all the walks.
## Why it can't change what's reported
The prefilter only gates the walks — they still decide what is reported
and reconstruct the cycle path for the diagnostic. It cannot skip a
cycle a walk would have found:
- it traverses the same edges under the same `should_traverse_module`
filter;
- it is unbounded where the walks are bounded by `max_depth`, so its
reachable set is a strict superset;
- it compares by resolved path, not `Arc` identity, matching what the
walks report a cycle on — a file split into multiple sections has one
record per section but one path.
It runs only under the default unbounded `max_depth`, where the walks
cover the whole subgraph anyway and the prefilter replaces work rather
than adding to it. With a small `max_depth` the walks are shallow enough
that an unbounded prefilter could cost more than it saves, so behaviour
there is exactly as before.
## Measurements
`vscode/src` — 7,368 files, 81,512 direct imports, 78,760 walks — with
`import/no-cycle` as the only enabled rule (`number_of_rules: 1`).
Paired interleaved runs with per-round order alternation, n=15,
`--silent`, release builds with `--features allocator`.
### Wall clock
| | mean | sd |
| --------------------- | --------- | -------- |
| `main` | 3217.3 ms | 169.3 ms |
| this PR | 1178.3 ms | 56.1 ms |
| **paired delta** | **−2038.9 ms** | 127.6 ms |
t = +61.9, faster in **15/15** paired rounds. Subtracting the parse-only
baseline, rule-attributable time goes **3110.5 ms → 1073.3 ms, 2.9×
faster**.
The parse-only control (same binaries, no rules enabled) is flat — 106.8
ms vs 105.0 ms, paired delta +1.8 ms, t = +0.49 — so the delta is
attributable to the rule and not to binary layout or the harness.
### Allocations
Counted with a global-allocator shim; rule-attributable = total minus a
parse-only run on the same binary.
| | allocations | bytes |
| ------- | ----------- | -------- |
| `main` | 331.0M | 22.52 GB |
| this PR | 86.8M | 6.05 GB |
| | **−73.8%** | **−73.1%** |
Counts are stable to within 0.05% across repetitions, and the two
parse-only baselines (separately built binaries) agree to 0.003%, which
is what makes that subtraction meaningful.
## Correctness
- 1,566 diagnostics on `main`, 1,566 on this branch, **identical** after
sorting by `(filename, labels, message, code)`.
- Full `oxc_linter` test suite passes (1,175 tests) with no snapshot
changes; `cargo clippy -p oxc_linter --all-features --all-targets -D
warnings` is clean.
### Cross-codebase parity
Both binaries were also run over five TypeScript codebases, under the
default options plus two variants chosen to stress what is most likely
to break: `ignoreTypes: false` changes which edges
`should_traverse_module` admits, so the prefilter has to apply the
identical filter, and `maxDepth: 5` disables the prefilter entirely.
| codebase | files | config | `main` | this PR | identical |
| --- | --- | --- | --- | --- | --- |
| vscode | 11,674 | default | 2,789 | 2,789 | ✅ |
| actualbudget/actual | 1,789 | default | 188 | 188 | ✅ |
| actualbudget/actual | 1,789 | `ignoreTypes: false` | 591 | 591 | ✅ |
| actualbudget/actual | 1,789 | `maxDepth: 5` | 47 | 47 | ✅ |
| storybook | 4,050 | default | 96 | 96 | ✅ |
| storybook | 4,050 | `ignoreTypes: false` | 430 | 430 | ✅ |
| storybook | 4,050 | `maxDepth: 5` | 40 | 40 | ✅ |
| rolldown | 6,411 | default | 77 | 77 | ✅ |
| rolldown | 6,411 | `ignoreTypes: false` | 239 | 239 | ✅ |
| rolldown | 6,411 | `maxDepth: 5` | 73 | 73 | ✅ |
| microsoft/TypeScript | 39,333 | default | 729 | 729 | ✅ |
| microsoft/TypeScript | 39,333 | `ignoreTypes: false` | 729 | 729 | ✅ |
| microsoft/TypeScript | 39,333 | `maxDepth: 5` | 242 | 242 | ✅ |
Counts are `import(no-cycle)` diagnostics. The comparison is stricter
than the counts: in all 13 runs the **entire** diagnostic array is
element-wise identical after sorting by `(filename, labels, message,
code)` — including the 30,713 parse errors the TypeScript repo's test
fixtures produce — so no diagnostic changed span or cycle path either.
These are full repository checkouts, which is why vscode is 11,674 files
here rather than the 7,368 measured above.
Adds formatter regression coverage for concise arrow bodies involving
nested `await` expressions, assignment chains, and AMD `define()` calls.
This locks existing parent-context behavior ahead of the
`ArrowFunctionBody` AST migration.
I may have caused a regression in #24987, so this PR adds test coverage
to make sure I don't break it.
## Summary
- rewrite BadReplaceAllArg::run as a single if-let condition chain
- preserve CallExpression-only rule dispatch and existing behavior
Stacked on #24900.
Co-authored-by: camc314 <18101008+camc314@users.noreply.github.com>
This is stacked on #22905.
The first PR moved Oxfmt ecosystem CI to a label-triggered workflow.
This follow-up keeps that approach, but avoids having one workflow file
per app. Instead, the Oxfmt-specific workflow is renamed to a shared
ecosystem CI dispatcher that handles both trigger labels:
- `run-oxlint-ecosystem-ci` dispatches `oxlint-ci.yml`
- `run-oxfmt-ecosystem-ci` dispatches `oxfmt-ci.yml`
The release-apps workflow now adds both labels to the generated release
PR, so Oxlint and Oxfmt take the same path: create or update the
tracking comment, dispatch the matching workflow in `oxc-ecosystem-ci`
with the PR ref, then remove the label that triggered the run.
The Oxfmt ecosystem CI trigger was still using `/oxfmt-ecosys` comment
command, while the newer monitor-oxc flow uses a short-lived PR label to
kick off the external workflow and then removes the label afterward.
This moves Oxfmt to the same pattern. Adding `run-oxfmt-ecosystem-ci` to
a PR now creates or updates the tracking comment, dispatches
`oxfmt-ci.yml` in `oxc-ecosystem-ci` with the PR ref, and removes the
trigger label after dispatch. The app release workflow now adds that
label to release PRs instead of directly creating the Oxfmt comment and
dispatching the external workflow itself.
Oxlint's release ecosystem dispatch is left as-is for now, since this
change is scoped to the Oxfmt workflow requested in the PR follow-up.
## Summary
Fixes semantic scope metadata for private method helper functions that
are lifted out of classes nested inside static field initializers.
The class-properties transform converts a private method into a helper
function declaration and emits that helper outside the class. For a
nested class inside a static field initializer, `current_scope_id()`
points at the nested class scope, but the helper is actually emitted
into the surrounding hoist scope. That made semantic rebuild disagree
with the transformed AST.
## Before
```js
class Outer {
static value = class Inner {
static #method() {}
};
}
```
The helper function was reparented as if it still lived under the nested
class scope:
```mermaid
flowchart TD
Program["enclosing hoist scope"] --> Outer["Outer class scope"]
Outer --> Field["static field initializer"]
Field --> Inner["Inner class scope"]
Inner --> Helper["lifted helper function scope"]
Helper -.-> Problem["incorrect parent + strict-mode source"]
```
In code, the old logic always used the current scope and current scope
flags:
```rust
let new_parent_id = ctx.current_scope_id();
ctx.scoping_mut().change_scope_parent_id(scope_id, Some(new_parent_id));
let make_sloppy_mode = !ctx.current_scope_flags().is_strict_mode();
```
That is correct for normal private methods, but not for private methods
inside a class expression that appears in a static field initializer.
## After
When the transform is running under a static `PropertyDefinitionValue`,
the helper is parented to the current hoist scope instead:
```rust
let new_parent_id = if Self::is_inside_static_property_initializer(ctx) {
ctx.current_hoist_scope_id()
} else {
ctx.current_scope_id()
};
ctx.scoping_mut().change_scope_parent_id(scope_id, Some(new_parent_id));
let make_sloppy_mode = !ctx.scoping().scope_flags(new_parent_id).is_strict_mode();
```
The predicate checks the field is actually static:
```rust
matches!(ancestor, Ancestor::PropertyDefinitionValue(property) if *property.r#static())
```
The resulting scope shape now matches where the helper is emitted:
```mermaid
flowchart TD
Program["enclosing hoist scope"] --> Helper["lifted helper function scope"]
Program --> Outer["Outer class scope"]
Outer --> Field["static field initializer"]
Field --> Inner["Inner class scope"]
```
The strict/sloppy adjustment also reads from the selected parent scope,
so the helper's flags match the output location rather than the original
nested class context.
## Validation
- `just fmt`
- `cargo test -p oxc_transformer`
- `just coverage` was run before review. It aborted later with a stack
overflow / signal 6 in `target/coverage/oxc_coverage`; before the abort,
the targeted `semantic_test262` failures were removed from the generated
snapshot output.
AI assistance: Codex was used to cherry-pick, review, test, and draft
this PR description.
Preserve import phases when formatting import declarations.
`oxfmt` was dropping `defer` and `source` from phase imports because the
formatter did not print `ImportDeclaration.phase`. This adds phase
printing after `import` and covers `import defer` plus `import source`
cases in formatter fixtures.
fixes#22691
This PR adds a capacity limit to `FixedSizeAllocatorPool` to prevent
unbounded memory consumption.
Previously, each call to `get()` on an empty pool would create a new 4
GiB allocator without any limit, risking memory exhaustion in
multi-threaded scenarios. Now the pool caps the number of allocators at
`thread_count` and blocks threads using a `Condvar` when all allocators
are in use.
Key changes include:
* Eager creation of the first allocator during construction (fail-fast).
* Lock-free ID assignment with compare-and-swap.
* Blocking with condition variables when at capacity.
* Tracking allocation failures to avoid repeated attempts.
---------
Co-authored-by: overlookmotel <theoverlookmotel@gmail.com>
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
Add functionality to detect and display linter rules that have pending
auto-fixes in the automated GitHub tracking issues.
- Add readAllPendingFixRuleNames() to scan rule files for `pending`
keyword
- Update RuleEntry type to include isPendingFix field
- Add updatePendingFixStatus() to mark implemented rules with pending
fixes
- Update markdown renderer to show ⏳ emoji for pending fixes
- Add pending fix counts to summary and table headers
- Preserve isPendingFix status across plugin sync operations
Fixes#15977
this seems to show a decent speed up on perf.
the thinking behind this change is:
1. subsequent calls to `search_original_line_and_column` will **never**
look at back at lines it has already searched
2. binary search is faster in this case as typically `idx` is only
increased by a small amount
Given this, we can skip checking a bunch of lines (that will never
return true) improving performance.
This is a very hot path.
```
$ hyperfine --warmup=3 ./target/release-with-debug/examples/sourcemap-main ./target/release-with-debug/examples/sourcemap
Benchmark 1: ./target/release-with-debug/examples/sourcemap-main
Time (mean ± σ): 79.5 ms ± 5.5 ms [User: 65.1 ms, System: 12.2 ms]
Range (min … max): 76.8 ms … 111.7 ms 37 runs
Warning: Statistical outliers were detected. Consider re-running this benchmark on a quiet system without any interferences from other programs. It might help to use the '--warmup' or '--prepare' options.
Benchmark 2: ./target/release-with-debug/examples/sourcemap
Time (mean ± σ): 72.9 ms ± 0.9 ms [User: 59.2 ms, System: 12.2 ms]
Range (min … max): 70.6 ms … 75.0 ms 40 runs
Summary
./target/release-with-debug/examples/sourcemap ran
1.09 ± 0.08 times faster than ./target/release-with-debug/examples/sourcemap-main
```
---------
Co-authored-by: overlookmotel <theoverlookmotel@gmail.com>
closes#7434
i couldn't test this. but what's happening is semaitc is being build
without scope child ids. When exhaustive_deps tries to access it its an
empty vec - hence the panic
basically:
`^#/i.test(hex)` is the same as `/^#/.test(hex)`
so, in this case the `i` flag does nothing and we can safely ignore it
inspired by https://x.com/the_moisrex/status/1787444601571221892
---
This could potentially lead a new `oxc` rule called
`no_useless_case_insensitive_regex_flag` that reports if you have a
regex with the `i` flag with no ascii alphabetic chars.