diff options
| author | Adam Mathes <adam@adammathes.com> | 2026-02-13 21:34:48 -0800 |
|---|---|---|
| committer | Adam Mathes <adam@adammathes.com> | 2026-02-13 21:34:48 -0800 |
| commit | 76cb9c2a39d477a64824a985ade40507e3bbade1 (patch) | |
| tree | 41e997aa9c6f538d3a136af61dae9424db2005a9 /vanilla/node_modules/css-tree/lib/syntax | |
| parent | 819a39a21ac992b1393244a4c283bbb125208c69 (diff) | |
| download | neko-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/syntax')
81 files changed, 3957 insertions, 0 deletions
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 +}; |
