diff options
Diffstat (limited to 'vanilla/node_modules/css-tree/cjs/lexer')
| -rw-r--r-- | vanilla/node_modules/css-tree/cjs/lexer/Lexer.cjs | 517 | ||||
| -rw-r--r-- | vanilla/node_modules/css-tree/cjs/lexer/error.cjs | 128 | ||||
| -rw-r--r-- | vanilla/node_modules/css-tree/cjs/lexer/generic-an-plus-b.cjs | 235 | ||||
| -rw-r--r-- | vanilla/node_modules/css-tree/cjs/lexer/generic-const.cjs | 12 | ||||
| -rw-r--r-- | vanilla/node_modules/css-tree/cjs/lexer/generic-urange.cjs | 149 | ||||
| -rw-r--r-- | vanilla/node_modules/css-tree/cjs/lexer/generic.cjs | 589 | ||||
| -rw-r--r-- | vanilla/node_modules/css-tree/cjs/lexer/index.cjs | 7 | ||||
| -rw-r--r-- | vanilla/node_modules/css-tree/cjs/lexer/match-graph.cjs | 530 | ||||
| -rw-r--r-- | vanilla/node_modules/css-tree/cjs/lexer/match.cjs | 632 | ||||
| -rw-r--r-- | vanilla/node_modules/css-tree/cjs/lexer/prepare-tokens.cjs | 54 | ||||
| -rw-r--r-- | vanilla/node_modules/css-tree/cjs/lexer/search.cjs | 65 | ||||
| -rw-r--r-- | vanilla/node_modules/css-tree/cjs/lexer/structure.cjs | 173 | ||||
| -rw-r--r-- | vanilla/node_modules/css-tree/cjs/lexer/trace.cjs | 73 | ||||
| -rw-r--r-- | vanilla/node_modules/css-tree/cjs/lexer/units.cjs | 38 |
14 files changed, 3202 insertions, 0 deletions
diff --git a/vanilla/node_modules/css-tree/cjs/lexer/Lexer.cjs b/vanilla/node_modules/css-tree/cjs/lexer/Lexer.cjs new file mode 100644 index 0000000..a6d1fcb --- /dev/null +++ b/vanilla/node_modules/css-tree/cjs/lexer/Lexer.cjs @@ -0,0 +1,517 @@ +'use strict'; + +const error = require('./error.cjs'); +const names = require('../utils/names.cjs'); +const genericConst = require('./generic-const.cjs'); +const generic = require('./generic.cjs'); +const units = require('./units.cjs'); +const prepareTokens = require('./prepare-tokens.cjs'); +const matchGraph = require('./match-graph.cjs'); +const match = require('./match.cjs'); +const trace = require('./trace.cjs'); +const search = require('./search.cjs'); +const structure = require('./structure.cjs'); +const parse = require('../definition-syntax/parse.cjs'); +const generate = require('../definition-syntax/generate.cjs'); +const walk = require('../definition-syntax/walk.cjs'); + +function dumpMapSyntax(map, compact, syntaxAsAst) { + const result = {}; + + for (const name in map) { + if (map[name].syntax) { + result[name] = syntaxAsAst + ? map[name].syntax + : generate.generate(map[name].syntax, { compact }); + } + } + + return result; +} + +function dumpAtruleMapSyntax(map, compact, syntaxAsAst) { + const result = {}; + + for (const [name, atrule] of Object.entries(map)) { + result[name] = { + prelude: atrule.prelude && ( + syntaxAsAst + ? atrule.prelude.syntax + : generate.generate(atrule.prelude.syntax, { compact }) + ), + descriptors: atrule.descriptors && dumpMapSyntax(atrule.descriptors, compact, syntaxAsAst) + }; + } + + return result; +} + +function valueHasVar(tokens) { + for (let i = 0; i < tokens.length; i++) { + if (tokens[i].value.toLowerCase() === 'var(') { + return true; + } + } + + return false; +} + +function syntaxHasTopLevelCommaMultiplier(syntax) { + const singleTerm = syntax.terms[0]; + + return ( + syntax.explicit === false && + syntax.terms.length === 1 && + singleTerm.type === 'Multiplier' && + singleTerm.comma === true + ); +} + +function buildMatchResult(matched, error, iterations) { + return { + matched, + iterations, + error, + ...trace + }; +} + +function matchSyntax(lexer, syntax, value, useCssWideKeywords) { + const tokens = prepareTokens(value, lexer.syntax); + let result; + + if (valueHasVar(tokens)) { + return buildMatchResult(null, new Error('Matching for a tree with var() is not supported')); + } + + if (useCssWideKeywords) { + result = match.matchAsTree(tokens, lexer.cssWideKeywordsSyntax, lexer); + } + + if (!useCssWideKeywords || !result.match) { + result = match.matchAsTree(tokens, syntax.match, lexer); + if (!result.match) { + return buildMatchResult( + null, + new error.SyntaxMatchError(result.reason, syntax.syntax, value, result), + result.iterations + ); + } + } + + return buildMatchResult(result.match, null, result.iterations); +} + +class Lexer { + constructor(config, syntax, structure$1) { + this.cssWideKeywords = genericConst.cssWideKeywords; + this.syntax = syntax; + this.generic = false; + this.units = { ...units }; + this.atrules = Object.create(null); + this.properties = Object.create(null); + this.types = Object.create(null); + this.structure = structure$1 || structure.getStructureFromConfig(config); + + if (config) { + if (config.cssWideKeywords) { + this.cssWideKeywords = config.cssWideKeywords; + } + + if (config.units) { + for (const group of Object.keys(units)) { + if (Array.isArray(config.units[group])) { + this.units[group] = config.units[group]; + } + } + } + + if (config.types) { + for (const [name, type] of Object.entries(config.types)) { + this.addType_(name, type); + } + } + + if (config.generic) { + this.generic = true; + for (const [name, value] of Object.entries(generic.createGenericTypes(this.units))) { + this.addType_(name, value); + } + } + + if (config.atrules) { + for (const [name, atrule] of Object.entries(config.atrules)) { + this.addAtrule_(name, atrule); + } + } + + if (config.properties) { + for (const [name, property] of Object.entries(config.properties)) { + this.addProperty_(name, property); + } + } + } + + this.cssWideKeywordsSyntax = matchGraph.buildMatchGraph(this.cssWideKeywords.join(' | ')); + } + + checkStructure(ast) { + function collectWarning(node, message) { + warns.push({ node, message }); + } + + const structure = this.structure; + const warns = []; + + this.syntax.walk(ast, function(node) { + if (structure.hasOwnProperty(node.type)) { + structure[node.type].check(node, collectWarning); + } else { + collectWarning(node, 'Unknown node type `' + node.type + '`'); + } + }); + + return warns.length ? warns : false; + } + + createDescriptor(syntax, type, name, parent = null) { + const ref = { + type, + name + }; + const descriptor = { + type, + name, + parent, + serializable: typeof syntax === 'string' || (syntax && typeof syntax.type === 'string'), + syntax: null, + match: null, + matchRef: null // used for properties when a syntax referenced as <'property'> in other syntax definitions + }; + + if (typeof syntax === 'function') { + descriptor.match = matchGraph.buildMatchGraph(syntax, ref); + } else { + if (typeof syntax === 'string') { + // lazy parsing on first access + Object.defineProperty(descriptor, 'syntax', { + get() { + Object.defineProperty(descriptor, 'syntax', { + value: parse.parse(syntax) + }); + + return descriptor.syntax; + } + }); + } else { + descriptor.syntax = syntax; + } + + // lazy graph build on first access + Object.defineProperty(descriptor, 'match', { + get() { + Object.defineProperty(descriptor, 'match', { + value: matchGraph.buildMatchGraph(descriptor.syntax, ref) + }); + + return descriptor.match; + } + }); + + if (type === 'Property') { + Object.defineProperty(descriptor, 'matchRef', { + get() { + const syntax = descriptor.syntax; + const value = syntaxHasTopLevelCommaMultiplier(syntax) + ? matchGraph.buildMatchGraph({ + ...syntax, + terms: [syntax.terms[0].term] + }, ref) + : null; + + Object.defineProperty(descriptor, 'matchRef', { + value + }); + + return value; + } + }); + } + } + + return descriptor; + } + addAtrule_(name, syntax) { + if (!syntax) { + return; + } + + this.atrules[name] = { + type: 'Atrule', + name: name, + prelude: syntax.prelude ? this.createDescriptor(syntax.prelude, 'AtrulePrelude', name) : null, + descriptors: syntax.descriptors + ? Object.keys(syntax.descriptors).reduce( + (map, descName) => { + map[descName] = this.createDescriptor(syntax.descriptors[descName], 'AtruleDescriptor', descName, name); + return map; + }, + Object.create(null) + ) + : null + }; + } + addProperty_(name, syntax) { + if (!syntax) { + return; + } + + this.properties[name] = this.createDescriptor(syntax, 'Property', name); + } + addType_(name, syntax) { + if (!syntax) { + return; + } + + this.types[name] = this.createDescriptor(syntax, 'Type', name); + } + + checkAtruleName(atruleName) { + if (!this.getAtrule(atruleName)) { + return new error.SyntaxReferenceError('Unknown at-rule', '@' + atruleName); + } + } + checkAtrulePrelude(atruleName, prelude) { + const error = this.checkAtruleName(atruleName); + + if (error) { + return error; + } + + const atrule = this.getAtrule(atruleName); + + if (!atrule.prelude && prelude) { + return new SyntaxError('At-rule `@' + atruleName + '` should not contain a prelude'); + } + + if (atrule.prelude && !prelude) { + if (!matchSyntax(this, atrule.prelude, '', false).matched) { + return new SyntaxError('At-rule `@' + atruleName + '` should contain a prelude'); + } + } + } + checkAtruleDescriptorName(atruleName, descriptorName) { + const error$1 = this.checkAtruleName(atruleName); + + if (error$1) { + return error$1; + } + + const atrule = this.getAtrule(atruleName); + const descriptor = names.keyword(descriptorName); + + if (!atrule.descriptors) { + return new SyntaxError('At-rule `@' + atruleName + '` has no known descriptors'); + } + + if (!atrule.descriptors[descriptor.name] && + !atrule.descriptors[descriptor.basename]) { + return new error.SyntaxReferenceError('Unknown at-rule descriptor', descriptorName); + } + } + checkPropertyName(propertyName) { + if (!this.getProperty(propertyName)) { + return new error.SyntaxReferenceError('Unknown property', propertyName); + } + } + + matchAtrulePrelude(atruleName, prelude) { + const error = this.checkAtrulePrelude(atruleName, prelude); + + if (error) { + return buildMatchResult(null, error); + } + + const atrule = this.getAtrule(atruleName); + + if (!atrule.prelude) { + return buildMatchResult(null, null); + } + + return matchSyntax(this, atrule.prelude, prelude || '', false); + } + matchAtruleDescriptor(atruleName, descriptorName, value) { + const error = this.checkAtruleDescriptorName(atruleName, descriptorName); + + if (error) { + return buildMatchResult(null, error); + } + + const atrule = this.getAtrule(atruleName); + const descriptor = names.keyword(descriptorName); + + return matchSyntax(this, atrule.descriptors[descriptor.name] || atrule.descriptors[descriptor.basename], value, false); + } + matchDeclaration(node) { + if (node.type !== 'Declaration') { + return buildMatchResult(null, new Error('Not a Declaration node')); + } + + return this.matchProperty(node.property, node.value); + } + matchProperty(propertyName, value) { + // don't match syntax for a custom property at the moment + if (names.property(propertyName).custom) { + return buildMatchResult(null, new Error('Lexer matching doesn\'t applicable for custom properties')); + } + + const error = this.checkPropertyName(propertyName); + + if (error) { + return buildMatchResult(null, error); + } + + return matchSyntax(this, this.getProperty(propertyName), value, true); + } + matchType(typeName, value) { + const typeSyntax = this.getType(typeName); + + if (!typeSyntax) { + return buildMatchResult(null, new error.SyntaxReferenceError('Unknown type', typeName)); + } + + return matchSyntax(this, typeSyntax, value, false); + } + match(syntax, value) { + if (typeof syntax !== 'string' && (!syntax || !syntax.type)) { + return buildMatchResult(null, new error.SyntaxReferenceError('Bad syntax')); + } + + if (typeof syntax === 'string' || !syntax.match) { + syntax = this.createDescriptor(syntax, 'Type', 'anonymous'); + } + + return matchSyntax(this, syntax, value, false); + } + + findValueFragments(propertyName, value, type, name) { + return search.matchFragments(this, value, this.matchProperty(propertyName, value), type, name); + } + findDeclarationValueFragments(declaration, type, name) { + return search.matchFragments(this, declaration.value, this.matchDeclaration(declaration), type, name); + } + findAllFragments(ast, type, name) { + const result = []; + + this.syntax.walk(ast, { + visit: 'Declaration', + enter: (declaration) => { + result.push.apply(result, this.findDeclarationValueFragments(declaration, type, name)); + } + }); + + return result; + } + + getAtrule(atruleName, fallbackBasename = true) { + const atrule = names.keyword(atruleName); + const atruleEntry = atrule.vendor && fallbackBasename + ? this.atrules[atrule.name] || this.atrules[atrule.basename] + : this.atrules[atrule.name]; + + return atruleEntry || null; + } + getAtrulePrelude(atruleName, fallbackBasename = true) { + const atrule = this.getAtrule(atruleName, fallbackBasename); + + return atrule && atrule.prelude || null; + } + getAtruleDescriptor(atruleName, name) { + return this.atrules.hasOwnProperty(atruleName) && this.atrules.declarators + ? this.atrules[atruleName].declarators[name] || null + : null; + } + getProperty(propertyName, fallbackBasename = true) { + const property = names.property(propertyName); + const propertyEntry = property.vendor && fallbackBasename + ? this.properties[property.name] || this.properties[property.basename] + : this.properties[property.name]; + + return propertyEntry || null; + } + getType(name) { + return hasOwnProperty.call(this.types, name) ? this.types[name] : null; + } + + validate() { + function syntaxRef(name, isType) { + return isType ? `<${name}>` : `<'${name}'>`; + } + + function validate(syntax, name, broken, descriptor) { + if (broken.has(name)) { + return broken.get(name); + } + + broken.set(name, false); + if (descriptor.syntax !== null) { + walk.walk(descriptor.syntax, function(node) { + if (node.type !== 'Type' && node.type !== 'Property') { + return; + } + + const map = node.type === 'Type' ? syntax.types : syntax.properties; + const brokenMap = node.type === 'Type' ? brokenTypes : brokenProperties; + + if (!hasOwnProperty.call(map, node.name)) { + errors.push(`${syntaxRef(name, broken === brokenTypes)} used missed syntax definition ${syntaxRef(node.name, node.type === 'Type')}`); + broken.set(name, true); + } else if (validate(syntax, node.name, brokenMap, map[node.name])) { + errors.push(`${syntaxRef(name, broken === brokenTypes)} used broken syntax definition ${syntaxRef(node.name, node.type === 'Type')}`); + broken.set(name, true); + } + }, this); + } + } + + const errors = []; + let brokenTypes = new Map(); + let brokenProperties = new Map(); + + for (const key in this.types) { + validate(this, key, brokenTypes, this.types[key]); + } + + for (const key in this.properties) { + validate(this, key, brokenProperties, this.properties[key]); + } + + const brokenTypesArray = [...brokenTypes.keys()].filter(name => brokenTypes.get(name)); + const brokenPropertiesArray = [...brokenProperties.keys()].filter(name => brokenProperties.get(name)); + + if (brokenTypesArray.length || brokenPropertiesArray.length) { + return { + errors, + types: brokenTypesArray, + properties: brokenPropertiesArray + }; + } + + return null; + } + dump(syntaxAsAst, pretty) { + return { + generic: this.generic, + cssWideKeywords: this.cssWideKeywords, + units: this.units, + types: dumpMapSyntax(this.types, !pretty, syntaxAsAst), + properties: dumpMapSyntax(this.properties, !pretty, syntaxAsAst), + atrules: dumpAtruleMapSyntax(this.atrules, !pretty, syntaxAsAst) + }; + } + toString() { + return JSON.stringify(this.dump()); + } +} + +exports.Lexer = Lexer; diff --git a/vanilla/node_modules/css-tree/cjs/lexer/error.cjs b/vanilla/node_modules/css-tree/cjs/lexer/error.cjs new file mode 100644 index 0000000..8d252ee --- /dev/null +++ b/vanilla/node_modules/css-tree/cjs/lexer/error.cjs @@ -0,0 +1,128 @@ +'use strict'; + +const createCustomError = require('../utils/create-custom-error.cjs'); +const generate = require('../definition-syntax/generate.cjs'); + +const defaultLoc = { offset: 0, line: 1, column: 1 }; + +function locateMismatch(matchResult, node) { + const tokens = matchResult.tokens; + const longestMatch = matchResult.longestMatch; + const mismatchNode = longestMatch < tokens.length ? tokens[longestMatch].node || null : null; + const badNode = mismatchNode !== node ? mismatchNode : null; + let mismatchOffset = 0; + let mismatchLength = 0; + let entries = 0; + let css = ''; + let start; + let end; + + for (let i = 0; i < tokens.length; i++) { + const token = tokens[i].value; + + if (i === longestMatch) { + mismatchLength = token.length; + mismatchOffset = css.length; + } + + if (badNode !== null && tokens[i].node === badNode) { + if (i <= longestMatch) { + entries++; + } else { + entries = 0; + } + } + + css += token; + } + + if (longestMatch === tokens.length || entries > 1) { // last + start = fromLoc(badNode || node, 'end') || buildLoc(defaultLoc, css); + end = buildLoc(start); + } else { + start = fromLoc(badNode, 'start') || + buildLoc(fromLoc(node, 'start') || defaultLoc, css.slice(0, mismatchOffset)); + end = fromLoc(badNode, 'end') || + buildLoc(start, css.substr(mismatchOffset, mismatchLength)); + } + + return { + css, + mismatchOffset, + mismatchLength, + start, + end + }; +} + +function fromLoc(node, point) { + const value = node && node.loc && node.loc[point]; + + if (value) { + return 'line' in value ? buildLoc(value) : value; + } + + return null; +} + +function buildLoc({ offset, line, column }, extra) { + const loc = { + offset, + line, + column + }; + + if (extra) { + const lines = extra.split(/\n|\r\n?|\f/); + + loc.offset += extra.length; + loc.line += lines.length - 1; + loc.column = lines.length === 1 ? loc.column + extra.length : lines.pop().length + 1; + } + + return loc; +} + +const SyntaxReferenceError = function(type, referenceName) { + const error = createCustomError.createCustomError( + 'SyntaxReferenceError', + type + (referenceName ? ' `' + referenceName + '`' : '') + ); + + error.reference = referenceName; + + return error; +}; + +const SyntaxMatchError = function(message, syntax, node, matchResult) { + const error = createCustomError.createCustomError('SyntaxMatchError', message); + const { + css, + mismatchOffset, + mismatchLength, + start, + end + } = locateMismatch(matchResult, node); + + error.rawMessage = message; + error.syntax = syntax ? generate.generate(syntax) : '<generic>'; + error.css = css; + error.mismatchOffset = mismatchOffset; + error.mismatchLength = mismatchLength; + error.message = message + '\n' + + ' syntax: ' + error.syntax + '\n' + + ' value: ' + (css || '<empty string>') + '\n' + + ' --------' + new Array(error.mismatchOffset + 1).join('-') + '^'; + + Object.assign(error, start); + error.loc = { + source: (node && node.loc && node.loc.source) || '<unknown>', + start, + end + }; + + return error; +}; + +exports.SyntaxMatchError = SyntaxMatchError; +exports.SyntaxReferenceError = SyntaxReferenceError; diff --git a/vanilla/node_modules/css-tree/cjs/lexer/generic-an-plus-b.cjs b/vanilla/node_modules/css-tree/cjs/lexer/generic-an-plus-b.cjs new file mode 100644 index 0000000..a5dfba3 --- /dev/null +++ b/vanilla/node_modules/css-tree/cjs/lexer/generic-an-plus-b.cjs @@ -0,0 +1,235 @@ +'use strict'; + +const charCodeDefinitions = require('../tokenizer/char-code-definitions.cjs'); +const types = require('../tokenizer/types.cjs'); +const utils = require('../tokenizer/utils.cjs'); + +const PLUSSIGN = 0x002B; // U+002B PLUS SIGN (+) +const HYPHENMINUS = 0x002D; // U+002D HYPHEN-MINUS (-) +const N = 0x006E; // U+006E LATIN SMALL LETTER N (n) +const DISALLOW_SIGN = true; +const ALLOW_SIGN = false; + +function isDelim(token, code) { + return token !== null && token.type === types.Delim && token.value.charCodeAt(0) === code; +} + +function skipSC(token, offset, getNextToken) { + while (token !== null && (token.type === types.WhiteSpace || token.type === types.Comment)) { + token = getNextToken(++offset); + } + + return offset; +} + +function checkInteger(token, valueOffset, disallowSign, offset) { + if (!token) { + return 0; + } + + const code = token.value.charCodeAt(valueOffset); + + if (code === PLUSSIGN || code === HYPHENMINUS) { + if (disallowSign) { + // Number sign is not allowed + return 0; + } + valueOffset++; + } + + for (; valueOffset < token.value.length; valueOffset++) { + if (!charCodeDefinitions.isDigit(token.value.charCodeAt(valueOffset))) { + // Integer is expected + return 0; + } + } + + return offset + 1; +} + +// ... <signed-integer> +// ... ['+' | '-'] <signless-integer> +function consumeB(token, offset_, getNextToken) { + let sign = false; + let offset = skipSC(token, offset_, getNextToken); + + token = getNextToken(offset); + + if (token === null) { + return offset_; + } + + if (token.type !== types.Number) { + if (isDelim(token, PLUSSIGN) || isDelim(token, HYPHENMINUS)) { + sign = true; + offset = skipSC(getNextToken(++offset), offset, getNextToken); + token = getNextToken(offset); + + if (token === null || token.type !== types.Number) { + return 0; + } + } else { + return offset_; + } + } + + if (!sign) { + const code = token.value.charCodeAt(0); + if (code !== PLUSSIGN && code !== HYPHENMINUS) { + // Number sign is expected + return 0; + } + } + + return checkInteger(token, sign ? 0 : 1, sign, offset); +} + +// An+B microsyntax https://www.w3.org/TR/css-syntax-3/#anb +function anPlusB(token, getNextToken) { + /* eslint-disable brace-style*/ + let offset = 0; + + if (!token) { + return 0; + } + + // <integer> + if (token.type === types.Number) { + return checkInteger(token, 0, ALLOW_SIGN, offset); // b + } + + // -n + // -n <signed-integer> + // -n ['+' | '-'] <signless-integer> + // -n- <signless-integer> + // <dashndashdigit-ident> + else if (token.type === types.Ident && token.value.charCodeAt(0) === HYPHENMINUS) { + // expect 1st char is N + if (!utils.cmpChar(token.value, 1, N)) { + return 0; + } + + switch (token.value.length) { + // -n + // -n <signed-integer> + // -n ['+' | '-'] <signless-integer> + case 2: + return consumeB(getNextToken(++offset), offset, getNextToken); + + // -n- <signless-integer> + case 3: + if (token.value.charCodeAt(2) !== HYPHENMINUS) { + return 0; + } + + offset = skipSC(getNextToken(++offset), offset, getNextToken); + token = getNextToken(offset); + + return checkInteger(token, 0, DISALLOW_SIGN, offset); + + // <dashndashdigit-ident> + default: + if (token.value.charCodeAt(2) !== HYPHENMINUS) { + return 0; + } + + return checkInteger(token, 3, DISALLOW_SIGN, offset); + } + } + + // '+'? n + // '+'? n <signed-integer> + // '+'? n ['+' | '-'] <signless-integer> + // '+'? n- <signless-integer> + // '+'? <ndashdigit-ident> + else if (token.type === types.Ident || (isDelim(token, PLUSSIGN) && getNextToken(offset + 1).type === types.Ident)) { + // just ignore a plus + if (token.type !== types.Ident) { + token = getNextToken(++offset); + } + + if (token === null || !utils.cmpChar(token.value, 0, N)) { + return 0; + } + + switch (token.value.length) { + // '+'? n + // '+'? n <signed-integer> + // '+'? n ['+' | '-'] <signless-integer> + case 1: + return consumeB(getNextToken(++offset), offset, getNextToken); + + // '+'? n- <signless-integer> + case 2: + if (token.value.charCodeAt(1) !== HYPHENMINUS) { + return 0; + } + + offset = skipSC(getNextToken(++offset), offset, getNextToken); + token = getNextToken(offset); + + return checkInteger(token, 0, DISALLOW_SIGN, offset); + + // '+'? <ndashdigit-ident> + default: + if (token.value.charCodeAt(1) !== HYPHENMINUS) { + return 0; + } + + return checkInteger(token, 2, DISALLOW_SIGN, offset); + } + } + + // <ndashdigit-dimension> + // <ndash-dimension> <signless-integer> + // <n-dimension> + // <n-dimension> <signed-integer> + // <n-dimension> ['+' | '-'] <signless-integer> + else if (token.type === types.Dimension) { + let code = token.value.charCodeAt(0); + let sign = code === PLUSSIGN || code === HYPHENMINUS ? 1 : 0; + let i = sign; + + for (; i < token.value.length; i++) { + if (!charCodeDefinitions.isDigit(token.value.charCodeAt(i))) { + break; + } + } + + if (i === sign) { + // Integer is expected + return 0; + } + + if (!utils.cmpChar(token.value, i, N)) { + return 0; + } + + // <n-dimension> + // <n-dimension> <signed-integer> + // <n-dimension> ['+' | '-'] <signless-integer> + if (i + 1 === token.value.length) { + return consumeB(getNextToken(++offset), offset, getNextToken); + } else { + if (token.value.charCodeAt(i + 1) !== HYPHENMINUS) { + return 0; + } + + // <ndash-dimension> <signless-integer> + if (i + 2 === token.value.length) { + offset = skipSC(getNextToken(++offset), offset, getNextToken); + token = getNextToken(offset); + + return checkInteger(token, 0, DISALLOW_SIGN, offset); + } + // <ndashdigit-dimension> + else { + return checkInteger(token, i + 2, DISALLOW_SIGN, offset); + } + } + } + + return 0; +} + +module.exports = anPlusB; diff --git a/vanilla/node_modules/css-tree/cjs/lexer/generic-const.cjs b/vanilla/node_modules/css-tree/cjs/lexer/generic-const.cjs new file mode 100644 index 0000000..9b9f615 --- /dev/null +++ b/vanilla/node_modules/css-tree/cjs/lexer/generic-const.cjs @@ -0,0 +1,12 @@ +'use strict'; + +// https://drafts.csswg.org/css-cascade-5/ +const cssWideKeywords = [ + 'initial', + 'inherit', + 'unset', + 'revert', + 'revert-layer' +]; + +exports.cssWideKeywords = cssWideKeywords; diff --git a/vanilla/node_modules/css-tree/cjs/lexer/generic-urange.cjs b/vanilla/node_modules/css-tree/cjs/lexer/generic-urange.cjs new file mode 100644 index 0000000..ce167bb --- /dev/null +++ b/vanilla/node_modules/css-tree/cjs/lexer/generic-urange.cjs @@ -0,0 +1,149 @@ +'use strict'; + +const charCodeDefinitions = require('../tokenizer/char-code-definitions.cjs'); +const types = require('../tokenizer/types.cjs'); +const utils = require('../tokenizer/utils.cjs'); + +const PLUSSIGN = 0x002B; // U+002B PLUS SIGN (+) +const HYPHENMINUS = 0x002D; // U+002D HYPHEN-MINUS (-) +const QUESTIONMARK = 0x003F; // U+003F QUESTION MARK (?) +const U = 0x0075; // U+0075 LATIN SMALL LETTER U (u) + +function isDelim(token, code) { + return token !== null && token.type === types.Delim && token.value.charCodeAt(0) === code; +} + +function startsWith(token, code) { + return token.value.charCodeAt(0) === code; +} + +function hexSequence(token, offset, allowDash) { + let hexlen = 0; + + for (let pos = offset; pos < token.value.length; pos++) { + const code = token.value.charCodeAt(pos); + + if (code === HYPHENMINUS && allowDash && hexlen !== 0) { + hexSequence(token, offset + hexlen + 1, false); + return 6; // dissallow following question marks + } + + if (!charCodeDefinitions.isHexDigit(code)) { + return 0; // not a hex digit + } + + if (++hexlen > 6) { + return 0; // too many hex digits + } } + + return hexlen; +} + +function withQuestionMarkSequence(consumed, length, getNextToken) { + if (!consumed) { + return 0; // nothing consumed + } + + while (isDelim(getNextToken(length), QUESTIONMARK)) { + if (++consumed > 6) { + return 0; // too many question marks + } + + length++; + } + + return length; +} + +// https://drafts.csswg.org/css-syntax/#urange +// Informally, the <urange> production has three forms: +// U+0001 +// Defines a range consisting of a single code point, in this case the code point "1". +// U+0001-00ff +// Defines a range of codepoints between the first and the second value, in this case +// the range between "1" and "ff" (255 in decimal) inclusive. +// U+00?? +// Defines a range of codepoints where the "?" characters range over all hex digits, +// in this case defining the same as the value U+0000-00ff. +// In each form, a maximum of 6 digits is allowed for each hexadecimal number (if you treat "?" as a hexadecimal digit). +// +// <urange> = +// u '+' <ident-token> '?'* | +// u <dimension-token> '?'* | +// u <number-token> '?'* | +// u <number-token> <dimension-token> | +// u <number-token> <number-token> | +// u '+' '?'+ +function urange(token, getNextToken) { + let length = 0; + + // should start with `u` or `U` + if (token === null || token.type !== types.Ident || !utils.cmpChar(token.value, 0, U)) { + return 0; + } + + token = getNextToken(++length); + if (token === null) { + return 0; + } + + // u '+' <ident-token> '?'* + // u '+' '?'+ + if (isDelim(token, PLUSSIGN)) { + token = getNextToken(++length); + if (token === null) { + return 0; + } + + if (token.type === types.Ident) { + // u '+' <ident-token> '?'* + return withQuestionMarkSequence(hexSequence(token, 0, true), ++length, getNextToken); + } + + if (isDelim(token, QUESTIONMARK)) { + // u '+' '?'+ + return withQuestionMarkSequence(1, ++length, getNextToken); + } + + // Hex digit or question mark is expected + return 0; + } + + // u <number-token> '?'* + // u <number-token> <dimension-token> + // u <number-token> <number-token> + if (token.type === types.Number) { + const consumedHexLength = hexSequence(token, 1, true); + if (consumedHexLength === 0) { + return 0; + } + + token = getNextToken(++length); + if (token === null) { + // u <number-token> <eof> + return length; + } + + if (token.type === types.Dimension || token.type === types.Number) { + // u <number-token> <dimension-token> + // u <number-token> <number-token> + if (!startsWith(token, HYPHENMINUS) || !hexSequence(token, 1, false)) { + return 0; + } + + return length + 1; + } + + // u <number-token> '?'* + return withQuestionMarkSequence(consumedHexLength, length, getNextToken); + } + + // u <dimension-token> '?'* + if (token.type === types.Dimension) { + return withQuestionMarkSequence(hexSequence(token, 1, true), ++length, getNextToken); + } + + return 0; +} + +module.exports = urange; diff --git a/vanilla/node_modules/css-tree/cjs/lexer/generic.cjs b/vanilla/node_modules/css-tree/cjs/lexer/generic.cjs new file mode 100644 index 0000000..8489911 --- /dev/null +++ b/vanilla/node_modules/css-tree/cjs/lexer/generic.cjs @@ -0,0 +1,589 @@ +'use strict'; + +const genericConst = require('./generic-const.cjs'); +const genericAnPlusB = require('./generic-an-plus-b.cjs'); +const genericUrange = require('./generic-urange.cjs'); +const charCodeDefinitions = require('../tokenizer/char-code-definitions.cjs'); +const types = require('../tokenizer/types.cjs'); +const utils = require('../tokenizer/utils.cjs'); + +const calcFunctionNames = ['calc(', '-moz-calc(', '-webkit-calc(']; +const balancePair = new Map([ + [types.Function, types.RightParenthesis], + [types.LeftParenthesis, types.RightParenthesis], + [types.LeftSquareBracket, types.RightSquareBracket], + [types.LeftCurlyBracket, types.RightCurlyBracket] +]); + +// safe char code getter +function charCodeAt(str, index) { + return index < str.length ? str.charCodeAt(index) : 0; +} + +function eqStr(actual, expected) { + return utils.cmpStr(actual, 0, actual.length, expected); +} + +function eqStrAny(actual, expected) { + for (let i = 0; i < expected.length; i++) { + if (eqStr(actual, expected[i])) { + return true; + } + } + + return false; +} + +// IE postfix hack, i.e. 123\0 or 123px\9 +function isPostfixIeHack(str, offset) { + if (offset !== str.length - 2) { + return false; + } + + return ( + charCodeAt(str, offset) === 0x005C && // U+005C REVERSE SOLIDUS (\) + charCodeDefinitions.isDigit(charCodeAt(str, offset + 1)) + ); +} + +function outOfRange(opts, value, numEnd) { + if (opts && opts.type === 'Range') { + const num = Number( + numEnd !== undefined && numEnd !== value.length + ? value.substr(0, numEnd) + : value + ); + + if (isNaN(num)) { + return true; + } + + // FIXME: when opts.min is a string it's a dimension, skip a range validation + // for now since it requires a type covertation which is not implmented yet + if (opts.min !== null && num < opts.min && typeof opts.min !== 'string') { + return true; + } + + // FIXME: when opts.max is a string it's a dimension, skip a range validation + // for now since it requires a type covertation which is not implmented yet + if (opts.max !== null && num > opts.max && typeof opts.max !== 'string') { + return true; + } + } + + return false; +} + +function consumeFunction(token, getNextToken) { + let balanceCloseType = 0; + let balanceStash = []; + let length = 0; + + // balanced token consuming + scan: + do { + switch (token.type) { + case types.RightCurlyBracket: + case types.RightParenthesis: + case types.RightSquareBracket: + if (token.type !== balanceCloseType) { + break scan; + } + + balanceCloseType = balanceStash.pop(); + + if (balanceStash.length === 0) { + length++; + break scan; + } + + break; + + case types.Function: + case types.LeftParenthesis: + case types.LeftSquareBracket: + case types.LeftCurlyBracket: + balanceStash.push(balanceCloseType); + balanceCloseType = balancePair.get(token.type); + break; + } + + length++; + } while (token = getNextToken(length)); + + return length; +} + +// TODO: implement +// can be used wherever <length>, <frequency>, <angle>, <time>, <percentage>, <number>, or <integer> values are allowed +// https://drafts.csswg.org/css-values/#calc-notation +function calc(next) { + return function(token, getNextToken, opts) { + if (token === null) { + return 0; + } + + if (token.type === types.Function && eqStrAny(token.value, calcFunctionNames)) { + return consumeFunction(token, getNextToken); + } + + return next(token, getNextToken, opts); + }; +} + +function tokenType(expectedTokenType) { + return function(token) { + if (token === null || token.type !== expectedTokenType) { + return 0; + } + + return 1; + }; +} + +// ========================= +// Complex types +// + +// https://drafts.csswg.org/css-values-4/#custom-idents +// 4.2. Author-defined Identifiers: the <custom-ident> type +// Some properties accept arbitrary author-defined identifiers as a component value. +// This generic data type is denoted by <custom-ident>, and represents any valid CSS identifier +// that would not be misinterpreted as a pre-defined keyword in that property’s value definition. +// +// See also: https://developer.mozilla.org/en-US/docs/Web/CSS/custom-ident +function customIdent(token) { + if (token === null || token.type !== types.Ident) { + return 0; + } + + const name = token.value.toLowerCase(); + + // The CSS-wide keywords are not valid <custom-ident>s + if (eqStrAny(name, genericConst.cssWideKeywords)) { + return 0; + } + + // The default keyword is reserved and is also not a valid <custom-ident> + if (eqStr(name, 'default')) { + return 0; + } + + // TODO: ignore property specific keywords (as described https://developer.mozilla.org/en-US/docs/Web/CSS/custom-ident) + // Specifications using <custom-ident> must specify clearly what other keywords + // are excluded from <custom-ident>, if any—for example by saying that any pre-defined keywords + // in that property’s value definition are excluded. Excluded keywords are excluded + // in all ASCII case permutations. + + return 1; +} + +// https://drafts.csswg.org/css-values-4/#dashed-idents +// The <dashed-ident> production is a <custom-ident>, with all the case-sensitivity that implies, +// with the additional restriction that it must start with two dashes (U+002D HYPHEN-MINUS). +function dashedIdent(token) { + if (token === null || token.type !== types.Ident) { + return 0; + } + + // ... must start with two dashes (U+002D HYPHEN-MINUS) + if (charCodeAt(token.value, 0) !== 0x002D || charCodeAt(token.value, 1) !== 0x002D) { + return 0; + } + + return 1; +} + +// https://drafts.csswg.org/css-variables/#typedef-custom-property-name +// A custom property is any property whose name starts with two dashes (U+002D HYPHEN-MINUS), like --foo. +// The <custom-property-name> production corresponds to this: it’s defined as any <dashed-ident> +// (a valid identifier that starts with two dashes), except -- itself, which is reserved for future use by CSS. +function customPropertyName(token) { + // ... it’s defined as any <dashed-ident> + if (!dashedIdent(token)) { + return 0; + } + + // ... except -- itself, which is reserved for future use by CSS + if (token.value === '--') { + return 0; + } + + return 1; +} + +// https://drafts.csswg.org/css-color-4/#hex-notation +// The syntax of a <hex-color> is a <hash-token> token whose value consists of 3, 4, 6, or 8 hexadecimal digits. +// In other words, a hex color is written as a hash character, "#", followed by some number of digits 0-9 or +// letters a-f (the case of the letters doesn’t matter - #00ff00 is identical to #00FF00). +function hexColor(token) { + if (token === null || token.type !== types.Hash) { + return 0; + } + + const length = token.value.length; + + // valid values (length): #rgb (4), #rgba (5), #rrggbb (7), #rrggbbaa (9) + if (length !== 4 && length !== 5 && length !== 7 && length !== 9) { + return 0; + } + + for (let i = 1; i < length; i++) { + if (!charCodeDefinitions.isHexDigit(charCodeAt(token.value, i))) { + return 0; + } + } + + return 1; +} + +function idSelector(token) { + if (token === null || token.type !== types.Hash) { + return 0; + } + + if (!charCodeDefinitions.isIdentifierStart(charCodeAt(token.value, 1), charCodeAt(token.value, 2), charCodeAt(token.value, 3))) { + return 0; + } + + return 1; +} + +// https://drafts.csswg.org/css-syntax/#any-value +// It represents the entirety of what a valid declaration can have as its value. +function declarationValue(token, getNextToken) { + if (!token) { + return 0; + } + + let balanceCloseType = 0; + let balanceStash = []; + let length = 0; + + // The <declaration-value> production matches any sequence of one or more tokens, + // so long as the sequence does not contain ... + scan: + do { + switch (token.type) { + // ... <bad-string-token>, <bad-url-token>, + case types.BadString: + case types.BadUrl: + break scan; + + // ... unmatched <)-token>, <]-token>, or <}-token>, + case types.RightCurlyBracket: + case types.RightParenthesis: + case types.RightSquareBracket: + if (token.type !== balanceCloseType) { + break scan; + } + + balanceCloseType = balanceStash.pop(); + break; + + // ... or top-level <semicolon-token> tokens + case types.Semicolon: + if (balanceCloseType === 0) { + break scan; + } + + break; + + // ... or <delim-token> tokens with a value of "!" + case types.Delim: + if (balanceCloseType === 0 && token.value === '!') { + break scan; + } + + break; + + case types.Function: + case types.LeftParenthesis: + case types.LeftSquareBracket: + case types.LeftCurlyBracket: + balanceStash.push(balanceCloseType); + balanceCloseType = balancePair.get(token.type); + break; + } + + length++; + } while (token = getNextToken(length)); + + return length; +} + +// https://drafts.csswg.org/css-syntax/#any-value +// The <any-value> production is identical to <declaration-value>, but also +// allows top-level <semicolon-token> tokens and <delim-token> tokens +// with a value of "!". It represents the entirety of what valid CSS can be in any context. +function anyValue(token, getNextToken) { + if (!token) { + return 0; + } + + let balanceCloseType = 0; + let balanceStash = []; + let length = 0; + + // The <any-value> production matches any sequence of one or more tokens, + // so long as the sequence ... + scan: + do { + switch (token.type) { + // ... does not contain <bad-string-token>, <bad-url-token>, + case types.BadString: + case types.BadUrl: + break scan; + + // ... unmatched <)-token>, <]-token>, or <}-token>, + case types.RightCurlyBracket: + case types.RightParenthesis: + case types.RightSquareBracket: + if (token.type !== balanceCloseType) { + break scan; + } + + balanceCloseType = balanceStash.pop(); + break; + + case types.Function: + case types.LeftParenthesis: + case types.LeftSquareBracket: + case types.LeftCurlyBracket: + balanceStash.push(balanceCloseType); + balanceCloseType = balancePair.get(token.type); + break; + } + + length++; + } while (token = getNextToken(length)); + + return length; +} + +// ========================= +// Dimensions +// + +function dimension(type) { + if (type) { + type = new Set(type); + } + + return function(token, getNextToken, opts) { + if (token === null || token.type !== types.Dimension) { + return 0; + } + + const numberEnd = utils.consumeNumber(token.value, 0); + + // check unit + if (type !== null) { + // check for IE postfix hack, i.e. 123px\0 or 123px\9 + const reverseSolidusOffset = token.value.indexOf('\\', numberEnd); + const unit = reverseSolidusOffset === -1 || !isPostfixIeHack(token.value, reverseSolidusOffset) + ? token.value.substr(numberEnd) + : token.value.substring(numberEnd, reverseSolidusOffset); + + if (type.has(unit.toLowerCase()) === false) { + return 0; + } + } + + // check range if specified + if (outOfRange(opts, token.value, numberEnd)) { + return 0; + } + + return 1; + }; +} + +// ========================= +// Percentage +// + +// §5.5. Percentages: the <percentage> type +// https://drafts.csswg.org/css-values-4/#percentages +function percentage(token, getNextToken, opts) { + // ... corresponds to the <percentage-token> production + if (token === null || token.type !== types.Percentage) { + return 0; + } + + // check range if specified + if (outOfRange(opts, token.value, token.value.length - 1)) { + return 0; + } + + return 1; +} + +// ========================= +// Numeric +// + +// https://drafts.csswg.org/css-values-4/#numbers +// The value <zero> represents a literal number with the value 0. Expressions that merely +// evaluate to a <number> with the value 0 (for example, calc(0)) do not match <zero>; +// only literal <number-token>s do. +function zero(next) { + if (typeof next !== 'function') { + next = function() { + return 0; + }; + } + + return function(token, getNextToken, opts) { + if (token !== null && token.type === types.Number) { + if (Number(token.value) === 0) { + return 1; + } + } + + return next(token, getNextToken, opts); + }; +} + +// § 5.3. Real Numbers: the <number> type +// https://drafts.csswg.org/css-values-4/#numbers +// Number values are denoted by <number>, and represent real numbers, possibly with a fractional component. +// ... It corresponds to the <number-token> production +function number(token, getNextToken, opts) { + if (token === null) { + return 0; + } + + const numberEnd = utils.consumeNumber(token.value, 0); + const isNumber = numberEnd === token.value.length; + if (!isNumber && !isPostfixIeHack(token.value, numberEnd)) { + return 0; + } + + // check range if specified + if (outOfRange(opts, token.value, numberEnd)) { + return 0; + } + + return 1; +} + +// §5.2. Integers: the <integer> type +// https://drafts.csswg.org/css-values-4/#integers +function integer(token, getNextToken, opts) { + // ... corresponds to a subset of the <number-token> production + if (token === null || token.type !== types.Number) { + return 0; + } + + // The first digit of an integer may be immediately preceded by `-` or `+` to indicate the integer’s sign. + let i = charCodeAt(token.value, 0) === 0x002B || // U+002B PLUS SIGN (+) + charCodeAt(token.value, 0) === 0x002D ? 1 : 0; // U+002D HYPHEN-MINUS (-) + + // When written literally, an integer is one or more decimal digits 0 through 9 ... + for (; i < token.value.length; i++) { + if (!charCodeDefinitions.isDigit(charCodeAt(token.value, i))) { + return 0; + } + } + + // check range if specified + if (outOfRange(opts, token.value, i)) { + return 0; + } + + return 1; +} + +// token types +const tokenTypes = { + 'ident-token': tokenType(types.Ident), + 'function-token': tokenType(types.Function), + 'at-keyword-token': tokenType(types.AtKeyword), + 'hash-token': tokenType(types.Hash), + 'string-token': tokenType(types.String), + 'bad-string-token': tokenType(types.BadString), + 'url-token': tokenType(types.Url), + 'bad-url-token': tokenType(types.BadUrl), + 'delim-token': tokenType(types.Delim), + 'number-token': tokenType(types.Number), + 'percentage-token': tokenType(types.Percentage), + 'dimension-token': tokenType(types.Dimension), + 'whitespace-token': tokenType(types.WhiteSpace), + 'CDO-token': tokenType(types.CDO), + 'CDC-token': tokenType(types.CDC), + 'colon-token': tokenType(types.Colon), + 'semicolon-token': tokenType(types.Semicolon), + 'comma-token': tokenType(types.Comma), + '[-token': tokenType(types.LeftSquareBracket), + ']-token': tokenType(types.RightSquareBracket), + '(-token': tokenType(types.LeftParenthesis), + ')-token': tokenType(types.RightParenthesis), + '{-token': tokenType(types.LeftCurlyBracket), + '}-token': tokenType(types.RightCurlyBracket) +}; + +// token production types +const productionTypes = { + // token type aliases + 'string': tokenType(types.String), + 'ident': tokenType(types.Ident), + + // percentage + 'percentage': calc(percentage), + + // numeric + 'zero': zero(), + 'number': calc(number), + 'integer': calc(integer), + + // complex types + 'custom-ident': customIdent, + 'dashed-ident': dashedIdent, + 'custom-property-name': customPropertyName, + 'hex-color': hexColor, + 'id-selector': idSelector, // element( <id-selector> ) + 'an-plus-b': genericAnPlusB, + 'urange': genericUrange, + 'declaration-value': declarationValue, + 'any-value': anyValue +}; + +// dimensions types depend on units set +function createDemensionTypes(units) { + const { + angle, + decibel, + frequency, + flex, + length, + resolution, + semitones, + time + } = units || {}; + + return { + 'dimension': calc(dimension(null)), + 'angle': calc(dimension(angle)), + 'decibel': calc(dimension(decibel)), + 'frequency': calc(dimension(frequency)), + 'flex': calc(dimension(flex)), + 'length': calc(zero(dimension(length))), + 'resolution': calc(dimension(resolution)), + 'semitones': calc(dimension(semitones)), + 'time': calc(dimension(time)) + }; +} + +function createGenericTypes(units) { + return { + ...tokenTypes, + ...productionTypes, + ...createDemensionTypes(units) + }; +} + +exports.createDemensionTypes = createDemensionTypes; +exports.createGenericTypes = createGenericTypes; +exports.productionTypes = productionTypes; +exports.tokenTypes = tokenTypes; diff --git a/vanilla/node_modules/css-tree/cjs/lexer/index.cjs b/vanilla/node_modules/css-tree/cjs/lexer/index.cjs new file mode 100644 index 0000000..2e3633e --- /dev/null +++ b/vanilla/node_modules/css-tree/cjs/lexer/index.cjs @@ -0,0 +1,7 @@ +'use strict'; + +const Lexer = require('./Lexer.cjs'); + + + +exports.Lexer = Lexer.Lexer; diff --git a/vanilla/node_modules/css-tree/cjs/lexer/match-graph.cjs b/vanilla/node_modules/css-tree/cjs/lexer/match-graph.cjs new file mode 100644 index 0000000..9f9675e --- /dev/null +++ b/vanilla/node_modules/css-tree/cjs/lexer/match-graph.cjs @@ -0,0 +1,530 @@ +'use strict'; + +const parse = require('../definition-syntax/parse.cjs'); + +const MATCH = { type: 'Match' }; +const MISMATCH = { type: 'Mismatch' }; +const DISALLOW_EMPTY = { type: 'DisallowEmpty' }; + +const LEFTPARENTHESIS = 40; // ( +const RIGHTPARENTHESIS = 41; // ) + +function createCondition(match, thenBranch, elseBranch) { + // reduce node count + if (thenBranch === MATCH && elseBranch === MISMATCH) { + return match; + } + + if (match === MATCH && thenBranch === MATCH && elseBranch === MATCH) { + return match; + } + + if (match.type === 'If' && match.else === MISMATCH && thenBranch === MATCH) { + thenBranch = match.then; + match = match.match; + } + + return { + type: 'If', + match, + then: thenBranch, + else: elseBranch + }; +} + +function isFunctionType(name) { + return ( + name.length > 2 && + name.charCodeAt(name.length - 2) === LEFTPARENTHESIS && + name.charCodeAt(name.length - 1) === RIGHTPARENTHESIS + ); +} + +function isEnumCapatible(term) { + return ( + term.type === 'Keyword' || + term.type === 'AtKeyword' || + term.type === 'Function' || + term.type === 'Type' && isFunctionType(term.name) + ); +} + +function groupNode(terms, combinator = ' ', explicit = false) { + return { + type: 'Group', + terms, + combinator, + disallowEmpty: false, + explicit + }; +} + +function replaceTypeInGraph(node, replacements, visited = new Set()) { + if (!visited.has(node)) { + visited.add(node); + + switch (node.type) { + case 'If': + node.match = replaceTypeInGraph(node.match, replacements, visited); + node.then = replaceTypeInGraph(node.then, replacements, visited); + node.else = replaceTypeInGraph(node.else, replacements, visited); + break; + + case 'Type': + return replacements[node.name] || node; + } + } + + return node; +} + +function buildGroupMatchGraph(combinator, terms, atLeastOneTermMatched) { + switch (combinator) { + case ' ': { + // Juxtaposing components means that all of them must occur, in the given order. + // + // a b c + // = + // match a + // then match b + // then match c + // then MATCH + // else MISMATCH + // else MISMATCH + // else MISMATCH + let result = MATCH; + + for (let i = terms.length - 1; i >= 0; i--) { + const term = terms[i]; + + result = createCondition( + term, + result, + MISMATCH + ); + } + return result; + } + + case '|': { + // A bar (|) separates two or more alternatives: exactly one of them must occur. + // + // a | b | c + // = + // match a + // then MATCH + // else match b + // then MATCH + // else match c + // then MATCH + // else MISMATCH + + let result = MISMATCH; + let map = null; + + for (let i = terms.length - 1; i >= 0; i--) { + let term = terms[i]; + + // reduce sequence of keywords into a Enum + if (isEnumCapatible(term)) { + if (map === null && i > 0 && isEnumCapatible(terms[i - 1])) { + map = Object.create(null); + result = createCondition( + { + type: 'Enum', + map + }, + MATCH, + result + ); + } + + if (map !== null) { + const key = (isFunctionType(term.name) ? term.name.slice(0, -1) : term.name).toLowerCase(); + if (key in map === false) { + map[key] = term; + continue; + } + } + } + + map = null; + + // create a new conditonal node + result = createCondition( + term, + MATCH, + result + ); + } + return result; + } + + case '&&': { + // A double ampersand (&&) separates two or more components, + // all of which must occur, in any order. + + // Use MatchOnce for groups with a large number of terms, + // since &&-groups produces at least N!-node trees + if (terms.length > 5) { + return { + type: 'MatchOnce', + terms, + all: true + }; + } + + // Use a combination tree for groups with small number of terms + // + // a && b && c + // = + // match a + // then [b && c] + // else match b + // then [a && c] + // else match c + // then [a && b] + // else MISMATCH + // + // a && b + // = + // match a + // then match b + // then MATCH + // else MISMATCH + // else match b + // then match a + // then MATCH + // else MISMATCH + // else MISMATCH + let result = MISMATCH; + + for (let i = terms.length - 1; i >= 0; i--) { + const term = terms[i]; + let thenClause; + + if (terms.length > 1) { + thenClause = buildGroupMatchGraph( + combinator, + terms.filter(function(newGroupTerm) { + return newGroupTerm !== term; + }), + false + ); + } else { + thenClause = MATCH; + } + + result = createCondition( + term, + thenClause, + result + ); + } + return result; + } + + case '||': { + // A double bar (||) separates two or more options: + // one or more of them must occur, in any order. + + // Use MatchOnce for groups with a large number of terms, + // since ||-groups produces at least N!-node trees + if (terms.length > 5) { + return { + type: 'MatchOnce', + terms, + all: false + }; + } + + // Use a combination tree for groups with small number of terms + // + // a || b || c + // = + // match a + // then [b || c] + // else match b + // then [a || c] + // else match c + // then [a || b] + // else MISMATCH + // + // a || b + // = + // match a + // then match b + // then MATCH + // else MATCH + // else match b + // then match a + // then MATCH + // else MATCH + // else MISMATCH + let result = atLeastOneTermMatched ? MATCH : MISMATCH; + + for (let i = terms.length - 1; i >= 0; i--) { + const term = terms[i]; + let thenClause; + + if (terms.length > 1) { + thenClause = buildGroupMatchGraph( + combinator, + terms.filter(function(newGroupTerm) { + return newGroupTerm !== term; + }), + true + ); + } else { + thenClause = MATCH; + } + + result = createCondition( + term, + thenClause, + result + ); + } + return result; + } + } +} + +function buildMultiplierMatchGraph(node) { + let result = MATCH; + let matchTerm = buildMatchGraphInternal(node.term); + + if (node.max === 0) { + // disable repeating of empty match to prevent infinite loop + matchTerm = createCondition( + matchTerm, + DISALLOW_EMPTY, + MISMATCH + ); + + // an occurrence count is not limited, make a cycle; + // to collect more terms on each following matching mismatch + result = createCondition( + matchTerm, + null, // will be a loop + MISMATCH + ); + + result.then = createCondition( + MATCH, + MATCH, + result // make a loop + ); + + if (node.comma) { + result.then.else = createCondition( + { type: 'Comma', syntax: node }, + result, + MISMATCH + ); + } + } else { + // create a match node chain for [min .. max] interval with optional matches + for (let i = node.min || 1; i <= node.max; i++) { + if (node.comma && result !== MATCH) { + result = createCondition( + { type: 'Comma', syntax: node }, + result, + MISMATCH + ); + } + + result = createCondition( + matchTerm, + createCondition( + MATCH, + MATCH, + result + ), + MISMATCH + ); + } + } + + if (node.min === 0) { + // allow zero match + result = createCondition( + MATCH, + MATCH, + result + ); + } else { + // create a match node chain to collect [0 ... min - 1] required matches + for (let i = 0; i < node.min - 1; i++) { + if (node.comma && result !== MATCH) { + result = createCondition( + { type: 'Comma', syntax: node }, + result, + MISMATCH + ); + } + + result = createCondition( + matchTerm, + result, + MISMATCH + ); + } + } + + return result; +} + +function buildMatchGraphInternal(node) { + if (typeof node === 'function') { + return { + type: 'Generic', + fn: node + }; + } + + switch (node.type) { + case 'Group': { + let result = buildGroupMatchGraph( + node.combinator, + node.terms.map(buildMatchGraphInternal), + false + ); + + if (node.disallowEmpty) { + result = createCondition( + result, + DISALLOW_EMPTY, + MISMATCH + ); + } + + return result; + } + + case 'Multiplier': + return buildMultiplierMatchGraph(node); + + // https://drafts.csswg.org/css-values-5/#boolean + case 'Boolean': { + const term = buildMatchGraphInternal(node.term); + // <boolean-expr[ <test> ]> = not <boolean-expr-group> | <boolean-expr-group> [ [ and <boolean-expr-group> ]* | [ or <boolean-expr-group> ]* ] + const matchNode = buildMatchGraphInternal(groupNode([ + groupNode([ + { type: 'Keyword', name: 'not' }, + { type: 'Type', name: '!boolean-group' } + ]), + groupNode([ + { type: 'Type', name: '!boolean-group' }, + groupNode([ + { type: 'Multiplier', comma: false, min: 0, max: 0, term: groupNode([ + { type: 'Keyword', name: 'and' }, + { type: 'Type', name: '!boolean-group' } + ]) }, + { type: 'Multiplier', comma: false, min: 0, max: 0, term: groupNode([ + { type: 'Keyword', name: 'or' }, + { type: 'Type', name: '!boolean-group' } + ]) } + ], '|') + ]) + ], '|')); + // <boolean-expr-group> = <test> | ( <boolean-expr[ <test> ]> ) | <general-enclosed> + const booleanGroup = buildMatchGraphInternal( + groupNode([ + { type: 'Type', name: '!term' }, + groupNode([ + { type: 'Token', value: '(' }, + { type: 'Type', name: '!self' }, + { type: 'Token', value: ')' } + ]), + { type: 'Type', name: 'general-enclosed' } + ], '|') + ); + + replaceTypeInGraph(booleanGroup, { '!term': term, '!self': matchNode }); + replaceTypeInGraph(matchNode, { '!boolean-group': booleanGroup }); + + return matchNode; + } + + case 'Type': + case 'Property': + return { + type: node.type, + name: node.name, + syntax: node + }; + + case 'Keyword': + return { + type: node.type, + name: node.name.toLowerCase(), + syntax: node + }; + + case 'AtKeyword': + return { + type: node.type, + name: '@' + node.name.toLowerCase(), + syntax: node + }; + + case 'Function': + return { + type: node.type, + name: node.name.toLowerCase() + '(', + syntax: node + }; + + case 'String': + // convert a one char length String to a Token + if (node.value.length === 3) { + return { + type: 'Token', + value: node.value.charAt(1), + syntax: node + }; + } + + // otherwise use it as is + return { + type: node.type, + value: node.value.substr(1, node.value.length - 2).replace(/\\'/g, '\''), + syntax: node + }; + + case 'Token': + return { + type: node.type, + value: node.value, + syntax: node + }; + + case 'Comma': + return { + type: node.type, + syntax: node + }; + + default: + throw new Error('Unknown node type:', node.type); + } +} + +function buildMatchGraph(syntaxTree, ref) { + if (typeof syntaxTree === 'string') { + syntaxTree = parse.parse(syntaxTree); + } + + return { + type: 'MatchGraph', + match: buildMatchGraphInternal(syntaxTree), + syntax: ref || null, + source: syntaxTree + }; +} + +exports.DISALLOW_EMPTY = DISALLOW_EMPTY; +exports.MATCH = MATCH; +exports.MISMATCH = MISMATCH; +exports.buildMatchGraph = buildMatchGraph; diff --git a/vanilla/node_modules/css-tree/cjs/lexer/match.cjs b/vanilla/node_modules/css-tree/cjs/lexer/match.cjs new file mode 100644 index 0000000..86f44ae --- /dev/null +++ b/vanilla/node_modules/css-tree/cjs/lexer/match.cjs @@ -0,0 +1,632 @@ +'use strict'; + +const matchGraph = require('./match-graph.cjs'); +const types = require('../tokenizer/types.cjs'); + +const { hasOwnProperty } = Object.prototype; +const STUB = 0; +const TOKEN = 1; +const OPEN_SYNTAX = 2; +const CLOSE_SYNTAX = 3; + +const EXIT_REASON_MATCH = 'Match'; +const EXIT_REASON_MISMATCH = 'Mismatch'; +const EXIT_REASON_ITERATION_LIMIT = 'Maximum iteration number exceeded (please fill an issue on https://github.com/csstree/csstree/issues)'; + +const ITERATION_LIMIT = 15000; + +function reverseList(list) { + let prev = null; + let next = null; + let item = list; + + while (item !== null) { + next = item.prev; + item.prev = prev; + prev = item; + item = next; + } + + return prev; +} + +function areStringsEqualCaseInsensitive(testStr, referenceStr) { + if (testStr.length !== referenceStr.length) { + return false; + } + + for (let i = 0; i < testStr.length; i++) { + const referenceCode = referenceStr.charCodeAt(i); + let testCode = testStr.charCodeAt(i); + + // testCode.toLowerCase() for U+0041 LATIN CAPITAL LETTER A (A) .. U+005A LATIN CAPITAL LETTER Z (Z). + if (testCode >= 0x0041 && testCode <= 0x005A) { + testCode = testCode | 32; + } + + if (testCode !== referenceCode) { + return false; + } + } + + return true; +} + +function isContextEdgeDelim(token) { + if (token.type !== types.Delim) { + return false; + } + + // Fix matching for unicode-range: U+30??, U+FF00-FF9F + // Probably we need to check out previous match instead + return token.value !== '?'; +} + +function isCommaContextStart(token) { + if (token === null) { + return true; + } + + return ( + token.type === types.Comma || + token.type === types.Function || + token.type === types.LeftParenthesis || + token.type === types.LeftSquareBracket || + token.type === types.LeftCurlyBracket || + isContextEdgeDelim(token) + ); +} + +function isCommaContextEnd(token) { + if (token === null) { + return true; + } + + return ( + token.type === types.RightParenthesis || + token.type === types.RightSquareBracket || + token.type === types.RightCurlyBracket || + (token.type === types.Delim && token.value === '/') + ); +} + +function internalMatch(tokens, state, syntaxes) { + function moveToNextToken() { + do { + tokenIndex++; + token = tokenIndex < tokens.length ? tokens[tokenIndex] : null; + } while (token !== null && (token.type === types.WhiteSpace || token.type === types.Comment)); + } + + function getNextToken(offset) { + const nextIndex = tokenIndex + offset; + + return nextIndex < tokens.length ? tokens[nextIndex] : null; + } + + function stateSnapshotFromSyntax(nextState, prev) { + return { + nextState, + matchStack, + syntaxStack, + thenStack, + tokenIndex, + prev + }; + } + + function pushThenStack(nextState) { + thenStack = { + nextState, + matchStack, + syntaxStack, + prev: thenStack + }; + } + + function pushElseStack(nextState) { + elseStack = stateSnapshotFromSyntax(nextState, elseStack); + } + + function addTokenToMatch() { + matchStack = { + type: TOKEN, + syntax: state.syntax, + token, + prev: matchStack + }; + + moveToNextToken(); + syntaxStash = null; + + if (tokenIndex > longestMatch) { + longestMatch = tokenIndex; + } + } + + function openSyntax() { + syntaxStack = { + syntax: state.syntax, + opts: state.syntax.opts || (syntaxStack !== null && syntaxStack.opts) || null, + prev: syntaxStack + }; + + matchStack = { + type: OPEN_SYNTAX, + syntax: state.syntax, + token: matchStack.token, + prev: matchStack + }; + } + + function closeSyntax() { + if (matchStack.type === OPEN_SYNTAX) { + matchStack = matchStack.prev; + } else { + matchStack = { + type: CLOSE_SYNTAX, + syntax: syntaxStack.syntax, + token: matchStack.token, + prev: matchStack + }; + } + + syntaxStack = syntaxStack.prev; + } + + let syntaxStack = null; + let thenStack = null; + let elseStack = null; + + // null – stashing allowed, nothing stashed + // false – stashing disabled, nothing stashed + // anithing else – fail stashable syntaxes, some syntax stashed + let syntaxStash = null; + + let iterationCount = 0; // count iterations and prevent infinite loop + let exitReason = null; + + let token = null; + let tokenIndex = -1; + let longestMatch = 0; + let matchStack = { + type: STUB, + syntax: null, + token: null, + prev: null + }; + + moveToNextToken(); + + while (exitReason === null && ++iterationCount < ITERATION_LIMIT) { + // function mapList(list, fn) { + // const result = []; + // while (list) { + // result.unshift(fn(list)); + // list = list.prev; + // } + // return result; + // } + // console.log('--\n', + // '#' + iterationCount, + // require('util').inspect({ + // match: mapList(matchStack, x => x.type === TOKEN ? x.token && x.token.value : x.syntax ? ({ [OPEN_SYNTAX]: '<', [CLOSE_SYNTAX]: '</' }[x.type] || x.type) + '!' + x.syntax.name : null), + // token: token && token.value, + // tokenIndex, + // syntax: syntax.type + (syntax.id ? ' #' + syntax.id : '') + // }, { depth: null }) + // ); + switch (state.type) { + case 'Match': + if (thenStack === null) { + // turn to MISMATCH when some tokens left unmatched + if (token !== null) { + // doesn't mismatch if just one token left and it's an IE hack + if (tokenIndex !== tokens.length - 1 || (token.value !== '\\0' && token.value !== '\\9')) { + state = matchGraph.MISMATCH; + break; + } + } + + // break the main loop, return a result - MATCH + exitReason = EXIT_REASON_MATCH; + break; + } + + // go to next syntax (`then` branch) + state = thenStack.nextState; + + // check match is not empty + if (state === matchGraph.DISALLOW_EMPTY) { + if (thenStack.matchStack === matchStack) { + state = matchGraph.MISMATCH; + break; + } else { + state = matchGraph.MATCH; + } + } + + // close syntax if needed + while (thenStack.syntaxStack !== syntaxStack) { + closeSyntax(); + } + + // pop stack + thenStack = thenStack.prev; + break; + + case 'Mismatch': + // when some syntax is stashed + if (syntaxStash !== null && syntaxStash !== false) { + // there is no else branches or a branch reduce match stack + if (elseStack === null || tokenIndex > elseStack.tokenIndex) { + // restore state from the stash + elseStack = syntaxStash; + syntaxStash = false; // disable stashing + } + } else if (elseStack === null) { + // no else branches -> break the main loop + // return a result - MISMATCH + exitReason = EXIT_REASON_MISMATCH; + break; + } + + // go to next syntax (`else` branch) + state = elseStack.nextState; + + // restore all the rest stack states + thenStack = elseStack.thenStack; + syntaxStack = elseStack.syntaxStack; + matchStack = elseStack.matchStack; + tokenIndex = elseStack.tokenIndex; + token = tokenIndex < tokens.length ? tokens[tokenIndex] : null; + + // pop stack + elseStack = elseStack.prev; + break; + + case 'MatchGraph': + state = state.match; + break; + + case 'If': + // IMPORTANT: else stack push must go first, + // since it stores the state of thenStack before changes + if (state.else !== matchGraph.MISMATCH) { + pushElseStack(state.else); + } + + if (state.then !== matchGraph.MATCH) { + pushThenStack(state.then); + } + + state = state.match; + break; + + case 'MatchOnce': + state = { + type: 'MatchOnceBuffer', + syntax: state, + index: 0, + mask: 0 + }; + break; + + case 'MatchOnceBuffer': { + const terms = state.syntax.terms; + + if (state.index === terms.length) { + // no matches at all or it's required all terms to be matched + if (state.mask === 0 || state.syntax.all) { + state = matchGraph.MISMATCH; + break; + } + + // a partial match is ok + state = matchGraph.MATCH; + break; + } + + // all terms are matched + if (state.mask === (1 << terms.length) - 1) { + state = matchGraph.MATCH; + break; + } + + for (; state.index < terms.length; state.index++) { + const matchFlag = 1 << state.index; + + if ((state.mask & matchFlag) === 0) { + // IMPORTANT: else stack push must go first, + // since it stores the state of thenStack before changes + pushElseStack(state); + pushThenStack({ + type: 'AddMatchOnce', + syntax: state.syntax, + mask: state.mask | matchFlag + }); + + // match + state = terms[state.index++]; + break; + } + } + break; + } + + case 'AddMatchOnce': + state = { + type: 'MatchOnceBuffer', + syntax: state.syntax, + index: 0, + mask: state.mask + }; + break; + + case 'Enum': + if (token !== null) { + let name = token.value.toLowerCase(); + + // drop \0 and \9 hack from keyword name + if (name.indexOf('\\') !== -1) { + name = name.replace(/\\[09].*$/, ''); + } + + if (hasOwnProperty.call(state.map, name)) { + state = state.map[name]; + break; + } + } + + state = matchGraph.MISMATCH; + break; + + case 'Generic': { + const opts = syntaxStack !== null ? syntaxStack.opts : null; + const lastTokenIndex = tokenIndex + Math.floor(state.fn(token, getNextToken, opts)); + + if (!isNaN(lastTokenIndex) && lastTokenIndex > tokenIndex) { + while (tokenIndex < lastTokenIndex) { + addTokenToMatch(); + } + + state = matchGraph.MATCH; + } else { + state = matchGraph.MISMATCH; + } + + break; + } + + case 'Type': + case 'Property': { + const syntaxDict = state.type === 'Type' ? 'types' : 'properties'; + const dictSyntax = hasOwnProperty.call(syntaxes, syntaxDict) ? syntaxes[syntaxDict][state.name] : null; + + if (!dictSyntax || !dictSyntax.match) { + throw new Error( + 'Bad syntax reference: ' + + (state.type === 'Type' + ? '<' + state.name + '>' + : '<\'' + state.name + '\'>') + ); + } + + // stash a syntax for types with low priority + if (syntaxStash !== false && token !== null && state.type === 'Type') { + const lowPriorityMatching = + // https://drafts.csswg.org/css-values-4/#custom-idents + // When parsing positionally-ambiguous keywords in a property value, a <custom-ident> production + // can only claim the keyword if no other unfulfilled production can claim it. + (state.name === 'custom-ident' && token.type === types.Ident) || + + // https://drafts.csswg.org/css-values-4/#lengths + // ... if a `0` could be parsed as either a <number> or a <length> in a property (such as line-height), + // it must parse as a <number> + (state.name === 'length' && token.value === '0'); + + if (lowPriorityMatching) { + if (syntaxStash === null) { + syntaxStash = stateSnapshotFromSyntax(state, elseStack); + } + + state = matchGraph.MISMATCH; + break; + } + } + + openSyntax(); + state = dictSyntax.matchRef || dictSyntax.match; + break; + } + + case 'Keyword': { + const name = state.name; + + if (token !== null) { + let keywordName = token.value; + + // drop \0 and \9 hack from keyword name + if (keywordName.indexOf('\\') !== -1) { + keywordName = keywordName.replace(/\\[09].*$/, ''); + } + + if (areStringsEqualCaseInsensitive(keywordName, name)) { + addTokenToMatch(); + state = matchGraph.MATCH; + break; + } + } + + state = matchGraph.MISMATCH; + break; + } + + case 'AtKeyword': + case 'Function': + if (token !== null && areStringsEqualCaseInsensitive(token.value, state.name)) { + addTokenToMatch(); + state = matchGraph.MATCH; + break; + } + + state = matchGraph.MISMATCH; + break; + + case 'Token': + if (token !== null && token.value === state.value) { + addTokenToMatch(); + state = matchGraph.MATCH; + break; + } + + state = matchGraph.MISMATCH; + break; + + case 'Comma': + if (token !== null && token.type === types.Comma) { + if (isCommaContextStart(matchStack.token)) { + state = matchGraph.MISMATCH; + } else { + addTokenToMatch(); + state = isCommaContextEnd(token) ? matchGraph.MISMATCH : matchGraph.MATCH; + } + } else { + state = isCommaContextStart(matchStack.token) || isCommaContextEnd(token) ? matchGraph.MATCH : matchGraph.MISMATCH; + } + + break; + + case 'String': + let string = ''; + let lastTokenIndex = tokenIndex; + + for (; lastTokenIndex < tokens.length && string.length < state.value.length; lastTokenIndex++) { + string += tokens[lastTokenIndex].value; + } + + if (areStringsEqualCaseInsensitive(string, state.value)) { + while (tokenIndex < lastTokenIndex) { + addTokenToMatch(); + } + + state = matchGraph.MATCH; + } else { + state = matchGraph.MISMATCH; + } + + break; + + default: + throw new Error('Unknown node type: ' + state.type); + } + } + + switch (exitReason) { + case null: + console.warn('[csstree-match] BREAK after ' + ITERATION_LIMIT + ' iterations'); + exitReason = EXIT_REASON_ITERATION_LIMIT; + matchStack = null; + break; + + case EXIT_REASON_MATCH: + while (syntaxStack !== null) { + closeSyntax(); + } + break; + + default: + matchStack = null; + } + + return { + tokens, + reason: exitReason, + iterations: iterationCount, + match: matchStack, + longestMatch + }; +} + +function matchAsList(tokens, matchGraph, syntaxes) { + const matchResult = internalMatch(tokens, matchGraph, syntaxes || {}); + + if (matchResult.match !== null) { + let item = reverseList(matchResult.match).prev; + + matchResult.match = []; + + while (item !== null) { + switch (item.type) { + case OPEN_SYNTAX: + case CLOSE_SYNTAX: + matchResult.match.push({ + type: item.type, + syntax: item.syntax + }); + break; + + default: + matchResult.match.push({ + token: item.token.value, + node: item.token.node + }); + break; + } + + item = item.prev; + } + } + + return matchResult; +} + +function matchAsTree(tokens, matchGraph, syntaxes) { + const matchResult = internalMatch(tokens, matchGraph, syntaxes || {}); + + if (matchResult.match === null) { + return matchResult; + } + + let item = matchResult.match; + let host = matchResult.match = { + syntax: matchGraph.syntax || null, + match: [] + }; + const hostStack = [host]; + + // revert a list and start with 2nd item since 1st is a stub item + item = reverseList(item).prev; + + // build a tree + while (item !== null) { + switch (item.type) { + case OPEN_SYNTAX: + host.match.push(host = { + syntax: item.syntax, + match: [] + }); + hostStack.push(host); + break; + + case CLOSE_SYNTAX: + hostStack.pop(); + host = hostStack[hostStack.length - 1]; + break; + + default: + host.match.push({ + syntax: item.syntax || null, + token: item.token.value, + node: item.token.node + }); + } + + item = item.prev; + } + + return matchResult; +} + +exports.matchAsList = matchAsList; +exports.matchAsTree = matchAsTree; diff --git a/vanilla/node_modules/css-tree/cjs/lexer/prepare-tokens.cjs b/vanilla/node_modules/css-tree/cjs/lexer/prepare-tokens.cjs new file mode 100644 index 0000000..24647a5 --- /dev/null +++ b/vanilla/node_modules/css-tree/cjs/lexer/prepare-tokens.cjs @@ -0,0 +1,54 @@ +'use strict'; + +const index = require('../tokenizer/index.cjs'); + +const astToTokens = { + decorator(handlers) { + const tokens = []; + let curNode = null; + + return { + ...handlers, + node(node) { + const tmp = curNode; + curNode = node; + handlers.node.call(this, node); + curNode = tmp; + }, + emit(value, type, auto) { + tokens.push({ + type, + value, + node: auto ? null : curNode + }); + }, + result() { + return tokens; + } + }; + } +}; + +function stringToTokens(str) { + const tokens = []; + + index.tokenize(str, (type, start, end) => + tokens.push({ + type, + value: str.slice(start, end), + node: null + }) + ); + + return tokens; +} + +function prepareTokens(value, syntax) { + if (typeof value === 'string') { + return stringToTokens(value); + } + + return syntax.generate(value, astToTokens); +} + +module.exports = prepareTokens; diff --git a/vanilla/node_modules/css-tree/cjs/lexer/search.cjs b/vanilla/node_modules/css-tree/cjs/lexer/search.cjs new file mode 100644 index 0000000..2b1e12c --- /dev/null +++ b/vanilla/node_modules/css-tree/cjs/lexer/search.cjs @@ -0,0 +1,65 @@ +'use strict'; + +const List = require('../utils/List.cjs'); + +function getFirstMatchNode(matchNode) { + if ('node' in matchNode) { + return matchNode.node; + } + + return getFirstMatchNode(matchNode.match[0]); +} + +function getLastMatchNode(matchNode) { + if ('node' in matchNode) { + return matchNode.node; + } + + return getLastMatchNode(matchNode.match[matchNode.match.length - 1]); +} + +function matchFragments(lexer, ast, match, type, name) { + function findFragments(matchNode) { + if (matchNode.syntax !== null && + matchNode.syntax.type === type && + matchNode.syntax.name === name) { + const start = getFirstMatchNode(matchNode); + const end = getLastMatchNode(matchNode); + + lexer.syntax.walk(ast, function(node, item, list) { + if (node === start) { + const nodes = new List.List(); + + do { + nodes.appendData(item.data); + + if (item.data === end) { + break; + } + + item = item.next; + } while (item !== null); + + fragments.push({ + parent: list, + nodes + }); + } + }); + } + + if (Array.isArray(matchNode.match)) { + matchNode.match.forEach(findFragments); + } + } + + const fragments = []; + + if (match.matched !== null) { + findFragments(match.matched); + } + + return fragments; +} + +exports.matchFragments = matchFragments; diff --git a/vanilla/node_modules/css-tree/cjs/lexer/structure.cjs b/vanilla/node_modules/css-tree/cjs/lexer/structure.cjs new file mode 100644 index 0000000..473b209 --- /dev/null +++ b/vanilla/node_modules/css-tree/cjs/lexer/structure.cjs @@ -0,0 +1,173 @@ +'use strict'; + +const List = require('../utils/List.cjs'); + +const { hasOwnProperty } = Object.prototype; + +function isValidNumber(value) { + // Number.isInteger(value) && value >= 0 + return ( + typeof value === 'number' && + isFinite(value) && + Math.floor(value) === value && + value >= 0 + ); +} + +function isValidLocation(loc) { + return ( + Boolean(loc) && + isValidNumber(loc.offset) && + isValidNumber(loc.line) && + isValidNumber(loc.column) + ); +} + +function createNodeStructureChecker(type, fields) { + return function checkNode(node, warn) { + if (!node || node.constructor !== Object) { + return warn(node, 'Type of node should be an Object'); + } + + for (let key in node) { + let valid = true; + + if (hasOwnProperty.call(node, key) === false) { + continue; + } + + if (key === 'type') { + if (node.type !== type) { + warn(node, 'Wrong node type `' + node.type + '`, expected `' + type + '`'); + } + } else if (key === 'loc') { + if (node.loc === null) { + continue; + } else if (node.loc && node.loc.constructor === Object) { + if (typeof node.loc.source !== 'string') { + key += '.source'; + } else if (!isValidLocation(node.loc.start)) { + key += '.start'; + } else if (!isValidLocation(node.loc.end)) { + key += '.end'; + } else { + continue; + } + } + + valid = false; + } else if (fields.hasOwnProperty(key)) { + valid = false; + + for (let i = 0; !valid && i < fields[key].length; i++) { + const fieldType = fields[key][i]; + + switch (fieldType) { + case String: + valid = typeof node[key] === 'string'; + break; + + case Boolean: + valid = typeof node[key] === 'boolean'; + break; + + case null: + valid = node[key] === null; + break; + + default: + if (typeof fieldType === 'string') { + valid = node[key] && node[key].type === fieldType; + } else if (Array.isArray(fieldType)) { + valid = node[key] instanceof List.List; + } + } + } + } else { + warn(node, 'Unknown field `' + key + '` for ' + type + ' node type'); + } + + if (!valid) { + warn(node, 'Bad value for `' + type + '.' + key + '`'); + } + } + + for (const key in fields) { + if (hasOwnProperty.call(fields, key) && + hasOwnProperty.call(node, key) === false) { + warn(node, 'Field `' + type + '.' + key + '` is missed'); + } + } + }; +} + +function genTypesList(fieldTypes, path) { + const docsTypes = []; + + for (let i = 0; i < fieldTypes.length; i++) { + const fieldType = fieldTypes[i]; + if (fieldType === String || fieldType === Boolean) { + docsTypes.push(fieldType.name.toLowerCase()); + } else if (fieldType === null) { + docsTypes.push('null'); + } else if (typeof fieldType === 'string') { + docsTypes.push(fieldType); + } else if (Array.isArray(fieldType)) { + docsTypes.push('List<' + (genTypesList(fieldType, path) || 'any') + '>'); // TODO: use type enum + } else { + throw new Error('Wrong value `' + fieldType + '` in `' + path + '` structure definition'); + } + } + + return docsTypes.join(' | '); +} + +function processStructure(name, nodeType) { + const structure = nodeType.structure; + const fields = { + type: String, + loc: true + }; + const docs = { + type: '"' + name + '"' + }; + + for (const key in structure) { + if (hasOwnProperty.call(structure, key) === false) { + continue; + } + + const fieldTypes = fields[key] = Array.isArray(structure[key]) + ? structure[key].slice() + : [structure[key]]; + + docs[key] = genTypesList(fieldTypes, name + '.' + key); + } + + return { + docs, + check: createNodeStructureChecker(name, fields) + }; +} + +function getStructureFromConfig(config) { + const structure = {}; + + if (config.node) { + for (const name in config.node) { + if (hasOwnProperty.call(config.node, name)) { + const nodeType = config.node[name]; + + if (nodeType.structure) { + structure[name] = processStructure(name, nodeType); + } else { + throw new Error('Missed `structure` field in `' + name + '` node type definition'); + } + } + } + } + + return structure; +} + +exports.getStructureFromConfig = getStructureFromConfig; diff --git a/vanilla/node_modules/css-tree/cjs/lexer/trace.cjs b/vanilla/node_modules/css-tree/cjs/lexer/trace.cjs new file mode 100644 index 0000000..13753f2 --- /dev/null +++ b/vanilla/node_modules/css-tree/cjs/lexer/trace.cjs @@ -0,0 +1,73 @@ +'use strict'; + +function getTrace(node) { + function shouldPutToTrace(syntax) { + if (syntax === null) { + return false; + } + + return ( + syntax.type === 'Type' || + syntax.type === 'Property' || + syntax.type === 'Keyword' + ); + } + + function hasMatch(matchNode) { + if (Array.isArray(matchNode.match)) { + // use for-loop for better perfomance + for (let i = 0; i < matchNode.match.length; i++) { + if (hasMatch(matchNode.match[i])) { + if (shouldPutToTrace(matchNode.syntax)) { + result.unshift(matchNode.syntax); + } + + return true; + } + } + } else if (matchNode.node === node) { + result = shouldPutToTrace(matchNode.syntax) + ? [matchNode.syntax] + : []; + + return true; + } + + return false; + } + + let result = null; + + if (this.matched !== null) { + hasMatch(this.matched); + } + + return result; +} + +function isType(node, type) { + return testNode(this, node, match => match.type === 'Type' && match.name === type); +} + +function isProperty(node, property) { + return testNode(this, node, match => match.type === 'Property' && match.name === property); +} + +function isKeyword(node) { + return testNode(this, node, match => match.type === 'Keyword'); +} + +function testNode(match, node, fn) { + const trace = getTrace.call(match, node); + + if (trace === null) { + return false; + } + + return trace.some(fn); +} + +exports.getTrace = getTrace; +exports.isKeyword = isKeyword; +exports.isProperty = isProperty; +exports.isType = isType; diff --git a/vanilla/node_modules/css-tree/cjs/lexer/units.cjs b/vanilla/node_modules/css-tree/cjs/lexer/units.cjs new file mode 100644 index 0000000..13f4e97 --- /dev/null +++ b/vanilla/node_modules/css-tree/cjs/lexer/units.cjs @@ -0,0 +1,38 @@ +'use strict'; + +const length = [ + // absolute length units https://www.w3.org/TR/css-values-3/#lengths + 'cm', 'mm', 'q', 'in', 'pt', 'pc', 'px', + // font-relative length units https://drafts.csswg.org/css-values-4/#font-relative-lengths + 'em', 'rem', + 'ex', 'rex', + 'cap', 'rcap', + 'ch', 'rch', + 'ic', 'ric', + 'lh', 'rlh', + // viewport-percentage lengths https://drafts.csswg.org/css-values-4/#viewport-relative-lengths + 'vw', 'svw', 'lvw', 'dvw', + 'vh', 'svh', 'lvh', 'dvh', + 'vi', 'svi', 'lvi', 'dvi', + 'vb', 'svb', 'lvb', 'dvb', + 'vmin', 'svmin', 'lvmin', 'dvmin', + 'vmax', 'svmax', 'lvmax', 'dvmax', + // container relative lengths https://drafts.csswg.org/css-contain-3/#container-lengths + 'cqw', 'cqh', 'cqi', 'cqb', 'cqmin', 'cqmax' +]; +const angle = ['deg', 'grad', 'rad', 'turn']; // https://www.w3.org/TR/css-values-3/#angles +const time = ['s', 'ms']; // https://www.w3.org/TR/css-values-3/#time +const frequency = ['hz', 'khz']; // https://www.w3.org/TR/css-values-3/#frequency +const resolution = ['dpi', 'dpcm', 'dppx', 'x']; // https://www.w3.org/TR/css-values-3/#resolution +const flex = ['fr']; // https://drafts.csswg.org/css-grid/#fr-unit +const decibel = ['db']; // https://www.w3.org/TR/css3-speech/#mixing-props-voice-volume +const semitones = ['st']; // https://www.w3.org/TR/css3-speech/#voice-props-voice-pitch + +exports.angle = angle; +exports.decibel = decibel; +exports.flex = flex; +exports.frequency = frequency; +exports.length = length; +exports.resolution = resolution; +exports.semitones = semitones; +exports.time = time; |
