perf: stop scanning the whole template for every expression (#18738)

Parsing a 90 KB component drops from 460 ms to 35 ms; typical components
parse ~30% faster.

`read_pattern` blanked out the entire template before the pattern with
`replace(/[^\n]/g, ' ')` to keep positions aligned, and
`acorn.parseExpressionAt` re-counted lines from the top of the file on
every call. Acorn never looks before `pos`, so the real template can be
the prefix, and acorn 8.18's `startLocation` takes the line we already
have from the locator.

The blanking also had `loc.column` off by one for every node inside a
destructured pattern (noted in #18087), hence the snapshot updates.
pull/18751/head
Nic Polumeyv 2 days ago committed by GitHub
parent 214dd3d005
commit 504a7536c6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -175,7 +175,7 @@
"@jridgewell/sourcemap-codec": "^1.5.0",
"@sveltejs/acorn-typescript": "^1.0.10",
"@types/estree": "^1.0.5",
"acorn": "^8.12.1",
"acorn": "^8.18.0",
"aria-query": "5.3.1",
"axobject-query": "^4.1.0",
"clsx": "^2.1.1",

@ -5,6 +5,7 @@ import * as acorn from 'acorn';
import { walk } from 'zimmerframe';
import { tsPlugin } from '@sveltejs/acorn-typescript';
import * as e from '../../errors.js';
import { locator } from '../../state.js';
const JSParser = acorn.Parser;
const TSParser = JSParser.extend(tsPlugin());
@ -87,7 +88,8 @@ export function parse_expression_at(parser, source, index) {
sourceType: 'module',
ecmaVersion: 16,
locations: true,
preserveParens: true
preserveParens: true,
startLocation: start_location(parser, index)
});
add_comments(ast);
@ -112,7 +114,13 @@ export function parse_statement_at(parser, source, index) {
try {
// This is like parseExpressionAt but for statements
const p = new acorn(
{ onComment, sourceType: 'module', ecmaVersion: 16, locations: true },
{
onComment,
sourceType: 'module',
ecmaVersion: 16,
locations: true,
startLocation: start_location(parser, index)
},
source,
index
);
@ -128,6 +136,25 @@ export function parse_statement_at(parser, source, index) {
}
}
const regex_non_lf_line_break = /\r(?!\n)|[\u2028\u2029]/;
let last_template = '';
let lf_only = true;
/**
* Without `startLocation`, acorn counts the lines before `index` on every call.
* It also breaks lines on bare `\r`, `\u2028` and `\u2029`, which the locator doesn't, so those templates are left to acorn
* @param {Parser} parser
* @param {number} index
*/
function start_location(parser, index) {
if (parser.template !== last_template) {
last_template = parser.template;
lf_only = !regex_non_lf_line_break.test(last_template);
}
return lf_only ? locator(index) : undefined;
}
const regex_position_indicator = / \(\d+:\d+\)$/;
/**

@ -2,7 +2,6 @@
/** @import { Parser } from '../index.js' */
import { match_bracket } from '../utils/bracket.js';
import { parse_expression_at, remove_parens } from '../acorn.js';
import { regex_not_newline_characters } from '../../patterns.js';
import * as e from '../../../errors.js';
/**
@ -33,24 +32,10 @@ export default function read_pattern(parser) {
i = match_bracket(parser, start);
parser.index = i;
const pattern_string = parser.template.slice(start, i);
// the length of the `space_with_newline` has to be start - 1
// because we added a `(` in front of the pattern_string,
// which shifted the entire string to right by 1
// so we offset it by removing 1 character in the `space_with_newline`
// to achieve that, we remove the 1st space encountered,
// so it will not affect the `column` of the node
let space_with_newline = parser.template
.slice(0, start)
.replace(regex_not_newline_characters, ' ');
const first_space = space_with_newline.indexOf(' ');
space_with_newline =
space_with_newline.slice(0, first_space) + space_with_newline.slice(first_space + 1);
// acorn never reads before `start`, so the template itself can serve as the prefix
/** @type {any} */
let expression = remove_parens(
parse_expression_at(parser, `${space_with_newline}(${pattern_string} = 1)`, start - 1)
parse_expression_at(parser, parser.template.slice(0, i) + ' = 1', start)
);
expression = expression.left;
@ -80,7 +65,7 @@ function read_type_annotation(parser) {
const insert = '_ as ';
let a = parser.index - insert.length;
const template =
parser.template.slice(0, a).replace(/[^\n]/g, ' ') +
parser.template.slice(0, a) +
insert +
// If this is a type annotation for a function parameter, Acorn-TS will treat subsequent
// parameters as part of a sequence expression instead, and will then error on optional

@ -488,12 +488,13 @@ function open(parser) {
parser.eat(')', true);
}
const prelude = parser.template.slice(0, params_start).replace(/\S/g, ' ');
const params = parser.template.slice(params_start, parser.index);
let function_expression = matched
? /** @type {ArrowFunctionExpression} */ (
parse_expression_at(parser, prelude + `${params} => {}`, params_start)
parse_expression_at(
parser,
parser.template.slice(0, parser.index) + ' => {}',
params_start
)
)
: { params: [] };

@ -82,11 +82,11 @@
"loc": {
"start": {
"line": 5,
"column": 19
"column": 18
},
"end": {
"line": 5,
"column": 40
"column": 39
}
},
"elements": [
@ -97,11 +97,11 @@
"loc": {
"start": {
"line": 5,
"column": 20
"column": 19
},
"end": {
"line": 5,
"column": 23
"column": 22
}
},
"name": "key"
@ -113,11 +113,11 @@
"loc": {
"start": {
"line": 5,
"column": 25
"column": 24
},
"end": {
"line": 5,
"column": 30
"column": 29
}
},
"name": "value"
@ -129,11 +129,11 @@
"loc": {
"start": {
"line": 5,
"column": 32
"column": 31
},
"end": {
"line": 5,
"column": 39
"column": 38
}
},
"argument": {
@ -143,11 +143,11 @@
"loc": {
"start": {
"line": 5,
"column": 35
"column": 34
},
"end": {
"line": 5,
"column": 39
"column": 38
}
},
"name": "rest"

@ -170,11 +170,11 @@
"loc": {
"start": {
"line": 3,
"column": 13
"column": 12
},
"end": {
"line": 3,
"column": 24
"column": 23
}
},
"properties": [
@ -185,11 +185,11 @@
"loc": {
"start": {
"line": 3,
"column": 15
"column": 14
},
"end": {
"line": 3,
"column": 22
"column": 21
}
},
"method": false,
@ -202,11 +202,11 @@
"loc": {
"start": {
"line": 3,
"column": 15
"column": 14
},
"end": {
"line": 3,
"column": 16
"column": 15
}
},
"name": "y"
@ -218,11 +218,11 @@
"loc": {
"start": {
"line": 3,
"column": 15
"column": 14
},
"end": {
"line": 3,
"column": 22
"column": 21
}
},
"left": {
@ -232,11 +232,11 @@
"loc": {
"start": {
"line": 3,
"column": 15
"column": 14
},
"end": {
"line": 3,
"column": 16
"column": 15
}
},
"name": "y"
@ -248,11 +248,11 @@
"loc": {
"start": {
"line": 3,
"column": 19
"column": 18
},
"end": {
"line": 3,
"column": 22
"column": 21
}
},
"value": "{",
@ -302,11 +302,11 @@
"loc": {
"start": {
"line": 5,
"column": 13
"column": 12
},
"end": {
"line": 5,
"column": 24
"column": 23
}
},
"properties": [
@ -317,11 +317,11 @@
"loc": {
"start": {
"line": 5,
"column": 15
"column": 14
},
"end": {
"line": 5,
"column": 22
"column": 21
}
},
"method": false,
@ -334,11 +334,11 @@
"loc": {
"start": {
"line": 5,
"column": 15
"column": 14
},
"end": {
"line": 5,
"column": 16
"column": 15
}
},
"name": "y"
@ -350,11 +350,11 @@
"loc": {
"start": {
"line": 5,
"column": 15
"column": 14
},
"end": {
"line": 5,
"column": 22
"column": 21
}
},
"left": {
@ -364,11 +364,11 @@
"loc": {
"start": {
"line": 5,
"column": 15
"column": 14
},
"end": {
"line": 5,
"column": 16
"column": 15
}
},
"name": "y"
@ -380,11 +380,11 @@
"loc": {
"start": {
"line": 5,
"column": 19
"column": 18
},
"end": {
"line": 5,
"column": 22
"column": 21
}
},
"value": "]",
@ -434,11 +434,11 @@
"loc": {
"start": {
"line": 7,
"column": 13
"column": 12
},
"end": {
"line": 7,
"column": 29
"column": 28
}
},
"properties": [
@ -449,11 +449,11 @@
"loc": {
"start": {
"line": 7,
"column": 15
"column": 14
},
"end": {
"line": 7,
"column": 27
"column": 26
}
},
"method": false,
@ -466,11 +466,11 @@
"loc": {
"start": {
"line": 7,
"column": 15
"column": 14
},
"end": {
"line": 7,
"column": 16
"column": 15
}
},
"name": "y"
@ -482,11 +482,11 @@
"loc": {
"start": {
"line": 7,
"column": 15
"column": 14
},
"end": {
"line": 7,
"column": 27
"column": 26
}
},
"left": {
@ -496,11 +496,11 @@
"loc": {
"start": {
"line": 7,
"column": 15
"column": 14
},
"end": {
"line": 7,
"column": 16
"column": 15
}
},
"name": "y"
@ -512,11 +512,11 @@
"loc": {
"start": {
"line": 7,
"column": 19
"column": 18
},
"end": {
"line": 7,
"column": 27
"column": 26
}
},
"expressions": [
@ -527,11 +527,11 @@
"loc": {
"start": {
"line": 7,
"column": 22
"column": 21
},
"end": {
"line": 7,
"column": 25
"column": 24
}
},
"expressions": [],
@ -543,11 +543,11 @@
"loc": {
"start": {
"line": 7,
"column": 23
"column": 22
},
"end": {
"line": 7,
"column": 24
"column": 23
}
},
"value": {
@ -567,11 +567,11 @@
"loc": {
"start": {
"line": 7,
"column": 20
"column": 19
},
"end": {
"line": 7,
"column": 20
"column": 19
}
},
"value": {
@ -587,11 +587,11 @@
"loc": {
"start": {
"line": 7,
"column": 26
"column": 25
},
"end": {
"line": 7,
"column": 26
"column": 25
}
},
"value": {
@ -646,11 +646,11 @@
"loc": {
"start": {
"line": 9,
"column": 13
"column": 12
},
"end": {
"line": 9,
"column": 32
"column": 31
}
},
"properties": [
@ -661,11 +661,11 @@
"loc": {
"start": {
"line": 9,
"column": 15
"column": 14
},
"end": {
"line": 9,
"column": 30
"column": 29
}
},
"method": false,
@ -678,11 +678,11 @@
"loc": {
"start": {
"line": 9,
"column": 15
"column": 14
},
"end": {
"line": 9,
"column": 16
"column": 15
}
},
"name": "y"
@ -694,11 +694,11 @@
"loc": {
"start": {
"line": 9,
"column": 15
"column": 14
},
"end": {
"line": 9,
"column": 30
"column": 29
}
},
"left": {
@ -708,11 +708,11 @@
"loc": {
"start": {
"line": 9,
"column": 15
"column": 14
},
"end": {
"line": 9,
"column": 16
"column": 15
}
},
"name": "y"
@ -724,11 +724,11 @@
"loc": {
"start": {
"line": 9,
"column": 19
"column": 18
},
"end": {
"line": 9,
"column": 30
"column": 29
}
},
"expressions": [
@ -739,11 +739,11 @@
"loc": {
"start": {
"line": 9,
"column": 22
"column": 21
},
"end": {
"line": 9,
"column": 28
"column": 27
}
},
"expressions": [],
@ -755,11 +755,11 @@
"loc": {
"start": {
"line": 9,
"column": 23
"column": 22
},
"end": {
"line": 9,
"column": 27
"column": 26
}
},
"value": {
@ -779,11 +779,11 @@
"loc": {
"start": {
"line": 9,
"column": 20
"column": 19
},
"end": {
"line": 9,
"column": 20
"column": 19
}
},
"value": {
@ -799,11 +799,11 @@
"loc": {
"start": {
"line": 9,
"column": 29
"column": 28
},
"end": {
"line": 9,
"column": 29
"column": 28
}
},
"value": {
@ -822,5 +822,6 @@
}
]
},
"options": null
"options": null,
"comments": []
}

@ -495,11 +495,11 @@
"loc": {
"start": {
"line": 5,
"column": 18
"column": 17
},
"end": {
"line": 5,
"column": 57
"column": 56
}
},
"properties": [
@ -510,11 +510,11 @@
"loc": {
"start": {
"line": 5,
"column": 20
"column": 19
},
"end": {
"line": 5,
"column": 42
"column": 41
}
},
"method": false,
@ -527,11 +527,11 @@
"loc": {
"start": {
"line": 5,
"column": 20
"column": 19
},
"end": {
"line": 5,
"column": 24
"column": 23
}
},
"name": "name"
@ -543,11 +543,11 @@
"loc": {
"start": {
"line": 5,
"column": 20
"column": 19
},
"end": {
"line": 5,
"column": 42
"column": 41
}
},
"left": {
@ -557,11 +557,11 @@
"loc": {
"start": {
"line": 5,
"column": 20
"column": 19
},
"end": {
"line": 5,
"column": 24
"column": 23
}
},
"name": "name"
@ -573,11 +573,11 @@
"loc": {
"start": {
"line": 5,
"column": 27
"column": 26
},
"end": {
"line": 5,
"column": 42
"column": 41
}
},
"expressions": [
@ -588,11 +588,11 @@
"loc": {
"start": {
"line": 5,
"column": 35
"column": 34
},
"end": {
"line": 5,
"column": 40
"column": 39
}
},
"value": "Doe",
@ -607,11 +607,11 @@
"loc": {
"start": {
"line": 5,
"column": 28
"column": 27
},
"end": {
"line": 5,
"column": 33
"column": 32
}
},
"value": {
@ -627,11 +627,11 @@
"loc": {
"start": {
"line": 5,
"column": 41
"column": 40
},
"end": {
"line": 5,
"column": 41
"column": 40
}
},
"value": {
@ -652,11 +652,11 @@
"loc": {
"start": {
"line": 5,
"column": 44
"column": 43
},
"end": {
"line": 5,
"column": 55
"column": 54
}
},
"method": false,
@ -669,11 +669,11 @@
"loc": {
"start": {
"line": 5,
"column": 44
"column": 43
},
"end": {
"line": 5,
"column": 48
"column": 47
}
},
"name": "cool"
@ -685,11 +685,11 @@
"loc": {
"start": {
"line": 5,
"column": 44
"column": 43
},
"end": {
"line": 5,
"column": 55
"column": 54
}
},
"left": {
@ -699,11 +699,11 @@
"loc": {
"start": {
"line": 5,
"column": 44
"column": 43
},
"end": {
"line": 5,
"column": 48
"column": 47
}
},
"name": "cool"
@ -715,11 +715,11 @@
"loc": {
"start": {
"line": 5,
"column": 51
"column": 50
},
"end": {
"line": 5,
"column": 55
"column": 54
}
},
"value": true,
@ -906,11 +906,11 @@
"loc": {
"start": {
"line": 9,
"column": 18
"column": 17
},
"end": {
"line": 9,
"column": 79
"column": 78
}
},
"properties": [
@ -921,11 +921,11 @@
"loc": {
"start": {
"line": 9,
"column": 20
"column": 19
},
"end": {
"line": 9,
"column": 64
"column": 63
}
},
"method": false,
@ -938,11 +938,11 @@
"loc": {
"start": {
"line": 9,
"column": 20
"column": 19
},
"end": {
"line": 9,
"column": 24
"column": 23
}
},
"name": "name"
@ -954,11 +954,11 @@
"loc": {
"start": {
"line": 9,
"column": 20
"column": 19
},
"end": {
"line": 9,
"column": 64
"column": 63
}
},
"left": {
@ -968,11 +968,11 @@
"loc": {
"start": {
"line": 9,
"column": 20
"column": 19
},
"end": {
"line": 9,
"column": 24
"column": 23
}
},
"name": "name"
@ -984,11 +984,11 @@
"loc": {
"start": {
"line": 9,
"column": 27
"column": 26
},
"end": {
"line": 9,
"column": 64
"column": 63
}
},
"callee": {
@ -998,11 +998,11 @@
"loc": {
"start": {
"line": 9,
"column": 28
"column": 27
},
"end": {
"line": 9,
"column": 61
"column": 60
}
},
"id": null,
@ -1017,11 +1017,11 @@
"loc": {
"start": {
"line": 9,
"column": 34
"column": 33
},
"end": {
"line": 9,
"column": 61
"column": 60
}
},
"body": [
@ -1032,11 +1032,11 @@
"loc": {
"start": {
"line": 9,
"column": 36
"column": 35
},
"end": {
"line": 9,
"column": 59
"column": 58
}
},
"argument": {
@ -1046,11 +1046,11 @@
"loc": {
"start": {
"line": 9,
"column": 43
"column": 42
},
"end": {
"line": 9,
"column": 58
"column": 57
}
},
"expressions": [
@ -1061,11 +1061,11 @@
"loc": {
"start": {
"line": 9,
"column": 51
"column": 50
},
"end": {
"line": 9,
"column": 56
"column": 55
}
},
"value": "Doe",
@ -1080,11 +1080,11 @@
"loc": {
"start": {
"line": 9,
"column": 44
"column": 43
},
"end": {
"line": 9,
"column": 49
"column": 48
}
},
"value": {
@ -1100,11 +1100,11 @@
"loc": {
"start": {
"line": 9,
"column": 57
"column": 56
},
"end": {
"line": 9,
"column": 57
"column": 56
}
},
"value": {
@ -1132,11 +1132,11 @@
"loc": {
"start": {
"line": 9,
"column": 66
"column": 65
},
"end": {
"line": 9,
"column": 77
"column": 76
}
},
"method": false,
@ -1149,11 +1149,11 @@
"loc": {
"start": {
"line": 9,
"column": 66
"column": 65
},
"end": {
"line": 9,
"column": 70
"column": 69
}
},
"name": "cool"
@ -1165,11 +1165,11 @@
"loc": {
"start": {
"line": 9,
"column": 66
"column": 65
},
"end": {
"line": 9,
"column": 77
"column": 76
}
},
"left": {
@ -1179,11 +1179,11 @@
"loc": {
"start": {
"line": 9,
"column": 66
"column": 65
},
"end": {
"line": 9,
"column": 70
"column": 69
}
},
"name": "cool"
@ -1195,11 +1195,11 @@
"loc": {
"start": {
"line": 9,
"column": 73
"column": 72
},
"end": {
"line": 9,
"column": 77
"column": 76
}
},
"value": true,
@ -1213,5 +1213,6 @@
}
]
},
"options": null
"options": null,
"comments": []
}

@ -133,11 +133,11 @@
"loc": {
"start": {
"line": 5,
"column": 15
"column": 14
},
"end": {
"line": 5,
"column": 39
"column": 38
}
},
"elements": [
@ -148,11 +148,11 @@
"loc": {
"start": {
"line": 5,
"column": 16
"column": 15
},
"end": {
"line": 5,
"column": 19
"column": 18
}
},
"name": "key"
@ -164,11 +164,11 @@
"loc": {
"start": {
"line": 5,
"column": 21
"column": 20
},
"end": {
"line": 5,
"column": 38
"column": 37
}
},
"left": {
@ -178,11 +178,11 @@
"loc": {
"start": {
"line": 5,
"column": 21
"column": 20
},
"end": {
"line": 5,
"column": 26
"column": 25
}
},
"name": "value"
@ -194,11 +194,11 @@
"loc": {
"start": {
"line": 5,
"column": 29
"column": 28
},
"end": {
"line": 5,
"column": 38
"column": 37
}
},
"value": "default",
@ -211,6 +211,7 @@
]
},
"options": null,
"comments": [],
"instance": {
"type": "Script",
"start": 0,

@ -76,13 +76,13 @@ importers:
version: 1.5.0
'@sveltejs/acorn-typescript':
specifier: ^1.0.10
version: 1.0.10(acorn@8.16.0)
version: 1.0.10(acorn@8.18.0)
'@types/estree':
specifier: ^1.0.5
version: 1.0.8
acorn:
specifier: ^8.12.1
version: 8.16.0
specifier: ^8.18.0
version: 8.18.0
aria-query:
specifier: 5.3.1
version: 5.3.1
@ -1241,13 +1241,8 @@ packages:
peerDependencies:
acorn: ^6.0.0 || ^7.0.0 || ^8.0.0
acorn@8.16.0:
resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==}
engines: {node: '>=0.4.0'}
hasBin: true
acorn@8.17.0:
resolution: {integrity: sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==}
acorn@8.18.0:
resolution: {integrity: sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==}
engines: {node: '>=0.4.0'}
hasBin: true
@ -3302,15 +3297,15 @@ snapshots:
'@stylistic/eslint-plugin-js@1.8.0(eslint@10.0.0)':
dependencies:
'@types/eslint': 8.56.12
acorn: 8.17.0
acorn: 8.18.0
escape-string-regexp: 4.0.0
eslint: 10.0.0
eslint-visitor-keys: 3.4.3
espree: 9.6.1
'@sveltejs/acorn-typescript@1.0.10(acorn@8.16.0)':
'@sveltejs/acorn-typescript@1.0.10(acorn@8.18.0)':
dependencies:
acorn: 8.16.0
acorn: 8.18.0
'@sveltejs/eslint-config@9.0.0(@eslint/js@10.0.1(eslint@10.0.0))(@stylistic/eslint-plugin-js@1.8.0(eslint@10.0.0))(eslint-config-prettier@9.1.0(eslint@10.0.0))(eslint-plugin-n@17.24.0(eslint@10.0.0)(typescript@5.5.4))(eslint-plugin-svelte@3.15.0(eslint@10.0.0)(svelte@packages+svelte))(eslint@10.0.0)(typescript-eslint@8.56.0(eslint@10.0.0)(typescript@5.5.4))(typescript@5.5.4)':
dependencies:
@ -3532,17 +3527,11 @@ snapshots:
convert-source-map: 2.0.0
tinyrainbow: 3.1.0
acorn-jsx@5.3.2(acorn@8.16.0):
acorn-jsx@5.3.2(acorn@8.18.0):
dependencies:
acorn: 8.16.0
acorn-jsx@5.3.2(acorn@8.17.0):
dependencies:
acorn: 8.17.0
acorn@8.16.0: {}
acorn: 8.18.0
acorn@8.17.0: {}
acorn@8.18.0: {}
agent-base@7.1.1:
dependencies:
@ -3898,20 +3887,20 @@ snapshots:
espree@10.1.0:
dependencies:
acorn: 8.16.0
acorn-jsx: 5.3.2(acorn@8.16.0)
acorn: 8.18.0
acorn-jsx: 5.3.2(acorn@8.18.0)
eslint-visitor-keys: 4.2.1
espree@11.1.0:
dependencies:
acorn: 8.16.0
acorn-jsx: 5.3.2(acorn@8.16.0)
acorn: 8.18.0
acorn-jsx: 5.3.2(acorn@8.18.0)
eslint-visitor-keys: 5.0.0
espree@9.6.1:
dependencies:
acorn: 8.17.0
acorn-jsx: 5.3.2(acorn@8.17.0)
acorn: 8.18.0
acorn-jsx: 5.3.2(acorn@8.18.0)
eslint-visitor-keys: 3.4.3
esprima@4.0.1: {}
@ -4664,7 +4653,7 @@ snapshots:
terser@5.27.0:
dependencies:
'@jridgewell/source-map': 0.3.6
acorn: 8.16.0
acorn: 8.18.0
commander: 2.20.3
source-map-support: 0.5.21

Loading…
Cancel
Save