diff --git a/.gitattributes b/.gitattributes index 5fcf2092..12cf0de1 100644 --- a/.gitattributes +++ b/.gitattributes @@ -18,10 +18,12 @@ /.github/** linguist-vendored # exclude all files in test/ from stats /rollup.config.js linguist-vendored +/dist/** linguist-vendored /docs/** linguist-vendored /tools/** linguist-vendored /dist/** linguist-vendored /test/** linguist-vendored +/benchmark/** linguist-vendored /coverage/** linguist-vendored # # do not replace lf by crlf diff --git a/.gitignore b/.gitignore index 1631267c..091c51e3 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ /ROADMAP.draft.md /.idea /.DS_Store +/benchmark /ROADMAP.md /package-lock.json test/*.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 715aba87..cf61c73f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +# v1.5.0 + +## Improvements + +- [x] Add support for input sourcemap + # v1.4.11 - fix bug in url resolution diff --git a/README.md b/README.md index 65508945..ac470844 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,9 @@ # css-parser -CSS parser, transformer, minifier and validator for node and the browser +An all-in-one CSS parsing solution for Node.js and the browser, covering parsing, validation, transformation, minification, and AST-based tooling. + +The library always fully parses the stylesheet into a structured AST, and token values are exposed as typed data so custom transforms, plugins, and analysis can work with reliable, semantic input instead of raw strings. ## Installation @@ -23,9 +25,11 @@ $ deno add @tbela99/css-parser ## Features * **Zero dependencies** — lightweight and easy to integrate into any project. +* **All-in-one CSS parsing solution** covering parsing, validation, transformation, minification, and AST manipulation. * **Standards-based CSS validation** powered by MDN data. * **Full CSS Modules support** for modern component-based workflows. -* **Fault-tolerant parsing** that follows the CSS Syntax Module Level 3 specification. +* **Fault-tolerant parsing** that follows the CSS Syntax Module Level 3 specification and always produces a complete, structured parse result. +* **Typed tokens and AST** — parsed CSS is exposed as strongly typed tokens and nodes so plugins and transforms can operate on semantic structures. * **High-performance minification** with safe optimizations and no unsafe transforms. * **Advanced color processing** with support for modern color spaces and functions, including `color()`, `lab()`, `lch()`, `oklab()`, `oklch()`, `color-mix()`, `light-dark()`, system colors, and relative colors. * **Color conversion engine** capable of transforming colors between all supported formats. @@ -39,7 +43,7 @@ $ deno add @tbela99/css-parser * **`@import` flattening** to produce self-contained stylesheets. ## Vendor prefix removal -**Experimental vendor prefix cleanup** to modernize generated CSS. +**Vendor prefix cleanup** to modernize generated CSS. ## Syntax lowering CSS-Parser can transform these modern CSS features into lower-level CSS syntax: @@ -81,6 +85,8 @@ Try it [online](https://tbela99.github.io/css-parser/playground/) - [CSS Modules](https://tbela99.github.io/css-parser/docs/documents/Guide.CSS_Modules.html) - [Minification](https://tbela99.github.io/css-parser/docs/documents/Guide.Minification.html) - [Custom Transform](https://tbela99.github.io/css-parser/docs/documents/Guide.Custom_Transform.html) +- [Sourcemap](https://tbela99.github.io/css-parser/docs/documents/Guide.Sourcemap.html) +- [Plugins API](https://tbela99.github.io/css-parser/docs/documents/Guide.Plugins_API.html) - [Syntax Lowering](https://tbela99.github.io/css-parser/docs/documents/Guide.Syntax_Lowering.html) - [Ast Manipulation](https://tbela99.github.io/css-parser/docs/documents/Guide.Ast_Manipulation.html) - [Utility Functions](https://tbela99.github.io/css-parser/docs/documents/Guide.Utility_Functions.html) diff --git a/benchmark/package.json b/benchmark/package.json index 84d10d42..dafde208 100644 --- a/benchmark/package.json +++ b/benchmark/package.json @@ -10,11 +10,11 @@ "all": "npm run sizes && npm run bench && npm run report" }, "dependencies": { - "@tbela99/css-parser": "^1.4.9", - "@tbela99/css-parser2": "github:tbela99/css-parser#2279484", + "@tbela99/css-parser": "^1.4.11", + "@tbela99/css-parser2": "github:tbela99/css-parser#2628ebce", "clean-css": "^5.3.3", "css-tree": "^3.2.1", - "cssnano": "^8.0.5", + "cssnano": "^8.0.6", "csso": "^5.0.5", "esbuild": "^0.28.2", "lightningcss": "^1.33.0", diff --git a/dist/index-umd-web.js b/dist/index-umd-web.js index 1792fbfb..0fa56d1e 100644 --- a/dist/index-umd-web.js +++ b/dist/index-umd-web.js @@ -391,7 +391,7 @@ /** * keyframe rule node type */ - EnumToken[EnumToken["KeyFramesRuleNodeType"] = 73] = "KeyFramesRuleNodeType"; + EnumToken[EnumToken["KeyframesRuleNodeType"] = 73] = "KeyframesRuleNodeType"; /** * class selector token type */ @@ -9435,6 +9435,8 @@ * @param filter control the walk process * @param reverse walk in reverse order * + * @private + * * ```ts * * import {walk} from '@tbela99/css-parser'; @@ -9506,11 +9508,19 @@ const parents = [node]; const root = node; const map = new Map(); + let options = filter; let isNumeric = false; + let includeValues = false; let i = 0; + if (options != null && typeof options == "object") { + filter = options.filter; + reverse = options.reverse; + includeValues = options.inludeValues; + } while ((node = parents[i++])) { let option = null; if (filter != null) { + // @ts-ignore option = filter(node); isNumeric = typeof option == "number"; if (isNumeric) { @@ -9538,8 +9548,16 @@ }, }; } - if ("chi" in node && (!isNumeric || (option & exports.WalkerOptionEnum.IgnoreChildren) === 0)) { - parents.splice(i, 0, ...node.chi[reverse ? "toReversed" : "slice"]()); + if (includeValues) { + if (node[TOKENS] != null) { + parents.splice(i, 0, ...(reverse ? node[TOKENS].toReversed() : node[TOKENS])); + } + else if (Array.isArray(node.val)) { + parents.splice(i, 0, ...(reverse ? node.val.toReversed() : node.val)); + } + } + if (node["chi"] != null && (!isNumeric || (option & exports.WalkerOptionEnum.IgnoreChildren) === 0)) { + parents.splice(i, 0, ...(reverse ? node.chi.toReversed() : node.chi)); for (const child of node.chi) { map.set(child, node); } @@ -9623,11 +9641,6 @@ continue; } used.add(value); - // parents.length = 0; - // while (node != null) { - // parents.push(node); - // node = map.get(node) ?? null; - // } if (filter.fn != null && eventType & exports.WalkerEvent.Enter) { const isValid = filter.type == null || value.typ == filter.type || @@ -11683,14 +11696,14 @@ clone[name] = value; } else if (Array.isArray(value)) { - clone[name] = - !cloneChildren && name == checkNode - ? [] - : value.map((c) => { - const newObj = cloneNode(c, cloneChildren, cloneMap); - cloneMap?.set?.(c, newObj); - return newObj; - }); + clone[name] = []; + if (cloneChildren || name !== checkNode) { + for (const c of value) { + const newObj = cloneNode(c, cloneChildren, cloneMap); + cloneMap?.set?.(c, newObj); + clone[name].push(newObj); + } + } } else { clone[name] = { ...value }; @@ -11705,11 +11718,19 @@ const config$3 = getSyntaxConfig(); // @ts-expect-error const allValues = config$3.declarations.all.syntax.split(/[\s|]+/g); + /** + * @type {Array.} + */ const funcTypes = [ ...tokensfuncDefMap.values(), exports.EnumToken.FunctionTokenType, exports.EnumToken.PseudoClassFuncTokenType, ]; + /** + * trim leading and trailing whitespace + * @param tokens + * @returns + */ function trimArray(tokens) { while (tokens[0]?.typ === exports.EnumToken.WhitespaceTokenType) { tokens.shift(); @@ -11810,6 +11831,11 @@ success: true, }; } + /** + * create validation context + * @param tokens + * @returns + */ function createValidationContext(tokens) { tokens = trimArray(tokens.filter((t) => t.typ !== exports.EnumToken.CommentTokenType)); if (tokens.at(-1)?.typ === exports.EnumToken.ImportantTokenType) { @@ -11973,6 +11999,14 @@ }; return token; } + /** + * match selector syntax + * @param stream + * @param errors + * @param options + * @param nested + * @returns + */ function matchSelectorSyntax(stream, errors, options, nested = true) { const stack = []; const tokens = []; @@ -12411,6 +12445,13 @@ stream.push(...tokens); return { success, errors }; } + /** + * matches all syntaxes + * @param syntaxes + * @param context + * @param options + * @returns + */ function matchAllSyntaxes(syntaxes, context, options) { const result = matchSyntax(syntaxes, context, { ...options, @@ -12449,6 +12490,13 @@ syntaxToken: !result.success ? result.syntaxToken : null, }; } + /** + * matches a list of syntaxes + * @param syntax + * @param context + * @param options + * @returns + */ function matchListSyntax(syntax, context, options) { const { isList, match, isOptional, ...rest } = syntax; let success = true; @@ -12493,6 +12541,13 @@ token: context.peek(), }; } + /** + * matches a list of syntaxes + * @param syntax + * @param context + * @param options + * @returns + */ function matchOccurenceSyntax(syntax, context, options) { const { match, ...rest } = syntax; let result = null; @@ -12534,6 +12589,13 @@ } return result; } + /** + * matches a list of syntaxes + * @param syntaxes + * @param context + * @param options + * @returns + */ function matchSyntax(syntaxes, context, options) { if (syntaxes == null) { return { @@ -12666,7 +12728,7 @@ if (syntaxes[i].isList) { result = matchListSyntax(syntaxes[i], context.slice(), options); if (result.success) { - options.visited.get(token).delete(syntaxes[i]); + options.visited.get(token)?.delete?.(syntaxes[i]); if (result.context.done()) { context.end(); return { @@ -13174,6 +13236,13 @@ errors: [], }; } + /** + * matches a column of syntaxes + * @param syntax + * @param context + * @param options + * @returns + */ function matchColumnSyntax(syntax, context, options) { let syntaxes = syntax.chi.slice(); let i = 0; @@ -13210,6 +13279,13 @@ errors: [], }; } + /** + * matches an ampersand of syntaxes + * @param syntax + * @param context + * @param options + * @returns + */ function matchAmpersandSyntax(syntax, context, options) { const syntaxes = [syntax.l, syntax.r]; let result; @@ -13228,6 +13304,13 @@ } return result; } + /** + * matches a property + * @param property + * @param context + * @param options + * @returns + */ function matchProperty(property, context, options) { let success = false; let t = context.peek()?.typ; @@ -13960,6 +14043,13 @@ errors: [], }; } + /** + * matches a repeatable syntax + * @param syntax + * @param context + * @param options + * @returns + */ function matchRepeatableSyntax(syntax, context, options) { const { isRepeatable, isOptional, isMandatatoryGroup, isRepeatableAtLeastOnce, ...rest } = syntax; let result = null; @@ -18912,12 +19002,19 @@ return acc; }, []); let isImportant = false; - const filtered = values.map(removeDefaults).filter((x) => x.val.filter((t) => { - if (t.typ == exports.EnumToken.ImportantTokenType) { - isImportant = true; + let dec; + const filtered = []; + for (const declaration of values) { + dec = removeDefaults(declaration); + for (const t of dec.val) { + if (t.typ == exports.EnumToken.ImportantTokenType) { + isImportant = true; + } + if (filtered.length == 0 && t.typ != exports.EnumToken.WhitespaceTokenType && t.typ != exports.EnumToken.ImportantTokenType) { + filtered.push(dec); + } } - return ![exports.EnumToken.WhitespaceTokenType, exports.EnumToken.ImportantTokenType].includes(t.typ); - }).length > 0); + } if (filtered.length == 0 && this.config.default.length > 0) { filtered.push({ typ: exports.EnumToken.DeclarationNodeType, @@ -19635,7 +19732,7 @@ accept = new Set([ exports.EnumToken.RuleNodeType, exports.EnumToken.AtRuleNodeType, - exports.EnumToken.KeyFramesRuleNodeType, + exports.EnumToken.KeyframesRuleNodeType, ]); get ordering() { return 10; @@ -20976,7 +21073,7 @@ } class TransformCssFeature { - accept = new Set([exports.EnumToken.RuleNodeType, exports.EnumToken.KeyFramesRuleNodeType]); + accept = new Set([exports.EnumToken.RuleNodeType, exports.EnumToken.KeyframesRuleNodeType]); get ordering() { return 3; } @@ -21043,7 +21140,7 @@ } /** - * Search the ast sub-tree and return the first match + * Search the ast subtree and return the first match * * ```ts * // find the first ast declaration node which name is 'aspect-ratio' @@ -21082,6 +21179,11 @@ return null; } /** + * + * @param ast + * @param matcher + * @returns + * * Search the ast sub-tree by checking each node's value token and return the first match * ```ts @@ -21107,10 +21209,6 @@ console.log({node, value}); ``` - * - * @param ast - * @param matcher - * @returns */ function findByValue(ast, matcher) { let source; @@ -21382,151 +21480,460 @@ TransformCssFeature: TransformCssFeature }); + // from https://github.com/Rich-Harris/vlq/tree/master + // credit: Rich Harris + const integer_to_char = {}; + const char_to_integer = {}; + let i = 0; + for (const char of 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=') { + char_to_integer[char] = i; + integer_to_char[i++] = char; + } /** - * Compute line and column of the offset + * @param {string} str */ - class LineMap { - /** - * line starts - */ - lineStarts; - /** - * Constructor - * @param lines - */ - constructor(lines) { - if (lines.length === 0) { - lines.push(0); - } - this.lineStarts = lines; - } - /** - * Compute line and column of the offset - * @param offset - * @returns - */ - getOffsets(offset) { - const line = this.search(offset); - if (offset < 0 || line < 0) { - return [1, 1]; + function decode(str) { + /** @type {number[]} */ + let result = []; + let shift = 0; + let value = 0; + for (let i = 0; i < str.length; i += 1) { + let integer = char_to_integer[str[i]]; + if (integer === undefined) { + throw new Error('Invalid character (' + str[i] + ')'); + } + const has_continuation_bit = integer & 32; + integer &= 31; + value += integer << shift; + if (has_continuation_bit) { + shift += 5; } - const column = offset - this.lineStarts[line]; - // [line, column] - return [line + 1, column === 0 ? 1 : column]; - } - /** - * search the greatest index of the value less than or equal to offset - * @param offset - * @returns - */ - search(offset) { - // search lineStarts using binary search - let start = 0; - let end = this.lineStarts.length - 1; - let mid = 0; - let result = -1; - while (start <= end) { - mid = start + ((end - start) >>> 1); - if (this.lineStarts[mid] <= offset) { - result = mid; - start = mid + 1; + else { + const should_negate = value & 1; + value >>>= 1; + if (should_negate) { + result.push(value === 0 ? -2147483648 : -value); } - else if (this.lineStarts[mid] > offset) { - end = mid - 1; + else { + result.push(value); } + // reset + value = shift = 0; } - return result; } - /** - * get line starts - * @returns - */ - getLineStarts() { - return this.lineStarts; + return result; + } + /** + * + * @param value + * @returns + */ + function encode(value) { + if (typeof value === 'number') { + return encode_integer(value); } - /** - * add line start - */ - addLineStart(lineStart) { - this.lineStarts.push(lineStart); + let result = ''; + for (let i = 0; i < value.length; i += 1) { + result += encode_integer(value[i]); } - /** - * clone the linemap - * @returns - */ - clone() { - return new LineMap(this.lineStarts.slice()); + return result; + } + function encode_integer(num) { + let result = ''; + if (num < 0) { + num = (-num << 1) | 1; } + else { + num <<= 1; + } + do { + let clamped = num & 31; + num >>>= 5; + if (num > 0) { + clamped |= 32; + } + result += integer_to_char[clamped]; + } while (num > 0); + return result; } /** - * Source file ID - */ - let sourceId = 0; - /** - * Source file helper class + * Generate and parse source map */ - class SourceFile { + class SourceMap { /** - * Source file ID + * + * @private */ - id; + keys = new Set(); /** - * Source file path + * Last location */ - file; + lastLocation = null; /** - * Line map + * Version + * @private */ - lineStarts; + version = 3; /** - * Source file content + * Sources map + * @private */ - content; + sourcesMap = []; /** - * Constructor - * @param id - * @param content - * @param lines - * @param file + * Sources content + * @private */ - constructor(content, lines, file = null) { - this.id = sourceId++; - this.content = content; - this.file = file; - this.lineStarts = new LineMap(lines); - } + sourcesContent = []; /** - * Update source content - * @param content - * @param lines + * Sources + * @private */ - append(content) { - this.content += content; - } + sources = []; /** - * get file name - * @returns + * Map + * @private + * */ - getFileName() { - return this.file; - } + map = new Map(); /** - * get content - * @returns + * Map + * @private + * */ - getContent() { - return this.content; - } + reverseMap = new Map(); /** - * get text - * @param start - * @param length - * @returns + * Line + * @private */ - getText(start, length) { - return this.content.slice(start, start + length); - } + line = -1; + /** + * + * @param sourcemaps + * @private + */ + constructor(sourcemaps) { + if (typeof sourcemaps === "string") { + sourcemaps = JSON.parse(sourcemaps); + } + if (sourcemaps != null) { + this.sources = sourcemaps.sources?.slice() ?? []; + this.sourcesContent = sourcemaps.sourcesContent?.slice() ?? []; + const decodedMappings = sourcemaps.mappings + .split(";") + .map((mapping) => mapping.split(",").map((mapping) => decode(mapping))); + this.line = decodedMappings.length - 1; + for (let index = 0; index < decodedMappings.length; index++) { + if (decodedMappings[index].length == 0 || + (decodedMappings[index].length == 1 && decodedMappings[index][0].length == 0)) { + continue; + } + this.map.set(index, decodedMappings[index]); + } + this.computePositions(); + } + } + /** + * Add all location + * @param maps + */ + addAll(maps) { + for (let [newLine, newColumn, srcId, ln, col, sourceFileName, sourceContent] of maps) { + const key = `${srcId}:${ln}:${sourceFileName}:${col}:${newLine}:${newColumn}:${sourceContent}`; + const sourcemap = `${srcId}:${sourceFileName}:${sourceContent}`; + if (this.keys.has(key)) { + continue; + } + this.keys.add(key); + if (!this.sourcesMap.includes(sourcemap)) { + this.sourcesMap.push(sourcemap); + this.sources.push(sourceFileName || null); + this.sourcesContent.push((sourceFileName != null ? null : sourceContent) || null); + } + const line = newLine - 1; + let record; + if (line > this.line) { + this.line = line; + } + if (!this.map.has(line)) { + record = [Math.max(0, newColumn - 1), this.sourcesMap.indexOf(sourcemap), ln - 1, col - 1]; + this.map.set(line, [record]); + } + else { + const arr = this.map.get(line); + record = [ + Math.max(0, newColumn - 1) - arr[0][0], + this.sourcesMap.indexOf(sourcemap) - arr[0][1], + ln - 1, + col - 1, + ]; + arr.push(record); + } + if (this.lastLocation != null) { + record[2] -= this.lastLocation.ln - 1; + record[3] -= this.lastLocation.col - 1; + } + this.lastLocation ??= { ln, col }; + this.lastLocation.ln = ln; + this.lastLocation.col = col; + } + } + /** + * compute original positions + */ + computePositions() { + this.reverseMap.clear(); + let sourceFileIndex = 0; // second field + let sourceCodeLine = 0; // third field + let sourceCodeColumn = 0; // fourth field + let nameIndex = 0; // fifth field + let generatedCodeColumn; + let result; + // mappings to original source + for (let [i, line] of this.map.entries()) { + if (line.length === 0 || (line.length === 1 && line[0].length === 0)) { + continue; + } + generatedCodeColumn = line[0][0]; // first field - reset each time + line = line + .map((segment, index, array) => { + if (segment.length === 0) { + return []; + } + generatedCodeColumn = index == 0 ? segment[0] : segment[0] + array[0][0]; + result = [generatedCodeColumn]; + if (segment.length <= 1) { + return result; + } + sourceFileIndex = index == 0 ? segment[1] : segment[1] + array[0][1]; + sourceCodeLine += segment[2]; + sourceCodeColumn += segment[3]; + result.push(sourceFileIndex, sourceCodeLine, sourceCodeColumn); + if (segment.length === 5) { + nameIndex += segment[4]; + result.push(nameIndex); + } + return result; + }) + .sort((a, b) => { + if (a[1] !== b[1]) { + return a[1] - b[1]; + } + return a[0] - b[0]; + }); + if (line.length == 0 || (line.length == 1 && line[0].length == 0)) { + continue; + } + this.reverseMap.set(i, line); + } + } + /** + * retrieve original sources, lines and columns + * @param line generated line + * @param column generated column + */ + find(line, column) { + if (!this.reverseMap.has(--line)) { + return null; + } + column--; + const result = []; + for (const record of this.reverseMap.get(line)) { + if (record.length == 0 || record[0] < column) { + continue; + } + if (record[0] > column) { + break; + } + result.push([ + this.sources?.[record[1]] ?? null, + record[2] + 1, + record[3] + 1, + this.sourcesContent?.[record[1]] ?? null, + ]); + } + return result.length == 0 ? null : result; + } + /** + * Convert to URL encoded string + */ + toUrl() { + // /*# sourceMappingURL = ${url} */ + return `data:application/json;charset=utf-8;base64,${btoa(JSON.stringify(this.toJSON()))}`; + } + /** + * Convert to JSON object + */ + toJSON() { + const mappings = []; + let i = 0; + for (; i <= this.line; i++) { + if (!this.map.has(i)) { + mappings.push(""); + } + else { + mappings.push(this.map.get(i).reduce((acc, curr) => acc + (acc === "" ? "" : ",") + encode(curr), "")); + } + } + return { + version: this.version, + sources: this.sources.slice(), + sourcesContent: this.sourcesContent?.slice(), + mappings: mappings.join(";"), + }; + } + /** + * to string + */ + toString() { + return JSON.stringify(this); + } + } + + /** + * Compute line and column of the offset + */ + class LineMap { + /** + * line starts + */ + lineStarts; + /** + * Constructor + * @param lines + */ + constructor(lines = []) { + if (lines.length === 0) { + lines.push(0); + } + this.lineStarts = lines; + } + /** + * Compute line and column of the offset + * @param offset + * @returns + */ + getOffsets(offset) { + const line = this.search(offset); + if (offset < 0 || line < 0) { + return [1, 1]; + } + const column = offset - this.lineStarts[line] + 1; + // [line, column] + return [line + 1, column == 0 ? 1 : column]; + } + /** + * search the greatest index of the value less than or equal to offset + * @param offset + * @returns + */ + search(offset) { + // search lineStarts using binary search + let start = 0; + let end = this.lineStarts.length - 1; + let mid = 0; + let result = -1; + while (start <= end) { + mid = start + ((end - start) >>> 1); + if (this.lineStarts[mid] <= offset) { + result = mid; + start = mid + 1; + } + else if (this.lineStarts[mid] > offset) { + end = mid - 1; + } + } + return result; + } + /** + * get line starts + * @returns + */ + getLineStarts() { + return this.lineStarts; + } + /** + * add line start + */ + addLineStart(lineStart) { + this.lineStarts.push(lineStart); + } + /** + * clone the linemap + * @returns + */ + clone() { + return new LineMap(this.lineStarts.slice()); + } + } + + /** + * Source file ID + */ + let sourceId = 0; + /** + * Source file helper class + */ + class SourceFile { + inputSourceMap = null; + /** + * Source file ID + */ + id; + /** + * Source file path + */ + file; + /** + * Line map + */ + lineStarts; + /** + * Source file content + */ + content; + /** + * Constructor + * @param content + * @param lines + * @param file + */ + constructor(content, lines, file = null) { + this.id = sourceId++; + this.content = content; + this.file = file; + this.lineStarts = new LineMap(lines); + } + /** + * Update source content + * @param content + */ + append(content) { + this.content += content; + } + /** + * get file name + * @returns + */ + getFileName() { + return this.file; + } + /** + * get content + * @returns + */ + getContent() { + return this.content; + } + /** + * get text + * @param start + * @param length + * @returns + */ + getText(start, length) { + return this.content.slice(start, start + length); + } /** * Compute line and column of the offset * @param offset @@ -21557,6 +21964,20 @@ addLineStart(lineStart) { this.lineStarts.addLineStart(lineStart); } + /** + * set input source map + * @param inputSourceMap + */ + setInputSourceMap(inputSourceMap) { + this.inputSourceMap = inputSourceMap == null ? null : new SourceMap(inputSourceMap); + } + /** + * return input source map + * @returns + */ + getInputSourceMap() { + return this.inputSourceMap; + } } const SymbolsMapTokens = { @@ -21933,7 +22354,7 @@ return char; } /** - * Tokenize css string + * Tokenize CSS string * @param parseInfo * @param yieldEOFToken */ @@ -21960,8 +22381,6 @@ parseInfo.buffer = ""; while ((value = peek(parseInfo))) { charCode = value.charCodeAt(0); - // nextCharCode = nextValue.charCodeAt(0); - // console.debug({value, buffer}); switch (charCode) { case 61 /* TokenMap.EQUALS */: if (buffer.length > 0) { @@ -22326,10 +22745,6 @@ break; } buffer += value + next(parseInfo); - // buffer += - // (parseInfo.offset == parseInfo.currentPosition - // ? parseInfo.buffer.slice(-1) - // : parseInfo.stream.charAt(parseInfo.currentPosition - parseInfo.offset - 1)) + value; break; case 39 /* TokenMap.SINGLE_QUOTE */: case 34 /* TokenMap.DOUBLE_QUOTE */: @@ -22404,7 +22819,7 @@ exports.EnumToken.AtRuleNodeType, exports.EnumToken.RuleNodeType, exports.EnumToken.AtRuleTokenType, - exports.EnumToken.KeyFramesRuleNodeType, + exports.EnumToken.KeyframesRuleNodeType, ]; // @ts-ignore const features = Object.values(allFeatures).sort((a, b) => a.ordering - b.ordering); @@ -22416,6 +22831,7 @@ * @param errors * @param nestingContent * + * @param context * @private */ function minify(ast, options = {}, recursive = false, errors, nestingContent, context = {}) { @@ -22423,22 +22839,24 @@ let postprocess = false; let parents; let replacement; - if (!("features" in options)) { + // @ts-ignore + let { sourcemap, module, ...options2 } = options; + if (!("features" in options2)) { // @ts-ignore - options = { + options2 = { removeDuplicateDeclarations: true, computeShorthand: true, computeCalcExpression: true, removePrefix: false, features: [], - ...options, + ...options2, }; for (const feature of features) { - feature.register(options); + feature.register(options2); } - options.features.sort((a, b) => a.ordering - b.ordering); + options2.features.sort((a, b) => a.ordering - b.ordering); } - for (const feature of options.features) { + for (const feature of options2.features) { if (feature.processMode & exports.FeatureWalkMode.Pre) { preprocess = true; } @@ -22453,17 +22871,17 @@ continue; } replacement = parent; - for (const feature of options.features) { + for (const feature of options2.features) { if ((feature.processMode & exports.FeatureWalkMode.Pre) === 0 || (feature.accept != null && !feature.accept.has(parent.typ))) { continue; } if (rules.includes(replacement.typ) && !Array.isArray(replacement[TOKENS])) { - replacement[TOKENS] = parseString(replacement.typ == exports.EnumToken.RuleNodeType || replacement.typ === exports.EnumToken.KeyFramesRuleNodeType + replacement[TOKENS] = parseString(replacement.typ == exports.EnumToken.RuleNodeType || replacement.typ === exports.EnumToken.KeyframesRuleNodeType ? replacement.sel : replacement.nam); } - const result = feature.run(replacement, options, parent[PARENT] ?? ast, context, exports.FeatureWalkMode.Pre); + const result = feature.run(replacement, options2, parent[PARENT] ?? ast, context, exports.FeatureWalkMode.Pre); if (result != null) { replacement = result; } @@ -22482,14 +22900,14 @@ } } } - for (const feature of options.features) { + for (const feature of options2.features) { if (feature.processMode & exports.FeatureWalkMode.Pre && "cleanup" in feature) { // @ts-ignore - feature.cleanup(ast, options, context, exports.FeatureWalkMode.Pre); + feature.cleanup(ast, options2, context, exports.FeatureWalkMode.Pre); } } } - doMinify(ast, options, recursive, errors, nestingContent, context); + doMinify(ast, options2, recursive, errors, nestingContent, context); parents = new Set([ast]); for (const parent of parents) { if (parent.typ == exports.EnumToken.CommentTokenType || parent.typ == exports.EnumToken.CDOCOMMTokenType) { @@ -22497,12 +22915,12 @@ } replacement = parent; if (postprocess) { - for (const feature of options.features) { + for (const feature of options2.features) { if ((feature.processMode & exports.FeatureWalkMode.Post) === 0 || (feature.accept != null && !feature.accept.has(parent.typ))) { continue; } - const result = feature.run(replacement, options, parent[PARENT] ?? ast, context, exports.FeatureWalkMode.Post); + const result = feature.run(replacement, options2, parent[PARENT] ?? ast, context, exports.FeatureWalkMode.Post); if (result != null) { replacement = result; } @@ -22523,10 +22941,10 @@ } } if (postprocess) { - for (const feature of options.features) { + for (const feature of options2.features) { if (feature.processMode & exports.FeatureWalkMode.Post && "cleanup" in feature) { // @ts-ignore - feature.cleanup(ast, options, context, exports.FeatureWalkMode.Post); + feature.cleanup(ast, options2, context, exports.FeatureWalkMode.Post); } } } @@ -22619,9 +23037,9 @@ * Minify at-rule media * - remove redundant tokens * - generate range queries - * @param ast * * @private + * @param tokens */ function minifyAtRuleMedia(tokens) { let hasUpdates = false; @@ -22719,7 +23137,6 @@ } while (previous?.typ === exports.EnumToken.CommentNodeType) { previous = ast.chi[--nodeIndex]; - continue; } node = ast.chi[i]; if (node.typ === exports.EnumToken.AtRuleNodeType && node.nam === "font-face") { @@ -22735,8 +23152,8 @@ continue; } } - else if (node.typ === exports.EnumToken.KeyFramesRuleNodeType) { - if (previous?.typ === exports.EnumToken.KeyFramesRuleNodeType && + else if (node.typ === exports.EnumToken.KeyframesRuleNodeType) { + if (previous?.typ === exports.EnumToken.KeyframesRuleNodeType && node.sel === previous.sel) { // do not merge keyframes // https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/At-rules/@keyframes#resolving_duplicates @@ -23024,7 +23441,7 @@ } if (shouldMerge) { if (((node.typ === exports.EnumToken.RuleNodeType || - node.typ === exports.EnumToken.KeyFramesRuleNodeType) && + node.typ === exports.EnumToken.KeyframesRuleNodeType) && node.sel === previous.sel) || (node.typ == exports.EnumToken.AtRuleNodeType && node.nam !== "font-face" && @@ -23037,7 +23454,7 @@ continue; } else if (node.typ == previous?.typ && - [exports.EnumToken.KeyFramesRuleNodeType, exports.EnumToken.RuleNodeType].includes(node.typ)) { + [exports.EnumToken.KeyframesRuleNodeType, exports.EnumToken.RuleNodeType].includes(node.typ)) { const intersect = diff$1(previous, node, options); if (intersect != null) { if (intersect.node1.chi.length == 0) { @@ -23190,7 +23607,9 @@ } break; } - selector.forEach((selector) => selector.splice(0, optimized.length)); + for (let i1 = 0; i1 < selector.length; i1++) { + selector[i1].splice(0, optimized.length); + } let reducible = optimized.length == 1; if (optimized[0] == "&") { if (optimized[1] == " ") { @@ -23572,7 +23991,6 @@ * Diff nodes * @param n1 * @param n2 - * @param reducer * @param options * * @private @@ -23691,17 +24109,36 @@ // @ts-ignore chi: intersect.reverse(), }; + let op = { level: 0, ...options }; if (result == null || [n1, n2].reduce((acc, curr) => { let css = options.cache.get(curr); if (css == null) { - css = doRender(curr, options).code; + let level = 0; + let parent = curr[PARENT]; + while (parent != null && parent.typ != exports.EnumToken.StyleSheetNodeType) { + level++; + parent = parent[PARENT]; + } + op.level = level; + css = doRender(curr, op).code; options.cache.set(curr, css); } return curr.chi.length == 0 ? acc : acc + css.length; }, 0) <= [node1, node2, result].reduce((acc, curr) => { - const css = doRender(curr, options).code; + let css = options.cache.get(curr); + if (css != null) { + return curr.chi.length == 0 ? acc : acc + css.length; + } + let level = 0; + let parent = curr[PARENT]; + while (parent != null && parent.typ != exports.EnumToken.StyleSheetNodeType) { + level++; + parent = parent[PARENT]; + } + op.level = level; + css = doRender(curr, op).code; return curr.chi.length == 0 ? acc : acc + css.length; }, 0)) { if (node1.chi.length != 0 && node2.chi.length != 0) { @@ -23792,11 +24229,16 @@ */ function expand(ast) { const result = { ...ast, chi: [] }; + let children; for (let i = 0; i < ast.chi.length; i++) { - const node = ast.chi[i]; + let node = ast.chi[i]; if (node.typ === exports.EnumToken.RuleNodeType) { + children = expandRule(node); + for (const child of children) { + child[PARENT] = result; + } // @ts-ignore - result.chi.push(...expandRule(node)); + result.chi.push(...children); } else if (node.typ == exports.EnumToken.AtRuleNodeType && "chi" in node) { let hasRule = false; @@ -23808,10 +24250,23 @@ break; } } - // @ts-ignore - result.chi.push({ ...(hasRule ? expand(node) : node) }); + if (hasRule) { + node = expand(node); + for (const child of node.chi) { + child[PARENT] = result; + } + node[PARENT] = result; + // @ts-ignore + result.chi.push(node); + } + else { + node[PARENT] = result; + // @ts-ignore + result.chi.push(node); + } } else { + node[PARENT] = result; // @ts-ignore result.chi.push(node); } @@ -23836,7 +24291,10 @@ ast.chi.splice(i--, 1); continue; } - selRule.forEach((arr) => combinators.includes(arr[0].charAt(0)) ? arr.unshift(arSelf) : arr.unshift(arSelf, " ")); + for (let i1 = 0; i1 < selRule.length; i1++) { + const arr = selRule[i1]; + combinators.includes(arr[0].charAt(0)) ? arr.unshift(arSelf) : arr.unshift(arSelf, " "); + } rule.sel = selRule .reduce((acc, curr) => { acc.push(curr.join("")); @@ -24028,145 +24486,9 @@ .reduce((acc, curr) => acc + (curr == "&" ? replace : curr), ""); } - // from https://github.com/Rich-Harris/vlq/tree/master - // credit: Rich Harris - const integer_to_char = {}; - let i = 0; - for (const char of 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=') { - integer_to_char[i++] = char; - } - function encode(value) { - if (typeof value === 'number') { - return encode_integer(value); - } - let result = ''; - for (let i = 0; i < value.length; i += 1) { - result += encode_integer(value[i]); - } - return result; - } - function encode_integer(num) { - let result = ''; - if (num < 0) { - num = (-num << 1) | 1; - } - else { - num <<= 1; - } - do { - let clamped = num & 31; - num >>>= 5; - if (num > 0) { - clamped |= 32; - } - result += integer_to_char[clamped]; - } while (num > 0); - return result; - } - /** - * Source map class - * @internal + * match url */ - class SourceMap { - /** - * Last location - */ - lastLocation = null; - /** - * Version - * @private - */ - version = 3; - /** - * Sources map - * @private - */ - sourcesMap = []; - /** - * Sources - * @private - */ - sources = []; - /** - * Map - * @private - */ - map = new Map(); - /** - * Line - * @private - */ - line = -1; - /** - * Add a location - * @param source - * @param original - */ - add(newLine, newColumn, srcId, ln, col, sourceFileName, sourceContent) { - if (!this.sourcesMap.includes(srcId)) { - if (sourceFileName == null && sourceContent != null) { - sourceFileName = "data:text/css;charset=utf-8;base64," + btoa(sourceContent); - } - this.sourcesMap.push(srcId); - this.sources.push(sourceFileName || null); - } - const line = newLine - 1; - let record; - if (line > this.line) { - this.line = line; - } - if (!this.map.has(line)) { - record = [Math.max(0, newColumn - 1), this.sourcesMap.indexOf(srcId), ln - 1, col - 1]; - this.map.set(line, [record]); - } - else { - const arr = this.map.get(line); - record = [ - Math.max(0, newColumn - 1 - arr[0][0]), - this.sourcesMap.indexOf(srcId) - arr[0][1], - ln - 1, - col - 1, - ]; - arr.push(record); - } - if (this.lastLocation != null) { - record[2] -= this.lastLocation.ln - 1; - record[3] -= this.lastLocation.col - 1; - } - this.lastLocation ??= { ln, col }; - this.lastLocation.ln = ln; - this.lastLocation.col = col; - } - /** - * Convert to URL encoded string - */ - toUrl() { - // /*# sourceMappingURL = ${url} */ - return `data:application/json;charset=utf-8;base64,${btoa(JSON.stringify(this.toJSON()))}`; - } - /** - * Convert to JSON object - */ - toJSON() { - const mappings = []; - let i = 0; - for (; i <= this.line; i++) { - if (!this.map.has(i)) { - mappings.push(""); - } - else { - mappings.push(this.map.get(i).reduce((acc, curr) => acc + (acc === "" ? "" : ",") + encode(curr), "")); - } - } - return { - version: this.version, - sources: this.sources.slice(), - mappings: mappings.join(";"), - }; - } - } - const matchUrl = /^(https?:)?\/\//; /** * return the directory name of a path @@ -24178,6 +24500,9 @@ if (path === "") { return ""; } + if (path.startsWith("data:")) { + return path; + } let i = 0; let parts = [""]; for (; i < path.length; i++) { @@ -24201,10 +24526,7 @@ if (result.length == 0) { return { parts: [], i: 0 }; } - // if (result === "/") { - // return { parts: ["/"], i: 0 }; - // } - const parts = [""]; + const parts = result == "/" ? [] : [""]; let i = 0; for (; i < result.length; i++) { const chr = result.charAt(i); @@ -24213,7 +24535,7 @@ } // else if (chr == "?" || chr == "#") { // break; - // } + // } else { parts[parts.length - 1] += chr; } @@ -24231,6 +24553,8 @@ } /** * Nomalize path + * @param path + * @private */ const normalize = memoize(function (path) { let parts = []; @@ -24259,14 +24583,20 @@ while (++k < parts.length) { // if (parts[k] == ".") { // parts.splice(k--, 1); - // } else - if (parts[k] == "..") { + // } else + if (k > 0 && parts[k] == "..") { parts.splice(k - 1, 2); k -= 2; } } return (path.charAt(0) == "/" ? "/" : "") + parts.join("/"); }); + /** + * diff path + * @param path1 + * @param path2 + * @private + */ const diff = memoize(function (path1, path2) { let { parts } = splitPath(path1); const { parts: dirs } = splitPath(path2); @@ -24289,40 +24619,61 @@ * @private */ const resolve = memoize(function (url, currentDirectory, cwd) { - cwd ??= ""; - currentDirectory ??= ""; if (matchUrl.test(url)) { return { absolute: url, relative: url, }; } + cwd ??= ""; + currentDirectory ??= ""; url = normalize(url); + if (cwd !== "") { + cwd = normalize(cwd); + } if (currentDirectory !== "") { currentDirectory = normalize(currentDirectory); - if (url.startsWith(currentDirectory + "/")) { - return { - absolute: url, - relative: url.slice(currentDirectory.length + 1), - }; - } - } - if ((currentDirectory === "" || currentDirectory === ".") && cwd !== "") { - cwd = normalize(cwd); - if (url.startsWith(cwd == "/" ? cwd : cwd + "/")) { - const absolute = url; - const prefix = cwd == "/" ? cwd : cwd + "/"; - return { - absolute, - relative: absolute.startsWith(prefix) ? absolute.slice(prefix.length) : diff(absolute, cwd), - }; - } } + const dir = cwd || currentDirectory; + const absolute = dir == "" || url.startsWith("/") ? resolvePath(url) : resolvePath(dir, url); return { - absolute: url, - relative: url === "" ? "" : diff(url, cwd || currentDirectory), + absolute, + relative: dir === "" ? absolute : diff(absolute, dir), }; }); + /** + * + * @param parts + * @returns + * @private + */ + function resolvePath(...parts) { + const path = parts.filter(Boolean).join("/"); + const isAbsolute = /^[\\/]/.test(path); + const segments = path.split(/[\\/]+/); + const resolved = []; + for (const segment of segments) { + if (!segment || segment === ".") { + continue; + } + if (segment === "..") { + if (resolved.length && resolved[resolved.length - 1] !== "..") { + resolved.pop(); + } + else if (!isAbsolute) { + resolved.push(".."); + } + } + else { + resolved.push(segment); + } + } + let result = resolved.join("/"); + if (isAbsolute) { + result = "/" + result; + } + return result || (isAbsolute ? "/" : "."); + } /** * render ast @@ -24375,22 +24726,28 @@ const startTime = performance.now(); const errors = []; const sourcemap = options.sourcemap ? new SourceMap() : null; + const sourcemaps = options.sourcemap ? [] : null; const cache = Object.create(null); const sourceLocation = { - srcId: 0, - sta: 0, end: 0, }; - const linesMap = new LineMap([]); + const linesMap = options.sourcemap ? new LineMap() : null; let code = ""; if (mapping != null) { if (mapping.importMapping != null) { - for (const [key, value] of Object.entries(mapping.importMapping)) { + const absolutePath = options.resolve(options.output != null ? dirname(options.output) : dirname(options.src), options.cwd).absolute; + for (let [key, value] of Object.entries(mapping.importMapping)) { + key = options.resolve(options.resolve(key, options.cwd).absolute, absolutePath).relative; + if (!key.startsWith("/") && !key.startsWith(".")) { + key = "./" + key; + } code += `:import("${key}")${options.indent}{${options.newLine}${Object.entries(value).reduce((acc, [k, v]) => acc + (acc.length > 0 ? options.newLine : "") + `${options.indent}${v}:${options.indent}${k};`, "")}${options.newLine}}${options.newLine}`; } } code += `:export${options.indent}{${options.newLine}${Object.entries(mapping.mapping).reduce((acc, [k, v]) => acc + (acc.length > 0 ? options.newLine : "") + `${options.indent}${k}:${options.indent}${v};`, "")}${options.newLine}}${options.newLine}`; - move(sourceLocation, linesMap, code); + if (sourcemap != null) { + move(sourceLocation, linesMap, code); + } } if (options.output != null) { // @ts-ignore @@ -24402,7 +24759,7 @@ [exports.EnumToken.StyleSheetNodeType, exports.EnumToken.AtRuleNodeType, exports.EnumToken.RuleNodeType].includes(data.typ) && "chi" in data ? expand(data) - : data, options, sourcemap, sourceLocation, linesMap, errors, function reducer(acc, curr) { + : data, options, sourcemaps, sourceLocation, linesMap, errors, function reducer(acc, curr) { if (curr.typ == exports.EnumToken.CommentTokenType && options.removeComments) { if (!options.preserveLicense || !curr.val.startsWith("/*!")) { return acc; @@ -24417,6 +24774,7 @@ }, }; if (sourcemap != null) { + sourcemap.addAll(sourcemaps); result.map = sourcemap; if (options.sourcemap === "inline") { result.code += `\n/*# sourceMappingURL=${result.map.toUrl()} */`; @@ -24429,37 +24787,88 @@ * @param node * @param options * @param cache - * @param sourcemap - * @param position + * @param sourcemaps + * @param sourceLocation + * @param linesMap * @param str * * @internal */ - function updateSourceMap(node, options, cache, sourcemap, sourceLocation, linesMap, str) { - if ([ - exports.EnumToken.RuleNodeType, - exports.EnumToken.AtRuleNodeType, - exports.EnumToken.KeyFramesRuleNodeType, - exports.EnumToken.KeyframesAtRuleNodeType, - ].includes(node.typ)) { - let srcId = node[LOC]?.srcId ?? 0; - let sourceFileName = options.sourcesMap?.get(srcId)?.getFileName?.() || null; - if (sourceFileName != null && options.output != null) { - if (cache[sourceFileName] == null) { - cache[sourceFileName] = options.resolve(sourceFileName, dirname(options.output)).relative; + function updateSourceMap(node, options, cache, sourcemaps, sourceLocation, linesMap, str) { + let offset = 0; + while (true) { + if (str.charAt(offset) == options.newLine) { + offset += options.newLine.length; + continue; + } + if (str.charAt(offset) == options.indent) { + offset += options.indent.length; + continue; + } + break; + } + if (offset > 0) { + move(sourceLocation, linesMap, str.slice(0, offset)); + } + if (node[LOC] != null && + [ + exports.EnumToken.RuleNodeType, + exports.EnumToken.AtRuleNodeType, + exports.EnumToken.KeyframesRuleNodeType, + exports.EnumToken.KeyframesAtRuleNodeType, + ].includes(node.typ)) { + const source = options.sourcesMap.get(node[LOC].srcId); + const inputSourceMap = source.getInputSourceMap(); + const offsets = source.getOffsets(node[LOC].sta); + const [newLine, newColumn] = linesMap.getOffsets(sourceLocation.end); + let records = null; + let srcId = node[LOC].srcId; + let sourceFileName = source.getFileName() || null; + let sourceContent = source.getContent() || null; + if (inputSourceMap != null && (records = inputSourceMap.find(offsets[0], offsets[1])) != null) { + for (const record of records) { + // @ts-ignore + sourceFileName = record[0] || null; + // @ts-ignore + offsets[0] = record[1]; + // @ts-ignore + offsets[1] = record[2]; + sourceContent = record[3] || null; + if (sourceFileName != null && options.output != null && !sourceFileName.startsWith("data:")) { + if (cache[sourceFileName] == null) { + const absolute = options.resolve(dirname(options.output), options.cwd) + .absolute; + const absoluteSourcePath = options.resolve(dirname(options.src || ""), options.cwd).absolute; + // resolution is relative to the source file + const absoluteSourceFileName = options.resolve(sourceFileName, absoluteSourcePath) + .absolute; + cache[sourceFileName] = options.resolve(absoluteSourceFileName, absolute).relative; + } + sourceFileName = cache[sourceFileName]; + } + sourcemaps.push([newLine, newColumn, srcId, ...offsets, sourceFileName, sourceContent]); } - sourceFileName = cache[sourceFileName]; } - // @ts-ignore - sourcemap.add(...linesMap.getOffsets(sourceLocation.end), srcId, - // @ts-ignore - ...options.sourcesMap?.get(srcId)?.getOffsets(sourceLocation.sta), sourceFileName, options.sourcesMap?.get(srcId)?.getContent?.()); + else { + if (sourceFileName != null && options.output != null && !sourceFileName.startsWith("data:")) { + if (cache[sourceFileName] == null) { + const absolute = options.resolve(dirname(options.output), options.cwd) + .absolute; + const absoluteSourceFileName = options.resolve(sourceFileName, options.cwd) + .absolute; + cache[sourceFileName] = options.resolve(absoluteSourceFileName, absolute).relative; + } + sourceFileName = cache[sourceFileName]; + } + sourcemaps.push([newLine, newColumn, srcId, ...offsets, sourceFileName, sourceContent]); + } } - move(sourceLocation, linesMap, str); + move(sourceLocation, linesMap, offset > 0 ? str.slice(offset) : str); } /** * Update position - * @param position + * @param sourceLocation + * @param linesMap * @param str */ function move(sourceLocation, linesMap, str) { @@ -24489,8 +24898,9 @@ * render ast node * @param data * @param options - * @param sourcemap - * @param position + * @param sourcemaps + * @param sourceLocation + * @param linesMap * @param errors * @param reducer * @param cache @@ -24499,13 +24909,16 @@ * * @internal */ - function renderAstNode(data, options, sourcemap, sourceLocation, linesMap, errors, reducer, cache, level = 0, indents = []) { + function renderAstNode(data, options, sourcemaps, sourceLocation, linesMap, errors, reducer, cache, level = 0, indents = []) { if (indents.length < level + 1) { indents.push(options.indent.repeat(level)); } if (indents.length < level + 2) { indents.push(options.indent.repeat(level + 1)); } + // @ts-ignore + let children = ""; + let str = ""; const indent = indents[level]; const indentSub = indents[level + 1]; switch (data.typ) { @@ -24516,37 +24929,46 @@ case exports.EnumToken.CommentNodeType: case exports.EnumToken.CDOCOMMNodeType: if (data.val.startsWith("/*# sourceMappingURL=")) { - // ignore sourcemap + // ignore sourcemap comment return ""; } return !options.removeComments || (options.preserveLicense && data.val.startsWith("/*!")) ? data.val : ""; case exports.EnumToken.StyleSheetNodeType: - return data.chi.reduce((css, node) => { - const hasPreviousContent = css !== ""; - const str = renderAstNode(node, options, sourcemap, sourceLocation, linesMap, errors, reducer, cache, level, indents); + for (const node of data.chi) { + str = renderAstNode(node, options, sourcemaps, sourceLocation, linesMap, errors, reducer, cache, level, indents); if (str === "") { - return css; + continue; } - if (sourcemap != null && node[LOC] != null) { - updateSourceMap(node, options, cache, sourcemap, sourceLocation, linesMap, (hasPreviousContent ? options.newLine : "") + str); + if (children.length > 0) { + str = options.newLine + str; } - if (!hasPreviousContent) { - return str; + children += str; + if (sourcemaps != null && str !== "" && options.newLine) { + move(sourceLocation, linesMap, options.newLine); } - return `${css}${options.newLine}${str}`; - }, ""); + } + return children; case exports.EnumToken.AtRuleNodeType: case exports.EnumToken.RuleNodeType: - case exports.EnumToken.KeyFramesRuleNodeType: + case exports.EnumToken.KeyframesRuleNodeType: case exports.EnumToken.KeyframesAtRuleNodeType: if ([exports.EnumToken.AtRuleNodeType, exports.EnumToken.KeyframesAtRuleNodeType].includes(data.typ) && !("chi" in data)) { return `${indent}@${data.nam}${data.val === "" ? "" : options.indent || " "}${data.val};`; } - // @ts-ignore - let children = data.chi.reduce((css, node) => { - let str; + const prelude = (indent.length > 0 ? options.newLine : "") + + indent + + ([exports.EnumToken.AtRuleNodeType, exports.EnumToken.KeyframesAtRuleNodeType].includes(data.typ) + ? `@${data.nam}${data.val === "" ? "" : options.indent || " "}${data.val}${options.indent}{` + : data.sel + `${options.indent}{`); + if (sourcemaps != null) { + updateSourceMap(data, options, cache, sourcemaps, sourceLocation, linesMap, prelude); + } + let node; + let recordDeclarationSourceMap = data.typ == exports.EnumToken.AtRuleNodeType; + for (let i = 0; i < data.chi.length; i++) { + node = data.chi[i]; if (node.typ == exports.EnumToken.CommentNodeType) { str = options.removeComments && @@ -24555,73 +24977,59 @@ : node.val; } else if (node.typ == exports.EnumToken.DeclarationNodeType) { - // if (!(node).nam.startsWith("--") && (node).val.length === 0) { - // // @ts-ignore - // errors.push({ - // action: "ignore", - // message: `render: invalid declaration ${JSON.stringify(node)}`, - // location: node[LOC], - // }); - // return ""; - // } str = `${node.nam}:${options.indent}${(options.minify ? filterValues(node.val) : node.val) .reduce(reducer, "") .trimEnd()};`; } - // else if (node.typ == EnumToken.AtRuleNodeType && !("chi" in node)) { - // str = `${(node).val === "" ? "" : options.indent || " "}${(node).val};`; - // } else { - str = renderAstNode(node, options, sourcemap, sourceLocation, linesMap, errors, reducer, cache, level + 1, indents); - } - if (css === "") { - return str; + str = renderAstNode(node, options, sourcemaps, sourceLocation, linesMap, errors, reducer, cache, level + 1, indents); + if (str === "") { + continue; + } + children += str; + str = ""; + continue; } if (str === "") { - return css; + continue; + } + str = options.newLine + indentSub + str; + children += str; + if (sourcemaps != null && str !== "") { + move(sourceLocation, linesMap, str); + if (node.typ == exports.EnumToken.DeclarationNodeType && recordDeclarationSourceMap) { + // if declaration is child of at-rule, then record it + // .rule { + // @media screen { + // color: red; + // } + // } + const source = options.sourcesMap.get(node[LOC].srcId); + sourcemaps.push([ + ...linesMap.getOffsets(sourceLocation.end - str.length + options.newLine.length + indentSub.length), + node[LOC].srcId, + ...source.getOffsets(node[LOC].sta), + source.getFileName(), + source.getContent(), + ]); + } } - return `${css}${options.newLine}${indentSub}${str}`; - }, ""); - if (options.removeEmpty && children === "") { - return ""; } if (children.endsWith(";")) { children = children.slice(0, -1); + sourceLocation.end--; } - const rendered = [exports.EnumToken.AtRuleNodeType, exports.EnumToken.KeyframesAtRuleNodeType].includes(data.typ) - ? `@${data.nam}${data.val === "" ? "" : options.indent || " "}${data.val}${options.indent}{${options.newLine}` + - (children === "" ? "" : indentSub + children + options.newLine) + - indent + - `}` - : data.sel + - `${options.indent}{${options.newLine}` + - (children === "" ? "" : indentSub + children + options.newLine) + - indent + - `}`; - if (sourcemap != null && data[LOC] != null) { - updateSourceMap(data, options, cache, sourcemap, { ...sourceLocation }, linesMap.clone(), rendered); - } - return rendered; - // case EnumToken.CssVariableTokenType: - // case EnumToken.CssVariableImportTokenType: - // return `@value ${(data).val}:${options.indent}${filterValues( - // options.minify - // ? (data).val - // : (data).val, - // ) - // .reduce(reducer, "") - // .trim()};`; - // case EnumToken.CssVariableDeclarationMapTokenType: - // return `@value ${filterValues((data as CssVariableMapTokenType).vars) - // .reduce((acc, curr) => acc + renderValue(curr), "") - // .trim()} from ${filterValues((data as CssVariableMapTokenType).from) - // .reduce((acc, curr) => acc + renderValue(curr), "") - // .trim()};`; - // case EnumToken.InvalidDeclarationNodeType: - // case EnumToken.InvalidRuleNodeType: - // case EnumToken.InvalidAtRuleNodeType: + if (options.removeEmpty && children === "") { + sourceLocation.end -= prelude.length; + return ""; + } + const end = options.newLine + indent + `}`; + if (sourcemaps != null) { + move(sourceLocation, linesMap, end); + } + return prelude + children + end; default: return ""; } @@ -24630,6 +25038,9 @@ * render ast token * @param token * @param options + * @param cache + * @param reducer + * @param errors * @private */ function renderValue(token, options = {}, cache = Object.create(null), reducer, errors) { @@ -25622,7 +26033,7 @@ return acc; }, [])); return { - typ: exports.EnumToken.KeyFramesRuleNodeType, + typ: exports.EnumToken.KeyframesRuleNodeType, sel: [ ...splitTokenList(trimArray(tokens)).reduce((acc, curr) => { acc.add(curr.reduce((acc, curr) => acc + renderValue(curr, { minify: false }), "")); @@ -28314,6 +28725,134 @@ // if leading char is digit, prefix underscore (very rare) return (/^[0-9]/.test(result) ? "_" : "") + result; }); + /** + * + * @param visitorsDef + * @param errors + * @private + */ + function parseVisitors(visitorsDef, errors) { + const visitors = Object.entries(typeof visitorsDef === "function" ? [visitorsDef] : visitorsDef); + let key; + let value; + let i; + const valuesHandlers = new Map(); + const preValuesHandlers = new Map(); + const postValuesHandlers = new Map(); + const visitorsHandlersMap = new Map(); + const preVisitorsHandlersMap = new Map(); + const postVisitorsHandlersMap = new Map(); + for (i = 0; i < visitors.length; i++) { + key = visitors[i][0]; + value = visitors[i][1]; + if (Number.isInteger(+key)) { + if (Array.isArray(value)) { + visitors.splice(i + 1, 0, ...Object.entries(value)); + continue; + } + if (typeof value == "function") { + key = value.name; + } + } + if (Array.isArray(value)) { + // @ts-ignore + visitors.splice(i + 1, 0, ...value.map((item) => [key, item])); + continue; + } + if (key in exports.EnumToken) { + if (typeof value == "function") { + if (!valuesHandlers.has(exports.EnumToken[key])) { + valuesHandlers.set(exports.EnumToken[key], []); + } + valuesHandlers.get(exports.EnumToken[key]).push(value); + } + else if (typeof value == "object" && "type" in value && "handler" in value && value.type in exports.WalkerEvent) { + if (value.type == exports.WalkerEvent.Enter) { + if (!preValuesHandlers.has(exports.EnumToken[key])) { + preValuesHandlers.set(exports.EnumToken[key], []); + } + preValuesHandlers.get(exports.EnumToken[key]).push(value.handler); + } + else if (value.type == exports.WalkerEvent.Leave) { + if (!postValuesHandlers.has(exports.EnumToken[key])) { + postValuesHandlers.set(exports.EnumToken[key], []); + } + postValuesHandlers.get(exports.EnumToken[key]).push(value.handler); + } + } + else { + errors.push({ action: "ignore", message: `doParse: visitor.${key} is not a valid key name` }); + } + } + else if (["Declaration", "Rule", "AtRule", "KeyframesRule", "KeyframesAtRule"].includes(key)) { + if (typeof value == "function") { + if (!visitorsHandlersMap.has(key)) { + visitorsHandlersMap.set(key, []); + } + visitorsHandlersMap + .get(key) + .push(value); + } + else if (typeof value == "object") { + if ("type" in value && "handler" in value && value.type in exports.WalkerEvent) { + if (value.type == exports.WalkerEvent.Enter) { + if (!preVisitorsHandlersMap.has(key)) { + preVisitorsHandlersMap.set(key, []); + } + preVisitorsHandlersMap + .get(key) + .push(value.handler); + } + else if (value.type == exports.WalkerEvent.Leave) { + if (!postVisitorsHandlersMap.has(key)) { + postVisitorsHandlersMap.set(key, []); + } + postVisitorsHandlersMap + .get(key) + .push(value.handler); + } + } + else { + if (!visitorsHandlersMap.has(key)) { + visitorsHandlersMap.set(key, []); + } + visitorsHandlersMap + .get(key) + .push(value); + } + } + else { + errors.push({ action: "ignore", message: `doParse: visitor.${key} is not a valid key name` }); + } + } + else { + errors.push({ action: "ignore", message: `doParse: visitor.${key} is not a valid key name` }); + } + } + const allHandlers = []; + if (preVisitorsHandlersMap.size > 0) { + allHandlers.push(preVisitorsHandlersMap); + } + if (preValuesHandlers.size > 0) { + allHandlers.push(preValuesHandlers); + } + if (visitorsHandlersMap.size > 0) { + allHandlers.push(visitorsHandlersMap); + } + if (valuesHandlers.size > 0) { + allHandlers.push(valuesHandlers); + } + if (postVisitorsHandlersMap.size > 0) { + allHandlers.push(postVisitorsHandlersMap); + } + if (postValuesHandlers.size > 0) { + allHandlers.push(postValuesHandlers); + } + return { + allHandlers, + includeTokens: preValuesHandlers.size > 0 || valuesHandlers.size > 0 || postValuesHandlers.size > 0, + }; + } /** * Parse css string * @param iter @@ -28379,131 +28918,24 @@ }; let tokens = []; let context = ast; - ast[ROOT] = ast; - ast[LOC] = { - sta: 0, - end: 0, - srcId: options.source.id, - }; - let valuesHandlers; - let preValuesHandlers; - let postValuesHandlers; - let preVisitorsHandlersMap; - let visitorsHandlersMap; - let postVisitorsHandlersMap; let item; let node; // @ts-ignore ignore error let parensMatch = 0; let curlyBracketMatch = 0; - if (options.visitor != null) { - valuesHandlers = new Map(); - preValuesHandlers = new Map(); - postValuesHandlers = new Map(); - preVisitorsHandlersMap = new Map(); - visitorsHandlersMap = new Map(); - postVisitorsHandlersMap = new Map(); - const visitors = Object.entries(options.visitor); - let key; - let value; - let i; - for (i = 0; i < visitors.length; i++) { - key = visitors[i][0]; - value = visitors[i][1]; - if (Number.isInteger(+key)) { - visitors.splice(i + 1, 0, ...Object.entries(value)); - continue; - } - if (Array.isArray(value)) { - // @ts-ignore - visitors.splice(i + 1, 0, ...value.map((item) => [key, item])); - continue; - } - if (key in exports.EnumToken) { - if (typeof value == "function") { - if (!valuesHandlers.has(exports.EnumToken[key])) { - valuesHandlers.set(exports.EnumToken[key], []); - } - valuesHandlers.get(exports.EnumToken[key]).push(value); - } - else if (typeof value == "object" && - "type" in value && - "handler" in value && - value.type in exports.WalkerEvent) { - if (value.type == exports.WalkerEvent.Enter) { - if (!preValuesHandlers.has(exports.EnumToken[key])) { - preValuesHandlers.set(exports.EnumToken[key], []); - } - preValuesHandlers - .get(exports.EnumToken[key]) - .push(value.handler); - } - else if (value.type == exports.WalkerEvent.Leave) { - if (!postValuesHandlers.has(exports.EnumToken[key])) { - postValuesHandlers.set(exports.EnumToken[key], []); - } - postValuesHandlers - .get(exports.EnumToken[key]) - .push(value.handler); - } - } - else { - errors.push({ action: "ignore", message: `doParse: visitor.${key} is not a valid key name` }); - } - } - else if (["Declaration", "Rule", "AtRule", "KeyframesRule", "KeyframesAtRule"].includes(key)) { - if (typeof value == "function") { - if (!visitorsHandlersMap.has(key)) { - visitorsHandlersMap.set(key, []); - } - visitorsHandlersMap - .get(key) - .push(value); - } - else if (typeof value == "object") { - if ("type" in value && "handler" in value && value.type in exports.WalkerEvent) { - if (value.type == exports.WalkerEvent.Enter) { - if (!preVisitorsHandlersMap.has(key)) { - preVisitorsHandlersMap.set(key, []); - } - preVisitorsHandlersMap - .get(key) - .push(value.handler); - } - else if (value.type == exports.WalkerEvent.Leave) { - if (!postVisitorsHandlersMap.has(key)) { - postVisitorsHandlersMap.set(key, []); - } - postVisitorsHandlersMap - .get(key) - .push(value.handler); - } - } - else { - if (!visitorsHandlersMap.has(key)) { - visitorsHandlersMap.set(key, []); - } - visitorsHandlersMap - .get(key) - .push(value); - } - } - else { - errors.push({ action: "ignore", message: `doParse: visitor.${key} is not a valid key name` }); - } - } - else { - errors.push({ action: "ignore", message: `doParse: visitor.${key} is not a valid key name` }); - } - } - } - if (Array.isArray(iter)) { - // @ts-expect-error - iter = iter[Symbol.iterator](); - } - while ( - // @ts-expect-error - (item = iter.next().value)) { + let currentItemIndex; + // ast[ROOT] = ast; + ast[LOC] = { + sta: 0, + end: 0, + srcId: options.source.id, + }; + // if (Array.isArray(iter)) { + // // @ts-expect-error + // iter = iter[Symbol.iterator]() as Iterator; + // } + for (currentItemIndex = 0; currentItemIndex < iter.length; currentItemIndex++) { + item = iter[currentItemIndex]; stats.bytesIn = item.bytesIn; stats.tokensCount++; if (BadTokensTypes.includes(item.token.typ)) { @@ -28531,8 +28963,6 @@ curlyBracketMatch--; } tokens.push(item.token); - // console.debug([item.token, {parensMatch, curlyBracketMatch}]); - // if (parensMatch === 0) { if (parensMatch === 0 && (item.token.typ === exports.EnumToken.SemiColonTokenType || item.token.typ === exports.EnumToken.BlockStartTokenType || @@ -28548,8 +28978,7 @@ let inBlock = 1; tokens = [item.token]; do { - // @ts-expect-error - item = iter.next().value; + item = iter[++currentItemIndex]; if (item == null) { break; } @@ -28604,198 +29033,160 @@ ast = expand(ast); } let replacement; - let callable; if (options.visitor != null) { + const handlers = []; + const visitors = parseVisitors(options.visitor, errors); + const subNodes = []; let parens; - for (const result of walk(ast)) { + let genericKey; + let nodes = new Array(stats.tokensCount); + let i; + let k; + let j; + let freeBlock = 1; + nodes[0] = ast; + for (i = 0; i < nodes.length; i++) { + if (nodes[i] == null) { + break; + } + subNodes.length = 0; + if (visitors.includeTokens) { + switch (nodes[i].typ) { + case exports.EnumToken.RuleNodeType: + case exports.EnumToken.AtRuleNodeType: + case exports.EnumToken.KeyframesRuleNodeType: + case exports.EnumToken.KeyframesAtRuleNodeType: + subNodes.push(...nodes[i][TOKENS]); + break; + case exports.EnumToken.DeclarationNodeType: + subNodes.push(...nodes[i].val); + break; + } + } + if (nodes[i].chi != null) { + subNodes.push(...nodes[i].chi); + } + if (subNodes.length > 0) { + if (freeBlock <= i) { + freeBlock = i + 1; + } + for (k = 0; k < subNodes.length; k++) { + j = k + freeBlock; + nodes[j] = subNodes[k]; + nodes[j][PARENT] = nodes[i]; + } + freeBlock += subNodes.length; + } parens = null; - if (valuesHandlers.size > 0 || - preVisitorsHandlersMap.size > 0 || - visitorsHandlersMap.size > 0 || - postVisitorsHandlersMap.size > 0) { - if ((result.node.typ == exports.EnumToken.DeclarationNodeType && - (preVisitorsHandlersMap.has("Declaration") || - visitorsHandlersMap.has("Declaration") || - postVisitorsHandlersMap.has("Declaration"))) || - (result.node.typ == exports.EnumToken.AtRuleNodeType && - (preVisitorsHandlersMap.has("AtRule") || - visitorsHandlersMap.has("AtRule") || - postVisitorsHandlersMap.has("AtRule"))) || - (result.node.typ == exports.EnumToken.KeyframesAtRuleNodeType && - (preVisitorsHandlersMap.has("KeyframesAtRule") || - visitorsHandlersMap.has("KeyframesAtRule") || - postVisitorsHandlersMap.has("KeyframesAtRule")))) { - const handlers = []; - const key = result.node.typ == exports.EnumToken.DeclarationNodeType - ? "Declaration" - : result.node.typ == exports.EnumToken.AtRuleNodeType - ? "AtRule" - : "KeyframesAtRule"; - if (preVisitorsHandlersMap.has(key)) { - handlers.push( - // @ts-expect-error - ...preVisitorsHandlersMap.get(key)); - } - if (visitorsHandlersMap.has(key)) { - // @ts-ignore - handlers.push(...visitorsHandlersMap.get(key)); - } - if (postVisitorsHandlersMap.has(key)) { - // @ts-ignore - handlers.push(...postVisitorsHandlersMap.get(key)); - } - let node = result.node; - for (const handler of handlers) { - callable = - typeof handler == "function" - ? handler - : handler[camelize(node.typ === exports.EnumToken.DeclarationNodeType || - node.typ === exports.EnumToken.AtRuleNodeType - ? node.nam - : node.val)]; - if (callable == null) { - continue; + handlers.length = 0; + genericKey = + nodes[i].typ == exports.EnumToken.DeclarationNodeType + ? "Declaration" + : nodes[i].typ == exports.EnumToken.AtRuleNodeType + ? "AtRule" + : nodes[i].typ == exports.EnumToken.KeyframesAtRuleNodeType + ? "KeyframesAtRule" + : nodes[i].typ === exports.EnumToken.KeyframesRuleNodeType + ? "KeyframesRule" + : nodes[i].typ == exports.EnumToken.RuleNodeType + ? "Rule" + : nodes[i].typ == exports.EnumToken.KeyframesRuleNodeType + ? "KeyframesRule" + : null; + let keyName = nodes[i].typ == exports.EnumToken.DeclarationNodeType || nodes[i].typ == exports.EnumToken.AtRuleNodeType + ? camelize(nodes[i].nam) + : nodes[i].typ == exports.EnumToken.KeyframesAtRuleNodeType + ? camelize(nodes[i].val) + : null; + for (const map of visitors.allHandlers) { + // @ts-ignore + if (genericKey != null && map.has(genericKey)) { + // @ts-ignore + for (const handler of map.get(genericKey)) { + if (typeof handler == "function") { + handlers.push(handler); } - // @ts-expect-error - replacement = callable(node, result[PARENT], ast, function* () { - if (parens == null) { - // @ts-expect-error - parens = [...result.parents()]; + else if (Array.isArray(handler)) { + for (const h of handler) { + if (typeof h == "function") { + handlers.push(h); + } + // @ts-ignore + else if (h[keyName] != null) { + // @ts-ignore + handlers.push(h[keyName]); + } } - yield* parens[Symbol.iterator](); - }); - if (replacement == null) { - continue; } - if (replacement == null || replacement == node) { - continue; + else if (typeof handler.handler == "function") { + handlers.push(handler.handler); } // @ts-ignore - node = replacement; - if (Array.isArray(node)) { - break; + else if (typeof handler[keyName] == "function") { + // @ts-ignore + handlers.push(handler[keyName]); } } - if (node != result.node) { - replaceNodeOrValue(result.parent, result.node, node); - } } - else if ((result.node.typ == exports.EnumToken.RuleNodeType && - (preVisitorsHandlersMap.has("Rule") || - visitorsHandlersMap.has("Rule") || - postVisitorsHandlersMap.has("Rule"))) || - (result.node.typ == exports.EnumToken.KeyFramesRuleNodeType && - (preVisitorsHandlersMap.has("KeyframesRule") || - visitorsHandlersMap.has("KeyframesRule") || - postVisitorsHandlersMap.has("KeyframesRule")))) { - const handlers = []; - const key = result.node.typ == exports.EnumToken.RuleNodeType ? "Rule" : "KeyframesRule"; - if (preVisitorsHandlersMap.has(key)) { - handlers.push(...preVisitorsHandlersMap.get(key)); - } - if (visitorsHandlersMap.has(key)) { - handlers.push(...visitorsHandlersMap.get(key)); - } - if (postVisitorsHandlersMap.has(key)) { - handlers.push(...postVisitorsHandlersMap.get(key)); - } - let node = result.node; - for (const callable of handlers) { - replacement = callable(node, result.parent, result.root, - // @ts-expect-error - function* () { - if (parens == null) { - // @ts-expect-error - parens = [...result.parents()]; - } - yield* parens[Symbol.iterator](); - }); - if (replacement == null) { - continue; - } - if (replacement == null || replacement == node) { - continue; - } - // @ts-ignore - node = replacement; - // - if (Array.isArray(node)) { - break; - } - } + // @ts-ignore + if (map.has(nodes[i].typ)) { // @ts-ignore - if (node != result.node) { - // @ts-ignore - replaceNodeOrValue(result.parent, result.node, node); - } - } - else if (valuesHandlers.size > 0) { - let node = null; - node = result.node; - if (valuesHandlers.has(node.typ)) { - for (const valueHandler of valuesHandlers.get(node.typ)) { - callable = valueHandler; - replacement = callable(node, result.parent, ast, - // @ts-expect-error - function* () { - if (parens == null) { - // @ts-expect-error - parens = [...result.parents()]; - } - yield* parens[Symbol.iterator](); - }); - if (replacement == null) { - continue; - } - if (replacement != null && replacement != node) { - node = replacement; - } - } - } - if (node != result.node) { - // @ts-ignore - replaceNodeOrValue(result[PARENT], value, node); - } - const tokens = Array.isArray(result.node[TOKENS]) ? result.node[TOKENS] : []; - if (Array.isArray(result.node.val)) { - tokens.push(...result.node.val); - } - if (tokens.length == 0) { - continue; - } - for (const { value, parent, root, parents } of walkValues(tokens, result.node)) { - node = value; - if (valuesHandlers.has(node.typ)) { - let parens = null; - for (const valueHandler of valuesHandlers.get(node.typ)) { - callable = valueHandler; - // @ts-expect-error - let result = callable(node, parent, root, function* () { - if (parens == null) { - // @ts-expect-error - parens = [...parents()]; - } - yield* parens[Symbol.iterator](); - }); - if (result == null) { - continue; - } - if (result != null && result != node) { - node = result; - } - if (Array.isArray(node)) { - break; + for (const handler of map.get(nodes[i].typ)) { + if (typeof handler == "function") { + handlers.push(handler); + } + else if (Array.isArray(handler)) { + for (const h of handler) { + if (typeof h == "function") { + handlers.push(h); } } } - if (node != value) { + else if (typeof handler.handler == "function") { + handlers.push(handler.handler); + } + // @ts-ignore + else if (typeof handler[keyName] == "function") { // @ts-ignore - replaceNodeOrValue(parent, value, node); + handlers.push(handler[keyName]); + } + } + } + } + if (handlers.length == 0) { + continue; + } + let node = nodes[i]; + for (const callable of handlers) { + replacement = callable(node, nodes[i][PARENT], ast, + // @ts-expect-error + function* () { + if (parens == null) { + let node = nodes[i][PARENT]; + while (node != null) { + yield node; + node = node[PARENT]; } } + }); + if (replacement == null) { + continue; + } + if (replacement == node) { + continue; + } + // @ts-ignore + node = replacement; + // + if (Array.isArray(node)) { + break; } } + if (node != nodes[i]) { + replaceNodeOrValue(nodes[i][PARENT], nodes[i], node); + } } + nodes = null; } if (invalidNodes.length > 0) { let count = invalidNodes.length; @@ -28818,19 +29209,6 @@ } } } - while (stack.length > 0 && context != ast) { - const previousNode = stack.pop(); - context = (stack[stack.length - 1] ?? ast); - // remove empty nodes - if (options.removeEmpty && - previousNode != null && - previousNode.chi.length == 0 && - context.chi[context.chi.length - 1] == previousNode) { - context.chi.pop(); - continue; - } - break; - } if (options.minify) { if (ast.chi.length > 0) { let passes = options.pass ?? 1; @@ -28859,7 +29237,7 @@ scoped: exports.ModuleScopeEnumOptions.Local, naming: exports.ModuleCaseTransformEnum.IgnoreCase, pattern: "", - generateScopedName, + generateScopedName: generateSyncScopedName, ...(typeof options.module != "object" ? {} : options.module), }; const parseModuleTime = performance.now(); @@ -28965,10 +29343,9 @@ if (node.typ == exports.EnumToken.DeclarationNodeType) { if (node.nam.startsWith("--")) { if (!(node.nam in namesMapping)) { - let result = moduleSettings.scoped & exports.ModuleScopeEnumOptions.Global + let value = moduleSettings.scoped & exports.ModuleScopeEnumOptions.Global ? node.nam : moduleSettings.generateScopedName(node.nam, moduleSettings.filePath, moduleSettings.pattern, moduleSettings.hashLength); - let value = result; mapping[node.nam] = "--" + (moduleSettings.naming & exports.ModuleCaseTransformEnum.DashCaseOnly || @@ -29010,10 +29387,9 @@ continue; } if (!(rule.val in mapping)) { - let result = moduleSettings.scoped & exports.ModuleScopeEnumOptions.Global + let value = moduleSettings.scoped & exports.ModuleScopeEnumOptions.Global ? rule.val : moduleSettings.generateScopedName(rule.val, moduleSettings.filePath, moduleSettings.pattern, moduleSettings.hashLength); - let value = result; mapping[rule.val] = (rule.typ == exports.EnumToken.DashedIdenTokenType ? "--" : "") + (moduleSettings.naming & exports.ModuleCaseTransformEnum.DashCaseOnly || @@ -29184,10 +29560,10 @@ "unset", ].includes(value.val)) { if (!(value.val in mapping)) { - const result = moduleSettings.scoped & exports.ModuleScopeEnumOptions.Global - ? value.val - : moduleSettings.generateScopedName(value.val, moduleSettings.filePath, moduleSettings.pattern, moduleSettings.hashLength); - mapping[value.val] = result; + mapping[value.val] = + moduleSettings.scoped & exports.ModuleScopeEnumOptions.Global + ? value.val + : moduleSettings.generateScopedName(value.val, moduleSettings.filePath, moduleSettings.pattern, moduleSettings.hashLength); revMapping[mapping[value.val]] = value.val; } value.val = mapping[value.val]; @@ -29259,10 +29635,9 @@ if (value.typ == exports.EnumToken.ClassSelectorTokenType) { const val = value.val.slice(1); if (!(val in mapping)) { - const result = moduleSettings.scoped & exports.ModuleScopeEnumOptions.Global + let value = moduleSettings.scoped & exports.ModuleScopeEnumOptions.Global ? val : moduleSettings.generateScopedName(val, moduleSettings.filePath, moduleSettings.pattern, moduleSettings.hashLength); - let value = result; mapping[val] = moduleSettings.naming & exports.ModuleCaseTransformEnum.DashCaseOnly || moduleSettings.naming & exports.ModuleCaseTransformEnum.CamelCaseOnly @@ -29295,10 +29670,9 @@ if ((prefix == "--" && value.typ == exports.EnumToken.DashedIdenTokenType) || (prefix == "" && value.typ == exports.EnumToken.IdenTokenType)) { if (!(value.val in mapping)) { - const result = moduleSettings.scoped & exports.ModuleScopeEnumOptions.Global + let val = moduleSettings.scoped & exports.ModuleScopeEnumOptions.Global ? value.val : moduleSettings.generateScopedName(value.val, moduleSettings.filePath, moduleSettings.pattern, moduleSettings.hashLength); - let val = result; mapping[value.val] = prefix + (moduleSettings.naming & exports.ModuleCaseTransformEnum.DashCaseOnly || @@ -29316,12 +29690,13 @@ } if (moduleSettings.naming != exports.ModuleCaseTransformEnum.IgnoreCase) { revMapping = {}; - mapping = Object.entries(mapping).reduce((acc, [key, value]) => { - const keyName = getKeyName(key, moduleSettings.naming); - acc[keyName] = value; + mapping = {}; + let keyName; + for (const [key, value] of Object.entries(mapping)) { + keyName = getKeyName(key, moduleSettings.naming); + mapping[keyName] = value; revMapping[value] = keyName; - return acc; - }, {}); + } } result.mapping = mapping; result.revMapping = revMapping; @@ -29406,18 +29781,6 @@ }; let tokens = []; let context = ast; - ast[ROOT] = ast; - ast[LOC] = { - sta: 0, - end: 0, - srcId: options.source.id, - }; - let valuesHandlers; - let preValuesHandlers; - let postValuesHandlers; - let preVisitorsHandlersMap; - let visitorsHandlersMap; - let postVisitorsHandlersMap; const imports = []; let item; let node; @@ -29425,107 +29788,12 @@ let isAsync = typeof iter[Symbol.asyncIterator] === "function"; let parensMatch = 0; let curlyBracketMatch = 0; - if (options.visitor != null) { - valuesHandlers = new Map(); - preValuesHandlers = new Map(); - postValuesHandlers = new Map(); - preVisitorsHandlersMap = new Map(); - visitorsHandlersMap = new Map(); - postVisitorsHandlersMap = new Map(); - const visitors = Object.entries(options.visitor); - let key; - let value; - let i; - for (i = 0; i < visitors.length; i++) { - key = visitors[i][0]; - value = visitors[i][1]; - if (Number.isInteger(+key)) { - visitors.splice(i + 1, 0, ...Object.entries(value)); - continue; - } - if (Array.isArray(value)) { - // @ts-ignore - visitors.splice(i + 1, 0, ...value.map((item) => [key, item])); - continue; - } - if (key in exports.EnumToken) { - if (typeof value == "function") { - if (!valuesHandlers.has(exports.EnumToken[key])) { - valuesHandlers.set(exports.EnumToken[key], []); - } - valuesHandlers.get(exports.EnumToken[key]).push(value); - } - else if (typeof value == "object" && - "type" in value && - "handler" in value && - value.type in exports.WalkerEvent) { - if (value.type == exports.WalkerEvent.Enter) { - if (!preValuesHandlers.has(exports.EnumToken[key])) { - preValuesHandlers.set(exports.EnumToken[key], []); - } - preValuesHandlers - .get(exports.EnumToken[key]) - .push(value.handler); - } - else if (value.type == exports.WalkerEvent.Leave) { - if (!postValuesHandlers.has(exports.EnumToken[key])) { - postValuesHandlers.set(exports.EnumToken[key], []); - } - postValuesHandlers - .get(exports.EnumToken[key]) - .push(value.handler); - } - } - else { - errors.push({ action: "ignore", message: `doParse: visitor.${key} is not a valid key name` }); - } - } - else if (["Declaration", "Rule", "AtRule", "KeyframesRule", "KeyframesAtRule"].includes(key)) { - if (typeof value == "function") { - if (!visitorsHandlersMap.has(key)) { - visitorsHandlersMap.set(key, []); - } - visitorsHandlersMap - .get(key) - .push(value); - } - else if (typeof value == "object") { - if ("type" in value && "handler" in value && value.type in exports.WalkerEvent) { - if (value.type == exports.WalkerEvent.Enter) { - if (!preVisitorsHandlersMap.has(key)) { - preVisitorsHandlersMap.set(key, []); - } - preVisitorsHandlersMap - .get(key) - .push(value.handler); - } - else if (value.type == exports.WalkerEvent.Leave) { - if (!postVisitorsHandlersMap.has(key)) { - postVisitorsHandlersMap.set(key, []); - } - postVisitorsHandlersMap - .get(key) - .push(value.handler); - } - } - else { - if (!visitorsHandlersMap.has(key)) { - visitorsHandlersMap.set(key, []); - } - visitorsHandlersMap - .get(key) - .push(value); - } - } - else { - errors.push({ action: "ignore", message: `doParse: visitor.${key} is not a valid key name` }); - } - } - else { - errors.push({ action: "ignore", message: `doParse: visitor.${key} is not a valid key name` }); - } - } - } + // ast[ROOT] = ast; + ast[LOC] = { + sta: 0, + end: 0, + srcId: options.source.id, + }; if (Array.isArray(iter)) { // @ts-expect-error iter = iter[Symbol.iterator](); @@ -29647,7 +29915,7 @@ const token = node[TOKENS][0]; const url = token.typ == exports.EnumToken.StringTokenType ? token.val.slice(1, -1) : token.val; try { - const src = options.resolve(url, options.src || options.cwd); + const src = options.resolve(url, options.src ? dirname(options.src) : options.cwd); const result = options.load(src); const stream = result instanceof Promise || Object.getPrototypeOf(result).constructor.name == "AsyncFunction" ? await result @@ -29687,210 +29955,163 @@ ast = expand(ast); } let replacement; - let callable; if (options.visitor != null) { let parens; - for (const result of walk(ast)) { + let genericKey; + const handlers = []; + const visitors = parseVisitors(options.visitor, errors); + let nodes = new Array(stats.tokensCount); + const subNodes = []; + let i; + let k; + let j; + let freeblock = 1; + nodes[0] = ast; + for (i = 0; i < nodes.length; i++) { + if (nodes[i] == null) { + break; + } + subNodes.length = 0; + if (visitors.includeTokens) { + switch (nodes[i].typ) { + case exports.EnumToken.RuleNodeType: + case exports.EnumToken.AtRuleNodeType: + case exports.EnumToken.KeyframesRuleNodeType: + case exports.EnumToken.KeyframesAtRuleNodeType: + subNodes.push(...nodes[i][TOKENS]); + break; + case exports.EnumToken.DeclarationNodeType: + subNodes.push(...nodes[i].val); + break; + } + } + if (nodes[i].chi != null) { + subNodes.push(...nodes[i].chi); + } + if (subNodes.length > 0) { + if (freeblock <= i) { + freeblock = i + 1; + } + for (k = 0; k < subNodes.length; k++) { + j = k + freeblock; + nodes[j] = subNodes[k]; + nodes[j][PARENT] = nodes[i]; + } + freeblock += subNodes.length; + } parens = null; - if (valuesHandlers.size > 0 || - preVisitorsHandlersMap.size > 0 || - visitorsHandlersMap.size > 0 || - postVisitorsHandlersMap.size > 0) { - if ((result.node.typ == exports.EnumToken.DeclarationNodeType && - (preVisitorsHandlersMap.has("Declaration") || - visitorsHandlersMap.has("Declaration") || - postVisitorsHandlersMap.has("Declaration"))) || - (result.node.typ == exports.EnumToken.AtRuleNodeType && - (preVisitorsHandlersMap.has("AtRule") || - visitorsHandlersMap.has("AtRule") || - postVisitorsHandlersMap.has("AtRule"))) || - (result.node.typ == exports.EnumToken.KeyframesAtRuleNodeType && - (preVisitorsHandlersMap.has("KeyframesAtRule") || - visitorsHandlersMap.has("KeyframesAtRule") || - postVisitorsHandlersMap.has("KeyframesAtRule")))) { - const handlers = []; - const key = result.node.typ == exports.EnumToken.DeclarationNodeType - ? "Declaration" - : result.node.typ == exports.EnumToken.AtRuleNodeType - ? "AtRule" - : "KeyframesAtRule"; - if (preVisitorsHandlersMap.has(key)) { - handlers.push( - // @ts-expect-error - ...preVisitorsHandlersMap.get(key)); - } - if (visitorsHandlersMap.has(key)) { - // @ts-ignore - handlers.push(...visitorsHandlersMap.get(key)); - } - if (postVisitorsHandlersMap.has(key)) { - // @ts-ignore - handlers.push(...postVisitorsHandlersMap.get(key)); - } - let node = result.node; - for (const handler of handlers) { - callable = - typeof handler == "function" - ? handler - : handler[camelize(node.typ === exports.EnumToken.DeclarationNodeType || - node.typ === exports.EnumToken.AtRuleNodeType - ? node.nam - : node.val)]; - if (callable == null) { - continue; + handlers.length = 0; + genericKey = + nodes[i].typ == exports.EnumToken.DeclarationNodeType + ? "Declaration" + : nodes[i].typ == exports.EnumToken.AtRuleNodeType + ? "AtRule" + : nodes[i].typ == exports.EnumToken.KeyframesAtRuleNodeType + ? "KeyframesAtRule" + : nodes[i].typ === exports.EnumToken.KeyframesRuleNodeType + ? "KeyframesRule" + : nodes[i].typ == exports.EnumToken.RuleNodeType + ? "Rule" + : nodes[i].typ == exports.EnumToken.KeyframesRuleNodeType + ? "KeyframesRule" + : null; + let keyName = nodes[i].typ == exports.EnumToken.DeclarationNodeType || nodes[i].typ == exports.EnumToken.AtRuleNodeType + ? camelize(nodes[i].nam) + : nodes[i].typ == exports.EnumToken.KeyframesAtRuleNodeType + ? camelize(nodes[i].val) + : null; + for (const map of visitors.allHandlers) { + // @ts-ignore + if (genericKey != null && map.has(genericKey)) { + // @ts-ignore + for (const handler of map.get(genericKey)) { + if (typeof handler == "function") { + handlers.push(handler); } - // @ts-expect-error - replacement = callable(node, result[PARENT], ast, function* () { - if (parens == null) { - // @ts-expect-error - parens = [...result.parents()]; + else if (Array.isArray(handler)) { + for (const h of handler) { + if (typeof h == "function") { + handlers.push(h); + } + // @ts-ignore + else if (h[keyName] != null) { + // @ts-ignore + handlers.push(h[keyName]); + } } - yield* parens[Symbol.iterator](); - }); - if (replacement == null) { - continue; - } - if (replacement instanceof Promise) { - replacement = await replacement; } - if (replacement == null || replacement == node) { - continue; + else if (typeof handler.handler == "function") { + handlers.push(handler.handler); } // @ts-ignore - node = replacement; - if (Array.isArray(node)) { - break; + else if (typeof handler[keyName] == "function") { + // @ts-ignore + handlers.push(handler[keyName]); } } - if (node != result.node) { - replaceNodeOrValue(result.parent, result.node, node); - } } - else if ((result.node.typ == exports.EnumToken.RuleNodeType && - (preVisitorsHandlersMap.has("Rule") || - visitorsHandlersMap.has("Rule") || - postVisitorsHandlersMap.has("Rule"))) || - (result.node.typ == exports.EnumToken.KeyFramesRuleNodeType && - (preVisitorsHandlersMap.has("KeyframesRule") || - visitorsHandlersMap.has("KeyframesRule") || - postVisitorsHandlersMap.has("KeyframesRule")))) { - const handlers = []; - const key = result.node.typ == exports.EnumToken.RuleNodeType ? "Rule" : "KeyframesRule"; - if (preVisitorsHandlersMap.has(key)) { - handlers.push(...preVisitorsHandlersMap.get(key)); - } - if (visitorsHandlersMap.has(key)) { - handlers.push(...visitorsHandlersMap.get(key)); - } - if (postVisitorsHandlersMap.has(key)) { - handlers.push(...postVisitorsHandlersMap.get(key)); - } - let node = result.node; - for (const callable of handlers) { - replacement = callable(node, result.parent, result.root, - // @ts-expect-error - function* () { - if (parens == null) { - // @ts-expect-error - parens = [...result.parents()]; - } - yield* parens[Symbol.iterator](); - }); - if (replacement == null) { - continue; + // @ts-ignore + if (map.has(nodes[i].typ)) { + // @ts-ignore + for (const handler of map.get(nodes[i].typ)) { + if (typeof handler == "function") { + handlers.push(handler); } - if (replacement instanceof Promise) { - replacement = await replacement; + else if (Array.isArray(handler)) { + for (const h of handler) { + if (typeof h == "function") { + handlers.push(h); + } + } } - if (replacement == null || replacement == node) { - continue; + else if (typeof handler.handler == "function") { + handlers.push(handler.handler); } // @ts-ignore - node = replacement; - // - if (Array.isArray(node)) { - break; + else if (typeof handler[keyName] == "function") { + // @ts-ignore + handlers.push(handler[keyName]); } } - // @ts-ignore - if (node != result.node) { - // @ts-ignore - replaceNodeOrValue(result.parent, result.node, node); - } } - else if (valuesHandlers.size > 0) { - let node = null; - node = result.node; - if (valuesHandlers.has(node.typ)) { - for (const valueHandler of valuesHandlers.get(node.typ)) { - callable = valueHandler; - replacement = callable(node, result.parent, ast, - // @ts-expect-error - function* () { - if (parens == null) { - // @ts-expect-error - parens = [...result.parents()]; - } - yield* parens[Symbol.iterator](); - }); - if (replacement == null) { - continue; - } - if (replacement instanceof Promise) { - replacement = await replacement; - } - if (replacement != null && replacement != node) { - node = replacement; - } - } - } - if (node != result.node) { - // @ts-ignore - replaceNodeOrValue(result[PARENT], value, node); - } - const tokens = Array.isArray(result.node[TOKENS]) ? result.node[TOKENS] : []; - if (Array.isArray(result.node.val)) { - tokens.push(...result.node.val); - } - if (tokens.length == 0) { - continue; - } - for (const { value, parent, root, parents } of walkValues(tokens, result.node)) { - node = value; - if (valuesHandlers.has(node.typ)) { - let parens = null; - for (const valueHandler of valuesHandlers.get(node.typ)) { - callable = valueHandler; - // @ts-expect-error - let result = callable(node, parent, root, function* () { - if (parens == null) { - // @ts-expect-error - parens = [...parents()]; - } - yield* parens[Symbol.iterator](); - }); - if (result == null) { - continue; - } - if (result instanceof Promise) { - result = await result; - } - if (result != null && result != node) { - node = result; - } - if (Array.isArray(node)) { - break; - } - } - } - if (node != value) { - // @ts-ignore - replaceNodeOrValue(parent, value, node); + } + if (handlers.length == 0) { + continue; + } + let node = nodes[i]; + for (const callable of handlers) { + replacement = callable(node, nodes[i][PARENT], ast, + // @ts-expect-error + function* () { + if (parens == null) { + let node = nodes[i][PARENT]; + while (node != null) { + yield node; + node = node[PARENT]; } } + }); + if (replacement == null) { + continue; + } + if (replacement instanceof Promise) { + replacement = await replacement; + } + if (replacement == null || replacement == node) { + continue; + } + // @ts-ignore + node = replacement; + // + if (Array.isArray(node)) { + break; } } + if (node != nodes[i]) { + replaceNodeOrValue(nodes[i][PARENT], nodes[i], node); + } } + nodes = null; } if (invalidNodes.length > 0) { let count = invalidNodes.length; @@ -29913,19 +30134,6 @@ } } } - while (stack.length > 0 && context != ast) { - const previousNode = stack.pop(); - context = (stack[stack.length - 1] ?? ast); - // remove empty nodes - if (options.removeEmpty && - previousNode != null && - previousNode.chi.length == 0 && - context.chi[context.chi.length - 1] == previousNode) { - context.chi.pop(); - continue; - } - break; - } if (options.minify) { if (ast.chi.length > 0) { let passes = options.pass ?? 1; @@ -30121,6 +30329,7 @@ parentRule.chi.splice(parentRule.chi.indexOf(node), 1); continue; } + const resolvedSrc = options.resolve(options.src, options.cwd); for (const token of composeSelectors) { // composes: a b c; if (token.r == null) { @@ -30190,8 +30399,10 @@ setParent: false, src: src.relative, })); - const srcIndex = (src.relative.startsWith("/") || src.relative.startsWith("../") ? "" : "./") + - src.relative; + let srcIndex = options.resolve(src.absolute, resolvedSrc.absolute).relative; + if (!srcIndex.startsWith("/") && !srcIndex.startsWith("../")) { + srcIndex = `./${srcIndex}`; + } if (Object.keys(root.mapping).length > 0) { importMapping[srcIndex] = {}; } @@ -30443,26 +30654,6 @@ exports.EnumToken.DescendantCombinatorTokenType) { parent[TOKENS].splice(index, 1); } - // if (val == ":global") { - // for (; index < (parent as AstRule)[TOKENS]!.length; index++) { - // if ( - // (parent as AstRule)[TOKENS]![index].typ == - // EnumToken.CommaTokenType || - // ([ - // EnumToken.PseudoClassFuncTokenType, - // EnumToken.PseudoClassTokenType, - // ].includes((parent as AstRule)[TOKENS]![index].typ) && - // [":global", ":local"].includes( - // ( - // (parent as AstRule)[TOKENS]![index] as PseudoClassToken - // ).val.toLowerCase(), - // )) - // ) { - // break; - // } - // global.add((parent as AstRule)[TOKENS]![index]); - // } - // } } break; } @@ -30476,12 +30667,6 @@ case ":local": parent[TOKENS].splice(parent[TOKENS].indexOf(value), 1, ...value.chi); break; - // (parent as AstRule)[TOKENS]!.splice( - // (parent as AstRule)[TOKENS]!.indexOf(value), - // 1, - // ...(value as FunctionToken).chi, - // ); - // break; } } })) { @@ -30767,6 +30952,8 @@ return null; } /** + * @param stream + * @param context * @param options * @param errors * @param parseAsBlock @@ -30843,7 +31030,6 @@ parseAsBlock = blockAllowed; } if (syntax != null && atRule.nam !== "layer" && parseAsBlock !== blockAllowed) { - success = false; errors.push({ action: "drop", node: atRule, @@ -31331,7 +31517,7 @@ action: "drop", node: atRule, location: options.source.getSourceLocation(atRule[LOC].sta), - message: "node is allowd only in @page rule", + message: "node is allowed only in @page rule", }); } else { @@ -31470,9 +31656,6 @@ if (result.errors.length > 0) { errors.push(...result.errors); } - // else if (atRuleName === "document") { - // parseUrlToken(stream); - // } if (result.success) { let i = 0; const stack = []; @@ -31568,12 +31751,14 @@ position: 0, currentPosition: -1, }; - const result = parseTokens([...tokenize(parseInfo)].map((t) => t.token), options, errors); - // remove EOF token - result.pop(); - if (result.at(-1)?.typ === exports.EnumToken.WhitespaceTokenType) { - result.pop(); + const tokenResults = tokenize(parseInfo); + const mapped = []; + for (const token of tokenResults) { + mapped.push(token.token); } + const result = parseTokens(mapped, options, errors); + // remove EOF token + result.splice(result.length - (result[result.length - 2]?.typ === exports.EnumToken.WhitespaceTokenType ? 2 : 1), 2); return result; } /** @@ -31664,7 +31849,6 @@ node, location: options.source.getSourceLocation(node[LOC].sta), }); - // return []; continue; } index = tokens.indexOf(stack.at(-1)); @@ -31811,6 +31995,67 @@ ResponseType[ResponseType["ArrayBuffer"] = 2] = "ArrayBuffer"; })(exports.ResponseType || (exports.ResponseType = {})); + /** + * parse result. process input sourcemap + * @param result + * @param options + * @returns + * @private + */ + function parseResult(result, options) { + if (options.sourcemap != null && options.source.getInputSourceMap() == null) { + if (options.inputSourceMap != null) { + options.source.setInputSourceMap(options.inputSourceMap); + } + else { + // extract inline source map from the input CSS + const token = result.ast.chi.at(-1); + if (token?.typ == exports.EnumToken.CommentTokenType && + token.val.startsWith("/*# sourceMappingURL=")) { + const data = token.val.slice(21, -2).trim(); + let sourcemap; + let encoding = ""; + if (data.startsWith("data:")) { + let offset = data.indexOf(",") + 1; + if (offset == 0) { + offset = data.lastIndexOf(";") + 1; + } + else { + encoding = data.slice(data.lastIndexOf(";") + 1, offset - 1); + } + if (encoding == "base64") { + sourcemap = atob(data.slice(offset)); + } + else { + sourcemap = decodeURIComponent(data.slice(offset)); + } + options.source.setInputSourceMap(sourcemap); + } + } + } + } + if (options.module) { + const { revMapping, ...res } = result; + return res; + } + return result; + } + function validateSyncArguments(options, prefix = "options.") { + const args = Object.entries(options); + let i; + for (i = 0; i < args.length; i++) { + const [key, value] = args[i]; + if (typeof value == 'function') { + if (value instanceof Promise || Object.getPrototypeOf(value).constructor.name == "AsyncFunction") { + throw new Error(`[${prefix + key}]: Async functions are not supported in sync mode. Use parse() or transform() instead.`); + } + } + else if (value != null && typeof value == 'object') { + validateSyncArguments(value, prefix + key + "."); + } + } + } + /** * Load file or url * @param url @@ -31819,7 +32064,7 @@ * @throws Error file not found * * ```ts - * import {load, ResponseType} from '@tbela99/css-parser'; + * import {load, ResponseType} from '@tbela99/css-parser/web'; * const result = await load(file, '.', ResponseType.ArrayBuffer) as ArrayBuffer; * ``` */ @@ -31861,7 +32106,7 @@ * * ```ts * - * import {render, ColorType} from '@tbela99/css-parser'; + * import {render, ColorType} from '@tbela99/css-parser/web'; * * const css = 'body { color: color(from hsl(0 100% 50%) xyz x y z); }'; * const parseResult = await parse(css); @@ -31920,15 +32165,16 @@ /** * Parse css * @param args + * @private * * Parsing a string * * ```ts * - * import {parse} from '@tbela99/css-parser'; + * import {parseSync} from '@tbela99/css-parser/web'; * * // css string - * let result = await parse(css, {nestingRules: true}); + * let result = await parseSync(css, {nestingRules: true}); * console.log(result.ast); * ``` * @@ -31945,9 +32191,12 @@ options = opt; stream = input; } + if (options != null) { + validateSyncArguments(options); + } options ??= {}; options.src ??= ""; - options.sourcesMap ??= new Map; + options.sourcesMap ??= new Map(); Object.assign(options, { resolve, dirname, @@ -31972,23 +32221,22 @@ currentPosition: -1, }; const result = doParseSync(tokenize(options.parseInfo), options); - const { revMapping, ...res } = result; - return res; + return !options.module && !options.inputSourceMap && !options.sourcemap ? result : parseResult(result, options); } /** - * Transform css - * @param css - * @param options + * Transform CSS * * ```ts * - * import {transform} from '@tbela99/css-parser'; + * import {transformSync} from '@tbela99/css-parser/web'; * * // css string - * const result = await transform(css); + * const result = transformSync(css); * console.log(result.code); * ``` * + * @param args + * @private */ function transformSync(...args) { let options; @@ -32038,8 +32286,6 @@ } /** * Parse css - * @param stream - * @param options * * Example: * @@ -32063,6 +32309,8 @@ * * console.log(result.ast); * ``` + * @param args + * @private */ async function parse(...args) { let options; @@ -32084,7 +32332,7 @@ } options ??= {}; options.src ??= ""; - options.sourcesMap ??= new Map; + options.sourcesMap ??= new Map(); Object.assign(options, { load, resolve, @@ -32108,13 +32356,10 @@ position: 0, currentPosition: -1, }; - return doParse(stream instanceof ReadableStream ? tokenizeStream(stream, options.parseInfo) : tokenize(options.parseInfo), options).then((result) => { - const { revMapping, ...res } = result; - return res; - }); + return doParse(stream instanceof ReadableStream ? tokenizeStream(stream, options.parseInfo) : tokenize(options.parseInfo), options).then((result) => (!options.module && !options.inputSourceMap ? result : parseResult(result, options))); } /** - * Transform css file + * Transform CSS file * @param file url or path * @param options * @param asStream load file as stream @@ -32145,8 +32390,6 @@ } /** * Transform css - * @param css - * @param options * * Example: * @@ -32164,6 +32407,8 @@ * * console.log(result.code); * ``` + * @param args + * @private */ async function transform(...args) { let options; diff --git a/dist/index.cjs b/dist/index.cjs index b3a4b908..8ba8ff59 100644 --- a/dist/index.cjs +++ b/dist/index.cjs @@ -394,7 +394,7 @@ exports.EnumToken = void 0; /** * keyframe rule node type */ - EnumToken[EnumToken["KeyFramesRuleNodeType"] = 73] = "KeyFramesRuleNodeType"; + EnumToken[EnumToken["KeyframesRuleNodeType"] = 73] = "KeyframesRuleNodeType"; /** * class selector token type */ @@ -9438,6 +9438,8 @@ exports.WalkerEvent = void 0; * @param filter control the walk process * @param reverse walk in reverse order * + * @private + * * ```ts * * import {walk} from '@tbela99/css-parser'; @@ -9509,11 +9511,19 @@ function* walk(node, filter, reverse) { const parents = [node]; const root = node; const map = new Map(); + let options = filter; let isNumeric = false; + let includeValues = false; let i = 0; + if (options != null && typeof options == "object") { + filter = options.filter; + reverse = options.reverse; + includeValues = options.inludeValues; + } while ((node = parents[i++])) { let option = null; if (filter != null) { + // @ts-ignore option = filter(node); isNumeric = typeof option == "number"; if (isNumeric) { @@ -9541,8 +9551,16 @@ function* walk(node, filter, reverse) { }, }; } - if ("chi" in node && (!isNumeric || (option & exports.WalkerOptionEnum.IgnoreChildren) === 0)) { - parents.splice(i, 0, ...node.chi[reverse ? "toReversed" : "slice"]()); + if (includeValues) { + if (node[TOKENS] != null) { + parents.splice(i, 0, ...(reverse ? node[TOKENS].toReversed() : node[TOKENS])); + } + else if (Array.isArray(node.val)) { + parents.splice(i, 0, ...(reverse ? node.val.toReversed() : node.val)); + } + } + if (node["chi"] != null && (!isNumeric || (option & exports.WalkerOptionEnum.IgnoreChildren) === 0)) { + parents.splice(i, 0, ...(reverse ? node.chi.toReversed() : node.chi)); for (const child of node.chi) { map.set(child, node); } @@ -9626,11 +9644,6 @@ function* walkValues(values, root = null, filter, reverse) { continue; } used.add(value); - // parents.length = 0; - // while (node != null) { - // parents.push(node); - // node = map.get(node) ?? null; - // } if (filter.fn != null && eventType & exports.WalkerEvent.Enter) { const isValid = filter.type == null || value.typ == filter.type || @@ -11686,14 +11699,14 @@ function cloneNode(node, cloneChildren = false, cloneMap = null) { clone[name] = value; } else if (Array.isArray(value)) { - clone[name] = - !cloneChildren && name == checkNode - ? [] - : value.map((c) => { - const newObj = cloneNode(c, cloneChildren, cloneMap); - cloneMap?.set?.(c, newObj); - return newObj; - }); + clone[name] = []; + if (cloneChildren || name !== checkNode) { + for (const c of value) { + const newObj = cloneNode(c, cloneChildren, cloneMap); + cloneMap?.set?.(c, newObj); + clone[name].push(newObj); + } + } } else { clone[name] = { ...value }; @@ -11708,11 +11721,19 @@ function cloneNode(node, cloneChildren = false, cloneMap = null) { const config$3 = getSyntaxConfig(); // @ts-expect-error const allValues = config$3.declarations.all.syntax.split(/[\s|]+/g); +/** + * @type {Array.} + */ const funcTypes = [ ...tokensfuncDefMap.values(), exports.EnumToken.FunctionTokenType, exports.EnumToken.PseudoClassFuncTokenType, ]; +/** + * trim leading and trailing whitespace + * @param tokens + * @returns + */ function trimArray(tokens) { while (tokens[0]?.typ === exports.EnumToken.WhitespaceTokenType) { tokens.shift(); @@ -11813,6 +11834,11 @@ function isMFValue(featureName, tokens, isMFRange) { success: true, }; } +/** + * create validation context + * @param tokens + * @returns + */ function createValidationContext(tokens) { tokens = trimArray(tokens.filter((t) => t.typ !== exports.EnumToken.CommentTokenType)); if (tokens.at(-1)?.typ === exports.EnumToken.ImportantTokenType) { @@ -11976,6 +12002,14 @@ function createValidationContext(tokens) { }; return token; } +/** + * match selector syntax + * @param stream + * @param errors + * @param options + * @param nested + * @returns + */ function matchSelectorSyntax(stream, errors, options, nested = true) { const stack = []; const tokens = []; @@ -12414,6 +12448,13 @@ function matchSelectorSyntax(stream, errors, options, nested = true) { stream.push(...tokens); return { success, errors }; } +/** + * matches all syntaxes + * @param syntaxes + * @param context + * @param options + * @returns + */ function matchAllSyntaxes(syntaxes, context, options) { const result = matchSyntax(syntaxes, context, { ...options, @@ -12452,6 +12493,13 @@ function matchAllSyntaxes(syntaxes, context, options) { syntaxToken: !result.success ? result.syntaxToken : null, }; } +/** + * matches a list of syntaxes + * @param syntax + * @param context + * @param options + * @returns + */ function matchListSyntax(syntax, context, options) { const { isList, match, isOptional, ...rest } = syntax; let success = true; @@ -12496,6 +12544,13 @@ function matchListSyntax(syntax, context, options) { token: context.peek(), }; } +/** + * matches a list of syntaxes + * @param syntax + * @param context + * @param options + * @returns + */ function matchOccurenceSyntax(syntax, context, options) { const { match, ...rest } = syntax; let result = null; @@ -12537,6 +12592,13 @@ function matchOccurenceSyntax(syntax, context, options) { } return result; } +/** + * matches a list of syntaxes + * @param syntaxes + * @param context + * @param options + * @returns + */ function matchSyntax(syntaxes, context, options) { if (syntaxes == null) { return { @@ -12669,7 +12731,7 @@ function matchSyntax(syntaxes, context, options) { if (syntaxes[i].isList) { result = matchListSyntax(syntaxes[i], context.slice(), options); if (result.success) { - options.visited.get(token).delete(syntaxes[i]); + options.visited.get(token)?.delete?.(syntaxes[i]); if (result.context.done()) { context.end(); return { @@ -13177,6 +13239,13 @@ function matchSyntax(syntaxes, context, options) { errors: [], }; } +/** + * matches a column of syntaxes + * @param syntax + * @param context + * @param options + * @returns + */ function matchColumnSyntax(syntax, context, options) { let syntaxes = syntax.chi.slice(); let i = 0; @@ -13213,6 +13282,13 @@ function matchColumnSyntax(syntax, context, options) { errors: [], }; } +/** + * matches an ampersand of syntaxes + * @param syntax + * @param context + * @param options + * @returns + */ function matchAmpersandSyntax(syntax, context, options) { const syntaxes = [syntax.l, syntax.r]; let result; @@ -13231,6 +13307,13 @@ function matchAmpersandSyntax(syntax, context, options) { } return result; } +/** + * matches a property + * @param property + * @param context + * @param options + * @returns + */ function matchProperty(property, context, options) { let success = false; let t = context.peek()?.typ; @@ -13963,6 +14046,13 @@ function matchProperty(property, context, options) { errors: [], }; } +/** + * matches a repeatable syntax + * @param syntax + * @param context + * @param options + * @returns + */ function matchRepeatableSyntax(syntax, context, options) { const { isRepeatable, isOptional, isMandatatoryGroup, isRepeatableAtLeastOnce, ...rest } = syntax; let result = null; @@ -18915,12 +19005,19 @@ class PropertyMap { return acc; }, []); let isImportant = false; - const filtered = values.map(removeDefaults).filter((x) => x.val.filter((t) => { - if (t.typ == exports.EnumToken.ImportantTokenType) { - isImportant = true; + let dec; + const filtered = []; + for (const declaration of values) { + dec = removeDefaults(declaration); + for (const t of dec.val) { + if (t.typ == exports.EnumToken.ImportantTokenType) { + isImportant = true; + } + if (filtered.length == 0 && t.typ != exports.EnumToken.WhitespaceTokenType && t.typ != exports.EnumToken.ImportantTokenType) { + filtered.push(dec); + } } - return ![exports.EnumToken.WhitespaceTokenType, exports.EnumToken.ImportantTokenType].includes(t.typ); - }).length > 0); + } if (filtered.length == 0 && this.config.default.length > 0) { filtered.push({ typ: exports.EnumToken.DeclarationNodeType, @@ -19638,7 +19735,7 @@ class ComputeShorthandFeature { accept = new Set([ exports.EnumToken.RuleNodeType, exports.EnumToken.AtRuleNodeType, - exports.EnumToken.KeyFramesRuleNodeType, + exports.EnumToken.KeyframesRuleNodeType, ]); get ordering() { return 10; @@ -20979,7 +21076,7 @@ function splitTransformList(transformList) { } class TransformCssFeature { - accept = new Set([exports.EnumToken.RuleNodeType, exports.EnumToken.KeyFramesRuleNodeType]); + accept = new Set([exports.EnumToken.RuleNodeType, exports.EnumToken.KeyframesRuleNodeType]); get ordering() { return 3; } @@ -21046,7 +21143,7 @@ class TransformCssFeature { } /** - * Search the ast sub-tree and return the first match + * Search the ast subtree and return the first match * * ```ts * // find the first ast declaration node which name is 'aspect-ratio' @@ -21085,6 +21182,11 @@ function find(ast, matcher) { return null; } /** + * + * @param ast + * @param matcher + * @returns + * * Search the ast sub-tree by checking each node's value token and return the first match * ```ts @@ -21110,10 +21212,6 @@ button { console.log({node, value}); ``` - * - * @param ast - * @param matcher - * @returns */ function findByValue(ast, matcher) { let source; @@ -21385,153 +21483,462 @@ var allFeatures = /*#__PURE__*/Object.freeze({ TransformCssFeature: TransformCssFeature }); +// from https://github.com/Rich-Harris/vlq/tree/master +// credit: Rich Harris +const integer_to_char = {}; +const char_to_integer = {}; +let i = 0; +for (const char of 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=') { + char_to_integer[char] = i; + integer_to_char[i++] = char; +} /** - * Compute line and column of the offset + * @param {string} str */ -class LineMap { - /** - * line starts - */ - lineStarts; - /** - * Constructor - * @param lines - */ - constructor(lines) { - if (lines.length === 0) { - lines.push(0); - } - this.lineStarts = lines; - } - /** - * Compute line and column of the offset - * @param offset - * @returns - */ - getOffsets(offset) { - const line = this.search(offset); - if (offset < 0 || line < 0) { - return [1, 1]; +function decode(str) { + /** @type {number[]} */ + let result = []; + let shift = 0; + let value = 0; + for (let i = 0; i < str.length; i += 1) { + let integer = char_to_integer[str[i]]; + if (integer === undefined) { + throw new Error('Invalid character (' + str[i] + ')'); + } + const has_continuation_bit = integer & 32; + integer &= 31; + value += integer << shift; + if (has_continuation_bit) { + shift += 5; } - const column = offset - this.lineStarts[line]; - // [line, column] - return [line + 1, column === 0 ? 1 : column]; - } - /** - * search the greatest index of the value less than or equal to offset - * @param offset - * @returns - */ - search(offset) { - // search lineStarts using binary search - let start = 0; - let end = this.lineStarts.length - 1; - let mid = 0; - let result = -1; - while (start <= end) { - mid = start + ((end - start) >>> 1); - if (this.lineStarts[mid] <= offset) { - result = mid; - start = mid + 1; + else { + const should_negate = value & 1; + value >>>= 1; + if (should_negate) { + result.push(value === 0 ? -2147483648 : -value); } - else if (this.lineStarts[mid] > offset) { - end = mid - 1; + else { + result.push(value); } + // reset + value = shift = 0; } - return result; } - /** - * get line starts - * @returns - */ - getLineStarts() { - return this.lineStarts; + return result; +} +/** + * + * @param value + * @returns + */ +function encode(value) { + if (typeof value === 'number') { + return encode_integer(value); } - /** - * add line start - */ - addLineStart(lineStart) { - this.lineStarts.push(lineStart); + let result = ''; + for (let i = 0; i < value.length; i += 1) { + result += encode_integer(value[i]); } - /** - * clone the linemap - * @returns - */ - clone() { - return new LineMap(this.lineStarts.slice()); + return result; +} +function encode_integer(num) { + let result = ''; + if (num < 0) { + num = (-num << 1) | 1; + } + else { + num <<= 1; } + do { + let clamped = num & 31; + num >>>= 5; + if (num > 0) { + clamped |= 32; + } + result += integer_to_char[clamped]; + } while (num > 0); + return result; } /** - * Source file ID - */ -let sourceId = 0; -/** - * Source file helper class + * Generate and parse source map */ -class SourceFile { +class SourceMap { /** - * Source file ID + * + * @private */ - id; + keys = new Set(); /** - * Source file path + * Last location */ - file; + lastLocation = null; /** - * Line map + * Version + * @private */ - lineStarts; + version = 3; /** - * Source file content + * Sources map + * @private */ - content; + sourcesMap = []; /** - * Constructor - * @param id - * @param content - * @param lines - * @param file + * Sources content + * @private */ - constructor(content, lines, file = null) { - this.id = sourceId++; - this.content = content; - this.file = file; - this.lineStarts = new LineMap(lines); - } + sourcesContent = []; /** - * Update source content - * @param content - * @param lines + * Sources + * @private */ - append(content) { - this.content += content; - } + sources = []; /** - * get file name - * @returns + * Map + * @private + * */ - getFileName() { - return this.file; - } + map = new Map(); /** - * get content - * @returns + * Map + * @private + * */ - getContent() { - return this.content; - } + reverseMap = new Map(); /** - * get text - * @param start - * @param length - * @returns + * Line + * @private */ - getText(start, length) { - return this.content.slice(start, start + length); - } + line = -1; /** - * Compute line and column of the offset + * + * @param sourcemaps + * @private + */ + constructor(sourcemaps) { + if (typeof sourcemaps === "string") { + sourcemaps = JSON.parse(sourcemaps); + } + if (sourcemaps != null) { + this.sources = sourcemaps.sources?.slice() ?? []; + this.sourcesContent = sourcemaps.sourcesContent?.slice() ?? []; + const decodedMappings = sourcemaps.mappings + .split(";") + .map((mapping) => mapping.split(",").map((mapping) => decode(mapping))); + this.line = decodedMappings.length - 1; + for (let index = 0; index < decodedMappings.length; index++) { + if (decodedMappings[index].length == 0 || + (decodedMappings[index].length == 1 && decodedMappings[index][0].length == 0)) { + continue; + } + this.map.set(index, decodedMappings[index]); + } + this.computePositions(); + } + } + /** + * Add all location + * @param maps + */ + addAll(maps) { + for (let [newLine, newColumn, srcId, ln, col, sourceFileName, sourceContent] of maps) { + const key = `${srcId}:${ln}:${sourceFileName}:${col}:${newLine}:${newColumn}:${sourceContent}`; + const sourcemap = `${srcId}:${sourceFileName}:${sourceContent}`; + if (this.keys.has(key)) { + continue; + } + this.keys.add(key); + if (!this.sourcesMap.includes(sourcemap)) { + this.sourcesMap.push(sourcemap); + this.sources.push(sourceFileName || null); + this.sourcesContent.push((sourceFileName != null ? null : sourceContent) || null); + } + const line = newLine - 1; + let record; + if (line > this.line) { + this.line = line; + } + if (!this.map.has(line)) { + record = [Math.max(0, newColumn - 1), this.sourcesMap.indexOf(sourcemap), ln - 1, col - 1]; + this.map.set(line, [record]); + } + else { + const arr = this.map.get(line); + record = [ + Math.max(0, newColumn - 1) - arr[0][0], + this.sourcesMap.indexOf(sourcemap) - arr[0][1], + ln - 1, + col - 1, + ]; + arr.push(record); + } + if (this.lastLocation != null) { + record[2] -= this.lastLocation.ln - 1; + record[3] -= this.lastLocation.col - 1; + } + this.lastLocation ??= { ln, col }; + this.lastLocation.ln = ln; + this.lastLocation.col = col; + } + } + /** + * compute original positions + */ + computePositions() { + this.reverseMap.clear(); + let sourceFileIndex = 0; // second field + let sourceCodeLine = 0; // third field + let sourceCodeColumn = 0; // fourth field + let nameIndex = 0; // fifth field + let generatedCodeColumn; + let result; + // mappings to original source + for (let [i, line] of this.map.entries()) { + if (line.length === 0 || (line.length === 1 && line[0].length === 0)) { + continue; + } + generatedCodeColumn = line[0][0]; // first field - reset each time + line = line + .map((segment, index, array) => { + if (segment.length === 0) { + return []; + } + generatedCodeColumn = index == 0 ? segment[0] : segment[0] + array[0][0]; + result = [generatedCodeColumn]; + if (segment.length <= 1) { + return result; + } + sourceFileIndex = index == 0 ? segment[1] : segment[1] + array[0][1]; + sourceCodeLine += segment[2]; + sourceCodeColumn += segment[3]; + result.push(sourceFileIndex, sourceCodeLine, sourceCodeColumn); + if (segment.length === 5) { + nameIndex += segment[4]; + result.push(nameIndex); + } + return result; + }) + .sort((a, b) => { + if (a[1] !== b[1]) { + return a[1] - b[1]; + } + return a[0] - b[0]; + }); + if (line.length == 0 || (line.length == 1 && line[0].length == 0)) { + continue; + } + this.reverseMap.set(i, line); + } + } + /** + * retrieve original sources, lines and columns + * @param line generated line + * @param column generated column + */ + find(line, column) { + if (!this.reverseMap.has(--line)) { + return null; + } + column--; + const result = []; + for (const record of this.reverseMap.get(line)) { + if (record.length == 0 || record[0] < column) { + continue; + } + if (record[0] > column) { + break; + } + result.push([ + this.sources?.[record[1]] ?? null, + record[2] + 1, + record[3] + 1, + this.sourcesContent?.[record[1]] ?? null, + ]); + } + return result.length == 0 ? null : result; + } + /** + * Convert to URL encoded string + */ + toUrl() { + // /*# sourceMappingURL = ${url} */ + return `data:application/json;charset=utf-8;base64,${btoa(JSON.stringify(this.toJSON()))}`; + } + /** + * Convert to JSON object + */ + toJSON() { + const mappings = []; + let i = 0; + for (; i <= this.line; i++) { + if (!this.map.has(i)) { + mappings.push(""); + } + else { + mappings.push(this.map.get(i).reduce((acc, curr) => acc + (acc === "" ? "" : ",") + encode(curr), "")); + } + } + return { + version: this.version, + sources: this.sources.slice(), + sourcesContent: this.sourcesContent?.slice(), + mappings: mappings.join(";"), + }; + } + /** + * to string + */ + toString() { + return JSON.stringify(this); + } +} + +/** + * Compute line and column of the offset + */ +class LineMap { + /** + * line starts + */ + lineStarts; + /** + * Constructor + * @param lines + */ + constructor(lines = []) { + if (lines.length === 0) { + lines.push(0); + } + this.lineStarts = lines; + } + /** + * Compute line and column of the offset + * @param offset + * @returns + */ + getOffsets(offset) { + const line = this.search(offset); + if (offset < 0 || line < 0) { + return [1, 1]; + } + const column = offset - this.lineStarts[line] + 1; + // [line, column] + return [line + 1, column == 0 ? 1 : column]; + } + /** + * search the greatest index of the value less than or equal to offset + * @param offset + * @returns + */ + search(offset) { + // search lineStarts using binary search + let start = 0; + let end = this.lineStarts.length - 1; + let mid = 0; + let result = -1; + while (start <= end) { + mid = start + ((end - start) >>> 1); + if (this.lineStarts[mid] <= offset) { + result = mid; + start = mid + 1; + } + else if (this.lineStarts[mid] > offset) { + end = mid - 1; + } + } + return result; + } + /** + * get line starts + * @returns + */ + getLineStarts() { + return this.lineStarts; + } + /** + * add line start + */ + addLineStart(lineStart) { + this.lineStarts.push(lineStart); + } + /** + * clone the linemap + * @returns + */ + clone() { + return new LineMap(this.lineStarts.slice()); + } +} + +/** + * Source file ID + */ +let sourceId = 0; +/** + * Source file helper class + */ +class SourceFile { + inputSourceMap = null; + /** + * Source file ID + */ + id; + /** + * Source file path + */ + file; + /** + * Line map + */ + lineStarts; + /** + * Source file content + */ + content; + /** + * Constructor + * @param content + * @param lines + * @param file + */ + constructor(content, lines, file = null) { + this.id = sourceId++; + this.content = content; + this.file = file; + this.lineStarts = new LineMap(lines); + } + /** + * Update source content + * @param content + */ + append(content) { + this.content += content; + } + /** + * get file name + * @returns + */ + getFileName() { + return this.file; + } + /** + * get content + * @returns + */ + getContent() { + return this.content; + } + /** + * get text + * @param start + * @param length + * @returns + */ + getText(start, length) { + return this.content.slice(start, start + length); + } + /** + * Compute line and column of the offset * @param offset * @returns */ @@ -21560,6 +21967,20 @@ class SourceFile { addLineStart(lineStart) { this.lineStarts.addLineStart(lineStart); } + /** + * set input source map + * @param inputSourceMap + */ + setInputSourceMap(inputSourceMap) { + this.inputSourceMap = inputSourceMap == null ? null : new SourceMap(inputSourceMap); + } + /** + * return input source map + * @returns + */ + getInputSourceMap() { + return this.inputSourceMap; + } } const SymbolsMapTokens = { @@ -21936,7 +22357,7 @@ function next(parseInfo, count = 1) { return char; } /** - * Tokenize css string + * Tokenize CSS string * @param parseInfo * @param yieldEOFToken */ @@ -21963,8 +22384,6 @@ function tokenize(parseInfo, yieldEOFToken = true) { parseInfo.buffer = ""; while ((value = peek(parseInfo))) { charCode = value.charCodeAt(0); - // nextCharCode = nextValue.charCodeAt(0); - // console.debug({value, buffer}); switch (charCode) { case 61 /* TokenMap.EQUALS */: if (buffer.length > 0) { @@ -22329,10 +22748,6 @@ function tokenize(parseInfo, yieldEOFToken = true) { break; } buffer += value + next(parseInfo); - // buffer += - // (parseInfo.offset == parseInfo.currentPosition - // ? parseInfo.buffer.slice(-1) - // : parseInfo.stream.charAt(parseInfo.currentPosition - parseInfo.offset - 1)) + value; break; case 39 /* TokenMap.SINGLE_QUOTE */: case 34 /* TokenMap.DOUBLE_QUOTE */: @@ -22407,7 +22822,7 @@ const rules = [ exports.EnumToken.AtRuleNodeType, exports.EnumToken.RuleNodeType, exports.EnumToken.AtRuleTokenType, - exports.EnumToken.KeyFramesRuleNodeType, + exports.EnumToken.KeyframesRuleNodeType, ]; // @ts-ignore const features = Object.values(allFeatures).sort((a, b) => a.ordering - b.ordering); @@ -22419,6 +22834,7 @@ const features = Object.values(allFeatures).sort((a, b) => a.ordering - b.orderi * @param errors * @param nestingContent * + * @param context * @private */ function minify(ast, options = {}, recursive = false, errors, nestingContent, context = {}) { @@ -22426,22 +22842,24 @@ function minify(ast, options = {}, recursive = false, errors, nestingContent, co let postprocess = false; let parents; let replacement; - if (!("features" in options)) { + // @ts-ignore + let { sourcemap, module, ...options2 } = options; + if (!("features" in options2)) { // @ts-ignore - options = { + options2 = { removeDuplicateDeclarations: true, computeShorthand: true, computeCalcExpression: true, removePrefix: false, features: [], - ...options, + ...options2, }; for (const feature of features) { - feature.register(options); + feature.register(options2); } - options.features.sort((a, b) => a.ordering - b.ordering); + options2.features.sort((a, b) => a.ordering - b.ordering); } - for (const feature of options.features) { + for (const feature of options2.features) { if (feature.processMode & exports.FeatureWalkMode.Pre) { preprocess = true; } @@ -22456,17 +22874,17 @@ function minify(ast, options = {}, recursive = false, errors, nestingContent, co continue; } replacement = parent; - for (const feature of options.features) { + for (const feature of options2.features) { if ((feature.processMode & exports.FeatureWalkMode.Pre) === 0 || (feature.accept != null && !feature.accept.has(parent.typ))) { continue; } if (rules.includes(replacement.typ) && !Array.isArray(replacement[TOKENS])) { - replacement[TOKENS] = parseString(replacement.typ == exports.EnumToken.RuleNodeType || replacement.typ === exports.EnumToken.KeyFramesRuleNodeType + replacement[TOKENS] = parseString(replacement.typ == exports.EnumToken.RuleNodeType || replacement.typ === exports.EnumToken.KeyframesRuleNodeType ? replacement.sel : replacement.nam); } - const result = feature.run(replacement, options, parent[PARENT] ?? ast, context, exports.FeatureWalkMode.Pre); + const result = feature.run(replacement, options2, parent[PARENT] ?? ast, context, exports.FeatureWalkMode.Pre); if (result != null) { replacement = result; } @@ -22485,14 +22903,14 @@ function minify(ast, options = {}, recursive = false, errors, nestingContent, co } } } - for (const feature of options.features) { + for (const feature of options2.features) { if (feature.processMode & exports.FeatureWalkMode.Pre && "cleanup" in feature) { // @ts-ignore - feature.cleanup(ast, options, context, exports.FeatureWalkMode.Pre); + feature.cleanup(ast, options2, context, exports.FeatureWalkMode.Pre); } } } - doMinify(ast, options, recursive, errors, nestingContent, context); + doMinify(ast, options2, recursive, errors, nestingContent, context); parents = new Set([ast]); for (const parent of parents) { if (parent.typ == exports.EnumToken.CommentTokenType || parent.typ == exports.EnumToken.CDOCOMMTokenType) { @@ -22500,12 +22918,12 @@ function minify(ast, options = {}, recursive = false, errors, nestingContent, co } replacement = parent; if (postprocess) { - for (const feature of options.features) { + for (const feature of options2.features) { if ((feature.processMode & exports.FeatureWalkMode.Post) === 0 || (feature.accept != null && !feature.accept.has(parent.typ))) { continue; } - const result = feature.run(replacement, options, parent[PARENT] ?? ast, context, exports.FeatureWalkMode.Post); + const result = feature.run(replacement, options2, parent[PARENT] ?? ast, context, exports.FeatureWalkMode.Post); if (result != null) { replacement = result; } @@ -22526,10 +22944,10 @@ function minify(ast, options = {}, recursive = false, errors, nestingContent, co } } if (postprocess) { - for (const feature of options.features) { + for (const feature of options2.features) { if (feature.processMode & exports.FeatureWalkMode.Post && "cleanup" in feature) { // @ts-ignore - feature.cleanup(ast, options, context, exports.FeatureWalkMode.Post); + feature.cleanup(ast, options2, context, exports.FeatureWalkMode.Post); } } } @@ -22622,9 +23040,9 @@ function transformAtRuleMediaPrelude(values) { * Minify at-rule media * - remove redundant tokens * - generate range queries - * @param ast * * @private + * @param tokens */ function minifyAtRuleMedia(tokens) { let hasUpdates = false; @@ -22722,7 +23140,6 @@ function doMinify(ast, options = {}, recursive = false, errors, nestingContent, } while (previous?.typ === exports.EnumToken.CommentNodeType) { previous = ast.chi[--nodeIndex]; - continue; } node = ast.chi[i]; if (node.typ === exports.EnumToken.AtRuleNodeType && node.nam === "font-face") { @@ -22738,8 +23155,8 @@ function doMinify(ast, options = {}, recursive = false, errors, nestingContent, continue; } } - else if (node.typ === exports.EnumToken.KeyFramesRuleNodeType) { - if (previous?.typ === exports.EnumToken.KeyFramesRuleNodeType && + else if (node.typ === exports.EnumToken.KeyframesRuleNodeType) { + if (previous?.typ === exports.EnumToken.KeyframesRuleNodeType && node.sel === previous.sel) { // do not merge keyframes // https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/At-rules/@keyframes#resolving_duplicates @@ -23027,7 +23444,7 @@ function doMinify(ast, options = {}, recursive = false, errors, nestingContent, } if (shouldMerge) { if (((node.typ === exports.EnumToken.RuleNodeType || - node.typ === exports.EnumToken.KeyFramesRuleNodeType) && + node.typ === exports.EnumToken.KeyframesRuleNodeType) && node.sel === previous.sel) || (node.typ == exports.EnumToken.AtRuleNodeType && node.nam !== "font-face" && @@ -23040,7 +23457,7 @@ function doMinify(ast, options = {}, recursive = false, errors, nestingContent, continue; } else if (node.typ == previous?.typ && - [exports.EnumToken.KeyFramesRuleNodeType, exports.EnumToken.RuleNodeType].includes(node.typ)) { + [exports.EnumToken.KeyframesRuleNodeType, exports.EnumToken.RuleNodeType].includes(node.typ)) { const intersect = diff$1(previous, node, options); if (intersect != null) { if (intersect.node1.chi.length == 0) { @@ -23193,7 +23610,9 @@ function optimizeSelector(selector) { } break; } - selector.forEach((selector) => selector.splice(0, optimized.length)); + for (let i1 = 0; i1 < selector.length; i1++) { + selector[i1].splice(0, optimized.length); + } let reducible = optimized.length == 1; if (optimized[0] == "&") { if (optimized[1] == " ") { @@ -23575,7 +23994,6 @@ function wrapNodes(previous, node, match, ast, reducer, i, nodeIndex) { * Diff nodes * @param n1 * @param n2 - * @param reducer * @param options * * @private @@ -23694,17 +24112,36 @@ function diff$1(n1, n2, options = {}) { // @ts-ignore chi: intersect.reverse(), }; + let op = { level: 0, ...options }; if (result == null || [n1, n2].reduce((acc, curr) => { let css = options.cache.get(curr); if (css == null) { - css = doRender(curr, options).code; + let level = 0; + let parent = curr[PARENT]; + while (parent != null && parent.typ != exports.EnumToken.StyleSheetNodeType) { + level++; + parent = parent[PARENT]; + } + op.level = level; + css = doRender(curr, op).code; options.cache.set(curr, css); } return curr.chi.length == 0 ? acc : acc + css.length; }, 0) <= [node1, node2, result].reduce((acc, curr) => { - const css = doRender(curr, options).code; + let css = options.cache.get(curr); + if (css != null) { + return curr.chi.length == 0 ? acc : acc + css.length; + } + let level = 0; + let parent = curr[PARENT]; + while (parent != null && parent.typ != exports.EnumToken.StyleSheetNodeType) { + level++; + parent = parent[PARENT]; + } + op.level = level; + css = doRender(curr, op).code; return curr.chi.length == 0 ? acc : acc + css.length; }, 0)) { if (node1.chi.length != 0 && node2.chi.length != 0) { @@ -23795,11 +24232,16 @@ function reduceRuleSelector(node) { */ function expand(ast) { const result = { ...ast, chi: [] }; + let children; for (let i = 0; i < ast.chi.length; i++) { - const node = ast.chi[i]; + let node = ast.chi[i]; if (node.typ === exports.EnumToken.RuleNodeType) { + children = expandRule(node); + for (const child of children) { + child[PARENT] = result; + } // @ts-ignore - result.chi.push(...expandRule(node)); + result.chi.push(...children); } else if (node.typ == exports.EnumToken.AtRuleNodeType && "chi" in node) { let hasRule = false; @@ -23811,10 +24253,23 @@ function expand(ast) { break; } } - // @ts-ignore - result.chi.push({ ...(hasRule ? expand(node) : node) }); + if (hasRule) { + node = expand(node); + for (const child of node.chi) { + child[PARENT] = result; + } + node[PARENT] = result; + // @ts-ignore + result.chi.push(node); + } + else { + node[PARENT] = result; + // @ts-ignore + result.chi.push(node); + } } else { + node[PARENT] = result; // @ts-ignore result.chi.push(node); } @@ -23839,7 +24294,10 @@ function expandRule(node) { ast.chi.splice(i--, 1); continue; } - selRule.forEach((arr) => combinators.includes(arr[0].charAt(0)) ? arr.unshift(arSelf) : arr.unshift(arSelf, " ")); + for (let i1 = 0; i1 < selRule.length; i1++) { + const arr = selRule[i1]; + combinators.includes(arr[0].charAt(0)) ? arr.unshift(arSelf) : arr.unshift(arSelf, " "); + } rule.sel = selRule .reduce((acc, curr) => { acc.push(curr.join("")); @@ -24031,145 +24489,9 @@ function replaceCompoundLiteral(selector, replace) { .reduce((acc, curr) => acc + (curr == "&" ? replace : curr), ""); } -// from https://github.com/Rich-Harris/vlq/tree/master -// credit: Rich Harris -const integer_to_char = {}; -let i = 0; -for (const char of 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=') { - integer_to_char[i++] = char; -} -function encode(value) { - if (typeof value === 'number') { - return encode_integer(value); - } - let result = ''; - for (let i = 0; i < value.length; i += 1) { - result += encode_integer(value[i]); - } - return result; -} -function encode_integer(num) { - let result = ''; - if (num < 0) { - num = (-num << 1) | 1; - } - else { - num <<= 1; - } - do { - let clamped = num & 31; - num >>>= 5; - if (num > 0) { - clamped |= 32; - } - result += integer_to_char[clamped]; - } while (num > 0); - return result; -} - /** - * Source map class - * @internal + * match url */ -class SourceMap { - /** - * Last location - */ - lastLocation = null; - /** - * Version - * @private - */ - version = 3; - /** - * Sources map - * @private - */ - sourcesMap = []; - /** - * Sources - * @private - */ - sources = []; - /** - * Map - * @private - */ - map = new Map(); - /** - * Line - * @private - */ - line = -1; - /** - * Add a location - * @param source - * @param original - */ - add(newLine, newColumn, srcId, ln, col, sourceFileName, sourceContent) { - if (!this.sourcesMap.includes(srcId)) { - if (sourceFileName == null && sourceContent != null) { - sourceFileName = "data:text/css;charset=utf-8;base64," + btoa(sourceContent); - } - this.sourcesMap.push(srcId); - this.sources.push(sourceFileName || null); - } - const line = newLine - 1; - let record; - if (line > this.line) { - this.line = line; - } - if (!this.map.has(line)) { - record = [Math.max(0, newColumn - 1), this.sourcesMap.indexOf(srcId), ln - 1, col - 1]; - this.map.set(line, [record]); - } - else { - const arr = this.map.get(line); - record = [ - Math.max(0, newColumn - 1 - arr[0][0]), - this.sourcesMap.indexOf(srcId) - arr[0][1], - ln - 1, - col - 1, - ]; - arr.push(record); - } - if (this.lastLocation != null) { - record[2] -= this.lastLocation.ln - 1; - record[3] -= this.lastLocation.col - 1; - } - this.lastLocation ??= { ln, col }; - this.lastLocation.ln = ln; - this.lastLocation.col = col; - } - /** - * Convert to URL encoded string - */ - toUrl() { - // /*# sourceMappingURL = ${url} */ - return `data:application/json;charset=utf-8;base64,${btoa(JSON.stringify(this.toJSON()))}`; - } - /** - * Convert to JSON object - */ - toJSON() { - const mappings = []; - let i = 0; - for (; i <= this.line; i++) { - if (!this.map.has(i)) { - mappings.push(""); - } - else { - mappings.push(this.map.get(i).reduce((acc, curr) => acc + (acc === "" ? "" : ",") + encode(curr), "")); - } - } - return { - version: this.version, - sources: this.sources.slice(), - mappings: mappings.join(";"), - }; - } -} - const matchUrl = /^(https?:)?\/\//; /** * return the directory name of a path @@ -24181,6 +24503,9 @@ function dirname(path) { if (path === "") { return ""; } + if (path.startsWith("data:")) { + return path; + } let i = 0; let parts = [""]; for (; i < path.length; i++) { @@ -24204,10 +24529,7 @@ function splitPath(result) { if (result.length == 0) { return { parts: [], i: 0 }; } - // if (result === "/") { - // return { parts: ["/"], i: 0 }; - // } - const parts = [""]; + const parts = result == "/" ? [] : [""]; let i = 0; for (; i < result.length; i++) { const chr = result.charAt(i); @@ -24216,7 +24538,7 @@ function splitPath(result) { } // else if (chr == "?" || chr == "#") { // break; - // } + // } else { parts[parts.length - 1] += chr; } @@ -24234,6 +24556,8 @@ function splitPath(result) { } /** * Nomalize path + * @param path + * @private */ const normalize = memoize(function (path) { let parts = []; @@ -24262,14 +24586,20 @@ const normalize = memoize(function (path) { while (++k < parts.length) { // if (parts[k] == ".") { // parts.splice(k--, 1); - // } else - if (parts[k] == "..") { + // } else + if (k > 0 && parts[k] == "..") { parts.splice(k - 1, 2); k -= 2; } } return (path.charAt(0) == "/" ? "/" : "") + parts.join("/"); }); +/** + * diff path + * @param path1 + * @param path2 + * @private + */ const diff = memoize(function (path1, path2) { let { parts } = splitPath(path1); const { parts: dirs } = splitPath(path2); @@ -24292,40 +24622,61 @@ const diff = memoize(function (path1, path2) { * @private */ const resolve = memoize(function (url, currentDirectory, cwd) { - cwd ??= ""; - currentDirectory ??= ""; if (matchUrl.test(url)) { return { absolute: url, relative: url, }; } + cwd ??= ""; + currentDirectory ??= ""; url = normalize(url); + if (cwd !== "") { + cwd = normalize(cwd); + } if (currentDirectory !== "") { currentDirectory = normalize(currentDirectory); - if (url.startsWith(currentDirectory + "/")) { - return { - absolute: url, - relative: url.slice(currentDirectory.length + 1), - }; - } - } - if ((currentDirectory === "" || currentDirectory === ".") && cwd !== "") { - cwd = normalize(cwd); - if (url.startsWith(cwd == "/" ? cwd : cwd + "/")) { - const absolute = url; - const prefix = cwd == "/" ? cwd : cwd + "/"; - return { - absolute, - relative: absolute.startsWith(prefix) ? absolute.slice(prefix.length) : diff(absolute, cwd), - }; - } } + const dir = cwd || currentDirectory; + const absolute = dir == "" || url.startsWith("/") ? resolvePath(url) : resolvePath(dir, url); return { - absolute: url, - relative: url === "" ? "" : diff(url, cwd || currentDirectory), + absolute, + relative: dir === "" ? absolute : diff(absolute, dir), }; }); +/** + * + * @param parts + * @returns + * @private + */ +function resolvePath(...parts) { + const path = parts.filter(Boolean).join("/"); + const isAbsolute = /^[\\/]/.test(path); + const segments = path.split(/[\\/]+/); + const resolved = []; + for (const segment of segments) { + if (!segment || segment === ".") { + continue; + } + if (segment === "..") { + if (resolved.length && resolved[resolved.length - 1] !== "..") { + resolved.pop(); + } + else if (!isAbsolute) { + resolved.push(".."); + } + } + else { + resolved.push(segment); + } + } + let result = resolved.join("/"); + if (isAbsolute) { + result = "/" + result; + } + return result || (isAbsolute ? "/" : "."); +} /** * render ast @@ -24378,22 +24729,28 @@ function doRender(data, options = {}, mapping) { const startTime = performance.now(); const errors = []; const sourcemap = options.sourcemap ? new SourceMap() : null; + const sourcemaps = options.sourcemap ? [] : null; const cache = Object.create(null); const sourceLocation = { - srcId: 0, - sta: 0, end: 0, }; - const linesMap = new LineMap([]); + const linesMap = options.sourcemap ? new LineMap() : null; let code = ""; if (mapping != null) { if (mapping.importMapping != null) { - for (const [key, value] of Object.entries(mapping.importMapping)) { + const absolutePath = options.resolve(options.output != null ? dirname(options.output) : dirname(options.src), options.cwd).absolute; + for (let [key, value] of Object.entries(mapping.importMapping)) { + key = options.resolve(options.resolve(key, options.cwd).absolute, absolutePath).relative; + if (!key.startsWith("/") && !key.startsWith(".")) { + key = "./" + key; + } code += `:import("${key}")${options.indent}{${options.newLine}${Object.entries(value).reduce((acc, [k, v]) => acc + (acc.length > 0 ? options.newLine : "") + `${options.indent}${v}:${options.indent}${k};`, "")}${options.newLine}}${options.newLine}`; } } code += `:export${options.indent}{${options.newLine}${Object.entries(mapping.mapping).reduce((acc, [k, v]) => acc + (acc.length > 0 ? options.newLine : "") + `${options.indent}${k}:${options.indent}${v};`, "")}${options.newLine}}${options.newLine}`; - move(sourceLocation, linesMap, code); + if (sourcemap != null) { + move(sourceLocation, linesMap, code); + } } if (options.output != null) { // @ts-ignore @@ -24405,7 +24762,7 @@ function doRender(data, options = {}, mapping) { [exports.EnumToken.StyleSheetNodeType, exports.EnumToken.AtRuleNodeType, exports.EnumToken.RuleNodeType].includes(data.typ) && "chi" in data ? expand(data) - : data, options, sourcemap, sourceLocation, linesMap, errors, function reducer(acc, curr) { + : data, options, sourcemaps, sourceLocation, linesMap, errors, function reducer(acc, curr) { if (curr.typ == exports.EnumToken.CommentTokenType && options.removeComments) { if (!options.preserveLicense || !curr.val.startsWith("/*!")) { return acc; @@ -24420,6 +24777,7 @@ function doRender(data, options = {}, mapping) { }, }; if (sourcemap != null) { + sourcemap.addAll(sourcemaps); result.map = sourcemap; if (options.sourcemap === "inline") { result.code += `\n/*# sourceMappingURL=${result.map.toUrl()} */`; @@ -24432,37 +24790,88 @@ function doRender(data, options = {}, mapping) { * @param node * @param options * @param cache - * @param sourcemap - * @param position + * @param sourcemaps + * @param sourceLocation + * @param linesMap * @param str * * @internal */ -function updateSourceMap(node, options, cache, sourcemap, sourceLocation, linesMap, str) { - if ([ - exports.EnumToken.RuleNodeType, - exports.EnumToken.AtRuleNodeType, - exports.EnumToken.KeyFramesRuleNodeType, - exports.EnumToken.KeyframesAtRuleNodeType, - ].includes(node.typ)) { - let srcId = node[LOC]?.srcId ?? 0; - let sourceFileName = options.sourcesMap?.get(srcId)?.getFileName?.() || null; - if (sourceFileName != null && options.output != null) { - if (cache[sourceFileName] == null) { - cache[sourceFileName] = options.resolve(sourceFileName, dirname(options.output)).relative; +function updateSourceMap(node, options, cache, sourcemaps, sourceLocation, linesMap, str) { + let offset = 0; + while (true) { + if (str.charAt(offset) == options.newLine) { + offset += options.newLine.length; + continue; + } + if (str.charAt(offset) == options.indent) { + offset += options.indent.length; + continue; + } + break; + } + if (offset > 0) { + move(sourceLocation, linesMap, str.slice(0, offset)); + } + if (node[LOC] != null && + [ + exports.EnumToken.RuleNodeType, + exports.EnumToken.AtRuleNodeType, + exports.EnumToken.KeyframesRuleNodeType, + exports.EnumToken.KeyframesAtRuleNodeType, + ].includes(node.typ)) { + const source = options.sourcesMap.get(node[LOC].srcId); + const inputSourceMap = source.getInputSourceMap(); + const offsets = source.getOffsets(node[LOC].sta); + const [newLine, newColumn] = linesMap.getOffsets(sourceLocation.end); + let records = null; + let srcId = node[LOC].srcId; + let sourceFileName = source.getFileName() || null; + let sourceContent = source.getContent() || null; + if (inputSourceMap != null && (records = inputSourceMap.find(offsets[0], offsets[1])) != null) { + for (const record of records) { + // @ts-ignore + sourceFileName = record[0] || null; + // @ts-ignore + offsets[0] = record[1]; + // @ts-ignore + offsets[1] = record[2]; + sourceContent = record[3] || null; + if (sourceFileName != null && options.output != null && !sourceFileName.startsWith("data:")) { + if (cache[sourceFileName] == null) { + const absolute = options.resolve(dirname(options.output), options.cwd) + .absolute; + const absoluteSourcePath = options.resolve(dirname(options.src || ""), options.cwd).absolute; + // resolution is relative to the source file + const absoluteSourceFileName = options.resolve(sourceFileName, absoluteSourcePath) + .absolute; + cache[sourceFileName] = options.resolve(absoluteSourceFileName, absolute).relative; + } + sourceFileName = cache[sourceFileName]; + } + sourcemaps.push([newLine, newColumn, srcId, ...offsets, sourceFileName, sourceContent]); } - sourceFileName = cache[sourceFileName]; } - // @ts-ignore - sourcemap.add(...linesMap.getOffsets(sourceLocation.end), srcId, - // @ts-ignore - ...options.sourcesMap?.get(srcId)?.getOffsets(sourceLocation.sta), sourceFileName, options.sourcesMap?.get(srcId)?.getContent?.()); + else { + if (sourceFileName != null && options.output != null && !sourceFileName.startsWith("data:")) { + if (cache[sourceFileName] == null) { + const absolute = options.resolve(dirname(options.output), options.cwd) + .absolute; + const absoluteSourceFileName = options.resolve(sourceFileName, options.cwd) + .absolute; + cache[sourceFileName] = options.resolve(absoluteSourceFileName, absolute).relative; + } + sourceFileName = cache[sourceFileName]; + } + sourcemaps.push([newLine, newColumn, srcId, ...offsets, sourceFileName, sourceContent]); + } } - move(sourceLocation, linesMap, str); + move(sourceLocation, linesMap, offset > 0 ? str.slice(offset) : str); } /** * Update position - * @param position + * @param sourceLocation + * @param linesMap * @param str */ function move(sourceLocation, linesMap, str) { @@ -24492,8 +24901,9 @@ function move(sourceLocation, linesMap, str) { * render ast node * @param data * @param options - * @param sourcemap - * @param position + * @param sourcemaps + * @param sourceLocation + * @param linesMap * @param errors * @param reducer * @param cache @@ -24502,13 +24912,16 @@ function move(sourceLocation, linesMap, str) { * * @internal */ -function renderAstNode(data, options, sourcemap, sourceLocation, linesMap, errors, reducer, cache, level = 0, indents = []) { +function renderAstNode(data, options, sourcemaps, sourceLocation, linesMap, errors, reducer, cache, level = 0, indents = []) { if (indents.length < level + 1) { indents.push(options.indent.repeat(level)); } if (indents.length < level + 2) { indents.push(options.indent.repeat(level + 1)); } + // @ts-ignore + let children = ""; + let str = ""; const indent = indents[level]; const indentSub = indents[level + 1]; switch (data.typ) { @@ -24519,37 +24932,46 @@ function renderAstNode(data, options, sourcemap, sourceLocation, linesMap, error case exports.EnumToken.CommentNodeType: case exports.EnumToken.CDOCOMMNodeType: if (data.val.startsWith("/*# sourceMappingURL=")) { - // ignore sourcemap + // ignore sourcemap comment return ""; } return !options.removeComments || (options.preserveLicense && data.val.startsWith("/*!")) ? data.val : ""; case exports.EnumToken.StyleSheetNodeType: - return data.chi.reduce((css, node) => { - const hasPreviousContent = css !== ""; - const str = renderAstNode(node, options, sourcemap, sourceLocation, linesMap, errors, reducer, cache, level, indents); + for (const node of data.chi) { + str = renderAstNode(node, options, sourcemaps, sourceLocation, linesMap, errors, reducer, cache, level, indents); if (str === "") { - return css; + continue; } - if (sourcemap != null && node[LOC] != null) { - updateSourceMap(node, options, cache, sourcemap, sourceLocation, linesMap, (hasPreviousContent ? options.newLine : "") + str); + if (children.length > 0) { + str = options.newLine + str; } - if (!hasPreviousContent) { - return str; + children += str; + if (sourcemaps != null && str !== "" && options.newLine) { + move(sourceLocation, linesMap, options.newLine); } - return `${css}${options.newLine}${str}`; - }, ""); + } + return children; case exports.EnumToken.AtRuleNodeType: case exports.EnumToken.RuleNodeType: - case exports.EnumToken.KeyFramesRuleNodeType: + case exports.EnumToken.KeyframesRuleNodeType: case exports.EnumToken.KeyframesAtRuleNodeType: if ([exports.EnumToken.AtRuleNodeType, exports.EnumToken.KeyframesAtRuleNodeType].includes(data.typ) && !("chi" in data)) { return `${indent}@${data.nam}${data.val === "" ? "" : options.indent || " "}${data.val};`; } - // @ts-ignore - let children = data.chi.reduce((css, node) => { - let str; + const prelude = (indent.length > 0 ? options.newLine : "") + + indent + + ([exports.EnumToken.AtRuleNodeType, exports.EnumToken.KeyframesAtRuleNodeType].includes(data.typ) + ? `@${data.nam}${data.val === "" ? "" : options.indent || " "}${data.val}${options.indent}{` + : data.sel + `${options.indent}{`); + if (sourcemaps != null) { + updateSourceMap(data, options, cache, sourcemaps, sourceLocation, linesMap, prelude); + } + let node; + let recordDeclarationSourceMap = data.typ == exports.EnumToken.AtRuleNodeType; + for (let i = 0; i < data.chi.length; i++) { + node = data.chi[i]; if (node.typ == exports.EnumToken.CommentNodeType) { str = options.removeComments && @@ -24558,73 +24980,59 @@ function renderAstNode(data, options, sourcemap, sourceLocation, linesMap, error : node.val; } else if (node.typ == exports.EnumToken.DeclarationNodeType) { - // if (!(node).nam.startsWith("--") && (node).val.length === 0) { - // // @ts-ignore - // errors.push({ - // action: "ignore", - // message: `render: invalid declaration ${JSON.stringify(node)}`, - // location: node[LOC], - // }); - // return ""; - // } str = `${node.nam}:${options.indent}${(options.minify ? filterValues(node.val) : node.val) .reduce(reducer, "") .trimEnd()};`; } - // else if (node.typ == EnumToken.AtRuleNodeType && !("chi" in node)) { - // str = `${(node).val === "" ? "" : options.indent || " "}${(node).val};`; - // } else { - str = renderAstNode(node, options, sourcemap, sourceLocation, linesMap, errors, reducer, cache, level + 1, indents); - } - if (css === "") { - return str; + str = renderAstNode(node, options, sourcemaps, sourceLocation, linesMap, errors, reducer, cache, level + 1, indents); + if (str === "") { + continue; + } + children += str; + str = ""; + continue; } if (str === "") { - return css; + continue; + } + str = options.newLine + indentSub + str; + children += str; + if (sourcemaps != null && str !== "") { + move(sourceLocation, linesMap, str); + if (node.typ == exports.EnumToken.DeclarationNodeType && recordDeclarationSourceMap) { + // if declaration is child of at-rule, then record it + // .rule { + // @media screen { + // color: red; + // } + // } + const source = options.sourcesMap.get(node[LOC].srcId); + sourcemaps.push([ + ...linesMap.getOffsets(sourceLocation.end - str.length + options.newLine.length + indentSub.length), + node[LOC].srcId, + ...source.getOffsets(node[LOC].sta), + source.getFileName(), + source.getContent(), + ]); + } } - return `${css}${options.newLine}${indentSub}${str}`; - }, ""); - if (options.removeEmpty && children === "") { - return ""; } if (children.endsWith(";")) { children = children.slice(0, -1); + sourceLocation.end--; } - const rendered = [exports.EnumToken.AtRuleNodeType, exports.EnumToken.KeyframesAtRuleNodeType].includes(data.typ) - ? `@${data.nam}${data.val === "" ? "" : options.indent || " "}${data.val}${options.indent}{${options.newLine}` + - (children === "" ? "" : indentSub + children + options.newLine) + - indent + - `}` - : data.sel + - `${options.indent}{${options.newLine}` + - (children === "" ? "" : indentSub + children + options.newLine) + - indent + - `}`; - if (sourcemap != null && data[LOC] != null) { - updateSourceMap(data, options, cache, sourcemap, { ...sourceLocation }, linesMap.clone(), rendered); - } - return rendered; - // case EnumToken.CssVariableTokenType: - // case EnumToken.CssVariableImportTokenType: - // return `@value ${(data).val}:${options.indent}${filterValues( - // options.minify - // ? (data).val - // : (data).val, - // ) - // .reduce(reducer, "") - // .trim()};`; - // case EnumToken.CssVariableDeclarationMapTokenType: - // return `@value ${filterValues((data as CssVariableMapTokenType).vars) - // .reduce((acc, curr) => acc + renderValue(curr), "") - // .trim()} from ${filterValues((data as CssVariableMapTokenType).from) - // .reduce((acc, curr) => acc + renderValue(curr), "") - // .trim()};`; - // case EnumToken.InvalidDeclarationNodeType: - // case EnumToken.InvalidRuleNodeType: - // case EnumToken.InvalidAtRuleNodeType: + if (options.removeEmpty && children === "") { + sourceLocation.end -= prelude.length; + return ""; + } + const end = options.newLine + indent + `}`; + if (sourcemaps != null) { + move(sourceLocation, linesMap, end); + } + return prelude + children + end; default: return ""; } @@ -24633,6 +25041,9 @@ function renderAstNode(data, options, sourcemap, sourceLocation, linesMap, error * render ast token * @param token * @param options + * @param cache + * @param reducer + * @param errors * @private */ function renderValue(token, options = {}, cache = Object.create(null), reducer, errors) { @@ -25625,7 +26036,7 @@ function parseSelector(tokens, context, options, errors) { return acc; }, [])); return { - typ: exports.EnumToken.KeyFramesRuleNodeType, + typ: exports.EnumToken.KeyframesRuleNodeType, sel: [ ...splitTokenList(trimArray(tokens)).reduce((acc, curr) => { acc.add(curr.reduce((acc, curr) => acc + renderValue(curr, { minify: false }), "")); @@ -28317,6 +28728,134 @@ const generateSyncScopedName = memoize((localName, filePath, pattern, hashLength // if leading char is digit, prefix underscore (very rare) return (/^[0-9]/.test(result) ? "_" : "") + result; }); +/** + * + * @param visitorsDef + * @param errors + * @private + */ +function parseVisitors(visitorsDef, errors) { + const visitors = Object.entries(typeof visitorsDef === "function" ? [visitorsDef] : visitorsDef); + let key; + let value; + let i; + const valuesHandlers = new Map(); + const preValuesHandlers = new Map(); + const postValuesHandlers = new Map(); + const visitorsHandlersMap = new Map(); + const preVisitorsHandlersMap = new Map(); + const postVisitorsHandlersMap = new Map(); + for (i = 0; i < visitors.length; i++) { + key = visitors[i][0]; + value = visitors[i][1]; + if (Number.isInteger(+key)) { + if (Array.isArray(value)) { + visitors.splice(i + 1, 0, ...Object.entries(value)); + continue; + } + if (typeof value == "function") { + key = value.name; + } + } + if (Array.isArray(value)) { + // @ts-ignore + visitors.splice(i + 1, 0, ...value.map((item) => [key, item])); + continue; + } + if (key in exports.EnumToken) { + if (typeof value == "function") { + if (!valuesHandlers.has(exports.EnumToken[key])) { + valuesHandlers.set(exports.EnumToken[key], []); + } + valuesHandlers.get(exports.EnumToken[key]).push(value); + } + else if (typeof value == "object" && "type" in value && "handler" in value && value.type in exports.WalkerEvent) { + if (value.type == exports.WalkerEvent.Enter) { + if (!preValuesHandlers.has(exports.EnumToken[key])) { + preValuesHandlers.set(exports.EnumToken[key], []); + } + preValuesHandlers.get(exports.EnumToken[key]).push(value.handler); + } + else if (value.type == exports.WalkerEvent.Leave) { + if (!postValuesHandlers.has(exports.EnumToken[key])) { + postValuesHandlers.set(exports.EnumToken[key], []); + } + postValuesHandlers.get(exports.EnumToken[key]).push(value.handler); + } + } + else { + errors.push({ action: "ignore", message: `doParse: visitor.${key} is not a valid key name` }); + } + } + else if (["Declaration", "Rule", "AtRule", "KeyframesRule", "KeyframesAtRule"].includes(key)) { + if (typeof value == "function") { + if (!visitorsHandlersMap.has(key)) { + visitorsHandlersMap.set(key, []); + } + visitorsHandlersMap + .get(key) + .push(value); + } + else if (typeof value == "object") { + if ("type" in value && "handler" in value && value.type in exports.WalkerEvent) { + if (value.type == exports.WalkerEvent.Enter) { + if (!preVisitorsHandlersMap.has(key)) { + preVisitorsHandlersMap.set(key, []); + } + preVisitorsHandlersMap + .get(key) + .push(value.handler); + } + else if (value.type == exports.WalkerEvent.Leave) { + if (!postVisitorsHandlersMap.has(key)) { + postVisitorsHandlersMap.set(key, []); + } + postVisitorsHandlersMap + .get(key) + .push(value.handler); + } + } + else { + if (!visitorsHandlersMap.has(key)) { + visitorsHandlersMap.set(key, []); + } + visitorsHandlersMap + .get(key) + .push(value); + } + } + else { + errors.push({ action: "ignore", message: `doParse: visitor.${key} is not a valid key name` }); + } + } + else { + errors.push({ action: "ignore", message: `doParse: visitor.${key} is not a valid key name` }); + } + } + const allHandlers = []; + if (preVisitorsHandlersMap.size > 0) { + allHandlers.push(preVisitorsHandlersMap); + } + if (preValuesHandlers.size > 0) { + allHandlers.push(preValuesHandlers); + } + if (visitorsHandlersMap.size > 0) { + allHandlers.push(visitorsHandlersMap); + } + if (valuesHandlers.size > 0) { + allHandlers.push(valuesHandlers); + } + if (postVisitorsHandlersMap.size > 0) { + allHandlers.push(postVisitorsHandlersMap); + } + if (postValuesHandlers.size > 0) { + allHandlers.push(postValuesHandlers); + } + return { + allHandlers, + includeTokens: preValuesHandlers.size > 0 || valuesHandlers.size > 0 || postValuesHandlers.size > 0, + }; +} /** * Parse css string * @param iter @@ -28382,131 +28921,24 @@ function doParseSync(iter, options = {}) { }; let tokens = []; let context = ast; - ast[ROOT] = ast; - ast[LOC] = { - sta: 0, - end: 0, - srcId: options.source.id, - }; - let valuesHandlers; - let preValuesHandlers; - let postValuesHandlers; - let preVisitorsHandlersMap; - let visitorsHandlersMap; - let postVisitorsHandlersMap; let item; let node; // @ts-ignore ignore error let parensMatch = 0; let curlyBracketMatch = 0; - if (options.visitor != null) { - valuesHandlers = new Map(); - preValuesHandlers = new Map(); - postValuesHandlers = new Map(); - preVisitorsHandlersMap = new Map(); - visitorsHandlersMap = new Map(); - postVisitorsHandlersMap = new Map(); - const visitors = Object.entries(options.visitor); - let key; - let value; - let i; - for (i = 0; i < visitors.length; i++) { - key = visitors[i][0]; - value = visitors[i][1]; - if (Number.isInteger(+key)) { - visitors.splice(i + 1, 0, ...Object.entries(value)); - continue; - } - if (Array.isArray(value)) { - // @ts-ignore - visitors.splice(i + 1, 0, ...value.map((item) => [key, item])); - continue; - } - if (key in exports.EnumToken) { - if (typeof value == "function") { - if (!valuesHandlers.has(exports.EnumToken[key])) { - valuesHandlers.set(exports.EnumToken[key], []); - } - valuesHandlers.get(exports.EnumToken[key]).push(value); - } - else if (typeof value == "object" && - "type" in value && - "handler" in value && - value.type in exports.WalkerEvent) { - if (value.type == exports.WalkerEvent.Enter) { - if (!preValuesHandlers.has(exports.EnumToken[key])) { - preValuesHandlers.set(exports.EnumToken[key], []); - } - preValuesHandlers - .get(exports.EnumToken[key]) - .push(value.handler); - } - else if (value.type == exports.WalkerEvent.Leave) { - if (!postValuesHandlers.has(exports.EnumToken[key])) { - postValuesHandlers.set(exports.EnumToken[key], []); - } - postValuesHandlers - .get(exports.EnumToken[key]) - .push(value.handler); - } - } - else { - errors.push({ action: "ignore", message: `doParse: visitor.${key} is not a valid key name` }); - } - } - else if (["Declaration", "Rule", "AtRule", "KeyframesRule", "KeyframesAtRule"].includes(key)) { - if (typeof value == "function") { - if (!visitorsHandlersMap.has(key)) { - visitorsHandlersMap.set(key, []); - } - visitorsHandlersMap - .get(key) - .push(value); - } - else if (typeof value == "object") { - if ("type" in value && "handler" in value && value.type in exports.WalkerEvent) { - if (value.type == exports.WalkerEvent.Enter) { - if (!preVisitorsHandlersMap.has(key)) { - preVisitorsHandlersMap.set(key, []); - } - preVisitorsHandlersMap - .get(key) - .push(value.handler); - } - else if (value.type == exports.WalkerEvent.Leave) { - if (!postVisitorsHandlersMap.has(key)) { - postVisitorsHandlersMap.set(key, []); - } - postVisitorsHandlersMap - .get(key) - .push(value.handler); - } - } - else { - if (!visitorsHandlersMap.has(key)) { - visitorsHandlersMap.set(key, []); - } - visitorsHandlersMap - .get(key) - .push(value); - } - } - else { - errors.push({ action: "ignore", message: `doParse: visitor.${key} is not a valid key name` }); - } - } - else { - errors.push({ action: "ignore", message: `doParse: visitor.${key} is not a valid key name` }); - } - } - } - if (Array.isArray(iter)) { - // @ts-expect-error - iter = iter[Symbol.iterator](); - } - while ( - // @ts-expect-error - (item = iter.next().value)) { + let currentItemIndex; + // ast[ROOT] = ast; + ast[LOC] = { + sta: 0, + end: 0, + srcId: options.source.id, + }; + // if (Array.isArray(iter)) { + // // @ts-expect-error + // iter = iter[Symbol.iterator]() as Iterator; + // } + for (currentItemIndex = 0; currentItemIndex < iter.length; currentItemIndex++) { + item = iter[currentItemIndex]; stats.bytesIn = item.bytesIn; stats.tokensCount++; if (BadTokensTypes.includes(item.token.typ)) { @@ -28534,8 +28966,6 @@ function doParseSync(iter, options = {}) { curlyBracketMatch--; } tokens.push(item.token); - // console.debug([item.token, {parensMatch, curlyBracketMatch}]); - // if (parensMatch === 0) { if (parensMatch === 0 && (item.token.typ === exports.EnumToken.SemiColonTokenType || item.token.typ === exports.EnumToken.BlockStartTokenType || @@ -28551,8 +28981,7 @@ function doParseSync(iter, options = {}) { let inBlock = 1; tokens = [item.token]; do { - // @ts-expect-error - item = iter.next().value; + item = iter[++currentItemIndex]; if (item == null) { break; } @@ -28607,198 +29036,160 @@ function doParseSync(iter, options = {}) { ast = expand(ast); } let replacement; - let callable; if (options.visitor != null) { + const handlers = []; + const visitors = parseVisitors(options.visitor, errors); + const subNodes = []; let parens; - for (const result of walk(ast)) { + let genericKey; + let nodes = new Array(stats.tokensCount); + let i; + let k; + let j; + let freeBlock = 1; + nodes[0] = ast; + for (i = 0; i < nodes.length; i++) { + if (nodes[i] == null) { + break; + } + subNodes.length = 0; + if (visitors.includeTokens) { + switch (nodes[i].typ) { + case exports.EnumToken.RuleNodeType: + case exports.EnumToken.AtRuleNodeType: + case exports.EnumToken.KeyframesRuleNodeType: + case exports.EnumToken.KeyframesAtRuleNodeType: + subNodes.push(...nodes[i][TOKENS]); + break; + case exports.EnumToken.DeclarationNodeType: + subNodes.push(...nodes[i].val); + break; + } + } + if (nodes[i].chi != null) { + subNodes.push(...nodes[i].chi); + } + if (subNodes.length > 0) { + if (freeBlock <= i) { + freeBlock = i + 1; + } + for (k = 0; k < subNodes.length; k++) { + j = k + freeBlock; + nodes[j] = subNodes[k]; + nodes[j][PARENT] = nodes[i]; + } + freeBlock += subNodes.length; + } parens = null; - if (valuesHandlers.size > 0 || - preVisitorsHandlersMap.size > 0 || - visitorsHandlersMap.size > 0 || - postVisitorsHandlersMap.size > 0) { - if ((result.node.typ == exports.EnumToken.DeclarationNodeType && - (preVisitorsHandlersMap.has("Declaration") || - visitorsHandlersMap.has("Declaration") || - postVisitorsHandlersMap.has("Declaration"))) || - (result.node.typ == exports.EnumToken.AtRuleNodeType && - (preVisitorsHandlersMap.has("AtRule") || - visitorsHandlersMap.has("AtRule") || - postVisitorsHandlersMap.has("AtRule"))) || - (result.node.typ == exports.EnumToken.KeyframesAtRuleNodeType && - (preVisitorsHandlersMap.has("KeyframesAtRule") || - visitorsHandlersMap.has("KeyframesAtRule") || - postVisitorsHandlersMap.has("KeyframesAtRule")))) { - const handlers = []; - const key = result.node.typ == exports.EnumToken.DeclarationNodeType - ? "Declaration" - : result.node.typ == exports.EnumToken.AtRuleNodeType - ? "AtRule" - : "KeyframesAtRule"; - if (preVisitorsHandlersMap.has(key)) { - handlers.push( - // @ts-expect-error - ...preVisitorsHandlersMap.get(key)); - } - if (visitorsHandlersMap.has(key)) { - // @ts-ignore - handlers.push(...visitorsHandlersMap.get(key)); - } - if (postVisitorsHandlersMap.has(key)) { - // @ts-ignore - handlers.push(...postVisitorsHandlersMap.get(key)); - } - let node = result.node; - for (const handler of handlers) { - callable = - typeof handler == "function" - ? handler - : handler[camelize(node.typ === exports.EnumToken.DeclarationNodeType || - node.typ === exports.EnumToken.AtRuleNodeType - ? node.nam - : node.val)]; - if (callable == null) { - continue; - } - // @ts-expect-error - replacement = callable(node, result[PARENT], ast, function* () { - if (parens == null) { - // @ts-expect-error - parens = [...result.parents()]; - } - yield* parens[Symbol.iterator](); - }); - if (replacement == null) { - continue; - } - if (replacement == null || replacement == node) { - continue; - } - // @ts-ignore - node = replacement; - if (Array.isArray(node)) { - break; + handlers.length = 0; + genericKey = + nodes[i].typ == exports.EnumToken.DeclarationNodeType + ? "Declaration" + : nodes[i].typ == exports.EnumToken.AtRuleNodeType + ? "AtRule" + : nodes[i].typ == exports.EnumToken.KeyframesAtRuleNodeType + ? "KeyframesAtRule" + : nodes[i].typ === exports.EnumToken.KeyframesRuleNodeType + ? "KeyframesRule" + : nodes[i].typ == exports.EnumToken.RuleNodeType + ? "Rule" + : nodes[i].typ == exports.EnumToken.KeyframesRuleNodeType + ? "KeyframesRule" + : null; + let keyName = nodes[i].typ == exports.EnumToken.DeclarationNodeType || nodes[i].typ == exports.EnumToken.AtRuleNodeType + ? camelize(nodes[i].nam) + : nodes[i].typ == exports.EnumToken.KeyframesAtRuleNodeType + ? camelize(nodes[i].val) + : null; + for (const map of visitors.allHandlers) { + // @ts-ignore + if (genericKey != null && map.has(genericKey)) { + // @ts-ignore + for (const handler of map.get(genericKey)) { + if (typeof handler == "function") { + handlers.push(handler); } - } - if (node != result.node) { - replaceNodeOrValue(result.parent, result.node, node); - } - } - else if ((result.node.typ == exports.EnumToken.RuleNodeType && - (preVisitorsHandlersMap.has("Rule") || - visitorsHandlersMap.has("Rule") || - postVisitorsHandlersMap.has("Rule"))) || - (result.node.typ == exports.EnumToken.KeyFramesRuleNodeType && - (preVisitorsHandlersMap.has("KeyframesRule") || - visitorsHandlersMap.has("KeyframesRule") || - postVisitorsHandlersMap.has("KeyframesRule")))) { - const handlers = []; - const key = result.node.typ == exports.EnumToken.RuleNodeType ? "Rule" : "KeyframesRule"; - if (preVisitorsHandlersMap.has(key)) { - handlers.push(...preVisitorsHandlersMap.get(key)); - } - if (visitorsHandlersMap.has(key)) { - handlers.push(...visitorsHandlersMap.get(key)); - } - if (postVisitorsHandlersMap.has(key)) { - handlers.push(...postVisitorsHandlersMap.get(key)); - } - let node = result.node; - for (const callable of handlers) { - replacement = callable(node, result.parent, result.root, - // @ts-expect-error - function* () { - if (parens == null) { - // @ts-expect-error - parens = [...result.parents()]; + else if (Array.isArray(handler)) { + for (const h of handler) { + if (typeof h == "function") { + handlers.push(h); + } + // @ts-ignore + else if (h[keyName] != null) { + // @ts-ignore + handlers.push(h[keyName]); + } } - yield* parens[Symbol.iterator](); - }); - if (replacement == null) { - continue; } - if (replacement == null || replacement == node) { - continue; + else if (typeof handler.handler == "function") { + handlers.push(handler.handler); } // @ts-ignore - node = replacement; - // - if (Array.isArray(node)) { - break; + else if (typeof handler[keyName] == "function") { + // @ts-ignore + handlers.push(handler[keyName]); } } - // @ts-ignore - if (node != result.node) { - // @ts-ignore - replaceNodeOrValue(result.parent, result.node, node); - } } - else if (valuesHandlers.size > 0) { - let node = null; - node = result.node; - if (valuesHandlers.has(node.typ)) { - for (const valueHandler of valuesHandlers.get(node.typ)) { - callable = valueHandler; - replacement = callable(node, result.parent, ast, - // @ts-expect-error - function* () { - if (parens == null) { - // @ts-expect-error - parens = [...result.parents()]; - } - yield* parens[Symbol.iterator](); - }); - if (replacement == null) { - continue; - } - if (replacement != null && replacement != node) { - node = replacement; - } - } - } - if (node != result.node) { - // @ts-ignore - replaceNodeOrValue(result[PARENT], value, node); - } - const tokens = Array.isArray(result.node[TOKENS]) ? result.node[TOKENS] : []; - if (Array.isArray(result.node.val)) { - tokens.push(...result.node.val); - } - if (tokens.length == 0) { - continue; - } - for (const { value, parent, root, parents } of walkValues(tokens, result.node)) { - node = value; - if (valuesHandlers.has(node.typ)) { - let parens = null; - for (const valueHandler of valuesHandlers.get(node.typ)) { - callable = valueHandler; - // @ts-expect-error - let result = callable(node, parent, root, function* () { - if (parens == null) { - // @ts-expect-error - parens = [...parents()]; - } - yield* parens[Symbol.iterator](); - }); - if (result == null) { - continue; - } - if (result != null && result != node) { - node = result; - } - if (Array.isArray(node)) { - break; + // @ts-ignore + if (map.has(nodes[i].typ)) { + // @ts-ignore + for (const handler of map.get(nodes[i].typ)) { + if (typeof handler == "function") { + handlers.push(handler); + } + else if (Array.isArray(handler)) { + for (const h of handler) { + if (typeof h == "function") { + handlers.push(h); } } } - if (node != value) { + else if (typeof handler.handler == "function") { + handlers.push(handler.handler); + } + // @ts-ignore + else if (typeof handler[keyName] == "function") { // @ts-ignore - replaceNodeOrValue(parent, value, node); + handlers.push(handler[keyName]); + } + } + } + } + if (handlers.length == 0) { + continue; + } + let node = nodes[i]; + for (const callable of handlers) { + replacement = callable(node, nodes[i][PARENT], ast, + // @ts-expect-error + function* () { + if (parens == null) { + let node = nodes[i][PARENT]; + while (node != null) { + yield node; + node = node[PARENT]; } } + }); + if (replacement == null) { + continue; + } + if (replacement == node) { + continue; + } + // @ts-ignore + node = replacement; + // + if (Array.isArray(node)) { + break; } } + if (node != nodes[i]) { + replaceNodeOrValue(nodes[i][PARENT], nodes[i], node); + } } + nodes = null; } if (invalidNodes.length > 0) { let count = invalidNodes.length; @@ -28821,19 +29212,6 @@ function doParseSync(iter, options = {}) { } } } - while (stack.length > 0 && context != ast) { - const previousNode = stack.pop(); - context = (stack[stack.length - 1] ?? ast); - // remove empty nodes - if (options.removeEmpty && - previousNode != null && - previousNode.chi.length == 0 && - context.chi[context.chi.length - 1] == previousNode) { - context.chi.pop(); - continue; - } - break; - } if (options.minify) { if (ast.chi.length > 0) { let passes = options.pass ?? 1; @@ -28862,7 +29240,7 @@ function doParseSync(iter, options = {}) { scoped: exports.ModuleScopeEnumOptions.Local, naming: exports.ModuleCaseTransformEnum.IgnoreCase, pattern: "", - generateScopedName, + generateScopedName: generateSyncScopedName, ...(typeof options.module != "object" ? {} : options.module), }; const parseModuleTime = performance.now(); @@ -28968,10 +29346,9 @@ function doParseSync(iter, options = {}) { if (node.typ == exports.EnumToken.DeclarationNodeType) { if (node.nam.startsWith("--")) { if (!(node.nam in namesMapping)) { - let result = moduleSettings.scoped & exports.ModuleScopeEnumOptions.Global + let value = moduleSettings.scoped & exports.ModuleScopeEnumOptions.Global ? node.nam : moduleSettings.generateScopedName(node.nam, moduleSettings.filePath, moduleSettings.pattern, moduleSettings.hashLength); - let value = result; mapping[node.nam] = "--" + (moduleSettings.naming & exports.ModuleCaseTransformEnum.DashCaseOnly || @@ -29013,10 +29390,9 @@ function doParseSync(iter, options = {}) { continue; } if (!(rule.val in mapping)) { - let result = moduleSettings.scoped & exports.ModuleScopeEnumOptions.Global + let value = moduleSettings.scoped & exports.ModuleScopeEnumOptions.Global ? rule.val : moduleSettings.generateScopedName(rule.val, moduleSettings.filePath, moduleSettings.pattern, moduleSettings.hashLength); - let value = result; mapping[rule.val] = (rule.typ == exports.EnumToken.DashedIdenTokenType ? "--" : "") + (moduleSettings.naming & exports.ModuleCaseTransformEnum.DashCaseOnly || @@ -29187,10 +29563,10 @@ function doParseSync(iter, options = {}) { "unset", ].includes(value.val)) { if (!(value.val in mapping)) { - const result = moduleSettings.scoped & exports.ModuleScopeEnumOptions.Global - ? value.val - : moduleSettings.generateScopedName(value.val, moduleSettings.filePath, moduleSettings.pattern, moduleSettings.hashLength); - mapping[value.val] = result; + mapping[value.val] = + moduleSettings.scoped & exports.ModuleScopeEnumOptions.Global + ? value.val + : moduleSettings.generateScopedName(value.val, moduleSettings.filePath, moduleSettings.pattern, moduleSettings.hashLength); revMapping[mapping[value.val]] = value.val; } value.val = mapping[value.val]; @@ -29262,10 +29638,9 @@ function doParseSync(iter, options = {}) { if (value.typ == exports.EnumToken.ClassSelectorTokenType) { const val = value.val.slice(1); if (!(val in mapping)) { - const result = moduleSettings.scoped & exports.ModuleScopeEnumOptions.Global + let value = moduleSettings.scoped & exports.ModuleScopeEnumOptions.Global ? val : moduleSettings.generateScopedName(val, moduleSettings.filePath, moduleSettings.pattern, moduleSettings.hashLength); - let value = result; mapping[val] = moduleSettings.naming & exports.ModuleCaseTransformEnum.DashCaseOnly || moduleSettings.naming & exports.ModuleCaseTransformEnum.CamelCaseOnly @@ -29298,10 +29673,9 @@ function doParseSync(iter, options = {}) { if ((prefix == "--" && value.typ == exports.EnumToken.DashedIdenTokenType) || (prefix == "" && value.typ == exports.EnumToken.IdenTokenType)) { if (!(value.val in mapping)) { - const result = moduleSettings.scoped & exports.ModuleScopeEnumOptions.Global + let val = moduleSettings.scoped & exports.ModuleScopeEnumOptions.Global ? value.val : moduleSettings.generateScopedName(value.val, moduleSettings.filePath, moduleSettings.pattern, moduleSettings.hashLength); - let val = result; mapping[value.val] = prefix + (moduleSettings.naming & exports.ModuleCaseTransformEnum.DashCaseOnly || @@ -29319,12 +29693,13 @@ function doParseSync(iter, options = {}) { } if (moduleSettings.naming != exports.ModuleCaseTransformEnum.IgnoreCase) { revMapping = {}; - mapping = Object.entries(mapping).reduce((acc, [key, value]) => { - const keyName = getKeyName(key, moduleSettings.naming); - acc[keyName] = value; + mapping = {}; + let keyName; + for (const [key, value] of Object.entries(mapping)) { + keyName = getKeyName(key, moduleSettings.naming); + mapping[keyName] = value; revMapping[value] = keyName; - return acc; - }, {}); + } } result.mapping = mapping; result.revMapping = revMapping; @@ -29409,18 +29784,6 @@ async function doParse(iter, options = {}) { }; let tokens = []; let context = ast; - ast[ROOT] = ast; - ast[LOC] = { - sta: 0, - end: 0, - srcId: options.source.id, - }; - let valuesHandlers; - let preValuesHandlers; - let postValuesHandlers; - let preVisitorsHandlersMap; - let visitorsHandlersMap; - let postVisitorsHandlersMap; const imports = []; let item; let node; @@ -29428,107 +29791,12 @@ async function doParse(iter, options = {}) { let isAsync = typeof iter[Symbol.asyncIterator] === "function"; let parensMatch = 0; let curlyBracketMatch = 0; - if (options.visitor != null) { - valuesHandlers = new Map(); - preValuesHandlers = new Map(); - postValuesHandlers = new Map(); - preVisitorsHandlersMap = new Map(); - visitorsHandlersMap = new Map(); - postVisitorsHandlersMap = new Map(); - const visitors = Object.entries(options.visitor); - let key; - let value; - let i; - for (i = 0; i < visitors.length; i++) { - key = visitors[i][0]; - value = visitors[i][1]; - if (Number.isInteger(+key)) { - visitors.splice(i + 1, 0, ...Object.entries(value)); - continue; - } - if (Array.isArray(value)) { - // @ts-ignore - visitors.splice(i + 1, 0, ...value.map((item) => [key, item])); - continue; - } - if (key in exports.EnumToken) { - if (typeof value == "function") { - if (!valuesHandlers.has(exports.EnumToken[key])) { - valuesHandlers.set(exports.EnumToken[key], []); - } - valuesHandlers.get(exports.EnumToken[key]).push(value); - } - else if (typeof value == "object" && - "type" in value && - "handler" in value && - value.type in exports.WalkerEvent) { - if (value.type == exports.WalkerEvent.Enter) { - if (!preValuesHandlers.has(exports.EnumToken[key])) { - preValuesHandlers.set(exports.EnumToken[key], []); - } - preValuesHandlers - .get(exports.EnumToken[key]) - .push(value.handler); - } - else if (value.type == exports.WalkerEvent.Leave) { - if (!postValuesHandlers.has(exports.EnumToken[key])) { - postValuesHandlers.set(exports.EnumToken[key], []); - } - postValuesHandlers - .get(exports.EnumToken[key]) - .push(value.handler); - } - } - else { - errors.push({ action: "ignore", message: `doParse: visitor.${key} is not a valid key name` }); - } - } - else if (["Declaration", "Rule", "AtRule", "KeyframesRule", "KeyframesAtRule"].includes(key)) { - if (typeof value == "function") { - if (!visitorsHandlersMap.has(key)) { - visitorsHandlersMap.set(key, []); - } - visitorsHandlersMap - .get(key) - .push(value); - } - else if (typeof value == "object") { - if ("type" in value && "handler" in value && value.type in exports.WalkerEvent) { - if (value.type == exports.WalkerEvent.Enter) { - if (!preVisitorsHandlersMap.has(key)) { - preVisitorsHandlersMap.set(key, []); - } - preVisitorsHandlersMap - .get(key) - .push(value.handler); - } - else if (value.type == exports.WalkerEvent.Leave) { - if (!postVisitorsHandlersMap.has(key)) { - postVisitorsHandlersMap.set(key, []); - } - postVisitorsHandlersMap - .get(key) - .push(value.handler); - } - } - else { - if (!visitorsHandlersMap.has(key)) { - visitorsHandlersMap.set(key, []); - } - visitorsHandlersMap - .get(key) - .push(value); - } - } - else { - errors.push({ action: "ignore", message: `doParse: visitor.${key} is not a valid key name` }); - } - } - else { - errors.push({ action: "ignore", message: `doParse: visitor.${key} is not a valid key name` }); - } - } - } + // ast[ROOT] = ast; + ast[LOC] = { + sta: 0, + end: 0, + srcId: options.source.id, + }; if (Array.isArray(iter)) { // @ts-expect-error iter = iter[Symbol.iterator](); @@ -29650,7 +29918,7 @@ async function doParse(iter, options = {}) { const token = node[TOKENS][0]; const url = token.typ == exports.EnumToken.StringTokenType ? token.val.slice(1, -1) : token.val; try { - const src = options.resolve(url, options.src || options.cwd); + const src = options.resolve(url, options.src ? dirname(options.src) : options.cwd); const result = options.load(src); const stream = result instanceof Promise || Object.getPrototypeOf(result).constructor.name == "AsyncFunction" ? await result @@ -29690,210 +29958,163 @@ async function doParse(iter, options = {}) { ast = expand(ast); } let replacement; - let callable; if (options.visitor != null) { let parens; - for (const result of walk(ast)) { + let genericKey; + const handlers = []; + const visitors = parseVisitors(options.visitor, errors); + let nodes = new Array(stats.tokensCount); + const subNodes = []; + let i; + let k; + let j; + let freeblock = 1; + nodes[0] = ast; + for (i = 0; i < nodes.length; i++) { + if (nodes[i] == null) { + break; + } + subNodes.length = 0; + if (visitors.includeTokens) { + switch (nodes[i].typ) { + case exports.EnumToken.RuleNodeType: + case exports.EnumToken.AtRuleNodeType: + case exports.EnumToken.KeyframesRuleNodeType: + case exports.EnumToken.KeyframesAtRuleNodeType: + subNodes.push(...nodes[i][TOKENS]); + break; + case exports.EnumToken.DeclarationNodeType: + subNodes.push(...nodes[i].val); + break; + } + } + if (nodes[i].chi != null) { + subNodes.push(...nodes[i].chi); + } + if (subNodes.length > 0) { + if (freeblock <= i) { + freeblock = i + 1; + } + for (k = 0; k < subNodes.length; k++) { + j = k + freeblock; + nodes[j] = subNodes[k]; + nodes[j][PARENT] = nodes[i]; + } + freeblock += subNodes.length; + } parens = null; - if (valuesHandlers.size > 0 || - preVisitorsHandlersMap.size > 0 || - visitorsHandlersMap.size > 0 || - postVisitorsHandlersMap.size > 0) { - if ((result.node.typ == exports.EnumToken.DeclarationNodeType && - (preVisitorsHandlersMap.has("Declaration") || - visitorsHandlersMap.has("Declaration") || - postVisitorsHandlersMap.has("Declaration"))) || - (result.node.typ == exports.EnumToken.AtRuleNodeType && - (preVisitorsHandlersMap.has("AtRule") || - visitorsHandlersMap.has("AtRule") || - postVisitorsHandlersMap.has("AtRule"))) || - (result.node.typ == exports.EnumToken.KeyframesAtRuleNodeType && - (preVisitorsHandlersMap.has("KeyframesAtRule") || - visitorsHandlersMap.has("KeyframesAtRule") || - postVisitorsHandlersMap.has("KeyframesAtRule")))) { - const handlers = []; - const key = result.node.typ == exports.EnumToken.DeclarationNodeType - ? "Declaration" - : result.node.typ == exports.EnumToken.AtRuleNodeType - ? "AtRule" - : "KeyframesAtRule"; - if (preVisitorsHandlersMap.has(key)) { - handlers.push( - // @ts-expect-error - ...preVisitorsHandlersMap.get(key)); - } - if (visitorsHandlersMap.has(key)) { - // @ts-ignore - handlers.push(...visitorsHandlersMap.get(key)); - } - if (postVisitorsHandlersMap.has(key)) { - // @ts-ignore - handlers.push(...postVisitorsHandlersMap.get(key)); - } - let node = result.node; - for (const handler of handlers) { - callable = - typeof handler == "function" - ? handler - : handler[camelize(node.typ === exports.EnumToken.DeclarationNodeType || - node.typ === exports.EnumToken.AtRuleNodeType - ? node.nam - : node.val)]; - if (callable == null) { - continue; + handlers.length = 0; + genericKey = + nodes[i].typ == exports.EnumToken.DeclarationNodeType + ? "Declaration" + : nodes[i].typ == exports.EnumToken.AtRuleNodeType + ? "AtRule" + : nodes[i].typ == exports.EnumToken.KeyframesAtRuleNodeType + ? "KeyframesAtRule" + : nodes[i].typ === exports.EnumToken.KeyframesRuleNodeType + ? "KeyframesRule" + : nodes[i].typ == exports.EnumToken.RuleNodeType + ? "Rule" + : nodes[i].typ == exports.EnumToken.KeyframesRuleNodeType + ? "KeyframesRule" + : null; + let keyName = nodes[i].typ == exports.EnumToken.DeclarationNodeType || nodes[i].typ == exports.EnumToken.AtRuleNodeType + ? camelize(nodes[i].nam) + : nodes[i].typ == exports.EnumToken.KeyframesAtRuleNodeType + ? camelize(nodes[i].val) + : null; + for (const map of visitors.allHandlers) { + // @ts-ignore + if (genericKey != null && map.has(genericKey)) { + // @ts-ignore + for (const handler of map.get(genericKey)) { + if (typeof handler == "function") { + handlers.push(handler); } - // @ts-expect-error - replacement = callable(node, result[PARENT], ast, function* () { - if (parens == null) { - // @ts-expect-error - parens = [...result.parents()]; + else if (Array.isArray(handler)) { + for (const h of handler) { + if (typeof h == "function") { + handlers.push(h); + } + // @ts-ignore + else if (h[keyName] != null) { + // @ts-ignore + handlers.push(h[keyName]); + } } - yield* parens[Symbol.iterator](); - }); - if (replacement == null) { - continue; - } - if (replacement instanceof Promise) { - replacement = await replacement; } - if (replacement == null || replacement == node) { - continue; + else if (typeof handler.handler == "function") { + handlers.push(handler.handler); } // @ts-ignore - node = replacement; - if (Array.isArray(node)) { - break; + else if (typeof handler[keyName] == "function") { + // @ts-ignore + handlers.push(handler[keyName]); } } - if (node != result.node) { - replaceNodeOrValue(result.parent, result.node, node); - } } - else if ((result.node.typ == exports.EnumToken.RuleNodeType && - (preVisitorsHandlersMap.has("Rule") || - visitorsHandlersMap.has("Rule") || - postVisitorsHandlersMap.has("Rule"))) || - (result.node.typ == exports.EnumToken.KeyFramesRuleNodeType && - (preVisitorsHandlersMap.has("KeyframesRule") || - visitorsHandlersMap.has("KeyframesRule") || - postVisitorsHandlersMap.has("KeyframesRule")))) { - const handlers = []; - const key = result.node.typ == exports.EnumToken.RuleNodeType ? "Rule" : "KeyframesRule"; - if (preVisitorsHandlersMap.has(key)) { - handlers.push(...preVisitorsHandlersMap.get(key)); - } - if (visitorsHandlersMap.has(key)) { - handlers.push(...visitorsHandlersMap.get(key)); - } - if (postVisitorsHandlersMap.has(key)) { - handlers.push(...postVisitorsHandlersMap.get(key)); - } - let node = result.node; - for (const callable of handlers) { - replacement = callable(node, result.parent, result.root, - // @ts-expect-error - function* () { - if (parens == null) { - // @ts-expect-error - parens = [...result.parents()]; - } - yield* parens[Symbol.iterator](); - }); - if (replacement == null) { - continue; + // @ts-ignore + if (map.has(nodes[i].typ)) { + // @ts-ignore + for (const handler of map.get(nodes[i].typ)) { + if (typeof handler == "function") { + handlers.push(handler); } - if (replacement instanceof Promise) { - replacement = await replacement; + else if (Array.isArray(handler)) { + for (const h of handler) { + if (typeof h == "function") { + handlers.push(h); + } + } } - if (replacement == null || replacement == node) { - continue; + else if (typeof handler.handler == "function") { + handlers.push(handler.handler); } // @ts-ignore - node = replacement; - // - if (Array.isArray(node)) { - break; + else if (typeof handler[keyName] == "function") { + // @ts-ignore + handlers.push(handler[keyName]); } } - // @ts-ignore - if (node != result.node) { - // @ts-ignore - replaceNodeOrValue(result.parent, result.node, node); - } } - else if (valuesHandlers.size > 0) { - let node = null; - node = result.node; - if (valuesHandlers.has(node.typ)) { - for (const valueHandler of valuesHandlers.get(node.typ)) { - callable = valueHandler; - replacement = callable(node, result.parent, ast, - // @ts-expect-error - function* () { - if (parens == null) { - // @ts-expect-error - parens = [...result.parents()]; - } - yield* parens[Symbol.iterator](); - }); - if (replacement == null) { - continue; - } - if (replacement instanceof Promise) { - replacement = await replacement; - } - if (replacement != null && replacement != node) { - node = replacement; - } - } - } - if (node != result.node) { - // @ts-ignore - replaceNodeOrValue(result[PARENT], value, node); - } - const tokens = Array.isArray(result.node[TOKENS]) ? result.node[TOKENS] : []; - if (Array.isArray(result.node.val)) { - tokens.push(...result.node.val); - } - if (tokens.length == 0) { - continue; - } - for (const { value, parent, root, parents } of walkValues(tokens, result.node)) { - node = value; - if (valuesHandlers.has(node.typ)) { - let parens = null; - for (const valueHandler of valuesHandlers.get(node.typ)) { - callable = valueHandler; - // @ts-expect-error - let result = callable(node, parent, root, function* () { - if (parens == null) { - // @ts-expect-error - parens = [...parents()]; - } - yield* parens[Symbol.iterator](); - }); - if (result == null) { - continue; - } - if (result instanceof Promise) { - result = await result; - } - if (result != null && result != node) { - node = result; - } - if (Array.isArray(node)) { - break; - } - } - } - if (node != value) { - // @ts-ignore - replaceNodeOrValue(parent, value, node); + } + if (handlers.length == 0) { + continue; + } + let node = nodes[i]; + for (const callable of handlers) { + replacement = callable(node, nodes[i][PARENT], ast, + // @ts-expect-error + function* () { + if (parens == null) { + let node = nodes[i][PARENT]; + while (node != null) { + yield node; + node = node[PARENT]; } } + }); + if (replacement == null) { + continue; + } + if (replacement instanceof Promise) { + replacement = await replacement; + } + if (replacement == null || replacement == node) { + continue; + } + // @ts-ignore + node = replacement; + // + if (Array.isArray(node)) { + break; } } + if (node != nodes[i]) { + replaceNodeOrValue(nodes[i][PARENT], nodes[i], node); + } } + nodes = null; } if (invalidNodes.length > 0) { let count = invalidNodes.length; @@ -29916,19 +30137,6 @@ async function doParse(iter, options = {}) { } } } - while (stack.length > 0 && context != ast) { - const previousNode = stack.pop(); - context = (stack[stack.length - 1] ?? ast); - // remove empty nodes - if (options.removeEmpty && - previousNode != null && - previousNode.chi.length == 0 && - context.chi[context.chi.length - 1] == previousNode) { - context.chi.pop(); - continue; - } - break; - } if (options.minify) { if (ast.chi.length > 0) { let passes = options.pass ?? 1; @@ -30124,6 +30332,7 @@ async function doParse(iter, options = {}) { parentRule.chi.splice(parentRule.chi.indexOf(node), 1); continue; } + const resolvedSrc = options.resolve(options.src, options.cwd); for (const token of composeSelectors) { // composes: a b c; if (token.r == null) { @@ -30193,8 +30402,10 @@ async function doParse(iter, options = {}) { setParent: false, src: src.relative, })); - const srcIndex = (src.relative.startsWith("/") || src.relative.startsWith("../") ? "" : "./") + - src.relative; + let srcIndex = options.resolve(src.absolute, resolvedSrc.absolute).relative; + if (!srcIndex.startsWith("/") && !srcIndex.startsWith("../")) { + srcIndex = `./${srcIndex}`; + } if (Object.keys(root.mapping).length > 0) { importMapping[srcIndex] = {}; } @@ -30446,26 +30657,6 @@ async function doParse(iter, options = {}) { exports.EnumToken.DescendantCombinatorTokenType) { parent[TOKENS].splice(index, 1); } - // if (val == ":global") { - // for (; index < (parent as AstRule)[TOKENS]!.length; index++) { - // if ( - // (parent as AstRule)[TOKENS]![index].typ == - // EnumToken.CommaTokenType || - // ([ - // EnumToken.PseudoClassFuncTokenType, - // EnumToken.PseudoClassTokenType, - // ].includes((parent as AstRule)[TOKENS]![index].typ) && - // [":global", ":local"].includes( - // ( - // (parent as AstRule)[TOKENS]![index] as PseudoClassToken - // ).val.toLowerCase(), - // )) - // ) { - // break; - // } - // global.add((parent as AstRule)[TOKENS]![index]); - // } - // } } break; } @@ -30479,12 +30670,6 @@ async function doParse(iter, options = {}) { case ":local": parent[TOKENS].splice(parent[TOKENS].indexOf(value), 1, ...value.chi); break; - // (parent as AstRule)[TOKENS]!.splice( - // (parent as AstRule)[TOKENS]!.indexOf(value), - // 1, - // ...(value as FunctionToken).chi, - // ); - // break; } } })) { @@ -30770,6 +30955,8 @@ function parseNode(tokens, context, options, errors, stats, invalidNodes) { return null; } /** + * @param stream + * @param context * @param options * @param errors * @param parseAsBlock @@ -30846,7 +31033,6 @@ function parseAtRule(stream, context, options, errors, parseAsBlock = null) { parseAsBlock = blockAllowed; } if (syntax != null && atRule.nam !== "layer" && parseAsBlock !== blockAllowed) { - success = false; errors.push({ action: "drop", node: atRule, @@ -31334,7 +31520,7 @@ function parseAtRule(stream, context, options, errors, parseAsBlock = null) { action: "drop", node: atRule, location: options.source.getSourceLocation(atRule[LOC].sta), - message: "node is allowd only in @page rule", + message: "node is allowed only in @page rule", }); } else { @@ -31473,9 +31659,6 @@ function parseAtRule(stream, context, options, errors, parseAsBlock = null) { if (result.errors.length > 0) { errors.push(...result.errors); } - // else if (atRuleName === "document") { - // parseUrlToken(stream); - // } if (result.success) { let i = 0; const stack = []; @@ -31571,12 +31754,14 @@ function parseString(src, options = { parseColor: true }, errors) { position: 0, currentPosition: -1, }; - const result = parseTokens([...tokenize(parseInfo)].map((t) => t.token), options, errors); - // remove EOF token - result.pop(); - if (result.at(-1)?.typ === exports.EnumToken.WhitespaceTokenType) { - result.pop(); + const tokenResults = tokenize(parseInfo); + const mapped = []; + for (const token of tokenResults) { + mapped.push(token.token); } + const result = parseTokens(mapped, options, errors); + // remove EOF token + result.splice(result.length - (result[result.length - 2]?.typ === exports.EnumToken.WhitespaceTokenType ? 2 : 1), 2); return result; } /** @@ -31667,7 +31852,6 @@ function parseTokens(tokens, options, errors) { node, location: options.source.getSourceLocation(node[LOC].sta), }); - // return []; continue; } index = tokens.indexOf(stack.at(-1)); @@ -31814,6 +31998,67 @@ exports.ResponseType = void 0; ResponseType[ResponseType["ArrayBuffer"] = 2] = "ArrayBuffer"; })(exports.ResponseType || (exports.ResponseType = {})); +/** + * parse result. process input sourcemap + * @param result + * @param options + * @returns + * @private + */ +function parseResult(result, options) { + if (options.sourcemap != null && options.source.getInputSourceMap() == null) { + if (options.inputSourceMap != null) { + options.source.setInputSourceMap(options.inputSourceMap); + } + else { + // extract inline source map from the input CSS + const token = result.ast.chi.at(-1); + if (token?.typ == exports.EnumToken.CommentTokenType && + token.val.startsWith("/*# sourceMappingURL=")) { + const data = token.val.slice(21, -2).trim(); + let sourcemap; + let encoding = ""; + if (data.startsWith("data:")) { + let offset = data.indexOf(",") + 1; + if (offset == 0) { + offset = data.lastIndexOf(";") + 1; + } + else { + encoding = data.slice(data.lastIndexOf(";") + 1, offset - 1); + } + if (encoding == "base64") { + sourcemap = atob(data.slice(offset)); + } + else { + sourcemap = decodeURIComponent(data.slice(offset)); + } + options.source.setInputSourceMap(sourcemap); + } + } + } + } + if (options.module) { + const { revMapping, ...res } = result; + return res; + } + return result; +} +function validateSyncArguments(options, prefix = "options.") { + const args = Object.entries(options); + let i; + for (i = 0; i < args.length; i++) { + const [key, value] = args[i]; + if (typeof value == 'function') { + if (value instanceof Promise || Object.getPrototypeOf(value).constructor.name == "AsyncFunction") { + throw new Error(`[${prefix + key}]: Async functions are not supported in sync mode. Use parse() or transform() instead.`); + } + } + else if (value != null && typeof value == 'object') { + validateSyncArguments(value, prefix + key + "."); + } + } +} + /** * Load file or url * @param url @@ -31925,15 +32170,16 @@ const parseFile = node_util.deprecate(async (file, options = {}, asStream = fals /** * Parse css * @param args + * @private * * Parsing a string * * ```ts * - * import {parse} from '@tbela99/css-parser'; + * import {parseSync} from '@tbela99/css-parser'; * * // css string - * let result = await parse(css, {nestingRules: true}); + * let result = parseSync(css, {nestingRules: true}); * console.log(result.ast); * ``` * @@ -31950,9 +32196,12 @@ function parseSync(...args) { options = opt; stream = input; } + if (options != null) { + validateSyncArguments(options); + } options ??= {}; options.src ??= ""; - options.sourcesMap ??= new Map; + options.sourcesMap ??= new Map(); Object.assign(options, { resolve, dirname, @@ -31975,23 +32224,22 @@ function parseSync(...args) { currentPosition: -1, }; const result = doParseSync(tokenize(options.parseInfo), options); - const { revMapping, ...res } = result; - return res; + return !options.module && !options.inputSourceMap && !options.sourcemap ? result : parseResult(result, options); } /** * Transform css - * @param css - * @param options * * ```ts * - * import {transform} from '@tbela99/css-parser'; + * import {transformSync} from '@tbela99/css-parser'; * * // css string - * const result = await transform(css); + * const result = transformSync(css); * console.log(result.code); * ``` * + * @param args + * @private */ function transformSync(...args) { let options; @@ -32044,6 +32292,7 @@ function transformSync(...args) { * @param args * * @throws Error file not found + * @private * * Parsing a string * @@ -32103,7 +32352,7 @@ async function parse(...args) { } options ??= {}; options.src ??= ""; - options.sourcesMap ??= new Map; + options.sourcesMap ??= new Map(); Object.assign(options, { load, resolve, @@ -32126,13 +32375,10 @@ async function parse(...args) { position: 0, currentPosition: -1, }; - return doParse(stream instanceof ReadableStream ? tokenizeStream(stream, options.parseInfo) : tokenize(options.parseInfo), options).then((result) => { - const { revMapping, ...res } = result; - return res; - }); + return doParse(stream instanceof ReadableStream ? tokenizeStream(stream, options.parseInfo) : tokenize(options.parseInfo), options).then((result) => (!options.module && !options.inputSourceMap ? result : parseResult(result, options))); } /** - * Transform css file + * Transform CSS file * @param file url or path * @param options * @param asStream load file as stream @@ -32162,8 +32408,6 @@ const transformFile = node_util.deprecate(async (file, options = {}, asStream = }), "transformFile is deprecated, use transform instead as transform({file, asStream, ...options})"); /** * Transform css - * @param css - * @param options * * Parsing a string * @@ -32202,6 +32446,8 @@ const transformFile = node_util.deprecate(async (file, options = {}, asStream = * * console.log(result.code); * ``` + * @param args + * @private */ async function transform(...args) { let options; diff --git a/dist/index.d.ts b/dist/index.d.ts index 1eae9a4f..695682dd 100644 --- a/dist/index.d.ts +++ b/dist/index.d.ts @@ -379,7 +379,7 @@ declare enum EnumToken { /** * keyframe rule node type */ - KeyFramesRuleNodeType = 73, + KeyframesRuleNodeType = 73, /** * class selector token type */ @@ -980,7 +980,7 @@ declare const OPTIMIZED: unique symbol; */ export declare interface LiteralToken extends BaseToken { /** - * literal type + * @inheritdoc */ typ: EnumToken.LiteralTokenType; /** @@ -994,7 +994,7 @@ export declare interface LiteralToken extends BaseToken { */ export declare interface ClassSelectorToken extends BaseToken { /** - * class selector type + * @inheritdoc */ typ: EnumToken.ClassSelectorTokenType; /** @@ -1008,7 +1008,7 @@ export declare interface ClassSelectorToken extends BaseToken { */ export declare interface InvalidClassSelectorToken extends BaseToken { /** - * invalid class selector type + * @inheritdoc */ typ: EnumToken.InvalidClassSelectorTokenType; /** @@ -1022,7 +1022,7 @@ export declare interface InvalidClassSelectorToken extends BaseToken { */ export declare interface UniversalSelectorToken extends BaseToken { /** - * universal selector type + * @inheritdoc */ typ: EnumToken.UniversalSelectorTokenType; } @@ -1032,7 +1032,7 @@ export declare interface UniversalSelectorToken extends BaseToken { */ export declare interface IdentToken extends BaseToken { /** - * ident type + * @inheritdoc */ typ: EnumToken.IdenTokenType; /** @@ -1046,7 +1046,7 @@ export declare interface IdentToken extends BaseToken { */ export declare interface IdentListToken extends BaseToken { /** - * ident list type + * @inheritdoc */ typ: EnumToken.IdenListTokenType; /** @@ -1060,7 +1060,7 @@ export declare interface IdentListToken extends BaseToken { */ export declare interface DashedIdentToken extends BaseToken { /** - * ident type + * @inheritdoc */ typ: EnumToken.DashedIdenTokenType; /** @@ -1074,7 +1074,7 @@ export declare interface DashedIdentToken extends BaseToken { */ export declare interface CommaToken extends BaseToken { /** - * comma type + * @inheritdoc */ typ: EnumToken.CommaTokenType; } @@ -1084,7 +1084,7 @@ export declare interface CommaToken extends BaseToken { */ export declare interface ColonToken extends BaseToken { /** - * colon type ':' + * @inheritdoc */ typ: EnumToken.ColonTokenType; } @@ -1094,7 +1094,7 @@ export declare interface ColonToken extends BaseToken { */ export declare interface DoubleColonToken extends BaseToken { /** - * double colon type '::' + * @inheritdoc */ typ: EnumToken.DoubleColonTokenType; } @@ -1104,7 +1104,7 @@ export declare interface DoubleColonToken extends BaseToken { */ export declare interface SemiColonToken extends BaseToken { /** - * semicolon type + * @inheritdoc */ typ: EnumToken.SemiColonTokenType; } @@ -1114,7 +1114,7 @@ export declare interface SemiColonToken extends BaseToken { */ export declare interface NestingSelectorToken extends BaseToken { /** - * nesting selector type + * @inheritdoc */ typ: EnumToken.NestingSelectorTokenType; } @@ -1124,7 +1124,7 @@ export declare interface NestingSelectorToken extends BaseToken { */ export declare interface NumberToken extends BaseToken { /** - * number type + * @inheritdoc */ typ: EnumToken.NumberTokenType; /** @@ -1142,7 +1142,7 @@ export declare interface NumberToken extends BaseToken { */ export declare interface AtRuleToken extends BaseToken { /** - * at rule type + * @inheritdoc */ typ: EnumToken.AtRuleTokenType; /** @@ -1160,7 +1160,7 @@ export declare interface AtRuleToken extends BaseToken { */ export declare interface PercentageToken extends BaseToken { /** - * percentage type + * @inheritdoc */ typ: EnumToken.PercentageTokenType; /** @@ -1174,7 +1174,7 @@ export declare interface PercentageToken extends BaseToken { */ export declare interface FlexToken extends BaseToken { /** - * flex type + * @inheritdoc */ typ: EnumToken.FlexTokenType; /** @@ -1216,7 +1216,7 @@ export declare interface FunctionToken extends BaseToken { */ export declare interface GridTemplateFuncToken extends BaseToken { /** - * function type + * @inheritdoc */ typ: EnumToken.GridTemplateFuncTokenType; /** @@ -1234,7 +1234,7 @@ export declare interface GridTemplateFuncToken extends BaseToken { */ export declare interface FunctionURLToken extends BaseToken { /** - * function type + * @inheritdoc */ typ: EnumToken.UrlFunctionTokenType; /** @@ -1252,7 +1252,7 @@ export declare interface FunctionURLToken extends BaseToken { */ export declare interface FunctionImageToken extends BaseToken { /** - * function type + * @inheritdoc */ typ: EnumToken.ImageFunctionTokenType; /** @@ -1279,7 +1279,7 @@ export declare interface FunctionImageToken extends BaseToken { */ export declare interface TimingFunctionToken extends BaseToken { /** - * timing function type + * @inheritdoc */ typ: EnumToken.TimingFunctionTokenType; /** @@ -1297,7 +1297,7 @@ export declare interface TimingFunctionToken extends BaseToken { */ export declare interface TimelineFunctionToken extends BaseToken { /** - * timeline function type + * @inheritdoc */ typ: EnumToken.TimelineFunctionTokenType; /** @@ -1315,7 +1315,7 @@ export declare interface TimelineFunctionToken extends BaseToken { */ export declare interface StringToken extends BaseToken { /** - * string type + * @inheritdoc */ typ: EnumToken.StringTokenType; /** @@ -1329,7 +1329,7 @@ export declare interface StringToken extends BaseToken { */ export declare interface BadStringToken extends BaseToken { /** - * bad string type + * @inheritdoc */ typ: EnumToken.BadStringTokenType; /** @@ -1343,7 +1343,7 @@ export declare interface BadStringToken extends BaseToken { */ export declare interface UnclosedStringToken extends BaseToken { /** - * unclosed string type + * @inheritdoc */ typ: EnumToken.UnclosedStringTokenType; /** @@ -1357,7 +1357,7 @@ export declare interface UnclosedStringToken extends BaseToken { */ export declare interface DimensionToken extends BaseToken { /** - * dimension type + * @inheritdoc */ typ: EnumToken.DimensionTokenType; /** @@ -1375,7 +1375,7 @@ export declare interface DimensionToken extends BaseToken { */ export declare interface LengthToken extends BaseToken { /** - * length type + * @inheritdoc */ typ: EnumToken.LengthTokenType; /** @@ -1393,7 +1393,7 @@ export declare interface LengthToken extends BaseToken { */ export declare interface AngleToken extends BaseToken { /** - * angle type + * @inheritdoc */ typ: EnumToken.AngleTokenType; /** @@ -1411,7 +1411,7 @@ export declare interface AngleToken extends BaseToken { */ export declare interface TimeToken extends BaseToken { /** - * time type + * @inheritdoc */ typ: EnumToken.TimeTokenType; /** @@ -1419,7 +1419,7 @@ export declare interface TimeToken extends BaseToken { */ val: number | FractionToken; /** - * time unit + * time unit */ unit: "ms" | "s"; } @@ -1429,7 +1429,7 @@ export declare interface TimeToken extends BaseToken { */ export declare interface FrequencyToken extends BaseToken { /** - * frequency type + * @inheritdoc */ typ: EnumToken.FrequencyTokenType; /** @@ -1447,7 +1447,7 @@ export declare interface FrequencyToken extends BaseToken { */ export declare interface ResolutionToken extends BaseToken { /** - * resolution type + * @inheritdoc */ typ: EnumToken.ResolutionTokenType; /** @@ -1465,7 +1465,7 @@ export declare interface ResolutionToken extends BaseToken { */ export declare interface HashToken extends BaseToken { /** - * hash type + * @inheritdoc */ typ: EnumToken.HashTokenType; /** @@ -1479,7 +1479,7 @@ export declare interface HashToken extends BaseToken { */ export declare interface BlockStartToken extends BaseToken { /** - * block start type + * @inheritdoc */ typ: EnumToken.BlockStartTokenType; } @@ -1489,7 +1489,7 @@ export declare interface BlockStartToken extends BaseToken { */ export declare interface BlockEndToken extends BaseToken { /** - * block end type + * @inheritdoc */ typ: EnumToken.BlockEndTokenType; } @@ -1499,7 +1499,7 @@ export declare interface BlockEndToken extends BaseToken { */ export declare interface AttrStartToken extends BaseToken { /** - * attribute start type + * @inheritdoc */ typ: EnumToken.AttrStartTokenType; /** @@ -1513,7 +1513,7 @@ export declare interface AttrStartToken extends BaseToken { */ export declare interface AttrEndToken extends BaseToken { /** - * attribute end type + * @inheritdoc */ typ: EnumToken.AttrEndTokenType; } @@ -1523,7 +1523,7 @@ export declare interface AttrEndToken extends BaseToken { */ export declare interface ParensStartToken extends BaseToken { /** - * parenthesis start type + * @inheritdoc */ typ: EnumToken.StartParensTokenType; } @@ -1533,7 +1533,7 @@ export declare interface ParensStartToken extends BaseToken { */ export declare interface ParensEndToken extends BaseToken { /** - * parenthesis end type + * @inheritdoc */ typ: EnumToken.EndParensTokenType; } @@ -1543,7 +1543,7 @@ export declare interface ParensEndToken extends BaseToken { */ export declare interface ParensToken extends BaseToken { /** - * parenthesis type + * @inheritdoc */ typ: EnumToken.ParensTokenType; /** @@ -1557,7 +1557,7 @@ export declare interface ParensToken extends BaseToken { */ export declare interface WhitespaceToken extends BaseToken { /** - * whitespace type + * @inheritdoc */ typ: EnumToken.WhitespaceTokenType; /** @@ -1571,7 +1571,7 @@ export declare interface WhitespaceToken extends BaseToken { */ export declare interface CommentToken extends BaseToken { /** - * comment type + * @inheritdoc */ typ: EnumToken.CommentTokenType; /** @@ -1585,7 +1585,7 @@ export declare interface CommentToken extends BaseToken { */ export declare interface BadCommentToken extends BaseToken { /** - * bad comment type + * @inheritdoc */ typ: EnumToken.BadCommentTokenType; /** @@ -1598,7 +1598,13 @@ export declare interface BadCommentToken extends BaseToken { * CDO comment token */ export declare interface CDOCommentToken extends BaseToken { + /** + * @inheritdoc + */ typ: EnumToken.CDOCOMMTokenType; + /** + * CDO comment value + */ val: string; } @@ -1607,7 +1613,7 @@ export declare interface CDOCommentToken extends BaseToken { */ export declare interface BadCDOCommentToken extends BaseToken { /** - * bad CDO comment type + * @inheritdoc */ typ: EnumToken.BadCdoTokenType; /** @@ -1621,7 +1627,7 @@ export declare interface BadCDOCommentToken extends BaseToken { */ export declare interface IncludeMatchToken extends BaseToken { /** - * include match type + * @inheritdoc */ typ: EnumToken.IncludeMatchTokenType; // val: '~='; @@ -1632,7 +1638,7 @@ export declare interface IncludeMatchToken extends BaseToken { */ export declare interface DashMatchToken extends BaseToken { /** - * dash match type + * @inheritdoc */ typ: EnumToken.DashMatchTokenType; // val: '|='; @@ -1643,7 +1649,7 @@ export declare interface DashMatchToken extends BaseToken { */ export declare interface EqualMatchToken extends BaseToken { /** - * equal match type + * @inheritdoc */ typ: EnumToken.EqualMatchTokenType; // val: '|='; @@ -1654,7 +1660,7 @@ export declare interface EqualMatchToken extends BaseToken { */ export declare interface StartMatchToken extends BaseToken { /** - * start match type + * @inheritdoc */ typ: EnumToken.StartMatchTokenType; // val: '^='; @@ -1665,7 +1671,7 @@ export declare interface StartMatchToken extends BaseToken { */ export declare interface EndMatchToken extends BaseToken { /** - * end match type + * @inheritdoc */ typ: EnumToken.EndMatchTokenType; // val: '|='; @@ -1676,7 +1682,7 @@ export declare interface EndMatchToken extends BaseToken { */ export declare interface ContainMatchToken extends BaseToken { /** - * contain match type + * @inheritdoc */ typ: EnumToken.ContainMatchTokenType; // val: '|='; @@ -1687,7 +1693,7 @@ export declare interface ContainMatchToken extends BaseToken { */ export declare interface LessThanToken extends BaseToken { /** - * less than type + * @inheritdoc */ typ: EnumToken.LtTokenType; } @@ -1697,7 +1703,7 @@ export declare interface LessThanToken extends BaseToken { */ export declare interface LessThanOrEqualToken extends BaseToken { /** - * less than or equal type + * @inheritdoc */ typ: EnumToken.LteTokenType; } @@ -1707,7 +1713,7 @@ export declare interface LessThanOrEqualToken extends BaseToken { */ export declare interface GreaterThanToken extends BaseToken { /** - * greater than type + * @inheritdoc */ typ: EnumToken.GtTokenType; } @@ -1717,7 +1723,7 @@ export declare interface GreaterThanToken extends BaseToken { */ export declare interface GreaterThanOrEqualToken extends BaseToken { /** - * greater than or equal type + * @inheritdoc */ typ: EnumToken.GteTokenType; } @@ -1727,7 +1733,7 @@ export declare interface GreaterThanOrEqualToken extends BaseToken { */ export declare interface ColumnCombinatorToken extends BaseToken { /** - * column combinator type + * @inheritdoc */ typ: EnumToken.ColumnCombinatorTokenType; } @@ -1737,7 +1743,7 @@ export declare interface ColumnCombinatorToken extends BaseToken { */ export declare interface PseudoClassToken extends BaseToken { /** - * Pseudo class + * @inheritdoc */ typ: EnumToken.PseudoClassTokenType; /** @@ -1751,7 +1757,7 @@ export declare interface PseudoClassToken extends BaseToken { */ export declare interface PseudoElementToken extends BaseToken { /** - * Pseudo element + * @inheritdoc */ typ: EnumToken.PseudoElementTokenType; /** @@ -1765,7 +1771,7 @@ export declare interface PseudoElementToken extends BaseToken { */ export declare interface PseudoPageToken extends BaseToken { /** - * Pseudo page + * @inheritdoc */ typ: EnumToken.PseudoPageTokenType; /** @@ -1779,7 +1785,7 @@ export declare interface PseudoPageToken extends BaseToken { */ export declare interface PseudoClassFunctionToken extends BaseToken { /** - * Pseudo class function + * @inheritdoc */ typ: EnumToken.PseudoClassFuncTokenType; /** @@ -1797,7 +1803,7 @@ export declare interface PseudoClassFunctionToken extends BaseToken { */ export declare interface DelimToken extends BaseToken { /** - * Delimiter token type + * @inheritdoc */ typ: EnumToken.DelimTokenType; } @@ -1807,7 +1813,7 @@ export declare interface DelimToken extends BaseToken { */ export declare interface BadUrlToken extends BaseToken { /** - * Bad URL + * @inheritdoc */ typ: EnumToken.BadUrlTokenType; /** @@ -1821,7 +1827,7 @@ export declare interface BadUrlToken extends BaseToken { */ export declare interface UrlToken extends BaseToken { /** - * URL + * @inheritdoc */ typ: EnumToken.UrlTokenTokenType; /** @@ -1835,7 +1841,7 @@ export declare interface UrlToken extends BaseToken { */ export declare interface EOFToken extends BaseToken { /** - * End of file + * @inheritdoc */ typ: EnumToken.EOFTokenType; } @@ -1845,7 +1851,7 @@ export declare interface EOFToken extends BaseToken { */ export declare interface ImportantToken extends BaseToken { /** - * Important + * @inheritdoc */ typ: EnumToken.ImportantTokenType; } @@ -1855,7 +1861,7 @@ export declare interface ImportantToken extends BaseToken { */ export declare interface ColorToken extends BaseToken { /** - * Color type + * @inheritdoc */ typ: EnumToken.ColorTokenType; /** @@ -1881,7 +1887,7 @@ export declare interface ColorToken extends BaseToken { */ export declare interface AttrToken extends BaseToken { /** - * Attribute type + * @inheritdoc */ typ: EnumToken.AttrTokenType; /** @@ -1895,7 +1901,7 @@ export declare interface AttrToken extends BaseToken { */ export declare interface InvalidAttrToken extends BaseToken { /** - * Attribute type + * @inheritdoc */ typ: EnumToken.InvalidAttrTokenType; /** @@ -1908,6 +1914,9 @@ export declare interface InvalidAttrToken extends BaseToken { * Child combinator token */ export declare interface ChildCombinatorToken extends BaseToken { + /** + * @inheritdoc + */ typ: EnumToken.ChildCombinatorTokenType; } @@ -1916,7 +1925,7 @@ export declare interface ChildCombinatorToken extends BaseToken { */ export declare interface MediaFeatureToken extends BaseToken { /** - * Media feature type + * @inheritdoc */ typ: EnumToken.MediaFeatureTokenType; /** @@ -1930,7 +1939,7 @@ export declare interface MediaFeatureToken extends BaseToken { */ export declare interface NotToken extends BaseToken { /** - * Media feature not type + * @inheritdoc */ typ: EnumToken.NotTokenType; /** @@ -1944,7 +1953,7 @@ export declare interface NotToken extends BaseToken { */ export declare interface MediaFeatureOnlyToken extends BaseToken { /** - * Media feature only type + * @inheritdoc */ typ: EnumToken.OnlyTokenType; /** @@ -1958,7 +1967,7 @@ export declare interface MediaFeatureOnlyToken extends BaseToken { */ export declare interface AndToken extends BaseToken { /** - * Media feature and type + * @inheritdoc */ typ: EnumToken.AndTokenType; } @@ -1968,7 +1977,7 @@ export declare interface AndToken extends BaseToken { */ export declare interface OrToken extends BaseToken { /** - * Media feature or type + * @inheritdoc */ typ: EnumToken.OrTokenType; } @@ -1978,7 +1987,7 @@ export declare interface OrToken extends BaseToken { */ export declare interface MediaQueryUnaryFeatureToken extends BaseToken { /** - * Media query condition type + * @inheritdoc */ typ: EnumToken.MediaQueryUnaryFeatureTokenType; /** @@ -1993,7 +2002,7 @@ export declare interface MediaQueryUnaryFeatureToken extends BaseToken { export declare interface SupportsQueryUnaryConditionToken extends BaseToken { /** - * Supports query condition type + * @inheritdoc */ typ: EnumToken.SupportsQueryUnaryConditionTokenType; /** @@ -2008,7 +2017,7 @@ export declare interface SupportsQueryUnaryConditionToken extends BaseToken { export declare interface SupportsQueryConditionToken extends BaseToken { /** - * Supports query condition type + * @inheritdoc */ typ: EnumToken.SupportsQueryConditionTokenType; /** @@ -2027,7 +2036,7 @@ export declare interface SupportsQueryConditionToken extends BaseToken { export declare interface WhenElseQueryConditionToken extends BaseToken { /** - * When else query condition type + * @inheritdoc */ typ: EnumToken.WhenElseQueryConditionTokenType; /** @@ -2046,7 +2055,7 @@ export declare interface WhenElseQueryConditionToken extends BaseToken { export declare interface WhenElseUnaryConditionToken extends BaseToken { /** - * When else query condition type + * @inheritdoc */ typ: EnumToken.WhenElseUnaryConditionTokenType; /** @@ -2061,7 +2070,7 @@ export declare interface WhenElseUnaryConditionToken extends BaseToken { export declare interface MediaQueryConditionToken extends BaseToken { /** - * Media query condition type + * @inheritdoc */ typ: EnumToken.MediaQueryConditionTokenType; /** @@ -2088,7 +2097,7 @@ export declare interface MediaQueryConditionToken extends BaseToken { export declare interface IfConditionToken extends BaseToken { /** - * If condition type + * @inheritdoc */ typ: EnumToken.IfConditionTokenType; /** @@ -2103,7 +2112,7 @@ export declare interface IfConditionToken extends BaseToken { export declare interface IfElseConditionToken extends BaseToken { /** - * If else condition type + * @inheritdoc */ typ: EnumToken.IfElseConditionTokenType; /** @@ -2117,23 +2126,67 @@ export declare interface IfElseConditionToken extends BaseToken { } export declare interface ContainerStyleRangeToken extends BaseToken { + /** + * @inheritdoc + */ typ: EnumToken.ContainerStyleRangeTokenType; + /** + * condition left handle + */ l: Token$1[]; + /** + * condition operator + */ op: Token$1[]; + /** + * condition value + */ r: Token$1[]; } +// (20px <= width < 30px) +/** + * @inheritdoc + */ export declare interface MediaRangeQueryToken extends BaseToken { + /** + * @inheritdoc + */ typ: EnumToken.MediaRangeQueryTokenType; + /** + * left hanle + * */ l: Token$1[]; + /** + * media feature name + */ val: Token$1[]; + /** + * first comparator + */ op1: LessThanToken | GreaterThanToken | LessThanOrEqualToken | GreaterThanOrEqualToken; + /** + * second comparator + */ op2: LessThanToken | GreaterThanToken | LessThanOrEqualToken | GreaterThanOrEqualToken; + /** + * right handle + */ r: Token$1[]; } +/** + * @inheritdoc + */ export declare interface InvalidMediaQueryToken extends BaseToken { + /** + * @inheritdoc + */ typ: EnumToken.InvalidMediaQueryTokenType; + + /** + * children + */ chi: Token$1[]; } @@ -2141,6 +2194,9 @@ export declare interface InvalidMediaQueryToken extends BaseToken { * Descendant combinator token */ export declare interface DescendantCombinatorToken extends BaseToken { + /** + * @inheritdoc + */ typ: EnumToken.DescendantCombinatorTokenType; } @@ -2148,6 +2204,9 @@ export declare interface DescendantCombinatorToken extends BaseToken { * Next sibling combinator token */ export declare interface NextSiblingCombinatorToken extends BaseToken { + /** + * @inheritdoc + */ typ: EnumToken.NextSiblingCombinatorTokenType; } @@ -2155,6 +2214,9 @@ export declare interface NextSiblingCombinatorToken extends BaseToken { * Subsequent sibling combinator token */ export declare interface SubsequentCombinatorToken extends BaseToken { + /** + * @inheritdoc + */ typ: EnumToken.SubsequentSiblingCombinatorTokenType; } @@ -2162,6 +2224,9 @@ export declare interface SubsequentCombinatorToken extends BaseToken { * Add token */ export declare interface AddToken extends BaseToken { + /** + * @inheritdoc + */ typ: EnumToken.Add; } @@ -2169,6 +2234,9 @@ export declare interface AddToken extends BaseToken { * Sub token */ export declare interface SubToken extends BaseToken { + /** + * @inheritdoc + */ typ: EnumToken.Sub; } @@ -2176,6 +2244,9 @@ export declare interface SubToken extends BaseToken { * Div token */ export declare interface DivToken extends BaseToken { + /** + * @inheritdoc + */ typ: EnumToken.Div; } @@ -2184,7 +2255,7 @@ export declare interface DivToken extends BaseToken { */ export declare interface MulToken extends BaseToken { /** - * Type + * @inheritdoc */ typ: EnumToken.Mul; } @@ -2194,7 +2265,7 @@ export declare interface MulToken extends BaseToken { */ export declare interface WrappedValuesToken extends BaseToken { /** - * Type + * @inheritdoc */ typ: EnumToken.WrappedValuesTokenType; /** @@ -2208,7 +2279,7 @@ export declare interface WrappedValuesToken extends BaseToken { */ export declare interface UnaryExpression extends BaseToken { /** - * Type + * @inheritdoc */ typ: EnumToken.UnaryExpressionTokenType; /** @@ -2225,8 +2296,17 @@ export declare interface UnaryExpression extends BaseToken { * Fraction token */ export declare interface FractionToken extends BaseToken { + /** + * @inheritdoc + */ typ: EnumToken.FractionTokenType; + /** + * Left handle + */ l: NumberToken; + /** + * Right handle + */ r: NumberToken; } @@ -2234,9 +2314,21 @@ export declare interface FractionToken extends BaseToken { * Binary expression token */ export declare interface BinaryExpressionToken extends BaseToken { + /** + * @inheritdoc + */ typ: EnumToken.BinaryExpressionTokenType; + /** + * Operator + */ op: EnumToken.Add | EnumToken.Sub | EnumToken.Div | EnumToken.Mul; + /** + * Left handle + */ l: BinaryExpressionNode | Token$1; + /** + * Right handle + */ r: BinaryExpressionNode | Token$1; } @@ -2244,10 +2336,25 @@ export declare interface BinaryExpressionToken extends BaseToken { * Match expression token */ export declare interface MatchExpressionToken extends BaseToken { + /** + * @inheritdoc + */ typ: EnumToken.MatchExpressionTokenType; + /** + * Operator + */ op: EqualMatchToken | DashMatchToken | StartMatchToken | ContainMatchToken | EndMatchToken | IncludeMatchToken; + /** + * Left handle + */ l: Token$1; + /** + * Right handle + */ r: Token$1; + /** + * Flags + */ attr?: "i" | "s"; } @@ -2255,8 +2362,17 @@ export declare interface MatchExpressionToken extends BaseToken { * Name space attribute token */ export declare interface NameSpaceAttributeToken extends BaseToken { + /** + * @inheritdoc + */ typ: EnumToken.NameSpaceAttributeTokenType; + /** + * Left handle + */ l?: Token$1; + /** + * Right handle + */ r: Token$1; } @@ -2264,7 +2380,13 @@ export declare interface NameSpaceAttributeToken extends BaseToken { * List token */ export declare interface ListToken extends BaseToken { + /** + * @inheritdoc + */ typ: EnumToken.ListToken; + /** + * Children + */ chi: Token$1[]; } @@ -2272,8 +2394,17 @@ export declare interface ListToken extends BaseToken { * Composes selector token */ export declare interface ComposesSelectorToken extends BaseToken { + /** + * @inheritdoc + */ typ: EnumToken.ComposesSelectorTokenType; + /** + * Left handle + */ l: Token$1[]; + /** + * Right handle + */ r: Token$1 | null; } @@ -2281,20 +2412,53 @@ export declare interface ComposesSelectorToken extends BaseToken { * Css variable token */ export declare interface CssVariableToken$1 extends BaseToken { + /** + * @inheritdoc + */ typ: EnumToken.CssVariableTokenType; + /** + * Name + */ nam: string; + /** + * Value + */ val: Token$1[]; } +/** + * Css variable import token + */ export declare interface CssVariableImportTokenType$1 extends BaseToken { + /** + * @inheritdoc + */ typ: EnumToken.CssVariableImportTokenType; + /** + * Name + */ nam: string; + /** + * Value + */ val: Token$1[]; } +/** + * Css variable map token + */ export declare interface CssVariableMapTokenType extends BaseToken { + /** + * @inheritdoc + */ typ: EnumToken.CssVariableDeclarationMapTokenType; + /** + * CSS Variables + */ vars: Token$1[]; + /** + * From clause + */ from: Token$1[]; } @@ -2302,6 +2466,9 @@ export declare interface CssVariableMapTokenType extends BaseToken { * Function definition token */ export declare interface FunctionDefToken extends BaseToken { + /** + * @inheritdoc + */ typ: | EnumToken.FunctionDefTokenType | EnumToken.UrlFunctionTokenDefType @@ -2313,7 +2480,13 @@ export declare interface FunctionDefToken extends BaseToken { | EnumToken.MathFunctionTokenDefType | EnumToken.PseudoClassFunctionTokenDefType | EnumToken.TransformFunctionTokenDefType; + /** + * Name + */ nam: string; + /** + * Value + */ val: string; } @@ -2321,7 +2494,13 @@ export declare interface FunctionDefToken extends BaseToken { * Raw node token */ export declare interface RawNodeToken extends BaseToken, EnumAstNodeStatus$1 { + /** + * @inheritdoc + */ typ: EnumToken.RawNodeTokenType; + /** + * Value + */ val: Token$1[]; } @@ -2534,7 +2713,7 @@ export declare interface BaseToken { /** * parent node */ - parent?: AstAtRule | astRule | AstKeyframesAtRule | AstKeyFrameRule | AstInvalidRule | AstInvalidAtRule | null; + parent?: AstAtRule | astRule | AstKeyframesAtRule | AstKeyframesRule | AstInvalidRule | AstInvalidAtRule | null; /** * @private */ @@ -2676,36 +2855,6 @@ export declare interface AstInvalidAtRule extends BaseToken, AstNodeStatus { chi?: Array; } -/** - * keyframe rule node - */ -export declare interface AstKeyFrameRule extends BaseToken, AstNodeStatus { - /** - * token type - */ - typ: EnumToken.KeyFramesRuleNodeType; - /** - * selector - */ - sel: string; - /** - * child nodes - */ - chi: Array; - /** - * optimized selector - */ - optimized?: OptimizedSelector; - /** - * raw selector - */ - raw?: RawSelectorTokens; - /** - * tokens - */ - tokens?: Token$1[]; -} - /** * raw selector tokens */ @@ -2788,7 +2937,37 @@ export declare interface AstKeyframesRule extends BaseToken, AstNodeStatus { /** * token type */ - typ: EnumToken.KeyFramesRuleNodeType; + typ: EnumToken.KeyframesRuleNodeType; + /** + * selector + */ + sel: string; + /** + * child nodes + */ + chi: Array; + /** + * optimized selector + */ + optimized?: OptimizedSelector; + /** + * raw selector + */ + raw?: RawSelectorTokens; + /** + * tokens + */ + tokens?: Token$1[]; +} + +/** + * keyframe rule node + */ +export declare interface AstKeyframesRule extends BaseToken, AstNodeStatus { + /** + * token type + */ + typ: EnumToken.KeyframesRuleNodeType; /** * selector */ @@ -2837,7 +3016,7 @@ export declare type AstRuleList = | AstAtRule | AstRule | AstKeyframesAtRule - | AstKeyFrameRule + | AstKeyframesRule | AstInvalidRule; /** @@ -2865,7 +3044,7 @@ export declare type AstNode$1 = | AstRule | AstDeclaration | AstKeyframesAtRule - | AstKeyFrameRule + | AstKeyframesRule | AstInvalidRule | AstInvalidAtRule | AstInvalidDeclaration @@ -2992,7 +3171,7 @@ declare enum WalkerEvent { * } * * const result = await transform(css); - * for (const {node} of walk(result.ast, filter)) { + * for (const {node} of walk(result.ast, filter, false)) { * * console.error([EnumToken[node.typ]]); * } @@ -3007,6 +3186,79 @@ declare enum WalkerEvent { * ``` */ declare function walk(node: AstNode$1, filter?: WalkerFilter | null, reverse?: boolean): Generator; +/** + * Walk ast nodes + * @param node initial node + * @param filter control the walk process + * + * ```ts + * + * import {walk} from '@tbela99/css-parser'; + * + * const css = ` + * body { color: color(from var(--base-color) display-p3 r calc(g + 0.24) calc(b + 0.15)); } + * + * html, + * body { + * line-height: 1.474; + * } + * + * .ruler { + * + * height: 10px; + * } + * `; + * + * for (const {node, parent, root} of walk(ast)) { + * + * // do something with node + * } + * ``` + * + * Using a {@link filter} function to control the ast traversal. the filter function returns a value of type {@link WalkerOption}. + * + * ```ts + * import {EnumToken, transform, walk, WalkerOptionEnum} from '@tbela99/css-parser'; + * + * const css = ` + * body { color: color(from var(--base-color) display-p3 r calc(g + 0.24) calc(b + 0.15)); } + * + * html, + * body { + * line-height: 1.474; + * } + * + * .ruler { + * + * height: 10px; + * } + * `; + * + * function filter(node) { + * + * if (node.typ == EnumToken.AstRule && node.sel.includes('html')) { + * + * // skip the children of the current node + * return WalkerOptionEnum.IgnoreChildren; + * } + * } + * + * const result = await transform(css); + * for (const {node} of walk(result.ast, {filter, reverse: false})) { + * + * console.error([EnumToken[node.typ]]); + * } + * + * // [ "StyleSheetNodeType" ] + * // [ "RuleNodeType" ] + * // [ "DeclarationNodeType" ] + * // [ "RuleNodeType" ] + * // [ "DeclarationNodeType" ] + * // [ "RuleNodeType" ] + * // [ "DeclarationNodeType" ] + * ``` + */ +declare function walk(node: AstNode$1, filter?: WalkerOptions | null): Generator; /** * Walk ast node value tokens * @param values @@ -3060,24 +3312,40 @@ declare function walkValues(values: Token$1[], root?: AstNode$1 | Token$1 | null type?: EnumToken | EnumToken[] | ((token: Token$1) => boolean); }, reverse?: boolean): Generator; +/** + * Generic visitor result + */ export declare type GenericVisitorSyncResult = T | T[] | null; -export declare type GenericVisitorAsyncResult = Promise | Promise| Promise; +/** + * Generic visitor result + */ +export declare type GenericVisitorAsyncResult = Promise | Promise | Promise; +/** + * Generic visitor result + */ export declare type GenericVisitorResult = GenericVisitorSyncResult | GenericVisitorAsyncResult; - - +/** + * Generic visitor handler + */ export declare type GenericVisitorSyncHandler = ( node: T, parent?: AstNode | Token, root?: AstNode | Token, ) => GenericVisitorSyncResult; +/** + * Generic visitor handler + */ export declare type GenericVisitorAstNodeSyncHandlerMap = | Record> | GenericVisitorSyncHandler | { type: WalkerEvent; handler: GenericVisitorSyncHandler } | { type: WalkerEvent; handler: Record> }; +/** + * Generic visitor handler + */ export declare type ValueVisitorSyncHandler = GenericVisitorSyncHandler; /** @@ -3273,8 +3541,14 @@ export declare interface VisitorSyncNodeMap { */ Rule?: GenericVisitorAstNodeSyncHandlerMap; + /** + * keyframes rule visitor + */ KeyframesRule?: GenericVisitorAstNodeSyncHandlerMap; + /** + * keyframes at-rule visitor + */ KeyframesAtRule?: GenericVisitorAstNodeSyncHandlerMap; /** @@ -3329,22 +3603,32 @@ export declare interface VisitorSyncNodeMap { * // body {color:#f3fff0} * ``` */ - [key: keyof typeof EnumToken]: GenericVisitorAstNodeSyncHandlerMap | GenericVisitorAstNodeSyncHandlerMap; + [key: keyof typeof EnumToken]: + | GenericVisitorAstNodeSyncHandlerMap + | GenericVisitorAstNodeSyncHandlerMap; } - +/** + * Generic visitor handler + */ export declare type GenericVisitorHandler = ( node: T, parent?: AstNode | Token, root?: AstNode | Token, ) => GenericVisitorSyncResult | GenericVisitorAsyncResult; +/** + * Generic visitor handler + */ export declare type GenericVisitorAstNodeHandlerMap = | Record> | GenericVisitorHandler | { type: WalkerEvent; handler: GenericVisitorHandler } | { type: WalkerEvent; handler: Record> }; +/** + * Generic visitor handler + */ export declare type ValueVisitorHandler = GenericVisitorHandler; /** @@ -3628,10 +3912,14 @@ export declare interface VisitorNodeMap { } /** - * Source map class - * @internal + * Generate and parse source map */ declare class SourceMap { + /** + * + * @private + */ + private keys; /** * Last location */ @@ -3646,27 +3934,57 @@ declare class SourceMap { * @private */ private sourcesMap; + /** + * Sources content + * @private + */ + private readonly sourcesContent; /** * Sources * @private */ - private sources; + private readonly sources; /** * Map * @private + * */ private map; + /** + * Map + * @private + * + */ + private reverseMap; /** * Line * @private */ private line; /** - * Add a location - * @param source - * @param original + * + */ + constructor(); + /** + * + * @param sourcemaps + */ + constructor(sourcemaps: string | SourceMapObject); + /** + * Add all location + * @param maps + */ + addAll(maps: Array<[number, number, number, number, number, string | null, string | null]>): void; + /** + * compute original positions + */ + computePositions(): void; + /** + * retrieve original sources, lines and columns + * @param line generated line + * @param column generated column */ - add(newLine: number, newColumn: number, srcId: number, ln: number, col: number, sourceFileName: string, sourceContent: string): void; + find(line: number, column: number): Array<[string | null, number, number, string | null]> | null; /** * Convert to URL encoded string */ @@ -3675,6 +3993,10 @@ declare class SourceMap { * Convert to JSON object */ toJSON(): SourceMapObject; + /** + * to string + */ + toString(): string; } /** @@ -3689,7 +4011,7 @@ declare class LineMap { * Constructor * @param lines */ - constructor(lines: number[]); + constructor(lines?: number[]); /** * Compute line and column of the offset * @param offset @@ -3722,6 +4044,7 @@ declare class LineMap { * Source file helper class */ declare class SourceFile { + private inputSourceMap; /** * Source file ID */ @@ -3740,7 +4063,6 @@ declare class SourceFile { private content; /** * Constructor - * @param id * @param content * @param lines * @param file @@ -3749,7 +4071,6 @@ declare class SourceFile { /** * Update source content * @param content - * @param lines */ append(content: string): void; /** @@ -3791,6 +4112,16 @@ declare class SourceFile { * @param lineStart */ addLineStart(lineStart: number): void; + /** + * set input source map + * @param inputSourceMap + */ + setInputSourceMap(inputSourceMap: SourceMapObject | string | null): void; + /** + * return input source map + * @returns + */ + getInputSourceMap(): SourceMap | null; } export declare interface PropertyListOptions { @@ -4567,6 +4898,26 @@ interface BorderRadius { keywords: string[]; } +/** + * node walker options + */ +export declare interface WalkerOptions { + + /** + * walk in reverse + */ + reverse?: boolean; + + /** + * Traverse node value tokens. If false, only traverse node children + */ + inludeValues?: boolean; + /** + * filter function to control the walk + */ + filter?: WalkerFilter; +} + /** * node walker option */ @@ -4598,19 +4949,55 @@ export declare type WalkerValueFilter = ( parents?: Generator, ) => WalkerOption | null; +/** + * walker result + */ export declare interface WalkResult { + /** + * current node + */ node: AstNode$1; + /** + * parent node + */ parent?: AstRuleList; + /** + * root node + */ root?: AstNode$1; + /** + * parent nodes + */ parents: Generator; } +/** + * walker result + */ export declare interface WalkAttributesResult { + /** + * current node + */ value: Token$1; + /** + * previous node + */ previousValue: Token$1 | null; + /** + * next node + */ nextValue: Token$1 | null; + /** + * root node + */ root?: AstNode$1 | Token$1 | null; + /** + * parent node + */ parent: AstNode$1 | Token$1 | null; + /** + * parent nodes + */ parents: Generator; } @@ -5040,21 +5427,52 @@ export declare interface ParseInputStreamOptions { input: string | ReadableStream; } +/** + * Input options for string or stream + * @internal + */ export declare interface ParseSourceOptions { + /** + * Source file to be used for sourcemap + * @internal + */ sourcesMap?: Map; + /** + * Source file to be used for sourcemap + * @internal + */ source?: SourceFile | null; } +/** + * Parser sourcemap options + */ +export declare interface ParserSourceMapOptions { + /** + * Include sourcemap in the ast. Sourcemap info is always generated + */ + sourcemap?: boolean | "inline"; + /** + * Input source map + */ + inputSourceMap?: SourceMapObject | string; +} + +/** + * Sync parseroptions + */ export declare interface ParserSyncOptions - extends MinifyOptions, MinifyFeatureOptions, ValidationOptions, PropertyListOptions, ParseSourceOptions { + extends + MinifyOptions, + ParserSourceMapOptions, + MinifyFeatureOptions, + ValidationOptions, + PropertyListOptions, + ParseSourceOptions { /** * Source file to be used for sourcemap */ src?: string; - /** - * Include sourcemap in the ast. Sourcemap info is always generated - */ - sourcemap?: boolean | "inline"; /** * Remove at-rule charset */ @@ -5112,7 +5530,7 @@ export declare interface ParserSyncOptions * Node visitor * {@link VisitorSyncNodeMap | VisitorSyncNodeMap[]} */ - visitor?: VisitorSyncNodeMap | VisitorSyncNodeMap[]; + visitor?: GenericVisitorAstNodeSyncHandlerMap | VisitorSyncNodeMap | VisitorSyncNodeMap[]; /** * Abort signal * @@ -5177,7 +5595,11 @@ export declare interface ParserOptions extends ParserSyncOptions, ModuleAsyncOpt * Node visitor * {@link VisitorNodeMap | VisitorNodeMap[]} */ - visitor?: VisitorNodeMap | VisitorNodeMap[]; + visitor?: + | GenericVisitorAstNodeSyncHandlerMap + | GenericVisitorAstNodeHandlerMap + | VisitorNodeMap + | VisitorNodeMap[]; } /** @@ -5256,6 +5678,11 @@ export declare interface ResolvedPath { * Ast node render options */ export declare interface RenderOptions { + /** + * Source file to be used as CSS input file for sourcemap resolution + */ + src?: string; + /** * Minify css values. */ @@ -5665,69 +6092,108 @@ declare enum ResponseType$1 { ArrayBuffer = 2 } +/** + * Validation syntax + * @internal + */ export declare interface ValidationSyntaxNode { + /** + * mdn data syntax + */ syntax: string; + /** + * validation tokens + */ ast?: ValidationToken[]; + /** + * descriptors + */ descriptors?: Record>; } +/** + * Validation selector options + * @internal + */ interface ValidationSelectorOptions extends ValidationOptions { + /** + * nested selector + */ nestedSelector?: boolean; } +/** + * Validation media feature + * @internal + */ export declare interface ValidationMediaFeature { + /** + * media feature type + */ type: MediaFeatureType; + /** + * media feature status + */ status?: string; + /** + * media feature category + */ category: string; + /** + * media feature values + */ values?: Array | Array; } +/** + * Validation configuration + * @internal + */ export declare type ValidationConfiguration = Record< ValidationSyntaxGroupEnum, ValidationSyntaxNode | Record | Record >; +/** + * Validation result + * @internal + */ interface ValidationResult { + /** + * validation result + */ valid: SyntaxValidationResult; + /** + * node + */ node: AstNode$1 | Token$1 | null; + /** + * syntax + */ syntax: ValidationToken | string | null; + /** + * error + */ error: string; + /** + * cycle + */ cycle?: boolean; } +/** + * Validation syntax result + * @internal + */ interface ValidationSyntaxResult extends ValidationResult { + /** + * syntax + */ syntax: ValidationToken | string | null; - context: Context | Token$1[]; -} - -interface Context { - index: number; - /** - * The length of the context tokens to be consumed + * context */ - - readonly length: number; - - current(): Type | null; - - update(context: Context): void; - - consume(token: Type, howMany?: number): boolean; - - peek(): Type | null; - - // tokens(): Type[]; - - next(): Type | null; - - consume(token: Type, howMany?: number): boolean; - - slice(): Type[]; - - clone(): Context; - - done(): boolean; + context: ValidationContext | Token$1[]; } /** @@ -5793,6 +6259,9 @@ declare function parseString(src: string, options?: { * render ast token * @param token * @param options + * @param cache + * @param reducer + * @param errors * @private */ declare function renderValue(token: Token$1, options?: RenderOptions, cache?: { @@ -5833,7 +6302,7 @@ declare function okLabDistance(color1: ColorToken, color2: ColorToken): number | declare function isOkLabClose(color1: ColorToken, color2: ColorToken, threshold?: number): boolean; /** - * Search the ast sub-tree and return the first match + * Search the ast subtree and return the first match * * ```ts * // find the first ast declaration node which name is 'aspect-ratio' @@ -5865,6 +6334,11 @@ button { */ declare function find(ast: AstNode$1, matcher: (node: AstNode$1, parent?: AstNode$1 | null) => boolean): AstNode$1 | null; /** + * + * @param ast + * @param matcher + * @returns + * * Search the ast sub-tree by checking each node's value token and return the first match * ```ts @@ -5890,10 +6364,6 @@ button { console.log({node, value}); ``` - * - * @param ast - * @param matcher - * @returns */ declare function findByValue(ast: AstNode$1, matcher: AstValueMatcher): { node: AstNode$1; @@ -6074,10 +6544,10 @@ declare const parseFile: (file: string, options?: ParserOptions, asStream?: bool * * ```ts * - * import {parse} from '@tbela99/css-parser'; + * import {parseSync} from '@tbela99/css-parser'; * * // css string - * let result = await parse(css, {nestingRules: true}); + * let result = parseSync(css, {nestingRules: true}); * console.log(result.ast); * ``` * @@ -6085,17 +6555,16 @@ declare const parseFile: (file: string, options?: ParserOptions, asStream?: bool declare function parseSync(stream: string, options?: ParserSyncOptions): ParseResult; /** * Parse css string - * @param stream * @param options * * Parsing a string * * ```ts * - * import {parse} from '@tbela99/css-parser'; + * import {parseSync} from '@tbela99/css-parser'; * * // css string - * let result = await parse({input: css, nestingRules: true}); + * let result = parseSync({input: css, nestingRules: true}); * console.log(result.ast); * ``` * @@ -6109,10 +6578,10 @@ declare function parseSync(options: ParseInputOptions & ParserSyncOptions): Pars * * ```ts * - * import {transform} from '@tbela99/css-parser'; + * import {transformSync} from '@tbela99/css-parser'; * * // css string - * const result = await transform(css, {beautify: true}); + * const result = transformSync(css, {beautify: true}); * console.log(result.code); * ``` * @@ -6124,22 +6593,20 @@ declare function transformSync(css: string, options?: TransformSyncOptions): Tra * * ```ts * - * import {transform} from '@tbela99/css-parser'; + * import {transformSync} from '@tbela99/css-parser'; * * // css string - * const result = await transform({input: css, beautify: true}); + * const result = transformSync({input: css, beautify: true}); * console.log(result.code); * ``` * */ declare function transformSync(options: ParseInputOptions & TransformSyncOptions): TransformResult; /** - * Parse css + * Parse CSS * @param stream * @param options * - * @throws Error file not found - * * Example: * * ```ts @@ -6151,7 +6618,7 @@ declare function transformSync(options: ParseInputOptions & TransformSyncOptions * console.log(result.ast); * ``` * - * parsing a Readable stream + * parsing a ReadableStream * * ```ts * @@ -6166,7 +6633,7 @@ declare function transformSync(options: ParseInputOptions & TransformSyncOptions * console.log(result.ast); * ``` * - * Example using fetch and readable stream + * Parsing a file as a ReadableStream * * ```ts * @@ -6181,7 +6648,6 @@ declare function transformSync(options: ParseInputOptions & TransformSyncOptions declare function parse(stream: string | ReadableStream, options?: ParserOptions): Promise; /** * Parse css - * @param stream * @param options * * @throws Error file not found @@ -6214,7 +6680,6 @@ declare function parse(stream: string | ReadableStream, options?: Pa declare function parse(options: ParseInputFileOptions & ParserOptions): Promise; /** * Parse css - * @param stream * @param options * * Parsing a string @@ -6255,7 +6720,7 @@ declare function parse(options: ParseInputFileOptions & ParserOptions): Promise< */ declare function parse(options: ParseInputStreamOptions & ParserOptions): Promise; /** - * Transform css file + * Transform CSS file * @param file url or path * @param options * @param asStream load file as stream @@ -6280,7 +6745,7 @@ declare function parse(options: ParseInputStreamOptions & ParserOptions): Promis */ declare const transformFile: (file: string, options?: TransformOptions, asStream?: boolean) => Promise; /** - * Transform css + * Transform CSS * @param css * @param options * @@ -6325,7 +6790,6 @@ declare const transformFile: (file: string, options?: TransformOptions, asStream declare function transform(css: string | ReadableStream, options?: TransformOptions): Promise; /** * Transform css - * @param css * @param options * * Parsing a string @@ -6354,7 +6818,7 @@ declare function transform(css: string | ReadableStream, options?: T * console.log(result.code); * ``` * - * Example using fetch + * Parse a file as a ReadableStream * * ```ts * @@ -6368,48 +6832,20 @@ declare function transform(css: string | ReadableStream, options?: T */ declare function transform(options: ParseInputStreamOptions & TransformOptions): Promise; /** - * Transform css - * @param css + * Transform CSS * @param options * - * Parsing a string - * - * ```ts - * - * import {transform} from '@tbela99/css-parser'; - * - * // css string - * const result = await transform({input: css}); - * console.log(result.code); - * ``` - * - * Parsing a Readable stream + * Parsing a file * * ```ts * * import {transform} from '@tbela99/css-parser'; - * import {Readable} from "node:stream"; - * - * // usage: node index.ts < styles.css or cat styles.css | node index.ts - * - * const readableStream = Readable.toWeb(process.stdin); - * const result = await transform( {input: readableStream, beautify: true}); - * - * console.log(result.code); - * ``` - * - * Example using fetch - * - * ```ts - * - * import {transform} from '@tbela99/css-parser'; - * - * result = await transform({file: 'https://docs.deno.com/styles.css', beautify: true}); * + * const result = await transform( {file: 'https://docs.deno.com/styles.css', beautify: true}); * console.log(result.code); * ``` */ declare function transform(options: ParseInputFileOptions & TransformOptions): Promise; export { ColorType$1 as ColorType, EnumAstNodeStatus$1 as EnumAstNodeStatus, EnumToken, FeatureWalkMode, ModuleCaseTransformEnum, ModuleScopeEnumOptions, ResponseType$1 as ResponseType, SourceMap, ValidationLevel, WalkerEvent, WalkerOptionEnum, cloneNode, convertColor, dirname, expand, find, findAll, findByValue, findLast, isOkLabClose, load, minify, okLabDistance, parse, parseDeclarations, parseFile, parseString, parseSync, render, renderValue as renderToken, replaceNodeOrValue, resolve, transform, transformFile, transformSync, walk, walkValues }; -export type { AddToken, AndToken, AngleToken, AstAtRule, AstComment, AstDeclaration, AstInvalidAtRule, AstInvalidDeclaration, AstInvalidRule, AstKeyFrameRule, AstKeyframesAtRule, AstKeyframesRule, AstNode$1 as AstNode, AstNodeStatus, AstRule, AstRuleList, AstStyleSheet, AstValueMatcher, AtRuleToken, AtRuleVisitorHandler, AttrEndToken, AttrStartToken, AttrToken, Background, BackgroundAttachmentMapping, BackgroundPosition, BackgroundPositionClass, BackgroundPositionConstraints, BackgroundPositionMapping, BackgroundProperties, BackgroundRepeat, BackgroundRepeatMapping, BackgroundSize, BackgroundSizeMapping, BadCDOCommentToken, BadCommentToken, BadStringToken, BadUrlToken, BaseToken, BinaryExpressionNode, BinaryExpressionToken, BlockEndToken, BlockStartToken, Border, BorderColor, BorderColorClass, BorderProperties, BorderRadius, CDOCommentToken, ChildCombinatorToken, ClassSelectorToken, ColonToken, ColorToken, ColumnCombinatorToken, CommaToken, CommentToken, ComposesSelectorToken, ConstraintsMapping, ContainMatchToken, ContainerStyleRangeToken, Context, CssVariableImportTokenType$1 as CssVariableImportTokenType, CssVariableMapTokenType, CssVariableToken$1 as CssVariableToken, DashMatchToken, DashedIdentToken, DeclarationVisitorHandler, DelimToken, DescendantCombinatorToken, DimensionToken, DivToken, DoubleColonToken, EOFToken, EndMatchToken, EqualMatchToken, ErrorDescription$1 as ErrorDescription, FlexToken, Font, FontFamily, FontProperties, FontWeight, FontWeightConstraints, FontWeightMapping, FractionToken, FrequencyToken, FunctionDefToken, FunctionImageToken, FunctionToken, FunctionURLToken, GenericVisitorAstNodeHandlerMap, GenericVisitorAstNodeSyncHandlerMap, GenericVisitorAsyncResult, GenericVisitorHandler, GenericVisitorResult, GenericVisitorSyncHandler, GenericVisitorSyncResult, GreaterThanOrEqualToken, GreaterThanToken, GridTemplateFuncToken, HashToken, IdentListToken, IdentToken, IfConditionToken, IfElseConditionToken, ImportantToken, IncludeMatchToken, InvalidAttrToken, InvalidClassSelectorToken, InvalidMediaQueryToken, LengthToken, LessThanOrEqualToken, LessThanToken, LineHeight, ListToken, LiteralToken, LoadResult, Map$1 as Map, MatchExpressionToken, MatchedSelector, MediaFeatureOnlyToken, MediaFeatureToken, MediaQueryConditionToken, MediaQueryUnaryFeatureToken, MediaRangeQueryToken, MinifyFeature, MinifyFeatureOptions, MinifyOptions, ModuleAsyncOptions, ModuleSyncOptions, MulToken, NameSpaceAttributeToken, NestingSelectorToken, NextSiblingCombinatorToken, NotToken, NumberToken, OptimizedSelector, OptimizedSelectorToken, OrToken, Outline, OutlineProperties, ParensEndToken, ParensStartToken, ParensToken, ParseInfo$1 as ParseInfo, ParseInputFileOptions, ParseInputOptions, ParseInputStreamOptions, ParseResult, ParseResultStats, ParseSourceOptions, ParseTokenOptions, ParserOptions, ParserSyncOptions, PercentageToken, Prefix, PropertiesConfig, PropertiesConfigProperties, PropertyListOptions, PropertyMapType, PropertySetType, PropertyType, PseudoClassFunctionToken, PseudoClassToken, PseudoElementToken, PseudoPageToken, PurpleBackgroundAttachment, RawNodeToken, RawSelectorTokens, RenderOptions, RenderResult, ResolutionToken, ResolvedPath, RuleVisitorHandler, SemiColonToken, Separator, ShorthandDef, ShorthandMapType, ShorthandProperties, ShorthandPropertyType, ShorthandType, SinglePropertyType, SinglePropertyTypeMapping, SourceLocation, SourceMapObject, StartMatchToken, StringToken, SubToken, SubsequentCombinatorToken, SupportsQueryConditionToken, SupportsQueryUnaryConditionToken, TimeToken, TimelineFunctionToken, TimingFunctionToken, Token$1 as Token, TokenSearchResult, TokenizeResult, TransformOptions, TransformResult, TransformSyncOptions, UnaryExpression, UnaryExpressionNode, UnclosedStringToken, UniversalSelectorToken, UrlToken, ValidationConfiguration, ValidationMediaFeature, ValidationOptions, ValidationResult, ValidationSelectorOptions, ValidationSyntaxNode, ValidationSyntaxResult, ValidationToken$1 as ValidationToken, Value, ValueVisitorHandler, ValueVisitorSyncHandler, VariableScopeInfo, VisitorNodeMap, VisitorSyncNodeMap, WalkAttributesResult, WalkResult, WalkerFilter, WalkerOption, WalkerValueFilter, WhenElseQueryConditionToken, WhenElseUnaryConditionToken, WhitespaceToken, WrappedValuesToken }; +export type { AddToken, AndToken, AngleToken, AstAtRule, AstComment, AstDeclaration, AstInvalidAtRule, AstInvalidDeclaration, AstInvalidRule, AstKeyframesAtRule, AstKeyframesRule, AstNode$1 as AstNode, AstNodeStatus, AstRule, AstRuleList, AstStyleSheet, AstValueMatcher, AtRuleToken, AtRuleVisitorHandler, AttrEndToken, AttrStartToken, AttrToken, Background, BackgroundAttachmentMapping, BackgroundPosition, BackgroundPositionClass, BackgroundPositionConstraints, BackgroundPositionMapping, BackgroundProperties, BackgroundRepeat, BackgroundRepeatMapping, BackgroundSize, BackgroundSizeMapping, BadCDOCommentToken, BadCommentToken, BadStringToken, BadUrlToken, BaseToken, BinaryExpressionNode, BinaryExpressionToken, BlockEndToken, BlockStartToken, Border, BorderColor, BorderColorClass, BorderProperties, BorderRadius, CDOCommentToken, ChildCombinatorToken, ClassSelectorToken, ColonToken, ColorToken, ColumnCombinatorToken, CommaToken, CommentToken, ComposesSelectorToken, ConstraintsMapping, ContainMatchToken, ContainerStyleRangeToken, CssVariableImportTokenType$1 as CssVariableImportTokenType, CssVariableMapTokenType, CssVariableToken$1 as CssVariableToken, DashMatchToken, DashedIdentToken, DeclarationVisitorHandler, DelimToken, DescendantCombinatorToken, DimensionToken, DivToken, DoubleColonToken, EOFToken, EndMatchToken, EqualMatchToken, ErrorDescription$1 as ErrorDescription, FlexToken, Font, FontFamily, FontProperties, FontWeight, FontWeightConstraints, FontWeightMapping, FractionToken, FrequencyToken, FunctionDefToken, FunctionImageToken, FunctionToken, FunctionURLToken, GenericVisitorAstNodeHandlerMap, GenericVisitorAstNodeSyncHandlerMap, GenericVisitorAsyncResult, GenericVisitorHandler, GenericVisitorResult, GenericVisitorSyncHandler, GenericVisitorSyncResult, GreaterThanOrEqualToken, GreaterThanToken, GridTemplateFuncToken, HashToken, IdentListToken, IdentToken, IfConditionToken, IfElseConditionToken, ImportantToken, IncludeMatchToken, InvalidAttrToken, InvalidClassSelectorToken, InvalidMediaQueryToken, LengthToken, LessThanOrEqualToken, LessThanToken, LineHeight, ListToken, LiteralToken, LoadResult, Map$1 as Map, MatchExpressionToken, MatchedSelector, MediaFeatureOnlyToken, MediaFeatureToken, MediaQueryConditionToken, MediaQueryUnaryFeatureToken, MediaRangeQueryToken, MinifyFeature, MinifyFeatureOptions, MinifyOptions, ModuleAsyncOptions, ModuleSyncOptions, MulToken, NameSpaceAttributeToken, NestingSelectorToken, NextSiblingCombinatorToken, NotToken, NumberToken, OptimizedSelector, OptimizedSelectorToken, OrToken, Outline, OutlineProperties, ParensEndToken, ParensStartToken, ParensToken, ParseInfo$1 as ParseInfo, ParseInputFileOptions, ParseInputOptions, ParseInputStreamOptions, ParseResult, ParseResultStats, ParseSourceOptions, ParseTokenOptions, ParserOptions, ParserSourceMapOptions, ParserSyncOptions, PercentageToken, Prefix, PropertiesConfig, PropertiesConfigProperties, PropertyListOptions, PropertyMapType, PropertySetType, PropertyType, PseudoClassFunctionToken, PseudoClassToken, PseudoElementToken, PseudoPageToken, PurpleBackgroundAttachment, RawNodeToken, RawSelectorTokens, RenderOptions, RenderResult, ResolutionToken, ResolvedPath, RuleVisitorHandler, SemiColonToken, Separator, ShorthandDef, ShorthandMapType, ShorthandProperties, ShorthandPropertyType, ShorthandType, SinglePropertyType, SinglePropertyTypeMapping, SourceLocation, SourceMapObject, StartMatchToken, StringToken, SubToken, SubsequentCombinatorToken, SupportsQueryConditionToken, SupportsQueryUnaryConditionToken, TimeToken, TimelineFunctionToken, TimingFunctionToken, Token$1 as Token, TokenSearchResult, TokenizeResult, TransformOptions, TransformResult, TransformSyncOptions, UnaryExpression, UnaryExpressionNode, UnclosedStringToken, UniversalSelectorToken, UrlToken, ValidationConfiguration, ValidationMediaFeature, ValidationOptions, ValidationResult, ValidationSelectorOptions, ValidationSyntaxNode, ValidationSyntaxResult, ValidationToken$1 as ValidationToken, Value, ValueVisitorHandler, ValueVisitorSyncHandler, VariableScopeInfo, VisitorNodeMap, VisitorSyncNodeMap, WalkAttributesResult, WalkResult, WalkerFilter, WalkerOption, WalkerOptions, WalkerValueFilter, WhenElseQueryConditionToken, WhenElseUnaryConditionToken, WhitespaceToken, WrappedValuesToken }; diff --git a/dist/lib/ast/clone.js b/dist/lib/ast/clone.js index b5a3537a..3842583d 100644 --- a/dist/lib/ast/clone.js +++ b/dist/lib/ast/clone.js @@ -17,14 +17,14 @@ function cloneNode(node, cloneChildren = false, cloneMap = null) { clone[name] = value; } else if (Array.isArray(value)) { - clone[name] = - !cloneChildren && name == checkNode - ? [] - : value.map((c) => { - const newObj = cloneNode(c, cloneChildren, cloneMap); - cloneMap?.set?.(c, newObj); - return newObj; - }); + clone[name] = []; + if (cloneChildren || name !== checkNode) { + for (const c of value) { + const newObj = cloneNode(c, cloneChildren, cloneMap); + cloneMap?.set?.(c, newObj); + clone[name].push(newObj); + } + } } else { clone[name] = { ...value }; diff --git a/dist/lib/ast/expand.js b/dist/lib/ast/expand.js index 3b4461d3..4da8070f 100644 --- a/dist/lib/ast/expand.js +++ b/dist/lib/ast/expand.js @@ -1,5 +1,5 @@ import { splitRule } from './minify.js'; -import { combinators, RAW } from '../syntax/constants.js'; +import { PARENT, combinators, RAW } from '../syntax/constants.js'; import { parseString } from '../parser/parse.js'; import { walkValues } from './walk.js'; import { renderValue } from '../renderer/render.js'; @@ -13,11 +13,16 @@ import { EnumToken } from './types.js'; */ function expand(ast) { const result = { ...ast, chi: [] }; + let children; for (let i = 0; i < ast.chi.length; i++) { - const node = ast.chi[i]; + let node = ast.chi[i]; if (node.typ === EnumToken.RuleNodeType) { + children = expandRule(node); + for (const child of children) { + child[PARENT] = result; + } // @ts-ignore - result.chi.push(...expandRule(node)); + result.chi.push(...children); } else if (node.typ == EnumToken.AtRuleNodeType && "chi" in node) { let hasRule = false; @@ -29,10 +34,23 @@ function expand(ast) { break; } } - // @ts-ignore - result.chi.push({ ...(hasRule ? expand(node) : node) }); + if (hasRule) { + node = expand(node); + for (const child of node.chi) { + child[PARENT] = result; + } + node[PARENT] = result; + // @ts-ignore + result.chi.push(node); + } + else { + node[PARENT] = result; + // @ts-ignore + result.chi.push(node); + } } else { + node[PARENT] = result; // @ts-ignore result.chi.push(node); } @@ -57,7 +75,10 @@ function expandRule(node) { ast.chi.splice(i--, 1); continue; } - selRule.forEach((arr) => combinators.includes(arr[0].charAt(0)) ? arr.unshift(arSelf) : arr.unshift(arSelf, " ")); + for (let i1 = 0; i1 < selRule.length; i1++) { + const arr = selRule[i1]; + combinators.includes(arr[0].charAt(0)) ? arr.unshift(arSelf) : arr.unshift(arSelf, " "); + } rule.sel = selRule .reduce((acc, curr) => { acc.push(curr.join("")); diff --git a/dist/lib/ast/features/shorthand.js b/dist/lib/ast/features/shorthand.js index 4c3fcfee..2843a483 100644 --- a/dist/lib/ast/features/shorthand.js +++ b/dist/lib/ast/features/shorthand.js @@ -6,7 +6,7 @@ class ComputeShorthandFeature { accept = new Set([ EnumToken.RuleNodeType, EnumToken.AtRuleNodeType, - EnumToken.KeyFramesRuleNodeType, + EnumToken.KeyframesRuleNodeType, ]); get ordering() { return 10; diff --git a/dist/lib/ast/features/transform.js b/dist/lib/ast/features/transform.js index 63eed655..0009cfe3 100644 --- a/dist/lib/ast/features/transform.js +++ b/dist/lib/ast/features/transform.js @@ -6,7 +6,7 @@ import { FeatureWalkMode } from './type.js'; import { STATE } from '../../syntax/constants.js'; class TransformCssFeature { - accept = new Set([EnumToken.RuleNodeType, EnumToken.KeyFramesRuleNodeType]); + accept = new Set([EnumToken.RuleNodeType, EnumToken.KeyframesRuleNodeType]); get ordering() { return 3; } diff --git a/dist/lib/ast/find.js b/dist/lib/ast/find.js index 57b3cdb8..99a59a98 100644 --- a/dist/lib/ast/find.js +++ b/dist/lib/ast/find.js @@ -3,7 +3,7 @@ import { walk, walkValues } from './walk.js'; import { TOKENS } from '../syntax/constants.js'; /** - * Search the ast sub-tree and return the first match + * Search the ast subtree and return the first match * * ```ts * // find the first ast declaration node which name is 'aspect-ratio' @@ -42,6 +42,11 @@ function find(ast, matcher) { return null; } /** + * + * @param ast + * @param matcher + * @returns + * * Search the ast sub-tree by checking each node's value token and return the first match * ```ts @@ -67,10 +72,6 @@ button { console.log({node, value}); ``` - * - * @param ast - * @param matcher - * @returns */ function findByValue(ast, matcher) { let source; diff --git a/dist/lib/ast/minify.js b/dist/lib/ast/minify.js index dc44fd30..11b2dac5 100644 --- a/dist/lib/ast/minify.js +++ b/dist/lib/ast/minify.js @@ -17,7 +17,7 @@ const rules = [ EnumToken.AtRuleNodeType, EnumToken.RuleNodeType, EnumToken.AtRuleTokenType, - EnumToken.KeyFramesRuleNodeType, + EnumToken.KeyframesRuleNodeType, ]; // @ts-ignore const features = Object.values(index).sort((a, b) => a.ordering - b.ordering); @@ -29,6 +29,7 @@ const features = Object.values(index).sort((a, b) => a.ordering - b.ordering); * @param errors * @param nestingContent * + * @param context * @private */ function minify(ast, options = {}, recursive = false, errors, nestingContent, context = {}) { @@ -36,22 +37,24 @@ function minify(ast, options = {}, recursive = false, errors, nestingContent, co let postprocess = false; let parents; let replacement; - if (!("features" in options)) { + // @ts-ignore + let { sourcemap, module, ...options2 } = options; + if (!("features" in options2)) { // @ts-ignore - options = { + options2 = { removeDuplicateDeclarations: true, computeShorthand: true, computeCalcExpression: true, removePrefix: false, features: [], - ...options, + ...options2, }; for (const feature of features) { - feature.register(options); + feature.register(options2); } - options.features.sort((a, b) => a.ordering - b.ordering); + options2.features.sort((a, b) => a.ordering - b.ordering); } - for (const feature of options.features) { + for (const feature of options2.features) { if (feature.processMode & FeatureWalkMode.Pre) { preprocess = true; } @@ -66,17 +69,17 @@ function minify(ast, options = {}, recursive = false, errors, nestingContent, co continue; } replacement = parent; - for (const feature of options.features) { + for (const feature of options2.features) { if ((feature.processMode & FeatureWalkMode.Pre) === 0 || (feature.accept != null && !feature.accept.has(parent.typ))) { continue; } if (rules.includes(replacement.typ) && !Array.isArray(replacement[TOKENS])) { - replacement[TOKENS] = parseString(replacement.typ == EnumToken.RuleNodeType || replacement.typ === EnumToken.KeyFramesRuleNodeType + replacement[TOKENS] = parseString(replacement.typ == EnumToken.RuleNodeType || replacement.typ === EnumToken.KeyframesRuleNodeType ? replacement.sel : replacement.nam); } - const result = feature.run(replacement, options, parent[PARENT] ?? ast, context, FeatureWalkMode.Pre); + const result = feature.run(replacement, options2, parent[PARENT] ?? ast, context, FeatureWalkMode.Pre); if (result != null) { replacement = result; } @@ -95,14 +98,14 @@ function minify(ast, options = {}, recursive = false, errors, nestingContent, co } } } - for (const feature of options.features) { + for (const feature of options2.features) { if (feature.processMode & FeatureWalkMode.Pre && "cleanup" in feature) { // @ts-ignore - feature.cleanup(ast, options, context, FeatureWalkMode.Pre); + feature.cleanup(ast, options2, context, FeatureWalkMode.Pre); } } } - doMinify(ast, options, recursive, errors, nestingContent, context); + doMinify(ast, options2, recursive, errors, nestingContent, context); parents = new Set([ast]); for (const parent of parents) { if (parent.typ == EnumToken.CommentTokenType || parent.typ == EnumToken.CDOCOMMTokenType) { @@ -110,12 +113,12 @@ function minify(ast, options = {}, recursive = false, errors, nestingContent, co } replacement = parent; if (postprocess) { - for (const feature of options.features) { + for (const feature of options2.features) { if ((feature.processMode & FeatureWalkMode.Post) === 0 || (feature.accept != null && !feature.accept.has(parent.typ))) { continue; } - const result = feature.run(replacement, options, parent[PARENT] ?? ast, context, FeatureWalkMode.Post); + const result = feature.run(replacement, options2, parent[PARENT] ?? ast, context, FeatureWalkMode.Post); if (result != null) { replacement = result; } @@ -136,10 +139,10 @@ function minify(ast, options = {}, recursive = false, errors, nestingContent, co } } if (postprocess) { - for (const feature of options.features) { + for (const feature of options2.features) { if (feature.processMode & FeatureWalkMode.Post && "cleanup" in feature) { // @ts-ignore - feature.cleanup(ast, options, context, FeatureWalkMode.Post); + feature.cleanup(ast, options2, context, FeatureWalkMode.Post); } } } @@ -232,9 +235,9 @@ function transformAtRuleMediaPrelude(values) { * Minify at-rule media * - remove redundant tokens * - generate range queries - * @param ast * * @private + * @param tokens */ function minifyAtRuleMedia(tokens) { let hasUpdates = false; @@ -332,7 +335,6 @@ function doMinify(ast, options = {}, recursive = false, errors, nestingContent, } while (previous?.typ === EnumToken.CommentNodeType) { previous = ast.chi[--nodeIndex]; - continue; } node = ast.chi[i]; if (node.typ === EnumToken.AtRuleNodeType && node.nam === "font-face") { @@ -348,8 +350,8 @@ function doMinify(ast, options = {}, recursive = false, errors, nestingContent, continue; } } - else if (node.typ === EnumToken.KeyFramesRuleNodeType) { - if (previous?.typ === EnumToken.KeyFramesRuleNodeType && + else if (node.typ === EnumToken.KeyframesRuleNodeType) { + if (previous?.typ === EnumToken.KeyframesRuleNodeType && node.sel === previous.sel) { // do not merge keyframes // https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/At-rules/@keyframes#resolving_duplicates @@ -637,7 +639,7 @@ function doMinify(ast, options = {}, recursive = false, errors, nestingContent, } if (shouldMerge) { if (((node.typ === EnumToken.RuleNodeType || - node.typ === EnumToken.KeyFramesRuleNodeType) && + node.typ === EnumToken.KeyframesRuleNodeType) && node.sel === previous.sel) || (node.typ == EnumToken.AtRuleNodeType && node.nam !== "font-face" && @@ -650,7 +652,7 @@ function doMinify(ast, options = {}, recursive = false, errors, nestingContent, continue; } else if (node.typ == previous?.typ && - [EnumToken.KeyFramesRuleNodeType, EnumToken.RuleNodeType].includes(node.typ)) { + [EnumToken.KeyframesRuleNodeType, EnumToken.RuleNodeType].includes(node.typ)) { const intersect = diff(previous, node, options); if (intersect != null) { if (intersect.node1.chi.length == 0) { @@ -803,7 +805,9 @@ function optimizeSelector(selector) { } break; } - selector.forEach((selector) => selector.splice(0, optimized.length)); + for (let i1 = 0; i1 < selector.length; i1++) { + selector[i1].splice(0, optimized.length); + } let reducible = optimized.length == 1; if (optimized[0] == "&") { if (optimized[1] == " ") { @@ -1185,7 +1189,6 @@ function wrapNodes(previous, node, match, ast, reducer, i, nodeIndex) { * Diff nodes * @param n1 * @param n2 - * @param reducer * @param options * * @private @@ -1304,17 +1307,36 @@ function diff(n1, n2, options = {}) { // @ts-ignore chi: intersect.reverse(), }; + let op = { level: 0, ...options }; if (result == null || [n1, n2].reduce((acc, curr) => { let css = options.cache.get(curr); if (css == null) { - css = doRender(curr, options).code; + let level = 0; + let parent = curr[PARENT]; + while (parent != null && parent.typ != EnumToken.StyleSheetNodeType) { + level++; + parent = parent[PARENT]; + } + op.level = level; + css = doRender(curr, op).code; options.cache.set(curr, css); } return curr.chi.length == 0 ? acc : acc + css.length; }, 0) <= [node1, node2, result].reduce((acc, curr) => { - const css = doRender(curr, options).code; + let css = options.cache.get(curr); + if (css != null) { + return curr.chi.length == 0 ? acc : acc + css.length; + } + let level = 0; + let parent = curr[PARENT]; + while (parent != null && parent.typ != EnumToken.StyleSheetNodeType) { + level++; + parent = parent[PARENT]; + } + op.level = level; + css = doRender(curr, op).code; return curr.chi.length == 0 ? acc : acc + css.length; }, 0)) { if (node1.chi.length != 0 && node2.chi.length != 0) { diff --git a/dist/lib/ast/types.js b/dist/lib/ast/types.js index 083d23c9..3786dc5c 100644 --- a/dist/lib/ast/types.js +++ b/dist/lib/ast/types.js @@ -385,7 +385,7 @@ var EnumToken; /** * keyframe rule node type */ - EnumToken[EnumToken["KeyFramesRuleNodeType"] = 73] = "KeyFramesRuleNodeType"; + EnumToken[EnumToken["KeyframesRuleNodeType"] = 73] = "KeyframesRuleNodeType"; /** * class selector token type */ diff --git a/dist/lib/ast/walk.js b/dist/lib/ast/walk.js index d593519c..c7260b95 100644 --- a/dist/lib/ast/walk.js +++ b/dist/lib/ast/walk.js @@ -1,3 +1,5 @@ +import { TOKENS } from '../syntax/constants.js'; + /** * Options for the walk function */ @@ -40,6 +42,8 @@ var WalkerEvent; * @param filter control the walk process * @param reverse walk in reverse order * + * @private + * * ```ts * * import {walk} from '@tbela99/css-parser'; @@ -111,11 +115,19 @@ function* walk(node, filter, reverse) { const parents = [node]; const root = node; const map = new Map(); + let options = filter; let isNumeric = false; + let includeValues = false; let i = 0; + if (options != null && typeof options == "object") { + filter = options.filter; + reverse = options.reverse; + includeValues = options.inludeValues; + } while ((node = parents[i++])) { let option = null; if (filter != null) { + // @ts-ignore option = filter(node); isNumeric = typeof option == "number"; if (isNumeric) { @@ -143,8 +155,16 @@ function* walk(node, filter, reverse) { }, }; } - if ("chi" in node && (!isNumeric || (option & WalkerOptionEnum.IgnoreChildren) === 0)) { - parents.splice(i, 0, ...node.chi[reverse ? "toReversed" : "slice"]()); + if (includeValues) { + if (node[TOKENS] != null) { + parents.splice(i, 0, ...(reverse ? node[TOKENS].toReversed() : node[TOKENS])); + } + else if (Array.isArray(node.val)) { + parents.splice(i, 0, ...(reverse ? node.val.toReversed() : node.val)); + } + } + if (node["chi"] != null && (!isNumeric || (option & WalkerOptionEnum.IgnoreChildren) === 0)) { + parents.splice(i, 0, ...(reverse ? node.chi.toReversed() : node.chi)); for (const child of node.chi) { map.set(child, node); } @@ -228,11 +248,6 @@ function* walkValues(values, root = null, filter, reverse) { continue; } used.add(value); - // parents.length = 0; - // while (node != null) { - // parents.push(node); - // node = map.get(node) ?? null; - // } if (filter.fn != null && eventType & WalkerEvent.Enter) { const isValid = filter.type == null || value.typ == filter.type || diff --git a/dist/lib/fs/resolve.js b/dist/lib/fs/resolve.js index 7a2d8b5a..51da577d 100644 --- a/dist/lib/fs/resolve.js +++ b/dist/lib/fs/resolve.js @@ -1,5 +1,8 @@ import { memoize } from '../parser/utils/cache.js'; +/** + * match url + */ const matchUrl = /^(https?:)?\/\//; /** * return the directory name of a path @@ -11,6 +14,9 @@ function dirname(path) { if (path === "") { return ""; } + if (path.startsWith("data:")) { + return path; + } let i = 0; let parts = [""]; for (; i < path.length; i++) { @@ -34,10 +40,7 @@ function splitPath(result) { if (result.length == 0) { return { parts: [], i: 0 }; } - // if (result === "/") { - // return { parts: ["/"], i: 0 }; - // } - const parts = [""]; + const parts = result == "/" ? [] : [""]; let i = 0; for (; i < result.length; i++) { const chr = result.charAt(i); @@ -46,7 +49,7 @@ function splitPath(result) { } // else if (chr == "?" || chr == "#") { // break; - // } + // } else { parts[parts.length - 1] += chr; } @@ -64,6 +67,8 @@ function splitPath(result) { } /** * Nomalize path + * @param path + * @private */ const normalize = memoize(function (path) { let parts = []; @@ -92,14 +97,20 @@ const normalize = memoize(function (path) { while (++k < parts.length) { // if (parts[k] == ".") { // parts.splice(k--, 1); - // } else - if (parts[k] == "..") { + // } else + if (k > 0 && parts[k] == "..") { parts.splice(k - 1, 2); k -= 2; } } return (path.charAt(0) == "/" ? "/" : "") + parts.join("/"); }); +/** + * diff path + * @param path1 + * @param path2 + * @private + */ const diff = memoize(function (path1, path2) { let { parts } = splitPath(path1); const { parts: dirs } = splitPath(path2); @@ -122,39 +133,60 @@ const diff = memoize(function (path1, path2) { * @private */ const resolve = memoize(function (url, currentDirectory, cwd) { - cwd ??= ""; - currentDirectory ??= ""; if (matchUrl.test(url)) { return { absolute: url, relative: url, }; } + cwd ??= ""; + currentDirectory ??= ""; url = normalize(url); + if (cwd !== "") { + cwd = normalize(cwd); + } if (currentDirectory !== "") { currentDirectory = normalize(currentDirectory); - if (url.startsWith(currentDirectory + "/")) { - return { - absolute: url, - relative: url.slice(currentDirectory.length + 1), - }; - } - } - if ((currentDirectory === "" || currentDirectory === ".") && cwd !== "") { - cwd = normalize(cwd); - if (url.startsWith(cwd == "/" ? cwd : cwd + "/")) { - const absolute = url; - const prefix = cwd == "/" ? cwd : cwd + "/"; - return { - absolute, - relative: absolute.startsWith(prefix) ? absolute.slice(prefix.length) : diff(absolute, cwd), - }; - } } + const dir = cwd || currentDirectory; + const absolute = dir == "" || url.startsWith("/") ? resolvePath(url) : resolvePath(dir, url); return { - absolute: url, - relative: url === "" ? "" : diff(url, cwd || currentDirectory), + absolute, + relative: dir === "" ? absolute : diff(absolute, dir), }; }); +/** + * + * @param parts + * @returns + * @private + */ +function resolvePath(...parts) { + const path = parts.filter(Boolean).join("/"); + const isAbsolute = /^[\\/]/.test(path); + const segments = path.split(/[\\/]+/); + const resolved = []; + for (const segment of segments) { + if (!segment || segment === ".") { + continue; + } + if (segment === "..") { + if (resolved.length && resolved[resolved.length - 1] !== "..") { + resolved.pop(); + } + else if (!isAbsolute) { + resolved.push(".."); + } + } + else { + resolved.push(segment); + } + } + let result = resolved.join("/"); + if (isAbsolute) { + result = "/" + result; + } + return result || (isAbsolute ? "/" : "."); +} export { diff, dirname, matchUrl, normalize, resolve }; diff --git a/dist/lib/parser/declaration/map.js b/dist/lib/parser/declaration/map.js index 736cf11f..d1cfc62c 100644 --- a/dist/lib/parser/declaration/map.js +++ b/dist/lib/parser/declaration/map.js @@ -286,12 +286,19 @@ class PropertyMap { return acc; }, []); let isImportant = false; - const filtered = values.map(removeDefaults).filter((x) => x.val.filter((t) => { - if (t.typ == EnumToken.ImportantTokenType) { - isImportant = true; + let dec; + const filtered = []; + for (const declaration of values) { + dec = removeDefaults(declaration); + for (const t of dec.val) { + if (t.typ == EnumToken.ImportantTokenType) { + isImportant = true; + } + if (filtered.length == 0 && t.typ != EnumToken.WhitespaceTokenType && t.typ != EnumToken.ImportantTokenType) { + filtered.push(dec); + } } - return ![EnumToken.WhitespaceTokenType, EnumToken.ImportantTokenType].includes(t.typ); - }).length > 0); + } if (filtered.length == 0 && this.config.default.length > 0) { filtered.push({ typ: EnumToken.DeclarationNodeType, diff --git a/dist/lib/parser/linesmap.js b/dist/lib/parser/linesmap.js index 0365942d..38c9fad1 100644 --- a/dist/lib/parser/linesmap.js +++ b/dist/lib/parser/linesmap.js @@ -10,7 +10,7 @@ class LineMap { * Constructor * @param lines */ - constructor(lines) { + constructor(lines = []) { if (lines.length === 0) { lines.push(0); } @@ -26,9 +26,9 @@ class LineMap { if (offset < 0 || line < 0) { return [1, 1]; } - const column = offset - this.lineStarts[line]; + const column = offset - this.lineStarts[line] + 1; // [line, column] - return [line + 1, column === 0 ? 1 : column]; + return [line + 1, column == 0 ? 1 : column]; } /** * search the greatest index of the value less than or equal to offset diff --git a/dist/lib/parser/parse.js b/dist/lib/parser/parse.js index 8325511f..642a7b1d 100644 --- a/dist/lib/parser/parse.js +++ b/dist/lib/parser/parse.js @@ -4,9 +4,9 @@ import { renderValue } from '../renderer/render.js'; import { EnumToken, EnumAstNodeStatus, ModuleCaseTransformEnum, ModuleScopeEnumOptions } from '../ast/types.js'; import { minify } from '../ast/minify.js'; import { expand } from '../ast/expand.js'; -import { WalkerEvent, walk, walkValues } from '../ast/walk.js'; +import { walk, walkValues, WalkerEvent } from '../ast/walk.js'; import { tokenizeStream, tokenize } from './tokenize.js'; -import { ROOT, LOC, tokensfuncDefMap, STATE, PARENT, TOKENS, ERRORS, pageMarginBoxType } from '../syntax/constants.js'; +import { LOC, tokensfuncDefMap, STATE, PARENT, TOKENS, ROOT, ERRORS, pageMarginBoxType } from '../syntax/constants.js'; import { hashAlgorithms, hash, syncHash } from './utils/hash.js'; import { parseSelector } from './utils/selector.js'; import { parseDeclaration } from './utils/declaration.js'; @@ -24,6 +24,7 @@ import { parseAtRuleFontFeatureValues } from './utils/at-rule-font-feature-value import { matchGenericSyntax } from './utils/at-rule-generic.js'; import { memoize } from './utils/cache.js'; import { SourceFile } from './source.js'; +import { dirname } from '../fs/resolve.js'; function renderTokens(tokens, options) { if (tokens == null || tokens.length === 0) @@ -290,6 +291,134 @@ const generateSyncScopedName = memoize((localName, filePath, pattern, hashLength // if leading char is digit, prefix underscore (very rare) return (/^[0-9]/.test(result) ? "_" : "") + result; }); +/** + * + * @param visitorsDef + * @param errors + * @private + */ +function parseVisitors(visitorsDef, errors) { + const visitors = Object.entries(typeof visitorsDef === "function" ? [visitorsDef] : visitorsDef); + let key; + let value; + let i; + const valuesHandlers = new Map(); + const preValuesHandlers = new Map(); + const postValuesHandlers = new Map(); + const visitorsHandlersMap = new Map(); + const preVisitorsHandlersMap = new Map(); + const postVisitorsHandlersMap = new Map(); + for (i = 0; i < visitors.length; i++) { + key = visitors[i][0]; + value = visitors[i][1]; + if (Number.isInteger(+key)) { + if (Array.isArray(value)) { + visitors.splice(i + 1, 0, ...Object.entries(value)); + continue; + } + if (typeof value == "function") { + key = value.name; + } + } + if (Array.isArray(value)) { + // @ts-ignore + visitors.splice(i + 1, 0, ...value.map((item) => [key, item])); + continue; + } + if (key in EnumToken) { + if (typeof value == "function") { + if (!valuesHandlers.has(EnumToken[key])) { + valuesHandlers.set(EnumToken[key], []); + } + valuesHandlers.get(EnumToken[key]).push(value); + } + else if (typeof value == "object" && "type" in value && "handler" in value && value.type in WalkerEvent) { + if (value.type == WalkerEvent.Enter) { + if (!preValuesHandlers.has(EnumToken[key])) { + preValuesHandlers.set(EnumToken[key], []); + } + preValuesHandlers.get(EnumToken[key]).push(value.handler); + } + else if (value.type == WalkerEvent.Leave) { + if (!postValuesHandlers.has(EnumToken[key])) { + postValuesHandlers.set(EnumToken[key], []); + } + postValuesHandlers.get(EnumToken[key]).push(value.handler); + } + } + else { + errors.push({ action: "ignore", message: `doParse: visitor.${key} is not a valid key name` }); + } + } + else if (["Declaration", "Rule", "AtRule", "KeyframesRule", "KeyframesAtRule"].includes(key)) { + if (typeof value == "function") { + if (!visitorsHandlersMap.has(key)) { + visitorsHandlersMap.set(key, []); + } + visitorsHandlersMap + .get(key) + .push(value); + } + else if (typeof value == "object") { + if ("type" in value && "handler" in value && value.type in WalkerEvent) { + if (value.type == WalkerEvent.Enter) { + if (!preVisitorsHandlersMap.has(key)) { + preVisitorsHandlersMap.set(key, []); + } + preVisitorsHandlersMap + .get(key) + .push(value.handler); + } + else if (value.type == WalkerEvent.Leave) { + if (!postVisitorsHandlersMap.has(key)) { + postVisitorsHandlersMap.set(key, []); + } + postVisitorsHandlersMap + .get(key) + .push(value.handler); + } + } + else { + if (!visitorsHandlersMap.has(key)) { + visitorsHandlersMap.set(key, []); + } + visitorsHandlersMap + .get(key) + .push(value); + } + } + else { + errors.push({ action: "ignore", message: `doParse: visitor.${key} is not a valid key name` }); + } + } + else { + errors.push({ action: "ignore", message: `doParse: visitor.${key} is not a valid key name` }); + } + } + const allHandlers = []; + if (preVisitorsHandlersMap.size > 0) { + allHandlers.push(preVisitorsHandlersMap); + } + if (preValuesHandlers.size > 0) { + allHandlers.push(preValuesHandlers); + } + if (visitorsHandlersMap.size > 0) { + allHandlers.push(visitorsHandlersMap); + } + if (valuesHandlers.size > 0) { + allHandlers.push(valuesHandlers); + } + if (postVisitorsHandlersMap.size > 0) { + allHandlers.push(postVisitorsHandlersMap); + } + if (postValuesHandlers.size > 0) { + allHandlers.push(postValuesHandlers); + } + return { + allHandlers, + includeTokens: preValuesHandlers.size > 0 || valuesHandlers.size > 0 || postValuesHandlers.size > 0, + }; +} /** * Parse css string * @param iter @@ -355,131 +484,24 @@ function doParseSync(iter, options = {}) { }; let tokens = []; let context = ast; - ast[ROOT] = ast; - ast[LOC] = { - sta: 0, - end: 0, - srcId: options.source.id, - }; - let valuesHandlers; - let preValuesHandlers; - let postValuesHandlers; - let preVisitorsHandlersMap; - let visitorsHandlersMap; - let postVisitorsHandlersMap; let item; let node; // @ts-ignore ignore error let parensMatch = 0; let curlyBracketMatch = 0; - if (options.visitor != null) { - valuesHandlers = new Map(); - preValuesHandlers = new Map(); - postValuesHandlers = new Map(); - preVisitorsHandlersMap = new Map(); - visitorsHandlersMap = new Map(); - postVisitorsHandlersMap = new Map(); - const visitors = Object.entries(options.visitor); - let key; - let value; - let i; - for (i = 0; i < visitors.length; i++) { - key = visitors[i][0]; - value = visitors[i][1]; - if (Number.isInteger(+key)) { - visitors.splice(i + 1, 0, ...Object.entries(value)); - continue; - } - if (Array.isArray(value)) { - // @ts-ignore - visitors.splice(i + 1, 0, ...value.map((item) => [key, item])); - continue; - } - if (key in EnumToken) { - if (typeof value == "function") { - if (!valuesHandlers.has(EnumToken[key])) { - valuesHandlers.set(EnumToken[key], []); - } - valuesHandlers.get(EnumToken[key]).push(value); - } - else if (typeof value == "object" && - "type" in value && - "handler" in value && - value.type in WalkerEvent) { - if (value.type == WalkerEvent.Enter) { - if (!preValuesHandlers.has(EnumToken[key])) { - preValuesHandlers.set(EnumToken[key], []); - } - preValuesHandlers - .get(EnumToken[key]) - .push(value.handler); - } - else if (value.type == WalkerEvent.Leave) { - if (!postValuesHandlers.has(EnumToken[key])) { - postValuesHandlers.set(EnumToken[key], []); - } - postValuesHandlers - .get(EnumToken[key]) - .push(value.handler); - } - } - else { - errors.push({ action: "ignore", message: `doParse: visitor.${key} is not a valid key name` }); - } - } - else if (["Declaration", "Rule", "AtRule", "KeyframesRule", "KeyframesAtRule"].includes(key)) { - if (typeof value == "function") { - if (!visitorsHandlersMap.has(key)) { - visitorsHandlersMap.set(key, []); - } - visitorsHandlersMap - .get(key) - .push(value); - } - else if (typeof value == "object") { - if ("type" in value && "handler" in value && value.type in WalkerEvent) { - if (value.type == WalkerEvent.Enter) { - if (!preVisitorsHandlersMap.has(key)) { - preVisitorsHandlersMap.set(key, []); - } - preVisitorsHandlersMap - .get(key) - .push(value.handler); - } - else if (value.type == WalkerEvent.Leave) { - if (!postVisitorsHandlersMap.has(key)) { - postVisitorsHandlersMap.set(key, []); - } - postVisitorsHandlersMap - .get(key) - .push(value.handler); - } - } - else { - if (!visitorsHandlersMap.has(key)) { - visitorsHandlersMap.set(key, []); - } - visitorsHandlersMap - .get(key) - .push(value); - } - } - else { - errors.push({ action: "ignore", message: `doParse: visitor.${key} is not a valid key name` }); - } - } - else { - errors.push({ action: "ignore", message: `doParse: visitor.${key} is not a valid key name` }); - } - } - } - if (Array.isArray(iter)) { - // @ts-expect-error - iter = iter[Symbol.iterator](); - } - while ( - // @ts-expect-error - (item = iter.next().value)) { + let currentItemIndex; + // ast[ROOT] = ast; + ast[LOC] = { + sta: 0, + end: 0, + srcId: options.source.id, + }; + // if (Array.isArray(iter)) { + // // @ts-expect-error + // iter = iter[Symbol.iterator]() as Iterator; + // } + for (currentItemIndex = 0; currentItemIndex < iter.length; currentItemIndex++) { + item = iter[currentItemIndex]; stats.bytesIn = item.bytesIn; stats.tokensCount++; if (BadTokensTypes.includes(item.token.typ)) { @@ -507,8 +529,6 @@ function doParseSync(iter, options = {}) { curlyBracketMatch--; } tokens.push(item.token); - // console.debug([item.token, {parensMatch, curlyBracketMatch}]); - // if (parensMatch === 0) { if (parensMatch === 0 && (item.token.typ === EnumToken.SemiColonTokenType || item.token.typ === EnumToken.BlockStartTokenType || @@ -524,8 +544,7 @@ function doParseSync(iter, options = {}) { let inBlock = 1; tokens = [item.token]; do { - // @ts-expect-error - item = iter.next().value; + item = iter[++currentItemIndex]; if (item == null) { break; } @@ -580,198 +599,160 @@ function doParseSync(iter, options = {}) { ast = expand(ast); } let replacement; - let callable; if (options.visitor != null) { + const handlers = []; + const visitors = parseVisitors(options.visitor, errors); + const subNodes = []; let parens; - for (const result of walk(ast)) { + let genericKey; + let nodes = new Array(stats.tokensCount); + let i; + let k; + let j; + let freeBlock = 1; + nodes[0] = ast; + for (i = 0; i < nodes.length; i++) { + if (nodes[i] == null) { + break; + } + subNodes.length = 0; + if (visitors.includeTokens) { + switch (nodes[i].typ) { + case EnumToken.RuleNodeType: + case EnumToken.AtRuleNodeType: + case EnumToken.KeyframesRuleNodeType: + case EnumToken.KeyframesAtRuleNodeType: + subNodes.push(...nodes[i][TOKENS]); + break; + case EnumToken.DeclarationNodeType: + subNodes.push(...nodes[i].val); + break; + } + } + if (nodes[i].chi != null) { + subNodes.push(...nodes[i].chi); + } + if (subNodes.length > 0) { + if (freeBlock <= i) { + freeBlock = i + 1; + } + for (k = 0; k < subNodes.length; k++) { + j = k + freeBlock; + nodes[j] = subNodes[k]; + nodes[j][PARENT] = nodes[i]; + } + freeBlock += subNodes.length; + } parens = null; - if (valuesHandlers.size > 0 || - preVisitorsHandlersMap.size > 0 || - visitorsHandlersMap.size > 0 || - postVisitorsHandlersMap.size > 0) { - if ((result.node.typ == EnumToken.DeclarationNodeType && - (preVisitorsHandlersMap.has("Declaration") || - visitorsHandlersMap.has("Declaration") || - postVisitorsHandlersMap.has("Declaration"))) || - (result.node.typ == EnumToken.AtRuleNodeType && - (preVisitorsHandlersMap.has("AtRule") || - visitorsHandlersMap.has("AtRule") || - postVisitorsHandlersMap.has("AtRule"))) || - (result.node.typ == EnumToken.KeyframesAtRuleNodeType && - (preVisitorsHandlersMap.has("KeyframesAtRule") || - visitorsHandlersMap.has("KeyframesAtRule") || - postVisitorsHandlersMap.has("KeyframesAtRule")))) { - const handlers = []; - const key = result.node.typ == EnumToken.DeclarationNodeType - ? "Declaration" - : result.node.typ == EnumToken.AtRuleNodeType - ? "AtRule" - : "KeyframesAtRule"; - if (preVisitorsHandlersMap.has(key)) { - handlers.push( - // @ts-expect-error - ...preVisitorsHandlersMap.get(key)); - } - if (visitorsHandlersMap.has(key)) { - // @ts-ignore - handlers.push(...visitorsHandlersMap.get(key)); - } - if (postVisitorsHandlersMap.has(key)) { - // @ts-ignore - handlers.push(...postVisitorsHandlersMap.get(key)); - } - let node = result.node; - for (const handler of handlers) { - callable = - typeof handler == "function" - ? handler - : handler[camelize(node.typ === EnumToken.DeclarationNodeType || - node.typ === EnumToken.AtRuleNodeType - ? node.nam - : node.val)]; - if (callable == null) { - continue; - } - // @ts-expect-error - replacement = callable(node, result[PARENT], ast, function* () { - if (parens == null) { - // @ts-expect-error - parens = [...result.parents()]; + handlers.length = 0; + genericKey = + nodes[i].typ == EnumToken.DeclarationNodeType + ? "Declaration" + : nodes[i].typ == EnumToken.AtRuleNodeType + ? "AtRule" + : nodes[i].typ == EnumToken.KeyframesAtRuleNodeType + ? "KeyframesAtRule" + : nodes[i].typ === EnumToken.KeyframesRuleNodeType + ? "KeyframesRule" + : nodes[i].typ == EnumToken.RuleNodeType + ? "Rule" + : nodes[i].typ == EnumToken.KeyframesRuleNodeType + ? "KeyframesRule" + : null; + let keyName = nodes[i].typ == EnumToken.DeclarationNodeType || nodes[i].typ == EnumToken.AtRuleNodeType + ? camelize(nodes[i].nam) + : nodes[i].typ == EnumToken.KeyframesAtRuleNodeType + ? camelize(nodes[i].val) + : null; + for (const map of visitors.allHandlers) { + // @ts-ignore + if (genericKey != null && map.has(genericKey)) { + // @ts-ignore + for (const handler of map.get(genericKey)) { + if (typeof handler == "function") { + handlers.push(handler); + } + else if (Array.isArray(handler)) { + for (const h of handler) { + if (typeof h == "function") { + handlers.push(h); + } + // @ts-ignore + else if (h[keyName] != null) { + // @ts-ignore + handlers.push(h[keyName]); + } } - yield* parens[Symbol.iterator](); - }); - if (replacement == null) { - continue; } - if (replacement == null || replacement == node) { - continue; + else if (typeof handler.handler == "function") { + handlers.push(handler.handler); } // @ts-ignore - node = replacement; - if (Array.isArray(node)) { - break; + else if (typeof handler[keyName] == "function") { + // @ts-ignore + handlers.push(handler[keyName]); } } - if (node != result.node) { - replaceNodeOrValue(result.parent, result.node, node); - } - } - else if ((result.node.typ == EnumToken.RuleNodeType && - (preVisitorsHandlersMap.has("Rule") || - visitorsHandlersMap.has("Rule") || - postVisitorsHandlersMap.has("Rule"))) || - (result.node.typ == EnumToken.KeyFramesRuleNodeType && - (preVisitorsHandlersMap.has("KeyframesRule") || - visitorsHandlersMap.has("KeyframesRule") || - postVisitorsHandlersMap.has("KeyframesRule")))) { - const handlers = []; - const key = result.node.typ == EnumToken.RuleNodeType ? "Rule" : "KeyframesRule"; - if (preVisitorsHandlersMap.has(key)) { - handlers.push(...preVisitorsHandlersMap.get(key)); - } - if (visitorsHandlersMap.has(key)) { - handlers.push(...visitorsHandlersMap.get(key)); - } - if (postVisitorsHandlersMap.has(key)) { - handlers.push(...postVisitorsHandlersMap.get(key)); - } - let node = result.node; - for (const callable of handlers) { - replacement = callable(node, result.parent, result.root, - // @ts-expect-error - function* () { - if (parens == null) { - // @ts-expect-error - parens = [...result.parents()]; + } + // @ts-ignore + if (map.has(nodes[i].typ)) { + // @ts-ignore + for (const handler of map.get(nodes[i].typ)) { + if (typeof handler == "function") { + handlers.push(handler); + } + else if (Array.isArray(handler)) { + for (const h of handler) { + if (typeof h == "function") { + handlers.push(h); + } } - yield* parens[Symbol.iterator](); - }); - if (replacement == null) { - continue; } - if (replacement == null || replacement == node) { - continue; + else if (typeof handler.handler == "function") { + handlers.push(handler.handler); } // @ts-ignore - node = replacement; - // - if (Array.isArray(node)) { - break; + else if (typeof handler[keyName] == "function") { + // @ts-ignore + handlers.push(handler[keyName]); } } - // @ts-ignore - if (node != result.node) { - // @ts-ignore - replaceNodeOrValue(result.parent, result.node, node); - } } - else if (valuesHandlers.size > 0) { - let node = null; - node = result.node; - if (valuesHandlers.has(node.typ)) { - for (const valueHandler of valuesHandlers.get(node.typ)) { - callable = valueHandler; - replacement = callable(node, result.parent, ast, - // @ts-expect-error - function* () { - if (parens == null) { - // @ts-expect-error - parens = [...result.parents()]; - } - yield* parens[Symbol.iterator](); - }); - if (replacement == null) { - continue; - } - if (replacement != null && replacement != node) { - node = replacement; - } - } - } - if (node != result.node) { - // @ts-ignore - replaceNodeOrValue(result[PARENT], value, node); - } - const tokens = Array.isArray(result.node[TOKENS]) ? result.node[TOKENS] : []; - if (Array.isArray(result.node.val)) { - tokens.push(...result.node.val); - } - if (tokens.length == 0) { - continue; - } - for (const { value, parent, root, parents } of walkValues(tokens, result.node)) { - node = value; - if (valuesHandlers.has(node.typ)) { - let parens = null; - for (const valueHandler of valuesHandlers.get(node.typ)) { - callable = valueHandler; - // @ts-expect-error - let result = callable(node, parent, root, function* () { - if (parens == null) { - // @ts-expect-error - parens = [...parents()]; - } - yield* parens[Symbol.iterator](); - }); - if (result == null) { - continue; - } - if (result != null && result != node) { - node = result; - } - if (Array.isArray(node)) { - break; - } - } - } - if (node != value) { - // @ts-ignore - replaceNodeOrValue(parent, value, node); + } + if (handlers.length == 0) { + continue; + } + let node = nodes[i]; + for (const callable of handlers) { + replacement = callable(node, nodes[i][PARENT], ast, + // @ts-expect-error + function* () { + if (parens == null) { + let node = nodes[i][PARENT]; + while (node != null) { + yield node; + node = node[PARENT]; } } + }); + if (replacement == null) { + continue; } + if (replacement == node) { + continue; + } + // @ts-ignore + node = replacement; + // + if (Array.isArray(node)) { + break; + } + } + if (node != nodes[i]) { + replaceNodeOrValue(nodes[i][PARENT], nodes[i], node); } } + nodes = null; } if (invalidNodes.length > 0) { let count = invalidNodes.length; @@ -794,19 +775,6 @@ function doParseSync(iter, options = {}) { } } } - while (stack.length > 0 && context != ast) { - const previousNode = stack.pop(); - context = (stack[stack.length - 1] ?? ast); - // remove empty nodes - if (options.removeEmpty && - previousNode != null && - previousNode.chi.length == 0 && - context.chi[context.chi.length - 1] == previousNode) { - context.chi.pop(); - continue; - } - break; - } if (options.minify) { if (ast.chi.length > 0) { let passes = options.pass ?? 1; @@ -835,7 +803,7 @@ function doParseSync(iter, options = {}) { scoped: ModuleScopeEnumOptions.Local, naming: ModuleCaseTransformEnum.IgnoreCase, pattern: "", - generateScopedName, + generateScopedName: generateSyncScopedName, ...(typeof options.module != "object" ? {} : options.module), }; const parseModuleTime = performance.now(); @@ -941,10 +909,9 @@ function doParseSync(iter, options = {}) { if (node.typ == EnumToken.DeclarationNodeType) { if (node.nam.startsWith("--")) { if (!(node.nam in namesMapping)) { - let result = moduleSettings.scoped & ModuleScopeEnumOptions.Global + let value = moduleSettings.scoped & ModuleScopeEnumOptions.Global ? node.nam : moduleSettings.generateScopedName(node.nam, moduleSettings.filePath, moduleSettings.pattern, moduleSettings.hashLength); - let value = result; mapping[node.nam] = "--" + (moduleSettings.naming & ModuleCaseTransformEnum.DashCaseOnly || @@ -986,10 +953,9 @@ function doParseSync(iter, options = {}) { continue; } if (!(rule.val in mapping)) { - let result = moduleSettings.scoped & ModuleScopeEnumOptions.Global + let value = moduleSettings.scoped & ModuleScopeEnumOptions.Global ? rule.val : moduleSettings.generateScopedName(rule.val, moduleSettings.filePath, moduleSettings.pattern, moduleSettings.hashLength); - let value = result; mapping[rule.val] = (rule.typ == EnumToken.DashedIdenTokenType ? "--" : "") + (moduleSettings.naming & ModuleCaseTransformEnum.DashCaseOnly || @@ -1160,10 +1126,10 @@ function doParseSync(iter, options = {}) { "unset", ].includes(value.val)) { if (!(value.val in mapping)) { - const result = moduleSettings.scoped & ModuleScopeEnumOptions.Global - ? value.val - : moduleSettings.generateScopedName(value.val, moduleSettings.filePath, moduleSettings.pattern, moduleSettings.hashLength); - mapping[value.val] = result; + mapping[value.val] = + moduleSettings.scoped & ModuleScopeEnumOptions.Global + ? value.val + : moduleSettings.generateScopedName(value.val, moduleSettings.filePath, moduleSettings.pattern, moduleSettings.hashLength); revMapping[mapping[value.val]] = value.val; } value.val = mapping[value.val]; @@ -1235,10 +1201,9 @@ function doParseSync(iter, options = {}) { if (value.typ == EnumToken.ClassSelectorTokenType) { const val = value.val.slice(1); if (!(val in mapping)) { - const result = moduleSettings.scoped & ModuleScopeEnumOptions.Global + let value = moduleSettings.scoped & ModuleScopeEnumOptions.Global ? val : moduleSettings.generateScopedName(val, moduleSettings.filePath, moduleSettings.pattern, moduleSettings.hashLength); - let value = result; mapping[val] = moduleSettings.naming & ModuleCaseTransformEnum.DashCaseOnly || moduleSettings.naming & ModuleCaseTransformEnum.CamelCaseOnly @@ -1271,10 +1236,9 @@ function doParseSync(iter, options = {}) { if ((prefix == "--" && value.typ == EnumToken.DashedIdenTokenType) || (prefix == "" && value.typ == EnumToken.IdenTokenType)) { if (!(value.val in mapping)) { - const result = moduleSettings.scoped & ModuleScopeEnumOptions.Global + let val = moduleSettings.scoped & ModuleScopeEnumOptions.Global ? value.val : moduleSettings.generateScopedName(value.val, moduleSettings.filePath, moduleSettings.pattern, moduleSettings.hashLength); - let val = result; mapping[value.val] = prefix + (moduleSettings.naming & ModuleCaseTransformEnum.DashCaseOnly || @@ -1292,12 +1256,13 @@ function doParseSync(iter, options = {}) { } if (moduleSettings.naming != ModuleCaseTransformEnum.IgnoreCase) { revMapping = {}; - mapping = Object.entries(mapping).reduce((acc, [key, value]) => { - const keyName = getKeyName(key, moduleSettings.naming); - acc[keyName] = value; + mapping = {}; + let keyName; + for (const [key, value] of Object.entries(mapping)) { + keyName = getKeyName(key, moduleSettings.naming); + mapping[keyName] = value; revMapping[value] = keyName; - return acc; - }, {}); + } } result.mapping = mapping; result.revMapping = revMapping; @@ -1382,18 +1347,6 @@ async function doParse(iter, options = {}) { }; let tokens = []; let context = ast; - ast[ROOT] = ast; - ast[LOC] = { - sta: 0, - end: 0, - srcId: options.source.id, - }; - let valuesHandlers; - let preValuesHandlers; - let postValuesHandlers; - let preVisitorsHandlersMap; - let visitorsHandlersMap; - let postVisitorsHandlersMap; const imports = []; let item; let node; @@ -1401,107 +1354,12 @@ async function doParse(iter, options = {}) { let isAsync = typeof iter[Symbol.asyncIterator] === "function"; let parensMatch = 0; let curlyBracketMatch = 0; - if (options.visitor != null) { - valuesHandlers = new Map(); - preValuesHandlers = new Map(); - postValuesHandlers = new Map(); - preVisitorsHandlersMap = new Map(); - visitorsHandlersMap = new Map(); - postVisitorsHandlersMap = new Map(); - const visitors = Object.entries(options.visitor); - let key; - let value; - let i; - for (i = 0; i < visitors.length; i++) { - key = visitors[i][0]; - value = visitors[i][1]; - if (Number.isInteger(+key)) { - visitors.splice(i + 1, 0, ...Object.entries(value)); - continue; - } - if (Array.isArray(value)) { - // @ts-ignore - visitors.splice(i + 1, 0, ...value.map((item) => [key, item])); - continue; - } - if (key in EnumToken) { - if (typeof value == "function") { - if (!valuesHandlers.has(EnumToken[key])) { - valuesHandlers.set(EnumToken[key], []); - } - valuesHandlers.get(EnumToken[key]).push(value); - } - else if (typeof value == "object" && - "type" in value && - "handler" in value && - value.type in WalkerEvent) { - if (value.type == WalkerEvent.Enter) { - if (!preValuesHandlers.has(EnumToken[key])) { - preValuesHandlers.set(EnumToken[key], []); - } - preValuesHandlers - .get(EnumToken[key]) - .push(value.handler); - } - else if (value.type == WalkerEvent.Leave) { - if (!postValuesHandlers.has(EnumToken[key])) { - postValuesHandlers.set(EnumToken[key], []); - } - postValuesHandlers - .get(EnumToken[key]) - .push(value.handler); - } - } - else { - errors.push({ action: "ignore", message: `doParse: visitor.${key} is not a valid key name` }); - } - } - else if (["Declaration", "Rule", "AtRule", "KeyframesRule", "KeyframesAtRule"].includes(key)) { - if (typeof value == "function") { - if (!visitorsHandlersMap.has(key)) { - visitorsHandlersMap.set(key, []); - } - visitorsHandlersMap - .get(key) - .push(value); - } - else if (typeof value == "object") { - if ("type" in value && "handler" in value && value.type in WalkerEvent) { - if (value.type == WalkerEvent.Enter) { - if (!preVisitorsHandlersMap.has(key)) { - preVisitorsHandlersMap.set(key, []); - } - preVisitorsHandlersMap - .get(key) - .push(value.handler); - } - else if (value.type == WalkerEvent.Leave) { - if (!postVisitorsHandlersMap.has(key)) { - postVisitorsHandlersMap.set(key, []); - } - postVisitorsHandlersMap - .get(key) - .push(value.handler); - } - } - else { - if (!visitorsHandlersMap.has(key)) { - visitorsHandlersMap.set(key, []); - } - visitorsHandlersMap - .get(key) - .push(value); - } - } - else { - errors.push({ action: "ignore", message: `doParse: visitor.${key} is not a valid key name` }); - } - } - else { - errors.push({ action: "ignore", message: `doParse: visitor.${key} is not a valid key name` }); - } - } - } + // ast[ROOT] = ast; + ast[LOC] = { + sta: 0, + end: 0, + srcId: options.source.id, + }; if (Array.isArray(iter)) { // @ts-expect-error iter = iter[Symbol.iterator](); @@ -1623,7 +1481,7 @@ async function doParse(iter, options = {}) { const token = node[TOKENS][0]; const url = token.typ == EnumToken.StringTokenType ? token.val.slice(1, -1) : token.val; try { - const src = options.resolve(url, options.src || options.cwd); + const src = options.resolve(url, options.src ? dirname(options.src) : options.cwd); const result = options.load(src); const stream = result instanceof Promise || Object.getPrototypeOf(result).constructor.name == "AsyncFunction" ? await result @@ -1663,210 +1521,163 @@ async function doParse(iter, options = {}) { ast = expand(ast); } let replacement; - let callable; if (options.visitor != null) { let parens; - for (const result of walk(ast)) { + let genericKey; + const handlers = []; + const visitors = parseVisitors(options.visitor, errors); + let nodes = new Array(stats.tokensCount); + const subNodes = []; + let i; + let k; + let j; + let freeblock = 1; + nodes[0] = ast; + for (i = 0; i < nodes.length; i++) { + if (nodes[i] == null) { + break; + } + subNodes.length = 0; + if (visitors.includeTokens) { + switch (nodes[i].typ) { + case EnumToken.RuleNodeType: + case EnumToken.AtRuleNodeType: + case EnumToken.KeyframesRuleNodeType: + case EnumToken.KeyframesAtRuleNodeType: + subNodes.push(...nodes[i][TOKENS]); + break; + case EnumToken.DeclarationNodeType: + subNodes.push(...nodes[i].val); + break; + } + } + if (nodes[i].chi != null) { + subNodes.push(...nodes[i].chi); + } + if (subNodes.length > 0) { + if (freeblock <= i) { + freeblock = i + 1; + } + for (k = 0; k < subNodes.length; k++) { + j = k + freeblock; + nodes[j] = subNodes[k]; + nodes[j][PARENT] = nodes[i]; + } + freeblock += subNodes.length; + } parens = null; - if (valuesHandlers.size > 0 || - preVisitorsHandlersMap.size > 0 || - visitorsHandlersMap.size > 0 || - postVisitorsHandlersMap.size > 0) { - if ((result.node.typ == EnumToken.DeclarationNodeType && - (preVisitorsHandlersMap.has("Declaration") || - visitorsHandlersMap.has("Declaration") || - postVisitorsHandlersMap.has("Declaration"))) || - (result.node.typ == EnumToken.AtRuleNodeType && - (preVisitorsHandlersMap.has("AtRule") || - visitorsHandlersMap.has("AtRule") || - postVisitorsHandlersMap.has("AtRule"))) || - (result.node.typ == EnumToken.KeyframesAtRuleNodeType && - (preVisitorsHandlersMap.has("KeyframesAtRule") || - visitorsHandlersMap.has("KeyframesAtRule") || - postVisitorsHandlersMap.has("KeyframesAtRule")))) { - const handlers = []; - const key = result.node.typ == EnumToken.DeclarationNodeType - ? "Declaration" - : result.node.typ == EnumToken.AtRuleNodeType - ? "AtRule" - : "KeyframesAtRule"; - if (preVisitorsHandlersMap.has(key)) { - handlers.push( - // @ts-expect-error - ...preVisitorsHandlersMap.get(key)); - } - if (visitorsHandlersMap.has(key)) { - // @ts-ignore - handlers.push(...visitorsHandlersMap.get(key)); - } - if (postVisitorsHandlersMap.has(key)) { - // @ts-ignore - handlers.push(...postVisitorsHandlersMap.get(key)); - } - let node = result.node; - for (const handler of handlers) { - callable = - typeof handler == "function" - ? handler - : handler[camelize(node.typ === EnumToken.DeclarationNodeType || - node.typ === EnumToken.AtRuleNodeType - ? node.nam - : node.val)]; - if (callable == null) { - continue; - } - // @ts-expect-error - replacement = callable(node, result[PARENT], ast, function* () { - if (parens == null) { - // @ts-expect-error - parens = [...result.parents()]; + handlers.length = 0; + genericKey = + nodes[i].typ == EnumToken.DeclarationNodeType + ? "Declaration" + : nodes[i].typ == EnumToken.AtRuleNodeType + ? "AtRule" + : nodes[i].typ == EnumToken.KeyframesAtRuleNodeType + ? "KeyframesAtRule" + : nodes[i].typ === EnumToken.KeyframesRuleNodeType + ? "KeyframesRule" + : nodes[i].typ == EnumToken.RuleNodeType + ? "Rule" + : nodes[i].typ == EnumToken.KeyframesRuleNodeType + ? "KeyframesRule" + : null; + let keyName = nodes[i].typ == EnumToken.DeclarationNodeType || nodes[i].typ == EnumToken.AtRuleNodeType + ? camelize(nodes[i].nam) + : nodes[i].typ == EnumToken.KeyframesAtRuleNodeType + ? camelize(nodes[i].val) + : null; + for (const map of visitors.allHandlers) { + // @ts-ignore + if (genericKey != null && map.has(genericKey)) { + // @ts-ignore + for (const handler of map.get(genericKey)) { + if (typeof handler == "function") { + handlers.push(handler); + } + else if (Array.isArray(handler)) { + for (const h of handler) { + if (typeof h == "function") { + handlers.push(h); + } + // @ts-ignore + else if (h[keyName] != null) { + // @ts-ignore + handlers.push(h[keyName]); + } } - yield* parens[Symbol.iterator](); - }); - if (replacement == null) { - continue; } - if (replacement instanceof Promise) { - replacement = await replacement; - } - if (replacement == null || replacement == node) { - continue; + else if (typeof handler.handler == "function") { + handlers.push(handler.handler); } // @ts-ignore - node = replacement; - if (Array.isArray(node)) { - break; + else if (typeof handler[keyName] == "function") { + // @ts-ignore + handlers.push(handler[keyName]); } } - if (node != result.node) { - replaceNodeOrValue(result.parent, result.node, node); - } - } - else if ((result.node.typ == EnumToken.RuleNodeType && - (preVisitorsHandlersMap.has("Rule") || - visitorsHandlersMap.has("Rule") || - postVisitorsHandlersMap.has("Rule"))) || - (result.node.typ == EnumToken.KeyFramesRuleNodeType && - (preVisitorsHandlersMap.has("KeyframesRule") || - visitorsHandlersMap.has("KeyframesRule") || - postVisitorsHandlersMap.has("KeyframesRule")))) { - const handlers = []; - const key = result.node.typ == EnumToken.RuleNodeType ? "Rule" : "KeyframesRule"; - if (preVisitorsHandlersMap.has(key)) { - handlers.push(...preVisitorsHandlersMap.get(key)); - } - if (visitorsHandlersMap.has(key)) { - handlers.push(...visitorsHandlersMap.get(key)); - } - if (postVisitorsHandlersMap.has(key)) { - handlers.push(...postVisitorsHandlersMap.get(key)); - } - let node = result.node; - for (const callable of handlers) { - replacement = callable(node, result.parent, result.root, - // @ts-expect-error - function* () { - if (parens == null) { - // @ts-expect-error - parens = [...result.parents()]; + } + // @ts-ignore + if (map.has(nodes[i].typ)) { + // @ts-ignore + for (const handler of map.get(nodes[i].typ)) { + if (typeof handler == "function") { + handlers.push(handler); + } + else if (Array.isArray(handler)) { + for (const h of handler) { + if (typeof h == "function") { + handlers.push(h); + } } - yield* parens[Symbol.iterator](); - }); - if (replacement == null) { - continue; - } - if (replacement instanceof Promise) { - replacement = await replacement; } - if (replacement == null || replacement == node) { - continue; + else if (typeof handler.handler == "function") { + handlers.push(handler.handler); } // @ts-ignore - node = replacement; - // - if (Array.isArray(node)) { - break; + else if (typeof handler[keyName] == "function") { + // @ts-ignore + handlers.push(handler[keyName]); } } - // @ts-ignore - if (node != result.node) { - // @ts-ignore - replaceNodeOrValue(result.parent, result.node, node); - } } - else if (valuesHandlers.size > 0) { - let node = null; - node = result.node; - if (valuesHandlers.has(node.typ)) { - for (const valueHandler of valuesHandlers.get(node.typ)) { - callable = valueHandler; - replacement = callable(node, result.parent, ast, - // @ts-expect-error - function* () { - if (parens == null) { - // @ts-expect-error - parens = [...result.parents()]; - } - yield* parens[Symbol.iterator](); - }); - if (replacement == null) { - continue; - } - if (replacement instanceof Promise) { - replacement = await replacement; - } - if (replacement != null && replacement != node) { - node = replacement; - } - } - } - if (node != result.node) { - // @ts-ignore - replaceNodeOrValue(result[PARENT], value, node); - } - const tokens = Array.isArray(result.node[TOKENS]) ? result.node[TOKENS] : []; - if (Array.isArray(result.node.val)) { - tokens.push(...result.node.val); - } - if (tokens.length == 0) { - continue; - } - for (const { value, parent, root, parents } of walkValues(tokens, result.node)) { - node = value; - if (valuesHandlers.has(node.typ)) { - let parens = null; - for (const valueHandler of valuesHandlers.get(node.typ)) { - callable = valueHandler; - // @ts-expect-error - let result = callable(node, parent, root, function* () { - if (parens == null) { - // @ts-expect-error - parens = [...parents()]; - } - yield* parens[Symbol.iterator](); - }); - if (result == null) { - continue; - } - if (result instanceof Promise) { - result = await result; - } - if (result != null && result != node) { - node = result; - } - if (Array.isArray(node)) { - break; - } - } - } - if (node != value) { - // @ts-ignore - replaceNodeOrValue(parent, value, node); + } + if (handlers.length == 0) { + continue; + } + let node = nodes[i]; + for (const callable of handlers) { + replacement = callable(node, nodes[i][PARENT], ast, + // @ts-expect-error + function* () { + if (parens == null) { + let node = nodes[i][PARENT]; + while (node != null) { + yield node; + node = node[PARENT]; } } + }); + if (replacement == null) { + continue; + } + if (replacement instanceof Promise) { + replacement = await replacement; + } + if (replacement == null || replacement == node) { + continue; + } + // @ts-ignore + node = replacement; + // + if (Array.isArray(node)) { + break; } } + if (node != nodes[i]) { + replaceNodeOrValue(nodes[i][PARENT], nodes[i], node); + } } + nodes = null; } if (invalidNodes.length > 0) { let count = invalidNodes.length; @@ -1889,19 +1700,6 @@ async function doParse(iter, options = {}) { } } } - while (stack.length > 0 && context != ast) { - const previousNode = stack.pop(); - context = (stack[stack.length - 1] ?? ast); - // remove empty nodes - if (options.removeEmpty && - previousNode != null && - previousNode.chi.length == 0 && - context.chi[context.chi.length - 1] == previousNode) { - context.chi.pop(); - continue; - } - break; - } if (options.minify) { if (ast.chi.length > 0) { let passes = options.pass ?? 1; @@ -2097,6 +1895,7 @@ async function doParse(iter, options = {}) { parentRule.chi.splice(parentRule.chi.indexOf(node), 1); continue; } + const resolvedSrc = options.resolve(options.src, options.cwd); for (const token of composeSelectors) { // composes: a b c; if (token.r == null) { @@ -2166,8 +1965,10 @@ async function doParse(iter, options = {}) { setParent: false, src: src.relative, })); - const srcIndex = (src.relative.startsWith("/") || src.relative.startsWith("../") ? "" : "./") + - src.relative; + let srcIndex = options.resolve(src.absolute, resolvedSrc.absolute).relative; + if (!srcIndex.startsWith("/") && !srcIndex.startsWith("../")) { + srcIndex = `./${srcIndex}`; + } if (Object.keys(root.mapping).length > 0) { importMapping[srcIndex] = {}; } @@ -2419,26 +2220,6 @@ async function doParse(iter, options = {}) { EnumToken.DescendantCombinatorTokenType) { parent[TOKENS].splice(index, 1); } - // if (val == ":global") { - // for (; index < (parent as AstRule)[TOKENS]!.length; index++) { - // if ( - // (parent as AstRule)[TOKENS]![index].typ == - // EnumToken.CommaTokenType || - // ([ - // EnumToken.PseudoClassFuncTokenType, - // EnumToken.PseudoClassTokenType, - // ].includes((parent as AstRule)[TOKENS]![index].typ) && - // [":global", ":local"].includes( - // ( - // (parent as AstRule)[TOKENS]![index] as PseudoClassToken - // ).val.toLowerCase(), - // )) - // ) { - // break; - // } - // global.add((parent as AstRule)[TOKENS]![index]); - // } - // } } break; } @@ -2452,12 +2233,6 @@ async function doParse(iter, options = {}) { case ":local": parent[TOKENS].splice(parent[TOKENS].indexOf(value), 1, ...value.chi); break; - // (parent as AstRule)[TOKENS]!.splice( - // (parent as AstRule)[TOKENS]!.indexOf(value), - // 1, - // ...(value as FunctionToken).chi, - // ); - // break; } } })) { @@ -2743,6 +2518,8 @@ function parseNode(tokens, context, options, errors, stats, invalidNodes) { return null; } /** + * @param stream + * @param context * @param options * @param errors * @param parseAsBlock @@ -2819,7 +2596,6 @@ function parseAtRule(stream, context, options, errors, parseAsBlock = null) { parseAsBlock = blockAllowed; } if (syntax != null && atRule.nam !== "layer" && parseAsBlock !== blockAllowed) { - success = false; errors.push({ action: "drop", node: atRule, @@ -3307,7 +3083,7 @@ function parseAtRule(stream, context, options, errors, parseAsBlock = null) { action: "drop", node: atRule, location: options.source.getSourceLocation(atRule[LOC].sta), - message: "node is allowd only in @page rule", + message: "node is allowed only in @page rule", }); } else { @@ -3446,9 +3222,6 @@ function parseAtRule(stream, context, options, errors, parseAsBlock = null) { if (result.errors.length > 0) { errors.push(...result.errors); } - // else if (atRuleName === "document") { - // parseUrlToken(stream); - // } if (result.success) { let i = 0; const stack = []; @@ -3544,12 +3317,14 @@ function parseString(src, options = { parseColor: true }, errors) { position: 0, currentPosition: -1, }; - const result = parseTokens([...tokenize(parseInfo)].map((t) => t.token), options, errors); - // remove EOF token - result.pop(); - if (result.at(-1)?.typ === EnumToken.WhitespaceTokenType) { - result.pop(); + const tokenResults = tokenize(parseInfo); + const mapped = []; + for (const token of tokenResults) { + mapped.push(token.token); } + const result = parseTokens(mapped, options, errors); + // remove EOF token + result.splice(result.length - (result[result.length - 2]?.typ === EnumToken.WhitespaceTokenType ? 2 : 1), 2); return result; } /** @@ -3640,7 +3415,6 @@ function parseTokens(tokens, options, errors) { node, location: options.source.getSourceLocation(node[LOC].sta), }); - // return []; continue; } index = tokens.indexOf(stack.at(-1)); diff --git a/dist/lib/parser/source.js b/dist/lib/parser/source.js index 6f758722..04d7d7e0 100644 --- a/dist/lib/parser/source.js +++ b/dist/lib/parser/source.js @@ -1,3 +1,4 @@ +import { SourceMap } from '../renderer/sourcemap/sourcemap.js'; import { LineMap } from './linesmap.js'; /** @@ -8,6 +9,7 @@ let sourceId = 0; * Source file helper class */ class SourceFile { + inputSourceMap = null; /** * Source file ID */ @@ -26,7 +28,6 @@ class SourceFile { content; /** * Constructor - * @param id * @param content * @param lines * @param file @@ -40,7 +41,6 @@ class SourceFile { /** * Update source content * @param content - * @param lines */ append(content) { this.content += content; @@ -98,6 +98,20 @@ class SourceFile { addLineStart(lineStart) { this.lineStarts.addLineStart(lineStart); } + /** + * set input source map + * @param inputSourceMap + */ + setInputSourceMap(inputSourceMap) { + this.inputSourceMap = inputSourceMap == null ? null : new SourceMap(inputSourceMap); + } + /** + * return input source map + * @returns + */ + getInputSourceMap() { + return this.inputSourceMap; + } } export { SourceFile }; diff --git a/dist/lib/parser/tokenize.js b/dist/lib/parser/tokenize.js index e0cb381b..2145962b 100644 --- a/dist/lib/parser/tokenize.js +++ b/dist/lib/parser/tokenize.js @@ -378,7 +378,7 @@ function next(parseInfo, count = 1) { return char; } /** - * Tokenize css string + * Tokenize CSS string * @param parseInfo * @param yieldEOFToken */ @@ -405,8 +405,6 @@ function tokenize(parseInfo, yieldEOFToken = true) { parseInfo.buffer = ""; while ((value = peek(parseInfo))) { charCode = value.charCodeAt(0); - // nextCharCode = nextValue.charCodeAt(0); - // console.debug({value, buffer}); switch (charCode) { case 61 /* TokenMap.EQUALS */: if (buffer.length > 0) { @@ -771,10 +769,6 @@ function tokenize(parseInfo, yieldEOFToken = true) { break; } buffer += value + next(parseInfo); - // buffer += - // (parseInfo.offset == parseInfo.currentPosition - // ? parseInfo.buffer.slice(-1) - // : parseInfo.stream.charAt(parseInfo.currentPosition - parseInfo.offset - 1)) + value; break; case 39 /* TokenMap.SINGLE_QUOTE */: case 34 /* TokenMap.DOUBLE_QUOTE */: diff --git a/dist/lib/parser/utils/selector.js b/dist/lib/parser/utils/selector.js index 090bf0c1..04b911a1 100644 --- a/dist/lib/parser/utils/selector.js +++ b/dist/lib/parser/utils/selector.js @@ -48,7 +48,7 @@ function parseSelector(tokens, context, options, errors) { return acc; }, [])); return { - typ: EnumToken.KeyFramesRuleNodeType, + typ: EnumToken.KeyframesRuleNodeType, sel: [ ...splitTokenList(trimArray(tokens)).reduce((acc, curr) => { acc.add(curr.reduce((acc, curr) => acc + renderValue(curr, { minify: false }), "")); diff --git a/dist/lib/renderer/render.js b/dist/lib/renderer/render.js index 23d1108e..6c90b117 100644 --- a/dist/lib/renderer/render.js +++ b/dist/lib/renderer/render.js @@ -61,22 +61,28 @@ function doRender(data, options = {}, mapping) { const startTime = performance.now(); const errors = []; const sourcemap = options.sourcemap ? new SourceMap() : null; + const sourcemaps = options.sourcemap ? [] : null; const cache = Object.create(null); const sourceLocation = { - srcId: 0, - sta: 0, end: 0, }; - const linesMap = new LineMap([]); + const linesMap = options.sourcemap ? new LineMap() : null; let code = ""; if (mapping != null) { if (mapping.importMapping != null) { - for (const [key, value] of Object.entries(mapping.importMapping)) { + const absolutePath = options.resolve(options.output != null ? dirname(options.output) : dirname(options.src), options.cwd).absolute; + for (let [key, value] of Object.entries(mapping.importMapping)) { + key = options.resolve(options.resolve(key, options.cwd).absolute, absolutePath).relative; + if (!key.startsWith("/") && !key.startsWith(".")) { + key = "./" + key; + } code += `:import("${key}")${options.indent}{${options.newLine}${Object.entries(value).reduce((acc, [k, v]) => acc + (acc.length > 0 ? options.newLine : "") + `${options.indent}${v}:${options.indent}${k};`, "")}${options.newLine}}${options.newLine}`; } } code += `:export${options.indent}{${options.newLine}${Object.entries(mapping.mapping).reduce((acc, [k, v]) => acc + (acc.length > 0 ? options.newLine : "") + `${options.indent}${k}:${options.indent}${v};`, "")}${options.newLine}}${options.newLine}`; - move(sourceLocation, linesMap, code); + if (sourcemap != null) { + move(sourceLocation, linesMap, code); + } } if (options.output != null) { // @ts-ignore @@ -88,7 +94,7 @@ function doRender(data, options = {}, mapping) { [EnumToken.StyleSheetNodeType, EnumToken.AtRuleNodeType, EnumToken.RuleNodeType].includes(data.typ) && "chi" in data ? expand(data) - : data, options, sourcemap, sourceLocation, linesMap, errors, function reducer(acc, curr) { + : data, options, sourcemaps, sourceLocation, linesMap, errors, function reducer(acc, curr) { if (curr.typ == EnumToken.CommentTokenType && options.removeComments) { if (!options.preserveLicense || !curr.val.startsWith("/*!")) { return acc; @@ -103,6 +109,7 @@ function doRender(data, options = {}, mapping) { }, }; if (sourcemap != null) { + sourcemap.addAll(sourcemaps); result.map = sourcemap; if (options.sourcemap === "inline") { result.code += `\n/*# sourceMappingURL=${result.map.toUrl()} */`; @@ -115,37 +122,88 @@ function doRender(data, options = {}, mapping) { * @param node * @param options * @param cache - * @param sourcemap - * @param position + * @param sourcemaps + * @param sourceLocation + * @param linesMap * @param str * * @internal */ -function updateSourceMap(node, options, cache, sourcemap, sourceLocation, linesMap, str) { - if ([ - EnumToken.RuleNodeType, - EnumToken.AtRuleNodeType, - EnumToken.KeyFramesRuleNodeType, - EnumToken.KeyframesAtRuleNodeType, - ].includes(node.typ)) { - let srcId = node[LOC]?.srcId ?? 0; - let sourceFileName = options.sourcesMap?.get(srcId)?.getFileName?.() || null; - if (sourceFileName != null && options.output != null) { - if (cache[sourceFileName] == null) { - cache[sourceFileName] = options.resolve(sourceFileName, dirname(options.output)).relative; +function updateSourceMap(node, options, cache, sourcemaps, sourceLocation, linesMap, str) { + let offset = 0; + while (true) { + if (str.charAt(offset) == options.newLine) { + offset += options.newLine.length; + continue; + } + if (str.charAt(offset) == options.indent) { + offset += options.indent.length; + continue; + } + break; + } + if (offset > 0) { + move(sourceLocation, linesMap, str.slice(0, offset)); + } + if (node[LOC] != null && + [ + EnumToken.RuleNodeType, + EnumToken.AtRuleNodeType, + EnumToken.KeyframesRuleNodeType, + EnumToken.KeyframesAtRuleNodeType, + ].includes(node.typ)) { + const source = options.sourcesMap.get(node[LOC].srcId); + const inputSourceMap = source.getInputSourceMap(); + const offsets = source.getOffsets(node[LOC].sta); + const [newLine, newColumn] = linesMap.getOffsets(sourceLocation.end); + let records = null; + let srcId = node[LOC].srcId; + let sourceFileName = source.getFileName() || null; + let sourceContent = source.getContent() || null; + if (inputSourceMap != null && (records = inputSourceMap.find(offsets[0], offsets[1])) != null) { + for (const record of records) { + // @ts-ignore + sourceFileName = record[0] || null; + // @ts-ignore + offsets[0] = record[1]; + // @ts-ignore + offsets[1] = record[2]; + sourceContent = record[3] || null; + if (sourceFileName != null && options.output != null && !sourceFileName.startsWith("data:")) { + if (cache[sourceFileName] == null) { + const absolute = options.resolve(dirname(options.output), options.cwd) + .absolute; + const absoluteSourcePath = options.resolve(dirname(options.src || ""), options.cwd).absolute; + // resolution is relative to the source file + const absoluteSourceFileName = options.resolve(sourceFileName, absoluteSourcePath) + .absolute; + cache[sourceFileName] = options.resolve(absoluteSourceFileName, absolute).relative; + } + sourceFileName = cache[sourceFileName]; + } + sourcemaps.push([newLine, newColumn, srcId, ...offsets, sourceFileName, sourceContent]); } - sourceFileName = cache[sourceFileName]; } - // @ts-ignore - sourcemap.add(...linesMap.getOffsets(sourceLocation.end), srcId, - // @ts-ignore - ...options.sourcesMap?.get(srcId)?.getOffsets(sourceLocation.sta), sourceFileName, options.sourcesMap?.get(srcId)?.getContent?.()); + else { + if (sourceFileName != null && options.output != null && !sourceFileName.startsWith("data:")) { + if (cache[sourceFileName] == null) { + const absolute = options.resolve(dirname(options.output), options.cwd) + .absolute; + const absoluteSourceFileName = options.resolve(sourceFileName, options.cwd) + .absolute; + cache[sourceFileName] = options.resolve(absoluteSourceFileName, absolute).relative; + } + sourceFileName = cache[sourceFileName]; + } + sourcemaps.push([newLine, newColumn, srcId, ...offsets, sourceFileName, sourceContent]); + } } - move(sourceLocation, linesMap, str); + move(sourceLocation, linesMap, offset > 0 ? str.slice(offset) : str); } /** * Update position - * @param position + * @param sourceLocation + * @param linesMap * @param str */ function move(sourceLocation, linesMap, str) { @@ -175,8 +233,9 @@ function move(sourceLocation, linesMap, str) { * render ast node * @param data * @param options - * @param sourcemap - * @param position + * @param sourcemaps + * @param sourceLocation + * @param linesMap * @param errors * @param reducer * @param cache @@ -185,13 +244,16 @@ function move(sourceLocation, linesMap, str) { * * @internal */ -function renderAstNode(data, options, sourcemap, sourceLocation, linesMap, errors, reducer, cache, level = 0, indents = []) { +function renderAstNode(data, options, sourcemaps, sourceLocation, linesMap, errors, reducer, cache, level = 0, indents = []) { if (indents.length < level + 1) { indents.push(options.indent.repeat(level)); } if (indents.length < level + 2) { indents.push(options.indent.repeat(level + 1)); } + // @ts-ignore + let children = ""; + let str = ""; const indent = indents[level]; const indentSub = indents[level + 1]; switch (data.typ) { @@ -202,37 +264,46 @@ function renderAstNode(data, options, sourcemap, sourceLocation, linesMap, error case EnumToken.CommentNodeType: case EnumToken.CDOCOMMNodeType: if (data.val.startsWith("/*# sourceMappingURL=")) { - // ignore sourcemap + // ignore sourcemap comment return ""; } return !options.removeComments || (options.preserveLicense && data.val.startsWith("/*!")) ? data.val : ""; case EnumToken.StyleSheetNodeType: - return data.chi.reduce((css, node) => { - const hasPreviousContent = css !== ""; - const str = renderAstNode(node, options, sourcemap, sourceLocation, linesMap, errors, reducer, cache, level, indents); + for (const node of data.chi) { + str = renderAstNode(node, options, sourcemaps, sourceLocation, linesMap, errors, reducer, cache, level, indents); if (str === "") { - return css; + continue; } - if (sourcemap != null && node[LOC] != null) { - updateSourceMap(node, options, cache, sourcemap, sourceLocation, linesMap, (hasPreviousContent ? options.newLine : "") + str); + if (children.length > 0) { + str = options.newLine + str; } - if (!hasPreviousContent) { - return str; + children += str; + if (sourcemaps != null && str !== "" && options.newLine) { + move(sourceLocation, linesMap, options.newLine); } - return `${css}${options.newLine}${str}`; - }, ""); + } + return children; case EnumToken.AtRuleNodeType: case EnumToken.RuleNodeType: - case EnumToken.KeyFramesRuleNodeType: + case EnumToken.KeyframesRuleNodeType: case EnumToken.KeyframesAtRuleNodeType: if ([EnumToken.AtRuleNodeType, EnumToken.KeyframesAtRuleNodeType].includes(data.typ) && !("chi" in data)) { return `${indent}@${data.nam}${data.val === "" ? "" : options.indent || " "}${data.val};`; } - // @ts-ignore - let children = data.chi.reduce((css, node) => { - let str; + const prelude = (indent.length > 0 ? options.newLine : "") + + indent + + ([EnumToken.AtRuleNodeType, EnumToken.KeyframesAtRuleNodeType].includes(data.typ) + ? `@${data.nam}${data.val === "" ? "" : options.indent || " "}${data.val}${options.indent}{` + : data.sel + `${options.indent}{`); + if (sourcemaps != null) { + updateSourceMap(data, options, cache, sourcemaps, sourceLocation, linesMap, prelude); + } + let node; + let recordDeclarationSourceMap = data.typ == EnumToken.AtRuleNodeType; + for (let i = 0; i < data.chi.length; i++) { + node = data.chi[i]; if (node.typ == EnumToken.CommentNodeType) { str = options.removeComments && @@ -241,73 +312,59 @@ function renderAstNode(data, options, sourcemap, sourceLocation, linesMap, error : node.val; } else if (node.typ == EnumToken.DeclarationNodeType) { - // if (!(node).nam.startsWith("--") && (node).val.length === 0) { - // // @ts-ignore - // errors.push({ - // action: "ignore", - // message: `render: invalid declaration ${JSON.stringify(node)}`, - // location: node[LOC], - // }); - // return ""; - // } str = `${node.nam}:${options.indent}${(options.minify ? filterValues(node.val) : node.val) .reduce(reducer, "") .trimEnd()};`; } - // else if (node.typ == EnumToken.AtRuleNodeType && !("chi" in node)) { - // str = `${(node).val === "" ? "" : options.indent || " "}${(node).val};`; - // } else { - str = renderAstNode(node, options, sourcemap, sourceLocation, linesMap, errors, reducer, cache, level + 1, indents); - } - if (css === "") { - return str; + str = renderAstNode(node, options, sourcemaps, sourceLocation, linesMap, errors, reducer, cache, level + 1, indents); + if (str === "") { + continue; + } + children += str; + str = ""; + continue; } if (str === "") { - return css; + continue; + } + str = options.newLine + indentSub + str; + children += str; + if (sourcemaps != null && str !== "") { + move(sourceLocation, linesMap, str); + if (node.typ == EnumToken.DeclarationNodeType && recordDeclarationSourceMap) { + // if declaration is child of at-rule, then record it + // .rule { + // @media screen { + // color: red; + // } + // } + const source = options.sourcesMap.get(node[LOC].srcId); + sourcemaps.push([ + ...linesMap.getOffsets(sourceLocation.end - str.length + options.newLine.length + indentSub.length), + node[LOC].srcId, + ...source.getOffsets(node[LOC].sta), + source.getFileName(), + source.getContent(), + ]); + } } - return `${css}${options.newLine}${indentSub}${str}`; - }, ""); - if (options.removeEmpty && children === "") { - return ""; } if (children.endsWith(";")) { children = children.slice(0, -1); + sourceLocation.end--; } - const rendered = [EnumToken.AtRuleNodeType, EnumToken.KeyframesAtRuleNodeType].includes(data.typ) - ? `@${data.nam}${data.val === "" ? "" : options.indent || " "}${data.val}${options.indent}{${options.newLine}` + - (children === "" ? "" : indentSub + children + options.newLine) + - indent + - `}` - : data.sel + - `${options.indent}{${options.newLine}` + - (children === "" ? "" : indentSub + children + options.newLine) + - indent + - `}`; - if (sourcemap != null && data[LOC] != null) { - updateSourceMap(data, options, cache, sourcemap, { ...sourceLocation }, linesMap.clone(), rendered); + if (options.removeEmpty && children === "") { + sourceLocation.end -= prelude.length; + return ""; } - return rendered; - // case EnumToken.CssVariableTokenType: - // case EnumToken.CssVariableImportTokenType: - // return `@value ${(data).val}:${options.indent}${filterValues( - // options.minify - // ? (data).val - // : (data).val, - // ) - // .reduce(reducer, "") - // .trim()};`; - // case EnumToken.CssVariableDeclarationMapTokenType: - // return `@value ${filterValues((data as CssVariableMapTokenType).vars) - // .reduce((acc, curr) => acc + renderValue(curr), "") - // .trim()} from ${filterValues((data as CssVariableMapTokenType).from) - // .reduce((acc, curr) => acc + renderValue(curr), "") - // .trim()};`; - // case EnumToken.InvalidDeclarationNodeType: - // case EnumToken.InvalidRuleNodeType: - // case EnumToken.InvalidAtRuleNodeType: + const end = options.newLine + indent + `}`; + if (sourcemaps != null) { + move(sourceLocation, linesMap, end); + } + return prelude + children + end; default: return ""; } @@ -316,6 +373,9 @@ function renderAstNode(data, options, sourcemap, sourceLocation, linesMap, error * render ast token * @param token * @param options + * @param cache + * @param reducer + * @param errors * @private */ function renderValue(token, options = {}, cache = Object.create(null), reducer, errors) { diff --git a/dist/lib/renderer/sourcemap/lib/codec.js b/dist/lib/renderer/sourcemap/lib/codec.js new file mode 100644 index 00000000..ab152589 --- /dev/null +++ b/dist/lib/renderer/sourcemap/lib/codec.js @@ -0,0 +1,78 @@ +// from https://github.com/Rich-Harris/vlq/tree/master +// credit: Rich Harris +const integer_to_char = {}; +const char_to_integer = {}; +let i = 0; +for (const char of 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=') { + char_to_integer[char] = i; + integer_to_char[i++] = char; +} +/** + * @param {string} str + */ +function decode(str) { + /** @type {number[]} */ + let result = []; + let shift = 0; + let value = 0; + for (let i = 0; i < str.length; i += 1) { + let integer = char_to_integer[str[i]]; + if (integer === undefined) { + throw new Error('Invalid character (' + str[i] + ')'); + } + const has_continuation_bit = integer & 32; + integer &= 31; + value += integer << shift; + if (has_continuation_bit) { + shift += 5; + } + else { + const should_negate = value & 1; + value >>>= 1; + if (should_negate) { + result.push(value === 0 ? -2147483648 : -value); + } + else { + result.push(value); + } + // reset + value = shift = 0; + } + } + return result; +} +/** + * + * @param value + * @returns + */ +function encode(value) { + if (typeof value === 'number') { + return encode_integer(value); + } + let result = ''; + for (let i = 0; i < value.length; i += 1) { + result += encode_integer(value[i]); + } + return result; +} +function encode_integer(num) { + let result = ''; + if (num < 0) { + num = (-num << 1) | 1; + } + else { + num <<= 1; + } + do { + let clamped = num & 31; + num >>>= 5; + if (num > 0) { + clamped |= 32; + } + result += integer_to_char[clamped]; + } while (num > 0); + return result; +} + +export { decode, encode }; diff --git a/dist/lib/renderer/sourcemap/lib/encode.js b/dist/lib/renderer/sourcemap/lib/encode.js deleted file mode 100644 index 9484f762..00000000 --- a/dist/lib/renderer/sourcemap/lib/encode.js +++ /dev/null @@ -1,37 +0,0 @@ -// from https://github.com/Rich-Harris/vlq/tree/master -// credit: Rich Harris -const integer_to_char = {}; -let i = 0; -for (const char of 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=') { - integer_to_char[i++] = char; -} -function encode(value) { - if (typeof value === 'number') { - return encode_integer(value); - } - let result = ''; - for (let i = 0; i < value.length; i += 1) { - result += encode_integer(value[i]); - } - return result; -} -function encode_integer(num) { - let result = ''; - if (num < 0) { - num = (-num << 1) | 1; - } - else { - num <<= 1; - } - do { - let clamped = num & 31; - num >>>= 5; - if (num > 0) { - clamped |= 32; - } - result += integer_to_char[clamped]; - } while (num > 0); - return result; -} - -export { encode }; diff --git a/dist/lib/renderer/sourcemap/sourcemap.js b/dist/lib/renderer/sourcemap/sourcemap.js index 8fb6c4ad..ba38bf05 100644 --- a/dist/lib/renderer/sourcemap/sourcemap.js +++ b/dist/lib/renderer/sourcemap/sourcemap.js @@ -1,10 +1,14 @@ -import { encode } from './lib/encode.js'; +import { decode, encode } from './lib/codec.js'; /** - * Source map class - * @internal + * Generate and parse source map */ class SourceMap { + /** + * + * @private + */ + keys = new Set(); /** * Last location */ @@ -19,6 +23,11 @@ class SourceMap { * @private */ sourcesMap = []; + /** + * Sources content + * @private + */ + sourcesContent = []; /** * Sources * @private @@ -27,52 +36,166 @@ class SourceMap { /** * Map * @private + * */ map = new Map(); + /** + * Map + * @private + * + */ + reverseMap = new Map(); /** * Line * @private */ line = -1; /** - * Add a location - * @param source - * @param original + * + * @param sourcemaps + * @private */ - add(newLine, newColumn, srcId, ln, col, sourceFileName, sourceContent) { - if (!this.sourcesMap.includes(srcId)) { - if (sourceFileName == null && sourceContent != null) { - sourceFileName = "data:text/css;charset=utf-8;base64," + btoa(sourceContent); + constructor(sourcemaps) { + if (typeof sourcemaps === "string") { + sourcemaps = JSON.parse(sourcemaps); + } + if (sourcemaps != null) { + this.sources = sourcemaps.sources?.slice() ?? []; + this.sourcesContent = sourcemaps.sourcesContent?.slice() ?? []; + const decodedMappings = sourcemaps.mappings + .split(";") + .map((mapping) => mapping.split(",").map((mapping) => decode(mapping))); + this.line = decodedMappings.length - 1; + for (let index = 0; index < decodedMappings.length; index++) { + if (decodedMappings[index].length == 0 || + (decodedMappings[index].length == 1 && decodedMappings[index][0].length == 0)) { + continue; + } + this.map.set(index, decodedMappings[index]); } - this.sourcesMap.push(srcId); - this.sources.push(sourceFileName || null); + this.computePositions(); } - const line = newLine - 1; - let record; - if (line > this.line) { - this.line = line; + } + /** + * Add all location + * @param maps + */ + addAll(maps) { + for (let [newLine, newColumn, srcId, ln, col, sourceFileName, sourceContent] of maps) { + const key = `${srcId}:${ln}:${sourceFileName}:${col}:${newLine}:${newColumn}:${sourceContent}`; + const sourcemap = `${srcId}:${sourceFileName}:${sourceContent}`; + if (this.keys.has(key)) { + continue; + } + this.keys.add(key); + if (!this.sourcesMap.includes(sourcemap)) { + this.sourcesMap.push(sourcemap); + this.sources.push(sourceFileName || null); + this.sourcesContent.push((sourceFileName != null ? null : sourceContent) || null); + } + const line = newLine - 1; + let record; + if (line > this.line) { + this.line = line; + } + if (!this.map.has(line)) { + record = [Math.max(0, newColumn - 1), this.sourcesMap.indexOf(sourcemap), ln - 1, col - 1]; + this.map.set(line, [record]); + } + else { + const arr = this.map.get(line); + record = [ + Math.max(0, newColumn - 1) - arr[0][0], + this.sourcesMap.indexOf(sourcemap) - arr[0][1], + ln - 1, + col - 1, + ]; + arr.push(record); + } + if (this.lastLocation != null) { + record[2] -= this.lastLocation.ln - 1; + record[3] -= this.lastLocation.col - 1; + } + this.lastLocation ??= { ln, col }; + this.lastLocation.ln = ln; + this.lastLocation.col = col; } - if (!this.map.has(line)) { - record = [Math.max(0, newColumn - 1), this.sourcesMap.indexOf(srcId), ln - 1, col - 1]; - this.map.set(line, [record]); + } + /** + * compute original positions + */ + computePositions() { + this.reverseMap.clear(); + let sourceFileIndex = 0; // second field + let sourceCodeLine = 0; // third field + let sourceCodeColumn = 0; // fourth field + let nameIndex = 0; // fifth field + let generatedCodeColumn; + let result; + // mappings to original source + for (let [i, line] of this.map.entries()) { + if (line.length === 0 || (line.length === 1 && line[0].length === 0)) { + continue; + } + generatedCodeColumn = line[0][0]; // first field - reset each time + line = line + .map((segment, index, array) => { + if (segment.length === 0) { + return []; + } + generatedCodeColumn = index == 0 ? segment[0] : segment[0] + array[0][0]; + result = [generatedCodeColumn]; + if (segment.length <= 1) { + return result; + } + sourceFileIndex = index == 0 ? segment[1] : segment[1] + array[0][1]; + sourceCodeLine += segment[2]; + sourceCodeColumn += segment[3]; + result.push(sourceFileIndex, sourceCodeLine, sourceCodeColumn); + if (segment.length === 5) { + nameIndex += segment[4]; + result.push(nameIndex); + } + return result; + }) + .sort((a, b) => { + if (a[1] !== b[1]) { + return a[1] - b[1]; + } + return a[0] - b[0]; + }); + if (line.length == 0 || (line.length == 1 && line[0].length == 0)) { + continue; + } + this.reverseMap.set(i, line); } - else { - const arr = this.map.get(line); - record = [ - Math.max(0, newColumn - 1 - arr[0][0]), - this.sourcesMap.indexOf(srcId) - arr[0][1], - ln - 1, - col - 1, - ]; - arr.push(record); + } + /** + * retrieve original sources, lines and columns + * @param line generated line + * @param column generated column + */ + find(line, column) { + if (!this.reverseMap.has(--line)) { + return null; } - if (this.lastLocation != null) { - record[2] -= this.lastLocation.ln - 1; - record[3] -= this.lastLocation.col - 1; + column--; + const result = []; + for (const record of this.reverseMap.get(line)) { + if (record.length == 0 || record[0] < column) { + continue; + } + if (record[0] > column) { + break; + } + result.push([ + this.sources?.[record[1]] ?? null, + record[2] + 1, + record[3] + 1, + this.sourcesContent?.[record[1]] ?? null, + ]); } - this.lastLocation ??= { ln, col }; - this.lastLocation.ln = ln; - this.lastLocation.col = col; + return result.length == 0 ? null : result; } /** * Convert to URL encoded string @@ -98,9 +221,16 @@ class SourceMap { return { version: this.version, sources: this.sources.slice(), + sourcesContent: this.sourcesContent?.slice(), mappings: mappings.join(";"), }; } + /** + * to string + */ + toString() { + return JSON.stringify(this); + } } export { SourceMap }; diff --git a/dist/lib/validation/match.js b/dist/lib/validation/match.js index 34ca59ac..23f06fbc 100644 --- a/dist/lib/validation/match.js +++ b/dist/lib/validation/match.js @@ -10,11 +10,19 @@ import { parseTokens } from '../parser/parse.js'; const config = getSyntaxConfig(); // @ts-expect-error const allValues = config.declarations.all.syntax.split(/[\s|]+/g); +/** + * @type {Array.} + */ const funcTypes = [ ...tokensfuncDefMap.values(), EnumToken.FunctionTokenType, EnumToken.PseudoClassFuncTokenType, ]; +/** + * trim leading and trailing whitespace + * @param tokens + * @returns + */ function trimArray(tokens) { while (tokens[0]?.typ === EnumToken.WhitespaceTokenType) { tokens.shift(); @@ -115,6 +123,11 @@ function isMFValue(featureName, tokens, isMFRange) { success: true, }; } +/** + * create validation context + * @param tokens + * @returns + */ function createValidationContext(tokens) { tokens = trimArray(tokens.filter((t) => t.typ !== EnumToken.CommentTokenType)); if (tokens.at(-1)?.typ === EnumToken.ImportantTokenType) { @@ -278,6 +291,14 @@ function createValidationContext(tokens) { }; return token; } +/** + * match selector syntax + * @param stream + * @param errors + * @param options + * @param nested + * @returns + */ function matchSelectorSyntax(stream, errors, options, nested = true) { const stack = []; const tokens = []; @@ -716,6 +737,13 @@ function matchSelectorSyntax(stream, errors, options, nested = true) { stream.push(...tokens); return { success, errors }; } +/** + * matches all syntaxes + * @param syntaxes + * @param context + * @param options + * @returns + */ function matchAllSyntaxes(syntaxes, context, options) { const result = matchSyntax(syntaxes, context, { ...options, @@ -754,6 +782,13 @@ function matchAllSyntaxes(syntaxes, context, options) { syntaxToken: !result.success ? result.syntaxToken : null, }; } +/** + * matches a list of syntaxes + * @param syntax + * @param context + * @param options + * @returns + */ function matchListSyntax(syntax, context, options) { const { isList, match, isOptional, ...rest } = syntax; let success = true; @@ -798,6 +833,13 @@ function matchListSyntax(syntax, context, options) { token: context.peek(), }; } +/** + * matches a list of syntaxes + * @param syntax + * @param context + * @param options + * @returns + */ function matchOccurenceSyntax(syntax, context, options) { const { match, ...rest } = syntax; let result = null; @@ -839,6 +881,13 @@ function matchOccurenceSyntax(syntax, context, options) { } return result; } +/** + * matches a list of syntaxes + * @param syntaxes + * @param context + * @param options + * @returns + */ function matchSyntax(syntaxes, context, options) { if (syntaxes == null) { return { @@ -971,7 +1020,7 @@ function matchSyntax(syntaxes, context, options) { if (syntaxes[i].isList) { result = matchListSyntax(syntaxes[i], context.slice(), options); if (result.success) { - options.visited.get(token).delete(syntaxes[i]); + options.visited.get(token)?.delete?.(syntaxes[i]); if (result.context.done()) { context.end(); return { @@ -1479,6 +1528,13 @@ function matchSyntax(syntaxes, context, options) { errors: [], }; } +/** + * matches a column of syntaxes + * @param syntax + * @param context + * @param options + * @returns + */ function matchColumnSyntax(syntax, context, options) { let syntaxes = syntax.chi.slice(); let i = 0; @@ -1515,6 +1571,13 @@ function matchColumnSyntax(syntax, context, options) { errors: [], }; } +/** + * matches an ampersand of syntaxes + * @param syntax + * @param context + * @param options + * @returns + */ function matchAmpersandSyntax(syntax, context, options) { const syntaxes = [syntax.l, syntax.r]; let result; @@ -1533,6 +1596,13 @@ function matchAmpersandSyntax(syntax, context, options) { } return result; } +/** + * matches a property + * @param property + * @param context + * @param options + * @returns + */ function matchProperty(property, context, options) { let success = false; let t = context.peek()?.typ; @@ -2265,6 +2335,13 @@ function matchProperty(property, context, options) { errors: [], }; } +/** + * matches a repeatable syntax + * @param syntax + * @param context + * @param options + * @returns + */ function matchRepeatableSyntax(syntax, context, options) { const { isRepeatable, isOptional, isMandatatoryGroup, isRepeatableAtLeastOnce, ...rest } = syntax; let result = null; diff --git a/dist/node.js b/dist/node.js index 47ce863a..269a3bd5 100644 --- a/dist/node.js +++ b/dist/node.js @@ -14,6 +14,7 @@ import { ResponseType } from './types.js'; import { resolve as resolve$1 } from 'node:path'; import { SourceFile } from './lib/parser/source.js'; import { cwd } from 'node:process'; +import { parseResult, validateSyncArguments } from './utils/sync.js'; export { minify } from './lib/ast/minify.js'; export { expand } from './lib/ast/expand.js'; export { WalkerEvent, WalkerOptionEnum, walk, walkValues } from './lib/ast/walk.js'; @@ -136,15 +137,16 @@ const parseFile = deprecate(async (file, options = {}, asStream = false) => pars /** * Parse css * @param args + * @private * * Parsing a string * * ```ts * - * import {parse} from '@tbela99/css-parser'; + * import {parseSync} from '@tbela99/css-parser'; * * // css string - * let result = await parse(css, {nestingRules: true}); + * let result = parseSync(css, {nestingRules: true}); * console.log(result.ast); * ``` * @@ -161,9 +163,12 @@ function parseSync(...args) { options = opt; stream = input; } + if (options != null) { + validateSyncArguments(options); + } options ??= {}; options.src ??= ""; - options.sourcesMap ??= new Map; + options.sourcesMap ??= new Map(); Object.assign(options, { resolve, dirname, @@ -186,23 +191,22 @@ function parseSync(...args) { currentPosition: -1, }; const result = doParseSync(tokenize(options.parseInfo), options); - const { revMapping, ...res } = result; - return res; + return !options.module && !options.inputSourceMap && !options.sourcemap ? result : parseResult(result, options); } /** * Transform css - * @param css - * @param options * * ```ts * - * import {transform} from '@tbela99/css-parser'; + * import {transformSync} from '@tbela99/css-parser'; * * // css string - * const result = await transform(css); + * const result = transformSync(css); * console.log(result.code); * ``` * + * @param args + * @private */ function transformSync(...args) { let options; @@ -255,6 +259,7 @@ function transformSync(...args) { * @param args * * @throws Error file not found + * @private * * Parsing a string * @@ -314,7 +319,7 @@ async function parse(...args) { } options ??= {}; options.src ??= ""; - options.sourcesMap ??= new Map; + options.sourcesMap ??= new Map(); Object.assign(options, { load, resolve, @@ -337,13 +342,10 @@ async function parse(...args) { position: 0, currentPosition: -1, }; - return doParse(stream instanceof ReadableStream ? tokenizeStream(stream, options.parseInfo) : tokenize(options.parseInfo), options).then((result) => { - const { revMapping, ...res } = result; - return res; - }); + return doParse(stream instanceof ReadableStream ? tokenizeStream(stream, options.parseInfo) : tokenize(options.parseInfo), options).then((result) => (!options.module && !options.inputSourceMap ? result : parseResult(result, options))); } /** - * Transform css file + * Transform CSS file * @param file url or path * @param options * @param asStream load file as stream @@ -373,8 +375,6 @@ const transformFile = deprecate(async (file, options = {}, asStream = false) => }), "transformFile is deprecated, use transform instead as transform({file, asStream, ...options})"); /** * Transform css - * @param css - * @param options * * Parsing a string * @@ -413,6 +413,8 @@ const transformFile = deprecate(async (file, options = {}, asStream = false) => * * console.log(result.code); * ``` + * @param args + * @private */ async function transform(...args) { let options; diff --git a/dist/utils/sync.d.ts b/dist/utils/sync.d.ts new file mode 100644 index 00000000..0242b12d --- /dev/null +++ b/dist/utils/sync.d.ts @@ -0,0 +1,10 @@ +import type { ParseResult, ParserOptions, ParserSyncOptions } from "../@types/index.js"; +/** + * parse result. process input sourcemap + * @param result + * @param options + * @returns + * @private + */ +export declare function parseResult(result: ParseResult, options: ParserOptions): ParseResult; +export declare function validateSyncArguments(options: ParserSyncOptions, prefix?: string): void; diff --git a/dist/utils/sync.js b/dist/utils/sync.js new file mode 100644 index 00000000..d8b3dff7 --- /dev/null +++ b/dist/utils/sync.js @@ -0,0 +1,64 @@ +import { EnumToken } from '../lib/ast/types.js'; + +/** + * parse result. process input sourcemap + * @param result + * @param options + * @returns + * @private + */ +function parseResult(result, options) { + if (options.sourcemap != null && options.source.getInputSourceMap() == null) { + if (options.inputSourceMap != null) { + options.source.setInputSourceMap(options.inputSourceMap); + } + else { + // extract inline source map from the input CSS + const token = result.ast.chi.at(-1); + if (token?.typ == EnumToken.CommentTokenType && + token.val.startsWith("/*# sourceMappingURL=")) { + const data = token.val.slice(21, -2).trim(); + let sourcemap; + let encoding = ""; + if (data.startsWith("data:")) { + let offset = data.indexOf(",") + 1; + if (offset == 0) { + offset = data.lastIndexOf(";") + 1; + } + else { + encoding = data.slice(data.lastIndexOf(";") + 1, offset - 1); + } + if (encoding == "base64") { + sourcemap = atob(data.slice(offset)); + } + else { + sourcemap = decodeURIComponent(data.slice(offset)); + } + options.source.setInputSourceMap(sourcemap); + } + } + } + } + if (options.module) { + const { revMapping, ...res } = result; + return res; + } + return result; +} +function validateSyncArguments(options, prefix = "options.") { + const args = Object.entries(options); + let i; + for (i = 0; i < args.length; i++) { + const [key, value] = args[i]; + if (typeof value == 'function') { + if (value instanceof Promise || Object.getPrototypeOf(value).constructor.name == "AsyncFunction") { + throw new Error(`[${prefix + key}]: Async functions are not supported in sync mode. Use parse() or transform() instead.`); + } + } + else if (value != null && typeof value == 'object') { + validateSyncArguments(value, prefix + key + "."); + } + } +} + +export { parseResult, validateSyncArguments }; diff --git a/dist/web.js b/dist/web.js index a6194266..eafdd719 100644 --- a/dist/web.js +++ b/dist/web.js @@ -8,6 +8,7 @@ import { tokenizeStream, tokenize } from './lib/parser/tokenize.js'; import { matchUrl, resolve, dirname } from './lib/fs/resolve.js'; import { ResponseType } from './types.js'; import { SourceFile } from './lib/parser/source.js'; +import { parseResult, validateSyncArguments } from './utils/sync.js'; export { minify } from './lib/ast/minify.js'; export { expand } from './lib/ast/expand.js'; export { WalkerEvent, WalkerOptionEnum, walk, walkValues } from './lib/ast/walk.js'; @@ -27,7 +28,7 @@ export { FeatureWalkMode } from './lib/ast/features/type.js'; * @throws Error file not found * * ```ts - * import {load, ResponseType} from '@tbela99/css-parser'; + * import {load, ResponseType} from '@tbela99/css-parser/web'; * const result = await load(file, '.', ResponseType.ArrayBuffer) as ArrayBuffer; * ``` */ @@ -69,7 +70,7 @@ async function load(url, currentDirectory = ".", responseType = false) { * * ```ts * - * import {render, ColorType} from '@tbela99/css-parser'; + * import {render, ColorType} from '@tbela99/css-parser/web'; * * const css = 'body { color: color(from hsl(0 100% 50%) xyz x y z); }'; * const parseResult = await parse(css); @@ -128,15 +129,16 @@ async function parseFile(file, options = {}, asStream = false) { /** * Parse css * @param args + * @private * * Parsing a string * * ```ts * - * import {parse} from '@tbela99/css-parser'; + * import {parseSync} from '@tbela99/css-parser/web'; * * // css string - * let result = await parse(css, {nestingRules: true}); + * let result = await parseSync(css, {nestingRules: true}); * console.log(result.ast); * ``` * @@ -153,9 +155,12 @@ function parseSync(...args) { options = opt; stream = input; } + if (options != null) { + validateSyncArguments(options); + } options ??= {}; options.src ??= ""; - options.sourcesMap ??= new Map; + options.sourcesMap ??= new Map(); Object.assign(options, { resolve, dirname, @@ -180,23 +185,22 @@ function parseSync(...args) { currentPosition: -1, }; const result = doParseSync(tokenize(options.parseInfo), options); - const { revMapping, ...res } = result; - return res; + return !options.module && !options.inputSourceMap && !options.sourcemap ? result : parseResult(result, options); } /** - * Transform css - * @param css - * @param options + * Transform CSS * * ```ts * - * import {transform} from '@tbela99/css-parser'; + * import {transformSync} from '@tbela99/css-parser/web'; * * // css string - * const result = await transform(css); + * const result = transformSync(css); * console.log(result.code); * ``` * + * @param args + * @private */ function transformSync(...args) { let options; @@ -246,8 +250,6 @@ function transformSync(...args) { } /** * Parse css - * @param stream - * @param options * * Example: * @@ -271,6 +273,8 @@ function transformSync(...args) { * * console.log(result.ast); * ``` + * @param args + * @private */ async function parse(...args) { let options; @@ -292,7 +296,7 @@ async function parse(...args) { } options ??= {}; options.src ??= ""; - options.sourcesMap ??= new Map; + options.sourcesMap ??= new Map(); Object.assign(options, { load, resolve, @@ -316,13 +320,10 @@ async function parse(...args) { position: 0, currentPosition: -1, }; - return doParse(stream instanceof ReadableStream ? tokenizeStream(stream, options.parseInfo) : tokenize(options.parseInfo), options).then((result) => { - const { revMapping, ...res } = result; - return res; - }); + return doParse(stream instanceof ReadableStream ? tokenizeStream(stream, options.parseInfo) : tokenize(options.parseInfo), options).then((result) => (!options.module && !options.inputSourceMap ? result : parseResult(result, options))); } /** - * Transform css file + * Transform CSS file * @param file url or path * @param options * @param asStream load file as stream @@ -353,8 +354,6 @@ async function transformFile(file, options = {}, asStream = false) { } /** * Transform css - * @param css - * @param options * * Example: * @@ -372,6 +371,8 @@ async function transformFile(file, options = {}, asStream = false) { * * console.log(result.code); * ``` + * @param args + * @private */ async function transform(...args) { let options; diff --git a/files/assets/typedoc-custom.css b/files/assets/typedoc-custom.css index 01821819..979dc2d2 100644 --- a/files/assets/typedoc-custom.css +++ b/files/assets/typedoc-custom.css @@ -78,6 +78,17 @@ html[data-theme="dark"] { } } -#main-function-differences ~ table td { - text-align: center; +#main-function-differences ~ table tbody tr { + &:hover { + background-color: var(--transparent-blue) !important; + color: var(--blue-dark) !important; + } + + td { + &:first-child { + text-align: left; + } + + text-align: center; + } } diff --git a/files/getting-started.md b/files/getting-started.md index 13e6c9a5..1aafb051 100644 --- a/files/getting-started.md +++ b/files/getting-started.md @@ -6,9 +6,9 @@ category: Guides ## About -CSS-Parser is a high-performance, fault-tolerant, and dependency-free CSS toolkit for Node.js and browsers. +CSS-Parser is a high-performance, fault-tolerant, and dependency-free all-in-one CSS parsing solution for Node.js and browsers. -It implements the [CSS Syntax Module Level 3](https://www.w3.org/TR/css-syntax-3/) specification and validates CSS using syntax rules from [MDN Data](https://github.com/mdn/data). +It implements the [CSS Syntax Module Level 3](https://www.w3.org/TR/css-syntax-3/) specification and validates CSS using syntax rules from [MDN Data](https://github.com/mdn/data). Every stylesheet is fully parsed into a structured AST, and token values are exposed as typed data so the library can support robust transformations, validation, and plugin-oriented workflows without falling back to raw strings. In addition to parsing and validation, CSS-Parser provides advanced optimization and minification capabilities. According to [this benchmark](https://tbela99.github.io/css-parser/benchmark/index.html), it is the most efficient CSS minifier available, producing smaller output than competing solutions while maintaining competitive performance. @@ -22,9 +22,11 @@ A non-exhaustive list of features is provided below: * **Zero dependencies** — lightweight and easy to integrate into any project. +* **All-in-one CSS parsing solution** for parsing, validation, transformation, and minification. * **Standards-based CSS validation** powered by MDN data. * **Full CSS Modules support** for modern component-based workflows. -* **Fault-tolerant parsing** that follows the CSS Syntax Module Level 3 specification. +* **Fault-tolerant parsing** that follows the CSS Syntax Module Level 3 specification and always yields a complete parse tree. +* **Typed tokens and AST** — parsed CSS is exposed as strongly typed tokens and nodes for safer plugin and transform logic. * **High-performance minification** with safe optimizations and no unsafe transforms. * **Advanced color processing** with support for modern color spaces and functions, including `color()`, `lab()`, `lch()`, `oklab()`, `oklch()`, `color-mix()`, `light-dark()`, system colors, and relative colors. * **Color conversion engine** capable of transforming colors between all supported formats. @@ -38,7 +40,7 @@ A non-exhaustive list of features is provided below: * **CSS variable inlining** where values can be safely resolved. * **Duplicate declaration removal** to eliminate redundant rules. * **`@import` flattening** to produce self-contained stylesheets. -* **Experimental vendor prefix cleanup** to modernize generated CSS. +* **Vendor prefix cleanup** to modernize generated CSS. ## Installation diff --git a/files/index.md b/files/index.md index 9b88907c..91b6094b 100644 --- a/files/index.md +++ b/files/index.md @@ -9,6 +9,8 @@ children: - ./css-module.md - ./minification.md - ./transform.md + - ./sourcemap.md + - ./plugins.md - ./syntax-lowering.md - ./ast.md - ./utilities.md @@ -22,6 +24,8 @@ children: - [CSS Modules](./css-module.md) - [Minification](./minification.md) - [Custom Transform](./transform.md) +- [Sourcemap](./sourcemap.md) +- [Plugins API](./plugins.md) - [Syntax Lowering](./syntax-lowering.md) - [Ast Manipulation](./ast.md) - [Utility Functions](./utilities.md) diff --git a/files/minification.md b/files/minification.md index f58f8b7d..56a418c6 100644 --- a/files/minification.md +++ b/files/minification.md @@ -657,7 +657,7 @@ Output: } ``` -### CSS prefix removal (Experimental) +### CSS prefix removal This feature is disabled by default. @@ -891,7 +891,7 @@ Output: ### Computed shorthands properties -Below is the list of computed shorthands properties: +Below is the list of computed shorthands properties. Minification is fully supported for the properties with a checkmark. - [ ] ~all~ - [x] animation diff --git a/files/plugins.md b/files/plugins.md new file mode 100644 index 00000000..ff9a9048 --- /dev/null +++ b/files/plugins.md @@ -0,0 +1,100 @@ +--- +title: Plugins API +group: Documents +category: Guides +--- + +# Plugins + +The CSS parser supports plugin-style extensions through its [visitor API](./transform.md). + +### Example + +A plugin implemented as visitor that inlines all images under a specific size. + +```ts +import { + EnumToken, + FunctionURLToken, + load, + StringToken, + Token, + transform, + UrlToken, + ResponseType, + AstDeclaration, + AstNode +} from "@tbela99/css-parser"; + +function toBase64(arraybuffer: Uint8Array) { + // @ts-ignore + if (typeof Uint8Array.prototype.toBase64! == "function") { + // @ts-ignore + return arraybuffer.toBase64(); + } + + let binary = ""; + for (const byte of arraybuffer) { + binary += String.fromCharCode(byte); + } + + return btoa(binary); +} + +function inlineImagesPlugin(maxSize: number, extensions: string[]) { + return async function UrlFunctionTokenType(node: FunctionURLToken, parent: AstNode) { + if (parent.typ == EnumToken.DeclarationNodeType) { + const t = node.chi.find( + (t) => t.typ != EnumToken.WhitespaceTokenType && t.typ != EnumToken.CommaTokenType, + ) as Token; + + if (t == null) { + return; + } + + const url = t.typ == EnumToken.StringTokenType ? (t as StringToken).val.slice(1, -1) : (t as UrlToken).val; + + if (url.startsWith("data:")) { + return; + } + + const matches = /(.*?\/)?([^/.]+)\.([^?#]+)([?#].*)?$/.exec(url); + + if (matches == null || !extensions.includes(matches[3].toLowerCase())) { + return; + } + + const buffer = (await load(url, ".", ResponseType.ArrayBuffer)) as ArrayBuffer; + + if (buffer.byteLength > maxSize) { + return; + } + + Object.assign(t, { + typ: EnumToken.StringTokenType, + val: `"data:image/${matches[3].toLowerCase()};base64,${toBase64(new Uint8Array(buffer))}"`, + }); + } + }; +} + +// 35 kb or something +const maxSize = 35 * 1024; +// accepted images +const extensions = ["jpg", "gif", "png", "webp"]; +const css = ` +.goal .bg-indigo { + background: url(/img/animatecss-opengraph.jpg); +} +`; + +const result = await transform(css, { + visitor: inlineImagesPlugin(maxSize, extensions), +}); + +console.error(result.code); +// .goal .bg-indigo{background:url("data:image/jpg;base64,/9j/4AAQSkZJRgABAQEASABIAAD/4QugRXhpZgAA ...")} +``` + +------ +[← Sourcemap](./sourcemap.md) | [Syntax Lowering →](./syntax-lowering.md) \ No newline at end of file diff --git a/files/sourcemap.md b/files/sourcemap.md new file mode 100644 index 00000000..c8568696 --- /dev/null +++ b/files/sourcemap.md @@ -0,0 +1,111 @@ +--- +title: Sourcemap +group: Documents +category: Guides +--- + + +# Sourcemaps + +**CSS-Parser** supports generating sourcemaps. To enable it, you must pass `sourcemap: true` or `sourcemap: 'inline`. +When the `output` parameter is provided, sourcemap file paths are resolved relative to the specified output file. + +```ts + +import {transform} from '@tbela99/css-parser'; + +const css = ` +@import 'styles.css'; +button { + background: linear-gradient( + if(media(min-width: 768px): to right; else: to bottom), + if(style(--dark-mode): #333; else: #fff), + if(style(--dark-mode): #000; else: #ccc) + ); +}`; + +result = await transform(css, { + + beautify: true, + sourcemap: true, + resolveImport: true, + output: 'dist/doc.html' +}); + +console.log(result.map.toJSON()); +``` + +### Input sourcemap + +If the input CSS comes from another tool, you can pass the sourcemap content to link the generated CSS positions to the original files. Additionally, if an inline sourcemap is provided with the CSS input, it will be automatically used as the input sourcemap. + + +```ts + +import {transform} from '@tbela99/css-parser'; + +const css = ` +table.colortable { + width: 100%; + text-shadow: none; + border-collapse: collapse; + & td { + text-align: center; + &.c { + text-transform: uppercase; + background: color(display-p3-linear 1 1 .08948) + } + } + & th { + text-align: center; + color: color(display-p3-linear .038323 .208695 .015628); + font-weight: 400; + padding: 2px 3px + } + & td,& th { + border: 1px solid color(display-p3-linear .695155 .700862 .720967); + padding: 5px + } +} +.foo { + color: color(display-p3-linear 0 0 .91052); + & { + padding: 2ch; + color: color(display-p3-linear 0 0 .91052); + && { + padding: 2ch + } + } +} +h1 { + text-transform: uppercase +} +button { + background: linear-gradient(color(display-p3-linear 1 1 1),color(display-p3-linear .603827 .603827 .603827)); + @media (min-width:768px) { + background: linear-gradient(90deg,color(display-p3-linear 1 1 1),color(display-p3-linear .603827 .603827 .603827)); + @container style(--dark-mode) { + background: linear-gradient(90deg,color(display-p3-linear .033105 .033105 .033105),color(display-p3-linear .603827 .603827 .603827)); + background: linear-gradient(90deg,color(display-p3-linear .033105 .033105 .033105),color(display-p3-linear 0 0 0)) + } + } + @container style(--dark-mode) { + background: linear-gradient(color(display-p3-linear .033105 .033105 .033105),color(display-p3-linear .603827 .603827 .603827)); + background: linear-gradient(color(display-p3-linear .033105 .033105 .033105),color(display-p3-linear 0 0 0)) + } +} +/*# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIm5lc3RlZC5jc3MiLG51bGxdLCJzb3VyY2VzQ29udGVudCI6W251bGwsIlxuQGltcG9ydCAnLi90ZXN0L25lc3RlZC5jc3MnO1xuaDEge1xuICB0ZXh0LXRyYW5zZm9ybTogdXBwZXJjYXNlO1xufVxuYnV0dG9uIHtcblx0YmFja2dyb3VuZDogbGluZWFyLWdyYWRpZW50KFxuXHRcdGlmKG1lZGlhKG1pbi13aWR0aDogNzY4cHgpOiB0byByaWdodDsgZWxzZTogdG8gYm90dG9tKSxcblx0XHRpZihzdHlsZSgtLWRhcmstbW9kZSk6ICMzMzM7IGVsc2U6ICNmZmYpLFxuXHRcdGlmKHN0eWxlKC0tZGFyay1tb2RlKTogIzAwMDsgZWxzZTogI2NjYylcblx0KTtcbn1cbiAgICAiXSwibWFwcGluZ3MiOiJBQUFBOzs7MEJBSUk7b0JBRUk7Ozs7Ozs7O0NBS0o7Ozs7O0NBTUE7Ozs7Ozs7O0FBTUo7MkNBRUk7OzRDQUdJOzs7Ozs7Ozs7Ozs7OztBQzFCUjs7QUNHQTs2R0NDQztvSENBQTs7Ozs7Ozs7Q0NBQSJ9 */ +`; + +result = await transform(css, { + + beautify: true, + sourcemap: true, + output: 'dist/doc.html' +}); + +console.log(result.map.toJSON()); +``` + +------ +[← Custom Transform](./transform.md) | [Plugins API →](./plugins.md) \ No newline at end of file diff --git a/files/syntax-lowering.md b/files/syntax-lowering.md index 8f4c6bed..e13fe447 100644 --- a/files/syntax-lowering.md +++ b/files/syntax-lowering.md @@ -135,4 +135,4 @@ table.colortable th { ```` ------ -[← Custom Transform](./transform.md) | [Ast Manipulation →](./ast.md) \ No newline at end of file +[← Plugins API](./plugins.md) | [Ast Manipulation →](./ast.md) \ No newline at end of file diff --git a/files/transform.md b/files/transform.md index 236970b3..cd3f7471 100644 --- a/files/transform.md +++ b/files/transform.md @@ -6,7 +6,9 @@ category: Guides ## Custom transform -Visitors are used to transform the ast tree produced by the parser. For more information about the visitor object see the [typescript definition](../docs/interfaces/node.VisitorNodeMap.html) +Visitors are used to transform the ast tree produced by the parser. For more information about the visitor object see the [typescript definition](../docs/interfaces/node.VisitorNodeMap.html). You can register handlers for specific AST node types and lifecycle events such as enter, visit, and leave to inspect, validate, or modify nodes without altering the parser internals. + +This pattern is useful for building reusable plugins that enforce conventions, inject transformations, or add custom analysis on top of the parsed AST. ## Visitors execution order @@ -421,94 +423,5 @@ console.debug(await transform(css, options)); // body {color:#f3fff0} ``` - -### Example of visitor that inlines images - -A visitor that inlines all images under a specific size - -```ts -import { - EnumToken, - FunctionURLToken, - load, - StringToken, - Token, - transform, - UrlToken, - ResponseType, - AstDeclaration, - AstNode -} from "@tbela99/css-parser"; -const css = ` -.goal .bg-indigo { - background: url(/img/animatecss-opengraph.jpg); -} -`; - -// 35 kb or something -const maxSize = 35 * 1024; -// accepted images -const extensions = ['jpg', 'gif', 'png', 'webp'] -const result = await transform(css, { - visitor: { - UrlFunctionTokenType: async (node: FunctionURLToken, parent : AstNode) => { - - if (parent.typ == EnumToken.DeclarationNodeType) { - - const t = node.chi.find(t => t.typ != EnumToken.WhitespaceTokenType && t.typ != EnumToken.CommaTokenType) as Token; - - if (t == null) { - - return; - } - - const url = t.typ == EnumToken.StringTokenType ? (t as StringToken).val.slice(1, -1) : (t as UrlToken).val; - - if (url.startsWith('data:')) { - - return; - } - - const matches = /(.*?\/)?([^/.]+)\.([^?#]+)([?#].*)?$/.exec(url); - - if (matches == null || !extensions.includes(matches[3].toLowerCase())) { - - return; - } - - const buffer = await load(url, '.', ResponseType.ArrayBuffer) as ArrayBuffer ; - - if (buffer.byteLength > maxSize) { - - return - } - - Object.assign(t, {typ: EnumToken.StringTokenType, val: `"data:image/${matches[3].toLowerCase()};base64,${toBase64(new Uint8Array(buffer))}"`}) - } - } - } -}); - -function toBase64(arraybuffer: Uint8Array) { - - // @ts-ignore - if (typeof Uint8Array.prototype.toBase64! == 'function') { - - // @ts-ignore - return arraybuffer.toBase64(); - } - - let binary = ''; - for (const byte of arraybuffer) { - binary += String.fromCharCode( byte); - } - - return btoa( binary ); -} - -console.error(result.code); -// .goal .bg-indigo{background:url("data:image/jpg;base64,/9j/4AAQSkZJRgABAQEASABIAAD/4QugRXhpZgAA ...")} -``` - ------ -[← Minification](./minification.md) | [Syntax Lowering →](./syntax-lowering.md) \ No newline at end of file +[← Minification](./minification.md) | [Sourcemap →](./sourcemap.md) \ No newline at end of file diff --git a/files/usage.md b/files/usage.md index b5a603b3..eabdc87a 100644 --- a/files/usage.md +++ b/files/usage.md @@ -17,9 +17,9 @@ The **synchronous API** is marginally faster than the asynchronous API, but it c | `transformSync()` | ✅ | ❌ | ✅ | | `render()` | ❌ | ❌ | ✅ | -> **Note:** `parse()` and `parseSync()` only produce the AST and does not generate CSS output. +> **Note:** `parse()` and `parseSync()` only produce the AST and do not generate CSS output. -By contrast, `transform()` and `transformSync()` parses the CSS **and** generates the transformed CSS text. This is useful when you want the rendered CSS directly without performing a separate AST rendering step. +By contrast, `transform()` and `transformSync()` parse the CSS **and** generate the transformed CSS text. This is useful when you want the rendered CSS directly without performing a separate AST rendering step. ### Usage @@ -53,18 +53,19 @@ console.debug(result.stats); Parsing converts input CSS into an **AST (Abstract Syntax Tree)**. -You can parse CSS in two ways: +You can parse CSS in multiple ways: -- `parse()` – Parses CSS and returns an AST. -- `transform()` – Parses and generate CSS as part of the transformation process. +- `parse()` or `parseSync()` – Parse CSS and returns an AST. +- `transform()` or `transformSync()` – Parse and generate CSS as part of the transformation process. For more information about the available parsing options, see the TypeScript documentation for [`ParserOptions`](../interfaces/node.ParserOptions.html). ### Usage -```javascript +```ts parse(css, parserOptions = {}) parse(parserOptions = {input: css}) +parse(parserOptions = {file: url_or_path}) ``` ### Example @@ -379,57 +380,25 @@ button { } } ``` -## Sourcemaps - -**CSS-Parser** supports generating sourcemaps. When the `output` parameter is provided, sourcemap file paths are resolved relative to the specified output file. - - -```ts - -import {transform} from '@tbela99/css-parser'; - -const css = ` -@import 'styles.css'; -button { - background: linear-gradient( - if(media(min-width: 768px): to right; else: to bottom), - if(style(--dark-mode): #333; else: #fff), - if(style(--dark-mode): #000; else: #ccc) - ); -}`; - -result = await transform(css, { - - beautify: true, - sourcemap: true, - resolveImport: true, - output: 'dist/doc.html' -}); - -console.log(result.map.toJSON()); -``` ## Difference Between Sync and Async APIs -The following features are **not supported by `parseSync()` and `transformSync()`**. - -### Unsupported Parsing Features - -* Flattening `@import` at-rules is not supported. -* The file loader `ParserOptions.load()` is not available. -* Parsing from a stream is not supported. -* Parsing with a file as the input parameter is not supported. +### Parsing features comparison -### Unsupported CSS Module Features +| Feature | parse() | transform() | transformSync() | ParseSync() | +| ----------------------- | ------- | ----------- | --------------- | ----------- | +| Parse from stream | ✅ | ✅ | ❌ | ❌ | +| Parse from file | ✅ | ✅ | ❌ | ❌ | +| Flatten @import at-rule | ✅ | ✅ | ❌ | ❌ | +| transformSync() | ✅ | ✅ | ❌ | ❌ | -* The `pattern` parameter does not support the following algorithms: +### CSS Module features comparison - * `sha1` - * `sha256` - * `sha384` - * `sha512` -* CSS `composes` does not support composing from a file. -* Importing CSS variables from a file using `@value` is not supported. +| Feature | parse() | transform() | transformSync() | ParseSync() | +| ---------------------------------------------------------------------- | ------- | ----------- | --------------- | ----------- | +| Algorithms supported by `pattern`:
sha1, sha256, sha384, sha512 | ✅ | ✅ | ❌ | ❌ | +| CSS `composes` from file | ✅ | ✅ | ❌ | ❌ | +| import CSS variables from file | ✅ | ✅ | ❌ | ❌ | ------ diff --git a/jsr.json b/jsr.json index 34aff9f9..2828ebed 100644 --- a/jsr.json +++ b/jsr.json @@ -1,6 +1,6 @@ { "name": "@tbela99/css-parser", - "version": "1.4.11", + "version": "1.5.0", "publish": { "include": [ "src", diff --git a/llms.txt b/llms.txt index ddef377c..ddf08057 100644 --- a/llms.txt +++ b/llms.txt @@ -1,9 +1,10 @@ # css-parser ## Project overview -- This repository contains css-parser, a dependency-free CSS parser, transformer, minifier, and validator for Node.js and browsers. +- This repository contains css-parser, a dependency-free all-in-one CSS parsing solution for Node.js and browsers. - The library follows the CSS Syntax Module Level 3 specification and uses MDN syntax data for validation. -- It is designed for fault-tolerant parsing, AST manipulation, CSS Modules, minification, color processing, and syntax lowering. +- It is designed for fault-tolerant parsing, full AST and typed-token analysis, AST manipulation, CSS Modules, minification, color processing, and syntax lowering. +- CSS is always fully parsed into a structured AST, and token values are typed so plugins and transforms can work against semantic data instead of raw strings. ## Installation - npm: npm install @tbela99/css-parser @@ -26,9 +27,15 @@ - Support CSS Modules with scoped class generation. - Minify CSS safely with options such as inlineCssVariables, computeCalcExpression, removeDuplicateDeclarations, and beautify. - Transform ASTs through visitors and custom traversal logic. +- Support plugin-style extensions through the visitor API, where custom handlers can observe and mutate AST nodes during enter/visit/leave phases. - Lower modern CSS syntax such as nested CSS and if() to broadly compatible output. - Generate source maps and handle advanced color functions and conversions. +## Visitor-based plugin model +- The parser exposes a visitor option that accepts node-specific handlers keyed by AST node type and event type. +- Plugins can be implemented as reusable visitor maps that inspect or transform nodes such as Rule, AtRule, Declaration, KeyframesRule, and Value nodes. +- This allows extension code to run alongside the core parser without modifying the parser itself. + ## Typical usage ```ts import {transform, ColorType} from '@tbela99/css-parser'; @@ -43,15 +50,15 @@ console.log(result.code); ``` ## Important behavior notes -- parse() and transform() are lenient by default and preserve unknown constructs unless configured otherwise. +- parse(), parseSync(), transform() and transformSync() are lenient by default and preserve unknown constructs unless configured otherwise. - Comments are removed by default; preserve them with removeComments: false or preserveLicense: true. - Validation errors are available through the parse/transform result and through node.state and node.errors. - The library prioritizes compact output while preserving semantics. ## Useful concepts -- parse() returns AST, errors, and stats. +- parse(), parseSync() return AST, errors, and stats. - render() turns an AST into CSS text. -- transform() performs parsing and rendering together. +- transform(), transformSync() perform parsing and rendering together. - AST node types include StyleSheet, Rule, AtRule, Declaration, Comment, and Keyframes variants. ## Documentation files diff --git a/package.json b/package.json index 3c22ae62..3f0014e3 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@tbela99/css-parser", "description": "CSS parser, minifier and validator for node and the browser", - "version": "1.4.11", + "version": "1.5.0", "exports": { ".": "./dist/node.js", "./node": "./dist/node.js", diff --git a/src/@types/ast.d.ts b/src/@types/ast.d.ts index bdc2b2e5..e018ba9d 100644 --- a/src/@types/ast.d.ts +++ b/src/@types/ast.d.ts @@ -1,6 +1,6 @@ -import {EnumToken} from "../lib/ast/types.ts"; -import {ERRORS, LOC, OPTIMIZED, PARENT, RAW, ROOT, STATE, TOKENS} from "../lib/syntax/constants.ts"; -import type {Token} from "./token.d.ts"; +import { EnumToken } from "../lib/ast/types.ts"; +import { ERRORS, LOC, OPTIMIZED, PARENT, RAW, ROOT, STATE, TOKENS } from "../lib/syntax/constants.ts"; +import type { Token } from "./token.d.ts"; /** * token or node location @@ -75,7 +75,7 @@ export declare interface BaseToken { /** * parent node */ - parent?: AstAtRule | astRule | AstKeyframesAtRule | AstKeyFrameRule | AstInvalidRule | AstInvalidAtRule | null; + parent?: AstAtRule | astRule | AstKeyframesAtRule | AstKeyframesRule | AstInvalidRule | AstInvalidAtRule | null; /** * @private */ @@ -220,11 +220,11 @@ export declare interface AstInvalidAtRule extends BaseToken, AstNodeStatus { /** * keyframe rule node */ -export declare interface AstKeyFrameRule extends BaseToken, AstNodeStatus { +export declare interface AstKeyframesRule extends BaseToken, AstNodeStatus { /** * token type */ - typ: EnumToken.KeyFramesRuleNodeType; + typ: EnumToken.KeyframesRuleNodeType; /** * selector */ @@ -329,7 +329,7 @@ export declare interface AstKeyframesRule extends BaseToken, AstNodeStatus { /** * token type */ - typ: EnumToken.KeyFramesRuleNodeType; + typ: EnumToken.KeyframesRuleNodeType; /** * selector */ @@ -378,7 +378,7 @@ export declare type AstRuleList = | AstAtRule | AstRule | AstKeyframesAtRule - | AstKeyFrameRule + | AstKeyframesRule | AstInvalidRule; /** @@ -406,7 +406,7 @@ export declare type AstNode = | AstRule | AstDeclaration | AstKeyframesAtRule - | AstKeyFrameRule + | AstKeyframesRule | AstInvalidRule | AstInvalidAtRule | AstInvalidDeclaration diff --git a/src/@types/index.d.ts b/src/@types/index.d.ts index fde50dcf..d1e49554 100644 --- a/src/@types/index.d.ts +++ b/src/@types/index.d.ts @@ -1,4 +1,9 @@ -import type { VisitorSyncNodeMap, VisitorNodeMap } from "./visitor.d.ts"; +import type { + GenericVisitorAstNodeSyncHandlerMap, + GenericVisitorAstNodeHandlerMap, + VisitorSyncNodeMap, + VisitorNodeMap, +} from "./visitor.d.ts"; import type { AstAtRule, AstDeclaration, AstNode, AstRule, AstStyleSheet, SourceLocation } from "./ast.d.ts"; import { SourceMap } from "../lib/renderer/sourcemap/sourcemap.ts"; import type { PropertyListOptions } from "./parse.d.ts"; @@ -442,21 +447,52 @@ export declare interface ParseInputStreamOptions { input: string | ReadableStream; } +/** + * Input options for string or stream + * @internal + */ export declare interface ParseSourceOptions { + /** + * Source file to be used for sourcemap + * @internal + */ sourcesMap?: Map; + /** + * Source file to be used for sourcemap + * @internal + */ source?: SourceFile | null; } +/** + * Parser sourcemap options + */ +export declare interface ParserSourceMapOptions { + /** + * Include sourcemap in the ast. Sourcemap info is always generated + */ + sourcemap?: boolean | "inline"; + /** + * Input source map + */ + inputSourceMap?: SourceMapObject | string; +} + +/** + * Sync parseroptions + */ export declare interface ParserSyncOptions - extends MinifyOptions, MinifyFeatureOptions, ValidationOptions, PropertyListOptions, ParseSourceOptions { + extends + MinifyOptions, + ParserSourceMapOptions, + MinifyFeatureOptions, + ValidationOptions, + PropertyListOptions, + ParseSourceOptions { /** * Source file to be used for sourcemap */ src?: string; - /** - * Include sourcemap in the ast. Sourcemap info is always generated - */ - sourcemap?: boolean | "inline"; /** * Remove at-rule charset */ @@ -514,7 +550,7 @@ export declare interface ParserSyncOptions * Node visitor * {@link VisitorSyncNodeMap | VisitorSyncNodeMap[]} */ - visitor?: VisitorSyncNodeMap | VisitorSyncNodeMap[]; + visitor?: GenericVisitorAstNodeSyncHandlerMap | VisitorSyncNodeMap | VisitorSyncNodeMap[]; /** * Abort signal * @@ -579,7 +615,11 @@ export declare interface ParserOptions extends ParserSyncOptions, ModuleAsyncOpt * Node visitor * {@link VisitorNodeMap | VisitorNodeMap[]} */ - visitor?: VisitorNodeMap | VisitorNodeMap[]; + visitor?: + | GenericVisitorAstNodeSyncHandlerMap + | GenericVisitorAstNodeHandlerMap + | VisitorNodeMap + | VisitorNodeMap[]; } /** @@ -658,6 +698,11 @@ export declare interface ResolvedPath { * Ast node render options */ export declare interface RenderOptions { + /** + * Source file to be used as CSS input file for sourcemap resolution + */ + src?: string; + /** * Minify css values. */ diff --git a/src/@types/token.d.ts b/src/@types/token.d.ts index 1c800e3e..667ebd2e 100644 --- a/src/@types/token.d.ts +++ b/src/@types/token.d.ts @@ -6,7 +6,7 @@ import { ColorType, EnumToken, EnumAstNodeStatus } from "../lib/ast/types.ts"; */ export declare interface LiteralToken extends BaseToken { /** - * literal type + * @inheritdoc */ typ: EnumToken.LiteralTokenType; /** @@ -20,7 +20,7 @@ export declare interface LiteralToken extends BaseToken { */ export declare interface ClassSelectorToken extends BaseToken { /** - * class selector type + * @inheritdoc */ typ: EnumToken.ClassSelectorTokenType; /** @@ -34,7 +34,7 @@ export declare interface ClassSelectorToken extends BaseToken { */ export declare interface InvalidClassSelectorToken extends BaseToken { /** - * invalid class selector type + * @inheritdoc */ typ: EnumToken.InvalidClassSelectorTokenType; /** @@ -48,7 +48,7 @@ export declare interface InvalidClassSelectorToken extends BaseToken { */ export declare interface UniversalSelectorToken extends BaseToken { /** - * universal selector type + * @inheritdoc */ typ: EnumToken.UniversalSelectorTokenType; } @@ -58,7 +58,7 @@ export declare interface UniversalSelectorToken extends BaseToken { */ export declare interface IdentToken extends BaseToken { /** - * ident type + * @inheritdoc */ typ: EnumToken.IdenTokenType; /** @@ -72,7 +72,7 @@ export declare interface IdentToken extends BaseToken { */ export declare interface IdentListToken extends BaseToken { /** - * ident list type + * @inheritdoc */ typ: EnumToken.IdenListTokenType; /** @@ -86,7 +86,7 @@ export declare interface IdentListToken extends BaseToken { */ export declare interface DashedIdentToken extends BaseToken { /** - * ident type + * @inheritdoc */ typ: EnumToken.DashedIdenTokenType; /** @@ -100,7 +100,7 @@ export declare interface DashedIdentToken extends BaseToken { */ export declare interface CommaToken extends BaseToken { /** - * comma type + * @inheritdoc */ typ: EnumToken.CommaTokenType; } @@ -110,7 +110,7 @@ export declare interface CommaToken extends BaseToken { */ export declare interface ColonToken extends BaseToken { /** - * colon type ':' + * @inheritdoc */ typ: EnumToken.ColonTokenType; } @@ -120,7 +120,7 @@ export declare interface ColonToken extends BaseToken { */ export declare interface DoubleColonToken extends BaseToken { /** - * double colon type '::' + * @inheritdoc */ typ: EnumToken.DoubleColonTokenType; } @@ -130,7 +130,7 @@ export declare interface DoubleColonToken extends BaseToken { */ export declare interface SemiColonToken extends BaseToken { /** - * semicolon type + * @inheritdoc */ typ: EnumToken.SemiColonTokenType; } @@ -140,7 +140,7 @@ export declare interface SemiColonToken extends BaseToken { */ export declare interface NestingSelectorToken extends BaseToken { /** - * nesting selector type + * @inheritdoc */ typ: EnumToken.NestingSelectorTokenType; } @@ -150,7 +150,7 @@ export declare interface NestingSelectorToken extends BaseToken { */ export declare interface NumberToken extends BaseToken { /** - * number type + * @inheritdoc */ typ: EnumToken.NumberTokenType; /** @@ -168,7 +168,7 @@ export declare interface NumberToken extends BaseToken { */ export declare interface AtRuleToken extends BaseToken { /** - * at rule type + * @inheritdoc */ typ: EnumToken.AtRuleTokenType; /** @@ -186,7 +186,7 @@ export declare interface AtRuleToken extends BaseToken { */ export declare interface PercentageToken extends BaseToken { /** - * percentage type + * @inheritdoc */ typ: EnumToken.PercentageTokenType; /** @@ -200,7 +200,7 @@ export declare interface PercentageToken extends BaseToken { */ export declare interface FlexToken extends BaseToken { /** - * flex type + * @inheritdoc */ typ: EnumToken.FlexTokenType; /** @@ -242,7 +242,7 @@ export declare interface FunctionToken extends BaseToken { */ export declare interface GridTemplateFuncToken extends BaseToken { /** - * function type + * @inheritdoc */ typ: EnumToken.GridTemplateFuncTokenType; /** @@ -260,7 +260,7 @@ export declare interface GridTemplateFuncToken extends BaseToken { */ export declare interface FunctionURLToken extends BaseToken { /** - * function type + * @inheritdoc */ typ: EnumToken.UrlFunctionTokenType; /** @@ -278,7 +278,7 @@ export declare interface FunctionURLToken extends BaseToken { */ export declare interface FunctionImageToken extends BaseToken { /** - * function type + * @inheritdoc */ typ: EnumToken.ImageFunctionTokenType; /** @@ -305,7 +305,7 @@ export declare interface FunctionImageToken extends BaseToken { */ export declare interface TimingFunctionToken extends BaseToken { /** - * timing function type + * @inheritdoc */ typ: EnumToken.TimingFunctionTokenType; /** @@ -323,7 +323,7 @@ export declare interface TimingFunctionToken extends BaseToken { */ export declare interface TimelineFunctionToken extends BaseToken { /** - * timeline function type + * @inheritdoc */ typ: EnumToken.TimelineFunctionTokenType; /** @@ -341,7 +341,7 @@ export declare interface TimelineFunctionToken extends BaseToken { */ export declare interface StringToken extends BaseToken { /** - * string type + * @inheritdoc */ typ: EnumToken.StringTokenType; /** @@ -355,7 +355,7 @@ export declare interface StringToken extends BaseToken { */ export declare interface BadStringToken extends BaseToken { /** - * bad string type + * @inheritdoc */ typ: EnumToken.BadStringTokenType; /** @@ -369,7 +369,7 @@ export declare interface BadStringToken extends BaseToken { */ export declare interface UnclosedStringToken extends BaseToken { /** - * unclosed string type + * @inheritdoc */ typ: EnumToken.UnclosedStringTokenType; /** @@ -383,7 +383,7 @@ export declare interface UnclosedStringToken extends BaseToken { */ export declare interface DimensionToken extends BaseToken { /** - * dimension type + * @inheritdoc */ typ: EnumToken.DimensionTokenType; /** @@ -401,7 +401,7 @@ export declare interface DimensionToken extends BaseToken { */ export declare interface LengthToken extends BaseToken { /** - * length type + * @inheritdoc */ typ: EnumToken.LengthTokenType; /** @@ -419,7 +419,7 @@ export declare interface LengthToken extends BaseToken { */ export declare interface AngleToken extends BaseToken { /** - * angle type + * @inheritdoc */ typ: EnumToken.AngleTokenType; /** @@ -437,7 +437,7 @@ export declare interface AngleToken extends BaseToken { */ export declare interface TimeToken extends BaseToken { /** - * time type + * @inheritdoc */ typ: EnumToken.TimeTokenType; /** @@ -445,7 +445,7 @@ export declare interface TimeToken extends BaseToken { */ val: number | FractionToken; /** - * time unit + * time unit */ unit: "ms" | "s"; } @@ -455,7 +455,7 @@ export declare interface TimeToken extends BaseToken { */ export declare interface FrequencyToken extends BaseToken { /** - * frequency type + * @inheritdoc */ typ: EnumToken.FrequencyTokenType; /** @@ -473,7 +473,7 @@ export declare interface FrequencyToken extends BaseToken { */ export declare interface ResolutionToken extends BaseToken { /** - * resolution type + * @inheritdoc */ typ: EnumToken.ResolutionTokenType; /** @@ -491,7 +491,7 @@ export declare interface ResolutionToken extends BaseToken { */ export declare interface HashToken extends BaseToken { /** - * hash type + * @inheritdoc */ typ: EnumToken.HashTokenType; /** @@ -505,7 +505,7 @@ export declare interface HashToken extends BaseToken { */ export declare interface BlockStartToken extends BaseToken { /** - * block start type + * @inheritdoc */ typ: EnumToken.BlockStartTokenType; } @@ -515,7 +515,7 @@ export declare interface BlockStartToken extends BaseToken { */ export declare interface BlockEndToken extends BaseToken { /** - * block end type + * @inheritdoc */ typ: EnumToken.BlockEndTokenType; } @@ -525,7 +525,7 @@ export declare interface BlockEndToken extends BaseToken { */ export declare interface AttrStartToken extends BaseToken { /** - * attribute start type + * @inheritdoc */ typ: EnumToken.AttrStartTokenType; /** @@ -539,7 +539,7 @@ export declare interface AttrStartToken extends BaseToken { */ export declare interface AttrEndToken extends BaseToken { /** - * attribute end type + * @inheritdoc */ typ: EnumToken.AttrEndTokenType; } @@ -549,7 +549,7 @@ export declare interface AttrEndToken extends BaseToken { */ export declare interface ParensStartToken extends BaseToken { /** - * parenthesis start type + * @inheritdoc */ typ: EnumToken.StartParensTokenType; } @@ -559,7 +559,7 @@ export declare interface ParensStartToken extends BaseToken { */ export declare interface ParensEndToken extends BaseToken { /** - * parenthesis end type + * @inheritdoc */ typ: EnumToken.EndParensTokenType; } @@ -569,7 +569,7 @@ export declare interface ParensEndToken extends BaseToken { */ export declare interface ParensToken extends BaseToken { /** - * parenthesis type + * @inheritdoc */ typ: EnumToken.ParensTokenType; /** @@ -583,7 +583,7 @@ export declare interface ParensToken extends BaseToken { */ export declare interface WhitespaceToken extends BaseToken { /** - * whitespace type + * @inheritdoc */ typ: EnumToken.WhitespaceTokenType; /** @@ -597,7 +597,7 @@ export declare interface WhitespaceToken extends BaseToken { */ export declare interface CommentToken extends BaseToken { /** - * comment type + * @inheritdoc */ typ: EnumToken.CommentTokenType; /** @@ -611,7 +611,7 @@ export declare interface CommentToken extends BaseToken { */ export declare interface BadCommentToken extends BaseToken { /** - * bad comment type + * @inheritdoc */ typ: EnumToken.BadCommentTokenType; /** @@ -624,7 +624,13 @@ export declare interface BadCommentToken extends BaseToken { * CDO comment token */ export declare interface CDOCommentToken extends BaseToken { + /** + * @inheritdoc + */ typ: EnumToken.CDOCOMMTokenType; + /** + * CDO comment value + */ val: string; } @@ -633,7 +639,7 @@ export declare interface CDOCommentToken extends BaseToken { */ export declare interface BadCDOCommentToken extends BaseToken { /** - * bad CDO comment type + * @inheritdoc */ typ: EnumToken.BadCdoTokenType; /** @@ -647,7 +653,7 @@ export declare interface BadCDOCommentToken extends BaseToken { */ export declare interface IncludeMatchToken extends BaseToken { /** - * include match type + * @inheritdoc */ typ: EnumToken.IncludeMatchTokenType; // val: '~='; @@ -658,7 +664,7 @@ export declare interface IncludeMatchToken extends BaseToken { */ export declare interface DashMatchToken extends BaseToken { /** - * dash match type + * @inheritdoc */ typ: EnumToken.DashMatchTokenType; // val: '|='; @@ -669,7 +675,7 @@ export declare interface DashMatchToken extends BaseToken { */ export declare interface EqualMatchToken extends BaseToken { /** - * equal match type + * @inheritdoc */ typ: EnumToken.EqualMatchTokenType; // val: '|='; @@ -680,7 +686,7 @@ export declare interface EqualMatchToken extends BaseToken { */ export declare interface StartMatchToken extends BaseToken { /** - * start match type + * @inheritdoc */ typ: EnumToken.StartMatchTokenType; // val: '^='; @@ -691,7 +697,7 @@ export declare interface StartMatchToken extends BaseToken { */ export declare interface EndMatchToken extends BaseToken { /** - * end match type + * @inheritdoc */ typ: EnumToken.EndMatchTokenType; // val: '|='; @@ -702,7 +708,7 @@ export declare interface EndMatchToken extends BaseToken { */ export declare interface ContainMatchToken extends BaseToken { /** - * contain match type + * @inheritdoc */ typ: EnumToken.ContainMatchTokenType; // val: '|='; @@ -713,7 +719,7 @@ export declare interface ContainMatchToken extends BaseToken { */ export declare interface LessThanToken extends BaseToken { /** - * less than type + * @inheritdoc */ typ: EnumToken.LtTokenType; } @@ -723,7 +729,7 @@ export declare interface LessThanToken extends BaseToken { */ export declare interface LessThanOrEqualToken extends BaseToken { /** - * less than or equal type + * @inheritdoc */ typ: EnumToken.LteTokenType; } @@ -733,7 +739,7 @@ export declare interface LessThanOrEqualToken extends BaseToken { */ export declare interface GreaterThanToken extends BaseToken { /** - * greater than type + * @inheritdoc */ typ: EnumToken.GtTokenType; } @@ -743,7 +749,7 @@ export declare interface GreaterThanToken extends BaseToken { */ export declare interface GreaterThanOrEqualToken extends BaseToken { /** - * greater than or equal type + * @inheritdoc */ typ: EnumToken.GteTokenType; } @@ -753,7 +759,7 @@ export declare interface GreaterThanOrEqualToken extends BaseToken { */ export declare interface ColumnCombinatorToken extends BaseToken { /** - * column combinator type + * @inheritdoc */ typ: EnumToken.ColumnCombinatorTokenType; } @@ -763,7 +769,7 @@ export declare interface ColumnCombinatorToken extends BaseToken { */ export declare interface PseudoClassToken extends BaseToken { /** - * Pseudo class + * @inheritdoc */ typ: EnumToken.PseudoClassTokenType; /** @@ -777,7 +783,7 @@ export declare interface PseudoClassToken extends BaseToken { */ export declare interface PseudoElementToken extends BaseToken { /** - * Pseudo element + * @inheritdoc */ typ: EnumToken.PseudoElementTokenType; /** @@ -791,7 +797,7 @@ export declare interface PseudoElementToken extends BaseToken { */ export declare interface PseudoPageToken extends BaseToken { /** - * Pseudo page + * @inheritdoc */ typ: EnumToken.PseudoPageTokenType; /** @@ -805,7 +811,7 @@ export declare interface PseudoPageToken extends BaseToken { */ export declare interface PseudoClassFunctionToken extends BaseToken { /** - * Pseudo class function + * @inheritdoc */ typ: EnumToken.PseudoClassFuncTokenType; /** @@ -823,7 +829,7 @@ export declare interface PseudoClassFunctionToken extends BaseToken { */ export declare interface DelimToken extends BaseToken { /** - * Delimiter token type + * @inheritdoc */ typ: EnumToken.DelimTokenType; } @@ -833,7 +839,7 @@ export declare interface DelimToken extends BaseToken { */ export declare interface BadUrlToken extends BaseToken { /** - * Bad URL + * @inheritdoc */ typ: EnumToken.BadUrlTokenType; /** @@ -847,7 +853,7 @@ export declare interface BadUrlToken extends BaseToken { */ export declare interface UrlToken extends BaseToken { /** - * URL + * @inheritdoc */ typ: EnumToken.UrlTokenTokenType; /** @@ -861,7 +867,7 @@ export declare interface UrlToken extends BaseToken { */ export declare interface EOFToken extends BaseToken { /** - * End of file + * @inheritdoc */ typ: EnumToken.EOFTokenType; } @@ -871,7 +877,7 @@ export declare interface EOFToken extends BaseToken { */ export declare interface ImportantToken extends BaseToken { /** - * Important + * @inheritdoc */ typ: EnumToken.ImportantTokenType; } @@ -881,7 +887,7 @@ export declare interface ImportantToken extends BaseToken { */ export declare interface ColorToken extends BaseToken { /** - * Color type + * @inheritdoc */ typ: EnumToken.ColorTokenType; /** @@ -907,7 +913,7 @@ export declare interface ColorToken extends BaseToken { */ export declare interface AttrToken extends BaseToken { /** - * Attribute type + * @inheritdoc */ typ: EnumToken.AttrTokenType; /** @@ -921,7 +927,7 @@ export declare interface AttrToken extends BaseToken { */ export declare interface InvalidAttrToken extends BaseToken { /** - * Attribute type + * @inheritdoc */ typ: EnumToken.InvalidAttrTokenType; /** @@ -934,6 +940,9 @@ export declare interface InvalidAttrToken extends BaseToken { * Child combinator token */ export declare interface ChildCombinatorToken extends BaseToken { + /** + * @inheritdoc + */ typ: EnumToken.ChildCombinatorTokenType; } @@ -942,7 +951,7 @@ export declare interface ChildCombinatorToken extends BaseToken { */ export declare interface MediaFeatureToken extends BaseToken { /** - * Media feature type + * @inheritdoc */ typ: EnumToken.MediaFeatureTokenType; /** @@ -956,7 +965,7 @@ export declare interface MediaFeatureToken extends BaseToken { */ export declare interface NotToken extends BaseToken { /** - * Media feature not type + * @inheritdoc */ typ: EnumToken.NotTokenType; /** @@ -970,7 +979,7 @@ export declare interface NotToken extends BaseToken { */ export declare interface MediaFeatureOnlyToken extends BaseToken { /** - * Media feature only type + * @inheritdoc */ typ: EnumToken.OnlyTokenType; /** @@ -984,7 +993,7 @@ export declare interface MediaFeatureOnlyToken extends BaseToken { */ export declare interface AndToken extends BaseToken { /** - * Media feature and type + * @inheritdoc */ typ: EnumToken.AndTokenType; } @@ -994,7 +1003,7 @@ export declare interface AndToken extends BaseToken { */ export declare interface OrToken extends BaseToken { /** - * Media feature or type + * @inheritdoc */ typ: EnumToken.OrTokenType; } @@ -1004,7 +1013,7 @@ export declare interface OrToken extends BaseToken { */ export declare interface MediaQueryUnaryFeatureToken extends BaseToken { /** - * Media query condition type + * @inheritdoc */ typ: EnumToken.MediaQueryUnaryFeatureTokenType; /** @@ -1019,7 +1028,7 @@ export declare interface MediaQueryUnaryFeatureToken extends BaseToken { export declare interface SupportsQueryUnaryConditionToken extends BaseToken { /** - * Supports query condition type + * @inheritdoc */ typ: EnumToken.SupportsQueryUnaryConditionTokenType; /** @@ -1034,7 +1043,7 @@ export declare interface SupportsQueryUnaryConditionToken extends BaseToken { export declare interface SupportsQueryConditionToken extends BaseToken { /** - * Supports query condition type + * @inheritdoc */ typ: EnumToken.SupportsQueryConditionTokenType; /** @@ -1053,7 +1062,7 @@ export declare interface SupportsQueryConditionToken extends BaseToken { export declare interface WhenElseQueryConditionToken extends BaseToken { /** - * When else query condition type + * @inheritdoc */ typ: EnumToken.WhenElseQueryConditionTokenType; /** @@ -1072,7 +1081,7 @@ export declare interface WhenElseQueryConditionToken extends BaseToken { export declare interface WhenElseUnaryConditionToken extends BaseToken { /** - * When else query condition type + * @inheritdoc */ typ: EnumToken.WhenElseUnaryConditionTokenType; /** @@ -1087,7 +1096,7 @@ export declare interface WhenElseUnaryConditionToken extends BaseToken { export declare interface MediaQueryConditionToken extends BaseToken { /** - * Media query condition type + * @inheritdoc */ typ: EnumToken.MediaQueryConditionTokenType; /** @@ -1114,7 +1123,7 @@ export declare interface MediaQueryConditionToken extends BaseToken { export declare interface IfConditionToken extends BaseToken { /** - * If condition type + * @inheritdoc */ typ: EnumToken.IfConditionTokenType; /** @@ -1129,7 +1138,7 @@ export declare interface IfConditionToken extends BaseToken { export declare interface IfElseConditionToken extends BaseToken { /** - * If else condition type + * @inheritdoc */ typ: EnumToken.IfElseConditionTokenType; /** @@ -1143,23 +1152,67 @@ export declare interface IfElseConditionToken extends BaseToken { } export declare interface ContainerStyleRangeToken extends BaseToken { + /** + * @inheritdoc + */ typ: EnumToken.ContainerStyleRangeTokenType; + /** + * condition left handle + */ l: Token[]; + /** + * condition operator + */ op: Token[]; + /** + * condition value + */ r: Token[]; } +// (20px <= width < 30px) +/** + * @inheritdoc + */ export declare interface MediaRangeQueryToken extends BaseToken { + /** + * @inheritdoc + */ typ: EnumToken.MediaRangeQueryTokenType; + /** + * left hanle + * */ l: Token[]; + /** + * media feature name + */ val: Token[]; + /** + * first comparator + */ op1: LessThanToken | GreaterThanToken | LessThanOrEqualToken | GreaterThanOrEqualToken; + /** + * second comparator + */ op2: LessThanToken | GreaterThanToken | LessThanOrEqualToken | GreaterThanOrEqualToken; + /** + * right handle + */ r: Token[]; } +/** + * @inheritdoc + */ export declare interface InvalidMediaQueryToken extends BaseToken { + /** + * @inheritdoc + */ typ: EnumToken.InvalidMediaQueryTokenType; + + /** + * children + */ chi: Token[]; } @@ -1167,6 +1220,9 @@ export declare interface InvalidMediaQueryToken extends BaseToken { * Descendant combinator token */ export declare interface DescendantCombinatorToken extends BaseToken { + /** + * @inheritdoc + */ typ: EnumToken.DescendantCombinatorTokenType; } @@ -1174,6 +1230,9 @@ export declare interface DescendantCombinatorToken extends BaseToken { * Next sibling combinator token */ export declare interface NextSiblingCombinatorToken extends BaseToken { + /** + * @inheritdoc + */ typ: EnumToken.NextSiblingCombinatorTokenType; } @@ -1181,6 +1240,9 @@ export declare interface NextSiblingCombinatorToken extends BaseToken { * Subsequent sibling combinator token */ export declare interface SubsequentCombinatorToken extends BaseToken { + /** + * @inheritdoc + */ typ: EnumToken.SubsequentSiblingCombinatorTokenType; } @@ -1188,6 +1250,9 @@ export declare interface SubsequentCombinatorToken extends BaseToken { * Add token */ export declare interface AddToken extends BaseToken { + /** + * @inheritdoc + */ typ: EnumToken.Add; } @@ -1195,6 +1260,9 @@ export declare interface AddToken extends BaseToken { * Sub token */ export declare interface SubToken extends BaseToken { + /** + * @inheritdoc + */ typ: EnumToken.Sub; } @@ -1202,6 +1270,9 @@ export declare interface SubToken extends BaseToken { * Div token */ export declare interface DivToken extends BaseToken { + /** + * @inheritdoc + */ typ: EnumToken.Div; } @@ -1210,7 +1281,7 @@ export declare interface DivToken extends BaseToken { */ export declare interface MulToken extends BaseToken { /** - * Type + * @inheritdoc */ typ: EnumToken.Mul; } @@ -1220,7 +1291,7 @@ export declare interface MulToken extends BaseToken { */ export declare interface WrappedValuesToken extends BaseToken { /** - * Type + * @inheritdoc */ typ: EnumToken.WrappedValuesTokenType; /** @@ -1234,7 +1305,7 @@ export declare interface WrappedValuesToken extends BaseToken { */ export declare interface UnaryExpression extends BaseToken { /** - * Type + * @inheritdoc */ typ: EnumToken.UnaryExpressionTokenType; /** @@ -1251,8 +1322,17 @@ export declare interface UnaryExpression extends BaseToken { * Fraction token */ export declare interface FractionToken extends BaseToken { + /** + * @inheritdoc + */ typ: EnumToken.FractionTokenType; + /** + * Left handle + */ l: NumberToken; + /** + * Right handle + */ r: NumberToken; } @@ -1260,9 +1340,21 @@ export declare interface FractionToken extends BaseToken { * Binary expression token */ export declare interface BinaryExpressionToken extends BaseToken { + /** + * @inheritdoc + */ typ: EnumToken.BinaryExpressionTokenType; + /** + * Operator + */ op: EnumToken.Add | EnumToken.Sub | EnumToken.Div | EnumToken.Mul; + /** + * Left handle + */ l: BinaryExpressionNode | Token; + /** + * Right handle + */ r: BinaryExpressionNode | Token; } @@ -1270,10 +1362,25 @@ export declare interface BinaryExpressionToken extends BaseToken { * Match expression token */ export declare interface MatchExpressionToken extends BaseToken { + /** + * @inheritdoc + */ typ: EnumToken.MatchExpressionTokenType; + /** + * Operator + */ op: EqualMatchToken | DashMatchToken | StartMatchToken | ContainMatchToken | EndMatchToken | IncludeMatchToken; + /** + * Left handle + */ l: Token; + /** + * Right handle + */ r: Token; + /** + * Flags + */ attr?: "i" | "s"; } @@ -1281,8 +1388,17 @@ export declare interface MatchExpressionToken extends BaseToken { * Name space attribute token */ export declare interface NameSpaceAttributeToken extends BaseToken { + /** + * @inheritdoc + */ typ: EnumToken.NameSpaceAttributeTokenType; + /** + * Left handle + */ l?: Token; + /** + * Right handle + */ r: Token; } @@ -1290,7 +1406,13 @@ export declare interface NameSpaceAttributeToken extends BaseToken { * List token */ export declare interface ListToken extends BaseToken { + /** + * @inheritdoc + */ typ: EnumToken.ListToken; + /** + * Children + */ chi: Token[]; } @@ -1298,8 +1420,17 @@ export declare interface ListToken extends BaseToken { * Composes selector token */ export declare interface ComposesSelectorToken extends BaseToken { + /** + * @inheritdoc + */ typ: EnumToken.ComposesSelectorTokenType; + /** + * Left handle + */ l: Token[]; + /** + * Right handle + */ r: Token | null; } @@ -1307,20 +1438,53 @@ export declare interface ComposesSelectorToken extends BaseToken { * Css variable token */ export declare interface CssVariableToken extends BaseToken { + /** + * @inheritdoc + */ typ: EnumToken.CssVariableTokenType; + /** + * Name + */ nam: string; + /** + * Value + */ val: Token[]; } +/** + * Css variable import token + */ export declare interface CssVariableImportTokenType extends BaseToken { + /** + * @inheritdoc + */ typ: EnumToken.CssVariableImportTokenType; + /** + * Name + */ nam: string; + /** + * Value + */ val: Token[]; } +/** + * Css variable map token + */ export declare interface CssVariableMapTokenType extends BaseToken { + /** + * @inheritdoc + */ typ: EnumToken.CssVariableDeclarationMapTokenType; + /** + * CSS Variables + */ vars: Token[]; + /** + * From clause + */ from: Token[]; } @@ -1328,6 +1492,9 @@ export declare interface CssVariableMapTokenType extends BaseToken { * Function definition token */ export declare interface FunctionDefToken extends BaseToken { + /** + * @inheritdoc + */ typ: | EnumToken.FunctionDefTokenType | EnumToken.UrlFunctionTokenDefType @@ -1339,7 +1506,13 @@ export declare interface FunctionDefToken extends BaseToken { | EnumToken.MathFunctionTokenDefType | EnumToken.PseudoClassFunctionTokenDefType | EnumToken.TransformFunctionTokenDefType; + /** + * Name + */ nam: string; + /** + * Value + */ val: string; } @@ -1347,7 +1520,13 @@ export declare interface FunctionDefToken extends BaseToken { * Raw node token */ export declare interface RawNodeToken extends BaseToken, EnumAstNodeStatus { + /** + * @inheritdoc + */ typ: EnumToken.RawNodeTokenType; + /** + * Value + */ val: Token[]; } diff --git a/src/@types/validation.d.ts b/src/@types/validation.d.ts index aa630143..b7b29f38 100644 --- a/src/@types/validation.d.ts +++ b/src/@types/validation.d.ts @@ -4,67 +4,106 @@ import type { Token } from "./token.d.ts"; import type { ValidationOptions } from "./index.d.ts"; import { MediaFeatureType, ValidationSyntaxGroupEnum } from "../lib/validation/parser/typedef.ts"; +/** + * Validation syntax + * @internal + */ export declare interface ValidationSyntaxNode { + /** + * mdn data syntax + */ syntax: string; + /** + * validation tokens + */ ast?: ValidationToken[]; + /** + * descriptors + */ descriptors?: Record>; } +/** + * Validation selector options + * @internal + */ export interface ValidationSelectorOptions extends ValidationOptions { + /** + * nested selector + */ nestedSelector?: boolean; } +/** + * Validation media feature + * @internal + */ export declare interface ValidationMediaFeature { + /** + * media feature type + */ type: MediaFeatureType; + /** + * media feature status + */ status?: string; + /** + * media feature category + */ category: string; + /** + * media feature values + */ values?: Array | Array; } +/** + * Validation configuration + * @internal + */ export declare type ValidationConfiguration = Record< ValidationSyntaxGroupEnum, ValidationSyntaxNode | Record | Record >; +/** + * Validation result + * @internal + */ export interface ValidationResult { + /** + * validation result + */ valid: SyntaxValidationResult; + /** + * node + */ node: AstNode | Token | null; + /** + * syntax + */ syntax: ValidationToken | string | null; + /** + * error + */ error: string; + /** + * cycle + */ cycle?: boolean; } +/** + * Validation syntax result + * @internal + */ export interface ValidationSyntaxResult extends ValidationResult { + /** + * syntax + */ syntax: ValidationToken | string | null; - context: Context | Token[]; -} - -export interface Context { - index: number; - /** - * The length of the context tokens to be consumed + * context */ - - readonly length: number; - - current(): Type | null; - - update(context: Context): void; - - consume(token: Type, howMany?: number): boolean; - - peek(): Type | null; - - // tokens(): Type[]; - - next(): Type | null; - - consume(token: Type, howMany?: number): boolean; - - slice(): Type[]; - - clone(): Context; - - done(): boolean; + context: ValidationContext | Token[]; } diff --git a/src/@types/visitor.d.ts b/src/@types/visitor.d.ts index 321a2450..8fc8d380 100644 --- a/src/@types/visitor.d.ts +++ b/src/@types/visitor.d.ts @@ -2,24 +2,40 @@ import type { AstAtRule, AstDeclaration, AstKeyframesAtRule, AstKeyframesRule, A import { WalkerEvent } from "../lib/ast/walk.ts"; import { EnumToken } from "../lib/ast/types.ts"; +/** + * Generic visitor result + */ export declare type GenericVisitorSyncResult = T | T[] | null; -export declare type GenericVisitorAsyncResult = Promise | Promise| Promise; +/** + * Generic visitor result + */ +export declare type GenericVisitorAsyncResult = Promise | Promise | Promise; +/** + * Generic visitor result + */ export declare type GenericVisitorResult = GenericVisitorSyncResult | GenericVisitorAsyncResult; - - +/** + * Generic visitor handler + */ export declare type GenericVisitorSyncHandler = ( node: T, parent?: AstNode | Token, root?: AstNode | Token, ) => GenericVisitorSyncResult; +/** + * Generic visitor handler + */ export declare type GenericVisitorAstNodeSyncHandlerMap = | Record> | GenericVisitorSyncHandler | { type: WalkerEvent; handler: GenericVisitorSyncHandler } | { type: WalkerEvent; handler: Record> }; +/** + * Generic visitor handler + */ export declare type ValueVisitorSyncHandler = GenericVisitorSyncHandler; /** @@ -229,8 +245,14 @@ export declare interface VisitorSyncNodeMap { */ Rule?: GenericVisitorAstNodeSyncHandlerMap; + /** + * keyframes rule visitor + */ KeyframesRule?: GenericVisitorAstNodeSyncHandlerMap; + /** + * keyframes at-rule visitor + */ KeyframesAtRule?: GenericVisitorAstNodeSyncHandlerMap; /** @@ -285,22 +307,32 @@ export declare interface VisitorSyncNodeMap { * // body {color:#f3fff0} * ``` */ - [key: keyof typeof EnumToken]: GenericVisitorAstNodeSyncHandlerMap | GenericVisitorAstNodeSyncHandlerMap; + [key: keyof typeof EnumToken]: + | GenericVisitorAstNodeSyncHandlerMap + | GenericVisitorAstNodeSyncHandlerMap; } - +/** + * Generic visitor handler + */ export declare type GenericVisitorHandler = ( node: T, parent?: AstNode | Token, root?: AstNode | Token, ) => GenericVisitorSyncResult | GenericVisitorAsyncResult; +/** + * Generic visitor handler + */ export declare type GenericVisitorAstNodeHandlerMap = | Record> | GenericVisitorHandler | { type: WalkerEvent; handler: GenericVisitorHandler } | { type: WalkerEvent; handler: Record> }; +/** + * Generic visitor handler + */ export declare type ValueVisitorHandler = GenericVisitorHandler; /** diff --git a/src/@types/walker.d.ts b/src/@types/walker.d.ts index 8eedabc6..6263d42a 100644 --- a/src/@types/walker.d.ts +++ b/src/@types/walker.d.ts @@ -2,6 +2,26 @@ import type { AstNode, AstRuleList } from "./ast.d.ts"; import type { Token } from "./token.d.ts"; import { WalkerEvent, WalkerOptionEnum } from "../lib/ast/walk.ts"; +/** + * node walker options + */ +export declare interface WalkerOptions { + + /** + * walk in reverse + */ + reverse?: boolean; + + /** + * Traverse node value tokens. If false, only traverse node children + */ + inludeValues?: boolean; + /** + * filter function to control the walk + */ + filter?: WalkerFilter; +} + /** * node walker option */ @@ -33,18 +53,54 @@ export declare type WalkerValueFilter = ( parents?: Generator, ) => WalkerOption | null; +/** + * walker result + */ export declare interface WalkResult { + /** + * current node + */ node: AstNode; + /** + * parent node + */ parent?: AstRuleList; + /** + * root node + */ root?: AstNode; + /** + * parent nodes + */ parents: Generator; } +/** + * walker result + */ export declare interface WalkAttributesResult { + /** + * current node + */ value: Token; + /** + * previous node + */ previousValue: Token | null; + /** + * next node + */ nextValue: Token | null; + /** + * root node + */ root?: AstNode | Token | null; + /** + * parent node + */ parent: AstNode | Token | null; + /** + * parent nodes + */ parents: Generator; } diff --git a/src/lib/ast/clone.ts b/src/lib/ast/clone.ts index 9389a67e..cba96bb2 100644 --- a/src/lib/ast/clone.ts +++ b/src/lib/ast/clone.ts @@ -24,15 +24,17 @@ export function cloneNode( if (value == null || typeof value != "object") { clone[name] = value; } else if (Array.isArray(value)) { - clone[name] = - !cloneChildren && name == checkNode - ? [] - : value.map((c) => { - const newObj = cloneNode(c, cloneChildren, cloneMap); + clone[name] = []; + if (cloneChildren || name !== checkNode) { + + for (const c of value) { + const newObj = cloneNode(c, cloneChildren, cloneMap); cloneMap?.set?.(c, newObj); - return newObj; - }); + clone[name].push(newObj); + } + } + } else { clone[name] = { ...value }; } diff --git a/src/lib/ast/expand.ts b/src/lib/ast/expand.ts index 42a9f6ec..b2687841 100644 --- a/src/lib/ast/expand.ts +++ b/src/lib/ast/expand.ts @@ -1,5 +1,5 @@ import { splitRule } from "./minify.ts"; -import { combinators, RAW } from "../syntax/constants.ts"; +import { combinators, PARENT, RAW } from "../syntax/constants.ts"; import { parseString } from "../parser/parse.ts"; import { walkValues } from "./walk.ts"; import { renderValue } from "../renderer/render.ts"; @@ -14,13 +14,20 @@ import { EnumToken } from "./types.ts"; */ export function expand(ast: AstStyleSheet | AstAtRule | AstRule): AstNode { const result = { ...ast, chi: [] }; + let children: AstNode[]; for (let i = 0; i < ast.chi!.length; i++) { - const node = ast.chi![i]; + let node = ast.chi![i]; if (node.typ === EnumToken.RuleNodeType) { + children = expandRule(node as AstRule); + + for (const child of children) { + child[PARENT] = result; + } + // @ts-ignore - result.chi.push(...expandRule(node)); + result.chi.push(...children); } else if (node.typ == EnumToken.AtRuleNodeType && "chi" in node) { let hasRule: boolean = false; let j: number = node!.chi!.length; @@ -33,10 +40,25 @@ export function expand(ast: AstStyleSheet | AstAtRule | AstRule): AstNode { } } - // @ts-ignore - result.chi.push({ ...(hasRule ? expand(node) : node) }); + if (hasRule) { + node = expand(node as AstRule); + + for (const child of node.chi) { + child[PARENT] = result; + } + + node[PARENT] = result; + // @ts-ignore + result.chi.push(node); + } else { + node[PARENT] = result; + // @ts-ignore + result.chi.push(node); + } } else { + node[PARENT] = result; // @ts-ignore + result.chi!.push(node); } } @@ -70,9 +92,10 @@ function expandRule(node: AstRule): Array { continue; } - selRule.forEach((arr) => - combinators.includes(arr[0].charAt(0)) ? arr.unshift(arSelf) : arr.unshift(arSelf, " "), - ); + for (let i1 = 0; i1 < selRule.length; i1++) { + const arr = selRule[i1]; + combinators.includes(arr[0].charAt(0)) ? arr.unshift(arSelf) : arr.unshift(arSelf, " "); + } rule.sel = selRule .reduce( @@ -84,8 +107,7 @@ function expandRule(node: AstRule): Array { [], ) .join(","); - - } else { + } else { let childSelectorCompound: string[] = []; let withCompound: string[] = []; let withoutCompound: string[] = []; @@ -102,7 +124,7 @@ function expandRule(node: AstRule): Array { continue; } - for (const sel of rule[RAW]?? splitRule(rule.sel)) { + for (const sel of rule[RAW] ?? splitRule(rule.sel)) { const s: string = sel.join(""); if (s.includes("&") || parentSelector) { diff --git a/src/lib/ast/features/shorthand.ts b/src/lib/ast/features/shorthand.ts index 20df72b3..daf97e2c 100644 --- a/src/lib/ast/features/shorthand.ts +++ b/src/lib/ast/features/shorthand.ts @@ -14,7 +14,7 @@ export class ComputeShorthandFeature { public accept: Set = new Set([ EnumToken.RuleNodeType, EnumToken.AtRuleNodeType, - EnumToken.KeyFramesRuleNodeType, + EnumToken.KeyframesRuleNodeType, ]); get ordering() { diff --git a/src/lib/ast/features/transform.ts b/src/lib/ast/features/transform.ts index 5cb304ed..1c525520 100644 --- a/src/lib/ast/features/transform.ts +++ b/src/lib/ast/features/transform.ts @@ -15,7 +15,7 @@ import { FeatureWalkMode } from "./type.ts"; import { STATE } from "../../syntax/constants.ts"; export class TransformCssFeature { - public accept: Set = new Set([EnumToken.RuleNodeType, EnumToken.KeyFramesRuleNodeType]); + public accept: Set = new Set([EnumToken.RuleNodeType, EnumToken.KeyframesRuleNodeType]); get ordering(): number { return 3; diff --git a/src/lib/ast/find.ts b/src/lib/ast/find.ts index 16e55e27..d283ced2 100644 --- a/src/lib/ast/find.ts +++ b/src/lib/ast/find.ts @@ -5,7 +5,7 @@ import { walk, walkValues } from "./walk.ts"; import { PARENT, TOKENS } from "../syntax/constants.ts"; /** - * Search the ast sub-tree and return the first match + * Search the ast subtree and return the first match * * ```ts * // find the first ast declaration node which name is 'aspect-ratio' @@ -46,6 +46,11 @@ export function find(ast: AstNode, matcher: (node: AstNode, parent?: AstNode | n } /** + * + * @param ast + * @param matcher + * @returns + * * Search the ast sub-tree by checking each node's value token and return the first match * ```ts @@ -71,10 +76,6 @@ button { console.log({node, value}); ``` - * - * @param ast - * @param matcher - * @returns */ export function findByValue( ast: AstNode, @@ -238,7 +239,7 @@ export function findValue( (ast.typ === EnumToken.StyleSheetNodeType || ast.typ === EnumToken.RuleNodeType || ast.typ === EnumToken.AtRuleNodeType || - ast.typ === EnumToken.KeyFramesRuleNodeType || + ast.typ === EnumToken.KeyframesRuleNodeType || ast.typ === EnumToken.KeyframesAtRuleNodeType) ) { if (Array.isArray(ast[TOKENS])) { diff --git a/src/lib/ast/minify.ts b/src/lib/ast/minify.ts index 26c93106..00b4acd3 100644 --- a/src/lib/ast/minify.ts +++ b/src/lib/ast/minify.ts @@ -1,12 +1,12 @@ -import { eq } from "../parser/utils/eq.ts"; -import { doRender, renderValue } from "../renderer/render.ts"; +import {eq} from "../parser/utils/eq.ts"; +import {doRender, renderValue} from "../renderer/render.ts"; import * as allFeatures from "./features/index.ts"; -import { walkValues } from "./walk.ts"; +import {walkValues} from "./walk.ts"; import type { AstAtRule, AstDeclaration, - AstKeyFrameRule, AstKeyframesAtRule, + AstKeyframesRule, AstNode, AstRule, AstStyleSheet, @@ -24,22 +24,22 @@ import type { RawSelectorTokens, Token, } from "../../@types/index.d.ts"; -import { EnumToken } from "./types.ts"; -import { isFunction, isIdent, isIdentStart, isWhiteSpace } from "../syntax/syntax.ts"; -import { FeatureWalkMode } from "./features/type.ts"; -import { trimArray } from "../validation/match.ts"; -import { combinators, LOC, OPTIMIZED, PARENT, RAW, TOKENS } from "../syntax/constants.ts"; -import { replaceNodeOrValue } from "../parser/utils/token.ts"; -import { parseString } from "../parser/parse.ts"; -import { tokenize } from "../parser/tokenize.ts"; -import { replaceCompound } from "./expand.ts"; +import {EnumToken} from "./types.ts"; +import {isFunction, isIdent, isIdentStart, isWhiteSpace} from "../syntax/syntax.ts"; +import {FeatureWalkMode} from "./features/type.ts"; +import {trimArray} from "../validation/match.ts"; +import {combinators, LOC, OPTIMIZED, PARENT, RAW, TOKENS} from "../syntax/constants.ts"; +import {replaceNodeOrValue} from "../parser/utils/token.ts"; +import {parseString} from "../parser/parse.ts"; +import {tokenize} from "../parser/tokenize.ts"; +import {replaceCompound} from "./expand.ts"; const notEndingWith: string[] = ["(", "["].concat(combinators); const rules: EnumToken[] = [ EnumToken.AtRuleNodeType, EnumToken.RuleNodeType, EnumToken.AtRuleTokenType, - EnumToken.KeyFramesRuleNodeType, + EnumToken.KeyframesRuleNodeType, ]; // @ts-ignore const features: MinifyFeature[] = Object.values(allFeatures as Record).sort( @@ -72,6 +72,7 @@ export function minify( * @param errors * @param nestingContent * + * @param context * @private */ export function minify( @@ -89,25 +90,28 @@ export function minify( let parents: Set; let replacement: AstNode | null; - if (!("features" in options)) { + // @ts-ignore + let { sourcemap, module, ...options2 } = options; + + if (!("features" in options2)) { // @ts-ignore - options = { + options2 = { removeDuplicateDeclarations: true, computeShorthand: true, computeCalcExpression: true, removePrefix: false, features: [], - ...options, + ...options2, }; for (const feature of features) { - feature.register(options); + feature.register(options2); } - options.features!.sort((a: MinifyFeature, b: MinifyFeature): number => a.ordering - b.ordering); + options2.features!.sort((a: MinifyFeature, b: MinifyFeature): number => a.ordering - b.ordering); } - for (const feature of options!.features as MinifyFeature[]) { + for (const feature of options2!.features as MinifyFeature[]) { if (feature.processMode & FeatureWalkMode.Pre) { preprocess = true; } @@ -127,7 +131,7 @@ export function minify( replacement = parent; - for (const feature of options.features as MinifyFeature[]) { + for (const feature of options2.features as MinifyFeature[]) { if ( (feature.processMode & FeatureWalkMode.Pre) === 0 || (feature.accept != null && !feature.accept.has(parent.typ)) @@ -137,7 +141,7 @@ export function minify( if (rules.includes(replacement.typ) && !Array.isArray(replacement[TOKENS])) { replacement[TOKENS] = parseString( - replacement.typ == EnumToken.RuleNodeType || replacement.typ === EnumToken.KeyFramesRuleNodeType + replacement.typ == EnumToken.RuleNodeType || replacement.typ === EnumToken.KeyframesRuleNodeType ? replacement.sel : replacement.nam, ); @@ -145,7 +149,7 @@ export function minify( const result = feature.run( replacement, - options, + options2, parent[PARENT] ?? ast, context, FeatureWalkMode.Pre, @@ -174,15 +178,15 @@ export function minify( } } - for (const feature of options.features as MinifyFeature[]) { + for (const feature of options2.features as MinifyFeature[]) { if (feature.processMode & FeatureWalkMode.Pre && "cleanup" in feature) { // @ts-ignore - feature.cleanup(ast, options, context, FeatureWalkMode.Pre); + feature.cleanup(ast, options2, context, FeatureWalkMode.Pre); } } } - doMinify(ast, options, recursive, errors, nestingContent, context); + doMinify(ast, options2, recursive, errors, nestingContent, context); parents = new Set([ast]); @@ -194,7 +198,7 @@ export function minify( replacement = parent; if (postprocess) { - for (const feature of options.features as MinifyFeature[]) { + for (const feature of options2.features as MinifyFeature[]) { if ( (feature.processMode & FeatureWalkMode.Post) === 0 || (feature.accept != null && !feature.accept.has(parent.typ)) @@ -204,8 +208,8 @@ export function minify( const result = feature.run( replacement as AstRule | AstAtRule, - options, - parent[PARENT] ?? ast, + options2, + parent[PARENT] ?? (ast as AstRule | AstAtRule | AstStyleSheet), context, FeatureWalkMode.Post, ); @@ -235,10 +239,10 @@ export function minify( } if (postprocess) { - for (const feature of options.features as MinifyFeature[]) { + for (const feature of options2.features as MinifyFeature[]) { if (feature.processMode & FeatureWalkMode.Post && "cleanup" in feature) { // @ts-ignore - feature.cleanup(ast, options, context, FeatureWalkMode.Post); + feature.cleanup(ast, options2, context, FeatureWalkMode.Post); } } } @@ -357,9 +361,9 @@ function transformAtRuleMediaPrelude(values: Token[]) { * Minify at-rule media * - remove redundant tokens * - generate range queries - * @param ast * * @private + * @param tokens */ function minifyAtRuleMedia(tokens: Token[]): Token[] { let hasUpdates: boolean = false; @@ -496,7 +500,6 @@ function doMinify( while (previous?.typ === EnumToken.CommentNodeType) { previous = ast.chi[--nodeIndex]; - continue; } node = ast.chi![i] as AstNode; @@ -517,14 +520,14 @@ function doMinify( continue; } - } else if (node.typ === EnumToken.KeyFramesRuleNodeType) { + } else if (node.typ === EnumToken.KeyframesRuleNodeType) { if ( - previous?.typ === EnumToken.KeyFramesRuleNodeType && - (node).sel === (previous).sel + previous?.typ === EnumToken.KeyframesRuleNodeType && + (node).sel === (previous).sel ) { // do not merge keyframes // https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/At-rules/@keyframes#resolving_duplicates - (previous).chi.push(...(node).chi); + (previous).chi.push(...(node).chi); ast.chi.splice(i, 1); previous = (ast?.chi?.[nodeIndex] as AstNode) ?? null; @@ -535,22 +538,22 @@ function doMinify( let k: number; - for (k = 0; k < (node as AstKeyFrameRule).chi.length; k++) { - if ((node as AstKeyFrameRule).chi[k].typ == EnumToken.DeclarationNodeType) { - let l: number = ((node as AstKeyFrameRule).chi[k] as AstDeclaration).val.length; + for (k = 0; k < (node as AstKeyframesRule).chi.length; k++) { + if ((node as AstKeyframesRule).chi[k].typ == EnumToken.DeclarationNodeType) { + let l: number = ((node as AstKeyframesRule).chi[k] as AstDeclaration).val.length; while (l--) { if ( - ((node as AstKeyFrameRule).chi[k] as AstDeclaration).val[l].typ == + ((node as AstKeyframesRule).chi[k] as AstDeclaration).val[l].typ == EnumToken.ImportantTokenType ) { - (node as AstKeyFrameRule).chi.splice(k--, 1); + (node as AstKeyframesRule).chi.splice(k--, 1); break; } if ( [EnumToken.WhitespaceTokenType, EnumToken.CommentTokenType].includes( - ((node as AstKeyFrameRule).chi[k] as AstDeclaration).val[l].typ, + ((node as AstKeyframesRule).chi[k] as AstDeclaration).val[l].typ, ) ) { continue; @@ -902,7 +905,7 @@ function doMinify( if (shouldMerge) { if ( ((node.typ === EnumToken.RuleNodeType || - node.typ === EnumToken.KeyFramesRuleNodeType) && + node.typ === EnumToken.KeyframesRuleNodeType) && (node as AstRule).sel === (previous as AstRule).sel) || (node.typ == EnumToken.AtRuleNodeType && (node as AstAtRule).nam !== "font-face" && @@ -920,7 +923,7 @@ function doMinify( continue; } else if ( node.typ == previous?.typ && - [EnumToken.KeyFramesRuleNodeType, EnumToken.RuleNodeType].includes(node.typ) + [EnumToken.KeyframesRuleNodeType, EnumToken.RuleNodeType].includes(node.typ) ) { const intersect = diff(previous as AstRule, node as AstRule, options); @@ -1107,7 +1110,9 @@ export function optimizeSelector(selector: string[][]): OptimizedSelector | null break; } - selector.forEach((selector: string[]) => selector.splice(0, optimized.length)); + for (let i1 = 0; i1 < selector.length; i1++) { + selector[i1].splice(0, optimized.length); + } let reducible: boolean = optimized.length == 1; @@ -1591,7 +1596,6 @@ function wrapNodes( * Diff nodes * @param n1 * @param n2 - * @param reducer * @param options * * @private @@ -1744,20 +1748,47 @@ function diff(n1: AstRule, n2: AstRule, options: ParserOptions = {}) { chi: intersect.reverse(), }; + let op = { level: 0, ...options }; + if ( result == null || [n1, n2].reduce((acc: number, curr: AstRule): number => { let css: string = options.cache!.get(curr) as string; if (css == null) { - css = doRender(curr, options).code; + let level: number = 0; + let parent: AstNode | null = curr[PARENT]; + + while (parent != null && parent.typ != EnumToken.StyleSheetNodeType) { + level++; + parent = parent[PARENT] as AstRule; + } + + op.level = level; + + css = doRender(curr, op).code; options.cache!.set(curr, css); } return curr.chi.length == 0 ? acc : acc + css.length; }, 0) <= [node1, node2, result].reduce((acc: number, curr: AstRule): number => { - const css = doRender(curr, options).code; + let css: string = options.cache!.get(curr) as string; + + if (css != null) { + return curr.chi.length == 0 ? acc : acc + css.length; + } + + let level: number = 0; + let parent: AstNode | null = curr[PARENT]; + + while (parent != null && parent.typ != EnumToken.StyleSheetNodeType) { + level++; + parent = parent[PARENT] as AstRule; + } + + op.level = level; + css = doRender(curr, op).code; return curr.chi.length == 0 ? acc : acc + css.length; }, 0) diff --git a/src/lib/ast/types.ts b/src/lib/ast/types.ts index 200c50a9..8e9d4c6c 100644 --- a/src/lib/ast/types.ts +++ b/src/lib/ast/types.ts @@ -386,7 +386,7 @@ export enum EnumToken { /** * keyframe rule node type */ - KeyFramesRuleNodeType, + KeyframesRuleNodeType, /** * class selector token type */ diff --git a/src/lib/ast/walk.ts b/src/lib/ast/walk.ts index e656fdba..b8e3bc52 100644 --- a/src/lib/ast/walk.ts +++ b/src/lib/ast/walk.ts @@ -8,9 +8,11 @@ import type { WalkAttributesResult, WalkerFilter, WalkerOption, + WalkerOptions, WalkerValueFilter, WalkResult, } from "../../@types/index.d.ts"; +import { TOKENS } from "../syntax/constants.ts"; import { EnumToken } from "./types.ts"; /** @@ -108,6 +110,157 @@ export enum WalkerEvent { * } * * const result = await transform(css); + * for (const {node} of walk(result.ast, filter, false)) { + * + * console.error([EnumToken[node.typ]]); + * } + * + * // [ "StyleSheetNodeType" ] + * // [ "RuleNodeType" ] + * // [ "DeclarationNodeType" ] + * // [ "RuleNodeType" ] + * // [ "DeclarationNodeType" ] + * // [ "RuleNodeType" ] + * // [ "DeclarationNodeType" ] + * ``` + */ +export function walk(node: AstNode, filter?: WalkerFilter | null, reverse?: boolean): Generator; + +/** + * Walk ast nodes + * @param node initial node + * @param filter control the walk process + * + * ```ts + * + * import {walk} from '@tbela99/css-parser'; + * + * const css = ` + * body { color: color(from var(--base-color) display-p3 r calc(g + 0.24) calc(b + 0.15)); } + * + * html, + * body { + * line-height: 1.474; + * } + * + * .ruler { + * + * height: 10px; + * } + * `; + * + * for (const {node, parent, root} of walk(ast)) { + * + * // do something with node + * } + * ``` + * + * Using a {@link filter} function to control the ast traversal. the filter function returns a value of type {@link WalkerOption}. + * + * ```ts + * import {EnumToken, transform, walk, WalkerOptionEnum} from '@tbela99/css-parser'; + * + * const css = ` + * body { color: color(from var(--base-color) display-p3 r calc(g + 0.24) calc(b + 0.15)); } + * + * html, + * body { + * line-height: 1.474; + * } + * + * .ruler { + * + * height: 10px; + * } + * `; + * + * function filter(node) { + * + * if (node.typ == EnumToken.AstRule && node.sel.includes('html')) { + * + * // skip the children of the current node + * return WalkerOptionEnum.IgnoreChildren; + * } + * } + * + * const result = await transform(css); + * for (const {node} of walk(result.ast, {filter, reverse: false})) { + * + * console.error([EnumToken[node.typ]]); + * } + * + * // [ "StyleSheetNodeType" ] + * // [ "RuleNodeType" ] + * // [ "DeclarationNodeType" ] + * // [ "RuleNodeType" ] + * // [ "DeclarationNodeType" ] + * // [ "RuleNodeType" ] + * // [ "DeclarationNodeType" ] + * ``` + */ +export function walk(node: AstNode, filter?: WalkerOptions | null): Generator; + +/** + * Walk ast nodes + * @param node initial node + * @param filter control the walk process + * @param reverse walk in reverse order + * + * @private + * + * ```ts + * + * import {walk} from '@tbela99/css-parser'; + * + * const css = ` + * body { color: color(from var(--base-color) display-p3 r calc(g + 0.24) calc(b + 0.15)); } + * + * html, + * body { + * line-height: 1.474; + * } + * + * .ruler { + * + * height: 10px; + * } + * `; + * + * for (const {node, parent, root} of walk(ast)) { + * + * // do something with node + * } + * ``` + * + * Using a {@link filter} function to control the ast traversal. the filter function returns a value of type {@link WalkerOption}. + * + * ```ts + * import {EnumToken, transform, walk, WalkerOptionEnum} from '@tbela99/css-parser'; + * + * const css = ` + * body { color: color(from var(--base-color) display-p3 r calc(g + 0.24) calc(b + 0.15)); } + * + * html, + * body { + * line-height: 1.474; + * } + * + * .ruler { + * + * height: 10px; + * } + * `; + * + * function filter(node) { + * + * if (node.typ == EnumToken.AstRule && node.sel.includes('html')) { + * + * // skip the children of the current node + * return WalkerOptionEnum.IgnoreChildren; + * } + * } + * + * const result = await transform(css); * for (const {node} of walk(result.ast, filter)) { * * console.error([EnumToken[node.typ]]); @@ -122,18 +275,31 @@ export enum WalkerEvent { * // [ "DeclarationNodeType" ] * ``` */ -export function* walk(node: AstNode, filter?: WalkerFilter | null, reverse?: boolean): Generator { +export function* walk( + node: AstNode, + filter?: WalkerFilter | WalkerOptions | null, + reverse?: boolean, +): Generator { const parents: AstNode[] = [node]; const root: AstRuleList = node; const map: Map = new Map(); + let options: WalkerOptions | null = filter as WalkerOptions | null; let isNumeric: boolean = false; + let includeValues: boolean = false; let i: number = 0; + if (options != null && typeof options == "object") { + filter = options.filter as WalkerFilter; + reverse = options.reverse; + includeValues = options.inludeValues as boolean; + } + while ((node = parents[i++])) { let option: WalkerOption = null; if (filter != null) { + // @ts-ignore option = filter(node); isNumeric = typeof option == "number"; @@ -166,8 +332,16 @@ export function* walk(node: AstNode, filter?: WalkerFilter | null, reverse?: boo }; } - if ("chi" in node && (!isNumeric || ((option as number) & WalkerOptionEnum.IgnoreChildren) === 0)) { - parents.splice(i, 0, ...((node).chi![reverse ? "toReversed" : "slice"]())); + if (includeValues) { + if (node[TOKENS] != null) { + parents.splice(i, 0, ...(reverse ? node[TOKENS]!.toReversed() : node[TOKENS])); + } else if (Array.isArray(node.val)) { + parents.splice(i, 0, ...(reverse ? node.val.toReversed() : node.val)); + } + } + + if (node["chi"] != null && (!isNumeric || ((option as number) & WalkerOptionEnum.IgnoreChildren) === 0)) { + parents.splice(i, 0, ...(reverse ? node.chi!.toReversed() : node.chi)); for (const child of (node).chi) { map.set(child, node); @@ -270,12 +444,6 @@ export function* walkValues( } used.add(value); - // parents.length = 0; - - // while (node != null) { - // parents.push(node); - // node = map.get(node) ?? null; - // } if (filter.fn != null && eventType & WalkerEvent.Enter) { const isValid: boolean = diff --git a/src/lib/fs/resolve.ts b/src/lib/fs/resolve.ts index e39004d8..057e0874 100644 --- a/src/lib/fs/resolve.ts +++ b/src/lib/fs/resolve.ts @@ -1,5 +1,8 @@ import { memoize } from "../parser/utils/cache.ts"; +/** + * match url + */ export const matchUrl: RegExp = /^(https?:)?\/\//; /** @@ -13,6 +16,10 @@ export function dirname(path: string): string { return ""; } + if (path.startsWith("data:")) { + return path; + } + let i: number = 0; let parts: string[] = [""]; @@ -42,11 +49,7 @@ function splitPath(result: string): { i: number; parts: string[] } { return { parts: [], i: 0 }; } - // if (result === "/") { - // return { parts: ["/"], i: 0 }; - // } - - const parts: string[] = [""]; + const parts: string[] = result == "/" ? [] : [""]; let i: number = 0; for (; i < result.length; i++) { @@ -54,10 +57,10 @@ function splitPath(result: string): { i: number; parts: string[] } { if (chr == "/") { parts.push(""); - } + } // else if (chr == "?" || chr == "#") { // break; - // } + // } else { parts[parts.length - 1] += chr; } @@ -79,6 +82,8 @@ function splitPath(result: string): { i: number; parts: string[] } { /** * Nomalize path + * @param path + * @private */ export const normalize = memoize(function (path: string) { let parts: string[] = []; @@ -111,8 +116,8 @@ export const normalize = memoize(function (path: string) { while (++k < parts.length) { // if (parts[k] == ".") { // parts.splice(k--, 1); - // } else - if (parts[k] == "..") { + // } else + if (k > 0 && parts[k] == "..") { parts.splice(k - 1, 2); k -= 2; } @@ -121,9 +126,16 @@ export const normalize = memoize(function (path: string) { return (path.charAt(0) == "/" ? "/" : "") + parts.join("/"); }); +/** + * diff path + * @param path1 + * @param path2 + * @private + */ export const diff = memoize(function (path1: string, path2: string) { let { parts } = splitPath(path1); const { parts: dirs } = splitPath(path2); + for (const p of dirs) { if (parts[0] == p) { parts.shift(); @@ -148,11 +160,6 @@ export const resolve = memoize(function ( currentDirectory: string, cwd?: string, ): { absolute: string; relative: string } { - - - cwd ??= ""; - currentDirectory ??= ""; - if (matchUrl.test(url)) { return { absolute: url, @@ -160,40 +167,61 @@ export const resolve = memoize(function ( }; } + cwd ??= ""; + currentDirectory ??= ""; + url = normalize(url); - + + if (cwd !== "") { + cwd = normalize(cwd); + } if (currentDirectory !== "") { currentDirectory = normalize(currentDirectory); - - if (url.startsWith(currentDirectory + "/")) { - return { - absolute: url, - relative: url.slice(currentDirectory.length + 1), - }; - } } - if ((currentDirectory === "" || currentDirectory === ".") && cwd !== "") { - cwd = normalize(cwd); + const dir = cwd || currentDirectory; + const absolute = dir == "" || url.startsWith("/") ? resolvePath(url) : resolvePath(dir, url); - if (url.startsWith(cwd == "/" ? cwd : cwd + "/")) { - const absolute: string = url; - const prefix: string = cwd == "/" ? cwd : cwd + "/"; + return { + absolute, + relative: dir === "" ? absolute : diff(absolute, dir), + }; +}) as (url: string, currentDirectory?: string, cwd?: string) => { absolute: string; relative: string }; + +/** + * + * @param parts + * @returns + * @private + */ +function resolvePath(...parts: string[]): string { + const path = parts.filter(Boolean).join("/"); + const isAbsolute: boolean = /^[\\/]/.test(path); + const segments: string[] = path.split(/[\\/]+/); + const resolved: string[] = []; + + for (const segment of segments) { + if (!segment || segment === ".") { + continue; + } - return { - absolute, - relative: absolute.startsWith(prefix) ? absolute.slice(prefix.length) : diff(absolute, cwd), - }; + if (segment === "..") { + if (resolved.length && resolved[resolved.length - 1] !== "..") { + resolved.pop(); + } else if (!isAbsolute) { + resolved.push(".."); + } + } else { + resolved.push(segment); } } - return { - absolute: url, - relative: url === "" ? "" : diff(url, cwd || currentDirectory), - }; -}) as ( - url: string, - currentDirectory?: string, - cwd?: string, -) => { absolute: string; relative: string }; + let result = resolved.join("/"); + + if (isAbsolute) { + result = "/" + result; + } + + return result || (isAbsolute ? "/" : "."); +} diff --git a/src/lib/parser/declaration/map.ts b/src/lib/parser/declaration/map.ts index e6140a44..23f02650 100644 --- a/src/lib/parser/declaration/map.ts +++ b/src/lib/parser/declaration/map.ts @@ -382,16 +382,25 @@ export class PropertyMap { ); let isImportant: boolean = false; - const filtered: AstDeclaration[] = values.map(removeDefaults).filter( - (x: AstDeclaration): boolean => - x.val.filter((t: Token) => { - if (t.typ == EnumToken.ImportantTokenType) { - isImportant = true; - } + let dec: AstDeclaration; - return ![EnumToken.WhitespaceTokenType, EnumToken.ImportantTokenType].includes(t.typ); - }).length > 0, - ); + const filtered: AstDeclaration[] = []; + + for (const declaration of values) { + + dec = removeDefaults(declaration); + + for (const t of dec.val) { + if (t.typ == EnumToken.ImportantTokenType) { + isImportant = true; + } + + if (filtered.length == 0 && t.typ != EnumToken.WhitespaceTokenType && t.typ != EnumToken.ImportantTokenType) { + + filtered.push(dec); + } + } + } if (filtered.length == 0 && this.config.default.length > 0) { filtered.push({ diff --git a/src/lib/parser/linesmap.ts b/src/lib/parser/linesmap.ts index c59070b6..9356d0ea 100644 --- a/src/lib/parser/linesmap.ts +++ b/src/lib/parser/linesmap.ts @@ -11,7 +11,7 @@ export class LineMap { * Constructor * @param lines */ - constructor(lines: number[]) { + constructor(lines: number[] = []) { if (lines.length === 0) { lines.push(0); } @@ -31,10 +31,9 @@ export class LineMap { return [1, 1]; } - const column: number = offset - this.lineStarts[line]; - + const column: number = offset - this.lineStarts[line] + 1; // [line, column] - return [line + 1, column === 0 ? 1 : column]; + return [line + 1, column == 0 ? 1 : column]; } /** diff --git a/src/lib/parser/parse.ts b/src/lib/parser/parse.ts index d62791a9..c8f5d95f 100644 --- a/src/lib/parser/parse.ts +++ b/src/lib/parser/parse.ts @@ -10,7 +10,6 @@ import type { AstAtRule, AstComment, AstDeclaration, - AstKeyFrameRule, AstKeyframesAtRule, AstKeyframesRule, AstNode, @@ -28,24 +27,26 @@ import type { ErrorDescription, FunctionToken, GenericVisitorAstNodeHandlerMap, + GenericVisitorAstNodeSyncHandlerMap, GenericVisitorHandler, + GenericVisitorResult, IdentToken, LoadResult, - SourceLocation, ModuleSyncOptions, ParseInfo, ParseResult, ParseResultStats, ParserOptions, + ParserSyncOptions, PseudoClassToken, ResolvedPath, + SourceLocation, StringToken, Token, TokenizeResult, UrlToken, + VisitorNodeMap, WhitespaceToken, - GenericVisitorResult, - ParserSyncOptions, } from "../../@types/index.d.ts"; import { ERRORS, LOC, pageMarginBoxType, PARENT, ROOT, STATE, TOKENS, tokensfuncDefMap } from "../syntax/constants.ts"; import { hash, hashAlgorithms, syncHash } from "../parser/utils/hash.ts"; @@ -67,6 +68,7 @@ import { parseAtRuleFontFeatureValues } from "./utils/at-rule-font-feature-value import { matchGenericSyntax } from "./utils/at-rule-generic.ts"; import { memoize } from "./utils/cache.ts"; import { SourceFile } from "./source.ts"; +import { dirname } from "../fs/resolve.ts"; function renderTokens(tokens: Token[] | null | undefined, options?: any): string { if (tokens == null || tokens.length === 0) return ""; @@ -90,9 +92,7 @@ const BadTokensTypes: EnumToken[] = [ EnumToken.BadUrlTokenType, EnumToken.BadStringTokenType, ]; - -export const atRulesMap: Map = new Map([["keyframes", EnumToken.KeyframesAtRuleNodeType]]); - +new Map([["keyframes", EnumToken.KeyframesAtRuleNodeType]]); let keyNameCounter: number = 0; const forbiddenStartCharacters: number[] = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"].map((c) => c.charCodeAt(0), @@ -110,8 +110,6 @@ const forbiddenStartCharacters: number[] = ["0", "1", "2", "3", "4", "5", "6", " */ export const getShortNameGenerator = memoize( (localName: string, filePath: string, pattern: string, hashLength = 5): string => { - const key = `${localName}_${filePath}_${pattern}_${hashLength}`; - let value: string = keyNameCounter!.toString(36); keyNameCounter!++; @@ -416,6 +414,200 @@ export const generateSyncScopedName = memoize( }, ) as (localName: string, filePath: string, pattern: string, hashLength?: number) => string; +/** + * + * @param visitorsDef + * @param errors + * @private + */ +function parseVisitors( + visitorsDef: GenericVisitorHandler | GenericVisitorAstNodeSyncHandlerMap | VisitorNodeMap | VisitorNodeMap[], + errors: ErrorDescription[], +) { + const visitors = Object.entries(typeof visitorsDef === "function" ? [visitorsDef] : visitorsDef); + let key: string; + let value: any; + let i: number; + + const valuesHandlers: Map>> = new Map(); + const preValuesHandlers: Map>> = new Map(); + const postValuesHandlers: Map>> = new Map(); + const visitorsHandlersMap: Map< + "Declaration" | "Rule" | "AtRule" | "KeyframesRule" | "KeyframesAtRule", + Array | Record>> + > = new Map(); + const preVisitorsHandlersMap: Map< + "Declaration" | "Rule" | "AtRule" | "KeyframesRule" | "KeyframesAtRule", + Array | Record>>> + > = new Map(); + const postVisitorsHandlersMap: Map< + "Declaration" | "Rule" | "AtRule" | "KeyframesRule" | "KeyframesAtRule", + Array | Record>>> + > = new Map(); + + for (i = 0; i < visitors.length; i++) { + key = visitors[i][0]; + value = visitors[i][1]; + + if (Number.isInteger(+key)) { + if (Array.isArray(value)) { + visitors.splice(i + 1, 0, ...Object.entries(value)); + continue; + } + + if (typeof value == "function") { + key = value.name; + } + } + + if (Array.isArray(value)) { + // @ts-ignore + visitors.splice(i + 1, 0, ...value.map((item) => [key, item])); + continue; + } + + if (key in EnumToken) { + if (typeof value == "function") { + if (!valuesHandlers.has(EnumToken[key as keyof typeof EnumToken] as EnumToken)) { + valuesHandlers.set(EnumToken[key as keyof typeof EnumToken] as EnumToken, []); + } + + valuesHandlers.get(EnumToken[key as keyof typeof EnumToken] as EnumToken)!.push(value); + } else if (typeof value == "object" && "type" in value && "handler" in value && value.type in WalkerEvent) { + if (value.type == WalkerEvent.Enter) { + if (!preValuesHandlers.has(EnumToken[key as keyof typeof EnumToken] as EnumToken)) { + preValuesHandlers.set(EnumToken[key as keyof typeof EnumToken] as EnumToken, []); + } + + preValuesHandlers.get(EnumToken[key as keyof typeof EnumToken] as EnumToken)!.push(value.handler); + } else if (value.type == WalkerEvent.Leave) { + if (!postValuesHandlers.has(EnumToken[key as keyof typeof EnumToken] as EnumToken)) { + postValuesHandlers.set(EnumToken[key as keyof typeof EnumToken] as EnumToken, []); + } + + postValuesHandlers.get(EnumToken[key as keyof typeof EnumToken] as EnumToken)!.push(value.handler); + } + } else { + errors.push({ action: "ignore", message: `doParse: visitor.${key} is not a valid key name` }); + } + } else if (["Declaration", "Rule", "AtRule", "KeyframesRule", "KeyframesAtRule"].includes(key)) { + if (typeof value == "function") { + if ( + !visitorsHandlersMap.has( + key as "Declaration" | "Rule" | "AtRule" | "KeyframesRule" | "KeyframesAtRule", + ) + ) { + visitorsHandlersMap.set( + key as "Declaration" | "Rule" | "AtRule" | "KeyframesRule" | "KeyframesAtRule", + [], + ); + } + + visitorsHandlersMap + .get(key as "Declaration" | "Rule" | "AtRule" | "KeyframesRule" | "KeyframesAtRule")! + .push(value); + } else if (typeof value == "object") { + if ("type" in value && "handler" in value && value.type in WalkerEvent) { + if (value.type == WalkerEvent.Enter) { + if ( + !preVisitorsHandlersMap.has( + key as "Declaration" | "Rule" | "AtRule" | "KeyframesRule" | "KeyframesAtRule", + ) + ) { + preVisitorsHandlersMap.set( + key as "Declaration" | "Rule" | "AtRule" | "KeyframesRule" | "KeyframesAtRule", + [], + ); + } + + preVisitorsHandlersMap + .get(key as "Declaration" | "Rule" | "AtRule" | "KeyframesRule" | "KeyframesAtRule")! + .push(value.handler); + } else if (value.type == WalkerEvent.Leave) { + if ( + !postVisitorsHandlersMap.has( + key as "Declaration" | "Rule" | "AtRule" | "KeyframesRule" | "KeyframesAtRule", + ) + ) { + postVisitorsHandlersMap.set( + key as "Declaration" | "Rule" | "AtRule" | "KeyframesRule" | "KeyframesAtRule", + [], + ); + } + + postVisitorsHandlersMap + .get(key as "Declaration" | "Rule" | "AtRule" | "KeyframesRule" | "KeyframesAtRule")! + .push(value.handler); + } + } else { + if ( + !visitorsHandlersMap.has( + key as "Declaration" | "Rule" | "AtRule" | "KeyframesRule" | "KeyframesAtRule", + ) + ) { + visitorsHandlersMap.set( + key as "Declaration" | "Rule" | "AtRule" | "KeyframesRule" | "KeyframesAtRule", + [], + ); + } + + visitorsHandlersMap + .get(key as "Declaration" | "Rule" | "AtRule" | "KeyframesRule" | "KeyframesAtRule")! + .push(value); + } + } else { + errors.push({ action: "ignore", message: `doParse: visitor.${key} is not a valid key name` }); + } + } else { + errors.push({ action: "ignore", message: `doParse: visitor.${key} is not a valid key name` }); + } + } + const allHandlers = [] as Array< + | Map< + "Declaration" | "Rule" | "AtRule" | "KeyframesRule" | "KeyframesAtRule", + Array | Record>>> + > + | Map< + "Declaration" | "Rule" | "AtRule" | "KeyframesRule" | "KeyframesAtRule", + Array | Record>> + > + | Map>> + | Map< + EnumToken, + Array | Record>>> + > + >; + + if (preVisitorsHandlersMap!.size > 0) { + allHandlers.push(preVisitorsHandlersMap!); + } + + if (preValuesHandlers!.size > 0) { + allHandlers.push(preValuesHandlers!); + } + + if (visitorsHandlersMap!.size > 0) { + allHandlers.push(visitorsHandlersMap!); + } + + if (valuesHandlers!.size > 0) { + allHandlers.push(valuesHandlers!); + } + + if (postVisitorsHandlersMap!.size > 0) { + allHandlers.push(postVisitorsHandlersMap!); + } + + if (postValuesHandlers!.size > 0) { + allHandlers.push(postValuesHandlers!); + } + + return { + allHandlers, + includeTokens: preValuesHandlers!.size > 0 || valuesHandlers!.size > 0 || postValuesHandlers!.size > 0, + }; +} + /** * Parse css string * @param iter @@ -493,184 +685,28 @@ export function doParseSync( let tokens: Token[] = []; let context: AstRuleList = ast; - ast[ROOT] = ast; - - ast[LOC] = { - sta: 0, - end: 0, - srcId: options.source!.id, - }; - - let valuesHandlers: Map>>; - let preValuesHandlers: Map>>; - let postValuesHandlers: Map>>; - let preVisitorsHandlersMap: Map< - "Declaration" | "Rule" | "AtRule" | "KeyframesRule" | "KeyframesAtRule", - Array | Record>>> - >; - let visitorsHandlersMap: Map< - "Declaration" | "Rule" | "AtRule" | "KeyframesRule" | "KeyframesAtRule", - Array | Record>> - >; - let postVisitorsHandlersMap: Map< - "Declaration" | "Rule" | "AtRule" | "KeyframesRule" | "KeyframesAtRule", - Array | Record>>> - >; - - const imports: AstAtRule[] = []; - let item: TokenizeResult; - let node: AstAtRule | AstRule | AstKeyFrameRule | AstKeyframesAtRule | AstDeclaration | AstComment | null; + let node: AstAtRule | AstRule | AstKeyframesRule | AstKeyframesAtRule | AstDeclaration | AstComment | null; // @ts-ignore ignore error let parensMatch: number = 0; let curlyBracketMatch: number = 0; + let currentItemIndex: number; - if (options.visitor != null) { - valuesHandlers = new Map() as Map>>; - preValuesHandlers = new Map() as Map>>; - postValuesHandlers = new Map() as Map>>; - - preVisitorsHandlersMap = new Map(); - visitorsHandlersMap = new Map(); - postVisitorsHandlersMap = new Map(); - - const visitors = Object.entries(options.visitor); - let key: string; - let value: any; - let i: number; - - for (i = 0; i < visitors.length; i++) { - key = visitors[i][0]; - value = visitors[i][1]; - - if (Number.isInteger(+key)) { - visitors.splice(i + 1, 0, ...Object.entries(value)); - continue; - } - - if (Array.isArray(value)) { - // @ts-ignore - visitors.splice(i + 1, 0, ...value.map((item) => [key, item])); - continue; - } - - if (key in EnumToken) { - if (typeof value == "function") { - if (!valuesHandlers.has(EnumToken[key as keyof typeof EnumToken] as EnumToken)) { - valuesHandlers.set(EnumToken[key as keyof typeof EnumToken] as EnumToken, []); - } - - valuesHandlers.get(EnumToken[key as keyof typeof EnumToken] as EnumToken)!.push(value); - } else if ( - typeof value == "object" && - "type" in value && - "handler" in value && - value.type in WalkerEvent - ) { - if (value.type == WalkerEvent.Enter) { - if (!preValuesHandlers.has(EnumToken[key as keyof typeof EnumToken] as EnumToken)) { - preValuesHandlers.set(EnumToken[key as keyof typeof EnumToken] as EnumToken, []); - } - - preValuesHandlers - .get(EnumToken[key as keyof typeof EnumToken] as EnumToken)! - .push(value.handler); - } else if (value.type == WalkerEvent.Leave) { - if (!postValuesHandlers.has(EnumToken[key as keyof typeof EnumToken] as EnumToken)) { - postValuesHandlers.set(EnumToken[key as keyof typeof EnumToken] as EnumToken, []); - } - - postValuesHandlers - .get(EnumToken[key as keyof typeof EnumToken] as EnumToken)! - .push(value.handler); - } - } else { - errors.push({ action: "ignore", message: `doParse: visitor.${key} is not a valid key name` }); - } - } else if (["Declaration", "Rule", "AtRule", "KeyframesRule", "KeyframesAtRule"].includes(key)) { - if (typeof value == "function") { - if ( - !visitorsHandlersMap.has( - key as "Declaration" | "Rule" | "AtRule" | "KeyframesRule" | "KeyframesAtRule", - ) - ) { - visitorsHandlersMap.set( - key as "Declaration" | "Rule" | "AtRule" | "KeyframesRule" | "KeyframesAtRule", - [], - ); - } - - visitorsHandlersMap - .get(key as "Declaration" | "Rule" | "AtRule" | "KeyframesRule" | "KeyframesAtRule")! - .push(value); - } else if (typeof value == "object") { - if ("type" in value && "handler" in value && value.type in WalkerEvent) { - if (value.type == WalkerEvent.Enter) { - if ( - !preVisitorsHandlersMap.has( - key as "Declaration" | "Rule" | "AtRule" | "KeyframesRule" | "KeyframesAtRule", - ) - ) { - preVisitorsHandlersMap.set( - key as "Declaration" | "Rule" | "AtRule" | "KeyframesRule" | "KeyframesAtRule", - [], - ); - } - - preVisitorsHandlersMap - .get(key as "Declaration" | "Rule" | "AtRule" | "KeyframesRule" | "KeyframesAtRule")! - .push(value.handler); - } else if (value.type == WalkerEvent.Leave) { - if ( - !postVisitorsHandlersMap.has( - key as "Declaration" | "Rule" | "AtRule" | "KeyframesRule" | "KeyframesAtRule", - ) - ) { - postVisitorsHandlersMap.set( - key as "Declaration" | "Rule" | "AtRule" | "KeyframesRule" | "KeyframesAtRule", - [], - ); - } - - postVisitorsHandlersMap - .get(key as "Declaration" | "Rule" | "AtRule" | "KeyframesRule" | "KeyframesAtRule")! - .push(value.handler); - } - } else { - if ( - !visitorsHandlersMap.has( - key as "Declaration" | "Rule" | "AtRule" | "KeyframesRule" | "KeyframesAtRule", - ) - ) { - visitorsHandlersMap.set( - key as "Declaration" | "Rule" | "AtRule" | "KeyframesRule" | "KeyframesAtRule", - [], - ); - } - - visitorsHandlersMap - .get(key as "Declaration" | "Rule" | "AtRule" | "KeyframesRule" | "KeyframesAtRule")! - .push(value); - } - } else { - errors.push({ action: "ignore", message: `doParse: visitor.${key} is not a valid key name` }); - } - } else { - errors.push({ action: "ignore", message: `doParse: visitor.${key} is not a valid key name` }); - } - } - } + // ast[ROOT] = ast; + ast[LOC] = { + sta: 0, + end: 0, + srcId: options.source!.id, + }; - if (Array.isArray(iter)) { - // @ts-expect-error - iter = iter[Symbol.iterator]() as Iterator; - } + // if (Array.isArray(iter)) { + // // @ts-expect-error + // iter = iter[Symbol.iterator]() as Iterator; + // } - while ( - // @ts-expect-error - (item = (iter as Iterator).next().value as TokenizeResult) - ) { + for (currentItemIndex = 0; currentItemIndex < (iter as Array).length; currentItemIndex++) { + item = (iter as Array)[currentItemIndex]; stats.bytesIn = item.bytesIn; stats.tokensCount++; @@ -702,9 +738,6 @@ export function doParseSync( tokens.push(item.token); - // console.debug([item.token, {parensMatch, curlyBracketMatch}]); - - // if (parensMatch === 0) { if ( parensMatch === 0 && (item.token.typ === EnumToken.SemiColonTokenType || @@ -715,18 +748,15 @@ export function doParseSync( if (node != null) { if ("chi" in node) { - stack.push(node as AstAtRule | AstRule | AstKeyFrameRule); + stack.push(node as AstAtRule | AstRule | AstKeyframesRule); context = node as AstRuleList; - } else if (node.typ == EnumToken.AtRuleNodeType && (node as AstAtRule).nam === "import") { - imports.push(node); } } else if (item.token.typ == EnumToken.BlockStartTokenType) { let inBlock: number = 1; tokens = [item.token]; do { - // @ts-expect-error - item = (iter as Iterator).next().value as TokenizeResult; + item = (iter as Array)[++currentItemIndex]; if (item == null) { break; @@ -781,10 +811,6 @@ export function doParseSync( node = parseNode(tokens, context, options as ParserOptions, errors, stats, invalidNodes); if (node != null) { - if (node.typ == EnumToken.AtRuleNodeType && "import" === (node as AstAtRule).val) { - imports.push(node); - } - if ("chi" in node /* && node.typ != EnumToken.InvalidRuleNodeType */) { stack.push(node); context = node as AstRuleList; @@ -799,273 +825,189 @@ export function doParseSync( } let replacement: GenericVisitorResult; - let callable: GenericVisitorHandler; if (options.visitor != null) { - let parens: Token[] | null; - for (const result of walk(ast)) { - parens = null; - - if ( - valuesHandlers!.size > 0 || - preVisitorsHandlersMap!.size > 0 || - visitorsHandlersMap!.size > 0 || - postVisitorsHandlersMap!.size > 0 - ) { - if ( - (result.node.typ == EnumToken.DeclarationNodeType && - (preVisitorsHandlersMap!.has("Declaration") || - visitorsHandlersMap!.has("Declaration") || - postVisitorsHandlersMap!.has("Declaration"))) || - (result.node.typ == EnumToken.AtRuleNodeType && - (preVisitorsHandlersMap!.has("AtRule") || - visitorsHandlersMap!.has("AtRule") || - postVisitorsHandlersMap!.has("AtRule"))) || - (result.node.typ == EnumToken.KeyframesAtRuleNodeType && - (preVisitorsHandlersMap!.has("KeyframesAtRule") || - visitorsHandlersMap!.has("KeyframesAtRule") || - postVisitorsHandlersMap!.has("KeyframesAtRule"))) - ) { - const handlers = [] as Array | Record>>; - const key = - result.node.typ == EnumToken.DeclarationNodeType - ? "Declaration" - : result.node.typ == EnumToken.AtRuleNodeType - ? "AtRule" - : "KeyframesAtRule"; - - if (preVisitorsHandlersMap!.has(key)) { - handlers.push( - // @ts-expect-error - ...(preVisitorsHandlersMap!.get(key)! as - | GenericVisitorHandler - | Record>), - ); - } - - if (visitorsHandlersMap!.has(key)) { - // @ts-ignore - handlers.push(...visitorsHandlersMap.get(key)!); - } - - if (postVisitorsHandlersMap!.has(key)) { - // @ts-ignore - handlers.push(...postVisitorsHandlersMap.get(key)); - } - - let node: AstDeclaration | AstAtRule | AstKeyframesAtRule = result.node as - | AstDeclaration - | AstAtRule - | AstKeyframesAtRule; - - for (const handler of handlers) { - callable = - typeof handler == "function" - ? handler - : (handler[ - camelize( - node.typ === EnumToken.DeclarationNodeType || - node.typ === EnumToken.AtRuleNodeType - ? (node as AstDeclaration | AstAtRule).nam - : (node as AstKeyframesAtRule).val, - ) - ] as GenericVisitorHandler); - - if (callable == null) { - continue; - } + const handlers = [] as Array>; + const visitors = parseVisitors(options.visitor, errors); - // @ts-expect-error - replacement = callable(node, result[PARENT], ast, function* () { - if (parens == null) { - // @ts-expect-error - parens = [...result.parents()]; - } - - yield* parens[Symbol.iterator](); - }); - - if (replacement == null) { - continue; - } - - if (replacement == null || replacement == node) { - continue; - } + const subNodes: Array = []; + let parens: Token[] | null; - // @ts-ignore - node = replacement; + let genericKey: string | null; + let nodes: AstNode[] | null = new Array(stats.tokensCount); + let i: number; + let k: number; + let j: number; + let freeBlock: number = 1; + nodes[0] = ast; - if (Array.isArray(node)) { - break; - } - } + for (i = 0; i < nodes.length; i++) { + if (nodes[i] == null) { + break; + } - if (node != result.node) { - replaceNodeOrValue( - result.parent as AstRule | AstAtRule | AstKeyframesAtRule | AstKeyFrameRule | AstStyleSheet, - result.node, - node, + subNodes.length = 0; + if (visitors.includeTokens) { + switch (nodes[i].typ) { + case EnumToken.RuleNodeType: + case EnumToken.AtRuleNodeType: + case EnumToken.KeyframesRuleNodeType: + case EnumToken.KeyframesAtRuleNodeType: + subNodes.push( + ...(nodes[i] as AstRule | AstAtRule | AstKeyframesRule | AstKeyframesAtRule)[TOKENS]!, ); - } - } else if ( - (result.node.typ == EnumToken.RuleNodeType && - (preVisitorsHandlersMap!.has("Rule") || - visitorsHandlersMap!.has("Rule") || - postVisitorsHandlersMap!.has("Rule"))) || - (result.node.typ == EnumToken.KeyFramesRuleNodeType && - (preVisitorsHandlersMap!.has("KeyframesRule") || - visitorsHandlersMap!.has("KeyframesRule") || - postVisitorsHandlersMap!.has("KeyframesRule"))) - ) { - const handlers = [] as Array< - | GenericVisitorHandler - | { - type: WalkerEvent; - handler: GenericVisitorHandler; - } - >; - const key = result.node.typ == EnumToken.RuleNodeType ? "Rule" : "KeyframesRule"; - - if (preVisitorsHandlersMap!.has(key)) { - handlers.push(...(preVisitorsHandlersMap!.get(key)! as Array>)); - } - - if (visitorsHandlersMap!.has(key)) { - handlers.push(...(visitorsHandlersMap!.get(key)! as Array>)); - } + break; + case EnumToken.DeclarationNodeType: + subNodes.push(...(nodes[i] as AstDeclaration).val); + break; + } + } - if (postVisitorsHandlersMap!.has(key)) { - handlers.push(...(postVisitorsHandlersMap!.get(key)! as Array>)); - } + if (nodes[i].chi != null) { + subNodes.push(...nodes[i].chi); + } - let node = result.node; + if (subNodes.length > 0) { + if (freeBlock <= i) { + freeBlock = i + 1; + } - for (const callable of handlers) { - replacement = (callable as GenericVisitorHandler)( - node as T, - result.parent, - result.root, - // @ts-expect-error - function* () { - if (parens == null) { - // @ts-expect-error - parens = [...result.parents()]; - } + for (k = 0; k < subNodes.length; k++) { + j = k + freeBlock; + nodes[j] = subNodes[k] as AstNode; + nodes[j][PARENT] = nodes[i]; + } - yield* parens[Symbol.iterator](); - }, - ) as GenericVisitorResult; + freeBlock += subNodes.length; + } - if (replacement == null) { - continue; - } + parens = null; + handlers.length = 0; + + genericKey = + nodes[i].typ == EnumToken.DeclarationNodeType + ? "Declaration" + : nodes[i].typ == EnumToken.AtRuleNodeType + ? "AtRule" + : nodes[i].typ == EnumToken.KeyframesAtRuleNodeType + ? "KeyframesAtRule" + : nodes[i].typ === EnumToken.KeyframesRuleNodeType + ? "KeyframesRule" + : nodes[i].typ == EnumToken.RuleNodeType + ? "Rule" + : nodes[i].typ == EnumToken.KeyframesRuleNodeType + ? "KeyframesRule" + : null; + let keyName: string | null = + nodes[i].typ == EnumToken.DeclarationNodeType || nodes[i].typ == EnumToken.AtRuleNodeType + ? camelize((nodes[i] as AstDeclaration | AstAtRule).nam) + : nodes[i].typ == EnumToken.KeyframesAtRuleNodeType + ? camelize((nodes[i] as AstKeyframesAtRule).val) + : null; + + for (const map of visitors.allHandlers) { + // @ts-ignore + if (genericKey != null && map!.has(genericKey)) { + // @ts-ignore + for (const handler of map!.get(genericKey)!) { + if (typeof handler == "function") { + handlers.push(handler as GenericVisitorHandler); + } else if (Array.isArray(handler)) { + for (const h of handler) { + if (typeof h == "function") { + handlers.push(h); + } - if (replacement == null || replacement == node) { - continue; + // @ts-ignore + else if (h[keyName] != null) { + // @ts-ignore + handlers.push(h[keyName]); + } + } + } else if (typeof handler.handler! == "function") { + handlers.push(handler.handler); } // @ts-ignore - node = replacement as AstNode; - - // - if (Array.isArray(node)) { - break; + else if (typeof handler[keyName]! == "function") { + // @ts-ignore + handlers.push(handler[keyName]); } } + } + // @ts-ignore + if (map!.has(nodes[i].typ)) { // @ts-ignore - if (node != result.node) { - // @ts-ignore - replaceNodeOrValue(result.parent, result.node, node); - } - } else if (valuesHandlers!.size > 0) { - let node: Token | AstNode | null = null; - - node = result.node; - - if (valuesHandlers!.has(node.typ)) { - for (const valueHandler of valuesHandlers!.get(node.typ)!) { - callable = valueHandler as GenericVisitorHandler; - replacement = callable( - node as T, - result.parent, - ast, - // @ts-expect-error - function* () { - if (parens == null) { - // @ts-expect-error - parens = [...result.parents()]; - } - - yield* parens[Symbol.iterator](); - }, - ); - - if (replacement == null) { - continue; - } - - if (replacement != null && replacement != node) { - node = replacement as AstNode; + for (const handler of map!.get(nodes[i].typ)!) { + if (typeof handler == "function") { + handlers.push(handler as GenericVisitorHandler); + } else if (Array.isArray(handler)) { + for (const h of handler) { + if (typeof h == "function") { + handlers.push(h); + } } + } else if (typeof handler.handler! == "function") { + handlers.push(handler.handler); } - } - if (node != result.node) { // @ts-ignore - replaceNodeOrValue(result[PARENT], value, node); - } - - const tokens: Token[] = Array.isArray(result.node[TOKENS]) ? (result.node[TOKENS] as Token[]) : []; - - if (Array.isArray(result.node.val)) { - tokens.push(...(result.node.val as Token[])); + else if (typeof handler[keyName]! == "function") { + // @ts-ignore + handlers.push(handler[keyName]); + } } + } + } - if (tokens.length == 0) { - continue; - } + if (handlers.length == 0) { + continue; + } - for (const { value, parent, root, parents } of walkValues(tokens, result.node)) { - node = value; + let node = nodes[i]; - if (valuesHandlers!.has(node!.typ)) { - let parens: Token[] | null = null; - for (const valueHandler of valuesHandlers!.get(node!.typ)!) { - callable = valueHandler as GenericVisitorHandler; - // @ts-expect-error - let result: GenericVisitorResult = callable(node as T, parent, root, function* () { - if (parens == null) { - // @ts-expect-error - parens = [...parents()]; - } + for (const callable of handlers) { + replacement = (callable as GenericVisitorHandler)( + node as T, + nodes[i][PARENT] as AstNode, + ast as AstStyleSheet, + // @ts-expect-error + function* () { + if (parens == null) { + let node = nodes![i][PARENT] as AstNode; - yield* parens[Symbol.iterator](); - }); + while (node != null) { + yield node; + node = node[PARENT] as AstNode; + } + } + }, + ) as GenericVisitorResult; - if (result == null) { - continue; - } + if (replacement == null) { + continue; + } - if (result != null && result != node) { - node = result as Token; - } + if (replacement == node) { + continue; + } - if (Array.isArray(node)) { - break; - } - } - } + // @ts-ignore + node = replacement as AstNode; - if (node != value) { - // @ts-ignore - replaceNodeOrValue(parent, value, node); - } - } + // + if (Array.isArray(node)) { + break; } } + + if (node != nodes[i]) { + replaceNodeOrValue(nodes[i][PARENT], nodes[i], node); + } } + + nodes = null; } if (invalidNodes.length > 0) { @@ -1092,25 +1034,7 @@ export function doParseSync( break; } } - } - } - - while (stack.length > 0 && context != ast) { - const previousNode: AstAtRule | AstRule = stack.pop() as AstAtRule | AstRule; - context = (stack[stack.length - 1] ?? ast) as AstRuleList; - - // remove empty nodes - if ( - options.removeEmpty && - previousNode != null && - previousNode.chi!.length == 0 && - context.chi![context.chi!.length - 1] == previousNode - ) { - context.chi!.pop(); - continue; - } - - break; + } } if (options.minify) { @@ -1145,7 +1069,7 @@ export function doParseSync( scoped: ModuleScopeEnumOptions.Local, naming: ModuleCaseTransformEnum.IgnoreCase, pattern: "", - generateScopedName, + generateScopedName: generateSyncScopedName, ...(typeof options.module != "object" ? {} : options.module), } as ModuleSyncOptions; @@ -1273,7 +1197,7 @@ export function doParseSync( if (node.typ == EnumToken.DeclarationNodeType) { if (node.nam.startsWith("--")) { if (!(node.nam in namesMapping)) { - let result = + let value: string = moduleSettings.scoped! & ModuleScopeEnumOptions.Global ? node.nam : moduleSettings.generateScopedName!( @@ -1282,7 +1206,6 @@ export function doParseSync( moduleSettings.pattern as string, moduleSettings.hashLength, ); - let value: string = result; mapping[node.nam] = "--" + @@ -1336,7 +1259,7 @@ export function doParseSync( } if (!((rule as IdentToken).val in mapping)) { - let result = + let value: string = moduleSettings.scoped! & ModuleScopeEnumOptions.Global ? (rule as IdentToken).val : moduleSettings.generateScopedName!( @@ -1345,7 +1268,6 @@ export function doParseSync( moduleSettings.pattern as string, moduleSettings.hashLength, ); - let value: string = result; mapping[(rule as DashedIdentToken | IdentToken).val] = (rule.typ == EnumToken.DashedIdenTokenType ? "--" : "") + @@ -1552,7 +1474,7 @@ export function doParseSync( ].includes((value as IdentToken).val) ) { if (!((value as IdentToken).val in mapping)) { - const result = + mapping[(value as IdentToken).val] = moduleSettings.scoped! & ModuleScopeEnumOptions.Global ? (value as IdentToken).val : moduleSettings.generateScopedName!( @@ -1561,7 +1483,6 @@ export function doParseSync( moduleSettings.pattern as string, moduleSettings.hashLength, ); - mapping[(value as IdentToken).val] = result; revMapping[mapping[(value as IdentToken).val]] = (value as IdentToken).val; } @@ -1658,7 +1579,7 @@ export function doParseSync( const val: string = (value as ClassSelectorToken).val.slice(1); if (!(val in mapping)) { - const result = + let value: string = moduleSettings.scoped! & ModuleScopeEnumOptions.Global ? val : moduleSettings.generateScopedName!( @@ -1667,7 +1588,6 @@ export function doParseSync( moduleSettings.pattern as string, moduleSettings.hashLength, ); - let value: string = result; mapping[val] = moduleSettings.naming! & ModuleCaseTransformEnum.DashCaseOnly || @@ -1711,7 +1631,7 @@ export function doParseSync( (prefix == "" && value.typ == EnumToken.IdenTokenType) ) { if (!((value as DashedIdentToken | IdentToken).val in mapping)) { - const result = + let val: string = moduleSettings.scoped! & ModuleScopeEnumOptions.Global ? (value as DashedIdentToken | IdentToken).val : moduleSettings.generateScopedName!( @@ -1720,7 +1640,6 @@ export function doParseSync( moduleSettings.pattern as string, moduleSettings.hashLength, ); - let val: string = result; mapping[(value as DashedIdentToken | IdentToken).val] = prefix + @@ -1742,17 +1661,15 @@ export function doParseSync( if (moduleSettings.naming != ModuleCaseTransformEnum.IgnoreCase) { revMapping = {}; - mapping = Object.entries(mapping).reduce( - (acc: Record, [key, value]: [string, string]) => { - const keyName = getKeyName(key, moduleSettings.naming!); + mapping = {} as Record; + let keyName: string; - acc[keyName] = value; - revMapping[value] = keyName; + for (const [key, value] of Object.entries(mapping)) { + keyName = getKeyName(key, moduleSettings.naming!); - return acc; - }, - {} as Record, - ); + mapping[keyName] = value; + revMapping[value] = keyName; + } } result.mapping = mapping; @@ -1856,175 +1773,23 @@ export async function doParse( let tokens: Token[] = []; let context: AstRuleList = ast; - ast[ROOT] = ast; - - ast[LOC] = { - sta: 0, - end: 0, - srcId: options.source!.id, - }; - - let valuesHandlers: Map>>; - let preValuesHandlers: Map>>; - let postValuesHandlers: Map>>; - let preVisitorsHandlersMap: Map< - "Declaration" | "Rule" | "AtRule" | "KeyframesRule" | "KeyframesAtRule", - Array | Record>>> - >; - let visitorsHandlersMap: Map< - "Declaration" | "Rule" | "AtRule" | "KeyframesRule" | "KeyframesAtRule", - Array | Record>> - >; - let postVisitorsHandlersMap: Map< - "Declaration" | "Rule" | "AtRule" | "KeyframesRule" | "KeyframesAtRule", - Array | Record>>> - >; - const imports: AstAtRule[] = []; let item: TokenizeResult; - let node: AstAtRule | AstRule | AstKeyFrameRule | AstKeyframesAtRule | AstDeclaration | AstComment | null; + let node: AstAtRule | AstRule | AstKeyframesRule | AstKeyframesAtRule | AstDeclaration | AstComment | null; // @ts-ignore ignore error let isAsync: boolean = typeof iter[Symbol.asyncIterator] === "function"; let parensMatch: number = 0; let curlyBracketMatch: number = 0; - if (options.visitor != null) { - valuesHandlers = new Map() as Map>>; - preValuesHandlers = new Map() as Map>>; - postValuesHandlers = new Map() as Map>>; - - preVisitorsHandlersMap = new Map(); - visitorsHandlersMap = new Map(); - postVisitorsHandlersMap = new Map(); - - const visitors = Object.entries(options.visitor); - let key: string; - let value: any; - let i: number; - - for (i = 0; i < visitors.length; i++) { - key = visitors[i][0]; - value = visitors[i][1]; - - if (Number.isInteger(+key)) { - visitors.splice(i + 1, 0, ...Object.entries(value)); - continue; - } - - if (Array.isArray(value)) { - // @ts-ignore - visitors.splice(i + 1, 0, ...value.map((item) => [key, item])); - continue; - } - - if (key in EnumToken) { - if (typeof value == "function") { - if (!valuesHandlers.has(EnumToken[key as keyof typeof EnumToken] as EnumToken)) { - valuesHandlers.set(EnumToken[key as keyof typeof EnumToken] as EnumToken, []); - } - - valuesHandlers.get(EnumToken[key as keyof typeof EnumToken] as EnumToken)!.push(value); - } else if ( - typeof value == "object" && - "type" in value && - "handler" in value && - value.type in WalkerEvent - ) { - if (value.type == WalkerEvent.Enter) { - if (!preValuesHandlers.has(EnumToken[key as keyof typeof EnumToken] as EnumToken)) { - preValuesHandlers.set(EnumToken[key as keyof typeof EnumToken] as EnumToken, []); - } - - preValuesHandlers - .get(EnumToken[key as keyof typeof EnumToken] as EnumToken)! - .push(value.handler); - } else if (value.type == WalkerEvent.Leave) { - if (!postValuesHandlers.has(EnumToken[key as keyof typeof EnumToken] as EnumToken)) { - postValuesHandlers.set(EnumToken[key as keyof typeof EnumToken] as EnumToken, []); - } - - postValuesHandlers - .get(EnumToken[key as keyof typeof EnumToken] as EnumToken)! - .push(value.handler); - } - } else { - errors.push({ action: "ignore", message: `doParse: visitor.${key} is not a valid key name` }); - } - } else if (["Declaration", "Rule", "AtRule", "KeyframesRule", "KeyframesAtRule"].includes(key)) { - if (typeof value == "function") { - if ( - !visitorsHandlersMap.has( - key as "Declaration" | "Rule" | "AtRule" | "KeyframesRule" | "KeyframesAtRule", - ) - ) { - visitorsHandlersMap.set( - key as "Declaration" | "Rule" | "AtRule" | "KeyframesRule" | "KeyframesAtRule", - [], - ); - } - - visitorsHandlersMap - .get(key as "Declaration" | "Rule" | "AtRule" | "KeyframesRule" | "KeyframesAtRule")! - .push(value); - } else if (typeof value == "object") { - if ("type" in value && "handler" in value && value.type in WalkerEvent) { - if (value.type == WalkerEvent.Enter) { - if ( - !preVisitorsHandlersMap.has( - key as "Declaration" | "Rule" | "AtRule" | "KeyframesRule" | "KeyframesAtRule", - ) - ) { - preVisitorsHandlersMap.set( - key as "Declaration" | "Rule" | "AtRule" | "KeyframesRule" | "KeyframesAtRule", - [], - ); - } - - preVisitorsHandlersMap - .get(key as "Declaration" | "Rule" | "AtRule" | "KeyframesRule" | "KeyframesAtRule")! - .push(value.handler); - } else if (value.type == WalkerEvent.Leave) { - if ( - !postVisitorsHandlersMap.has( - key as "Declaration" | "Rule" | "AtRule" | "KeyframesRule" | "KeyframesAtRule", - ) - ) { - postVisitorsHandlersMap.set( - key as "Declaration" | "Rule" | "AtRule" | "KeyframesRule" | "KeyframesAtRule", - [], - ); - } - - postVisitorsHandlersMap - .get(key as "Declaration" | "Rule" | "AtRule" | "KeyframesRule" | "KeyframesAtRule")! - .push(value.handler); - } - } else { - if ( - !visitorsHandlersMap.has( - key as "Declaration" | "Rule" | "AtRule" | "KeyframesRule" | "KeyframesAtRule", - ) - ) { - visitorsHandlersMap.set( - key as "Declaration" | "Rule" | "AtRule" | "KeyframesRule" | "KeyframesAtRule", - [], - ); - } + // ast[ROOT] = ast; - visitorsHandlersMap - .get(key as "Declaration" | "Rule" | "AtRule" | "KeyframesRule" | "KeyframesAtRule")! - .push(value); - } - } else { - errors.push({ action: "ignore", message: `doParse: visitor.${key} is not a valid key name` }); - } - } else { - errors.push({ action: "ignore", message: `doParse: visitor.${key} is not a valid key name` }); - } - } - } + ast[LOC] = { + sta: 0, + end: 0, + srcId: options.source!.id, + }; if (Array.isArray(iter)) { // @ts-expect-error @@ -2082,7 +1847,7 @@ export async function doParse( if (node != null) { if ("chi" in node) { - stack.push(node as AstAtRule | AstRule | AstKeyFrameRule); + stack.push(node as AstAtRule | AstRule | AstKeyframesRule); context = node as AstRuleList; } else if (node.typ == EnumToken.AtRuleNodeType && (node as AstAtRule).nam === "import") { imports.push(node); @@ -2173,7 +1938,10 @@ export async function doParse( const url: string = token.typ == EnumToken.StringTokenType ? token.val.slice(1, -1) : token.val; try { - const src = options.resolve!(url, options.src || (options.cwd as string)) as ResolvedPath; + const src = options.resolve!( + url, + options.src ? dirname(options.src as string) : (options.cwd as string), + ) as ResolvedPath; const result = options.load!(src) as LoadResult; const stream = result instanceof Promise || Object.getPrototypeOf(result).constructor.name == "AsyncFunction" @@ -2223,289 +1991,193 @@ export async function doParse( } let replacement: GenericVisitorResult; - let callable: GenericVisitorHandler; if (options.visitor != null) { let parens: Token[] | null; - for (const result of walk(ast)) { - parens = null; - - if ( - valuesHandlers!.size > 0 || - preVisitorsHandlersMap!.size > 0 || - visitorsHandlersMap!.size > 0 || - postVisitorsHandlersMap!.size > 0 - ) { - if ( - (result.node.typ == EnumToken.DeclarationNodeType && - (preVisitorsHandlersMap!.has("Declaration") || - visitorsHandlersMap!.has("Declaration") || - postVisitorsHandlersMap!.has("Declaration"))) || - (result.node.typ == EnumToken.AtRuleNodeType && - (preVisitorsHandlersMap!.has("AtRule") || - visitorsHandlersMap!.has("AtRule") || - postVisitorsHandlersMap!.has("AtRule"))) || - (result.node.typ == EnumToken.KeyframesAtRuleNodeType && - (preVisitorsHandlersMap!.has("KeyframesAtRule") || - visitorsHandlersMap!.has("KeyframesAtRule") || - postVisitorsHandlersMap!.has("KeyframesAtRule"))) - ) { - const handlers = [] as Array | Record>>; - const key = - result.node.typ == EnumToken.DeclarationNodeType - ? "Declaration" - : result.node.typ == EnumToken.AtRuleNodeType - ? "AtRule" - : "KeyframesAtRule"; - - if (preVisitorsHandlersMap!.has(key)) { - handlers.push( - // @ts-expect-error - ...(preVisitorsHandlersMap!.get(key)! as - | GenericVisitorHandler - | Record>), - ); - } - - if (visitorsHandlersMap!.has(key)) { - // @ts-ignore - handlers.push(...visitorsHandlersMap.get(key)!); - } - - if (postVisitorsHandlersMap!.has(key)) { - // @ts-ignore - handlers.push(...postVisitorsHandlersMap.get(key)); - } - - let node: AstDeclaration | AstAtRule | AstKeyframesAtRule = result.node as - | AstDeclaration - | AstAtRule - | AstKeyframesAtRule; - - for (const handler of handlers) { - callable = - typeof handler == "function" - ? handler - : (handler[ - camelize( - node.typ === EnumToken.DeclarationNodeType || - node.typ === EnumToken.AtRuleNodeType - ? (node as AstDeclaration | AstAtRule).nam - : (node as AstKeyframesAtRule).val, - ) - ] as GenericVisitorHandler); - - if (callable == null) { - continue; - } - - // @ts-expect-error - replacement = callable(node, result[PARENT], ast, function* () { - if (parens == null) { - // @ts-expect-error - parens = [...result.parents()]; - } - - yield* parens[Symbol.iterator](); - }); - - if (replacement == null) { - continue; - } - - if (replacement instanceof Promise) { - replacement = await replacement; - } - if (replacement == null || replacement == node) { - continue; - } + let genericKey: string | null; + const handlers = [] as Array>; + const visitors = parseVisitors(options.visitor, errors); - // @ts-ignore - node = replacement; + let nodes: AstNode[] | null = new Array(stats.tokensCount); + const subNodes: Array = []; + let i: number; + let k: number; + let j: number; + let freeblock: number = 1; + nodes[0] = ast; - if (Array.isArray(node)) { - break; - } - } + for (i = 0; i < nodes.length; i++) { + if (nodes[i] == null) { + break; + } - if (node != result.node) { - replaceNodeOrValue( - result.parent as AstRule | AstAtRule | AstKeyframesAtRule | AstKeyFrameRule | AstStyleSheet, - result.node, - node, + subNodes.length = 0; + if (visitors.includeTokens) { + switch (nodes[i].typ) { + case EnumToken.RuleNodeType: + case EnumToken.AtRuleNodeType: + case EnumToken.KeyframesRuleNodeType: + case EnumToken.KeyframesAtRuleNodeType: + subNodes.push( + ...(nodes[i] as AstRule | AstAtRule | AstKeyframesRule | AstKeyframesAtRule)[TOKENS]!, ); - } - } else if ( - (result.node.typ == EnumToken.RuleNodeType && - (preVisitorsHandlersMap!.has("Rule") || - visitorsHandlersMap!.has("Rule") || - postVisitorsHandlersMap!.has("Rule"))) || - (result.node.typ == EnumToken.KeyFramesRuleNodeType && - (preVisitorsHandlersMap!.has("KeyframesRule") || - visitorsHandlersMap!.has("KeyframesRule") || - postVisitorsHandlersMap!.has("KeyframesRule"))) - ) { - const handlers = [] as Array< - | GenericVisitorHandler - | { - type: WalkerEvent; - handler: GenericVisitorHandler; - } - >; - const key = result.node.typ == EnumToken.RuleNodeType ? "Rule" : "KeyframesRule"; - - if (preVisitorsHandlersMap!.has(key)) { - handlers.push(...(preVisitorsHandlersMap!.get(key)! as Array>)); - } - - if (visitorsHandlersMap!.has(key)) { - handlers.push(...(visitorsHandlersMap!.get(key)! as Array>)); - } - - if (postVisitorsHandlersMap!.has(key)) { - handlers.push(...(postVisitorsHandlersMap!.get(key)! as Array>)); - } + break; + case EnumToken.DeclarationNodeType: + subNodes.push(...(nodes[i] as AstDeclaration).val); + break; + } + } - let node = result.node; + if (nodes[i].chi != null) { + subNodes.push(...nodes[i].chi); + } - for (const callable of handlers) { - replacement = (callable as GenericVisitorHandler)( - node as T, - result.parent, - result.root, - // @ts-expect-error - function* () { - if (parens == null) { - // @ts-expect-error - parens = [...result.parents()]; - } + if (subNodes.length > 0) { + if (freeblock <= i) { + freeblock = i + 1; + } - yield* parens[Symbol.iterator](); - }, - ) as GenericVisitorResult; + for (k = 0; k < subNodes.length; k++) { + j = k + freeblock; + nodes[j] = subNodes[k] as AstNode; + nodes[j][PARENT] = nodes[i]; + } - if (replacement == null) { - continue; - } + freeblock += subNodes.length; + } - if (replacement instanceof Promise) { - replacement = await replacement; - } + parens = null; + handlers.length = 0; + + genericKey = + nodes[i].typ == EnumToken.DeclarationNodeType + ? "Declaration" + : nodes[i].typ == EnumToken.AtRuleNodeType + ? "AtRule" + : nodes[i].typ == EnumToken.KeyframesAtRuleNodeType + ? "KeyframesAtRule" + : nodes[i].typ === EnumToken.KeyframesRuleNodeType + ? "KeyframesRule" + : nodes[i].typ == EnumToken.RuleNodeType + ? "Rule" + : nodes[i].typ == EnumToken.KeyframesRuleNodeType + ? "KeyframesRule" + : null; + let keyName: string | null = + nodes[i].typ == EnumToken.DeclarationNodeType || nodes[i].typ == EnumToken.AtRuleNodeType + ? camelize((nodes[i] as AstDeclaration | AstAtRule).nam) + : nodes[i].typ == EnumToken.KeyframesAtRuleNodeType + ? camelize((nodes[i] as AstKeyframesAtRule).val) + : null; + + for (const map of visitors.allHandlers) { + // @ts-ignore + if (genericKey != null && map!.has(genericKey)) { + // @ts-ignore + for (const handler of map!.get(genericKey)!) { + if (typeof handler == "function") { + handlers.push(handler as GenericVisitorHandler); + } else if (Array.isArray(handler)) { + for (const h of handler) { + if (typeof h == "function") { + handlers.push(h); + } - if (replacement == null || replacement == node) { - continue; + // @ts-ignore + else if (h[keyName] != null) { + // @ts-ignore + handlers.push(h[keyName]); + } + } + } else if (typeof handler.handler! == "function") { + handlers.push(handler.handler); } // @ts-ignore - node = replacement as AstNode; - - // - if (Array.isArray(node)) { - break; + else if (typeof handler[keyName]! == "function") { + // @ts-ignore + handlers.push(handler[keyName]); } } + } + // @ts-ignore + if (map!.has(nodes[i].typ)) { // @ts-ignore - if (node != result.node) { - // @ts-ignore - replaceNodeOrValue(result.parent, result.node, node); - } - } else if (valuesHandlers!.size > 0) { - let node: Token | AstNode | null = null; - - node = result.node; - - if (valuesHandlers!.has(node.typ)) { - for (const valueHandler of valuesHandlers!.get(node.typ)!) { - callable = valueHandler as GenericVisitorHandler; - replacement = callable( - node as T, - result.parent, - ast, - // @ts-expect-error - function* () { - if (parens == null) { - // @ts-expect-error - parens = [...result.parents()]; - } - - yield* parens[Symbol.iterator](); - }, - ); - - if (replacement == null) { - continue; - } - - if (replacement instanceof Promise) { - replacement = await replacement; - } - - if (replacement != null && replacement != node) { - node = replacement as AstNode; + for (const handler of map!.get(nodes[i].typ)!) { + if (typeof handler == "function") { + handlers.push(handler as GenericVisitorHandler); + } else if (Array.isArray(handler)) { + for (const h of handler) { + if (typeof h == "function") { + handlers.push(h); + } } + } else if (typeof handler.handler! == "function") { + handlers.push(handler.handler); } - } - if (node != result.node) { // @ts-ignore - replaceNodeOrValue(result[PARENT], value, node); - } - - const tokens: Token[] = Array.isArray(result.node[TOKENS]) ? (result.node[TOKENS] as Token[]) : []; - - if (Array.isArray(result.node.val)) { - tokens.push(...(result.node.val as Token[])); + else if (typeof handler[keyName]! == "function") { + // @ts-ignore + handlers.push(handler[keyName]); + } } + } + } - if (tokens.length == 0) { - continue; - } + if (handlers.length == 0) { + continue; + } - for (const { value, parent, root, parents } of walkValues(tokens, result.node)) { - node = value; + let node = nodes[i] as AstNode; - if (valuesHandlers!.has(node!.typ)) { - let parens: Token[] | null = null; - for (const valueHandler of valuesHandlers!.get(node!.typ)!) { - callable = valueHandler as GenericVisitorHandler; - // @ts-expect-error - let result: GenericVisitorResult = callable(node as T, parent, root, function* () { - if (parens == null) { - // @ts-expect-error - parens = [...parents()]; - } + for (const callable of handlers) { + replacement = (callable as GenericVisitorHandler)( + node as T, + nodes[i][PARENT] as AstNode, + ast as AstStyleSheet, + // @ts-expect-error + function* () { + if (parens == null) { + let node = nodes![i][PARENT] as AstNode; - yield* parens[Symbol.iterator](); - }); + while (node != null) { + yield node; + node = node[PARENT] as AstNode; + } + } + }, + ) as GenericVisitorResult; - if (result == null) { - continue; - } + if (replacement == null) { + continue; + } - if (result instanceof Promise) { - result = await result; - } + if (replacement instanceof Promise) { + replacement = await replacement; + } - if (result != null && result != node) { - node = result as Token; - } + if (replacement == null || replacement == node) { + continue; + } - if (Array.isArray(node)) { - break; - } - } - } + // @ts-ignore + node = replacement as AstNode; - if (node != value) { - // @ts-ignore - replaceNodeOrValue(parent, value, node); - } - } + // + if (Array.isArray(node)) { + break; } } + + if (node != nodes[i]) { + replaceNodeOrValue(nodes[i][PARENT], nodes[i], node); + } } + + nodes = null; } if (invalidNodes.length > 0) { @@ -2535,24 +2207,6 @@ export async function doParse( } } - while (stack.length > 0 && context != ast) { - const previousNode: AstAtRule | AstRule = stack.pop() as AstAtRule | AstRule; - context = (stack[stack.length - 1] ?? ast) as AstRuleList; - - // remove empty nodes - if ( - options.removeEmpty && - previousNode != null && - previousNode.chi!.length == 0 && - context.chi![context.chi!.length - 1] == previousNode - ) { - context.chi!.pop(); - continue; - } - - break; - } - if (options.minify) { if (ast.chi.length > 0) { let passes: number = options.pass ?? (1 as number); @@ -2800,6 +2454,8 @@ export async function doParse( continue; } + const resolvedSrc = options.resolve!(options.src as string, options.cwd as string); + for (const token of composeSelectors) { // composes: a b c; if (token.r == null) { @@ -2889,9 +2545,11 @@ export async function doParse( }) as ParserOptions, ); - const srcIndex: string = - (src.relative.startsWith("/") || src.relative.startsWith("../") ? "" : "./") + - src.relative; + let srcIndex: string = options.resolve!(src.absolute, resolvedSrc.absolute).relative; + + if (!srcIndex.startsWith("/") && !srcIndex.startsWith("../")) { + srcIndex = `./${srcIndex}`; + } if (Object.keys(root.mapping as Record).length > 0) { importMapping[srcIndex] = {} as Record; @@ -3218,28 +2876,6 @@ export async function doParse( ) { (parent as AstRule)[TOKENS]!.splice(index, 1); } - - // if (val == ":global") { - // for (; index < (parent as AstRule)[TOKENS]!.length; index++) { - // if ( - // (parent as AstRule)[TOKENS]![index].typ == - // EnumToken.CommaTokenType || - // ([ - // EnumToken.PseudoClassFuncTokenType, - // EnumToken.PseudoClassTokenType, - // ].includes((parent as AstRule)[TOKENS]![index].typ) && - // [":global", ":local"].includes( - // ( - // (parent as AstRule)[TOKENS]![index] as PseudoClassToken - // ).val.toLowerCase(), - // )) - // ) { - // break; - // } - - // global.add((parent as AstRule)[TOKENS]![index]); - // } - // } } break; @@ -3259,13 +2895,6 @@ export async function doParse( ); break; - // (parent as AstRule)[TOKENS]!.splice( - // (parent as AstRule)[TOKENS]!.indexOf(value), - // 1, - // ...(value as FunctionToken).chi, - // ); - - // break; } } }, @@ -3443,7 +3072,7 @@ function parseNode( errors: ErrorDescription[], stats: ParseResultStats, invalidNodes: AstNode[], -): AstRule | AstAtRule | AstKeyFrameRule | AstKeyframesAtRule | AstDeclaration | AstComment | null { +): AstRule | AstAtRule | AstKeyframesRule | AstKeyframesAtRule | AstDeclaration | AstComment | null { let i: number = 0; if (tokens.at(-1)?.typ === EnumToken.EOFTokenType) { @@ -3654,6 +3283,8 @@ function parseNode( } /** + * @param stream + * @param context * @param options * @param errors * @param parseAsBlock @@ -3752,7 +3383,6 @@ export function parseAtRule( } if (syntax != null && atRule.nam !== "layer" && parseAsBlock !== blockAllowed) { - success = false; errors.push({ action: "drop", node: atRule, @@ -4335,7 +3965,7 @@ export function parseAtRule( action: "drop", node: atRule, location: options.source!.getSourceLocation(atRule[LOC]!.sta), - message: "node is allowd only in @page rule", + message: "node is allowed only in @page rule", }); } else { trimArray(stream); @@ -4500,9 +4130,6 @@ export function parseAtRule( if (result.errors.length > 0) { errors.push(...result.errors); } - // else if (atRuleName === "document") { - // parseUrlToken(stream); - // } if (result.success) { let i: number = 0; @@ -4616,19 +4243,18 @@ export function parseString( currentPosition: -1, }; - const result = parseTokens( - [...tokenize(parseInfo)].map((t) => t.token), - options, - errors, - ); - - // remove EOF token - result.pop(); + const tokenResults: TokenizeResult[] = tokenize(parseInfo); + const mapped: Token[] = []; - if (result.at(-1)?.typ === EnumToken.WhitespaceTokenType) { - result.pop(); + for (const token of tokenResults) { + mapped.push(token.token); } + const result: Token[] = parseTokens(mapped, options, errors); + + // remove EOF token + result.splice(result.length - (result[result.length - 2]?.typ === EnumToken.WhitespaceTokenType ? 2 : 1), 2); + return result; } @@ -4745,7 +4371,6 @@ export function parseTokens( node, location: options.source!.getSourceLocation(node[LOC]!.sta), }); - // return []; continue; } diff --git a/src/lib/parser/source.ts b/src/lib/parser/source.ts index 9ef29a73..a65ee98d 100644 --- a/src/lib/parser/source.ts +++ b/src/lib/parser/source.ts @@ -1,5 +1,7 @@ +import type { SourceMapObject } from "../../@types/index.d.ts"; +import { SourceMap } from "../renderer/sourcemap/sourcemap.ts"; import { LineMap } from "./linesmap.ts"; -import type {SourceLocation} from "../../@types/ast.d.ts"; + /** * Source file ID */ @@ -9,6 +11,7 @@ let sourceId: number = 0; * Source file helper class */ export class SourceFile { + private inputSourceMap: SourceMap | null = null; /** * Source file ID @@ -29,10 +32,9 @@ export class SourceFile { /** * Constructor - * @param id - * @param content - * @param lines - * @param file + * @param content + * @param lines + * @param file */ constructor(content: string, lines: number[], file: string | null = null) { this.id = sourceId++; @@ -43,8 +45,7 @@ export class SourceFile { /** * Update source content - * @param content - * @param lines + * @param content */ append(content: string) { this.content += content; @@ -52,16 +53,15 @@ export class SourceFile { /** * get file name - * @returns + * @returns */ getFileName(): string | null { - return this.file; } /** * get content - * @returns + * @returns */ getContent(): string { return this.content; @@ -69,9 +69,9 @@ export class SourceFile { /** * get text - * @param start - * @param length - * @returns + * @param start + * @param length + * @returns */ getText(start: number, length: number): string { return this.content.slice(start, start + length); @@ -79,8 +79,8 @@ export class SourceFile { /** * Compute line and column of the offset - * @param offset - * @returns + * @param offset + * @returns */ getOffsets(offset: number): [number, number] { return this.lineStarts.getOffsets(offset); @@ -88,26 +88,42 @@ export class SourceFile { /** * get source location - * @param offset - * @returns + * @param offset + * @returns */ getSourceLocation(offset: number): [string | null, number, number] { - return [this.file, ... this.getOffsets(offset)]; + return [this.file, ...this.getOffsets(offset)]; } /** * get line starts - * @returns + * @returns */ getLineStarts(): number[] { return this.lineStarts.getLineStarts(); } - + /** * add line start - * @param lineStart + * @param lineStart */ addLineStart(lineStart: number) { this.lineStarts.addLineStart(lineStart); } + + /** + * set input source map + * @param inputSourceMap + */ + setInputSourceMap(inputSourceMap: SourceMapObject | string | null) { + this.inputSourceMap = inputSourceMap == null ? null : new SourceMap(inputSourceMap as SourceMapObject | string); + } + + /** + * return input source map + * @returns + */ + getInputSourceMap(): SourceMap | null { + return this.inputSourceMap; + } } diff --git a/src/lib/parser/tokenize.ts b/src/lib/parser/tokenize.ts index 42071314..865ecc9e 100644 --- a/src/lib/parser/tokenize.ts +++ b/src/lib/parser/tokenize.ts @@ -477,7 +477,7 @@ export function next(parseInfo: ParseInfo, count: number = 1): string { } /** - * Tokenize css string + * Tokenize CSS string * @param parseInfo * @param yieldEOFToken */ @@ -495,7 +495,6 @@ export function tokenize(parseInfo: ParseInfo | string, yieldEOFToken: boolean = } let value: string; - let nextValue: string; let buffer: string = parseInfo.buffer; let charCode: number; let nextCharCode: number; @@ -509,9 +508,6 @@ export function tokenize(parseInfo: ParseInfo | string, yieldEOFToken: boolean = while ((value = peek(parseInfo))) { charCode = value.charCodeAt(0); - // nextCharCode = nextValue.charCodeAt(0); - - // console.debug({value, buffer}); switch (charCode) { case TokenMap.EQUALS: @@ -962,7 +958,7 @@ export function tokenize(parseInfo: ParseInfo | string, yieldEOFToken: boolean = next(parseInfo); // EOF - if (!(nextValue = peek(parseInfo))) { + if (!(peek(parseInfo))) { // end of stream ignore \\ if (buffer.length > 0) { result.push(yieldResult(buffer, parseInfo)); @@ -973,11 +969,6 @@ export function tokenize(parseInfo: ParseInfo | string, yieldEOFToken: boolean = } buffer += value + next(parseInfo); - - // buffer += - // (parseInfo.offset == parseInfo.currentPosition - // ? parseInfo.buffer.slice(-1) - // : parseInfo.stream.charAt(parseInfo.currentPosition - parseInfo.offset - 1)) + value; break; case TokenMap.SINGLE_QUOTE: diff --git a/src/lib/parser/utils/declaration.ts b/src/lib/parser/utils/declaration.ts index cf133c44..e30b807d 100644 --- a/src/lib/parser/utils/declaration.ts +++ b/src/lib/parser/utils/declaration.ts @@ -1,7 +1,7 @@ import type { AstAtRule, AstDeclaration, - AstKeyFrameRule, + AstKeyframesRule, AstRule, AstStyleSheet, AtRuleToken, @@ -91,7 +91,7 @@ function parseGridTemplate(template: string): string { export function parseDeclaration( tokens: Token[], - parent: AstRule | AstAtRule | AstKeyFrameRule | AstStyleSheet | AtRuleToken | null, + parent: AstRule | AstAtRule | AstKeyframesRule | AstStyleSheet | AtRuleToken | null, options: ParserOptions, errors: ErrorDescription[], ): AstDeclaration | RawNodeToken { diff --git a/src/lib/parser/utils/selector.ts b/src/lib/parser/utils/selector.ts index 41ddb815..4dfa2340 100644 --- a/src/lib/parser/utils/selector.ts +++ b/src/lib/parser/utils/selector.ts @@ -2,7 +2,7 @@ import type { Token, AstRule, AstAtRule, - AstKeyFrameRule, + AstKeyframesRule, AstKeyframesAtRule, AstStyleSheet, ParserOptions, @@ -36,10 +36,10 @@ import { trimWhiteSpace } from "../parse.ts"; export function parseSelector( tokens: Token[], - context: AtRuleToken | AstRule | AstAtRule | AstKeyFrameRule | AstKeyframesAtRule | AstStyleSheet | null, + context: AtRuleToken | AstRule | AstAtRule | AstKeyframesRule | AstKeyframesAtRule | AstStyleSheet | null, options: ParserOptions, errors: ErrorDescription[], -): AstRule | AstKeyFrameRule { +): AstRule | AstKeyframesRule { if (context?.typ === EnumToken.KeyframesAtRuleNodeType) { const result = matchAllSyntaxes( getParsedSyntax(ValidationSyntaxGroupEnum.Syntaxes, "keyframe-selectors"), @@ -97,7 +97,7 @@ export function parseSelector( ); return { - typ: EnumToken.KeyFramesRuleNodeType, + typ: EnumToken.KeyframesRuleNodeType, sel: [ ...splitTokenList(trimArray(tokens)).reduce((acc, curr: Token[]) => { acc.add(curr.reduce((acc, curr) => acc + renderValue(curr, { minify: false }), "")); @@ -112,7 +112,7 @@ export function parseSelector( [TOKENS]: tokens.length === 0 ? null : tokens, [STATE]: result.success ? EnumAstNodeStatus.Validated : EnumAstNodeStatus.Invalid, [ERRORS]: result.errors, - } as AstKeyFrameRule; + } as AstKeyframesRule; } const stack: Token[] = []; diff --git a/src/lib/parser/utils/token.ts b/src/lib/parser/utils/token.ts index f792cff7..134340eb 100644 --- a/src/lib/parser/utils/token.ts +++ b/src/lib/parser/utils/token.ts @@ -1,9 +1,8 @@ import type { AstAtRule, AstDeclaration, - AstKeyFrameRule, - AstKeyframesAtRule, AstKeyframesRule, + AstKeyframesAtRule, AstNode, AstRule, } from "../../../@types/ast.d.ts"; @@ -81,7 +80,7 @@ export function replaceNodeOrValue( | ParensToken | AstAtRule | AstKeyframesAtRule - | AstKeyFrameRule + | AstKeyframesRule | AstRule | AstKeyframesRule ).chi as Token[]) ?? parent); diff --git a/src/lib/renderer/render.ts b/src/lib/renderer/render.ts index 1c1387da..ee0d6106 100644 --- a/src/lib/renderer/render.ts +++ b/src/lib/renderer/render.ts @@ -28,7 +28,6 @@ import type { LengthToken, ListToken, LiteralToken, - SourceLocation, MediaFeatureToken, MediaQueryConditionToken, MediaQueryUnaryFeatureToken, @@ -39,6 +38,7 @@ import type { PseudoPageToken, RenderOptions, RenderResult, + SourceLocation, StringToken, SupportsQueryConditionToken, SupportsQueryUnaryConditionToken, @@ -47,19 +47,18 @@ import type { WhenElseUnaryConditionToken, WrappedValuesToken, } from "../../@types/index.d.ts"; -import { convertColor } from "../syntax/color/color.ts"; -import { getAngle } from "../syntax/color/color.ts"; +import { convertColor, getAngle } from "../syntax/color/color.ts"; import { reduceHexValue } from "../syntax/color/hex.ts"; import { ColorType, EnumToken } from "../ast/types.ts"; import { expand } from "../ast/expand.ts"; import { SourceMap } from "./sourcemap/sourcemap.ts"; import { colorPrecision, LOC, PARENT, pseudoElements, tokensfuncSet, urlTokenMatcher } from "../syntax/constants.ts"; import { + minifyNumber, parseColor, - reducegradientBackgroundPosition, reduceColorStops, reduceConicColorStops, - minifyNumber, + reducegradientBackgroundPosition, toPrecisionAngle, toPrecisionValue, } from "../syntax/syntax.ts"; @@ -67,6 +66,7 @@ import { equalsIgnoreCase } from "../parser/utils/text.ts"; import { toDegrees } from "../parser/utils/angle.ts"; import { LineMap as LinesMap } from "../parser/linesmap.ts"; import { dirname } from "../fs/resolve.ts"; +import { SourceFile } from "../parser/source.ts"; /** * render ast @@ -133,6 +133,8 @@ export function doRender( const startTime: number = performance.now(); const errors: ErrorDescription[] = []; const sourcemap: SourceMap | null = options.sourcemap ? new SourceMap() : null; + const sourcemaps: Array<[number, number, number, number, number, string | null, string | null]> | null = + options.sourcemap ? [] : null; const cache: { [key: string]: any; } = Object.create(null); @@ -142,13 +144,24 @@ export function doRender( sta: 0, end: 0, } as SourceLocation; - const linesMap = new LinesMap([]); + const linesMap: LinesMap | null = options.sourcemap ? new LinesMap() : null; let code: string = ""; if (mapping != null) { if (mapping.importMapping != null) { - for (const [key, value] of Object.entries(mapping.importMapping)) { + const absolutePath = options.resolve!( + options.output != null ? dirname(options.output as string) : dirname(options.src as string), + options.cwd as string, + ).absolute; + + for (let [key, value] of Object.entries(mapping.importMapping)) { + key = options.resolve!(options.resolve!(key, options.cwd as string).absolute, absolutePath).relative; + + if (!key.startsWith("/") && !key.startsWith(".")) { + key = "./" + key; + } + code += `:import("${key}")${options.indent}{${options.newLine}${Object.entries(value).reduce( (acc, [k, v]) => acc + (acc.length > 0 ? options.newLine : "") + `${options.indent}${v}:${options.indent}${k};`, @@ -162,7 +175,10 @@ export function doRender( acc + (acc.length > 0 ? options.newLine : "") + `${options.indent}${k}:${options.indent}${v};`, "", )}${options.newLine}}${options.newLine}`; - move(sourceLocation, linesMap, code); + + if (sourcemap != null) { + move(sourceLocation, linesMap!, code); + } } if (options.output != null) { @@ -182,7 +198,7 @@ export function doRender( ? expand(data as AstStyleSheet | AstAtRule | AstRule) : data, options, - sourcemap, + sourcemaps, sourceLocation, linesMap, errors, @@ -206,6 +222,7 @@ export function doRender( }; if (sourcemap != null) { + sourcemap.addAll(sourcemaps!); result.map = sourcemap; if (options.sourcemap === "inline") { @@ -221,8 +238,9 @@ export function doRender( * @param node * @param options * @param cache - * @param sourcemap - * @param position + * @param sourcemaps + * @param sourceLocation + * @param linesMap * @param str * * @internal @@ -233,48 +251,105 @@ function updateSourceMap( cache: { [p: string]: any; }, - sourcemap: SourceMap, + sourcemaps: Array<[number, number, number, number, number, string | null, string | null]>, sourceLocation: SourceLocation, linesMap: LinesMap, str: string, ) { + let offset: number = 0; + + while (true) { + if (str.charAt(offset) == options.newLine) { + offset += options.newLine.length; + continue; + } + + if (str.charAt(offset) == options.indent) { + offset += options.indent.length; + continue; + } + + break; + } + + if (offset > 0) { + move(sourceLocation, linesMap, str.slice(0, offset)); + } + if ( + node[LOC] != null && [ EnumToken.RuleNodeType, EnumToken.AtRuleNodeType, - EnumToken.KeyFramesRuleNodeType, + EnumToken.KeyframesRuleNodeType, EnumToken.KeyframesAtRuleNodeType, ].includes(node.typ) ) { - let srcId: number = (node[LOC])?.srcId ?? 0; + const source = options.sourcesMap!.get((node[LOC] as SourceLocation)!.srcId) as SourceFile; + const inputSourceMap = source.getInputSourceMap(); + const offsets: [number, number] = source.getOffsets(node[LOC].sta) as [number, number]; + const [newLine, newColumn] = linesMap.getOffsets(sourceLocation.end); + let records: Array<[string | null, number, number, string | null]> | null = null; + let srcId: number = (node[LOC] as SourceLocation)!.srcId; + let sourceFileName: string | null = (source.getFileName() as string) || null; + let sourceContent: string | null = (source.getContent() as string) || null; + + if (inputSourceMap != null && (records = inputSourceMap.find(offsets[0], offsets[1])) != null) { + for (const record of records) { + // @ts-ignore + sourceFileName = (record[0] as string) || null; + // @ts-ignore + offsets[0] = record[1] as number; + // @ts-ignore + offsets[1] = record[2] as number; + + sourceContent = (record[3] as string) || null; + + if (sourceFileName != null && options.output != null && !sourceFileName.startsWith("data:")) { + if (cache[sourceFileName] == null) { + const absolute = options.resolve!(dirname(options.output as string), options.cwd as string) + .absolute as string; + const absoluteSourcePath = options.resolve!( + dirname(options.src! || ("" as string)), + options.cwd as string, + ).absolute; + // resolution is relative to the source file + const absoluteSourceFileName = options.resolve!(sourceFileName, absoluteSourcePath as string) + .absolute as string; + + cache[sourceFileName] = options.resolve!(absoluteSourceFileName, absolute).relative as string; + } - let sourceFileName: string | null = (options.sourcesMap?.get(srcId)?.getFileName?.() as string) || null; + sourceFileName = cache[sourceFileName] as string; + } - if (sourceFileName != null && options.output != null) { - if (cache[sourceFileName] == null) { - cache[sourceFileName] = options.resolve!(sourceFileName, dirname(options.output)).relative as string; + sourcemaps.push([newLine, newColumn, srcId, ...offsets, sourceFileName as string, sourceContent]); } + } else { + if (sourceFileName != null && options.output != null && !sourceFileName.startsWith("data:")) { + if (cache[sourceFileName] == null) { + const absolute = options.resolve!(dirname(options.output as string), options.cwd as string) + .absolute as string; + const absoluteSourceFileName = options.resolve!(sourceFileName, options.cwd as string) + .absolute as string; + + cache[sourceFileName] = options.resolve!(absoluteSourceFileName, absolute).relative as string; + } - sourceFileName = cache[sourceFileName] as string; - } + sourceFileName = cache[sourceFileName] as string; + } - // @ts-ignore - sourcemap.add( - ...linesMap.getOffsets(sourceLocation.end), - srcId, - // @ts-ignore - ...(options.sourcesMap?.get(srcId)?.getOffsets(sourceLocation.sta) as [number, number]), - sourceFileName as string, - options.sourcesMap?.get(srcId)?.getContent?.() as string, - ); + sourcemaps.push([newLine, newColumn, srcId, ...offsets, sourceFileName as string, sourceContent]); + } } - move(sourceLocation, linesMap, str); + move(sourceLocation, linesMap, offset > 0 ? str.slice(offset) : str); } /** * Update position - * @param position + * @param sourceLocation + * @param linesMap * @param str */ export function move(sourceLocation: SourceLocation, linesMap: LinesMap, str: string) { @@ -310,8 +385,9 @@ export function move(sourceLocation: SourceLocation, linesMap: LinesMap, str: st * render ast node * @param data * @param options - * @param sourcemap - * @param position + * @param sourcemaps + * @param sourceLocation + * @param linesMap * @param errors * @param reducer * @param cache @@ -323,9 +399,9 @@ export function move(sourceLocation: SourceLocation, linesMap: LinesMap, str: st function renderAstNode( data: AstNode, options: RenderOptions, - sourcemap: SourceMap | null, + sourcemaps: Array<[number, number, number, number, number, string | null, string | null]> | null, sourceLocation: SourceLocation, - linesMap: LinesMap, + linesMap: LinesMap | null, errors: ErrorDescription[], reducer: (acc: string, curr: Token) => string, cache: { @@ -342,6 +418,10 @@ function renderAstNode( indents.push((options.indent).repeat(level + 1)); } + // @ts-ignore + let children: string = ""; + let str: string = ""; + const indent: string = indents[level]; const indentSub: string = indents[level + 1]; @@ -355,7 +435,7 @@ function renderAstNode( case EnumToken.CommentNodeType: case EnumToken.CDOCOMMNodeType: if ((data).val.startsWith("/*# sourceMappingURL=")) { - // ignore sourcemap + // ignore sourcemap comment return ""; } @@ -364,13 +444,11 @@ function renderAstNode( : ""; case EnumToken.StyleSheetNodeType: - return (data).chi.reduce((css: string, node: AstRuleList | AstComment) => { - const hasPreviousContent = css !== ""; - - const str: string = renderAstNode( + for (const node of (data).chi) { + str = renderAstNode( node, options, - sourcemap, + sourcemaps, sourceLocation, linesMap, errors, @@ -381,31 +459,25 @@ function renderAstNode( ); if (str === "") { - return css; + continue; } - if (sourcemap != null && node[LOC] != null) { - updateSourceMap( - node, - options, - cache, - sourcemap, - sourceLocation, - linesMap, - (hasPreviousContent ? options.newLine : "") + str, - ); + if (children.length > 0) { + str = options.newLine + str; } - if (!hasPreviousContent) { - return str; + children += str; + + if (sourcemaps != null && str !== "" && options.newLine) { + move(sourceLocation, linesMap!, options.newLine as string); } + } - return `${css}${options.newLine}${str}`; - }, ""); + return children; case EnumToken.AtRuleNodeType: case EnumToken.RuleNodeType: - case EnumToken.KeyFramesRuleNodeType: + case EnumToken.KeyframesRuleNodeType: case EnumToken.KeyframesAtRuleNodeType: if ([EnumToken.AtRuleNodeType, EnumToken.KeyframesAtRuleNodeType].includes(data.typ) && !("chi" in data)) { return `${indent}@${(data).nam}${(data).val === "" ? "" : options.indent || " "}${ @@ -413,10 +485,23 @@ function renderAstNode( };`; } - // @ts-ignore - let children: string = (data).chi.reduce((css: string, node: AstNode) => { - let str: string; + const prelude = + (indent.length > 0 ? options.newLine : "") + + indent + + ([EnumToken.AtRuleNodeType, EnumToken.KeyframesAtRuleNodeType].includes(data.typ) + ? `@${(data).nam}${(data).val === "" ? "" : options.indent || " "}${ + (data).val + }${options.indent}{` + : (data).sel + `${options.indent}{`); + + if (sourcemaps != null) { + updateSourceMap(data, options, cache, sourcemaps, sourceLocation, linesMap!, prelude); + } + let node: AstNode; + let recordDeclarationSourceMap: boolean = data.typ == EnumToken.AtRuleNodeType; + for (let i = 0; i < (data as AstRule | AstAtRule).chi!.length; i++) { + node = (data as AstRule | AstAtRule).chi![i]; if (node.typ == EnumToken.CommentNodeType) { str = options.removeComments && @@ -424,15 +509,6 @@ function renderAstNode( ? "" : (node).val; } else if (node.typ == EnumToken.DeclarationNodeType) { - // if (!(node).nam.startsWith("--") && (node).val.length === 0) { - // // @ts-ignore - // errors.push({ - // action: "ignore", - // message: `render: invalid declaration ${JSON.stringify(node)}`, - // location: node[LOC], - // }); - // return ""; - // } str = `${(node).nam}:${options.indent}${(options.minify ? filterValues((node).val) @@ -440,15 +516,11 @@ function renderAstNode( ) .reduce(reducer, "") .trimEnd()};`; - } - // else if (node.typ == EnumToken.AtRuleNodeType && !("chi" in node)) { - // str = `${(node).val === "" ? "" : options.indent || " "}${(node).val};`; - // } - else { + } else { str = renderAstNode( node, options, - sourcemap, + sourcemaps, sourceLocation, linesMap, errors, @@ -457,74 +529,64 @@ function renderAstNode( level + 1, indents, ); - } - if (css === "") { - return str; + if (str === "") { + continue; + } + + children += str; + str = ""; + continue; } if (str === "") { - return css; + continue; } - return `${css}${options.newLine}${indentSub}${str}`; - }, ""); - - if (options.removeEmpty && children === "") { - return ""; + str = options.newLine + indentSub + str; + children += str; + + if (sourcemaps != null && str !== "") { + move(sourceLocation, linesMap!, str); + + if (node.typ == EnumToken.DeclarationNodeType && recordDeclarationSourceMap) { + // if declaration is child of at-rule, then record it + // .rule { + // @media screen { + // color: red; + // } + // } + const source = options.sourcesMap!.get(node[LOC]!.srcId) as SourceFile; + sourcemaps.push([ + ...linesMap!.getOffsets( + sourceLocation.end - str.length + options.newLine!.length + indentSub.length, + ), + node[LOC]!.srcId, + ...source!.getOffsets(node[LOC].sta), + source.getFileName(), + source.getContent(), + ]); + } + } } if (children.endsWith(";")) { children = children.slice(0, -1); + sourceLocation.end--; } - const rendered = [EnumToken.AtRuleNodeType, EnumToken.KeyframesAtRuleNodeType].includes(data.typ) - ? `@${(data).nam}${(data).val === "" ? "" : options.indent || " "}${ - (data).val - }${options.indent}{${options.newLine}` + - (children === "" ? "" : indentSub + children + options.newLine) + - indent + - `}` - : (data).sel + - `${options.indent}{${options.newLine}` + - (children === "" ? "" : indentSub + children + options.newLine) + - indent + - `}`; - - if (sourcemap != null && data[LOC] != null) { - updateSourceMap( - data as AstRuleList, - options, - cache, - sourcemap, - { ...sourceLocation }, - linesMap.clone(), - rendered, - ); + if (options.removeEmpty && children === "") { + sourceLocation.end -= prelude.length; + return ""; } - return rendered; - - // case EnumToken.CssVariableTokenType: - // case EnumToken.CssVariableImportTokenType: - // return `@value ${(data).val}:${options.indent}${filterValues( - // options.minify - // ? (data).val - // : (data).val, - // ) - // .reduce(reducer, "") - // .trim()};`; - - // case EnumToken.CssVariableDeclarationMapTokenType: - // return `@value ${filterValues((data as CssVariableMapTokenType).vars) - // .reduce((acc, curr) => acc + renderValue(curr), "") - // .trim()} from ${filterValues((data as CssVariableMapTokenType).from) - // .reduce((acc, curr) => acc + renderValue(curr), "") - // .trim()};`; + const end: string = options.newLine + indent + `}`; - // case EnumToken.InvalidDeclarationNodeType: - // case EnumToken.InvalidRuleNodeType: - // case EnumToken.InvalidAtRuleNodeType: + if (sourcemaps != null) { + move(sourceLocation, linesMap!, end); + } + + return prelude + children + end; default: return ""; @@ -535,6 +597,9 @@ function renderAstNode( * render ast token * @param token * @param options + * @param cache + * @param reducer + * @param errors * @private */ export function renderValue( diff --git a/src/lib/renderer/sourcemap/lib/encode.ts b/src/lib/renderer/sourcemap/lib/codec.ts similarity index 52% rename from src/lib/renderer/sourcemap/lib/encode.ts rename to src/lib/renderer/sourcemap/lib/codec.ts index ead4a1f1..fd45e13a 100644 --- a/src/lib/renderer/sourcemap/lib/encode.ts +++ b/src/lib/renderer/sourcemap/lib/codec.ts @@ -1,13 +1,61 @@ // from https://github.com/Rich-Harris/vlq/tree/master // credit: Rich Harris const integer_to_char: { [key: number]: string } = {}; - +const char_to_integer: { [key: string]: number } = {}; let i = 0; for (const char of 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=') { + char_to_integer[char] = i; integer_to_char[i++] = char; } +/** + * @param {string} str + */ +export function decode(str: string) { + /** @type {number[]} */ + let result = []; + + let shift = 0; + let value = 0; + + for (let i = 0; i < str.length; i += 1) { + let integer = char_to_integer[str[i]]; + + if (integer === undefined) { + throw new Error('Invalid character (' + str[i] + ')'); + } + + const has_continuation_bit = integer & 32; + + integer &= 31; + value += integer << shift; + + if (has_continuation_bit) { + shift += 5; + } else { + const should_negate = value & 1; + value >>>= 1; + + if (should_negate) { + result.push(value === 0 ? -0x80000000 : -value); + } else { + result.push(value); + } + + // reset + value = shift = 0; + } + } + + return result; +} + +/** + * + * @param value + * @returns + */ export function encode(value: number | number[]) { if (typeof value === 'number') { return encode_integer(value); diff --git a/src/lib/renderer/sourcemap/sourcemap.ts b/src/lib/renderer/sourcemap/sourcemap.ts index d846ccd5..c6dc31ca 100644 --- a/src/lib/renderer/sourcemap/sourcemap.ts +++ b/src/lib/renderer/sourcemap/sourcemap.ts @@ -1,11 +1,16 @@ import type { SourceMapObject } from "../../../@types/index.d.ts"; -import { encode } from "./lib/encode.ts"; +import { decode, encode } from "./lib/codec.ts"; /** - * Source map class - * @internal + * Generate and parse source map */ export class SourceMap { + /** + * + * @private + */ + private keys: Set = new Set(); + /** * Last location */ @@ -20,19 +25,34 @@ export class SourceMap { * Sources map * @private */ - private sourcesMap: number[] = []; + private sourcesMap: string[] = []; + + /** + * Sources content + * @private + */ + private readonly sourcesContent: Array = []; /** * Sources * @private */ - private sources: Array = []; + private readonly sources: Array = []; /** * Map * @private + * */ private map: Map = new Map(); + + /** + * Map + * @private + * + */ + private reverseMap: Map = new Map(); + /** * Line * @private @@ -40,60 +60,196 @@ export class SourceMap { private line: number = -1; /** - * Add a location - * @param source - * @param original - */ - add( - newLine: number, - newColumn: number, - srcId: number, - ln: number, - col: number, - sourceFileName: string, - sourceContent: string, - ) { - if (!this.sourcesMap.includes(srcId)) { - if (sourceFileName == null && sourceContent != null) { - sourceFileName = "data:text/css;charset=utf-8;base64," + btoa(sourceContent); + * + */ + constructor(); + /** + * + * @param sourcemaps + */ + constructor(sourcemaps: string | SourceMapObject); + /** + * + * @param sourcemaps + * @private + */ + constructor(sourcemaps?: SourceMapObject | string) { + if (typeof sourcemaps === "string") { + sourcemaps = JSON.parse(sourcemaps) as SourceMapObject; + } + + if (sourcemaps != null) { + this.sources = sourcemaps.sources?.slice() ?? []; + this.sourcesContent = sourcemaps.sourcesContent?.slice() ?? []; + const decodedMappings = sourcemaps.mappings + .split(";") + .map((mapping) => mapping.split(",").map((mapping) => decode(mapping))) as number[][][]; + + this.line = decodedMappings.length - 1; + + for (let index = 0; index < decodedMappings.length; index++) { + if ( + decodedMappings[index].length == 0 || + (decodedMappings[index].length == 1 && decodedMappings[index][0].length == 0) + ) { + continue; + } + + this.map.set(index, decodedMappings[index]); } - this.sourcesMap.push(srcId); - this.sources.push((sourceFileName as string) || null); + this.computePositions(); } + } + + /** + * Add all location + * @param maps + */ + addAll(maps: Array<[number, number, number, number, number, string | null, string | null]>): void { + for (let [newLine, newColumn, srcId, ln, col, sourceFileName, sourceContent] of maps) { + const key = `${srcId}:${ln}:${sourceFileName}:${col}:${newLine}:${newColumn}:${sourceContent}`; + const sourcemap = `${srcId}:${sourceFileName}:${sourceContent}`; + + if (this.keys.has(key)) { + continue; + } + + this.keys.add(key); - const line = newLine - 1; - let record: number[]; + if (!this.sourcesMap.includes(sourcemap)) { + this.sourcesMap.push(sourcemap); + this.sources.push((sourceFileName as string) || null); + this.sourcesContent.push((sourceFileName != null ? null : sourceContent) || null); + } + + const line: number = newLine - 1; + let record: number[]; + + if (line > this.line) { + this.line = line; + } - if (line > this.line) { - this.line = line; + if (!this.map.has(line)) { + record = [Math.max(0, newColumn - 1), this.sourcesMap.indexOf(sourcemap), ln - 1, col - 1]; + + this.map.set(line, [record]); + } else { + const arr: number[][] = this.map.get(line) as number[][]; + + record = [ + Math.max(0, newColumn - 1) - arr[0][0], + this.sourcesMap.indexOf(sourcemap) - arr[0][1], + ln - 1, + col - 1, + ]; + arr.push(record); + } + + if (this.lastLocation != null) { + record[2] -= this.lastLocation.ln - 1; + record[3] -= this.lastLocation.col - 1; + } + + this.lastLocation ??= { ln, col }; + this.lastLocation.ln = ln; + this.lastLocation.col = col; } + } + + /** + * compute original positions + */ + computePositions(): void { + this.reverseMap.clear(); + let sourceFileIndex: number = 0; // second field + let sourceCodeLine: number = 0; // third field + let sourceCodeColumn: number = 0; // fourth field + let nameIndex: number = 0; // fifth field + let generatedCodeColumn: number; + let result: number[]; + + // mappings to original source + for (let [i, line] of this.map.entries()) { + if (line.length === 0 || (line.length === 1 && line[0].length === 0)) { + continue; + } + + generatedCodeColumn = line[0][0]; // first field - reset each time + + line = line + .map((segment: number[], index: number, array: number[][]) => { + if (segment.length === 0) { + return []; + } + + generatedCodeColumn = index == 0 ? segment[0] : segment[0] + array[0][0]; + + result = [generatedCodeColumn]; + + if (segment.length <= 1) { + return result; + } - if (!this.map.has(line)) { - record = [Math.max(0, newColumn - 1), this.sourcesMap.indexOf(srcId), ln - 1, col - 1]; + sourceFileIndex = index == 0 ? segment[1] : segment[1] + array[0][1]; + sourceCodeLine += segment[2]; + sourceCodeColumn += segment[3]; - this.map.set(line, [record]); - } else { - const arr: number[][] = this.map.get(line); + result.push(sourceFileIndex, sourceCodeLine, sourceCodeColumn); - record = [ - Math.max(0, newColumn - 1 - arr[0][0]), - this.sourcesMap.indexOf(srcId) - arr[0][1], - ln - 1, - col - 1, - ]; - arr.push(record); + if (segment.length === 5) { + nameIndex += segment[4]; + result.push(nameIndex); + } + + return result; + }) + .sort((a, b) => { + if (a[1] !== b[1]) { + return a[1] - b[1]; + } + + return a[0] - b[0]; + }); + + if (line.length == 0 || (line.length == 1 && line[0].length == 0)) { + continue; + } + + this.reverseMap.set(i, line); } + } - if (this.lastLocation != null) { - record[2] -= this.lastLocation.ln - 1; - record[3] -= this.lastLocation.col - 1; + /** + * retrieve original sources, lines and columns + * @param line generated line + * @param column generated column + */ + find(line: number, column: number): Array<[string | null, number, number, string | null]> | null { + if (!this.reverseMap.has(--line)) { + return null; } - this.lastLocation ??= { ln, col }; + column--; + const result: Array<[string | null, number, number, string | null]> = []; - this.lastLocation.ln = ln; - this.lastLocation.col = col; + for (const record of this.reverseMap.get(line)!) { + if (record.length == 0 || record[0] < column) { + continue; + } + if (record[0] > column) { + break; + } + + result.push([ + this.sources?.[record[1]] ?? null, + record[2] + 1, + record[3] + 1, + this.sourcesContent?.[record[1]] ?? null, + ]); + } + + return result.length == 0 ? null : result; } /** @@ -128,7 +284,15 @@ export class SourceMap { return { version: this.version, sources: this.sources.slice(), + sourcesContent: this.sourcesContent?.slice(), mappings: mappings.join(";"), }; } + + /** + * to string + */ + toString(): string { + return JSON.stringify(this); + } } diff --git a/src/lib/validation/match.ts b/src/lib/validation/match.ts index a32f0795..9341109d 100644 --- a/src/lib/validation/match.ts +++ b/src/lib/validation/match.ts @@ -49,12 +49,20 @@ const config: ValidationConfiguration = getSyntaxConfig(); // @ts-expect-error const allValues = config.declarations.all!.syntax.split(/[\s|]+/g) as string[]; +/** + * @type {Array.} + */ export const funcTypes: EnumToken[] = [ ...tokensfuncDefMap.values(), EnumToken.FunctionTokenType, EnumToken.PseudoClassFuncTokenType, ]; +/** + * trim leading and trailing whitespace + * @param tokens + * @returns + */ export function trimArray(tokens: Token[]): Token[] { while (tokens[0]?.typ === EnumToken.WhitespaceTokenType) { tokens.shift(); @@ -67,6 +75,11 @@ export function trimArray(tokens: Token[]): Token[] { return tokens; } +/** + * is a media feature + * @param featureName + * @returns + */ export function isMFName(featureName: string): boolean { // @ts-expect-error return featureName.startsWith("--") || config.mediaFeatures[featureName.toLowerCase()] != null; @@ -191,6 +204,11 @@ export function isMFValue( }; } +/** + * create validation context + * @param tokens + * @returns + */ export function createValidationContext(tokens: Token[]): ValidationContext { tokens = trimArray(tokens.filter((t) => t.typ !== EnumToken.CommentTokenType)); @@ -392,6 +410,14 @@ export function createValidationContext(tokens: Token[]): ValidationContext { return token; } +/** + * match selector syntax + * @param stream + * @param errors + * @param options + * @param nested + * @returns + */ export function matchSelectorSyntax( stream: Token[], errors: ErrorDescription[], @@ -966,6 +992,13 @@ export function matchSelectorSyntax( return { success, errors }; } +/** + * matches all syntaxes + * @param syntaxes + * @param context + * @param options + * @returns + */ export function matchAllSyntaxes( syntaxes: ValidationToken[] | null, context: ValidationContext, @@ -1018,6 +1051,13 @@ export function matchAllSyntaxes( }; } +/** + * matches a list of syntaxes + * @param syntax + * @param context + * @param options + * @returns + */ function matchListSyntax( syntax: ValidationToken, context: ValidationContext, @@ -1079,6 +1119,13 @@ function matchListSyntax( }; } +/** + * matches a list of syntaxes + * @param syntax + * @param context + * @param options + * @returns + */ export function matchOccurenceSyntax( syntax: ValidationToken, context: ValidationContext, @@ -1134,6 +1181,13 @@ export function matchOccurenceSyntax( return result as ValidationMatch; } +/** + * matches a list of syntaxes + * @param syntaxes + * @param context + * @param options + * @returns + */ function matchSyntax( syntaxes: ValidationToken[] | null, context: ValidationContext, @@ -1323,7 +1377,7 @@ function matchSyntax( result = matchListSyntax(syntaxes[i], context.slice(), options); if (result.success) { - (options.visited!.get(token) as Set)!.delete(syntaxes[i]); + (options.visited!.get(token) as Set)?.delete?.(syntaxes[i]); if (result.context.done()) { context.end(); @@ -1967,6 +2021,13 @@ function matchSyntax( }; } +/** + * matches a column of syntaxes + * @param syntax + * @param context + * @param options + * @returns + */ function matchColumnSyntax( syntax: ValidationColumnToken, context: ValidationContext, @@ -2017,6 +2078,13 @@ function matchColumnSyntax( }; } +/** + * matches an ampersand of syntaxes + * @param syntax + * @param context + * @param options + * @returns + */ function matchAmpersandSyntax( syntax: ValidationAmpersandToken, context: ValidationContext, @@ -2046,6 +2114,13 @@ function matchAmpersandSyntax( return result!; } +/** + * matches a property + * @param property + * @param context + * @param options + * @returns + */ function matchProperty( property: ValidationPropertyToken, context: ValidationContext, @@ -2998,6 +3073,13 @@ function matchProperty( }; } +/** + * matches a repeatable syntax + * @param syntax + * @param context + * @param options + * @returns + */ function matchRepeatableSyntax( syntax: ValidationToken, context: ValidationContext, diff --git a/src/node.ts b/src/node.ts index 401b4403..49fdddcc 100644 --- a/src/node.ts +++ b/src/node.ts @@ -27,6 +27,7 @@ import { ResponseType } from "./types.ts"; import { resolve as resolvePath } from "node:path"; import { SourceFile } from "./lib/parser/source.ts"; import { cwd } from "node:process"; +import { parseResult, validateSyncArguments } from "./utils/sync.ts"; export type * from "./@types/index.d.ts"; export type * from "./@types/ast.d.ts"; @@ -217,10 +218,10 @@ export const parseFile = deprecate( * * ```ts * - * import {parse} from '@tbela99/css-parser'; + * import {parseSync} from '@tbela99/css-parser'; * * // css string - * let result = await parse(css, {nestingRules: true}); + * let result = parseSync(css, {nestingRules: true}); * console.log(result.ast); * ``` * @@ -230,17 +231,16 @@ export function parseSync(stream: string, options?: ParserSyncOptions): ParseRes /** * Parse css string - * @param stream * @param options * * Parsing a string * * ```ts * - * import {parse} from '@tbela99/css-parser'; + * import {parseSync} from '@tbela99/css-parser'; * * // css string - * let result = await parse({input: css, nestingRules: true}); + * let result = parseSync({input: css, nestingRules: true}); * console.log(result.ast); * ``` * @@ -251,15 +251,16 @@ export function parseSync(options: ParseInputOptions & ParserSyncOptions): Parse /** * Parse css * @param args + * @private * * Parsing a string * * ```ts * - * import {parse} from '@tbela99/css-parser'; + * import {parseSync} from '@tbela99/css-parser'; * * // css string - * let result = await parse(css, {nestingRules: true}); + * let result = parseSync(css, {nestingRules: true}); * console.log(result.ast); * ``` * @@ -281,9 +282,14 @@ export function parseSync( stream = input; } + if (options != null) { + validateSyncArguments(options); + } + options ??= {}; + options.src ??= ""; - options.sourcesMap ??= new Map; + options.sourcesMap ??= new Map(); Object.assign(options, { resolve, @@ -310,10 +316,8 @@ export function parseSync( currentPosition: -1, } as ParseInfo; - const result = doParseSync(tokenize(options.parseInfo), options); - - const { revMapping, ...res } = result; - return res as ParseResult; + const result = doParseSync(tokenize(options.parseInfo), options) as ParseResult; + return !options.module && !options.inputSourceMap && !options.sourcemap ? result : parseResult(result, options); } /** @@ -324,10 +328,10 @@ export function parseSync( * * ```ts * - * import {transform} from '@tbela99/css-parser'; + * import {transformSync} from '@tbela99/css-parser'; * * // css string - * const result = await transform(css, {beautify: true}); + * const result = transformSync(css, {beautify: true}); * console.log(result.code); * ``` * @@ -340,10 +344,10 @@ export function transformSync(css: string, options?: TransformSyncOptions): Tran * * ```ts * - * import {transform} from '@tbela99/css-parser'; + * import {transformSync} from '@tbela99/css-parser'; * * // css string - * const result = await transform({input: css, beautify: true}); + * const result = transformSync({input: css, beautify: true}); * console.log(result.code); * ``` * @@ -352,23 +356,23 @@ export function transformSync(options: ParseInputOptions & TransformSyncOptions) /** * Transform css - * @param css - * @param options * * ```ts * - * import {transform} from '@tbela99/css-parser'; + * import {transformSync} from '@tbela99/css-parser'; * * // css string - * const result = await transform(css); + * const result = transformSync(css); * console.log(result.code); * ``` * + * @param args + * @private */ export function transformSync( ...args: [string, TransformSyncOptions?] | [ParseInputOptions & TransformSyncOptions] ): TransformResult { - let options: (ParseInputOptions & TransformSyncOptions) | TransformSyncOptions; + let options: (ParseInputStreamOptions & TransformSyncOptions) | TransformSyncOptions; let stream: string; if (typeof args[0] === "string") { @@ -427,12 +431,10 @@ export function transformSync( } /** - * Parse css + * Parse CSS * @param stream * @param options * - * @throws Error file not found - * * Example: * * ```ts @@ -444,7 +446,7 @@ export function transformSync( * console.log(result.ast); * ``` * - * parsing a Readable stream + * parsing a ReadableStream * * ```ts * @@ -459,7 +461,7 @@ export function transformSync( * console.log(result.ast); * ``` * - * Example using fetch and readable stream + * Parsing a file as a ReadableStream * * ```ts * @@ -476,7 +478,6 @@ export async function parse(stream: string | ReadableStream, options /** * Parse css - * @param stream * @param options * * @throws Error file not found @@ -511,7 +512,6 @@ export async function parse(options: ParseInputFileOptions & ParserOptions): Pro /** * Parse css - * @param stream * @param options * * Parsing a string @@ -558,6 +558,7 @@ export async function parse(options: ParseInputStreamOptions & ParserOptions): P * @param args * * @throws Error file not found + * @private * * Parsing a string * @@ -629,7 +630,7 @@ export async function parse( options ??= {}; options.src ??= ""; - options.sourcesMap ??= new Map; + options.sourcesMap ??= new Map(); Object.assign(options, { load, @@ -641,7 +642,6 @@ export async function parse( options.src = resolve(options.src!, options.cwd).relative; if (options.source == null) { - const source = new SourceFile(typeof stream == "string" ? stream : "", [], options.src); options.sourcesMap.set(source.id, source); options.source = source; @@ -661,14 +661,11 @@ export async function parse( return doParse( stream instanceof ReadableStream ? tokenizeStream(stream, options.parseInfo) : tokenize(options.parseInfo), options, - ).then((result) => { - const { revMapping, ...res } = result; - return res as ParseResult; - }); + ).then((result) => (!options.module && !options.inputSourceMap ? result : parseResult(result, options))); } /** - * Transform css file + * Transform CSS file * @param file url or path * @param options * @param asStream load file as stream @@ -703,7 +700,7 @@ export const transformFile = deprecate( ) as (file: string, options?: TransformOptions, asStream?: boolean) => Promise; /** - * Transform css + * Transform CSS * @param css * @param options * @@ -752,7 +749,6 @@ export async function transform( /** * Transform css - * @param css * @param options * * Parsing a string @@ -781,7 +777,7 @@ export async function transform( * console.log(result.code); * ``` * - * Example using fetch + * Parse a file as a ReadableStream * * ```ts * @@ -797,44 +793,16 @@ export async function transform( export async function transform(options: ParseInputStreamOptions & TransformOptions): Promise; /** - * Transform css - * @param css + * Transform CSS * @param options * - * Parsing a string - * - * ```ts - * - * import {transform} from '@tbela99/css-parser'; - * - * // css string - * const result = await transform({input: css}); - * console.log(result.code); - * ``` - * - * Parsing a Readable stream + * Parsing a file * * ```ts * * import {transform} from '@tbela99/css-parser'; - * import {Readable} from "node:stream"; - * - * // usage: node index.ts < styles.css or cat styles.css | node index.ts - * - * const readableStream = Readable.toWeb(process.stdin); - * const result = await transform( {input: readableStream, beautify: true}); - * - * console.log(result.code); - * ``` - * - * Example using fetch - * - * ```ts - * - * import {transform} from '@tbela99/css-parser'; - * - * result = await transform({file: 'https://docs.deno.com/styles.css', beautify: true}); * + * const result = await transform( {file: 'https://docs.deno.com/styles.css', beautify: true}); * console.log(result.code); * ``` */ @@ -843,8 +811,6 @@ export async function transform(options: ParseInputFileOptions & TransformOption /** * Transform css - * @param css - * @param options * * Parsing a string * @@ -883,6 +849,8 @@ export async function transform(options: ParseInputFileOptions & TransformOption * * console.log(result.code); * ``` + * @param args + * @private */ export async function transform( ...args: diff --git a/src/utils/sync.ts b/src/utils/sync.ts new file mode 100644 index 00000000..e21ea207 --- /dev/null +++ b/src/utils/sync.ts @@ -0,0 +1,74 @@ +import type { AstComment, ParseResult, ParserOptions, ParserSyncOptions } from "../@types/index.js"; +import { EnumToken } from "../lib/ast/types.ts"; + +/** + * parse result. process input sourcemap + * @param result + * @param options + * @returns + * @private + */ +export function parseResult(result: ParseResult, options: ParserOptions): ParseResult { + if (options.sourcemap != null && options!.source!.getInputSourceMap() == null) { + if (options.inputSourceMap != null) { + options!.source!.setInputSourceMap(options.inputSourceMap); + } else { + // extract inline source map from the input CSS + const token = result.ast.chi.at(-1); + + if ( + token?.typ == EnumToken.CommentTokenType && + (token as AstComment).val.startsWith("/*# sourceMappingURL=") + ) { + const data = (token as AstComment).val.slice(21, -2).trim(); + let sourcemap: string; + let encoding: string = ""; + + if (data.startsWith("data:")) { + let offset: number = data.indexOf(",") + 1; + + if (offset == 0) { + offset = data.lastIndexOf(";") + 1; + } else { + encoding = data.slice(data.lastIndexOf(";") + 1, offset - 1); + } + + if (encoding == "base64") { + sourcemap = atob(data.slice(offset)); + } else { + sourcemap = decodeURIComponent(data.slice(offset)); + } + + options!.source!.setInputSourceMap(sourcemap); + } + } + } + } + + if (options.module) { + const { revMapping, ...res } = result; + return res as ParseResult; + } + + return result; +} + +export function validateSyncArguments(options: ParserSyncOptions, prefix: string = "options."): void { + const args = Object.entries(options); + + let i: number; + + for (i = 0; i < args.length; i++) { + const [key, value] = args[i]; + + if (typeof value == "function") { + if (value instanceof Promise || Object.getPrototypeOf(value).constructor.name == "AsyncFunction") { + throw new Error( + `[${prefix + key}]: Async functions are not supported in sync mode. Use parse() or transform() instead.`, + ); + } + } else if (value != null && typeof value == "object") { + validateSyncArguments(value, prefix + key + "."); + } + } +} diff --git a/src/web.ts b/src/web.ts index 059a280e..69abccab 100644 --- a/src/web.ts +++ b/src/web.ts @@ -22,6 +22,7 @@ import { tokenize, tokenizeStream } from "./lib/parser/tokenize.ts"; import { dirname, matchUrl, resolve } from "./lib/fs/resolve.ts"; import { ResponseType } from "./types.ts"; import { SourceFile } from "./lib/parser/source.ts"; +import { parseResult, validateSyncArguments } from "./utils/sync.ts"; export type * from "./@types/index.d.ts"; export type * from "./@types/ast.d.ts"; @@ -72,7 +73,7 @@ export { dirname, resolve, ResponseType }; * @throws Error file not found * * ```ts - * import {load, ResponseType} from '@tbela99/css-parser'; + * import {load, ResponseType} from '@tbela99/css-parser/web'; * const result = await load(file, '.', ResponseType.ArrayBuffer) as ArrayBuffer; * ``` */ @@ -122,7 +123,7 @@ export async function load( * * ```ts * - * import {render, ColorType} from '@tbela99/css-parser'; + * import {render, ColorType} from '@tbela99/css-parser/web'; * * const css = 'body { color: color(from hsl(0 100% 50%) xyz x y z); }'; * const parseResult = await parse(css); @@ -207,33 +208,58 @@ export async function parseFile( * * ```ts * - * import {parse} from '@tbela99/css-parser'; + * import {parseSync} from '@tbela99/css-parser/web'; * * // css string - * let result = await parse(css, {nestingRules: true}); + * let result = await parseSync(css, {nestingRules: true}); * console.log(result.ast); * ``` * + * parsing a ReadableStream + * + * ```ts + * + * import {parseSync} from '@tbela99/css-parser/web'; + * + * const response = await fetch('https://docs.deno.com/styles.css'); + * + * // css string + * const result = parseSync(response.body, {beautify: true}); + * console.log(result.code); + * ``` + * */ export function parseSync(stream: string, options?: ParserSyncOptions): ParseResult; /** * Parse css string - * @param stream * @param options * * Parsing a string * * ```ts * - * import {parse} from '@tbela99/css-parser'; + * import {parseSync} from '@tbela99/css-parser/web'; * * // css string - * let result = await parse({input: css, nestingRules: true}); + * let result = parseSync({input: css, nestingRules: true}); * console.log(result.ast); * ``` * + * parsing a ReadableStream + * + * ```ts + * + * import {parseSync} from '@tbela99/css-parser/web'; + * + * const response = await fetch('https://docs.deno.com/styles.css'); + * + * // css string + * const result = parseSync({input: response.body, beautify: true}); + * console.log(result.code); + * ``` + * */ export function parseSync(options: ParseInputOptions & ParserSyncOptions): ParseResult; @@ -241,15 +267,16 @@ export function parseSync(options: ParseInputOptions & ParserSyncOptions): Parse /** * Parse css * @param args + * @private * * Parsing a string * * ```ts * - * import {parse} from '@tbela99/css-parser'; + * import {parseSync} from '@tbela99/css-parser/web'; * * // css string - * let result = await parse(css, {nestingRules: true}); + * let result = await parseSync(css, {nestingRules: true}); * console.log(result.ast); * ``` * @@ -271,9 +298,14 @@ export function parseSync( stream = input; } + if (options != null) { + validateSyncArguments(options); + } + options ??= {}; + options.src ??= ""; - options.sourcesMap ??= new Map; + options.sourcesMap ??= new Map(); Object.assign(options, { resolve, @@ -287,7 +319,7 @@ export function parseSync( options.src = resolve(options.src!, options.cwd).relative; if (options.source == null) { - const source = new SourceFile(typeof stream == "string" ? stream : "", [], options.src) + const source = new SourceFile(typeof stream == "string" ? stream : "", [], options.src); options.sourcesMap.set(source.id, source); options.source = source; } @@ -304,23 +336,22 @@ export function parseSync( } as ParseInfo; const result = doParseSync(tokenize(options.parseInfo), options); - - const { revMapping, ...res } = result; - return res as ParseResult; + return !options.module && !options.inputSourceMap && !options.sourcemap ? result : parseResult(result, options); } /** * Transform css * @param css * @param options + * @private * * * ```ts * - * import {transform} from '@tbela99/css-parser'; + * import {transformSync} from '@tbela99/css-parser/web'; * * // css string - * const result = await transform(css, {beautify: true}); + * const result = transformSync(css, {beautify: true}); * console.log(result.code); * ``` * @@ -331,12 +362,14 @@ export function transformSync(css: string, options?: TransformSyncOptions): Tran * Transform css * @param options * + * parsing a string + * * ```ts * - * import {transform} from '@tbela99/css-parser'; + * import {transformSync} from '@tbela99/css-parser/web'; * * // css string - * const result = await transform({input: css, beautify: true}); + * const result = transformSync({input: css, beautify: true}); * console.log(result.code); * ``` * @@ -344,25 +377,25 @@ export function transformSync(css: string, options?: TransformSyncOptions): Tran export function transformSync(options: ParseInputOptions & TransformSyncOptions): TransformResult; /** - * Transform css - * @param css - * @param options + * Transform CSS * * ```ts * - * import {transform} from '@tbela99/css-parser'; + * import {transformSync} from '@tbela99/css-parser/web'; * * // css string - * const result = await transform(css); + * const result = transformSync(css); * console.log(result.code); * ``` * + * @param args + * @private */ export function transformSync( ...args: [string, TransformSyncOptions?] | [ParseInputOptions & TransformSyncOptions] ): TransformResult { let options: (ParseInputOptions & TransformSyncOptions) | TransformSyncOptions; - let stream: string; + let stream: string | ReadableStream; if (typeof args[0] === "string") { stream = args[0]; @@ -419,15 +452,103 @@ export function transformSync( } as TransformResult; } +/** + * Parse CSS + * @param stream + * @param options + * + * Example: + * + * ```ts + * + * import {parse} from '@tbela99/css-parser/web'; + * + * // css string + * let result = await parse(css); + * console.log(result.ast); + * ``` + * + * Parse a file as a ReadableStream + * + * ```ts + * + * import {parse} from '@tbela99/css-parser/web'; + * + * const response = await fetch('https://docs.deno.com/styles.css'); + * const result = await parse(response.body, {beautify: true}); + * + * console.log(result.ast); + * ``` + */ + export async function parse(stream: string | ReadableStream, options?: ParserOptions): Promise; + +/** + * Parse css + * @param options + * + * @throws Error file not found + * + * Parsing a file + * + * ```ts + * + * import {parse} from '@tbela99/css-parser/web'; + * + * const file = 'https://docs.deno.com/styles.css'; + * // css file or url + * let result = await parse({file}); + * console.log(result.ast); + * ``` + * + * Parsing a file as stream + * + * ```ts + * + * import {parse} from '@tbela99/css-parser/web'; + * + * const file = 'https://docs.deno.com/styles.css'; + * let result = await parse({file, asStream: true, beautify: true}); + * + * console.log(result.ast); + * ``` + * + */ export async function parse(options: ParseInputFileOptions & ParserOptions): Promise; -export async function parse(options: ParseInputStreamOptions & ParserOptions): Promise; /** * Parse css - * @param stream * @param options * + * Parsing a string + * + * ```ts + * + * import {parse} from '@tbela99/css-parser/web'; + * + * // css string + * let result = await parse({input:css}); + * console.log(result.ast); + * ``` + * + * Parsing a Readable stream + * Parsing a file as a ReadableStream + * + * ```ts + * + * import {parse} from '@tbela99/css-parser/web'; + * + * const response = await fetch('https://docs.deno.com/styles.css'); + * const result = await parse({input: response.body, beautify: true}); + * + * console.log(result.ast); + * ``` + */ +export async function parse(options: ParseInputStreamOptions & ParserOptions): Promise; + +/** + * Parse css + * * Example: * * ```ts @@ -450,6 +571,8 @@ export async function parse(options: ParseInputStreamOptions & ParserOptions): P * * console.log(result.ast); * ``` + * @param args + * @private */ export async function parse( ...args: @@ -484,7 +607,7 @@ export async function parse( options ??= {}; options.src ??= ""; - options.sourcesMap ??= new Map; + options.sourcesMap ??= new Map(); Object.assign(options, { load, @@ -499,7 +622,7 @@ export async function parse( options.src = resolve(options.src!, options.cwd).relative; if (options.source == null) { - const source = new SourceFile(typeof stream === "string" ? stream : "", [], options.src) + const source = new SourceFile(typeof stream === "string" ? stream : "", [], options.src); options.sourcesMap.set(source.id, source); options.source = source; } @@ -517,14 +640,11 @@ export async function parse( return doParse( stream instanceof ReadableStream ? tokenizeStream(stream, options.parseInfo) : tokenize(options.parseInfo), options, - ).then((result) => { - const { revMapping, ...res } = result; - return res as ParseResult; - }); + ).then((result) => (!options.module && !options.inputSourceMap ? result : parseResult(result, options))); } /** - * Transform css file + * Transform CSS file * @param file url or path * @param options * @param asStream load file as stream @@ -561,19 +681,88 @@ export async function transformFile( }); } +/** + * Transform CSS + * @param css + * @param options + * + * Parsing a string + * + * ```ts + * + * import {transform} from '@tbela99/css-parser/web'; + * + * // css string + * const result = await transform(css); + * console.log(result.code); + * ``` + * + * Parsing a Readable stream + * + * ```ts + * + * import {transform} from '@tbela99/css-parser/web'; + * + * const response = await fetch('https://docs.deno.com/styles.css'); + * result = await transform(response.body, {beautify: true}); + * + * console.log(result.code); + * ``` + */ export async function transform( css: string | ReadableStream, options: TransformOptions, ): Promise; -export async function transform(options: ParseInputFileOptions & TransformOptions): Promise; +/** + * Transform CSS + * @param options + * + * Parsing a string + * + * ```ts + * + * import {transform} from '@tbela99/css-parser/web'; + * + * // css string + * const result = await transform({input: css}); // or transform(css) + * console.log(result.code); + * ``` + * + * Parsing a file as a Readable stream + * + * ```ts + * + * import {transform} from '@tbela99/css-parser/web'; + * + * const response = await fetch('https://docs.deno.com/styles.css'); + * const result = await transform( {input: response.body, beautify: true}); + * + * console.log(result.code); + * ``` + * + */ export async function transform(options: ParseInputStreamOptions & TransformOptions): Promise; /** - * Transform css - * @param css + * Transform CSS * @param options * + * Parsing a file + * + * ```ts + * + * import {transform} from '@tbela99/css-parser/web'; + * + * const result = await transform( {file: 'https://docs.deno.com/styles.css', beautify: true}); + * console.log(result.code); + * ``` + */ +export async function transform(options: ParseInputFileOptions & TransformOptions): Promise; + +/** + * Transform css + * * Example: * * ```ts @@ -590,6 +779,8 @@ export async function transform(options: ParseInputStreamOptions & TransformOpti * * console.log(result.code); * ``` + * @param args + * @private */ export async function transform( ...args: diff --git a/test/allFiles.js b/test/allFiles.js index 841590ef..e82da112 100644 --- a/test/allFiles.js +++ b/test/allFiles.js @@ -20,18 +20,18 @@ for (const file of await readdir(baseDir)) { message = ''; const result = await load(baseDir + file, import.meta.dirname).then(css => transform(css, { - src: baseDir + file, minify: true, sourcemap: true, + src: baseDir + file, minify: true, removePrefix: true, nestingRules: true, resolveImport: true, - sourcemap: true, + // sourcemap: true, validation: true })); message += `[inputSize]: ${toFileSize(result.stats.bytesIn)}\n `; message += `[outputSize]: ${toFileSize(result.stats.bytesOut)}\n `; message += `[ratio]: ${(100 * (1 - result.stats.bytesOut / result.stats.bytesIn)).toFixed(2)}%\n `; - message += `[sourcemap]: ${JSON.stringify(result.map.toJSON()).length}\n `; + // message += `[sourcemap]: ${JSON.stringify(result.map.toJSON()).length}\n `; for (const key in result.stats) { diff --git a/test/specs/code/block.js b/test/specs/code/block.js index eb12a38c..5250c7bb 100644 --- a/test/specs/code/block.js +++ b/test/specs/code/block.js @@ -1134,10 +1134,14 @@ font-family: random-item(--x, {Times, serif}, {Arial, sans-serif}, {Courier, mon }); it("stream file #50", async () => { - const dir = resolve((import.meta.dirname ?? dirname(new URL(import.meta.url).pathname)) + "/../..").absolute; + // const dir = resolve((import.meta.dirname ?? dirname(new URL(import.meta.url).pathname)) + "/../..").absolute; // const file = `@import '${dir}/files/css/line-awesome.css`; + + const url = new URL(import.meta.url); + url.pathname = dirname(url.pathname) + "/../../files/css/bootstrap-4.css"; + const options = { - file: `${dir}/files/css/bootstrap-4.css`, + file: url.pathname , beautify: true, }; @@ -1149,10 +1153,12 @@ font-family: random-item(--x, {Times, serif}, {Arial, sans-serif}, {Courier, mon }); it("stream file #51", async () => { - const dir = resolve((import.meta.dirname ?? dirname(new URL(import.meta.url).pathname)) + "/../..").absolute; - // const file = `@import '${dir}/files/css/line-awesome.css`; + + const url = new URL(import.meta.url); + url.pathname = dirname(url.pathname) + "/../../files/css/tailwind.css"; + const options = { - file: `${dir}/files/css/tailwind.css`, + file: url.pathname, beautify: true, }; diff --git a/test/specs/code/import1.js b/test/specs/code/import1.js index 891f287e..485d7bd6 100644 --- a/test/specs/code/import1.js +++ b/test/specs/code/import1.js @@ -1,8 +1,9 @@ export function run(describe, expect, it, transform, parse, render, dirname) { + const url = new URL(dirname(import.meta.url) + '/../../files/css/color.css?v=1'); const atRule = ` -@import '${(import.meta.dirname ?? dirname(new URL(import.meta.url).pathname)).replace(/\\/g, '/') + '/../../files/css/color.css?v=1'}'; +@import '${url.pathname}'; abbr[title], abbr[data-original-title] { text-decoration: underline dotted; -webkit-text-decoration: underline dotted; diff --git a/test/specs/code/modules.js b/test/specs/code/modules.js index 3ca56f6b..d038667e 100644 --- a/test/specs/code/modules.js +++ b/test/specs/code/modules.js @@ -1,6 +1,6 @@ import { ColorType, EnumToken, ModuleCaseTransformEnum, ModuleScopeEnumOptions } from "../../../dist/lib/ast/types.js"; -export function run(describe, expect, it, transform, parse, render, dirname, readFile) { +export function run(describe, expect, it, transform, parse, render, dirname, readFile, resolve, ColorType, EnumToken, ModuleCaseTransformEnum, ModuleScopeEnumOptions, transformSync, parseSync) { describe("css modules", function () { it("module #1", function () { return transform( @@ -99,6 +99,9 @@ export function run(describe, expect, it, transform, parse, render, dirname, rea }); it("module #4", function () { + + const url = new URL(dirname(import.meta.url) + '/../../css-modules/mixins.css'); + return transform( ` .goal .bg-indigo { @@ -107,7 +110,7 @@ export function run(describe, expect, it, transform, parse, render, dirname, rea .indigo-white { composes: bg-indigo; -composes: button cell title from "${(import.meta.dirname ?? dirname(new URL(import.meta.url).pathname)).replaceAll("\\", "/")}/../../css-modules/mixins.css"; color: white; +composes: button cell title from "${url.pathname}"; color: white; } `, { @@ -119,7 +122,7 @@ composes: button cell title from "${(import.meta.dirname ?? dirname(new URL(impo goal: "goal_r7bhp", "bg-indigo": "bg-indigo_gy28g", "indigo-white": - "indigo-white_wims0 bg-indigo_gy28g button_rptz7_mixins cell_dptz7_mixins title_fnrx5_mixins", + "indigo-white_wims0 bg-indigo_gy28g button_egkqy_mixins cell_s04ai_mixins title_seiow_mixins", }); expect(result.code).equals(`.goal_r7bhp .bg-indigo_gy28g { @@ -610,6 +613,9 @@ a span { }); it("module mode ICSS #17", function () { + + + const url = new URL(dirname(import.meta.url) + '/../../css-modules/mixins.css'); return transform( ` @@ -626,7 +632,7 @@ a span { .indigo-white { composes: bg-indigo; - composes: button cell title from "${(import.meta.dirname ?? dirname(new URL(import.meta.url).pathname)).replaceAll("\\", "/")}/../../css-modules/mixins.css"; color: white; + composes: button cell title from "${url.pathname}"; color: white; } `, { @@ -719,11 +725,13 @@ a span { // }); it("module import variables #19", function () { + + const url = new URL(dirname(import.meta.url) + '/../../css-modules/color.css'); return transform( ` /* import your colors... */ - @value colors: "${(import.meta.dirname ?? dirname(new URL(import.meta.url).pathname)).replaceAll("\\", "/")}/../../css-modules/color.css"; + @value colors: "${url.pathname}"; @value blue, red, green from colors; .button { @@ -922,5 +930,37 @@ a span { }`); }); }); + + it("module #24", function () { + const result = transformSync( + ` +.goal .bg-indigo { + background: indigo; +} + +.indigo-white { + composes: bg-indigo title; + color: white; +} +`, + { + module: true, + beautify: true, + }, + ); + + expect(result.mapping).deep.equals({ + goal: "goal_r7bhp", + "bg-indigo": "bg-indigo_gy28g", + "indigo-white": "indigo-white_wims0 bg-indigo_gy28g title_qw06e", + title: "title_qw06e", + }); + expect(result.code).equals(`.goal_r7bhp .bg-indigo_gy28g { + background: indigo +} +.indigo-white_wims0 { + color: #fff +}`); + }); }); } diff --git a/test/specs/code/sourcemaps.js b/test/specs/code/sourcemaps.js index 9badb040..3080b219 100644 --- a/test/specs/code/sourcemaps.js +++ b/test/specs/code/sourcemaps.js @@ -1,20 +1,66 @@ -export function run(describe, expect, it, transform, parse, render, dirname, readFile, resolve) { +import { ColorType, EnumToken, ModuleCaseTransformEnum, ModuleScopeEnumOptions } from "../../../dist/lib/ast/types.js"; - // describe('sourcemap', function () { - - // const dir = resolve((import.meta.dirname ?? dirname(new URL(import.meta.url).pathname)) + '/../..').absolute; - // // const file = `@import '${dir}/files/css/line-awesome.css`; - // const options = { - // file: `${dir}/files/css/line-awesome.css`, - // sourcemap: 'inline', - // }; - - // it('sourcemap file #1', async () => { - - // return transform(options).then(async result => { - - // return readFile(`${dir}/files/sourcemap/line-awesome-sourcemap.css`, {encoding: 'utf-8'}).then(expected => expect(`/*# sourceMappingURL=${result.map.toUrl()} */`).equals(expected.trim())); - // }); - // }); - // }); -} \ No newline at end of file +export function run( + describe, + expect, + it, + transform, + parse, + render, + dirname, + readFile, + resolve, + ColorType, + EnumToken, + ModuleCaseTransformEnum, + ModuleScopeEnumOptions, + transformSync, + parseSync, +) { + describe("sourcemap", function () { + const url = new URL(dirname(import.meta.url) + "/../../files/css/nested.css"); // const file = `@import '${dir}/files/css/line-awesome.css`; + + const options = { + input: ` +@import '${url.pathname}'; +h1 { + text-transform: uppercase; +} +button { + background: linear-gradient( + if(media(min-width: 768px): to right; else: to bottom), + if(style(--dark-mode): #333; else: #fff), + if(style(--dark-mode): #000; else: #ccc) + ); +} + `, + beautify: true, + sourcemap: "inline", + expandIfSyntax: true, + resolveImport: true, + output: "test/sourcemap.html", + }; + + it("sourcemap unminified #1", async () => { + return transform(options).then(async (result) => { + result.map.computePositions(); + let positions = result.map.find(39, 3); + expect(positions?.length == 1 && positions[0].slice(0, 3)).deep.equals([null, 7, 3]); + }); + }); + + it("sourcemap minified #2", async () => { + return transform(options).then(async (result) => { + const result2 = transformSync({ + input: result.code, + sourcemap: "inline", + output: "test/sourcemap.html", + }); + + result2.map.computePositions(); + const positions = result2.map.find(1, 255); + expect(positions?.length == 1 && positions[0].slice(0, 3)).deep.equals([null, 23, 2]); + }); + }); + }); +} diff --git a/test/specs/code/validation.js b/test/specs/code/validation.js index 13592a9c..8124a458 100644 --- a/test/specs/code/validation.js +++ b/test/specs/code/validation.js @@ -505,13 +505,15 @@ html, body, div, span, applet, object, iframe, it('file validation #21', function () { - return transform(`@import '${(import.meta.dirname ?? dirname(new URL(import.meta.url).pathname)).replaceAll('\\', '/')}/../../files/css/full.css'; + const url = new URL(dirname(import.meta.url) + '/../../files/css/full.css'); + return transform(`@import '${url.pathname}'; `, {validation: true, resolveImport: true}).then(result => expect(result.errors.length).equals(5)); }); it('file validation #22', function () { - transform(`@import '${(import.meta.dirname ?? dirname(new URL(import.meta.url).pathname)).replaceAll('\\', '/')}/../../files/css/bootstrap.css'; + const url = new URL(dirname(import.meta.url) + '/../../files/css/bootstrap.css'); + transform(`@import '${url.pathname}'; `, { validation: true, resolveImport: true @@ -520,7 +522,8 @@ html, body, div, span, applet, object, iframe, it('file validation #23', function () { - return transform(`@import '${(import.meta.dirname ?? dirname(new URL(import.meta.url).pathname)).replaceAll('\\', '/')}/../../files/css/bootstrap-4.css'; + const url = new URL(dirname(import.meta.url) + '/../../files/css/bootstrap-4.css'); + return transform(`@import '${url.pathname}'; `, { validation: true, resolveImport: true @@ -529,7 +532,8 @@ html, body, div, span, applet, object, iframe, it('file validation #24', function () { - return transform(`@import '${(import.meta.dirname ?? dirname(new URL(import.meta.url).pathname)).replaceAll('\\', '/')}/../../files/css/bootstrap-5.css'; + const url = new URL(dirname(import.meta.url) + '/../../files/css/bootstrap-5.css'); + return transform(`@import '${url.pathname}'; `, { validation: true, resolveImport: true @@ -538,7 +542,8 @@ html, body, div, span, applet, object, iframe, it('file validation #25', function () { - return transform(`@import '${(import.meta.dirname ?? dirname(new URL(import.meta.url).pathname)).replaceAll('\\', '/')}/../../files/css/tailwind.css'; + const url = new URL(dirname(import.meta.url) + '/../../files/css/tailwind.css'); + return transform(`@import '${url.pathname}'; `, { validation: true, resolveImport: true @@ -547,7 +552,9 @@ html, body, div, span, applet, object, iframe, it('file validation #26', function () { - return transform(`@import '${(import.meta.dirname ?? dirname(new URL(import.meta.url).pathname)).replaceAll('\\', '/')}/../../files/css/tailwind-2.0.4.css'; + const url = new URL(dirname(import.meta.url) + '/../../files/css/tailwind-2.0.4.css'); + + return transform(`@import '${url.pathname}'; `, { validation: true, resolveImport: true @@ -556,7 +563,9 @@ html, body, div, span, applet, object, iframe, it('file validation #27', function () { - return transform(`@import '${(import.meta.dirname ?? dirname(new URL(import.meta.url).pathname)).replaceAll('\\', '/')}/../../files/css/github-markdown.css'; + const url = new URL(dirname(import.meta.url) + '/../../files/css/github-markdown.css'); + + return transform(`@import '${url.pathname}'; `, { validation: true, resolveImport: true diff --git a/test/specs/code/visitors.js b/test/specs/code/visitors.js index 148c338a..66a99817 100644 --- a/test/specs/code/visitors.js +++ b/test/specs/code/visitors.js @@ -1,11 +1,25 @@ -import {ColorType, EnumToken} from "../../../dist/lib/ast/types.js"; - -export function run(describe, expect, it, transform, parse, render, dirname, readFile) { - - describe('node visitor', function () { - - it('visitor #1', function () { - +import { ColorType, EnumToken } from "../../../dist/lib/ast/types.js"; +import { WalkerEvent } from "../../../dist/lib/ast/walk.js"; + +export function run( + describe, + expect, + it, + transform, + parse, + render, + dirname, + readFile, + resolve, + ColorType, + EnumToken, + ModuleCaseTransformEnum, + ModuleScopeEnumOptions, + transformSync, + parseSync, +) { + describe("node visitor", function () { + it("visitor #1", function () { const css = ` @media screen { @@ -16,50 +30,43 @@ export function run(describe, expect, it, transform, parse, render, dirname, rea } `; const options = { - visitor: { - AtRule: { - media: (node) => { - - return {...node, val: 'all'} - } + return { ...node, val: "all" }; + }, }, Rule(node) { - - return {...node, sel: '.foo,.bar,.fubar'}; + return { ...node, sel: ".foo,.bar,.fubar" }; }, Declaration: { - height: (node) => { - return [ node, { - typ: EnumToken.DeclarationNodeType, - nam: 'width', + nam: "width", val: [ { typ: EnumToken.Length, - val: '3', - unit: 'px' - } - ] - } - ] - } - } - } - } + val: "3", + unit: "px", + }, + ], + }, + ]; + }, + }, + }, + }; - return transform(css, options).then(result => expect(result.code).equals('.foo,.bar,.fubar{height:calc(40px/3);width:3px}')); + return transform(css, options).then((result) => + expect(result.code).equals(".foo,.bar,.fubar{height:calc(40px/3);width:3px}"), + ); }); - it('visitor #2', function () { - + it("visitor #2", function () { const css = ` body { color: color(from var(--base-color) display-p3 r calc(g + 0.24) calc(b + 0.15)); } @@ -76,29 +83,25 @@ body { } `; const options = { - beautify: true, visitor: { - DeclarationNodeType: (declaration) => { - - if (declaration.nam == 'height') { - - declaration.nam = 'width'; + if (declaration.nam == "height") { + declaration.nam = "width"; } }, ColorTokenType: (color) => { - return { typ: EnumToken.Color, - val: 'red', - kin: ColorType.HEX - } - } - } - } + val: "red", + kin: ColorType.HEX, + }; + }, + }, + }; - return transform(css, options).then(result => expect(result.code).equals(`body { + return transform(css, options).then((result) => + expect(result.code).equals(`body { color: red } html,body { @@ -107,11 +110,11 @@ html,body { .ruler { width: 10px; background-color: red -}`)); +}`), + ); }); - it('visitor #3', function () { - + it("visitor #3", function () { const css = ` @media screen { @@ -136,37 +139,32 @@ body { } `; const options = { - beautify: true, inlineCssVariables: true, resolveImport: true, visitor: { - StyleSheetNodeType: async (node) => { - // insert a new rule - node.chi.unshift(await parse('html {--base-color: pink}').then(result => result.ast.chi[0])) + node.chi.unshift(await parse("html {--base-color: pink}").then((result) => result.ast.chi[0])); }, - ColorTokenType: (node) => { - + ColorTokenType: (node) => { // dump all color tokens // console.debug(node); }, - FunctionTokenType: (node) => { - + FunctionTokenType: (node) => { // dump all function tokens // console.debug(node); }, - DeclarationNodeType: (node) => { - + DeclarationNodeType: (node) => { // dump all declaration nodes // console.debug(node); - } - } + }, + }, }; - return transform(css, options).then(result => expect(result.code).equals(`@media screen { + return transform(css, options).then((result) => + expect(result.code).equals(`@media screen { .foo:-webkit-autofill { height: calc(40px/3) } @@ -180,11 +178,11 @@ html,body { .ruler { height: 10px; background-color: orange -}`)); +}`), + ); }); - it('visitor #4', function () { - + it("visitor #4", function () { const css = ` @keyframes slide-in { @@ -215,21 +213,261 @@ html,body { } `; const options = { - removePrefix: true, beautify: true, visitor: { KeyframesAtRule: { slideIn(node) { - - node.val = 'slide-in-out'; + node.val = "slide-in-out"; return node; + }, + }, + }, + }; + + return transform(css, options).then((result) => + expect(result.code).equals(`@keyframes slide-in-out { + 0% { + transform: translateX(0) + } + to { + transform: translateX(100%) + } +} +@keyframes identifier { + 0% { + top: 0; + left: 0 + } + 30% { + top: 50px + } + 68%,72% { + left: 50px + } + to { + top: 100px; + left: 100% + } +}`), + ); + }); + + it("visitor #5", function () { + const css = ` + +body { color: color(from var(--base-color) display-p3 r calc(g + 0.24) calc(b + 0.15)); } + +html, +body { + line-height: 1.474; +} + +.ruler { + + height: 10px; + background-color: orange +} +`; + const options = { + beautify: true, + visitor: { + DeclarationNodeType: { + type: WalkerEvent.Enter, + handler: (declaration) => { + if (declaration.nam == "height") { + declaration.nam = "width"; + } + }, + }, + ColorTokenType: (color) => { + return { + typ: EnumToken.Color, + val: "red", + kin: ColorType.HEX, + }; + }, + }, + }; + + const result = transformSync(css, options); + + expect(result.code).equals(`body { + color: red +} +html,body { + line-height: 1.474 +} +.ruler { + width: 10px; + background-color: red +}`); + }); + + it("visitor #6", function () { + const css = ` + +body { color: color(from var(--base-color) display-p3 r calc(g + 0.24) calc(b + 0.15)); } + +html, +body { + line-height: 1.474; +} + +.ruler { + + height: 10px; + background-color: orange +} +`; + const options = { + beautify: true, + visitor: { + DeclarationNodeType: (declaration) => { + if (declaration.nam == "height") { + declaration.nam = "width"; } - } - } - } + }, + ColorTokenType: (color) => { + return { + typ: EnumToken.Color, + val: "red", + kin: ColorType.HEX, + }; + }, + }, + }; + + const result = transformSync(css, options); + + expect(result.code).equals(`body { + color: red +} +html,body { + line-height: 1.474 +} +.ruler { + width: 10px; + background-color: red +}`); + }); + + it("visitor #7", function () { + const css = ` + +@media screen { + + .foo:-webkit-autofill { + height: calc(100px * 2/ 15); + } +} + + +body { color: color(from var(--base-color) display-p3 r calc(g + 0.24) calc(b + 0.15)); } + +html, +body { + line-height: 1.474; +} - return transform(css, options).then(result => expect(result.code).equals(`@keyframes slide-in-out { +.ruler { + + height: 10px; + background-color: orange +} +`; + const options = { + beautify: true, + inlineCssVariables: true, + resolveImport: true, + visitor: { + StyleSheetNodeType: { + type: WalkerEvent.Leave, + handler: (node) => { + // insert a new rule + node.chi.unshift(parseSync("html {--base-color: pink}").ast.chi[0]); + }, + }, + ColorTokenType: (node) => { + // dump all color tokens + // console.debug(node); + }, + FunctionTokenType: (node) => { + // dump all function tokens + // console.debug(node); + }, + DeclarationNodeType: (node) => { + // dump all declaration nodes + // console.debug(node); + }, + }, + }; + + const result = transformSync(css, options); + + expect(result.code).equals(`@media screen { + .foo:-webkit-autofill { + height: calc(40px/3) + } +} +body { + color: #f3fff0 +} +html,body { + line-height: 1.474 +} +.ruler { + height: 10px; + background-color: orange +}`); + }); + + it("visitor #8", function () { + const css = ` + +@keyframes slide-in { + from { + transform: translateX(0%); + } + + to { + transform: translateX(100%); + } +} +@keyframes identifier { + 0% { + top: 0; + left: 0; + } + 30% { + top: 50px; + } + 68%, + 72% { + left: 50px; + } + 100% { + top: 100px; + left: 100%; + } +} +`; + const options = { + removePrefix: true, + beautify: true, + visitor: { + KeyframesAtRule: { + slideIn(node) { + node.val = "slide-in-out"; + return node; + }, + }, + }, + }; + + const result = transformSync(css, options); + + expect(result.code).equals(`@keyframes slide-in-out { 0% { transform: translateX(0) } @@ -252,9 +490,55 @@ html,body { top: 100px; left: 100% } -}`)); +}`); }); - }); + it("visitor #9", function () { + const css = ` -} \ No newline at end of file +body { color: color(from var(--base-color) display-p3 r calc(g + 0.24) calc(b + 0.15)); } + +html, +body { + line-height: 1.474; +} + +.ruler { + + height: 10px; + background-color: orange +} +`; + const options = { + beautify: true, + visitor: [ + function DeclarationNodeType(declaration) { + if (declaration.nam == "height") { + declaration.nam = "width"; + } + }, + function ColorTokenType(color) { + return { + typ: EnumToken.Color, + val: "red", + kin: ColorType.HEX, + }; + }, + ], + }; + + return transform(css, options).then((result) => + expect(result.code).equals(`body { + color: red +} +html,body { + line-height: 1.474 +} +.ruler { + width: 10px; + background-color: red +}`), + ); + }); + }); +} diff --git a/typedoc.config.js b/typedoc.config.js index 2da9bb9f..43cb0480 100644 --- a/typedoc.config.js +++ b/typedoc.config.js @@ -9,7 +9,7 @@ export default { Benchmark: "https://tbela99.github.io/css-parser/benchmark/index.html", Docs: "https://tbela99.github.io/css-parser/docs/", Playground: "https://tbela99.github.io/css-parser/playground/", - "llm.txt": "https://github.com/tbela99/css-parser/llms.txt", + "llm.txt": "https://tbela99.github.io/css-parser/llms.txt", GitHub: "https://github.com/tbela99/css-parser", }, highlightLanguages: ["ts", "css", "javascript", "json", 'html', 'shell'],