@depup/webpack-sources 3.3.4-depup.0 → 3.6.0-depup.0

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.
Files changed (53) hide show
  1. package/README.md +2 -2
  2. package/changes.json +1 -1
  3. package/lib/CachedSource.js +257 -63
  4. package/lib/CompatSource.js +42 -1
  5. package/lib/ConcatSource.js +100 -28
  6. package/lib/OriginalSource.js +111 -45
  7. package/lib/PrefixSource.js +60 -11
  8. package/lib/RawSource.js +85 -29
  9. package/lib/ReplaceSource.js +269 -73
  10. package/lib/SizeOnlySource.js +10 -1
  11. package/lib/Source.js +39 -0
  12. package/lib/SourceMapSource.js +92 -10
  13. package/lib/helpers/createMappingsSerializer.js +307 -54
  14. package/lib/helpers/getFromStreamChunks.js +165 -73
  15. package/lib/helpers/getGeneratedSourceInfo.js +7 -4
  16. package/lib/helpers/readMappings.js +6 -3
  17. package/lib/helpers/scopes.js +438 -0
  18. package/lib/helpers/splitIntoLines.js +10 -9
  19. package/lib/helpers/splitIntoPotentialTokens.js +99 -26
  20. package/lib/helpers/streamAndGetSourceAndMap.js +61 -22
  21. package/lib/helpers/streamChunks.js +3 -2
  22. package/lib/helpers/streamChunksOfCombinedSourceMap.js +7 -3
  23. package/lib/helpers/streamChunksOfRawSource.js +23 -15
  24. package/lib/helpers/streamChunksOfSourceMap.js +50 -42
  25. package/lib/index.js +3 -0
  26. package/package.json +34 -23
  27. package/types/CachedSource.d.ts +192 -0
  28. package/types/CompatSource.d.ts +87 -0
  29. package/types/ConcatSource.d.ts +75 -0
  30. package/types/OriginalSource.d.ts +87 -0
  31. package/types/PrefixSource.d.ts +59 -0
  32. package/types/RawSource.d.ts +75 -0
  33. package/types/ReplaceSource.d.ts +107 -0
  34. package/types/SizeOnlySource.d.ts +24 -0
  35. package/types/Source.d.ts +189 -0
  36. package/types/SourceMapSource.d.ts +136 -0
  37. package/types/helpers/createMappingsSerializer.d.ts +60 -0
  38. package/types/helpers/getFromStreamChunks.d.ts +16 -0
  39. package/types/helpers/getGeneratedSourceInfo.d.ts +31 -0
  40. package/types/helpers/getName.d.ts +15 -0
  41. package/types/helpers/getSource.d.ts +15 -0
  42. package/types/helpers/readMappings.d.ts +19 -0
  43. package/types/helpers/scopes.d.ts +119 -0
  44. package/types/helpers/splitIntoLines.d.ts +6 -0
  45. package/types/helpers/splitIntoPotentialTokens.d.ts +37 -0
  46. package/types/helpers/streamAndGetSourceAndMap.d.ts +39 -0
  47. package/types/helpers/streamChunks.d.ts +56 -0
  48. package/types/helpers/streamChunksOfCombinedSourceMap.d.ts +42 -0
  49. package/types/helpers/streamChunksOfRawSource.d.ts +16 -0
  50. package/types/helpers/streamChunksOfSourceMap.d.ts +19 -0
  51. package/types/helpers/stringBufferUtils.d.ts +55 -0
  52. package/types/index.d.ts +50 -0
  53. package/types.d.ts +42 -439
