Skip to content

feat: Add locations option to parser#16935

Merged
nicolo-ribaudo merged 2 commits intobabel:mainfrom
liuxingbaoyu:feat-locations
Mar 7, 2026
Merged

feat: Add locations option to parser#16935
nicolo-ribaudo merged 2 commits intobabel:mainfrom
liuxingbaoyu:feat-locations

Conversation

@liuxingbaoyu
Copy link
Copy Markdown
Member

@liuxingbaoyu liuxingbaoyu commented Oct 25, 2024

Q                       A
Fixed Issues? Fixes #1, Fixes #2
Patch: Bug Fix?
Major: Breaking Change?
Minor: New Feature?
Tests Added + Pass? Yes
Documentation PR Link babel/website#3186
Any Dependency Changes?
License MIT

This allows Babel to be faster in certain use cases and fixes the performance regression in the estree plugin.

I tried many, many approaches and this was the one with the least performance impact and the fewest changes.
0~5% performance loss in the benchmark, ~10% without caching.

before

PS F:\babel\benchmark> node --expose-gc .\babel-parser\real-case.mjs --only-current
babel-parser/real-case.mjs babel-parser-express.ts @ current: 268 ops/sec ±4.26% 135 runs (3.725ms)
babel-parser/real-case.mjs jquery-3.6.js @ current: 104 ops/sec ±4.87% 52 runs (9.627ms)
babel-parser/real-case.mjs ts-checker.mjs @ current: 14.93 ops/sec ±3.58% 30 runs (67ms)
babel-parser/real-case.mjs ts-checker.ts @ current: 10.8 ops/sec ±2.44% 30 runs (93ms)
babel-parser/real-case.mjs ts-parser.mjs @ current: 103 ops/sec ±5.49% 52 runs (9.673ms)
babel-parser/real-case.mjs ts-parser.ts @ current: 59 ops/sec ±5.72% 30 runs (17ms)
babel-parser/real-case.mjs typescript-5.6.2.js @ current: 3.31 ops/sec ±2.63% 30 runs (302ms)

after
(no-loc seems to reduce performance for small files, which is strange)

PS F:\babel\benchmark> node --expose-gc .\babel-parser\real-case.mjs --only-current
babel-parser/real-case.mjs babel-parser-express.ts @ current: 266 ops/sec ±3.98% 133 runs (3.766ms)
babel-parser/real-case.mjs jquery-3.6.js @ current: 99.57 ops/sec ±4.37% 50 runs (10ms)
babel-parser/real-case.mjs ts-checker.mjs @ current: 14.01 ops/sec ±4.56% 30 runs (71ms)
babel-parser/real-case.mjs ts-checker.ts @ current: 10.64 ops/sec ±2% 30 runs (94ms)
babel-parser/real-case.mjs ts-parser.mjs @ current: 101 ops/sec ±5.06% 52 runs (9.904ms)
babel-parser/real-case.mjs ts-parser.ts @ current: 57.43 ops/sec ±5.56% 30 runs (17ms)
babel-parser/real-case.mjs typescript-5.6.2.js @ current: 3.24 ops/sec ±2.7% 30 runs (308ms)

PS F:\babel\benchmark> node --expose-gc .\babel-parser\real-case-no-loc.mjs
babel-parser/real-case-no-loc.mjs babel-parser-express.ts @ current: 265 ops/sec ±3.59% 133 runs (3.771ms)
babel-parser/real-case-no-loc.mjs babel-parser-express.ts @ baseline: 338 ops/sec ±3.38% 169 runs (2.96ms)
babel-parser/real-case-no-loc.mjs jquery-3.6.js @ current: 118 ops/sec ±3.77% 59 runs (8.506ms)
babel-parser/real-case-no-loc.mjs jquery-3.6.js @ baseline: 117 ops/sec ±3.25% 59 runs (8.568ms)
babel-parser/real-case-no-loc.mjs ts-checker.mjs @ current: 17.4 ops/sec ±2.05% 30 runs (57ms)
babel-parser/real-case-no-loc.mjs ts-checker.mjs @ baseline: 15.41 ops/sec ±3.2% 30 runs (65ms)
babel-parser/real-case-no-loc.mjs ts-checker.ts @ current: 11.8 ops/sec ±1.74% 30 runs (85ms)
babel-parser/real-case-no-loc.mjs ts-checker.ts @ baseline: 11.11 ops/sec ±2.49% 30 runs (90ms)
babel-parser/real-case-no-loc.mjs ts-parser.mjs @ current: 128 ops/sec ±2.86% 65 runs (7.8ms)
babel-parser/real-case-no-loc.mjs ts-parser.mjs @ baseline: 105 ops/sec ±5.55% 53 runs (9.509ms)
babel-parser/real-case-no-loc.mjs ts-parser.ts @ current: 67.66 ops/sec ±3.69% 34 runs (15ms)
babel-parser/real-case-no-loc.mjs ts-parser.ts @ baseline: 60.64 ops/sec ±6.51% 31 runs (16ms)
babel-parser/real-case-no-loc.mjs typescript-5.6.2.js @ current: 3.88 ops/sec ±1.58% 30 runs (258ms)
babel-parser/real-case-no-loc.mjs typescript-5.6.2.js @ baseline: 3.38 ops/sec ±2.61% 30 runs (296ms)

