aboutsummaryrefslogtreecommitdiffstats
path: root/vanilla/node_modules/css-tree/lib/parser
diff options
context:
space:
mode:
authorAdam Mathes <adam@adammathes.com>2026-02-13 21:34:48 -0800
committerAdam Mathes <adam@adammathes.com>2026-02-13 21:34:48 -0800
commit76cb9c2a39d477a64824a985ade40507e3bbade1 (patch)
tree41e997aa9c6f538d3a136af61dae9424db2005a9 /vanilla/node_modules/css-tree/lib/parser
parent819a39a21ac992b1393244a4c283bbb125208c69 (diff)
downloadneko-76cb9c2a39d477a64824a985ade40507e3bbade1.tar.gz
neko-76cb9c2a39d477a64824a985ade40507e3bbade1.tar.bz2
neko-76cb9c2a39d477a64824a985ade40507e3bbade1.zip
feat(vanilla): add testing infrastructure and tests (NK-wjnczv)
Diffstat (limited to 'vanilla/node_modules/css-tree/lib/parser')
-rw-r--r--vanilla/node_modules/css-tree/lib/parser/SyntaxError.js70
-rw-r--r--vanilla/node_modules/css-tree/lib/parser/create.js350
-rw-r--r--vanilla/node_modules/css-tree/lib/parser/index.js4
-rw-r--r--vanilla/node_modules/css-tree/lib/parser/parse-selector.js4
-rw-r--r--vanilla/node_modules/css-tree/lib/parser/sequence.js43
5 files changed, 471 insertions, 0 deletions
diff --git a/vanilla/node_modules/css-tree/lib/parser/SyntaxError.js b/vanilla/node_modules/css-tree/lib/parser/SyntaxError.js
new file mode 100644
index 0000000..55e01c1
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/parser/SyntaxError.js
@@ -0,0 +1,70 @@
+import { createCustomError } from '../utils/create-custom-error.js';
+
+const MAX_LINE_LENGTH = 100;
+const OFFSET_CORRECTION = 60;
+const TAB_REPLACEMENT = ' ';
+
+function sourceFragment({ source, line, column, baseLine, baseColumn }, extraLines) {
+ function processLines(start, end) {
+ return lines
+ .slice(start, end)
+ .map((line, idx) =>
+ String(start + idx + 1).padStart(maxNumLength) + ' |' + line
+ ).join('\n');
+ }
+
+ const prelines = '\n'.repeat(Math.max(baseLine - 1, 0));
+ const precolumns = ' '.repeat(Math.max(baseColumn - 1, 0));
+ const lines = (prelines + precolumns + source).split(/\r\n?|\n|\f/);
+ const startLine = Math.max(1, line - extraLines) - 1;
+ const endLine = Math.min(line + extraLines, lines.length + 1);
+ const maxNumLength = Math.max(4, String(endLine).length) + 1;
+ let cutLeft = 0;
+
+ // column correction according to replaced tab before column
+ column += (TAB_REPLACEMENT.length - 1) * (lines[line - 1].substr(0, column - 1).match(/\t/g) || []).length;
+
+ if (column > MAX_LINE_LENGTH) {
+ cutLeft = column - OFFSET_CORRECTION + 3;
+ column = OFFSET_CORRECTION - 2;
+ }
+
+ for (let i = startLine; i <= endLine; i++) {
+ if (i >= 0 && i < lines.length) {
+ lines[i] = lines[i].replace(/\t/g, TAB_REPLACEMENT);
+ lines[i] =
+ (cutLeft > 0 && lines[i].length > cutLeft ? '\u2026' : '') +
+ lines[i].substr(cutLeft, MAX_LINE_LENGTH - 2) +
+ (lines[i].length > cutLeft + MAX_LINE_LENGTH - 1 ? '\u2026' : '');
+ }
+ }
+
+ return [
+ processLines(startLine, line),
+ new Array(column + maxNumLength + 2).join('-') + '^',
+ processLines(line, endLine)
+ ].filter(Boolean)
+ .join('\n')
+ .replace(/^(\s+\d+\s+\|\n)+/, '')
+ .replace(/\n(\s+\d+\s+\|)+$/, '');
+}
+
+export function SyntaxError(message, source, offset, line, column, baseLine = 1, baseColumn = 1) {
+ const error = Object.assign(createCustomError('SyntaxError', message), {
+ source,
+ offset,
+ line,
+ column,
+ sourceFragment(extraLines) {
+ return sourceFragment({ source, line, column, baseLine, baseColumn }, isNaN(extraLines) ? 0 : extraLines);
+ },
+ get formattedMessage() {
+ return (
+ `Parse error: ${message}\n` +
+ sourceFragment({ source, line, column, baseLine, baseColumn }, 2)
+ );
+ }
+ });
+
+ return error;
+}
diff --git a/vanilla/node_modules/css-tree/lib/parser/create.js b/vanilla/node_modules/css-tree/lib/parser/create.js
new file mode 100644
index 0000000..8c08d81
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/parser/create.js
@@ -0,0 +1,350 @@
+import { List } from '../utils/List.js';
+import { SyntaxError } from './SyntaxError.js';
+import {
+ tokenize,
+ OffsetToLocation,
+ TokenStream,
+ tokenNames,
+
+ consumeNumber,
+ findWhiteSpaceStart,
+ cmpChar,
+ cmpStr,
+
+ WhiteSpace,
+ Comment,
+ Ident,
+ Function as FunctionToken,
+ Url,
+ Hash,
+ Percentage,
+ Number as NumberToken
+} from '../tokenizer/index.js';
+import { readSequence } from './sequence.js';
+
+const NOOP = () => {};
+const EXCLAMATIONMARK = 0x0021; // U+0021 EXCLAMATION MARK (!)
+const NUMBERSIGN = 0x0023; // U+0023 NUMBER SIGN (#)
+const SEMICOLON = 0x003B; // U+003B SEMICOLON (;)
+const LEFTCURLYBRACKET = 0x007B; // U+007B LEFT CURLY BRACKET ({)
+const NULL = 0;
+
+function createParseContext(name) {
+ return function() {
+ return this[name]();
+ };
+}
+
+function fetchParseValues(dict) {
+ const result = Object.create(null);
+
+ for (const name of Object.keys(dict)) {
+ const item = dict[name];
+ const fn = item.parse || item;
+
+ if (fn) {
+ result[name] = fn;
+ }
+ }
+
+ return result;
+}
+
+function processConfig(config) {
+ const parseConfig = {
+ context: Object.create(null),
+ features: Object.assign(Object.create(null), config.features),
+ scope: Object.assign(Object.create(null), config.scope),
+ atrule: fetchParseValues(config.atrule),
+ pseudo: fetchParseValues(config.pseudo),
+ node: fetchParseValues(config.node)
+ };
+
+ for (const [name, context] of Object.entries(config.parseContext)) {
+ switch (typeof context) {
+ case 'function':
+ parseConfig.context[name] = context;
+ break;
+
+ case 'string':
+ parseConfig.context[name] = createParseContext(context);
+ break;
+ }
+ }
+
+ return {
+ config: parseConfig,
+ ...parseConfig,
+ ...parseConfig.node
+ };
+}
+
+export function createParser(config) {
+ let source = '';
+ let filename = '<unknown>';
+ let needPositions = false;
+ let onParseError = NOOP;
+ let onParseErrorThrow = false;
+
+ const locationMap = new OffsetToLocation();
+ const parser = Object.assign(new TokenStream(), processConfig(config || {}), {
+ parseAtrulePrelude: true,
+ parseRulePrelude: true,
+ parseValue: true,
+ parseCustomProperty: false,
+
+ readSequence,
+
+ consumeUntilBalanceEnd: () => 0,
+ consumeUntilLeftCurlyBracket(code) {
+ return code === LEFTCURLYBRACKET ? 1 : 0;
+ },
+ consumeUntilLeftCurlyBracketOrSemicolon(code) {
+ return code === LEFTCURLYBRACKET || code === SEMICOLON ? 1 : 0;
+ },
+ consumeUntilExclamationMarkOrSemicolon(code) {
+ return code === EXCLAMATIONMARK || code === SEMICOLON ? 1 : 0;
+ },
+ consumeUntilSemicolonIncluded(code) {
+ return code === SEMICOLON ? 2 : 0;
+ },
+
+ createList() {
+ return new List();
+ },
+ createSingleNodeList(node) {
+ return new List().appendData(node);
+ },
+ getFirstListNode(list) {
+ return list && list.first;
+ },
+ getLastListNode(list) {
+ return list && list.last;
+ },
+
+ parseWithFallback(consumer, fallback) {
+ const startIndex = this.tokenIndex;
+
+ try {
+ return consumer.call(this);
+ } catch (e) {
+ if (onParseErrorThrow) {
+ throw e;
+ }
+
+ this.skip(startIndex - this.tokenIndex);
+ const fallbackNode = fallback.call(this);
+
+ onParseErrorThrow = true;
+ onParseError(e, fallbackNode);
+ onParseErrorThrow = false;
+
+ return fallbackNode;
+ }
+ },
+
+ lookupNonWSType(offset) {
+ let type;
+
+ do {
+ type = this.lookupType(offset++);
+ if (type !== WhiteSpace && type !== Comment) {
+ return type;
+ }
+ } while (type !== NULL);
+
+ return NULL;
+ },
+
+ charCodeAt(offset) {
+ return offset >= 0 && offset < source.length ? source.charCodeAt(offset) : 0;
+ },
+ substring(offsetStart, offsetEnd) {
+ return source.substring(offsetStart, offsetEnd);
+ },
+ substrToCursor(start) {
+ return this.source.substring(start, this.tokenStart);
+ },
+
+ cmpChar(offset, charCode) {
+ return cmpChar(source, offset, charCode);
+ },
+ cmpStr(offsetStart, offsetEnd, str) {
+ return cmpStr(source, offsetStart, offsetEnd, str);
+ },
+
+ consume(tokenType) {
+ const start = this.tokenStart;
+
+ this.eat(tokenType);
+
+ return this.substrToCursor(start);
+ },
+ consumeFunctionName() {
+ const name = source.substring(this.tokenStart, this.tokenEnd - 1);
+
+ this.eat(FunctionToken);
+
+ return name;
+ },
+ consumeNumber(type) {
+ const number = source.substring(this.tokenStart, consumeNumber(source, this.tokenStart));
+
+ this.eat(type);
+
+ return number;
+ },
+
+ eat(tokenType) {
+ if (this.tokenType !== tokenType) {
+ const tokenName = tokenNames[tokenType].slice(0, -6).replace(/-/g, ' ').replace(/^./, m => m.toUpperCase());
+ let message = `${/[[\](){}]/.test(tokenName) ? `"${tokenName}"` : tokenName} is expected`;
+ let offset = this.tokenStart;
+
+ // tweak message and offset
+ switch (tokenType) {
+ case Ident:
+ // when identifier is expected but there is a function or url
+ if (this.tokenType === FunctionToken || this.tokenType === Url) {
+ offset = this.tokenEnd - 1;
+ message = 'Identifier is expected but function found';
+ } else {
+ message = 'Identifier is expected';
+ }
+ break;
+
+ case Hash:
+ if (this.isDelim(NUMBERSIGN)) {
+ this.next();
+ offset++;
+ message = 'Name is expected';
+ }
+ break;
+
+ case Percentage:
+ if (this.tokenType === NumberToken) {
+ offset = this.tokenEnd;
+ message = 'Percent sign is expected';
+ }
+ break;
+ }
+
+ this.error(message, offset);
+ }
+
+ this.next();
+ },
+ eatIdent(name) {
+ if (this.tokenType !== Ident || this.lookupValue(0, name) === false) {
+ this.error(`Identifier "${name}" is expected`);
+ }
+
+ this.next();
+ },
+ eatDelim(code) {
+ if (!this.isDelim(code)) {
+ this.error(`Delim "${String.fromCharCode(code)}" is expected`);
+ }
+
+ this.next();
+ },
+
+ getLocation(start, end) {
+ if (needPositions) {
+ return locationMap.getLocationRange(
+ start,
+ end,
+ filename
+ );
+ }
+
+ return null;
+ },
+ getLocationFromList(list) {
+ if (needPositions) {
+ const head = this.getFirstListNode(list);
+ const tail = this.getLastListNode(list);
+ return locationMap.getLocationRange(
+ head !== null ? head.loc.start.offset - locationMap.startOffset : this.tokenStart,
+ tail !== null ? tail.loc.end.offset - locationMap.startOffset : this.tokenStart,
+ filename
+ );
+ }
+
+ return null;
+ },
+
+ error(message, offset) {
+ const location = typeof offset !== 'undefined' && offset < source.length
+ ? locationMap.getLocation(offset)
+ : this.eof
+ ? locationMap.getLocation(findWhiteSpaceStart(source, source.length - 1))
+ : locationMap.getLocation(this.tokenStart);
+
+ throw new SyntaxError(
+ message || 'Unexpected input',
+ source,
+ location.offset,
+ location.line,
+ location.column,
+ locationMap.startLine,
+ locationMap.startColumn
+ );
+ }
+ });
+
+ const parse = function(source_, options) {
+ source = source_;
+ options = options || {};
+
+ parser.setSource(source, tokenize);
+ locationMap.setSource(
+ source,
+ options.offset,
+ options.line,
+ options.column
+ );
+
+ filename = options.filename || '<unknown>';
+ needPositions = Boolean(options.positions);
+ onParseError = typeof options.onParseError === 'function' ? options.onParseError : NOOP;
+ onParseErrorThrow = false;
+
+ parser.parseAtrulePrelude = 'parseAtrulePrelude' in options ? Boolean(options.parseAtrulePrelude) : true;
+ parser.parseRulePrelude = 'parseRulePrelude' in options ? Boolean(options.parseRulePrelude) : true;
+ parser.parseValue = 'parseValue' in options ? Boolean(options.parseValue) : true;
+ parser.parseCustomProperty = 'parseCustomProperty' in options ? Boolean(options.parseCustomProperty) : false;
+
+ const { context = 'default', onComment } = options;
+
+ if (context in parser.context === false) {
+ throw new Error('Unknown context `' + context + '`');
+ }
+
+ if (typeof onComment === 'function') {
+ parser.forEachToken((type, start, end) => {
+ if (type === Comment) {
+ const loc = parser.getLocation(start, end);
+ const value = cmpStr(source, end - 2, end, '*/')
+ ? source.slice(start + 2, end - 2)
+ : source.slice(start + 2, end);
+
+ onComment(value, loc);
+ }
+ });
+ }
+
+ const ast = parser.context[context].call(parser, options);
+
+ if (!parser.eof) {
+ parser.error();
+ }
+
+ return ast;
+ };
+
+ return Object.assign(parse, {
+ SyntaxError,
+ config: parser.config
+ });
+};
diff --git a/vanilla/node_modules/css-tree/lib/parser/index.js b/vanilla/node_modules/css-tree/lib/parser/index.js
new file mode 100644
index 0000000..7cee62e
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/parser/index.js
@@ -0,0 +1,4 @@
+import { createParser } from './create.js';
+import config from '../syntax/config/parser.js';
+
+export default createParser(config);
diff --git a/vanilla/node_modules/css-tree/lib/parser/parse-selector.js b/vanilla/node_modules/css-tree/lib/parser/parse-selector.js
new file mode 100644
index 0000000..05b539a
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/parser/parse-selector.js
@@ -0,0 +1,4 @@
+import { createParser } from './create.js';
+import config from '../syntax/config/parser-selector.js';
+
+export default createParser(config);
diff --git a/vanilla/node_modules/css-tree/lib/parser/sequence.js b/vanilla/node_modules/css-tree/lib/parser/sequence.js
new file mode 100644
index 0000000..47d1ccd
--- /dev/null
+++ b/vanilla/node_modules/css-tree/lib/parser/sequence.js
@@ -0,0 +1,43 @@
+import { WhiteSpace, Comment } from '../tokenizer/index.js';
+
+export function readSequence(recognizer) {
+ const children = this.createList();
+ let space = false;
+ const context = {
+ recognizer
+ };
+
+ while (!this.eof) {
+ switch (this.tokenType) {
+ case Comment:
+ this.next();
+ continue;
+
+ case WhiteSpace:
+ space = true;
+ this.next();
+ continue;
+ }
+
+ let child = recognizer.getNode.call(this, context);
+
+ if (child === undefined) {
+ break;
+ }
+
+ if (space) {
+ if (recognizer.onWhiteSpace) {
+ recognizer.onWhiteSpace.call(this, child, children, context);
+ }
+ space = false;
+ }
+
+ children.push(child);
+ }
+
+ if (space && recognizer.onWhiteSpace) {
+ recognizer.onWhiteSpace.call(this, null, children, context);
+ }
+
+ return children;
+};