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/lib/generator/create.js | 97 +++++++++++ .../node_modules/css-tree/lib/generator/index.js | 4 + .../css-tree/lib/generator/sourceMap.js | 92 +++++++++++ .../css-tree/lib/generator/token-before.js | 182 +++++++++++++++++++++ 4 files changed, 375 insertions(+) create mode 100644 vanilla/node_modules/css-tree/lib/generator/create.js create mode 100644 vanilla/node_modules/css-tree/lib/generator/index.js create mode 100644 vanilla/node_modules/css-tree/lib/generator/sourceMap.js create mode 100644 vanilla/node_modules/css-tree/lib/generator/token-before.js (limited to 'vanilla/node_modules/css-tree/lib/generator') diff --git a/vanilla/node_modules/css-tree/lib/generator/create.js b/vanilla/node_modules/css-tree/lib/generator/create.js new file mode 100644 index 0000000..c542f4f --- /dev/null +++ b/vanilla/node_modules/css-tree/lib/generator/create.js @@ -0,0 +1,97 @@ +import { tokenize, Delim, WhiteSpace } from '../tokenizer/index.js'; +import { generateSourceMap } from './sourceMap.js'; +import * as tokenBefore from './token-before.js'; + +const REVERSESOLIDUS = 0x005c; // U+005C REVERSE SOLIDUS (\) + +function processChildren(node, delimeter) { + if (typeof delimeter === 'function') { + let prev = null; + + node.children.forEach(node => { + if (prev !== null) { + delimeter.call(this, prev); + } + + this.node(node); + prev = node; + }); + + return; + } + + node.children.forEach(this.node, this); +} + +function processChunk(chunk) { + tokenize(chunk, (type, start, end) => { + this.token(type, chunk.slice(start, end)); + }); +} + +export function createGenerator(config) { + const types = new Map(); + + for (let [name, item] of Object.entries(config.node)) { + const fn = item.generate || item; + + if (typeof fn === 'function') { + types.set(name, item.generate || item); + } + } + + return function(node, options) { + let buffer = ''; + let prevCode = 0; + let handlers = { + node(node) { + if (types.has(node.type)) { + types.get(node.type).call(publicApi, node); + } else { + throw new Error('Unknown node type: ' + node.type); + } + }, + tokenBefore: tokenBefore.safe, + token(type, value) { + prevCode = this.tokenBefore(prevCode, type, value); + + this.emit(value, type, false); + + if (type === Delim && value.charCodeAt(0) === REVERSESOLIDUS) { + this.emit('\n', WhiteSpace, true); + } + }, + emit(value) { + buffer += value; + }, + result() { + return buffer; + } + }; + + if (options) { + if (typeof options.decorator === 'function') { + handlers = options.decorator(handlers); + } + + if (options.sourceMap) { + handlers = generateSourceMap(handlers); + } + + if (options.mode in tokenBefore) { + handlers.tokenBefore = tokenBefore[options.mode]; + } + } + + const publicApi = { + node: (node) => handlers.node(node), + children: processChildren, + token: (type, value) => handlers.token(type, value), + tokenize: processChunk + }; + + handlers.node(node); + + return handlers.result(); + }; +}; diff --git a/vanilla/node_modules/css-tree/lib/generator/index.js b/vanilla/node_modules/css-tree/lib/generator/index.js new file mode 100644 index 0000000..dc05116 --- /dev/null +++ b/vanilla/node_modules/css-tree/lib/generator/index.js @@ -0,0 +1,4 @@ +import { createGenerator } from './create.js'; +import config from '../syntax/config/generator.js'; + +export default createGenerator(config); diff --git a/vanilla/node_modules/css-tree/lib/generator/sourceMap.js b/vanilla/node_modules/css-tree/lib/generator/sourceMap.js new file mode 100644 index 0000000..0c5ff27 --- /dev/null +++ b/vanilla/node_modules/css-tree/lib/generator/sourceMap.js @@ -0,0 +1,92 @@ +import { SourceMapGenerator } from 'source-map-js/lib/source-map-generator.js'; + +const trackNodes = new Set(['Atrule', 'Selector', 'Declaration']); + +export function generateSourceMap(handlers) { + const map = new SourceMapGenerator(); + const generated = { + line: 1, + column: 0 + }; + const original = { + line: 0, // should be zero to add first mapping + column: 0 + }; + const activatedGenerated = { + line: 1, + column: 0 + }; + const activatedMapping = { + generated: activatedGenerated + }; + let line = 1; + let column = 0; + let sourceMappingActive = false; + + const origHandlersNode = handlers.node; + handlers.node = function(node) { + if (node.loc && node.loc.start && trackNodes.has(node.type)) { + const nodeLine = node.loc.start.line; + const nodeColumn = node.loc.start.column - 1; + + if (original.line !== nodeLine || + original.column !== nodeColumn) { + original.line = nodeLine; + original.column = nodeColumn; + + generated.line = line; + generated.column = column; + + if (sourceMappingActive) { + sourceMappingActive = false; + if (generated.line !== activatedGenerated.line || + generated.column !== activatedGenerated.column) { + map.addMapping(activatedMapping); + } + } + + sourceMappingActive = true; + map.addMapping({ + source: node.loc.source, + original, + generated + }); + } + } + + origHandlersNode.call(this, node); + + if (sourceMappingActive && trackNodes.has(node.type)) { + activatedGenerated.line = line; + activatedGenerated.column = column; + } + }; + + const origHandlersEmit = handlers.emit; + handlers.emit = function(value, type, auto) { + for (let i = 0; i < value.length; i++) { + if (value.charCodeAt(i) === 10) { // \n + line++; + column = 0; + } else { + column++; + } + } + + origHandlersEmit(value, type, auto); + }; + + const origHandlersResult = handlers.result; + handlers.result = function() { + if (sourceMappingActive) { + map.addMapping(activatedMapping); + } + + return { + css: origHandlersResult(), + map + }; + }; + + return handlers; +}; diff --git a/vanilla/node_modules/css-tree/lib/generator/token-before.js b/vanilla/node_modules/css-tree/lib/generator/token-before.js new file mode 100644 index 0000000..da3fed0 --- /dev/null +++ b/vanilla/node_modules/css-tree/lib/generator/token-before.js @@ -0,0 +1,182 @@ +import { + WhiteSpace, + Delim, + Ident, + Function as FunctionToken, + Url, + BadUrl, + AtKeyword, + Hash, + Percentage, + Dimension, + Number as NumberToken, + String as StringToken, + Colon, + LeftParenthesis, + RightParenthesis, + CDC +} from '../tokenizer/index.js'; + +const PLUSSIGN = 0x002B; // U+002B PLUS SIGN (+) +const HYPHENMINUS = 0x002D; // U+002D HYPHEN-MINUS (-) + +const code = (type, value) => { + if (type === Delim) { + type = value; + } + + if (typeof type === 'string') { + const charCode = type.charCodeAt(0); + return charCode > 0x7F ? 0x8000 : charCode << 8; + } + + return type; +}; + +// https://www.w3.org/TR/css-syntax-3/#serialization +// The only requirement for serialization is that it must "round-trip" with parsing, +// that is, parsing the stylesheet must produce the same data structures as parsing, +// serializing, and parsing again, except for consecutive s, +// which may be collapsed into a single token. + +const specPairs = [ + [Ident, Ident], + [Ident, FunctionToken], + [Ident, Url], + [Ident, BadUrl], + [Ident, '-'], + [Ident, NumberToken], + [Ident, Percentage], + [Ident, Dimension], + [Ident, CDC], + [Ident, LeftParenthesis], + + [AtKeyword, Ident], + [AtKeyword, FunctionToken], + [AtKeyword, Url], + [AtKeyword, BadUrl], + [AtKeyword, '-'], + [AtKeyword, NumberToken], + [AtKeyword, Percentage], + [AtKeyword, Dimension], + [AtKeyword, CDC], + + [Hash, Ident], + [Hash, FunctionToken], + [Hash, Url], + [Hash, BadUrl], + [Hash, '-'], + [Hash, NumberToken], + [Hash, Percentage], + [Hash, Dimension], + [Hash, CDC], + + [Dimension, Ident], + [Dimension, FunctionToken], + [Dimension, Url], + [Dimension, BadUrl], + [Dimension, '-'], + [Dimension, NumberToken], + [Dimension, Percentage], + [Dimension, Dimension], + [Dimension, CDC], + + ['#', Ident], + ['#', FunctionToken], + ['#', Url], + ['#', BadUrl], + ['#', '-'], + ['#', NumberToken], + ['#', Percentage], + ['#', Dimension], + ['#', CDC], // https://github.com/w3c/csswg-drafts/pull/6874 + + ['-', Ident], + ['-', FunctionToken], + ['-', Url], + ['-', BadUrl], + ['-', '-'], + ['-', NumberToken], + ['-', Percentage], + ['-', Dimension], + ['-', CDC], // https://github.com/w3c/csswg-drafts/pull/6874 + + [NumberToken, Ident], + [NumberToken, FunctionToken], + [NumberToken, Url], + [NumberToken, BadUrl], + [NumberToken, NumberToken], + [NumberToken, Percentage], + [NumberToken, Dimension], + [NumberToken, '%'], + [NumberToken, CDC], // https://github.com/w3c/csswg-drafts/pull/6874 + + ['@', Ident], + ['@', FunctionToken], + ['@', Url], + ['@', BadUrl], + ['@', '-'], + ['@', CDC], // https://github.com/w3c/csswg-drafts/pull/6874 + + ['.', NumberToken], + ['.', Percentage], + ['.', Dimension], + + ['+', NumberToken], + ['+', Percentage], + ['+', Dimension], + + ['/', '*'] +]; +// validate with scripts/generate-safe +const safePairs = specPairs.concat([ + [Ident, Hash], + + [Dimension, Hash], + + [Hash, Hash], + + [AtKeyword, LeftParenthesis], + [AtKeyword, StringToken], + [AtKeyword, Colon], + + [Percentage, Percentage], + [Percentage, Dimension], + [Percentage, FunctionToken], + [Percentage, '-'], + + [RightParenthesis, Ident], + [RightParenthesis, FunctionToken], + [RightParenthesis, Percentage], + [RightParenthesis, Dimension], + [RightParenthesis, Hash], + [RightParenthesis, '-'] +]); + +function createMap(pairs) { + const isWhiteSpaceRequired = new Set( + pairs.map(([prev, next]) => (code(prev) << 16 | code(next))) + ); + + return function(prevCode, type, value) { + const nextCode = code(type, value); + const nextCharCode = value.charCodeAt(0); + const emitWs = + (nextCharCode === HYPHENMINUS && + type !== Ident && + type !== FunctionToken && + type !== CDC) || + (nextCharCode === PLUSSIGN) + ? isWhiteSpaceRequired.has(prevCode << 16 | nextCharCode << 8) + : isWhiteSpaceRequired.has(prevCode << 16 | nextCode); + + if (emitWs) { + this.emit(' ', WhiteSpace, true); + } + + return nextCode; + }; +} + +export const spec = createMap(specPairs); +export const safe = createMap(safePairs); -- cgit v1.2.3