## Summary
Fix the generated defaults for `unicorn/numeric-separators-style`. The rule already uses `number.minimumDigits: 5`, matching Unicorn, but the generated documentation and JSON schema advertise `0`. The same schema issue also advertises `groupLength: 0` for every numeric base.
## Why the defaults are wrong
`NumericBaseConfig` is shared by binary, hexadecimal, octal, and decimal configuration. Its derived `Default` initializes both numeric fields to zero. The rule supplies the actual defaults in `NumericSeparatorsStyleConfig::default()` and `NumericNumberConfig::default()`.
Schema generation uses the shared type's defaults for its individual properties. In particular, flattening `NumericBaseConfig` into `NumericNumberConfig` preserves the zero-valued property defaults instead of the decimal defaults. This makes the documented threshold disagree with the rule's existing behavior:
```js
1234; // Allowed with the default configuration.
12345; // Reported; the fix is 12_345.
```
## Change
Add a single parameterized schema helper for `NumericBaseConfig`, then select the appropriate defaults with `schemars(schema_with = ...)` on each field. The helper retains the derived schema's property descriptions, types, and constraints, and overrides only the `groupLength` and `minimumDigits` default metadata.
The resulting defaults are:
| Configuration | `groupLength` | `minimumDigits` |
| --- | --- | --- |
| `binary` | 4 | 0 |
| `hexadecimal` | 2 | 0 |
| `octal` | 4 | 0 |
| `number` | 3 | 5 |
The schema annotation on the flattened decimal base also corrects `number.minimumDigits`. `number.fractionGroupLength` remains unchanged, and the documentation continues to display its default as `Infinity`.
Regenerate the JSON configuration schema and the TypeScript configuration types from it. The generated TypeScript types inline the binary, hexadecimal, and octal option objects because those properties now have individual schemas; their accepted configuration shapes are unchanged.
This changes schema and documentation metadata only. Runtime defaults, option parsing, diagnostics, and autofixes are unchanged.
Fixes#26391
`ParserReturn` contains a property `panicked`.
The name `panicked` is misleading. The parser did _not_ panic. When `true`, it means that the parser encountered a fatal error and is unable to parse the source, because it contains an unrecoverable syntax error.
Rename this property to `fatal_error`, to better describe what it means.
This is a breaking change, but one that's trivial to fix in downstream code - the property doesn't change its meaning or behavior, just its name.
All the changes in this PR in other crates are just reacting to this change and making everywhere use the term "fatal error" instead of "panicked", so the codebase is consistent.
## 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>
## 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
## 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
## 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
## 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>
## 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.
## 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.
## Summary
- avoid suggesting deletion for empty class expressions
- keep deletion suggestions for removable class declarations
- add regression coverage for class expressions used as object property values
Fixes#26228
## Summary
- recognize references returned from arrow functions stored on properties
- treat arrow functions passed to calls and constructors as independently callable
- preserve self-use detection for immediately invoked arrow functions
- add regression coverage based on VSC-NeuroPilot/neuropilot
AI Disclosure: Generated with Claude Code, Fable 5. Reviewed by me. This sets up the short_description to be added to the various rules shared between jest and vitest.
`declare_oxc_lint!` now accepts a path to a shared constant for `short_description`, mirroring how `docs = SHARED_DOCUMENTATION` works. Like `documentation`, the handling is gated behind the `ruledocs` feature.
A shared short description is added to `expect-expect`, `no-disabled-tests`, and `valid-expect`.
Rules where the wording legitimately differs per framework (e.g. `no-alias-methods`) can keep per-plugin string literals, which remain supported. Or we can opt to add the raw strings on every one of these rules and drop the shared short desc idea, I don't have a strong preference either way.
Once we decide and merge this, we can add the short descriptions for all the other remaining shared rules. We'll also need to split up the two remaining rules that are problematic (https://github.com/oxc-project/oxc/issues/23186), then we can expose the `short_description` field in the JSON rules formatter for use on the website's rules list view.
The example incorrectly used a named import for a default export.
Changed `export default function bar()` to `export function bar()` to
align with the `import { bar }` statement.
Signed-off-by: billychannnnnn <billychannnnnn@foxmail.com>
Attempts to address:
- oxc-project/oxc#17070
- oxc-project/oxc#21402,
- oxc-project/oxc-vscode#264
I recently started enabling the type-aware rules as part of my editor's
linter (zed). I quickly started hitting resource exhaustion limits on
macOS. Listing processes showed thousands of zombie tsgolint entries
with oxlint as the parent.
Doing a little digging, I found the above issues listing similar
resource exhaustion. oxc-project/oxc#24670 attempted to solve this issue
by calling `child.kill` on the tsgolint process, but this is not enough
on macOS. From [the `Child`
docs](https://doc.rust-lang.org/std/process/struct.Child.html):
> On some systems, calling wait or similar is necessary for the OS to
release resources. A process that terminated but has not been waited on
is still around as a “zombie”. Leaving too many zombies around may
exhaust global resources (for example process IDs).
>
> The standard library does not automatically wait on child processes
(not even if the Child is dropped), it is up to the application
developer to do so. As a consequence, dropping Child handles without
waiting on them first is not recommended in long-running applications.
This change follows this advice and adds a `wait` after the existing
`kill`.
## Summary
- classify removal of empty value imports as a suggestion because deleting them can drop module evaluation side effects
- keep removal of empty type-only imports as an automatic fix
- preserve the rule’s existing deletion behavior
## Summary
- verify that new Set constructors resolve to the intrinsic global before reporting or fixing
- preserve detection for direct constructions and const-bound Set instances
- add regression coverage for a locally shadowed constructor that returns an array
Follow-up to #26138, closes#25867.
External plugins can report a node range past the source text. The default formatter rejects that invalid span and previously panicked. Clamp external diagnostic offsets to the source's UTF-16 length before normalizing reversed ranges.
The end-to-end fixture now covers:
- reversed locations
- negative loc values
- negative node ranges
- an out-of-range node range in Unicode source text
Fixes#26113.
The rule was moved from the TypeScript plugin to the ESLint plugin in 79fc6d7e9f, but its TypeScript-only execution gate was retained. As a result, eslint/no-use-before-define never ran for JavaScript or JSX files.Remove the gate and add a JavaScript regression test for a temporal-dead-zone reference.
Closes#25868.
## What
`unicorn/no-useless-spread` inferred "returns a plain array" from the
method name
alone, so `[...new Uint8Array(buf).slice(0, 12)]` was reported and
`--fix` removed
the spread. That spread is the conversion to `number[]`, so the
following `.map()`
kept running as `TypedArray.prototype.map` and coerced every callback
result back
into the element type. #25868 has the measurements and the seven
affected methods.
## Approach
Rather than guard the one call site, this adds a
`ValueHint::NewTypedArray` variant
beside the existing `NewArray` / `NewObject` / `NewIterable`.
The reason is chains. A one-off receiver check handles
`new Uint8Array(buf).slice(0)` and misses `new
Uint8Array(buf).slice(0).map(f)`,
where the receiver is itself a call. Carrying the typed-array fact
through
`const_eval` means the existing recursion does that work, and
`[...new Uint8Array(buf).slice(0, 12).map(f)]` passes with no extra
code.
`is_array()` stays false for the new variant, so nothing is reported.
Three places
feed it:
- `NewExpression::const_eval`, via the existing `is_new_typed_array`
- `is_typed_array_from`, for `Uint8Array.from(x)`
- `is_typed_array_method`, for a clone method whose receiver already
evaluates to
`NewTypedArray`
## One deviation from the fix sketched in the issue
The issue proposed splitting the typed-array names out of
`is_array_from`. That
would regress an existing `fail` vector, because `is_array_from` has a
second
consumer doing a different job.
`mod.rs:362` uses it to decide whether a spread passed *into* `from` is
useless,
which it is for every constructor in that list, since they all accept an
iterable.
`BigUint64Array.from([...iterable])` is in the `fail` vectors today and
should stay
there. Only the *result*-type inference in `const_eval` was wrong.
So `is_array_from` keeps its full list and gains a doc comment saying
which of the
two questions it answers, and `const_eval` drops its call to it.
`Array.from(x)`
keeps evaluating to `NewArray` through `is_array_factory`, which already
matches
`Array.{from,of}` with any argument count.
## Behaviour
| expression | before | after |
|---|---|---|
| `[...new Uint8Array(b).slice(0, 12)]` | reported, fixed | passes |
| `[...new Uint8Array(b).map(f)]` | reported, fixed | passes |
| `[...new Uint8Array(b).filter(f)]` | reported, fixed | passes |
| `[...new Uint8Array(b).toReversed()]` | reported, fixed | passes |
| `[...new Uint8Array(b).toSorted()]` | reported, fixed | passes |
| `[...new Uint8Array(b).with(0, 1)]` | reported, fixed | passes |
| `[...Uint8Array.from([1, 2, 3])]` | reported, fixed | passes |
| `[...new Uint8Array(b).slice(0).map(f)]` | reported, fixed | passes |
| `[...[1, 2, 3].slice(0, 2)]` | reported, fixed | unchanged |
| `[...Array.from(foo)]` | reported, fixed | unchanged |
| `BigUint64Array.from([...iterable])` | reported, fixed | unchanged |
| `[...new Uint8Array([1, 2, 3])]` (#7936) | passes | unchanged |
## Tests
19 `pass` vectors added under a `#25868` comment next to the existing
`#7936` pair:
the seven methods from the issue, a chained case, and all 11 typed array
constructors. The `fail` vectors are untouched, which is the regression
check that
plain arrays and `Array.from` still report.
## Known limits, both deliberate
**Variable receivers still report.** `[...view.slice(0, 12)]` where
`view` is a
`Uint8Array` needs type information. #24107 asked for that general case
and was
closed on the grounds that upstream's `isArray` is type-aware and this
rule is not
moving to tsgolint. This PR stays inside the syntactic subset, where the
receiver is
literally `new <TypedArray>(...)` or `<TypedArray>.from(...)`.
**A shadowed constructor is a false negative.** With `class Uint8Array`
in scope,
`[...new Uint8Array().slice(0)]` is a genuine useless spread and is no
longer
reported. This matches how `is_new_typed_array` and every other name
matcher in the
file already behave, so it introduces no new inconsistency. Happy to add
a
global-reference check if you'd rather have it here.
## Verification
```
cargo check -p oxc_linter clean
cargo test -p oxc_linter no_useless_spread rules::unicorn::no_useless_spread::test ... ok
```
The snapshot is unchanged, so no existing `fail` case flipped.
Control, to show the tests have teeth: reverting `const_eval.rs` to its
previous
state while keeping the new vectors fails the test with 19 diagnostics,
one per new
vector.
## AI usage disclosure
Per the AI usage policy: this patch was drafted with Claude Code,
including the
`ValueHint::NewTypedArray` approach and the test vectors. Everything in
the
verification section was run locally against this branch rather than
asserted, and
the control run is there because a test that passes with and without the
fix would
prove nothing. Happy to take the change in a different direction if
you'd prefer the
narrower receiver guard described in #25868.
---------
Co-authored-by: Cameron <cameron.clark@hey.com>
## Summary
- check the immediate object property before the enclosing JSX attribute when matching `propNamePattern`
- preserve matching through either the object property or JSX attribute
- add coverage for inline arrays of render-property objects
Fixes#26086
## Summary
- clarify that `defaultCaseCommentPattern` recognizes a trailing comment in place of an omitted `default` clause
- document the required location, absence of a live `default`, and the `considerDefaultExhaustiveForUnions` requirement for union switches
- regenerate the JSON schema and TypeScript configuration types
Fixes#26087
add suggestion for `nextjs/no-typos` rule, upstream rule rn doesnt
provide suggestion, but since we already have the matched suggestion, I
think it should be safe to implement
issue #1929
## Summary
Closes#26038.
`no-unassigned-vars` is a `correctness` rule, so this fires by default
with no config:
```svelte
<script lang="ts">
let dialog: HTMLDialogElement;
function openDialog() {
dialog.showModal();
}
</script>
<dialog bind:this={dialog}>Hello</dialog>
```
```text
repro.svelte:2:7: warning eslint(no-unassigned-vars): 'dialog' is always 'undefined' because it's never assigned.
```
## Why the rule cannot get this right today
The rule's decision is `has_read && no reference is_write()`, taken over
the symbol's references in the current script block. Oxlint reaches
`.svelte`/`.vue` through `PartialLoader`, which extracts the `<script>`
blocks and throws the markup away before parsing — so the template's
write is not in the AST the rule sees, and no reference on `dialog` is
ever a write. Every ingredient of the diagnostic is satisfied, and the
diagnostic is still wrong: Svelte assigns the element to `dialog` on
mount.
The same shape exists in Vue, where a `<script setup>` `let` is a
`setup-let` binding that `v-model="x"` and inline handlers such as
`@click="x = 1"` compile to direct assignments against.
This is not a matter of tightening the check — the write is in a part of
the file the linter never parses.
## The change
`prefer-const` hit exactly this and already skips both extensions
([#25148](https://github.com/oxc-project/oxc/pull/25148)):
```rust
fn should_run(&self, ctx: &crate::context::ContextHost) -> bool {
// ignore svelte/vue: their templates can reassign a binding, which we can't see.
!ctx.file_extension().is_some_and(|ext| ext == "svelte" || ext == "vue")
}
```
This applies the sibling rule's guard rather than inventing one, and
documents it in the rule docs under an `#### Ignored Files` heading,
matching `prefer-const`'s wording.
`.astro` is deliberately **not** included: an Astro template renders a
frontmatter binding but has no construct that writes back to it, so the
false positive does not arise there. `prefer-const` drew the same line.
## Scope, stated honestly
This trades a false positive for lost coverage: a genuinely unassigned
variable inside a `.svelte`/`.vue` `<script>` block is no longer
reported. That matches the precedent and the reporter's own request, and
a `correctness` false positive that fires on the documented Svelte idiom
costs more than the missed report. Restoring coverage needs
template-aware analysis, which is the language-support effort tracked
separately (#13017, #17991).
## Verification
- `cargo test -p oxc_linter --lib` — **1254 passed, 0 failed**.
- Two new tests, `test_svelte` and `test_vue`, cover the reported repro,
`bind:value`, a two-`<script>` Svelte component, `v-model`, and an
inline `@click` handler.
- **Revert-checked:** with `should_run` removed and the tests kept,
exactly those two tests fail and the rest pass. The failure output
reproduces the reported diagnostic verbatim, including the `let shared`
case from the two-script component — so the tests fail for the right
reason, not because the fixture path changed.
- `cargo clippy -p oxc_linter --lib --all-features` — no new findings
(the two `FromIterator::from_iter` warnings are pre-existing, in
`tsgolint.rs` and `lib.rs`).
- `cargo fmt --check` clean.
- The rule is not part of `docs_rule_pages.snap`, so the doc addition
needs no snapshot regeneration.
Built and tested on Windows with the `1.97.1-x86_64-pc-windows-gnu`
toolchain rather than the pinned `1.98.0`, and with `RUSTFLAGS`
overridden to drop the MSVC-only link args that `.cargo/config.toml`
applies to every `target_os = "windows"` target. Nothing in the
repository was changed for that.
---
Written with AI assistance (Claude Code), reviewed and tested by me
before submitting, per AGENTS.md.
---------
Signed-off-by: hamodywe <iosapk.org@gmail.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
While working on #26077, Claude spotted another related bug.
JS plugins have to run last because they need to mutate the AST, and in the process they destroy the stored AST.
For files with multiple sections (Vue, Astro, Svelte), we ran native rules then JS rules for each section in turn. That appeared fine - JS rules do run last. But the problem is that some rules e.g. `vue/valid-define-emits` also access _earlier_ sections. Because JS plugins already ran on those previous sections, their ASTs have been discarded already, leading to incorrect results.
Instead, lint in 3 separate passes which each run on _all_ sections before going on to the next pass:
1. Native rules.
2. JS rules.
3. Unused directives.
i.e.:
- Before: section 1 native, section 1 JS, section 2 native, section 2 JS.
- After: section 1 native, section 2 native, section 1 JS, section 2 JS.
Unused directives check has to be in a separate final pass, as it requires both native rules and JS rules to have run before it.
Looping over sections is cheap compared to the work that happens in each turn of the loops, so I very much doubt this has a measurable impact on perf.
This change does have one visible effect: The order of diagnostics will change for multi-section files. Previously diagnostics would be output in section order, now they're in rule type (native/JS) order. I don't _think_ that matters.
`source_text` field was added to `ContextSubHost` in #25280 to work around the code that runs JS plugins (`run_external_rules`) nuking the whole of `Semantic`, replacing `semantic.source_text` with an empty string.
#26077 has removed that behavior, so we can now return to using `semantic.source_text`, and it remains what it should be, even after `run_external_rules`.
### What's needed for JS plugins
When running JS plugins, we need to obtain a mutable copy of the AST (`&mut Program`) in order to alter it (update spans to UTF-16 offsets). But `Semantic` only contains an immutable `&Program` reference, and also its `AstNodes` contains immutable references to _all_ nodes in the AST. So we need to do 2 things:
1. Convert the `&Program` from `Semantic` to `&mut Program`.
2. Get rid of all the active references to AST nodes stored in `AstNodes` so that the `&mut Program` doesn't illegally alias with those references.
### Previous solution and its problems
Previously we achieved this with a dodgy hack where we discarded all of `Semantic` (to get rid of the AST node references) and then "laundered" the pointer to the `Program`, upgrading it to write permission, by taking the provenance of the `Allocator`'s cursor pointer (which does have write permission).
However, this relied on the `Program` being in the `Allocator`'s current chunk, so that the `Allocator`'s current cursor and the `Program` are within the same allocation (a requirement for the pointer returned from `NonNull::with_addr` being legal to dereference).
This assumption was always a bit shaky, but when we enabled React Compiler rules by default in last release, it became clear it was completely untenable. React Compiler rules allocate a lot, and the debug assertions which checked that the `Program` was in current chunk started failing - so many allocations had occured that the `Allocator` had grown a fresh chunk to accomodate them.
### How this PR fixes the problem
This PR alters the method by which we obtain the `&mut Program`. It still discards all the references to AST nodes held in `Semantic`, but now performs a bitwise copy of the original `Program` and allocates that copy back into the arena, yielding a `&mut Program`.
This avoids the need for pointer "laudering" and removes the requirement for `Program` to be in the current `Allocator` chunk.
As noted in the comments, this is still not sound. We have no way to prove that no other references to AST nodes exist, stashed somewhere, which would be an aliasing violation (UB).
However, a thorough review of the code shows that we currently do not do this, so there is no actual UB at present.
So, in short, this change does not achieve soundness, but it does at least swap definite proven UB for a _potential risk_ of UB which isn't currently triggered. Not great, but better.
### Side effects
Previously we nuked the whole of `Semantic`, replacing it all with a new empty copy. Instead this PR operates more "surgically", just removing the AST node and `Comment` references that we _need_ to get rid of to avoid aliasing violations, and leaving the rest of `Semantic` alone. This is much cheaper.
## Summary
- classify the rule replacement as a suggestion instead of a normal autofix
- preserve the diagnostic and suggested Math.min/Math.max replacement
- avoid changing behavior based on value analysis
## Summary
- detect adjacent comparisons across left-associated logical-expression groups
- limit the diagnostic and suggestion span to the comparisons being simplified
- cover mixed precedence, parentheses, grouped `&&`, and reparsed suggestion outputs
`eslint(no-control-regex)` correctly flags this regex
```ts
const fnKeyRe = /^(?:\x1b+)(O|N|\[|\[\[)(?:(\d+)(?:;(\d+))?([~^$])|(?:1;)?(\d+)?([a-zA-Z]))/
```
`'U+001B' is a control character.` and shows in help text, `help: Avoid
matching control characters in regular expressions. If intentional,
consider using a Unicode escape instead.`
but the help text is misleading, all forms of unicode escapes are also
flagged.
```ts
const a = /\x1b/;
const b = /\u001b/;
const c = /\u{1b}/u;
const d = /\u{001b}/u;
```
This behaviour is consistent with upstream eslint rule, just the help
text is misleading, so I have changed the help text to say to disable
this rule when its intentional
Preserves inline `type` modifiers when `no-useless-rename` removes a
redundant import or export alias, avoiding semantic changes under
`verbatimModuleSyntax`.
fixes#26004
Addresses deprecations reported by the latest Rust nightly while preserving Rust 1.98 compatibility.
- Rename `unit-bindings` to `unit_bindings`: hyphenated lint names are deprecated in favor of their canonical underscore spelling.
- Replace `isize::max_value()` and `usize::max_value()` with `isize::MAX` and `usize::MAX`: the legacy numeric methods are deprecated in favor of associated constants.
- Set explicit `MAX_INDEX` values on `define_index_type!`: `oxc_index` otherwise generates `<raw>::max_value() as usize`; the explicit modern constants avoid that deprecated expansion without changing the index limits.
- Remove obsolete `clippy::legacy_numeric_constants` expectations: the corrected code no longer triggers that lint, so the expectations would be unfulfilled.
- Allow the AST scanner to consume optional `define_index_type!` configuration: it previously parsed only the struct declaration and rejected the added `MAX_INDEX` option.
AI-assisted.
This PR contains the following updates:
| Package | Type | Update | Change |
|---|---|---|---|
| [rust](https://rust-lang.org/)
([source](https://redirect.github.com/rust-lang/rust),
[changelog](https://redirect.github.com/rust-lang/rust/blob/main/RELEASES.md))
| toolchain | minor | `1.97.1` → `1.98.0` |
---
### Configuration
📅 **Schedule**: (in timezone Asia/Shanghai)
- Branch creation
- "before 10am on monday"
- Automerge
- At any time (no schedule defined)
🚦 **Automerge**: Enabled.
♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.
🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.
---
- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box
---
This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/oxc-project/oxc).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC4zNS40IiwidXBkYXRlZEluVmVyIjoiNDQuMzUuNCIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->
---------
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Cameron <cameron.clark@hey.com>
AI Disclosure: Generated with Claude Code, Opus 5. Tested and reviewed
by me.
Why do many code when few do trick?
Follow-up to #25901. Hand-rolled import checks can simply be replaced
with the shared `is_import_symbol` helper, this also helps improve the
accuracy of the code to catch aliased imports for one of them.
### Bug fixed
`is_vue_define_component_reference` (used by
`vue/no-deprecated-delete-set` to decide whether a `this.$set` /
`this.$delete` call sits inside a Vue component) gated on the **local**
name being `defineComponent` before walking up to the
`ImportDeclaration`, so an aliased import was never recognised:
```vue
<script>
import { defineComponent as dc } from 'vue'
dc({
mounted () {
this.$set(obj, key, value)
this.$delete(obj, key)
}
})
</script>
```
Neither call was reported before; both are now. Covered by a new test
case.
### Pure simplifications
- `is_imported_set_or_del_from_vue` (`no-deprecated-delete-set`) — 20
lines → two `is_import_symbol` calls.
- `is_vue_next_tick_import` (`utils/vue.rs`, used by
`vue/next-tick-style` and `vue/valid-next-tick`) — 18 lines → one call.
Both already matched by symbol id, so behavior is unchanged.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
AI Disclosure: Generated with Claude Code, Opus 5. Tested and reviewed
by me.
`no-react-children` decided whether an identifier came from `react` with
a hand-rolled helper that matched by **name**:
```rust
fn is_imported_from_react(local_name: &str, ctx: &LintContext) -> bool {
ctx.module_record().import_entries.iter().any(|entry| {
entry.module_request.name() == "react" && entry.local_name.name() == local_name
})
}
```
This replaces it with the existing shared helpers, `is_import_symbol`
and `is_import_from_module`.
### Bugs fixed
Four false positives, where a local binding shadowed the import:
```js
import { Children } from 'react';
function f(x) { const Children = { count: () => 0 }; return Children.count(x); }
import React from 'react';
function f(x) { const React = { Children: { map: () => {} } }; return React.Children.map(x); }
import { Children } from 'react';
export function f(Children) { return Children.count(1); }
import { count as Children } from 'react'; Children.toArray(x)
```
One false negative, an aliased import the local-name check couldn't see:
```js
import { Children as C } from 'react'; C.toArray(x)
```
All five are covered by new test cases.
### Performance
This has a minor downside in perf, but it's significantly less than a
millisecond difference and is probably worth the downside. We had
originally decided not to care about aliases when implementing this
rule, but this change allows us to reuse the existing util code for
checking where the import comes from, and fixes the aforementioned edge
cases, so I think this is a worthwhile change.
Measured with two `--features allocator` builds, paired and interleaved,
using `--debug=timings --threads=1` to isolate the rule's own time from
parse/semantic noise.
| corpus | base | new | change |
| --- | --- | --- | --- |
| sentry | 4.416ms | 4.589ms | +3.9% slower |
| material-ui | 3.111ms | 3.217ms | +3.4% slower |
| bluesky | 0.503ms | 0.540ms | +7.2% slower |
| actualbudget | 2.012ms | 2.105ms | +4.6% slower |
| synthetic, `Children` used heavily | 19.112ms | 15.443ms | 19.2%
faster |
There is a small, consistent regression on real code, and it's
deliberate. Dropping the `ident.name == "Children"` gate means every
`obj.method()` call in a file importing `react` now does two array
lookups instead of one string compare; removing the `O(imports)` scan
only pays off where `Children` is actually used, which is 27 files out
of 8.8K in sentry. The absolute cost is ≤0.17ms, and `no-react-children`
isn't a default rule anyway.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: Cameron <cameron.clark@hey.com>
AI Disclosure: Generated with Claude Code, Opus 5. Tested and reviewed
by me.
Ten call sites across nine rules checked whether an identifier is a
specific global with:
```rust
ctx.scoping().root_unresolved_references().contains_key(&ident.name)
```
That asks whether the **name** is unresolved **somewhere in the file**,
not whether **this reference** is global. Any file that shadows the
global in one scope and uses the real global in another gets a false
positive on the shadowed use:
```js
function f(myLogger) {
const console = myLogger;
console.log(1); // reported by `no-console` — this is the bug
}
console.log(2); // the real global, elsewhere in the file
```
`ctx.is_reference_to_global_variable(ident)` already exists for this and
is used at ~50 other call sites. It is `reference.symbol_id().is_none()`
— one field read instead of hashing the name into the
unresolved-references map — and it honours the user's `globals: { X:
"off" }` config.
### Rules fixed
| rule | shadowed code that was wrongly reported |
| --- | --- |
| `eslint/no-console` | `const console = myLogger; console.log(foo)` |
| `eslint/no-constant-binary-expression` | `const String = MyString;
String(x) ?? y` |
| `eslint/no-new-native-nonconstructor` | `const Symbol = FakeSymbol;
new Symbol()` |
| `eslint/prefer-object-spread` | `const Object = MyObject;
Object.assign({}, a)` |
| `eslint/symbol-description` | `const Symbol = FakeSymbol; Symbol()` |
| `eslint/valid-typeof` | `const undefined = u; typeof x === undefined`
|
| `promise/avoid-new` | `const Promise = FakePromise; new Promise(fn)` |
| `unicorn/new-for-builtins` | `const String = MyString; new
String('x')` |
| `unicorn/prefer-global-this` | `const window = fake; window.foo` |
Each rule gets a pass test in that shape. Both halves matter — without a
second, genuine use of the global the name lookup already returns false,
so a single-scope test would pass before the fix too.
Three of these ship a fix or suggestion (`prefer-global-this`,
`valid-typeof`, `no-console`), so the false positive was rewriting or
deleting code the user owns.
### Real-world impact
Base vs. branch over four corpora (vscode, sentry, material-ui, actual),
each rule isolated with `-A all -A nursery -D <rule> --format=json
--no-ignore --disable-nested-config`, diagnostics normalised to `(file,
span, code, message)`:
**0 added, 23 removed.** Every removal is `prefer-global-this` on a
genuinely shadowed `window`:
- `vscode/src/vs/base/browser/dompurify/dompurify.js` (16) — `let window
= arguments.length > 0 ? arguments[0] : getGlobal()`
- `material-ui/packages-internal/core-docs/src/Demo/DemoSandbox.tsx` (5)
— `function IsolatedDemo({ children, cssVarPrefix, colorSchemeNode,
window })`, a destructured prop
- `sentry/src/sentry/templates/sentry/error-page-embed.js` (2) —
`(function (window, document, JSON) { ... })(window, document, JSON)`,
the classic IIFE parameter
In each file the real global references outside the shadowing scope are
still reported.
### Notes
- `no-console` uses `ident.is_global_reference(ctx.scoping())` rather
than the context helper. An existing fail case pins that `globals: {
console: "off" }` must still report, and the context helper treats
`"off"` as "not a global". The other eight sites use the context helper,
so they now respect that config, but this one doesn't.
- Five upstream ESLint pass cases that were commented out in
`no-constant-binary-expression` now pass and are re-enabled (`function
Boolean(n) { return n; }; Boolean(x) ?? foo` and friends). Two others in
that list stay commented — they are `function foo(undefined) { ... }`,
which is a separate problem: the rule matches `undefined` by name via
`Expression::is_undefined()`. Left a TODO pointing at it.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Signed-off-by: Cameron <cameron.clark@hey.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: Cameron <cameron.clark@hey.com>
The 22 React Compiler rules split out by #25500, plus `eslint/no-unreachable-loop`, all first shipped in oxlint 1.79.0 but were still marked `version = "next"`. I think the release PR must've failed to run this part of the release task?
🤖 Generated with [Claude Code](https://claude.com/claude-code)
The shared React Compiler rule macro generated `react.dev` links for internal categories without public lint pages. Render those attributions without links and verify linked versus unlinked rule documentation.
Validated with `just ready`.
AI-assisted.
## Summary
- skip React Compiler transform and lint analysis for `node_modules` by default
- let explicit transform `sources` allowlists opt dependencies back in
- document the behavior and add regression coverage
## Validation
- focused Oxlint and transform-react checks
- Clippy with warnings denied
- `just ready` reached the all-features suite; three Oxlint ANSI-color snapshots differed in this environment
AI assistance: Codex was used to implement and verify this change.
## Summary
- avoid reporting forwarding constructors that declare TypeScript parameter properties
- share the modifier and decorator check across base and derived constructors
- cover private, public, protected, readonly, and combined parameter-property variants
Closes#25806
Generate `RuleEnum::to_configuration` arms only for the 33 rules that override the trait default, returning `None` for all other rules.
Release `oxlint`: 12,491,264 → 12,491,200 bytes (-64 bytes). `__text`: 10,589,512 → 10,585,376 bytes (-4,136 bytes).
AI-assisted; reviewed and validated by the contributor.
Add a shared non-generic JS-style token update entry point so `oxc_linter` and `oxlint` do not each instantiate the ESTree token visitor.
On `aarch64-apple-darwin`, `cargo build -p oxlint --release --features allocator` reduces the `oxlint` binary by 49,616 bytes (48.5 KiB, 0.39%), from 12,756,960 to 12,707,344 bytes.
Validated with `just ready`.
AI assistance was used for implementation and measurement.