estree

PS F:\babel\benchmark> node --expose-gc .\babel-parser\real-case-estree.mjs
babel-parser/real-case-estree.mjs babel-parser-express.ts @ current: 215 ops/sec ±4.17% 108 runs (4.643ms)
babel-parser/real-case-estree.mjs babel-parser-express.ts @ baseline: 110 ops/sec ±4.46% 56 runs (9.06ms)
babel-parser/real-case-estree.mjs jquery-3.6.js @ current: 67.25 ops/sec ±5.21% 34 runs (15ms)
babel-parser/real-case-estree.mjs jquery-3.6.js @ baseline: 37.98 ops/sec ±2.02% 30 runs (26ms)
babel-parser/real-case-estree.mjs ts-checker.mjs @ current: 11.86 ops/sec ±3.37% 30 runs (84ms)
babel-parser/real-case-estree.mjs ts-checker.mjs @ baseline: 6.02 ops/sec ±1.32% 30 runs (166ms)
babel-parser/real-case-estree.mjs ts-checker.ts @ current: 9.11 ops/sec ±2.57% 30 runs (110ms)
babel-parser/real-case-estree.mjs ts-checker.ts @ baseline: 4.84 ops/sec ±1.13% 30 runs (206ms)
babel-parser/real-case-estree.mjs ts-parser.mjs @ current: 74.22 ops/sec ±5.76% 38 runs (13ms)
babel-parser/real-case-estree.mjs ts-parser.mjs @ baseline: 37.91 ops/sec ±1.77% 30 runs (26ms)
babel-parser/real-case-estree.mjs ts-parser.ts @ current: 50.29 ops/sec ±5.94% 30 runs (20ms)
babel-parser/real-case-estree.mjs ts-parser.ts @ baseline: 26.76 ops/sec ±2.22% 30 runs (37ms)
babel-parser/real-case-estree.mjs typescript-5.6.2.js @ current: 2.56 ops/sec ±1.97% 30 runs (391ms)
babel-parser/real-case-estree.mjs typescript-5.6.2.js @ baseline: 1.37 ops/sec ±0.94% 30 runs (732ms)

@babel-bot
Copy link
Copy Markdown
Collaborator

babel-bot commented Oct 25, 2024

Build successful! You can test your changes in the REPL here: https://babeljs.io/repl/build/61133

Comment on lines +8 to +12
export class Position {
// If `declare` is used here, the `jquery` benchmark will drop by 10%. :)
line: number;
column: number;
index: number;
declare index?: number;
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ugh, this is super weird, I tried for a long time before I found it was related to it.🤦‍♂️

Comment on lines +103 to +105
if (!locDataCache || locDataCache.length < (this.length + 1) * 2) {
locDataCache = new Uint32Array((this.length + 1) * 2);
}
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Performance drops by about 10% when there is no cache.

Comment on lines +185 to +190
this.raise(
Errors.MissingEqInAssignment,
this.optionFlags & OptionFlags.Locations
? node.left.loc.end
: node.left,
);
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here is one of the ones that were skipped during testing.

Comment on lines +2571 to +2575
? this.optionFlags & OptionFlags.Locations
? // @ts-expect-error node.key has been guarded
node.key.loc.end
: // @ts-expect-error node.key has been guarded
node.key
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here is one of the ones that were skipped during testing.

@liuxingbaoyu liuxingbaoyu force-pushed the feat-locations branch 2 times, most recently from 99ad61b to 58efcf9 Compare November 1, 2024 05:15
Copy link
Copy Markdown
Contributor

@JLHwung JLHwung left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the option flags bit packs. Can you extract that refactor into a small PR so that we don't have to wait for the next minor release?

declare loc: SourceLocation;
}

