From 76cb9c2a39d477a64824a985ade40507e3bbade1 Mon Sep 17 00:00:00 2001 From: Adam Mathes Date: Fri, 13 Feb 2026 21:34:48 -0800 Subject: feat(vanilla): add testing infrastructure and tests (NK-wjnczv) --- .../node_modules/css-tree/cjs/parser/create.cjs | 340 +++++++++++++++++++++ 1 file changed, 340 insertions(+) create mode 100644 vanilla/node_modules/css-tree/cjs/parser/create.cjs (limited to 'vanilla/node_modules/css-tree/cjs/parser/create.cjs') diff --git a/vanilla/node_modules/css-tree/cjs/parser/create.cjs b/vanilla/node_modules/css-tree/cjs/parser/create.cjs new file mode 100644 index 0000000..0a59c4e --- /dev/null +++ b/vanilla/node_modules/css-tree/cjs/parser/create.cjs @@ -0,0 +1,340 @@ +'use strict'; + +const List = require('../utils/List.cjs'); +const SyntaxError = require('./SyntaxError.cjs'); +const index = require('../tokenizer/index.cjs'); +const sequence = require('./sequence.cjs'); +const OffsetToLocation = require('../tokenizer/OffsetToLocation.cjs'); +const TokenStream = require('../tokenizer/TokenStream.cjs'); +const utils = require('../tokenizer/utils.cjs'); +const types = require('../tokenizer/types.cjs'); +const names = require('../tokenizer/names.cjs'); + +const NOOP = () => {}; +const EXCLAMATIONMARK = 0x0021; // U+0021 EXCLAMATION MARK (!) +const NUMBERSIGN = 0x0023; // U+0023 NUMBER SIGN (#) +const SEMICOLON = 0x003B; // U+003B SEMICOLON (;) +const LEFTCURLYBRACKET = 0x007B; // U+007B LEFT CURLY BRACKET ({) +const NULL = 0; + +function createParseContext(name) { + return function() { + return this[name](); + }; +} + +function fetchParseValues(dict) { + const result = Object.create(null); + + for (const name of Object.keys(dict)) { + const item = dict[name]; + const fn = item.parse || item; + + if (fn) { + result[name] = fn; + } + } + + return result; +} + +function processConfig(config) { + const parseConfig = { + context: Object.create(null), + features: Object.assign(Object.create(null), config.features), + scope: Object.assign(Object.create(null), config.scope), + atrule: fetchParseValues(config.atrule), + pseudo: fetchParseValues(config.pseudo), + node: fetchParseValues(config.node) + }; + + for (const [name, context] of Object.entries(config.parseContext)) { + switch (typeof context) { + case 'function': + parseConfig.context[name] = context; + break; + + case 'string': + parseConfig.context[name] = createParseContext(context); + break; + } + } + + return { + config: parseConfig, + ...parseConfig, + ...parseConfig.node + }; +} + +function createParser(config) { + let source = ''; + let filename = ''; + let needPositions = false; + let onParseError = NOOP; + let onParseErrorThrow = false; + + const locationMap = new OffsetToLocation.OffsetToLocation(); + const parser = Object.assign(new TokenStream.TokenStream(), processConfig(config || {}), { + parseAtrulePrelude: true, + parseRulePrelude: true, + parseValue: true, + parseCustomProperty: false, + + readSequence: sequence.readSequence, + + consumeUntilBalanceEnd: () => 0, + consumeUntilLeftCurlyBracket(code) { + return code === LEFTCURLYBRACKET ? 1 : 0; + }, + consumeUntilLeftCurlyBracketOrSemicolon(code) { + return code === LEFTCURLYBRACKET || code === SEMICOLON ? 1 : 0; + }, + consumeUntilExclamationMarkOrSemicolon(code) { + return code === EXCLAMATIONMARK || code === SEMICOLON ? 1 : 0; + }, + consumeUntilSemicolonIncluded(code) { + return code === SEMICOLON ? 2 : 0; + }, + + createList() { + return new List.List(); + }, + createSingleNodeList(node) { + return new List.List().appendData(node); + }, + getFirstListNode(list) { + return list && list.first; + }, + getLastListNode(list) { + return list && list.last; + }, + + parseWithFallback(consumer, fallback) { + const startIndex = this.tokenIndex; + + try { + return consumer.call(this); + } catch (e) { + if (onParseErrorThrow) { + throw e; + } + + this.skip(startIndex - this.tokenIndex); + const fallbackNode = fallback.call(this); + + onParseErrorThrow = true; + onParseError(e, fallbackNode); + onParseErrorThrow = false; + + return fallbackNode; + } + }, + + lookupNonWSType(offset) { + let type; + + do { + type = this.lookupType(offset++); + if (type !== types.WhiteSpace && type !== types.Comment) { + return type; + } + } while (type !== NULL); + + return NULL; + }, + + charCodeAt(offset) { + return offset >= 0 && offset < source.length ? source.charCodeAt(offset) : 0; + }, + substring(offsetStart, offsetEnd) { + return source.substring(offsetStart, offsetEnd); + }, + substrToCursor(start) { + return this.source.substring(start, this.tokenStart); + }, + + cmpChar(offset, charCode) { + return utils.cmpChar(source, offset, charCode); + }, + cmpStr(offsetStart, offsetEnd, str) { + return utils.cmpStr(source, offsetStart, offsetEnd, str); + }, + + consume(tokenType) { + const start = this.tokenStart; + + this.eat(tokenType); + + return this.substrToCursor(start); + }, + consumeFunctionName() { + const name = source.substring(this.tokenStart, this.tokenEnd - 1); + + this.eat(types.Function); + + return name; + }, + consumeNumber(type) { + const number = source.substring(this.tokenStart, utils.consumeNumber(source, this.tokenStart)); + + this.eat(type); + + return number; + }, + + eat(tokenType) { + if (this.tokenType !== tokenType) { + const tokenName = names[tokenType].slice(0, -6).replace(/-/g, ' ').replace(/^./, m => m.toUpperCase()); + let message = `${/[[\](){}]/.test(tokenName) ? `"${tokenName}"` : tokenName} is expected`; + let offset = this.tokenStart; + + // tweak message and offset + switch (tokenType) { + case types.Ident: + // when identifier is expected but there is a function or url + if (this.tokenType === types.Function || this.tokenType === types.Url) { + offset = this.tokenEnd - 1; + message = 'Identifier is expected but function found'; + } else { + message = 'Identifier is expected'; + } + break; + + case types.Hash: + if (this.isDelim(NUMBERSIGN)) { + this.next(); + offset++; + message = 'Name is expected'; + } + break; + + case types.Percentage: + if (this.tokenType === types.Number) { + offset = this.tokenEnd; + message = 'Percent sign is expected'; + } + break; + } + + this.error(message, offset); + } + + this.next(); + }, + eatIdent(name) { + if (this.tokenType !== types.Ident || this.lookupValue(0, name) === false) { + this.error(`Identifier "${name}" is expected`); + } + + this.next(); + }, + eatDelim(code) { + if (!this.isDelim(code)) { + this.error(`Delim "${String.fromCharCode(code)}" is expected`); + } + + this.next(); + }, + + getLocation(start, end) { + if (needPositions) { + return locationMap.getLocationRange( + start, + end, + filename + ); + } + + return null; + }, + getLocationFromList(list) { + if (needPositions) { + const head = this.getFirstListNode(list); + const tail = this.getLastListNode(list); + return locationMap.getLocationRange( + head !== null ? head.loc.start.offset - locationMap.startOffset : this.tokenStart, + tail !== null ? tail.loc.end.offset - locationMap.startOffset : this.tokenStart, + filename + ); + } + + return null; + }, + + error(message, offset) { + const location = typeof offset !== 'undefined' && offset < source.length + ? locationMap.getLocation(offset) + : this.eof + ? locationMap.getLocation(utils.findWhiteSpaceStart(source, source.length - 1)) + : locationMap.getLocation(this.tokenStart); + + throw new SyntaxError.SyntaxError( + message || 'Unexpected input', + source, + location.offset, + location.line, + location.column, + locationMap.startLine, + locationMap.startColumn + ); + } + }); + + const parse = function(source_, options) { + source = source_; + options = options || {}; + + parser.setSource(source, index.tokenize); + locationMap.setSource( + source, + options.offset, + options.line, + options.column + ); + + filename = options.filename || ''; + needPositions = Boolean(options.positions); + onParseError = typeof options.onParseError === 'function' ? options.onParseError : NOOP; + onParseErrorThrow = false; + + parser.parseAtrulePrelude = 'parseAtrulePrelude' in options ? Boolean(options.parseAtrulePrelude) : true; + parser.parseRulePrelude = 'parseRulePrelude' in options ? Boolean(options.parseRulePrelude) : true; + parser.parseValue = 'parseValue' in options ? Boolean(options.parseValue) : true; + parser.parseCustomProperty = 'parseCustomProperty' in options ? Boolean(options.parseCustomProperty) : false; + + const { context = 'default', onComment } = options; + + if (context in parser.context === false) { + throw new Error('Unknown context `' + context + '`'); + } + + if (typeof onComment === 'function') { + parser.forEachToken((type, start, end) => { + if (type === types.Comment) { + const loc = parser.getLocation(start, end); + const value = utils.cmpStr(source, end - 2, end, '*/') + ? source.slice(start + 2, end - 2) + : source.slice(start + 2, end); + + onComment(value, loc); + } + }); + } + + const ast = parser.context[context].call(parser, options); + + if (!parser.eof) { + parser.error(); + } + + return ast; + }; + + return Object.assign(parse, { + SyntaxError: SyntaxError.SyntaxError, + config: parser.config + }); +} + +exports.createParser = createParser; -- cgit v1.2.3