aboutsummaryrefslogtreecommitdiffstats
path: root/vanilla/node_modules/css-tree/lib
diff options
context:
space:
mode:
authorAdam Mathes <adam@adammathes.com>2026-02-13 21:34:48 -0800
committerAdam Mathes <adam@adammathes.com>2026-02-13 21:34:48 -0800
commit76cb9c2a39d477a64824a985ade40507e3bbade1 (patch)
tree41e997aa9c6f538d3a136af61dae9424db2005a9 /vanilla/node_modules/css-tree/lib
parent819a39a21ac992b1393244a4c283bbb125208c69 (diff)
downloadneko-76cb9c2a39d477a64824a985ade40507e3bbade1.tar.gz
neko-76cb9c2a39d477a64824a985ade40507e3bbade1.tar.bz2
neko-76cb9c2a39d477a64824a985ade40507e3bbade1.zip
feat(vanilla): add testing infrastructure and tests (NK-wjnczv)
Diffstat (limited to 'vanilla/node_modules/css-tree/lib')
-rw-r--r--vanilla/node_modules/css-tree/lib/convertor/create.js28
-rw-r--r--vanilla/node_modules/css-tree/lib/convertor/index.js4
-rw-r--r--vanilla/node_modules/css-tree/lib/data-patch.js6
-rwxr-xr-xvanilla/node_modules/css-tree/lib/data.js118
-rw-r--r--vanilla/node_modules/css-tree/lib/definition-syntax/SyntaxError.js12
-rw-r--r--vanilla/node_modules/css-tree/lib/definition-syntax/generate.js135
-rw-r--r--vanilla/node_modules/css-tree/lib/definition-syntax/index.js4
-rw-r--r--vanilla/node_modules/css-tree/lib/definition-syntax/parse.js552
-rw-r--r--vanilla/node_modules/css-tree/lib/definition-syntax/scanner.js109
-rw-r--r--vanilla/node_modules/css-tree/lib/definition-syntax/walk.js53
-rw-r--r--vanilla/node_modules/css-tree/lib/generator/create.js97
-rw-r--r--vanilla/node_modules/css-tree/lib/generator/index.js4
-rw-r--r--vanilla/node_modules/css-tree/lib/generator/sourceMap.js92
-rw-r--r--vanilla/node_modules/css-tree/lib/generator/token-before.js182
-rw-r--r--vanilla/node_modules/css-tree/lib/index.js30
-rw-r--r--vanilla/node_modules/css-tree/lib/lexer/Lexer.js511
-rw-r--r--vanilla/node_modules/css-tree/lib/lexer/error.js123
-rw-r--r--vanilla/node_modules/css-tree/lib/lexer/generic-an-plus-b.js238
-rw-r--r--vanilla/node_modules/css-tree/lib/lexer/generic-const.js8
-rw-r--r--vanilla/node_modules/css-tree/lib/lexer/generic-urange.js151
-rw-r--r--vanilla/node_modules/css-tree/lib/lexer/generic.js622
-rw-r--r--vanilla/node_modules/css-tree/lib/lexer/index.js1
-rw-r--r--vanilla/node_modules/css-tree/lib/lexer/match-graph.js527
-rw-r--r--vanilla/node_modules/css-tree/lib/lexer/match.js630
-rw-r--r--vanilla/node_modules/css-tree/lib/lexer/prepare-tokens.js50
-rw-r--r--vanilla/node_modules/css-tree/lib/lexer/search.js61
-rw-r--r--vanilla/node_modules/css-tree/lib/lexer/structure.js169
-rw-r--r--vanilla/node_modules/css-tree/lib/lexer/trace.js66
-rw-r--r--vanilla/node_modules/css-tree/lib/lexer/units.js27
-rw-r--r--vanilla/node_modules/css-tree/lib/parser/SyntaxError.js70
-rw-r--r--vanilla/node_modules/css-tree/lib/parser/create.js350
-rw-r--r--vanilla/node_modules/css-tree/lib/parser/index.js4
-rw-r--r--vanilla/node_modules/css-tree/lib/parser/parse-selector.js4
-rw-r--r--vanilla/node_modules/css-tree/lib/parser/sequence.js43
-rw-r--r--vanilla/node_modules/css-tree/lib/syntax/atrule/container.js28
-rw-r--r--vanilla/node_modules/css-tree/lib/syntax/atrule/font-face.js8
-rw-r--r--vanilla/node_modules/css-tree/lib/syntax/atrule/import.js104
-rw-r--r--vanilla/node_modules/css-tree/lib/syntax/atrule/index.js23
-rw-r--r--vanilla/node_modules/css-tree/lib/syntax/atrule/layer.js12
-rw-r--r--vanilla/node_modules/css-tree/lib/syntax/atrule/media.js12
-rw-r--r--vanilla/node_modules/css-tree/lib/syntax/atrule/nest.js12
-rw-r--r--vanilla/node_modules/css-tree/lib/syntax/atrule/page.js12
-rw-r--r--vanilla/node_modules/css-tree/lib/syntax/atrule/scope.js12
-rw-r--r--vanilla/node_modules/css-tree/lib/syntax/atrule/starting-style.js8
-rw-r--r--vanilla/node_modules/css-tree/lib/syntax/atrule/supports.js12
-rw-r--r--vanilla/node_modules/css-tree/lib/syntax/config/generator.js5
-rw-r--r--vanilla/node_modules/css-tree/lib/syntax/config/lexer.js10
-rw-r--r--vanilla/node_modules/css-tree/lib/syntax/config/mix.js123
-rw-r--r--vanilla/node_modules/css-tree/lib/syntax/config/parser-selector.js15
-rw-r--r--vanilla/node_modules/css-tree/lib/syntax/config/parser.js45
-rw-r--r--vanilla/node_modules/css-tree/lib/syntax/config/walker.js5
-rw-r--r--vanilla/node_modules/css-tree/lib/syntax/create.js55
-rw-r--r--vanilla/node_modules/css-tree/lib/syntax/function/expression.js7
-rw-r--r--vanilla/node_modules/css-tree/lib/syntax/function/var.js39
-rw-r--r--vanilla/node_modules/css-tree/lib/syntax/index.js10
-rw-r--r--vanilla/node_modules/css-tree/lib/syntax/node/AnPlusB.js292
-rw-r--r--vanilla/node_modules/css-tree/lib/syntax/node/Atrule.js100
-rw-r--r--vanilla/node_modules/css-tree/lib/syntax/node/AtrulePrelude.js47
-rw-r--r--vanilla/node_modules/css-tree/lib/syntax/node/AttributeSelector.js147
-rw-r--r--vanilla/node_modules/css-tree/lib/syntax/node/Block.js95
-rw-r--r--vanilla/node_modules/css-tree/lib/syntax/node/Brackets.js35
-rw-r--r--vanilla/node_modules/css-tree/lib/syntax/node/CDC.js19
-rw-r--r--vanilla/node_modules/css-tree/lib/syntax/node/CDO.js19
-rw-r--r--vanilla/node_modules/css-tree/lib/syntax/node/ClassSelector.js24
-rw-r--r--vanilla/node_modules/css-tree/lib/syntax/node/Combinator.js54
-rw-r--r--vanilla/node_modules/css-tree/lib/syntax/node/Comment.js33
-rw-r--r--vanilla/node_modules/css-tree/lib/syntax/node/Condition.js123
-rw-r--r--vanilla/node_modules/css-tree/lib/syntax/node/Declaration.js165
-rw-r--r--vanilla/node_modules/css-tree/lib/syntax/node/DeclarationList.js62
-rw-r--r--vanilla/node_modules/css-tree/lib/syntax/node/Dimension.js23
-rw-r--r--vanilla/node_modules/css-tree/lib/syntax/node/Feature.js103
-rw-r--r--vanilla/node_modules/css-tree/lib/syntax/node/FeatureFunction.js63
-rw-r--r--vanilla/node_modules/css-tree/lib/syntax/node/FeatureRange.js133
-rw-r--r--vanilla/node_modules/css-tree/lib/syntax/node/Function.js41
-rw-r--r--vanilla/node_modules/css-tree/lib/syntax/node/GeneralEnclosed.js66
-rw-r--r--vanilla/node_modules/css-tree/lib/syntax/node/Hash.js23
-rw-r--r--vanilla/node_modules/css-tree/lib/syntax/node/IdSelector.js26
-rw-r--r--vanilla/node_modules/css-tree/lib/syntax/node/Identifier.js18
-rw-r--r--vanilla/node_modules/css-tree/lib/syntax/node/Layer.js28
-rw-r--r--vanilla/node_modules/css-tree/lib/syntax/node/LayerList.js36
-rw-r--r--vanilla/node_modules/css-tree/lib/syntax/node/MediaQuery.js102
-rw-r--r--vanilla/node_modules/css-tree/lib/syntax/node/MediaQueryList.js34
-rw-r--r--vanilla/node_modules/css-tree/lib/syntax/node/NestingSelector.js22
-rw-r--r--vanilla/node_modules/css-tree/lib/syntax/node/Nth.js47
-rw-r--r--vanilla/node_modules/css-tree/lib/syntax/node/Number.js18
-rw-r--r--vanilla/node_modules/css-tree/lib/syntax/node/Operator.js21
-rw-r--r--vanilla/node_modules/css-tree/lib/syntax/node/Parentheses.js34
-rw-r--r--vanilla/node_modules/css-tree/lib/syntax/node/Percentage.js18
-rw-r--r--vanilla/node_modules/css-tree/lib/syntax/node/PseudoClassSelector.js65
-rw-r--r--vanilla/node_modules/css-tree/lib/syntax/node/PseudoElementSelector.js66
-rw-r--r--vanilla/node_modules/css-tree/lib/syntax/node/Ratio.js68
-rw-r--r--vanilla/node_modules/css-tree/lib/syntax/node/Raw.js41
-rw-r--r--vanilla/node_modules/css-tree/lib/syntax/node/Rule.js51
-rw-r--r--vanilla/node_modules/css-tree/lib/syntax/node/Scope.js66
-rw-r--r--vanilla/node_modules/css-tree/lib/syntax/node/Selector.js31
-rw-r--r--vanilla/node_modules/css-tree/lib/syntax/node/SelectorList.js35
-rw-r--r--vanilla/node_modules/css-tree/lib/syntax/node/String.js19
-rw-r--r--vanilla/node_modules/css-tree/lib/syntax/node/StyleSheet.js82
-rw-r--r--vanilla/node_modules/css-tree/lib/syntax/node/SupportsDeclaration.js34
-rw-r--r--vanilla/node_modules/css-tree/lib/syntax/node/TypeSelector.js52
-rw-r--r--vanilla/node_modules/css-tree/lib/syntax/node/UnicodeRange.js156
-rw-r--r--vanilla/node_modules/css-tree/lib/syntax/node/Url.js52
-rw-r--r--vanilla/node_modules/css-tree/lib/syntax/node/Value.js19
-rw-r--r--vanilla/node_modules/css-tree/lib/syntax/node/WhiteSpace.js27
-rw-r--r--vanilla/node_modules/css-tree/lib/syntax/node/index-generate.js49
-rw-r--r--vanilla/node_modules/css-tree/lib/syntax/node/index-parse-selector.js17
-rw-r--r--vanilla/node_modules/css-tree/lib/syntax/node/index-parse.js49
-rw-r--r--vanilla/node_modules/css-tree/lib/syntax/node/index.js49
-rw-r--r--vanilla/node_modules/css-tree/lib/syntax/pseudo/index.js56
-rw-r--r--vanilla/node_modules/css-tree/lib/syntax/pseudo/lang.js33
-rw-r--r--vanilla/node_modules/css-tree/lib/syntax/scope/atrulePrelude.js5
-rw-r--r--vanilla/node_modules/css-tree/lib/syntax/scope/default.js85
-rw-r--r--vanilla/node_modules/css-tree/lib/syntax/scope/index.js3
-rw-r--r--vanilla/node_modules/css-tree/lib/syntax/scope/selector.js94
-rw-r--r--vanilla/node_modules/css-tree/lib/syntax/scope/value.js25
-rw-r--r--vanilla/node_modules/css-tree/lib/tokenizer/OffsetToLocation.js87
-rw-r--r--vanilla/node_modules/css-tree/lib/tokenizer/TokenStream.js316
-rw-r--r--vanilla/node_modules/css-tree/lib/tokenizer/adopt-buffer.js9
-rw-r--r--vanilla/node_modules/css-tree/lib/tokenizer/char-code-definitions.js212
-rw-r--r--vanilla/node_modules/css-tree/lib/tokenizer/index.js513
-rw-r--r--vanilla/node_modules/css-tree/lib/tokenizer/names.js28
-rw-r--r--vanilla/node_modules/css-tree/lib/tokenizer/types.js28
-rw-r--r--vanilla/node_modules/css-tree/lib/tokenizer/utils.js254
-rw-r--r--vanilla/node_modules/css-tree/lib/utils/List.js469
-rw-r--r--vanilla/node_modules/css-tree/lib/utils/clone.js21
-rw-r--r--vanilla/node_modules/css-tree/lib/utils/create-custom-error.js14
-rw-r--r--vanilla/node_modules/css-tree/lib/utils/ident.js101
-rw-r--r--vanilla/node_modules/css-tree/lib/utils/index.js6
-rw-r--r--vanilla/node_modules/css-tree/lib/utils/names.js106
-rw-r--r--vanilla/node_modules/css-tree/lib/utils/string.js99
-rw-r--r--vanilla/node_modules/css-tree/lib/utils/url.js108
-rw-r--r--vanilla/node_modules/css-tree/lib/version.js5
-rw-r--r--vanilla/node_modules/css-tree/lib/walker/create.js287
-rw-r--r--vanilla/node_modules/css-tree/lib/walker/index.js4
134 files changed, 11705 insertions, 0 deletions
diff --git a/vanilla/node_modules/css-tree/lib/convertor/create.js b/vanilla/node_modules/css-tree/lib/convertor/create.js
new file mode 100644
index 0000000..56e4333
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/convertor/create.js
@@ -0,0 +1,28 @@
+import { List } from '../utils/List.js';
+
+export function createConvertor(walk) {
+ return {
+ fromPlainObject(ast) {
+ walk(ast, {
+ enter(node) {
+ if (node.children && node.children instanceof List === false) {
+ node.children = new List().fromArray(node.children);
+ }
+ }
+ });
+
+ return ast;
+ },
+ toPlainObject(ast) {
+ walk(ast, {
+ leave(node) {
+ if (node.children && node.children instanceof List) {
+ node.children = node.children.toArray();
+ }
+ }
+ });
+
+ return ast;
+ }
+ };
+};
diff --git a/vanilla/node_modules/css-tree/lib/convertor/index.js b/vanilla/node_modules/css-tree/lib/convertor/index.js
new file mode 100644
index 0000000..0f8d4af
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/convertor/index.js
@@ -0,0 +1,4 @@
+import { createConvertor } from './create.js';
+import walker from '../walker/index.js';
+
+export default createConvertor(walker);
diff --git a/vanilla/node_modules/css-tree/lib/data-patch.js b/vanilla/node_modules/css-tree/lib/data-patch.js
new file mode 100644
index 0000000..cd49cbe
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/data-patch.js
@@ -0,0 +1,6 @@
+import { createRequire } from 'module';
+
+const require = createRequire(import.meta.url);
+const patch = require('../data/patch.json');
+
+export default patch;
diff --git a/vanilla/node_modules/css-tree/lib/data.js b/vanilla/node_modules/css-tree/lib/data.js
new file mode 100755
index 0000000..162fa20
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/data.js
@@ -0,0 +1,118 @@
+import { createRequire } from 'module';
+import patch from './data-patch.js';
+
+const require = createRequire(import.meta.url);
+const mdnAtrules = require('mdn-data/css/at-rules.json');
+const mdnProperties = require('mdn-data/css/properties.json');
+const mdnSyntaxes = require('mdn-data/css/syntaxes.json');
+
+const hasOwn = Object.hasOwn || ((object, property) => Object.prototype.hasOwnProperty.call(object, property));
+const extendSyntax = /^\s*\|\s*/;
+
+function preprocessAtrules(dict) {
+ const result = Object.create(null);
+
+ for (const [atruleName, atrule] of Object.entries(dict)) {
+ let descriptors = null;
+
+ if (atrule.descriptors) {
+ descriptors = Object.create(null);
+
+ for (const [name, descriptor] of Object.entries(atrule.descriptors)) {
+ descriptors[name] = descriptor.syntax;
+ }
+ }
+
+ result[atruleName.substr(1)] = {
+ prelude: atrule.syntax.trim().replace(/\{(.|\s)+\}/, '').match(/^@\S+\s+([^;\{]*)/)[1].trim() || null,
+ descriptors
+ };
+ }
+
+ return result;
+}
+
+function patchDictionary(dict, patchDict) {
+ const result = Object.create(null);
+
+ // copy all syntaxes for an original dict
+ for (const [key, value] of Object.entries(dict)) {
+ if (value) {
+ result[key] = value.syntax || value;
+ }
+ }
+
+ // apply a patch
+ for (const key of Object.keys(patchDict)) {
+ if (hasOwn(dict, key)) {
+ if (patchDict[key].syntax) {
+ result[key] = extendSyntax.test(patchDict[key].syntax)
+ ? result[key] + ' ' + patchDict[key].syntax.trim()
+ : patchDict[key].syntax;
+ } else {
+ delete result[key];
+ }
+ } else {
+ if (patchDict[key].syntax) {
+ result[key] = patchDict[key].syntax.replace(extendSyntax, '');
+ }
+ }
+ }
+
+ return result;
+}
+
+function preprocessPatchAtrulesDescritors(declarations) {
+ const result = {};
+
+ for (const [key, value] of Object.entries(declarations || {})) {
+ result[key] = typeof value === 'string'
+ ? { syntax: value }
+ : value;
+ }
+
+ return result;
+}
+
+function patchAtrules(dict, patchDict) {
+ const result = {};
+
+ // copy all syntaxes for an original dict
+ for (const key in dict) {
+ if (patchDict[key] === null) {
+ continue;
+ }
+
+ const atrulePatch = patchDict[key] || {};
+
+ result[key] = {
+ prelude: key in patchDict && 'prelude' in atrulePatch
+ ? atrulePatch.prelude
+ : dict[key].prelude || null,
+ descriptors: patchDictionary(
+ dict[key].descriptors || {},
+ preprocessPatchAtrulesDescritors(atrulePatch.descriptors)
+ )
+ };
+ }
+
+ // apply a patch
+ for (const [key, atrulePatch] of Object.entries(patchDict)) {
+ if (atrulePatch && !hasOwn(dict, key)) {
+ result[key] = {
+ prelude: atrulePatch.prelude || null,
+ descriptors: atrulePatch.descriptors
+ ? patchDictionary({}, preprocessPatchAtrulesDescritors(atrulePatch.descriptors))
+ : null
+ };
+ }
+ }
+
+ return result;
+}
+
+export default {
+ types: patchDictionary(mdnSyntaxes, patch.types),
+ atrules: patchAtrules(preprocessAtrules(mdnAtrules), patch.atrules),
+ properties: patchDictionary(mdnProperties, patch.properties)
+};
diff --git a/vanilla/node_modules/css-tree/lib/definition-syntax/SyntaxError.js b/vanilla/node_modules/css-tree/lib/definition-syntax/SyntaxError.js
new file mode 100644
index 0000000..5f63138
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/definition-syntax/SyntaxError.js
@@ -0,0 +1,12 @@
+import { createCustomError } from '../utils/create-custom-error.js';
+
+export function SyntaxError(message, input, offset) {
+ return Object.assign(createCustomError('SyntaxError', message), {
+ input,
+ offset,
+ rawMessage: message,
+ message: message + '\n' +
+ ' ' + input + '\n' +
+ '--' + new Array((offset || input.length) + 1).join('-') + '^'
+ });
+};
diff --git a/vanilla/node_modules/css-tree/lib/definition-syntax/generate.js b/vanilla/node_modules/css-tree/lib/definition-syntax/generate.js
new file mode 100644
index 0000000..b2636d1
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/definition-syntax/generate.js
@@ -0,0 +1,135 @@
+function noop(value) {
+ return value;
+}
+
+function generateMultiplier(multiplier) {
+ const { min, max, comma } = multiplier;
+
+ if (min === 0 && max === 0) {
+ return comma ? '#?' : '*';
+ }
+
+ if (min === 0 && max === 1) {
+ return '?';
+ }
+
+ if (min === 1 && max === 0) {
+ return comma ? '#' : '+';
+ }
+
+ if (min === 1 && max === 1) {
+ return '';
+ }
+
+ return (
+ (comma ? '#' : '') +
+ (min === max
+ ? '{' + min + '}'
+ : '{' + min + ',' + (max !== 0 ? max : '') + '}'
+ )
+ );
+}
+
+function generateTypeOpts(node) {
+ switch (node.type) {
+ case 'Range':
+ return (
+ ' [' +
+ (node.min === null ? '-∞' : node.min) +
+ ',' +
+ (node.max === null ? '∞' : node.max) +
+ ']'
+ );
+
+ default:
+ throw new Error('Unknown node type `' + node.type + '`');
+ }
+}
+
+function generateSequence(node, decorate, forceBraces, compact) {
+ const combinator = node.combinator === ' ' || compact ? node.combinator : ' ' + node.combinator + ' ';
+ const result = node.terms
+ .map(term => internalGenerate(term, decorate, forceBraces, compact))
+ .join(combinator);
+
+ if (node.explicit || forceBraces) {
+ return (compact || result[0] === ',' ? '[' : '[ ') + result + (compact ? ']' : ' ]');
+ }
+
+ return result;
+}
+
+function internalGenerate(node, decorate, forceBraces, compact) {
+ let result;
+
+ switch (node.type) {
+ case 'Group':
+ result =
+ generateSequence(node, decorate, forceBraces, compact) +
+ (node.disallowEmpty ? '!' : '');
+ break;
+
+ case 'Multiplier':
+ // return since node is a composition
+ return (
+ internalGenerate(node.term, decorate, forceBraces, compact) +
+ decorate(generateMultiplier(node), node)
+ );
+
+ case 'Boolean':
+ result = '<boolean-expr[' + internalGenerate(node.term, decorate, forceBraces, compact) + ']>';
+ break;
+
+ case 'Type':
+ result = '<' + node.name + (node.opts ? decorate(generateTypeOpts(node.opts), node.opts) : '') + '>';
+ break;
+
+ case 'Property':
+ result = '<\'' + node.name + '\'>';
+ break;
+
+ case 'Keyword':
+ result = node.name;
+ break;
+
+ case 'AtKeyword':
+ result = '@' + node.name;
+ break;
+
+ case 'Function':
+ result = node.name + '(';
+ break;
+
+ case 'String':
+ case 'Token':
+ result = node.value;
+ break;
+
+ case 'Comma':
+ result = ',';
+ break;
+
+ default:
+ throw new Error('Unknown node type `' + node.type + '`');
+ }
+
+ return decorate(result, node);
+}
+
+export function generate(node, options) {
+ let decorate = noop;
+ let forceBraces = false;
+ let compact = false;
+
+ if (typeof options === 'function') {
+ decorate = options;
+ } else if (options) {
+ forceBraces = Boolean(options.forceBraces);
+ compact = Boolean(options.compact);
+ if (typeof options.decorate === 'function') {
+ decorate = options.decorate;
+ }
+ }
+
+ return internalGenerate(node, decorate, forceBraces, compact);
+};
diff --git a/vanilla/node_modules/css-tree/lib/definition-syntax/index.js b/vanilla/node_modules/css-tree/lib/definition-syntax/index.js
new file mode 100644
index 0000000..0c24dbd
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/definition-syntax/index.js
@@ -0,0 +1,4 @@
+export { SyntaxError } from './SyntaxError.js';
+export { generate } from './generate.js';
+export { parse } from './parse.js';
+export { walk } from './walk.js';
diff --git a/vanilla/node_modules/css-tree/lib/definition-syntax/parse.js b/vanilla/node_modules/css-tree/lib/definition-syntax/parse.js
new file mode 100644
index 0000000..4e13ad8
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/definition-syntax/parse.js
@@ -0,0 +1,552 @@
+import { Scanner } from './scanner.js';
+
+const TAB = 9;
+const N = 10;
+const F = 12;
+const R = 13;
+const SPACE = 32;
+const EXCLAMATIONMARK = 33; // !
+const NUMBERSIGN = 35; // #
+const AMPERSAND = 38; // &
+const APOSTROPHE = 39; // '
+const LEFTPARENTHESIS = 40; // (
+const RIGHTPARENTHESIS = 41; // )
+const ASTERISK = 42; // *
+const PLUSSIGN = 43; // +
+const COMMA = 44; // ,
+const HYPERMINUS = 45; // -
+const LESSTHANSIGN = 60; // <
+const GREATERTHANSIGN = 62; // >
+const QUESTIONMARK = 63; // ?
+const COMMERCIALAT = 64; // @
+const LEFTSQUAREBRACKET = 91; // [
+const RIGHTSQUAREBRACKET = 93; // ]
+const LEFTCURLYBRACKET = 123; // {
+const VERTICALLINE = 124; // |
+const RIGHTCURLYBRACKET = 125; // }
+const INFINITY = 8734; // ∞
+const COMBINATOR_PRECEDENCE = {
+ ' ': 1,
+ '&&': 2,
+ '||': 3,
+ '|': 4
+};
+
+function readMultiplierRange(scanner) {
+ let min = null;
+ let max = null;
+
+ scanner.eat(LEFTCURLYBRACKET);
+ scanner.skipWs();
+
+ min = scanner.scanNumber(scanner);
+ scanner.skipWs();
+
+ if (scanner.charCode() === COMMA) {
+ scanner.pos++;
+ scanner.skipWs();
+
+ if (scanner.charCode() !== RIGHTCURLYBRACKET) {
+ max = scanner.scanNumber(scanner);
+ scanner.skipWs();
+ }
+ } else {
+ max = min;
+ }
+
+ scanner.eat(RIGHTCURLYBRACKET);
+
+ return {
+ min: Number(min),
+ max: max ? Number(max) : 0
+ };
+}
+
+function readMultiplier(scanner) {
+ let range = null;
+ let comma = false;
+
+ switch (scanner.charCode()) {
+ case ASTERISK:
+ scanner.pos++;
+
+ range = {
+ min: 0,
+ max: 0
+ };
+
+ break;
+
+ case PLUSSIGN:
+ scanner.pos++;
+
+ range = {
+ min: 1,
+ max: 0
+ };
+
+ break;
+
+ case QUESTIONMARK:
+ scanner.pos++;
+
+ range = {
+ min: 0,
+ max: 1
+ };
+
+ break;
+
+ case NUMBERSIGN:
+ scanner.pos++;
+
+ comma = true;
+
+ if (scanner.charCode() === LEFTCURLYBRACKET) {
+ range = readMultiplierRange(scanner);
+ } else if (scanner.charCode() === QUESTIONMARK) {
+ // https://www.w3.org/TR/css-values-4/#component-multipliers
+ // > the # and ? multipliers may be stacked as #?
+ // In this case just treat "#?" as a single multiplier
+ // { min: 0, max: 0, comma: true }
+ scanner.pos++;
+ range = {
+ min: 0,
+ max: 0
+ };
+ } else {
+ range = {
+ min: 1,
+ max: 0
+ };
+ }
+
+ break;
+
+ case LEFTCURLYBRACKET:
+ range = readMultiplierRange(scanner);
+ break;
+
+ default:
+ return null;
+ }
+
+ return {
+ type: 'Multiplier',
+ comma,
+ min: range.min,
+ max: range.max,
+ term: null
+ };
+}
+
+function maybeMultiplied(scanner, node) {
+ const multiplier = readMultiplier(scanner);
+
+ if (multiplier !== null) {
+ multiplier.term = node;
+
+ // https://www.w3.org/TR/css-values-4/#component-multipliers
+ // > The + and # multipliers may be stacked as +#;
+ // Represent "+#" as nested multipliers:
+ // { ...<multiplier #>,
+ // term: {
+ // ...<multipler +>,
+ // term: node
+ // }
+ // }
+ if (scanner.charCode() === NUMBERSIGN &&
+ scanner.charCodeAt(scanner.pos - 1) === PLUSSIGN) {
+ return maybeMultiplied(scanner, multiplier);
+ }
+
+ return multiplier;
+ }
+
+ return node;
+}
+
+function maybeToken(scanner) {
+ const ch = scanner.peek();
+
+ if (ch === '') {
+ return null;
+ }
+
+ return maybeMultiplied(scanner, {
+ type: 'Token',
+ value: ch
+ });
+}
+
+function readProperty(scanner) {
+ let name;
+
+ scanner.eat(LESSTHANSIGN);
+ scanner.eat(APOSTROPHE);
+
+ name = scanner.scanWord();
+
+ scanner.eat(APOSTROPHE);
+ scanner.eat(GREATERTHANSIGN);
+
+ return maybeMultiplied(scanner, {
+ type: 'Property',
+ name
+ });
+}
+
+// https://drafts.csswg.org/css-values-3/#numeric-ranges
+// 4.1. Range Restrictions and Range Definition Notation
+//
+// Range restrictions can be annotated in the numeric type notation using CSS bracketed
+// range notation—[min,max]—within the angle brackets, after the identifying keyword,
+// indicating a closed range between (and including) min and max.
+// For example, <integer [0, 10]> indicates an integer between 0 and 10, inclusive.
+function readTypeRange(scanner) {
+ // use null for Infinity to make AST format JSON serializable/deserializable
+ let min = null; // -Infinity
+ let max = null; // Infinity
+ let sign = 1;
+
+ scanner.eat(LEFTSQUAREBRACKET);
+
+ if (scanner.charCode() === HYPERMINUS) {
+ scanner.peek();
+ sign = -1;
+ }
+
+ if (sign == -1 && scanner.charCode() === INFINITY) {
+ scanner.peek();
+ } else {
+ min = sign * Number(scanner.scanNumber(scanner));
+
+ if (scanner.isNameCharCode()) {
+ min += scanner.scanWord();
+ }
+ }
+
+ scanner.skipWs();
+ scanner.eat(COMMA);
+ scanner.skipWs();
+
+ if (scanner.charCode() === INFINITY) {
+ scanner.peek();
+ } else {
+ sign = 1;
+
+ if (scanner.charCode() === HYPERMINUS) {
+ scanner.peek();
+ sign = -1;
+ }
+
+ max = sign * Number(scanner.scanNumber(scanner));
+
+ if (scanner.isNameCharCode()) {
+ max += scanner.scanWord();
+ }
+ }
+
+ scanner.eat(RIGHTSQUAREBRACKET);
+
+ return {
+ type: 'Range',
+ min,
+ max
+ };
+}
+
+function readType(scanner) {
+ let name;
+ let opts = null;
+
+ scanner.eat(LESSTHANSIGN);
+ name = scanner.scanWord();
+
+ // https://drafts.csswg.org/css-values-5/#boolean
+ if (name === 'boolean-expr') {
+ scanner.eat(LEFTSQUAREBRACKET);
+
+ const implicitGroup = readImplicitGroup(scanner, RIGHTSQUAREBRACKET);
+
+ scanner.eat(RIGHTSQUAREBRACKET);
+ scanner.eat(GREATERTHANSIGN);
+
+ return maybeMultiplied(scanner, {
+ type: 'Boolean',
+ term: implicitGroup.terms.length === 1
+ ? implicitGroup.terms[0]
+ : implicitGroup
+ });
+ }
+
+ if (scanner.charCode() === LEFTPARENTHESIS &&
+ scanner.nextCharCode() === RIGHTPARENTHESIS) {
+ scanner.pos += 2;
+ name += '()';
+ }
+
+ if (scanner.charCodeAt(scanner.findWsEnd(scanner.pos)) === LEFTSQUAREBRACKET) {
+ scanner.skipWs();
+ opts = readTypeRange(scanner);
+ }
+
+ scanner.eat(GREATERTHANSIGN);
+
+ return maybeMultiplied(scanner, {
+ type: 'Type',
+ name,
+ opts
+ });
+}
+
+function readKeywordOrFunction(scanner) {
+ const name = scanner.scanWord();
+
+ if (scanner.charCode() === LEFTPARENTHESIS) {
+ scanner.pos++;
+
+ return {
+ type: 'Function',
+ name
+ };
+ }
+
+ return maybeMultiplied(scanner, {
+ type: 'Keyword',
+ name
+ });
+}
+
+function regroupTerms(terms, combinators) {
+ function createGroup(terms, combinator) {
+ return {
+ type: 'Group',
+ terms,
+ combinator,
+ disallowEmpty: false,
+ explicit: false
+ };
+ }
+
+ let combinator;
+
+ combinators = Object.keys(combinators)
+ .sort((a, b) => COMBINATOR_PRECEDENCE[a] - COMBINATOR_PRECEDENCE[b]);
+
+ while (combinators.length > 0) {
+ combinator = combinators.shift();
+
+ let i = 0;
+ let subgroupStart = 0;
+
+ for (; i < terms.length; i++) {
+ const term = terms[i];
+
+ if (term.type === 'Combinator') {
+ if (term.value === combinator) {
+ if (subgroupStart === -1) {
+ subgroupStart = i - 1;
+ }
+ terms.splice(i, 1);
+ i--;
+ } else {
+ if (subgroupStart !== -1 && i - subgroupStart > 1) {
+ terms.splice(
+ subgroupStart,
+ i - subgroupStart,
+ createGroup(terms.slice(subgroupStart, i), combinator)
+ );
+ i = subgroupStart + 1;
+ }
+ subgroupStart = -1;
+ }
+ }
+ }
+
+ if (subgroupStart !== -1 && combinators.length) {
+ terms.splice(
+ subgroupStart,
+ i - subgroupStart,
+ createGroup(terms.slice(subgroupStart, i), combinator)
+ );
+ }
+ }
+
+ return combinator;
+}
+
+function readImplicitGroup(scanner, stopCharCode) {
+ const combinators = Object.create(null);
+ const terms = [];
+ let token;
+ let prevToken = null;
+ let prevTokenPos = scanner.pos;
+
+ while (scanner.charCode() !== stopCharCode && (token = peek(scanner, stopCharCode))) {
+ if (token.type !== 'Spaces') {
+ if (token.type === 'Combinator') {
+ // check for combinator in group beginning and double combinator sequence
+ if (prevToken === null || prevToken.type === 'Combinator') {
+ scanner.pos = prevTokenPos;
+ scanner.error('Unexpected combinator');
+ }
+
+ combinators[token.value] = true;
+ } else if (prevToken !== null && prevToken.type !== 'Combinator') {
+ combinators[' '] = true; // a b
+ terms.push({
+ type: 'Combinator',
+ value: ' '
+ });
+ }
+
+ terms.push(token);
+ prevToken = token;
+ prevTokenPos = scanner.pos;
+ }
+ }
+
+ // check for combinator in group ending
+ if (prevToken !== null && prevToken.type === 'Combinator') {
+ scanner.pos -= prevTokenPos;
+ scanner.error('Unexpected combinator');
+ }
+
+ return {
+ type: 'Group',
+ terms,
+ combinator: regroupTerms(terms, combinators) || ' ',
+ disallowEmpty: false,
+ explicit: false
+ };
+}
+
+function readGroup(scanner, stopCharCode) {
+ let result;
+
+ scanner.eat(LEFTSQUAREBRACKET);
+ result = readImplicitGroup(scanner, stopCharCode);
+ scanner.eat(RIGHTSQUAREBRACKET);
+
+ result.explicit = true;
+
+ if (scanner.charCode() === EXCLAMATIONMARK) {
+ scanner.pos++;
+ result.disallowEmpty = true;
+ }
+
+ return result;
+}
+
+function peek(scanner, stopCharCode) {
+ let code = scanner.charCode();
+
+ switch (code) {
+ case RIGHTSQUAREBRACKET:
+ // don't eat, stop scan a group
+ break;
+
+ case LEFTSQUAREBRACKET:
+ return maybeMultiplied(scanner, readGroup(scanner, stopCharCode));
+
+ case LESSTHANSIGN:
+ return scanner.nextCharCode() === APOSTROPHE
+ ? readProperty(scanner)
+ : readType(scanner);
+
+ case VERTICALLINE:
+ return {
+ type: 'Combinator',
+ value: scanner.substringToPos(
+ scanner.pos + (scanner.nextCharCode() === VERTICALLINE ? 2 : 1)
+ )
+ };
+
+ case AMPERSAND:
+ scanner.pos++;
+ scanner.eat(AMPERSAND);
+
+ return {
+ type: 'Combinator',
+ value: '&&'
+ };
+
+ case COMMA:
+ scanner.pos++;
+ return {
+ type: 'Comma'
+ };
+
+ case APOSTROPHE:
+ return maybeMultiplied(scanner, {
+ type: 'String',
+ value: scanner.scanString()
+ });
+
+ case SPACE:
+ case TAB:
+ case N:
+ case R:
+ case F:
+ return {
+ type: 'Spaces',
+ value: scanner.scanSpaces()
+ };
+
+ case COMMERCIALAT:
+ code = scanner.nextCharCode();
+
+ if (scanner.isNameCharCode(code)) {
+ scanner.pos++;
+ return {
+ type: 'AtKeyword',
+ name: scanner.scanWord()
+ };
+ }
+
+ return maybeToken(scanner);
+
+ case ASTERISK:
+ case PLUSSIGN:
+ case QUESTIONMARK:
+ case NUMBERSIGN:
+ case EXCLAMATIONMARK:
+ // prohibited tokens (used as a multiplier start)
+ break;
+
+ case LEFTCURLYBRACKET:
+ // LEFTCURLYBRACKET is allowed since mdn/data uses it w/o quoting
+ // check next char isn't a number, because it's likely a disjoined multiplier
+ code = scanner.nextCharCode();
+
+ if (code < 48 || code > 57) {
+ return maybeToken(scanner);
+ }
+
+ break;
+
+ default:
+ if (scanner.isNameCharCode(code)) {
+ return readKeywordOrFunction(scanner);
+ }
+
+ return maybeToken(scanner);
+ }
+}
+
+export function parse(source) {
+ const scanner = new Scanner(source);
+ const result = readImplicitGroup(scanner);
+
+ if (scanner.pos !== source.length) {
+ scanner.error('Unexpected input');
+ }
+
+ // reduce redundant groups with single group term
+ if (result.terms.length === 1 && result.terms[0].type === 'Group') {
+ return result.terms[0];
+ }
+
+ return result;
+};
diff --git a/vanilla/node_modules/css-tree/lib/definition-syntax/scanner.js b/vanilla/node_modules/css-tree/lib/definition-syntax/scanner.js
new file mode 100644
index 0000000..d0b1895
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/definition-syntax/scanner.js
@@ -0,0 +1,109 @@
+import { SyntaxError } from './SyntaxError.js';
+
+const TAB = 9;
+const N = 10;
+const F = 12;
+const R = 13;
+const SPACE = 32;
+const NAME_CHAR = new Uint8Array(128).map((_, idx) =>
+ /[a-zA-Z0-9\-]/.test(String.fromCharCode(idx)) ? 1 : 0
+);
+
+export class Scanner {
+ constructor(str) {
+ this.str = str;
+ this.pos = 0;
+ }
+
+ charCodeAt(pos) {
+ return pos < this.str.length ? this.str.charCodeAt(pos) : 0;
+ }
+ charCode() {
+ return this.charCodeAt(this.pos);
+ }
+ isNameCharCode(code = this.charCode()) {
+ return code < 128 && NAME_CHAR[code] === 1;
+ }
+ nextCharCode() {
+ return this.charCodeAt(this.pos + 1);
+ }
+ nextNonWsCode(pos) {
+ return this.charCodeAt(this.findWsEnd(pos));
+ }
+ skipWs() {
+ this.pos = this.findWsEnd(this.pos);
+ }
+ findWsEnd(pos) {
+ for (; pos < this.str.length; pos++) {
+ const code = this.str.charCodeAt(pos);
+ if (code !== R && code !== N && code !== F && code !== SPACE && code !== TAB) {
+ break;
+ }
+ }
+
+ return pos;
+ }
+ substringToPos(end) {
+ return this.str.substring(this.pos, this.pos = end);
+ }
+ eat(code) {
+ if (this.charCode() !== code) {
+ this.error('Expect `' + String.fromCharCode(code) + '`');
+ }
+
+ this.pos++;
+ }
+ peek() {
+ return this.pos < this.str.length ? this.str.charAt(this.pos++) : '';
+ }
+ error(message) {
+ throw new SyntaxError(message, this.str, this.pos);
+ }
+
+ scanSpaces() {
+ return this.substringToPos(this.findWsEnd(this.pos));
+ }
+ scanWord() {
+ let end = this.pos;
+
+ for (; end < this.str.length; end++) {
+ const code = this.str.charCodeAt(end);
+ if (code >= 128 || NAME_CHAR[code] === 0) {
+ break;
+ }
+ }
+
+ if (this.pos === end) {
+ this.error('Expect a keyword');
+ }
+
+ return this.substringToPos(end);
+ }
+ scanNumber() {
+ let end = this.pos;
+
+ for (; end < this.str.length; end++) {
+ const code = this.str.charCodeAt(end);
+
+ if (code < 48 || code > 57) {
+ break;
+ }
+ }
+
+ if (this.pos === end) {
+ this.error('Expect a number');
+ }
+
+ return this.substringToPos(end);
+ }
+ scanString() {
+ const end = this.str.indexOf('\'', this.pos + 1);
+
+ if (end === -1) {
+ this.pos = this.str.length;
+ this.error('Expect an apostrophe');
+ }
+
+ return this.substringToPos(end + 1);
+ }
+};
diff --git a/vanilla/node_modules/css-tree/lib/definition-syntax/walk.js b/vanilla/node_modules/css-tree/lib/definition-syntax/walk.js
new file mode 100644
index 0000000..b8c7957
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/definition-syntax/walk.js
@@ -0,0 +1,53 @@
+const noop = function() {};
+
+function ensureFunction(value) {
+ return typeof value === 'function' ? value : noop;
+}
+
+export function walk(node, options, context) {
+ function walk(node) {
+ enter.call(context, node);
+
+ switch (node.type) {
+ case 'Group':
+ node.terms.forEach(walk);
+ break;
+
+ case 'Multiplier':
+ case 'Boolean':
+ walk(node.term);
+ break;
+
+ case 'Type':
+ case 'Property':
+ case 'Keyword':
+ case 'AtKeyword':
+ case 'Function':
+ case 'String':
+ case 'Token':
+ case 'Comma':
+ break;
+
+ default:
+ throw new Error('Unknown type: ' + node.type);
+ }
+
+ leave.call(context, node);
+ }
+
+ let enter = noop;
+ let leave = noop;
+
+ if (typeof options === 'function') {
+ enter = options;
+ } else if (options) {
+ enter = ensureFunction(options.enter);
+ leave = ensureFunction(options.leave);
+ }
+
+ if (enter === noop && leave === noop) {
+ throw new Error('Neither `enter` nor `leave` walker handler is set or both aren\'t a function');
+ }
+
+ walk(node, context);
+};
diff --git a/vanilla/node_modules/css-tree/lib/generator/create.js b/vanilla/node_modules/css-tree/lib/generator/create.js
new file mode 100644
index 0000000..c542f4f
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/generator/create.js
@@ -0,0 +1,97 @@
+import { tokenize, Delim, WhiteSpace } from '../tokenizer/index.js';
+import { generateSourceMap } from './sourceMap.js';
+import * as tokenBefore from './token-before.js';
+
+const REVERSESOLIDUS = 0x005c; // U+005C REVERSE SOLIDUS (\)
+
+function processChildren(node, delimeter) {
+ if (typeof delimeter === 'function') {
+ let prev = null;
+
+ node.children.forEach(node => {
+ if (prev !== null) {
+ delimeter.call(this, prev);
+ }
+
+ this.node(node);
+ prev = node;
+ });
+
+ return;
+ }
+
+ node.children.forEach(this.node, this);
+}
+
+function processChunk(chunk) {
+ tokenize(chunk, (type, start, end) => {
+ this.token(type, chunk.slice(start, end));
+ });
+}
+
+export function createGenerator(config) {
+ const types = new Map();
+
+ for (let [name, item] of Object.entries(config.node)) {
+ const fn = item.generate || item;
+
+ if (typeof fn === 'function') {
+ types.set(name, item.generate || item);
+ }
+ }
+
+ return function(node, options) {
+ let buffer = '';
+ let prevCode = 0;
+ let handlers = {
+ node(node) {
+ if (types.has(node.type)) {
+ types.get(node.type).call(publicApi, node);
+ } else {
+ throw new Error('Unknown node type: ' + node.type);
+ }
+ },
+ tokenBefore: tokenBefore.safe,
+ token(type, value) {
+ prevCode = this.tokenBefore(prevCode, type, value);
+
+ this.emit(value, type, false);
+
+ if (type === Delim && value.charCodeAt(0) === REVERSESOLIDUS) {
+ this.emit('\n', WhiteSpace, true);
+ }
+ },
+ emit(value) {
+ buffer += value;
+ },
+ result() {
+ return buffer;
+ }
+ };
+
+ if (options) {
+ if (typeof options.decorator === 'function') {
+ handlers = options.decorator(handlers);
+ }
+
+ if (options.sourceMap) {
+ handlers = generateSourceMap(handlers);
+ }
+
+ if (options.mode in tokenBefore) {
+ handlers.tokenBefore = tokenBefore[options.mode];
+ }
+ }
+
+ const publicApi = {
+ node: (node) => handlers.node(node),
+ children: processChildren,
+ token: (type, value) => handlers.token(type, value),
+ tokenize: processChunk
+ };
+
+ handlers.node(node);
+
+ return handlers.result();
+ };
+};
diff --git a/vanilla/node_modules/css-tree/lib/generator/index.js b/vanilla/node_modules/css-tree/lib/generator/index.js
new file mode 100644
index 0000000..dc05116
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/generator/index.js
@@ -0,0 +1,4 @@
+import { createGenerator } from './create.js';
+import config from '../syntax/config/generator.js';
+
+export default createGenerator(config);
diff --git a/vanilla/node_modules/css-tree/lib/generator/sourceMap.js b/vanilla/node_modules/css-tree/lib/generator/sourceMap.js
new file mode 100644
index 0000000..0c5ff27
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/generator/sourceMap.js
@@ -0,0 +1,92 @@
+import { SourceMapGenerator } from 'source-map-js/lib/source-map-generator.js';
+
+const trackNodes = new Set(['Atrule', 'Selector', 'Declaration']);
+
+export function generateSourceMap(handlers) {
+ const map = new SourceMapGenerator();
+ const generated = {
+ line: 1,
+ column: 0
+ };
+ const original = {
+ line: 0, // should be zero to add first mapping
+ column: 0
+ };
+ const activatedGenerated = {
+ line: 1,
+ column: 0
+ };
+ const activatedMapping = {
+ generated: activatedGenerated
+ };
+ let line = 1;
+ let column = 0;
+ let sourceMappingActive = false;
+
+ const origHandlersNode = handlers.node;
+ handlers.node = function(node) {
+ if (node.loc && node.loc.start && trackNodes.has(node.type)) {
+ const nodeLine = node.loc.start.line;
+ const nodeColumn = node.loc.start.column - 1;
+
+ if (original.line !== nodeLine ||
+ original.column !== nodeColumn) {
+ original.line = nodeLine;
+ original.column = nodeColumn;
+
+ generated.line = line;
+ generated.column = column;
+
+ if (sourceMappingActive) {
+ sourceMappingActive = false;
+ if (generated.line !== activatedGenerated.line ||
+ generated.column !== activatedGenerated.column) {
+ map.addMapping(activatedMapping);
+ }
+ }
+
+ sourceMappingActive = true;
+ map.addMapping({
+ source: node.loc.source,
+ original,
+ generated
+ });
+ }
+ }
+
+ origHandlersNode.call(this, node);
+
+ if (sourceMappingActive && trackNodes.has(node.type)) {
+ activatedGenerated.line = line;
+ activatedGenerated.column = column;
+ }
+ };
+
+ const origHandlersEmit = handlers.emit;
+ handlers.emit = function(value, type, auto) {
+ for (let i = 0; i < value.length; i++) {
+ if (value.charCodeAt(i) === 10) { // \n
+ line++;
+ column = 0;
+ } else {
+ column++;
+ }
+ }
+
+ origHandlersEmit(value, type, auto);
+ };
+
+ const origHandlersResult = handlers.result;
+ handlers.result = function() {
+ if (sourceMappingActive) {
+ map.addMapping(activatedMapping);
+ }
+
+ return {
+ css: origHandlersResult(),
+ map
+ };
+ };
+
+ return handlers;
+};
diff --git a/vanilla/node_modules/css-tree/lib/generator/token-before.js b/vanilla/node_modules/css-tree/lib/generator/token-before.js
new file mode 100644
index 0000000..da3fed0
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/generator/token-before.js
@@ -0,0 +1,182 @@
+import {
+ WhiteSpace,
+ Delim,
+ Ident,
+ Function as FunctionToken,
+ Url,
+ BadUrl,
+ AtKeyword,
+ Hash,
+ Percentage,
+ Dimension,
+ Number as NumberToken,
+ String as StringToken,
+ Colon,
+ LeftParenthesis,
+ RightParenthesis,
+ CDC
+} from '../tokenizer/index.js';
+
+const PLUSSIGN = 0x002B; // U+002B PLUS SIGN (+)
+const HYPHENMINUS = 0x002D; // U+002D HYPHEN-MINUS (-)
+
+const code = (type, value) => {
+ if (type === Delim) {
+ type = value;
+ }
+
+ if (typeof type === 'string') {
+ const charCode = type.charCodeAt(0);
+ return charCode > 0x7F ? 0x8000 : charCode << 8;
+ }
+
+ return type;
+};
+
+// https://www.w3.org/TR/css-syntax-3/#serialization
+// The only requirement for serialization is that it must "round-trip" with parsing,
+// that is, parsing the stylesheet must produce the same data structures as parsing,
+// serializing, and parsing again, except for consecutive <whitespace-token>s,
+// which may be collapsed into a single token.
+
+const specPairs = [
+ [Ident, Ident],
+ [Ident, FunctionToken],
+ [Ident, Url],
+ [Ident, BadUrl],
+ [Ident, '-'],
+ [Ident, NumberToken],
+ [Ident, Percentage],
+ [Ident, Dimension],
+ [Ident, CDC],
+ [Ident, LeftParenthesis],
+
+ [AtKeyword, Ident],
+ [AtKeyword, FunctionToken],
+ [AtKeyword, Url],
+ [AtKeyword, BadUrl],
+ [AtKeyword, '-'],
+ [AtKeyword, NumberToken],
+ [AtKeyword, Percentage],
+ [AtKeyword, Dimension],
+ [AtKeyword, CDC],
+
+ [Hash, Ident],
+ [Hash, FunctionToken],
+ [Hash, Url],
+ [Hash, BadUrl],
+ [Hash, '-'],
+ [Hash, NumberToken],
+ [Hash, Percentage],
+ [Hash, Dimension],
+ [Hash, CDC],
+
+ [Dimension, Ident],
+ [Dimension, FunctionToken],
+ [Dimension, Url],
+ [Dimension, BadUrl],
+ [Dimension, '-'],
+ [Dimension, NumberToken],
+ [Dimension, Percentage],
+ [Dimension, Dimension],
+ [Dimension, CDC],
+
+ ['#', Ident],
+ ['#', FunctionToken],
+ ['#', Url],
+ ['#', BadUrl],
+ ['#', '-'],
+ ['#', NumberToken],
+ ['#', Percentage],
+ ['#', Dimension],
+ ['#', CDC], // https://github.com/w3c/csswg-drafts/pull/6874
+
+ ['-', Ident],
+ ['-', FunctionToken],
+ ['-', Url],
+ ['-', BadUrl],
+ ['-', '-'],
+ ['-', NumberToken],
+ ['-', Percentage],
+ ['-', Dimension],
+ ['-', CDC], // https://github.com/w3c/csswg-drafts/pull/6874
+
+ [NumberToken, Ident],
+ [NumberToken, FunctionToken],
+ [NumberToken, Url],
+ [NumberToken, BadUrl],
+ [NumberToken, NumberToken],
+ [NumberToken, Percentage],
+ [NumberToken, Dimension],
+ [NumberToken, '%'],
+ [NumberToken, CDC], // https://github.com/w3c/csswg-drafts/pull/6874
+
+ ['@', Ident],
+ ['@', FunctionToken],
+ ['@', Url],
+ ['@', BadUrl],
+ ['@', '-'],
+ ['@', CDC], // https://github.com/w3c/csswg-drafts/pull/6874
+
+ ['.', NumberToken],
+ ['.', Percentage],
+ ['.', Dimension],
+
+ ['+', NumberToken],
+ ['+', Percentage],
+ ['+', Dimension],
+
+ ['/', '*']
+];
+// validate with scripts/generate-safe
+const safePairs = specPairs.concat([
+ [Ident, Hash],
+
+ [Dimension, Hash],
+
+ [Hash, Hash],
+
+ [AtKeyword, LeftParenthesis],
+ [AtKeyword, StringToken],
+ [AtKeyword, Colon],
+
+ [Percentage, Percentage],
+ [Percentage, Dimension],
+ [Percentage, FunctionToken],
+ [Percentage, '-'],
+
+ [RightParenthesis, Ident],
+ [RightParenthesis, FunctionToken],
+ [RightParenthesis, Percentage],
+ [RightParenthesis, Dimension],
+ [RightParenthesis, Hash],
+ [RightParenthesis, '-']
+]);
+
+function createMap(pairs) {
+ const isWhiteSpaceRequired = new Set(
+ pairs.map(([prev, next]) => (code(prev) << 16 | code(next)))
+ );
+
+ return function(prevCode, type, value) {
+ const nextCode = code(type, value);
+ const nextCharCode = value.charCodeAt(0);
+ const emitWs =
+ (nextCharCode === HYPHENMINUS &&
+ type !== Ident &&
+ type !== FunctionToken &&
+ type !== CDC) ||
+ (nextCharCode === PLUSSIGN)
+ ? isWhiteSpaceRequired.has(prevCode << 16 | nextCharCode << 8)
+ : isWhiteSpaceRequired.has(prevCode << 16 | nextCode);
+
+ if (emitWs) {
+ this.emit(' ', WhiteSpace, true);
+ }
+
+ return nextCode;
+ };
+}
+
+export const spec = createMap(specPairs);
+export const safe = createMap(safePairs);
diff --git a/vanilla/node_modules/css-tree/lib/index.js b/vanilla/node_modules/css-tree/lib/index.js
new file mode 100644
index 0000000..22cf491
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/index.js
@@ -0,0 +1,30 @@
+import syntax from './syntax/index.js';
+
+export * from './version.js';
+export { default as createSyntax } from './syntax/create.js';
+export { List } from './utils/List.js';
+export { Lexer } from './lexer/Lexer.js';
+export { tokenTypes, tokenNames, TokenStream, OffsetToLocation } from './tokenizer/index.js';
+export * as definitionSyntax from './definition-syntax/index.js';
+export { clone } from './utils/clone.js';
+export * from './utils/names.js';
+export * as ident from './utils/ident.js';
+export * as string from './utils/string.js';
+export * as url from './utils/url.js';
+export const {
+ tokenize,
+ parse,
+ generate,
+ lexer,
+ createLexer,
+
+ walk,
+ find,
+ findLast,
+ findAll,
+
+ toPlainObject,
+ fromPlainObject,
+
+ fork
+} = syntax;
diff --git a/vanilla/node_modules/css-tree/lib/lexer/Lexer.js b/vanilla/node_modules/css-tree/lib/lexer/Lexer.js
new file mode 100644
index 0000000..d09f574
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/lexer/Lexer.js
@@ -0,0 +1,511 @@
+import { SyntaxReferenceError, SyntaxMatchError } from './error.js';
+import * as names from '../utils/names.js';
+import { cssWideKeywords } from './generic-const.js';
+import { createGenericTypes } from './generic.js';
+import * as units from './units.js';
+import { parse, generate, walk } from '../definition-syntax/index.js';
+import prepareTokens from './prepare-tokens.js';
+import { buildMatchGraph } from './match-graph.js';
+import { matchAsTree } from './match.js';
+import * as trace from './trace.js';
+import { matchFragments } from './search.js';
+import { getStructureFromConfig } from './structure.js';
+
+function dumpMapSyntax(map, compact, syntaxAsAst) {
+ const result = {};
+
+ for (const name in map) {
+ if (map[name].syntax) {
+ result[name] = syntaxAsAst
+ ? map[name].syntax
+ : generate(map[name].syntax, { compact });
+ }
+ }
+
+ return result;
+}
+
+function dumpAtruleMapSyntax(map, compact, syntaxAsAst) {
+ const result = {};
+
+ for (const [name, atrule] of Object.entries(map)) {
+ result[name] = {
+ prelude: atrule.prelude && (
+ syntaxAsAst
+ ? atrule.prelude.syntax
+ : generate(atrule.prelude.syntax, { compact })
+ ),
+ descriptors: atrule.descriptors && dumpMapSyntax(atrule.descriptors, compact, syntaxAsAst)
+ };
+ }
+
+ return result;
+}
+
+function valueHasVar(tokens) {
+ for (let i = 0; i < tokens.length; i++) {
+ if (tokens[i].value.toLowerCase() === 'var(') {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+function syntaxHasTopLevelCommaMultiplier(syntax) {
+ const singleTerm = syntax.terms[0];
+
+ return (
+ syntax.explicit === false &&
+ syntax.terms.length === 1 &&
+ singleTerm.type === 'Multiplier' &&
+ singleTerm.comma === true
+ );
+}
+
+function buildMatchResult(matched, error, iterations) {
+ return {
+ matched,
+ iterations,
+ error,
+ ...trace
+ };
+}
+
+function matchSyntax(lexer, syntax, value, useCssWideKeywords) {
+ const tokens = prepareTokens(value, lexer.syntax);
+ let result;
+
+ if (valueHasVar(tokens)) {
+ return buildMatchResult(null, new Error('Matching for a tree with var() is not supported'));
+ }
+
+ if (useCssWideKeywords) {
+ result = matchAsTree(tokens, lexer.cssWideKeywordsSyntax, lexer);
+ }
+
+ if (!useCssWideKeywords || !result.match) {
+ result = matchAsTree(tokens, syntax.match, lexer);
+ if (!result.match) {
+ return buildMatchResult(
+ null,
+ new SyntaxMatchError(result.reason, syntax.syntax, value, result),
+ result.iterations
+ );
+ }
+ }
+
+ return buildMatchResult(result.match, null, result.iterations);
+}
+
+export class Lexer {
+ constructor(config, syntax, structure) {
+ this.cssWideKeywords = cssWideKeywords;
+ this.syntax = syntax;
+ this.generic = false;
+ this.units = { ...units };
+ this.atrules = Object.create(null);
+ this.properties = Object.create(null);
+ this.types = Object.create(null);
+ this.structure = structure || getStructureFromConfig(config);
+
+ if (config) {
+ if (config.cssWideKeywords) {
+ this.cssWideKeywords = config.cssWideKeywords;
+ }
+
+ if (config.units) {
+ for (const group of Object.keys(units)) {
+ if (Array.isArray(config.units[group])) {
+ this.units[group] = config.units[group];
+ }
+ }
+ }
+
+ if (config.types) {
+ for (const [name, type] of Object.entries(config.types)) {
+ this.addType_(name, type);
+ }
+ }
+
+ if (config.generic) {
+ this.generic = true;
+ for (const [name, value] of Object.entries(createGenericTypes(this.units))) {
+ this.addType_(name, value);
+ }
+ }
+
+ if (config.atrules) {
+ for (const [name, atrule] of Object.entries(config.atrules)) {
+ this.addAtrule_(name, atrule);
+ }
+ }
+
+ if (config.properties) {
+ for (const [name, property] of Object.entries(config.properties)) {
+ this.addProperty_(name, property);
+ }
+ }
+ }
+
+ this.cssWideKeywordsSyntax = buildMatchGraph(this.cssWideKeywords.join(' | '));
+ }
+
+ checkStructure(ast) {
+ function collectWarning(node, message) {
+ warns.push({ node, message });
+ }
+
+ const structure = this.structure;
+ const warns = [];
+
+ this.syntax.walk(ast, function(node) {
+ if (structure.hasOwnProperty(node.type)) {
+ structure[node.type].check(node, collectWarning);
+ } else {
+ collectWarning(node, 'Unknown node type `' + node.type + '`');
+ }
+ });
+
+ return warns.length ? warns : false;
+ }
+
+ createDescriptor(syntax, type, name, parent = null) {
+ const ref = {
+ type,
+ name
+ };
+ const descriptor = {
+ type,
+ name,
+ parent,
+ serializable: typeof syntax === 'string' || (syntax && typeof syntax.type === 'string'),
+ syntax: null,
+ match: null,
+ matchRef: null // used for properties when a syntax referenced as <'property'> in other syntax definitions
+ };
+
+ if (typeof syntax === 'function') {
+ descriptor.match = buildMatchGraph(syntax, ref);
+ } else {
+ if (typeof syntax === 'string') {
+ // lazy parsing on first access
+ Object.defineProperty(descriptor, 'syntax', {
+ get() {
+ Object.defineProperty(descriptor, 'syntax', {
+ value: parse(syntax)
+ });
+
+ return descriptor.syntax;
+ }
+ });
+ } else {
+ descriptor.syntax = syntax;
+ }
+
+ // lazy graph build on first access
+ Object.defineProperty(descriptor, 'match', {
+ get() {
+ Object.defineProperty(descriptor, 'match', {
+ value: buildMatchGraph(descriptor.syntax, ref)
+ });
+
+ return descriptor.match;
+ }
+ });
+
+ if (type === 'Property') {
+ Object.defineProperty(descriptor, 'matchRef', {
+ get() {
+ const syntax = descriptor.syntax;
+ const value = syntaxHasTopLevelCommaMultiplier(syntax)
+ ? buildMatchGraph({
+ ...syntax,
+ terms: [syntax.terms[0].term]
+ }, ref)
+ : null;
+
+ Object.defineProperty(descriptor, 'matchRef', {
+ value
+ });
+
+ return value;
+ }
+ });
+ }
+ }
+
+ return descriptor;
+ }
+ addAtrule_(name, syntax) {
+ if (!syntax) {
+ return;
+ }
+
+ this.atrules[name] = {
+ type: 'Atrule',
+ name: name,
+ prelude: syntax.prelude ? this.createDescriptor(syntax.prelude, 'AtrulePrelude', name) : null,
+ descriptors: syntax.descriptors
+ ? Object.keys(syntax.descriptors).reduce(
+ (map, descName) => {
+ map[descName] = this.createDescriptor(syntax.descriptors[descName], 'AtruleDescriptor', descName, name);
+ return map;
+ },
+ Object.create(null)
+ )
+ : null
+ };
+ }
+ addProperty_(name, syntax) {
+ if (!syntax) {
+ return;
+ }
+
+ this.properties[name] = this.createDescriptor(syntax, 'Property', name);
+ }
+ addType_(name, syntax) {
+ if (!syntax) {
+ return;
+ }
+
+ this.types[name] = this.createDescriptor(syntax, 'Type', name);
+ }
+
+ checkAtruleName(atruleName) {
+ if (!this.getAtrule(atruleName)) {
+ return new SyntaxReferenceError('Unknown at-rule', '@' + atruleName);
+ }
+ }
+ checkAtrulePrelude(atruleName, prelude) {
+ const error = this.checkAtruleName(atruleName);
+
+ if (error) {
+ return error;
+ }
+
+ const atrule = this.getAtrule(atruleName);
+
+ if (!atrule.prelude && prelude) {
+ return new SyntaxError('At-rule `@' + atruleName + '` should not contain a prelude');
+ }
+
+ if (atrule.prelude && !prelude) {
+ if (!matchSyntax(this, atrule.prelude, '', false).matched) {
+ return new SyntaxError('At-rule `@' + atruleName + '` should contain a prelude');
+ }
+ }
+ }
+ checkAtruleDescriptorName(atruleName, descriptorName) {
+ const error = this.checkAtruleName(atruleName);
+
+ if (error) {
+ return error;
+ }
+
+ const atrule = this.getAtrule(atruleName);
+ const descriptor = names.keyword(descriptorName);
+
+ if (!atrule.descriptors) {
+ return new SyntaxError('At-rule `@' + atruleName + '` has no known descriptors');
+ }
+
+ if (!atrule.descriptors[descriptor.name] &&
+ !atrule.descriptors[descriptor.basename]) {
+ return new SyntaxReferenceError('Unknown at-rule descriptor', descriptorName);
+ }
+ }
+ checkPropertyName(propertyName) {
+ if (!this.getProperty(propertyName)) {
+ return new SyntaxReferenceError('Unknown property', propertyName);
+ }
+ }
+
+ matchAtrulePrelude(atruleName, prelude) {
+ const error = this.checkAtrulePrelude(atruleName, prelude);
+
+ if (error) {
+ return buildMatchResult(null, error);
+ }
+
+ const atrule = this.getAtrule(atruleName);
+
+ if (!atrule.prelude) {
+ return buildMatchResult(null, null);
+ }
+
+ return matchSyntax(this, atrule.prelude, prelude || '', false);
+ }
+ matchAtruleDescriptor(atruleName, descriptorName, value) {
+ const error = this.checkAtruleDescriptorName(atruleName, descriptorName);
+
+ if (error) {
+ return buildMatchResult(null, error);
+ }
+
+ const atrule = this.getAtrule(atruleName);
+ const descriptor = names.keyword(descriptorName);
+
+ return matchSyntax(this, atrule.descriptors[descriptor.name] || atrule.descriptors[descriptor.basename], value, false);
+ }
+ matchDeclaration(node) {
+ if (node.type !== 'Declaration') {
+ return buildMatchResult(null, new Error('Not a Declaration node'));
+ }
+
+ return this.matchProperty(node.property, node.value);
+ }
+ matchProperty(propertyName, value) {
+ // don't match syntax for a custom property at the moment
+ if (names.property(propertyName).custom) {
+ return buildMatchResult(null, new Error('Lexer matching doesn\'t applicable for custom properties'));
+ }
+
+ const error = this.checkPropertyName(propertyName);
+
+ if (error) {
+ return buildMatchResult(null, error);
+ }
+
+ return matchSyntax(this, this.getProperty(propertyName), value, true);
+ }
+ matchType(typeName, value) {
+ const typeSyntax = this.getType(typeName);
+
+ if (!typeSyntax) {
+ return buildMatchResult(null, new SyntaxReferenceError('Unknown type', typeName));
+ }
+
+ return matchSyntax(this, typeSyntax, value, false);
+ }
+ match(syntax, value) {
+ if (typeof syntax !== 'string' && (!syntax || !syntax.type)) {
+ return buildMatchResult(null, new SyntaxReferenceError('Bad syntax'));
+ }
+
+ if (typeof syntax === 'string' || !syntax.match) {
+ syntax = this.createDescriptor(syntax, 'Type', 'anonymous');
+ }
+
+ return matchSyntax(this, syntax, value, false);
+ }
+
+ findValueFragments(propertyName, value, type, name) {
+ return matchFragments(this, value, this.matchProperty(propertyName, value), type, name);
+ }
+ findDeclarationValueFragments(declaration, type, name) {
+ return matchFragments(this, declaration.value, this.matchDeclaration(declaration), type, name);
+ }
+ findAllFragments(ast, type, name) {
+ const result = [];
+
+ this.syntax.walk(ast, {
+ visit: 'Declaration',
+ enter: (declaration) => {
+ result.push.apply(result, this.findDeclarationValueFragments(declaration, type, name));
+ }
+ });
+
+ return result;
+ }
+
+ getAtrule(atruleName, fallbackBasename = true) {
+ const atrule = names.keyword(atruleName);
+ const atruleEntry = atrule.vendor && fallbackBasename
+ ? this.atrules[atrule.name] || this.atrules[atrule.basename]
+ : this.atrules[atrule.name];
+
+ return atruleEntry || null;
+ }
+ getAtrulePrelude(atruleName, fallbackBasename = true) {
+ const atrule = this.getAtrule(atruleName, fallbackBasename);
+
+ return atrule && atrule.prelude || null;
+ }
+ getAtruleDescriptor(atruleName, name) {
+ return this.atrules.hasOwnProperty(atruleName) && this.atrules.declarators
+ ? this.atrules[atruleName].declarators[name] || null
+ : null;
+ }
+ getProperty(propertyName, fallbackBasename = true) {
+ const property = names.property(propertyName);
+ const propertyEntry = property.vendor && fallbackBasename
+ ? this.properties[property.name] || this.properties[property.basename]
+ : this.properties[property.name];
+
+ return propertyEntry || null;
+ }
+ getType(name) {
+ return hasOwnProperty.call(this.types, name) ? this.types[name] : null;
+ }
+
+ validate() {
+ function syntaxRef(name, isType) {
+ return isType ? `<${name}>` : `<'${name}'>`;
+ }
+
+ function validate(syntax, name, broken, descriptor) {
+ if (broken.has(name)) {
+ return broken.get(name);
+ }
+
+ broken.set(name, false);
+ if (descriptor.syntax !== null) {
+ walk(descriptor.syntax, function(node) {
+ if (node.type !== 'Type' && node.type !== 'Property') {
+ return;
+ }
+
+ const map = node.type === 'Type' ? syntax.types : syntax.properties;
+ const brokenMap = node.type === 'Type' ? brokenTypes : brokenProperties;
+
+ if (!hasOwnProperty.call(map, node.name)) {
+ errors.push(`${syntaxRef(name, broken === brokenTypes)} used missed syntax definition ${syntaxRef(node.name, node.type === 'Type')}`);
+ broken.set(name, true);
+ } else if (validate(syntax, node.name, brokenMap, map[node.name])) {
+ errors.push(`${syntaxRef(name, broken === brokenTypes)} used broken syntax definition ${syntaxRef(node.name, node.type === 'Type')}`);
+ broken.set(name, true);
+ }
+ }, this);
+ }
+ }
+
+ const errors = [];
+ let brokenTypes = new Map();
+ let brokenProperties = new Map();
+
+ for (const key in this.types) {
+ validate(this, key, brokenTypes, this.types[key]);
+ }
+
+ for (const key in this.properties) {
+ validate(this, key, brokenProperties, this.properties[key]);
+ }
+
+ const brokenTypesArray = [...brokenTypes.keys()].filter(name => brokenTypes.get(name));
+ const brokenPropertiesArray = [...brokenProperties.keys()].filter(name => brokenProperties.get(name));
+
+ if (brokenTypesArray.length || brokenPropertiesArray.length) {
+ return {
+ errors,
+ types: brokenTypesArray,
+ properties: brokenPropertiesArray
+ };
+ }
+
+ return null;
+ }
+ dump(syntaxAsAst, pretty) {
+ return {
+ generic: this.generic,
+ cssWideKeywords: this.cssWideKeywords,
+ units: this.units,
+ types: dumpMapSyntax(this.types, !pretty, syntaxAsAst),
+ properties: dumpMapSyntax(this.properties, !pretty, syntaxAsAst),
+ atrules: dumpAtruleMapSyntax(this.atrules, !pretty, syntaxAsAst)
+ };
+ }
+ toString() {
+ return JSON.stringify(this.dump());
+ }
+};
diff --git a/vanilla/node_modules/css-tree/lib/lexer/error.js b/vanilla/node_modules/css-tree/lib/lexer/error.js
new file mode 100644
index 0000000..89a5400
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/lexer/error.js
@@ -0,0 +1,123 @@
+import { createCustomError } from '../utils/create-custom-error.js';
+import { generate } from '../definition-syntax/generate.js';
+
+const defaultLoc = { offset: 0, line: 1, column: 1 };
+
+function locateMismatch(matchResult, node) {
+ const tokens = matchResult.tokens;
+ const longestMatch = matchResult.longestMatch;
+ const mismatchNode = longestMatch < tokens.length ? tokens[longestMatch].node || null : null;
+ const badNode = mismatchNode !== node ? mismatchNode : null;
+ let mismatchOffset = 0;
+ let mismatchLength = 0;
+ let entries = 0;
+ let css = '';
+ let start;
+ let end;
+
+ for (let i = 0; i < tokens.length; i++) {
+ const token = tokens[i].value;
+
+ if (i === longestMatch) {
+ mismatchLength = token.length;
+ mismatchOffset = css.length;
+ }
+
+ if (badNode !== null && tokens[i].node === badNode) {
+ if (i <= longestMatch) {
+ entries++;
+ } else {
+ entries = 0;
+ }
+ }
+
+ css += token;
+ }
+
+ if (longestMatch === tokens.length || entries > 1) { // last
+ start = fromLoc(badNode || node, 'end') || buildLoc(defaultLoc, css);
+ end = buildLoc(start);
+ } else {
+ start = fromLoc(badNode, 'start') ||
+ buildLoc(fromLoc(node, 'start') || defaultLoc, css.slice(0, mismatchOffset));
+ end = fromLoc(badNode, 'end') ||
+ buildLoc(start, css.substr(mismatchOffset, mismatchLength));
+ }
+
+ return {
+ css,
+ mismatchOffset,
+ mismatchLength,
+ start,
+ end
+ };
+}
+
+function fromLoc(node, point) {
+ const value = node && node.loc && node.loc[point];
+
+ if (value) {
+ return 'line' in value ? buildLoc(value) : value;
+ }
+
+ return null;
+}
+
+function buildLoc({ offset, line, column }, extra) {
+ const loc = {
+ offset,
+ line,
+ column
+ };
+
+ if (extra) {
+ const lines = extra.split(/\n|\r\n?|\f/);
+
+ loc.offset += extra.length;
+ loc.line += lines.length - 1;
+ loc.column = lines.length === 1 ? loc.column + extra.length : lines.pop().length + 1;
+ }
+
+ return loc;
+}
+
+export const SyntaxReferenceError = function(type, referenceName) {
+ const error = createCustomError(
+ 'SyntaxReferenceError',
+ type + (referenceName ? ' `' + referenceName + '`' : '')
+ );
+
+ error.reference = referenceName;
+
+ return error;
+};
+
+export const SyntaxMatchError = function(message, syntax, node, matchResult) {
+ const error = createCustomError('SyntaxMatchError', message);
+ const {
+ css,
+ mismatchOffset,
+ mismatchLength,
+ start,
+ end
+ } = locateMismatch(matchResult, node);
+
+ error.rawMessage = message;
+ error.syntax = syntax ? generate(syntax) : '<generic>';
+ error.css = css;
+ error.mismatchOffset = mismatchOffset;
+ error.mismatchLength = mismatchLength;
+ error.message = message + '\n' +
+ ' syntax: ' + error.syntax + '\n' +
+ ' value: ' + (css || '<empty string>') + '\n' +
+ ' --------' + new Array(error.mismatchOffset + 1).join('-') + '^';
+
+ Object.assign(error, start);
+ error.loc = {
+ source: (node && node.loc && node.loc.source) || '<unknown>',
+ start,
+ end
+ };
+
+ return error;
+};
diff --git a/vanilla/node_modules/css-tree/lib/lexer/generic-an-plus-b.js b/vanilla/node_modules/css-tree/lib/lexer/generic-an-plus-b.js
new file mode 100644
index 0000000..347715c
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/lexer/generic-an-plus-b.js
@@ -0,0 +1,238 @@
+import {
+ isDigit,
+ cmpChar,
+ Delim,
+ WhiteSpace,
+ Comment,
+ Ident,
+ Number as NumberToken,
+ Dimension
+} from '../tokenizer/index.js';
+
+const PLUSSIGN = 0x002B; // U+002B PLUS SIGN (+)
+const HYPHENMINUS = 0x002D; // U+002D HYPHEN-MINUS (-)
+const N = 0x006E; // U+006E LATIN SMALL LETTER N (n)
+const DISALLOW_SIGN = true;
+const ALLOW_SIGN = false;
+
+function isDelim(token, code) {
+ return token !== null && token.type === Delim && token.value.charCodeAt(0) === code;
+}
+
+function skipSC(token, offset, getNextToken) {
+ while (token !== null && (token.type === WhiteSpace || token.type === Comment)) {
+ token = getNextToken(++offset);
+ }
+
+ return offset;
+}
+
+function checkInteger(token, valueOffset, disallowSign, offset) {
+ if (!token) {
+ return 0;
+ }
+
+ const code = token.value.charCodeAt(valueOffset);
+
+ if (code === PLUSSIGN || code === HYPHENMINUS) {
+ if (disallowSign) {
+ // Number sign is not allowed
+ return 0;
+ }
+ valueOffset++;
+ }
+
+ for (; valueOffset < token.value.length; valueOffset++) {
+ if (!isDigit(token.value.charCodeAt(valueOffset))) {
+ // Integer is expected
+ return 0;
+ }
+ }
+
+ return offset + 1;
+}
+
+// ... <signed-integer>
+// ... ['+' | '-'] <signless-integer>
+function consumeB(token, offset_, getNextToken) {
+ let sign = false;
+ let offset = skipSC(token, offset_, getNextToken);
+
+ token = getNextToken(offset);
+
+ if (token === null) {
+ return offset_;
+ }
+
+ if (token.type !== NumberToken) {
+ if (isDelim(token, PLUSSIGN) || isDelim(token, HYPHENMINUS)) {
+ sign = true;
+ offset = skipSC(getNextToken(++offset), offset, getNextToken);
+ token = getNextToken(offset);
+
+ if (token === null || token.type !== NumberToken) {
+ return 0;
+ }
+ } else {
+ return offset_;
+ }
+ }
+
+ if (!sign) {
+ const code = token.value.charCodeAt(0);
+ if (code !== PLUSSIGN && code !== HYPHENMINUS) {
+ // Number sign is expected
+ return 0;
+ }
+ }
+
+ return checkInteger(token, sign ? 0 : 1, sign, offset);
+}
+
+// An+B microsyntax https://www.w3.org/TR/css-syntax-3/#anb
+export default function anPlusB(token, getNextToken) {
+ /* eslint-disable brace-style*/
+ let offset = 0;
+
+ if (!token) {
+ return 0;
+ }
+
+ // <integer>
+ if (token.type === NumberToken) {
+ return checkInteger(token, 0, ALLOW_SIGN, offset); // b
+ }
+
+ // -n
+ // -n <signed-integer>
+ // -n ['+' | '-'] <signless-integer>
+ // -n- <signless-integer>
+ // <dashndashdigit-ident>
+ else if (token.type === Ident && token.value.charCodeAt(0) === HYPHENMINUS) {
+ // expect 1st char is N
+ if (!cmpChar(token.value, 1, N)) {
+ return 0;
+ }
+
+ switch (token.value.length) {
+ // -n
+ // -n <signed-integer>
+ // -n ['+' | '-'] <signless-integer>
+ case 2:
+ return consumeB(getNextToken(++offset), offset, getNextToken);
+
+ // -n- <signless-integer>
+ case 3:
+ if (token.value.charCodeAt(2) !== HYPHENMINUS) {
+ return 0;
+ }
+
+ offset = skipSC(getNextToken(++offset), offset, getNextToken);
+ token = getNextToken(offset);
+
+ return checkInteger(token, 0, DISALLOW_SIGN, offset);
+
+ // <dashndashdigit-ident>
+ default:
+ if (token.value.charCodeAt(2) !== HYPHENMINUS) {
+ return 0;
+ }
+
+ return checkInteger(token, 3, DISALLOW_SIGN, offset);
+ }
+ }
+
+ // '+'? n
+ // '+'? n <signed-integer>
+ // '+'? n ['+' | '-'] <signless-integer>
+ // '+'? n- <signless-integer>
+ // '+'? <ndashdigit-ident>
+ else if (token.type === Ident || (isDelim(token, PLUSSIGN) && getNextToken(offset + 1).type === Ident)) {
+ // just ignore a plus
+ if (token.type !== Ident) {
+ token = getNextToken(++offset);
+ }
+
+ if (token === null || !cmpChar(token.value, 0, N)) {
+ return 0;
+ }
+
+ switch (token.value.length) {
+ // '+'? n
+ // '+'? n <signed-integer>
+ // '+'? n ['+' | '-'] <signless-integer>
+ case 1:
+ return consumeB(getNextToken(++offset), offset, getNextToken);
+
+ // '+'? n- <signless-integer>
+ case 2:
+ if (token.value.charCodeAt(1) !== HYPHENMINUS) {
+ return 0;
+ }
+
+ offset = skipSC(getNextToken(++offset), offset, getNextToken);
+ token = getNextToken(offset);
+
+ return checkInteger(token, 0, DISALLOW_SIGN, offset);
+
+ // '+'? <ndashdigit-ident>
+ default:
+ if (token.value.charCodeAt(1) !== HYPHENMINUS) {
+ return 0;
+ }
+
+ return checkInteger(token, 2, DISALLOW_SIGN, offset);
+ }
+ }
+
+ // <ndashdigit-dimension>
+ // <ndash-dimension> <signless-integer>
+ // <n-dimension>
+ // <n-dimension> <signed-integer>
+ // <n-dimension> ['+' | '-'] <signless-integer>
+ else if (token.type === Dimension) {
+ let code = token.value.charCodeAt(0);
+ let sign = code === PLUSSIGN || code === HYPHENMINUS ? 1 : 0;
+ let i = sign;
+
+ for (; i < token.value.length; i++) {
+ if (!isDigit(token.value.charCodeAt(i))) {
+ break;
+ }
+ }
+
+ if (i === sign) {
+ // Integer is expected
+ return 0;
+ }
+
+ if (!cmpChar(token.value, i, N)) {
+ return 0;
+ }
+
+ // <n-dimension>
+ // <n-dimension> <signed-integer>
+ // <n-dimension> ['+' | '-'] <signless-integer>
+ if (i + 1 === token.value.length) {
+ return consumeB(getNextToken(++offset), offset, getNextToken);
+ } else {
+ if (token.value.charCodeAt(i + 1) !== HYPHENMINUS) {
+ return 0;
+ }
+
+ // <ndash-dimension> <signless-integer>
+ if (i + 2 === token.value.length) {
+ offset = skipSC(getNextToken(++offset), offset, getNextToken);
+ token = getNextToken(offset);
+
+ return checkInteger(token, 0, DISALLOW_SIGN, offset);
+ }
+ // <ndashdigit-dimension>
+ else {
+ return checkInteger(token, i + 2, DISALLOW_SIGN, offset);
+ }
+ }
+ }
+
+ return 0;
+};
diff --git a/vanilla/node_modules/css-tree/lib/lexer/generic-const.js b/vanilla/node_modules/css-tree/lib/lexer/generic-const.js
new file mode 100644
index 0000000..8efe6ae
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/lexer/generic-const.js
@@ -0,0 +1,8 @@
+// https://drafts.csswg.org/css-cascade-5/
+export const cssWideKeywords = [
+ 'initial',
+ 'inherit',
+ 'unset',
+ 'revert',
+ 'revert-layer'
+];
diff --git a/vanilla/node_modules/css-tree/lib/lexer/generic-urange.js b/vanilla/node_modules/css-tree/lib/lexer/generic-urange.js
new file mode 100644
index 0000000..d878409
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/lexer/generic-urange.js
@@ -0,0 +1,151 @@
+import {
+ isHexDigit,
+ cmpChar,
+ Ident,
+ Delim,
+ Number as NumberToken,
+ Dimension
+} from '../tokenizer/index.js';
+
+const PLUSSIGN = 0x002B; // U+002B PLUS SIGN (+)
+const HYPHENMINUS = 0x002D; // U+002D HYPHEN-MINUS (-)
+const QUESTIONMARK = 0x003F; // U+003F QUESTION MARK (?)
+const U = 0x0075; // U+0075 LATIN SMALL LETTER U (u)
+
+function isDelim(token, code) {
+ return token !== null && token.type === Delim && token.value.charCodeAt(0) === code;
+}
+
+function startsWith(token, code) {
+ return token.value.charCodeAt(0) === code;
+}
+
+function hexSequence(token, offset, allowDash) {
+ let hexlen = 0;
+
+ for (let pos = offset; pos < token.value.length; pos++) {
+ const code = token.value.charCodeAt(pos);
+
+ if (code === HYPHENMINUS && allowDash && hexlen !== 0) {
+ hexSequence(token, offset + hexlen + 1, false);
+ return 6; // dissallow following question marks
+ }
+
+ if (!isHexDigit(code)) {
+ return 0; // not a hex digit
+ }
+
+ if (++hexlen > 6) {
+ return 0; // too many hex digits
+ };
+ }
+
+ return hexlen;
+}
+
+function withQuestionMarkSequence(consumed, length, getNextToken) {
+ if (!consumed) {
+ return 0; // nothing consumed
+ }
+
+ while (isDelim(getNextToken(length), QUESTIONMARK)) {
+ if (++consumed > 6) {
+ return 0; // too many question marks
+ }
+
+ length++;
+ }
+
+ return length;
+}
+
+// https://drafts.csswg.org/css-syntax/#urange
+// Informally, the <urange> production has three forms:
+// U+0001
+// Defines a range consisting of a single code point, in this case the code point "1".
+// U+0001-00ff
+// Defines a range of codepoints between the first and the second value, in this case
+// the range between "1" and "ff" (255 in decimal) inclusive.
+// U+00??
+// Defines a range of codepoints where the "?" characters range over all hex digits,
+// in this case defining the same as the value U+0000-00ff.
+// In each form, a maximum of 6 digits is allowed for each hexadecimal number (if you treat "?" as a hexadecimal digit).
+//
+// <urange> =
+// u '+' <ident-token> '?'* |
+// u <dimension-token> '?'* |
+// u <number-token> '?'* |
+// u <number-token> <dimension-token> |
+// u <number-token> <number-token> |
+// u '+' '?'+
+export default function urange(token, getNextToken) {
+ let length = 0;
+
+ // should start with `u` or `U`
+ if (token === null || token.type !== Ident || !cmpChar(token.value, 0, U)) {
+ return 0;
+ }
+
+ token = getNextToken(++length);
+ if (token === null) {
+ return 0;
+ }
+
+ // u '+' <ident-token> '?'*
+ // u '+' '?'+
+ if (isDelim(token, PLUSSIGN)) {
+ token = getNextToken(++length);
+ if (token === null) {
+ return 0;
+ }
+
+ if (token.type === Ident) {
+ // u '+' <ident-token> '?'*
+ return withQuestionMarkSequence(hexSequence(token, 0, true), ++length, getNextToken);
+ }
+
+ if (isDelim(token, QUESTIONMARK)) {
+ // u '+' '?'+
+ return withQuestionMarkSequence(1, ++length, getNextToken);
+ }
+
+ // Hex digit or question mark is expected
+ return 0;
+ }
+
+ // u <number-token> '?'*
+ // u <number-token> <dimension-token>
+ // u <number-token> <number-token>
+ if (token.type === NumberToken) {
+ const consumedHexLength = hexSequence(token, 1, true);
+ if (consumedHexLength === 0) {
+ return 0;
+ }
+
+ token = getNextToken(++length);
+ if (token === null) {
+ // u <number-token> <eof>
+ return length;
+ }
+
+ if (token.type === Dimension || token.type === NumberToken) {
+ // u <number-token> <dimension-token>
+ // u <number-token> <number-token>
+ if (!startsWith(token, HYPHENMINUS) || !hexSequence(token, 1, false)) {
+ return 0;
+ }
+
+ return length + 1;
+ }
+
+ // u <number-token> '?'*
+ return withQuestionMarkSequence(consumedHexLength, length, getNextToken);
+ }
+
+ // u <dimension-token> '?'*
+ if (token.type === Dimension) {
+ return withQuestionMarkSequence(hexSequence(token, 1, true), ++length, getNextToken);
+ }
+
+ return 0;
+};
diff --git a/vanilla/node_modules/css-tree/lib/lexer/generic.js b/vanilla/node_modules/css-tree/lib/lexer/generic.js
new file mode 100644
index 0000000..422e130
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/lexer/generic.js
@@ -0,0 +1,622 @@
+import { cssWideKeywords } from './generic-const.js';
+import anPlusB from './generic-an-plus-b.js';
+import urange from './generic-urange.js';
+import {
+ isIdentifierStart,
+ isHexDigit,
+ isDigit,
+ cmpStr,
+ consumeNumber,
+
+ Ident,
+ Function as FunctionToken,
+ AtKeyword,
+ Hash,
+ String as StringToken,
+ BadString,
+ Url,
+ BadUrl,
+ Delim,
+ Number as NumberToken,
+ Percentage,
+ Dimension,
+ WhiteSpace,
+ CDO,
+ CDC,
+ Colon,
+ Semicolon,
+ Comma,
+ LeftSquareBracket,
+ RightSquareBracket,
+ LeftParenthesis,
+ RightParenthesis,
+ LeftCurlyBracket,
+ RightCurlyBracket
+} from '../tokenizer/index.js';
+
+const calcFunctionNames = ['calc(', '-moz-calc(', '-webkit-calc('];
+const balancePair = new Map([
+ [FunctionToken, RightParenthesis],
+ [LeftParenthesis, RightParenthesis],
+ [LeftSquareBracket, RightSquareBracket],
+ [LeftCurlyBracket, RightCurlyBracket]
+]);
+
+// safe char code getter
+function charCodeAt(str, index) {
+ return index < str.length ? str.charCodeAt(index) : 0;
+}
+
+function eqStr(actual, expected) {
+ return cmpStr(actual, 0, actual.length, expected);
+}
+
+function eqStrAny(actual, expected) {
+ for (let i = 0; i < expected.length; i++) {
+ if (eqStr(actual, expected[i])) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+// IE postfix hack, i.e. 123\0 or 123px\9
+function isPostfixIeHack(str, offset) {
+ if (offset !== str.length - 2) {
+ return false;
+ }
+
+ return (
+ charCodeAt(str, offset) === 0x005C && // U+005C REVERSE SOLIDUS (\)
+ isDigit(charCodeAt(str, offset + 1))
+ );
+}
+
+function outOfRange(opts, value, numEnd) {
+ if (opts && opts.type === 'Range') {
+ const num = Number(
+ numEnd !== undefined && numEnd !== value.length
+ ? value.substr(0, numEnd)
+ : value
+ );
+
+ if (isNaN(num)) {
+ return true;
+ }
+
+ // FIXME: when opts.min is a string it's a dimension, skip a range validation
+ // for now since it requires a type covertation which is not implmented yet
+ if (opts.min !== null && num < opts.min && typeof opts.min !== 'string') {
+ return true;
+ }
+
+ // FIXME: when opts.max is a string it's a dimension, skip a range validation
+ // for now since it requires a type covertation which is not implmented yet
+ if (opts.max !== null && num > opts.max && typeof opts.max !== 'string') {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+function consumeFunction(token, getNextToken) {
+ let balanceCloseType = 0;
+ let balanceStash = [];
+ let length = 0;
+
+ // balanced token consuming
+ scan:
+ do {
+ switch (token.type) {
+ case RightCurlyBracket:
+ case RightParenthesis:
+ case RightSquareBracket:
+ if (token.type !== balanceCloseType) {
+ break scan;
+ }
+
+ balanceCloseType = balanceStash.pop();
+
+ if (balanceStash.length === 0) {
+ length++;
+ break scan;
+ }
+
+ break;
+
+ case FunctionToken:
+ case LeftParenthesis:
+ case LeftSquareBracket:
+ case LeftCurlyBracket:
+ balanceStash.push(balanceCloseType);
+ balanceCloseType = balancePair.get(token.type);
+ break;
+ }
+
+ length++;
+ } while (token = getNextToken(length));
+
+ return length;
+}
+
+// TODO: implement
+// can be used wherever <length>, <frequency>, <angle>, <time>, <percentage>, <number>, or <integer> values are allowed
+// https://drafts.csswg.org/css-values/#calc-notation
+function calc(next) {
+ return function(token, getNextToken, opts) {
+ if (token === null) {
+ return 0;
+ }
+
+ if (token.type === FunctionToken && eqStrAny(token.value, calcFunctionNames)) {
+ return consumeFunction(token, getNextToken);
+ }
+
+ return next(token, getNextToken, opts);
+ };
+}
+
+function tokenType(expectedTokenType) {
+ return function(token) {
+ if (token === null || token.type !== expectedTokenType) {
+ return 0;
+ }
+
+ return 1;
+ };
+}
+
+// =========================
+// Complex types
+//
+
+// https://drafts.csswg.org/css-values-4/#custom-idents
+// 4.2. Author-defined Identifiers: the <custom-ident> type
+// Some properties accept arbitrary author-defined identifiers as a component value.
+// This generic data type is denoted by <custom-ident>, and represents any valid CSS identifier
+// that would not be misinterpreted as a pre-defined keyword in that property’s value definition.
+//
+// See also: https://developer.mozilla.org/en-US/docs/Web/CSS/custom-ident
+function customIdent(token) {
+ if (token === null || token.type !== Ident) {
+ return 0;
+ }
+
+ const name = token.value.toLowerCase();
+
+ // The CSS-wide keywords are not valid <custom-ident>s
+ if (eqStrAny(name, cssWideKeywords)) {
+ return 0;
+ }
+
+ // The default keyword is reserved and is also not a valid <custom-ident>
+ if (eqStr(name, 'default')) {
+ return 0;
+ }
+
+ // TODO: ignore property specific keywords (as described https://developer.mozilla.org/en-US/docs/Web/CSS/custom-ident)
+ // Specifications using <custom-ident> must specify clearly what other keywords
+ // are excluded from <custom-ident>, if any—for example by saying that any pre-defined keywords
+ // in that property’s value definition are excluded. Excluded keywords are excluded
+ // in all ASCII case permutations.
+
+ return 1;
+}
+
+// https://drafts.csswg.org/css-values-4/#dashed-idents
+// The <dashed-ident> production is a <custom-ident>, with all the case-sensitivity that implies,
+// with the additional restriction that it must start with two dashes (U+002D HYPHEN-MINUS).
+function dashedIdent(token) {
+ if (token === null || token.type !== Ident) {
+ return 0;
+ }
+
+ // ... must start with two dashes (U+002D HYPHEN-MINUS)
+ if (charCodeAt(token.value, 0) !== 0x002D || charCodeAt(token.value, 1) !== 0x002D) {
+ return 0;
+ }
+
+ return 1;
+}
+
+// https://drafts.csswg.org/css-variables/#typedef-custom-property-name
+// A custom property is any property whose name starts with two dashes (U+002D HYPHEN-MINUS), like --foo.
+// The <custom-property-name> production corresponds to this: it’s defined as any <dashed-ident>
+// (a valid identifier that starts with two dashes), except -- itself, which is reserved for future use by CSS.
+function customPropertyName(token) {
+ // ... it’s defined as any <dashed-ident>
+ if (!dashedIdent(token)) {
+ return 0;
+ }
+
+ // ... except -- itself, which is reserved for future use by CSS
+ if (token.value === '--') {
+ return 0;
+ }
+
+ return 1;
+}
+
+// https://drafts.csswg.org/css-color-4/#hex-notation
+// The syntax of a <hex-color> is a <hash-token> token whose value consists of 3, 4, 6, or 8 hexadecimal digits.
+// In other words, a hex color is written as a hash character, "#", followed by some number of digits 0-9 or
+// letters a-f (the case of the letters doesn’t matter - #00ff00 is identical to #00FF00).
+function hexColor(token) {
+ if (token === null || token.type !== Hash) {
+ return 0;
+ }
+
+ const length = token.value.length;
+
+ // valid values (length): #rgb (4), #rgba (5), #rrggbb (7), #rrggbbaa (9)
+ if (length !== 4 && length !== 5 && length !== 7 && length !== 9) {
+ return 0;
+ }
+
+ for (let i = 1; i < length; i++) {
+ if (!isHexDigit(charCodeAt(token.value, i))) {
+ return 0;
+ }
+ }
+
+ return 1;
+}
+
+function idSelector(token) {
+ if (token === null || token.type !== Hash) {
+ return 0;
+ }
+
+ if (!isIdentifierStart(charCodeAt(token.value, 1), charCodeAt(token.value, 2), charCodeAt(token.value, 3))) {
+ return 0;
+ }
+
+ return 1;
+}
+
+// https://drafts.csswg.org/css-syntax/#any-value
+// It represents the entirety of what a valid declaration can have as its value.
+function declarationValue(token, getNextToken) {
+ if (!token) {
+ return 0;
+ }
+
+ let balanceCloseType = 0;
+ let balanceStash = [];
+ let length = 0;
+
+ // The <declaration-value> production matches any sequence of one or more tokens,
+ // so long as the sequence does not contain ...
+ scan:
+ do {
+ switch (token.type) {
+ // ... <bad-string-token>, <bad-url-token>,
+ case BadString:
+ case BadUrl:
+ break scan;
+
+ // ... unmatched <)-token>, <]-token>, or <}-token>,
+ case RightCurlyBracket:
+ case RightParenthesis:
+ case RightSquareBracket:
+ if (token.type !== balanceCloseType) {
+ break scan;
+ }
+
+ balanceCloseType = balanceStash.pop();
+ break;
+
+ // ... or top-level <semicolon-token> tokens
+ case Semicolon:
+ if (balanceCloseType === 0) {
+ break scan;
+ }
+
+ break;
+
+ // ... or <delim-token> tokens with a value of "!"
+ case Delim:
+ if (balanceCloseType === 0 && token.value === '!') {
+ break scan;
+ }
+
+ break;
+
+ case FunctionToken:
+ case LeftParenthesis:
+ case LeftSquareBracket:
+ case LeftCurlyBracket:
+ balanceStash.push(balanceCloseType);
+ balanceCloseType = balancePair.get(token.type);
+ break;
+ }
+
+ length++;
+ } while (token = getNextToken(length));
+
+ return length;
+}
+
+// https://drafts.csswg.org/css-syntax/#any-value
+// The <any-value> production is identical to <declaration-value>, but also
+// allows top-level <semicolon-token> tokens and <delim-token> tokens
+// with a value of "!". It represents the entirety of what valid CSS can be in any context.
+function anyValue(token, getNextToken) {
+ if (!token) {
+ return 0;
+ }
+
+ let balanceCloseType = 0;
+ let balanceStash = [];
+ let length = 0;
+
+ // The <any-value> production matches any sequence of one or more tokens,
+ // so long as the sequence ...
+ scan:
+ do {
+ switch (token.type) {
+ // ... does not contain <bad-string-token>, <bad-url-token>,
+ case BadString:
+ case BadUrl:
+ break scan;
+
+ // ... unmatched <)-token>, <]-token>, or <}-token>,
+ case RightCurlyBracket:
+ case RightParenthesis:
+ case RightSquareBracket:
+ if (token.type !== balanceCloseType) {
+ break scan;
+ }
+
+ balanceCloseType = balanceStash.pop();
+ break;
+
+ case FunctionToken:
+ case LeftParenthesis:
+ case LeftSquareBracket:
+ case LeftCurlyBracket:
+ balanceStash.push(balanceCloseType);
+ balanceCloseType = balancePair.get(token.type);
+ break;
+ }
+
+ length++;
+ } while (token = getNextToken(length));
+
+ return length;
+}
+
+// =========================
+// Dimensions
+//
+
+function dimension(type) {
+ if (type) {
+ type = new Set(type);
+ }
+
+ return function(token, getNextToken, opts) {
+ if (token === null || token.type !== Dimension) {
+ return 0;
+ }
+
+ const numberEnd = consumeNumber(token.value, 0);
+
+ // check unit
+ if (type !== null) {
+ // check for IE postfix hack, i.e. 123px\0 or 123px\9
+ const reverseSolidusOffset = token.value.indexOf('\\', numberEnd);
+ const unit = reverseSolidusOffset === -1 || !isPostfixIeHack(token.value, reverseSolidusOffset)
+ ? token.value.substr(numberEnd)
+ : token.value.substring(numberEnd, reverseSolidusOffset);
+
+ if (type.has(unit.toLowerCase()) === false) {
+ return 0;
+ }
+ }
+
+ // check range if specified
+ if (outOfRange(opts, token.value, numberEnd)) {
+ return 0;
+ }
+
+ return 1;
+ };
+}
+
+// =========================
+// Percentage
+//
+
+// §5.5. Percentages: the <percentage> type
+// https://drafts.csswg.org/css-values-4/#percentages
+function percentage(token, getNextToken, opts) {
+ // ... corresponds to the <percentage-token> production
+ if (token === null || token.type !== Percentage) {
+ return 0;
+ }
+
+ // check range if specified
+ if (outOfRange(opts, token.value, token.value.length - 1)) {
+ return 0;
+ }
+
+ return 1;
+}
+
+// =========================
+// Numeric
+//
+
+// https://drafts.csswg.org/css-values-4/#numbers
+// The value <zero> represents a literal number with the value 0. Expressions that merely
+// evaluate to a <number> with the value 0 (for example, calc(0)) do not match <zero>;
+// only literal <number-token>s do.
+function zero(next) {
+ if (typeof next !== 'function') {
+ next = function() {
+ return 0;
+ };
+ }
+
+ return function(token, getNextToken, opts) {
+ if (token !== null && token.type === NumberToken) {
+ if (Number(token.value) === 0) {
+ return 1;
+ }
+ }
+
+ return next(token, getNextToken, opts);
+ };
+}
+
+// § 5.3. Real Numbers: the <number> type
+// https://drafts.csswg.org/css-values-4/#numbers
+// Number values are denoted by <number>, and represent real numbers, possibly with a fractional component.
+// ... It corresponds to the <number-token> production
+function number(token, getNextToken, opts) {
+ if (token === null) {
+ return 0;
+ }
+
+ const numberEnd = consumeNumber(token.value, 0);
+ const isNumber = numberEnd === token.value.length;
+ if (!isNumber && !isPostfixIeHack(token.value, numberEnd)) {
+ return 0;
+ }
+
+ // check range if specified
+ if (outOfRange(opts, token.value, numberEnd)) {
+ return 0;
+ }
+
+ return 1;
+}
+
+// §5.2. Integers: the <integer> type
+// https://drafts.csswg.org/css-values-4/#integers
+function integer(token, getNextToken, opts) {
+ // ... corresponds to a subset of the <number-token> production
+ if (token === null || token.type !== NumberToken) {
+ return 0;
+ }
+
+ // The first digit of an integer may be immediately preceded by `-` or `+` to indicate the integer’s sign.
+ let i = charCodeAt(token.value, 0) === 0x002B || // U+002B PLUS SIGN (+)
+ charCodeAt(token.value, 0) === 0x002D ? 1 : 0; // U+002D HYPHEN-MINUS (-)
+
+ // When written literally, an integer is one or more decimal digits 0 through 9 ...
+ for (; i < token.value.length; i++) {
+ if (!isDigit(charCodeAt(token.value, i))) {
+ return 0;
+ }
+ }
+
+ // check range if specified
+ if (outOfRange(opts, token.value, i)) {
+ return 0;
+ }
+
+ return 1;
+}
+
+// token types
+export const tokenTypes = {
+ 'ident-token': tokenType(Ident),
+ 'function-token': tokenType(FunctionToken),
+ 'at-keyword-token': tokenType(AtKeyword),
+ 'hash-token': tokenType(Hash),
+ 'string-token': tokenType(StringToken),
+ 'bad-string-token': tokenType(BadString),
+ 'url-token': tokenType(Url),
+ 'bad-url-token': tokenType(BadUrl),
+ 'delim-token': tokenType(Delim),
+ 'number-token': tokenType(NumberToken),
+ 'percentage-token': tokenType(Percentage),
+ 'dimension-token': tokenType(Dimension),
+ 'whitespace-token': tokenType(WhiteSpace),
+ 'CDO-token': tokenType(CDO),
+ 'CDC-token': tokenType(CDC),
+ 'colon-token': tokenType(Colon),
+ 'semicolon-token': tokenType(Semicolon),
+ 'comma-token': tokenType(Comma),
+ '[-token': tokenType(LeftSquareBracket),
+ ']-token': tokenType(RightSquareBracket),
+ '(-token': tokenType(LeftParenthesis),
+ ')-token': tokenType(RightParenthesis),
+ '{-token': tokenType(LeftCurlyBracket),
+ '}-token': tokenType(RightCurlyBracket)
+};
+
+// token production types
+export const productionTypes = {
+ // token type aliases
+ 'string': tokenType(StringToken),
+ 'ident': tokenType(Ident),
+
+ // percentage
+ 'percentage': calc(percentage),
+
+ // numeric
+ 'zero': zero(),
+ 'number': calc(number),
+ 'integer': calc(integer),
+
+ // complex types
+ 'custom-ident': customIdent,
+ 'dashed-ident': dashedIdent,
+ 'custom-property-name': customPropertyName,
+ 'hex-color': hexColor,
+ 'id-selector': idSelector, // element( <id-selector> )
+ 'an-plus-b': anPlusB,
+ 'urange': urange,
+ 'declaration-value': declarationValue,
+ 'any-value': anyValue
+};
+
+export const unitGroups = [
+ 'length',
+ 'angle',
+ 'time',
+ 'frequency',
+ 'resolution',
+ 'flex',
+ 'decibel',
+ 'semitones'
+];
+
+// dimensions types depend on units set
+export function createDemensionTypes(units) {
+ const {
+ angle,
+ decibel,
+ frequency,
+ flex,
+ length,
+ resolution,
+ semitones,
+ time
+ } = units || {};
+
+ return {
+ 'dimension': calc(dimension(null)),
+ 'angle': calc(dimension(angle)),
+ 'decibel': calc(dimension(decibel)),
+ 'frequency': calc(dimension(frequency)),
+ 'flex': calc(dimension(flex)),
+ 'length': calc(zero(dimension(length))),
+ 'resolution': calc(dimension(resolution)),
+ 'semitones': calc(dimension(semitones)),
+ 'time': calc(dimension(time))
+ };
+}
+
+export function createGenericTypes(units) {
+ return {
+ ...tokenTypes,
+ ...productionTypes,
+ ...createDemensionTypes(units)
+ };
+};
diff --git a/vanilla/node_modules/css-tree/lib/lexer/index.js b/vanilla/node_modules/css-tree/lib/lexer/index.js
new file mode 100644
index 0000000..1661b76
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/lexer/index.js
@@ -0,0 +1 @@
+export { Lexer } from './Lexer.js';
diff --git a/vanilla/node_modules/css-tree/lib/lexer/match-graph.js b/vanilla/node_modules/css-tree/lib/lexer/match-graph.js
new file mode 100644
index 0000000..5d3d800
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/lexer/match-graph.js
@@ -0,0 +1,527 @@
+import { parse } from '../definition-syntax/parse.js';
+
+export const MATCH = { type: 'Match' };
+export const MISMATCH = { type: 'Mismatch' };
+export const DISALLOW_EMPTY = { type: 'DisallowEmpty' };
+
+const LEFTPARENTHESIS = 40; // (
+const RIGHTPARENTHESIS = 41; // )
+
+function createCondition(match, thenBranch, elseBranch) {
+ // reduce node count
+ if (thenBranch === MATCH && elseBranch === MISMATCH) {
+ return match;
+ }
+
+ if (match === MATCH && thenBranch === MATCH && elseBranch === MATCH) {
+ return match;
+ }
+
+ if (match.type === 'If' && match.else === MISMATCH && thenBranch === MATCH) {
+ thenBranch = match.then;
+ match = match.match;
+ }
+
+ return {
+ type: 'If',
+ match,
+ then: thenBranch,
+ else: elseBranch
+ };
+}
+
+function isFunctionType(name) {
+ return (
+ name.length > 2 &&
+ name.charCodeAt(name.length - 2) === LEFTPARENTHESIS &&
+ name.charCodeAt(name.length - 1) === RIGHTPARENTHESIS
+ );
+}
+
+function isEnumCapatible(term) {
+ return (
+ term.type === 'Keyword' ||
+ term.type === 'AtKeyword' ||
+ term.type === 'Function' ||
+ term.type === 'Type' && isFunctionType(term.name)
+ );
+}
+
+function groupNode(terms, combinator = ' ', explicit = false) {
+ return {
+ type: 'Group',
+ terms,
+ combinator,
+ disallowEmpty: false,
+ explicit
+ };
+}
+
+function replaceTypeInGraph(node, replacements, visited = new Set()) {
+ if (!visited.has(node)) {
+ visited.add(node);
+
+ switch (node.type) {
+ case 'If':
+ node.match = replaceTypeInGraph(node.match, replacements, visited);
+ node.then = replaceTypeInGraph(node.then, replacements, visited);
+ node.else = replaceTypeInGraph(node.else, replacements, visited);
+ break;
+
+ case 'Type':
+ return replacements[node.name] || node;
+ }
+ }
+
+ return node;
+}
+
+function buildGroupMatchGraph(combinator, terms, atLeastOneTermMatched) {
+ switch (combinator) {
+ case ' ': {
+ // Juxtaposing components means that all of them must occur, in the given order.
+ //
+ // a b c
+ // =
+ // match a
+ // then match b
+ // then match c
+ // then MATCH
+ // else MISMATCH
+ // else MISMATCH
+ // else MISMATCH
+ let result = MATCH;
+
+ for (let i = terms.length - 1; i >= 0; i--) {
+ const term = terms[i];
+
+ result = createCondition(
+ term,
+ result,
+ MISMATCH
+ );
+ };
+
+ return result;
+ }
+
+ case '|': {
+ // A bar (|) separates two or more alternatives: exactly one of them must occur.
+ //
+ // a | b | c
+ // =
+ // match a
+ // then MATCH
+ // else match b
+ // then MATCH
+ // else match c
+ // then MATCH
+ // else MISMATCH
+
+ let result = MISMATCH;
+ let map = null;
+
+ for (let i = terms.length - 1; i >= 0; i--) {
+ let term = terms[i];
+
+ // reduce sequence of keywords into a Enum
+ if (isEnumCapatible(term)) {
+ if (map === null && i > 0 && isEnumCapatible(terms[i - 1])) {
+ map = Object.create(null);
+ result = createCondition(
+ {
+ type: 'Enum',
+ map
+ },
+ MATCH,
+ result
+ );
+ }
+
+ if (map !== null) {
+ const key = (isFunctionType(term.name) ? term.name.slice(0, -1) : term.name).toLowerCase();
+ if (key in map === false) {
+ map[key] = term;
+ continue;
+ }
+ }
+ }
+
+ map = null;
+
+ // create a new conditonal node
+ result = createCondition(
+ term,
+ MATCH,
+ result
+ );
+ };
+
+ return result;
+ }
+
+ case '&&': {
+ // A double ampersand (&&) separates two or more components,
+ // all of which must occur, in any order.
+
+ // Use MatchOnce for groups with a large number of terms,
+ // since &&-groups produces at least N!-node trees
+ if (terms.length > 5) {
+ return {
+ type: 'MatchOnce',
+ terms,
+ all: true
+ };
+ }
+
+ // Use a combination tree for groups with small number of terms
+ //
+ // a && b && c
+ // =
+ // match a
+ // then [b && c]
+ // else match b
+ // then [a && c]
+ // else match c
+ // then [a && b]
+ // else MISMATCH
+ //
+ // a && b
+ // =
+ // match a
+ // then match b
+ // then MATCH
+ // else MISMATCH
+ // else match b
+ // then match a
+ // then MATCH
+ // else MISMATCH
+ // else MISMATCH
+ let result = MISMATCH;
+
+ for (let i = terms.length - 1; i >= 0; i--) {
+ const term = terms[i];
+ let thenClause;
+
+ if (terms.length > 1) {
+ thenClause = buildGroupMatchGraph(
+ combinator,
+ terms.filter(function(newGroupTerm) {
+ return newGroupTerm !== term;
+ }),
+ false
+ );
+ } else {
+ thenClause = MATCH;
+ }
+
+ result = createCondition(
+ term,
+ thenClause,
+ result
+ );
+ };
+
+ return result;
+ }
+
+ case '||': {
+ // A double bar (||) separates two or more options:
+ // one or more of them must occur, in any order.
+
+ // Use MatchOnce for groups with a large number of terms,
+ // since ||-groups produces at least N!-node trees
+ if (terms.length > 5) {
+ return {
+ type: 'MatchOnce',
+ terms,
+ all: false
+ };
+ }
+
+ // Use a combination tree for groups with small number of terms
+ //
+ // a || b || c
+ // =
+ // match a
+ // then [b || c]
+ // else match b
+ // then [a || c]
+ // else match c
+ // then [a || b]
+ // else MISMATCH
+ //
+ // a || b
+ // =
+ // match a
+ // then match b
+ // then MATCH
+ // else MATCH
+ // else match b
+ // then match a
+ // then MATCH
+ // else MATCH
+ // else MISMATCH
+ let result = atLeastOneTermMatched ? MATCH : MISMATCH;
+
+ for (let i = terms.length - 1; i >= 0; i--) {
+ const term = terms[i];
+ let thenClause;
+
+ if (terms.length > 1) {
+ thenClause = buildGroupMatchGraph(
+ combinator,
+ terms.filter(function(newGroupTerm) {
+ return newGroupTerm !== term;
+ }),
+ true
+ );
+ } else {
+ thenClause = MATCH;
+ }
+
+ result = createCondition(
+ term,
+ thenClause,
+ result
+ );
+ };
+
+ return result;
+ }
+ }
+}
+
+function buildMultiplierMatchGraph(node) {
+ let result = MATCH;
+ let matchTerm = buildMatchGraphInternal(node.term);
+
+ if (node.max === 0) {
+ // disable repeating of empty match to prevent infinite loop
+ matchTerm = createCondition(
+ matchTerm,
+ DISALLOW_EMPTY,
+ MISMATCH
+ );
+
+ // an occurrence count is not limited, make a cycle;
+ // to collect more terms on each following matching mismatch
+ result = createCondition(
+ matchTerm,
+ null, // will be a loop
+ MISMATCH
+ );
+
+ result.then = createCondition(
+ MATCH,
+ MATCH,
+ result // make a loop
+ );
+
+ if (node.comma) {
+ result.then.else = createCondition(
+ { type: 'Comma', syntax: node },
+ result,
+ MISMATCH
+ );
+ }
+ } else {
+ // create a match node chain for [min .. max] interval with optional matches
+ for (let i = node.min || 1; i <= node.max; i++) {
+ if (node.comma && result !== MATCH) {
+ result = createCondition(
+ { type: 'Comma', syntax: node },
+ result,
+ MISMATCH
+ );
+ }
+
+ result = createCondition(
+ matchTerm,
+ createCondition(
+ MATCH,
+ MATCH,
+ result
+ ),
+ MISMATCH
+ );
+ }
+ }
+
+ if (node.min === 0) {
+ // allow zero match
+ result = createCondition(
+ MATCH,
+ MATCH,
+ result
+ );
+ } else {
+ // create a match node chain to collect [0 ... min - 1] required matches
+ for (let i = 0; i < node.min - 1; i++) {
+ if (node.comma && result !== MATCH) {
+ result = createCondition(
+ { type: 'Comma', syntax: node },
+ result,
+ MISMATCH
+ );
+ }
+
+ result = createCondition(
+ matchTerm,
+ result,
+ MISMATCH
+ );
+ }
+ }
+
+ return result;
+}
+
+function buildMatchGraphInternal(node) {
+ if (typeof node === 'function') {
+ return {
+ type: 'Generic',
+ fn: node
+ };
+ }
+
+ switch (node.type) {
+ case 'Group': {
+ let result = buildGroupMatchGraph(
+ node.combinator,
+ node.terms.map(buildMatchGraphInternal),
+ false
+ );
+
+ if (node.disallowEmpty) {
+ result = createCondition(
+ result,
+ DISALLOW_EMPTY,
+ MISMATCH
+ );
+ }
+
+ return result;
+ }
+
+ case 'Multiplier':
+ return buildMultiplierMatchGraph(node);
+
+ // https://drafts.csswg.org/css-values-5/#boolean
+ case 'Boolean': {
+ const term = buildMatchGraphInternal(node.term);
+ // <boolean-expr[ <test> ]> = not <boolean-expr-group> | <boolean-expr-group> [ [ and <boolean-expr-group> ]* | [ or <boolean-expr-group> ]* ]
+ const matchNode = buildMatchGraphInternal(groupNode([
+ groupNode([
+ { type: 'Keyword', name: 'not' },
+ { type: 'Type', name: '!boolean-group' }
+ ]),
+ groupNode([
+ { type: 'Type', name: '!boolean-group' },
+ groupNode([
+ { type: 'Multiplier', comma: false, min: 0, max: 0, term: groupNode([
+ { type: 'Keyword', name: 'and' },
+ { type: 'Type', name: '!boolean-group' }
+ ]) },
+ { type: 'Multiplier', comma: false, min: 0, max: 0, term: groupNode([
+ { type: 'Keyword', name: 'or' },
+ { type: 'Type', name: '!boolean-group' }
+ ]) }
+ ], '|')
+ ])
+ ], '|'));
+ // <boolean-expr-group> = <test> | ( <boolean-expr[ <test> ]> ) | <general-enclosed>
+ const booleanGroup = buildMatchGraphInternal(
+ groupNode([
+ { type: 'Type', name: '!term' },
+ groupNode([
+ { type: 'Token', value: '(' },
+ { type: 'Type', name: '!self' },
+ { type: 'Token', value: ')' }
+ ]),
+ { type: 'Type', name: 'general-enclosed' }
+ ], '|')
+ );
+
+ replaceTypeInGraph(booleanGroup, { '!term': term, '!self': matchNode });
+ replaceTypeInGraph(matchNode, { '!boolean-group': booleanGroup });
+
+ return matchNode;
+ }
+
+ case 'Type':
+ case 'Property':
+ return {
+ type: node.type,
+ name: node.name,
+ syntax: node
+ };
+
+ case 'Keyword':
+ return {
+ type: node.type,
+ name: node.name.toLowerCase(),
+ syntax: node
+ };
+
+ case 'AtKeyword':
+ return {
+ type: node.type,
+ name: '@' + node.name.toLowerCase(),
+ syntax: node
+ };
+
+ case 'Function':
+ return {
+ type: node.type,
+ name: node.name.toLowerCase() + '(',
+ syntax: node
+ };
+
+ case 'String':
+ // convert a one char length String to a Token
+ if (node.value.length === 3) {
+ return {
+ type: 'Token',
+ value: node.value.charAt(1),
+ syntax: node
+ };
+ }
+
+ // otherwise use it as is
+ return {
+ type: node.type,
+ value: node.value.substr(1, node.value.length - 2).replace(/\\'/g, '\''),
+ syntax: node
+ };
+
+ case 'Token':
+ return {
+ type: node.type,
+ value: node.value,
+ syntax: node
+ };
+
+ case 'Comma':
+ return {
+ type: node.type,
+ syntax: node
+ };
+
+ default:
+ throw new Error('Unknown node type:', node.type);
+ }
+}
+
+export function buildMatchGraph(syntaxTree, ref) {
+ if (typeof syntaxTree === 'string') {
+ syntaxTree = parse(syntaxTree);
+ }
+
+ return {
+ type: 'MatchGraph',
+ match: buildMatchGraphInternal(syntaxTree),
+ syntax: ref || null,
+ source: syntaxTree
+ };
+}
diff --git a/vanilla/node_modules/css-tree/lib/lexer/match.js b/vanilla/node_modules/css-tree/lib/lexer/match.js
new file mode 100644
index 0000000..5ac9c48
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/lexer/match.js
@@ -0,0 +1,630 @@
+import { MATCH, MISMATCH, DISALLOW_EMPTY } from './match-graph.js';
+import * as TYPE from '../tokenizer/types.js';
+
+const { hasOwnProperty } = Object.prototype;
+const STUB = 0;
+const TOKEN = 1;
+const OPEN_SYNTAX = 2;
+const CLOSE_SYNTAX = 3;
+
+const EXIT_REASON_MATCH = 'Match';
+const EXIT_REASON_MISMATCH = 'Mismatch';
+const EXIT_REASON_ITERATION_LIMIT = 'Maximum iteration number exceeded (please fill an issue on https://github.com/csstree/csstree/issues)';
+
+const ITERATION_LIMIT = 15000;
+export let totalIterationCount = 0;
+
+function reverseList(list) {
+ let prev = null;
+ let next = null;
+ let item = list;
+
+ while (item !== null) {
+ next = item.prev;
+ item.prev = prev;
+ prev = item;
+ item = next;
+ }
+
+ return prev;
+}
+
+function areStringsEqualCaseInsensitive(testStr, referenceStr) {
+ if (testStr.length !== referenceStr.length) {
+ return false;
+ }
+
+ for (let i = 0; i < testStr.length; i++) {
+ const referenceCode = referenceStr.charCodeAt(i);
+ let testCode = testStr.charCodeAt(i);
+
+ // testCode.toLowerCase() for U+0041 LATIN CAPITAL LETTER A (A) .. U+005A LATIN CAPITAL LETTER Z (Z).
+ if (testCode >= 0x0041 && testCode <= 0x005A) {
+ testCode = testCode | 32;
+ }
+
+ if (testCode !== referenceCode) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+function isContextEdgeDelim(token) {
+ if (token.type !== TYPE.Delim) {
+ return false;
+ }
+
+ // Fix matching for unicode-range: U+30??, U+FF00-FF9F
+ // Probably we need to check out previous match instead
+ return token.value !== '?';
+}
+
+function isCommaContextStart(token) {
+ if (token === null) {
+ return true;
+ }
+
+ return (
+ token.type === TYPE.Comma ||
+ token.type === TYPE.Function ||
+ token.type === TYPE.LeftParenthesis ||
+ token.type === TYPE.LeftSquareBracket ||
+ token.type === TYPE.LeftCurlyBracket ||
+ isContextEdgeDelim(token)
+ );
+}
+
+function isCommaContextEnd(token) {
+ if (token === null) {
+ return true;
+ }
+
+ return (
+ token.type === TYPE.RightParenthesis ||
+ token.type === TYPE.RightSquareBracket ||
+ token.type === TYPE.RightCurlyBracket ||
+ (token.type === TYPE.Delim && token.value === '/')
+ );
+}
+
+function internalMatch(tokens, state, syntaxes) {
+ function moveToNextToken() {
+ do {
+ tokenIndex++;
+ token = tokenIndex < tokens.length ? tokens[tokenIndex] : null;
+ } while (token !== null && (token.type === TYPE.WhiteSpace || token.type === TYPE.Comment));
+ }
+
+ function getNextToken(offset) {
+ const nextIndex = tokenIndex + offset;
+
+ return nextIndex < tokens.length ? tokens[nextIndex] : null;
+ }
+
+ function stateSnapshotFromSyntax(nextState, prev) {
+ return {
+ nextState,
+ matchStack,
+ syntaxStack,
+ thenStack,
+ tokenIndex,
+ prev
+ };
+ }
+
+ function pushThenStack(nextState) {
+ thenStack = {
+ nextState,
+ matchStack,
+ syntaxStack,
+ prev: thenStack
+ };
+ }
+
+ function pushElseStack(nextState) {
+ elseStack = stateSnapshotFromSyntax(nextState, elseStack);
+ }
+
+ function addTokenToMatch() {
+ matchStack = {
+ type: TOKEN,
+ syntax: state.syntax,
+ token,
+ prev: matchStack
+ };
+
+ moveToNextToken();
+ syntaxStash = null;
+
+ if (tokenIndex > longestMatch) {
+ longestMatch = tokenIndex;
+ }
+ }
+
+ function openSyntax() {
+ syntaxStack = {
+ syntax: state.syntax,
+ opts: state.syntax.opts || (syntaxStack !== null && syntaxStack.opts) || null,
+ prev: syntaxStack
+ };
+
+ matchStack = {
+ type: OPEN_SYNTAX,
+ syntax: state.syntax,
+ token: matchStack.token,
+ prev: matchStack
+ };
+ }
+
+ function closeSyntax() {
+ if (matchStack.type === OPEN_SYNTAX) {
+ matchStack = matchStack.prev;
+ } else {
+ matchStack = {
+ type: CLOSE_SYNTAX,
+ syntax: syntaxStack.syntax,
+ token: matchStack.token,
+ prev: matchStack
+ };
+ }
+
+ syntaxStack = syntaxStack.prev;
+ }
+
+ let syntaxStack = null;
+ let thenStack = null;
+ let elseStack = null;
+
+ // null – stashing allowed, nothing stashed
+ // false – stashing disabled, nothing stashed
+ // anithing else – fail stashable syntaxes, some syntax stashed
+ let syntaxStash = null;
+
+ let iterationCount = 0; // count iterations and prevent infinite loop
+ let exitReason = null;
+
+ let token = null;
+ let tokenIndex = -1;
+ let longestMatch = 0;
+ let matchStack = {
+ type: STUB,
+ syntax: null,
+ token: null,
+ prev: null
+ };
+
+ moveToNextToken();
+
+ while (exitReason === null && ++iterationCount < ITERATION_LIMIT) {
+ // function mapList(list, fn) {
+ // const result = [];
+ // while (list) {
+ // result.unshift(fn(list));
+ // list = list.prev;
+ // }
+ // return result;
+ // }
+ // console.log('--\n',
+ // '#' + iterationCount,
+ // require('util').inspect({
+ // match: mapList(matchStack, x => x.type === TOKEN ? x.token && x.token.value : x.syntax ? ({ [OPEN_SYNTAX]: '<', [CLOSE_SYNTAX]: '</' }[x.type] || x.type) + '!' + x.syntax.name : null),
+ // token: token && token.value,
+ // tokenIndex,
+ // syntax: syntax.type + (syntax.id ? ' #' + syntax.id : '')
+ // }, { depth: null })
+ // );
+ switch (state.type) {
+ case 'Match':
+ if (thenStack === null) {
+ // turn to MISMATCH when some tokens left unmatched
+ if (token !== null) {
+ // doesn't mismatch if just one token left and it's an IE hack
+ if (tokenIndex !== tokens.length - 1 || (token.value !== '\\0' && token.value !== '\\9')) {
+ state = MISMATCH;
+ break;
+ }
+ }
+
+ // break the main loop, return a result - MATCH
+ exitReason = EXIT_REASON_MATCH;
+ break;
+ }
+
+ // go to next syntax (`then` branch)
+ state = thenStack.nextState;
+
+ // check match is not empty
+ if (state === DISALLOW_EMPTY) {
+ if (thenStack.matchStack === matchStack) {
+ state = MISMATCH;
+ break;
+ } else {
+ state = MATCH;
+ }
+ }
+
+ // close syntax if needed
+ while (thenStack.syntaxStack !== syntaxStack) {
+ closeSyntax();
+ }
+
+ // pop stack
+ thenStack = thenStack.prev;
+ break;
+
+ case 'Mismatch':
+ // when some syntax is stashed
+ if (syntaxStash !== null && syntaxStash !== false) {
+ // there is no else branches or a branch reduce match stack
+ if (elseStack === null || tokenIndex > elseStack.tokenIndex) {
+ // restore state from the stash
+ elseStack = syntaxStash;
+ syntaxStash = false; // disable stashing
+ }
+ } else if (elseStack === null) {
+ // no else branches -> break the main loop
+ // return a result - MISMATCH
+ exitReason = EXIT_REASON_MISMATCH;
+ break;
+ }
+
+ // go to next syntax (`else` branch)
+ state = elseStack.nextState;
+
+ // restore all the rest stack states
+ thenStack = elseStack.thenStack;
+ syntaxStack = elseStack.syntaxStack;
+ matchStack = elseStack.matchStack;
+ tokenIndex = elseStack.tokenIndex;
+ token = tokenIndex < tokens.length ? tokens[tokenIndex] : null;
+
+ // pop stack
+ elseStack = elseStack.prev;
+ break;
+
+ case 'MatchGraph':
+ state = state.match;
+ break;
+
+ case 'If':
+ // IMPORTANT: else stack push must go first,
+ // since it stores the state of thenStack before changes
+ if (state.else !== MISMATCH) {
+ pushElseStack(state.else);
+ }
+
+ if (state.then !== MATCH) {
+ pushThenStack(state.then);
+ }
+
+ state = state.match;
+ break;
+
+ case 'MatchOnce':
+ state = {
+ type: 'MatchOnceBuffer',
+ syntax: state,
+ index: 0,
+ mask: 0
+ };
+ break;
+
+ case 'MatchOnceBuffer': {
+ const terms = state.syntax.terms;
+
+ if (state.index === terms.length) {
+ // no matches at all or it's required all terms to be matched
+ if (state.mask === 0 || state.syntax.all) {
+ state = MISMATCH;
+ break;
+ }
+
+ // a partial match is ok
+ state = MATCH;
+ break;
+ }
+
+ // all terms are matched
+ if (state.mask === (1 << terms.length) - 1) {
+ state = MATCH;
+ break;
+ }
+
+ for (; state.index < terms.length; state.index++) {
+ const matchFlag = 1 << state.index;
+
+ if ((state.mask & matchFlag) === 0) {
+ // IMPORTANT: else stack push must go first,
+ // since it stores the state of thenStack before changes
+ pushElseStack(state);
+ pushThenStack({
+ type: 'AddMatchOnce',
+ syntax: state.syntax,
+ mask: state.mask | matchFlag
+ });
+
+ // match
+ state = terms[state.index++];
+ break;
+ }
+ }
+ break;
+ }
+
+ case 'AddMatchOnce':
+ state = {
+ type: 'MatchOnceBuffer',
+ syntax: state.syntax,
+ index: 0,
+ mask: state.mask
+ };
+ break;
+
+ case 'Enum':
+ if (token !== null) {
+ let name = token.value.toLowerCase();
+
+ // drop \0 and \9 hack from keyword name
+ if (name.indexOf('\\') !== -1) {
+ name = name.replace(/\\[09].*$/, '');
+ }
+
+ if (hasOwnProperty.call(state.map, name)) {
+ state = state.map[name];
+ break;
+ }
+ }
+
+ state = MISMATCH;
+ break;
+
+ case 'Generic': {
+ const opts = syntaxStack !== null ? syntaxStack.opts : null;
+ const lastTokenIndex = tokenIndex + Math.floor(state.fn(token, getNextToken, opts));
+
+ if (!isNaN(lastTokenIndex) && lastTokenIndex > tokenIndex) {
+ while (tokenIndex < lastTokenIndex) {
+ addTokenToMatch();
+ }
+
+ state = MATCH;
+ } else {
+ state = MISMATCH;
+ }
+
+ break;
+ }
+
+ case 'Type':
+ case 'Property': {
+ const syntaxDict = state.type === 'Type' ? 'types' : 'properties';
+ const dictSyntax = hasOwnProperty.call(syntaxes, syntaxDict) ? syntaxes[syntaxDict][state.name] : null;
+
+ if (!dictSyntax || !dictSyntax.match) {
+ throw new Error(
+ 'Bad syntax reference: ' +
+ (state.type === 'Type'
+ ? '<' + state.name + '>'
+ : '<\'' + state.name + '\'>')
+ );
+ }
+
+ // stash a syntax for types with low priority
+ if (syntaxStash !== false && token !== null && state.type === 'Type') {
+ const lowPriorityMatching =
+ // https://drafts.csswg.org/css-values-4/#custom-idents
+ // When parsing positionally-ambiguous keywords in a property value, a <custom-ident> production
+ // can only claim the keyword if no other unfulfilled production can claim it.
+ (state.name === 'custom-ident' && token.type === TYPE.Ident) ||
+
+ // https://drafts.csswg.org/css-values-4/#lengths
+ // ... if a `0` could be parsed as either a <number> or a <length> in a property (such as line-height),
+ // it must parse as a <number>
+ (state.name === 'length' && token.value === '0');
+
+ if (lowPriorityMatching) {
+ if (syntaxStash === null) {
+ syntaxStash = stateSnapshotFromSyntax(state, elseStack);
+ }
+
+ state = MISMATCH;
+ break;
+ }
+ }
+
+ openSyntax();
+ state = dictSyntax.matchRef || dictSyntax.match;
+ break;
+ }
+
+ case 'Keyword': {
+ const name = state.name;
+
+ if (token !== null) {
+ let keywordName = token.value;
+
+ // drop \0 and \9 hack from keyword name
+ if (keywordName.indexOf('\\') !== -1) {
+ keywordName = keywordName.replace(/\\[09].*$/, '');
+ }
+
+ if (areStringsEqualCaseInsensitive(keywordName, name)) {
+ addTokenToMatch();
+ state = MATCH;
+ break;
+ }
+ }
+
+ state = MISMATCH;
+ break;
+ }
+
+ case 'AtKeyword':
+ case 'Function':
+ if (token !== null && areStringsEqualCaseInsensitive(token.value, state.name)) {
+ addTokenToMatch();
+ state = MATCH;
+ break;
+ }
+
+ state = MISMATCH;
+ break;
+
+ case 'Token':
+ if (token !== null && token.value === state.value) {
+ addTokenToMatch();
+ state = MATCH;
+ break;
+ }
+
+ state = MISMATCH;
+ break;
+
+ case 'Comma':
+ if (token !== null && token.type === TYPE.Comma) {
+ if (isCommaContextStart(matchStack.token)) {
+ state = MISMATCH;
+ } else {
+ addTokenToMatch();
+ state = isCommaContextEnd(token) ? MISMATCH : MATCH;
+ }
+ } else {
+ state = isCommaContextStart(matchStack.token) || isCommaContextEnd(token) ? MATCH : MISMATCH;
+ }
+
+ break;
+
+ case 'String':
+ let string = '';
+ let lastTokenIndex = tokenIndex;
+
+ for (; lastTokenIndex < tokens.length && string.length < state.value.length; lastTokenIndex++) {
+ string += tokens[lastTokenIndex].value;
+ }
+
+ if (areStringsEqualCaseInsensitive(string, state.value)) {
+ while (tokenIndex < lastTokenIndex) {
+ addTokenToMatch();
+ }
+
+ state = MATCH;
+ } else {
+ state = MISMATCH;
+ }
+
+ break;
+
+ default:
+ throw new Error('Unknown node type: ' + state.type);
+ }
+ }
+
+ totalIterationCount += iterationCount;
+
+ switch (exitReason) {
+ case null:
+ console.warn('[csstree-match] BREAK after ' + ITERATION_LIMIT + ' iterations');
+ exitReason = EXIT_REASON_ITERATION_LIMIT;
+ matchStack = null;
+ break;
+
+ case EXIT_REASON_MATCH:
+ while (syntaxStack !== null) {
+ closeSyntax();
+ }
+ break;
+
+ default:
+ matchStack = null;
+ }
+
+ return {
+ tokens,
+ reason: exitReason,
+ iterations: iterationCount,
+ match: matchStack,
+ longestMatch
+ };
+}
+
+export function matchAsList(tokens, matchGraph, syntaxes) {
+ const matchResult = internalMatch(tokens, matchGraph, syntaxes || {});
+
+ if (matchResult.match !== null) {
+ let item = reverseList(matchResult.match).prev;
+
+ matchResult.match = [];
+
+ while (item !== null) {
+ switch (item.type) {
+ case OPEN_SYNTAX:
+ case CLOSE_SYNTAX:
+ matchResult.match.push({
+ type: item.type,
+ syntax: item.syntax
+ });
+ break;
+
+ default:
+ matchResult.match.push({
+ token: item.token.value,
+ node: item.token.node
+ });
+ break;
+ }
+
+ item = item.prev;
+ }
+ }
+
+ return matchResult;
+}
+
+export function matchAsTree(tokens, matchGraph, syntaxes) {
+ const matchResult = internalMatch(tokens, matchGraph, syntaxes || {});
+
+ if (matchResult.match === null) {
+ return matchResult;
+ }
+
+ let item = matchResult.match;
+ let host = matchResult.match = {
+ syntax: matchGraph.syntax || null,
+ match: []
+ };
+ const hostStack = [host];
+
+ // revert a list and start with 2nd item since 1st is a stub item
+ item = reverseList(item).prev;
+
+ // build a tree
+ while (item !== null) {
+ switch (item.type) {
+ case OPEN_SYNTAX:
+ host.match.push(host = {
+ syntax: item.syntax,
+ match: []
+ });
+ hostStack.push(host);
+ break;
+
+ case CLOSE_SYNTAX:
+ hostStack.pop();
+ host = hostStack[hostStack.length - 1];
+ break;
+
+ default:
+ host.match.push({
+ syntax: item.syntax || null,
+ token: item.token.value,
+ node: item.token.node
+ });
+ }
+
+ item = item.prev;
+ }
+
+ return matchResult;
+}
diff --git a/vanilla/node_modules/css-tree/lib/lexer/prepare-tokens.js b/vanilla/node_modules/css-tree/lib/lexer/prepare-tokens.js
new file mode 100644
index 0000000..4243fa8
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/lexer/prepare-tokens.js
@@ -0,0 +1,50 @@
+import { tokenize } from '../tokenizer/index.js';
+
+const astToTokens = {
+ decorator(handlers) {
+ const tokens = [];
+ let curNode = null;
+
+ return {
+ ...handlers,
+ node(node) {
+ const tmp = curNode;
+ curNode = node;
+ handlers.node.call(this, node);
+ curNode = tmp;
+ },
+ emit(value, type, auto) {
+ tokens.push({
+ type,
+ value,
+ node: auto ? null : curNode
+ });
+ },
+ result() {
+ return tokens;
+ }
+ };
+ }
+};
+
+function stringToTokens(str) {
+ const tokens = [];
+
+ tokenize(str, (type, start, end) =>
+ tokens.push({
+ type,
+ value: str.slice(start, end),
+ node: null
+ })
+ );
+
+ return tokens;
+}
+
+export default function(value, syntax) {
+ if (typeof value === 'string') {
+ return stringToTokens(value);
+ }
+
+ return syntax.generate(value, astToTokens);
+};
diff --git a/vanilla/node_modules/css-tree/lib/lexer/search.js b/vanilla/node_modules/css-tree/lib/lexer/search.js
new file mode 100644
index 0000000..ee68e2d
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/lexer/search.js
@@ -0,0 +1,61 @@
+import { List } from '../utils/List.js';
+
+function getFirstMatchNode(matchNode) {
+ if ('node' in matchNode) {
+ return matchNode.node;
+ }
+
+ return getFirstMatchNode(matchNode.match[0]);
+}
+
+function getLastMatchNode(matchNode) {
+ if ('node' in matchNode) {
+ return matchNode.node;
+ }
+
+ return getLastMatchNode(matchNode.match[matchNode.match.length - 1]);
+}
+
+export function matchFragments(lexer, ast, match, type, name) {
+ function findFragments(matchNode) {
+ if (matchNode.syntax !== null &&
+ matchNode.syntax.type === type &&
+ matchNode.syntax.name === name) {
+ const start = getFirstMatchNode(matchNode);
+ const end = getLastMatchNode(matchNode);
+
+ lexer.syntax.walk(ast, function(node, item, list) {
+ if (node === start) {
+ const nodes = new List();
+
+ do {
+ nodes.appendData(item.data);
+
+ if (item.data === end) {
+ break;
+ }
+
+ item = item.next;
+ } while (item !== null);
+
+ fragments.push({
+ parent: list,
+ nodes
+ });
+ }
+ });
+ }
+
+ if (Array.isArray(matchNode.match)) {
+ matchNode.match.forEach(findFragments);
+ }
+ }
+
+ const fragments = [];
+
+ if (match.matched !== null) {
+ findFragments(match.matched);
+ }
+
+ return fragments;
+}
diff --git a/vanilla/node_modules/css-tree/lib/lexer/structure.js b/vanilla/node_modules/css-tree/lib/lexer/structure.js
new file mode 100644
index 0000000..3bf4ced
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/lexer/structure.js
@@ -0,0 +1,169 @@
+import { List } from '../utils/List.js';
+
+const { hasOwnProperty } = Object.prototype;
+
+function isValidNumber(value) {
+ // Number.isInteger(value) && value >= 0
+ return (
+ typeof value === 'number' &&
+ isFinite(value) &&
+ Math.floor(value) === value &&
+ value >= 0
+ );
+}
+
+function isValidLocation(loc) {
+ return (
+ Boolean(loc) &&
+ isValidNumber(loc.offset) &&
+ isValidNumber(loc.line) &&
+ isValidNumber(loc.column)
+ );
+}
+
+function createNodeStructureChecker(type, fields) {
+ return function checkNode(node, warn) {
+ if (!node || node.constructor !== Object) {
+ return warn(node, 'Type of node should be an Object');
+ }
+
+ for (let key in node) {
+ let valid = true;
+
+ if (hasOwnProperty.call(node, key) === false) {
+ continue;
+ }
+
+ if (key === 'type') {
+ if (node.type !== type) {
+ warn(node, 'Wrong node type `' + node.type + '`, expected `' + type + '`');
+ }
+ } else if (key === 'loc') {
+ if (node.loc === null) {
+ continue;
+ } else if (node.loc && node.loc.constructor === Object) {
+ if (typeof node.loc.source !== 'string') {
+ key += '.source';
+ } else if (!isValidLocation(node.loc.start)) {
+ key += '.start';
+ } else if (!isValidLocation(node.loc.end)) {
+ key += '.end';
+ } else {
+ continue;
+ }
+ }
+
+ valid = false;
+ } else if (fields.hasOwnProperty(key)) {
+ valid = false;
+
+ for (let i = 0; !valid && i < fields[key].length; i++) {
+ const fieldType = fields[key][i];
+
+ switch (fieldType) {
+ case String:
+ valid = typeof node[key] === 'string';
+ break;
+
+ case Boolean:
+ valid = typeof node[key] === 'boolean';
+ break;
+
+ case null:
+ valid = node[key] === null;
+ break;
+
+ default:
+ if (typeof fieldType === 'string') {
+ valid = node[key] && node[key].type === fieldType;
+ } else if (Array.isArray(fieldType)) {
+ valid = node[key] instanceof List;
+ }
+ }
+ }
+ } else {
+ warn(node, 'Unknown field `' + key + '` for ' + type + ' node type');
+ }
+
+ if (!valid) {
+ warn(node, 'Bad value for `' + type + '.' + key + '`');
+ }
+ }
+
+ for (const key in fields) {
+ if (hasOwnProperty.call(fields, key) &&
+ hasOwnProperty.call(node, key) === false) {
+ warn(node, 'Field `' + type + '.' + key + '` is missed');
+ }
+ }
+ };
+}
+
+function genTypesList(fieldTypes, path) {
+ const docsTypes = [];
+
+ for (let i = 0; i < fieldTypes.length; i++) {
+ const fieldType = fieldTypes[i];
+ if (fieldType === String || fieldType === Boolean) {
+ docsTypes.push(fieldType.name.toLowerCase());
+ } else if (fieldType === null) {
+ docsTypes.push('null');
+ } else if (typeof fieldType === 'string') {
+ docsTypes.push(fieldType);
+ } else if (Array.isArray(fieldType)) {
+ docsTypes.push('List<' + (genTypesList(fieldType, path) || 'any') + '>'); // TODO: use type enum
+ } else {
+ throw new Error('Wrong value `' + fieldType + '` in `' + path + '` structure definition');
+ }
+ }
+
+ return docsTypes.join(' | ');
+}
+
+function processStructure(name, nodeType) {
+ const structure = nodeType.structure;
+ const fields = {
+ type: String,
+ loc: true
+ };
+ const docs = {
+ type: '"' + name + '"'
+ };
+
+ for (const key in structure) {
+ if (hasOwnProperty.call(structure, key) === false) {
+ continue;
+ }
+
+ const fieldTypes = fields[key] = Array.isArray(structure[key])
+ ? structure[key].slice()
+ : [structure[key]];
+
+ docs[key] = genTypesList(fieldTypes, name + '.' + key);
+ }
+
+ return {
+ docs,
+ check: createNodeStructureChecker(name, fields)
+ };
+}
+
+export function getStructureFromConfig(config) {
+ const structure = {};
+
+ if (config.node) {
+ for (const name in config.node) {
+ if (hasOwnProperty.call(config.node, name)) {
+ const nodeType = config.node[name];
+
+ if (nodeType.structure) {
+ structure[name] = processStructure(name, nodeType);
+ } else {
+ throw new Error('Missed `structure` field in `' + name + '` node type definition');
+ }
+ }
+ }
+ }
+
+ return structure;
+};
diff --git a/vanilla/node_modules/css-tree/lib/lexer/trace.js b/vanilla/node_modules/css-tree/lib/lexer/trace.js
new file mode 100644
index 0000000..959813c
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/lexer/trace.js
@@ -0,0 +1,66 @@
+export function getTrace(node) {
+ function shouldPutToTrace(syntax) {
+ if (syntax === null) {
+ return false;
+ }
+
+ return (
+ syntax.type === 'Type' ||
+ syntax.type === 'Property' ||
+ syntax.type === 'Keyword'
+ );
+ }
+
+ function hasMatch(matchNode) {
+ if (Array.isArray(matchNode.match)) {
+ // use for-loop for better perfomance
+ for (let i = 0; i < matchNode.match.length; i++) {
+ if (hasMatch(matchNode.match[i])) {
+ if (shouldPutToTrace(matchNode.syntax)) {
+ result.unshift(matchNode.syntax);
+ }
+
+ return true;
+ }
+ }
+ } else if (matchNode.node === node) {
+ result = shouldPutToTrace(matchNode.syntax)
+ ? [matchNode.syntax]
+ : [];
+
+ return true;
+ }
+
+ return false;
+ }
+
+ let result = null;
+
+ if (this.matched !== null) {
+ hasMatch(this.matched);
+ }
+
+ return result;
+}
+
+export function isType(node, type) {
+ return testNode(this, node, match => match.type === 'Type' && match.name === type);
+}
+
+export function isProperty(node, property) {
+ return testNode(this, node, match => match.type === 'Property' && match.name === property);
+}
+
+export function isKeyword(node) {
+ return testNode(this, node, match => match.type === 'Keyword');
+}
+
+function testNode(match, node, fn) {
+ const trace = getTrace.call(match, node);
+
+ if (trace === null) {
+ return false;
+ }
+
+ return trace.some(fn);
+}
diff --git a/vanilla/node_modules/css-tree/lib/lexer/units.js b/vanilla/node_modules/css-tree/lib/lexer/units.js
new file mode 100644
index 0000000..88b1f69
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/lexer/units.js
@@ -0,0 +1,27 @@
+export const length = [
+ // absolute length units https://www.w3.org/TR/css-values-3/#lengths
+ 'cm', 'mm', 'q', 'in', 'pt', 'pc', 'px',
+ // font-relative length units https://drafts.csswg.org/css-values-4/#font-relative-lengths
+ 'em', 'rem',
+ 'ex', 'rex',
+ 'cap', 'rcap',
+ 'ch', 'rch',
+ 'ic', 'ric',
+ 'lh', 'rlh',
+ // viewport-percentage lengths https://drafts.csswg.org/css-values-4/#viewport-relative-lengths
+ 'vw', 'svw', 'lvw', 'dvw',
+ 'vh', 'svh', 'lvh', 'dvh',
+ 'vi', 'svi', 'lvi', 'dvi',
+ 'vb', 'svb', 'lvb', 'dvb',
+ 'vmin', 'svmin', 'lvmin', 'dvmin',
+ 'vmax', 'svmax', 'lvmax', 'dvmax',
+ // container relative lengths https://drafts.csswg.org/css-contain-3/#container-lengths
+ 'cqw', 'cqh', 'cqi', 'cqb', 'cqmin', 'cqmax'
+];
+export const angle = ['deg', 'grad', 'rad', 'turn']; // https://www.w3.org/TR/css-values-3/#angles
+export const time = ['s', 'ms']; // https://www.w3.org/TR/css-values-3/#time
+export const frequency = ['hz', 'khz']; // https://www.w3.org/TR/css-values-3/#frequency
+export const resolution = ['dpi', 'dpcm', 'dppx', 'x']; // https://www.w3.org/TR/css-values-3/#resolution
+export const flex = ['fr']; // https://drafts.csswg.org/css-grid/#fr-unit
+export const decibel = ['db']; // https://www.w3.org/TR/css3-speech/#mixing-props-voice-volume
+export const semitones = ['st']; // https://www.w3.org/TR/css3-speech/#voice-props-voice-pitch
diff --git a/vanilla/node_modules/css-tree/lib/parser/SyntaxError.js b/vanilla/node_modules/css-tree/lib/parser/SyntaxError.js
new file mode 100644
index 0000000..55e01c1
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/parser/SyntaxError.js
@@ -0,0 +1,70 @@
+import { createCustomError } from '../utils/create-custom-error.js';
+
+const MAX_LINE_LENGTH = 100;
+const OFFSET_CORRECTION = 60;
+const TAB_REPLACEMENT = ' ';
+
+function sourceFragment({ source, line, column, baseLine, baseColumn }, extraLines) {
+ function processLines(start, end) {
+ return lines
+ .slice(start, end)
+ .map((line, idx) =>
+ String(start + idx + 1).padStart(maxNumLength) + ' |' + line
+ ).join('\n');
+ }
+
+ const prelines = '\n'.repeat(Math.max(baseLine - 1, 0));
+ const precolumns = ' '.repeat(Math.max(baseColumn - 1, 0));
+ const lines = (prelines + precolumns + source).split(/\r\n?|\n|\f/);
+ const startLine = Math.max(1, line - extraLines) - 1;
+ const endLine = Math.min(line + extraLines, lines.length + 1);
+ const maxNumLength = Math.max(4, String(endLine).length) + 1;
+ let cutLeft = 0;
+
+ // column correction according to replaced tab before column
+ column += (TAB_REPLACEMENT.length - 1) * (lines[line - 1].substr(0, column - 1).match(/\t/g) || []).length;
+
+ if (column > MAX_LINE_LENGTH) {
+ cutLeft = column - OFFSET_CORRECTION + 3;
+ column = OFFSET_CORRECTION - 2;
+ }
+
+ for (let i = startLine; i <= endLine; i++) {
+ if (i >= 0 && i < lines.length) {
+ lines[i] = lines[i].replace(/\t/g, TAB_REPLACEMENT);
+ lines[i] =
+ (cutLeft > 0 && lines[i].length > cutLeft ? '\u2026' : '') +
+ lines[i].substr(cutLeft, MAX_LINE_LENGTH - 2) +
+ (lines[i].length > cutLeft + MAX_LINE_LENGTH - 1 ? '\u2026' : '');
+ }
+ }
+
+ return [
+ processLines(startLine, line),
+ new Array(column + maxNumLength + 2).join('-') + '^',
+ processLines(line, endLine)
+ ].filter(Boolean)
+ .join('\n')
+ .replace(/^(\s+\d+\s+\|\n)+/, '')
+ .replace(/\n(\s+\d+\s+\|)+$/, '');
+}
+
+export function SyntaxError(message, source, offset, line, column, baseLine = 1, baseColumn = 1) {
+ const error = Object.assign(createCustomError('SyntaxError', message), {
+ source,
+ offset,
+ line,
+ column,
+ sourceFragment(extraLines) {
+ return sourceFragment({ source, line, column, baseLine, baseColumn }, isNaN(extraLines) ? 0 : extraLines);
+ },
+ get formattedMessage() {
+ return (
+ `Parse error: ${message}\n` +
+ sourceFragment({ source, line, column, baseLine, baseColumn }, 2)
+ );
+ }
+ });
+
+ return error;
+}
diff --git a/vanilla/node_modules/css-tree/lib/parser/create.js b/vanilla/node_modules/css-tree/lib/parser/create.js
new file mode 100644
index 0000000..8c08d81
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/parser/create.js
@@ -0,0 +1,350 @@
+import { List } from '../utils/List.js';
+import { SyntaxError } from './SyntaxError.js';
+import {
+ tokenize,
+ OffsetToLocation,
+ TokenStream,
+ tokenNames,
+
+ consumeNumber,
+ findWhiteSpaceStart,
+ cmpChar,
+ cmpStr,
+
+ WhiteSpace,
+ Comment,
+ Ident,
+ Function as FunctionToken,
+ Url,
+ Hash,
+ Percentage,
+ Number as NumberToken
+} from '../tokenizer/index.js';
+import { readSequence } from './sequence.js';
+
+const NOOP = () => {};
+const EXCLAMATIONMARK = 0x0021; // U+0021 EXCLAMATION MARK (!)
+const NUMBERSIGN = 0x0023; // U+0023 NUMBER SIGN (#)
+const SEMICOLON = 0x003B; // U+003B SEMICOLON (;)
+const LEFTCURLYBRACKET = 0x007B; // U+007B LEFT CURLY BRACKET ({)
+const NULL = 0;
+
+function createParseContext(name) {
+ return function() {
+ return this[name]();
+ };
+}
+
+function fetchParseValues(dict) {
+ const result = Object.create(null);
+
+ for (const name of Object.keys(dict)) {
+ const item = dict[name];
+ const fn = item.parse || item;
+
+ if (fn) {
+ result[name] = fn;
+ }
+ }
+
+ return result;
+}
+
+function processConfig(config) {
+ const parseConfig = {
+ context: Object.create(null),
+ features: Object.assign(Object.create(null), config.features),
+ scope: Object.assign(Object.create(null), config.scope),
+ atrule: fetchParseValues(config.atrule),
+ pseudo: fetchParseValues(config.pseudo),
+ node: fetchParseValues(config.node)
+ };
+
+ for (const [name, context] of Object.entries(config.parseContext)) {
+ switch (typeof context) {
+ case 'function':
+ parseConfig.context[name] = context;
+ break;
+
+ case 'string':
+ parseConfig.context[name] = createParseContext(context);
+ break;
+ }
+ }
+
+ return {
+ config: parseConfig,
+ ...parseConfig,
+ ...parseConfig.node
+ };
+}
+
+export function createParser(config) {
+ let source = '';
+ let filename = '<unknown>';
+ let needPositions = false;
+ let onParseError = NOOP;
+ let onParseErrorThrow = false;
+
+ const locationMap = new OffsetToLocation();
+ const parser = Object.assign(new TokenStream(), processConfig(config || {}), {
+ parseAtrulePrelude: true,
+ parseRulePrelude: true,
+ parseValue: true,
+ parseCustomProperty: false,
+
+ readSequence,
+
+ consumeUntilBalanceEnd: () => 0,
+ consumeUntilLeftCurlyBracket(code) {
+ return code === LEFTCURLYBRACKET ? 1 : 0;
+ },
+ consumeUntilLeftCurlyBracketOrSemicolon(code) {
+ return code === LEFTCURLYBRACKET || code === SEMICOLON ? 1 : 0;
+ },
+ consumeUntilExclamationMarkOrSemicolon(code) {
+ return code === EXCLAMATIONMARK || code === SEMICOLON ? 1 : 0;
+ },
+ consumeUntilSemicolonIncluded(code) {
+ return code === SEMICOLON ? 2 : 0;
+ },
+
+ createList() {
+ return new List();
+ },
+ createSingleNodeList(node) {
+ return new List().appendData(node);
+ },
+ getFirstListNode(list) {
+ return list && list.first;
+ },
+ getLastListNode(list) {
+ return list && list.last;
+ },
+
+ parseWithFallback(consumer, fallback) {
+ const startIndex = this.tokenIndex;
+
+ try {
+ return consumer.call(this);
+ } catch (e) {
+ if (onParseErrorThrow) {
+ throw e;
+ }
+
+ this.skip(startIndex - this.tokenIndex);
+ const fallbackNode = fallback.call(this);
+
+ onParseErrorThrow = true;
+ onParseError(e, fallbackNode);
+ onParseErrorThrow = false;
+
+ return fallbackNode;
+ }
+ },
+
+ lookupNonWSType(offset) {
+ let type;
+
+ do {
+ type = this.lookupType(offset++);
+ if (type !== WhiteSpace && type !== Comment) {
+ return type;
+ }
+ } while (type !== NULL);
+
+ return NULL;
+ },
+
+ charCodeAt(offset) {
+ return offset >= 0 && offset < source.length ? source.charCodeAt(offset) : 0;
+ },
+ substring(offsetStart, offsetEnd) {
+ return source.substring(offsetStart, offsetEnd);
+ },
+ substrToCursor(start) {
+ return this.source.substring(start, this.tokenStart);
+ },
+
+ cmpChar(offset, charCode) {
+ return cmpChar(source, offset, charCode);
+ },
+ cmpStr(offsetStart, offsetEnd, str) {
+ return cmpStr(source, offsetStart, offsetEnd, str);
+ },
+
+ consume(tokenType) {
+ const start = this.tokenStart;
+
+ this.eat(tokenType);
+
+ return this.substrToCursor(start);
+ },
+ consumeFunctionName() {
+ const name = source.substring(this.tokenStart, this.tokenEnd - 1);
+
+ this.eat(FunctionToken);
+
+ return name;
+ },
+ consumeNumber(type) {
+ const number = source.substring(this.tokenStart, consumeNumber(source, this.tokenStart));
+
+ this.eat(type);
+
+ return number;
+ },
+
+ eat(tokenType) {
+ if (this.tokenType !== tokenType) {
+ const tokenName = tokenNames[tokenType].slice(0, -6).replace(/-/g, ' ').replace(/^./, m => m.toUpperCase());
+ let message = `${/[[\](){}]/.test(tokenName) ? `"${tokenName}"` : tokenName} is expected`;
+ let offset = this.tokenStart;
+
+ // tweak message and offset
+ switch (tokenType) {
+ case Ident:
+ // when identifier is expected but there is a function or url
+ if (this.tokenType === FunctionToken || this.tokenType === Url) {
+ offset = this.tokenEnd - 1;
+ message = 'Identifier is expected but function found';
+ } else {
+ message = 'Identifier is expected';
+ }
+ break;
+
+ case Hash:
+ if (this.isDelim(NUMBERSIGN)) {
+ this.next();
+ offset++;
+ message = 'Name is expected';
+ }
+ break;
+
+ case Percentage:
+ if (this.tokenType === NumberToken) {
+ offset = this.tokenEnd;
+ message = 'Percent sign is expected';
+ }
+ break;
+ }
+
+ this.error(message, offset);
+ }
+
+ this.next();
+ },
+ eatIdent(name) {
+ if (this.tokenType !== Ident || this.lookupValue(0, name) === false) {
+ this.error(`Identifier "${name}" is expected`);
+ }
+
+ this.next();
+ },
+ eatDelim(code) {
+ if (!this.isDelim(code)) {
+ this.error(`Delim "${String.fromCharCode(code)}" is expected`);
+ }
+
+ this.next();
+ },
+
+ getLocation(start, end) {
+ if (needPositions) {
+ return locationMap.getLocationRange(
+ start,
+ end,
+ filename
+ );
+ }
+
+ return null;
+ },
+ getLocationFromList(list) {
+ if (needPositions) {
+ const head = this.getFirstListNode(list);
+ const tail = this.getLastListNode(list);
+ return locationMap.getLocationRange(
+ head !== null ? head.loc.start.offset - locationMap.startOffset : this.tokenStart,
+ tail !== null ? tail.loc.end.offset - locationMap.startOffset : this.tokenStart,
+ filename
+ );
+ }
+
+ return null;
+ },
+
+ error(message, offset) {
+ const location = typeof offset !== 'undefined' && offset < source.length
+ ? locationMap.getLocation(offset)
+ : this.eof
+ ? locationMap.getLocation(findWhiteSpaceStart(source, source.length - 1))
+ : locationMap.getLocation(this.tokenStart);
+
+ throw new SyntaxError(
+ message || 'Unexpected input',
+ source,
+ location.offset,
+ location.line,
+ location.column,
+ locationMap.startLine,
+ locationMap.startColumn
+ );
+ }
+ });
+
+ const parse = function(source_, options) {
+ source = source_;
+ options = options || {};
+
+ parser.setSource(source, tokenize);
+ locationMap.setSource(
+ source,
+ options.offset,
+ options.line,
+ options.column
+ );
+
+ filename = options.filename || '<unknown>';
+ needPositions = Boolean(options.positions);
+ onParseError = typeof options.onParseError === 'function' ? options.onParseError : NOOP;
+ onParseErrorThrow = false;
+
+ parser.parseAtrulePrelude = 'parseAtrulePrelude' in options ? Boolean(options.parseAtrulePrelude) : true;
+ parser.parseRulePrelude = 'parseRulePrelude' in options ? Boolean(options.parseRulePrelude) : true;
+ parser.parseValue = 'parseValue' in options ? Boolean(options.parseValue) : true;
+ parser.parseCustomProperty = 'parseCustomProperty' in options ? Boolean(options.parseCustomProperty) : false;
+
+ const { context = 'default', onComment } = options;
+
+ if (context in parser.context === false) {
+ throw new Error('Unknown context `' + context + '`');
+ }
+
+ if (typeof onComment === 'function') {
+ parser.forEachToken((type, start, end) => {
+ if (type === Comment) {
+ const loc = parser.getLocation(start, end);
+ const value = cmpStr(source, end - 2, end, '*/')
+ ? source.slice(start + 2, end - 2)
+ : source.slice(start + 2, end);
+
+ onComment(value, loc);
+ }
+ });
+ }
+
+ const ast = parser.context[context].call(parser, options);
+
+ if (!parser.eof) {
+ parser.error();
+ }
+
+ return ast;
+ };
+
+ return Object.assign(parse, {
+ SyntaxError,
+ config: parser.config
+ });
+};
diff --git a/vanilla/node_modules/css-tree/lib/parser/index.js b/vanilla/node_modules/css-tree/lib/parser/index.js
new file mode 100644
index 0000000..7cee62e
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/parser/index.js
@@ -0,0 +1,4 @@
+import { createParser } from './create.js';
+import config from '../syntax/config/parser.js';
+
+export default createParser(config);
diff --git a/vanilla/node_modules/css-tree/lib/parser/parse-selector.js b/vanilla/node_modules/css-tree/lib/parser/parse-selector.js
new file mode 100644
index 0000000..05b539a
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/parser/parse-selector.js
@@ -0,0 +1,4 @@
+import { createParser } from './create.js';
+import config from '../syntax/config/parser-selector.js';
+
+export default createParser(config);
diff --git a/vanilla/node_modules/css-tree/lib/parser/sequence.js b/vanilla/node_modules/css-tree/lib/parser/sequence.js
new file mode 100644
index 0000000..47d1ccd
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/parser/sequence.js
@@ -0,0 +1,43 @@
+import { WhiteSpace, Comment } from '../tokenizer/index.js';
+
+export function readSequence(recognizer) {
+ const children = this.createList();
+ let space = false;
+ const context = {
+ recognizer
+ };
+
+ while (!this.eof) {
+ switch (this.tokenType) {
+ case Comment:
+ this.next();
+ continue;
+
+ case WhiteSpace:
+ space = true;
+ this.next();
+ continue;
+ }
+
+ let child = recognizer.getNode.call(this, context);
+
+ if (child === undefined) {
+ break;
+ }
+
+ if (space) {
+ if (recognizer.onWhiteSpace) {
+ recognizer.onWhiteSpace.call(this, child, children, context);
+ }
+ space = false;
+ }
+
+ children.push(child);
+ }
+
+ if (space && recognizer.onWhiteSpace) {
+ recognizer.onWhiteSpace.call(this, null, children, context);
+ }
+
+ return children;
+};
diff --git a/vanilla/node_modules/css-tree/lib/syntax/atrule/container.js b/vanilla/node_modules/css-tree/lib/syntax/atrule/container.js
new file mode 100644
index 0000000..aa550de
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/syntax/atrule/container.js
@@ -0,0 +1,28 @@
+import { Ident } from '../../tokenizer/index.js';
+
+// https://drafts.csswg.org/css-contain-3/#container-rule
+// The keywords `none`, `and`, `not`, and `or` are excluded from the <custom-ident> above.
+const nonContainerNameKeywords = new Set(['none', 'and', 'not', 'or']);
+
+export default {
+ parse: {
+ prelude() {
+ const children = this.createList();
+
+ if (this.tokenType === Ident) {
+ const name = this.substring(this.tokenStart, this.tokenEnd);
+
+ if (!nonContainerNameKeywords.has(name.toLowerCase())) {
+ children.push(this.Identifier());
+ }
+ }
+
+ children.push(this.Condition('container'));
+
+ return children;
+ },
+ block(nested = false) {
+ return this.Block(nested);
+ }
+ }
+};
diff --git a/vanilla/node_modules/css-tree/lib/syntax/atrule/font-face.js b/vanilla/node_modules/css-tree/lib/syntax/atrule/font-face.js
new file mode 100644
index 0000000..48a0570
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/syntax/atrule/font-face.js
@@ -0,0 +1,8 @@
+export default {
+ parse: {
+ prelude: null,
+ block() {
+ return this.Block(true);
+ }
+ }
+};
diff --git a/vanilla/node_modules/css-tree/lib/syntax/atrule/import.js b/vanilla/node_modules/css-tree/lib/syntax/atrule/import.js
new file mode 100644
index 0000000..4e5a637
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/syntax/atrule/import.js
@@ -0,0 +1,104 @@
+import {
+ String as StringToken,
+ Ident,
+ Url,
+ Function as FunctionToken,
+ LeftParenthesis,
+ RightParenthesis
+} from '../../tokenizer/index.js';
+
+function parseWithFallback(parse, fallback) {
+ return this.parseWithFallback(
+ () => {
+ try {
+ return parse.call(this);
+ } finally {
+ this.skipSC();
+ if (this.lookupNonWSType(0) !== RightParenthesis) {
+ this.error();
+ }
+ }
+ },
+ fallback || (() => this.Raw(null, true))
+ );
+}
+
+const parseFunctions = {
+ layer() {
+ this.skipSC();
+
+ const children = this.createList();
+ const node = parseWithFallback.call(this, this.Layer);
+
+ if (node.type !== 'Raw' || node.value !== '') {
+ children.push(node);
+ }
+
+ return children;
+ },
+ supports() {
+ this.skipSC();
+
+ const children = this.createList();
+ const node = parseWithFallback.call(
+ this,
+ this.Declaration,
+ () => parseWithFallback.call(this, () => this.Condition('supports'))
+ );
+
+ if (node.type !== 'Raw' || node.value !== '') {
+ children.push(node);
+ }
+
+ return children;
+ }
+};
+
+export default {
+ parse: {
+ prelude() {
+ const children = this.createList();
+
+ switch (this.tokenType) {
+ case StringToken:
+ children.push(this.String());
+ break;
+
+ case Url:
+ case FunctionToken:
+ children.push(this.Url());
+ break;
+
+ default:
+ this.error('String or url() is expected');
+ }
+
+ this.skipSC();
+
+ if (this.tokenType === Ident &&
+ this.cmpStr(this.tokenStart, this.tokenEnd, 'layer')) {
+ children.push(this.Identifier());
+ } else if (
+ this.tokenType === FunctionToken &&
+ this.cmpStr(this.tokenStart, this.tokenEnd, 'layer(')
+ ) {
+ children.push(this.Function(null, parseFunctions));
+ }
+
+ this.skipSC();
+
+ if (this.tokenType === FunctionToken &&
+ this.cmpStr(this.tokenStart, this.tokenEnd, 'supports(')) {
+ children.push(this.Function(null, parseFunctions));
+ }
+
+ if (this.lookupNonWSType(0) === Ident ||
+ this.lookupNonWSType(0) === LeftParenthesis) {
+ children.push(this.MediaQueryList());
+ }
+
+ return children;
+ },
+ block: null
+ }
+};
diff --git a/vanilla/node_modules/css-tree/lib/syntax/atrule/index.js b/vanilla/node_modules/css-tree/lib/syntax/atrule/index.js
new file mode 100644
index 0000000..46ac7f8
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/syntax/atrule/index.js
@@ -0,0 +1,23 @@
+import container from './container.js';
+import fontFace from './font-face.js';
+import importAtrule from './import.js';
+import layer from './layer.js';
+import media from './media.js';
+import nest from './nest.js';
+import page from './page.js';
+import scope from './scope.js';
+import startingStyle from './starting-style.js';
+import supports from './supports.js';
+
+export default {
+ container,
+ 'font-face': fontFace,
+ import: importAtrule,
+ layer,
+ media,
+ nest,
+ page,
+ scope,
+ 'starting-style': startingStyle,
+ supports
+};
diff --git a/vanilla/node_modules/css-tree/lib/syntax/atrule/layer.js b/vanilla/node_modules/css-tree/lib/syntax/atrule/layer.js
new file mode 100644
index 0000000..232eb29
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/syntax/atrule/layer.js
@@ -0,0 +1,12 @@
+export default {
+ parse: {
+ prelude() {
+ return this.createSingleNodeList(
+ this.LayerList()
+ );
+ },
+ block() {
+ return this.Block(false);
+ }
+ }
+};
diff --git a/vanilla/node_modules/css-tree/lib/syntax/atrule/media.js b/vanilla/node_modules/css-tree/lib/syntax/atrule/media.js
new file mode 100644
index 0000000..24d4608
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/syntax/atrule/media.js
@@ -0,0 +1,12 @@
+export default {
+ parse: {
+ prelude() {
+ return this.createSingleNodeList(
+ this.MediaQueryList()
+ );
+ },
+ block(nested = false) {
+ return this.Block(nested);
+ }
+ }
+};
diff --git a/vanilla/node_modules/css-tree/lib/syntax/atrule/nest.js b/vanilla/node_modules/css-tree/lib/syntax/atrule/nest.js
new file mode 100644
index 0000000..99fc15e
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/syntax/atrule/nest.js
@@ -0,0 +1,12 @@
+export default {
+ parse: {
+ prelude() {
+ return this.createSingleNodeList(
+ this.SelectorList()
+ );
+ },
+ block() {
+ return this.Block(true);
+ }
+ }
+};
diff --git a/vanilla/node_modules/css-tree/lib/syntax/atrule/page.js b/vanilla/node_modules/css-tree/lib/syntax/atrule/page.js
new file mode 100644
index 0000000..99fc15e
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/syntax/atrule/page.js
@@ -0,0 +1,12 @@
+export default {
+ parse: {
+ prelude() {
+ return this.createSingleNodeList(
+ this.SelectorList()
+ );
+ },
+ block() {
+ return this.Block(true);
+ }
+ }
+};
diff --git a/vanilla/node_modules/css-tree/lib/syntax/atrule/scope.js b/vanilla/node_modules/css-tree/lib/syntax/atrule/scope.js
new file mode 100644
index 0000000..7185ca1
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/syntax/atrule/scope.js
@@ -0,0 +1,12 @@
+export default {
+ parse: {
+ prelude() {
+ return this.createSingleNodeList(
+ this.Scope()
+ );
+ },
+ block(nested = false) {
+ return this.Block(nested);
+ }
+ }
+};
diff --git a/vanilla/node_modules/css-tree/lib/syntax/atrule/starting-style.js b/vanilla/node_modules/css-tree/lib/syntax/atrule/starting-style.js
new file mode 100644
index 0000000..b964704
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/syntax/atrule/starting-style.js
@@ -0,0 +1,8 @@
+export default {
+ parse: {
+ prelude: null,
+ block(nested = false) {
+ return this.Block(nested);
+ }
+ }
+};
diff --git a/vanilla/node_modules/css-tree/lib/syntax/atrule/supports.js b/vanilla/node_modules/css-tree/lib/syntax/atrule/supports.js
new file mode 100644
index 0000000..bdfffce
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/syntax/atrule/supports.js
@@ -0,0 +1,12 @@
+export default {
+ parse: {
+ prelude() {
+ return this.createSingleNodeList(
+ this.Condition('supports')
+ );
+ },
+ block(nested = false) {
+ return this.Block(nested);
+ }
+ }
+};
diff --git a/vanilla/node_modules/css-tree/lib/syntax/config/generator.js b/vanilla/node_modules/css-tree/lib/syntax/config/generator.js
new file mode 100644
index 0000000..82e874c
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/syntax/config/generator.js
@@ -0,0 +1,5 @@
+import * as node from '../node/index-generate.js';
+
+export default {
+ node
+};
diff --git a/vanilla/node_modules/css-tree/lib/syntax/config/lexer.js b/vanilla/node_modules/css-tree/lib/syntax/config/lexer.js
new file mode 100644
index 0000000..b479ac5
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/syntax/config/lexer.js
@@ -0,0 +1,10 @@
+import { cssWideKeywords } from '../../lexer/generic-const.js';
+import definitions from '../../data.js';
+import * as node from '../node/index.js';
+
+export default {
+ generic: true,
+ cssWideKeywords,
+ ...definitions,
+ node
+};
diff --git a/vanilla/node_modules/css-tree/lib/syntax/config/mix.js b/vanilla/node_modules/css-tree/lib/syntax/config/mix.js
new file mode 100644
index 0000000..f722ceb
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/syntax/config/mix.js
@@ -0,0 +1,123 @@
+function appendOrSet(a, b) {
+ if (typeof b === 'string' && /^\s*\|/.test(b)) {
+ return typeof a === 'string'
+ ? a + b
+ : b.replace(/^\s*\|\s*/, '');
+ }
+
+ return b || null;
+}
+
+function sliceProps(obj, props) {
+ const result = Object.create(null);
+
+ for (const [key, value] of Object.entries(obj)) {
+ if (value) {
+ result[key] = {};
+ for (const prop of Object.keys(value)) {
+ if (props.includes(prop)) {
+ result[key][prop] = value[prop];
+ }
+ }
+ }
+ }
+
+ return result;
+}
+
+export default function mix(dest, src) {
+ const result = { ...dest };
+
+ for (const [prop, value] of Object.entries(src)) {
+ switch (prop) {
+ case 'generic':
+ result[prop] = Boolean(value);
+ break;
+
+ case 'cssWideKeywords':
+ result[prop] = dest[prop]
+ ? [...dest[prop], ...value]
+ : value || [];
+ break;
+
+ case 'units':
+ result[prop] = { ...dest[prop] };
+ for (const [name, patch] of Object.entries(value)) {
+ result[prop][name] = Array.isArray(patch) ? patch : [];
+ }
+ break;
+
+ case 'atrules':
+ result[prop] = { ...dest[prop] };
+
+ for (const [name, atrule] of Object.entries(value)) {
+ const exists = result[prop][name] || {};
+ const current = result[prop][name] = {
+ prelude: exists.prelude || null,
+ descriptors: {
+ ...exists.descriptors
+ }
+ };
+
+ if (!atrule) {
+ continue;
+ }
+
+ current.prelude = atrule.prelude
+ ? appendOrSet(current.prelude, atrule.prelude)
+ : current.prelude || null;
+
+ for (const [descriptorName, descriptorValue] of Object.entries(atrule.descriptors || {})) {
+ current.descriptors[descriptorName] = descriptorValue
+ ? appendOrSet(current.descriptors[descriptorName], descriptorValue)
+ : null;
+ }
+
+ if (!Object.keys(current.descriptors).length) {
+ current.descriptors = null;
+ }
+ }
+ break;
+
+ case 'types':
+ case 'properties':
+ result[prop] = { ...dest[prop] };
+ for (const [name, syntax] of Object.entries(value)) {
+ result[prop][name] = appendOrSet(result[prop][name], syntax);
+ }
+ break;
+
+ case 'scope':
+ case 'features':
+ result[prop] = { ...dest[prop] };
+ for (const [name, props] of Object.entries(value)) {
+ result[prop][name] = { ...result[prop][name], ...props };
+ }
+ break;
+
+ case 'parseContext':
+ result[prop] = {
+ ...dest[prop],
+ ...value
+ };
+ break;
+
+ case 'atrule':
+ case 'pseudo':
+ result[prop] = {
+ ...dest[prop],
+ ...sliceProps(value, ['parse'])
+ };
+ break;
+
+ case 'node':
+ result[prop] = {
+ ...dest[prop],
+ ...sliceProps(value, ['name', 'structure', 'parse', 'generate', 'walkContext'])
+ };
+ break;
+ }
+ }
+
+ return result;
+}
diff --git a/vanilla/node_modules/css-tree/lib/syntax/config/parser-selector.js b/vanilla/node_modules/css-tree/lib/syntax/config/parser-selector.js
new file mode 100644
index 0000000..c9e0ee2
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/syntax/config/parser-selector.js
@@ -0,0 +1,15 @@
+import { Selector } from '../scope/index.js';
+import pseudo from '../pseudo/index.js';
+import * as node from '../node/index-parse-selector.js';
+
+export default {
+ parseContext: {
+ default: 'SelectorList',
+ selectorList: 'SelectorList',
+ selector: 'Selector'
+ },
+ scope: { Selector },
+ atrule: {},
+ pseudo,
+ node
+};
diff --git a/vanilla/node_modules/css-tree/lib/syntax/config/parser.js b/vanilla/node_modules/css-tree/lib/syntax/config/parser.js
new file mode 100644
index 0000000..0b455aa
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/syntax/config/parser.js
@@ -0,0 +1,45 @@
+import * as scope from '../scope/index.js';
+import atrule from '../atrule/index.js';
+import pseudo from '../pseudo/index.js';
+import * as node from '../node/index-parse.js';
+
+export default {
+ parseContext: {
+ default: 'StyleSheet',
+ stylesheet: 'StyleSheet',
+ atrule: 'Atrule',
+ atrulePrelude(options) {
+ return this.AtrulePrelude(options.atrule ? String(options.atrule) : null);
+ },
+ mediaQueryList: 'MediaQueryList',
+ mediaQuery: 'MediaQuery',
+ condition(options) {
+ return this.Condition(options.kind);
+ },
+ rule: 'Rule',
+ selectorList: 'SelectorList',
+ selector: 'Selector',
+ block() {
+ return this.Block(true);
+ },
+ declarationList: 'DeclarationList',
+ declaration: 'Declaration',
+ value: 'Value'
+ },
+ features: {
+ supports: {
+ selector() {
+ return this.Selector();
+ }
+ },
+ container: {
+ style() {
+ return this.Declaration();
+ }
+ }
+ },
+ scope,
+ atrule,
+ pseudo,
+ node
+};
diff --git a/vanilla/node_modules/css-tree/lib/syntax/config/walker.js b/vanilla/node_modules/css-tree/lib/syntax/config/walker.js
new file mode 100644
index 0000000..215d024
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/syntax/config/walker.js
@@ -0,0 +1,5 @@
+import * as node from '../node/index.js';
+
+export default {
+ node
+};
diff --git a/vanilla/node_modules/css-tree/lib/syntax/create.js b/vanilla/node_modules/css-tree/lib/syntax/create.js
new file mode 100644
index 0000000..9bb32c5
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/syntax/create.js
@@ -0,0 +1,55 @@
+import { tokenize } from '../tokenizer/index.js';
+import { createParser } from '../parser/create.js';
+import { createGenerator } from '../generator/create.js';
+import { createConvertor } from '../convertor/create.js';
+import { createWalker } from '../walker/create.js';
+import { Lexer } from '../lexer/Lexer.js';
+import mix from './config/mix.js';
+
+function createSyntax(config) {
+ const parse = createParser(config);
+ const walk = createWalker(config);
+ const generate = createGenerator(config);
+ const { fromPlainObject, toPlainObject } = createConvertor(walk);
+
+ const syntax = {
+ lexer: null,
+ createLexer: config => new Lexer(config, syntax, syntax.lexer.structure),
+
+ tokenize,
+ parse,
+ generate,
+
+ walk,
+ find: walk.find,
+ findLast: walk.findLast,
+ findAll: walk.findAll,
+
+ fromPlainObject,
+ toPlainObject,
+
+ fork(extension) {
+ const base = mix({}, config); // copy of config
+
+ return createSyntax(
+ typeof extension === 'function'
+ ? extension(base) // TODO: remove Object.assign as second parameter
+ : mix(base, extension)
+ );
+ }
+ };
+
+ syntax.lexer = new Lexer({
+ generic: config.generic,
+ cssWideKeywords: config.cssWideKeywords,
+ units: config.units,
+ types: config.types,
+ atrules: config.atrules,
+ properties: config.properties,
+ node: config.node
+ }, syntax);
+
+ return syntax;
+};
+
+export default config => createSyntax(mix({}, config));
diff --git a/vanilla/node_modules/css-tree/lib/syntax/function/expression.js b/vanilla/node_modules/css-tree/lib/syntax/function/expression.js
new file mode 100644
index 0000000..040f826
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/syntax/function/expression.js
@@ -0,0 +1,7 @@
+// legacy IE function
+// expression( <any-value> )
+export default function() {
+ return this.createSingleNodeList(
+ this.Raw(null, false)
+ );
+}
diff --git a/vanilla/node_modules/css-tree/lib/syntax/function/var.js b/vanilla/node_modules/css-tree/lib/syntax/function/var.js
new file mode 100644
index 0000000..010dc0a
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/syntax/function/var.js
@@ -0,0 +1,39 @@
+import { Comma, WhiteSpace } from '../../tokenizer/index.js';
+
+// var( <ident> , <value>? )
+export default function() {
+ const children = this.createList();
+
+ this.skipSC();
+
+ // NOTE: Don't check more than a first argument is an ident, rest checks are for lexer
+ children.push(this.Identifier());
+
+ this.skipSC();
+
+ if (this.tokenType === Comma) {
+ children.push(this.Operator());
+
+ const startIndex = this.tokenIndex;
+ const value = this.parseCustomProperty
+ ? this.Value(null)
+ : this.Raw(this.consumeUntilExclamationMarkOrSemicolon, false);
+
+ if (value.type === 'Value' && value.children.isEmpty) {
+ for (let offset = startIndex - this.tokenIndex; offset <= 0; offset++) {
+ if (this.lookupType(offset) === WhiteSpace) {
+ value.children.appendData({
+ type: 'WhiteSpace',
+ loc: null,
+ value: ' '
+ });
+ break;
+ }
+ }
+ }
+
+ children.push(value);
+ }
+
+ return children;
+};
diff --git a/vanilla/node_modules/css-tree/lib/syntax/index.js b/vanilla/node_modules/css-tree/lib/syntax/index.js
new file mode 100644
index 0000000..c8c9152
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/syntax/index.js
@@ -0,0 +1,10 @@
+import createSyntax from './create.js';
+import lexerConfig from './config/lexer.js';
+import parserConfig from './config/parser.js';
+import walkerConfig from './config/walker.js';
+
+export default createSyntax({
+ ...lexerConfig,
+ ...parserConfig,
+ ...walkerConfig
+});
diff --git a/vanilla/node_modules/css-tree/lib/syntax/node/AnPlusB.js b/vanilla/node_modules/css-tree/lib/syntax/node/AnPlusB.js
new file mode 100644
index 0000000..05c7e44
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/syntax/node/AnPlusB.js
@@ -0,0 +1,292 @@
+import {
+ isDigit,
+ WhiteSpace,
+ Comment,
+ Ident,
+ Number,
+ Dimension
+} from '../../tokenizer/index.js';
+
+const PLUSSIGN = 0x002B; // U+002B PLUS SIGN (+)
+const HYPHENMINUS = 0x002D; // U+002D HYPHEN-MINUS (-)
+const N = 0x006E; // U+006E LATIN SMALL LETTER N (n)
+const DISALLOW_SIGN = true;
+const ALLOW_SIGN = false;
+
+function checkInteger(offset, disallowSign) {
+ let pos = this.tokenStart + offset;
+ const code = this.charCodeAt(pos);
+
+ if (code === PLUSSIGN || code === HYPHENMINUS) {
+ if (disallowSign) {
+ this.error('Number sign is not allowed');
+ }
+ pos++;
+ }
+
+ for (; pos < this.tokenEnd; pos++) {
+ if (!isDigit(this.charCodeAt(pos))) {
+ this.error('Integer is expected', pos);
+ }
+ }
+}
+
+function checkTokenIsInteger(disallowSign) {
+ return checkInteger.call(this, 0, disallowSign);
+}
+
+function expectCharCode(offset, code) {
+ if (!this.cmpChar(this.tokenStart + offset, code)) {
+ let msg = '';
+
+ switch (code) {
+ case N:
+ msg = 'N is expected';
+ break;
+ case HYPHENMINUS:
+ msg = 'HyphenMinus is expected';
+ break;
+ }
+
+ this.error(msg, this.tokenStart + offset);
+ }
+}
+
+// ... <signed-integer>
+// ... ['+' | '-'] <signless-integer>
+function consumeB() {
+ let offset = 0;
+ let sign = 0;
+ let type = this.tokenType;
+
+ while (type === WhiteSpace || type === Comment) {
+ type = this.lookupType(++offset);
+ }
+
+ if (type !== Number) {
+ if (this.isDelim(PLUSSIGN, offset) ||
+ this.isDelim(HYPHENMINUS, offset)) {
+ sign = this.isDelim(PLUSSIGN, offset) ? PLUSSIGN : HYPHENMINUS;
+
+ do {
+ type = this.lookupType(++offset);
+ } while (type === WhiteSpace || type === Comment);
+
+ if (type !== Number) {
+ this.skip(offset);
+ checkTokenIsInteger.call(this, DISALLOW_SIGN);
+ }
+ } else {
+ return null;
+ }
+ }
+
+ if (offset > 0) {
+ this.skip(offset);
+ }
+
+ if (sign === 0) {
+ type = this.charCodeAt(this.tokenStart);
+ if (type !== PLUSSIGN && type !== HYPHENMINUS) {
+ this.error('Number sign is expected');
+ }
+ }
+
+ checkTokenIsInteger.call(this, sign !== 0);
+ return sign === HYPHENMINUS ? '-' + this.consume(Number) : this.consume(Number);
+}
+
+// An+B microsyntax https://www.w3.org/TR/css-syntax-3/#anb
+export const name = 'AnPlusB';
+export const structure = {
+ a: [String, null],
+ b: [String, null]
+};
+
+export function parse() {
+ /* eslint-disable brace-style*/
+ const start = this.tokenStart;
+ let a = null;
+ let b = null;
+
+ // <integer>
+ if (this.tokenType === Number) {
+ checkTokenIsInteger.call(this, ALLOW_SIGN);
+ b = this.consume(Number);
+ }
+
+ // -n
+ // -n <signed-integer>
+ // -n ['+' | '-'] <signless-integer>
+ // -n- <signless-integer>
+ // <dashndashdigit-ident>
+ else if (this.tokenType === Ident && this.cmpChar(this.tokenStart, HYPHENMINUS)) {
+ a = '-1';
+
+ expectCharCode.call(this, 1, N);
+
+ switch (this.tokenEnd - this.tokenStart) {
+ // -n
+ // -n <signed-integer>
+ // -n ['+' | '-'] <signless-integer>
+ case 2:
+ this.next();
+ b = consumeB.call(this);
+ break;
+
+ // -n- <signless-integer>
+ case 3:
+ expectCharCode.call(this, 2, HYPHENMINUS);
+
+ this.next();
+ this.skipSC();
+
+ checkTokenIsInteger.call(this, DISALLOW_SIGN);
+
+ b = '-' + this.consume(Number);
+ break;
+
+ // <dashndashdigit-ident>
+ default:
+ expectCharCode.call(this, 2, HYPHENMINUS);
+ checkInteger.call(this, 3, DISALLOW_SIGN);
+ this.next();
+
+ b = this.substrToCursor(start + 2);
+ }
+ }
+
+ // '+'? n
+ // '+'? n <signed-integer>
+ // '+'? n ['+' | '-'] <signless-integer>
+ // '+'? n- <signless-integer>
+ // '+'? <ndashdigit-ident>
+ else if (this.tokenType === Ident || (this.isDelim(PLUSSIGN) && this.lookupType(1) === Ident)) {
+ let sign = 0;
+ a = '1';
+
+ // just ignore a plus
+ if (this.isDelim(PLUSSIGN)) {
+ sign = 1;
+ this.next();
+ }
+
+ expectCharCode.call(this, 0, N);
+
+ switch (this.tokenEnd - this.tokenStart) {
+ // '+'? n
+ // '+'? n <signed-integer>
+ // '+'? n ['+' | '-'] <signless-integer>
+ case 1:
+ this.next();
+ b = consumeB.call(this);
+ break;
+
+ // '+'? n- <signless-integer>
+ case 2:
+ expectCharCode.call(this, 1, HYPHENMINUS);
+
+ this.next();
+ this.skipSC();
+
+ checkTokenIsInteger.call(this, DISALLOW_SIGN);
+
+ b = '-' + this.consume(Number);
+ break;
+
+ // '+'? <ndashdigit-ident>
+ default:
+ expectCharCode.call(this, 1, HYPHENMINUS);
+ checkInteger.call(this, 2, DISALLOW_SIGN);
+ this.next();
+
+ b = this.substrToCursor(start + sign + 1);
+ }
+ }
+
+ // <ndashdigit-dimension>
+ // <ndash-dimension> <signless-integer>
+ // <n-dimension>
+ // <n-dimension> <signed-integer>
+ // <n-dimension> ['+' | '-'] <signless-integer>
+ else if (this.tokenType === Dimension) {
+ const code = this.charCodeAt(this.tokenStart);
+ const sign = code === PLUSSIGN || code === HYPHENMINUS;
+ let i = this.tokenStart + sign;
+
+ for (; i < this.tokenEnd; i++) {
+ if (!isDigit(this.charCodeAt(i))) {
+ break;
+ }
+ }
+
+ if (i === this.tokenStart + sign) {
+ this.error('Integer is expected', this.tokenStart + sign);
+ }
+
+ expectCharCode.call(this, i - this.tokenStart, N);
+ a = this.substring(start, i);
+
+ // <n-dimension>
+ // <n-dimension> <signed-integer>
+ // <n-dimension> ['+' | '-'] <signless-integer>
+ if (i + 1 === this.tokenEnd) {
+ this.next();
+ b = consumeB.call(this);
+ } else {
+ expectCharCode.call(this, i - this.tokenStart + 1, HYPHENMINUS);
+
+ // <ndash-dimension> <signless-integer>
+ if (i + 2 === this.tokenEnd) {
+ this.next();
+ this.skipSC();
+ checkTokenIsInteger.call(this, DISALLOW_SIGN);
+ b = '-' + this.consume(Number);
+ }
+ // <ndashdigit-dimension>
+ else {
+ checkInteger.call(this, i - this.tokenStart + 2, DISALLOW_SIGN);
+ this.next();
+ b = this.substrToCursor(i + 1);
+ }
+ }
+ } else {
+ this.error();
+ }
+
+ if (a !== null && a.charCodeAt(0) === PLUSSIGN) {
+ a = a.substr(1);
+ }
+
+ if (b !== null && b.charCodeAt(0) === PLUSSIGN) {
+ b = b.substr(1);
+ }
+
+ return {
+ type: 'AnPlusB',
+ loc: this.getLocation(start, this.tokenStart),
+ a,
+ b
+ };
+}
+
+export function generate(node) {
+ if (node.a) {
+ const a =
+ node.a === '+1' && 'n' ||
+ node.a === '1' && 'n' ||
+ node.a === '-1' && '-n' ||
+ node.a + 'n';
+
+ if (node.b) {
+ const b = node.b[0] === '-' || node.b[0] === '+'
+ ? node.b
+ : '+' + node.b;
+ this.tokenize(a + b);
+ } else {
+ this.tokenize(a);
+ }
+ } else {
+ this.tokenize(node.b);
+ }
+}
diff --git a/vanilla/node_modules/css-tree/lib/syntax/node/Atrule.js b/vanilla/node_modules/css-tree/lib/syntax/node/Atrule.js
new file mode 100644
index 0000000..99d1284
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/syntax/node/Atrule.js
@@ -0,0 +1,100 @@
+import {
+ AtKeyword,
+ Semicolon,
+ LeftCurlyBracket,
+ RightCurlyBracket
+} from '../../tokenizer/index.js';
+
+function consumeRaw() {
+ return this.Raw(this.consumeUntilLeftCurlyBracketOrSemicolon, true);
+}
+
+function isDeclarationBlockAtrule() {
+ for (let offset = 1, type; type = this.lookupType(offset); offset++) {
+ if (type === RightCurlyBracket) {
+ return true;
+ }
+
+ if (type === LeftCurlyBracket ||
+ type === AtKeyword) {
+ return false;
+ }
+ }
+
+ return false;
+}
+
+
+export const name = 'Atrule';
+export const walkContext = 'atrule';
+export const structure = {
+ name: String,
+ prelude: ['AtrulePrelude', 'Raw', null],
+ block: ['Block', null]
+};
+
+export function parse(isDeclaration = false) {
+ const start = this.tokenStart;
+ let name;
+ let nameLowerCase;
+ let prelude = null;
+ let block = null;
+
+ this.eat(AtKeyword);
+
+ name = this.substrToCursor(start + 1);
+ nameLowerCase = name.toLowerCase();
+ this.skipSC();
+
+ // parse prelude
+ if (this.eof === false &&
+ this.tokenType !== LeftCurlyBracket &&
+ this.tokenType !== Semicolon) {
+ if (this.parseAtrulePrelude) {
+ prelude = this.parseWithFallback(this.AtrulePrelude.bind(this, name, isDeclaration), consumeRaw);
+ } else {
+ prelude = consumeRaw.call(this, this.tokenIndex);
+ }
+
+ this.skipSC();
+ }
+
+ switch (this.tokenType) {
+ case Semicolon:
+ this.next();
+ break;
+
+ case LeftCurlyBracket:
+ if (hasOwnProperty.call(this.atrule, nameLowerCase) &&
+ typeof this.atrule[nameLowerCase].block === 'function') {
+ block = this.atrule[nameLowerCase].block.call(this, isDeclaration);
+ } else {
+ // TODO: should consume block content as Raw?
+ block = this.Block(isDeclarationBlockAtrule.call(this));
+ }
+
+ break;
+ }
+
+ return {
+ type: 'Atrule',
+ loc: this.getLocation(start, this.tokenStart),
+ name,
+ prelude,
+ block
+ };
+}
+
+export function generate(node) {
+ this.token(AtKeyword, '@' + node.name);
+
+ if (node.prelude !== null) {
+ this.node(node.prelude);
+ }
+
+ if (node.block) {
+ this.node(node.block);
+ } else {
+ this.token(Semicolon, ';');
+ }
+}
diff --git a/vanilla/node_modules/css-tree/lib/syntax/node/AtrulePrelude.js b/vanilla/node_modules/css-tree/lib/syntax/node/AtrulePrelude.js
new file mode 100644
index 0000000..5b5645a
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/syntax/node/AtrulePrelude.js
@@ -0,0 +1,47 @@
+import {
+ Semicolon,
+ LeftCurlyBracket
+} from '../../tokenizer/index.js';
+
+export const name = 'AtrulePrelude';
+export const walkContext = 'atrulePrelude';
+export const structure = {
+ children: [[]]
+};
+
+export function parse(name) {
+ let children = null;
+
+ if (name !== null) {
+ name = name.toLowerCase();
+ }
+
+ this.skipSC();
+
+ if (hasOwnProperty.call(this.atrule, name) &&
+ typeof this.atrule[name].prelude === 'function') {
+ // custom consumer
+ children = this.atrule[name].prelude.call(this);
+ } else {
+ // default consumer
+ children = this.readSequence(this.scope.AtrulePrelude);
+ }
+
+ this.skipSC();
+
+ if (this.eof !== true &&
+ this.tokenType !== LeftCurlyBracket &&
+ this.tokenType !== Semicolon) {
+ this.error('Semicolon or block is expected');
+ }
+
+ return {
+ type: 'AtrulePrelude',
+ loc: this.getLocationFromList(children),
+ children
+ };
+}
+
+export function generate(node) {
+ this.children(node);
+}
diff --git a/vanilla/node_modules/css-tree/lib/syntax/node/AttributeSelector.js b/vanilla/node_modules/css-tree/lib/syntax/node/AttributeSelector.js
new file mode 100644
index 0000000..8578dad
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/syntax/node/AttributeSelector.js
@@ -0,0 +1,147 @@
+import {
+ Ident,
+ String as StringToken,
+ Delim,
+ LeftSquareBracket,
+ RightSquareBracket
+} from '../../tokenizer/index.js';
+
+const DOLLARSIGN = 0x0024; // U+0024 DOLLAR SIGN ($)
+const ASTERISK = 0x002A; // U+002A ASTERISK (*)
+const EQUALSSIGN = 0x003D; // U+003D EQUALS SIGN (=)
+const CIRCUMFLEXACCENT = 0x005E; // U+005E (^)
+const VERTICALLINE = 0x007C; // U+007C VERTICAL LINE (|)
+const TILDE = 0x007E; // U+007E TILDE (~)
+
+function getAttributeName() {
+ if (this.eof) {
+ this.error('Unexpected end of input');
+ }
+
+ const start = this.tokenStart;
+ let expectIdent = false;
+
+ if (this.isDelim(ASTERISK)) {
+ expectIdent = true;
+ this.next();
+ } else if (!this.isDelim(VERTICALLINE)) {
+ this.eat(Ident);
+ }
+
+ if (this.isDelim(VERTICALLINE)) {
+ if (this.charCodeAt(this.tokenStart + 1) !== EQUALSSIGN) {
+ this.next();
+ this.eat(Ident);
+ } else if (expectIdent) {
+ this.error('Identifier is expected', this.tokenEnd);
+ }
+ } else if (expectIdent) {
+ this.error('Vertical line is expected');
+ }
+
+ return {
+ type: 'Identifier',
+ loc: this.getLocation(start, this.tokenStart),
+ name: this.substrToCursor(start)
+ };
+}
+
+function getOperator() {
+ const start = this.tokenStart;
+ const code = this.charCodeAt(start);
+
+ if (code !== EQUALSSIGN && // =
+ code !== TILDE && // ~=
+ code !== CIRCUMFLEXACCENT && // ^=
+ code !== DOLLARSIGN && // $=
+ code !== ASTERISK && // *=
+ code !== VERTICALLINE // |=
+ ) {
+ this.error('Attribute selector (=, ~=, ^=, $=, *=, |=) is expected');
+ }
+
+ this.next();
+
+ if (code !== EQUALSSIGN) {
+ if (!this.isDelim(EQUALSSIGN)) {
+ this.error('Equal sign is expected');
+ }
+
+ this.next();
+ }
+
+ return this.substrToCursor(start);
+}
+
+// '[' <wq-name> ']'
+// '[' <wq-name> <attr-matcher> [ <string-token> | <ident-token> ] <attr-modifier>? ']'
+export const name = 'AttributeSelector';
+export const structure = {
+ name: 'Identifier',
+ matcher: [String, null],
+ value: ['String', 'Identifier', null],
+ flags: [String, null]
+};
+
+export function parse() {
+ const start = this.tokenStart;
+ let name;
+ let matcher = null;
+ let value = null;
+ let flags = null;
+
+ this.eat(LeftSquareBracket);
+ this.skipSC();
+
+ name = getAttributeName.call(this);
+ this.skipSC();
+
+ if (this.tokenType !== RightSquareBracket) {
+ // avoid case `[name i]`
+ if (this.tokenType !== Ident) {
+ matcher = getOperator.call(this);
+
+ this.skipSC();
+
+ value = this.tokenType === StringToken
+ ? this.String()
+ : this.Identifier();
+
+ this.skipSC();
+ }
+
+ // attribute flags
+ if (this.tokenType === Ident) {
+ flags = this.consume(Ident);
+
+ this.skipSC();
+ }
+ }
+
+ this.eat(RightSquareBracket);
+
+ return {
+ type: 'AttributeSelector',
+ loc: this.getLocation(start, this.tokenStart),
+ name,
+ matcher,
+ value,
+ flags
+ };
+}
+
+export function generate(node) {
+ this.token(Delim, '[');
+ this.node(node.name);
+
+ if (node.matcher !== null) {
+ this.tokenize(node.matcher);
+ this.node(node.value);
+ }
+
+ if (node.flags !== null) {
+ this.token(Ident, node.flags);
+ }
+
+ this.token(Delim, ']');
+}
diff --git a/vanilla/node_modules/css-tree/lib/syntax/node/Block.js b/vanilla/node_modules/css-tree/lib/syntax/node/Block.js
new file mode 100644
index 0000000..10bf6fc
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/syntax/node/Block.js
@@ -0,0 +1,95 @@
+import {
+ WhiteSpace,
+ Comment,
+ Semicolon,
+ AtKeyword,
+ LeftCurlyBracket,
+ RightCurlyBracket
+} from '../../tokenizer/index.js';
+
+const AMPERSAND = 0x0026; // U+0026 AMPERSAND (&)
+
+function consumeRaw() {
+ return this.Raw(null, true);
+}
+function consumeRule() {
+ return this.parseWithFallback(this.Rule, consumeRaw);
+}
+function consumeRawDeclaration() {
+ return this.Raw(this.consumeUntilSemicolonIncluded, true);
+}
+function consumeDeclaration() {
+ if (this.tokenType === Semicolon) {
+ return consumeRawDeclaration.call(this, this.tokenIndex);
+ }
+
+ const node = this.parseWithFallback(this.Declaration, consumeRawDeclaration);
+
+ if (this.tokenType === Semicolon) {
+ this.next();
+ }
+
+ return node;
+}
+
+export const name = 'Block';
+export const walkContext = 'block';
+export const structure = {
+ children: [[
+ 'Atrule',
+ 'Rule',
+ 'Declaration'
+ ]]
+};
+
+export function parse(isStyleBlock) {
+ const consumer = isStyleBlock ? consumeDeclaration : consumeRule;
+ const start = this.tokenStart;
+ let children = this.createList();
+
+ this.eat(LeftCurlyBracket);
+
+ scan:
+ while (!this.eof) {
+ switch (this.tokenType) {
+ case RightCurlyBracket:
+ break scan;
+
+ case WhiteSpace:
+ case Comment:
+ this.next();
+ break;
+
+ case AtKeyword:
+ children.push(this.parseWithFallback(this.Atrule.bind(this, isStyleBlock), consumeRaw));
+ break;
+
+ default:
+ if (isStyleBlock && this.isDelim(AMPERSAND)) {
+ children.push(consumeRule.call(this));
+ } else {
+ children.push(consumer.call(this));
+ }
+ }
+ }
+
+ if (!this.eof) {
+ this.eat(RightCurlyBracket);
+ }
+
+ return {
+ type: 'Block',
+ loc: this.getLocation(start, this.tokenStart),
+ children
+ };
+}
+
+export function generate(node) {
+ this.token(LeftCurlyBracket, '{');
+ this.children(node, prev => {
+ if (prev.type === 'Declaration') {
+ this.token(Semicolon, ';');
+ }
+ });
+ this.token(RightCurlyBracket, '}');
+}
diff --git a/vanilla/node_modules/css-tree/lib/syntax/node/Brackets.js b/vanilla/node_modules/css-tree/lib/syntax/node/Brackets.js
new file mode 100644
index 0000000..1d97a4c
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/syntax/node/Brackets.js
@@ -0,0 +1,35 @@
+import {
+ Delim,
+ LeftSquareBracket,
+ RightSquareBracket
+} from '../../tokenizer/index.js';
+
+export const name = 'Brackets';
+export const structure = {
+ children: [[]]
+};
+
+export function parse(readSequence, recognizer) {
+ const start = this.tokenStart;
+ let children = null;
+
+ this.eat(LeftSquareBracket);
+
+ children = readSequence.call(this, recognizer);
+
+ if (!this.eof) {
+ this.eat(RightSquareBracket);
+ }
+
+ return {
+ type: 'Brackets',
+ loc: this.getLocation(start, this.tokenStart),
+ children
+ };
+}
+
+export function generate(node) {
+ this.token(Delim, '[');
+ this.children(node);
+ this.token(Delim, ']');
+}
diff --git a/vanilla/node_modules/css-tree/lib/syntax/node/CDC.js b/vanilla/node_modules/css-tree/lib/syntax/node/CDC.js
new file mode 100644
index 0000000..efed4a6
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/syntax/node/CDC.js
@@ -0,0 +1,19 @@
+import { CDC } from '../../tokenizer/index.js';
+
+export const name = 'CDC';
+export const structure = [];
+
+export function parse() {
+ const start = this.tokenStart;
+
+ this.eat(CDC); // -->
+
+ return {
+ type: 'CDC',
+ loc: this.getLocation(start, this.tokenStart)
+ };
+}
+
+export function generate() {
+ this.token(CDC, '-->');
+}
diff --git a/vanilla/node_modules/css-tree/lib/syntax/node/CDO.js b/vanilla/node_modules/css-tree/lib/syntax/node/CDO.js
new file mode 100644
index 0000000..3a9de89
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/syntax/node/CDO.js
@@ -0,0 +1,19 @@
+import { CDO } from '../../tokenizer/index.js';
+
+export const name = 'CDO';
+export const structure = [];
+
+export function parse() {
+ const start = this.tokenStart;
+
+ this.eat(CDO); // <!--
+
+ return {
+ type: 'CDO',
+ loc: this.getLocation(start, this.tokenStart)
+ };
+}
+
+export function generate() {
+ this.token(CDO, '<!--');
+}
diff --git a/vanilla/node_modules/css-tree/lib/syntax/node/ClassSelector.js b/vanilla/node_modules/css-tree/lib/syntax/node/ClassSelector.js
new file mode 100644
index 0000000..febb0d9
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/syntax/node/ClassSelector.js
@@ -0,0 +1,24 @@
+import { Delim, Ident } from '../../tokenizer/index.js';
+
+const FULLSTOP = 0x002E; // U+002E FULL STOP (.)
+
+// '.' ident
+export const name = 'ClassSelector';
+export const structure = {
+ name: String
+};
+
+export function parse() {
+ this.eatDelim(FULLSTOP);
+
+ return {
+ type: 'ClassSelector',
+ loc: this.getLocation(this.tokenStart - 1, this.tokenEnd),
+ name: this.consume(Ident)
+ };
+}
+
+export function generate(node) {
+ this.token(Delim, '.');
+ this.token(Ident, node.name);
+}
diff --git a/vanilla/node_modules/css-tree/lib/syntax/node/Combinator.js b/vanilla/node_modules/css-tree/lib/syntax/node/Combinator.js
new file mode 100644
index 0000000..2e5bb1f
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/syntax/node/Combinator.js
@@ -0,0 +1,54 @@
+import { WhiteSpace, Delim } from '../../tokenizer/index.js';
+
+const PLUSSIGN = 0x002B; // U+002B PLUS SIGN (+)
+const SOLIDUS = 0x002F; // U+002F SOLIDUS (/)
+const GREATERTHANSIGN = 0x003E; // U+003E GREATER-THAN SIGN (>)
+const TILDE = 0x007E; // U+007E TILDE (~)
+
+export const name = 'Combinator';
+export const structure = {
+ name: String
+};
+
+// + | > | ~ | /deep/
+export function parse() {
+ const start = this.tokenStart;
+ let name;
+
+ switch (this.tokenType) {
+ case WhiteSpace:
+ name = ' ';
+ break;
+
+ case Delim:
+ switch (this.charCodeAt(this.tokenStart)) {
+ case GREATERTHANSIGN:
+ case PLUSSIGN:
+ case TILDE:
+ this.next();
+ break;
+
+ case SOLIDUS:
+ this.next();
+ this.eatIdent('deep');
+ this.eatDelim(SOLIDUS);
+ break;
+
+ default:
+ this.error('Combinator is expected');
+ }
+
+ name = this.substrToCursor(start);
+ break;
+ }
+
+ return {
+ type: 'Combinator',
+ loc: this.getLocation(start, this.tokenStart),
+ name
+ };
+}
+
+export function generate(node) {
+ this.tokenize(node.name);
+}
diff --git a/vanilla/node_modules/css-tree/lib/syntax/node/Comment.js b/vanilla/node_modules/css-tree/lib/syntax/node/Comment.js
new file mode 100644
index 0000000..8a9d291
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/syntax/node/Comment.js
@@ -0,0 +1,33 @@
+import { Comment } from '../../tokenizer/index.js';
+
+const ASTERISK = 0x002A; // U+002A ASTERISK (*)
+const SOLIDUS = 0x002F; // U+002F SOLIDUS (/)
+
+
+export const name = 'Comment';
+export const structure = {
+ value: String
+};
+
+export function parse() {
+ const start = this.tokenStart;
+ let end = this.tokenEnd;
+
+ this.eat(Comment);
+
+ if ((end - start + 2) >= 2 &&
+ this.charCodeAt(end - 2) === ASTERISK &&
+ this.charCodeAt(end - 1) === SOLIDUS) {
+ end -= 2;
+ }
+
+ return {
+ type: 'Comment',
+ loc: this.getLocation(start, this.tokenStart),
+ value: this.substring(start + 2, end)
+ };
+}
+
+export function generate(node) {
+ this.token(Comment, '/*' + node.value + '*/');
+}
diff --git a/vanilla/node_modules/css-tree/lib/syntax/node/Condition.js b/vanilla/node_modules/css-tree/lib/syntax/node/Condition.js
new file mode 100644
index 0000000..e4b1e0d
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/syntax/node/Condition.js
@@ -0,0 +1,123 @@
+import {
+ WhiteSpace,
+ Comment,
+ Ident,
+ LeftParenthesis,
+ RightParenthesis,
+ Function as FunctionToken,
+ Colon,
+ EOF
+} from '../../tokenizer/index.js';
+
+const likelyFeatureToken = new Set([Colon, RightParenthesis, EOF]);
+
+export const name = 'Condition';
+export const structure = {
+ kind: String,
+ children: [[
+ 'Identifier',
+ 'Feature',
+ 'FeatureFunction',
+ 'FeatureRange',
+ 'SupportsDeclaration'
+ ]]
+};
+
+function featureOrRange(kind) {
+ if (this.lookupTypeNonSC(1) === Ident &&
+ likelyFeatureToken.has(this.lookupTypeNonSC(2))) {
+ return this.Feature(kind);
+ }
+
+ return this.FeatureRange(kind);
+}
+
+const parentheses = {
+ media: featureOrRange,
+ container: featureOrRange,
+ supports() {
+ return this.SupportsDeclaration();
+ }
+};
+
+export function parse(kind = 'media') {
+ const children = this.createList();
+
+ scan: while (!this.eof) {
+ switch (this.tokenType) {
+ case Comment:
+ case WhiteSpace:
+ this.next();
+ continue;
+
+ case Ident:
+ children.push(this.Identifier());
+ break;
+
+ case LeftParenthesis: {
+ let term = this.parseWithFallback(
+ () => parentheses[kind].call(this, kind),
+ () => null
+ );
+
+ if (!term) {
+ term = this.parseWithFallback(
+ () => {
+ this.eat(LeftParenthesis);
+ const res = this.Condition(kind);
+ this.eat(RightParenthesis);
+ return res;
+ },
+ () => {
+ return this.GeneralEnclosed(kind);
+ }
+ );
+ }
+
+ children.push(term);
+ break;
+ }
+
+ case FunctionToken: {
+ let term = this.parseWithFallback(
+ () => this.FeatureFunction(kind),
+ () => null
+ );
+
+ if (!term) {
+ term = this.GeneralEnclosed(kind);
+ }
+
+ children.push(term);
+ break;
+ }
+
+ default:
+ break scan;
+ }
+ }
+
+ if (children.isEmpty) {
+ this.error('Condition is expected');
+ }
+
+ return {
+ type: 'Condition',
+ loc: this.getLocationFromList(children),
+ kind,
+ children
+ };
+}
+
+export function generate(node) {
+ node.children.forEach(child => {
+ if (child.type === 'Condition') {
+ this.token(LeftParenthesis, '(');
+ this.node(child);
+ this.token(RightParenthesis, ')');
+ } else {
+ this.node(child);
+ }
+ });
+}
+
diff --git a/vanilla/node_modules/css-tree/lib/syntax/node/Declaration.js b/vanilla/node_modules/css-tree/lib/syntax/node/Declaration.js
new file mode 100644
index 0000000..af3ea60
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/syntax/node/Declaration.js
@@ -0,0 +1,165 @@
+import { isCustomProperty } from '../../utils/names.js';
+import {
+ Ident,
+ Hash,
+ Colon,
+ Semicolon,
+ Delim,
+ WhiteSpace
+} from '../../tokenizer/index.js';
+
+const EXCLAMATIONMARK = 0x0021; // U+0021 EXCLAMATION MARK (!)
+const NUMBERSIGN = 0x0023; // U+0023 NUMBER SIGN (#)
+const DOLLARSIGN = 0x0024; // U+0024 DOLLAR SIGN ($)
+const AMPERSAND = 0x0026; // U+0026 AMPERSAND (&)
+const ASTERISK = 0x002A; // U+002A ASTERISK (*)
+const PLUSSIGN = 0x002B; // U+002B PLUS SIGN (+)
+const SOLIDUS = 0x002F; // U+002F SOLIDUS (/)
+
+function consumeValueRaw() {
+ return this.Raw(this.consumeUntilExclamationMarkOrSemicolon, true);
+}
+
+function consumeCustomPropertyRaw() {
+ return this.Raw(this.consumeUntilExclamationMarkOrSemicolon, false);
+}
+
+function consumeValue() {
+ const startValueToken = this.tokenIndex;
+ const value = this.Value();
+
+ if (value.type !== 'Raw' &&
+ this.eof === false &&
+ this.tokenType !== Semicolon &&
+ this.isDelim(EXCLAMATIONMARK) === false &&
+ this.isBalanceEdge(startValueToken) === false) {
+ this.error();
+ }
+
+ return value;
+}
+
+export const name = 'Declaration';
+export const walkContext = 'declaration';
+export const structure = {
+ important: [Boolean, String],
+ property: String,
+ value: ['Value', 'Raw']
+};
+
+export function parse() {
+ const start = this.tokenStart;
+ const startToken = this.tokenIndex;
+ const property = readProperty.call(this);
+ const customProperty = isCustomProperty(property);
+ const parseValue = customProperty ? this.parseCustomProperty : this.parseValue;
+ const consumeRaw = customProperty ? consumeCustomPropertyRaw : consumeValueRaw;
+ let important = false;
+ let value;
+
+ this.skipSC();
+ this.eat(Colon);
+
+ const valueStart = this.tokenIndex;
+
+ if (!customProperty) {
+ this.skipSC();
+ }
+
+ if (parseValue) {
+ value = this.parseWithFallback(consumeValue, consumeRaw);
+ } else {
+ value = consumeRaw.call(this, this.tokenIndex);
+ }
+
+ if (customProperty && value.type === 'Value' && value.children.isEmpty) {
+ for (let offset = valueStart - this.tokenIndex; offset <= 0; offset++) {
+ if (this.lookupType(offset) === WhiteSpace) {
+ value.children.appendData({
+ type: 'WhiteSpace',
+ loc: null,
+ value: ' '
+ });
+ break;
+ }
+ }
+ }
+
+ if (this.isDelim(EXCLAMATIONMARK)) {
+ important = getImportant.call(this);
+ this.skipSC();
+ }
+
+ // Do not include semicolon to range per spec
+ // https://drafts.csswg.org/css-syntax/#declaration-diagram
+
+ if (this.eof === false &&
+ this.tokenType !== Semicolon &&
+ this.isBalanceEdge(startToken) === false) {
+ this.error();
+ }
+
+ return {
+ type: 'Declaration',
+ loc: this.getLocation(start, this.tokenStart),
+ important,
+ property,
+ value
+ };
+}
+
+export function generate(node) {
+ this.token(Ident, node.property);
+ this.token(Colon, ':');
+ this.node(node.value);
+
+ if (node.important) {
+ this.token(Delim, '!');
+ this.token(Ident, node.important === true ? 'important' : node.important);
+ }
+}
+
+function readProperty() {
+ const start = this.tokenStart;
+
+ // hacks
+ if (this.tokenType === Delim) {
+ switch (this.charCodeAt(this.tokenStart)) {
+ case ASTERISK:
+ case DOLLARSIGN:
+ case PLUSSIGN:
+ case NUMBERSIGN:
+ case AMPERSAND:
+ this.next();
+ break;
+
+ // TODO: not sure we should support this hack
+ case SOLIDUS:
+ this.next();
+ if (this.isDelim(SOLIDUS)) {
+ this.next();
+ }
+ break;
+ }
+ }
+
+ if (this.tokenType === Hash) {
+ this.eat(Hash);
+ } else {
+ this.eat(Ident);
+ }
+
+ return this.substrToCursor(start);
+}
+
+// ! ws* important
+function getImportant() {
+ this.eat(Delim);
+ this.skipSC();
+
+ const important = this.consume(Ident);
+
+ // store original value in case it differ from `important`
+ // for better original source restoring and hacks like `!ie` support
+ return important === 'important' ? true : important;
+}
diff --git a/vanilla/node_modules/css-tree/lib/syntax/node/DeclarationList.js b/vanilla/node_modules/css-tree/lib/syntax/node/DeclarationList.js
new file mode 100644
index 0000000..2b40c99
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/syntax/node/DeclarationList.js
@@ -0,0 +1,62 @@
+import {
+ WhiteSpace,
+ Comment,
+ Semicolon,
+ AtKeyword
+} from '../../tokenizer/index.js';
+
+const AMPERSAND = 0x0026; // U+0026 AMPERSAND (&)
+
+function consumeRaw() {
+ return this.Raw(this.consumeUntilSemicolonIncluded, true);
+}
+
+export const name = 'DeclarationList';
+export const structure = {
+ children: [[
+ 'Declaration',
+ 'Atrule',
+ 'Rule'
+ ]]
+};
+
+export function parse() {
+ const children = this.createList();
+
+ scan:
+ while (!this.eof) {
+ switch (this.tokenType) {
+ case WhiteSpace:
+ case Comment:
+ case Semicolon:
+ this.next();
+ break;
+
+ case AtKeyword:
+ children.push(this.parseWithFallback(this.Atrule.bind(this, true), consumeRaw));
+ break;
+
+ default:
+ if (this.isDelim(AMPERSAND)) {
+ children.push(this.parseWithFallback(this.Rule, consumeRaw));
+ } else {
+ children.push(this.parseWithFallback(this.Declaration, consumeRaw));
+ }
+ }
+ }
+
+ return {
+ type: 'DeclarationList',
+ loc: this.getLocationFromList(children),
+ children
+ };
+}
+
+export function generate(node) {
+ this.children(node, prev => {
+ if (prev.type === 'Declaration') {
+ this.token(Semicolon, ';');
+ }
+ });
+}
+
diff --git a/vanilla/node_modules/css-tree/lib/syntax/node/Dimension.js b/vanilla/node_modules/css-tree/lib/syntax/node/Dimension.js
new file mode 100644
index 0000000..4b9bffc
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/syntax/node/Dimension.js
@@ -0,0 +1,23 @@
+import { Dimension } from '../../tokenizer/index.js';
+
+export const name = 'Dimension';
+export const structure = {
+ value: String,
+ unit: String
+};
+
+export function parse() {
+ const start = this.tokenStart;
+ const value = this.consumeNumber(Dimension);
+
+ return {
+ type: 'Dimension',
+ loc: this.getLocation(start, this.tokenStart),
+ value,
+ unit: this.substring(start + value.length, this.tokenStart)
+ };
+}
+
+export function generate(node) {
+ this.token(Dimension, node.value + node.unit);
+}
diff --git a/vanilla/node_modules/css-tree/lib/syntax/node/Feature.js b/vanilla/node_modules/css-tree/lib/syntax/node/Feature.js
new file mode 100644
index 0000000..2f2dc3d
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/syntax/node/Feature.js
@@ -0,0 +1,103 @@
+import {
+ Ident,
+ Number,
+ Dimension,
+ Function as FunctionToken,
+ LeftParenthesis,
+ RightParenthesis,
+ Colon,
+ Delim
+} from '../../tokenizer/index.js';
+
+const SOLIDUS = 0x002F; // U+002F SOLIDUS (/)
+
+export const name = 'Feature';
+export const structure = {
+ kind: String,
+ name: String,
+ value: ['Identifier', 'Number', 'Dimension', 'Ratio', 'Function', null]
+};
+
+export function parse(kind) {
+ const start = this.tokenStart;
+ let name;
+ let value = null;
+
+ this.eat(LeftParenthesis);
+ this.skipSC();
+
+ name = this.consume(Ident);
+ this.skipSC();
+
+ if (this.tokenType !== RightParenthesis) {
+ this.eat(Colon);
+ this.skipSC();
+
+ switch (this.tokenType) {
+ case Number:
+ if (this.lookupNonWSType(1) === Delim) {
+ value = this.Ratio();
+ } else {
+ value = this.Number();
+ }
+
+ break;
+
+ case Dimension:
+ value = this.Dimension();
+ break;
+
+ case Ident:
+ value = this.Identifier();
+ break;
+
+ case FunctionToken:
+ value = this.parseWithFallback(
+ () => {
+ const res = this.Function(this.readSequence, this.scope.Value);
+
+ this.skipSC();
+
+ if (this.isDelim(SOLIDUS)) {
+ this.error();
+ }
+
+ return res;
+ },
+ () => {
+ return this.Ratio();
+ }
+ );
+ break;
+
+ default:
+ this.error('Number, dimension, ratio or identifier is expected');
+ }
+
+ this.skipSC();
+ }
+
+ if (!this.eof) {
+ this.eat(RightParenthesis);
+ }
+
+ return {
+ type: 'Feature',
+ loc: this.getLocation(start, this.tokenStart),
+ kind,
+ name,
+ value
+ };
+}
+
+export function generate(node) {
+ this.token(LeftParenthesis, '(');
+ this.token(Ident, node.name);
+
+ if (node.value !== null) {
+ this.token(Colon, ':');
+ this.node(node.value);
+ }
+
+ this.token(RightParenthesis, ')');
+}
diff --git a/vanilla/node_modules/css-tree/lib/syntax/node/FeatureFunction.js b/vanilla/node_modules/css-tree/lib/syntax/node/FeatureFunction.js
new file mode 100644
index 0000000..5869479
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/syntax/node/FeatureFunction.js
@@ -0,0 +1,63 @@
+import {
+ Function as FunctionToken,
+ RightParenthesis
+} from '../../tokenizer/index.js';
+
+export const name = 'FeatureFunction';
+export const structure = {
+ kind: String,
+ feature: String,
+ value: ['Declaration', 'Selector']
+};
+
+function getFeatureParser(kind, name) {
+ const featuresOfKind = this.features[kind] || {};
+ const parser = featuresOfKind[name];
+
+ if (typeof parser !== 'function') {
+ this.error(`Unknown feature ${name}()`);
+ }
+
+ return parser;
+}
+
+export function parse(kind = 'unknown') {
+ const start = this.tokenStart;
+ const functionName = this.consumeFunctionName();
+ const valueParser = getFeatureParser.call(this, kind, functionName.toLowerCase());
+
+ this.skipSC();
+
+ const value = this.parseWithFallback(
+ () => {
+ const startValueToken = this.tokenIndex;
+ const value = valueParser.call(this);
+
+ if (this.eof === false &&
+ this.isBalanceEdge(startValueToken) === false) {
+ this.error();
+ }
+
+ return value;
+ },
+ () => this.Raw(null, false)
+ );
+
+ if (!this.eof) {
+ this.eat(RightParenthesis);
+ }
+
+ return {
+ type: 'FeatureFunction',
+ loc: this.getLocation(start, this.tokenStart),
+ kind,
+ feature: functionName,
+ value
+ };
+}
+
+export function generate(node) {
+ this.token(FunctionToken, node.feature + '(');
+ this.node(node.value);
+ this.token(RightParenthesis, ')');
+}
diff --git a/vanilla/node_modules/css-tree/lib/syntax/node/FeatureRange.js b/vanilla/node_modules/css-tree/lib/syntax/node/FeatureRange.js
new file mode 100644
index 0000000..a83e479
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/syntax/node/FeatureRange.js
@@ -0,0 +1,133 @@
+import {
+ Ident,
+ Number,
+ Dimension,
+ Function as FunctionToken,
+ LeftParenthesis,
+ RightParenthesis
+} from '../../tokenizer/index.js';
+
+const SOLIDUS = 0x002F; // U+002F SOLIDUS (/)
+const LESSTHANSIGN = 0x003C; // U+003C LESS-THAN SIGN (<)
+const EQUALSSIGN = 0x003D; // U+003D EQUALS SIGN (=)
+const GREATERTHANSIGN = 0x003E; // U+003E GREATER-THAN SIGN (>)
+
+export const name = 'FeatureRange';
+export const structure = {
+ kind: String,
+ left: ['Identifier', 'Number', 'Dimension', 'Ratio', 'Function'],
+ leftComparison: String,
+ middle: ['Identifier', 'Number', 'Dimension', 'Ratio', 'Function'],
+ rightComparison: [String, null],
+ right: ['Identifier', 'Number', 'Dimension', 'Ratio', 'Function', null]
+};
+
+function readTerm() {
+ this.skipSC();
+
+ switch (this.tokenType) {
+ case Number:
+ if (this.isDelim(SOLIDUS, this.lookupOffsetNonSC(1))) {
+ return this.Ratio();
+ } else {
+ return this.Number();
+ }
+
+ case Dimension:
+ return this.Dimension();
+
+ case Ident:
+ return this.Identifier();
+
+ case FunctionToken:
+ return this.parseWithFallback(
+ () => {
+ const res = this.Function(this.readSequence, this.scope.Value);
+
+ this.skipSC();
+
+ if (this.isDelim(SOLIDUS)) {
+ this.error();
+ }
+
+ return res;
+ },
+ () => {
+ return this.Ratio();
+ }
+ );
+
+ default:
+ this.error('Number, dimension, ratio or identifier is expected');
+ }
+}
+
+function readComparison(expectColon) {
+ this.skipSC();
+
+ if (this.isDelim(LESSTHANSIGN) ||
+ this.isDelim(GREATERTHANSIGN)) {
+ const value = this.source[this.tokenStart];
+
+ this.next();
+
+ if (this.isDelim(EQUALSSIGN)) {
+ this.next();
+ return value + '=';
+ }
+
+ return value;
+ }
+
+ if (this.isDelim(EQUALSSIGN)) {
+ return '=';
+ }
+
+ this.error(`Expected ${expectColon ? '":", ' : ''}"<", ">", "=" or ")"`);
+}
+
+export function parse(kind = 'unknown') {
+ const start = this.tokenStart;
+
+ this.skipSC();
+ this.eat(LeftParenthesis);
+
+ const left = readTerm.call(this);
+ const leftComparison = readComparison.call(this, left.type === 'Identifier');
+ const middle = readTerm.call(this);
+ let rightComparison = null;
+ let right = null;
+
+ if (this.lookupNonWSType(0) !== RightParenthesis) {
+ rightComparison = readComparison.call(this);
+ right = readTerm.call(this);
+ }
+
+ this.skipSC();
+ this.eat(RightParenthesis);
+
+ return {
+ type: 'FeatureRange',
+ loc: this.getLocation(start, this.tokenStart),
+ kind,
+ left,
+ leftComparison,
+ middle,
+ rightComparison,
+ right
+ };
+}
+
+export function generate(node) {
+ this.token(LeftParenthesis, '(');
+ this.node(node.left);
+ this.tokenize(node.leftComparison);
+ this.node(node.middle);
+
+ if (node.right) {
+ this.tokenize(node.rightComparison);
+ this.node(node.right);
+ }
+
+ this.token(RightParenthesis, ')');
+}
diff --git a/vanilla/node_modules/css-tree/lib/syntax/node/Function.js b/vanilla/node_modules/css-tree/lib/syntax/node/Function.js
new file mode 100644
index 0000000..1fdc414
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/syntax/node/Function.js
@@ -0,0 +1,41 @@
+import {
+ Function as FunctionToken,
+ RightParenthesis
+} from '../../tokenizer/index.js';
+
+
+export const name = 'Function';
+export const walkContext = 'function';
+export const structure = {
+ name: String,
+ children: [[]]
+};
+
+// <function-token> <sequence> )
+export function parse(readSequence, recognizer) {
+ const start = this.tokenStart;
+ const name = this.consumeFunctionName();
+ const nameLowerCase = name.toLowerCase();
+ let children;
+
+ children = recognizer.hasOwnProperty(nameLowerCase)
+ ? recognizer[nameLowerCase].call(this, recognizer)
+ : readSequence.call(this, recognizer);
+
+ if (!this.eof) {
+ this.eat(RightParenthesis);
+ }
+
+ return {
+ type: 'Function',
+ loc: this.getLocation(start, this.tokenStart),
+ name,
+ children
+ };
+}
+
+export function generate(node) {
+ this.token(FunctionToken, node.name + '(');
+ this.children(node);
+ this.token(RightParenthesis, ')');
+}
diff --git a/vanilla/node_modules/css-tree/lib/syntax/node/GeneralEnclosed.js b/vanilla/node_modules/css-tree/lib/syntax/node/GeneralEnclosed.js
new file mode 100644
index 0000000..8ac8cae
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/syntax/node/GeneralEnclosed.js
@@ -0,0 +1,66 @@
+import {
+ Function as FunctionToken,
+ LeftParenthesis,
+ RightParenthesis
+} from '../../tokenizer/index.js';
+
+
+export const name = 'GeneralEnclosed';
+export const structure = {
+ kind: String,
+ function: [String, null],
+ children: [[]]
+};
+
+// <function-token> <any-value> )
+// ( <any-value> )
+export function parse(kind) {
+ const start = this.tokenStart;
+ let functionName = null;
+
+ if (this.tokenType === FunctionToken) {
+ functionName = this.consumeFunctionName();
+ } else {
+ this.eat(LeftParenthesis);
+ }
+
+ const children = this.parseWithFallback(
+ () => {
+ const startValueToken = this.tokenIndex;
+ const children = this.readSequence(this.scope.Value);
+
+ if (this.eof === false &&
+ this.isBalanceEdge(startValueToken) === false) {
+ this.error();
+ }
+
+ return children;
+ },
+ () => this.createSingleNodeList(
+ this.Raw(null, false)
+ )
+ );
+
+ if (!this.eof) {
+ this.eat(RightParenthesis);
+ }
+
+ return {
+ type: 'GeneralEnclosed',
+ loc: this.getLocation(start, this.tokenStart),
+ kind,
+ function: functionName,
+ children
+ };
+}
+
+export function generate(node) {
+ if (node.function) {
+ this.token(FunctionToken, node.function + '(');
+ } else {
+ this.token(LeftParenthesis, '(');
+ }
+
+ this.children(node);
+ this.token(RightParenthesis, ')');
+}
diff --git a/vanilla/node_modules/css-tree/lib/syntax/node/Hash.js b/vanilla/node_modules/css-tree/lib/syntax/node/Hash.js
new file mode 100644
index 0000000..b752752
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/syntax/node/Hash.js
@@ -0,0 +1,23 @@
+import { Hash } from '../../tokenizer/index.js';
+
+// '#' ident
+export const xxx = 'XXX';
+export const name = 'Hash';
+export const structure = {
+ value: String
+};
+export function parse() {
+ const start = this.tokenStart;
+
+ this.eat(Hash);
+
+ return {
+ type: 'Hash',
+ loc: this.getLocation(start, this.tokenStart),
+ value: this.substrToCursor(start + 1)
+ };
+}
+export function generate(node) {
+ this.token(Hash, '#' + node.value);
+}
+
diff --git a/vanilla/node_modules/css-tree/lib/syntax/node/IdSelector.js b/vanilla/node_modules/css-tree/lib/syntax/node/IdSelector.js
new file mode 100644
index 0000000..c85c1b2
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/syntax/node/IdSelector.js
@@ -0,0 +1,26 @@
+import { Hash, Delim } from '../../tokenizer/index.js';
+
+export const name = 'IdSelector';
+export const structure = {
+ name: String
+};
+
+export function parse() {
+ const start = this.tokenStart;
+
+ // TODO: check value is an ident
+ this.eat(Hash);
+
+ return {
+ type: 'IdSelector',
+ loc: this.getLocation(start, this.tokenStart),
+ name: this.substrToCursor(start + 1)
+ };
+}
+
+export function generate(node) {
+ // Using Delim instead of Hash is a hack to avoid for a whitespace between ident and id-selector
+ // in safe mode (e.g. "a#id"), because IE11 doesn't allow a sequence <ident-token> <hash-token>
+ // without a whitespace in values (e.g. "1px solid#000")
+ this.token(Delim, '#' + node.name);
+}
diff --git a/vanilla/node_modules/css-tree/lib/syntax/node/Identifier.js b/vanilla/node_modules/css-tree/lib/syntax/node/Identifier.js
new file mode 100644
index 0000000..067c2d0
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/syntax/node/Identifier.js
@@ -0,0 +1,18 @@
+import { Ident } from '../../tokenizer/index.js';
+
+export const name = 'Identifier';
+export const structure = {
+ name: String
+};
+
+export function parse() {
+ return {
+ type: 'Identifier',
+ loc: this.getLocation(this.tokenStart, this.tokenEnd),
+ name: this.consume(Ident)
+ };
+}
+
+export function generate(node) {
+ this.token(Ident, node.name);
+}
diff --git a/vanilla/node_modules/css-tree/lib/syntax/node/Layer.js b/vanilla/node_modules/css-tree/lib/syntax/node/Layer.js
new file mode 100644
index 0000000..d170dcb
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/syntax/node/Layer.js
@@ -0,0 +1,28 @@
+import { Ident, Delim } from '../../tokenizer/index.js';
+
+const FULLSTOP = 0x002E; // U+002E FULL STOP (.)
+
+export const name = 'Layer';
+export const structure = {
+ name: String
+};
+
+export function parse() {
+ let tokenStart = this.tokenStart;
+ let name = this.consume(Ident);
+
+ while (this.isDelim(FULLSTOP)) {
+ this.eat(Delim);
+ name += '.' + this.consume(Ident);
+ }
+
+ return {
+ type: 'Layer',
+ loc: this.getLocation(tokenStart, this.tokenStart),
+ name
+ };
+}
+
+export function generate(node) {
+ this.tokenize(node.name);
+}
diff --git a/vanilla/node_modules/css-tree/lib/syntax/node/LayerList.js b/vanilla/node_modules/css-tree/lib/syntax/node/LayerList.js
new file mode 100644
index 0000000..e8a05cd
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/syntax/node/LayerList.js
@@ -0,0 +1,36 @@
+import { Comma } from '../../tokenizer/index.js';
+
+export const name = 'LayerList';
+export const structure = {
+ children: [[
+ 'Layer'
+ ]]
+};
+
+export function parse() {
+ const children = this.createList();
+
+ this.skipSC();
+
+ while (!this.eof) {
+ children.push(this.Layer());
+
+ if (this.lookupTypeNonSC(0) !== Comma) {
+ break;
+ }
+
+ this.skipSC();
+ this.next();
+ this.skipSC();
+ }
+
+ return {
+ type: 'LayerList',
+ loc: this.getLocationFromList(children),
+ children
+ };
+}
+
+export function generate(node) {
+ this.children(node, () => this.token(Comma, ','));
+}
diff --git a/vanilla/node_modules/css-tree/lib/syntax/node/MediaQuery.js b/vanilla/node_modules/css-tree/lib/syntax/node/MediaQuery.js
new file mode 100644
index 0000000..f569c75
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/syntax/node/MediaQuery.js
@@ -0,0 +1,102 @@
+import {
+ Comma,
+ EOF,
+ Ident,
+ LeftCurlyBracket,
+ LeftParenthesis,
+ Function as FunctionToken,
+ Semicolon
+} from '../../tokenizer/index.js';
+
+export const name = 'MediaQuery';
+export const structure = {
+ modifier: [String, null],
+ mediaType: [String, null],
+ condition: ['Condition', null]
+};
+
+export function parse() {
+ const start = this.tokenStart;
+ let modifier = null;
+ let mediaType = null;
+ let condition = null;
+
+ this.skipSC();
+
+ if (this.tokenType === Ident && this.lookupTypeNonSC(1) !== LeftParenthesis) {
+ // [ not | only ]? <media-type>
+ const ident = this.consume(Ident);
+ const identLowerCase = ident.toLowerCase();
+
+ if (identLowerCase === 'not' || identLowerCase === 'only') {
+ this.skipSC();
+ modifier = identLowerCase;
+ mediaType = this.consume(Ident);
+ } else {
+ mediaType = ident;
+ }
+
+ switch (this.lookupTypeNonSC(0)) {
+ case Ident: {
+ // and <media-condition-without-or>
+ this.skipSC();
+ this.eatIdent('and');
+ condition = this.Condition('media');
+ break;
+ }
+
+ case LeftCurlyBracket:
+ case Semicolon:
+ case Comma:
+ case EOF:
+ break;
+
+ default:
+ this.error('Identifier or parenthesis is expected');
+ }
+ } else {
+ switch (this.tokenType) {
+ case Ident:
+ case LeftParenthesis:
+ case FunctionToken: {
+ // <media-condition>
+ condition = this.Condition('media');
+ break;
+ }
+
+ case LeftCurlyBracket:
+ case Semicolon:
+ case EOF:
+ break;
+
+ default:
+ this.error('Identifier or parenthesis is expected');
+ }
+ }
+
+ return {
+ type: 'MediaQuery',
+ loc: this.getLocation(start, this.tokenStart),
+ modifier,
+ mediaType,
+ condition
+ };
+}
+
+export function generate(node) {
+ if (node.mediaType) {
+ if (node.modifier) {
+ this.token(Ident, node.modifier);
+ }
+
+ this.token(Ident, node.mediaType);
+
+ if (node.condition) {
+ this.token(Ident, 'and');
+ this.node(node.condition);
+ }
+ } else if (node.condition) {
+ this.node(node.condition);
+ }
+}
+
diff --git a/vanilla/node_modules/css-tree/lib/syntax/node/MediaQueryList.js b/vanilla/node_modules/css-tree/lib/syntax/node/MediaQueryList.js
new file mode 100644
index 0000000..1c16bcd
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/syntax/node/MediaQueryList.js
@@ -0,0 +1,34 @@
+import { Comma } from '../../tokenizer/index.js';
+
+export const name = 'MediaQueryList';
+export const structure = {
+ children: [[
+ 'MediaQuery'
+ ]]
+};
+
+export function parse() {
+ const children = this.createList();
+
+ this.skipSC();
+
+ while (!this.eof) {
+ children.push(this.MediaQuery());
+
+ if (this.tokenType !== Comma) {
+ break;
+ }
+
+ this.next();
+ }
+
+ return {
+ type: 'MediaQueryList',
+ loc: this.getLocationFromList(children),
+ children
+ };
+}
+
+export function generate(node) {
+ this.children(node, () => this.token(Comma, ','));
+}
diff --git a/vanilla/node_modules/css-tree/lib/syntax/node/NestingSelector.js b/vanilla/node_modules/css-tree/lib/syntax/node/NestingSelector.js
new file mode 100644
index 0000000..33c8b6a
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/syntax/node/NestingSelector.js
@@ -0,0 +1,22 @@
+import { Delim } from '../../tokenizer/index.js';
+
+const AMPERSAND = 0x0026; // U+0026 AMPERSAND (&)
+
+export const name = 'NestingSelector';
+export const structure = {
+};
+
+export function parse() {
+ const start = this.tokenStart;
+
+ this.eatDelim(AMPERSAND);
+
+ return {
+ type: 'NestingSelector',
+ loc: this.getLocation(start, this.tokenStart)
+ };
+}
+
+export function generate() {
+ this.token(Delim, '&');
+}
diff --git a/vanilla/node_modules/css-tree/lib/syntax/node/Nth.js b/vanilla/node_modules/css-tree/lib/syntax/node/Nth.js
new file mode 100644
index 0000000..bfd74a1
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/syntax/node/Nth.js
@@ -0,0 +1,47 @@
+import { Ident } from '../../tokenizer/index.js';
+
+export const name = 'Nth';
+export const structure = {
+ nth: ['AnPlusB', 'Identifier'],
+ selector: ['SelectorList', null]
+};
+
+export function parse() {
+ this.skipSC();
+
+ const start = this.tokenStart;
+ let end = start;
+ let selector = null;
+ let nth;
+
+ if (this.lookupValue(0, 'odd') || this.lookupValue(0, 'even')) {
+ nth = this.Identifier();
+ } else {
+ nth = this.AnPlusB();
+ }
+
+ end = this.tokenStart;
+ this.skipSC();
+
+ if (this.lookupValue(0, 'of')) {
+ this.next();
+
+ selector = this.SelectorList();
+ end = this.tokenStart;
+ }
+
+ return {
+ type: 'Nth',
+ loc: this.getLocation(start, end),
+ nth,
+ selector
+ };
+}
+
+export function generate(node) {
+ this.node(node.nth);
+ if (node.selector !== null) {
+ this.token(Ident, 'of');
+ this.node(node.selector);
+ }
+}
diff --git a/vanilla/node_modules/css-tree/lib/syntax/node/Number.js b/vanilla/node_modules/css-tree/lib/syntax/node/Number.js
new file mode 100644
index 0000000..a9d8f0a
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/syntax/node/Number.js
@@ -0,0 +1,18 @@
+import { Number as NumberToken } from '../../tokenizer/index.js';
+
+export const name = 'Number';
+export const structure = {
+ value: String
+};
+
+export function parse() {
+ return {
+ type: 'Number',
+ loc: this.getLocation(this.tokenStart, this.tokenEnd),
+ value: this.consume(NumberToken)
+ };
+}
+
+export function generate(node) {
+ this.token(NumberToken, node.value);
+}
diff --git a/vanilla/node_modules/css-tree/lib/syntax/node/Operator.js b/vanilla/node_modules/css-tree/lib/syntax/node/Operator.js
new file mode 100644
index 0000000..4f1238b
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/syntax/node/Operator.js
@@ -0,0 +1,21 @@
+// '/' | '*' | ',' | ':' | '+' | '-'
+export const name = 'Operator';
+export const structure = {
+ value: String
+};
+
+export function parse() {
+ const start = this.tokenStart;
+
+ this.next();
+
+ return {
+ type: 'Operator',
+ loc: this.getLocation(start, this.tokenStart),
+ value: this.substrToCursor(start)
+ };
+}
+
+export function generate(node) {
+ this.tokenize(node.value);
+}
diff --git a/vanilla/node_modules/css-tree/lib/syntax/node/Parentheses.js b/vanilla/node_modules/css-tree/lib/syntax/node/Parentheses.js
new file mode 100644
index 0000000..8c6cdb5
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/syntax/node/Parentheses.js
@@ -0,0 +1,34 @@
+import {
+ LeftParenthesis,
+ RightParenthesis
+} from '../../tokenizer/index.js';
+
+export const name = 'Parentheses';
+export const structure = {
+ children: [[]]
+};
+
+export function parse(readSequence, recognizer) {
+ const start = this.tokenStart;
+ let children = null;
+
+ this.eat(LeftParenthesis);
+
+ children = readSequence.call(this, recognizer);
+
+ if (!this.eof) {
+ this.eat(RightParenthesis);
+ }
+
+ return {
+ type: 'Parentheses',
+ loc: this.getLocation(start, this.tokenStart),
+ children
+ };
+}
+
+export function generate(node) {
+ this.token(LeftParenthesis, '(');
+ this.children(node);
+ this.token(RightParenthesis, ')');
+}
diff --git a/vanilla/node_modules/css-tree/lib/syntax/node/Percentage.js b/vanilla/node_modules/css-tree/lib/syntax/node/Percentage.js
new file mode 100644
index 0000000..3c8b628
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/syntax/node/Percentage.js
@@ -0,0 +1,18 @@
+import { Percentage } from '../../tokenizer/index.js';
+
+export const name = 'Percentage';
+export const structure = {
+ value: String
+};
+
+export function parse() {
+ return {
+ type: 'Percentage',
+ loc: this.getLocation(this.tokenStart, this.tokenEnd),
+ value: this.consumeNumber(Percentage)
+ };
+}
+
+export function generate(node) {
+ this.token(Percentage, node.value + '%');
+}
diff --git a/vanilla/node_modules/css-tree/lib/syntax/node/PseudoClassSelector.js b/vanilla/node_modules/css-tree/lib/syntax/node/PseudoClassSelector.js
new file mode 100644
index 0000000..584546c
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/syntax/node/PseudoClassSelector.js
@@ -0,0 +1,65 @@
+import {
+ Ident,
+ Function as FunctionToken,
+ Colon,
+ RightParenthesis
+} from '../../tokenizer/index.js';
+
+
+export const name = 'PseudoClassSelector';
+export const walkContext = 'function';
+export const structure = {
+ name: String,
+ children: [['Raw'], null]
+};
+
+// : [ <ident> | <function-token> <any-value>? ) ]
+export function parse() {
+ const start = this.tokenStart;
+ let children = null;
+ let name;
+ let nameLowerCase;
+
+ this.eat(Colon);
+
+ if (this.tokenType === FunctionToken) {
+ name = this.consumeFunctionName();
+ nameLowerCase = name.toLowerCase();
+
+ if (this.lookupNonWSType(0) == RightParenthesis) {
+ children = this.createList();
+ } else if (hasOwnProperty.call(this.pseudo, nameLowerCase)) {
+ this.skipSC();
+ children = this.pseudo[nameLowerCase].call(this);
+ this.skipSC();
+ } else {
+ children = this.createList();
+ children.push(
+ this.Raw(null, false)
+ );
+ }
+
+ this.eat(RightParenthesis);
+ } else {
+ name = this.consume(Ident);
+ }
+
+ return {
+ type: 'PseudoClassSelector',
+ loc: this.getLocation(start, this.tokenStart),
+ name,
+ children
+ };
+}
+
+export function generate(node) {
+ this.token(Colon, ':');
+
+ if (node.children === null) {
+ this.token(Ident, node.name);
+ } else {
+ this.token(FunctionToken, node.name + '(');
+ this.children(node);
+ this.token(RightParenthesis, ')');
+ }
+}
diff --git a/vanilla/node_modules/css-tree/lib/syntax/node/PseudoElementSelector.js b/vanilla/node_modules/css-tree/lib/syntax/node/PseudoElementSelector.js
new file mode 100644
index 0000000..b23b9a0
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/syntax/node/PseudoElementSelector.js
@@ -0,0 +1,66 @@
+import {
+ Ident,
+ Function as FunctionToken,
+ Colon,
+ RightParenthesis
+} from '../../tokenizer/index.js';
+
+export const name = 'PseudoElementSelector';
+export const walkContext = 'function';
+export const structure = {
+ name: String,
+ children: [['Raw'], null]
+};
+
+// :: [ <ident> | <function-token> <any-value>? ) ]
+export function parse() {
+ const start = this.tokenStart;
+ let children = null;
+ let name;
+ let nameLowerCase;
+
+ this.eat(Colon);
+ this.eat(Colon);
+
+ if (this.tokenType === FunctionToken) {
+ name = this.consumeFunctionName();
+ nameLowerCase = name.toLowerCase();
+
+ if (this.lookupNonWSType(0) == RightParenthesis) {
+ children = this.createList();
+ } else if (hasOwnProperty.call(this.pseudo, nameLowerCase)) {
+ this.skipSC();
+ children = this.pseudo[nameLowerCase].call(this);
+ this.skipSC();
+ } else {
+ children = this.createList();
+ children.push(
+ this.Raw(null, false)
+ );
+ }
+
+ this.eat(RightParenthesis);
+ } else {
+ name = this.consume(Ident);
+ }
+
+ return {
+ type: 'PseudoElementSelector',
+ loc: this.getLocation(start, this.tokenStart),
+ name,
+ children
+ };
+}
+
+export function generate(node) {
+ this.token(Colon, ':');
+ this.token(Colon, ':');
+
+ if (node.children === null) {
+ this.token(Ident, node.name);
+ } else {
+ this.token(FunctionToken, node.name + '(');
+ this.children(node);
+ this.token(RightParenthesis, ')');
+ }
+}
diff --git a/vanilla/node_modules/css-tree/lib/syntax/node/Ratio.js b/vanilla/node_modules/css-tree/lib/syntax/node/Ratio.js
new file mode 100644
index 0000000..c26cf10
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/syntax/node/Ratio.js
@@ -0,0 +1,68 @@
+import {
+ Delim,
+ Number as NumberToken,
+ Function as FunctionToken
+} from '../../tokenizer/index.js';
+
+const SOLIDUS = 0x002F; // U+002F SOLIDUS (/)
+
+// Media Queries Level 3 defines terms of <ratio> as a positive (not zero or negative)
+// integers (see https://drafts.csswg.org/mediaqueries-3/#values)
+// However, Media Queries Level 4 removes any definition of values
+// (see https://drafts.csswg.org/mediaqueries-4/#values) and refers to
+// CSS Values and Units for detail. In CSS Values and Units Level 4 a <ratio>
+// definition was added (see https://drafts.csswg.org/css-values-4/#ratios) which
+// defines ratio as "<number [0,∞]> [ / <number [0,∞]> ]?" and based on it
+// any constrains on terms were removed. Parser also doesn't test numbers
+// in any way to make possible for linting and fixing them by the tools using CSSTree.
+// An additional syntax examination may be applied by a lexer.
+function consumeTerm() {
+ this.skipSC();
+
+ switch (this.tokenType) {
+ case NumberToken:
+ return this.Number();
+
+ case FunctionToken:
+ return this.Function(this.readSequence, this.scope.Value);
+
+ default:
+ this.error('Number of function is expected');
+ }
+}
+
+export const name = 'Ratio';
+export const structure = {
+ left: ['Number', 'Function'],
+ right: ['Number', 'Function', null]
+};
+
+// <number [0,∞]> [ / <number [0,∞]> ]?
+export function parse() {
+ const start = this.tokenStart;
+ const left = consumeTerm.call(this);
+ let right = null;
+
+ this.skipSC();
+ if (this.isDelim(SOLIDUS)) {
+ this.eatDelim(SOLIDUS);
+ right = consumeTerm.call(this);
+ }
+
+ return {
+ type: 'Ratio',
+ loc: this.getLocation(start, this.tokenStart),
+ left,
+ right
+ };
+}
+
+export function generate(node) {
+ this.node(node.left);
+ this.token(Delim, '/');
+ if (node.right) {
+ this.node(node.right);
+ } else {
+ this.node(NumberToken, 1);
+ }
+}
diff --git a/vanilla/node_modules/css-tree/lib/syntax/node/Raw.js b/vanilla/node_modules/css-tree/lib/syntax/node/Raw.js
new file mode 100644
index 0000000..0c2ea69
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/syntax/node/Raw.js
@@ -0,0 +1,41 @@
+import { WhiteSpace } from '../../tokenizer/index.js';
+
+function getOffsetExcludeWS() {
+ if (this.tokenIndex > 0) {
+ if (this.lookupType(-1) === WhiteSpace) {
+ return this.tokenIndex > 1
+ ? this.getTokenStart(this.tokenIndex - 1)
+ : this.firstCharOffset;
+ }
+ }
+
+ return this.tokenStart;
+}
+
+export const name = 'Raw';
+export const structure = {
+ value: String
+};
+
+export function parse(consumeUntil, excludeWhiteSpace) {
+ const startOffset = this.getTokenStart(this.tokenIndex);
+ let endOffset;
+
+ this.skipUntilBalanced(this.tokenIndex, consumeUntil || this.consumeUntilBalanceEnd);
+
+ if (excludeWhiteSpace && this.tokenStart > startOffset) {
+ endOffset = getOffsetExcludeWS.call(this);
+ } else {
+ endOffset = this.tokenStart;
+ }
+
+ return {
+ type: 'Raw',
+ loc: this.getLocation(startOffset, endOffset),
+ value: this.substring(startOffset, endOffset)
+ };
+}
+
+export function generate(node) {
+ this.tokenize(node.value);
+}
diff --git a/vanilla/node_modules/css-tree/lib/syntax/node/Rule.js b/vanilla/node_modules/css-tree/lib/syntax/node/Rule.js
new file mode 100644
index 0000000..89745cf
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/syntax/node/Rule.js
@@ -0,0 +1,51 @@
+import { LeftCurlyBracket } from '../../tokenizer/index.js';
+
+function consumeRaw() {
+ return this.Raw(this.consumeUntilLeftCurlyBracket, true);
+}
+
+function consumePrelude() {
+ const prelude = this.SelectorList();
+
+ if (prelude.type !== 'Raw' &&
+ this.eof === false &&
+ this.tokenType !== LeftCurlyBracket) {
+ this.error();
+ }
+
+ return prelude;
+}
+
+export const name = 'Rule';
+export const walkContext = 'rule';
+export const structure = {
+ prelude: ['SelectorList', 'Raw'],
+ block: ['Block']
+};
+
+export function parse() {
+ const startToken = this.tokenIndex;
+ const startOffset = this.tokenStart;
+ let prelude;
+ let block;
+
+ if (this.parseRulePrelude) {
+ prelude = this.parseWithFallback(consumePrelude, consumeRaw);
+ } else {
+ prelude = consumeRaw.call(this, startToken);
+ }
+
+ block = this.Block(true);
+
+ return {
+ type: 'Rule',
+ loc: this.getLocation(startOffset, this.tokenStart),
+ prelude,
+ block
+ };
+}
+export function generate(node) {
+ this.node(node.prelude);
+ this.node(node.block);
+}
+
diff --git a/vanilla/node_modules/css-tree/lib/syntax/node/Scope.js b/vanilla/node_modules/css-tree/lib/syntax/node/Scope.js
new file mode 100644
index 0000000..650b035
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/syntax/node/Scope.js
@@ -0,0 +1,66 @@
+import {
+ Ident,
+ LeftParenthesis,
+ RightParenthesis
+} from '../../tokenizer/index.js';
+
+export const name = 'Scope';
+export const structure = {
+ root: ['SelectorList', 'Raw', null],
+ limit: ['SelectorList', 'Raw', null]
+};
+
+export function parse() {
+ let root = null;
+ let limit = null;
+
+ this.skipSC();
+
+ const startOffset = this.tokenStart;
+ if (this.tokenType === LeftParenthesis) {
+ this.next();
+ this.skipSC();
+ root = this.parseWithFallback(
+ this.SelectorList,
+ () => this.Raw(false, true)
+ );
+ this.skipSC();
+ this.eat(RightParenthesis);
+ }
+
+ if (this.lookupNonWSType(0) === Ident) {
+ this.skipSC();
+ this.eatIdent('to');
+ this.skipSC();
+ this.eat(LeftParenthesis);
+ this.skipSC();
+ limit = this.parseWithFallback(
+ this.SelectorList,
+ () => this.Raw(false, true)
+ );
+ this.skipSC();
+ this.eat(RightParenthesis);
+ }
+
+ return {
+ type: 'Scope',
+ loc: this.getLocation(startOffset, this.tokenStart),
+ root,
+ limit
+ };
+}
+
+export function generate(node) {
+ if (node.root) {
+ this.token(LeftParenthesis, '(');
+ this.node(node.root);
+ this.token(RightParenthesis, ')');
+ }
+
+ if (node.limit) {
+ this.token(Ident, 'to');
+ this.token(LeftParenthesis, '(');
+ this.node(node.limit);
+ this.token(RightParenthesis, ')');
+ }
+}
diff --git a/vanilla/node_modules/css-tree/lib/syntax/node/Selector.js b/vanilla/node_modules/css-tree/lib/syntax/node/Selector.js
new file mode 100644
index 0000000..36028e0
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/syntax/node/Selector.js
@@ -0,0 +1,31 @@
+export const name = 'Selector';
+export const structure = {
+ children: [[
+ 'TypeSelector',
+ 'IdSelector',
+ 'ClassSelector',
+ 'AttributeSelector',
+ 'PseudoClassSelector',
+ 'PseudoElementSelector',
+ 'Combinator'
+ ]]
+};
+
+export function parse() {
+ const children = this.readSequence(this.scope.Selector);
+
+ // nothing were consumed
+ if (this.getFirstListNode(children) === null) {
+ this.error('Selector is expected');
+ }
+
+ return {
+ type: 'Selector',
+ loc: this.getLocationFromList(children),
+ children
+ };
+}
+
+export function generate(node) {
+ this.children(node);
+}
diff --git a/vanilla/node_modules/css-tree/lib/syntax/node/SelectorList.js b/vanilla/node_modules/css-tree/lib/syntax/node/SelectorList.js
new file mode 100644
index 0000000..ebba3cd
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/syntax/node/SelectorList.js
@@ -0,0 +1,35 @@
+import { Comma } from '../../tokenizer/index.js';
+
+export const name = 'SelectorList';
+export const walkContext = 'selector';
+export const structure = {
+ children: [[
+ 'Selector',
+ 'Raw'
+ ]]
+};
+
+export function parse() {
+ const children = this.createList();
+
+ while (!this.eof) {
+ children.push(this.Selector());
+
+ if (this.tokenType === Comma) {
+ this.next();
+ continue;
+ }
+
+ break;
+ }
+
+ return {
+ type: 'SelectorList',
+ loc: this.getLocationFromList(children),
+ children
+ };
+}
+
+export function generate(node) {
+ this.children(node, () => this.token(Comma, ','));
+}
diff --git a/vanilla/node_modules/css-tree/lib/syntax/node/String.js b/vanilla/node_modules/css-tree/lib/syntax/node/String.js
new file mode 100644
index 0000000..95e1aaa
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/syntax/node/String.js
@@ -0,0 +1,19 @@
+import { String as StringToken } from '../../tokenizer/index.js';
+import { decode, encode } from '../../utils/string.js';
+
+export const name = 'String';
+export const structure = {
+ value: String
+};
+
+export function parse() {
+ return {
+ type: 'String',
+ loc: this.getLocation(this.tokenStart, this.tokenEnd),
+ value: decode(this.consume(StringToken))
+ };
+}
+
+export function generate(node) {
+ this.token(StringToken, encode(node.value));
+}
diff --git a/vanilla/node_modules/css-tree/lib/syntax/node/StyleSheet.js b/vanilla/node_modules/css-tree/lib/syntax/node/StyleSheet.js
new file mode 100644
index 0000000..3bc5347
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/syntax/node/StyleSheet.js
@@ -0,0 +1,82 @@
+import {
+ WhiteSpace,
+ Comment,
+ AtKeyword,
+ CDO,
+ CDC
+} from '../../tokenizer/index.js';
+
+const EXCLAMATIONMARK = 0x0021; // U+0021 EXCLAMATION MARK (!)
+
+function consumeRaw() {
+ return this.Raw(null, false);
+}
+
+export const name = 'StyleSheet';
+export const walkContext = 'stylesheet';
+export const structure = {
+ children: [[
+ 'Comment',
+ 'CDO',
+ 'CDC',
+ 'Atrule',
+ 'Rule',
+ 'Raw'
+ ]]
+};
+
+export function parse() {
+ const start = this.tokenStart;
+ const children = this.createList();
+ let child;
+
+ scan:
+ while (!this.eof) {
+ switch (this.tokenType) {
+ case WhiteSpace:
+ this.next();
+ continue;
+
+ case Comment:
+ // ignore comments except exclamation comments (i.e. /*! .. */) on top level
+ if (this.charCodeAt(this.tokenStart + 2) !== EXCLAMATIONMARK) {
+ this.next();
+ continue;
+ }
+
+ child = this.Comment();
+ break;
+
+ case CDO: // <!--
+ child = this.CDO();
+ break;
+
+ case CDC: // -->
+ child = this.CDC();
+ break;
+
+ // CSS Syntax Module Level 3
+ // §2.2 Error handling
+ // At the "top level" of a stylesheet, an <at-keyword-token> starts an at-rule.
+ case AtKeyword:
+ child = this.parseWithFallback(this.Atrule, consumeRaw);
+ break;
+
+ // Anything else starts a qualified rule ...
+ default:
+ child = this.parseWithFallback(this.Rule, consumeRaw);
+ }
+
+ children.push(child);
+ }
+
+ return {
+ type: 'StyleSheet',
+ loc: this.getLocation(start, this.tokenStart),
+ children
+ };
+}
+
+export function generate(node) {
+ this.children(node);
+}
diff --git a/vanilla/node_modules/css-tree/lib/syntax/node/SupportsDeclaration.js b/vanilla/node_modules/css-tree/lib/syntax/node/SupportsDeclaration.js
new file mode 100644
index 0000000..ee816e5
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/syntax/node/SupportsDeclaration.js
@@ -0,0 +1,34 @@
+import {
+ LeftParenthesis,
+ RightParenthesis
+} from '../../tokenizer/index.js';
+
+export const name = 'SupportsDeclaration';
+export const structure = {
+ declaration: 'Declaration'
+};
+
+export function parse() {
+ const start = this.tokenStart;
+
+ this.eat(LeftParenthesis);
+ this.skipSC();
+
+ const declaration = this.Declaration();
+
+ if (!this.eof) {
+ this.eat(RightParenthesis);
+ }
+
+ return {
+ type: 'SupportsDeclaration',
+ loc: this.getLocation(start, this.tokenStart),
+ declaration
+ };
+}
+
+export function generate(node) {
+ this.token(LeftParenthesis, '(');
+ this.node(node.declaration);
+ this.token(RightParenthesis, ')');
+}
diff --git a/vanilla/node_modules/css-tree/lib/syntax/node/TypeSelector.js b/vanilla/node_modules/css-tree/lib/syntax/node/TypeSelector.js
new file mode 100644
index 0000000..272e195
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/syntax/node/TypeSelector.js
@@ -0,0 +1,52 @@
+import { Ident } from '../../tokenizer/index.js';
+
+const ASTERISK = 0x002A; // U+002A ASTERISK (*)
+const VERTICALLINE = 0x007C; // U+007C VERTICAL LINE (|)
+
+function eatIdentifierOrAsterisk() {
+ if (this.tokenType !== Ident &&
+ this.isDelim(ASTERISK) === false) {
+ this.error('Identifier or asterisk is expected');
+ }
+
+ this.next();
+}
+
+export const name = 'TypeSelector';
+export const structure = {
+ name: String
+};
+
+// ident
+// ident|ident
+// ident|*
+// *
+// *|ident
+// *|*
+// |ident
+// |*
+export function parse() {
+ const start = this.tokenStart;
+
+ if (this.isDelim(VERTICALLINE)) {
+ this.next();
+ eatIdentifierOrAsterisk.call(this);
+ } else {
+ eatIdentifierOrAsterisk.call(this);
+
+ if (this.isDelim(VERTICALLINE)) {
+ this.next();
+ eatIdentifierOrAsterisk.call(this);
+ }
+ }
+
+ return {
+ type: 'TypeSelector',
+ loc: this.getLocation(start, this.tokenStart),
+ name: this.substrToCursor(start)
+ };
+}
+
+export function generate(node) {
+ this.tokenize(node.name);
+}
diff --git a/vanilla/node_modules/css-tree/lib/syntax/node/UnicodeRange.js b/vanilla/node_modules/css-tree/lib/syntax/node/UnicodeRange.js
new file mode 100644
index 0000000..95ee8b9
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/syntax/node/UnicodeRange.js
@@ -0,0 +1,156 @@
+import {
+ isHexDigit,
+ Ident,
+ Number,
+ Dimension
+} from '../../tokenizer/index.js';
+
+const PLUSSIGN = 0x002B; // U+002B PLUS SIGN (+)
+const HYPHENMINUS = 0x002D; // U+002D HYPHEN-MINUS (-)
+const QUESTIONMARK = 0x003F; // U+003F QUESTION MARK (?)
+
+function eatHexSequence(offset, allowDash) {
+ let len = 0;
+
+ for (let pos = this.tokenStart + offset; pos < this.tokenEnd; pos++) {
+ const code = this.charCodeAt(pos);
+
+ if (code === HYPHENMINUS && allowDash && len !== 0) {
+ eatHexSequence.call(this, offset + len + 1, false);
+ return -1;
+ }
+
+ if (!isHexDigit(code)) {
+ this.error(
+ allowDash && len !== 0
+ ? 'Hyphen minus' + (len < 6 ? ' or hex digit' : '') + ' is expected'
+ : (len < 6 ? 'Hex digit is expected' : 'Unexpected input'),
+ pos
+ );
+ }
+
+ if (++len > 6) {
+ this.error('Too many hex digits', pos);
+ };
+ }
+
+ this.next();
+ return len;
+}
+
+function eatQuestionMarkSequence(max) {
+ let count = 0;
+
+ while (this.isDelim(QUESTIONMARK)) {
+ if (++count > max) {
+ this.error('Too many question marks');
+ }
+
+ this.next();
+ }
+}
+
+function startsWith(code) {
+ if (this.charCodeAt(this.tokenStart) !== code) {
+ this.error((code === PLUSSIGN ? 'Plus sign' : 'Hyphen minus') + ' is expected');
+ }
+}
+
+// https://drafts.csswg.org/css-syntax/#urange
+// Informally, the <urange> production has three forms:
+// U+0001
+// Defines a range consisting of a single code point, in this case the code point "1".
+// U+0001-00ff
+// Defines a range of codepoints between the first and the second value, in this case
+// the range between "1" and "ff" (255 in decimal) inclusive.
+// U+00??
+// Defines a range of codepoints where the "?" characters range over all hex digits,
+// in this case defining the same as the value U+0000-00ff.
+// In each form, a maximum of 6 digits is allowed for each hexadecimal number (if you treat "?" as a hexadecimal digit).
+//
+// <urange> =
+// u '+' <ident-token> '?'* |
+// u <dimension-token> '?'* |
+// u <number-token> '?'* |
+// u <number-token> <dimension-token> |
+// u <number-token> <number-token> |
+// u '+' '?'+
+function scanUnicodeRange() {
+ let hexLength = 0;
+
+ switch (this.tokenType) {
+ case Number:
+ // u <number-token> '?'*
+ // u <number-token> <dimension-token>
+ // u <number-token> <number-token>
+ hexLength = eatHexSequence.call(this, 1, true);
+
+ if (this.isDelim(QUESTIONMARK)) {
+ eatQuestionMarkSequence.call(this, 6 - hexLength);
+ break;
+ }
+
+ if (this.tokenType === Dimension ||
+ this.tokenType === Number) {
+ startsWith.call(this, HYPHENMINUS);
+ eatHexSequence.call(this, 1, false);
+ break;
+ }
+
+ break;
+
+ case Dimension:
+ // u <dimension-token> '?'*
+ hexLength = eatHexSequence.call(this, 1, true);
+
+ if (hexLength > 0) {
+ eatQuestionMarkSequence.call(this, 6 - hexLength);
+ }
+
+ break;
+
+ default:
+ // u '+' <ident-token> '?'*
+ // u '+' '?'+
+ this.eatDelim(PLUSSIGN);
+
+ if (this.tokenType === Ident) {
+ hexLength = eatHexSequence.call(this, 0, true);
+ if (hexLength > 0) {
+ eatQuestionMarkSequence.call(this, 6 - hexLength);
+ }
+ break;
+ }
+
+ if (this.isDelim(QUESTIONMARK)) {
+ this.next();
+ eatQuestionMarkSequence.call(this, 5);
+ break;
+ }
+
+ this.error('Hex digit or question mark is expected');
+ }
+}
+
+export const name = 'UnicodeRange';
+export const structure = {
+ value: String
+};
+
+export function parse() {
+ const start = this.tokenStart;
+
+ // U or u
+ this.eatIdent('u');
+ scanUnicodeRange.call(this);
+
+ return {
+ type: 'UnicodeRange',
+ loc: this.getLocation(start, this.tokenStart),
+ value: this.substrToCursor(start)
+ };
+}
+
+export function generate(node) {
+ this.tokenize(node.value);
+}
diff --git a/vanilla/node_modules/css-tree/lib/syntax/node/Url.js b/vanilla/node_modules/css-tree/lib/syntax/node/Url.js
new file mode 100644
index 0000000..ac52c9d
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/syntax/node/Url.js
@@ -0,0 +1,52 @@
+import * as url from '../../utils/url.js';
+import * as string from '../../utils/string.js';
+import {
+ Function as FunctionToken,
+ String as StringToken,
+ Url,
+ RightParenthesis
+} from '../../tokenizer/index.js';
+
+export const name = 'Url';
+export const structure = {
+ value: String
+};
+
+// <url-token> | <function-token> <string> )
+export function parse() {
+ const start = this.tokenStart;
+ let value;
+
+ switch (this.tokenType) {
+ case Url:
+ value = url.decode(this.consume(Url));
+ break;
+
+ case FunctionToken:
+ if (!this.cmpStr(this.tokenStart, this.tokenEnd, 'url(')) {
+ this.error('Function name must be `url`');
+ }
+
+ this.eat(FunctionToken);
+ this.skipSC();
+ value = string.decode(this.consume(StringToken));
+ this.skipSC();
+ if (!this.eof) {
+ this.eat(RightParenthesis);
+ }
+ break;
+
+ default:
+ this.error('Url or Function is expected');
+ }
+
+ return {
+ type: 'Url',
+ loc: this.getLocation(start, this.tokenStart),
+ value
+ };
+}
+
+export function generate(node) {
+ this.token(Url, url.encode(node.value));
+}
diff --git a/vanilla/node_modules/css-tree/lib/syntax/node/Value.js b/vanilla/node_modules/css-tree/lib/syntax/node/Value.js
new file mode 100644
index 0000000..ba465bc
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/syntax/node/Value.js
@@ -0,0 +1,19 @@
+export const name = 'Value';
+export const structure = {
+ children: [[]]
+};
+
+export function parse() {
+ const start = this.tokenStart;
+ const children = this.readSequence(this.scope.Value);
+
+ return {
+ type: 'Value',
+ loc: this.getLocation(start, this.tokenStart),
+ children
+ };
+}
+
+export function generate(node) {
+ this.children(node);
+}
diff --git a/vanilla/node_modules/css-tree/lib/syntax/node/WhiteSpace.js b/vanilla/node_modules/css-tree/lib/syntax/node/WhiteSpace.js
new file mode 100644
index 0000000..df34e6f
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/syntax/node/WhiteSpace.js
@@ -0,0 +1,27 @@
+import { WhiteSpace } from '../../tokenizer/index.js';
+
+const SPACE = Object.freeze({
+ type: 'WhiteSpace',
+ loc: null,
+ value: ' '
+});
+
+export const name = 'WhiteSpace';
+export const structure = {
+ value: String
+};
+
+export function parse() {
+ this.eat(WhiteSpace);
+ return SPACE;
+
+ // return {
+ // type: 'WhiteSpace',
+ // loc: this.getLocation(this.tokenStart, this.tokenEnd),
+ // value: this.consume(WHITESPACE)
+ // };
+}
+
+export function generate(node) {
+ this.token(WhiteSpace, node.value);
+}
diff --git a/vanilla/node_modules/css-tree/lib/syntax/node/index-generate.js b/vanilla/node_modules/css-tree/lib/syntax/node/index-generate.js
new file mode 100644
index 0000000..568736f
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/syntax/node/index-generate.js
@@ -0,0 +1,49 @@
+export { generate as AnPlusB } from './AnPlusB.js';
+export { generate as Atrule } from './Atrule.js';
+export { generate as AtrulePrelude } from './AtrulePrelude.js';
+export { generate as AttributeSelector } from './AttributeSelector.js';
+export { generate as Block } from './Block.js';
+export { generate as Brackets } from './Brackets.js';
+export { generate as CDC } from './CDC.js';
+export { generate as CDO } from './CDO.js';
+export { generate as ClassSelector } from './ClassSelector.js';
+export { generate as Combinator } from './Combinator.js';
+export { generate as Comment } from './Comment.js';
+export { generate as Condition } from './Condition.js';
+export { generate as Declaration } from './Declaration.js';
+export { generate as DeclarationList } from './DeclarationList.js';
+export { generate as Dimension } from './Dimension.js';
+export { generate as Feature } from './Feature.js';
+export { generate as FeatureFunction } from './FeatureFunction.js';
+export { generate as FeatureRange } from './FeatureRange.js';
+export { generate as Function } from './Function.js';
+export { generate as GeneralEnclosed } from './GeneralEnclosed.js';
+export { generate as Hash } from './Hash.js';
+export { generate as Identifier } from './Identifier.js';
+export { generate as IdSelector } from './IdSelector.js';
+export { generate as Layer } from './Layer.js';
+export { generate as LayerList } from './LayerList.js';
+export { generate as MediaQuery } from './MediaQuery.js';
+export { generate as MediaQueryList } from './MediaQueryList.js';
+export { generate as NestingSelector } from './NestingSelector.js';
+export { generate as Nth } from './Nth.js';
+export { generate as Number } from './Number.js';
+export { generate as Operator } from './Operator.js';
+export { generate as Parentheses } from './Parentheses.js';
+export { generate as Percentage } from './Percentage.js';
+export { generate as PseudoClassSelector } from './PseudoClassSelector.js';
+export { generate as PseudoElementSelector } from './PseudoElementSelector.js';
+export { generate as Ratio } from './Ratio.js';
+export { generate as Raw } from './Raw.js';
+export { generate as Rule } from './Rule.js';
+export { generate as Scope } from './Scope.js';
+export { generate as Selector } from './Selector.js';
+export { generate as SelectorList } from './SelectorList.js';
+export { generate as String } from './String.js';
+export { generate as StyleSheet } from './StyleSheet.js';
+export { generate as SupportsDeclaration } from './SupportsDeclaration.js';
+export { generate as TypeSelector } from './TypeSelector.js';
+export { generate as UnicodeRange } from './UnicodeRange.js';
+export { generate as Url } from './Url.js';
+export { generate as Value } from './Value.js';
+export { generate as WhiteSpace } from './WhiteSpace.js';
diff --git a/vanilla/node_modules/css-tree/lib/syntax/node/index-parse-selector.js b/vanilla/node_modules/css-tree/lib/syntax/node/index-parse-selector.js
new file mode 100644
index 0000000..3a0a2a3
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/syntax/node/index-parse-selector.js
@@ -0,0 +1,17 @@
+export { parse as AnPlusB } from './AnPlusB.js';
+export { parse as AttributeSelector } from './AttributeSelector.js';
+export { parse as ClassSelector } from './ClassSelector.js';
+export { parse as Combinator } from './Combinator.js';
+export { parse as Identifier } from './Identifier.js';
+export { parse as IdSelector } from './IdSelector.js';
+export { parse as NestingSelector } from './NestingSelector.js';
+export { parse as Nth } from './Nth.js';
+export { parse as Operator } from './Operator.js';
+export { parse as Percentage } from './Percentage.js';
+export { parse as PseudoClassSelector } from './PseudoClassSelector.js';
+export { parse as PseudoElementSelector } from './PseudoElementSelector.js';
+export { parse as Raw } from './Raw.js';
+export { parse as Selector } from './Selector.js';
+export { parse as SelectorList } from './SelectorList.js';
+export { parse as String } from './String.js';
+export { parse as TypeSelector } from './TypeSelector.js';
diff --git a/vanilla/node_modules/css-tree/lib/syntax/node/index-parse.js b/vanilla/node_modules/css-tree/lib/syntax/node/index-parse.js
new file mode 100644
index 0000000..80136d3
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/syntax/node/index-parse.js
@@ -0,0 +1,49 @@
+export { parse as AnPlusB } from './AnPlusB.js';
+export { parse as Atrule } from './Atrule.js';
+export { parse as AtrulePrelude } from './AtrulePrelude.js';
+export { parse as AttributeSelector } from './AttributeSelector.js';
+export { parse as Block } from './Block.js';
+export { parse as Brackets } from './Brackets.js';
+export { parse as CDC } from './CDC.js';
+export { parse as CDO } from './CDO.js';
+export { parse as ClassSelector } from './ClassSelector.js';
+export { parse as Combinator } from './Combinator.js';
+export { parse as Comment } from './Comment.js';
+export { parse as Condition } from './Condition.js';
+export { parse as Declaration } from './Declaration.js';
+export { parse as DeclarationList } from './DeclarationList.js';
+export { parse as Dimension } from './Dimension.js';
+export { parse as Feature } from './Feature.js';
+export { parse as FeatureFunction } from './FeatureFunction.js';
+export { parse as FeatureRange } from './FeatureRange.js';
+export { parse as Function } from './Function.js';
+export { parse as GeneralEnclosed } from './GeneralEnclosed.js';
+export { parse as Hash } from './Hash.js';
+export { parse as Identifier } from './Identifier.js';
+export { parse as IdSelector } from './IdSelector.js';
+export { parse as Layer } from './Layer.js';
+export { parse as LayerList } from './LayerList.js';
+export { parse as MediaQuery } from './MediaQuery.js';
+export { parse as MediaQueryList } from './MediaQueryList.js';
+export { parse as NestingSelector } from './NestingSelector.js';
+export { parse as Nth } from './Nth.js';
+export { parse as Number } from './Number.js';
+export { parse as Operator } from './Operator.js';
+export { parse as Parentheses } from './Parentheses.js';
+export { parse as Percentage } from './Percentage.js';
+export { parse as PseudoClassSelector } from './PseudoClassSelector.js';
+export { parse as PseudoElementSelector } from './PseudoElementSelector.js';
+export { parse as Ratio } from './Ratio.js';
+export { parse as Raw } from './Raw.js';
+export { parse as Rule } from './Rule.js';
+export { parse as Scope } from './Scope.js';
+export { parse as Selector } from './Selector.js';
+export { parse as SelectorList } from './SelectorList.js';
+export { parse as String } from './String.js';
+export { parse as StyleSheet } from './StyleSheet.js';
+export { parse as SupportsDeclaration } from './SupportsDeclaration.js';
+export { parse as TypeSelector } from './TypeSelector.js';
+export { parse as UnicodeRange } from './UnicodeRange.js';
+export { parse as Url } from './Url.js';
+export { parse as Value } from './Value.js';
+export { parse as WhiteSpace } from './WhiteSpace.js';
diff --git a/vanilla/node_modules/css-tree/lib/syntax/node/index.js b/vanilla/node_modules/css-tree/lib/syntax/node/index.js
new file mode 100644
index 0000000..4aa2dea
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/syntax/node/index.js
@@ -0,0 +1,49 @@
+export * as AnPlusB from './AnPlusB.js';
+export * as Atrule from './Atrule.js';
+export * as AtrulePrelude from './AtrulePrelude.js';
+export * as AttributeSelector from './AttributeSelector.js';
+export * as Block from './Block.js';
+export * as Brackets from './Brackets.js';
+export * as CDC from './CDC.js';
+export * as CDO from './CDO.js';
+export * as ClassSelector from './ClassSelector.js';
+export * as Combinator from './Combinator.js';
+export * as Comment from './Comment.js';
+export * as Condition from './Condition.js';
+export * as Declaration from './Declaration.js';
+export * as DeclarationList from './DeclarationList.js';
+export * as Dimension from './Dimension.js';
+export * as Feature from './Feature.js';
+export * as FeatureFunction from './FeatureFunction.js';
+export * as FeatureRange from './FeatureRange.js';
+export * as Function from './Function.js';
+export * as GeneralEnclosed from './GeneralEnclosed.js';
+export * as Hash from './Hash.js';
+export * as Identifier from './Identifier.js';
+export * as IdSelector from './IdSelector.js';
+export * as Layer from './Layer.js';
+export * as LayerList from './LayerList.js';
+export * as MediaQuery from './MediaQuery.js';
+export * as MediaQueryList from './MediaQueryList.js';
+export * as NestingSelector from './NestingSelector.js';
+export * as Nth from './Nth.js';
+export * as Number from './Number.js';
+export * as Operator from './Operator.js';
+export * as Parentheses from './Parentheses.js';
+export * as Percentage from './Percentage.js';
+export * as PseudoClassSelector from './PseudoClassSelector.js';
+export * as PseudoElementSelector from './PseudoElementSelector.js';
+export * as Ratio from './Ratio.js';
+export * as Raw from './Raw.js';
+export * as Rule from './Rule.js';
+export * as Scope from './Scope.js';
+export * as Selector from './Selector.js';
+export * as SelectorList from './SelectorList.js';
+export * as String from './String.js';
+export * as StyleSheet from './StyleSheet.js';
+export * as SupportsDeclaration from './SupportsDeclaration.js';
+export * as TypeSelector from './TypeSelector.js';
+export * as UnicodeRange from './UnicodeRange.js';
+export * as Url from './Url.js';
+export * as Value from './Value.js';
+export * as WhiteSpace from './WhiteSpace.js';
diff --git a/vanilla/node_modules/css-tree/lib/syntax/pseudo/index.js b/vanilla/node_modules/css-tree/lib/syntax/pseudo/index.js
new file mode 100644
index 0000000..7d75fe1
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/syntax/pseudo/index.js
@@ -0,0 +1,56 @@
+import { parseLanguageRangeList } from './lang.js';
+
+const selectorList = {
+ parse() {
+ return this.createSingleNodeList(
+ this.SelectorList()
+ );
+ }
+};
+
+const selector = {
+ parse() {
+ return this.createSingleNodeList(
+ this.Selector()
+ );
+ }
+};
+
+const identList = {
+ parse() {
+ return this.createSingleNodeList(
+ this.Identifier()
+ );
+ }
+};
+
+const langList = {
+ parse: parseLanguageRangeList
+};
+
+const nth = {
+ parse() {
+ return this.createSingleNodeList(
+ this.Nth()
+ );
+ }
+};
+
+export default {
+ 'dir': identList,
+ 'has': selectorList,
+ 'lang': langList,
+ 'matches': selectorList,
+ 'is': selectorList,
+ '-moz-any': selectorList,
+ '-webkit-any': selectorList,
+ 'where': selectorList,
+ 'not': selectorList,
+ 'nth-child': nth,
+ 'nth-last-child': nth,
+ 'nth-last-of-type': nth,
+ 'nth-of-type': nth,
+ 'slotted': selector,
+ 'host': selector,
+ 'host-context': selector
+};
diff --git a/vanilla/node_modules/css-tree/lib/syntax/pseudo/lang.js b/vanilla/node_modules/css-tree/lib/syntax/pseudo/lang.js
new file mode 100644
index 0000000..3adfdb8
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/syntax/pseudo/lang.js
@@ -0,0 +1,33 @@
+import { Comma, String as StringToken, Ident, RightParenthesis } from '../../tokenizer/index.js';
+
+export function parseLanguageRangeList() {
+ const children = this.createList();
+
+ this.skipSC();
+
+ loop: while (!this.eof) {
+ switch (this.tokenType) {
+ case Ident:
+ children.push(this.Identifier());
+ break;
+
+ case StringToken:
+ children.push(this.String());
+ break;
+
+ case Comma:
+ children.push(this.Operator());
+ break;
+
+ case RightParenthesis:
+ break loop;
+
+ default:
+ this.error('Identifier, string or comma is expected');
+ }
+
+ this.skipSC();
+ }
+
+ return children;
+}
diff --git a/vanilla/node_modules/css-tree/lib/syntax/scope/atrulePrelude.js b/vanilla/node_modules/css-tree/lib/syntax/scope/atrulePrelude.js
new file mode 100644
index 0000000..c903555
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/syntax/scope/atrulePrelude.js
@@ -0,0 +1,5 @@
+import getNode from './default.js';
+
+export default {
+ getNode
+};
diff --git a/vanilla/node_modules/css-tree/lib/syntax/scope/default.js b/vanilla/node_modules/css-tree/lib/syntax/scope/default.js
new file mode 100644
index 0000000..8f14035
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/syntax/scope/default.js
@@ -0,0 +1,85 @@
+import {
+ Ident,
+ String as StringToken,
+ Number as NumberToken,
+ Function as FunctionToken,
+ Url,
+ Hash,
+ Dimension,
+ Percentage,
+ LeftParenthesis,
+ LeftSquareBracket,
+ Comma,
+ Delim
+} from '../../tokenizer/index.js';
+
+const NUMBERSIGN = 0x0023; // U+0023 NUMBER SIGN (#)
+const ASTERISK = 0x002A; // U+002A ASTERISK (*)
+const PLUSSIGN = 0x002B; // U+002B PLUS SIGN (+)
+const HYPHENMINUS = 0x002D; // U+002D HYPHEN-MINUS (-)
+const SOLIDUS = 0x002F; // U+002F SOLIDUS (/)
+const U = 0x0075; // U+0075 LATIN SMALL LETTER U (u)
+
+export default function defaultRecognizer(context) {
+ switch (this.tokenType) {
+ case Hash:
+ return this.Hash();
+
+ case Comma:
+ return this.Operator();
+
+ case LeftParenthesis:
+ return this.Parentheses(this.readSequence, context.recognizer);
+
+ case LeftSquareBracket:
+ return this.Brackets(this.readSequence, context.recognizer);
+
+ case StringToken:
+ return this.String();
+
+ case Dimension:
+ return this.Dimension();
+
+ case Percentage:
+ return this.Percentage();
+
+ case NumberToken:
+ return this.Number();
+
+ case FunctionToken:
+ return this.cmpStr(this.tokenStart, this.tokenEnd, 'url(')
+ ? this.Url()
+ : this.Function(this.readSequence, context.recognizer);
+
+ case Url:
+ return this.Url();
+
+ case Ident:
+ // check for unicode range, it should start with u+ or U+
+ if (this.cmpChar(this.tokenStart, U) &&
+ this.cmpChar(this.tokenStart + 1, PLUSSIGN)) {
+ return this.UnicodeRange();
+ } else {
+ return this.Identifier();
+ }
+
+ case Delim: {
+ const code = this.charCodeAt(this.tokenStart);
+
+ if (code === SOLIDUS ||
+ code === ASTERISK ||
+ code === PLUSSIGN ||
+ code === HYPHENMINUS) {
+ return this.Operator(); // TODO: replace with Delim
+ }
+
+ // TODO: produce a node with Delim node type
+
+ if (code === NUMBERSIGN) {
+ this.error('Hex or identifier is expected', this.tokenStart + 1);
+ }
+
+ break;
+ }
+ }
+};
diff --git a/vanilla/node_modules/css-tree/lib/syntax/scope/index.js b/vanilla/node_modules/css-tree/lib/syntax/scope/index.js
new file mode 100644
index 0000000..6dabbbe
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/syntax/scope/index.js
@@ -0,0 +1,3 @@
+export { default as AtrulePrelude } from './atrulePrelude.js';
+export { default as Selector } from './selector.js';
+export { default as Value } from './value.js';
diff --git a/vanilla/node_modules/css-tree/lib/syntax/scope/selector.js b/vanilla/node_modules/css-tree/lib/syntax/scope/selector.js
new file mode 100644
index 0000000..a8efcd5
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/syntax/scope/selector.js
@@ -0,0 +1,94 @@
+import {
+ Delim,
+ Ident,
+ Dimension,
+ Percentage,
+ Number as NumberToken,
+ Hash,
+ Colon,
+ LeftSquareBracket
+} from '../../tokenizer/index.js';
+
+const NUMBERSIGN = 0x0023; // U+0023 NUMBER SIGN (#)
+const AMPERSAND = 0x0026; // U+0026 AMPERSAND (&)
+const ASTERISK = 0x002A; // U+002A ASTERISK (*)
+const PLUSSIGN = 0x002B; // U+002B PLUS SIGN (+)
+const SOLIDUS = 0x002F; // U+002F SOLIDUS (/)
+const FULLSTOP = 0x002E; // U+002E FULL STOP (.)
+const GREATERTHANSIGN = 0x003E; // U+003E GREATER-THAN SIGN (>)
+const VERTICALLINE = 0x007C; // U+007C VERTICAL LINE (|)
+const TILDE = 0x007E; // U+007E TILDE (~)
+
+function onWhiteSpace(next, children) {
+ if (children.last !== null && children.last.type !== 'Combinator' &&
+ next !== null && next.type !== 'Combinator') {
+ children.push({ // FIXME: this.Combinator() should be used instead
+ type: 'Combinator',
+ loc: null,
+ name: ' '
+ });
+ }
+}
+
+function getNode() {
+ switch (this.tokenType) {
+ case LeftSquareBracket:
+ return this.AttributeSelector();
+
+ case Hash:
+ return this.IdSelector();
+
+ case Colon:
+ if (this.lookupType(1) === Colon) {
+ return this.PseudoElementSelector();
+ } else {
+ return this.PseudoClassSelector();
+ }
+
+ case Ident:
+ return this.TypeSelector();
+
+ case NumberToken:
+ case Percentage:
+ return this.Percentage();
+
+ case Dimension:
+ // throws when .123ident
+ if (this.charCodeAt(this.tokenStart) === FULLSTOP) {
+ this.error('Identifier is expected', this.tokenStart + 1);
+ }
+ break;
+
+ case Delim: {
+ const code = this.charCodeAt(this.tokenStart);
+
+ switch (code) {
+ case PLUSSIGN:
+ case GREATERTHANSIGN:
+ case TILDE:
+ case SOLIDUS: // /deep/
+ return this.Combinator();
+
+ case FULLSTOP:
+ return this.ClassSelector();
+
+ case ASTERISK:
+ case VERTICALLINE:
+ return this.TypeSelector();
+
+ case NUMBERSIGN:
+ return this.IdSelector();
+
+ case AMPERSAND:
+ return this.NestingSelector();
+ }
+
+ break;
+ }
+ }
+};
+
+export default {
+ onWhiteSpace,
+ getNode
+};
diff --git a/vanilla/node_modules/css-tree/lib/syntax/scope/value.js b/vanilla/node_modules/css-tree/lib/syntax/scope/value.js
new file mode 100644
index 0000000..dc94219
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/syntax/scope/value.js
@@ -0,0 +1,25 @@
+import getNode from './default.js';
+import expressionFn from '../function/expression.js';
+import varFn from '../function/var.js';
+
+function isPlusMinusOperator(node) {
+ return (
+ node !== null &&
+ node.type === 'Operator' &&
+ (node.value[node.value.length - 1] === '-' || node.value[node.value.length - 1] === '+')
+ );
+}
+
+export default {
+ getNode,
+ onWhiteSpace(next, children) {
+ if (isPlusMinusOperator(next)) {
+ next.value = ' ' + next.value;
+ }
+ if (isPlusMinusOperator(children.last)) {
+ children.last.value += ' ';
+ }
+ },
+ 'expression': expressionFn,
+ 'var': varFn
+};
diff --git a/vanilla/node_modules/css-tree/lib/tokenizer/OffsetToLocation.js b/vanilla/node_modules/css-tree/lib/tokenizer/OffsetToLocation.js
new file mode 100644
index 0000000..cc584c0
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/tokenizer/OffsetToLocation.js
@@ -0,0 +1,87 @@
+import { adoptBuffer } from './adopt-buffer.js';
+import { isBOM } from './char-code-definitions.js';
+
+const N = 10;
+const F = 12;
+const R = 13;
+
+function computeLinesAndColumns(host) {
+ const source = host.source;
+ const sourceLength = source.length;
+ const startOffset = source.length > 0 ? isBOM(source.charCodeAt(0)) : 0;
+ const lines = adoptBuffer(host.lines, sourceLength);
+ const columns = adoptBuffer(host.columns, sourceLength);
+ let line = host.startLine;
+ let column = host.startColumn;
+
+ for (let i = startOffset; i < sourceLength; i++) {
+ const code = source.charCodeAt(i);
+
+ lines[i] = line;
+ columns[i] = column++;
+
+ if (code === N || code === R || code === F) {
+ if (code === R && i + 1 < sourceLength && source.charCodeAt(i + 1) === N) {
+ i++;
+ lines[i] = line;
+ columns[i] = column;
+ }
+
+ line++;
+ column = 1;
+ }
+ }
+
+ lines[sourceLength] = line;
+ columns[sourceLength] = column;
+
+ host.lines = lines;
+ host.columns = columns;
+ host.computed = true;
+}
+
+export class OffsetToLocation {
+ constructor(source, startOffset, startLine, startColumn) {
+ this.setSource(source, startOffset, startLine, startColumn);
+ this.lines = null;
+ this.columns = null;
+ }
+ setSource(source = '', startOffset = 0, startLine = 1, startColumn = 1) {
+ this.source = source;
+ this.startOffset = startOffset;
+ this.startLine = startLine;
+ this.startColumn = startColumn;
+ this.computed = false;
+ }
+ getLocation(offset, filename) {
+ if (!this.computed) {
+ computeLinesAndColumns(this);
+ }
+
+ return {
+ source: filename,
+ offset: this.startOffset + offset,
+ line: this.lines[offset],
+ column: this.columns[offset]
+ };
+ }
+ getLocationRange(start, end, filename) {
+ if (!this.computed) {
+ computeLinesAndColumns(this);
+ }
+
+ return {
+ source: filename,
+ start: {
+ offset: this.startOffset + start,
+ line: this.lines[start],
+ column: this.columns[start]
+ },
+ end: {
+ offset: this.startOffset + end,
+ line: this.lines[end],
+ column: this.columns[end]
+ }
+ };
+ }
+};
diff --git a/vanilla/node_modules/css-tree/lib/tokenizer/TokenStream.js b/vanilla/node_modules/css-tree/lib/tokenizer/TokenStream.js
new file mode 100644
index 0000000..96d48b7
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/tokenizer/TokenStream.js
@@ -0,0 +1,316 @@
+import { adoptBuffer } from './adopt-buffer.js';
+import { cmpStr } from './utils.js';
+import tokenNames from './names.js';
+import {
+ WhiteSpace,
+ Comment,
+ Delim,
+ EOF,
+ Function as FunctionToken,
+ LeftParenthesis,
+ RightParenthesis,
+ LeftSquareBracket,
+ RightSquareBracket,
+ LeftCurlyBracket,
+ RightCurlyBracket
+} from './types.js';
+
+const OFFSET_MASK = 0x00FFFFFF;
+const TYPE_SHIFT = 24;
+const balancePair = new Uint8Array(32); // 32b of memory ought to be enough for anyone (any number of tokens)
+balancePair[FunctionToken] = RightParenthesis;
+balancePair[LeftParenthesis] = RightParenthesis;
+balancePair[LeftSquareBracket] = RightSquareBracket;
+balancePair[LeftCurlyBracket] = RightCurlyBracket;
+
+function isBlockOpenerToken(tokenType) {
+ return balancePair[tokenType] !== 0;
+}
+
+export class TokenStream {
+ constructor(source, tokenize) {
+ this.setSource(source, tokenize);
+ }
+ reset() {
+ this.eof = false;
+ this.tokenIndex = -1;
+ this.tokenType = 0;
+ this.tokenStart = this.firstCharOffset;
+ this.tokenEnd = this.firstCharOffset;
+ }
+ setSource(source = '', tokenize = () => {}) {
+ source = String(source || '');
+
+ const sourceLength = source.length;
+ const offsetAndType = adoptBuffer(this.offsetAndType, source.length + 1); // +1 because of eof-token
+ const balance = adoptBuffer(this.balance, source.length + 1);
+ let tokenCount = 0;
+ let firstCharOffset = -1;
+ let balanceCloseType = 0;
+ let balanceStart = source.length;
+
+ // capture buffers
+ this.offsetAndType = null;
+ this.balance = null;
+ balance.fill(0);
+
+ tokenize(source, (type, start, end) => {
+ const index = tokenCount++;
+
+ // type & offset
+ offsetAndType[index] = (type << TYPE_SHIFT) | end;
+
+ if (firstCharOffset === -1) {
+ firstCharOffset = start;
+ }
+
+ // balance
+ balance[index] = balanceStart;
+
+ if (type === balanceCloseType) {
+ const prevBalanceStart = balance[balanceStart];
+
+ // set reference to balance end for a block opener
+ balance[balanceStart] = index;
+
+ // pop state
+ balanceStart = prevBalanceStart;
+ balanceCloseType = balancePair[offsetAndType[prevBalanceStart] >> TYPE_SHIFT];
+ } else if (isBlockOpenerToken(type)) { // check for FunctionToken, <(-token>, <[-token> and <{-token>
+ // push state
+ balanceStart = index;
+ balanceCloseType = balancePair[type];
+ }
+ });
+
+ // finalize buffers
+ offsetAndType[tokenCount] = (EOF << TYPE_SHIFT) | sourceLength; // <EOF-token>
+ balance[tokenCount] = tokenCount; // prevents false positive balance match with any token
+
+ // reverse references from balance start to end
+ // tokens
+ // token: a ( [ b c ] d e ) {
+ // index: 0 1 2 3 4 5 6 7 8 9
+ // before
+ // balance: 0 8 5 2 2 2 1 1 1 0
+ // - > > < < < < < < -
+ // after
+ // balance: 9 8 5 5 5 2 8 8 1 9
+ // > > > > > < > > < >
+ for (let i = 0; i < tokenCount; i++) {
+ const balanceStart = balance[i];
+
+ if (balanceStart <= i) {
+ const balanceEnd = balance[balanceStart];
+
+ if (balanceEnd !== i) {
+ balance[i] = balanceEnd;
+ }
+ } else if (balanceStart > tokenCount) {
+ balance[i] = tokenCount;
+ }
+ }
+
+ // balance[0] = tokenCount;
+
+ this.source = source;
+ this.firstCharOffset = firstCharOffset === -1 ? 0 : firstCharOffset;
+ this.tokenCount = tokenCount;
+ this.offsetAndType = offsetAndType;
+ this.balance = balance;
+
+ this.reset();
+ this.next();
+ }
+
+ lookupType(offset) {
+ offset += this.tokenIndex;
+
+ if (offset < this.tokenCount) {
+ return this.offsetAndType[offset] >> TYPE_SHIFT;
+ }
+
+ return EOF;
+ }
+ lookupTypeNonSC(idx) {
+ for (let offset = this.tokenIndex; offset < this.tokenCount; offset++) {
+ const tokenType = this.offsetAndType[offset] >> TYPE_SHIFT;
+
+ if (tokenType !== WhiteSpace && tokenType !== Comment) {
+ if (idx-- === 0) {
+ return tokenType;
+ }
+ }
+ }
+
+ return EOF;
+ }
+ lookupOffset(offset) {
+ offset += this.tokenIndex;
+
+ if (offset < this.tokenCount) {
+ return this.offsetAndType[offset - 1] & OFFSET_MASK;
+ }
+
+ return this.source.length;
+ }
+ lookupOffsetNonSC(idx) {
+ for (let offset = this.tokenIndex; offset < this.tokenCount; offset++) {
+ const tokenType = this.offsetAndType[offset] >> TYPE_SHIFT;
+
+ if (tokenType !== WhiteSpace && tokenType !== Comment) {
+ if (idx-- === 0) {
+ return offset - this.tokenIndex;
+ }
+ }
+ }
+
+ return EOF;
+ }
+ lookupValue(offset, referenceStr) {
+ offset += this.tokenIndex;
+
+ if (offset < this.tokenCount) {
+ return cmpStr(
+ this.source,
+ this.offsetAndType[offset - 1] & OFFSET_MASK,
+ this.offsetAndType[offset] & OFFSET_MASK,
+ referenceStr
+ );
+ }
+
+ return false;
+ }
+ getTokenStart(tokenIndex) {
+ if (tokenIndex === this.tokenIndex) {
+ return this.tokenStart;
+ }
+
+ if (tokenIndex > 0) {
+ return tokenIndex < this.tokenCount
+ ? this.offsetAndType[tokenIndex - 1] & OFFSET_MASK
+ : this.offsetAndType[this.tokenCount] & OFFSET_MASK;
+ }
+
+ return this.firstCharOffset;
+ }
+ substrToCursor(start) {
+ return this.source.substring(start, this.tokenStart);
+ }
+
+ isBalanceEdge(pos) {
+ return this.balance[this.tokenIndex] < pos;
+ // return this.balance[this.balance[pos]] !== this.tokenIndex;
+ }
+ isDelim(code, offset) {
+ if (offset) {
+ return (
+ this.lookupType(offset) === Delim &&
+ this.source.charCodeAt(this.lookupOffset(offset)) === code
+ );
+ }
+
+ return (
+ this.tokenType === Delim &&
+ this.source.charCodeAt(this.tokenStart) === code
+ );
+ }
+
+ skip(tokenCount) {
+ let next = this.tokenIndex + tokenCount;
+
+ if (next < this.tokenCount) {
+ this.tokenIndex = next;
+ this.tokenStart = this.offsetAndType[next - 1] & OFFSET_MASK;
+ next = this.offsetAndType[next];
+ this.tokenType = next >> TYPE_SHIFT;
+ this.tokenEnd = next & OFFSET_MASK;
+ } else {
+ this.tokenIndex = this.tokenCount;
+ this.next();
+ }
+ }
+ next() {
+ let next = this.tokenIndex + 1;
+
+ if (next < this.tokenCount) {
+ this.tokenIndex = next;
+ this.tokenStart = this.tokenEnd;
+ next = this.offsetAndType[next];
+ this.tokenType = next >> TYPE_SHIFT;
+ this.tokenEnd = next & OFFSET_MASK;
+ } else {
+ this.eof = true;
+ this.tokenIndex = this.tokenCount;
+ this.tokenType = EOF;
+ this.tokenStart = this.tokenEnd = this.source.length;
+ }
+ }
+ skipSC() {
+ while (this.tokenType === WhiteSpace || this.tokenType === Comment) {
+ this.next();
+ }
+ }
+ skipUntilBalanced(startToken, stopConsume) {
+ let cursor = startToken;
+ let balanceEnd = 0;
+ let offset = 0;
+
+ loop:
+ for (; cursor < this.tokenCount; cursor++) {
+ balanceEnd = this.balance[cursor];
+
+ // stop scanning on balance edge that points to offset before start token
+ if (balanceEnd < startToken) {
+ break loop;
+ }
+
+ offset = cursor > 0 ? this.offsetAndType[cursor - 1] & OFFSET_MASK : this.firstCharOffset;
+
+ // check stop condition
+ switch (stopConsume(this.source.charCodeAt(offset))) {
+ case 1: // just stop
+ break loop;
+
+ case 2: // stop & included
+ cursor++;
+ break loop;
+
+ default:
+ // fast forward to the end of balanced block for an open block tokens
+ if (isBlockOpenerToken(this.offsetAndType[cursor] >> TYPE_SHIFT)) {
+ cursor = balanceEnd;
+ }
+ }
+ }
+
+ this.skip(cursor - this.tokenIndex);
+ }
+
+ forEachToken(fn) {
+ for (let i = 0, offset = this.firstCharOffset; i < this.tokenCount; i++) {
+ const start = offset;
+ const item = this.offsetAndType[i];
+ const end = item & OFFSET_MASK;
+ const type = item >> TYPE_SHIFT;
+
+ offset = end;
+
+ fn(type, start, end, i);
+ }
+ }
+ dump() {
+ const tokens = new Array(this.tokenCount);
+
+ this.forEachToken((type, start, end, index) => {
+ tokens[index] = {
+ idx: index,
+ type: tokenNames[type],
+ chunk: this.source.substring(start, end),
+ balance: this.balance[index]
+ };
+ });
+
+ return tokens;
+ }
+};
diff --git a/vanilla/node_modules/css-tree/lib/tokenizer/adopt-buffer.js b/vanilla/node_modules/css-tree/lib/tokenizer/adopt-buffer.js
new file mode 100644
index 0000000..ab4566d
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/tokenizer/adopt-buffer.js
@@ -0,0 +1,9 @@
+const MIN_SIZE = 16 * 1024;
+
+export function adoptBuffer(buffer = null, size) {
+ if (buffer === null || buffer.length < size) {
+ return new Uint32Array(Math.max(size + 1024, MIN_SIZE));
+ }
+
+ return buffer;
+};
diff --git a/vanilla/node_modules/css-tree/lib/tokenizer/char-code-definitions.js b/vanilla/node_modules/css-tree/lib/tokenizer/char-code-definitions.js
new file mode 100644
index 0000000..715572a
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/tokenizer/char-code-definitions.js
@@ -0,0 +1,212 @@
+const EOF = 0;
+
+// https://drafts.csswg.org/css-syntax-3/
+// § 4.2. Definitions
+
+// digit
+// A code point between U+0030 DIGIT ZERO (0) and U+0039 DIGIT NINE (9).
+export function isDigit(code) {
+ return code >= 0x0030 && code <= 0x0039;
+}
+
+// hex digit
+// A digit, or a code point between U+0041 LATIN CAPITAL LETTER A (A) and U+0046 LATIN CAPITAL LETTER F (F),
+// or a code point between U+0061 LATIN SMALL LETTER A (a) and U+0066 LATIN SMALL LETTER F (f).
+export function isHexDigit(code) {
+ return (
+ isDigit(code) || // 0 .. 9
+ (code >= 0x0041 && code <= 0x0046) || // A .. F
+ (code >= 0x0061 && code <= 0x0066) // a .. f
+ );
+}
+
+// uppercase letter
+// A code point between U+0041 LATIN CAPITAL LETTER A (A) and U+005A LATIN CAPITAL LETTER Z (Z).
+export function isUppercaseLetter(code) {
+ return code >= 0x0041 && code <= 0x005A;
+}
+
+// lowercase letter
+// A code point between U+0061 LATIN SMALL LETTER A (a) and U+007A LATIN SMALL LETTER Z (z).
+export function isLowercaseLetter(code) {
+ return code >= 0x0061 && code <= 0x007A;
+}
+
+// letter
+// An uppercase letter or a lowercase letter.
+export function isLetter(code) {
+ return isUppercaseLetter(code) || isLowercaseLetter(code);
+}
+
+// non-ASCII code point
+// A code point with a value equal to or greater than U+0080 <control>.
+//
+// 2024-09-02: The latest spec narrows the range for non-ASCII characters (see https://github.com/csstree/csstree/issues/188).
+// However, all modern browsers support a wider range, and strictly following the latest spec could result
+// in some CSS being parsed incorrectly, even though it works in the browser. Therefore, this function adheres
+// to the previous, broader definition of non-ASCII characters.
+export function isNonAscii(code) {
+ return code >= 0x0080;
+}
+
+// name-start code point
+// A letter, a non-ASCII code point, or U+005F LOW LINE (_).
+export function isNameStart(code) {
+ return isLetter(code) || isNonAscii(code) || code === 0x005F;
+}
+
+// name code point
+// A name-start code point, a digit, or U+002D HYPHEN-MINUS (-).
+export function isName(code) {
+ return isNameStart(code) || isDigit(code) || code === 0x002D;
+}
+
+// non-printable code point
+// A code point between U+0000 NULL and U+0008 BACKSPACE, or U+000B LINE TABULATION,
+// or a code point between U+000E SHIFT OUT and U+001F INFORMATION SEPARATOR ONE, or U+007F DELETE.
+export function isNonPrintable(code) {
+ return (
+ (code >= 0x0000 && code <= 0x0008) ||
+ (code === 0x000B) ||
+ (code >= 0x000E && code <= 0x001F) ||
+ (code === 0x007F)
+ );
+}
+
+// newline
+// U+000A LINE FEED. Note that U+000D CARRIAGE RETURN and U+000C FORM FEED are not included in this definition,
+// as they are converted to U+000A LINE FEED during preprocessing.
+// TODO: we doesn't do a preprocessing, so check a code point for U+000D CARRIAGE RETURN and U+000C FORM FEED
+export function isNewline(code) {
+ return code === 0x000A || code === 0x000D || code === 0x000C;
+}
+
+// whitespace
+// A newline, U+0009 CHARACTER TABULATION, or U+0020 SPACE.
+export function isWhiteSpace(code) {
+ return isNewline(code) || code === 0x0020 || code === 0x0009;
+}
+
+// § 4.3.8. Check if two code points are a valid escape
+export function isValidEscape(first, second) {
+ // If the first code point is not U+005C REVERSE SOLIDUS (\), return false.
+ if (first !== 0x005C) {
+ return false;
+ }
+
+ // Otherwise, if the second code point is a newline or EOF, return false.
+ if (isNewline(second) || second === EOF) {
+ return false;
+ }
+
+ // Otherwise, return true.
+ return true;
+}
+
+// § 4.3.9. Check if three code points would start an identifier
+export function isIdentifierStart(first, second, third) {
+ // Look at the first code point:
+
+ // U+002D HYPHEN-MINUS
+ if (first === 0x002D) {
+ // If the second code point is a name-start code point or a U+002D HYPHEN-MINUS,
+ // or the second and third code points are a valid escape, return true. Otherwise, return false.
+ return (
+ isNameStart(second) ||
+ second === 0x002D ||
+ isValidEscape(second, third)
+ );
+ }
+
+ // name-start code point
+ if (isNameStart(first)) {
+ // Return true.
+ return true;
+ }
+
+ // U+005C REVERSE SOLIDUS (\)
+ if (first === 0x005C) {
+ // If the first and second code points are a valid escape, return true. Otherwise, return false.
+ return isValidEscape(first, second);
+ }
+
+ // anything else
+ // Return false.
+ return false;
+}
+
+// § 4.3.10. Check if three code points would start a number
+export function isNumberStart(first, second, third) {
+ // Look at the first code point:
+
+ // U+002B PLUS SIGN (+)
+ // U+002D HYPHEN-MINUS (-)
+ if (first === 0x002B || first === 0x002D) {
+ // If the second code point is a digit, return true.
+ if (isDigit(second)) {
+ return 2;
+ }
+
+ // Otherwise, if the second code point is a U+002E FULL STOP (.)
+ // and the third code point is a digit, return true.
+ // Otherwise, return false.
+ return second === 0x002E && isDigit(third) ? 3 : 0;
+ }
+
+ // U+002E FULL STOP (.)
+ if (first === 0x002E) {
+ // If the second code point is a digit, return true. Otherwise, return false.
+ return isDigit(second) ? 2 : 0;
+ }
+
+ // digit
+ if (isDigit(first)) {
+ // Return true.
+ return 1;
+ }
+
+ // anything else
+ // Return false.
+ return 0;
+}
+
+//
+// Misc
+//
+
+// detect BOM (https://en.wikipedia.org/wiki/Byte_order_mark)
+export function isBOM(code) {
+ // UTF-16BE
+ if (code === 0xFEFF) {
+ return 1;
+ }
+
+ // UTF-16LE
+ if (code === 0xFFFE) {
+ return 1;
+ }
+
+ return 0;
+}
+
+// Fast code category
+// Only ASCII code points has a special meaning, that's why we define a maps for 0..127 codes only
+const CATEGORY = new Array(0x80);
+export const EofCategory = 0x80;
+export const WhiteSpaceCategory = 0x82;
+export const DigitCategory = 0x83;
+export const NameStartCategory = 0x84;
+export const NonPrintableCategory = 0x85;
+
+for (let i = 0; i < CATEGORY.length; i++) {
+ CATEGORY[i] =
+ isWhiteSpace(i) && WhiteSpaceCategory ||
+ isDigit(i) && DigitCategory ||
+ isNameStart(i) && NameStartCategory ||
+ isNonPrintable(i) && NonPrintableCategory ||
+ i || EofCategory;
+}
+
+export function charCodeCategory(code) {
+ return code < 0x80 ? CATEGORY[code] : NameStartCategory;
+}
diff --git a/vanilla/node_modules/css-tree/lib/tokenizer/index.js b/vanilla/node_modules/css-tree/lib/tokenizer/index.js
new file mode 100644
index 0000000..16df44c
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/tokenizer/index.js
@@ -0,0 +1,513 @@
+import * as TYPE from './types.js';
+import {
+ isNewline,
+ isName,
+ isValidEscape,
+ isNumberStart,
+ isIdentifierStart,
+ isBOM,
+ charCodeCategory,
+ WhiteSpaceCategory,
+ DigitCategory,
+ NameStartCategory,
+ NonPrintableCategory
+} from './char-code-definitions.js';
+import {
+ cmpStr,
+ getNewlineLength,
+ findWhiteSpaceEnd,
+ consumeEscaped,
+ consumeName,
+ consumeNumber,
+ consumeBadUrlRemnants
+} from './utils.js';
+
+export function tokenize(source, onToken) {
+ function getCharCode(offset) {
+ return offset < sourceLength ? source.charCodeAt(offset) : 0;
+ }
+
+ // § 4.3.3. Consume a numeric token
+ function consumeNumericToken() {
+ // Consume a number and let number be the result.
+ offset = consumeNumber(source, offset);
+
+ // If the next 3 input code points would start an identifier, then:
+ if (isIdentifierStart(getCharCode(offset), getCharCode(offset + 1), getCharCode(offset + 2))) {
+ // Create a <dimension-token> with the same value and type flag as number, and a unit set initially to the empty string.
+ // Consume a name. Set the <dimension-token>’s unit to the returned value.
+ // Return the <dimension-token>.
+ type = TYPE.Dimension;
+ offset = consumeName(source, offset);
+ return;
+ }
+
+ // Otherwise, if the next input code point is U+0025 PERCENTAGE SIGN (%), consume it.
+ if (getCharCode(offset) === 0x0025) {
+ // Create a <percentage-token> with the same value as number, and return it.
+ type = TYPE.Percentage;
+ offset++;
+ return;
+ }
+
+ // Otherwise, create a <number-token> with the same value and type flag as number, and return it.
+ type = TYPE.Number;
+ }
+
+ // § 4.3.4. Consume an ident-like token
+ function consumeIdentLikeToken() {
+ const nameStartOffset = offset;
+
+ // Consume a name, and let string be the result.
+ offset = consumeName(source, offset);
+
+ // If string’s value is an ASCII case-insensitive match for "url",
+ // and the next input code point is U+0028 LEFT PARENTHESIS ((), consume it.
+ if (cmpStr(source, nameStartOffset, offset, 'url') && getCharCode(offset) === 0x0028) {
+ // While the next two input code points are whitespace, consume the next input code point.
+ offset = findWhiteSpaceEnd(source, offset + 1);
+
+ // If the next one or two input code points are U+0022 QUOTATION MARK ("), U+0027 APOSTROPHE ('),
+ // or whitespace followed by U+0022 QUOTATION MARK (") or U+0027 APOSTROPHE ('),
+ // then create a <function-token> with its value set to string and return it.
+ if (getCharCode(offset) === 0x0022 ||
+ getCharCode(offset) === 0x0027) {
+ type = TYPE.Function;
+ offset = nameStartOffset + 4;
+ return;
+ }
+
+ // Otherwise, consume a url token, and return it.
+ consumeUrlToken();
+ return;
+ }
+
+ // Otherwise, if the next input code point is U+0028 LEFT PARENTHESIS ((), consume it.
+ // Create a <function-token> with its value set to string and return it.
+ if (getCharCode(offset) === 0x0028) {
+ type = TYPE.Function;
+ offset++;
+ return;
+ }
+
+ // Otherwise, create an <ident-token> with its value set to string and return it.
+ type = TYPE.Ident;
+ }
+
+ // § 4.3.5. Consume a string token
+ function consumeStringToken(endingCodePoint) {
+ // This algorithm may be called with an ending code point, which denotes the code point
+ // that ends the string. If an ending code point is not specified,
+ // the current input code point is used.
+ if (!endingCodePoint) {
+ endingCodePoint = getCharCode(offset++);
+ }
+
+ // Initially create a <string-token> with its value set to the empty string.
+ type = TYPE.String;
+
+ // Repeatedly consume the next input code point from the stream:
+ for (; offset < source.length; offset++) {
+ const code = source.charCodeAt(offset);
+
+ switch (charCodeCategory(code)) {
+ // ending code point
+ case endingCodePoint:
+ // Return the <string-token>.
+ offset++;
+ return;
+
+ // EOF
+ // case EofCategory:
+ // This is a parse error. Return the <string-token>.
+ // return;
+
+ // newline
+ case WhiteSpaceCategory:
+ if (isNewline(code)) {
+ // This is a parse error. Reconsume the current input code point,
+ // create a <bad-string-token>, and return it.
+ offset += getNewlineLength(source, offset, code);
+ type = TYPE.BadString;
+ return;
+ }
+ break;
+
+ // U+005C REVERSE SOLIDUS (\)
+ case 0x005C:
+ // If the next input code point is EOF, do nothing.
+ if (offset === source.length - 1) {
+ break;
+ }
+
+ const nextCode = getCharCode(offset + 1);
+
+ // Otherwise, if the next input code point is a newline, consume it.
+ if (isNewline(nextCode)) {
+ offset += getNewlineLength(source, offset + 1, nextCode);
+ } else if (isValidEscape(code, nextCode)) {
+ // Otherwise, (the stream starts with a valid escape) consume
+ // an escaped code point and append the returned code point to
+ // the <string-token>’s value.
+ offset = consumeEscaped(source, offset) - 1;
+ }
+ break;
+
+ // anything else
+ // Append the current input code point to the <string-token>’s value.
+ }
+ }
+ }
+
+ // § 4.3.6. Consume a url token
+ // Note: This algorithm assumes that the initial "url(" has already been consumed.
+ // This algorithm also assumes that it’s being called to consume an "unquoted" value, like url(foo).
+ // A quoted value, like url("foo"), is parsed as a <function-token>. Consume an ident-like token
+ // automatically handles this distinction; this algorithm shouldn’t be called directly otherwise.
+ function consumeUrlToken() {
+ // Initially create a <url-token> with its value set to the empty string.
+ type = TYPE.Url;
+
+ // Consume as much whitespace as possible.
+ offset = findWhiteSpaceEnd(source, offset);
+
+ // Repeatedly consume the next input code point from the stream:
+ for (; offset < source.length; offset++) {
+ const code = source.charCodeAt(offset);
+
+ switch (charCodeCategory(code)) {
+ // U+0029 RIGHT PARENTHESIS ())
+ case 0x0029:
+ // Return the <url-token>.
+ offset++;
+ return;
+
+ // EOF
+ // case EofCategory:
+ // This is a parse error. Return the <url-token>.
+ // return;
+
+ // whitespace
+ case WhiteSpaceCategory:
+ // Consume as much whitespace as possible.
+ offset = findWhiteSpaceEnd(source, offset);
+
+ // If the next input code point is U+0029 RIGHT PARENTHESIS ()) or EOF,
+ // consume it and return the <url-token>
+ // (if EOF was encountered, this is a parse error);
+ if (getCharCode(offset) === 0x0029 || offset >= source.length) {
+ if (offset < source.length) {
+ offset++;
+ }
+ return;
+ }
+
+ // otherwise, consume the remnants of a bad url, create a <bad-url-token>,
+ // and return it.
+ offset = consumeBadUrlRemnants(source, offset);
+ type = TYPE.BadUrl;
+ return;
+
+ // U+0022 QUOTATION MARK (")
+ // U+0027 APOSTROPHE (')
+ // U+0028 LEFT PARENTHESIS (()
+ // non-printable code point
+ case 0x0022:
+ case 0x0027:
+ case 0x0028:
+ case NonPrintableCategory:
+ // This is a parse error. Consume the remnants of a bad url,
+ // create a <bad-url-token>, and return it.
+ offset = consumeBadUrlRemnants(source, offset);
+ type = TYPE.BadUrl;
+ return;
+
+ // U+005C REVERSE SOLIDUS (\)
+ case 0x005C:
+ // If the stream starts with a valid escape, consume an escaped code point and
+ // append the returned code point to the <url-token>’s value.
+ if (isValidEscape(code, getCharCode(offset + 1))) {
+ offset = consumeEscaped(source, offset) - 1;
+ break;
+ }
+
+ // Otherwise, this is a parse error. Consume the remnants of a bad url,
+ // create a <bad-url-token>, and return it.
+ offset = consumeBadUrlRemnants(source, offset);
+ type = TYPE.BadUrl;
+ return;
+
+ // anything else
+ // Append the current input code point to the <url-token>’s value.
+ }
+ }
+ }
+
+ // ensure source is a string
+ source = String(source || '');
+
+ const sourceLength = source.length;
+ let start = isBOM(getCharCode(0));
+ let offset = start;
+ let type;
+
+ // https://drafts.csswg.org/css-syntax-3/#consume-token
+ // § 4.3.1. Consume a token
+ while (offset < sourceLength) {
+ const code = source.charCodeAt(offset);
+
+ switch (charCodeCategory(code)) {
+ // whitespace
+ case WhiteSpaceCategory:
+ // Consume as much whitespace as possible. Return a <whitespace-token>.
+ type = TYPE.WhiteSpace;
+ offset = findWhiteSpaceEnd(source, offset + 1);
+ break;
+
+ // U+0022 QUOTATION MARK (")
+ case 0x0022:
+ // Consume a string token and return it.
+ consumeStringToken();
+ break;
+
+ // U+0023 NUMBER SIGN (#)
+ case 0x0023:
+ // If the next input code point is a name code point or the next two input code points are a valid escape, then:
+ if (isName(getCharCode(offset + 1)) || isValidEscape(getCharCode(offset + 1), getCharCode(offset + 2))) {
+ // Create a <hash-token>.
+ type = TYPE.Hash;
+
+ // If the next 3 input code points would start an identifier, set the <hash-token>’s type flag to "id".
+ // if (isIdentifierStart(getCharCode(offset + 1), getCharCode(offset + 2), getCharCode(offset + 3))) {
+ // // TODO: set id flag
+ // }
+
+ // Consume a name, and set the <hash-token>’s value to the returned string.
+ offset = consumeName(source, offset + 1);
+
+ // Return the <hash-token>.
+ } else {
+ // Otherwise, return a <delim-token> with its value set to the current input code point.
+ type = TYPE.Delim;
+ offset++;
+ }
+
+ break;
+
+ // U+0027 APOSTROPHE (')
+ case 0x0027:
+ // Consume a string token and return it.
+ consumeStringToken();
+ break;
+
+ // U+0028 LEFT PARENTHESIS (()
+ case 0x0028:
+ // Return a <(-token>.
+ type = TYPE.LeftParenthesis;
+ offset++;
+ break;
+
+ // U+0029 RIGHT PARENTHESIS ())
+ case 0x0029:
+ // Return a <)-token>.
+ type = TYPE.RightParenthesis;
+ offset++;
+ break;
+
+ // U+002B PLUS SIGN (+)
+ case 0x002B:
+ // If the input stream starts with a number, ...
+ if (isNumberStart(code, getCharCode(offset + 1), getCharCode(offset + 2))) {
+ // ... reconsume the current input code point, consume a numeric token, and return it.
+ consumeNumericToken();
+ } else {
+ // Otherwise, return a <delim-token> with its value set to the current input code point.
+ type = TYPE.Delim;
+ offset++;
+ }
+ break;
+
+ // U+002C COMMA (,)
+ case 0x002C:
+ // Return a <comma-token>.
+ type = TYPE.Comma;
+ offset++;
+ break;
+
+ // U+002D HYPHEN-MINUS (-)
+ case 0x002D:
+ // If the input stream starts with a number, reconsume the current input code point, consume a numeric token, and return it.
+ if (isNumberStart(code, getCharCode(offset + 1), getCharCode(offset + 2))) {
+ consumeNumericToken();
+ } else {
+ // Otherwise, if the next 2 input code points are U+002D HYPHEN-MINUS U+003E GREATER-THAN SIGN (->), consume them and return a <CDC-token>.
+ if (getCharCode(offset + 1) === 0x002D &&
+ getCharCode(offset + 2) === 0x003E) {
+ type = TYPE.CDC;
+ offset = offset + 3;
+ } else {
+ // Otherwise, if the input stream starts with an identifier, ...
+ if (isIdentifierStart(code, getCharCode(offset + 1), getCharCode(offset + 2))) {
+ // ... reconsume the current input code point, consume an ident-like token, and return it.
+ consumeIdentLikeToken();
+ } else {
+ // Otherwise, return a <delim-token> with its value set to the current input code point.
+ type = TYPE.Delim;
+ offset++;
+ }
+ }
+ }
+ break;
+
+ // U+002E FULL STOP (.)
+ case 0x002E:
+ // If the input stream starts with a number, ...
+ if (isNumberStart(code, getCharCode(offset + 1), getCharCode(offset + 2))) {
+ // ... reconsume the current input code point, consume a numeric token, and return it.
+ consumeNumericToken();
+ } else {
+ // Otherwise, return a <delim-token> with its value set to the current input code point.
+ type = TYPE.Delim;
+ offset++;
+ }
+
+ break;
+
+ // U+002F SOLIDUS (/)
+ case 0x002F:
+ // If the next two input code point are U+002F SOLIDUS (/) followed by a U+002A ASTERISK (*),
+ if (getCharCode(offset + 1) === 0x002A) {
+ // ... consume them and all following code points up to and including the first U+002A ASTERISK (*)
+ // followed by a U+002F SOLIDUS (/), or up to an EOF code point.
+ type = TYPE.Comment;
+ offset = source.indexOf('*/', offset + 2);
+ offset = offset === -1 ? source.length : offset + 2;
+ } else {
+ type = TYPE.Delim;
+ offset++;
+ }
+ break;
+
+ // U+003A COLON (:)
+ case 0x003A:
+ // Return a <colon-token>.
+ type = TYPE.Colon;
+ offset++;
+ break;
+
+ // U+003B SEMICOLON (;)
+ case 0x003B:
+ // Return a <semicolon-token>.
+ type = TYPE.Semicolon;
+ offset++;
+ break;
+
+ // U+003C LESS-THAN SIGN (<)
+ case 0x003C:
+ // If the next 3 input code points are U+0021 EXCLAMATION MARK U+002D HYPHEN-MINUS U+002D HYPHEN-MINUS (!--), ...
+ if (getCharCode(offset + 1) === 0x0021 &&
+ getCharCode(offset + 2) === 0x002D &&
+ getCharCode(offset + 3) === 0x002D) {
+ // ... consume them and return a <CDO-token>.
+ type = TYPE.CDO;
+ offset = offset + 4;
+ } else {
+ // Otherwise, return a <delim-token> with its value set to the current input code point.
+ type = TYPE.Delim;
+ offset++;
+ }
+
+ break;
+
+ // U+0040 COMMERCIAL AT (@)
+ case 0x0040:
+ // If the next 3 input code points would start an identifier, ...
+ if (isIdentifierStart(getCharCode(offset + 1), getCharCode(offset + 2), getCharCode(offset + 3))) {
+ // ... consume a name, create an <at-keyword-token> with its value set to the returned value, and return it.
+ type = TYPE.AtKeyword;
+ offset = consumeName(source, offset + 1);
+ } else {
+ // Otherwise, return a <delim-token> with its value set to the current input code point.
+ type = TYPE.Delim;
+ offset++;
+ }
+
+ break;
+
+ // U+005B LEFT SQUARE BRACKET ([)
+ case 0x005B:
+ // Return a <[-token>.
+ type = TYPE.LeftSquareBracket;
+ offset++;
+ break;
+
+ // U+005C REVERSE SOLIDUS (\)
+ case 0x005C:
+ // If the input stream starts with a valid escape, ...
+ if (isValidEscape(code, getCharCode(offset + 1))) {
+ // ... reconsume the current input code point, consume an ident-like token, and return it.
+ consumeIdentLikeToken();
+ } else {
+ // Otherwise, this is a parse error. Return a <delim-token> with its value set to the current input code point.
+ type = TYPE.Delim;
+ offset++;
+ }
+ break;
+
+ // U+005D RIGHT SQUARE BRACKET (])
+ case 0x005D:
+ // Return a <]-token>.
+ type = TYPE.RightSquareBracket;
+ offset++;
+ break;
+
+ // U+007B LEFT CURLY BRACKET ({)
+ case 0x007B:
+ // Return a <{-token>.
+ type = TYPE.LeftCurlyBracket;
+ offset++;
+ break;
+
+ // U+007D RIGHT CURLY BRACKET (})
+ case 0x007D:
+ // Return a <}-token>.
+ type = TYPE.RightCurlyBracket;
+ offset++;
+ break;
+
+ // digit
+ case DigitCategory:
+ // Reconsume the current input code point, consume a numeric token, and return it.
+ consumeNumericToken();
+ break;
+
+ // name-start code point
+ case NameStartCategory:
+ // Reconsume the current input code point, consume an ident-like token, and return it.
+ consumeIdentLikeToken();
+ break;
+
+ // EOF
+ // case EofCategory:
+ // Return an <EOF-token>.
+ // break;
+
+ // anything else
+ default:
+ // Return a <delim-token> with its value set to the current input code point.
+ type = TYPE.Delim;
+ offset++;
+ }
+
+ // put token to stream
+ onToken(type, start, start = offset);
+ }
+}
+
+export * from './types.js';
+export * as tokenTypes from './types.js';
+export { default as tokenNames } from './names.js';
+export * from './char-code-definitions.js';
+export * from './utils.js';
+export * from './OffsetToLocation.js';
+export * from './TokenStream.js';
diff --git a/vanilla/node_modules/css-tree/lib/tokenizer/names.js b/vanilla/node_modules/css-tree/lib/tokenizer/names.js
new file mode 100644
index 0000000..54831bd
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/tokenizer/names.js
@@ -0,0 +1,28 @@
+export default [
+ 'EOF-token',
+ 'ident-token',
+ 'function-token',
+ 'at-keyword-token',
+ 'hash-token',
+ 'string-token',
+ 'bad-string-token',
+ 'url-token',
+ 'bad-url-token',
+ 'delim-token',
+ 'number-token',
+ 'percentage-token',
+ 'dimension-token',
+ 'whitespace-token',
+ 'CDO-token',
+ 'CDC-token',
+ 'colon-token',
+ 'semicolon-token',
+ 'comma-token',
+ '[-token',
+ ']-token',
+ '(-token',
+ ')-token',
+ '{-token',
+ '}-token',
+ 'comment-token'
+];
diff --git a/vanilla/node_modules/css-tree/lib/tokenizer/types.js b/vanilla/node_modules/css-tree/lib/tokenizer/types.js
new file mode 100644
index 0000000..5018569
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/tokenizer/types.js
@@ -0,0 +1,28 @@
+// CSS Syntax Module Level 3
+// https://www.w3.org/TR/css-syntax-3/
+export const EOF = 0; // <EOF-token>
+export const Ident = 1; // <ident-token>
+export const Function = 2; // <function-token>
+export const AtKeyword = 3; // <at-keyword-token>
+export const Hash = 4; // <hash-token>
+export const String = 5; // <string-token>
+export const BadString = 6; // <bad-string-token>
+export const Url = 7; // <url-token>
+export const BadUrl = 8; // <bad-url-token>
+export const Delim = 9; // <delim-token>
+export const Number = 10; // <number-token>
+export const Percentage = 11; // <percentage-token>
+export const Dimension = 12; // <dimension-token>
+export const WhiteSpace = 13; // <whitespace-token>
+export const CDO = 14; // <CDO-token>
+export const CDC = 15; // <CDC-token>
+export const Colon = 16; // <colon-token> :
+export const Semicolon = 17; // <semicolon-token> ;
+export const Comma = 18; // <comma-token> ,
+export const LeftSquareBracket = 19; // <[-token>
+export const RightSquareBracket = 20; // <]-token>
+export const LeftParenthesis = 21; // <(-token>
+export const RightParenthesis = 22; // <)-token>
+export const LeftCurlyBracket = 23; // <{-token>
+export const RightCurlyBracket = 24; // <}-token>
+export const Comment = 25;
diff --git a/vanilla/node_modules/css-tree/lib/tokenizer/utils.js b/vanilla/node_modules/css-tree/lib/tokenizer/utils.js
new file mode 100644
index 0000000..c131ec5
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/tokenizer/utils.js
@@ -0,0 +1,254 @@
+import {
+ isDigit,
+ isHexDigit,
+ isUppercaseLetter,
+ isName,
+ isWhiteSpace,
+ isValidEscape
+} from './char-code-definitions.js';
+
+function getCharCode(source, offset) {
+ return offset < source.length ? source.charCodeAt(offset) : 0;
+}
+
+export function getNewlineLength(source, offset, code) {
+ if (code === 13 /* \r */ && getCharCode(source, offset + 1) === 10 /* \n */) {
+ return 2;
+ }
+
+ return 1;
+}
+
+export function cmpChar(testStr, offset, referenceCode) {
+ let code = testStr.charCodeAt(offset);
+
+ // code.toLowerCase() for A..Z
+ if (isUppercaseLetter(code)) {
+ code = code | 32;
+ }
+
+ return code === referenceCode;
+}
+
+export function cmpStr(testStr, start, end, referenceStr) {
+ if (end - start !== referenceStr.length) {
+ return false;
+ }
+
+ if (start < 0 || end > testStr.length) {
+ return false;
+ }
+
+ for (let i = start; i < end; i++) {
+ const referenceCode = referenceStr.charCodeAt(i - start);
+ let testCode = testStr.charCodeAt(i);
+
+ // testCode.toLowerCase() for A..Z
+ if (isUppercaseLetter(testCode)) {
+ testCode = testCode | 32;
+ }
+
+ if (testCode !== referenceCode) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+export function findWhiteSpaceStart(source, offset) {
+ for (; offset >= 0; offset--) {
+ if (!isWhiteSpace(source.charCodeAt(offset))) {
+ break;
+ }
+ }
+
+ return offset + 1;
+}
+
+export function findWhiteSpaceEnd(source, offset) {
+ for (; offset < source.length; offset++) {
+ if (!isWhiteSpace(source.charCodeAt(offset))) {
+ break;
+ }
+ }
+
+ return offset;
+}
+
+export function findDecimalNumberEnd(source, offset) {
+ for (; offset < source.length; offset++) {
+ if (!isDigit(source.charCodeAt(offset))) {
+ break;
+ }
+ }
+
+ return offset;
+}
+
+// § 4.3.7. Consume an escaped code point
+export function consumeEscaped(source, offset) {
+ // It assumes that the U+005C REVERSE SOLIDUS (\) has already been consumed and
+ // that the next input code point has already been verified to be part of a valid escape.
+ offset += 2;
+
+ // hex digit
+ if (isHexDigit(getCharCode(source, offset - 1))) {
+ // Consume as many hex digits as possible, but no more than 5.
+ // Note that this means 1-6 hex digits have been consumed in total.
+ for (const maxOffset = Math.min(source.length, offset + 5); offset < maxOffset; offset++) {
+ if (!isHexDigit(getCharCode(source, offset))) {
+ break;
+ }
+ }
+
+ // If the next input code point is whitespace, consume it as well.
+ const code = getCharCode(source, offset);
+ if (isWhiteSpace(code)) {
+ offset += getNewlineLength(source, offset, code);
+ }
+ }
+
+ return offset;
+}
+
+// §4.3.11. Consume a name
+// Note: This algorithm does not do the verification of the first few code points that are necessary
+// to ensure the returned code points would constitute an <ident-token>. If that is the intended use,
+// ensure that the stream starts with an identifier before calling this algorithm.
+export function consumeName(source, offset) {
+ // Let result initially be an empty string.
+ // Repeatedly consume the next input code point from the stream:
+ for (; offset < source.length; offset++) {
+ const code = source.charCodeAt(offset);
+
+ // name code point
+ if (isName(code)) {
+ // Append the code point to result.
+ continue;
+ }
+
+ // the stream starts with a valid escape
+ if (isValidEscape(code, getCharCode(source, offset + 1))) {
+ // Consume an escaped code point. Append the returned code point to result.
+ offset = consumeEscaped(source, offset) - 1;
+ continue;
+ }
+
+ // anything else
+ // Reconsume the current input code point. Return result.
+ break;
+ }
+
+ return offset;
+}
+
+// §4.3.12. Consume a number
+export function consumeNumber(source, offset) {
+ let code = source.charCodeAt(offset);
+
+ // 2. If the next input code point is U+002B PLUS SIGN (+) or U+002D HYPHEN-MINUS (-),
+ // consume it and append it to repr.
+ if (code === 0x002B || code === 0x002D) {
+ code = source.charCodeAt(offset += 1);
+ }
+
+ // 3. While the next input code point is a digit, consume it and append it to repr.
+ if (isDigit(code)) {
+ offset = findDecimalNumberEnd(source, offset + 1);
+ code = source.charCodeAt(offset);
+ }
+
+ // 4. If the next 2 input code points are U+002E FULL STOP (.) followed by a digit, then:
+ if (code === 0x002E && isDigit(source.charCodeAt(offset + 1))) {
+ // 4.1 Consume them.
+ // 4.2 Append them to repr.
+ offset += 2;
+
+ // 4.3 Set type to "number".
+ // TODO
+
+ // 4.4 While the next input code point is a digit, consume it and append it to repr.
+
+ offset = findDecimalNumberEnd(source, offset);
+ }
+
+ // 5. If the next 2 or 3 input code points are U+0045 LATIN CAPITAL LETTER E (E)
+ // or U+0065 LATIN SMALL LETTER E (e), ... , followed by a digit, then:
+ if (cmpChar(source, offset, 101 /* e */)) {
+ let sign = 0;
+ code = source.charCodeAt(offset + 1);
+
+ // ... optionally followed by U+002D HYPHEN-MINUS (-) or U+002B PLUS SIGN (+) ...
+ if (code === 0x002D || code === 0x002B) {
+ sign = 1;
+ code = source.charCodeAt(offset + 2);
+ }
+
+ // ... followed by a digit
+ if (isDigit(code)) {
+ // 5.1 Consume them.
+ // 5.2 Append them to repr.
+
+ // 5.3 Set type to "number".
+ // TODO
+
+ // 5.4 While the next input code point is a digit, consume it and append it to repr.
+ offset = findDecimalNumberEnd(source, offset + 1 + sign + 1);
+ }
+ }
+
+ return offset;
+}
+
+// § 4.3.14. Consume the remnants of a bad url
+// ... its sole use is to consume enough of the input stream to reach a recovery point
+// where normal tokenizing can resume.
+export function consumeBadUrlRemnants(source, offset) {
+ // Repeatedly consume the next input code point from the stream:
+ for (; offset < source.length; offset++) {
+ const code = source.charCodeAt(offset);
+
+ // U+0029 RIGHT PARENTHESIS ())
+ // EOF
+ if (code === 0x0029) {
+ // Return.
+ offset++;
+ break;
+ }
+
+ if (isValidEscape(code, getCharCode(source, offset + 1))) {
+ // Consume an escaped code point.
+ // Note: This allows an escaped right parenthesis ("\)") to be encountered
+ // without ending the <bad-url-token>. This is otherwise identical to
+ // the "anything else" clause.
+ offset = consumeEscaped(source, offset);
+ }
+ }
+
+ return offset;
+}
+
+// § 4.3.7. Consume an escaped code point
+// Note: This algorithm assumes that escaped is valid without leading U+005C REVERSE SOLIDUS (\)
+export function decodeEscaped(escaped) {
+ // Single char escaped that's not a hex digit
+ if (escaped.length === 1 && !isHexDigit(escaped.charCodeAt(0))) {
+ return escaped[0];
+ }
+
+ // Interpret the hex digits as a hexadecimal number.
+ let code = parseInt(escaped, 16);
+
+ if (
+ (code === 0) || // If this number is zero,
+ (code >= 0xD800 && code <= 0xDFFF) || // or is for a surrogate,
+ (code > 0x10FFFF) // or is greater than the maximum allowed code point
+ ) {
+ // ... return U+FFFD REPLACEMENT CHARACTER
+ code = 0xFFFD;
+ }
+
+ // Otherwise, return the code point with that value.
+ return String.fromCodePoint(code);
+}
diff --git a/vanilla/node_modules/css-tree/lib/utils/List.js b/vanilla/node_modules/css-tree/lib/utils/List.js
new file mode 100644
index 0000000..8953264
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/utils/List.js
@@ -0,0 +1,469 @@
+//
+// list
+// ┌──────┐
+// ┌──────────────┼─head │
+// │ │ tail─┼──────────────┐
+// │ └──────┘ │
+// ▼ ▼
+// item item item item
+// ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐
+// null ◀──┼─prev │◀───┼─prev │◀───┼─prev │◀───┼─prev │
+// │ next─┼───▶│ next─┼───▶│ next─┼───▶│ next─┼──▶ null
+// ├──────┤ ├──────┤ ├──────┤ ├──────┤
+// │ data │ │ data │ │ data │ │ data │
+// └──────┘ └──────┘ └──────┘ └──────┘
+//
+
+let releasedCursors = null;
+
+export class List {
+ static createItem(data) {
+ return {
+ prev: null,
+ next: null,
+ data
+ };
+ }
+
+ constructor() {
+ this.head = null;
+ this.tail = null;
+ this.cursor = null;
+ }
+ createItem(data) {
+ return List.createItem(data);
+ }
+
+ // cursor helpers
+ allocateCursor(prev, next) {
+ let cursor;
+
+ if (releasedCursors !== null) {
+ cursor = releasedCursors;
+ releasedCursors = releasedCursors.cursor;
+ cursor.prev = prev;
+ cursor.next = next;
+ cursor.cursor = this.cursor;
+ } else {
+ cursor = {
+ prev,
+ next,
+ cursor: this.cursor
+ };
+ }
+
+ this.cursor = cursor;
+
+ return cursor;
+ }
+ releaseCursor() {
+ const { cursor } = this;
+
+ this.cursor = cursor.cursor;
+ cursor.prev = null;
+ cursor.next = null;
+ cursor.cursor = releasedCursors;
+ releasedCursors = cursor;
+ }
+ updateCursors(prevOld, prevNew, nextOld, nextNew) {
+ let { cursor } = this;
+
+ while (cursor !== null) {
+ if (cursor.prev === prevOld) {
+ cursor.prev = prevNew;
+ }
+
+ if (cursor.next === nextOld) {
+ cursor.next = nextNew;
+ }
+
+ cursor = cursor.cursor;
+ }
+ }
+ *[Symbol.iterator]() {
+ for (let cursor = this.head; cursor !== null; cursor = cursor.next) {
+ yield cursor.data;
+ }
+ }
+
+ // getters
+ get size() {
+ let size = 0;
+
+ for (let cursor = this.head; cursor !== null; cursor = cursor.next) {
+ size++;
+ }
+
+ return size;
+ }
+ get isEmpty() {
+ return this.head === null;
+ }
+ get first() {
+ return this.head && this.head.data;
+ }
+ get last() {
+ return this.tail && this.tail.data;
+ }
+
+ // convertors
+ fromArray(array) {
+ let cursor = null;
+ this.head = null;
+
+ for (let data of array) {
+ const item = List.createItem(data);
+
+ if (cursor !== null) {
+ cursor.next = item;
+ } else {
+ this.head = item;
+ }
+
+ item.prev = cursor;
+ cursor = item;
+ }
+
+ this.tail = cursor;
+ return this;
+ }
+ toArray() {
+ return [...this];
+ }
+ toJSON() {
+ return [...this];
+ }
+
+ // array-like methods
+ forEach(fn, thisArg = this) {
+ // push cursor
+ const cursor = this.allocateCursor(null, this.head);
+
+ while (cursor.next !== null) {
+ const item = cursor.next;
+ cursor.next = item.next;
+ fn.call(thisArg, item.data, item, this);
+ }
+
+ // pop cursor
+ this.releaseCursor();
+ }
+ forEachRight(fn, thisArg = this) {
+ // push cursor
+ const cursor = this.allocateCursor(this.tail, null);
+
+ while (cursor.prev !== null) {
+ const item = cursor.prev;
+ cursor.prev = item.prev;
+ fn.call(thisArg, item.data, item, this);
+ }
+
+ // pop cursor
+ this.releaseCursor();
+ }
+ reduce(fn, initialValue, thisArg = this) {
+ // push cursor
+ let cursor = this.allocateCursor(null, this.head);
+ let acc = initialValue;
+ let item;
+
+ while (cursor.next !== null) {
+ item = cursor.next;
+ cursor.next = item.next;
+
+ acc = fn.call(thisArg, acc, item.data, item, this);
+ }
+
+ // pop cursor
+ this.releaseCursor();
+
+ return acc;
+ }
+ reduceRight(fn, initialValue, thisArg = this) {
+ // push cursor
+ let cursor = this.allocateCursor(this.tail, null);
+ let acc = initialValue;
+ let item;
+
+ while (cursor.prev !== null) {
+ item = cursor.prev;
+ cursor.prev = item.prev;
+
+ acc = fn.call(thisArg, acc, item.data, item, this);
+ }
+
+ // pop cursor
+ this.releaseCursor();
+
+ return acc;
+ }
+ some(fn, thisArg = this) {
+ for (let cursor = this.head; cursor !== null; cursor = cursor.next) {
+ if (fn.call(thisArg, cursor.data, cursor, this)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+ map(fn, thisArg = this) {
+ const result = new List();
+
+ for (let cursor = this.head; cursor !== null; cursor = cursor.next) {
+ result.appendData(fn.call(thisArg, cursor.data, cursor, this));
+ }
+
+ return result;
+ }
+ filter(fn, thisArg = this) {
+ const result = new List();
+
+ for (let cursor = this.head; cursor !== null; cursor = cursor.next) {
+ if (fn.call(thisArg, cursor.data, cursor, this)) {
+ result.appendData(cursor.data);
+ }
+ }
+
+ return result;
+ }
+
+ nextUntil(start, fn, thisArg = this) {
+ if (start === null) {
+ return;
+ }
+
+ // push cursor
+ const cursor = this.allocateCursor(null, start);
+
+ while (cursor.next !== null) {
+ const item = cursor.next;
+ cursor.next = item.next;
+ if (fn.call(thisArg, item.data, item, this)) {
+ break;
+ }
+ }
+
+ // pop cursor
+ this.releaseCursor();
+ }
+ prevUntil(start, fn, thisArg = this) {
+ if (start === null) {
+ return;
+ }
+
+ // push cursor
+ const cursor = this.allocateCursor(start, null);
+
+ while (cursor.prev !== null) {
+ const item = cursor.prev;
+ cursor.prev = item.prev;
+ if (fn.call(thisArg, item.data, item, this)) {
+ break;
+ }
+ }
+
+ // pop cursor
+ this.releaseCursor();
+ }
+
+ // mutation
+ clear() {
+ this.head = null;
+ this.tail = null;
+ }
+ copy() {
+ const result = new List();
+
+ for (let data of this) {
+ result.appendData(data);
+ }
+
+ return result;
+ }
+ prepend(item) {
+ // head
+ // ^
+ // item
+ this.updateCursors(null, item, this.head, item);
+
+ // insert to the beginning of the list
+ if (this.head !== null) {
+ // new item <- first item
+ this.head.prev = item;
+ // new item -> first item
+ item.next = this.head;
+ } else {
+ // if list has no head, then it also has no tail
+ // in this case tail points to the new item
+ this.tail = item;
+ }
+
+ // head always points to new item
+ this.head = item;
+ return this;
+ }
+ prependData(data) {
+ return this.prepend(List.createItem(data));
+ }
+ append(item) {
+ return this.insert(item);
+ }
+ appendData(data) {
+ return this.insert(List.createItem(data));
+ }
+ insert(item, before = null) {
+ if (before !== null) {
+ // prev before
+ // ^
+ // item
+ this.updateCursors(before.prev, item, before, item);
+
+ if (before.prev === null) {
+ // insert to the beginning of list
+ if (this.head !== before) {
+ throw new Error('before doesn\'t belong to list');
+ }
+ // since head points to before therefore list doesn't empty
+ // no need to check tail
+ this.head = item;
+ before.prev = item;
+ item.next = before;
+ this.updateCursors(null, item);
+ } else {
+ // insert between two items
+ before.prev.next = item;
+ item.prev = before.prev;
+ before.prev = item;
+ item.next = before;
+ }
+ } else {
+ // tail
+ // ^
+ // item
+ this.updateCursors(this.tail, item, null, item);
+
+ // insert to the ending of the list
+ if (this.tail !== null) {
+ // last item -> new item
+ this.tail.next = item;
+ // last item <- new item
+ item.prev = this.tail;
+ } else {
+ // if list has no tail, then it also has no head
+ // in this case head points to new item
+ this.head = item;
+ }
+
+ // tail always points to new item
+ this.tail = item;
+ }
+
+ return this;
+ }
+ insertData(data, before) {
+ return this.insert(List.createItem(data), before);
+ }
+ remove(item) {
+ // item
+ // ^
+ // prev next
+ this.updateCursors(item, item.prev, item, item.next);
+
+ if (item.prev !== null) {
+ item.prev.next = item.next;
+ } else {
+ if (this.head !== item) {
+ throw new Error('item doesn\'t belong to list');
+ }
+
+ this.head = item.next;
+ }
+
+ if (item.next !== null) {
+ item.next.prev = item.prev;
+ } else {
+ if (this.tail !== item) {
+ throw new Error('item doesn\'t belong to list');
+ }
+
+ this.tail = item.prev;
+ }
+
+ item.prev = null;
+ item.next = null;
+
+ return item;
+ }
+ push(data) {
+ this.insert(List.createItem(data));
+ }
+ pop() {
+ return this.tail !== null ? this.remove(this.tail) : null;
+ }
+ unshift(data) {
+ this.prepend(List.createItem(data));
+ }
+ shift() {
+ return this.head !== null ? this.remove(this.head) : null;
+ }
+ prependList(list) {
+ return this.insertList(list, this.head);
+ }
+ appendList(list) {
+ return this.insertList(list);
+ }
+ insertList(list, before) {
+ // ignore empty lists
+ if (list.head === null) {
+ return this;
+ }
+
+ if (before !== undefined && before !== null) {
+ this.updateCursors(before.prev, list.tail, before, list.head);
+
+ // insert in the middle of dist list
+ if (before.prev !== null) {
+ // before.prev <-> list.head
+ before.prev.next = list.head;
+ list.head.prev = before.prev;
+ } else {
+ this.head = list.head;
+ }
+
+ before.prev = list.tail;
+ list.tail.next = before;
+ } else {
+ this.updateCursors(this.tail, list.tail, null, list.head);
+
+ // insert to end of the list
+ if (this.tail !== null) {
+ // if destination list has a tail, then it also has a head,
+ // but head doesn't change
+ // dest tail -> source head
+ this.tail.next = list.head;
+ // dest tail <- source head
+ list.head.prev = this.tail;
+ } else {
+ // if list has no a tail, then it also has no a head
+ // in this case points head to new item
+ this.head = list.head;
+ }
+
+ // tail always start point to new item
+ this.tail = list.tail;
+ }
+
+ list.head = null;
+ list.tail = null;
+ return this;
+ }
+ replace(oldItem, newItemOrList) {
+ if ('head' in newItemOrList) {
+ this.insertList(newItemOrList, oldItem);
+ } else {
+ this.insert(newItemOrList, oldItem);
+ }
+
+ this.remove(oldItem);
+ }
+}
diff --git a/vanilla/node_modules/css-tree/lib/utils/clone.js b/vanilla/node_modules/css-tree/lib/utils/clone.js
new file mode 100644
index 0000000..84819c0
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/utils/clone.js
@@ -0,0 +1,21 @@
+import { List } from './List.js';
+
+export function clone(node) {
+ const result = {};
+
+ for (const key of Object.keys(node)) {
+ let value = node[key];
+
+ if (value) {
+ if (Array.isArray(value) || value instanceof List) {
+ value = value.map(clone);
+ } else if (value.constructor === Object) {
+ value = clone(value);
+ }
+ }
+
+ result[key] = value;
+ }
+
+ return result;
+}
diff --git a/vanilla/node_modules/css-tree/lib/utils/create-custom-error.js b/vanilla/node_modules/css-tree/lib/utils/create-custom-error.js
new file mode 100644
index 0000000..dba122f
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/utils/create-custom-error.js
@@ -0,0 +1,14 @@
+export function createCustomError(name, message) {
+ // use Object.create(), because some VMs prevent setting line/column otherwise
+ // (iOS Safari 10 even throws an exception)
+ const error = Object.create(SyntaxError.prototype);
+ const errorStack = new Error();
+
+ return Object.assign(error, {
+ name,
+ message,
+ get stack() {
+ return (errorStack.stack || '').replace(/^(.+\n){1,3}/, `${name}: ${message}\n`);
+ }
+ });
+};
diff --git a/vanilla/node_modules/css-tree/lib/utils/ident.js b/vanilla/node_modules/css-tree/lib/utils/ident.js
new file mode 100644
index 0000000..9cbe0f8
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/utils/ident.js
@@ -0,0 +1,101 @@
+import {
+ isName,
+ isValidEscape,
+ consumeEscaped,
+ decodeEscaped
+} from '../tokenizer/index.js';
+
+const REVERSE_SOLIDUS = 0x005c; // U+005C REVERSE SOLIDUS (\)
+
+export function decode(str) {
+ const end = str.length - 1;
+ let decoded = '';
+
+ for (let i = 0; i < str.length; i++) {
+ let code = str.charCodeAt(i);
+
+ if (code === REVERSE_SOLIDUS) {
+ // special case at the ending
+ if (i === end) {
+ // if the next input code point is EOF, do nothing
+ break;
+ }
+
+ code = str.charCodeAt(++i);
+
+ // consume escaped
+ if (isValidEscape(REVERSE_SOLIDUS, code)) {
+ const escapeStart = i - 1;
+ const escapeEnd = consumeEscaped(str, escapeStart);
+
+ i = escapeEnd - 1;
+ decoded += decodeEscaped(str.substring(escapeStart + 1, escapeEnd));
+ } else {
+ // \r\n
+ if (code === 0x000d && str.charCodeAt(i + 1) === 0x000a) {
+ i++;
+ }
+ }
+ } else {
+ decoded += str[i];
+ }
+ }
+
+ return decoded;
+}
+
+// https://drafts.csswg.org/cssom/#serialize-an-identifier
+// § 2.1. Common Serializing Idioms
+export function encode(str) {
+ let encoded = '';
+
+ // If the character is the first character and is a "-" (U+002D),
+ // and there is no second character, then the escaped character.
+ // Note: That's means a single dash string "-" return as escaped dash,
+ // so move the condition out of the main loop
+ if (str.length === 1 && str.charCodeAt(0) === 0x002D) {
+ return '\\-';
+ }
+
+ // To serialize an identifier means to create a string represented
+ // by the concatenation of, for each character of the identifier:
+ for (let i = 0; i < str.length; i++) {
+ const code = str.charCodeAt(i);
+
+ // If the character is NULL (U+0000), then the REPLACEMENT CHARACTER (U+FFFD).
+ if (code === 0x0000) {
+ encoded += '\uFFFD';
+ continue;
+ }
+
+ if (
+ // If the character is in the range [\1-\1f] (U+0001 to U+001F) or is U+007F ...
+ // Note: Do not compare with 0x0001 since 0x0000 is precessed before
+ code <= 0x001F || code === 0x007F ||
+ // [or] ... is in the range [0-9] (U+0030 to U+0039),
+ (code >= 0x0030 && code <= 0x0039 && (
+ // If the character is the first character ...
+ i === 0 ||
+ // If the character is the second character ... and the first character is a "-" (U+002D)
+ i === 1 && str.charCodeAt(0) === 0x002D
+ ))
+ ) {
+ // ... then the character escaped as code point.
+ encoded += '\\' + code.toString(16) + ' ';
+ continue;
+ }
+
+ // If the character is not handled by one of the above rules and is greater
+ // than or equal to U+0080, is "-" (U+002D) or "_" (U+005F), or is in one
+ // of the ranges [0-9] (U+0030 to U+0039), [A-Z] (U+0041 to U+005A),
+ // or \[a-z] (U+0061 to U+007A), then the character itself.
+ if (isName(code)) {
+ encoded += str.charAt(i);
+ } else {
+ // Otherwise, the escaped character.
+ encoded += '\\' + str.charAt(i);
+ }
+ }
+
+ return encoded;
+}
diff --git a/vanilla/node_modules/css-tree/lib/utils/index.js b/vanilla/node_modules/css-tree/lib/utils/index.js
new file mode 100644
index 0000000..07bf0f9
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/utils/index.js
@@ -0,0 +1,6 @@
+export * from './clone.js';
+export * as ident from './ident.js';
+export * from './List.js';
+export * from './names.js';
+export * as string from './string.js';
+export * as url from './url.js';
diff --git a/vanilla/node_modules/css-tree/lib/utils/names.js b/vanilla/node_modules/css-tree/lib/utils/names.js
new file mode 100644
index 0000000..b4f74b9
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/utils/names.js
@@ -0,0 +1,106 @@
+const keywords = new Map();
+const properties = new Map();
+const HYPHENMINUS = 45; // '-'.charCodeAt()
+
+export const keyword = getKeywordDescriptor;
+export const property = getPropertyDescriptor;
+export const vendorPrefix = getVendorPrefix;
+export function isCustomProperty(str, offset) {
+ offset = offset || 0;
+
+ return str.length - offset >= 2 &&
+ str.charCodeAt(offset) === HYPHENMINUS &&
+ str.charCodeAt(offset + 1) === HYPHENMINUS;
+}
+
+function getVendorPrefix(str, offset) {
+ offset = offset || 0;
+
+ // verdor prefix should be at least 3 chars length
+ if (str.length - offset >= 3) {
+ // vendor prefix starts with hyper minus following non-hyper minus
+ if (str.charCodeAt(offset) === HYPHENMINUS &&
+ str.charCodeAt(offset + 1) !== HYPHENMINUS) {
+ // vendor prefix should contain a hyper minus at the ending
+ const secondDashIndex = str.indexOf('-', offset + 2);
+
+ if (secondDashIndex !== -1) {
+ return str.substring(offset, secondDashIndex + 1);
+ }
+ }
+ }
+
+ return '';
+}
+
+function getKeywordDescriptor(keyword) {
+ if (keywords.has(keyword)) {
+ return keywords.get(keyword);
+ }
+
+ const name = keyword.toLowerCase();
+ let descriptor = keywords.get(name);
+
+ if (descriptor === undefined) {
+ const custom = isCustomProperty(name, 0);
+ const vendor = !custom ? getVendorPrefix(name, 0) : '';
+ descriptor = Object.freeze({
+ basename: name.substr(vendor.length),
+ name,
+ prefix: vendor,
+ vendor,
+ custom
+ });
+ }
+
+ keywords.set(keyword, descriptor);
+
+ return descriptor;
+}
+
+function getPropertyDescriptor(property) {
+ if (properties.has(property)) {
+ return properties.get(property);
+ }
+
+ let name = property;
+ let hack = property[0];
+
+ if (hack === '/') {
+ hack = property[1] === '/' ? '//' : '/';
+ } else if (hack !== '_' &&
+ hack !== '*' &&
+ hack !== '$' &&
+ hack !== '#' &&
+ hack !== '+' &&
+ hack !== '&') {
+ hack = '';
+ }
+
+ const custom = isCustomProperty(name, hack.length);
+
+ // re-use result when possible (the same as for lower case)
+ if (!custom) {
+ name = name.toLowerCase();
+ if (properties.has(name)) {
+ const descriptor = properties.get(name);
+ properties.set(property, descriptor);
+ return descriptor;
+ }
+ }
+
+ const vendor = !custom ? getVendorPrefix(name, hack.length) : '';
+ const prefix = name.substr(0, hack.length + vendor.length);
+ const descriptor = Object.freeze({
+ basename: name.substr(prefix.length),
+ name: name.substr(hack.length),
+ hack,
+ vendor,
+ prefix,
+ custom
+ });
+
+ properties.set(property, descriptor);
+
+ return descriptor;
+}
diff --git a/vanilla/node_modules/css-tree/lib/utils/string.js b/vanilla/node_modules/css-tree/lib/utils/string.js
new file mode 100644
index 0000000..928a85b
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/utils/string.js
@@ -0,0 +1,99 @@
+import {
+ isHexDigit,
+ isWhiteSpace,
+ isValidEscape,
+ consumeEscaped,
+ decodeEscaped
+} from '../tokenizer/index.js';
+
+const REVERSE_SOLIDUS = 0x005c; // U+005C REVERSE SOLIDUS (\)
+const QUOTATION_MARK = 0x0022; // "
+const APOSTROPHE = 0x0027; // '
+
+export function decode(str) {
+ const len = str.length;
+ const firstChar = str.charCodeAt(0);
+ const start = firstChar === QUOTATION_MARK || firstChar === APOSTROPHE ? 1 : 0;
+ const end = start === 1 && len > 1 && str.charCodeAt(len - 1) === firstChar ? len - 2 : len - 1;
+ let decoded = '';
+
+ for (let i = start; i <= end; i++) {
+ let code = str.charCodeAt(i);
+
+ if (code === REVERSE_SOLIDUS) {
+ // special case at the ending
+ if (i === end) {
+ // if the next input code point is EOF, do nothing
+ // otherwise include last quote as escaped
+ if (i !== len - 1) {
+ decoded = str.substr(i + 1);
+ }
+ break;
+ }
+
+ code = str.charCodeAt(++i);
+
+ // consume escaped
+ if (isValidEscape(REVERSE_SOLIDUS, code)) {
+ const escapeStart = i - 1;
+ const escapeEnd = consumeEscaped(str, escapeStart);
+
+ i = escapeEnd - 1;
+ decoded += decodeEscaped(str.substring(escapeStart + 1, escapeEnd));
+ } else {
+ // \r\n
+ if (code === 0x000d && str.charCodeAt(i + 1) === 0x000a) {
+ i++;
+ }
+ }
+ } else {
+ decoded += str[i];
+ }
+ }
+
+ return decoded;
+}
+
+// https://drafts.csswg.org/cssom/#serialize-a-string
+// § 2.1. Common Serializing Idioms
+export function encode(str, apostrophe) {
+ const quote = apostrophe ? '\'' : '"';
+ const quoteCode = apostrophe ? APOSTROPHE : QUOTATION_MARK;
+ let encoded = '';
+ let wsBeforeHexIsNeeded = false;
+
+ for (let i = 0; i < str.length; i++) {
+ const code = str.charCodeAt(i);
+
+ // If the character is NULL (U+0000), then the REPLACEMENT CHARACTER (U+FFFD).
+ if (code === 0x0000) {
+ encoded += '\uFFFD';
+ continue;
+ }
+
+ // If the character is in the range [\1-\1f] (U+0001 to U+001F) or is U+007F,
+ // the character escaped as code point.
+ // Note: Do not compare with 0x0001 since 0x0000 is precessed before
+ if (code <= 0x001f || code === 0x007F) {
+ encoded += '\\' + code.toString(16);
+ wsBeforeHexIsNeeded = true;
+ continue;
+ }
+
+ // If the character is '"' (U+0022) or "\" (U+005C), the escaped character.
+ if (code === quoteCode || code === REVERSE_SOLIDUS) {
+ encoded += '\\' + str.charAt(i);
+ wsBeforeHexIsNeeded = false;
+ } else {
+ if (wsBeforeHexIsNeeded && (isHexDigit(code) || isWhiteSpace(code))) {
+ encoded += ' ';
+ }
+
+ // Otherwise, the character itself.
+ encoded += str.charAt(i);
+ wsBeforeHexIsNeeded = false;
+ }
+ }
+
+ return quote + encoded + quote;
+}
diff --git a/vanilla/node_modules/css-tree/lib/utils/url.js b/vanilla/node_modules/css-tree/lib/utils/url.js
new file mode 100644
index 0000000..cce5709
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/utils/url.js
@@ -0,0 +1,108 @@
+import {
+ isHexDigit,
+ isWhiteSpace,
+ isValidEscape,
+ consumeEscaped,
+ decodeEscaped
+} from '../tokenizer/index.js';
+
+const SPACE = 0x0020; // U+0020 SPACE
+const REVERSE_SOLIDUS = 0x005c; // U+005C REVERSE SOLIDUS (\)
+const QUOTATION_MARK = 0x0022; // "
+const APOSTROPHE = 0x0027; // '
+const LEFTPARENTHESIS = 0x0028; // U+0028 LEFT PARENTHESIS (()
+const RIGHTPARENTHESIS = 0x0029; // U+0029 RIGHT PARENTHESIS ())
+
+export function decode(str) {
+ const len = str.length;
+ let start = 4; // length of "url("
+ let end = str.charCodeAt(len - 1) === RIGHTPARENTHESIS ? len - 2 : len - 1;
+ let decoded = '';
+
+ while (start < end && isWhiteSpace(str.charCodeAt(start))) {
+ start++;
+ }
+
+ while (start < end && isWhiteSpace(str.charCodeAt(end))) {
+ end--;
+ }
+
+ for (let i = start; i <= end; i++) {
+ let code = str.charCodeAt(i);
+
+ if (code === REVERSE_SOLIDUS) {
+ // special case at the ending
+ if (i === end) {
+ // if the next input code point is EOF, do nothing
+ // otherwise include last left parenthesis as escaped
+ if (i !== len - 1) {
+ decoded = str.substr(i + 1);
+ }
+ break;
+ }
+
+ code = str.charCodeAt(++i);
+
+ // consume escaped
+ if (isValidEscape(REVERSE_SOLIDUS, code)) {
+ const escapeStart = i - 1;
+ const escapeEnd = consumeEscaped(str, escapeStart);
+
+ i = escapeEnd - 1;
+ decoded += decodeEscaped(str.substring(escapeStart + 1, escapeEnd));
+ } else {
+ // \r\n
+ if (code === 0x000d && str.charCodeAt(i + 1) === 0x000a) {
+ i++;
+ }
+ }
+ } else {
+ decoded += str[i];
+ }
+ }
+
+ return decoded;
+}
+
+export function encode(str) {
+ let encoded = '';
+ let wsBeforeHexIsNeeded = false;
+
+ for (let i = 0; i < str.length; i++) {
+ const code = str.charCodeAt(i);
+
+ // If the character is NULL (U+0000), then the REPLACEMENT CHARACTER (U+FFFD).
+ if (code === 0x0000) {
+ encoded += '\uFFFD';
+ continue;
+ }
+
+ // If the character is in the range [\1-\1f] (U+0001 to U+001F) or is U+007F,
+ // the character escaped as code point.
+ // Note: Do not compare with 0x0001 since 0x0000 is precessed before
+ if (code <= 0x001f || code === 0x007F) {
+ encoded += '\\' + code.toString(16);
+ wsBeforeHexIsNeeded = true;
+ continue;
+ }
+
+ if (code === SPACE ||
+ code === REVERSE_SOLIDUS ||
+ code === QUOTATION_MARK ||
+ code === APOSTROPHE ||
+ code === LEFTPARENTHESIS ||
+ code === RIGHTPARENTHESIS) {
+ encoded += '\\' + str.charAt(i);
+ wsBeforeHexIsNeeded = false;
+ } else {
+ if (wsBeforeHexIsNeeded && isHexDigit(code)) {
+ encoded += ' ';
+ }
+
+ encoded += str.charAt(i);
+ wsBeforeHexIsNeeded = false;
+ }
+ }
+
+ return 'url(' + encoded + ')';
+}
diff --git a/vanilla/node_modules/css-tree/lib/version.js b/vanilla/node_modules/css-tree/lib/version.js
new file mode 100644
index 0000000..dde3a83
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/version.js
@@ -0,0 +1,5 @@
+import { createRequire } from 'module';
+
+const require = createRequire(import.meta.url);
+
+export const { version } = require('../package.json');
diff --git a/vanilla/node_modules/css-tree/lib/walker/create.js b/vanilla/node_modules/css-tree/lib/walker/create.js
new file mode 100644
index 0000000..ca76e03
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/walker/create.js
@@ -0,0 +1,287 @@
+const { hasOwnProperty } = Object.prototype;
+const noop = function() {};
+
+function ensureFunction(value) {
+ return typeof value === 'function' ? value : noop;
+}
+
+function invokeForType(fn, type) {
+ return function(node, item, list) {
+ if (node.type === type) {
+ fn.call(this, node, item, list);
+ }
+ };
+}
+
+function getWalkersFromStructure(name, nodeType) {
+ const structure = nodeType.structure;
+ const walkers = [];
+
+ for (const key in structure) {
+ if (hasOwnProperty.call(structure, key) === false) {
+ continue;
+ }
+
+ let fieldTypes = structure[key];
+ const walker = {
+ name: key,
+ type: false,
+ nullable: false
+ };
+
+ if (!Array.isArray(fieldTypes)) {
+ fieldTypes = [fieldTypes];
+ }
+
+ for (const fieldType of fieldTypes) {
+ if (fieldType === null) {
+ walker.nullable = true;
+ } else if (typeof fieldType === 'string') {
+ walker.type = 'node';
+ } else if (Array.isArray(fieldType)) {
+ walker.type = 'list';
+ }
+ }
+
+ if (walker.type) {
+ walkers.push(walker);
+ }
+ }
+
+ if (walkers.length) {
+ return {
+ context: nodeType.walkContext,
+ fields: walkers
+ };
+ }
+
+ return null;
+}
+
+function getTypesFromConfig(config) {
+ const types = {};
+
+ for (const name in config.node) {
+ if (hasOwnProperty.call(config.node, name)) {
+ const nodeType = config.node[name];
+
+ if (!nodeType.structure) {
+ throw new Error('Missed `structure` field in `' + name + '` node type definition');
+ }
+
+ types[name] = getWalkersFromStructure(name, nodeType);
+ }
+ }
+
+ return types;
+}
+
+function createTypeIterator(config, reverse) {
+ const fields = config.fields.slice();
+ const contextName = config.context;
+ const useContext = typeof contextName === 'string';
+
+ if (reverse) {
+ fields.reverse();
+ }
+
+ return function(node, context, walk, walkReducer) {
+ let prevContextValue;
+
+ if (useContext) {
+ prevContextValue = context[contextName];
+ context[contextName] = node;
+ }
+
+ for (const field of fields) {
+ const ref = node[field.name];
+
+ if (!field.nullable || ref) {
+ if (field.type === 'list') {
+ const breakWalk = reverse
+ ? ref.reduceRight(walkReducer, false)
+ : ref.reduce(walkReducer, false);
+
+ if (breakWalk) {
+ return true;
+ }
+ } else if (walk(ref)) {
+ return true;
+ }
+ }
+ }
+
+ if (useContext) {
+ context[contextName] = prevContextValue;
+ }
+ };
+}
+
+function createFastTraveralMap({
+ StyleSheet,
+ Atrule,
+ Rule,
+ Block,
+ DeclarationList
+}) {
+ return {
+ Atrule: {
+ StyleSheet,
+ Atrule,
+ Rule,
+ Block
+ },
+ Rule: {
+ StyleSheet,
+ Atrule,
+ Rule,
+ Block
+ },
+ Declaration: {
+ StyleSheet,
+ Atrule,
+ Rule,
+ Block,
+ DeclarationList
+ }
+ };
+}
+
+export function createWalker(config) {
+ const types = getTypesFromConfig(config);
+ const iteratorsNatural = {};
+ const iteratorsReverse = {};
+ const breakWalk = Symbol('break-walk');
+ const skipNode = Symbol('skip-node');
+
+ for (const name in types) {
+ if (hasOwnProperty.call(types, name) && types[name] !== null) {
+ iteratorsNatural[name] = createTypeIterator(types[name], false);
+ iteratorsReverse[name] = createTypeIterator(types[name], true);
+ }
+ }
+
+ const fastTraversalIteratorsNatural = createFastTraveralMap(iteratorsNatural);
+ const fastTraversalIteratorsReverse = createFastTraveralMap(iteratorsReverse);
+
+ const walk = function(root, options) {
+ function walkNode(node, item, list) {
+ const enterRet = enter.call(context, node, item, list);
+
+ if (enterRet === breakWalk) {
+ return true;
+ }
+
+ if (enterRet === skipNode) {
+ return false;
+ }
+
+ if (iterators.hasOwnProperty(node.type)) {
+ if (iterators[node.type](node, context, walkNode, walkReducer)) {
+ return true;
+ }
+ }
+
+ if (leave.call(context, node, item, list) === breakWalk) {
+ return true;
+ }
+
+ return false;
+ }
+
+ let enter = noop;
+ let leave = noop;
+ let iterators = iteratorsNatural;
+ let walkReducer = (ret, data, item, list) => ret || walkNode(data, item, list);
+ const context = {
+ break: breakWalk,
+ skip: skipNode,
+
+ root,
+ stylesheet: null,
+ atrule: null,
+ atrulePrelude: null,
+ rule: null,
+ selector: null,
+ block: null,
+ declaration: null,
+ function: null
+ };
+
+ if (typeof options === 'function') {
+ enter = options;
+ } else if (options) {
+ enter = ensureFunction(options.enter);
+ leave = ensureFunction(options.leave);
+
+ if (options.reverse) {
+ iterators = iteratorsReverse;
+ }
+
+ if (options.visit) {
+ if (fastTraversalIteratorsNatural.hasOwnProperty(options.visit)) {
+ iterators = options.reverse
+ ? fastTraversalIteratorsReverse[options.visit]
+ : fastTraversalIteratorsNatural[options.visit];
+ } else if (!types.hasOwnProperty(options.visit)) {
+ throw new Error('Bad value `' + options.visit + '` for `visit` option (should be: ' + Object.keys(types).sort().join(', ') + ')');
+ }
+
+ enter = invokeForType(enter, options.visit);
+ leave = invokeForType(leave, options.visit);
+ }
+ }
+
+ if (enter === noop && leave === noop) {
+ throw new Error('Neither `enter` nor `leave` walker handler is set or both aren\'t a function');
+ }
+
+ walkNode(root);
+ };
+
+ walk.break = breakWalk;
+ walk.skip = skipNode;
+
+ walk.find = function(ast, fn) {
+ let found = null;
+
+ walk(ast, function(node, item, list) {
+ if (fn.call(this, node, item, list)) {
+ found = node;
+ return breakWalk;
+ }
+ });
+
+ return found;
+ };
+
+ walk.findLast = function(ast, fn) {
+ let found = null;
+
+ walk(ast, {
+ reverse: true,
+ enter(node, item, list) {
+ if (fn.call(this, node, item, list)) {
+ found = node;
+ return breakWalk;
+ }
+ }
+ });
+
+ return found;
+ };
+
+ walk.findAll = function(ast, fn) {
+ const found = [];
+
+ walk(ast, function(node, item, list) {
+ if (fn.call(this, node, item, list)) {
+ found.push(node);
+ }
+ });
+
+ return found;
+ };
+
+ return walk;
+};
diff --git a/vanilla/node_modules/css-tree/lib/walker/index.js b/vanilla/node_modules/css-tree/lib/walker/index.js
new file mode 100644
index 0000000..24cb477
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/walker/index.js
@@ -0,0 +1,4 @@
+import { createWalker } from './create.js';
+import config from '../syntax/config/walker.js';
+
+export default createWalker(config);