webpack 5.108.4 → 5.109.1
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.
- package/bin/webpack.js +12 -0
- package/lib/APIPlugin.js +48 -29
- package/lib/CacheFacade.js +8 -0
- package/lib/ChunkGraph.js +17 -15
- package/lib/ChunkGroup.js +1 -0
- package/lib/CompatibilityPlugin.js +62 -29
- package/lib/Compilation.js +58 -6
- package/lib/ConstPlugin.js +11 -11
- package/lib/ContextModule.js +239 -6
- package/lib/ContextModuleFactory.js +139 -14
- package/lib/DefinePlugin.js +433 -112
- package/lib/Entrypoint.js +1 -1
- package/lib/ExportsInfo.js +22 -5
- package/lib/ExportsInfoApiPlugin.js +2 -2
- package/lib/ExternalModule.js +107 -5
- package/lib/ExternalModuleFactoryPlugin.js +20 -1
- package/lib/FileSystemInfo.js +256 -100
- package/lib/FlagDependencyUsagePlugin.js +33 -12
- package/lib/HotModuleReplacementPlugin.js +18 -15
- package/lib/InitFragment.js +20 -32
- package/lib/Module.js +3 -0
- package/lib/ModuleFilenameHelpers.js +2 -2
- package/lib/ModuleTypeConstants.js +8 -1
- package/lib/MultiCompiler.js +13 -2
- package/lib/MultiWatching.js +1 -0
- package/lib/NodeStuffPlugin.js +51 -33
- package/lib/NormalModule.js +175 -401
- package/lib/NormalModuleFactory.js +76 -2
- package/lib/ProgressPlugin.js +211 -119
- package/lib/ProvidePlugin.js +2 -2
- package/lib/RuntimeGlobals.js +47 -0
- package/lib/RuntimePlugin.js +67 -107
- package/lib/RuntimeTemplate.js +304 -16
- package/lib/Template.js +23 -3
- package/lib/UseStrictPlugin.js +1 -1
- package/lib/WebpackIsIncludedPlugin.js +1 -1
- package/lib/WebpackOptionsApply.js +24 -19
- package/lib/asset/AssetGenerator.js +110 -28
- package/lib/asset/AssetModulesPlugin.js +92 -2
- package/lib/asset/WebManifestGenerator.js +164 -0
- package/lib/asset/WebManifestParser.js +130 -0
- package/lib/async-modules/AwaitDependenciesInitFragment.js +14 -4
- package/lib/async-modules/isGeneratorLowered.js +26 -0
- package/lib/cache/PackFileCacheStrategy.js +9 -3
- package/lib/config/browserslistTargetHandler.js +40 -0
- package/lib/config/defaults.js +219 -29
- package/lib/config/normalization.js +26 -0
- package/lib/config/target.js +9 -1
- package/lib/container/HoistContainerReferencesPlugin.js +47 -55
- package/lib/css/CssGenerator.js +40 -31
- package/lib/css/CssLoadingRuntimeModule.js +5 -1
- package/lib/css/CssModulesPlugin.js +13 -0
- package/lib/css/CssParser.js +729 -313
- package/lib/css/syntax.js +1371 -861
- package/lib/dependencies/AMDDefineDependencyParserPlugin.js +6 -6
- package/lib/dependencies/AMDPlugin.js +2 -2
- package/lib/dependencies/AMDRequireDependenciesBlockParserPlugin.js +13 -13
- package/lib/dependencies/CommonJsDependencyHelpers.js +39 -0
- package/lib/dependencies/CommonJsExportRequireDependency.js +19 -0
- package/lib/dependencies/CommonJsExportsDependency.js +47 -2
- package/lib/dependencies/CommonJsExportsParserPlugin.js +10 -10
- package/lib/dependencies/CommonJsImportsParserPlugin.js +18 -18
- package/lib/dependencies/CommonJsPlugin.js +2 -2
- package/lib/dependencies/CommonJsRequireDependency.js +26 -1
- package/lib/dependencies/CommonJsSelfReferenceDependency.js +32 -1
- package/lib/dependencies/ContextDependencyHelpers.js +3 -3
- package/lib/dependencies/CreateRequireParserPlugin.js +6 -6
- package/lib/dependencies/CssIcssExportDependency.js +162 -71
- package/lib/dependencies/CssUrlDependency.js +35 -4
- package/lib/dependencies/ExternalModuleInitFragmentDependency.js +2 -2
- package/lib/dependencies/HarmonyCompatibilityDependency.js +32 -14
- package/lib/dependencies/HarmonyDetectionParserPlugin.js +43 -13
- package/lib/dependencies/HarmonyEvaluatedImportSpecifierDependency.js +2 -1
- package/lib/dependencies/HarmonyExportDependencyParserPlugin.js +7 -19
- package/lib/dependencies/HarmonyExportImportedSpecifierDependency.js +17 -9
- package/lib/dependencies/HarmonyImportDependency.js +13 -2
- package/lib/dependencies/HarmonyImportDependencyParserPlugin.js +11 -11
- package/lib/dependencies/HarmonyImportSpecifierDependency.js +10 -7
- package/lib/dependencies/HarmonyModulesPlugin.js +6 -0
- package/lib/dependencies/HarmonyTopLevelThisParserPlugin.js +1 -1
- package/lib/dependencies/HtmlEntryDependency.js +797 -172
- package/lib/dependencies/HtmlSourceDependency.js +26 -0
- package/lib/dependencies/ImportDependency.js +43 -1
- package/lib/dependencies/ImportMetaContextDependencyParserPlugin.js +31 -18
- package/lib/dependencies/ImportMetaContextPlugin.js +38 -7
- package/lib/dependencies/ImportMetaGlobDependency.js +81 -0
- package/lib/dependencies/ImportMetaGlobDependencyParserPlugin.js +78 -0
- package/lib/dependencies/ImportMetaGlobHelpers.js +660 -0
- package/lib/dependencies/ImportMetaPlugin.js +532 -180
- package/lib/dependencies/ImportMetaResolveDependency.js +90 -0
- package/lib/dependencies/ImportParserPlugin.js +80 -17
- package/lib/dependencies/ImportPhase.js +10 -8
- package/lib/dependencies/LoaderPlugin.js +5 -1
- package/lib/dependencies/RequireContextDependencyParserPlugin.js +1 -1
- package/lib/dependencies/RequireEnsureDependenciesBlockParserPlugin.js +3 -6
- package/lib/dependencies/RequireIncludeDependencyParserPlugin.js +4 -11
- package/lib/dependencies/SystemPlugin.js +2 -4
- package/lib/dependencies/TopLevelAwaitDependency.js +93 -0
- package/lib/dependencies/URLDependency.js +141 -21
- package/lib/dependencies/{WorkerPlugin.js → WorkerAndWorkletPlugin.js} +324 -112
- package/lib/dependencies/WorkerDependency.js +86 -18
- package/lib/dependencies/WorkletDependency.js +278 -0
- package/lib/dll/DelegatedModuleFactoryPlugin.js +19 -0
- package/lib/errors/EnvironmentNotSupportAsyncWarning.js +1 -1
- package/lib/errors/LoaderLoadingError.js +20 -0
- package/lib/errors/ModuleParseError.js +39 -22
- package/lib/errors/NodeStuffInWebError.js +1 -1
- package/lib/esm/ModuleChunkFormatPlugin.js +47 -14
- package/lib/esm/ModuleChunkLoadingPlugin.js +8 -1
- package/lib/esm/ModuleChunkLoadingRuntimeModule.js +29 -1
- package/lib/hmr/lazyCompilationBackend.js +51 -12
- package/lib/html/HtmlGenerator.js +907 -15
- package/lib/html/HtmlModule.js +2 -2
- package/lib/html/HtmlModulesPlugin.js +610 -166
- package/lib/html/HtmlParser.js +603 -247
- package/lib/html/favicon.svg +1 -0
- package/lib/html/syntax.js +3451 -2762
- package/lib/ids/IdHelpers.js +25 -11
- package/lib/index.js +6 -0
- package/lib/javascript/ArrayPushCallbackChunkFormatPlugin.js +4 -5
- package/lib/javascript/BasicEvaluatedExpression.js +366 -48
- package/lib/javascript/JavascriptGenerator.js +197 -49
- package/lib/javascript/JavascriptModulesPlugin.js +178 -24
- package/lib/javascript/JavascriptParser.js +704 -355
- package/lib/javascript/JavascriptParserHelpers.js +3 -6
- package/lib/javascript/syntax.js +5037 -0
- package/lib/library/ModuleLibraryPlugin.js +38 -1
- package/lib/loaders/LoaderRunner.js +714 -0
- package/lib/loaders/loadLoader.js +100 -0
- package/lib/node/ReadFileCompileAsyncWasmPlugin.js +29 -2
- package/lib/node/ReadFileCompileWasmPlugin.js +24 -2
- package/lib/optimize/AggressiveMergingPlugin.js +19 -15
- package/lib/optimize/ConcatenatedModule.js +139 -31
- package/lib/optimize/FlagIncludedChunksPlugin.js +13 -3
- package/lib/optimize/InnerGraphPlugin.js +50 -30
- package/lib/optimize/ModuleConcatenationPlugin.js +139 -24
- package/lib/optimize/SideEffectsFlagPlugin.js +86 -43
- package/lib/optimize/SplitChunksPlugin.js +146 -44
- package/lib/prefetch/ChunkPrefetchPreloadPlugin.js +15 -0
- package/lib/prefetch/ChunkPreloadTriggerRuntimeModule.js +24 -13
- package/lib/prefetch/ResourceHintPlugin.js +687 -0
- package/lib/prefetch/ResourceHintRuntimeModule.js +149 -0
- package/lib/prefetch/StartupAssetHintRuntimeModule.js +210 -0
- package/lib/prefetch/parseResourceHintOptions.js +103 -0
- package/lib/rules/RuleSetCompiler.js +114 -0
- package/lib/runtime/AsyncModuleGeneratorRuntimeModule.js +56 -0
- package/lib/runtime/AsyncModuleRuntimeModule.js +24 -8
- package/lib/runtime/AutoPublicPathRuntimeModule.js +7 -1
- package/lib/runtime/CommonJsWrapRuntimeModule.js +37 -0
- package/lib/runtime/GetWorkletBootstrapRuntimeModule.js +59 -0
- package/lib/runtime/MakeDeferredNamespaceObjectRuntime.js +204 -8
- package/lib/runtime/SetAnonymousDefaultNameRuntimeModule.js +4 -1
- package/lib/schemes/HttpUriPlugin.js +40 -1
- package/lib/schemes/VirtualUrlPlugin.js +1 -2
- package/lib/serialization/BinaryMiddleware.js +270 -938
- package/lib/serialization/FileMiddleware.js +29 -3
- package/lib/stats/DefaultStatsFactoryPlugin.js +229 -194
- package/lib/stats/DefaultStatsPresetPlugin.js +2 -0
- package/lib/stats/DefaultStatsPrinterPlugin.js +36 -168
- package/lib/stats/StatsFactory.js +46 -17
- package/lib/url/URLParserPlugin.js +66 -13
- package/lib/util/AsyncQueue.js +122 -72
- package/lib/util/SetHelpers.js +1 -1
- package/lib/util/StackedMap.js +122 -48
- package/lib/util/binarySearchBounds.js +2 -2
- package/lib/util/concatenate.js +40 -14
- package/lib/util/createHooksRegistry.js +13 -5
- package/lib/util/dataURL.js +2 -2
- package/lib/util/deterministicGrouping.js +7 -5
- package/lib/util/formatSize.js +6 -2
- package/lib/util/globUtils.js +710 -0
- package/lib/util/identifier.js +23 -6
- package/lib/util/implicitTypeLoaderFallback.js +62 -0
- package/lib/util/internalSerializables.js +88 -71
- package/lib/util/magicComment.js +121 -25
- package/lib/util/numberHash.js +2 -2
- package/lib/wasm-async/AsyncWasmCompileRuntimeModule.js +24 -18
- package/lib/wasm-async/AsyncWasmLoadingRuntimeModule.js +33 -22
- package/lib/wasm-sync/WasmChunkLoadingRuntimeModule.js +79 -16
- package/lib/web/FetchCompileAsyncWasmPlugin.js +19 -0
- package/lib/web/FetchCompileWasmPlugin.js +14 -0
- package/lib/web/JsonpChunkLoadingRuntimeModule.js +2 -0
- package/lib/webpack.js +65 -0
- package/lib/webworker/ImportScriptsChunkLoadingRuntimeModule.js +3 -0
- package/module.d.ts +21 -0
- package/package.json +12 -11
- package/schemas/WebpackOptions.check.js +1 -1
- package/schemas/WebpackOptions.json +759 -36
- package/schemas/plugins/HtmlParserOptions.check.js +1 -1
- package/schemas/plugins/ProgressPlugin.check.js +1 -1
- package/schemas/plugins/ProgressPlugin.json +17 -1
- package/schemas/plugins/container/ContainerReferencePlugin.check.js +1 -1
- package/schemas/plugins/container/ContainerReferencePlugin.json +1 -0
- package/schemas/plugins/container/ExternalsType.check.js +1 -1
- package/schemas/plugins/container/ModuleFederationPlugin.check.js +1 -1
- package/schemas/plugins/container/ModuleFederationPlugin.json +1 -0
- package/schemas/plugins/css/CssAutoOrModuleParserOptions.check.js +1 -1
- package/schemas/plugins/css/CssModuleParserOptions.check.js +1 -1
- package/schemas/plugins/css/CssParserOptions.check.js +1 -1
- package/types.d.ts +1212 -99
package/lib/css/syntax.js
CHANGED
|
@@ -199,6 +199,7 @@ const CC_AT_SIGN = "@".charCodeAt(0);
|
|
|
199
199
|
|
|
200
200
|
const CC_LOW_LINE = "_".charCodeAt(0);
|
|
201
201
|
const CC_LOWER_A = "a".charCodeAt(0);
|
|
202
|
+
const CC_LOWER_D = "d".charCodeAt(0);
|
|
202
203
|
const CC_LOWER_F = "f".charCodeAt(0);
|
|
203
204
|
const CC_LOWER_E = "e".charCodeAt(0);
|
|
204
205
|
const CC_LOWER_U = "u".charCodeAt(0);
|
|
@@ -341,11 +342,15 @@ const _isIdentStartCodePointCC = (cc) =>
|
|
|
341
342
|
/**
|
|
342
343
|
* Spec: ident-code = ident-start / digit / hyphen-minus.
|
|
343
344
|
*/
|
|
344
|
-
//
|
|
345
|
-
//
|
|
346
|
-
//
|
|
347
|
-
//
|
|
348
|
-
|
|
345
|
+
// Full `charCodeAt` range (0..0xFFFF) so the per-code-point ident test is one
|
|
346
|
+
// table load with no `cc < 128` branch — `_consumeAnIdentSequence` runs this on
|
|
347
|
+
// every character of every ident / class / property name (the tokenizer's
|
|
348
|
+
// hottest loop). Every non-ASCII code unit (>= 0x80) is an ident code point per
|
|
349
|
+
// spec, so those default to 1; only the ASCII rows carry real classification.
|
|
350
|
+
// Callers must index with `cc | 0`: EOF (`charCodeAt` → NaN) becomes 0 (NUL,
|
|
351
|
+
// not an ident) — a raw NaN index is an out-of-range access that permanently
|
|
352
|
+
// degrades the load site's IC.
|
|
353
|
+
const _identCharTable = new Uint8Array(0x10000).fill(1);
|
|
349
354
|
for (let i = 0; i < 128; i++) {
|
|
350
355
|
_identCharTable[i] =
|
|
351
356
|
_isLetter(i) || i === CC_LOW_LINE || _isDigit(i) || i === CC_HYPHEN_MINUS
|
|
@@ -356,8 +361,7 @@ for (let i = 0; i < 128; i++) {
|
|
|
356
361
|
* @param {number} cc char code
|
|
357
362
|
* @returns {boolean} true, if cc is an ident-sequence code point
|
|
358
363
|
*/
|
|
359
|
-
const _isIdentCodePoint = (cc) =>
|
|
360
|
-
cc < 128 ? _identCharTable[cc] === 1 : _isIdentStartCodePointCC(cc);
|
|
364
|
+
const _isIdentCodePoint = (cc) => _identCharTable[cc | 0] === 1;
|
|
361
365
|
|
|
362
366
|
/**
|
|
363
367
|
* ASCII case-insensitive equality against a lowercase literal — avoids the
|
|
@@ -377,6 +381,58 @@ const equalsLowerCase = (s, lit) => {
|
|
|
377
381
|
return true;
|
|
378
382
|
};
|
|
379
383
|
|
|
384
|
+
/**
|
|
385
|
+
* Case-sensitive equality of a source range against a literal — no slice.
|
|
386
|
+
* @param {string} input source
|
|
387
|
+
* @param {number} start range start
|
|
388
|
+
* @param {number} end range end (exclusive)
|
|
389
|
+
* @param {string} lit literal to match
|
|
390
|
+
* @returns {boolean} true when the range equals `lit`
|
|
391
|
+
*/
|
|
392
|
+
const rangeEquals = (input, start, end, lit) =>
|
|
393
|
+
end - start === lit.length && input.startsWith(lit, start);
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* ASCII case-insensitive equality of a source range against a lowercase ASCII literal — no slice.
|
|
397
|
+
* @param {string} input source
|
|
398
|
+
* @param {number} start range start
|
|
399
|
+
* @param {number} end range end (exclusive)
|
|
400
|
+
* @param {string} lit lowercase ASCII literal to match
|
|
401
|
+
* @returns {boolean} true when the range equals `lit` ignoring ASCII case
|
|
402
|
+
*/
|
|
403
|
+
const rangeEqualsLowerCase = (input, start, end, lit) => {
|
|
404
|
+
if (end - start !== lit.length) return false;
|
|
405
|
+
for (let i = 0; i < lit.length; i++) {
|
|
406
|
+
let c = input.charCodeAt(start + i);
|
|
407
|
+
if (c >= CC_UPPER_A && c <= CC_UPPER_Z) c |= 0x20;
|
|
408
|
+
if (c !== lit.charCodeAt(i)) return false;
|
|
409
|
+
}
|
|
410
|
+
return true;
|
|
411
|
+
};
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* `s.toLowerCase()` that returns `s` itself (no allocation) when it can't
|
|
415
|
+
* change — no ASCII uppercase and no non-ASCII (whose Unicode case mapping is
|
|
416
|
+
* left to the real `toLowerCase`).
|
|
417
|
+
* @param {string} s string
|
|
418
|
+
* @returns {string} lowercased string
|
|
419
|
+
*/
|
|
420
|
+
const toLowerCaseIfNeeded = (s) => {
|
|
421
|
+
for (let i = 0; i < s.length; i++) {
|
|
422
|
+
const c = s.charCodeAt(i);
|
|
423
|
+
if ((c >= CC_UPPER_A && c <= CC_UPPER_Z) || c > 127) return s.toLowerCase();
|
|
424
|
+
}
|
|
425
|
+
return s;
|
|
426
|
+
};
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* A custom property name (`<dashed-ident>`): a `--`-prefixed identifier other than bare `--`.
|
|
430
|
+
* @param {string} identifier identifier
|
|
431
|
+
* @returns {boolean} true when identifier is dashed, otherwise false
|
|
432
|
+
*/
|
|
433
|
+
const isDashedIdentifier = (identifier) =>
|
|
434
|
+
identifier.startsWith("--") && identifier.length >= 3;
|
|
435
|
+
|
|
380
436
|
/**
|
|
381
437
|
* Consume an escaped code point.
|
|
382
438
|
* @param {string} input input
|
|
@@ -394,7 +450,8 @@ const _consumeAnEscapedCodePoint = (input, pos) => {
|
|
|
394
450
|
if (pos === input.length) return pos;
|
|
395
451
|
if (_isHexDigit(cc)) {
|
|
396
452
|
for (let i = 0; i < 5; i++) {
|
|
397
|
-
if (_isHexDigit(input.charCodeAt(pos)))
|
|
453
|
+
if (!_isHexDigit(input.charCodeAt(pos))) break;
|
|
454
|
+
pos++;
|
|
398
455
|
}
|
|
399
456
|
const trail = input.charCodeAt(pos);
|
|
400
457
|
if (_isWhiteSpace(trail)) {
|
|
@@ -480,12 +537,13 @@ const _ifThreeCodePointsWouldStartANumber = (input, pos, f, s, t) => {
|
|
|
480
537
|
const _consumeAnIdentSequence = (input, pos) => {
|
|
481
538
|
// Hot loop (every ident, at-keyword, hash, function name, unit). Both checks
|
|
482
539
|
// are inlined from `_isIdentCodePoint` / `_ifTwoCodePointsAreValidEscape`: the
|
|
483
|
-
//
|
|
484
|
-
// following code point only when `cc` is a `\` (rare)
|
|
540
|
+
// ident test is a single full-range table load (no `cc < 128` branch), and the
|
|
541
|
+
// escape test reads the following code point only when `cc` is a `\` (rare)
|
|
542
|
+
// instead of eagerly.
|
|
485
543
|
for (;;) {
|
|
486
|
-
const cc = input.charCodeAt(pos);
|
|
544
|
+
const cc = input.charCodeAt(pos) | 0;
|
|
487
545
|
pos++;
|
|
488
|
-
if (
|
|
546
|
+
if (_identCharTable[cc] === 1) {
|
|
489
547
|
continue;
|
|
490
548
|
}
|
|
491
549
|
if (cc === CC_REVERSE_SOLIDUS && !_isNewline(input.charCodeAt(pos))) {
|
|
@@ -625,6 +683,15 @@ function consumeSpace(input, pos, out) {
|
|
|
625
683
|
return fill(out, TT_WHITESPACE, start, pos);
|
|
626
684
|
}
|
|
627
685
|
|
|
686
|
+
// Sticky fast-forward classes: a native run-skip over the ordinary characters of
|
|
687
|
+
// a string / url token, so long values (data: URIs, base64) don't cost one JS
|
|
688
|
+
// char read each. The negated classes match exactly the per-char terminators the
|
|
689
|
+
// loops below handle (quotes / backslash / newlines for strings; plus `(`, `)`,
|
|
690
|
+
// whitespace and non-printable code points for urls).
|
|
691
|
+
const _STRING_SAFE = /[^"'\\\n\r\f]+/y;
|
|
692
|
+
// eslint-disable-next-line no-control-regex -- url terminators include the control range and DEL (they make a bad-url)
|
|
693
|
+
const _URL_SAFE = /[^\u0000-\u0020\u007F"'()\\]+/y;
|
|
694
|
+
|
|
628
695
|
/**
|
|
629
696
|
* Consume a string token. Caller advanced past the opening quote so
|
|
630
697
|
* `pos - 1` holds the ending code point and `pos - 1` is the start.
|
|
@@ -637,6 +704,8 @@ function consumeAStringToken(input, pos, out) {
|
|
|
637
704
|
const start = pos - 1;
|
|
638
705
|
const endingCodePoint = input.charCodeAt(pos - 1);
|
|
639
706
|
for (;;) {
|
|
707
|
+
_STRING_SAFE.lastIndex = pos;
|
|
708
|
+
if (_STRING_SAFE.test(input)) pos = _STRING_SAFE.lastIndex;
|
|
640
709
|
if (pos === input.length) {
|
|
641
710
|
return fill(out, TT_STRING, start, pos);
|
|
642
711
|
}
|
|
@@ -700,17 +769,33 @@ function consumeNumberSign(input, pos, out) {
|
|
|
700
769
|
* @returns {MutableToken | undefined} the resulting token, or undefined at EOF
|
|
701
770
|
*/
|
|
702
771
|
function consumeHyphenMinus(input, pos, out) {
|
|
703
|
-
|
|
772
|
+
// Read the two lookahead code points once; the lead is the known `-`.
|
|
773
|
+
const second = input.charCodeAt(pos);
|
|
774
|
+
const third = input.charCodeAt(pos + 1);
|
|
775
|
+
if (
|
|
776
|
+
_ifThreeCodePointsWouldStartANumber(
|
|
777
|
+
input,
|
|
778
|
+
pos,
|
|
779
|
+
CC_HYPHEN_MINUS,
|
|
780
|
+
second,
|
|
781
|
+
third
|
|
782
|
+
)
|
|
783
|
+
) {
|
|
704
784
|
pos--;
|
|
705
785
|
return consumeANumericToken(input, pos, out);
|
|
706
786
|
}
|
|
707
|
-
if (
|
|
708
|
-
input.charCodeAt(pos) === CC_HYPHEN_MINUS &&
|
|
709
|
-
input.charCodeAt(pos + 1) === CC_GREATER_THAN_SIGN
|
|
710
|
-
) {
|
|
787
|
+
if (second === CC_HYPHEN_MINUS && third === CC_GREATER_THAN_SIGN) {
|
|
711
788
|
return fill(out, TT_CDC, pos - 1, pos + 2);
|
|
712
789
|
}
|
|
713
|
-
if (
|
|
790
|
+
if (
|
|
791
|
+
_ifThreeCodePointsWouldStartAnIdentSequence(
|
|
792
|
+
input,
|
|
793
|
+
pos,
|
|
794
|
+
CC_HYPHEN_MINUS,
|
|
795
|
+
second,
|
|
796
|
+
third
|
|
797
|
+
)
|
|
798
|
+
) {
|
|
714
799
|
pos--;
|
|
715
800
|
return consumeAnIdentLikeToken(input, pos, out);
|
|
716
801
|
}
|
|
@@ -800,8 +885,10 @@ function consumeAUrlToken(input, pos, fnStart, out) {
|
|
|
800
885
|
const contentStart = pos;
|
|
801
886
|
out.contentStart = contentStart;
|
|
802
887
|
for (;;) {
|
|
888
|
+
_URL_SAFE.lastIndex = pos;
|
|
889
|
+
if (_URL_SAFE.test(input)) pos = _URL_SAFE.lastIndex;
|
|
803
890
|
if (pos === input.length) {
|
|
804
|
-
out.contentEnd = pos
|
|
891
|
+
out.contentEnd = pos;
|
|
805
892
|
return fill(out, TT_URL, fnStart, pos);
|
|
806
893
|
}
|
|
807
894
|
const cc = input.charCodeAt(pos);
|
|
@@ -999,7 +1086,11 @@ const HC_AT_SIGN = 9;
|
|
|
999
1086
|
const HC_REVERSE_SOLIDUS = 10;
|
|
1000
1087
|
const HC_DIGIT = 11;
|
|
1001
1088
|
const HC_IDENT = 12;
|
|
1002
|
-
|
|
1089
|
+
// Full `charCodeAt` range so `consumeAToken` dispatches with one table load and
|
|
1090
|
+
// no `cc < 128` branch. Every non-ASCII code point (>= 0x80) is an ident-start
|
|
1091
|
+
// lead per §4, so those rows are seeded to `HC_IDENT`; the ASCII rows below
|
|
1092
|
+
// overwrite 0..127 with their real class.
|
|
1093
|
+
const _charClass = new Uint8Array(0x10000).fill(HC_IDENT, 128);
|
|
1003
1094
|
const _singleTT = new Uint8Array(128);
|
|
1004
1095
|
_singleTT[CC_LEFT_PARENTHESIS] = TT_LEFT_PARENTHESIS;
|
|
1005
1096
|
_singleTT[CC_RIGHT_PARENTHESIS] = TT_RIGHT_PARENTHESIS;
|
|
@@ -1054,7 +1145,7 @@ for (let i = 0; i < 128; i++) {
|
|
|
1054
1145
|
function consumeAToken(input, pos, cc, out) {
|
|
1055
1146
|
// `u` / `U` would start a unicode-range token in the spec; those are not
|
|
1056
1147
|
// produced, so they map to HC_IDENT and fall through to ident-like.
|
|
1057
|
-
switch (
|
|
1148
|
+
switch (_charClass[cc]) {
|
|
1058
1149
|
// Run of whitespace → one <whitespace-token>.
|
|
1059
1150
|
case HC_WHITESPACE:
|
|
1060
1151
|
return consumeSpace(input, pos, out);
|
|
@@ -1211,78 +1302,23 @@ const {
|
|
|
1211
1302
|
} = NodeType;
|
|
1212
1303
|
|
|
1213
1304
|
/**
|
|
1214
|
-
* Base AST node
|
|
1215
|
-
*
|
|
1216
|
-
* of the source slice
|
|
1217
|
-
* shared `LocConverter
|
|
1218
|
-
*
|
|
1305
|
+
* Base AST node — the property-accessor view the `parseA*` entry points return
|
|
1306
|
+
* (see `_makeReader`). Every concrete node carries the `[start, end)` byte
|
|
1307
|
+
* `range` of the source slice it covers; `loc` is computed on demand from the
|
|
1308
|
+
* shared `LocConverter`, so line/column conversion is only paid when a consumer
|
|
1309
|
+
* needs it. The concrete node typedefs below extend this via `&`.
|
|
1310
|
+
*
|
|
1311
|
+
* Inside the parser a node ref is an integer id into the columns; the reader
|
|
1312
|
+
* exposes this property shape over a retained snapshot of those columns.
|
|
1313
|
+
* @typedef {object} Node
|
|
1314
|
+
* @property {number} type node-type discriminator
|
|
1315
|
+
* @property {number} start byte offset of the node's first code point
|
|
1316
|
+
* @property {number} end byte offset just past the node's last code point
|
|
1317
|
+
* @property {[number, number]} range the `[start, end)` byte range
|
|
1318
|
+
* @property {{ start: { line: number, column: number }, end: { line: number, column: number } }} loc source location (1-based line, 0-based column)
|
|
1319
|
+
* @property {() => string} toString source slice for this node
|
|
1320
|
+
* @property {string} unescapedName name with CSS escapes resolved (name-bearing nodes only)
|
|
1219
1321
|
*/
|
|
1220
|
-
class Node {
|
|
1221
|
-
/**
|
|
1222
|
-
* @param {number} type node type discriminator
|
|
1223
|
-
* @param {number} start byte offset of the node's first code point
|
|
1224
|
-
* @param {number} end byte offset just past the node's last code point
|
|
1225
|
-
* @param {LocConverter} locConverter shared loc converter
|
|
1226
|
-
*/
|
|
1227
|
-
constructor(type, start, end, locConverter) {
|
|
1228
|
-
/** @type {number} */
|
|
1229
|
-
this.type = type;
|
|
1230
|
-
// Byte range as two inline fields rather than a `[start, end]` array —
|
|
1231
|
-
// one fewer allocation per node and ~56 bytes lighter (×100k+ nodes).
|
|
1232
|
-
/** @type {number} */
|
|
1233
|
-
this.start = start;
|
|
1234
|
-
/** @type {number} */
|
|
1235
|
-
this.end = end;
|
|
1236
|
-
/** @type {LocConverter} */
|
|
1237
|
-
this._locConverter = locConverter;
|
|
1238
|
-
}
|
|
1239
|
-
|
|
1240
|
-
/**
|
|
1241
|
-
* The `[start, end)` byte range as a tuple — compatibility view over
|
|
1242
|
-
* `start` / `end` (builds the array lazily; hot code reads the
|
|
1243
|
-
* fields directly).
|
|
1244
|
-
* @returns {[number, number]} the byte range
|
|
1245
|
-
*/
|
|
1246
|
-
get range() {
|
|
1247
|
-
return [this.start, this.end];
|
|
1248
|
-
}
|
|
1249
|
-
|
|
1250
|
-
get loc() {
|
|
1251
|
-
const lc = this._locConverter;
|
|
1252
|
-
// `LocConverter#get` mutates and returns the converter itself, so we
|
|
1253
|
-
// must snapshot `line`/`column` between the two calls.
|
|
1254
|
-
const s = lc.get(this.start);
|
|
1255
|
-
const sl = s.line;
|
|
1256
|
-
const sc = s.column;
|
|
1257
|
-
const e = lc.get(this.end);
|
|
1258
|
-
return {
|
|
1259
|
-
start: { line: sl, column: sc },
|
|
1260
|
-
end: { line: e.line, column: e.column }
|
|
1261
|
-
};
|
|
1262
|
-
}
|
|
1263
|
-
|
|
1264
|
-
/**
|
|
1265
|
-
* Serialize back to source — re-slices the original input (zero-alloc for
|
|
1266
|
-
* untouched nodes).
|
|
1267
|
-
* @returns {string} the source slice for this node
|
|
1268
|
-
*/
|
|
1269
|
-
toString() {
|
|
1270
|
-
return this._locConverter._input.slice(this.start, this.end);
|
|
1271
|
-
}
|
|
1272
|
-
|
|
1273
|
-
/**
|
|
1274
|
-
* For name-bearing nodes (function / at-rule): the `name` with CSS escapes
|
|
1275
|
-
* resolved, for case-insensitive keyword matching (`\75 rl` → `url`). Computed
|
|
1276
|
-
* on read via `unescapeIdentifier`'s no-escape fast path. Callers without a
|
|
1277
|
-
* `name` must not read this.
|
|
1278
|
-
* @returns {string} the unescaped name
|
|
1279
|
-
*/
|
|
1280
|
-
get unescapedName() {
|
|
1281
|
-
return unescapeIdentifier(
|
|
1282
|
-
/** @type {{ name: string }} */ (/** @type {unknown} */ (this)).name
|
|
1283
|
-
);
|
|
1284
|
-
}
|
|
1285
|
-
}
|
|
1286
1322
|
|
|
1287
1323
|
/**
|
|
1288
1324
|
* @param {string} s numeric text
|
|
@@ -1301,175 +1337,15 @@ const _typeFlagOf = (s) =>
|
|
|
1301
1337
|
s.includes(".") || s.includes("e") || s.includes("E") ? "number" : "integer";
|
|
1302
1338
|
|
|
1303
1339
|
/**
|
|
1304
|
-
* Leaf token node
|
|
1340
|
+
* Leaf token node (property-accessor view) — `value` is the raw source slice
|
|
1305
1341
|
* (identifier text, quoted string including quotes, a dimension's full `123px`,
|
|
1306
|
-
*
|
|
1307
|
-
* `NumberToken` / `DimensionToken`
|
|
1308
|
-
*
|
|
1309
|
-
*
|
|
1310
|
-
*
|
|
1311
|
-
*
|
|
1312
|
-
* token keeps a single object shape so the walker's `node.type` dispatch stays
|
|
1313
|
-
* monomorphic. They are only meaningful on the matching token type.
|
|
1342
|
+
* …; hash / at-keyword drop their `#` / `@` prefix, url uses its content range).
|
|
1343
|
+
* The `NumberToken` / `HashToken` / `UrlToken` / `DimensionToken` typedefs below
|
|
1344
|
+
* narrow the value accessors. `numericValue` / `typeFlag` / `sign` / `unit` are
|
|
1345
|
+
* derived from the source on read and are only meaningful on the matching token
|
|
1346
|
+
* type; `contentStart` / `contentEnd` mark a url token's inner content range.
|
|
1347
|
+
* @typedef {Node & { value: string, unescaped: string, numericValue: number, typeFlag: "integer" | "number" | "id" | "unrestricted", sign: "+" | "-" | "", unit: string, contentStart: number, contentEnd: number }} Token
|
|
1314
1348
|
*/
|
|
1315
|
-
class Token extends Node {
|
|
1316
|
-
/**
|
|
1317
|
-
* @param {number} type node type
|
|
1318
|
-
* @param {number} start byte offset of the token's first code point
|
|
1319
|
-
* @param {number} end byte offset just past the token's last code point
|
|
1320
|
-
* @param {LocConverter} locConverter shared loc converter
|
|
1321
|
-
*/
|
|
1322
|
-
// No own fields: a leaf token is exactly a `Node` plus the value getters
|
|
1323
|
-
// below. `value` is derived from the byte range on read instead of cached in
|
|
1324
|
-
// a `_value` slot — most tokens (whitespace, punctuation) never read it, and
|
|
1325
|
-
// dropping the slot is ~8 bytes saved on every token (the bulk of all nodes).
|
|
1326
|
-
// hash / at-keyword strip their `#` / `@` prefix; url uses its content range
|
|
1327
|
-
// (`contentStart` / `contentEnd`, the token's only own fields).
|
|
1328
|
-
|
|
1329
|
-
/**
|
|
1330
|
-
* @returns {string} the token's value (raw source slice unless overridden)
|
|
1331
|
-
*/
|
|
1332
|
-
get value() {
|
|
1333
|
-
const input = this._locConverter._input;
|
|
1334
|
-
const type = this.type;
|
|
1335
|
-
// hash (`#name` → `name`) and at-keyword (`@name` → `name`) drop one char.
|
|
1336
|
-
if (type === T_HASH || type === T_AT_KEYWORD) {
|
|
1337
|
-
return input.slice(this.start + 1, this.end);
|
|
1338
|
-
}
|
|
1339
|
-
if (type === T_URL) {
|
|
1340
|
-
const u = /** @type {UrlToken} */ (/** @type {unknown} */ (this));
|
|
1341
|
-
return input.slice(u.contentStart, u.contentEnd);
|
|
1342
|
-
}
|
|
1343
|
-
return input.slice(this.start, this.end);
|
|
1344
|
-
}
|
|
1345
|
-
|
|
1346
|
-
/**
|
|
1347
|
-
* The token's value with CSS escapes resolved (`\2d` → `-`, `\75 rl` → `url`),
|
|
1348
|
-
* per https://www.w3.org/TR/css-syntax-3/#consume-escaped-code-point — the
|
|
1349
|
-
* form to match keywords / export as a CSS-Modules name against. For a string
|
|
1350
|
-
* token it is the content between the quotes (the spec string value). Computed
|
|
1351
|
-
* on read; `unescapeIdentifier` fast-returns the value unchanged when it has no
|
|
1352
|
-
* escapes (the common case), so nothing is stored per token.
|
|
1353
|
-
* @returns {string} the unescaped value
|
|
1354
|
-
*/
|
|
1355
|
-
get unescaped() {
|
|
1356
|
-
const v = this.value;
|
|
1357
|
-
// A string token's `value` carries its delimiting quotes; its value is the content between them.
|
|
1358
|
-
return this.type === T_STRING
|
|
1359
|
-
? unescapeIdentifier(v.slice(1, -1))
|
|
1360
|
-
: unescapeIdentifier(v);
|
|
1361
|
-
}
|
|
1362
|
-
|
|
1363
|
-
/**
|
|
1364
|
-
* Parsed numeric value (number / percentage / dimension tokens). Derived from
|
|
1365
|
-
* `value` on access — the `%` is dropped for percentages and the unit for
|
|
1366
|
-
* dimensions (split with `_consumeANumber`, recomputed here so nothing is
|
|
1367
|
-
* stored per token).
|
|
1368
|
-
* @returns {number} the parsed numeric value
|
|
1369
|
-
*/
|
|
1370
|
-
get numericValue() {
|
|
1371
|
-
const v = this.value;
|
|
1372
|
-
if (this.type === T_DIMENSION) {
|
|
1373
|
-
return Number(v.slice(0, _consumeANumber(v, 0)));
|
|
1374
|
-
}
|
|
1375
|
-
if (this.type === T_PERCENTAGE) return Number(v.slice(0, -1));
|
|
1376
|
-
return Number(v);
|
|
1377
|
-
}
|
|
1378
|
-
|
|
1379
|
-
/**
|
|
1380
|
-
* Spec type flag. For number / dimension tokens it's "integer" / "number"
|
|
1381
|
-
* (derived from `value`); for hash tokens it's "id" / "unrestricted" (re-derived
|
|
1382
|
-
* from the source). The two senses share the name in the spec; both are computed
|
|
1383
|
-
* on read so every leaf token keeps the same object shape (no own `typeFlag`).
|
|
1384
|
-
* @returns {"integer" | "number" | "id" | "unrestricted"} the spec type flag
|
|
1385
|
-
*/
|
|
1386
|
-
get typeFlag() {
|
|
1387
|
-
if (this.type === T_HASH) {
|
|
1388
|
-
// Re-derive id-ness from the source (whether the name after `#` starts an
|
|
1389
|
-
// ident sequence) rather than storing an `_isId` slot — keeps hash tokens
|
|
1390
|
-
// the same shape as every other leaf token. Matches the lexer's
|
|
1391
|
-
// `consumeNumberSign`, which sets `isId` from the same check.
|
|
1392
|
-
const input = this._locConverter._input;
|
|
1393
|
-
const p = this.start + 1;
|
|
1394
|
-
return _ifThreeCodePointsWouldStartAnIdentSequence(
|
|
1395
|
-
input,
|
|
1396
|
-
p,
|
|
1397
|
-
input.charCodeAt(p),
|
|
1398
|
-
input.charCodeAt(p + 1),
|
|
1399
|
-
input.charCodeAt(p + 2)
|
|
1400
|
-
)
|
|
1401
|
-
? "id"
|
|
1402
|
-
: "unrestricted";
|
|
1403
|
-
}
|
|
1404
|
-
const v = this.value;
|
|
1405
|
-
return _typeFlagOf(
|
|
1406
|
-
this.type === T_DIMENSION ? v.slice(0, _consumeANumber(v, 0)) : v
|
|
1407
|
-
);
|
|
1408
|
-
}
|
|
1409
|
-
|
|
1410
|
-
/**
|
|
1411
|
-
* @returns {"+" | "-" | ""} the spec sign (number / percentage / dimension tokens)
|
|
1412
|
-
*/
|
|
1413
|
-
get sign() {
|
|
1414
|
-
return _signOf(this.value);
|
|
1415
|
-
}
|
|
1416
|
-
|
|
1417
|
-
/**
|
|
1418
|
-
* @returns {string} the unit, lower-cased per spec (dimension tokens)
|
|
1419
|
-
*/
|
|
1420
|
-
get unit() {
|
|
1421
|
-
const v = this.value;
|
|
1422
|
-
return v.slice(_consumeANumber(v, 0)).toLowerCase();
|
|
1423
|
-
}
|
|
1424
|
-
}
|
|
1425
|
-
|
|
1426
|
-
/**
|
|
1427
|
-
* The non-leaf `Node` subclass: functions, simple blocks, declarations, at-rules
|
|
1428
|
-
* and qualified rules. It declares the union of every container field up front so
|
|
1429
|
-
* all five share **one** hidden class — the consume algorithms only overwrite the
|
|
1430
|
-
* slots relevant to their type, never adding a property, so the shape never
|
|
1431
|
-
* transitions. This caps the shape count: the walker's hot `.type` / `.value` /
|
|
1432
|
-
* `.prelude` loads otherwise see eight distinct node maps (five container types
|
|
1433
|
-
* plus the token / hash / url token maps), tipping V8's inline cache into the
|
|
1434
|
-
* slow megamorphic path; folding the five containers into one leaves four maps
|
|
1435
|
-
* (token, hash, url, container) — within the polymorphic limit, so those loads
|
|
1436
|
-
* stay inline-cached. Unused-for-the-type slots keep their defaults (the field
|
|
1437
|
-
* typedefs below document which fields each type uses). The stylesheet node is
|
|
1438
|
-
* rare (one per parse) and stays a bare `Node`.
|
|
1439
|
-
*/
|
|
1440
|
-
class Container extends Node {
|
|
1441
|
-
/**
|
|
1442
|
-
* @param {number} type node type
|
|
1443
|
-
* @param {number} start byte offset of the node's first code point
|
|
1444
|
-
* @param {number} end byte offset just past the node's last code point
|
|
1445
|
-
* @param {LocConverter} locConverter shared loc converter
|
|
1446
|
-
*/
|
|
1447
|
-
constructor(type, start, end, locConverter) {
|
|
1448
|
-
super(type, start, end, locConverter);
|
|
1449
|
-
/** @type {string} name (function / at-rule / declaration) */
|
|
1450
|
-
this.name = "";
|
|
1451
|
-
/** @type {number} */
|
|
1452
|
-
this.nameStart = start;
|
|
1453
|
-
/** @type {number} */
|
|
1454
|
-
this.nameEnd = start;
|
|
1455
|
-
/** @type {ComponentValue[] | null} component values (function / block / declaration) */
|
|
1456
|
-
this.value = null;
|
|
1457
|
-
/** @type {ComponentValue[] | null} prelude (at-rule / qualified rule) */
|
|
1458
|
-
this.prelude = null;
|
|
1459
|
-
/** @type {Declaration[] | null} */
|
|
1460
|
-
this.declarations = null;
|
|
1461
|
-
/** @type {Rule[] | null} */
|
|
1462
|
-
this.childRules = null;
|
|
1463
|
-
/** @type {number} `{` start offset, or -1 (at-rule / qualified rule) */
|
|
1464
|
-
this.blockStart = -1;
|
|
1465
|
-
/** @type {number} `}` end offset, or -1 (at-rule / qualified rule) */
|
|
1466
|
-
this.blockEnd = -1;
|
|
1467
|
-
/** @type {boolean} stripped `!important` (declaration) */
|
|
1468
|
-
this.important = false;
|
|
1469
|
-
/** @type {SimpleBlockToken | undefined} opening char (simple block) */
|
|
1470
|
-
this.token = undefined;
|
|
1471
|
-
}
|
|
1472
|
-
}
|
|
1473
1349
|
|
|
1474
1350
|
/**
|
|
1475
1351
|
* Number token (`123`, `-1.5`, `+2e3`). `value` is the raw source slice (the spec's "value"); `numericValue` / `typeFlag` / `sign` are lazy getters derived from it (see `Token`).
|
|
@@ -1568,10 +1444,10 @@ class Container extends Node {
|
|
|
1568
1444
|
* @typedef {Node & { rules: Rule[] }} Stylesheet
|
|
1569
1445
|
*/
|
|
1570
1446
|
|
|
1571
|
-
// Lexer-token-type → AST-node-type map. A single
|
|
1572
|
-
//
|
|
1573
|
-
//
|
|
1574
|
-
//
|
|
1447
|
+
// Lexer-token-type → AST-node-type map. A single `_makeLeaf` call site (vs a
|
|
1448
|
+
// ~20-case switch with an alloc in each arm) keeps V8 on the fast monomorphic
|
|
1449
|
+
// path — the switch form showed up as generic stubs in profiles. URL is the one
|
|
1450
|
+
// type with extra own state, handled first.
|
|
1575
1451
|
const _ttToNodeType = new Uint8Array(27);
|
|
1576
1452
|
_ttToNodeType[TT_WHITESPACE] = T_WHITESPACE;
|
|
1577
1453
|
_ttToNodeType[TT_IDENTIFIER] = T_IDENT;
|
|
@@ -1593,19 +1469,14 @@ _ttToNodeType[TT_RIGHT_CURLY_BRACKET] = T_RIGHT_CURLY_BRACKET;
|
|
|
1593
1469
|
_ttToNodeType[TT_CDO] = T_CDO;
|
|
1594
1470
|
_ttToNodeType[TT_CDC] = T_CDC;
|
|
1595
1471
|
|
|
1596
|
-
// === AST construction
|
|
1597
|
-
//
|
|
1598
|
-
//
|
|
1599
|
-
//
|
|
1600
|
-
//
|
|
1601
|
-
//
|
|
1602
|
-
// the
|
|
1603
|
-
//
|
|
1604
|
-
// backends (a node ref is an object or an integer index); only node creation /
|
|
1605
|
-
// field access differs, so only those ops are swapped.
|
|
1606
|
-
|
|
1607
|
-
// Current loc converter for the active parse (object backend reads it).
|
|
1608
|
-
let _lc = /** @type {LocConverter} */ (/** @type {unknown} */ (null));
|
|
1472
|
+
// === AST construction ===
|
|
1473
|
+
// Nodes live in one struct-of-arrays node store: a node ref is an integer id
|
|
1474
|
+
// into parallel typed-array columns, so per-node allocation is avoided entirely.
|
|
1475
|
+
// The consume algorithms build nodes through the `_make*` / `_set*` primitives
|
|
1476
|
+
// below, which write those columns directly. The streaming `grammar` walks each
|
|
1477
|
+
// top-level node and recycles the columns; the `parseA*` entry points instead
|
|
1478
|
+
// retain the columns as a snapshot and hand back property-accessor nodes over it
|
|
1479
|
+
// (see `_makeReader`). Child lists are plain arrays of node ids in both modes.
|
|
1609
1480
|
|
|
1610
1481
|
// Active skip state (from `CssProcessOptions.skip`), applied by the grammar.
|
|
1611
1482
|
// `_skipTypes` is indexed by `NodeType` (1 = skip): drop that component-value
|
|
@@ -1614,8 +1485,15 @@ let _lc = /** @type {LocConverter} */ (/** @type {unknown} */ (null));
|
|
|
1614
1485
|
// / functions kept, so `url()` in a selector or `@import url(…)` still resolves).
|
|
1615
1486
|
// A skipped node is still tokenized (positions stay correct) but never pushed,
|
|
1616
1487
|
// so it is never walked or read — the caller must only skip what nothing reads.
|
|
1617
|
-
// `
|
|
1488
|
+
// `parseA*` leave these at their no-skip defaults so they build the full tree.
|
|
1618
1489
|
const _NO_SKIP_TYPES = new Uint8Array(32);
|
|
1490
|
+
// Shared frozen empty list for block bodies with no decls / no child rules (the
|
|
1491
|
+
// common case — most rules carry only declarations). Every consumer reads these
|
|
1492
|
+
// lists read-only and null-guards, so one immutable instance replaces ~one empty
|
|
1493
|
+
// array allocation per rule; frozen so any errant push fails loud.
|
|
1494
|
+
const _EMPTY_LIST = /** @type {Rule[]} */ (
|
|
1495
|
+
/** @type {unknown} */ (Object.freeze([]))
|
|
1496
|
+
);
|
|
1619
1497
|
/** @type {Uint8Array} */
|
|
1620
1498
|
let _skipTypes = _NO_SKIP_TYPES;
|
|
1621
1499
|
// Fast-path flag: true only when a real skip set is active, so the (dominant)
|
|
@@ -1624,278 +1502,255 @@ let _skipActive = false;
|
|
|
1624
1502
|
let _skipSelectorPrelude = false;
|
|
1625
1503
|
let _skipAtRulePrelude = false;
|
|
1626
1504
|
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
/** @type {
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
/** @type {
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
/** @type {(r: Node, blockStart: number, blockEnd: number) => void} */
|
|
1640
|
-
let _setBlock;
|
|
1641
|
-
/** @type {(r: Node) => void} */
|
|
1642
|
-
let _setImportant;
|
|
1643
|
-
/** @type {(r: Node, ch: SimpleBlockToken) => void} */
|
|
1644
|
-
let _setToken;
|
|
1645
|
-
/** @type {(r: Node, list: Node[]) => void} */
|
|
1646
|
-
let _setValue;
|
|
1647
|
-
/** @type {(r: Node, list: Node[]) => void} */
|
|
1648
|
-
let _setPrelude;
|
|
1649
|
-
/** @type {(r: Node, decls: Node[], childRules: Node[]) => void} */
|
|
1650
|
-
let _setBody;
|
|
1651
|
-
/** @type {(r: Node, list: Node[]) => void} */
|
|
1652
|
-
let _setRules;
|
|
1653
|
-
/** @type {(r: Node) => number} */
|
|
1654
|
-
let _nType;
|
|
1655
|
-
/** @type {(r: Node) => number} */
|
|
1656
|
-
let _nStart;
|
|
1657
|
-
/** @type {(r: Node) => string} */
|
|
1658
|
-
let _nValue;
|
|
1659
|
-
/** @type {(r: Node) => SimpleBlockToken} */
|
|
1660
|
-
let _nToken;
|
|
1661
|
-
|
|
1662
|
-
// Child lists are plain arrays in every backend; refs are pushed by value.
|
|
1663
|
-
const _list = () => /** @type {Node[]} */ ([]);
|
|
1664
|
-
|
|
1665
|
-
// -- object backend: builds the retainable class-instance tree --
|
|
1666
|
-
/** @type {typeof _mkLeaf} */
|
|
1667
|
-
const _objLeaf = (type, start, end) => new Token(type, start, end, _lc);
|
|
1668
|
-
/** @type {typeof _mkUrl} */
|
|
1669
|
-
const _objUrl = (start, end, cs, ce) => {
|
|
1670
|
-
const u = /** @type {UrlToken} */ (new Token(T_URL, start, end, _lc));
|
|
1671
|
-
u.contentStart = cs;
|
|
1672
|
-
u.contentEnd = ce;
|
|
1673
|
-
return u;
|
|
1674
|
-
};
|
|
1675
|
-
/** @type {typeof _mkContainer} */
|
|
1676
|
-
const _objContainer = (type, start, end) =>
|
|
1677
|
-
new Container(type, start, end, _lc);
|
|
1678
|
-
/** @type {typeof _mkStylesheet} */
|
|
1679
|
-
const _objStylesheet = (start) => {
|
|
1680
|
-
const s = /** @type {Stylesheet} */ (
|
|
1681
|
-
new Node(T_STYLESHEET, start, start, _lc)
|
|
1682
|
-
);
|
|
1683
|
-
s.rules = [];
|
|
1684
|
-
return s;
|
|
1685
|
-
};
|
|
1686
|
-
/** @param {LocConverter} lc loc converter for this parse */
|
|
1687
|
-
const useObjectBackend = (lc) => {
|
|
1688
|
-
_lc = lc;
|
|
1689
|
-
// `parseA*` return the full tree, so nothing is skipped.
|
|
1690
|
-
_skipTypes = _NO_SKIP_TYPES;
|
|
1691
|
-
_skipActive = false;
|
|
1692
|
-
_skipSelectorPrelude = false;
|
|
1693
|
-
_skipAtRulePrelude = false;
|
|
1694
|
-
_mkLeaf = _objLeaf;
|
|
1695
|
-
_mkUrl = _objUrl;
|
|
1696
|
-
_mkContainer = _objContainer;
|
|
1697
|
-
_mkStylesheet = _objStylesheet;
|
|
1698
|
-
_setName = (r, v, ns, ne) => {
|
|
1699
|
-
const c = /** @type {Container} */ (r);
|
|
1700
|
-
c.name = v;
|
|
1701
|
-
c.nameStart = ns;
|
|
1702
|
-
c.nameEnd = ne;
|
|
1703
|
-
};
|
|
1704
|
-
_setEnd = (r, v) => {
|
|
1705
|
-
r.end = v;
|
|
1706
|
-
};
|
|
1707
|
-
_setBlock = (r, bs, be) => {
|
|
1708
|
-
const c = /** @type {Container} */ (r);
|
|
1709
|
-
c.blockStart = bs;
|
|
1710
|
-
c.blockEnd = be;
|
|
1711
|
-
};
|
|
1712
|
-
_setImportant = (r) => {
|
|
1713
|
-
/** @type {Container} */ (r).important = true;
|
|
1714
|
-
};
|
|
1715
|
-
_setToken = (r, ch) => {
|
|
1716
|
-
/** @type {Container} */ (r).token = ch;
|
|
1717
|
-
};
|
|
1718
|
-
_setValue = (r, list) => {
|
|
1719
|
-
/** @type {Container} */ (r).value = /** @type {ComponentValue[]} */ (list);
|
|
1720
|
-
};
|
|
1721
|
-
_setPrelude = (r, list) => {
|
|
1722
|
-
/** @type {Container} */ (r).prelude = /** @type {ComponentValue[]} */ (
|
|
1723
|
-
list
|
|
1724
|
-
);
|
|
1725
|
-
};
|
|
1726
|
-
_setBody = (r, decls, childRules) => {
|
|
1727
|
-
const c = /** @type {Container} */ (r);
|
|
1728
|
-
c.declarations = /** @type {Declaration[]} */ (decls);
|
|
1729
|
-
c.childRules = /** @type {Rule[]} */ (childRules);
|
|
1730
|
-
};
|
|
1731
|
-
_setRules = (r, list) => {
|
|
1732
|
-
/** @type {Stylesheet} */ (r).rules = /** @type {Rule[]} */ (list);
|
|
1733
|
-
};
|
|
1734
|
-
_nType = (r) => r.type;
|
|
1735
|
-
_nStart = (r) => r.start;
|
|
1736
|
-
_nValue = (r) => /** @type {Token} */ (r).value;
|
|
1737
|
-
_nToken = (r) =>
|
|
1738
|
-
/** @type {SimpleBlockToken} */ (/** @type {Container} */ (r).token);
|
|
1739
|
-
};
|
|
1740
|
-
|
|
1741
|
-
// -- struct-of-arrays backend: writes nodes into reused typed arrays --
|
|
1505
|
+
// Scratch content-list pool: a container's value / prelude is built in a plain
|
|
1506
|
+
// array, then sealed into the flat value buffer by `_setValue`, which returns
|
|
1507
|
+
// the array to the pool — so a parse allocates almost no per-list arrays. An
|
|
1508
|
+
// abandoned (never-sealed) list simply falls out of the pool.
|
|
1509
|
+
/** @type {Node[][]} */
|
|
1510
|
+
const _listPool = [];
|
|
1511
|
+
const _takeList = () =>
|
|
1512
|
+
_listPool.length > 0
|
|
1513
|
+
? /** @type {Node[]} */ (_listPool.pop())
|
|
1514
|
+
: /** @type {Node[]} */ ([]);
|
|
1515
|
+
|
|
1516
|
+
// -- struct-of-arrays store: nodes live in reused typed-array columns --
|
|
1742
1517
|
// A node ref is its integer id; fields live in parallel arrays indexed by id.
|
|
1743
|
-
//
|
|
1744
|
-
// extras; child lists hang off three object arrays.
|
|
1745
|
-
// url:
|
|
1746
|
-
// function:
|
|
1747
|
-
// declaration:
|
|
1748
|
-
// at-rule:
|
|
1749
|
-
// qualified:
|
|
1518
|
+
// Two reused int slots (`_aux0/1`) plus a flags byte carry the per-type
|
|
1519
|
+
// extras; child lists hang off three object arrays. Aux slot meaning by type:
|
|
1520
|
+
// url: aux0 contentStart, aux1 contentEnd
|
|
1521
|
+
// function: aux0 nameEnd
|
|
1522
|
+
// declaration: aux0 nameEnd, flags bit0 important
|
|
1523
|
+
// at-rule: aux0 nameEnd, aux1 blockStart (blockEnd == end)
|
|
1524
|
+
// qualified: aux1 blockStart (blockEnd == end)
|
|
1750
1525
|
// `name` / `nameStart` / a simple block's `token` are derived from the source
|
|
1751
|
-
// on read (see the
|
|
1752
|
-
// value | prelude | stylesheet rules
|
|
1753
|
-
// `grammar` resets `
|
|
1526
|
+
// on read (see the accessors), so they need no slot. A node's main content
|
|
1527
|
+
// (value | prelude | stylesheet rules) is a `_flat` span (see below).
|
|
1528
|
+
// `grammar` resets `_nodeCount` to 0 after each top-level rule's walk, so the
|
|
1754
1529
|
// buffers are reused across rules and the parse allocates almost nothing.
|
|
1755
|
-
let
|
|
1756
|
-
let
|
|
1757
|
-
let
|
|
1758
|
-
let
|
|
1759
|
-
let
|
|
1760
|
-
let
|
|
1761
|
-
let
|
|
1762
|
-
let
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1530
|
+
let _capacity = 0;
|
|
1531
|
+
let _nodeCount = 0;
|
|
1532
|
+
let _types = new Uint8Array(0);
|
|
1533
|
+
let _starts = new Int32Array(0);
|
|
1534
|
+
let _ends = new Int32Array(0);
|
|
1535
|
+
let _aux0 = new Int32Array(0);
|
|
1536
|
+
let _aux1 = new Int32Array(0);
|
|
1537
|
+
let _flags = new Uint8Array(0);
|
|
1538
|
+
// Content-list spans: a container's value / prelude is `_flat[start, start+len)`
|
|
1539
|
+
// (node refs), recycled per top-level rule like the node columns.
|
|
1540
|
+
let _listStarts = new Int32Array(0);
|
|
1541
|
+
let _listLens = new Int32Array(0);
|
|
1542
|
+
let _flat = new Int32Array(0);
|
|
1543
|
+
let _flatTop = 0;
|
|
1544
|
+
// Peak usage of the current parse, and use-once regrow hints: after an
|
|
1545
|
+
// over-capacity shrink the next grow jumps straight back to the previous
|
|
1546
|
+
// parse's peak (one exact-fit allocation instead of re-doubling up).
|
|
1547
|
+
let _peak = 0;
|
|
1548
|
+
let _flatPeak = 0;
|
|
1549
|
+
let _growHint = 0;
|
|
1550
|
+
let _flatGrowHint = 0;
|
|
1551
|
+
|
|
1552
|
+
/** @param {number} need minimum flat-buffer capacity */
|
|
1553
|
+
const _flatGrow = (need) => {
|
|
1554
|
+
let cap = _flat.length || 4096;
|
|
1555
|
+
if (_flatGrowHint > cap) cap = _flatGrowHint;
|
|
1556
|
+
_flatGrowHint = 0;
|
|
1557
|
+
while (cap < need) cap *= 2;
|
|
1558
|
+
const next = new Int32Array(cap);
|
|
1559
|
+
next.set(_flat);
|
|
1560
|
+
_flat = next;
|
|
1561
|
+
};
|
|
1562
|
+
// Reassigned (not mutated) when a `parseA*` parse hands its columns to a
|
|
1563
|
+
// retained snapshot, so the next parse starts on fresh arrays.
|
|
1766
1564
|
/** @type {(Node[] | null)[]} */
|
|
1767
|
-
|
|
1565
|
+
let _declarationLists = [];
|
|
1768
1566
|
/** @type {(Node[] | null)[]} */
|
|
1769
|
-
|
|
1770
|
-
let
|
|
1771
|
-
let
|
|
1567
|
+
let _childRuleLists = [];
|
|
1568
|
+
let _input = "";
|
|
1569
|
+
let _locConverter = /** @type {LocConverter} */ (/** @type {unknown} */ (null));
|
|
1772
1570
|
|
|
1773
1571
|
// Node refs are integers here but typed `Node` across the parser; these are
|
|
1774
1572
|
// identity casts that just satisfy the type system at the boundary.
|
|
1775
1573
|
/** @type {(n: Node) => number} */
|
|
1776
|
-
const
|
|
1574
|
+
const _nodeIndex = (n) => /** @type {number} */ (/** @type {unknown} */ (n));
|
|
1777
1575
|
/** @type {(i: number) => Node} */
|
|
1778
|
-
const
|
|
1576
|
+
const _nodeRef = (i) => /** @type {Node} */ (/** @type {unknown} */ (i));
|
|
1779
1577
|
|
|
1780
1578
|
/** @param {number} need minimum capacity */
|
|
1781
|
-
const
|
|
1782
|
-
let cap =
|
|
1579
|
+
const _grow = (need) => {
|
|
1580
|
+
let cap = _capacity || 4096;
|
|
1581
|
+
if (_growHint > cap) cap = _growHint;
|
|
1582
|
+
_growHint = 0;
|
|
1783
1583
|
while (cap < need) cap *= 2;
|
|
1784
1584
|
const ty = new Uint8Array(cap);
|
|
1785
|
-
ty.set(
|
|
1786
|
-
|
|
1585
|
+
ty.set(_types);
|
|
1586
|
+
_types = ty;
|
|
1787
1587
|
const st = new Int32Array(cap);
|
|
1788
|
-
st.set(
|
|
1789
|
-
|
|
1588
|
+
st.set(_starts);
|
|
1589
|
+
_starts = st;
|
|
1790
1590
|
const en = new Int32Array(cap);
|
|
1791
|
-
en.set(
|
|
1792
|
-
|
|
1591
|
+
en.set(_ends);
|
|
1592
|
+
_ends = en;
|
|
1793
1593
|
const a0 = new Int32Array(cap);
|
|
1794
|
-
a0.set(
|
|
1795
|
-
|
|
1594
|
+
a0.set(_aux0);
|
|
1595
|
+
_aux0 = a0;
|
|
1796
1596
|
const a1 = new Int32Array(cap);
|
|
1797
|
-
a1.set(
|
|
1798
|
-
|
|
1799
|
-
const a2 = new Int32Array(cap);
|
|
1800
|
-
a2.set(_soaA2);
|
|
1801
|
-
_soaA2 = a2;
|
|
1597
|
+
a1.set(_aux1);
|
|
1598
|
+
_aux1 = a1;
|
|
1802
1599
|
const fl = new Uint8Array(cap);
|
|
1803
|
-
fl.set(
|
|
1804
|
-
|
|
1805
|
-
|
|
1600
|
+
fl.set(_flags);
|
|
1601
|
+
_flags = fl;
|
|
1602
|
+
const ls = new Int32Array(cap);
|
|
1603
|
+
ls.set(_listStarts);
|
|
1604
|
+
_listStarts = ls;
|
|
1605
|
+
const ll = new Int32Array(cap);
|
|
1606
|
+
ll.set(_listLens);
|
|
1607
|
+
_listLens = ll;
|
|
1608
|
+
_capacity = cap;
|
|
1806
1609
|
};
|
|
1807
1610
|
/** @type {(type: number, start: number, end: number) => Node} */
|
|
1808
|
-
const
|
|
1611
|
+
const _makeLeaf = (type, start, end) => {
|
|
1809
1612
|
// Ids are 1-based: a node ref is used in truthiness checks (`if (!parent)`),
|
|
1810
1613
|
// so 0 must stay reserved for "no node".
|
|
1811
|
-
// Leaves never read the flag / list slots — `
|
|
1614
|
+
// Leaves never read the flag / list slots — `_makeContainer` clears
|
|
1812
1615
|
// them instead, keeping the dominant leaf allocation at three writes.
|
|
1813
|
-
const i =
|
|
1814
|
-
if (i >=
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
return
|
|
1616
|
+
const i = _nodeCount + 1;
|
|
1617
|
+
if (i >= _capacity) _grow(i + 1);
|
|
1618
|
+
_types[i] = type;
|
|
1619
|
+
_starts[i] = start;
|
|
1620
|
+
_ends[i] = end;
|
|
1621
|
+
_nodeCount = i;
|
|
1622
|
+
return _nodeRef(i);
|
|
1820
1623
|
};
|
|
1821
1624
|
/** @type {(type: number, start: number, end: number) => Node} */
|
|
1822
|
-
const
|
|
1823
|
-
const r =
|
|
1824
|
-
const i =
|
|
1825
|
-
|
|
1826
|
-
// Clear
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1625
|
+
const _makeContainer = (type, start, end) => {
|
|
1626
|
+
const r = _makeLeaf(type, start, end);
|
|
1627
|
+
const i = _nodeIndex(r);
|
|
1628
|
+
_flags[i] = 0;
|
|
1629
|
+
// Clear the content-span length so a reused id never exposes a previous
|
|
1630
|
+
// node's children (content lists are flat spans, so zeroing the length
|
|
1631
|
+
// suffices). `blockStart` (aux1) is NOT defaulted here: only at-rules and
|
|
1632
|
+
// qualified rules carry a block, and they set it on every return path
|
|
1633
|
+
// (`_setBlock`, or `-1` for the no-block forms). `blockEnd` is not stored — a
|
|
1634
|
+
// block rule's `end` is its `blockEnd` (see `_setBlock`), else it is `-1`.
|
|
1635
|
+
_listLens[i] = 0;
|
|
1636
|
+
// The decl / child-rule slots are read only for rules (the walk guards on
|
|
1637
|
+
// type; readers treat a missing slot as `null`), so only rules clear them —
|
|
1638
|
+
// clearing every container would write these `id`-indexed arrays at scattered
|
|
1639
|
+
// ids and degrade them to slow dictionary mode on large non-recycling parses.
|
|
1640
|
+
if (type === T_AT_RULE || type === T_QUALIFIED_RULE) {
|
|
1641
|
+
_declarationLists[i] = null;
|
|
1642
|
+
_childRuleLists[i] = null;
|
|
1643
|
+
}
|
|
1830
1644
|
return r;
|
|
1831
1645
|
};
|
|
1832
1646
|
// Raw token value (the lazy `Token.value` form): hash / at-keyword drop their
|
|
1833
1647
|
// one-char prefix, url uses its content range. Shared by the parser's
|
|
1834
|
-
// mid-parse reads and the
|
|
1648
|
+
// mid-parse reads and the accessor.
|
|
1835
1649
|
/**
|
|
1836
1650
|
* @param {number} i node id
|
|
1837
1651
|
* @returns {string} raw token value
|
|
1838
1652
|
*/
|
|
1839
|
-
const
|
|
1840
|
-
const ty =
|
|
1653
|
+
const _valueOf = (i) => {
|
|
1654
|
+
const ty = _types[i];
|
|
1841
1655
|
if (ty === T_HASH || ty === T_AT_KEYWORD) {
|
|
1842
|
-
return
|
|
1656
|
+
return _input.slice(_starts[i] + 1, _ends[i]);
|
|
1843
1657
|
}
|
|
1844
|
-
if (ty === T_URL) return
|
|
1845
|
-
return
|
|
1658
|
+
if (ty === T_URL) return _input.slice(_aux0[i], _aux1[i]);
|
|
1659
|
+
return _input.slice(_starts[i], _ends[i]);
|
|
1660
|
+
};
|
|
1661
|
+
// Module-level constants (not per-parse closures), so each consume-algorithm
|
|
1662
|
+
// call site keeps one function identity and stays monomorphic.
|
|
1663
|
+
/** @type {(start: number, end: number, contentStart: number, contentEnd: number) => Node} */
|
|
1664
|
+
const _makeUrl = (start, end, cs, ce) => {
|
|
1665
|
+
const r = _makeLeaf(T_URL, start, end);
|
|
1666
|
+
_aux0[_nodeIndex(r)] = cs;
|
|
1667
|
+
_aux1[_nodeIndex(r)] = ce;
|
|
1668
|
+
return r;
|
|
1669
|
+
};
|
|
1670
|
+
/** @type {(start: number) => Node} */
|
|
1671
|
+
const _makeStylesheet = (start) => _makeContainer(T_STYLESHEET, start, start);
|
|
1672
|
+
// name / nameStart are derived from start + nameEnd; only nameEnd is stored.
|
|
1673
|
+
/** @type {(r: Node, nameStart: number, nameEnd: number) => void} */
|
|
1674
|
+
const _setName = (r, ns, ne) => {
|
|
1675
|
+
_aux0[_nodeIndex(r)] = ne;
|
|
1676
|
+
};
|
|
1677
|
+
/** @type {(r: Node, v: number) => void} */
|
|
1678
|
+
const _setEnd = (r, v) => {
|
|
1679
|
+
_ends[_nodeIndex(r)] = v;
|
|
1680
|
+
};
|
|
1681
|
+
// `blockEnd` is not stored: for a block rule the parser sets `end` to the
|
|
1682
|
+
// block end right after this (so `A.blockEnd` reads `end`); a `-1` blockStart
|
|
1683
|
+
// marks the no-block forms, where `blockEnd` derives to `-1`.
|
|
1684
|
+
/** @type {(r: Node, blockStart: number) => void} */
|
|
1685
|
+
const _setBlock = (r, bs) => {
|
|
1686
|
+
_aux1[_nodeIndex(r)] = bs;
|
|
1846
1687
|
};
|
|
1688
|
+
/** @type {(r: Node) => void} */
|
|
1689
|
+
const _setImportant = (r) => {
|
|
1690
|
+
_flags[_nodeIndex(r)] |= 1;
|
|
1691
|
+
};
|
|
1692
|
+
// A simple block's token is derived from its opening char on read.
|
|
1693
|
+
/** @type {(r: Node, ch: SimpleBlockToken) => void} */
|
|
1694
|
+
const _setToken = (r, ch) => {};
|
|
1695
|
+
/** @type {(r: Node, list: Node[]) => void} */
|
|
1696
|
+
const _setValue = (r, list) => {
|
|
1697
|
+
// Seal the finished list: copy its refs into the flat buffer and hand the
|
|
1698
|
+
// scratch array back to the pool. The caller never touches `list` again.
|
|
1699
|
+
const len = list.length;
|
|
1700
|
+
// Empty seal (common in non-modules skip mode, where value/prelude leaves are
|
|
1701
|
+
// dropped): `_makeContainer` already left `_listLens` at 0, so skip the flat
|
|
1702
|
+
// writes entirely and just recycle the scratch array.
|
|
1703
|
+
if (len !== 0) {
|
|
1704
|
+
const i = _nodeIndex(r);
|
|
1705
|
+
const start = _flatTop;
|
|
1706
|
+
if (start + len > _flat.length) _flatGrow(start + len);
|
|
1707
|
+
for (let k = 0; k < len; k++) {
|
|
1708
|
+
_flat[start + k] = _nodeIndex(list[k]);
|
|
1709
|
+
}
|
|
1710
|
+
_flatTop = start + len;
|
|
1711
|
+
_listStarts[i] = start;
|
|
1712
|
+
_listLens[i] = len;
|
|
1713
|
+
list.length = 0;
|
|
1714
|
+
}
|
|
1715
|
+
_listPool.push(list);
|
|
1716
|
+
};
|
|
1717
|
+
/** @type {(r: Node, decls: Node[], childRules: Node[]) => void} */
|
|
1718
|
+
const _setBody = (r, decls, childRules) => {
|
|
1719
|
+
const i = _nodeIndex(r);
|
|
1720
|
+
_declarationLists[i] = decls;
|
|
1721
|
+
_childRuleLists[i] = childRules;
|
|
1722
|
+
};
|
|
1723
|
+
/** @type {(r: Node) => number} */
|
|
1724
|
+
const _nodeTypeOf = (r) => _types[_nodeIndex(r)];
|
|
1725
|
+
/** @type {(r: Node) => number} */
|
|
1726
|
+
const _nodeStartOf = (r) => _starts[_nodeIndex(r)];
|
|
1727
|
+
/** @type {(r: Node) => string} */
|
|
1728
|
+
const _nodeValueOf = (r) => _valueOf(_nodeIndex(r));
|
|
1729
|
+
/** @type {(r: Node) => SimpleBlockToken} */
|
|
1730
|
+
const _nodeTokenOf = (r) =>
|
|
1731
|
+
/** @type {SimpleBlockToken} */ (_input[_starts[_nodeIndex(r)]]);
|
|
1732
|
+
// A container's value, a rule's prelude, and a stylesheet's rules all seal into
|
|
1733
|
+
// the one content-list writer, so `_setPrelude` / `_setRules` are named views of
|
|
1734
|
+
// `_setValue` that keep the consume algorithms reading in spec terms.
|
|
1735
|
+
const _setPrelude = _setValue;
|
|
1736
|
+
const _setRules = _setValue;
|
|
1737
|
+
|
|
1847
1738
|
/**
|
|
1739
|
+
* Start a parse into the store: point it at this source, reset the node /
|
|
1740
|
+
* flat cursors so nodes accumulate from id 1, and clear skip state (`grammar`
|
|
1741
|
+
* sets its own afterwards; `parseA*` leave it off to build the full tree).
|
|
1848
1742
|
* @param {string} input source
|
|
1849
1743
|
* @param {LocConverter} lc loc converter
|
|
1850
1744
|
*/
|
|
1851
|
-
const
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
return r;
|
|
1861
|
-
};
|
|
1862
|
-
_mkContainer = (type, start, end) => _soaAllocContainer(type, start, end);
|
|
1863
|
-
_mkStylesheet = (start) => _soaAllocContainer(T_STYLESHEET, start, start);
|
|
1864
|
-
// name / nameStart are derived from start + nameEnd; only nameEnd is stored.
|
|
1865
|
-
_setName = (r, v, ns, ne) => {
|
|
1866
|
-
_soaA0[_ix(r)] = ne;
|
|
1867
|
-
};
|
|
1868
|
-
_setEnd = (r, v) => {
|
|
1869
|
-
_soaEn[_ix(r)] = v;
|
|
1870
|
-
};
|
|
1871
|
-
_setBlock = (r, bs, be) => {
|
|
1872
|
-
const i = _ix(r);
|
|
1873
|
-
_soaA1[i] = bs;
|
|
1874
|
-
_soaA2[i] = be;
|
|
1875
|
-
};
|
|
1876
|
-
_setImportant = (r) => {
|
|
1877
|
-
_soaFl[_ix(r)] |= 1;
|
|
1878
|
-
};
|
|
1879
|
-
// A simple block's token is derived from its opening char on read.
|
|
1880
|
-
_setToken = (r, ch) => {};
|
|
1881
|
-
_setValue = (r, list) => {
|
|
1882
|
-
_soaL0[_ix(r)] = list;
|
|
1883
|
-
};
|
|
1884
|
-
_setPrelude = (r, list) => {
|
|
1885
|
-
_soaL0[_ix(r)] = list;
|
|
1886
|
-
};
|
|
1887
|
-
_setBody = (r, decls, childRules) => {
|
|
1888
|
-
const i = _ix(r);
|
|
1889
|
-
_soaL1[i] = decls;
|
|
1890
|
-
_soaL2[i] = childRules;
|
|
1891
|
-
};
|
|
1892
|
-
_setRules = (r, list) => {
|
|
1893
|
-
_soaL0[_ix(r)] = list;
|
|
1894
|
-
};
|
|
1895
|
-
_nType = (r) => _soaTy[_ix(r)];
|
|
1896
|
-
_nStart = (r) => _soaSt[_ix(r)];
|
|
1897
|
-
_nValue = (r) => _soaValueOf(_ix(r));
|
|
1898
|
-
_nToken = (r) => /** @type {SimpleBlockToken} */ (_soaInput[_soaSt[_ix(r)]]);
|
|
1745
|
+
const _setupParse = (input, lc) => {
|
|
1746
|
+
_input = input;
|
|
1747
|
+
_locConverter = lc;
|
|
1748
|
+
_nodeCount = 0;
|
|
1749
|
+
_flatTop = 0;
|
|
1750
|
+
_skipTypes = _NO_SKIP_TYPES;
|
|
1751
|
+
_skipActive = false;
|
|
1752
|
+
_skipSelectorPrelude = false;
|
|
1753
|
+
_skipAtRulePrelude = false;
|
|
1899
1754
|
};
|
|
1900
1755
|
|
|
1901
1756
|
/**
|
|
@@ -1909,9 +1764,9 @@ const tokenToNode = (t) => {
|
|
|
1909
1764
|
// plain leaf whose node type comes from the map.
|
|
1910
1765
|
if (tt === TT_URL) {
|
|
1911
1766
|
const ut = /** @type {CssUrlToken} */ (t);
|
|
1912
|
-
return
|
|
1767
|
+
return _makeUrl(t.start, t.end, ut.contentStart, ut.contentEnd);
|
|
1913
1768
|
}
|
|
1914
|
-
return
|
|
1769
|
+
return _makeLeaf(_ttToNodeType[tt], t.start, t.end);
|
|
1915
1770
|
};
|
|
1916
1771
|
|
|
1917
1772
|
/**
|
|
@@ -2033,6 +1888,20 @@ class TokenStream {
|
|
|
2033
1888
|
}
|
|
2034
1889
|
}
|
|
2035
1890
|
|
|
1891
|
+
/**
|
|
1892
|
+
* Advance past the already-peeked next token, skipping the redundant `next()`
|
|
1893
|
+
* re-check `consume` / `discard` pay. Precondition: the caller has just called
|
|
1894
|
+
* `next()` (so `_tok` is the cached next token and `_hasNext` is true) and that
|
|
1895
|
+
* token is not the `<eof-token>` — the hot "peek, decide, advance" sites where
|
|
1896
|
+
* both always hold. Callers that can't guarantee a non-EOF cached token use
|
|
1897
|
+
* `consume` / `discard` instead.
|
|
1898
|
+
* @returns {void}
|
|
1899
|
+
*/
|
|
1900
|
+
advance() {
|
|
1901
|
+
this._pos = this._tok.end;
|
|
1902
|
+
this._hasNext = false;
|
|
1903
|
+
}
|
|
1904
|
+
|
|
2036
1905
|
/**
|
|
2037
1906
|
* Mark (CSS Syntax §3 "mark") — push the current cursor position.
|
|
2038
1907
|
* @returns {void}
|
|
@@ -2088,30 +1957,380 @@ const normalizeIntoTokenStream = (input, pos, onComment) =>
|
|
|
2088
1957
|
// algorithms that drive the tokenizer.
|
|
2089
1958
|
|
|
2090
1959
|
/**
|
|
2091
|
-
* @typedef {object}
|
|
1960
|
+
* @typedef {object} ParseOptions
|
|
2092
1961
|
* @property {((input: string, start: number, end: number) => number)=} comment optional comment-token callback; the public `parse*` entry points use it to build the `TokenStream` so the outer parser's comment tracker still sees magic comments inside the consumed range
|
|
2093
1962
|
*/
|
|
2094
1963
|
|
|
1964
|
+
// === parseA* retained store + property-accessor readers ===
|
|
1965
|
+
// `grammar` recycles the columns per top-level rule, but the `parseA*` entry
|
|
1966
|
+
// points must hand back a tree that outlives the parse. Each `parseA*` run parses
|
|
1967
|
+
// into the columns without recycling, then `_finishStore` hands them to a
|
|
1968
|
+
// snapshot object and resets the module columns to fresh arrays so the next parse
|
|
1969
|
+
// can't clobber it. Nodes are exposed as `parseA*` readers: plain objects sharing
|
|
1970
|
+
// one module-level prototype whose getters index the reader's own snapshot by node
|
|
1971
|
+
// id — no per-node class, no eager string slices, child readers built lazily on
|
|
1972
|
+
// access. A single shared prototype (rather than one per store) keeps the readers
|
|
1973
|
+
// monomorphic across parses, so `_readerAt` and every getter stay on the fast path.
|
|
1974
|
+
|
|
1975
|
+
/**
|
|
1976
|
+
* @typedef {object} NodeReader
|
|
1977
|
+
* @property {NodeStore} _store snapshot this reader indexes
|
|
1978
|
+
* @property {number} _i node id into the snapshot columns
|
|
1979
|
+
* @property {number} type node type
|
|
1980
|
+
* @property {number} start start offset
|
|
1981
|
+
* @property {number} end end offset
|
|
1982
|
+
* @property {[number, number]} range start / end offsets
|
|
1983
|
+
* @property {{ start: { line: number, column: number }, end: { line: number, column: number } }} loc source location
|
|
1984
|
+
* @property {() => string} toString source slice
|
|
1985
|
+
* @property {string | ComponentValue[]} value token value (leaf) or component-value list (function / block / declaration)
|
|
1986
|
+
* @property {string} unescaped unescaped token value
|
|
1987
|
+
* @property {number} numericValue parsed numeric value
|
|
1988
|
+
* @property {"integer" | "number" | "id" | "unrestricted"} typeFlag spec type flag
|
|
1989
|
+
* @property {"+" | "-" | ""} sign spec sign
|
|
1990
|
+
* @property {string} unit dimension unit (lower-cased)
|
|
1991
|
+
* @property {number} contentStart url content start offset
|
|
1992
|
+
* @property {number} contentEnd url content end offset
|
|
1993
|
+
* @property {string} name rule / declaration / function name
|
|
1994
|
+
* @property {number} nameStart name start offset
|
|
1995
|
+
* @property {number} nameEnd name end offset
|
|
1996
|
+
* @property {string} unescapedName unescaped name
|
|
1997
|
+
* @property {ComponentValue[]} prelude rule prelude
|
|
1998
|
+
* @property {Declaration[] | null} declarations block declarations
|
|
1999
|
+
* @property {Rule[] | null} childRules block child rules
|
|
2000
|
+
* @property {number} blockStart `{` start offset
|
|
2001
|
+
* @property {number} blockEnd `}` end offset
|
|
2002
|
+
* @property {boolean} important `!important` flag
|
|
2003
|
+
* @property {SimpleBlockToken} token simple-block opening char
|
|
2004
|
+
* @property {Rule[]} rules stylesheet rules
|
|
2005
|
+
*/
|
|
2006
|
+
|
|
2007
|
+
/**
|
|
2008
|
+
* @typedef {object} NodeStore
|
|
2009
|
+
* @property {string} input source
|
|
2010
|
+
* @property {LocConverter} lc loc converter
|
|
2011
|
+
* @property {Uint8Array} types node-type column
|
|
2012
|
+
* @property {Int32Array} starts start-offset column
|
|
2013
|
+
* @property {Int32Array} ends end-offset column
|
|
2014
|
+
* @property {Int32Array} aux0 aux slot 0
|
|
2015
|
+
* @property {Int32Array} aux1 aux slot 1
|
|
2016
|
+
* @property {Uint8Array} flags flags column
|
|
2017
|
+
* @property {Int32Array} listStarts content-span start column
|
|
2018
|
+
* @property {Int32Array} listLens content-span length column
|
|
2019
|
+
* @property {Int32Array} flat flat node-ref buffer
|
|
2020
|
+
* @property {(Node[] | null)[]} declLists per-node declaration lists
|
|
2021
|
+
* @property {(Node[] | null)[]} childLists per-node child-rule lists
|
|
2022
|
+
*/
|
|
2023
|
+
|
|
2024
|
+
// Shared frozen empty list for a block body with no declarations / child rules,
|
|
2025
|
+
// so equal-empty reads return one reference (mirrors `_EMPTY_LIST`).
|
|
2026
|
+
const _READER_EMPTY = /** @type {Rule[]} */ (
|
|
2027
|
+
/** @type {unknown} */ (Object.freeze([]))
|
|
2028
|
+
);
|
|
2029
|
+
|
|
2030
|
+
/**
|
|
2031
|
+
* Raw token value over a snapshot (the lazy `Token.value` form): hash / at-keyword
|
|
2032
|
+
* drop their one-char prefix, url uses its content range.
|
|
2033
|
+
* @param {NodeStore} store snapshot
|
|
2034
|
+
* @param {number} i node id
|
|
2035
|
+
* @returns {string} raw token value
|
|
2036
|
+
*/
|
|
2037
|
+
const _storeValueOf = (store, i) => {
|
|
2038
|
+
const ty = store.types[i];
|
|
2039
|
+
if (ty === T_HASH || ty === T_AT_KEYWORD) {
|
|
2040
|
+
return store.input.slice(store.starts[i] + 1, store.ends[i]);
|
|
2041
|
+
}
|
|
2042
|
+
if (ty === T_URL) return store.input.slice(store.aux0[i], store.aux1[i]);
|
|
2043
|
+
return store.input.slice(store.starts[i], store.ends[i]);
|
|
2044
|
+
};
|
|
2045
|
+
|
|
2046
|
+
/**
|
|
2047
|
+
* @param {NodeStore} store snapshot
|
|
2048
|
+
* @param {number} i node id
|
|
2049
|
+
* @returns {Node} reader over node `i`
|
|
2050
|
+
*/
|
|
2051
|
+
const _readerAt = (store, i) => {
|
|
2052
|
+
const o = Object.create(NODE_READER_PROTO);
|
|
2053
|
+
o._store = store;
|
|
2054
|
+
o._i = i;
|
|
2055
|
+
return /** @type {Node} */ (o);
|
|
2056
|
+
};
|
|
2057
|
+
|
|
2058
|
+
/**
|
|
2059
|
+
* Readers over a container's flat content span (value / prelude / rules).
|
|
2060
|
+
* @param {NodeStore} store snapshot
|
|
2061
|
+
* @param {number} i container id
|
|
2062
|
+
* @returns {Node[]} child readers
|
|
2063
|
+
*/
|
|
2064
|
+
const _readList = (store, i) => {
|
|
2065
|
+
const s = store.listStarts[i];
|
|
2066
|
+
const len = store.listLens[i];
|
|
2067
|
+
const flat = store.flat;
|
|
2068
|
+
/** @type {Node[]} */
|
|
2069
|
+
const out = [];
|
|
2070
|
+
for (let k = 0; k < len; k++) out.push(_readerAt(store, flat[s + k]));
|
|
2071
|
+
return out;
|
|
2072
|
+
};
|
|
2073
|
+
|
|
2074
|
+
/**
|
|
2075
|
+
* Readers over a node-id list (declarations / child rules).
|
|
2076
|
+
* @param {NodeStore} store snapshot
|
|
2077
|
+
* @param {Node[]} list node-id list
|
|
2078
|
+
* @returns {Node[]} child readers
|
|
2079
|
+
*/
|
|
2080
|
+
const _readRefList = (store, list) => {
|
|
2081
|
+
/** @type {Node[]} */
|
|
2082
|
+
const out = [];
|
|
2083
|
+
for (let k = 0; k < list.length; k++) {
|
|
2084
|
+
out.push(_readerAt(store, _nodeIndex(list[k])));
|
|
2085
|
+
}
|
|
2086
|
+
return out;
|
|
2087
|
+
};
|
|
2088
|
+
|
|
2089
|
+
// Module-level reader prototype shared by every `parseA*` reader. Getters index
|
|
2090
|
+
// the reader's own `_store` snapshot by its `_i` node id; because the prototype is
|
|
2091
|
+
// created once (not per store), all readers share one hidden map and stay
|
|
2092
|
+
// monomorphic across parses.
|
|
2093
|
+
const NODE_READER_PROTO = /** @type {NodeReader} */ ({
|
|
2094
|
+
_store: /** @type {NodeStore} */ (/** @type {unknown} */ (null)),
|
|
2095
|
+
_i: 0,
|
|
2096
|
+
get type() {
|
|
2097
|
+
return this._store.types[this._i];
|
|
2098
|
+
},
|
|
2099
|
+
get start() {
|
|
2100
|
+
return this._store.starts[this._i];
|
|
2101
|
+
},
|
|
2102
|
+
get end() {
|
|
2103
|
+
return this._store.ends[this._i];
|
|
2104
|
+
},
|
|
2105
|
+
get range() {
|
|
2106
|
+
const store = this._store;
|
|
2107
|
+
const i = this._i;
|
|
2108
|
+
return /** @type {[number, number]} */ ([store.starts[i], store.ends[i]]);
|
|
2109
|
+
},
|
|
2110
|
+
get loc() {
|
|
2111
|
+
const store = this._store;
|
|
2112
|
+
const i = this._i;
|
|
2113
|
+
const lc = store.lc;
|
|
2114
|
+
// `LocConverter#get` mutates and returns itself, so snapshot the first.
|
|
2115
|
+
const s = lc.get(store.starts[i]);
|
|
2116
|
+
const sl = s.line;
|
|
2117
|
+
const sc = s.column;
|
|
2118
|
+
const e = lc.get(store.ends[i]);
|
|
2119
|
+
return {
|
|
2120
|
+
start: { line: sl, column: sc },
|
|
2121
|
+
end: { line: e.line, column: e.column }
|
|
2122
|
+
};
|
|
2123
|
+
},
|
|
2124
|
+
toString() {
|
|
2125
|
+
const store = this._store;
|
|
2126
|
+
const i = this._i;
|
|
2127
|
+
return store.input.slice(store.starts[i], store.ends[i]);
|
|
2128
|
+
},
|
|
2129
|
+
get value() {
|
|
2130
|
+
const store = this._store;
|
|
2131
|
+
const i = this._i;
|
|
2132
|
+
const ty = store.types[i];
|
|
2133
|
+
// function / simple-block / declaration expose their component-value
|
|
2134
|
+
// list; every leaf token exposes its raw string value.
|
|
2135
|
+
return ty === T_FUNCTION || ty === T_SIMPLE_BLOCK || ty === T_DECLARATION
|
|
2136
|
+
? /** @type {ComponentValue[]} */ (_readList(store, i))
|
|
2137
|
+
: _storeValueOf(store, i);
|
|
2138
|
+
},
|
|
2139
|
+
get unescaped() {
|
|
2140
|
+
const store = this._store;
|
|
2141
|
+
const i = this._i;
|
|
2142
|
+
const v = _storeValueOf(store, i);
|
|
2143
|
+
return store.types[i] === T_STRING
|
|
2144
|
+
? unescapeIdentifier(v.slice(1, -1))
|
|
2145
|
+
: unescapeIdentifier(v);
|
|
2146
|
+
},
|
|
2147
|
+
get numericValue() {
|
|
2148
|
+
const store = this._store;
|
|
2149
|
+
const i = this._i;
|
|
2150
|
+
const v = _storeValueOf(store, i);
|
|
2151
|
+
if (store.types[i] === T_DIMENSION) {
|
|
2152
|
+
return Number(v.slice(0, _consumeANumber(v, 0)));
|
|
2153
|
+
}
|
|
2154
|
+
if (store.types[i] === T_PERCENTAGE) return Number(v.slice(0, -1));
|
|
2155
|
+
return Number(v);
|
|
2156
|
+
},
|
|
2157
|
+
get typeFlag() {
|
|
2158
|
+
const store = this._store;
|
|
2159
|
+
const i = this._i;
|
|
2160
|
+
if (store.types[i] === T_HASH) {
|
|
2161
|
+
const input = store.input;
|
|
2162
|
+
const p = store.starts[i] + 1;
|
|
2163
|
+
return _ifThreeCodePointsWouldStartAnIdentSequence(
|
|
2164
|
+
input,
|
|
2165
|
+
p,
|
|
2166
|
+
input.charCodeAt(p),
|
|
2167
|
+
input.charCodeAt(p + 1),
|
|
2168
|
+
input.charCodeAt(p + 2)
|
|
2169
|
+
)
|
|
2170
|
+
? "id"
|
|
2171
|
+
: "unrestricted";
|
|
2172
|
+
}
|
|
2173
|
+
const v = _storeValueOf(store, i);
|
|
2174
|
+
return _typeFlagOf(
|
|
2175
|
+
store.types[i] === T_DIMENSION ? v.slice(0, _consumeANumber(v, 0)) : v
|
|
2176
|
+
);
|
|
2177
|
+
},
|
|
2178
|
+
get sign() {
|
|
2179
|
+
return _signOf(_storeValueOf(this._store, this._i));
|
|
2180
|
+
},
|
|
2181
|
+
get unit() {
|
|
2182
|
+
const v = _storeValueOf(this._store, this._i);
|
|
2183
|
+
return v.slice(_consumeANumber(v, 0)).toLowerCase();
|
|
2184
|
+
},
|
|
2185
|
+
get contentStart() {
|
|
2186
|
+
return this._store.aux0[this._i];
|
|
2187
|
+
},
|
|
2188
|
+
get contentEnd() {
|
|
2189
|
+
return this._store.aux1[this._i];
|
|
2190
|
+
},
|
|
2191
|
+
get name() {
|
|
2192
|
+
const store = this._store;
|
|
2193
|
+
const i = this._i;
|
|
2194
|
+
// An at-rule's name skips its `@`; others start at the node.
|
|
2195
|
+
return store.types[i] === T_AT_RULE
|
|
2196
|
+
? store.input.slice(store.starts[i] + 1, store.aux0[i])
|
|
2197
|
+
: store.input.slice(store.starts[i], store.aux0[i]);
|
|
2198
|
+
},
|
|
2199
|
+
get nameStart() {
|
|
2200
|
+
return this._store.starts[this._i];
|
|
2201
|
+
},
|
|
2202
|
+
get nameEnd() {
|
|
2203
|
+
return this._store.aux0[this._i];
|
|
2204
|
+
},
|
|
2205
|
+
get unescapedName() {
|
|
2206
|
+
return unescapeIdentifier(this.name);
|
|
2207
|
+
},
|
|
2208
|
+
get prelude() {
|
|
2209
|
+
return /** @type {ComponentValue[]} */ (_readList(this._store, this._i));
|
|
2210
|
+
},
|
|
2211
|
+
get declarations() {
|
|
2212
|
+
const store = this._store;
|
|
2213
|
+
// Non-rule containers never populate this slot (see `_makeContainer`), so a
|
|
2214
|
+
// missing entry (`undefined`) is normalized to `null` — same as a rule with
|
|
2215
|
+
// no block.
|
|
2216
|
+
const list = store.declLists[this._i] || null;
|
|
2217
|
+
return list === null
|
|
2218
|
+
? null
|
|
2219
|
+
: /** @type {Declaration[]} */ (
|
|
2220
|
+
list.length > 0 ? _readRefList(store, list) : _READER_EMPTY
|
|
2221
|
+
);
|
|
2222
|
+
},
|
|
2223
|
+
get childRules() {
|
|
2224
|
+
const store = this._store;
|
|
2225
|
+
const list = store.childLists[this._i] || null;
|
|
2226
|
+
return list === null
|
|
2227
|
+
? null
|
|
2228
|
+
: /** @type {Rule[]} */ (
|
|
2229
|
+
list.length > 0 ? _readRefList(store, list) : _READER_EMPTY
|
|
2230
|
+
);
|
|
2231
|
+
},
|
|
2232
|
+
get blockStart() {
|
|
2233
|
+
return this._store.aux1[this._i];
|
|
2234
|
+
},
|
|
2235
|
+
get blockEnd() {
|
|
2236
|
+
const store = this._store;
|
|
2237
|
+
const i = this._i;
|
|
2238
|
+
return store.aux1[i] !== -1 ? store.ends[i] : -1;
|
|
2239
|
+
},
|
|
2240
|
+
get important() {
|
|
2241
|
+
return (this._store.flags[this._i] & 1) !== 0;
|
|
2242
|
+
},
|
|
2243
|
+
get token() {
|
|
2244
|
+
const store = this._store;
|
|
2245
|
+
return /** @type {SimpleBlockToken} */ (store.input[store.starts[this._i]]);
|
|
2246
|
+
},
|
|
2247
|
+
get rules() {
|
|
2248
|
+
return /** @type {Rule[]} */ (_readList(this._store, this._i));
|
|
2249
|
+
}
|
|
2250
|
+
});
|
|
2251
|
+
|
|
2252
|
+
/**
|
|
2253
|
+
* Hand the module's live columns to a retained snapshot, then reset the
|
|
2254
|
+
* module columns to fresh empty arrays so the next parse starts clean and can't
|
|
2255
|
+
* mutate this store. Called once per `parseA*` after all consuming is done.
|
|
2256
|
+
* @returns {NodeStore} the retained snapshot
|
|
2257
|
+
*/
|
|
2258
|
+
const _finishStore = () => {
|
|
2259
|
+
/** @type {NodeStore} */
|
|
2260
|
+
const store = {
|
|
2261
|
+
input: _input,
|
|
2262
|
+
lc: _locConverter,
|
|
2263
|
+
types: _types,
|
|
2264
|
+
starts: _starts,
|
|
2265
|
+
ends: _ends,
|
|
2266
|
+
aux0: _aux0,
|
|
2267
|
+
aux1: _aux1,
|
|
2268
|
+
flags: _flags,
|
|
2269
|
+
listStarts: _listStarts,
|
|
2270
|
+
listLens: _listLens,
|
|
2271
|
+
flat: _flat,
|
|
2272
|
+
declLists: _declarationLists,
|
|
2273
|
+
childLists: _childRuleLists
|
|
2274
|
+
};
|
|
2275
|
+
// Carry this parse's size forward as a one-shot grow hint so the next parse
|
|
2276
|
+
// allocates its columns once instead of re-doubling from scratch (node ids
|
|
2277
|
+
// are 1-based, so `+1`).
|
|
2278
|
+
_growHint = _nodeCount + 1;
|
|
2279
|
+
_flatGrowHint = _flatTop;
|
|
2280
|
+
// Reset module state — the columns now belong to `store`.
|
|
2281
|
+
_capacity = 0;
|
|
2282
|
+
_nodeCount = 0;
|
|
2283
|
+
_flatTop = 0;
|
|
2284
|
+
_types = new Uint8Array(0);
|
|
2285
|
+
_starts = new Int32Array(0);
|
|
2286
|
+
_ends = new Int32Array(0);
|
|
2287
|
+
_aux0 = new Int32Array(0);
|
|
2288
|
+
_aux1 = new Int32Array(0);
|
|
2289
|
+
_flags = new Uint8Array(0);
|
|
2290
|
+
_listStarts = new Int32Array(0);
|
|
2291
|
+
_listLens = new Int32Array(0);
|
|
2292
|
+
_flat = new Int32Array(0);
|
|
2293
|
+
_declarationLists = [];
|
|
2294
|
+
_childRuleLists = [];
|
|
2295
|
+
_listPool.length = 0;
|
|
2296
|
+
_input = "";
|
|
2297
|
+
_locConverter = /** @type {LocConverter} */ (/** @type {unknown} */ (null));
|
|
2298
|
+
return store;
|
|
2299
|
+
};
|
|
2300
|
+
|
|
2301
|
+
/**
|
|
2302
|
+
* Finish the parse and wrap one consumed node ref as a `parseA*` reader, keeping
|
|
2303
|
+
* the caller's node type.
|
|
2304
|
+
* @template {Node} T
|
|
2305
|
+
* @param {T} ref consumed node ref
|
|
2306
|
+
* @returns {T} reader over the retained snapshot
|
|
2307
|
+
*/
|
|
2308
|
+
const _finishOne = (ref) =>
|
|
2309
|
+
/** @type {T} */ (_readerAt(_finishStore(), _nodeIndex(ref)));
|
|
2310
|
+
|
|
2095
2311
|
/**
|
|
2096
2312
|
* Parse a stylesheet, CSS Syntax Level 3
|
|
2097
2313
|
* [§5.3.4](https://drafts.csswg.org/css-syntax/#parse-stylesheet).
|
|
2098
2314
|
* @param {string | TokenStream} input source string or an existing token stream
|
|
2099
|
-
* @param {
|
|
2315
|
+
* @param {number=} pos start position (string input only)
|
|
2316
|
+
* @param {ParseOptions=} options optional comment-token callback (string input only)
|
|
2100
2317
|
* @returns {Stylesheet} the parsed stylesheet
|
|
2101
2318
|
*/
|
|
2102
|
-
const parseAStylesheet = (input,
|
|
2319
|
+
const parseAStylesheet = (input, pos = 0, options = {}) => {
|
|
2103
2320
|
// 1. If input is a byte stream for a stylesheet, decode bytes from input, and set input to the result.
|
|
2104
2321
|
// 2. Normalize input, and set input to the result.
|
|
2105
|
-
const ts = normalizeIntoTokenStream(input,
|
|
2106
|
-
|
|
2322
|
+
const ts = normalizeIntoTokenStream(input, pos, options.comment);
|
|
2323
|
+
_setupParse(ts.input, ts.locConverter);
|
|
2107
2324
|
// 3. Create a new stylesheet, with its location set to location (or null, if location was not passed).
|
|
2108
2325
|
const start = ts.next().start;
|
|
2109
|
-
const stylesheet =
|
|
2326
|
+
const stylesheet = _makeStylesheet(start);
|
|
2110
2327
|
// 4. Consume a stylesheet's contents from input, and set the stylesheet's rules to the result.
|
|
2111
2328
|
_setRules(stylesheet, consumeAStylesheetsContents(ts));
|
|
2112
2329
|
_setEnd(stylesheet, ts.next().start);
|
|
2113
2330
|
// 5. Return the stylesheet.
|
|
2114
|
-
return
|
|
2331
|
+
return /** @type {Stylesheet} */ (
|
|
2332
|
+
_readerAt(_finishStore(), _nodeIndex(stylesheet))
|
|
2333
|
+
);
|
|
2115
2334
|
};
|
|
2116
2335
|
|
|
2117
2336
|
/**
|
|
@@ -2121,15 +2340,18 @@ const parseAStylesheet = (input, comment) => {
|
|
|
2121
2340
|
* declarations are parse errors (never produced) and top-level CDO (`<!--`) /
|
|
2122
2341
|
* CDC (`-->`) tokens are discarded.
|
|
2123
2342
|
* @param {string | TokenStream} input source string or an existing token stream
|
|
2124
|
-
* @param {
|
|
2343
|
+
* @param {number=} pos start position (string input only)
|
|
2344
|
+
* @param {ParseOptions=} options optional comment-token callback (string input only)
|
|
2125
2345
|
* @returns {Rule[]} top-level rules
|
|
2126
2346
|
*/
|
|
2127
|
-
const parseAStylesheetsContents = (input,
|
|
2347
|
+
const parseAStylesheetsContents = (input, pos = 0, options = {}) => {
|
|
2128
2348
|
// 1. Normalize input, and set input to the result.
|
|
2129
|
-
const ts = normalizeIntoTokenStream(input,
|
|
2130
|
-
|
|
2349
|
+
const ts = normalizeIntoTokenStream(input, pos, options.comment);
|
|
2350
|
+
_setupParse(ts.input, ts.locConverter);
|
|
2131
2351
|
// 2. Consume a stylesheet’s contents from input, and return the result.
|
|
2132
|
-
|
|
2352
|
+
const rules = consumeAStylesheetsContents(ts);
|
|
2353
|
+
const store = _finishStore();
|
|
2354
|
+
return /** @type {Rule[]} */ (_readRefList(store, rules));
|
|
2133
2355
|
};
|
|
2134
2356
|
|
|
2135
2357
|
/**
|
|
@@ -2137,15 +2359,23 @@ const parseAStylesheetsContents = (input, comment) => {
|
|
|
2137
2359
|
* [§5.3.6](https://drafts.csswg.org/css-syntax/#parse-block-contents).
|
|
2138
2360
|
* @param {string | TokenStream} input source string or an existing token stream
|
|
2139
2361
|
* @param {number=} pos start position (string input only; just past the opening `{`, or 0)
|
|
2140
|
-
* @param {
|
|
2362
|
+
* @param {ParseOptions=} options optional comment-token callback (string input only)
|
|
2141
2363
|
* @returns {{ decls: Declaration[], rules: Rule[] }} block decls + rules
|
|
2142
2364
|
*/
|
|
2143
|
-
const parseABlocksContents = (input, pos,
|
|
2365
|
+
const parseABlocksContents = (input, pos = 0, options = {}) => {
|
|
2144
2366
|
// 1. Normalize input, and set input to the result.
|
|
2145
|
-
const ts = normalizeIntoTokenStream(input, pos, comment);
|
|
2146
|
-
|
|
2367
|
+
const ts = normalizeIntoTokenStream(input, pos, options.comment);
|
|
2368
|
+
_setupParse(ts.input, ts.locConverter);
|
|
2147
2369
|
// 2. Consume a block’s contents from input, and return the result.
|
|
2148
|
-
|
|
2370
|
+
const { decls, rules } = consumeABlocksContents(ts);
|
|
2371
|
+
const store = _finishStore();
|
|
2372
|
+
return {
|
|
2373
|
+
decls: /** @type {Declaration[]} */ (_readRefList(store, decls)),
|
|
2374
|
+
rules:
|
|
2375
|
+
rules.length > 0
|
|
2376
|
+
? /** @type {Rule[]} */ (_readRefList(store, rules))
|
|
2377
|
+
: _READER_EMPTY
|
|
2378
|
+
};
|
|
2149
2379
|
};
|
|
2150
2380
|
|
|
2151
2381
|
/**
|
|
@@ -2155,15 +2385,15 @@ const parseABlocksContents = (input, pos, comment) => {
|
|
|
2155
2385
|
* whitespace; `undefined` (syntax error) otherwise.
|
|
2156
2386
|
* @param {string | TokenStream} input source string or an existing token stream
|
|
2157
2387
|
* @param {number=} pos start position (string input only)
|
|
2158
|
-
* @param {
|
|
2388
|
+
* @param {ParseOptions=} options optional comment-token callback (string input only)
|
|
2159
2389
|
* @returns {Rule | undefined} the parsed rule
|
|
2160
2390
|
*/
|
|
2161
|
-
const parseARule = (input, pos,
|
|
2391
|
+
const parseARule = (input, pos = 0, options = {}) => {
|
|
2162
2392
|
// 1. Normalize input, and set input to the result.
|
|
2163
|
-
const ts = normalizeIntoTokenStream(input, pos, comment);
|
|
2164
|
-
|
|
2393
|
+
const ts = normalizeIntoTokenStream(input, pos, options.comment);
|
|
2394
|
+
_setupParse(ts.input, ts.locConverter);
|
|
2165
2395
|
// 2. Discard whitespace from input.
|
|
2166
|
-
while (ts.next().type === TT_WHITESPACE) ts.
|
|
2396
|
+
while (ts.next().type === TT_WHITESPACE) ts.advance();
|
|
2167
2397
|
// 3. If the next token from input is an <EOF-token>, return a syntax error.
|
|
2168
2398
|
// Otherwise, if the next token from input is an <at-keyword-token>, consume an at-rule from input, and let rule be the return value.
|
|
2169
2399
|
// Otherwise, consume a qualified rule from input and let rule be the return value.
|
|
@@ -2176,9 +2406,9 @@ const parseARule = (input, pos, comment) => {
|
|
|
2176
2406
|
: consumeAQualifiedRule(ts);
|
|
2177
2407
|
if (!rule) return undefined;
|
|
2178
2408
|
// 4. Discard whitespace from input.
|
|
2179
|
-
while (ts.next().type === TT_WHITESPACE) ts.
|
|
2409
|
+
while (ts.next().type === TT_WHITESPACE) ts.advance();
|
|
2180
2410
|
// 5. If the next token from input is an <EOF-token>, return rule. Otherwise, return a syntax error.
|
|
2181
|
-
return ts.next().type === TT_EOF ? rule : undefined;
|
|
2411
|
+
return ts.next().type === TT_EOF ? _finishOne(rule) : undefined;
|
|
2182
2412
|
};
|
|
2183
2413
|
|
|
2184
2414
|
/**
|
|
@@ -2186,41 +2416,41 @@ const parseARule = (input, pos, comment) => {
|
|
|
2186
2416
|
* [§5.3.8](https://drafts.csswg.org/css-syntax/#parse-declaration).
|
|
2187
2417
|
* @param {string | TokenStream} input source string or an existing token stream
|
|
2188
2418
|
* @param {number=} pos start position (string input only)
|
|
2189
|
-
* @param {
|
|
2419
|
+
* @param {ParseOptions=} options optional comment-token callback (string input only)
|
|
2190
2420
|
* @returns {Declaration | undefined} the parsed declaration, or undefined
|
|
2191
2421
|
*/
|
|
2192
|
-
const parseADeclaration = (input, pos,
|
|
2422
|
+
const parseADeclaration = (input, pos = 0, options = {}) => {
|
|
2193
2423
|
// 1. Normalize input, and set input to the result.
|
|
2194
|
-
const ts = normalizeIntoTokenStream(input, pos, comment);
|
|
2195
|
-
|
|
2424
|
+
const ts = normalizeIntoTokenStream(input, pos, options.comment);
|
|
2425
|
+
_setupParse(ts.input, ts.locConverter);
|
|
2196
2426
|
// 2. Discard whitespace from input.
|
|
2197
|
-
while (ts.next().type === TT_WHITESPACE) ts.
|
|
2427
|
+
while (ts.next().type === TT_WHITESPACE) ts.advance();
|
|
2198
2428
|
// 3. Consume a declaration from input. If anything was returned, return it. Otherwise, return a syntax error.
|
|
2199
|
-
|
|
2429
|
+
const decl = consumeADeclaration(ts);
|
|
2430
|
+
return decl === undefined ? undefined : _finishOne(decl);
|
|
2200
2431
|
};
|
|
2201
2432
|
|
|
2202
2433
|
/**
|
|
2203
2434
|
* Parse a component value, CSS Syntax Level 3 [§5.3.9](https://drafts.csswg.org/css-syntax/#parse-component-value) — strict entry point that consumes one value and returns `undefined` if non-whitespace input trails (use `consumeAComponentValue` for "one value, ignore the rest").
|
|
2204
2435
|
* @param {string | TokenStream} input source string or an existing token stream
|
|
2205
2436
|
* @param {number=} pos start position (string input only)
|
|
2206
|
-
* @param {
|
|
2437
|
+
* @param {ParseOptions=} options optional comment-token callback (string input only)
|
|
2207
2438
|
* @returns {ComponentValue | undefined} the parsed component value, or `undefined` on empty / trailing-garbage input
|
|
2208
2439
|
*/
|
|
2209
|
-
const parseAComponentValue = (input, pos, options = {}) => {
|
|
2440
|
+
const parseAComponentValue = (input, pos = 0, options = {}) => {
|
|
2210
2441
|
// 1. Normalize input, and set input to the result.
|
|
2211
2442
|
const ts = normalizeIntoTokenStream(input, pos, options.comment);
|
|
2212
|
-
|
|
2443
|
+
_setupParse(ts.input, ts.locConverter);
|
|
2213
2444
|
// 2. Discard whitespace from input.
|
|
2214
|
-
while (ts.next().type === TT_WHITESPACE) ts.
|
|
2445
|
+
while (ts.next().type === TT_WHITESPACE) ts.advance();
|
|
2215
2446
|
// 3. If input is empty, return a syntax error.
|
|
2216
2447
|
if (ts.next().type === TT_EOF) return undefined;
|
|
2217
2448
|
// 4. Consume a component value from input and let value be the return value.
|
|
2218
2449
|
const result = consumeAComponentValue(ts);
|
|
2219
2450
|
// 5. Discard whitespace from input.
|
|
2220
|
-
while (ts.next().type === TT_WHITESPACE) ts.
|
|
2451
|
+
while (ts.next().type === TT_WHITESPACE) ts.advance();
|
|
2221
2452
|
// 6. If input is empty, return value. Otherwise, return a syntax error.
|
|
2222
|
-
|
|
2223
|
-
return undefined;
|
|
2453
|
+
return ts.next().type === TT_EOF ? _finishOne(result) : undefined;
|
|
2224
2454
|
};
|
|
2225
2455
|
|
|
2226
2456
|
/**
|
|
@@ -2228,44 +2458,52 @@ const parseAComponentValue = (input, pos, options = {}) => {
|
|
|
2228
2458
|
* [§5.3.10](https://drafts.csswg.org/css-syntax/#parse-list-of-components).
|
|
2229
2459
|
* @param {string | TokenStream} input source string or an existing token stream
|
|
2230
2460
|
* @param {number=} pos start position (string input only)
|
|
2231
|
-
* @param {
|
|
2461
|
+
* @param {ParseOptions=} options comment callback
|
|
2232
2462
|
* @returns {ComponentValue[]} component values
|
|
2233
2463
|
*/
|
|
2234
|
-
const parseAListOfComponentValues = (input, pos, options = {}) => {
|
|
2464
|
+
const parseAListOfComponentValues = (input, pos = 0, options = {}) => {
|
|
2235
2465
|
// 1. Normalize input, and set input to the result.
|
|
2236
2466
|
const ts = normalizeIntoTokenStream(input, pos, options.comment);
|
|
2237
|
-
|
|
2467
|
+
_setupParse(ts.input, ts.locConverter);
|
|
2238
2468
|
// 2. Consume a list of component values from input, and return the result.
|
|
2239
|
-
|
|
2469
|
+
// (`null` needs `bailOnCurly`, which is not passed here.)
|
|
2470
|
+
const values = /** @type {Node[]} */ (consumeAListOfComponentValues(ts));
|
|
2471
|
+
const store = _finishStore();
|
|
2472
|
+
return /** @type {ComponentValue[]} */ (_readRefList(store, values));
|
|
2240
2473
|
};
|
|
2241
2474
|
|
|
2242
2475
|
/**
|
|
2243
2476
|
* Parse a comma-separated list of component values, CSS Syntax Level 3 [§5.3.11](https://drafts.csswg.org/css-syntax/#parse-comma-list) — consumes one `<comma-token>`-stopped group of component values per iteration until EOF.
|
|
2244
2477
|
* @param {string | TokenStream} input source string or an existing token stream
|
|
2245
2478
|
* @param {number=} pos start position (string input only)
|
|
2246
|
-
* @param {
|
|
2479
|
+
* @param {ParseOptions=} options comment callback
|
|
2247
2480
|
* @returns {ComponentValue[][]} comma-separated groups of component values
|
|
2248
2481
|
*/
|
|
2249
2482
|
const parseACommaSeparatedListOfComponentValues = (
|
|
2250
2483
|
input,
|
|
2251
|
-
pos,
|
|
2484
|
+
pos = 0,
|
|
2252
2485
|
options = {}
|
|
2253
2486
|
) => {
|
|
2254
2487
|
// 1. Normalize input, and set input to the result.
|
|
2255
2488
|
const ts = normalizeIntoTokenStream(input, pos, options.comment);
|
|
2256
|
-
|
|
2489
|
+
_setupParse(ts.input, ts.locConverter);
|
|
2257
2490
|
// 2. Let groups be an empty list.
|
|
2258
|
-
/** @type {
|
|
2491
|
+
/** @type {Node[][]} */
|
|
2259
2492
|
const groups = [];
|
|
2260
2493
|
// 3. While input is not empty:
|
|
2261
2494
|
while (ts.next().type !== TT_EOF) {
|
|
2262
2495
|
// 3.1. Consume a list of component values from input, with <comma-token> as the stop token, and append the result to groups.
|
|
2263
|
-
groups.push(
|
|
2496
|
+
groups.push(
|
|
2497
|
+
/** @type {Node[]} */ (consumeAListOfComponentValues(ts, TT_COMMA))
|
|
2498
|
+
);
|
|
2264
2499
|
// 3.2 Discard a token from input.
|
|
2265
2500
|
ts.discard();
|
|
2266
2501
|
}
|
|
2267
|
-
// 4. Return groups.
|
|
2268
|
-
|
|
2502
|
+
// 4. Return groups — wrap each group's refs against the retained snapshot.
|
|
2503
|
+
const store = _finishStore();
|
|
2504
|
+
return groups.map(
|
|
2505
|
+
(g) => /** @type {ComponentValue[]} */ (_readRefList(store, g))
|
|
2506
|
+
);
|
|
2269
2507
|
};
|
|
2270
2508
|
|
|
2271
2509
|
// === Parser algorithms (CSS Syntax Level 3 §5.4) ===
|
|
@@ -2335,18 +2573,14 @@ const consumeAnAtRule = (ts, nested = false) => {
|
|
|
2335
2573
|
// Consume a token from input, and let rule be a new at-rule with its name set to the returned token’s value, its prelude initially set to an empty list, and no declarations or child rules.
|
|
2336
2574
|
const head = ts.consume();
|
|
2337
2575
|
const rule = /** @type {AtRule} */ (
|
|
2338
|
-
|
|
2576
|
+
_makeContainer(T_AT_RULE, head.start, head.end)
|
|
2339
2577
|
);
|
|
2340
|
-
_setName(
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
const prelude = _list();
|
|
2347
|
-
_setPrelude(rule, prelude);
|
|
2348
|
-
// declarations / childRules / blockStart (-1) / blockEnd (-1) keep their
|
|
2349
|
-
// defaults (no block: the `;` / EOF / nested-`}` at-rule forms).
|
|
2578
|
+
_setName(rule, head.start, head.end);
|
|
2579
|
+
// Sealed (`_setPrelude`) at each return — the store consumes the
|
|
2580
|
+
// scratch array when sealing, so it must be complete by then.
|
|
2581
|
+
const prelude = _takeList();
|
|
2582
|
+
// declarations / childRules stay null (no block); the `;` / EOF / nested-`}`
|
|
2583
|
+
// forms set blockStart / blockEnd to -1 explicitly at their return below.
|
|
2350
2584
|
|
|
2351
2585
|
// Like `consumeAQualifiedRule`: skip mode scans the prelude without
|
|
2352
2586
|
// materializing it (url tokens / functions kept so `@import url(…)` still
|
|
@@ -2362,6 +2596,8 @@ const consumeAnAtRule = (ts, nested = false) => {
|
|
|
2362
2596
|
// Discard a token from input. If rule is valid in the current context, return it; otherwise return nothing.
|
|
2363
2597
|
if (t.type === TT_SEMICOLON || t.type === TT_EOF) {
|
|
2364
2598
|
ts.discard();
|
|
2599
|
+
_setPrelude(rule, prelude);
|
|
2600
|
+
_setBlock(rule, -1);
|
|
2365
2601
|
_setEnd(rule, t.start);
|
|
2366
2602
|
return rule;
|
|
2367
2603
|
}
|
|
@@ -2370,6 +2606,8 @@ const consumeAnAtRule = (ts, nested = false) => {
|
|
|
2370
2606
|
// Otherwise, consume a token and append the result to rule’s prelude.
|
|
2371
2607
|
else if (t.type === TT_RIGHT_CURLY_BRACKET) {
|
|
2372
2608
|
if (nested) {
|
|
2609
|
+
_setPrelude(rule, prelude);
|
|
2610
|
+
_setBlock(rule, -1);
|
|
2373
2611
|
_setEnd(rule, t.start);
|
|
2374
2612
|
return rule;
|
|
2375
2613
|
}
|
|
@@ -2380,9 +2618,10 @@ const consumeAnAtRule = (ts, nested = false) => {
|
|
|
2380
2618
|
// <{-token>
|
|
2381
2619
|
// Consume a block from input, and assign the result to rule's declarations and child rules.
|
|
2382
2620
|
else if (t.type === TT_LEFT_CURLY_BRACKET) {
|
|
2621
|
+
_setPrelude(rule, prelude);
|
|
2383
2622
|
const block = consumeABlock(ts);
|
|
2384
2623
|
_setBody(rule, block.decls, block.rules);
|
|
2385
|
-
_setBlock(rule, block.blockStart
|
|
2624
|
+
_setBlock(rule, block.blockStart);
|
|
2386
2625
|
_setEnd(rule, block.blockEnd);
|
|
2387
2626
|
return rule;
|
|
2388
2627
|
}
|
|
@@ -2392,7 +2631,10 @@ const consumeAnAtRule = (ts, nested = false) => {
|
|
|
2392
2631
|
const node = consumeAComponentValue(ts, t);
|
|
2393
2632
|
if (!skip) {
|
|
2394
2633
|
prelude.push(node);
|
|
2395
|
-
} else if (
|
|
2634
|
+
} else if (
|
|
2635
|
+
_nodeTypeOf(node) === T_FUNCTION ||
|
|
2636
|
+
_nodeTypeOf(node) === T_URL
|
|
2637
|
+
) {
|
|
2396
2638
|
prelude.push(node);
|
|
2397
2639
|
}
|
|
2398
2640
|
}
|
|
@@ -2423,19 +2665,31 @@ const consumeAQualifiedRule = (ts, stopToken, nested = false) => {
|
|
|
2423
2665
|
const start = ts.next().start;
|
|
2424
2666
|
// Let rule be a new qualified rule with its prelude, declarations, and child rules all initially set to empty lists.
|
|
2425
2667
|
const rule = /** @type {QualifiedRule} */ (
|
|
2426
|
-
|
|
2668
|
+
_makeContainer(T_QUALIFIED_RULE, start, start)
|
|
2427
2669
|
);
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
//
|
|
2431
|
-
//
|
|
2670
|
+
// Sealed (`_setPrelude`) at the `{` exit — the only path that returns the
|
|
2671
|
+
// rule; the parse-error exits abandon the scratch unsealed. Allocated lazily
|
|
2672
|
+
// on first push: skip mode usually pushes nothing (empty selector prelude),
|
|
2673
|
+
// and `_makeContainer` already zeroed the rule's list length, so a null
|
|
2674
|
+
// prelude reads as empty in the walk.
|
|
2675
|
+
/** @type {Node[] | null} */
|
|
2676
|
+
let prelude = null;
|
|
2677
|
+
// A returned qualified rule always has a block (only the `{` exit returns a
|
|
2678
|
+
// rule), so `_setBlock` always runs — no blockStart / blockEnd default needed.
|
|
2432
2679
|
|
|
2433
2680
|
// Skip mode leaves `prelude` empty (selector text is recovered from the
|
|
2434
2681
|
// rule's byte range, not its nodes); `first`/`second` still track the first
|
|
2435
2682
|
// two non-whitespace tokens the `--foo: {` disambiguation below needs.
|
|
2436
2683
|
const skip = _skipSelectorPrelude;
|
|
2684
|
+
// Non-skip: first two non-whitespace prelude nodes (computed at the `{`).
|
|
2437
2685
|
let first = /** @type {Node} */ (/** @type {unknown} */ (0));
|
|
2438
2686
|
let second = /** @type {Node} */ (/** @type {unknown} */ (0));
|
|
2687
|
+
// Skip mode: the same two tokens tracked by token type + start, so a dropped
|
|
2688
|
+
// leaf selector token needs no materialized node just for the disambiguation.
|
|
2689
|
+
// `0` (no token type) = unset.
|
|
2690
|
+
let firstTT = 0;
|
|
2691
|
+
let firstStart = 0;
|
|
2692
|
+
let secondTT = 0;
|
|
2439
2693
|
|
|
2440
2694
|
// Process input
|
|
2441
2695
|
for (;;) {
|
|
@@ -2450,12 +2704,17 @@ const consumeAQualifiedRule = (ts, stopToken, nested = false) => {
|
|
|
2450
2704
|
// This is a parse error. If nested is true, return nothing. Otherwise, consume a token and append the result to rule’s prelude.
|
|
2451
2705
|
else if (t.type === TT_RIGHT_CURLY_BRACKET) {
|
|
2452
2706
|
if (nested) return undefined;
|
|
2453
|
-
const node = consumeATokenAsNode(ts);
|
|
2454
2707
|
if (skip) {
|
|
2455
|
-
|
|
2456
|
-
|
|
2708
|
+
// Stray `}` is a non-ws token; record its type/start, drop the node.
|
|
2709
|
+
if (firstTT === 0) {
|
|
2710
|
+
firstTT = t.type;
|
|
2711
|
+
firstStart = t.start;
|
|
2712
|
+
} else if (secondTT === 0) {
|
|
2713
|
+
secondTT = t.type;
|
|
2714
|
+
}
|
|
2715
|
+
ts.advance();
|
|
2457
2716
|
} else {
|
|
2458
|
-
prelude.push(
|
|
2717
|
+
(prelude || (prelude = _takeList())).push(consumeATokenAsNode(ts));
|
|
2459
2718
|
}
|
|
2460
2719
|
continue;
|
|
2461
2720
|
}
|
|
@@ -2466,34 +2725,45 @@ const consumeAQualifiedRule = (ts, stopToken, nested = false) => {
|
|
|
2466
2725
|
// (This disambiguates custom-property declarations from nested qualified rules — `--foo: { … }` at top level of a block is a declaration, not a rule.)
|
|
2467
2726
|
// Otherwise, consume a block from input, and let child rules be the result.
|
|
2468
2727
|
else if (t.type === TT_LEFT_CURLY_BRACKET) {
|
|
2469
|
-
|
|
2728
|
+
// `--foo: {` disambiguation: are the first two non-ws prelude tokens an
|
|
2729
|
+
// ident starting with `--` followed by a colon? Skip mode reads the
|
|
2730
|
+
// tracked token type/start; non-skip reads the materialized prelude.
|
|
2731
|
+
let dashedDeclaration;
|
|
2732
|
+
if (skip) {
|
|
2733
|
+
dashedDeclaration =
|
|
2734
|
+
firstTT === TT_IDENTIFIER &&
|
|
2735
|
+
ts.input.startsWith("--", firstStart) &&
|
|
2736
|
+
secondTT === TT_COLON;
|
|
2737
|
+
} else if (prelude === null) {
|
|
2738
|
+
dashedDeclaration = false;
|
|
2739
|
+
} else {
|
|
2470
2740
|
let firstIdx = 0;
|
|
2471
2741
|
/* istanbul ignore next -- @preserve: leading whitespace is discarded before the rule, so the prelude never starts with it */
|
|
2472
2742
|
while (
|
|
2473
2743
|
firstIdx < prelude.length &&
|
|
2474
|
-
|
|
2744
|
+
_nodeTypeOf(prelude[firstIdx]) === T_WHITESPACE
|
|
2475
2745
|
) {
|
|
2476
2746
|
firstIdx++;
|
|
2477
2747
|
}
|
|
2478
2748
|
let secondIdx = firstIdx + 1;
|
|
2479
2749
|
while (
|
|
2480
2750
|
secondIdx < prelude.length &&
|
|
2481
|
-
|
|
2751
|
+
_nodeTypeOf(prelude[secondIdx]) === T_WHITESPACE
|
|
2482
2752
|
) {
|
|
2483
2753
|
secondIdx++;
|
|
2484
2754
|
}
|
|
2485
2755
|
first = prelude[firstIdx];
|
|
2486
2756
|
second = prelude[secondIdx];
|
|
2757
|
+
dashedDeclaration =
|
|
2758
|
+
first &&
|
|
2759
|
+
_nodeTypeOf(first) === T_IDENT &&
|
|
2760
|
+
// Test the source bytes directly — avoids forcing the lazy `value`
|
|
2761
|
+
// slice just to check the `--` custom-property prefix.
|
|
2762
|
+
ts.input.startsWith("--", _nodeStartOf(first)) &&
|
|
2763
|
+
second &&
|
|
2764
|
+
_nodeTypeOf(second) === T_COLON;
|
|
2487
2765
|
}
|
|
2488
|
-
if (
|
|
2489
|
-
first &&
|
|
2490
|
-
_nType(first) === T_IDENT &&
|
|
2491
|
-
// Test the source bytes directly — avoids forcing the lazy `value`
|
|
2492
|
-
// slice just to check the `--` custom-property prefix.
|
|
2493
|
-
ts.input.startsWith("--", _nStart(first)) &&
|
|
2494
|
-
second &&
|
|
2495
|
-
_nType(second) === T_COLON
|
|
2496
|
-
) {
|
|
2766
|
+
if (dashedDeclaration) {
|
|
2497
2767
|
/* istanbul ignore if -- @preserve: when nested, `declarationStartLikely` routes every `--x:` to consumeADeclaration (which accepts custom properties), so this fallthrough is unreachable */
|
|
2498
2768
|
if (nested) {
|
|
2499
2769
|
consumeTheRemnantsOfABadDeclaration(ts, true);
|
|
@@ -2502,29 +2772,45 @@ const consumeAQualifiedRule = (ts, stopToken, nested = false) => {
|
|
|
2502
2772
|
}
|
|
2503
2773
|
return undefined;
|
|
2504
2774
|
}
|
|
2775
|
+
if (prelude !== null) _setPrelude(rule, prelude);
|
|
2505
2776
|
const block = consumeABlock(ts);
|
|
2506
2777
|
_setBody(rule, block.decls, block.rules);
|
|
2507
|
-
_setBlock(rule, block.blockStart
|
|
2778
|
+
_setBlock(rule, block.blockStart);
|
|
2508
2779
|
_setEnd(rule, block.blockEnd);
|
|
2509
2780
|
return rule;
|
|
2510
2781
|
}
|
|
2511
2782
|
|
|
2512
2783
|
// anything else
|
|
2513
2784
|
// Consume a component value from input and append the result to rule’s prelude.
|
|
2514
|
-
const node = consumeAComponentValue(ts, t);
|
|
2515
2785
|
if (skip) {
|
|
2516
|
-
|
|
2517
|
-
// url like `:unknown(url(x))`) so the url visitor still rewrites them;
|
|
2518
|
-
// other selector tokens have no non-modules consumer, so drop them.
|
|
2519
|
-
const ty = _nType(node);
|
|
2520
|
-
if (ty === T_FUNCTION || ty === T_URL) prelude.push(node);
|
|
2786
|
+
const tt = t.type;
|
|
2521
2787
|
// Track the first two non-whitespace tokens for the disambiguation above.
|
|
2522
|
-
if (
|
|
2523
|
-
if (
|
|
2524
|
-
|
|
2788
|
+
if (tt !== TT_WHITESPACE) {
|
|
2789
|
+
if (firstTT === 0) {
|
|
2790
|
+
firstTT = tt;
|
|
2791
|
+
firstStart = t.start;
|
|
2792
|
+
} else if (secondTT === 0) {
|
|
2793
|
+
secondTT = tt;
|
|
2794
|
+
}
|
|
2795
|
+
}
|
|
2796
|
+
// Only functions (which may hold a url like `:unknown(url(x))`) and the
|
|
2797
|
+
// `(` / `[` blocks that must be balanced are materialized; the url
|
|
2798
|
+
// visitor keeps url / function nodes. Every other selector leaf token has
|
|
2799
|
+
// no non-modules consumer — drop it without building a node.
|
|
2800
|
+
if (
|
|
2801
|
+
tt === TT_FUNCTION ||
|
|
2802
|
+
(tt >= TT_LEFT_PARENTHESIS && tt <= TT_LEFT_CURLY_BRACKET)
|
|
2803
|
+
) {
|
|
2804
|
+
const node = consumeAComponentValue(ts, t);
|
|
2805
|
+
const ty = _nodeTypeOf(node);
|
|
2806
|
+
if (ty === T_FUNCTION || ty === T_URL) {
|
|
2807
|
+
(prelude || (prelude = _takeList())).push(node);
|
|
2808
|
+
}
|
|
2809
|
+
} else {
|
|
2810
|
+
ts.advance();
|
|
2525
2811
|
}
|
|
2526
2812
|
} else {
|
|
2527
|
-
prelude.push(
|
|
2813
|
+
(prelude || (prelude = _takeList())).push(consumeAComponentValue(ts, t));
|
|
2528
2814
|
}
|
|
2529
2815
|
}
|
|
2530
2816
|
};
|
|
@@ -2601,22 +2887,26 @@ const declarationStartLikely = (ts) => {
|
|
|
2601
2887
|
const consumeABlocksContents = (ts, onNode) => {
|
|
2602
2888
|
/** @type {Declaration[]} */
|
|
2603
2889
|
const decls = [];
|
|
2604
|
-
|
|
2605
|
-
|
|
2890
|
+
// Child rules are the common empty case (most rules carry only declarations),
|
|
2891
|
+
// so `rules` is allocated lazily and returned as the shared frozen
|
|
2892
|
+
// `_EMPTY_LIST` when nothing was appended — one fewer array per rule. `decls`
|
|
2893
|
+
// stays eager so the hot declaration append keeps a branch-free `push`.
|
|
2894
|
+
/** @type {Rule[] | null} */
|
|
2895
|
+
let rules = null;
|
|
2606
2896
|
|
|
2607
2897
|
// Process input:
|
|
2608
2898
|
for (;;) {
|
|
2609
2899
|
const t = ts.next();
|
|
2610
2900
|
|
|
2611
2901
|
// <whitespace-token> / <semicolon-token>
|
|
2612
|
-
// Discard a token from input.
|
|
2902
|
+
// Discard a token from input (`t` was just peeked and is non-EOF).
|
|
2613
2903
|
if (t.type === TT_WHITESPACE || t.type === TT_SEMICOLON) {
|
|
2614
|
-
ts.
|
|
2904
|
+
ts.advance();
|
|
2615
2905
|
}
|
|
2616
2906
|
// <EOF-token> / <}-token>
|
|
2617
2907
|
// Return decls and rules.
|
|
2618
2908
|
else if (t.type === TT_EOF || t.type === TT_RIGHT_CURLY_BRACKET) {
|
|
2619
|
-
return { decls, rules };
|
|
2909
|
+
return { decls, rules: rules || _EMPTY_LIST };
|
|
2620
2910
|
}
|
|
2621
2911
|
// <at-keyword-token>
|
|
2622
2912
|
// Consume an at-rule from input, with nested set to true. If a rule was returned, append it to rules.
|
|
@@ -2624,7 +2914,7 @@ const consumeABlocksContents = (ts, onNode) => {
|
|
|
2624
2914
|
const atRule = consumeAnAtRule(ts, true);
|
|
2625
2915
|
if (atRule) {
|
|
2626
2916
|
if (onNode) onNode(atRule);
|
|
2627
|
-
else rules.push(atRule);
|
|
2917
|
+
else (rules || (rules = [])).push(atRule);
|
|
2628
2918
|
}
|
|
2629
2919
|
}
|
|
2630
2920
|
// anything else
|
|
@@ -2647,7 +2937,7 @@ const consumeABlocksContents = (ts, onNode) => {
|
|
|
2647
2937
|
const rule = consumeAQualifiedRule(ts, TT_SEMICOLON, true);
|
|
2648
2938
|
if (rule) {
|
|
2649
2939
|
if (onNode) onNode(rule);
|
|
2650
|
-
else rules.push(rule);
|
|
2940
|
+
else (rules || (rules = [])).push(rule);
|
|
2651
2941
|
}
|
|
2652
2942
|
}
|
|
2653
2943
|
}
|
|
@@ -2692,54 +2982,70 @@ const consumeADeclaration = (ts, nested = false) => {
|
|
|
2692
2982
|
const { input } = ts;
|
|
2693
2983
|
// Let decl be a new declaration, with an initially empty name and a value set to an empty list.
|
|
2694
2984
|
const start = ts.next().start;
|
|
2695
|
-
//
|
|
2696
|
-
// `
|
|
2985
|
+
// nameEnd (= start) / important (unset) keep their container defaults;
|
|
2986
|
+
// `value` is set unconditionally at step 5 below.
|
|
2697
2987
|
const decl = /** @type {Declaration} */ (
|
|
2698
|
-
|
|
2988
|
+
_makeContainer(T_DECLARATION, start, start)
|
|
2699
2989
|
);
|
|
2700
2990
|
|
|
2701
2991
|
// 1. If the next token is an <ident-token>, consume a token from input and set decl's name to the returned token's value.
|
|
2702
2992
|
// Otherwise, consume the remnants of a bad declaration from input, with nested, and return nothing.
|
|
2703
2993
|
if (ts.next().type === TT_IDENTIFIER) {
|
|
2704
2994
|
const head = ts.consume();
|
|
2705
|
-
_setName(decl,
|
|
2995
|
+
_setName(decl, head.start, head.end);
|
|
2706
2996
|
} else {
|
|
2707
2997
|
consumeTheRemnantsOfABadDeclaration(ts, nested);
|
|
2708
2998
|
return undefined;
|
|
2709
2999
|
}
|
|
2710
3000
|
|
|
2711
3001
|
// 2. Discard whitespace from input.
|
|
2712
|
-
while (ts.next().type === TT_WHITESPACE) ts.
|
|
3002
|
+
while (ts.next().type === TT_WHITESPACE) ts.advance();
|
|
2713
3003
|
|
|
2714
3004
|
// 3. If the next token is a <colon-token>, discard a token from input.
|
|
2715
3005
|
// Otherwise, consume the remnants of a bad declaration from input, with nested, and return nothing.
|
|
2716
3006
|
if (ts.next().type === TT_COLON) {
|
|
2717
|
-
ts.
|
|
3007
|
+
ts.advance();
|
|
2718
3008
|
} else {
|
|
2719
3009
|
consumeTheRemnantsOfABadDeclaration(ts, nested);
|
|
2720
3010
|
return undefined;
|
|
2721
3011
|
}
|
|
2722
3012
|
|
|
2723
3013
|
// 4. Discard whitespace from input.
|
|
2724
|
-
while (ts.next().type === TT_WHITESPACE) ts.
|
|
3014
|
+
while (ts.next().type === TT_WHITESPACE) ts.advance();
|
|
3015
|
+
|
|
3016
|
+
// Step 8's custom-property test, computed early so the value parse can bail.
|
|
3017
|
+
const isCustomProperty = input.startsWith("--", start);
|
|
2725
3018
|
|
|
2726
3019
|
// 5. Consume a list of component values from input, with nested, and with <semicolon-token> as the stop token, and set decl's value to the result.
|
|
2727
|
-
|
|
2728
|
-
|
|
3020
|
+
// A nested non-custom declaration bails on a top-level `{` — step 8 would
|
|
3021
|
+
// reject it and the caller restores its mark, so parsing the block (the
|
|
3022
|
+
// entire nested-rule body, re-parsed as a qualified rule after the
|
|
3023
|
+
// restore) would be pure waste.
|
|
3024
|
+
const value = consumeAListOfComponentValues(
|
|
3025
|
+
ts,
|
|
3026
|
+
TT_SEMICOLON,
|
|
3027
|
+
nested,
|
|
3028
|
+
nested && !isCustomProperty
|
|
3029
|
+
);
|
|
3030
|
+
if (value === null) return undefined;
|
|
3031
|
+
// `_setValue` waits until step 9: steps 6-8 still trim / scan the scratch,
|
|
3032
|
+
// and the store consumes it when sealing.
|
|
2729
3033
|
_setEnd(decl, ts.next().start);
|
|
2730
3034
|
|
|
2731
3035
|
// 6. If the last two non-<whitespace-token>s in decl's value are a <delim-token> with the value "!" followed by an <ident-token> with a value that is an ASCII case-insensitive match for "important", remove them from decl's value and set decl's important flag.
|
|
2732
3036
|
{
|
|
2733
3037
|
let last = value.length - 1;
|
|
2734
|
-
while (last >= 0 &&
|
|
3038
|
+
while (last >= 0 && _nodeTypeOf(value[last]) === T_WHITESPACE) last--;
|
|
2735
3039
|
let prev = last - 1;
|
|
2736
|
-
while (prev >= 0 &&
|
|
3040
|
+
while (prev >= 0 && _nodeTypeOf(value[prev]) === T_WHITESPACE) prev--;
|
|
3041
|
+
// `!` delim first: it's almost always absent, and `_nodeValueOf` allocates
|
|
3042
|
+
// a slice — this order pays it only for genuine `!important` candidates.
|
|
2737
3043
|
if (
|
|
2738
3044
|
prev >= 0 &&
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
|
|
3045
|
+
_nodeTypeOf(value[prev]) === T_DELIM &&
|
|
3046
|
+
input.charCodeAt(_nodeStartOf(value[prev])) === CC_EXCLAMATION &&
|
|
3047
|
+
_nodeTypeOf(value[last]) === T_IDENT &&
|
|
3048
|
+
equalsLowerCase(_nodeValueOf(value[last]), "important")
|
|
2743
3049
|
) {
|
|
2744
3050
|
_setImportant(decl);
|
|
2745
3051
|
value.length = prev;
|
|
@@ -2747,7 +3053,10 @@ const consumeADeclaration = (ts, nested = false) => {
|
|
|
2747
3053
|
}
|
|
2748
3054
|
|
|
2749
3055
|
// 7. While the last item in decl's value is a <whitespace-token>, remove that token.
|
|
2750
|
-
while (
|
|
3056
|
+
while (
|
|
3057
|
+
value.length > 0 &&
|
|
3058
|
+
_nodeTypeOf(value[value.length - 1]) === T_WHITESPACE
|
|
3059
|
+
) {
|
|
2751
3060
|
value.pop();
|
|
2752
3061
|
}
|
|
2753
3062
|
|
|
@@ -2755,17 +3064,17 @@ const consumeADeclaration = (ts, nested = false) => {
|
|
|
2755
3064
|
// Otherwise, if decl's value contains a top-level simple block with an associated token of <{-token>, return nothing.
|
|
2756
3065
|
// (That is, a top-level {}-block is only allowed as the entire value of a non-custom property — for CSS Nesting, `consumeABlocksContents`'s `mark` / `restore a mark` will retry the input as a qualified rule.)
|
|
2757
3066
|
// Otherwise, accept the declaration. (The spec also checks "contains any non-whitespace-tokens at the top level" → return nothing; we keep empty-value declarations because callers — e.g. `@value name:;` — rely on them.)
|
|
2758
|
-
const isCustomProperty = input.startsWith("--", start);
|
|
2759
3067
|
if (!isCustomProperty) {
|
|
2760
3068
|
for (let i = 0; i < value.length; i++) {
|
|
2761
3069
|
const v = value[i];
|
|
2762
|
-
if (
|
|
3070
|
+
if (_nodeTypeOf(v) === T_SIMPLE_BLOCK && _nodeTokenOf(v) === "{") {
|
|
2763
3071
|
return undefined;
|
|
2764
3072
|
}
|
|
2765
3073
|
}
|
|
2766
3074
|
}
|
|
2767
3075
|
|
|
2768
3076
|
// 9. Return decl.
|
|
3077
|
+
_setValue(decl, value);
|
|
2769
3078
|
return decl;
|
|
2770
3079
|
};
|
|
2771
3080
|
|
|
@@ -2774,11 +3083,16 @@ const consumeADeclaration = (ts, nested = false) => {
|
|
|
2774
3083
|
* @param {TokenStream} ts token stream
|
|
2775
3084
|
* @param {number=} stopToken token type that terminates the list (left unconsumed)
|
|
2776
3085
|
* @param {boolean=} nested true inside a `{}` block — a top-level `}` ends the list (left unconsumed)
|
|
2777
|
-
* @
|
|
3086
|
+
* @param {boolean=} bailOnCurly abort with `null` on a top-level `{` (left unconsumed) — for callers that would reject the list anyway (consume-a-declaration step 8) and restore a mark
|
|
3087
|
+
* @returns {ComponentValue[] | null} consumed component values, or `null` when `bailOnCurly` hit
|
|
2778
3088
|
*/
|
|
2779
|
-
const consumeAListOfComponentValues = (
|
|
2780
|
-
|
|
2781
|
-
|
|
3089
|
+
const consumeAListOfComponentValues = (
|
|
3090
|
+
ts,
|
|
3091
|
+
stopToken,
|
|
3092
|
+
nested = false,
|
|
3093
|
+
bailOnCurly = false
|
|
3094
|
+
) => {
|
|
3095
|
+
const values = /** @type {ComponentValue[]} */ (_takeList());
|
|
2782
3096
|
// Process input
|
|
2783
3097
|
for (;;) {
|
|
2784
3098
|
const t = ts.next();
|
|
@@ -2797,12 +3111,17 @@ const consumeAListOfComponentValues = (ts, stopToken, nested = false) => {
|
|
|
2797
3111
|
const closer = consumeATokenAsNode(ts);
|
|
2798
3112
|
// Keep unless the type is explicitly marked skip (1); an out-of-range
|
|
2799
3113
|
// lookup on a short `skip.types` yields `undefined`, which must not drop.
|
|
2800
|
-
if (!_skipActive || _skipTypes[
|
|
3114
|
+
if (!_skipActive || _skipTypes[_nodeTypeOf(closer)] !== 1) {
|
|
3115
|
+
values.push(closer);
|
|
3116
|
+
}
|
|
2801
3117
|
continue;
|
|
2802
3118
|
}
|
|
3119
|
+
// A top-level `{` dooms the list for a bailing caller — stop before the
|
|
3120
|
+
// whole block is parsed only to be thrown away on the caller's restore.
|
|
3121
|
+
if (bailOnCurly && t.type === TT_LEFT_CURLY_BRACKET) return null;
|
|
2803
3122
|
// anything else
|
|
2804
3123
|
// Consume a component value from input, and append the result to values.
|
|
2805
|
-
// Skipped leaf types short-circuit before materializing: no
|
|
3124
|
+
// Skipped leaf types short-circuit before materializing: no column slot is
|
|
2806
3125
|
// written and no node is built (blocks / functions never skip here).
|
|
2807
3126
|
const tt = t.type;
|
|
2808
3127
|
if (
|
|
@@ -2811,11 +3130,12 @@ const consumeAListOfComponentValues = (ts, stopToken, nested = false) => {
|
|
|
2811
3130
|
!(tt >= TT_LEFT_PARENTHESIS && tt <= TT_LEFT_CURLY_BRACKET) &&
|
|
2812
3131
|
_skipTypes[_ttToNodeType[tt]] === 1
|
|
2813
3132
|
) {
|
|
2814
|
-
|
|
3133
|
+
// `t` was just peeked and is a skipped value leaf (never EOF).
|
|
3134
|
+
ts.advance();
|
|
2815
3135
|
continue;
|
|
2816
3136
|
}
|
|
2817
3137
|
const node = consumeAComponentValue(ts, t);
|
|
2818
|
-
if (!_skipActive || _skipTypes[
|
|
3138
|
+
if (!_skipActive || _skipTypes[_nodeTypeOf(node)] !== 1) values.push(node);
|
|
2819
3139
|
}
|
|
2820
3140
|
};
|
|
2821
3141
|
|
|
@@ -2840,10 +3160,11 @@ const consumeAComponentValue = (ts, t = ts.next()) => {
|
|
|
2840
3160
|
}
|
|
2841
3161
|
// anything else
|
|
2842
3162
|
// Consume a token from input and return the result. (Asserted: not EOF.)
|
|
2843
|
-
// Inlined `consumeATokenAsNode`: `t` is already the peeked next token
|
|
2844
|
-
// advance past it and materialize it directly —
|
|
2845
|
-
// component value (the bulk of the nodes
|
|
2846
|
-
|
|
3163
|
+
// Inlined `consumeATokenAsNode`: `t` is already the peeked next token (and not
|
|
3164
|
+
// EOF), so `advance` past it and materialize it directly — no redundant
|
|
3165
|
+
// `next()` and one fewer call per leaf component value (the bulk of the nodes
|
|
3166
|
+
// on a large stylesheet).
|
|
3167
|
+
ts.advance();
|
|
2847
3168
|
return /** @type {ComponentValue} */ (tokenToNode(t));
|
|
2848
3169
|
};
|
|
2849
3170
|
|
|
@@ -2861,11 +3182,11 @@ const consumeASimpleBlock = (ts) => {
|
|
|
2861
3182
|
|
|
2862
3183
|
// Let block be a new simple block with its associated token set to the next token and with its value initially set to an empty list.
|
|
2863
3184
|
const block = /** @type {SimpleBlock} */ (
|
|
2864
|
-
|
|
3185
|
+
_makeContainer(T_SIMPLE_BLOCK, open.start, open.end)
|
|
2865
3186
|
);
|
|
2866
3187
|
_setToken(block, token);
|
|
2867
|
-
|
|
2868
|
-
|
|
3188
|
+
// Sealed (`_setValue`) at the return, once complete.
|
|
3189
|
+
const val = _takeList();
|
|
2869
3190
|
|
|
2870
3191
|
// Discard a token from input.
|
|
2871
3192
|
ts.discard();
|
|
@@ -2879,6 +3200,7 @@ const consumeASimpleBlock = (ts) => {
|
|
|
2879
3200
|
// Discard a token from input. Return block.
|
|
2880
3201
|
if (t.type === TT_EOF || t.type === ending) {
|
|
2881
3202
|
ts.discard();
|
|
3203
|
+
_setValue(block, val);
|
|
2882
3204
|
_setEnd(block, t.end);
|
|
2883
3205
|
return block;
|
|
2884
3206
|
}
|
|
@@ -2895,16 +3217,15 @@ const consumeASimpleBlock = (ts) => {
|
|
|
2895
3217
|
* @returns {FunctionNode | undefined} the consumed function node
|
|
2896
3218
|
*/
|
|
2897
3219
|
const consumeAFunction = (ts) => {
|
|
2898
|
-
const { input } = ts;
|
|
2899
3220
|
// Assert (spec): the next token is a <function-token>.
|
|
2900
3221
|
// Consume a token from input, and let function be a new function with its name equal the returned token’s value, and a value set to an empty list.
|
|
2901
3222
|
const tFn = ts.consume();
|
|
2902
3223
|
const fn = /** @type {FunctionNode} */ (
|
|
2903
|
-
|
|
3224
|
+
_makeContainer(T_FUNCTION, tFn.start, tFn.end)
|
|
2904
3225
|
);
|
|
2905
|
-
_setName(fn,
|
|
2906
|
-
|
|
2907
|
-
|
|
3226
|
+
_setName(fn, tFn.start, tFn.end - 1);
|
|
3227
|
+
// Sealed (`_setValue`) at the return, once complete.
|
|
3228
|
+
const val = _takeList();
|
|
2908
3229
|
|
|
2909
3230
|
// Process input
|
|
2910
3231
|
for (;;) {
|
|
@@ -2915,6 +3236,7 @@ const consumeAFunction = (ts) => {
|
|
|
2915
3236
|
// <)-token>
|
|
2916
3237
|
// Discard a token from input. Return function.
|
|
2917
3238
|
ts.discard();
|
|
3239
|
+
_setValue(fn, val);
|
|
2918
3240
|
_setEnd(fn, t.end);
|
|
2919
3241
|
return fn;
|
|
2920
3242
|
}
|
|
@@ -2929,11 +3251,12 @@ const consumeAFunction = (ts) => {
|
|
|
2929
3251
|
!(tt >= TT_LEFT_PARENTHESIS && tt <= TT_LEFT_CURLY_BRACKET) &&
|
|
2930
3252
|
_skipTypes[_ttToNodeType[tt]] === 1
|
|
2931
3253
|
) {
|
|
2932
|
-
|
|
3254
|
+
// `t` was just peeked and is a skipped value leaf (never EOF).
|
|
3255
|
+
ts.advance();
|
|
2933
3256
|
continue;
|
|
2934
3257
|
}
|
|
2935
3258
|
const node = consumeAComponentValue(ts, t);
|
|
2936
|
-
if (!_skipActive || _skipTypes[
|
|
3259
|
+
if (!_skipActive || _skipTypes[_nodeTypeOf(node)] !== 1) val.push(node);
|
|
2937
3260
|
}
|
|
2938
3261
|
};
|
|
2939
3262
|
|
|
@@ -2991,13 +3314,18 @@ const _escapeIdentifier = (str) => {
|
|
|
2991
3314
|
}
|
|
2992
3315
|
output = lastFlush === 0 ? str : output + str.slice(lastFlush);
|
|
2993
3316
|
|
|
2994
|
-
|
|
2995
|
-
|
|
2996
|
-
|
|
3317
|
+
// `-` and digits are class 0 (never escaped above), so testing `str`'s lead
|
|
3318
|
+
// char codes is equivalent to regexes over `output` — and keeps the common
|
|
3319
|
+
// nothing-to-do call regex-free.
|
|
3320
|
+
const first = str.charCodeAt(0);
|
|
3321
|
+
if (
|
|
3322
|
+
first === CC_HYPHEN_MINUS &&
|
|
3323
|
+
(str.charCodeAt(1) === CC_HYPHEN_MINUS || _isDigit(str.charCodeAt(1)))
|
|
3324
|
+
) {
|
|
2997
3325
|
output = `\\-${output.slice(1)}`;
|
|
2998
|
-
} else if (
|
|
3326
|
+
} else if (_isDigit(first)) {
|
|
2999
3327
|
// A leading digit becomes `\3<digit> `, another `\HEX ` run to clean up.
|
|
3000
|
-
output = `\\3${
|
|
3328
|
+
output = `\\3${str.charAt(0)} ${output.slice(1)}`;
|
|
3001
3329
|
needSpaceFix = true;
|
|
3002
3330
|
}
|
|
3003
3331
|
|
|
@@ -3031,7 +3359,6 @@ const _escapeIdentifier = (str) => {
|
|
|
3031
3359
|
*/
|
|
3032
3360
|
const gobbleHex = (str, start) => {
|
|
3033
3361
|
let hex = "";
|
|
3034
|
-
let spaceTerminated = false;
|
|
3035
3362
|
|
|
3036
3363
|
for (let i = 0; i < 6; i++) {
|
|
3037
3364
|
const code = str.charCodeAt(start + i);
|
|
@@ -3040,8 +3367,6 @@ const gobbleHex = (str, start) => {
|
|
|
3040
3367
|
(code >= 48 && code <= 57) ||
|
|
3041
3368
|
(code >= 65 && code <= 70) ||
|
|
3042
3369
|
(code >= 97 && code <= 102);
|
|
3043
|
-
// https://drafts.csswg.org/css-syntax/#consume-escaped-code-point
|
|
3044
|
-
spaceTerminated = code === 32;
|
|
3045
3370
|
if (!valid) break;
|
|
3046
3371
|
// parseInt below is case-insensitive, so keep the original char.
|
|
3047
3372
|
hex += str[start + i];
|
|
@@ -3049,6 +3374,16 @@ const gobbleHex = (str, start) => {
|
|
|
3049
3374
|
|
|
3050
3375
|
if (hex.length === 0) return undefined;
|
|
3051
3376
|
|
|
3377
|
+
// One trailing whitespace terminates the escape, matching the tokenizer's
|
|
3378
|
+
// `_consumeAnEscapedCodePoint` — including after a full 6-digit escape, for
|
|
3379
|
+
// any CSS whitespace (not just space), plus the extra LF of a CRLF pair.
|
|
3380
|
+
// https://drafts.csswg.org/css-syntax/#consume-escaped-code-point
|
|
3381
|
+
let consumed = hex.length;
|
|
3382
|
+
const trail = str.charCodeAt(start + hex.length);
|
|
3383
|
+
if (_isWhiteSpace(trail)) {
|
|
3384
|
+
consumed = consumeExtraNewline(trail, str, start + hex.length + 1) - start;
|
|
3385
|
+
}
|
|
3386
|
+
|
|
3052
3387
|
const codePoint = Number.parseInt(hex, 16);
|
|
3053
3388
|
const isSurrogate = codePoint >= 0xd800 && codePoint <= 0xdfff;
|
|
3054
3389
|
|
|
@@ -3056,13 +3391,10 @@ const gobbleHex = (str, start) => {
|
|
|
3056
3391
|
// "If this number is zero, or is for a surrogate, or is greater than the maximum allowed code point"
|
|
3057
3392
|
// https://drafts.csswg.org/css-syntax/#maximum-allowed-code-point
|
|
3058
3393
|
if (isSurrogate || codePoint === 0x0000 || codePoint > 0x10ffff) {
|
|
3059
|
-
return ["�",
|
|
3394
|
+
return ["�", consumed];
|
|
3060
3395
|
}
|
|
3061
3396
|
|
|
3062
|
-
return [
|
|
3063
|
-
String.fromCodePoint(codePoint),
|
|
3064
|
-
hex.length + (spaceTerminated ? 1 : 0)
|
|
3065
|
-
];
|
|
3397
|
+
return [String.fromCodePoint(codePoint), consumed];
|
|
3066
3398
|
};
|
|
3067
3399
|
|
|
3068
3400
|
/**
|
|
@@ -3108,6 +3440,67 @@ const _unescapeIdentifier = (str) => {
|
|
|
3108
3440
|
const escapeIdentifier = makeCacheable(_escapeIdentifier);
|
|
3109
3441
|
const unescapeIdentifier = makeCacheable(_unescapeIdentifier);
|
|
3110
3442
|
|
|
3443
|
+
// A url-token / url-string value's escaped newlines (`url("im\<newline>g.png")`).
|
|
3444
|
+
const STRING_MULTILINE = /\\[\n\r\f]/g;
|
|
3445
|
+
// Leading / trailing CSS whitespace inside a quoted url value.
|
|
3446
|
+
const TRIM_WHITE_SPACES = /(^[ \t\n\r\f]*|[ \t\n\r\f]*$)/g;
|
|
3447
|
+
// One CSS escape: `\` + up to 6 hex digits (+ optional whitespace) or any char.
|
|
3448
|
+
const UNESCAPE = /\\([0-9a-f]{1,6}[ \t\n\r\f]?|[\s\S])/gi;
|
|
3449
|
+
|
|
3450
|
+
/**
|
|
3451
|
+
* Normalize a url value (a url-token's content or a url string's body) into
|
|
3452
|
+
* the form requests are resolved from: escaped newlines removed (string form),
|
|
3453
|
+
* edge whitespace trimmed, CSS escapes and percent-encoding decoded
|
|
3454
|
+
* (`data:` URIs excepted).
|
|
3455
|
+
* @param {string} str url string
|
|
3456
|
+
* @param {boolean} isString is url wrapped in quotes
|
|
3457
|
+
* @returns {string} normalized url
|
|
3458
|
+
*/
|
|
3459
|
+
const normalizeUrl = (str, isString) => {
|
|
3460
|
+
// Fast paths: skip the regex engine for the common URL with no escape and
|
|
3461
|
+
// no edge whitespace (e.g. `./img.png`). Each guard is equivalent to the
|
|
3462
|
+
// regex being a no-op.
|
|
3463
|
+
// Remove escaped newlines from a string-token url like `url("im\<newline>g.png")`.
|
|
3464
|
+
if (isString && str.includes("\\")) {
|
|
3465
|
+
str = str.replace(STRING_MULTILINE, "");
|
|
3466
|
+
}
|
|
3467
|
+
|
|
3468
|
+
// Remove unnecessary spaces from `url(" img.png ")`
|
|
3469
|
+
if (
|
|
3470
|
+
str.length !== 0 &&
|
|
3471
|
+
(_isWhiteSpace(str.charCodeAt(0)) ||
|
|
3472
|
+
_isWhiteSpace(str.charCodeAt(str.length - 1)))
|
|
3473
|
+
) {
|
|
3474
|
+
str = str.replace(TRIM_WHITE_SPACES, "");
|
|
3475
|
+
}
|
|
3476
|
+
|
|
3477
|
+
// Unescape
|
|
3478
|
+
if (str.includes("\\")) {
|
|
3479
|
+
str = str.replace(UNESCAPE, (match) => {
|
|
3480
|
+
if (match.length > 2) {
|
|
3481
|
+
return String.fromCharCode(Number.parseInt(match.slice(1).trim(), 16));
|
|
3482
|
+
}
|
|
3483
|
+
return match[1];
|
|
3484
|
+
});
|
|
3485
|
+
}
|
|
3486
|
+
|
|
3487
|
+
// Char-code gate so the dominant non-`data:` url skips the regex test.
|
|
3488
|
+
if ((str.charCodeAt(0) | 0x20) === CC_LOWER_D && /^data:/i.test(str)) {
|
|
3489
|
+
return str;
|
|
3490
|
+
}
|
|
3491
|
+
|
|
3492
|
+
if (str.includes("%")) {
|
|
3493
|
+
// Convert `url('%2E/img.png')` -> `url('./img.png')`
|
|
3494
|
+
try {
|
|
3495
|
+
str = decodeURIComponent(str);
|
|
3496
|
+
} catch (_err) {
|
|
3497
|
+
// Ignore
|
|
3498
|
+
}
|
|
3499
|
+
}
|
|
3500
|
+
|
|
3501
|
+
return str;
|
|
3502
|
+
};
|
|
3503
|
+
|
|
3111
3504
|
// CSS-typed views over the generic visitor machinery (`util/SourceProcessor`),
|
|
3112
3505
|
// re-exported so consumers keep importing them from this module.
|
|
3113
3506
|
/**
|
|
@@ -3151,6 +3544,148 @@ const TOP_LEVEL_CONSUMERS = {
|
|
|
3151
3544
|
* @property {boolean=} atRulePrelude drop at-rule preludes — the at-rule and its block are still produced (default false)
|
|
3152
3545
|
*/
|
|
3153
3546
|
|
|
3547
|
+
// Per-parse walk state in module slots (same pattern as `_skip*`) so the walk
|
|
3548
|
+
// functions below are module-level constants: one function identity across
|
|
3549
|
+
// parses keeps the recursive per-node call sites monomorphic and drops the
|
|
3550
|
+
// per-parse closure allocations.
|
|
3551
|
+
/** @typedef {import("../util/SourceProcessor").CompiledVisitorBucket<CssPath>} CompiledVisitorBucket */
|
|
3552
|
+
/** @type {CompiledVisitorMap} */
|
|
3553
|
+
let _visitors = /** @type {CompiledVisitorMap} */ (/** @type {unknown} */ ([]));
|
|
3554
|
+
let _recurseBlocks = true;
|
|
3555
|
+
/** @type {CompiledVisitorBucket | undefined} */
|
|
3556
|
+
let _commentBucket;
|
|
3557
|
+
|
|
3558
|
+
// Comments reach the visitor map through `NodeType.Comment` instead of a
|
|
3559
|
+
// side callback. They fire during tokenization — in source order among
|
|
3560
|
+
// comments, not interleaved with the node walk — on a transient store node so
|
|
3561
|
+
// `A.start`/`end`/`loc`/`source` work. No comment visitor → no callback →
|
|
3562
|
+
// the tokenizer skips comments with zero overhead.
|
|
3563
|
+
/** @type {(input: string, start: number, end: number) => number} */
|
|
3564
|
+
const _grammarOnComment = (_src, start, end) => {
|
|
3565
|
+
const node = _makeLeaf(T_COMMENT, start, end);
|
|
3566
|
+
_currentNode = node;
|
|
3567
|
+
_currentParent = null;
|
|
3568
|
+
_currentIndex = 0;
|
|
3569
|
+
const bucket = /** @type {CompiledVisitorBucket} */ (_commentBucket);
|
|
3570
|
+
const e = bucket.enter;
|
|
3571
|
+
for (let i = 0; i < e.length; i++) e[i](A);
|
|
3572
|
+
const x = bucket.exit;
|
|
3573
|
+
for (let i = 0; i < x.length; i++) x[i](A);
|
|
3574
|
+
return end;
|
|
3575
|
+
};
|
|
3576
|
+
|
|
3577
|
+
/**
|
|
3578
|
+
* Walk a component-value subtree; children are already materialized. Fetches
|
|
3579
|
+
* the node's visitor bucket once (reused for enter + exit) and uses index
|
|
3580
|
+
* loops — `for…of` would allocate an iterator per node on this hot path.
|
|
3581
|
+
* @param {Node} node component-value root
|
|
3582
|
+
* @param {Node | null} parent enclosing node
|
|
3583
|
+
* @param {number} index node's index within its sibling list
|
|
3584
|
+
*/
|
|
3585
|
+
const _walkValue = (node, parent, index) => {
|
|
3586
|
+
const ty = _types[_nodeIndex(node)];
|
|
3587
|
+
const b = _visitors[ty];
|
|
3588
|
+
let skip = false;
|
|
3589
|
+
if (b !== undefined && b.enter.length !== 0) {
|
|
3590
|
+
_walkSkip = false;
|
|
3591
|
+
_currentNode = node;
|
|
3592
|
+
_currentParent = parent;
|
|
3593
|
+
_currentIndex = index;
|
|
3594
|
+
const e = b.enter;
|
|
3595
|
+
for (let i = 0; i < e.length; i++) e[i](A);
|
|
3596
|
+
skip = _walkSkip;
|
|
3597
|
+
_walkSkip = false;
|
|
3598
|
+
}
|
|
3599
|
+
if (!skip && (ty === T_FUNCTION || ty === T_SIMPLE_BLOCK)) {
|
|
3600
|
+
const i0 = _nodeIndex(node);
|
|
3601
|
+
const vs = _listStarts[i0];
|
|
3602
|
+
const ve = vs + _listLens[i0];
|
|
3603
|
+
for (let i = vs; i < ve; i++) _walkValue(_nodeRef(_flat[i]), node, i - vs);
|
|
3604
|
+
}
|
|
3605
|
+
if (b !== undefined) {
|
|
3606
|
+
// Rebind: descending into children moved the path.
|
|
3607
|
+
_currentNode = node;
|
|
3608
|
+
_currentParent = parent;
|
|
3609
|
+
_currentIndex = index;
|
|
3610
|
+
const x = b.exit;
|
|
3611
|
+
for (let i = 0; i < x.length; i++) x[i](A);
|
|
3612
|
+
}
|
|
3613
|
+
};
|
|
3614
|
+
|
|
3615
|
+
/**
|
|
3616
|
+
* Walk a structural subtree; an at-rule / qualified-rule's block was parsed
|
|
3617
|
+
* eagerly (§5.4.4), so its `value` holds the nested rules / declarations.
|
|
3618
|
+
* @param {Node} node structural-tree root
|
|
3619
|
+
* @param {Node | null} parent enclosing node
|
|
3620
|
+
* @param {number} index node's index within its sibling list (declarations and child rules index independently)
|
|
3621
|
+
*/
|
|
3622
|
+
const _walkRule = (node, parent, index) => {
|
|
3623
|
+
const i0 = _nodeIndex(node);
|
|
3624
|
+
const ty = _types[i0];
|
|
3625
|
+
const b = _visitors[ty];
|
|
3626
|
+
let skip = false;
|
|
3627
|
+
if (b !== undefined && b.enter.length !== 0) {
|
|
3628
|
+
_walkSkip = false;
|
|
3629
|
+
_currentNode = node;
|
|
3630
|
+
_currentParent = parent;
|
|
3631
|
+
_currentIndex = index;
|
|
3632
|
+
const e = b.enter;
|
|
3633
|
+
for (let i = 0; i < e.length; i++) e[i](A);
|
|
3634
|
+
skip = _walkSkip;
|
|
3635
|
+
_walkSkip = false;
|
|
3636
|
+
}
|
|
3637
|
+
if (!skip) {
|
|
3638
|
+
if (ty === T_AT_RULE || ty === T_QUALIFIED_RULE) {
|
|
3639
|
+
const ps = _listStarts[i0];
|
|
3640
|
+
const pe = ps + _listLens[i0];
|
|
3641
|
+
for (let i = ps; i < pe; i++) {
|
|
3642
|
+
_walkValue(_nodeRef(_flat[i]), node, i - ps);
|
|
3643
|
+
}
|
|
3644
|
+
if (_recurseBlocks) {
|
|
3645
|
+
// Declarations then child rules — downstream consumers don't need them strictly interleaved in source order.
|
|
3646
|
+
const decls = _declarationLists[i0];
|
|
3647
|
+
if (decls) {
|
|
3648
|
+
for (let i = 0; i < decls.length; i++) _walkRule(decls[i], node, i);
|
|
3649
|
+
}
|
|
3650
|
+
const ch = _childRuleLists[i0];
|
|
3651
|
+
if (ch) for (let i = 0; i < ch.length; i++) _walkRule(ch[i], node, i);
|
|
3652
|
+
}
|
|
3653
|
+
} else if (ty === T_DECLARATION) {
|
|
3654
|
+
const vs = _listStarts[i0];
|
|
3655
|
+
const ve = vs + _listLens[i0];
|
|
3656
|
+
for (let i = vs; i < ve; i++) {
|
|
3657
|
+
_walkValue(_nodeRef(_flat[i]), node, i - vs);
|
|
3658
|
+
}
|
|
3659
|
+
}
|
|
3660
|
+
}
|
|
3661
|
+
if (b !== undefined) {
|
|
3662
|
+
// Rebind: descending into children moved the path.
|
|
3663
|
+
_currentNode = node;
|
|
3664
|
+
_currentParent = parent;
|
|
3665
|
+
_currentIndex = index;
|
|
3666
|
+
const x = b.exit;
|
|
3667
|
+
for (let i = 0; i < x.length; i++) x[i](A);
|
|
3668
|
+
}
|
|
3669
|
+
};
|
|
3670
|
+
|
|
3671
|
+
/**
|
|
3672
|
+
* The `grammar` streaming sink: walk one top-level node, then recycle the
|
|
3673
|
+
* buffers for the next.
|
|
3674
|
+
* @param {Rule | Declaration} node top-level node
|
|
3675
|
+
*/
|
|
3676
|
+
const _walkTopLevel = (node) => {
|
|
3677
|
+
_walkRule(node, null, 0);
|
|
3678
|
+
if (_nodeCount > _peak) _peak = _nodeCount;
|
|
3679
|
+
if (_flatTop > _flatPeak) _flatPeak = _flatTop;
|
|
3680
|
+
_nodeCount = 0;
|
|
3681
|
+
_flatTop = 0;
|
|
3682
|
+
};
|
|
3683
|
+
|
|
3684
|
+
// The store buffers grow to the largest single top-level rule ever parsed and
|
|
3685
|
+
// live at module level; above this capacity they are re-shrunk after a parse
|
|
3686
|
+
// so one pathological rule can't pin megabytes for the process lifetime.
|
|
3687
|
+
const _SHRINK_CAPACITY = 65536;
|
|
3688
|
+
|
|
3154
3689
|
/**
|
|
3155
3690
|
* The CSS `SourceProcessor` grammar: consume top-level rules one at a time
|
|
3156
3691
|
* (§5.4.1) and walk each immediately, firing `enter` / `exit` in source order
|
|
@@ -3163,136 +3698,61 @@ const TOP_LEVEL_CONSUMERS = {
|
|
|
3163
3698
|
*/
|
|
3164
3699
|
const grammar = (input, visitors, options) => {
|
|
3165
3700
|
const locConverter = options.locConverter || new LocConverter(input);
|
|
3166
|
-
|
|
3701
|
+
_setupParse(input, locConverter);
|
|
3167
3702
|
const skip = options.skip;
|
|
3168
3703
|
_skipTypes = (skip && skip.types) || _NO_SKIP_TYPES;
|
|
3169
3704
|
_skipActive = _skipTypes !== _NO_SKIP_TYPES;
|
|
3170
3705
|
_skipSelectorPrelude = skip !== undefined && skip.selectorPrelude === true;
|
|
3171
3706
|
_skipAtRulePrelude = skip !== undefined && skip.atRulePrelude === true;
|
|
3172
|
-
|
|
3173
|
-
|
|
3174
|
-
|
|
3175
|
-
// side callback. They fire during tokenization — in source order among
|
|
3176
|
-
// comments, not interleaved with the node walk — on a transient SoA node so
|
|
3177
|
-
// `A.start`/`end`/`loc`/`source` work. No comment visitor → no callback →
|
|
3178
|
-
// the tokenizer skips comments with zero overhead.
|
|
3179
|
-
const commentBucket = visitors[T_COMMENT];
|
|
3180
|
-
const onComment =
|
|
3181
|
-
commentBucket === undefined
|
|
3182
|
-
? undefined
|
|
3183
|
-
: /** @type {(input: string, start: number, end: number) => number} */ (
|
|
3184
|
-
(_input, start, end) => {
|
|
3185
|
-
const node = _soaAlloc(T_COMMENT, start, end);
|
|
3186
|
-
_curNode = node;
|
|
3187
|
-
_curParent = null;
|
|
3188
|
-
const e = commentBucket.enter;
|
|
3189
|
-
for (let i = 0; i < e.length; i++) e[i](A);
|
|
3190
|
-
const x = commentBucket.exit;
|
|
3191
|
-
for (let i = 0; i < x.length; i++) x[i](A);
|
|
3192
|
-
return end;
|
|
3193
|
-
}
|
|
3194
|
-
);
|
|
3195
|
-
|
|
3196
|
-
/**
|
|
3197
|
-
* Walk a component-value subtree; children are already materialized. Fetches
|
|
3198
|
-
* the node's visitor bucket once (reused for enter + exit) and uses index
|
|
3199
|
-
* loops — `for…of` would allocate an iterator per node on this hot path.
|
|
3200
|
-
* @param {Node} node component-value root
|
|
3201
|
-
* @param {Node | null} parent enclosing node
|
|
3202
|
-
*/
|
|
3203
|
-
const walkValue = (node, parent) => {
|
|
3204
|
-
const ty = _soaTy[_ix(node)];
|
|
3205
|
-
const b = visitors[ty];
|
|
3206
|
-
let skip = false;
|
|
3207
|
-
if (b !== undefined && b.enter.length !== 0) {
|
|
3208
|
-
_walkSkip = false;
|
|
3209
|
-
_curNode = node;
|
|
3210
|
-
_curParent = parent;
|
|
3211
|
-
const e = b.enter;
|
|
3212
|
-
for (let i = 0; i < e.length; i++) e[i](A);
|
|
3213
|
-
skip = _walkSkip;
|
|
3214
|
-
_walkSkip = false;
|
|
3215
|
-
}
|
|
3216
|
-
if (!skip && (ty === T_FUNCTION || ty === T_SIMPLE_BLOCK)) {
|
|
3217
|
-
const v = /** @type {Node[]} */ (_soaL0[_ix(node)]);
|
|
3218
|
-
for (let i = 0; i < v.length; i++) walkValue(v[i], node);
|
|
3219
|
-
}
|
|
3220
|
-
if (b !== undefined) {
|
|
3221
|
-
// Rebind: descending into children moved the path.
|
|
3222
|
-
_curNode = node;
|
|
3223
|
-
_curParent = parent;
|
|
3224
|
-
const x = b.exit;
|
|
3225
|
-
for (let i = 0; i < x.length; i++) x[i](A);
|
|
3226
|
-
}
|
|
3227
|
-
};
|
|
3228
|
-
|
|
3229
|
-
/**
|
|
3230
|
-
* Walk a structural subtree; an at-rule / qualified-rule's block was parsed
|
|
3231
|
-
* eagerly (§5.4.4), so its `value` holds the nested rules / declarations.
|
|
3232
|
-
* @param {Node} node structural-tree root
|
|
3233
|
-
* @param {Node | null} parent enclosing node
|
|
3234
|
-
*/
|
|
3235
|
-
const walkRule = (node, parent) => {
|
|
3236
|
-
const i0 = _ix(node);
|
|
3237
|
-
const ty = _soaTy[i0];
|
|
3238
|
-
const b = visitors[ty];
|
|
3239
|
-
let skip = false;
|
|
3240
|
-
if (b !== undefined && b.enter.length !== 0) {
|
|
3241
|
-
_walkSkip = false;
|
|
3242
|
-
_curNode = node;
|
|
3243
|
-
_curParent = parent;
|
|
3244
|
-
const e = b.enter;
|
|
3245
|
-
for (let i = 0; i < e.length; i++) e[i](A);
|
|
3246
|
-
skip = _walkSkip;
|
|
3247
|
-
_walkSkip = false;
|
|
3248
|
-
}
|
|
3249
|
-
if (!skip) {
|
|
3250
|
-
if (ty === T_AT_RULE || ty === T_QUALIFIED_RULE) {
|
|
3251
|
-
const p = /** @type {Node[]} */ (_soaL0[i0]);
|
|
3252
|
-
for (let i = 0; i < p.length; i++) walkValue(p[i], node);
|
|
3253
|
-
if (recurseBlocks) {
|
|
3254
|
-
// Declarations then child rules — downstream consumers don't need them strictly interleaved in source order.
|
|
3255
|
-
const decls = _soaL1[i0];
|
|
3256
|
-
if (decls) {
|
|
3257
|
-
for (let i = 0; i < decls.length; i++) walkRule(decls[i], node);
|
|
3258
|
-
}
|
|
3259
|
-
const ch = _soaL2[i0];
|
|
3260
|
-
if (ch) for (let i = 0; i < ch.length; i++) walkRule(ch[i], node);
|
|
3261
|
-
}
|
|
3262
|
-
} else if (ty === T_DECLARATION) {
|
|
3263
|
-
const v = /** @type {Node[]} */ (_soaL0[i0]);
|
|
3264
|
-
for (let i = 0; i < v.length; i++) walkValue(v[i], node);
|
|
3265
|
-
}
|
|
3266
|
-
}
|
|
3267
|
-
if (b !== undefined) {
|
|
3268
|
-
// Rebind: descending into children moved the path.
|
|
3269
|
-
_curNode = node;
|
|
3270
|
-
_curParent = parent;
|
|
3271
|
-
const x = b.exit;
|
|
3272
|
-
for (let i = 0; i < x.length; i++) x[i](A);
|
|
3273
|
-
}
|
|
3274
|
-
};
|
|
3707
|
+
_recurseBlocks = options.recurseBlocks !== false;
|
|
3708
|
+
_visitors = visitors;
|
|
3709
|
+
_commentBucket = visitors[T_COMMENT];
|
|
3275
3710
|
|
|
3276
3711
|
// Stream each top-level node (selected by `as`) to the walker the moment it's
|
|
3277
3712
|
// consumed, rather than collecting them first — so the whole AST is never
|
|
3278
3713
|
// held at once; peak heap is ~one top-level node's subtree.
|
|
3279
|
-
const ts = new TokenStream(
|
|
3714
|
+
const ts = new TokenStream(
|
|
3715
|
+
input,
|
|
3716
|
+
0,
|
|
3717
|
+
locConverter,
|
|
3718
|
+
_commentBucket === undefined ? undefined : _grammarOnComment
|
|
3719
|
+
);
|
|
3280
3720
|
const consume =
|
|
3281
3721
|
TOP_LEVEL_CONSUMERS[options.as || "stylesheet"] ||
|
|
3282
3722
|
consumeAStylesheetsContents;
|
|
3283
3723
|
try {
|
|
3284
|
-
consume(ts,
|
|
3285
|
-
walkRule(node, null);
|
|
3286
|
-
_soaN = 0;
|
|
3287
|
-
});
|
|
3724
|
+
consume(ts, _walkTopLevel);
|
|
3288
3725
|
} finally {
|
|
3289
|
-
// Drop the module-level
|
|
3290
|
-
// its LocConverter / child lists) don't stay alive between
|
|
3291
|
-
|
|
3292
|
-
|
|
3293
|
-
|
|
3294
|
-
|
|
3295
|
-
|
|
3726
|
+
// Drop the module-level column references so the last parsed source (and
|
|
3727
|
+
// its LocConverter / child lists / visitors) don't stay alive between
|
|
3728
|
+
// parses.
|
|
3729
|
+
_input = "";
|
|
3730
|
+
_locConverter = /** @type {LocConverter} */ (/** @type {unknown} */ (null));
|
|
3731
|
+
_declarationLists.length = 0;
|
|
3732
|
+
_childRuleLists.length = 0;
|
|
3733
|
+
_flatTop = 0;
|
|
3734
|
+
_listPool.length = 0;
|
|
3735
|
+
if (_flat.length > _SHRINK_CAPACITY) {
|
|
3736
|
+
_flatGrowHint = _flatPeak;
|
|
3737
|
+
_flat = new Int32Array(0);
|
|
3738
|
+
}
|
|
3739
|
+
_visitors = /** @type {CompiledVisitorMap} */ (/** @type {unknown} */ ([]));
|
|
3740
|
+
_commentBucket = undefined;
|
|
3741
|
+
if (_capacity > _SHRINK_CAPACITY) {
|
|
3742
|
+
// +1: node ids are 1-based and grow fires at `id >= capacity`.
|
|
3743
|
+
_growHint = _peak + 1;
|
|
3744
|
+
_capacity = 0;
|
|
3745
|
+
_types = new Uint8Array(0);
|
|
3746
|
+
_starts = new Int32Array(0);
|
|
3747
|
+
_ends = new Int32Array(0);
|
|
3748
|
+
_aux0 = new Int32Array(0);
|
|
3749
|
+
_aux1 = new Int32Array(0);
|
|
3750
|
+
_flags = new Uint8Array(0);
|
|
3751
|
+
_listStarts = new Int32Array(0);
|
|
3752
|
+
_listLens = new Int32Array(0);
|
|
3753
|
+
}
|
|
3754
|
+
_peak = 0;
|
|
3755
|
+
_flatPeak = 0;
|
|
3296
3756
|
}
|
|
3297
3757
|
};
|
|
3298
3758
|
|
|
@@ -3340,36 +3800,56 @@ const buildSkipSet = (nodeTypes) => {
|
|
|
3340
3800
|
*/
|
|
3341
3801
|
/* eslint-enable jsdoc/require-template */
|
|
3342
3802
|
|
|
3803
|
+
// A fresh (safely retainable) array view of a node's flat content span —
|
|
3804
|
+
// visitors that read `A.children` / `A.prelude` may keep the result.
|
|
3805
|
+
/** @type {(n: Node) => Node[]} */
|
|
3806
|
+
const _materializeList = (n) => {
|
|
3807
|
+
const i = _nodeIndex(n);
|
|
3808
|
+
const start = _listStarts[i];
|
|
3809
|
+
const len = _listLens[i];
|
|
3810
|
+
/** @type {Node[]} */
|
|
3811
|
+
const out = [];
|
|
3812
|
+
for (let k = 0; k < len; k++) out.push(_nodeRef(_flat[start + k]));
|
|
3813
|
+
return out;
|
|
3814
|
+
};
|
|
3815
|
+
|
|
3343
3816
|
// Babel's `path.skip()`, children-only: set by `A.skipChildren()` during an
|
|
3344
3817
|
// `enter` dispatch, consumed by the walk.
|
|
3345
3818
|
let _walkSkip = false;
|
|
3346
3819
|
// The walk's current position (`A.node` / `A.parent` read these; module-level
|
|
3347
3820
|
// so the accessor methods' defaults avoid self-referential `this` typing).
|
|
3348
3821
|
/** @type {Node} */
|
|
3349
|
-
let
|
|
3822
|
+
let _currentNode = /** @type {Node} */ (/** @type {unknown} */ (0));
|
|
3350
3823
|
/** @type {Node | null} */
|
|
3351
|
-
let
|
|
3824
|
+
let _currentParent = null;
|
|
3825
|
+
// Index of the current node within its sibling list (a rule body's declarations
|
|
3826
|
+
// and child rules are separate lists, so each indexes from 0 independently).
|
|
3827
|
+
let _currentIndex = 0;
|
|
3352
3828
|
|
|
3353
3829
|
// AST field-access seam. Every AST-node field read by `CssParser` goes through
|
|
3354
|
-
// one of these accessors
|
|
3355
|
-
//
|
|
3356
|
-
//
|
|
3357
|
-
//
|
|
3358
|
-
// consumer edit. `value` is the leaf-token string; container child lists are
|
|
3359
|
-
// `children` / `prelude` / `declarations` / `childRules`.
|
|
3830
|
+
// one of these accessors (`n` is an integer node id into the columns), so
|
|
3831
|
+
// the storage stays behind the accessor without any consumer edit. `value` is
|
|
3832
|
+
// the leaf-token string; container child lists are `children` / `prelude` /
|
|
3833
|
+
// `declarations` / `childRules`.
|
|
3360
3834
|
const A = {
|
|
3361
3835
|
// === path position (rebound by the walk before every visitor call) ===
|
|
3362
3836
|
/**
|
|
3363
3837
|
* @returns {Node} current node — only valid during a visitor callback
|
|
3364
3838
|
*/
|
|
3365
3839
|
get node() {
|
|
3366
|
-
return
|
|
3840
|
+
return _currentNode;
|
|
3367
3841
|
},
|
|
3368
3842
|
/**
|
|
3369
3843
|
* @returns {Node | null} enclosing node (null = a top-level node)
|
|
3370
3844
|
*/
|
|
3371
3845
|
get parent() {
|
|
3372
|
-
return
|
|
3846
|
+
return _currentParent;
|
|
3847
|
+
},
|
|
3848
|
+
/**
|
|
3849
|
+
* @returns {number} index of the current node within its sibling list (0 for a top-level node) — only valid during a visitor callback
|
|
3850
|
+
*/
|
|
3851
|
+
get index() {
|
|
3852
|
+
return _currentIndex;
|
|
3373
3853
|
},
|
|
3374
3854
|
/** Stop the walk descending into the current node (enter only). */
|
|
3375
3855
|
skipChildren() {
|
|
@@ -3380,42 +3860,42 @@ const A = {
|
|
|
3380
3860
|
* @param {Node=} n node
|
|
3381
3861
|
* @returns {number} node type
|
|
3382
3862
|
*/
|
|
3383
|
-
type(n =
|
|
3384
|
-
return
|
|
3863
|
+
type(n = _currentNode) {
|
|
3864
|
+
return _types[_nodeIndex(n)];
|
|
3385
3865
|
},
|
|
3386
3866
|
/**
|
|
3387
3867
|
* @param {Node=} n node
|
|
3388
3868
|
* @returns {number} start offset
|
|
3389
3869
|
*/
|
|
3390
|
-
start(n =
|
|
3391
|
-
return
|
|
3870
|
+
start(n = _currentNode) {
|
|
3871
|
+
return _starts[_nodeIndex(n)];
|
|
3392
3872
|
},
|
|
3393
3873
|
/**
|
|
3394
3874
|
* @param {Node=} n node
|
|
3395
3875
|
* @returns {number} end offset
|
|
3396
3876
|
*/
|
|
3397
|
-
end(n =
|
|
3398
|
-
return
|
|
3877
|
+
end(n = _currentNode) {
|
|
3878
|
+
return _ends[_nodeIndex(n)];
|
|
3399
3879
|
},
|
|
3400
3880
|
/**
|
|
3401
3881
|
* @param {Node=} n node
|
|
3402
3882
|
* @returns {[number, number]} start / end offsets
|
|
3403
3883
|
*/
|
|
3404
|
-
range(n =
|
|
3405
|
-
const i =
|
|
3406
|
-
return [
|
|
3884
|
+
range(n = _currentNode) {
|
|
3885
|
+
const i = _nodeIndex(n);
|
|
3886
|
+
return [_starts[i], _ends[i]];
|
|
3407
3887
|
},
|
|
3408
3888
|
/**
|
|
3409
3889
|
* @param {Node=} n node
|
|
3410
3890
|
* @returns {{ start: { line: number, column: number }, end: { line: number, column: number } }} source location
|
|
3411
3891
|
*/
|
|
3412
|
-
loc(n =
|
|
3413
|
-
const i =
|
|
3414
|
-
const lc =
|
|
3415
|
-
const s = lc.get(
|
|
3892
|
+
loc(n = _currentNode) {
|
|
3893
|
+
const i = _nodeIndex(n);
|
|
3894
|
+
const lc = _locConverter;
|
|
3895
|
+
const s = lc.get(_starts[i]);
|
|
3416
3896
|
const sl = s.line;
|
|
3417
3897
|
const sc = s.column;
|
|
3418
|
-
const e = lc.get(
|
|
3898
|
+
const e = lc.get(_ends[i]);
|
|
3419
3899
|
return {
|
|
3420
3900
|
start: { line: sl, column: sc },
|
|
3421
3901
|
end: { line: e.line, column: e.column }
|
|
@@ -3425,25 +3905,25 @@ const A = {
|
|
|
3425
3905
|
* @param {Node=} n node
|
|
3426
3906
|
* @returns {string} raw source slice
|
|
3427
3907
|
*/
|
|
3428
|
-
source(n =
|
|
3429
|
-
const i =
|
|
3430
|
-
return
|
|
3908
|
+
source(n = _currentNode) {
|
|
3909
|
+
const i = _nodeIndex(n);
|
|
3910
|
+
return _input.slice(_starts[i], _ends[i]);
|
|
3431
3911
|
},
|
|
3432
3912
|
/**
|
|
3433
3913
|
* @param {Node=} n node
|
|
3434
3914
|
* @returns {string} raw token value
|
|
3435
3915
|
*/
|
|
3436
|
-
value(n =
|
|
3437
|
-
return
|
|
3916
|
+
value(n = _currentNode) {
|
|
3917
|
+
return _valueOf(_nodeIndex(n));
|
|
3438
3918
|
},
|
|
3439
3919
|
/**
|
|
3440
3920
|
* @param {Node=} n node
|
|
3441
3921
|
* @returns {string} unescaped token value
|
|
3442
3922
|
*/
|
|
3443
|
-
unescaped(n =
|
|
3444
|
-
const i =
|
|
3445
|
-
const v =
|
|
3446
|
-
return
|
|
3923
|
+
unescaped(n = _currentNode) {
|
|
3924
|
+
const i = _nodeIndex(n);
|
|
3925
|
+
const v = _valueOf(i);
|
|
3926
|
+
return _types[i] === T_STRING
|
|
3447
3927
|
? unescapeIdentifier(v.slice(1, -1))
|
|
3448
3928
|
: unescapeIdentifier(v);
|
|
3449
3929
|
},
|
|
@@ -3451,11 +3931,11 @@ const A = {
|
|
|
3451
3931
|
* @param {Node=} n node
|
|
3452
3932
|
* @returns {string} hash / numeric type flag
|
|
3453
3933
|
*/
|
|
3454
|
-
typeFlag(n =
|
|
3455
|
-
const i =
|
|
3456
|
-
if (
|
|
3457
|
-
const input =
|
|
3458
|
-
const p =
|
|
3934
|
+
typeFlag(n = _currentNode) {
|
|
3935
|
+
const i = _nodeIndex(n);
|
|
3936
|
+
if (_types[i] === T_HASH) {
|
|
3937
|
+
const input = _input;
|
|
3938
|
+
const p = _starts[i] + 1;
|
|
3459
3939
|
return _ifThreeCodePointsWouldStartAnIdentSequence(
|
|
3460
3940
|
input,
|
|
3461
3941
|
p,
|
|
@@ -3466,139 +3946,162 @@ const A = {
|
|
|
3466
3946
|
? "id"
|
|
3467
3947
|
: "unrestricted";
|
|
3468
3948
|
}
|
|
3469
|
-
const v =
|
|
3949
|
+
const v = _valueOf(i);
|
|
3470
3950
|
return _typeFlagOf(
|
|
3471
|
-
|
|
3951
|
+
_types[i] === T_DIMENSION ? v.slice(0, _consumeANumber(v, 0)) : v
|
|
3472
3952
|
);
|
|
3473
3953
|
},
|
|
3474
3954
|
/**
|
|
3475
3955
|
* @param {Node=} n node
|
|
3476
3956
|
* @returns {number} url content start offset
|
|
3477
3957
|
*/
|
|
3478
|
-
contentStart(n =
|
|
3479
|
-
return
|
|
3958
|
+
contentStart(n = _currentNode) {
|
|
3959
|
+
return _aux0[_nodeIndex(n)];
|
|
3480
3960
|
},
|
|
3481
3961
|
/**
|
|
3482
3962
|
* @param {Node=} n node
|
|
3483
3963
|
* @returns {number} url content end offset
|
|
3484
3964
|
*/
|
|
3485
|
-
contentEnd(n =
|
|
3486
|
-
return
|
|
3965
|
+
contentEnd(n = _currentNode) {
|
|
3966
|
+
return _aux1[_nodeIndex(n)];
|
|
3487
3967
|
},
|
|
3488
3968
|
/**
|
|
3489
3969
|
* @param {Node=} n node
|
|
3490
3970
|
* @returns {string} rule / declaration / function name
|
|
3491
3971
|
*/
|
|
3492
|
-
name(n =
|
|
3493
|
-
const i =
|
|
3494
|
-
return
|
|
3495
|
-
?
|
|
3496
|
-
:
|
|
3972
|
+
name(n = _currentNode) {
|
|
3973
|
+
const i = _nodeIndex(n);
|
|
3974
|
+
return _types[i] === T_AT_RULE
|
|
3975
|
+
? _input.slice(_starts[i] + 1, _aux0[i])
|
|
3976
|
+
: _input.slice(_starts[i], _aux0[i]);
|
|
3497
3977
|
},
|
|
3498
3978
|
/**
|
|
3499
3979
|
* @param {Node=} n node
|
|
3500
3980
|
* @returns {number} name start offset
|
|
3501
3981
|
*/
|
|
3502
|
-
nameStart(n =
|
|
3503
|
-
return
|
|
3982
|
+
nameStart(n = _currentNode) {
|
|
3983
|
+
return _starts[_nodeIndex(n)];
|
|
3504
3984
|
},
|
|
3505
3985
|
/**
|
|
3506
3986
|
* @param {Node=} n node
|
|
3507
3987
|
* @returns {number} name end offset
|
|
3508
3988
|
*/
|
|
3509
|
-
nameEnd(n =
|
|
3510
|
-
return
|
|
3989
|
+
nameEnd(n = _currentNode) {
|
|
3990
|
+
return _aux0[_nodeIndex(n)];
|
|
3511
3991
|
},
|
|
3512
3992
|
/**
|
|
3513
3993
|
* @param {Node=} n node
|
|
3514
3994
|
* @returns {string} unescaped name
|
|
3515
3995
|
*/
|
|
3516
|
-
unescapedName(n =
|
|
3996
|
+
unescapedName(n = _currentNode) {
|
|
3517
3997
|
return unescapeIdentifier(A.name(n));
|
|
3518
3998
|
},
|
|
3519
3999
|
/**
|
|
3520
4000
|
* @param {Node=} n node
|
|
3521
4001
|
* @returns {ComponentValue[]} function / block children
|
|
3522
4002
|
*/
|
|
3523
|
-
children(n =
|
|
3524
|
-
return /** @type {ComponentValue[]} */ (
|
|
4003
|
+
children(n = _currentNode) {
|
|
4004
|
+
return /** @type {ComponentValue[]} */ (_materializeList(n));
|
|
3525
4005
|
},
|
|
3526
4006
|
/**
|
|
3527
4007
|
* @param {Node=} n node
|
|
3528
4008
|
* @returns {ComponentValue[]} rule prelude
|
|
3529
4009
|
*/
|
|
3530
|
-
prelude(n =
|
|
3531
|
-
return /** @type {ComponentValue[]} */ (
|
|
4010
|
+
prelude(n = _currentNode) {
|
|
4011
|
+
return /** @type {ComponentValue[]} */ (_materializeList(n));
|
|
4012
|
+
},
|
|
4013
|
+
/**
|
|
4014
|
+
* @param {Node=} n node
|
|
4015
|
+
* @returns {number} number of children (value / prelude) without materializing the list
|
|
4016
|
+
*/
|
|
4017
|
+
childCount(n = _currentNode) {
|
|
4018
|
+
return _listLens[_nodeIndex(n)];
|
|
4019
|
+
},
|
|
4020
|
+
/**
|
|
4021
|
+
* @param {Node} n node
|
|
4022
|
+
* @param {number} i child index (`0 <= i < childCount(n)`)
|
|
4023
|
+
* @returns {ComponentValue} i-th child (value / prelude) without materializing the list
|
|
4024
|
+
*/
|
|
4025
|
+
childAt(n, i) {
|
|
4026
|
+
return /** @type {ComponentValue} */ (
|
|
4027
|
+
_nodeRef(_flat[_listStarts[_nodeIndex(n)] + i])
|
|
4028
|
+
);
|
|
3532
4029
|
},
|
|
3533
4030
|
/**
|
|
3534
4031
|
* @param {Node=} n node
|
|
3535
4032
|
* @returns {Declaration[] | null} block declarations
|
|
3536
4033
|
*/
|
|
3537
|
-
declarations(n =
|
|
3538
|
-
|
|
4034
|
+
declarations(n = _currentNode) {
|
|
4035
|
+
// `_makeContainer` only populates this slot for rules; a non-rule node
|
|
4036
|
+
// reads `undefined`, normalized to `null` to keep the documented contract.
|
|
4037
|
+
return /** @type {Declaration[] | null} */ (
|
|
4038
|
+
_declarationLists[_nodeIndex(n)] || null
|
|
4039
|
+
);
|
|
3539
4040
|
},
|
|
3540
4041
|
/**
|
|
3541
4042
|
* @param {Node=} n node
|
|
3542
4043
|
* @returns {Rule[] | null} block child rules
|
|
3543
4044
|
*/
|
|
3544
|
-
childRules(n =
|
|
3545
|
-
return /** @type {Rule[] | null} */ (
|
|
4045
|
+
childRules(n = _currentNode) {
|
|
4046
|
+
return /** @type {Rule[] | null} */ (
|
|
4047
|
+
_childRuleLists[_nodeIndex(n)] || null
|
|
4048
|
+
);
|
|
3546
4049
|
},
|
|
3547
4050
|
/**
|
|
3548
4051
|
* @param {Node=} n node
|
|
3549
4052
|
* @returns {number} block start offset
|
|
3550
4053
|
*/
|
|
3551
|
-
blockStart(n =
|
|
3552
|
-
return
|
|
4054
|
+
blockStart(n = _currentNode) {
|
|
4055
|
+
return _aux1[_nodeIndex(n)];
|
|
3553
4056
|
},
|
|
3554
4057
|
/**
|
|
3555
4058
|
* @param {Node=} n node
|
|
3556
4059
|
* @returns {number} block end offset
|
|
3557
4060
|
*/
|
|
3558
|
-
blockEnd(n =
|
|
3559
|
-
|
|
4061
|
+
blockEnd(n = _currentNode) {
|
|
4062
|
+
const i = _nodeIndex(n);
|
|
4063
|
+
return _aux1[i] !== -1 ? _ends[i] : -1;
|
|
3560
4064
|
},
|
|
3561
4065
|
/**
|
|
3562
4066
|
* @param {Node=} n node
|
|
3563
4067
|
* @returns {boolean} `!important` flag
|
|
3564
4068
|
*/
|
|
3565
|
-
important(n =
|
|
3566
|
-
return (
|
|
4069
|
+
important(n = _currentNode) {
|
|
4070
|
+
return (_flags[_nodeIndex(n)] & 1) !== 0;
|
|
3567
4071
|
},
|
|
3568
4072
|
/**
|
|
3569
4073
|
* @param {Node=} n node
|
|
3570
4074
|
* @returns {SimpleBlockToken} block opening token
|
|
3571
4075
|
*/
|
|
3572
|
-
blockToken(n =
|
|
3573
|
-
return /** @type {SimpleBlockToken} */ (
|
|
4076
|
+
blockToken(n = _currentNode) {
|
|
4077
|
+
return /** @type {SimpleBlockToken} */ (_input[_starts[_nodeIndex(n)]]);
|
|
3574
4078
|
},
|
|
3575
4079
|
// Writers — `CssParser` rewrites a rule's end / block-end when it folds an
|
|
3576
|
-
// inline ICSS `:import` / `:export` body into a single dependency.
|
|
3577
|
-
//
|
|
4080
|
+
// inline ICSS `:import` / `:export` body into a single dependency. A block
|
|
4081
|
+
// rule's `blockEnd` is its `end`, so `setBlockEnd` writes the same `end` slot.
|
|
3578
4082
|
/**
|
|
3579
4083
|
* @param {Node} n node
|
|
3580
4084
|
* @param {number} v new end offset
|
|
3581
4085
|
*/
|
|
3582
4086
|
setEnd(n, v) {
|
|
3583
|
-
|
|
4087
|
+
_ends[_nodeIndex(n)] = v;
|
|
3584
4088
|
},
|
|
3585
4089
|
/**
|
|
3586
4090
|
* @param {Node} n node
|
|
3587
4091
|
* @param {number} v new block end offset
|
|
3588
4092
|
*/
|
|
3589
4093
|
setBlockEnd(n, v) {
|
|
3590
|
-
|
|
4094
|
+
_ends[_nodeIndex(n)] = v;
|
|
3591
4095
|
}
|
|
3592
4096
|
};
|
|
3593
4097
|
|
|
3594
|
-
// The
|
|
3595
|
-
//
|
|
3596
|
-
//
|
|
3597
|
-
//
|
|
3598
|
-
//
|
|
3599
|
-
//
|
|
4098
|
+
// The AST node shapes (`Node`, `Token`, and the container typedefs) are types
|
|
4099
|
+
// only — nodes are integer ids into the store, surfaced by the `A` visitor
|
|
4100
|
+
// accessor and the `parseA*` readers. Runtime exports: the `A` accessor, the
|
|
4101
|
+
// full CSS-Syntax-3 §5.3 `parseA*` entry-point surface, the `TokenStream` (so
|
|
4102
|
+
// callers can pass a pre-built stream to any `parseA*`), and the `escape` /
|
|
4103
|
+
// `unescapeIdentifier` string utils.
|
|
3600
4104
|
module.exports.A = A;
|
|
3601
|
-
module.exports.Node = Node;
|
|
3602
4105
|
module.exports.NodeType = NodeType;
|
|
3603
4106
|
module.exports.SourceProcessor = SourceProcessor;
|
|
3604
4107
|
module.exports.TT_AT_KEYWORD = TT_AT_KEYWORD;
|
|
@@ -3627,11 +4130,15 @@ module.exports.TT_SEMICOLON = TT_SEMICOLON;
|
|
|
3627
4130
|
module.exports.TT_STRING = TT_STRING;
|
|
3628
4131
|
module.exports.TT_URL = TT_URL;
|
|
3629
4132
|
module.exports.TT_WHITESPACE = TT_WHITESPACE;
|
|
3630
|
-
module.exports.Token = Token;
|
|
3631
4133
|
module.exports.TokenStream = TokenStream;
|
|
3632
4134
|
module.exports.buildSkipSet = buildSkipSet;
|
|
3633
4135
|
module.exports.equalsLowerCase = equalsLowerCase;
|
|
3634
4136
|
module.exports.escapeIdentifier = escapeIdentifier;
|
|
4137
|
+
module.exports.isDashedIdentifier = isDashedIdentifier;
|
|
4138
|
+
// CSS Syntax §4.2 "whitespace" (space / tab / newline / CR / FF) — the
|
|
4139
|
+
// tokenizer's whitespace class, exported under the spec's name.
|
|
4140
|
+
module.exports.isWhitespace = _isWhiteSpace;
|
|
4141
|
+
module.exports.normalizeUrl = normalizeUrl;
|
|
3635
4142
|
module.exports.parseABlocksContents = parseABlocksContents;
|
|
3636
4143
|
module.exports.parseACommaSeparatedListOfComponentValues =
|
|
3637
4144
|
parseACommaSeparatedListOfComponentValues;
|
|
@@ -3641,5 +4148,8 @@ module.exports.parseAListOfComponentValues = parseAListOfComponentValues;
|
|
|
3641
4148
|
module.exports.parseARule = parseARule;
|
|
3642
4149
|
module.exports.parseAStylesheet = parseAStylesheet;
|
|
3643
4150
|
module.exports.parseAStylesheetsContents = parseAStylesheetsContents;
|
|
4151
|
+
module.exports.rangeEquals = rangeEquals;
|
|
4152
|
+
module.exports.rangeEqualsLowerCase = rangeEqualsLowerCase;
|
|
3644
4153
|
module.exports.readToken = readToken;
|
|
4154
|
+
module.exports.toLowerCaseIfNeeded = toLowerCaseIfNeeded;
|
|
3645
4155
|
module.exports.unescapeIdentifier = unescapeIdentifier;
|