`>>` 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.
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.
Small optimization. Don't create temporary objects repeatedly just to pass props to `Object.defineProperty`. Re-use a single object and reuse it instead.
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.
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.
#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.
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.
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.
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.
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.
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.
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.
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.
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!).
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.
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.
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()`).
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).
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.
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.