@@ -0,0 +1,438 @@
1
+ /*
2
+ MIT License http://www.opensource.org/licenses/mit-license.php
3
+ Author Alexander Akait @alexander-akait
4
+ */
5
+
6
+ "use strict";
7
+
8
+ /** @typedef {import("../Source").RawSourceMap} RawSourceMap */
9
+ /** @typedef {import("./streamChunks").ScopeBindings} ScopeBindings */
10
+
11
+ /**
12
+ * A generated or original position, both counted from zero.
13
+ * @typedef {object} ScopePosition
14
+ * @property {number} line line
15
+ * @property {number} column column
16
+ */
17
+
18
+ /**
19
+ * One source's scope, and the span of generated code it explains.
20
+ * @typedef {object} SourceScope
21
+ * @property {number} sourceIndex index into the map's `sources`
22
+ * @property {string[]} variables the names the source declares
23
+ * @property {string[]} values the generated expression each name evaluates to
24
+ * @property {ScopePosition} originalEnd end of the original scope, exclusive
25
+ * @property {ScopePosition[]} rangeStarts start of each generated range, inclusive
26
+ * @property {ScopePosition[]} rangeEnds end of each generated range, exclusive
27
+ */
28
+
29
+ const BASE64_CHARS =
30
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
31
+
32
+ const BASE64_VALUES = new Int8Array(128).fill(-1);
33
+ for (let index = 0; index < BASE64_CHARS.length; index++) {
34
+ BASE64_VALUES[BASE64_CHARS.charCodeAt(index)] = index;
35
+ }
36
+
37
+ const VLQ_BASE_SHIFT = 5;
38
+ const VLQ_BASE_MASK = (1 << VLQ_BASE_SHIFT) - 1;
39
+ const VLQ_CONTINUATION_BIT = 1 << VLQ_BASE_SHIFT;
40
+
41
+ // Item tags of the "Scopes" proposal, base64 digits of their numeric values.
42
+ const TAG_EMPTY = "A";
43
+ const TAG_ORIGINAL_SCOPE_START = "B";
44
+ const TAG_ORIGINAL_SCOPE_END = "C";
45
+ const TAG_ORIGINAL_SCOPE_VARIABLES = "D";
46
+ const TAG_GENERATED_RANGE_START = "E";
47
+ const TAG_GENERATED_RANGE_END = "F";
48
+ const TAG_GENERATED_RANGE_BINDINGS = "G";
49
+
50
+ const ORIGINAL_SCOPE_FLAG_HAS_KIND = 0x2;
51
+ const GENERATED_RANGE_FLAG_HAS_LINE = 0x1;
52
+ const GENERATED_RANGE_FLAG_HAS_DEFINITION = 0x2;
53
+
54
+ /**
55
+ * Every scope webpack emits describes one module, which the proposal's
56
+ * JavaScript vocabulary calls a module scope.
57
+ */
58
+ const MODULE_SCOPE_KIND = "Module";
59
+
60
+ /**
61
+ * @param {number} value non-negative value
62
+ * @returns {string} base64 VLQ digits
63
+ */
64
+ const encodeUnsigned = (value) => {
65
+ let result = "";
66
+ let rest = value;
67
+ for (;;) {
68
+ const digit = rest & VLQ_BASE_MASK;
69
+ rest >>>= VLQ_BASE_SHIFT;
70
+ if (rest === 0) return result + BASE64_CHARS[digit];
71
+ result += BASE64_CHARS[VLQ_CONTINUATION_BIT + digit];
72
+ }
73
+ };
74
+
75
+ /**
76
+ * @param {number} value value of either sign
77
+ * @returns {string} base64 VLQ digits
78
+ */
79
+ const encodeSigned = (value) =>
80
+ encodeUnsigned(value >= 0 ? 2 * value : 1 - 2 * value);
81
+
82
+ /**
83
+ * Walks the map's segments in generated order, reporting each one's source and
84
+ * original line. A segment with no source position reports `sourceIndex` -1,
85
+ * which ends whatever run precedes it.
86
+ * @param {string} mappings the map's `mappings` field
87
+ * @param {(generatedLine: number, generatedColumn: number, sourceIndex: number, originalLine: number) => void} onSegment called per segment
88
+ * @returns {number} the last generated line the mappings reach
89
+ */
90
+ const forEachMapping = (mappings, onSegment) => {
91
+ let generatedLine = 0;
92
+ let generatedColumn = 0;
93
+ let sourceIndex = 0;
94
+ let originalLine = 0;
95
+ let position = 0;
96
+ const { length } = mappings;
97
+ /** @type {number[]} */
98
+ const fields = [];
99
+ while (position < length) {
100
+ const char = mappings.charCodeAt(position);
101
+ if (char === 59 /* ; */) {
102
+ generatedLine++;
103
+ generatedColumn = 0;
104
+ position++;
105
+ continue;
106
+ }
107
+ if (char === 44 /* , */) {
108
+ position++;
109
+ continue;
110
+ }
111
+ fields.length = 0;
112
+ while (position < length) {
113
+ const next = mappings.charCodeAt(position);
114
+ if (next === 59 || next === 44) break;
115
+ let value = 0;
116
+ let shift = 0;
117
+ let digit;
118
+ do {
119
+ digit = BASE64_VALUES[mappings.charCodeAt(position++)];
120
+ // A digit outside the alphabet cannot be recovered from, and
121
+ // pretending otherwise would misplace every later segment.
122
+ if (digit < 0) return generatedLine;
123
+ value += (digit & VLQ_BASE_MASK) << shift;
124
+ shift += VLQ_BASE_SHIFT;
125
+ } while (digit & VLQ_CONTINUATION_BIT);
126
+ fields.push(value & 1 ? -(value >>> 1) : value >>> 1);
127
+ }
128
+ if (fields.length === 0) continue;
129
+ generatedColumn += fields[0];
130
+ if (fields.length >= 4) {
131
+ sourceIndex += fields[1];
132
+ originalLine += fields[2];
133
+ onSegment(generatedLine, generatedColumn, sourceIndex, originalLine);
134
+ } else {
135
+ onSegment(generatedLine, generatedColumn, -1, -1);
136
+ }
137
+ }
138
+ return generatedLine;
139
+ };
140
+
141
+ /**
142
+ * Collects, segment by segment, the generated runs each source explains and how
143
+ * far into it the map reaches. Both lines are counted from zero, so a caller
144
+ * whose lines start at one subtracts before feeding a segment in.
145
+ * @param {number=} sourceCount number of entries in the map's `sources`, when known
146
+ * @returns {{ add: (generatedLine: number, generatedColumn: number, sourceIndex: number, originalLine: number) => void, finish: (lastLine: number, lastColumn?: number) => SourceScope[] }} collector
147
+ */
148
+ const createScopeCollector = (sourceCount) => {
149
+ /** @type {Map<number, SourceScope>} */
150
+ const scopes = new Map();
151
+ let openSource = -1;
152
+ /** @type {SourceScope | undefined} */
153
+ let openScope;
154
+ /**
155
+ * @param {number} line generated line the run ends at
156
+ * @param {number} column generated column the run ends at
157
+ * @returns {void}
158
+ */
159
+ const closeRun = (line, column) => {
160
+ if (openScope === undefined) return;
161
+ openScope.rangeEnds.push({ line, column });
162
+ openScope = undefined;
163
+ openSource = -1;
164
+ };
165
+ return {
166
+ add(generatedLine, generatedColumn, sourceIndex, originalLine) {
167
+ // A segment naming no source reports -1, which is also what no open
168
+ // run reports, so the run has to be there before it is extended.
169
+ if (openScope !== undefined && sourceIndex === openSource) {
170
+ openScope.originalEnd.line = Math.max(
171
+ openScope.originalEnd.line,
172
+ originalLine + 1,
173
+ );
174
+ return;
175
+ }
176
+ closeRun(generatedLine, generatedColumn);
177
+ if (
178
+ sourceIndex < 0 ||
179
+ (sourceCount !== undefined && sourceIndex >= sourceCount)
180
+ ) {
181
+ return;
182
+ }
183
+ let scope = scopes.get(sourceIndex);
184
+ if (scope === undefined) {
185
+ scope = {
186
+ sourceIndex,
187
+ variables: [],
188
+ values: [],
189
+ originalEnd: { line: originalLine + 1, column: 0 },
190
+ rangeStarts: [],
191
+ rangeEnds: [],
192
+ };
193
+ scopes.set(sourceIndex, scope);
194
+ } else {
195
+ scope.originalEnd.line = Math.max(
196
+ scope.originalEnd.line,
197
+ originalLine + 1,
198
+ );
199
+ }
200
+ scope.rangeStarts.push({ line: generatedLine, column: generatedColumn });
201
+ openScope = scope;
202
+ openSource = sourceIndex;
203
+ },
204
+ finish(lastLine, lastColumn = 0) {
205
+ closeRun(lastLine, lastColumn);
206
+ return [...scopes.values()].sort((a, b) => a.sourceIndex - b.sourceIndex);
207
+ },
208
+ };
209
+ };
210
+
211
+ /**
212
+ * Groups a finished map's segments into one entry per source. Prefer the
213
+ * collector when the segments are still being written, which saves decoding
214
+ * back what was just encoded.
215
+ * @param {string} mappings the map's `mappings` field
216
+ * @param {number} sourceCount number of entries in the map's `sources`
217
+ * @returns {SourceScope[]} one entry per source that the mappings reach, in source order
218
+ */
219
+ const collectSourceScopes = (mappings, sourceCount) => {
220
+ const collector = createScopeCollector(sourceCount);
221
+ const lastLine = forEachMapping(mappings, collector.add);
222
+ return collector.finish(lastLine + 1);
223
+ };
224
+
225
+ /**
226
+ * Encodes the scope tree into the `scopes` field of the proposal, appending any
227
+ * name it needs to the map's `names`.
228
+ * @param {SourceScope[]} scopes the scopes to encode, in source order
229
+ * @param {number} sourceCount number of entries in the map's `sources`
230
+ * @param {string[]} names the map's `names`, extended in place
231
+ * @returns {string} the encoded `scopes` field
232
+ */
233
+ const encodeScopes = (scopes, sourceCount, names) => {
234
+ /** @type {Map<string, number>} */
235
+ const nameToIndex = new Map();
236
+ for (let index = 0; index < names.length; index++) {
237
+ if (!nameToIndex.has(names[index])) nameToIndex.set(names[index], index);
238
+ }
239
+ /**
240
+ * @param {string} name the name to resolve
241
+ * @returns {number} its index in `names`
242
+ */
243
+ const nameIndex = (name) => {
244
+ const existing = nameToIndex.get(name);
245
+ if (existing !== undefined) return existing;
246
+ const added = names.length;
247
+ names.push(name);
248
+ nameToIndex.set(name, added);
249
+ return added;
250
+ };
251
+
252
+ /** @type {Map<number, SourceScope>} */
253
+ const scopeBySource = new Map();
254
+ for (const scope of scopes) scopeBySource.set(scope.sourceIndex, scope);
255
+
256
+ /** @type {string[]} */
257
+ const items = [];
258
+ // The three running indices the proposal encodes names and definitions
259
+ // against. Only the position pair restarts per source.
260
+ let lastKind = 0;
261
+ let lastVariable = 0;
262
+ let lastDefinition = 0;
263
+ let scopeCount = 0;
264
+ /** @type {Map<number, number>} */
265
+ const scopeIndexBySource = new Map();
266
+
267
+ for (let sourceIndex = 0; sourceIndex < sourceCount; sourceIndex++) {
268
+ const scope = scopeBySource.get(sourceIndex);
269
+ if (scope === undefined || scope.variables.length === 0) {
270
+ items.push(TAG_EMPTY);
271
+ continue;
272
+ }
273
+ const kind = nameIndex(MODULE_SCOPE_KIND);
274
+ items.push(
275
+ TAG_ORIGINAL_SCOPE_START +
276
+ encodeUnsigned(ORIGINAL_SCOPE_FLAG_HAS_KIND) +
277
+ encodeUnsigned(0) +
278
+ encodeUnsigned(0) +
279
+ encodeSigned(kind - lastKind),
280
+ );
281
+ lastKind = kind;
282
+ let variables = TAG_ORIGINAL_SCOPE_VARIABLES;
283
+ for (const variable of scope.variables) {
284
+ const index = nameIndex(variable);
285
+ variables += encodeSigned(index - lastVariable);
286
+ lastVariable = index;
287
+ }
288
+ items.push(variables);
289
+ items.push(
290
+ TAG_ORIGINAL_SCOPE_END +
291
+ encodeUnsigned(scope.originalEnd.line) +
292
+ encodeUnsigned(scope.originalEnd.column),
293
+ );
294
+ scopeIndexBySource.set(sourceIndex, scopeCount++);
295
+ }
296
+
297
+ /** @type {{ start: ScopePosition, end: ScopePosition, scope: SourceScope }[]} */
298
+ const ranges = [];
299
+ for (const scope of scopes) {
300
+ if (!scopeIndexBySource.has(scope.sourceIndex)) continue;
301
+ for (let index = 0; index < scope.rangeStarts.length; index++) {
302
+ ranges.push({
303
+ start: scope.rangeStarts[index],
304
+ end: scope.rangeEnds[index],
305
+ scope,
306
+ });
307
+ }
308
+ }
309
+ ranges.sort(
310
+ (a, b) => a.start.line - b.start.line || a.start.column - b.start.column,
311
+ );
312
+
313
+ let lastLine = 0;
314
+ let lastColumn = 0;
315
+ /**
316
+ * Encodes a position against the running one, reporting whether it needed a
317
+ * line of its own. A line is only carried when it moved forward, and then
318
+ * the column is absolute rather than relative.
319
+ * @param {ScopePosition} position the position to encode
320
+ * @returns {[boolean, string]} whether a line is carried, and the digits
321
+ */
322
+ const encodePosition = (position) => {
323
+ const line = position.line - lastLine;
324
+ const carriesLine = line > 0;
325
+ const column = carriesLine ? position.column : position.column - lastColumn;
326
+ lastLine = position.line;
327
+ lastColumn = position.column;
328
+ return [
329
+ carriesLine,
330
+ (carriesLine ? encodeUnsigned(line) : "") + encodeUnsigned(column),
331
+ ];
332
+ };
333
+
334
+ for (const { start, end, scope } of ranges) {
335
+ const definition =
336
+ /** @type {number} */
337
+ (scopeIndexBySource.get(scope.sourceIndex));
338
+ const [startCarriesLine, startDigits] = encodePosition(start);
339
+ items.push(
340
+ TAG_GENERATED_RANGE_START +
341
+ encodeUnsigned(
342
+ GENERATED_RANGE_FLAG_HAS_DEFINITION |
343
+ (startCarriesLine ? GENERATED_RANGE_FLAG_HAS_LINE : 0),
344
+ ) +
345
+ startDigits +
346
+ encodeSigned(definition - lastDefinition),
347
+ );
348
+ lastDefinition = definition;
349
+ let bindings = TAG_GENERATED_RANGE_BINDINGS;
350
+ for (const value of scope.values) {
351
+ bindings += encodeUnsigned(value === "" ? 0 : nameIndex(value) + 1);
352
+ }
353
+ items.push(bindings);
354
+ // An end item carries no flags: one digit is a column, two a line and a
355
+ // column, which is what tells the reader whether the line moved.
356
+ items.push(TAG_GENERATED_RANGE_END + encodePosition(end)[1]);
357
+ }
358
+ return items.join(",");
359
+ };
360
+
361
+ /**
362
+ * Adds the `scopes` field of the "Scopes" proposal to a source map, naming for
363
+ * each source the bindings a debugger cannot resolve on its own and the
364
+ * generated expression each one evaluates to. Does nothing when no source
365
+ * contributes a binding.
366
+ * @param {RawSourceMap} sourceMap the map to extend in place
367
+ * @param {(sourceIndex: number) => Map<string, string> | undefined} getBindings binding expressions per source
368
+ * @returns {void}
369
+ */
370
+ const addScopesToSourceMap = (sourceMap, getBindings) => {
371
+ if (!sourceMap.mappings || !sourceMap.sources) return;
372
+ const sourceCount = sourceMap.sources.length;
373
+ const scopes = collectSourceScopes(sourceMap.mappings, sourceCount);
374
+ let any = false;
375
+ for (const scope of scopes) {
376
+ const bindings = getBindings(scope.sourceIndex);
377
+ if (bindings === undefined || bindings.size === 0) continue;
378
+ for (const [name, value] of bindings) {
379
+ scope.variables.push(name);
380
+ scope.values.push(value);
381
+ }
382
+ any = true;
383
+ }
384
+ if (!any) return;
385
+ const names = sourceMap.names || (sourceMap.names = []);
386
+ sourceMap.scopes = encodeScopes(scopes, sourceCount, names);
387
+ };
388
+
389
+ /**
390
+ * Builds the `scopes` field while a map is being written, so the segments are
391
+ * read as they are produced rather than decoded back out of `mappings`. Lines
392
+ * are the one-based ones the chunk stream reports; `finish` takes the position
393
+ * the generated code ends at, where the last range closes.
394
+ * @returns {{ add: (generatedLine: number, generatedColumn: number, sourceIndex: number, originalLine: number) => void, addSource: (sourceIndex: number, scopeBindings?: ScopeBindings) => void, finish: (map: RawSourceMap, generatedLine: number, generatedColumn?: number) => void }} writer
395
+ */
396
+ const createScopesWriter = () => {
397
+ const collector = createScopeCollector();
398
+ /** @type {Map<number, ScopeBindings>} */
399
+ const bindingsBySource = new Map();
400
+ return {
401
+ add(generatedLine, generatedColumn, sourceIndex, originalLine) {
402
+ collector.add(
403
+ generatedLine - 1,
404
+ generatedColumn,
405
+ sourceIndex,
406
+ originalLine - 1,
407
+ );
408
+ },
409
+ addSource(sourceIndex, scopeBindings) {
410
+ if (scopeBindings !== undefined && scopeBindings.size > 0) {
411
+ bindingsBySource.set(sourceIndex, scopeBindings);
412
+ }
413
+ },
414
+ finish(map, generatedLine, generatedColumn = 0) {
415
+ if (bindingsBySource.size === 0) return;
416
+ const scopes = collector.finish(generatedLine - 1, generatedColumn);
417
+ let any = false;
418
+ for (const scope of scopes) {
419
+ const bindings = bindingsBySource.get(scope.sourceIndex);
420
+ if (bindings === undefined) continue;
421
+ for (const [name, value] of bindings) {
422
+ scope.variables.push(name);
423
+ scope.values.push(value);
424
+ }
425
+ any = true;
426
+ }
427
+ if (!any) return;
428
+ const names = map.names || (map.names = []);
429
+ map.scopes = encodeScopes(scopes, map.sources.length, names);
430
+ },
431
+ };
432
+ };
433
+
434
+ module.exports.addScopesToSourceMap = addScopesToSourceMap;
435
+ module.exports.collectSourceScopes = collectSourceScopes;
436
+ module.exports.createScopeCollector = createScopeCollector;
437
+ module.exports.createScopesWriter = createScopesWriter;
438
+ module.exports.encodeScopes = encodeScopes;
@@ -14,18 +14,19 @@ const splitIntoLines = (str) => {
14
14
  const len = str.length;
15
15
  let i = 0;
16
16
  while (i < len) {
17
- const cc = str.charCodeAt(i);
18
- // 10 is "\n".charCodeAt(0)
19
- if (cc === 10) {
17
+ // indexOf is implemented natively and is significantly faster than
18
+ // scanning char-by-char with charCodeAt for long lines.
19
+ const n = str.indexOf("\n", i);
20
+ if (n === -1) {
21
+ results.push(i === 0 ? str : str.slice(i));
22
+ break;
23
+ }
24
+ if (n === i) {
20
25
  results.push("\n");
21
- i++;
22
26
  } else {
23
- let j = i + 1;
24
- // 10 is "\n".charCodeAt(0)
25
- while (j < len && str.charCodeAt(j) !== 10) j++;
26
- results.push(str.slice(i, j + 1));
27
- i = j + 1;
27
+ results.push(str.slice(i, n + 1));
28
28
  }
29
+ i = n + 1;
29
30
  }
30
31
  return results;
31
32
  };
@@ -5,15 +5,87 @@
5
5
 
6
6
  "use strict";
7
7
 
8
- // \n = 10
9
- // ; = 59
10
- // { = 123
11
- // } = 125
12
- // <space> = 32
13
- // \r = 13
14
- // \t = 9
8
+ // Character classification via a lookup table. A single bitmask test
9
+ // replaces the multi-comparison chains in each inner loop phase.
10
+ //
11
+ // BIT layout per character:
12
+ // bit 0 (STOP1 = 1): stops phase-1 scan (\n ; { })
13
+ // bit 1 (CONT2 = 2): continues phase-2 scan (; { } space \r \t)
14
+ //
15
+ // Phase 1: scan regular source chars that are NOT a phase-1 stop.
16
+ // Phase 2: consume runs of statement-boundary / whitespace chars.
17
+ // Phase 3: consume a trailing \n if present.
18
+
19
+ const STOP1 = 1;
20
+ const CONT2 = 2;
21
+
22
+ /** @type {Uint8Array} */
23
+ const CF = new Uint8Array(128);
24
+ CF[10] = STOP1; // \n – stops phase 1, NOT consumed in phase 2
25
+ CF[59] = STOP1 | CONT2; // ;
26
+ CF[123] = STOP1 | CONT2; // {
27
+ CF[125] = STOP1 | CONT2; // }
28
+ CF[32] = CONT2; // space
29
+ CF[13] = CONT2; // \r
30
+ CF[9] = CONT2; // \t
31
+
32
+ /**
33
+ * @callback OnPotentialToken
34
+ * @param {number} start start offset (inclusive)
35
+ * @param {number} end end offset (exclusive)
36
+ * @param {boolean} newline whether the token ends with a `\n`
37
+ * @returns {void}
38
+ */
39
+
40
+ /**
41
+ * Streaming core: report each potential token by its `[start, end)` bounds
42
+ * instead of materialising substrings. The single real consumer
43
+ * (`OriginalSource.streamChunks`) slices on demand — and skips slicing
44
+ * entirely when emitting the final source (the `map()` / `sourceAndMap()`
45
+ * paths, which discard the chunk text) — so this avoids both the
46
+ * intermediate results array and every per-token `String.slice` allocation
47
+ * in the dominant case.
48
+ * @param {string} str string
49
+ * @param {OnPotentialToken} onToken called for each token
50
+ * @returns {void}
51
+ */
52
+ const eachPotentialToken = (str, onToken) => {
53
+ const len = str.length;
54
+ let i = 0;
55
+ outer: while (i < len) {
56
+ const start = i;
57
+ // Phase 1 – skip regular (non-stop) characters
58
+ let cc = str.charCodeAt(i);
59
+ while (cc > 127 || !(CF[cc] & STOP1)) {
60
+ if (++i >= len) {
61
+ onToken(start, i, false);
62
+ break outer;
63
+ }
64
+ cc = str.charCodeAt(i);
65
+ }
66
+ // Phase 2 – consume delimiter / whitespace run (; { } space \r \t)
67
+ while (cc < 128 && CF[cc] & CONT2) {
68
+ if (++i >= len) {
69
+ onToken(start, i, false);
70
+ break outer;
71
+ }
72
+ cc = str.charCodeAt(i);
73
+ }
74
+ // Phase 3 – consume trailing newline
75
+ if (cc === 10) {
76
+ i++;
77
+ onToken(start, i, true);
78
+ } else {
79
+ onToken(start, i, false);
80
+ }
81
+ }
82
+ };
15
83
 
16
84
  /**
85
+ * Array-returning variant. Kept as a standalone loop rather than wrapping
86
+ * `eachPotentialToken` with a per-token callback: the callback indirection
87
+ * measurably slows this hot scan (V8 can no longer inline the slice/push),
88
+ * and the two only share the same small, well-tested classification table.
17
89
  * @param {string} str string
18
90
  * @returns {string[] | null} array of string separated by potential tokens
19
91
  */
@@ -22,28 +94,28 @@ const splitIntoPotentialTokens = (str) => {
22
94
  if (len === 0) return null;
23
95
  const results = [];
24
96
  let i = 0;
25
- while (i < len) {
97
+ outer: while (i < len) {
26
98
  const start = i;
27
- block: {
28
- let cc = str.charCodeAt(i);
29
- while (cc !== 10 && cc !== 59 && cc !== 123 && cc !== 125) {
30
- if (++i >= len) break block;
31
- cc = str.charCodeAt(i);
32
- }
33
- while (
34
- cc === 59 ||
35
- cc === 32 ||
36
- cc === 123 ||
37
- cc === 125 ||
38
- cc === 13 ||
39
- cc === 9
40
- ) {
41
- if (++i >= len) break block;
42
- cc = str.charCodeAt(i);
99
+ // Phase 1 – skip regular (non-stop) characters
100
+ let cc = str.charCodeAt(i);
101
+ while (cc > 127 || !(CF[cc] & STOP1)) {
102
+ if (++i >= len) {
103
+ results.push(str.slice(start, i));
104
+ break outer;
43
105
  }
44
- if (cc === 10) {
45
- i++;
106
+ cc = str.charCodeAt(i);
107
+ }
108
+ // Phase 2 – consume delimiter / whitespace run (; { } space \r \t)
109
+ while (cc < 128 && CF[cc] & CONT2) {
110
+ if (++i >= len) {
111
+ results.push(str.slice(start, i));
112
+ break outer;
46
113
  }
114
+ cc = str.charCodeAt(i);
115
+ }
116
+ // Phase 3 – consume trailing newline
117
+ if (cc === 10) {
118
+ i++;
47
119
  }
48
120
  results.push(str.slice(start, i));
49
121
  }
@@ -51,3 +123,4 @@ const splitIntoPotentialTokens = (str) => {
51
123
  };
52
124
 
53
125
  module.exports = splitIntoPotentialTokens;
126
+ module.exports.eachPotentialToken = eachPotentialToken;