Commit Graph

29 Commits

Author SHA1 Message Date
overlookmotel 55be9feb3a refactor(linter/plugins): pass fix/suggestion offsets to Rust as i64s (#19159)
Follow-on after #19092.

That PR added 1 to offsets of fixes before sending them to Rust side, to support offset `-1` (legal when fix is to remove a BOM).

Upon reflection, I think that solution is too complicated and confusing. Instead, represent `start` and `end` offsets of fixes as `i64`s in `JsFix`. This supports `-1` as an offset, and also the full range of valid `u32` values. Check on Rust side that the values are in range before converting to `u32`s.

This also makes for better error messages for invalid offsets - no more inexplicable "Failed to deserialize JSON returned by `lintFile`" errors.
2026-02-08 21:04:50 +00:00
overlookmotel 28d8882757 refactor(linter/plugins): getMessage return messageId (#19105)
Pure refactor. Remove duplicated code at 2 x call sites for `getMessage`, and move the logic into `getMessage` itself instead.
2026-02-08 20:40:30 +00:00
overlookmotel e3dc5f6c10 feat(linter/plugins): RuleTester test suggestions (#19104)
`RuleTester` test that suggestions provided by the rule match what's expected in the test case - that code after applying each suggestion matches the test case's `output` property, and that suggestions' descriptions also match those specified in test cases.

Skip one conformance test for ESLint's `prefer-regex-literals` rule where the suggestion description in Oxlint is different from ESLint, but in a completely cosmetic way. There's no need to match it exactly. That apart, no change to conformance test results, which strongly suggests that Oxlint is treating suggestions correctly.
2026-02-08 20:33:18 +00:00
overlookmotel ecd24560a1 fix(linter/plugins): handle fix with -1 offsets in file with BOM (#19092)
Fix an edge case bug where Oxlint was unable to handle fixes with with offset `-1`. Offset `-1` is used in files which contain a BOM to represent "before the BOM".

ESLint's `unicode-bom` rule produces a fix `{ range: [-1, 0], text: "" }` to remove BOM from a file with such a BOM.

`-1` can't be represented as a `u32`, so we have to take a rather annoying workaround - add 1 to offsets before sending them to Rust.

An alternative would have been to make `start` and `end` `i64`s so they can represent `-1`, but this would seem wasteful since we only need to represent -1, not any other negative value. So adding 1 to offsets is cheaper.

This fixes 2 ESLint conformance tests.
2026-02-08 20:05:57 +00:00
overlookmotel 65c6c33512 refactor(linter/plugins): simplify fix type and validation (#19078)
Refactor. Simplify the type used to represent a fix when sending from JS to Rust.

Previously we avoided creating new objects by using the same fix object returned by `fix` function, and so were locked to ESLint's design.

That worked, but the code was excessively complicated. Fixes should be fairly rare, so this optimization is on a cold path - it's not worth it.

Simplify implementation by converting `Fix`es to a simpler type (`FixReport`) before sending to Rust.
2026-02-06 17:13:22 +00:00
overlookmotel e2d28fe9a0 feat(linter/plugins): implement suggestions (#18963)
Implement support for suggestions.

Implementation exactly matches ESLint's.

The tests demonstrate that:

* `--fix-suggestions` applies suggestions.
* `--fix` does *not* apply suggestions.
* When a rule provides multiple suggestions, only the 1st is applied.

I've also confirmed that suggestions show up correctly in VSCode (once language server supports JS plugins).
2026-02-05 11:51:51 +00:00
overlookmotel 28f9fdb54c refactor(linter/plugins): split getFixes into 2 functions (#18958)
Pure refactor. Split `getFixes` into 2 functions. New function `getFixesFromFixFn` calls a fix function and processes the fixes it returns. `getFixesFromFixFn` will also be used for getting the fixes for suggestions in a later PR.
2026-02-05 02:24:15 +00:00
overlookmotel da509be78a refactor(linter/plugins): convert global properties to vars with TSDown plugin, instead of manually (#16961)
Previously we had a pattern of destructuring properties of global objects into top-level vars. e.g.:

```js
const { keys: ObjectKeys, hasOwn } = Object,
  { isArray } = Array;
```

Doing this is more performant, but it makes the code harder to read.

Instead, write code normally, and use a TSDown plugin to convert:

```js
const keys = Object.keys(obj);
```

to:

```js
import { ObjectKeys } from "../utils/globals.ts";
const keys = ObjectKeys(obj);
```

Defining all the destructured globals in a single file (`utils/globals.ts`) is advantageous for bundle size. If the TSDown plugin created top level `const ObjectKeys = Object.keys;` statements in each file, then you can end up with this in bundle as minifier doesn't de-duplicate:

```js
const ObjectKeys = Object.keys;
const ObjectKeys$1 = Object.keys;
const ObjectKeys$2 = Object.keys;
```

The globals we use have to be manually added to `utils/globals.ts`, but TSDown plugin warns when this needs to be done, and doesn't generate a broken build in the meantime.

This plugin is run in debug builds too, so that the tests will catch any bugs.
2025-12-16 17:33:37 +00:00
overlookmotel 3f3d646280 refactor(linter/plugins): imports use .ts extension (#16390)
Pure refactor. Use `.ts` extension when importing `.ts` files.
2025-12-02 14:08:49 +00:00
overlookmotel af3618f0cd style(linter/plugins): prefer const to let (#16385)
Use `const` where variables are not reassigned instead of `let`. Preparation for enabling ESLint's `prefer-const` rule (running as an Oxlint JS plugin) in our linting setup.
2025-12-02 10:06:17 +00:00
Boshen 523a30a016 style(all): apply oxfmt default options (#16091)
Embrace the defaults!

---------

Co-authored-by: Yuji Sugiura <y.sugiura.0316@gmail.com>
2025-11-25 15:56:24 +09:00
overlookmotel ae137042f5 refactor(linter/plugins): rename assertion functions (#15968)
Rename assertion functions to clarify that they're not "real" assertions which we can rely on for validating input, and are only for compile-time testing or debug assertions.

`typeAssertIs` is not a great name, but I couldn't come up with anything better. `debugAssertIs` would be not be accurate, as it'd imply that in debug builds in performs an actual assertion, which it doesn't.

In any case, we should replace `typeAssertIs` with specialized assertion functions which do actually perform runtime assertions in debug builds (e.g. `typeAssertIs<Fix>()` -> `debugAssertIsFix()`).
2025-11-22 15:54:17 +00:00
overlookmotel 512e43a204 refactor(linter/plugins): move assertions into own file (#15945)
Pure refactor. Split up `plugins/utils.ts` into 3 files in `utils` directory.

Most importantly, assertion functions are by themselves in an `asserts.ts` file. This paves the way for the next PR (#15946).
2025-11-21 14:42:25 +00:00
overlookmotel d2ff4baab2 refactor(linter/plugins): move report function and diagnostic types to separate file (#15635)
Pure refactor. Move `report` function and `Diagnostic` types into a separate file. `context.ts` was getting too long.
2025-11-12 15:15:58 +00:00
overlookmotel c4abee47e1 refactor(linter/plugins): move location-related types into location.ts (#15634)
Pure refactor.

Move TS type defs for location-related types into `location.ts`. `types.ts` is too crowded.

Also add JSDoc comments to these type defs.
2025-11-12 13:59:37 +00:00
overlookmotel c96ce29007 refactor(linter/plugins): rename RuleAndContext to RuleDetails (#15480)
Pure refactor. Give this type a better name. It now contains a lot more than just the `Rule` and `Context`.
2025-11-08 13:04:54 +00:00
overlookmotel a17ca32399 fix(linter/plugins): replace Context class (#15448)
Fix #15325.

Some ESLint rules create a wrapped copy of `context`, in order to patch `report` and pass that wrapped context object to another rule.

Some rules also rely on unspecified details of ESLint's implementation - that `context` is formed of 2 layers - top layer being `RuleContext` (`id`, `report` etc), and bottom layer being `FileContext` (`filename`, `sourceText` etc).

This usage was broken in our implementation because:

1. We collapsed the 2 layers into 1.
2. We used a `Context` class which stored its internal data in a private property. When `context.report` was called with a different `this`, it failed because the private property couldn't be accessed from an object which isn't an instance of the class.

Fix this problem by:

1. Splitting `Context` into 2 layers like ESLint does.
2. Not using classes or private properties.
3. No methods/getters on context objects use `this`.

We now have:

* Separate `Context` object per rule, containing properties related to the rule - a plain object which uses data from closure scope.
* All rule `Context`s inherit from the same singleton `FILE_CONTEXT` object, which provides getters for properties related to the file (same for all rules).

Tests adapted from #15326.
2025-11-07 19:43:57 +00:00
leaysgur a8e5181ab7 chore(infra): dogfooding oxfmt (#14979)
Fixes #14803

- Added `oxfmt` and `oxfmtrc.jsonc`
- Remove `dprint` plugin and useless ignore paths
- Apply `dprint` again
- Apply `oxfmt`
2025-10-28 01:16:46 +00:00
overlookmotel 9a902c0a41 fix(linter/plugins): make range field non-optional on AST types (#14354)
Alter AST types exported from `oxlint` to make the `range` field non-optional - it's always present on AST nodes in Oxlint's AST.
2025-10-05 13:47:32 +00:00
overlookmotel 5505a86121 feat(linter/plugins): include range field in AST (#14321)
Add `range` field to AST nodes, using the support for `range` in raw transfer added in #14319.

Use `range` field rather than `start` + `end` in nodes passed to `context.report` and fixer methods - for closer alignment with ESLint.
2025-10-04 15:47:47 +00:00
overlookmotel 65873ba277 refactor(linter/plugins): add stubs for all SourceCode methods (#14285)
Add stub implementations for all methods and properties of `SourceCode`. All unimplemented methods/getters just throw an error.
2025-10-02 08:32:03 +00:00
overlookmotel 989ce2f552 refactor(linter/plugins): convert Node type to interface (#14280)
Make `Node` type an `interface Node` instead of `type Node`. We can then use it in more places.
2025-10-01 15:32:10 +00:00
overlookmotel 257506582d perf(linter/plugins): store if rule is fixable as boolean (#14205)
Small optimization. Store if a rule is fixable as a single `isFixable` boolean, instead of putting the whole `RuleMeta` object in `Context`.

This is more efficient on memory, and a boolean is faster to check as it's stored inline in the object, as opposed to strings which are stored as a reference to another object on the heap.
2025-09-28 18:29:33 +00:00
overlookmotel acd1266d60 feat(linter/plugins): oxlint export types (#14163)
`oxlint` package export TS types. `defineRule` and `definePlugin` now give you type-safety and intellisense.

Writing plugins in TS is only supported on versions of NodeJS which support type-stripping. Have not added a library (e.g. Jiti) to support older NodeJS versions.

Probably some of the types are wrong. But I think this is a decent start.

"Are the types wrong" (added in #14177) is reporting an error in `index.d.ts`:

```ts
import { VisitorObject as Visitor } from "../dist/generated/visit/visitor.js";
//                                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
```

because the file in `dist/generated/visit` is actually called `visitor.d.ts` (`.d.ts`, not `.js`).

I'm not sure if that's a mistake in Rolldown's bundling, "Are the types wrong" being too strict, or a genuine problem.

But regardless, TS seems to pick up the types from `visitor.d.ts`, so I think we can probably merge this and look into / fix it later.
2025-09-28 14:59:48 +00:00
overlookmotel e9a14d130b fix(linter/plugins): allow fix function to return undefined (#14182)
Fix types for `fix` function. Allow it to return `undefined`, so a pattern where bail out unable to fix is legal:

```js
const rule = {
  create(context) {
    return {
      Identifier(node) {
        context.report({
          message: 'No identifiers!',
          node,
          fix(fixer) {
            if (node.name === 'foo') return; // Can't fix 'foo' for some reason
            return fixer.remove(node);
          },
        });
      },
    };
  },
};
```
2025-09-27 10:58:57 +00:00
overlookmotel 983dd1b87d refactor(linter/plugins): add Fixer type (#14180)
Pure refactor. Add a `Fixer` type.
2025-09-27 10:35:51 +00:00
overlookmotel 2f8b0760df refactor(linter/plugins): remove dead code (#14178)
#14166 made import of `Node` type dead code. Remove that import.
2025-09-27 09:01:29 +00:00
overlookmotel ee9ecbe187 fix(linter/plugins): fix TS type for fixer methods (#14166)
Another faulty type I found. `NodeOrToken` type was too restrictive. The `node` argument passed to fixer methods just needs to have `start` and `end` properties.
2025-09-26 15:51:26 +00:00
overlookmotel 9c3afea704 feat(linter/plugins): support fixes (#14094)
Add support for fixes in JS plugins.

```js
export const noDebuggerRule = {
  create(context) {
    return {
      DebuggerStatement(node) {
        return context.report({
          message: 'Remove this filth!',
          node,
          fix(fixer) {
            return fixer.remove(node);
          },
        });
      },
    };
  },
};
```

All of [ESLint's fixer APIs](https://eslint.org/docs/latest/extend/custom-rules#applying-fixes) are supported.

Note: Suggestions are not implemented yet, only fixes.
2025-09-25 08:15:33 +00:00