Commit Graph

33 Commits

Author SHA1 Message Date
Boshen 1193c9dbaf chore(oxfmt): dogfood operator position at line start (#25937)
dogfood
2026-08-20 14:36:47 +00:00
overlookmotel 43482c7bb1 perf(linter/plugins): use >> not >>> in binary search loops (#21129)
`>>` is cheaper than `>>>` because `>>` produces a 32-bit _signed_ integer which is V8's native number type (SMI). `>>>` produces a 32-bit _unsigned_ integer, which needs to be boxed and stored on the heap.

Source text in raw transfer is limited to 1 GiB, and therefore source offsets, number of lines, number of tokens, and number of comments after all less than `1 << 30`. Therefore even the sum of 2 of them cannot reach `1 << 31` (the maximum positive integer which can be stored as a positive signed 31-bit int. Therefore it's safe to use `>>` in these binary loops.
2026-04-07 17:10:32 +00:00
overlookmotel 336f7f7206 perf(linter/plugins): faster conversion of span to Location (#20507)
Speed up `computeLoc`, which converts a `start` + `end` offset pair to a `Location` of form `{ start: { line: ?, column: ? }, end: { line: ?, column: ? } }`.

`computeLoc` is only called with `start` and `end` pairs from AST nodes, tokens, and comments, which are all produced by Oxc's parser, so it's guaranteed that `start < end`.

Use this invariant for 2 optimizations:

1. Fast path for common case where `start` and `end` are on same line.
2. Reduce range of lines which are searched looking for end to only lines which are after the line `start` is on. `end` can't be on an earlier line, so there's not point search them.
2026-03-21 13:02:27 +00:00
overlookmotel f064f8049a docs(linter/plugins): correct comment about offset to line-column conversion (#20506)
Correct a comment. It didn't accurately describe what the code does.
2026-03-21 13:02:26 +00:00
overlookmotel a1873334b5 perf(linter/plugins): reuse descriptor objects for Object.defineProperty calls (#20505)
Small optimization. Don't create temporary objects repeatedly just to pass props to `Object.defineProperty`. Re-use a single object and reuse it instead.
2026-03-21 12:57:19 +00:00
overlookmotel 5984a6673b perf(linter/plugins): recycle Location objects (#20491)
Add an object pool for `Location` objects, and recycle them across files.

Usually, only a minority of AST nodes, tokens, and comments will have their `loc` property accessed, so the pool is not pre-populated - `Location` objects are only created on demand.

Like the other object pools, once warmed up over the first batch of files, the fast path for "use an existing object from the pool" will be consistently taken and no further objects will be allocated. All `Location` objects will soon graduate to "old space" where they don't impede or trigger minor GC.
2026-03-21 12:46:33 +00:00
overlookmotel 872961446a perf(linter/plugins): reduce operations in binary search (#20490)
Tiny optimization. Use 1 less operation in binary search when converting offset to line-column pair.
2026-03-21 12:46:33 +00:00
overlookmotel 9cd612f1e9 perf(linter/plugins): recycle comment objects (#20362)
Apply the same optimization as #19978 to comments - hold a pool of `Comment` objects, and re-use those objects rather than creating new objects each time.

Same as with `Token`s, `loc` property is a getter which calculates `loc` lazily, and caches it in a private property.
2026-03-14 12:19:27 +00:00
overlookmotel 77f1c719ce perf(linter/plugins): make tokens class instances (#19980)
#19978 introduced caching of tokens, and got a large speed-up. However, it had one major flaw.

Token objects (like AST nodes) are plain objects with a getter on the prototype for `loc` property. When `loc` getter is triggered, it adds a property to the token object itself, so that accessing `loc` again evaluates to the same `Location` object, rather than recalculating the location and creating a new object.

Now that we cache tokens objects, any tokens which had `loc` property added need to have that property deleted again when tokens are reset, ready for reuse.

Deleting the `loc` property has an unfortunate side effect. V8 transitions the object into "dictionary mode", which makes all property accesses much slower on that object for the rest of its lifecycle.

Worse, it means that token objects don't have a consistent object "shape", which inhibits inline caches and transitions `deserializeTokenInto` from monomorphic to polymorphic, which makes V8 deoptimize it. This is one of the hottest JS functions in Oxlint, so has a heavy perf impact.

Solve this problem by making tokens instances of a `Token` class. `Token` stores `loc` in a private `#loc` property, and the `loc` getter can fetch it if `loc` for that token was already accessed.

All `Token` objects now have a consistent object shape throughout, regardless of whether `loc` is accessed on them or not.

This does make the `loc` getter a little more expensive, but that cost is heavily outweighed by the improved performance yielded by consistent object shapes.
2026-03-03 23:23:50 +00:00
overlookmotel 236847fe89 perf(linter/plugins): cache token objects (#19978)
Add a cache for token objects. Once warmed up over the first few files, no further token objects are created - the same objects are reused over and over across all files.

Running a JS plugin which just accesses `context.sourceCode.ast.tokens` (so triggering deserialization of tokens) and does nothing else, on NodeJS repo, this nets a 40% speed-up.
2026-03-03 23:23:49 +00:00
overlookmotel 4d684483e7 fix(linter/plugins): report accept out of range column indexes in loc (#18199)
ESLint's `func-call-spacing` rule sometimes reports errors with `loc` containing `column: -1`. As ESLint doesn't complain, probably other plugins do the same too.

Make the checks for validity of `loc` in `report` more relaxed, to support this behavior.
2026-01-19 04:39:05 +00:00
overlookmotel b2b87c608f feat(linter/plugins): add getRange and getLoc methods to SourceCode (#17179)
ESLint has undocumented `getRange` and `getLoc` methods on `SourceCode`. Add them.

These are the last 2 missing methods on `SourceCode` (according to Claude).

Also add more tests for correct functioning of `range` and `loc` properties.
2025-12-20 15:49:56 +00:00
overlookmotel 1714719eb5 docs(linter/plugin): correct doc comment on getNodeLoc function (#17178)
Fix these comments. They were out of date - there's no longer a `loc` getter on AST node objects - the getter is on the prototype.

Also alter this function to take a `NodeOrToken`, not `Node` - it's also used for tokens.
2025-12-20 15:08:48 +00:00
overlookmotel e39deb610e refactor(linter/plugins): add error messages to debug assertions (#17171)
Add error messages to all `debugAssert()` calls, for easier debugging.

When running Oxlint normally, we can easily figure out where errors are coming from stack traces, but in conformance test snapshot, line and column are removed from errors in the snapshot, so it's less easy.
2025-12-20 13:59:12 +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 32e23f7de2 fix(linter/plugins): correct punctuation in error message (#16718)
Tiny change. Our convention is not to have full stops on end of error messages, unless they have multiple sentences. Alter this one to make it consistent with that style.
2025-12-11 02:17:10 +00:00
overlookmotel 8c85e087f0 docs(linter/plugins): add TODO comment (#16511)
Just add a TODO comment for something to think about later.
2025-12-04 19:25:35 +00:00
overlookmotel b38c2d06be feat(linter/plugins): add SourceCode#lineStartIndices getter (#16510)
Add a getter `lineStartIndices` to `SourceCode`. ESLint has this property, but it's undocumented. Possible that some ESLint plugins rely on it (if it's there, people will use it!).
2025-12-04 19:14:21 +00:00
overlookmotel 4fdbacf4f7 refactor(linter/plugins): add more debug assertions (#16508)
Add more debug assertions to functions related to source text lines.
2025-12-04 17:48:26 +00:00
overlookmotel 77ee7c90a2 refactor(linter/plugins): rename lineStartIndices (#16507)
Pure refactor. Rename `lineStartOffsets` to `lineStartIndices`, to match ESLint.
2025-12-04 17:48:26 +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 9149a26f4f fix(linter/plugins, napi/parser): deep freeze visitor keys (#16293)
Visitor keys object used by `oxlint` and `napi/parser` is frozen, but the individual arrays of keys were not. Deep freeze the whole structure to prevent user modifying it.
2025-11-30 14:04:52 +00:00
overlookmotel 71502090c8 feat(linter/plugins): implement SourceCode#getNodeByRangeIndex (#16256)
Implement `SourceCode#getNodeByRangeIndex` method.

Behaves the same as ESLint's version, but this implementation should be more performant as it's much simpler, avoiding ESLint's `Traverser` abstraction, and exiting loops earlier.
2025-11-29 02:42:34 +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 a72b093b05 refactor(linter/plugins): enable TS strictNullChecks (#15886)
Adapted from #15868. Enable `strictNullChecks` in TSConfig for `apps/oxlint`.

Rather than using `!` all over the place, I've added a function `assertIsNonNull`, which performs the same function. Idea is that we can later on produce a debug build where `assertIsNonNull` does an actual runtime assertion (rather than just a TS `asserts`) that the value is not `null` or `undefined`. We can use the debug build in tests, and catch any logic errors.

Unfortunately, TypeScript doesn't seem able to track side effects, so there seems to be no way to tell it "after calling `initAst()`, `ast` is always non-null". Don't want to switch to a having a `getAst` function which returns a non-null value, as that'd introduce unnecessary temp vars, and function calls, on hot paths. So this seems to be the best we can do without incurring runtime cost.
2025-11-20 15:52:49 +00:00
overlookmotel 54061e9612 perf(linter/plugins): avoid implicit boolean coercion in initLines (#15641)
Tiny perf optimization. Explicitly check `!== null` instead of incurring cost of implicit boolean coercion.
2025-11-12 16:33:54 +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
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 45206951f2 refactor(linter/plugins): reorganise SourceCode methods (#14773)
Pure refactor. Tidy up and reorder code in `source_code.js`, for clarity.
2025-10-19 12:16:19 +00:00
overlookmotel a2914fe8d8 feat(linter/plugins): add loc field getter to all AST nodes (#14355)
Add `loc` to all AST nodes.

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

We can consider making `loc` a "real" property once we have a faster translation table built on Rust side.
2025-10-05 14:02:12 +00:00
overlookmotel 6e52bbd73c refactor(linter/plugins): move location-related code into separate file (#14350)
Pure refactor. Move code related to dividing source text into lines and converting offset <--> `Location` into a separate file.
2025-10-05 12:40:13 +00:00