lighthouse 13.5.0-dev.20261006 → 13.5.0-dev.20261007

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,6 +2,7 @@ export type Pattern = {
2
2
  name: string;
3
3
  expression: string;
4
4
  estimateBytes?: (content: string) => number;
5
+ resolveName?: (match: string) => string;
5
6
  };
6
7
  export type PatternMatchResult = {
7
8
  name: string;
@@ -11,7 +11,7 @@
11
11
  * ./core/scripts/legacy-javascript - verification tool.
12
12
  */
13
13
 
14
- /** @typedef {{name: string, expression: string, estimateBytes?: (content: string) => number}} Pattern */
14
+ /** @typedef {{name: string, expression: string, estimateBytes?: (content: string) => number, resolveName?: (match: string) => string}} Pattern */
15
15
  /** @typedef {{name: string, line: number, column: number}} PatternMatchResult */
16
16
  /** @typedef {{matches: PatternMatchResult[], estimatedByteSavings: number}} Result */
17
17
 
@@ -27,7 +27,7 @@ const graph = graph_;
27
27
  /**
28
28
  * Takes a list of patterns (consisting of a name identifier and a RegExp expression string)
29
29
  * and via `match` returns match results with line / column information for a given code input.
30
- * Only returns the first match per pattern given.
30
+ * Only returns the first match per pattern name.
31
31
  */
32
32
  class CodePatternMatcher {
33
33
  /**
@@ -45,12 +45,13 @@ class CodePatternMatcher {
45
45
  if (!this.re) {
46
46
  const patternsExpression =
47
47
  this.patterns.map(pattern => `(${pattern.expression})`).join('|');
48
- this.re = new RegExp(`(^\r\n|\r|\n)|${patternsExpression}`, 'g');
48
+ this.re = new RegExp(patternsExpression, 'g');
49
49
  }
50
50
 
51
51
  // Reset RegExp state.
52
52
  this.re.lastIndex = 0;
53
53
 
54
+ /** @type {Set<string>} */
54
55
  const seen = new Set();
55
56
  /** @type {PatternMatchResult[]} */
56
57
  const matches = [];
@@ -58,29 +59,41 @@ class CodePatternMatcher {
58
59
  let result;
59
60
  let line = 0;
60
61
  let lineBeginsAtIndex = 0;
62
+ let scannedToIndex = 0;
61
63
  // Each pattern maps to one subgroup in the generated regex. For each iteration of RegExp.exec,
62
64
  // only one subgroup will be defined. Exec until no more matches.
63
65
  while ((result = this.re.exec(code)) !== null) {
64
- // Discard first value in `result` - it's just the entire match.
65
- const captureGroups = result.slice(1);
66
- // isNewline - truthy if matching a newline, used to track the line number.
67
- // `patternExpressionMatches` maps to each possible pattern in `this.patterns`.
68
- // Only one of [isNewline, ...patternExpressionMatches] is ever truthy.
69
- const [isNewline, ...patternExpressionMatches] = captureGroups;
70
- if (isNewline) {
71
- line++;
72
- lineBeginsAtIndex = result.index + 1;
73
- continue;
66
+ let patternIndex = -1;
67
+ for (let i = 1; i < result.length; i++) {
68
+ if (result[i] !== undefined) {
69
+ patternIndex = i - 1;
70
+ break;
71
+ }
74
72
  }
75
- const pattern = this.patterns[patternExpressionMatches.findIndex(Boolean)];
73
+ if (patternIndex === -1) continue;
76
74
 
77
- if (seen.has(pattern)) {
75
+ const pattern = this.patterns[patternIndex];
76
+ const name = pattern.resolveName ? pattern.resolveName(result[0]) : pattern.name;
77
+ if (seen.has(name)) {
78
78
  continue;
79
79
  }
80
- seen.add(pattern);
80
+ seen.add(name);
81
+
82
+ // Lazily advance line and column tracking up to `result.index`.
83
+ // Because `[scannedToIndex, result.index)` includes the text of any previous matches,
84
+ // this also accounts for newlines inside multi-line matches.
85
+ // Scanning for `\n` handles both LF (`\n`) and CRLF (`\r\n`) line endings since
86
+ // the next line always begins at `nlIdx + 1`.
87
+ let nlIdx = code.indexOf('\n', scannedToIndex);
88
+ while (nlIdx !== -1 && nlIdx < result.index) {
89
+ line++;
90
+ lineBeginsAtIndex = nlIdx + 1;
91
+ nlIdx = code.indexOf('\n', lineBeginsAtIndex);
92
+ }
93
+ scannedToIndex = result.index;
81
94
 
82
95
  matches.push({
83
- name: pattern.name,
96
+ name,
84
97
  line,
85
98
  column: result.index - lineBeginsAtIndex,
86
99
  });
@@ -90,70 +103,6 @@ class CodePatternMatcher {
90
103
  }
91
104
  }
92
105
 
93
- /**
94
- * @param {string?} object
95
- * @param {string} property
96
- * @param {string} coreJs3Module
97
- */
98
- function buildPolyfillExpression(object, property, coreJs3Module) {
99
- const qt = (/** @type {string} */ token) =>
100
- `['"]${token}['"]`; // don't worry about matching string delims
101
-
102
- let expression = '';
103
-
104
- if (object) {
105
- // String.prototype.startsWith =
106
- expression += `${object}\\.${property}\\s?=[^=]`;
107
- } else {
108
- // Promise =
109
- // window.Promise =// Promise =Z
110
- // but not: SomePromise =
111
- expression += `(?:window\\.|[\\s;]+)${property}\\s?=[^=]`;
112
- }
113
-
114
- // String.prototype['startsWith'] =
115
- if (object) {
116
- expression += `|${object}\\[${qt(property)}\\]\\s?=[^=]`;
117
- }
118
-
119
- // Object.defineProperty(String.prototype, 'startsWith'
120
- expression += `|defineProperty\\(${object || 'window'},\\s?${qt(property)}`;
121
-
122
- // es-shims
123
- // no(Object,{entries:r},{entries:function
124
- if (object) {
125
- expression += `|\\(${object},\\s*{${property}:.*},\\s*{${property}`;
126
- }
127
-
128
- // core-js
129
- if (object) {
130
- const objectWithoutPrototype = object.replace('.prototype', '');
131
- // e(e.S,"Object",{values
132
- // Minified + mangled pattern found in CDN babel-polyfill.
133
- // see https://cdnjs.cloudflare.com/ajax/libs/babel-polyfill/7.2.5/polyfill.min.js
134
- // TODO: perhaps this is the wrong place to check for a CDN polyfill. Remove?
135
- // expression += `|;e\\([^,]+,${qt(objectWithoutPrototype)},{${property}:`;
136
-
137
- // core-js@3 minified pattern.
138
- // {target:"Array",proto:true},{fill:fill
139
- // {target:"Array",proto:true,forced:!HAS_SPECIES_SUPPORT||!USES_TO_LENGTH},{filter:
140
- expression += `|{target:${qt(objectWithoutPrototype)}[^;]*},{${property}:`;
141
- } else {
142
- // Detect polyfills for new classes: Map, Set, WeakSet, etc.
143
- // TODO: so far, no class polyfills are enabled for detection.
144
- // See `modulesToSkip` in create-polyfill-module-data.js
145
-
146
- // collection("Map",
147
- // expression += `|collection\\(${qt(property)},`;
148
- }
149
-
150
- // Un-minified code may have module names.
151
- // core-js/modules/es.object.is-frozen
152
- expression += `|${coreJs3Module.replaceAll('.', '\\.')}(?:\\.js)?"`;
153
-
154
- return expression;
155
- }
156
-
157
106
  function getCoreJsPolyfillData() {
158
107
  return polyfillModuleData.filter(d => d.corejs).map(d => {
159
108
  return {
@@ -164,34 +113,171 @@ function getCoreJsPolyfillData() {
164
113
  }
165
114
 
166
115
  /**
116
+ * Builds factored polyfill patterns grouped by receiver object, core-js target, and
117
+ * core-js module prefix so the regex engine evaluates a few shared branches instead
118
+ * of repeating common prefixes (like `defineProperty(`, `{target:`, `Array.prototype.`)
119
+ * once per polyfill.
120
+ *
167
121
  * @return {Pattern[]}
168
122
  */
169
123
  function getPolyfillPatterns() {
170
- /** @type {Pattern[]} */
171
- const patterns = [];
124
+ /** @param {string} str */
125
+ const escapeRegExp = str => str.replaceAll('.', '\\.');
126
+
127
+ /** @type {Map<string, string[]>} */
128
+ const byObject = new Map();
129
+ /** @type {Map<string, string[]>} */
130
+ const byTarget = new Map();
131
+ /** @type {Map<string, string>} */
132
+ const targetPropToName = new Map();
133
+ /** @type {Map<string, string>} */
134
+ const coreJsModuleToName = new Map();
135
+ /** @type {Map<string, string[]>} */
136
+ const byModulePrefix = new Map();
172
137
 
173
138
  for (const {name, coreJs3Module} of getCoreJsPolyfillData()) {
174
139
  const parts = name.split('.');
175
- const object = parts.length > 1 ? parts.slice(0, parts.length - 1).join('.') : null;
140
+ const object = parts.slice(0, parts.length - 1).join('.');
176
141
  const property = parts[parts.length - 1];
177
- patterns.push({
178
- name,
179
- expression: buildPolyfillExpression(object, property, coreJs3Module),
180
- });
142
+
143
+ let objProps = byObject.get(object);
144
+ if (!objProps) {
145
+ objProps = [];
146
+ byObject.set(object, objProps);
147
+ }
148
+ objProps.push(property);
149
+
150
+ const target = object.replace('.prototype', '');
151
+ let targetProps = byTarget.get(target);
152
+ if (!targetProps) {
153
+ targetProps = [];
154
+ byTarget.set(target, targetProps);
155
+ }
156
+ targetProps.push(property);
157
+ targetPropToName.set(`${target}.${property}`, name);
158
+
159
+ coreJsModuleToName.set(coreJs3Module, name);
160
+ const modParts = coreJs3Module.split('.');
161
+ const modPrefix = modParts.slice(0, 2).join('.');
162
+ const modRest = modParts.slice(2).join('.');
163
+ let modRests = byModulePrefix.get(modPrefix);
164
+ if (!modRests) {
165
+ modRests = [];
166
+ byModulePrefix.set(modPrefix, modRests);
167
+ }
168
+ modRests.push(modRest);
169
+ }
170
+
171
+ // 1. Direct property assignment: `String.prototype.startsWith =`
172
+ const directBranches = [];
173
+ for (const [object, props] of byObject) {
174
+ directBranches.push(`${escapeRegExp(object)}\\.(?:${props.join('|')})\\s?=[^=]`);
175
+ }
176
+
177
+ // 2. Bracket property assignment: `String.prototype['startsWith'] =`
178
+ const bracketBranches = [];
179
+ for (const [object, props] of byObject) {
180
+ bracketBranches.push(`${escapeRegExp(object)}\\[['"](?:${props.join('|')})['"]\\]\\s?=[^=]`);
181
+ }
182
+
183
+ // 3. Object.defineProperty: `Object.defineProperty(String.prototype, 'startsWith'`
184
+ const definePropBranches = [];
185
+ for (const [object, props] of byObject) {
186
+ definePropBranches.push(`${escapeRegExp(object)},\\s?['"](?:${props.join('|')})['"]`);
187
+ }
188
+
189
+ // 4. es-shims: `no(Object,{entries:r},{entries:function`
190
+ const esShimsBranches = [];
191
+ for (const [object, props] of byObject) {
192
+ const propAlt = `(?:${props.join('|')})`;
193
+ esShimsBranches.push(`${escapeRegExp(object)},\\s*{${propAlt}:.*},\\s*{${propAlt}`);
194
+ }
195
+
196
+ // 5. core-js@3 minified pattern:
197
+ // `{target:"Array",proto:true},{fill:fill`
198
+ // `{target:"Array",proto:true,forced:!HAS_SPECIES_SUPPORT||!USES_TO_LENGTH},{filter:`
199
+ const targetBranches = [];
200
+ for (const [target, props] of byTarget) {
201
+ targetBranches.push(`['"]${escapeRegExp(target)}['"][^;]*?},{(?:${props.join('|')}):`);
202
+ }
203
+
204
+ // 6. Un-minified core-js module names: `core-js/modules/es.object.is-frozen`
205
+ const moduleBranches = [];
206
+ for (const [prefix, rests] of byModulePrefix) {
207
+ moduleBranches.push(
208
+ `${escapeRegExp(prefix)}\\.(?:${rests.map(escapeRegExp).join('|')})`
209
+ );
181
210
  }
182
211
 
183
- return patterns;
212
+ return [
213
+ {
214
+ name: 'direct-assignment',
215
+ expression: directBranches.join('|'),
216
+ resolveName: match => match.slice(0, match.indexOf('=')).trimEnd(),
217
+ },
218
+ {
219
+ name: 'bracket-assignment',
220
+ expression: bracketBranches.join('|'),
221
+ resolveName: match => {
222
+ const bracketIdx = match.indexOf('[');
223
+ const closeIdx = match.indexOf(']', bracketIdx);
224
+ return `${match.slice(0, bracketIdx)}.${match.slice(bracketIdx + 2, closeIdx - 1)}`;
225
+ },
226
+ },
227
+ {
228
+ name: 'defineProperty',
229
+ expression: `defineProperty\\((?:${definePropBranches.join('|')})`,
230
+ resolveName: match => {
231
+ const openIdx = match.indexOf('(');
232
+ const commaIdx = match.indexOf(',', openIdx);
233
+ const obj = match.slice(openIdx + 1, commaIdx);
234
+ const prop = match.slice(commaIdx + 1).trimStart().slice(1, -1);
235
+ return `${obj}.${prop}`;
236
+ },
237
+ },
238
+ {
239
+ name: 'es-shims',
240
+ expression: `\\((?:${esShimsBranches.join('|')})`,
241
+ resolveName: match => {
242
+ const commaIdx = match.indexOf(',');
243
+ const obj = match.slice(1, commaIdx);
244
+ const braceIdx = match.indexOf('{', commaIdx);
245
+ const colonIdx = match.indexOf(':', braceIdx);
246
+ const prop = match.slice(braceIdx + 1, colonIdx);
247
+ return `${obj}.${prop}`;
248
+ },
249
+ },
250
+ {
251
+ name: 'core-js-target',
252
+ expression: `{target:(?:${targetBranches.join('|')})`,
253
+ resolveName: match => {
254
+ const targetEnd = match.indexOf(match[8], 9);
255
+ const target = match.slice(9, targetEnd);
256
+ const lastBrace = match.lastIndexOf('{');
257
+ const prop = match.slice(lastBrace + 1, -1);
258
+ return /** @type {string} */ (targetPropToName.get(`${target}.${prop}`));
259
+ },
260
+ },
261
+ {
262
+ name: 'core-js-module',
263
+ expression: `(?:${moduleBranches.join('|')})(?:\\.js)?"`,
264
+ resolveName: match => {
265
+ const mod = match.endsWith('.js"') ? match.slice(0, -4) : match.slice(0, -1);
266
+ return /** @type {string} */ (coreJsModuleToName.get(mod));
267
+ },
268
+ },
269
+ ];
184
270
  }
185
271
 
186
272
  /**
187
273
  * @return {Pattern[]}
188
274
  */
189
275
  function getTransformPatterns() {
190
- /**
191
- * @param {string} content
192
- * @param {RegExp|string} pattern
193
- * @return {number}
194
- */
276
+ /**
277
+ * @param {string} content
278
+ * @param {RegExp|string} pattern
279
+ * @return {number}
280
+ */
195
281
  const count = (content, pattern) => {
196
282
  // Split is slightly faster than match.
197
283
  if (typeof pattern === 'string') {
@@ -259,6 +345,17 @@ function getTransformPatterns() {
259
345
  ];
260
346
  }
261
347
 
348
+ const transformPatterns = getTransformPatterns();
349
+ const transformPatternsByName = new Map(transformPatterns.map(p => [p.name, p]));
350
+
351
+ /** @type {Map<string, string>} */
352
+ const moduleToPolyfillName = new Map();
353
+ for (const {name, modules} of polyfillModuleData) {
354
+ for (const mod of modules) {
355
+ moduleToPolyfillName.set(mod, name);
356
+ }
357
+ }
358
+
262
359
  /**
263
360
  * @param {string} content
264
361
  * @param {PatternMatchResult[]} matches
@@ -287,7 +384,7 @@ function estimateWastedBytes(content, matches) {
287
384
  let estimatedWastedBytesFromTransforms = 0;
288
385
 
289
386
  for (const result of transformResults) {
290
- const pattern = getTransformPatterns().find(p => p.name === result.name);
387
+ const pattern = transformPatternsByName.get(result.name);
291
388
  if (!pattern || !pattern.estimateBytes || !content) continue;
292
389
  estimatedWastedBytesFromTransforms += pattern.estimateBytes(content);
293
390
  }
@@ -299,7 +396,7 @@ function estimateWastedBytes(content, matches) {
299
396
 
300
397
  const matcher = new CodePatternMatcher([
301
398
  ...getPolyfillPatterns(),
302
- ...getTransformPatterns(),
399
+ ...transformPatterns,
303
400
  ]);
304
401
 
305
402
  /**
@@ -315,20 +412,47 @@ function detectLegacyJavaScript(content, map) {
315
412
 
316
413
  // If it's a bundle with source maps, add in the polyfill modules by name too.
317
414
  if (map) {
318
- for (const {name, modules} of polyfillModuleData) {
319
- // Skip if the pattern matching found a match for this polyfill.
320
- if (matches.some(m => m.name === name)) continue;
321
-
322
- const source = map.sourceURLs().find(source => modules.some(module => {
323
- return source.endsWith(`/${module}.js`) || source.includes(`node_modules/${module}/`);
324
- }));
325
- if (!source) continue;
326
-
327
- const mapping = map.mappings().find(m => m.sourceURL === source);
328
- if (mapping) {
329
- matches.push({name, line: mapping.lineNumber, column: mapping.columnNumber});
330
- } else {
331
- matches.push({name, line: 0, column: 0});
415
+ const matchedNames = new Set(matches.map(m => m.name));
416
+ /** @type {Array<{name: string, source: string}>} */
417
+ const foundInMap = [];
418
+
419
+ for (const source of map.sourceURLs()) {
420
+ if (source.endsWith('.js')) {
421
+ const slashIdx = source.lastIndexOf('/');
422
+ if (slashIdx !== -1) {
423
+ const polyfillName = moduleToPolyfillName.get(source.slice(slashIdx + 1, -3));
424
+ if (polyfillName && !matchedNames.has(polyfillName)) {
425
+ matchedNames.add(polyfillName);
426
+ foundInMap.push({name: polyfillName, source});
427
+ }
428
+ }
429
+ }
430
+ if (source.includes('node_modules/')) {
431
+ let nmIdx = source.indexOf('node_modules/');
432
+ while (nmIdx !== -1) {
433
+ const modStart = nmIdx + 13; // 'node_modules/'.length
434
+ const nextSlash = source.indexOf('/', modStart);
435
+ if (nextSlash !== -1) {
436
+ const polyfillName = moduleToPolyfillName.get(source.slice(modStart, nextSlash));
437
+ if (polyfillName && !matchedNames.has(polyfillName)) {
438
+ matchedNames.add(polyfillName);
439
+ foundInMap.push({name: polyfillName, source});
440
+ }
441
+ }
442
+ nmIdx = source.indexOf('node_modules/', modStart);
443
+ }
444
+ }
445
+ }
446
+
447
+ if (foundInMap.length > 0) {
448
+ const mappings = map.mappings();
449
+ for (const {name, source} of foundInMap) {
450
+ const mapping = mappings.find(m => m.sourceURL === source);
451
+ if (mapping) {
452
+ matches.push({name, line: mapping.lineNumber, column: mapping.columnNumber});
453
+ } else {
454
+ matches.push({name, line: 0, column: 0});
455
+ }
332
456
  }
333
457
  }
334
458
  }
@@ -1,93 +1,98 @@
1
1
  {
2
- "moduleSizes": [26070, 498, 282, 294, 281, 467, 161, 236, 229, 765, 546, 339, 1608, 723, 729, 1545, 438, 214, 657, 111, 759, 537, 209, 281, 685, 217, 757, 631, 293, 182, 475, 79, 407, 140, 366, 792, 269, 222, 158, 280, 188, 137, 158, 105, 189, 543, 160, 742, 1436, 88, 904, 146, 314, 375, 183, 1083, 195, 503, 269, 208, 334, 350, 460, 568, 229, 1155, 334, 266, 30, 120, 309, 370, 358, 1952, 1638, 304, 153, 274, 1288, 192, 543, 74, 144, 137, 33, 336, 457, 2122, 535, 711, 1323, 117, 1961, 244, 557, 318, 119, 124, 108, 144, 96, 133, 441, 210, 1627, 1956, 693, 1426, 863, 637, 301, 51, 708, 583, 119, 600, 221, 370, 728, 1085, 552, 629, 125, 1746, 97, 441, 543, 2756, 371, 447, 548, 243, 266, 217, 99, 440, 183, 546, 137, 464, 207, 983, 503, 237, 382, 249, 675, 402, 254, 223, 164, 214, 191, 831, 218, 202, 232, 124, 249, 160, 251, 217, 717, 78, 561, 1627, 256, 386, 225, 432, 499, 394, 364, 445, 634, 667, 177, 346, 470, 663, 142, 588, 414, 617, 1559, 380, 2520, 1040, 417, 289, 238, 220, 214, 303, 163, 141, 510, 397, 137, 137, 133, 133, 390, 266, 137, 183, 215, 191, 485, 328, 575, 799, 533, 148, 215, 589, 589, 130, 362, 562, 471, 179, 186, 1266, 1456, 521, 1536, 427, 444, 406, 912, 150, 283, 144, 485, 470, 205, 1268, 796, 658, 306, 3751, 814, 146, 2328, 1226, 922, 237, 206, 198, 250, 283, 60, 3000],
2
+ "moduleSizes": [26175, 498, 282, 294, 281, 467, 164, 236, 229, 765, 555, 339, 300, 1977, 723, 729, 1547, 438, 214, 657, 111, 759, 537, 209, 281, 685, 217, 757, 631, 293, 182, 475, 79, 407, 140, 366, 792, 269, 222, 158, 280, 192, 137, 158, 105, 189, 543, 160, 742, 1436, 88, 978, 146, 314, 375, 183, 1083, 195, 503, 269, 208, 334, 233, 350, 580, 491, 229, 1155, 334, 266, 30, 120, 309, 370, 358, 1952, 1638, 304, 153, 274, 1288, 192, 543, 74, 144, 137, 33, 336, 457, 2219, 535, 711, 1323, 64, 117, 1961, 244, 557, 318, 119, 173, 108, 144, 96, 133, 441, 210, 1627, 1956, 693, 1426, 863, 637, 301, 51, 708, 583, 119, 600, 221, 370, 728, 1085, 552, 629, 125, 1746, 97, 441, 543, 2931, 767, 371, 588, 548, 243, 266, 217, 99, 440, 183, 607, 180, 464, 207, 983, 530, 237, 382, 249, 675, 402, 254, 223, 164, 214, 191, 831, 218, 202, 232, 124, 250, 160, 251, 217, 717, 78, 561, 1696, 256, 386, 225, 432, 499, 394, 364, 445, 625, 680, 177, 346, 663, 142, 588, 414, 686, 1628, 380, 2580, 558, 783, 1410, 1040, 417, 433, 266, 263, 214, 303, 163, 141, 510, 397, 137, 137, 133, 133, 390, 266, 137, 183, 215, 191, 485, 328, 575, 799, 533, 148, 215, 589, 589, 130, 362, 562, 471, 179, 186, 1266, 1456, 366, 521, 1536, 511, 444, 406, 1053, 150, 283, 144, 485, 470, 205, 1268, 796, 658, 595, 306, 3750, 814, 146, 2298, 1226, 881, 1144, 237, 206, 198, 250, 283, 60, 3000],
3
3
  "dependencies": {
4
- "Array.prototype.at": [0, 5, 69, 105, 106, 116, 164],
5
- "Array.prototype.concat": [0, 16, 21, 22, 26, 34, 40, 76, 78, 155, 165],
6
- "Array.prototype.copyWithin": [0, 5, 9, 37, 69, 105, 106, 116, 166],
7
- "Array.prototype.every": [0, 15, 17, 21, 22, 26, 53, 59, 76, 78, 155, 167],
8
- "Array.prototype.fill": [0, 5, 10, 69, 105, 106, 116, 168],
9
- "Array.prototype.filter": [0, 15, 16, 21, 22, 26, 53, 59, 76, 78, 155, 169],
10
- "Array.prototype.find": [0, 5, 15, 21, 22, 26, 53, 59, 69, 76, 78, 105, 106, 116, 155, 173],
11
- "Array.prototype.findIndex": [0, 5, 15, 21, 22, 26, 53, 59, 69, 76, 78, 105, 106, 116, 155, 170],
12
- "Array.prototype.findLast": [0, 5, 14, 53, 59, 69, 105, 106, 116, 172],
13
- "Array.prototype.findLastIndex": [0, 5, 14, 53, 59, 69, 105, 106, 116, 171],
14
- "Array.prototype.flat": [0, 21, 22, 26, 40, 50, 53, 59, 76, 78, 155, 175],
15
- "Array.prototype.flatMap": [0, 21, 22, 26, 40, 50, 53, 59, 76, 78, 155, 174],
16
- "Array.prototype.forEach": [0, 11, 15, 17, 21, 22, 26, 53, 59, 76, 78, 155, 176],
17
- "Array.from": [0, 12, 23, 24, 26, 34, 53, 59, 62, 63, 75, 78, 88, 155, 177],
18
- "Array.prototype.includes": [0, 5, 69, 105, 106, 116, 178],
19
- "Array.prototype.indexOf": [0, 17, 59, 179],
20
- "Array.isArray": [0, 76, 180],
21
- "Array.prototype.join": [0, 17, 181],
22
- "Array.prototype.map": [0, 15, 16, 21, 22, 26, 53, 59, 76, 78, 155, 182],
23
- "Array.of": [0, 26, 34, 78, 155, 183],
24
- "Array.prototype.slice": [0, 16, 19, 26, 34, 76, 78, 155, 184],
25
- "Array.prototype.some": [0, 15, 17, 21, 22, 26, 53, 59, 76, 78, 155, 185],
26
- "Array.prototype.sort": [0, 17, 19, 20, 26, 37, 42, 43, 46, 155, 156, 186],
27
- "Array.prototype.unshift": [0, 18, 37, 40, 76, 187],
28
- "Math.acosh": [0, 97, 188],
29
- "Math.asinh": [0, 189],
30
- "Math.atanh": [0, 190],
31
- "Math.cbrt": [0, 100, 191],
32
- "Math.clz32": [0, 192],
33
- "Math.cosh": [0, 93, 193],
34
- "Math.expm1": [0, 93, 194],
35
- "Math.fround": [0, 94, 95, 99, 100, 195],
36
- "Math.hypot": [0, 196],
37
- "Math.imul": [0, 197],
38
- "Math.log10": [0, 96, 198],
39
- "Math.log1p": [0, 97, 199],
40
- "Math.log2": [0, 98, 200],
41
- "Math.sign": [0, 100, 201],
42
- "Math.sinh": [0, 93, 202],
43
- "Math.tanh": [0, 93, 203],
44
- "Math.trunc": [0, 204],
45
- "Object.assign": [0, 104, 116, 205],
46
- "Object.create": [0, 69, 105, 106, 116, 206],
47
- "Object.entries": [0, 29, 112, 116, 119, 207],
48
- "Object.freeze": [0, 8, 19, 51, 73, 109, 113, 208],
49
- "Object.fromEntries": [0, 26, 34, 53, 59, 62, 63, 75, 87, 88, 155, 209],
50
- "Object.getOwnPropertyDescriptor": [0, 210],
51
- "Object.getOwnPropertyDescriptors": [0, 34, 211],
52
- "Object.getPrototypeOf": [0, 29, 112, 212],
53
- "Object.hasOwn": [0, 213],
54
- "Object.is": [0, 134, 217],
55
- "Object.isExtensible": [0, 8, 113, 214],
56
- "Object.isFrozen": [0, 8, 215],
57
- "Object.isSealed": [0, 8, 216],
58
- "Object.keys": [0, 116, 218],
59
- "Object.preventExtensions": [0, 8, 19, 51, 73, 109, 113, 219],
60
- "Object.seal": [0, 8, 19, 51, 73, 109, 113, 220],
61
- "Object.setPrototypeOf": [0, 4, 58, 83, 118, 221],
62
- "Object.values": [0, 29, 112, 116, 119, 222],
63
- "Promise.any": [0, 24, 26, 47, 53, 59, 62, 63, 75, 87, 88, 102, 122, 123, 124, 125, 155, 224],
64
- "Reflect.apply": [0, 52, 225],
65
- "Reflect.construct": [0, 3, 19, 26, 52, 55, 69, 78, 105, 106, 116, 155, 226],
66
- "Reflect.deleteProperty": [0, 227],
67
- "Reflect.get": [0, 29, 79, 112, 230],
68
- "Reflect.getOwnPropertyDescriptor": [0, 228],
69
- "Reflect.getPrototypeOf": [0, 29, 112, 229],
70
- "Reflect.has": [0, 231],
71
- "Reflect.isExtensible": [0, 8, 113, 232],
72
- "Reflect.ownKeys": [0, 233],
73
- "Reflect.preventExtensions": [0, 51, 234],
74
- "Reflect.setPrototypeOf": [0, 4, 58, 83, 118, 235],
75
- "String.prototype.codePointAt": [0, 26, 141, 155, 156, 236],
76
- "String.prototype.endsWith": [0, 26, 28, 59, 85, 103, 155, 156, 237],
77
- "String.fromCodePoint": [0, 238],
78
- "String.prototype.includes": [0, 26, 28, 85, 103, 155, 156, 239],
79
- "String.prototype.matchAll": [0, 3, 6, 26, 29, 31, 59, 69, 78, 85, 89, 90, 105, 106, 112, 116, 126, 127, 128, 129, 130, 131, 132, 135, 139, 141, 155, 156, 241],
80
- "String.raw": [0, 26, 155, 156, 242],
81
- "String.prototype.repeat": [0, 26, 142, 155, 156, 243],
82
- "String.prototype.replaceAll": [0, 26, 65, 85, 128, 129, 155, 156, 244],
83
- "String.prototype.startsWith": [0, 26, 28, 59, 85, 103, 155, 156, 245],
84
- "String.prototype.substr": [0, 26, 155, 156, 246],
85
- "String.prototype.trim": [0, 26, 144, 146, 155, 156, 163, 251],
86
- "String.prototype.trimEnd": [0, 26, 143, 144, 146, 155, 156, 163, 247, 249],
87
- "String.prototype.trimStart": [0, 26, 144, 145, 146, 155, 156, 163, 248, 250],
88
- "String.prototype.link": [0, 26, 30, 140, 155, 156, 240],
89
- "Promise.allSettled": [0, 24, 26, 47, 53, 59, 62, 63, 75, 87, 88, 102, 122, 123, 124, 125, 155, 223, 252],
90
- "focus-visible": [253]
4
+ "Array.prototype.at": [0, 5, 71, 108, 109, 119, 168],
5
+ "Array.prototype.concat": [0, 17, 19, 22, 23, 27, 35, 41, 78, 80, 159, 169],
6
+ "Array.prototype.copyWithin": [0, 5, 9, 38, 71, 108, 109, 119, 170],
7
+ "Array.prototype.every": [0, 16, 18, 22, 23, 27, 35, 54, 60, 78, 80, 159, 171],
8
+ "Array.prototype.fill": [0, 5, 10, 71, 108, 109, 119, 172],
9
+ "Array.prototype.filter": [0, 16, 17, 22, 23, 27, 35, 54, 60, 78, 80, 159, 173],
10
+ "Array.prototype.find": [0, 5, 16, 22, 23, 27, 35, 54, 60, 71, 78, 80, 108, 109, 119, 159, 177],
11
+ "Array.prototype.findIndex": [0, 5, 16, 22, 23, 27, 35, 54, 60, 71, 78, 80, 108, 109, 119, 159, 174],
12
+ "Array.prototype.findLast": [0, 5, 15, 54, 60, 71, 108, 109, 119, 176],
13
+ "Array.prototype.findLastIndex": [0, 5, 15, 54, 60, 71, 108, 109, 119, 175],
14
+ "Array.prototype.flat": [0, 22, 23, 27, 35, 41, 51, 54, 60, 78, 80, 159, 179],
15
+ "Array.prototype.flatMap": [0, 22, 23, 27, 35, 41, 51, 54, 60, 78, 80, 159, 178],
16
+ "Array.prototype.forEach": [0, 11, 16, 18, 22, 23, 27, 35, 54, 60, 78, 80, 159, 180],
17
+ "Array.from": [0, 13, 19, 24, 25, 27, 35, 41, 54, 60, 64, 65, 77, 78, 80, 90, 93, 159, 181],
18
+ "Array.prototype.indexOf": [0, 18, 60, 182],
19
+ "Array.isArray": [0, 78, 183],
20
+ "Array.prototype.join": [0, 18, 184],
21
+ "Array.prototype.map": [0, 16, 17, 22, 23, 27, 35, 54, 60, 78, 80, 159, 185],
22
+ "Array.of": [0, 19, 27, 35, 78, 80, 159, 186],
23
+ "Array.prototype.slice": [0, 17, 19, 20, 27, 35, 78, 80, 159, 187],
24
+ "Array.prototype.some": [0, 16, 18, 22, 23, 27, 35, 54, 60, 78, 80, 159, 188],
25
+ "Array.prototype.sort": [0, 18, 20, 21, 27, 38, 43, 44, 47, 159, 160, 189],
26
+ "Array.prototype.toReversed": [0, 5, 35, 71, 108, 109, 119, 190],
27
+ "Array.prototype.toSorted": [0, 5, 12, 62, 71, 108, 109, 119, 191],
28
+ "Array.prototype.toSpliced": [0, 5, 35, 41, 71, 108, 109, 119, 192],
29
+ "Array.prototype.unshift": [0, 19, 38, 41, 78, 193],
30
+ "Math.acosh": [0, 100, 194],
31
+ "Math.asinh": [0, 100, 195],
32
+ "Math.atanh": [0, 100, 196],
33
+ "Math.cbrt": [0, 103, 197],
34
+ "Math.clz32": [0, 198],
35
+ "Math.cosh": [0, 96, 199],
36
+ "Math.expm1": [0, 96, 200],
37
+ "Math.fround": [0, 97, 98, 102, 103, 201],
38
+ "Math.hypot": [0, 202],
39
+ "Math.imul": [0, 203],
40
+ "Math.log10": [0, 99, 204],
41
+ "Math.log1p": [0, 100, 205],
42
+ "Math.log2": [0, 101, 206],
43
+ "Math.sign": [0, 103, 207],
44
+ "Math.sinh": [0, 96, 208],
45
+ "Math.tanh": [0, 96, 209],
46
+ "Math.trunc": [0, 210],
47
+ "Object.assign": [0, 107, 119, 211],
48
+ "Object.create": [0, 71, 108, 109, 119, 212],
49
+ "Object.entries": [0, 30, 115, 119, 122, 213],
50
+ "Object.freeze": [0, 8, 20, 52, 75, 112, 116, 214],
51
+ "Object.fromEntries": [0, 35, 54, 60, 64, 65, 77, 89, 90, 93, 215],
52
+ "Object.getOwnPropertyDescriptor": [0, 216],
53
+ "Object.getOwnPropertyDescriptors": [0, 35, 217],
54
+ "Object.getPrototypeOf": [0, 30, 115, 218],
55
+ "Object.hasOwn": [0, 219],
56
+ "Object.is": [0, 138, 223],
57
+ "Object.isExtensible": [0, 8, 116, 220],
58
+ "Object.isFrozen": [0, 8, 221],
59
+ "Object.isSealed": [0, 8, 222],
60
+ "Object.keys": [0, 119, 224],
61
+ "Object.preventExtensions": [0, 8, 20, 52, 75, 112, 116, 225],
62
+ "Object.seal": [0, 8, 20, 52, 75, 112, 116, 226],
63
+ "Object.setPrototypeOf": [0, 4, 59, 85, 121, 227],
64
+ "Object.values": [0, 30, 115, 119, 122, 228],
65
+ "Promise.any": [0, 25, 48, 54, 60, 64, 65, 77, 89, 90, 93, 105, 125, 126, 127, 128, 230],
66
+ "Promise.withResolvers": [0, 105, 231],
67
+ "Reflect.apply": [0, 53, 232],
68
+ "Reflect.construct": [0, 3, 20, 27, 53, 56, 71, 80, 108, 109, 119, 159, 233],
69
+ "Reflect.deleteProperty": [0, 234],
70
+ "Reflect.get": [0, 30, 81, 115, 237],
71
+ "Reflect.getOwnPropertyDescriptor": [0, 235],
72
+ "Reflect.getPrototypeOf": [0, 30, 115, 236],
73
+ "Reflect.has": [0, 238],
74
+ "Reflect.isExtensible": [0, 8, 116, 239],
75
+ "Reflect.ownKeys": [0, 240],
76
+ "Reflect.preventExtensions": [0, 52, 241],
77
+ "Reflect.setPrototypeOf": [0, 4, 59, 85, 121, 242],
78
+ "String.prototype.codePointAt": [0, 27, 145, 159, 160, 243],
79
+ "String.prototype.endsWith": [0, 27, 29, 60, 87, 106, 159, 160, 244],
80
+ "String.fromCodePoint": [0, 245],
81
+ "String.prototype.includes": [0, 27, 29, 87, 106, 159, 160, 246],
82
+ "String.prototype.isWellFormed": [0, 27, 159, 160, 247],
83
+ "String.prototype.matchAll": [0, 3, 6, 27, 30, 32, 60, 71, 80, 87, 91, 92, 93, 108, 109, 115, 119, 129, 130, 131, 132, 133, 134, 135, 136, 139, 143, 145, 159, 160, 249],
84
+ "String.raw": [0, 27, 159, 160, 250],
85
+ "String.prototype.repeat": [0, 27, 146, 159, 160, 251],
86
+ "String.prototype.replaceAll": [0, 27, 67, 87, 131, 132, 133, 159, 160, 252],
87
+ "String.prototype.startsWith": [0, 27, 29, 60, 87, 106, 159, 160, 253],
88
+ "String.prototype.substr": [0, 27, 159, 160, 254],
89
+ "String.prototype.toWellFormed": [0, 27, 159, 160, 255],
90
+ "String.prototype.trim": [0, 27, 148, 150, 159, 160, 167, 260],
91
+ "String.prototype.trimEnd": [0, 27, 147, 148, 150, 159, 160, 167, 256, 258],
92
+ "String.prototype.trimStart": [0, 27, 148, 149, 150, 159, 160, 167, 257, 259],
93
+ "String.prototype.link": [0, 27, 31, 144, 159, 160, 248],
94
+ "Promise.allSettled": [0, 25, 48, 54, 60, 64, 65, 77, 89, 90, 93, 105, 125, 126, 127, 128, 229, 261],
95
+ "focus-visible": [262]
91
96
  },
92
- "maxSize": 155835
97
+ "maxSize": 163427
93
98
  }
@@ -112,13 +112,6 @@
112
112
  ],
113
113
  "corejs": true
114
114
  },
115
- {
116
- "name": "Array.prototype.includes",
117
- "modules": [
118
- "es.array.includes"
119
- ],
120
- "corejs": true
121
- },
122
115
  {
123
116
  "name": "Array.prototype.indexOf",
124
117
  "modules": [
@@ -175,6 +168,30 @@
175
168
  ],
176
169
  "corejs": true
177
170
  },
171
+ {
172
+ "name": "Array.prototype.toReversed",
173
+ "modules": [
174
+ "es.array.to-reversed",
175
+ "esnext.array.to-reversed"
176
+ ],
177
+ "corejs": true
178
+ },
179
+ {
180
+ "name": "Array.prototype.toSorted",
181
+ "modules": [
182
+ "es.array.to-sorted",
183
+ "esnext.array.to-sorted"
184
+ ],
185
+ "corejs": true
186
+ },
187
+ {
188
+ "name": "Array.prototype.toSpliced",
189
+ "modules": [
190
+ "es.array.to-spliced",
191
+ "esnext.array.to-spliced"
192
+ ],
193
+ "corejs": true
194
+ },
178
195
  {
179
196
  "name": "Array.prototype.unshift",
180
197
  "modules": [
@@ -436,6 +453,14 @@
436
453
  ],
437
454
  "corejs": true
438
455
  },
456
+ {
457
+ "name": "Promise.withResolvers",
458
+ "modules": [
459
+ "es.promise.with-resolvers",
460
+ "esnext.promise.with-resolvers"
461
+ ],
462
+ "corejs": true
463
+ },
439
464
  {
440
465
  "name": "Reflect.apply",
441
466
  "modules": [
@@ -541,6 +566,14 @@
541
566
  ],
542
567
  "corejs": true
543
568
  },
569
+ {
570
+ "name": "String.prototype.isWellFormed",
571
+ "modules": [
572
+ "es.string.is-well-formed",
573
+ "esnext.string.is-well-formed"
574
+ ],
575
+ "corejs": true
576
+ },
544
577
  {
545
578
  "name": "String.prototype.matchAll",
546
579
  "modules": [
@@ -585,6 +618,14 @@
585
618
  ],
586
619
  "corejs": true
587
620
  },
621
+ {
622
+ "name": "String.prototype.toWellFormed",
623
+ "modules": [
624
+ "es.string.to-well-formed",
625
+ "esnext.string.to-well-formed"
626
+ ],
627
+ "corejs": true
628
+ },
588
629
  {
589
630
  "name": "String.prototype.trim",
590
631
  "modules": [
@@ -7,7 +7,6 @@
7
7
  // https://www.ecma-international.org/ecma-262/9.0/index.html#sec-punctuators
8
8
  // eslint-disable-next-line max-len
9
9
  const PUNCTUATOR_REGEX = /(return|case|else|{|\(|\[|\.\.\.|;|,|<|>|<=|>=|==|!=|===|!==|\+|-|\*|%|\*\*|\+\+|--|<<|>>|>>>|&|\||\^|!|~|&&|\|\||\?|:|=|\+=|-=|\*=|%=|\*\*=|<<=|>>=|>>>=|&=|\|=|\^=|=>|\/|\/=|\})$/;
10
- const WHITESPACE_REGEX = /( |\n|\t)+$/;
11
10
 
12
11
  /**
13
12
  * Look backwards from `startPosition` in `content` for an ECMAScript punctuator.
@@ -19,11 +18,13 @@ const WHITESPACE_REGEX = /( |\n|\t)+$/;
19
18
  */
20
19
  function hasPunctuatorBefore(content, startPosition) {
21
20
  for (let i = startPosition; i > 0; i--) {
21
+ const char = content[i - 1];
22
+ // Skip over any ending whitespace
23
+ if (char === ' ' || char === '\n' || char === '\t') continue;
24
+
22
25
  // Try to grab at least 6 characters so we can check for `return`
23
26
  const sliceStart = Math.max(0, i - 6);
24
27
  const precedingCharacters = content.slice(sliceStart, i);
25
- // Skip over any ending whitespace
26
- if (WHITESPACE_REGEX.test(precedingCharacters)) continue;
27
28
  // Check if it's a punctuator
28
29
  return PUNCTUATOR_REGEX.test(precedingCharacters);
29
30
  }
@@ -56,8 +57,7 @@ function computeTokenLength(content, features) {
56
57
  const templateLiteralDepth = [];
57
58
 
58
59
  for (let i = 0; i < content.length; i++) {
59
- const twoChars = content.substr(i, 2);
60
- const char = twoChars.charAt(0);
60
+ const char = content[i];
61
61
 
62
62
  const isWhitespace = char === ' ' || char === '\n' || char === '\t';
63
63
  const isAStringOpenChar = char === `'` || char === '"' || char === '`';
@@ -71,7 +71,7 @@ function computeTokenLength(content, features) {
71
71
  // License comments count
72
72
  if (isInLicenseComment) totalTokenLength++;
73
73
 
74
- if (twoChars === '*/') {
74
+ if (char === '*' && content[i + 1] === '/') {
75
75
  // License comments count, account for the '/' character we're skipping over
76
76
  if (isInLicenseComment) totalTokenLength++;
77
77
  // End the comment when we hit the closing sequence
@@ -83,7 +83,7 @@ function computeTokenLength(content, features) {
83
83
  // String characters count
84
84
  totalTokenLength++;
85
85
 
86
- if (stringOpenChar === '`' && twoChars === '${') {
86
+ if (stringOpenChar === '`' && char === '$' && content[i + 1] === '{') {
87
87
  // Start new template literal
88
88
  templateLiteralDepth.push('templateBrace');
89
89
  isInString = false;
@@ -119,16 +119,16 @@ function computeTokenLength(content, features) {
119
119
  }
120
120
  } else {
121
121
  // We're not in any particular token mode, look for the start of different
122
- if (twoChars === '/*') {
122
+ if (char === '/' && content[i + 1] === '*') {
123
123
  // Start the multi-line comment
124
124
  isInMultilineComment = true;
125
125
  // Check if it's a license comment so we know whether to count it
126
- isInLicenseComment = content.charAt(i + 2) === '!';
126
+ isInLicenseComment = content[i + 2] === '!';
127
127
  // += 2 because we are processing 2 characters, not just 1
128
128
  if (isInLicenseComment) totalTokenLength += 2;
129
129
  // Skip over the '*' character since we've already processed it
130
130
  i++;
131
- } else if (twoChars === '//' && features.singlelineComments) {
131
+ } else if (char === '/' && content[i + 1] === '/' && features.singlelineComments) {
132
132
  // Start the single-line comment
133
133
  isInSinglelineComment = true;
134
134
  isInMultilineComment = false;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "lighthouse",
3
3
  "type": "module",
4
- "version": "13.5.0-dev.20261006",
4
+ "version": "13.5.0-dev.20261007",
5
5
  "description": "Automated auditing, performance metrics, and best practices for the web.",
6
6
  "main": "./core/index.js",
7
7
  "bin": {