let locDataCache: Uint32Array;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the location data cache shared between different parser inputs?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, actually this is just sharing a Uint32Array instance to avoid the time-consuming creation of a new one, the data inside is not shared.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IIRC the data inside is correct if and only if for every getLoc call, there is a setLoc call preparing the location data for the exact index. If the getLoc call is issued before setLoc, it will read 1) uninitialized locDataCache cells or 2) location data used by previous inputs. This is prone to errors because of the implicit constraint here that if one should throw an error at the location, the location must have been prepared by setLoc, and there will be no immediate errors if it happens that the previous input gives the same location data.

I would suggest we zero-filling the locDataCache in the parse() entry and check if they are zero when getLoc is called, if they are zero, we should throw an internal error reminding contributors to manually add the setLoc call.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added it in the non-release version.
Because it affects performance and the user may still be able to use it normally even if the location information is wrong.

// `startLoc` and `endLoc` properties.

export class Position {
// If `declare` is used here, the `jquery` benchmark will drop by 10%. :)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you mean if declare is used for line and column? Since the index property is declared.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, if I use declare for line and column, the performance will decrease.🤦‍♂️

@liuxingbaoyu liuxingbaoyu force-pushed the feat-locations branch 2 times, most recently from b9e72c6 to 07a7af5 Compare May 19, 2025 17:41
@liuxingbaoyu liuxingbaoyu force-pushed the feat-locations branch 2 times, most recently from 3050870 to 5897bd6 Compare July 31, 2025 00:32
@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new bot commented Jul 31, 2025

Open in StackBlitz

commit: c63904b

@liuxingbaoyu liuxingbaoyu force-pushed the feat-locations branch 2 times, most recently from b198d53 to 0084a35 Compare November 30, 2025 20:38
@nicolo-ribaudo nicolo-ribaudo added the PR: New Feature 🚀 A type of pull request used for our changelog categories label Jan 23, 2026
@nicolo-ribaudo nicolo-ribaudo added this to the v7.29.0 milestone Jan 27, 2026
@nicolo-ribaudo nicolo-ribaudo added PR: Needs Docs PR: New Feature 🚀 A type of pull request used for our changelog categories and removed PR: New Feature 🚀 A type of pull request used for our changelog categories labels Jan 27, 2026
@nicolo-ribaudo nicolo-ribaudo removed this from the v7.29.0 & v8.0.0-rc milestone Jan 29, 2026
@liuxingbaoyu liuxingbaoyu force-pushed the feat-locations branch 3 times, most recently from 5b24f3b to 17506a3 Compare February 19, 2026 12:21
Copy link
Copy Markdown
Contributor

@JLHwung JLHwung left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great work!

@nicolo-ribaudo
Copy link
Copy Markdown
Member

To clarify, how does this PR affect performance in the default case of using @babel/parser as part of a @babel/core transform pipeline?

@liuxingbaoyu
Copy link
Copy Markdown
Member Author

This PR should have no impact at present. Ideally, we can switch the default usage of @babel/core to locations: "packad" in the future.

@JLHwung
Copy link
Copy Markdown
Contributor

JLHwung commented Mar 4, 2026

@liuxingbaoyu Could you rebase?

@liuxingbaoyu liuxingbaoyu added PR: New Feature (next major) 🚀 A type of pull request used for our changelog categories for next major release and removed PR: New Feature 🚀 A type of pull request used for our changelog categories labels Mar 4, 2026
@nicolo-ribaudo nicolo-ribaudo merged commit 52593e1 into babel:main Mar 7, 2026
56 checks passed
@nicolo-ribaudo
Copy link
Copy Markdown
Member

Could you open a docs PR? 🙏

@nicolo-ribaudo nicolo-ribaudo added PR: New Feature 🚀 A type of pull request used for our changelog categories PR: New Feature (next major) 🚀 A type of pull request used for our changelog categories for next major release and removed PR: New Feature (next major) 🚀 A type of pull request used for our changelog categories for next major release PR: New Feature 🚀 A type of pull request used for our changelog categories labels Mar 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

pkg: parser PR: New Feature (next major) 🚀 A type of pull request used for our changelog categories for next major release PR: New Feature 🚀 A type of pull request used for our changelog categories

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants