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/.github/workflows/jsr.yml b/.github/workflows/jsr.yml index bbcbe825..ecec9eed 100644 --- a/.github/workflows/jsr.yml +++ b/.github/workflows/jsr.yml @@ -14,7 +14,7 @@ jobs: contents: read id-token: write # The OIDC ID token is used for authentication with JSR. steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 - run: npx jsr publish diff --git a/.github/workflows/node.yml b/.github/workflows/node.yml index b583ada5..f9efd8d6 100644 --- a/.github/workflows/node.yml +++ b/.github/workflows/node.yml @@ -23,7 +23,7 @@ jobs: steps: - uses: actions/checkout@v7 - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v6 + uses: actions/setup-node@v7 with: node-version: ${{ matrix.node-version }} - name: Install NPM dependencies diff --git a/.github/workflows/npm.yml b/.github/workflows/npm.yml index 2c598d04..11e7293f 100644 --- a/.github/workflows/npm.yml +++ b/.github/workflows/npm.yml @@ -10,8 +10,8 @@ jobs: contents: read id-token: write steps: - - uses: actions/checkout@v6 - - uses: actions/setup-node@v6 + - uses: actions/checkout@v7 + - uses: actions/setup-node@v7 with: node-version: '24.x' registry-url: 'https://registry.npmjs.org' diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index a829dd79..9933d689 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -23,7 +23,7 @@ jobs: steps: - uses: actions/checkout@v7 - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v6 + uses: actions/setup-node@v7 with: node-version: ${{ matrix.node-version }} - name: Install NPM dependencies 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..660514be 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,10 +85,13 @@ 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) +- [Prefix Removal](https://tbela99.github.io/css-parser/docs/documents/Guide.Prefix_Removal.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) -- + ## AST ### Comment diff --git a/benchmark/package.json b/benchmark/package.json index 84d10d42..b243d652 100644 --- a/benchmark/package.json +++ b/benchmark/package.json @@ -10,15 +10,15 @@ "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#52223b9", "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", "postcss": "^8.5.26", - "vitest": "^4.1.10" + "vitest": "^4.1.11" } } \ No newline at end of file diff --git a/dist/index-umd-web.js b/dist/index-umd-web.js index 1792fbfb..08381ca2 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,21 @@ }, }; } - 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) { + // @ts-ignore + parents.splice(i, 0, ...(reverse ? node[TOKENS].toReversed() : node[TOKENS])); + // @ts-ignore + } + else if (Array.isArray(node.val)) { + // @ts-ignore + parents.splice(i, 0, ...(reverse ? node.val.toReversed() : node.val)); + } + } + // @ts-ignore + if (node["chi"] != null && (!isNumeric || (option & exports.WalkerOptionEnum.IgnoreChildren) === 0)) { + // @ts-ignore + parents.splice(i, 0, ...(reverse ? node.chi.toReversed() : node.chi)); for (const child of node.chi) { map.set(child, node); } @@ -9623,11 +9646,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 || @@ -10923,7 +10941,7 @@ [LOC]: pos, }; } - if (isPseudo(token)) { + if (isPseudo$1(token)) { return { typ: ValidationTokenEnum.PseudoClassToken, val: token, @@ -11680,23 +11698,28 @@ cloneMap?.set?.(node, clone); for (const [name, value] of Object.entries(node)) { if (value == null || typeof value != "object") { + // @ts-ignore 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; - }); + // @ts-ignore + clone[name] = []; + if (cloneChildren || name !== checkNode) { + for (const c of value) { + const newObj = cloneNode(c, cloneChildren, cloneMap); + cloneMap?.set?.(c, newObj); + // @ts-ignore + clone[name].push(newObj); + } + } } else { + // @ts-ignore clone[name] = { ...value }; } } for (const symbol of Object.getOwnPropertySymbols(node)) { + // @ts-ignore clone[symbol] = node[symbol]; } return clone; @@ -11705,11 +11728,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 +11841,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 +12009,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 +12455,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 +12500,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 +12551,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 +12599,13 @@ } return result; } + /** + * matches a list of syntaxes + * @param syntaxes + * @param context + * @param options + * @returns + */ function matchSyntax(syntaxes, context, options) { if (syntaxes == null) { return { @@ -12636,6 +12708,7 @@ return result; } if (tokensfuncDefMap.has(token.typ) && + // @ts-ignore token.typ === exports.EnumToken.WildCardFunctionTokenDefType) { const range = trimArray(context.peekRange()); result = matchSyntax(getParsedSyntax(ValidationSyntaxGroupEnum.Syntaxes, token.val + "()")?.[0]?.chi, createValidationContext(range.slice(1, -1)), options); @@ -12666,7 +12739,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 +13247,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 +13290,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 +13315,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; @@ -13615,7 +13709,9 @@ // ) // ) { const newRange = range.map((t) => cloneNode(t, true)); + // @ts-ignore parseTokens(newRange, { parseColor: true }, errors); + // @ts-ignore success = newRange.length == 1 && isColor(newRange[0], errors); if (success) { context.update(range.at(-1)); @@ -13960,6 +14056,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; @@ -15516,7 +15619,6 @@ action: "drop", message: `Invalid color`, node: token, - // location: options.source!.getSourLocation(token[LOC]!.sta), }); return false; } @@ -15539,31 +15641,6 @@ if (children.length == 4 || (isRelative && children.length == 6)) { return true; } - if (children.length == 8 || children.length == 6) { - children.at(-2); - children.at(-1); - // @ts-ignore - // if ( - // ((children.length > 6 || !isRelative) && sep.typ != EnumToken.LiteralTokenType) || - // (sep as LiteralToken).val != "/" - // ) { - // return false; - // } - // if (alpha.typ == EnumToken.IdenTokenType && (alpha as IdentToken).val != "none") { - // return false; - // } else { - // // @ts-ignore - // if (alpha.typ == EnumToken.PercentageTokenType) { - // if (+(alpha as PercentageToken).val < 0 || +(alpha as PercentageToken).val > 100) { - // return false; - // } - // } else if (alpha.typ == EnumToken.NumberTokenType) { - // if (+(alpha as NumberToken).val < 0 || +(alpha as NumberToken).val > 1) { - // return false; - // } - // } - // } - } return true; } // @ts-ignore @@ -15580,9 +15657,6 @@ } return acc; }, [[]]); - // if (children.length === 0 || children[0].length === 0) { - // return false; - // } let j = 0; let k = 0; if (children[j][0].typ === exports.EnumToken.IdenTokenType && @@ -15616,50 +15690,13 @@ k++; } } - // else { - // return false; - // } } else { k++; } } - // else { - // return false; - // } - // if (k != children[j].length) { - // return false; - // } j++; } - // while (j < children.length) { - // if (children[j].length > 2) { - // return false; - // } - // if ( - // !isColor(children[j][0]) && - // !( - // children[j][0].typ == EnumToken.WildCardFunctionTokenType && - // equalsIgnoreCase("calc", (children[j][0] as FunctionToken).val) - // ) - // ) { - // return false; - // } - // if (children[j][0].typ == EnumToken.WildCardFunctionTokenType) { - // const result = matchAllSyntaxes( - // getParsedSyntax(ValidationSyntaxGroupEnum.Syntaxes, "calc()") as ValidationFunctionToken[], - // createValidationContext([children[j][0]]), - // {}, - // ); - // if (!result.success) { - // return false; - // } - // } - // if (children[j].length > 1 && !isPercentageToken(children[j][1])) { - // return false; - // } - // j++; - // } return true; } else { @@ -15672,21 +15709,6 @@ // @ts-ignore for (const v of token.chi) { if (v.typ == exports.EnumToken.IdenTokenType) { - // if (isColor(v)) { - // continue; - // } - // if (!(keywords.includes(v.val) || COLORS_NAMES[v.val.toLowerCase()] != null)) { - // return false; - // } - // if (keywords.includes(v.val)) { - // if (isLegacySyntax) { - // return false; - // } - // // @ts-ignore - // if (v.val == "from" && ["rgba", "hsla"].includes((token as ColorToken).val)) { - // return false; - // } - // } continue; } if (v.typ === exports.EnumToken.MathFunctionTokenType || @@ -15694,20 +15716,6 @@ colorsFunc.includes(v.val)) { continue; } - // if ( - // ![ - // EnumToken.ColorTokenType, - // EnumToken.IdenTokenType, - // EnumToken.NumberTokenType, - // EnumToken.AngleTokenType, - // EnumToken.PercentageTokenType, - // EnumToken.CommaTokenType, - // EnumToken.WhitespaceTokenType, - // EnumToken.LiteralTokenType, - // ].includes(v.typ) - // ) { - // return false; - // } } } return true; @@ -15763,63 +15771,12 @@ } if (token.val == "color") { let index = token.chi.indexOf(tk); - // if ((token as ColorToken).cal == "rel") { - // for (let k = 0; k < (token as ColorToken).chi!.length; k++) { - // if (EnumToken.DashedIdenTokenType == (token as ColorToken).chi![k].typ) { - // index = k; - // break; - // } - // } - // } if (exports.EnumToken.DashedIdenTokenType == token?.chi?.[index]?.typ) { token.kin = exports.ColorType.CUSTOM_COLOR; } } } - // return token; } - // @ts-ignore - // token.typ = EnumToken.ColorTokenType; - // // @ts-ignore - // (token as ColorToken).kin = ColorType[token.val.replaceAll("-", "_").toUpperCase()]; - // if (!("chi" in token)) { - // const val: string = (token as ColorToken).val.toLowerCase(); - // if (val == "currentcolor" || val == "transparent" || val in COLORS_NAMES) { - // (token as ColorToken).kin = ColorType.LIT; - // } else if (isHexColor(val)) { - // (token as ColorToken).kin = ColorType.HEX; - // } - // const tk = (token as ColorToken).chi?.find( - // (t) => t.typ !== EnumToken.WhitespaceTokenType && t.typ !== EnumToken.CommentTokenType, - // ); - // if (tk?.typ === EnumToken.IdenTokenType && (tk as IdentToken).val === "from") { - // (token as ColorToken).cal = "rel"; - // } else if ((token as ColorToken).val == "color-mix" && (tk as IdentToken).val == "in") { - // (token as ColorToken).cal = "mix"; - // } else if ((token as ColorToken).val == "color") { - // (token as ColorToken).cal = "col"; - // } - // return token; - // } - // // @ts-ignore - // if (((token as ColorToken).chi as Token[])[0].typ == EnumToken.IdenTokenType) { - // // @ts-ignore - // if (((token as ColorToken).chi as Token[])[0].val == "from") { - // // @ts-ignore - // (token as ColorToken).cal = "rel"; - // } - // // @ts-ignore - // else if ((token as ColorToken).val == "color-mix" && ((token as ColorToken).chi as Token[])[0].val == "in") { - // // @ts-ignore - // (token as ColorToken).cal = "mix"; - // } else { - // // @ts-ignore - // if ((token as ColorToken).val == "color") { - // // @ts-ignore - // (token as ColorToken).cal = "col"; - // } - // } - // } return token; } function isLetter(codepoint) { @@ -15865,6 +15822,9 @@ return false; } if (codepoint == REVERSE_SOLIDUS) { + if (i + 1 > j) { + return false; + } codepoint = name.charCodeAt(i + 1); // if (!isIdentCodepoint(codepoint)) { // return false; @@ -15892,18 +15852,24 @@ } return true; }); - function isPseudo(name) { + function isNonPrintable(codepoint) { + // null -> backspace + return ((codepoint >= 0 && codepoint <= 0x8) || + // tab + codepoint == 0xb || + // delete + codepoint == 0x7f || + (codepoint >= 0xe && codepoint <= 0x1f)); + } + function isPseudo$1(name) { return (name.charAt(0) == ":" && ((name.endsWith("(") && isIdent(name.charAt(1) == ":" ? name.slice(2, -1) : name.slice(1, -1))) || isIdent(name.charAt(1) == ":" ? name.slice(2) : name.slice(1)))); } function isHash(name) { - return name.charAt(0) == "#" && isIdent(name.charAt(1)); + return name.charAt(0) == "#" && isIdentStart(name.charCodeAt(1)); } const isNumber = memoize(function (name) { - // if (name.length == 0) { - // return false; - // } let codepoint = name.charCodeAt(0); let i = 0; const j = name.length; @@ -16249,16 +16215,19 @@ if (key in pseudoAliasMap) { const isPseudClass = pseudoAliasMap[key].startsWith("::"); value.val = pseudoAliasMap[key]; - if (value.typ == exports.EnumToken.IdenTokenType && - ["min-resolution", "max-resolution"].includes(value.val) && - parent?.typ == exports.EnumToken.MediaQueryConditionTokenType && - parent.r?.[0]?.typ == exports.EnumToken.NumberTokenType) { - Object.assign(parent.r?.[0], { - typ: exports.EnumToken.ResolutionTokenType, - unit: "x", - }); - } - else if (isPseudClass && value.typ == exports.EnumToken.PseudoElementTokenType) { + // if ( + // value.typ == EnumToken.IdenTokenType && + // ["min-resolution", "max-resolution"].includes((value as IdentToken).val) && + // parent?.typ == EnumToken.MediaQueryConditionTokenType && + // (parent as MediaQueryConditionToken).r?.[0]?.typ == EnumToken.NumberTokenType + // ) { + // Object.assign((parent as MediaQueryConditionToken).r?.[0], { + // typ: EnumToken.ResolutionTokenType, + // unit: "x", + // }); + // } + // else + if (isPseudClass && value.typ == exports.EnumToken.PseudoElementTokenType) { // @ts-ignore value.typ = exports.EnumToken.PseudoClassTokenType; } @@ -16471,32 +16440,11 @@ let commaCount; let type = ""; let tokens = token.chi.slice(); - // while ( - // i < tokens.length && - // (tokens[i].typ === EnumToken.WhitespaceTokenType || tokens[i].typ === EnumToken.CommentTokenType) - // ) { - // i++; - // } - // if (i >= tokens.length || tokens[i].typ !== EnumToken.IdenTokenType) { - // return; - // } // linear or radial if (equalsIgnoreCase(tokens[i].val, "linear")) { type = "linear-gradient"; i++; } - // else { - // return; - // } - // while ( - // i < tokens.length && - // (tokens[i].typ === EnumToken.WhitespaceTokenType || tokens[i].typ === EnumToken.CommentTokenType) - // ) { - // i++; - // } - // if (tokens[i].typ !== EnumToken.CommaTokenType) { - // return; - // } tokens.splice(0, i + 1); commaCount = 0; for (i = 0; i < tokens.length; i++) { @@ -18912,12 +18860,21 @@ 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 +19592,7 @@ accept = new Set([ exports.EnumToken.RuleNodeType, exports.EnumToken.AtRuleNodeType, - exports.EnumToken.KeyFramesRuleNodeType, + exports.EnumToken.KeyframesRuleNodeType, ]); get ordering() { return 10; @@ -19792,7 +19749,8 @@ // @ts-ignore const children = parent.typ == exports.EnumToken.DeclarationNodeType ? parent.val - : parent.chi; + : // @ts-ignore + parent.chi; if (values.length == 1 && values[0].typ != exports.EnumToken.BinaryExpressionTokenType) { for (let i = 0; i < children.length; i++) { if (children[i] == value) { @@ -20976,7 +20934,11 @@ } class TransformCssFeature { - accept = new Set([exports.EnumToken.RuleNodeType, exports.EnumToken.KeyFramesRuleNodeType]); + accept = new Set([ + exports.EnumToken.RuleNodeType, + exports.EnumToken.AtRuleNodeType, + exports.EnumToken.KeyframesRuleNodeType, + ]); get ordering() { return 3; } @@ -21013,7 +20975,6 @@ ? minifyTransformFunctions(child) : child); } - // consumeWhitespace(children); let { matrix, cumulative, minified } = compute(children) ?? { matrix: null, cumulative: null, @@ -21043,7 +21004,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 +21043,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 +21073,6 @@ console.log({node, value}); ``` - * - * @param ast - * @param matcher - * @returns */ function findByValue(ast, matcher) { let source; @@ -21127,6 +21089,7 @@ } for (const { value, parent, root: rootNode, parents } of walkValues(source, node)) { if (matcher(value, node)) { + // @ts-ignore return { node, value: { node: value, parent, root: rootNode, parents } }; } } @@ -21235,6 +21198,7 @@ exports.EnumToken.SemiColonTokenType ? trimArray(node.r.r.slice(0, -1)) : node.r.r); + // @ts-expect-error if (targetParentWrapper.typ != exports.EnumToken.DeclarationNodeType) { let index = targetParentWrapper.chi.indexOf(targetWrapper); if (index != -1) { @@ -21256,6 +21220,7 @@ .r.r.slice(0, -1)) : siblingWrapper.chi[k] .r.r); + // @ts-ignore cache.add(siblingWrapper.chi[k].l); } } @@ -21279,7 +21244,9 @@ } if (left.typ === exports.EnumToken.IdenTokenType && equalsIgnoreCase("else", left.val)) { clonedDeclaration = cloneNode(declaration, true, nodeMap); - replaceNodeOrValue(nodeMap.get(parentWrapper), nodeMap.get(targetWrapper.typ === exports.EnumToken.DeclarationNodeType ? node : targetWrapper), node.r.at(-1)?.typ === exports.EnumToken.SemiColonTokenType ? trimArray(node.r.slice(0, -1)) : node.r); + replaceNodeOrValue(nodeMap.get(parentWrapper), + // @ts-expect-error + nodeMap.get(targetWrapper.typ === exports.EnumToken.DeclarationNodeType ? node : targetWrapper), node.r.at(-1)?.typ === exports.EnumToken.SemiColonTokenType ? trimArray(node.r.slice(0, -1)) : node.r); result.push(clonedDeclaration); } else if (left?.typ === exports.EnumToken.WhenElseFunctionTokenType) { @@ -21288,6 +21255,9 @@ nam: left.val, chi: [], }); + if (declaration[PARENT] != null) { + atRule[LOC] = declaration[PARENT][LOC]; + } atRule[TOKENS] = [{ typ: exports.EnumToken.ParensTokenType, chi: left.chi.slice() }]; const minify = atRule.nam !== "supports"; const options = { @@ -21310,6 +21280,9 @@ }); atRule[TOKENS] = [left]; atRule.val = atRule[TOKENS].reduce((acc, curr) => acc + renderValue(curr), ""); + if (declaration[PARENT] != null) { + atRule[LOC] = declaration[PARENT][LOC]; + } clonedDeclaration = cloneNode(declaration, true, nodeMap); replaceNodeOrValue(nodeMap.get(targetWrapper.typ === exports.EnumToken.WildCardFunctionTokenType ? targetParentWrapper : targetWrapper), nodeMap.get(targetWrapper.typ === exports.EnumToken.WildCardFunctionTokenType ? targetWrapper : node), node.r.at(-1)?.typ === exports.EnumToken.SemiColonTokenType ? trimArray(node.r.slice(0, -1)) @@ -21340,17 +21313,27 @@ const parentWrapper = node.parent ?? parents.find((node) => !nodeMatcher(node)); if (node.node.typ === exports.EnumToken.WildCardFunctionTokenType) { for (i = 0; i < node.node.chi.length; i++) { - stack.push(...substituteIfElseNode(declaration, node.node.chi[i], node.node, parentWrapper, cache)); + stack.push( + // @ts-expect-error + ...substituteIfElseNode( + // @ts-expect-error + declaration, node.node.chi[i], node.node, parentWrapper, cache)); } } else { - stack.push(...substituteIfElseNode(declaration, node.node, parentWrapper, parents[parents.indexOf(parentWrapper) + 1] ?? declaration, cache)); + stack.push( + // @ts-expect-error + ...substituteIfElseNode( + // @ts-expect-error + declaration, node.node, parentWrapper, parents[parents.indexOf(parentWrapper) + 1] ?? declaration, cache)); } } if (result.length > 0) { + // @ts-expect-error replaceNodeOrValue(declarationNode[PARENT], declarationNode, result); } // else remove node? + // @ts-expect-error return result; } class ExpandIfFeature { @@ -21382,95 +21365,424 @@ 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; - } - /** - * add line start - */ - addLineStart(lineStart) { - this.lineStarts.push(lineStart); - } - /** - * clone the linemap - * @returns - */ - clone() { - return new LineMap(this.lineStarts.slice()); } + return result; } - - /** - * Source file ID - */ - let sourceId = 0; /** - * Source file helper class + * + * @param value + * @returns */ - class SourceFile { - /** - * Source file ID - */ - id; + 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; + } + + /** + * Generate and parse source map + */ + class SourceMap { + /** + * + * @private + */ + keys = new Set(); + /** + * Last location + */ + lastLocation = null; + /** + * Version + * @private + */ + version = 3; + /** + * Sources map + * @private + */ + sourcesMap = []; + /** + * Sources content + * @private + */ + sourcesContent = []; + /** + * Sources + * @private + */ + sources = []; + /** + * Map + * @private + * + */ + map = new Map(); + /** + * Map + * @private + * + */ + reverseMap = new Map(); + /** + * Line + * @private + */ + line = -1; + /** + * + * @param sourcemaps + */ + constructor(sourcemaps) { + if (typeof sourcemaps === "string") { + if (sourcemaps.startsWith("data:")) { + let encoding = ""; + let offset = sourcemaps.indexOf(",") + 1; + if (offset == 0) { + offset = sourcemaps.lastIndexOf(";") + 1; + } + else { + encoding = sourcemaps.slice(sourcemaps.lastIndexOf(";") + 1, offset - 1); + } + if (encoding == "base64") { + sourcemaps = atob(sourcemaps.slice(offset)); + } + else { + sourcemaps = decodeURIComponent(sourcemaps.slice(offset)); + } + } + 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 source + * @param id + * @param fileName + * @param content + * @returns + */ + addSourceContent(id, fileName, content) { + if (this.sourcesMap.includes(id)) { + return; + } + this.sourcesMap[this.sourcesMap.length] = id; + this.sources[this.sources.length] = fileName || null; + this.sourcesContent[this.sourcesContent.length] = content || null; + } + /** + * Add all location + * @param maps + * @throws + */ + add(...maps) { + let srcIndex; + if (typeof maps[0] === "number") { + maps = [maps]; + } + for (let [newLine, newColumn, srcId, ln, col] of maps) { + const key = `${srcId}:${ln}:${col}:${newLine}:${newColumn}`; + if (this.keys.has(key)) { + continue; + } + this.keys.add(key); + const line = newLine - 1; + let record; + if (line > this.line) { + this.line = line; + } + srcIndex = this.sourcesMap.indexOf(srcId); + if (srcIndex == -1) { + throw new Error(`Source file ${srcId} not added to sourcemap`); + } + if (!this.map.has(line)) { + record = [Math.max(0, newColumn - 1), srcIndex, 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], srcIndex - 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: number = 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); + // nameIndex not needed + // 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.size == 0) { + this.computePositions(); + } + 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(";"), + }; + } + } + + /** + * 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]; + // } + // [line, column] + return [line + 1, offset - this.lineStarts[line] + 1]; + } + /** + * 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); + } + } + + /** + * Source file ID + */ + let sourceId = 0; + /** + * Source file helper class + */ + class SourceFile { + inputSourceMap = null; + /** + * Source file ID + */ + id; /** * Source file path */ @@ -21485,7 +21797,6 @@ content; /** * Constructor - * @param id * @param content * @param lines * @param file @@ -21499,7 +21810,6 @@ /** * Update source content * @param content - * @param lines */ append(content) { this.content += content; @@ -21557,6 +21867,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 = { @@ -21691,17 +22015,17 @@ TokenMap[TokenMap["GREATERTHAN"] = 62] = "GREATERTHAN"; })(TokenMap || (TokenMap = {})); function consumeString(parseInfo) { - const quote = next(parseInfo); - let value; - let buffer = quote; + const quote = next(parseInfo).charCodeAt(0); + let charCode; + let decodeSegments = false; const result = []; - while ((value = parseInfo.stream.charAt(parseInfo.currentPosition - parseInfo.offset + 1))) { - if (value == "\\") { - if ("\\" == parseInfo.stream.charAt(parseInfo.currentPosition - parseInfo.offset + 2)) { - buffer += next(parseInfo, 2); + while ((charCode = parseInfo.stream.charCodeAt(parseInfo.currentPosition - parseInfo.offset)) == charCode) { + if (charCode == 92 /* TokenMap.REVERSE_SOLIDUS */) { + if (charCode == parseInfo.stream.charCodeAt(parseInfo.currentPosition - parseInfo.offset + 1)) { + next(parseInfo, 2); continue; } - const sequence = peek(parseInfo, 6); + const sequence = peek(parseInfo, 7); let escapeSequence = ""; let codepoint; let i; @@ -21720,50 +22044,66 @@ break; } if (escapeSequence.trimEnd().length > 0) { - const codepoint = parseInt(escapeSequence, 16); - if (codepoint == 0 || - // leading surrogate - (0xd800 <= codepoint && codepoint <= 0xdbff) || - // trailing surrogate - (0xdc00 <= codepoint && codepoint <= 0xdfff)) { - buffer += String.fromCodePoint(0xfffd); - } - else { - buffer += String.fromCodePoint(codepoint); - } - next(parseInfo, escapeSequence.length + + // const codepoint = parseInt(escapeSequence, 16); + // TODO set decode flag ON + // if ( + // codepoint == 0 || + // // leading surrogate + // (0xd800 <= codepoint && codepoint <= 0xdbff) || + // // trailing surrogate + // (0xdc00 <= codepoint && codepoint <= 0xdfff) + // ) { + // buffer += String.fromCodePoint(0xfffd); + // } else { + // buffer += String.fromCodePoint(codepoint); + // } + const length = escapeSequence.length + 1 + - (isWhiteSpace(parseInfo.stream.charAt(parseInfo.currentPosition - parseInfo.offset + 1)?.charCodeAt(0)) + (isWhiteSpace(parseInfo.stream.charAt(parseInfo.currentPosition - parseInfo.offset)?.charCodeAt(0)) ? 1 - : 0)); + : 0); + decodeSegments = true; + next(parseInfo, length); continue; } - buffer += next(parseInfo, 2); + next(parseInfo, 2); continue; } - if (value == quote) { - buffer += value; - result.push(yieldResult(buffer, parseInfo, - /* hasNewLine ? EnumToken.BadStringTokenType : */ exports.EnumToken.StringTokenType)); + if (charCode == quote) { next(parseInfo); - buffer = ""; + result.push(yieldResult(parseInfo, + /* hasNewLine ? EnumToken.BadStringTokenType : */ exports.EnumToken.StringTokenType, decodeSegments ? { decodeSegments } : null)); return result; } - if (isNewLine(value.charCodeAt(0))) { - result.push(yieldResult(buffer + next(parseInfo), parseInfo, exports.EnumToken.BadStringTokenType)); + if (isNewLine(charCode)) { + next(parseInfo); + result.push(yieldResult(parseInfo, exports.EnumToken.BadStringTokenType)); return result; } - buffer += value; next(parseInfo); } // EOF - 'Unclosed-string' fixed - result.push(yieldResult(buffer + quote, parseInfo, exports.EnumToken.StringTokenType)); + result.push(yieldResult(parseInfo, exports.EnumToken.StringTokenType)); return result; } - function yieldResult(val, parseInfo, hint) { + function yieldResult(parseInfo, hint, options) { + let val = parseInfo.stream.slice(parseInfo.position - parseInfo.offset, parseInfo.currentPosition - parseInfo.offset); let token = null; let dimension; - // console.debug(`Yield result: ${val}, ${hint}`); + if (options?.decodeSegments) { + val = val.replace(/\\([0-9a-fA-F]{1,6})(?:\s)?/g, (_, sequence) => { + const codepoint = parseInt(sequence, 16); + if (codepoint == 0 || + // leading surrogate + (0xd800 <= codepoint && codepoint <= 0xdbff) || + // trailing surrogate + (0xdc00 <= codepoint && codepoint <= 0xdfff) || + codepoint > 0x10ffff) { + return "\uFFFD"; + } + return String.fromCodePoint(codepoint); + }); + } if (hint != null) { let searchArray = null; switch (hint) { @@ -21815,7 +22155,12 @@ else { let slice = val.slice(1); const chr = val.charAt(0); - if (chr == "@" && isIdent(slice)) { + if (chr == "!" && equalsIgnoreCase("!important", val)) { + token = { + typ: exports.EnumToken.ImportantTokenType, + }; + } + else if (chr == "@" && isIdent(slice)) { token = { typ: exports.EnumToken.AtRuleTokenType, nam: slice, @@ -21890,12 +22235,12 @@ end: parseInfo.currentPosition, }; parseInfo.position = parseInfo.currentPosition; - return { token, bytesIn: parseInfo.currentPosition + 1 }; + return { token, bytesIn: parseInfo.currentPosition }; } function match(parseInfo, input) { let position = parseInfo.currentPosition - parseInfo.offset; for (let i = 0; i < input.length; i++) { - if (parseInfo.stream[position + i + 1] != input.charAt(i)) { + if (parseInfo.stream[position + i] != input.charAt(i)) { return false; } } @@ -21903,14 +22248,14 @@ } function peek(parseInfo, count = 1) { if (count == 1) { - return parseInfo.stream.charAt(parseInfo.currentPosition - parseInfo.offset + 1); + return parseInfo.stream.charAt(parseInfo.currentPosition - parseInfo.offset); } const position = parseInfo.currentPosition - parseInfo.offset; - return parseInfo.stream.slice(position + 1, position + count + 1); + return parseInfo.stream.slice(position, position + count); } function next(parseInfo, count = 1) { let position = parseInfo.currentPosition - parseInfo.offset; - let char = count == 1 ? parseInfo.stream.charAt(position + 1) : parseInfo.stream.slice(position + 1, position + 1 + count); + let char = count == 1 ? parseInfo.stream.charAt(position) : parseInfo.stream.slice(position, position + count); let i = 0; let codepoint; for (; i < char.length; i++) { @@ -21929,198 +22274,295 @@ } } } - parseInfo.currentPosition += char.length; - return char; + parseInfo.currentPosition += char.length; + return char; + } + function isIdentToken(parseInfo, start, end) { + let j = parseInfo.currentPosition - parseInfo.offset; + let i = parseInfo.position - parseInfo.offset; + if (start != null) { + if (end == null) { + if (start < 0) { + j += start; + } + else { + i += start; + } + } + else { + if (end < 0) { + j += end; + } + else { + j = parseInfo.position + end; + } + } + } + j--; + let codepoint = parseInfo.stream.charCodeAt(i); + // - + if (codepoint == 0x2d) { + let nextCodepoint; + if ((nextCodepoint = parseInfo.stream.charCodeAt(i + 1)) != nextCodepoint) { + return false; + } + if (isDigit(nextCodepoint)) { + return false; + } + codepoint = nextCodepoint; + i++; + } + if (codepoint !== 0x2d && !isIdentStart(codepoint)) { + return false; + } + if (codepoint == 92 /* TokenMap.REVERSE_SOLIDUS */) { + codepoint = parseInfo.stream.charCodeAt(i + 1); + // if (!isIdentCodepoint(codepoint)) { + // return false; + // } + i += String.fromCodePoint(codepoint).length; + // if (i < j) { + // codepoint = name.charCodeAt(i) as number; + // if (!isIdentCodepoint(codepoint)) { + // return false; + // } + // } + } + while (i < j) { + i += codepoint < 0x80 ? 1 : String.fromCodePoint(codepoint).length; + codepoint = parseInfo.stream.charCodeAt(i); + if (codepoint == 92 /* TokenMap.REVERSE_SOLIDUS */) { + i += codepoint < 0x80 ? 1 : String.fromCodePoint(codepoint).length; + codepoint = parseInfo.stream.charCodeAt(i); + i += codepoint < 0x80 ? 1 : String.fromCodePoint(codepoint).length; + continue; + } + if (codepoint !== 0x2d && !isIdentCodepoint(codepoint)) { + return false; + } + } + return true; + } + function isPseudo(parseInfo) { + let position = parseInfo.currentPosition - parseInfo.offset; + let endPosition = parseInfo.currentPosition - parseInfo.offset; + return (parseInfo.stream.charAt(position) == ":" && + parseInfo.stream.charAt(endPosition - 1) == "(" && + (parseInfo.stream.charAt(position + 1) == ":" + ? isIdentToken(parseInfo, 2, -1) + : isIdentToken(parseInfo, 1, -1))) || + parseInfo.stream.charAt(position + 1) == ":" + ? isIdentToken(parseInfo, 2) + : isIdentToken(parseInfo, 1); + } + function startsWith(parseInfo, input) { + let i = 0; + let j = input.length; + while (i < j) { + if (parseInfo.stream.charAt(parseInfo.position - parseInfo.offset + i) != input.charAt(i)) { + return false; + } + i++; + } + return true; + } + function isURLToken(parseInfo) { + let i = parseInfo.position - parseInfo.offset; + let c; + while (++i < parseInfo.currentPosition) { + c = parseInfo.stream.charCodeAt(i); + // single quote or double quote or start parenthesis or close parenthesis + if (isNonPrintable(c) || c == 0x27 || c == 0x22 || c == 0x28 || c == 0x29) { + return false; + } + // valid escape + if (c == 92 /* TokenMap.REVERSE_SOLIDUS */) { + i++; + if (i >= parseInfo.currentPosition) { + return false; + } + c = parseInfo.stream.charCodeAt(i); + // c is not '\n' or '\r' or '\f' + if (c == 0x6e || c == 0x72 || c == 0x66) { + return false; + } + continue; + } + // is white space + if (c == 0x20 || c == 0x09) { + break; + } + } + return i == parseInfo.currentPosition; } /** - * Tokenize css string + * Tokenize CSS string * @param parseInfo * @param yieldEOFToken */ function tokenize(parseInfo, yieldEOFToken = true) { if (typeof parseInfo == "string") { parseInfo = { - buffer: "", stream: parseInfo, source: new SourceFile(parseInfo, [], ""), offset: 0, time: 0, position: 0, - currentPosition: -1, + currentPosition: 0, }; } - let value; - let buffer = parseInfo.buffer; let charCode; let nextCharCode; const startTime = performance.now(); const result = []; // allow 10 characters buffer for the streaming parser to avoid incomplete tokens - const endPosition = parseInfo.stream.length - 10; - parseInfo.buffer = ""; - while ((value = peek(parseInfo))) { - charCode = value.charCodeAt(0); - // nextCharCode = nextValue.charCodeAt(0); - // console.debug({value, buffer}); + const endPosition = parseInfo.stream.length - 1; + // NaN is not equal to NaN + while ((charCode = peek(parseInfo).charCodeAt(0)) == charCode) { switch (charCode) { case 61 /* TokenMap.EQUALS */: - if (buffer.length > 0) { - result.push(yieldResult(buffer, parseInfo)); - buffer = ""; + if (parseInfo.position < parseInfo.currentPosition) { + result.push(yieldResult(parseInfo)); } - result.push(yieldResult(next(parseInfo), parseInfo, exports.EnumToken.DelimTokenType)); + next(parseInfo); + result.push(yieldResult(parseInfo, exports.EnumToken.DelimTokenType)); break; // '+' or '-' case 43 /* TokenMap.PLUS */: case 45 /* TokenMap.MINUS */: - next(parseInfo); - if (charCode === 43 /* TokenMap.PLUS */ && !isNumber(peek(parseInfo))) { - if (buffer.length > 0) { - result.push(yieldResult(buffer, parseInfo)); - buffer = ""; - } - result.push(yieldResult(value, parseInfo, SymbolsMapTokens[value])); + nextCharCode = peek(parseInfo).charCodeAt(0); + // not a number + if (charCode === 43 /* TokenMap.PLUS */ && !(nextCharCode >= 0x30 && nextCharCode <= 0x39)) { + if (parseInfo.position < parseInfo.currentPosition) { + result.push(yieldResult(parseInfo)); + } + next(parseInfo); + result.push(yieldResult(parseInfo, SymbolsMapTokens[parseInfo.stream + .slice(parseInfo.position - parseInfo.offset, parseInfo.currentPosition - parseInfo.offset) + .toLowerCase()])); break; } - buffer += value; + next(parseInfo); break; // '{' case 123 /* TokenMap.LEFT_BRACE */: - if (buffer.length > 0) { - result.push(yieldResult(buffer, parseInfo)); - buffer = ""; + if (parseInfo.position < parseInfo.currentPosition) { + result.push(yieldResult(parseInfo)); } - result.push(yieldResult(next(parseInfo), parseInfo, exports.EnumToken.BlockStartTokenType)); + next(parseInfo); + result.push(yieldResult(parseInfo, exports.EnumToken.BlockStartTokenType)); break; // '}' case 125 /* TokenMap.RIGHT_BRACE */: - if (buffer.length > 0) { - result.push(yieldResult(buffer, parseInfo)); - buffer = ""; + if (parseInfo.position < parseInfo.currentPosition) { + result.push(yieldResult(parseInfo)); } - result.push(yieldResult(next(parseInfo), parseInfo, exports.EnumToken.BlockEndTokenType)); + next(parseInfo); + result.push(yieldResult(parseInfo, exports.EnumToken.BlockEndTokenType)); break; // '(' case 40 /* TokenMap.LEFT_PARENTHESIS */: - if (buffer.length > 0) { - if (buffer[0] === ":" && isPseudo(buffer)) { + if (parseInfo.position < parseInfo.currentPosition) { + if (parseInfo.stream[parseInfo.position - parseInfo.offset] === ":" && isPseudo(parseInfo)) { next(parseInfo); - result.push(yieldResult(buffer, parseInfo, exports.EnumToken.PseudoClassFunctionTokenDefType)); - buffer = ""; + result.push(yieldResult(parseInfo, exports.EnumToken.PseudoClassFunctionTokenDefType)); break; } - else if (isIdent(buffer)) { - const hint = buffer.startsWith("--") + else if (isIdentToken(parseInfo)) { + const hint = startsWith(parseInfo, "--") ? exports.EnumToken.CustomFunctionTokenDefType - : (SymbolsMapTokens[buffer.toLowerCase() + "("] ?? exports.EnumToken.FunctionTokenDefType); - result.push(yieldResult(buffer, parseInfo, hint)); + : (SymbolsMapTokens[parseInfo.stream + .slice(parseInfo.position - parseInfo.offset, parseInfo.currentPosition - parseInfo.offset) + .toLowerCase() + "("] ?? exports.EnumToken.FunctionTokenDefType); + result.push(yieldResult(parseInfo, hint)); next(parseInfo); - buffer = ""; + // consume '(' + parseInfo.position = parseInfo.currentPosition; if (hint === exports.EnumToken.UrlFunctionTokenDefType) { - buffer = ""; - value = peek(parseInfo); // consume an - while (isWhiteSpace((charCode = value.charCodeAt(0)))) { - buffer += next(parseInfo); - value = peek(parseInfo); - charCode = value.charCodeAt(0); - if (value === "/" && match(parseInfo, "/*")) { - if (buffer.length > 0) { - result.push(yieldResult(buffer, parseInfo)); - buffer = ""; - } - buffer += next(parseInfo, 2); - while ((value = next(parseInfo))) { - if (value == "*") { - buffer += value; - if (match(parseInfo, "/")) { - result.push(yieldResult(buffer + next(parseInfo), parseInfo, exports.EnumToken.CommentTokenType)); - buffer = ""; - break; - } - } - else { - buffer += value; - } - } - if (buffer.length > 0) { - result.push(yieldResult(buffer, parseInfo, exports.EnumToken.BadCommentTokenType)); - buffer = ""; - } - value = peek(parseInfo); - charCode = value.charCodeAt(0); - } + while (isWhiteSpace(peek(parseInfo).charCodeAt(0))) { + next(parseInfo); } - if (buffer.length > 0) { - result.push(yieldResult(buffer, parseInfo, exports.EnumToken.WhitespaceTokenType)); - buffer = ""; + charCode = peek(parseInfo).charCodeAt(0); + let values = null; + if (charCode == 34 /* TokenMap.DOUBLE_QUOTE */ || charCode == 39 /* TokenMap.SINGLE_QUOTE */) { + values = consumeString(parseInfo); } - if (value === ")" || value === '"' || value === "'") { - break; + else { + do { + next(parseInfo); + // value = peek(parseInfo); + charCode = peek(parseInfo).charCodeAt(0); + } while ( + // !(value === "/" && match(parseInfo, "/*") && + charCode !== 41 /* TokenMap.RIGHT_PARENTHESIS */ && + parseInfo.currentPosition < endPosition); + } + if (values != null) { + // NaN is not equal to NaN + if ((charCode = peek(parseInfo).charCodeAt(0)) != charCode) { + for (let i = 0; i < values.length; i++) { + values[i].token.typ = exports.EnumToken.BadUrlTokenType; + } + } + result.push(...values); } - do { - buffer += next(parseInfo); - value = peek(parseInfo); - charCode = value.charCodeAt(0); - } while (value !== ")" && - !isWhiteSpace(charCode) && - !(value === "/" && match(parseInfo, "/*"))); - if (buffer.length > 0) { - result.push(yieldResult(buffer, parseInfo, peek(parseInfo) === "" + else if (parseInfo.position < parseInfo.currentPosition) { + result.push(yieldResult(parseInfo, + // parseInfo.position < parseInfo.currentPosition + (charCode = peek(parseInfo).charCodeAt(0)) != charCode || !isURLToken(parseInfo) ? exports.EnumToken.BadUrlTokenType : exports.EnumToken.UrlTokenTokenType)); - buffer = ""; } } - // console.debug({value: peek(parseInfo)}); break; } } - result.push(yieldResult(next(parseInfo), parseInfo, exports.EnumToken.StartParensTokenType)); - buffer = ""; + next(parseInfo); + result.push(yieldResult(parseInfo, exports.EnumToken.StartParensTokenType)); break; // ')' case 41 /* TokenMap.RIGHT_PARENTHESIS */: - if (buffer.length > 0) { - result.push(yieldResult(buffer, parseInfo)); - buffer = ""; + if (parseInfo.position < parseInfo.currentPosition) { + result.push(yieldResult(parseInfo)); } - result.push(yieldResult(next(parseInfo), parseInfo, exports.EnumToken.EndParensTokenType)); + next(parseInfo); + result.push(yieldResult(parseInfo, exports.EnumToken.EndParensTokenType)); break; // '[' case 91 /* TokenMap.LEFT_BRACKETS */: - if (buffer.length > 0) { - result.push(yieldResult(buffer, parseInfo)); - buffer = ""; + if (parseInfo.position < parseInfo.currentPosition) { + result.push(yieldResult(parseInfo)); } - result.push(yieldResult(next(parseInfo), parseInfo, exports.EnumToken.AttrStartTokenType)); + next(parseInfo); + result.push(yieldResult(parseInfo, exports.EnumToken.AttrStartTokenType)); break; // ']' case 93 /* TokenMap.RIGHT_BRACKETS */: - if (buffer.length > 0) { - result.push(yieldResult(buffer, parseInfo)); - buffer = ""; + if (parseInfo.position < parseInfo.currentPosition) { + result.push(yieldResult(parseInfo)); } - result.push(yieldResult(next(parseInfo), parseInfo, exports.EnumToken.AttrEndTokenType)); + next(parseInfo); + result.push(yieldResult(parseInfo, exports.EnumToken.AttrEndTokenType)); break; case 59 /* TokenMap.SEMICOLON */: - if (buffer.length > 0) { - result.push(yieldResult(buffer, parseInfo)); - buffer = ""; + if (parseInfo.position < parseInfo.currentPosition) { + result.push(yieldResult(parseInfo)); } - result.push(yieldResult(next(parseInfo), parseInfo, exports.EnumToken.SemiColonTokenType)); + next(parseInfo); + result.push(yieldResult(parseInfo, exports.EnumToken.SemiColonTokenType)); break; case 58 /* TokenMap.COLON */: - if (buffer.length > 0) { - result.push(yieldResult(buffer, parseInfo)); - buffer = ""; + if (parseInfo.position < parseInfo.currentPosition) { + result.push(yieldResult(parseInfo)); } next(parseInfo); if (peek(parseInfo).charCodeAt(0) == 58 /* TokenMap.COLON */) { - result.push(yieldResult(value + next(parseInfo), parseInfo, exports.EnumToken.DoubleColonTokenType)); + next(parseInfo); + result.push(yieldResult(parseInfo, exports.EnumToken.DoubleColonTokenType)); break; } - result.push(yieldResult(value, parseInfo, exports.EnumToken.ColonTokenType)); + result.push(yieldResult(parseInfo, exports.EnumToken.ColonTokenType)); break; // \n \r \f \v \t space case 0x9: @@ -22131,241 +22573,229 @@ case 0xd: case 0x2028: case 0x2029: - if (buffer.length > 0) { - result.push(yieldResult(buffer, parseInfo)); - buffer = ""; + if (parseInfo.position < parseInfo.currentPosition) { + result.push(yieldResult(parseInfo)); } - buffer += next(parseInfo); - nextCharCode = parseInfo.stream.charAt(parseInfo.currentPosition - parseInfo.offset + 1).charCodeAt(0); + next(parseInfo); + nextCharCode = parseInfo.stream.charAt(parseInfo.currentPosition - parseInfo.offset).charCodeAt(0); while (nextCharCode == 0x20 || (nextCharCode >= 0x9 && nextCharCode <= 0xd) || nextCharCode == 0x2028 || nextCharCode == 0x2029) { - value += next(parseInfo); - nextCharCode = parseInfo.stream - .charAt(parseInfo.currentPosition - parseInfo.offset + 1) - .charCodeAt(0); + next(parseInfo); + nextCharCode = parseInfo.stream.charAt(parseInfo.currentPosition - parseInfo.offset).charCodeAt(0); } - result.push(yieldResult(value, parseInfo, exports.EnumToken.WhitespaceTokenType)); - buffer = ""; + result.push(yieldResult(parseInfo, exports.EnumToken.WhitespaceTokenType)); break; case 44 /* TokenMap.COMMA */: - if (buffer.length > 0) { - result.push(yieldResult(buffer, parseInfo)); - buffer = ""; + if (parseInfo.position < parseInfo.currentPosition) { + result.push(yieldResult(parseInfo)); } - result.push(yieldResult(next(parseInfo), parseInfo, exports.EnumToken.CommaTokenType)); + next(parseInfo); + result.push(yieldResult(parseInfo, exports.EnumToken.CommaTokenType)); break; case 36 /* TokenMap.DOLLAR */: - if (buffer.length > 0) { - result.push(yieldResult(buffer, parseInfo)); - buffer = ""; + if (parseInfo.position < parseInfo.currentPosition) { + result.push(yieldResult(parseInfo)); } if (match(parseInfo, "$=")) { - result.push(yieldResult(next(parseInfo, 2), parseInfo, exports.EnumToken.EndMatchTokenType)); + next(parseInfo, 2); + result.push(yieldResult(parseInfo, exports.EnumToken.EndMatchTokenType)); break; } - buffer += next(parseInfo); + next(parseInfo); break; case 126 /* TokenMap.TILDA */: - if (buffer.length > 0) { - result.push(yieldResult(buffer, parseInfo)); - buffer = ""; + if (parseInfo.position < parseInfo.currentPosition) { + result.push(yieldResult(parseInfo)); } if (match(parseInfo, "~=")) { - result.push(yieldResult(next(parseInfo, 2), parseInfo, exports.EnumToken.IncludeMatchTokenType)); + next(parseInfo, 2); + result.push(yieldResult(parseInfo, exports.EnumToken.IncludeMatchTokenType)); break; } - result.push(yieldResult(next(parseInfo), parseInfo, exports.EnumToken.Tilda)); - buffer = ""; + next(parseInfo); + result.push(yieldResult(parseInfo, exports.EnumToken.Tilda)); break; // case '^': case 94 /* TokenMap.CARET */: - if (buffer.length > 0) { - result.push(yieldResult(buffer, parseInfo)); - buffer = ""; + if (parseInfo.position < parseInfo.currentPosition) { + result.push(yieldResult(parseInfo)); } if (match(parseInfo, "^=")) { - result.push(yieldResult(next(parseInfo, 2), parseInfo, exports.EnumToken.StartMatchTokenType)); + next(parseInfo, 2); + result.push(yieldResult(parseInfo, exports.EnumToken.StartMatchTokenType)); break; } - buffer += next(parseInfo); + next(parseInfo); break; case 42 /* TokenMap.STAR */: - if (buffer.length > 0) { - result.push(yieldResult(buffer, parseInfo)); - buffer = ""; + if (parseInfo.position < parseInfo.currentPosition) { + result.push(yieldResult(parseInfo)); } if (match(parseInfo, "*=")) { - result.push(yieldResult(next(parseInfo, 2), parseInfo, exports.EnumToken.ContainMatchTokenType)); + next(parseInfo, 2); + result.push(yieldResult(parseInfo, exports.EnumToken.ContainMatchTokenType)); break; } - result.push(yieldResult(next(parseInfo), parseInfo, exports.EnumToken.Star)); - buffer = ""; + next(parseInfo); + result.push(yieldResult(parseInfo, exports.EnumToken.Star)); break; case 38 /* TokenMap.AMPERSAND */: - if (buffer.length > 0) { - result.push(yieldResult(buffer, parseInfo)); - buffer = ""; + if (parseInfo.position < parseInfo.currentPosition) { + result.push(yieldResult(parseInfo)); } - result.push(yieldResult(next(parseInfo), parseInfo, exports.EnumToken.NestingSelectorTokenType)); - buffer = ""; + next(parseInfo); + result.push(yieldResult(parseInfo, exports.EnumToken.NestingSelectorTokenType)); break; case 124 /* TokenMap.PIPE */: - if (buffer.length > 0) { - result.push(yieldResult(buffer, parseInfo)); - buffer = ""; + if (parseInfo.position < parseInfo.currentPosition) { + result.push(yieldResult(parseInfo)); } // '||' if (match(parseInfo, "||")) { - result.push(yieldResult(next(parseInfo, 2), parseInfo, exports.EnumToken.ColumnCombinatorTokenType)); + next(parseInfo, 2); + result.push(yieldResult(parseInfo, exports.EnumToken.ColumnCombinatorTokenType)); break; } else if (match(parseInfo, "|=")) { - result.push(yieldResult(next(parseInfo, 2), parseInfo, exports.EnumToken.DashMatchTokenType)); + next(parseInfo, 2); + result.push(yieldResult(parseInfo, exports.EnumToken.DashMatchTokenType)); break; } - result.push(yieldResult(next(parseInfo), parseInfo, exports.EnumToken.Pipe)); - buffer = ""; + next(parseInfo); + result.push(yieldResult(parseInfo, exports.EnumToken.Pipe)); break; case 33 /* TokenMap.EXCLAMATION */: - if (buffer.length > 0) { - result.push(yieldResult(buffer, parseInfo)); - buffer = ""; + if (parseInfo.position < parseInfo.currentPosition) { + result.push(yieldResult(parseInfo)); } if (match(parseInfo, "!important")) { - result.push(yieldResult(next(parseInfo, 10), parseInfo, exports.EnumToken.ImportantTokenType)); - buffer = ""; + next(parseInfo, 10); + result.push(yieldResult(parseInfo, exports.EnumToken.ImportantTokenType)); break; } - buffer += next(parseInfo); + next(parseInfo); break; case 47 /* TokenMap.SLASH */: - if (buffer.length > 0) { - result.push(yieldResult(buffer, parseInfo)); - buffer = ""; + if (parseInfo.position < parseInfo.currentPosition) { + result.push(yieldResult(parseInfo)); } if (!match(parseInfo, "/*")) { - result.push(yieldResult(next(parseInfo), parseInfo, SymbolsMapTokens[value])); + next(parseInfo); + result.push(yieldResult(parseInfo, SymbolsMapTokens[parseInfo.stream.slice(parseInfo.position, parseInfo.currentPosition)])); break; } - buffer += next(parseInfo, 2); - while ((value = next(parseInfo))) { - if (value == "*") { - buffer += value; + next(parseInfo, 2); + while ((charCode = next(parseInfo).charCodeAt(0)) == charCode) { + if (charCode == 42 /* TokenMap.STAR */) { if (match(parseInfo, "/")) { - result.push(yieldResult(buffer + next(parseInfo), parseInfo, exports.EnumToken.CommentTokenType)); - buffer = ""; + next(parseInfo); + result.push(yieldResult(parseInfo, exports.EnumToken.CommentTokenType)); break; } } - else { - buffer += value; - } + // else { + // buffer += value; + // } } - if (buffer.length > 0) { - result.push(yieldResult(buffer, parseInfo, exports.EnumToken.BadCommentTokenType)); - buffer = ""; + if (parseInfo.position < parseInfo.currentPosition) { + result.push(yieldResult(parseInfo, exports.EnumToken.BadCommentTokenType)); } break; case 62 /* TokenMap.GREATERTHAN */: - if (buffer.length > 0) { - result.push(yieldResult(buffer, parseInfo)); - buffer = ""; + if (parseInfo.position < parseInfo.currentPosition) { + result.push(yieldResult(parseInfo)); } if (match(parseInfo, ">=")) { - result.push(yieldResult(next(parseInfo, 2), parseInfo, exports.EnumToken.GteTokenType)); + next(parseInfo, 2); + result.push(yieldResult(parseInfo, exports.EnumToken.GteTokenType)); break; } - result.push(yieldResult(next(parseInfo), parseInfo, exports.EnumToken.GtTokenType)); - buffer = ""; + next(parseInfo); + result.push(yieldResult(parseInfo, exports.EnumToken.GtTokenType)); break; case 60 /* TokenMap.LOWERTHAN */: - if (buffer.length > 0) { - result.push(yieldResult(buffer, parseInfo)); - buffer = ""; + if (parseInfo.position < parseInfo.currentPosition) { + result.push(yieldResult(parseInfo)); } if (match(parseInfo, "<=")) { - result.push(yieldResult(next(parseInfo, 2), parseInfo, exports.EnumToken.LteTokenType)); + next(parseInfo, 2); + result.push(yieldResult(parseInfo, exports.EnumToken.LteTokenType)); break; } - buffer += next(parseInfo); + next(parseInfo); if (match(parseInfo, "!--")) { - buffer += next(parseInfo, 3); - while ((value = next(parseInfo))) { - buffer += value; - if (value == "-" && match(parseInfo, "->")) { + next(parseInfo, 3); + while ((charCode = next(parseInfo).charCodeAt(0)) == charCode) { + if (charCode == 45 /* TokenMap.MINUS */ && match(parseInfo, "->")) { break; } } - if (value === "") { - result.push(yieldResult(buffer, parseInfo, exports.EnumToken.BadCdoTokenType)); + if (parseInfo.currentPosition >= endPosition) { + result.push(yieldResult(parseInfo, exports.EnumToken.BadCdoTokenType)); } else { - result.push(yieldResult(buffer + next(parseInfo, 2), parseInfo, exports.EnumToken.CDOCOMMTokenType)); + next(parseInfo, 2); + result.push(yieldResult(parseInfo, exports.EnumToken.CDOCOMMTokenType)); } - buffer = ""; } break; case 35 /* TokenMap.HASH */: - if (buffer.length > 0) { - result.push(yieldResult(buffer, parseInfo)); - buffer = ""; + if (parseInfo.position < parseInfo.currentPosition) { + result.push(yieldResult(parseInfo)); } - buffer += next(parseInfo); + next(parseInfo); break; case 92 /* TokenMap.REVERSE_SOLIDUS */: + if (!yieldEOFToken && parseInfo.stream.length == parseInfo.currentPosition - parseInfo.offset + 1) { + break; + } next(parseInfo); // EOF - if (!(peek(parseInfo))) { + if (!peek(parseInfo)) { + if (!yieldEOFToken) { + break; + } // end of stream ignore \\ - if (buffer.length > 0) { - result.push(yieldResult(buffer, parseInfo)); - buffer = ""; + if (parseInfo.position < parseInfo.currentPosition) { + result.push(yieldResult(parseInfo)); } break; } - buffer += value + next(parseInfo); - // buffer += - // (parseInfo.offset == parseInfo.currentPosition - // ? parseInfo.buffer.slice(-1) - // : parseInfo.stream.charAt(parseInfo.currentPosition - parseInfo.offset - 1)) + value; + next(parseInfo); break; case 39 /* TokenMap.SINGLE_QUOTE */: case 34 /* TokenMap.DOUBLE_QUOTE */: - if (buffer.length > 0) { - result.push(yieldResult(buffer, parseInfo)); - buffer = ""; + if (parseInfo.position < parseInfo.currentPosition) { + result.push(yieldResult(parseInfo)); } result.push(...consumeString(parseInfo)); break; case 46 /* TokenMap.DOT */: const codepoint = parseInfo.stream - .charAt(parseInfo.currentPosition - parseInfo.offset + 2) + .charAt(parseInfo.currentPosition - parseInfo.offset + 1) .charCodeAt(0); - if (!isDigit(codepoint) && buffer !== "") { - result.push(yieldResult(buffer, parseInfo)); - buffer = next(parseInfo, 2); + if (!isDigit(codepoint) && parseInfo.position !== parseInfo.currentPosition) { + result.push(yieldResult(parseInfo)); + next(parseInfo, 2); break; } - buffer += next(parseInfo); + next(parseInfo); break; default: - buffer += next(parseInfo); + next(parseInfo); break; } - if (!yieldEOFToken && endPosition <= parseInfo.stream.length - parseInfo.currentPosition + parseInfo.offset) { + if (!yieldEOFToken && endPosition <= parseInfo.currentPosition - parseInfo.offset + 1) { break; } } if (yieldEOFToken) { - if (buffer.length > 0) { - result.push(yieldResult(buffer, parseInfo)); + if (parseInfo.position < parseInfo.currentPosition) { + result.push(yieldResult(parseInfo)); } - result.push(yieldResult("", parseInfo, exports.EnumToken.EOFTokenType)); - } - else { - parseInfo.buffer = buffer; + result.push(yieldResult(parseInfo, exports.EnumToken.EOFTokenType)); } parseInfo.time += performance.now() - startTime; return result; @@ -22378,19 +22808,17 @@ async function* tokenizeStream(input, parseInfo) { const decoder = new TextDecoder("utf-8"); const reader = input.getReader(); + parseInfo.stream = ""; while (true) { const { done, value } = await reader.read(); const stream = ArrayBuffer.isView(value) ? decoder.decode(value, { stream: true }) : value; if (!done) { parseInfo.source.append(stream); - if (typeof parseInfo.stream != "string") { - parseInfo.stream = stream; - } - else { - parseInfo.stream = (parseInfo.stream.slice(parseInfo.currentPosition - parseInfo.offset + 1) + - stream); - } - parseInfo.offset = parseInfo.currentPosition + 1; + parseInfo.stream = (parseInfo.stream.slice(parseInfo.position - parseInfo.offset) + stream); + parseInfo.offset = parseInfo.offset = parseInfo.position; + } + else { + parseInfo.stream = ""; } yield* tokenize(parseInfo, done); if (done) { @@ -22404,7 +22832,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 +22844,7 @@ * @param errors * @param nestingContent * + * @param context * @private */ function minify(ast, options = {}, recursive = false, errors, nestingContent, context = {}) { @@ -22423,22 +22852,22 @@ let postprocess = false; let parents; let replacement; - if (!("features" in options)) { - // @ts-ignore - options = { + let { sourcemap, module, ...options2 } = options; + if (!(options2.features != null)) { + 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 +22882,20 @@ 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); + : // @ts-ignore + replacement.nam); } - const result = feature.run(replacement, options, parent[PARENT] ?? ast, context, exports.FeatureWalkMode.Pre); + const result = feature.run(replacement, options2, + // @ts-ignore + parent[PARENT] ?? ast, context, exports.FeatureWalkMode.Pre); if (result != null) { replacement = result; } @@ -22472,24 +22904,26 @@ (!Array.isArray(replacement) || replacement.length > 0) && replacement != parent && parent[PARENT] != null) { + // @ts-ignore replaceNodeOrValue(parent[PARENT], parent, replacement); } - if ("chi" in replacement) { + // @ts-ignore + if (replacement.chi != null) { // @ts-ignore for (const node of replacement.chi) { - // node[PARENT] = replacement; + node[PARENT] = replacement; parents.add(node); } } } - 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 +22931,14 @@ } 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, + // @ts-ignore + parent[PARENT] ?? ast, context, exports.FeatureWalkMode.Post); if (result != null) { replacement = result; } @@ -22515,18 +22951,20 @@ // @ts-ignore replaceNodeOrValue(parent[PARENT], parent, replacement); } - if ("chi" in replacement) { + // @ts-ignore + if (replacement.chi != null) { + // @ts-ignore for (const node of replacement.chi) { - // node[PARENT] = replacement; + node[PARENT] = replacement; parents.add(node); } } } 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); } } } @@ -22546,6 +22984,7 @@ values[values.indexOf(value)] = value.l; } else { + // @ts-ignore replaceNodeOrValue(parent, value, value.l); // @ts-ignore value = value.l; @@ -22602,9 +23041,11 @@ // @ts-expect-error const p = parents?.[parents?.indexOf?.(parent) + 1]; if (p != null) { + // @ts-ignore replaceNodeOrValue(p, parent, replacement); } else { + // @ts-ignore values.splice(values.indexOf(parent), 1, replacement); } hasUpdates = true; @@ -22619,9 +23060,9 @@ * Minify at-rule media * - remove redundant tokens * - generate range queries - * @param ast * * @private + * @param tokens */ function minifyAtRuleMedia(tokens) { let hasUpdates = false; @@ -22718,8 +23159,8 @@ continue; } while (previous?.typ === exports.EnumToken.CommentNodeType) { + // @ts-ignore previous = ast.chi[--nodeIndex]; - continue; } node = ast.chi[i]; if (node.typ === exports.EnumToken.AtRuleNodeType && node.nam === "font-face") { @@ -22735,12 +23176,13 @@ 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 previous.chi.push(...node.chi); + // @ts-ignore ast.chi.splice(i, 1); previous = ast?.chi?.[nodeIndex] ?? null; i = nodeIndex; @@ -22813,6 +23255,7 @@ else if (ast.typ === node.typ && ast.nam === node.nam && ast.val === node.val) { + // @ts-ignore replaceNodeOrValue(ast, node, node.chi); i--; continue; @@ -22906,7 +23349,9 @@ ":is(" + node[OPTIMIZED].selector.reduce(reducer, []).join(",") + ")"; - const sel2 = node[OPTIMIZED].selector.reduce((acc, curr) => (acc.length > 0 ? acc + "," : "") + node[OPTIMIZED].optimized[0] + curr.join(""), ""); + const sel2 = node[OPTIMIZED].selector.reduce((acc, curr) => + // @ts-ignore + (acc.length > 0 ? acc + "," : "") + node[OPTIMIZED].optimized[0] + curr.join(""), ""); node.sel = sel1.length < sel2.length ? sel1 : sel2; node[TOKENS] = null; } @@ -22986,7 +23431,9 @@ ":is(" + node[OPTIMIZED].selector.reduce(reducer, []).join(",") + ")"; - const sel2 = node[OPTIMIZED].selector.reduce((acc, curr) => (acc.length > 0 ? acc + "," : "") + node[OPTIMIZED].optimized[0] + curr.join(""), ""); + const sel2 = node[OPTIMIZED].selector.reduce((acc, curr) => + // @ts-ignore + (acc.length > 0 ? acc + "," : "") + node[OPTIMIZED].optimized[0] + curr.join(""), ""); node.sel = sel1.length < sel2.length ? sel1 : sel2; node[TOKENS] = null; } @@ -22997,11 +23444,14 @@ curr.join(""), ""); node[TOKENS] = null; } + // @ts-ignore } else if (node[OPTIMIZED]?.optimized.length > 0) { + // @ts-ignore const sel = node[OPTIMIZED].optimized.join(""); if (sel.length < node.sel.length) { node.sel = sel; + // @ts-ignore node[RAW] = [node[OPTIMIZED].optimized.slice()]; node[TOKENS] = null; } @@ -23024,11 +23474,14 @@ } if (shouldMerge) { if (((node.typ === exports.EnumToken.RuleNodeType || - node.typ === exports.EnumToken.KeyFramesRuleNodeType) && + node.typ === exports.EnumToken.KeyframesRuleNodeType) && node.sel === previous.sel) || + // @ts-ignore (node.typ == exports.EnumToken.AtRuleNodeType && node.nam !== "font-face" && + // @ts-ignore node.nam === previous.nam)) { + // @ts-ignore node.chi.unshift(...previous.chi); doMinify(node, options, recursive, errors, nestingContent, context); ast.chi.splice(nodeIndex, 1); @@ -23037,7 +23490,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 +23643,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 +24027,6 @@ * Diff nodes * @param n1 * @param n2 - * @param reducer * @param options * * @private @@ -23691,17 +24145,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) { @@ -23791,12 +24264,24 @@ * @private */ function expand(ast) { - const result = { ...ast, chi: [] }; + if (ast[STATE] == exports.EnumAstNodeStatus.Invalid || + ast[STATE] == exports.EnumAstNodeStatus.Disallowed || + ast[STATE] == exports.EnumAstNodeStatus.Unknown || + ast[STATE] == exports.EnumAstNodeStatus.Unparsed || + ast[STATE] == exports.EnumAstNodeStatus.Malformed) { + return ast; + } + const result = Object.assign(cloneNode(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 +24293,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); } @@ -23819,7 +24317,14 @@ return result; } function expandRule(node) { - const ast = { ...node, chi: node.chi.slice() }; + if (node[STATE] == exports.EnumAstNodeStatus.Invalid || + node[STATE] == exports.EnumAstNodeStatus.Disallowed || + node[STATE] == exports.EnumAstNodeStatus.Unknown || + node[STATE] == exports.EnumAstNodeStatus.Unparsed || + node[STATE] == exports.EnumAstNodeStatus.Malformed) { + return [node]; + } + const ast = Object.assign(cloneNode(node), { chi: node.chi.slice() }); const result = []; if (ast.typ == exports.EnumToken.RuleNodeType) { let i = 0; @@ -23836,7 +24341,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("")); @@ -24007,166 +24515,30 @@ val: rule.length > 1 ? ":is(" + replace + ")" : replace, }); } - } - return tokens.reduce((acc, curr) => acc + renderValue(curr), ""); - } - function replaceCompoundLiteral(selector, replace) { - const tokens = [""]; - let i = 0; - for (; i < selector.length; i++) { - if (selector.charAt(i) == "&") { - tokens.push("&", ""); - } - } - return tokens - .sort((a, b) => { - if (a == "&") { - return 1; - } - return b == "&" ? -1 : 0; - }) - .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 - */ - 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 tokens.reduce((acc, curr) => acc + renderValue(curr), ""); + } + function replaceCompoundLiteral(selector, replace) { + const tokens = [""]; + let i = 0; + for (; i < selector.length; i++) { + if (selector.charAt(i) == "&") { + tokens.push("&", ""); } - return { - version: this.version, - sources: this.sources.slice(), - mappings: mappings.join(";"), - }; } + return tokens + .sort((a, b) => { + if (a == "&") { + return 1; + } + return b == "&" ? -1 : 0; + }) + .reduce((acc, curr) => acc + (curr == "&" ? replace : curr), ""); } + /** + * match url + */ const matchUrl = /^(https?:)?\/\//; /** * return the directory name of a path @@ -24178,6 +24550,9 @@ if (path === "") { return ""; } + if (path.startsWith("data:")) { + return path; + } let i = 0; let parts = [""]; for (; i < path.length; i++) { @@ -24201,10 +24576,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 +24585,7 @@ } // else if (chr == "?" || chr == "#") { // break; - // } + // } else { parts[parts.length - 1] += chr; } @@ -24231,6 +24603,8 @@ } /** * Nomalize path + * @param path + * @private */ const normalize = memoize(function (path) { let parts = []; @@ -24259,14 +24633,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 +24669,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("/") || url.match(/^[a-zA-Z]:/) ? 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 +24776,28 @@ const startTime = performance.now(); const errors = []; const sourcemap = options.sourcemap ? new SourceMap() : null; + const sourcemaps = options.sourcemap ? { sources: [], maps: [] } : 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 +24809,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 +24824,12 @@ }, }; if (sourcemap != null) { + let source; + for (const sourceId of sourcemaps.sources) { + source = options.sourcesMap.get(sourceId); + sourcemap.addSourceContent(source.id, source.getFileName(), source.getContent()); + } + sourcemap.add(...sourcemaps.maps); result.map = sourcemap; if (options.sourcemap === "inline") { result.code += `\n/*# sourceMappingURL=${result.map.toUrl()} */`; @@ -24429,37 +24842,93 @@ * @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; + 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]; + 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]; + } + if (!sourcemaps.sources.includes(srcId)) { + sourcemaps.sources.push(srcId); + } + sourcemaps.maps.push([newLine, newColumn, srcId, ...offsets]); } - 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]; + } + if (!sourcemaps.sources.includes(srcId)) { + sourcemaps.sources.push(srcId); + } + sourcemaps.maps.push([newLine, newColumn, srcId, ...offsets]); + } } - 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 +24958,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 +24969,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 +24989,47 @@ 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 lineMapLength = linesMap ? linesMap.getLineStarts().length : 0; + 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 +25038,63 @@ : 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); + if (!sourcemaps.sources.includes(node[LOC].srcId)) { + sourcemaps.sources.push(node[LOC].srcId); + } + sourcemaps.maps.push([ + ...linesMap.getOffsets(sourceLocation.end - str.length + options.newLine.length + indentSub.length), + node[LOC].srcId, + ...source.getOffsets(node[LOC].sta), + ]); + } } - return `${css}${options.newLine}${indentSub}${str}`; - }, ""); - if (options.removeEmpty && children === "") { - return ""; } if (children.endsWith(";")) { children = children.slice(0, -1); + sourceLocation.end--; + } + if (options.removeEmpty && children === "") { + if (sourcemaps != null) { + sourceLocation.end -= prelude.length; + linesMap.getLineStarts().length = lineMapLength; + } + return ""; } - 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: + const end = options.newLine + indent + `}`; + if (sourcemaps != null) { + move(sourceLocation, linesMap, end); + } + return prelude + children + end; default: return ""; } @@ -24630,6 +25103,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) { @@ -24706,8 +25182,8 @@ return " + "; case exports.EnumToken.Sub: return " - "; - case exports.EnumToken.Star: case exports.EnumToken.UniversalSelectorTokenType: + case exports.EnumToken.Star: case exports.EnumToken.Mul: return "*"; case exports.EnumToken.Div: @@ -25546,11 +26022,11 @@ case exports.EnumToken.OrTokenType: return "or"; case exports.EnumToken.InvalidMediaQueryTokenType: - // case EnumToken.InvalidDeclarationNodeType: case exports.EnumToken.InvalidCommentTokenType: case exports.EnumToken.BadCommentTokenType: case exports.EnumToken.BadCdoTokenType: case exports.EnumToken.BadStringTokenType: + case exports.EnumToken.BadUrlTokenType: case exports.EnumToken.EOFTokenType: return ""; default: @@ -25622,7 +26098,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 }), "")); @@ -25632,7 +26108,7 @@ chi: [], [LOC]: { ...tokens[0][LOC], - end: tokens[tokens.length - 1]?.[LOC]?.end ?? tokens[0]?.[LOC]?.end + end: tokens[tokens.length - 1]?.[LOC]?.end ?? tokens[0]?.[LOC]?.end, }, [TOKENS]: tokens.length === 0 ? null : tokens, [STATE]: result.success ? exports.EnumAstNodeStatus.Validated : exports.EnumAstNodeStatus.Invalid, @@ -25725,16 +26201,70 @@ } } if (tokens[i].typ == exports.EnumToken.ColorTokenType) { - // if (isIdent((tokens[i] as ColorToken).val)) { - // Object.assign(tokens[i], { - // typ: EnumToken.IdenTokenType, - // }); - // } else if (isHash(tokens[i].val)) { Object.assign(tokens[i], { typ: exports.EnumToken.HashTokenType, }); } + else { + return { + typ: exports.EnumToken.RuleNodeType, + sel: [ + ...tokens + .reduce((acc, curr, index, array) => { + // if (curr.typ == EnumToken.CommentTokenType) { + // return acc; + // } + if (curr.typ == exports.EnumToken.WhitespaceTokenType) { + if (trimWhiteSpace.includes(array[index - 1]?.typ) || + trimWhiteSpace.includes(array[index + 1]?.typ) || + combinators.includes(array[index - 1]?.val) || + combinators.includes(array[index + 1]?.val)) { + return acc; + } + } + let t = renderValue(curr, { minify: false }); + if (t == ",") { + acc.push([]); + } + else { + acc[acc.length - 1].push(t); + } + return acc; + }, [[]]) + .reduce((acc, curr) => { + let i = 0; + for (; i < curr.length; i++) { + if (i + 1 < curr.length && curr[i] == "*") { + if (curr[i] == "*") { + let index = curr[i + 1] == " " ? 2 : 1; + if (![">", "~", "+"].includes(curr[index])) { + curr.splice(i, index); + } + } + } + } + acc.set(curr.join(""), curr); + return acc; + }, uniq) + .keys(), + ].join(","), + chi: [], + [LOC]: { + ...tokens[0][LOC], + end: tokens[tokens.length - 1][LOC].end, + }, + [TOKENS]: tokens, + [STATE]: exports.EnumAstNodeStatus.Invalid, + [ERRORS]: [ + { + action: "drop", + node: tokens[i], + message: "invalid hash id", + }, + ], + }; + } } } const result = matchSelectorSyntax(tokens, errors, options, nested === true); @@ -25786,6 +26316,29 @@ func.val == ":nth-last-child" || func.val == ":nth-of-type" || func.val == ":nth-last-of-type") { + const list = []; + let index; + for (index = 0; index < func.chi.length; index++) { + if (func.chi[index].typ == exports.EnumToken.CommentTokenType || func.chi[index].typ == exports.EnumToken.WhitespaceTokenType) { + continue; + } + if (func.chi[index].typ == exports.EnumToken.IdenTokenType && equalsIgnoreCase('of', func.chi[index].val)) { + index--; + break; + } + list.push(func.chi[index]); + } + if (list.length == 3) { + if (list[0].typ == exports.EnumToken.IdenTokenType && ('n' == list[0].val || '-n' == list[0].val || '+n' == list[0].val)) { + if (list[1].typ == exports.EnumToken.NextSiblingCombinatorTokenType) { + if (list[2].typ == exports.EnumToken.NumberTokenType && (0 == list[2].val)) { + list[0].val = 'n'; + func.chi.splice(0, index, list[0]); + break; + } + } + } + } const token = func.chi.find((t) => t.typ != exports.EnumToken.WhitespaceTokenType && t.typ != exports.EnumToken.CommentTokenType); if (token?.typ == exports.EnumToken.IdenTokenType || token?.typ == exports.EnumToken.LiteralTokenType) { if (token.typ == exports.EnumToken.IdenTokenType && @@ -25875,7 +26428,7 @@ // } else { // Object.assign(token, { typ: EnumToken.NumberTokenType, val: b1 }); // } - // } else + // } else if (b1 === 0) { Object.assign(token, Math.abs(a1) === 1 ? { @@ -25946,7 +26499,7 @@ // func.chi.splice(0, i); // } // break; - // } else + // } else if (num.val === 0) { func.chi.splice(index + 1, i - index); if (token.val < 0) { @@ -27601,7 +28154,8 @@ success = false; errors.push({ action: "drop", - node: options.source.getSourceLocation(stream[i][LOC].sta), + node: stream[i], + location: options.source.getSourceLocation(stream[i][LOC].sta), message: ` is not allowed outside of parentheses`, }); break; @@ -28071,7 +28625,6 @@ exports.EnumToken.BadStringTokenType, ]; let keyNameCounter = 0; - const forbiddenStartCharacters = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"].map((c) => c.charCodeAt(0)); /** * Short-scoped name generator. * @@ -28082,12 +28635,15 @@ * * @returns string */ - const getShortNameGenerator = memoize((localName, filePath, pattern, hashLength = 5) => { + const getShortNameGenerator = memoize(() => { let value = keyNameCounter.toString(36); + let val = value.charAt(0).charCodeAt(0); keyNameCounter++; - while (forbiddenStartCharacters.includes(value.charCodeAt(0))) { + // starts with'0' - '9' + while (48 <= val && val <= 57) { value = keyNameCounter.toString(36); keyNameCounter++; + val = value.charAt(0).charCodeAt(0); } return value; }); @@ -28265,55 +28821,193 @@ } } } - if (parts.length == 3) { - // @ts-ignore - [key, hashAlgo, length] = parts; - } - if (length != null && !Number.isInteger(+length)) { - throw new Error(`Unsupported hash length: '${length}'. expecting format [hash:length] or [hash:hash-algo:length]`); + if (parts.length == 3) { + // @ts-ignore + [key, hashAlgo, length] = parts; + } + if (length != null && !Number.isInteger(+length)) { + throw new Error(`Unsupported hash length: '${length}'. expecting format [hash:length] or [hash:hash-algo:length]`); + } + } + const slice = length != null && length != fileBase.length; + switch (key) { + case "hash": + result += syncHash(hashString, length ?? hashLength, hashAlgo); + break; + case "name": + // @ts-expect-error + result += slice ? fileBase.slice(0, +length) : fileBase; + break; + case "local": + // @ts-expect-error + result += slice ? safeLocal.slice(0, +length) : localName; + break; + case "ext": + // @ts-expect-error + result += slice ? ext.slice(0, +length) : ext; + break; + case "path": + // @ts-expect-error + result += slice ? path.slice(0, +length) : path; + break; + case "folder": + // @ts-expect-error + result += slice ? folder.slice(0, +length) : folder; + break; + default: + throw new Error(`Unsupported key: '${key}'`); + } + key = ""; + continue; + } + if (inParens > 0) { + key += char; + } + else { + result += char; + } + } + // 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") { + if ("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 { + visitors.push(...Object.entries(value)); + } + } + 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") { + // visitors.push(...Object.entries(value)); + 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); } } - const slice = length != null && length != fileBase.length; - switch (key) { - case "hash": - result += syncHash(hashString, length ?? hashLength, hashAlgo); - break; - case "name": - // @ts-expect-error - result += slice ? fileBase.slice(0, +length) : fileBase; - break; - case "local": - // @ts-expect-error - result += slice ? safeLocal.slice(0, +length) : localName; - break; - case "ext": - // @ts-expect-error - result += slice ? ext.slice(0, +length) : ext; - break; - case "path": - // @ts-expect-error - result += slice ? path.slice(0, +length) : path; - break; - case "folder": - // @ts-expect-error - result += slice ? folder.slice(0, +length) : folder; - break; - default: - throw new Error(`Unsupported key: '${key}'`); + else { + errors.push({ action: "ignore", message: `doParse: visitor.${key} is not a valid key name` }); } - key = ""; - continue; - } - if (inParens > 0) { - key += char; } else { - result += char; + errors.push({ action: "ignore", message: `doParse: visitor.${key} is not a valid key name` }); } } - // if leading char is digit, prefix underscore (very rare) - return (/^[0-9]/.test(result) ? "_" : "") + result; - }); + 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 +29073,20 @@ }; 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, + }; + for (currentItemIndex = 0; currentItemIndex < iter.length; currentItemIndex++) { + item = iter[currentItemIndex]; stats.bytesIn = item.bytesIn; stats.tokensCount++; if (BadTokensTypes.includes(item.token.typ)) { @@ -28531,8 +29114,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 +29129,7 @@ let inBlock = 1; tokens = [item.token]; do { - // @ts-expect-error - item = iter.next().value; + item = iter[++currentItemIndex]; if (item == null) { break; } @@ -28604,198 +29184,162 @@ 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; + } + } + // @ts-ignore + if (nodes[i].chi != null) { + // @ts-ignore + 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; + 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); } + // 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]); + // } + // } + // } 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()]; + // @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]) { + // @ts-ignore + replaceNodeOrValue(nodes[i][PARENT], nodes[i], node); + } } + nodes = null; } if (invalidNodes.length > 0) { let count = invalidNodes.length; @@ -28818,19 +29362,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 +29390,7 @@ scoped: exports.ModuleScopeEnumOptions.Local, naming: exports.ModuleCaseTransformEnum.IgnoreCase, pattern: "", - generateScopedName, + generateScopedName: generateSyncScopedName, ...(typeof options.module != "object" ? {} : options.module), }; const parseModuleTime = performance.now(); @@ -28924,6 +29455,7 @@ throw new Error("css variable import not supported by parseSync() or transformSync(). use parse() or transform() instead.\nat " + options.source.getSourceLocation(node[LOC].sta).join(":")); } + // @ts-ignore if (node.typ == exports.EnumToken.CssVariableDeclarationMapTokenType) { const from = node.from.find((t) => t.typ == exports.EnumToken.IdenTokenType || isIdentColor(t)); if (!(from.val in cssVariablesMap)) { @@ -28965,10 +29497,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 +29541,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 +29714,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]; @@ -29200,6 +29730,7 @@ } else if ((value.typ == exports.EnumToken.IdenTokenType || isIdentColor(value)) && value.val in importedCssVariables) { + // @ts-ignore replaceNodeOrValue(parent, value, importedCssVariables[value.val].val); } } @@ -29259,10 +29790,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 @@ -29276,7 +29806,7 @@ } if (moduleSettings.scoped & exports.ModuleScopeEnumOptions.Pure) { if (!hasIdOrClass) { - throw new Error(`pure module: No id or class found in selector '${node.sel}' at '${node[LOC]?.src ?? ""}':${node[LOC]?.sta?.lin ?? ""}:${node[LOC]?.sta?.col ?? ""}`); + throw new Error(`pure module: No id or class found in selector '${node.sel}' at '${options.source.getOffsets(node[LOC]?.sta).join(":")}'`); } } node.sel = ""; @@ -29295,10 +29825,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 +29845,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 +29936,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 +29943,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](); @@ -29562,8 +29985,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 || @@ -29647,7 +30068,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 @@ -29656,11 +30077,10 @@ options.sourcesMap.set(source.id, source); const parseInfo = { stream, - buffer: "", offset: 0, source, position: 0, - currentPosition: -1, + currentPosition: 0, }; const root = await doParse(stream instanceof ReadableStream ? tokenizeStream(stream, parseInfo) : tokenize(parseInfo), Object.assign({}, options, { minify: false, @@ -29671,6 +30091,7 @@ stats.nodesCount += root.stats.nodesCount; stats.tokensCount += root.stats.tokensCount; stats.imports.push(root.stats); + // @ts-ignore node[PARENT].chi.splice(node[PARENT].chi.indexOf(node), 1, ...root.ast.chi); if (root.errors.length > 0) { errors.push(...root.errors); @@ -29687,210 +30108,165 @@ 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; + } + } + // @ts-ignore + if (nodes[i].chi != null) { + // @ts-ignore + 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 instanceof Promise) { - replacement = await replacement; - } - if (replacement == null || replacement == node) { - 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); } + // 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]); + // } + // } + // } 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]) { + // @ts-ignore + replaceNodeOrValue(nodes[i][PARENT], nodes[i], node); + } } + nodes = null; } if (invalidNodes.length > 0) { let count = invalidNodes.length; @@ -29913,19 +30289,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; @@ -30031,7 +30394,7 @@ time: 0, source, position: 0, - currentPosition: -1, + currentPosition: 0, }; const root = await doParse(stream instanceof ReadableStream ? tokenizeStream(stream, parseInfo) : tokenize(parseInfo), Object.assign({}, options, { source, @@ -30044,6 +30407,7 @@ parent.chi.splice(parent.chi.indexOf(node), 1); continue; } + // @ts-ignore if (node.typ == exports.EnumToken.CssVariableDeclarationMapTokenType) { const from = node.from.find((t) => t.typ == exports.EnumToken.IdenTokenType || isIdentColor(t)); if (!(from.val in cssVariablesMap)) { @@ -30088,6 +30452,7 @@ let result = moduleSettings.scoped & exports.ModuleScopeEnumOptions.Global ? node.nam : moduleSettings.generateScopedName(node.nam, moduleSettings.filePath, moduleSettings.pattern, moduleSettings.hashLength); + // @ts-ignore let value = result instanceof Promise ? await result : result; mapping[node.nam] = "--" + @@ -30121,6 +30486,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) { @@ -30172,26 +30538,26 @@ : result; const root = await doParse(stream instanceof ReadableStream ? tokenizeStream(stream, { - buffer: "", offset: 0, source: new SourceFile("", [], src.relative), position: 0, - currentPosition: -1, + currentPosition: 0, }) : tokenize({ stream, - buffer: "", offset: 0, position: 0, source: new SourceFile(stream, [], src.relative), - currentPosition: -1, + currentPosition: 0, }), Object.assign({}, options, { minify: false, 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] = {}; } @@ -30392,30 +30758,13 @@ } for (const { value, parent } of walkValues(node.val, node)) { if (value.typ == exports.EnumToken.DashedIdenTokenType) { - // if (!((value as DashedIdentToken).val in mapping)) { - // const result = - // moduleSettings.scoped! & ModuleScopeEnumOptions.Global - // ? (value as DashedIdentToken).val - // : moduleSettings.generateScopedName!( - // (value as DashedIdentToken).val, - // moduleSettings.filePath as string, - // moduleSettings.pattern as string, - // moduleSettings.hashLength, - // ); - // let val: string = result instanceof Promise ? await result : result; - // mapping[(value as DashedIdentToken).val] = - // "--" + - // (moduleSettings.naming! & ModuleCaseTransformEnum.DashCaseOnly || - // moduleSettings.naming! & ModuleCaseTransformEnum.CamelCaseOnly - // ? getKeyName(val, moduleSettings.naming as ModuleCaseTransformEnum) - // : val); - // revMapping[mapping[(value as DashedIdentToken).val]] = (value as DashedIdentToken).val; - // } value.val = mapping[value.val]; } else if ((value.typ == exports.EnumToken.IdenTokenType || isIdentColor(value)) && value.val in importedCssVariables) { - replaceNodeOrValue(parent, value, importedCssVariables[value.val].val); + replaceNodeOrValue( + // @ts-ignore + parent, value, importedCssVariables[value.val].val); } } } @@ -30443,26 +30792,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 +30805,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; } } })) { @@ -30519,7 +30842,7 @@ } if (moduleSettings.scoped & exports.ModuleScopeEnumOptions.Pure) { if (!hasIdOrClass) { - throw new Error(`pure module: No id or class found in selector '${node.sel}' at '${node[LOC]?.src ?? ""}':${node[LOC]?.sta?.lin ?? ""}:${node[LOC]?.sta?.col ?? ""}`); + throw new Error(`pure module: No id or class found in selector '${node.sel}' at '${(options.source?.getOffsets?.(node[LOC]?.sta) ?? []).join(":")}'`); } } node.sel = ""; @@ -30767,6 +31090,8 @@ return null; } /** + * @param stream + * @param context * @param options * @param errors * @param parseAsBlock @@ -30843,7 +31168,6 @@ parseAsBlock = blockAllowed; } if (syntax != null && atRule.nam !== "layer" && parseAsBlock !== blockAllowed) { - success = false; errors.push({ action: "drop", node: atRule, @@ -31138,10 +31462,13 @@ let definedAfterLastElse = false; if (sibling == null || sibling.typ !== exports.EnumToken.AtRuleNodeType) { missingWhen = true; + // @ts-expect-error } else if (sibling.nam !== "when") { + // @ts-expect-error if (sibling.nam !== "else") { missingWhen = true; + // @ts-expect-error } else if (sibling.val === "") { definedAfterLastElse = true; @@ -31331,7 +31658,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 +31797,6 @@ if (result.errors.length > 0) { errors.push(...result.errors); } - // else if (atRuleName === "document") { - // parseUrlToken(stream); - // } if (result.success) { let i = 0; const stack = []; @@ -31527,11 +31851,10 @@ const stream = `.x{${declaration}}`; return doParse(tokenize({ stream, - buffer: "", offset: 0, position: 0, source: new SourceFile(stream, [], ""), - currentPosition: -1, + currentPosition: 0, }), { setParent: false, minify: false, validation: false }).then((result) => { return result.ast.chi[0].chi.filter((t) => t.typ == exports.EnumToken.DeclarationNodeType || t.typ == exports.EnumToken.CommentNodeType); }); @@ -31561,19 +31884,20 @@ function parseString(src, options = { parseColor: true }, errors) { const parseInfo = { stream: src, - buffer: "", offset: 0, time: 0, source: new SourceFile(src, [], ""), position: 0, - currentPosition: -1, + currentPosition: 0, }; - 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 +31988,6 @@ node, location: options.source.getSourceLocation(node[LOC].sta), }); - // return []; continue; } index = tokens.indexOf(stack.at(-1)); @@ -31811,6 +32134,102 @@ 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=")) { + options.source.setInputSourceMap(token.val.slice(21, -2).trim()); + } + } + } + if (options.module) { + const { revMapping, ...res } = result; + return res; + } + return result; + } + /** + * + * @param options + * @param prefix + * @private + */ + 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 + "."); + } + } + } + + /** + * + * @param node + * @param key + * @returns + */ + function getNodeProperty(node, key) { + switch (key) { + case "parent": + return node[PARENT]; + case "location": + return node[LOC]; + case "state": + return node[STATE]; + case "errors": + return node[ERRORS]; + case "tokens": + return node[TOKENS]; + } + return undefined; + } + /** + * + * @param node + * @param key + * @param value + */ + function setNodeProperty(node, key, value) { + switch (key) { + case "parent": + node[PARENT] = value; + break; + case "location": + node[LOC] = value; + break; + case "state": + node[STATE] = value; + break; + case "errors": + node[ERRORS] = value; + break; + case "tokens": + node[TOKENS] = value; + break; + } + } + /** * Load file or url * @param url @@ -31819,7 +32238,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 +32280,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); @@ -31890,7 +32309,7 @@ }), mapping); } /** - * Parse css file + * Parse CSS file * @param file url or path * @param options * @param asStream load file as stream @@ -31918,17 +32337,17 @@ return parse({ file, asStream, ...options }); } /** - * Parse css + * Parse CSS * @param args * * 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 +32364,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, @@ -31969,26 +32391,24 @@ time: 0, source: options.source, position: 0, - currentPosition: -1, + currentPosition: 0, }; 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 */ function transformSync(...args) { let options; @@ -32037,9 +32457,7 @@ }; } /** - * Parse css - * @param stream - * @param options + * Parse CSS * * Example: * @@ -32063,6 +32481,7 @@ * * console.log(result.ast); * ``` + * @param args */ async function parse(...args) { let options; @@ -32084,7 +32503,7 @@ } options ??= {}; options.src ??= ""; - options.sourcesMap ??= new Map; + options.sourcesMap ??= new Map(); Object.assign(options, { load, resolve, @@ -32106,15 +32525,12 @@ time: 0, source: options.source, position: 0, - currentPosition: -1, + currentPosition: 0, }; - 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 @@ -32144,9 +32560,7 @@ }); } /** - * Transform css - * @param css - * @param options + * Transform CSS * * Example: * @@ -32164,6 +32578,7 @@ * * console.log(result.code); * ``` + * @param args */ async function transform(...args) { let options; @@ -32226,6 +32641,7 @@ exports.findAll = findAll; exports.findByValue = findByValue; exports.findLast = findLast; + exports.getNodeProperty = getNodeProperty; exports.isOkLabClose = isOkLabClose; exports.load = load; exports.minify = minify; @@ -32239,6 +32655,7 @@ exports.renderToken = renderValue; exports.replaceNodeOrValue = replaceNodeOrValue; exports.resolve = resolve; + exports.setNodeProperty = setNodeProperty; exports.transform = transform; exports.transformFile = transformFile; exports.transformSync = transformSync; diff --git a/dist/index.cjs b/dist/index.cjs index b3a4b908..fceaa1b3 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,21 @@ 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) { + // @ts-ignore + parents.splice(i, 0, ...(reverse ? node[TOKENS].toReversed() : node[TOKENS])); + // @ts-ignore + } + else if (Array.isArray(node.val)) { + // @ts-ignore + parents.splice(i, 0, ...(reverse ? node.val.toReversed() : node.val)); + } + } + // @ts-ignore + if (node["chi"] != null && (!isNumeric || (option & exports.WalkerOptionEnum.IgnoreChildren) === 0)) { + // @ts-ignore + parents.splice(i, 0, ...(reverse ? node.chi.toReversed() : node.chi)); for (const child of node.chi) { map.set(child, node); } @@ -9626,11 +9649,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 || @@ -10926,7 +10944,7 @@ function getTokenType(token, position, currentPosition) { [LOC]: pos, }; } - if (isPseudo(token)) { + if (isPseudo$1(token)) { return { typ: ValidationTokenEnum.PseudoClassToken, val: token, @@ -11683,23 +11701,28 @@ function cloneNode(node, cloneChildren = false, cloneMap = null) { cloneMap?.set?.(node, clone); for (const [name, value] of Object.entries(node)) { if (value == null || typeof value != "object") { + // @ts-ignore 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; - }); + // @ts-ignore + clone[name] = []; + if (cloneChildren || name !== checkNode) { + for (const c of value) { + const newObj = cloneNode(c, cloneChildren, cloneMap); + cloneMap?.set?.(c, newObj); + // @ts-ignore + clone[name].push(newObj); + } + } } else { + // @ts-ignore clone[name] = { ...value }; } } for (const symbol of Object.getOwnPropertySymbols(node)) { + // @ts-ignore clone[symbol] = node[symbol]; } return clone; @@ -11708,11 +11731,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 +11844,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 +12012,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 +12458,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 +12503,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 +12554,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 +12602,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 { @@ -12639,6 +12711,7 @@ function matchSyntax(syntaxes, context, options) { return result; } if (tokensfuncDefMap.has(token.typ) && + // @ts-ignore token.typ === exports.EnumToken.WildCardFunctionTokenDefType) { const range = trimArray(context.peekRange()); result = matchSyntax(getParsedSyntax(ValidationSyntaxGroupEnum.Syntaxes, token.val + "()")?.[0]?.chi, createValidationContext(range.slice(1, -1)), options); @@ -12669,7 +12742,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 +13250,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 +13293,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 +13318,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; @@ -13618,7 +13712,9 @@ function matchProperty(property, context, options) { // ) // ) { const newRange = range.map((t) => cloneNode(t, true)); + // @ts-ignore parseTokens(newRange, { parseColor: true }, errors); + // @ts-ignore success = newRange.length == 1 && isColor(newRange[0], errors); if (success) { context.update(range.at(-1)); @@ -13963,6 +14059,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; @@ -15519,7 +15622,6 @@ function isColor(token, errors) { action: "drop", message: `Invalid color`, node: token, - // location: options.source!.getSourLocation(token[LOC]!.sta), }); return false; } @@ -15542,31 +15644,6 @@ function isColor(token, errors) { if (children.length == 4 || (isRelative && children.length == 6)) { return true; } - if (children.length == 8 || children.length == 6) { - children.at(-2); - children.at(-1); - // @ts-ignore - // if ( - // ((children.length > 6 || !isRelative) && sep.typ != EnumToken.LiteralTokenType) || - // (sep as LiteralToken).val != "/" - // ) { - // return false; - // } - // if (alpha.typ == EnumToken.IdenTokenType && (alpha as IdentToken).val != "none") { - // return false; - // } else { - // // @ts-ignore - // if (alpha.typ == EnumToken.PercentageTokenType) { - // if (+(alpha as PercentageToken).val < 0 || +(alpha as PercentageToken).val > 100) { - // return false; - // } - // } else if (alpha.typ == EnumToken.NumberTokenType) { - // if (+(alpha as NumberToken).val < 0 || +(alpha as NumberToken).val > 1) { - // return false; - // } - // } - // } - } return true; } // @ts-ignore @@ -15583,9 +15660,6 @@ function isColor(token, errors) { } return acc; }, [[]]); - // if (children.length === 0 || children[0].length === 0) { - // return false; - // } let j = 0; let k = 0; if (children[j][0].typ === exports.EnumToken.IdenTokenType && @@ -15619,50 +15693,13 @@ function isColor(token, errors) { k++; } } - // else { - // return false; - // } } else { k++; } } - // else { - // return false; - // } - // if (k != children[j].length) { - // return false; - // } j++; } - // while (j < children.length) { - // if (children[j].length > 2) { - // return false; - // } - // if ( - // !isColor(children[j][0]) && - // !( - // children[j][0].typ == EnumToken.WildCardFunctionTokenType && - // equalsIgnoreCase("calc", (children[j][0] as FunctionToken).val) - // ) - // ) { - // return false; - // } - // if (children[j][0].typ == EnumToken.WildCardFunctionTokenType) { - // const result = matchAllSyntaxes( - // getParsedSyntax(ValidationSyntaxGroupEnum.Syntaxes, "calc()") as ValidationFunctionToken[], - // createValidationContext([children[j][0]]), - // {}, - // ); - // if (!result.success) { - // return false; - // } - // } - // if (children[j].length > 1 && !isPercentageToken(children[j][1])) { - // return false; - // } - // j++; - // } return true; } else { @@ -15675,21 +15712,6 @@ function isColor(token, errors) { // @ts-ignore for (const v of token.chi) { if (v.typ == exports.EnumToken.IdenTokenType) { - // if (isColor(v)) { - // continue; - // } - // if (!(keywords.includes(v.val) || COLORS_NAMES[v.val.toLowerCase()] != null)) { - // return false; - // } - // if (keywords.includes(v.val)) { - // if (isLegacySyntax) { - // return false; - // } - // // @ts-ignore - // if (v.val == "from" && ["rgba", "hsla"].includes((token as ColorToken).val)) { - // return false; - // } - // } continue; } if (v.typ === exports.EnumToken.MathFunctionTokenType || @@ -15697,20 +15719,6 @@ function isColor(token, errors) { colorsFunc.includes(v.val)) { continue; } - // if ( - // ![ - // EnumToken.ColorTokenType, - // EnumToken.IdenTokenType, - // EnumToken.NumberTokenType, - // EnumToken.AngleTokenType, - // EnumToken.PercentageTokenType, - // EnumToken.CommaTokenType, - // EnumToken.WhitespaceTokenType, - // EnumToken.LiteralTokenType, - // ].includes(v.typ) - // ) { - // return false; - // } } } return true; @@ -15766,63 +15774,12 @@ function parseColor(token) { } if (token.val == "color") { let index = token.chi.indexOf(tk); - // if ((token as ColorToken).cal == "rel") { - // for (let k = 0; k < (token as ColorToken).chi!.length; k++) { - // if (EnumToken.DashedIdenTokenType == (token as ColorToken).chi![k].typ) { - // index = k; - // break; - // } - // } - // } if (exports.EnumToken.DashedIdenTokenType == token?.chi?.[index]?.typ) { token.kin = exports.ColorType.CUSTOM_COLOR; } } } - // return token; } - // @ts-ignore - // token.typ = EnumToken.ColorTokenType; - // // @ts-ignore - // (token as ColorToken).kin = ColorType[token.val.replaceAll("-", "_").toUpperCase()]; - // if (!("chi" in token)) { - // const val: string = (token as ColorToken).val.toLowerCase(); - // if (val == "currentcolor" || val == "transparent" || val in COLORS_NAMES) { - // (token as ColorToken).kin = ColorType.LIT; - // } else if (isHexColor(val)) { - // (token as ColorToken).kin = ColorType.HEX; - // } - // const tk = (token as ColorToken).chi?.find( - // (t) => t.typ !== EnumToken.WhitespaceTokenType && t.typ !== EnumToken.CommentTokenType, - // ); - // if (tk?.typ === EnumToken.IdenTokenType && (tk as IdentToken).val === "from") { - // (token as ColorToken).cal = "rel"; - // } else if ((token as ColorToken).val == "color-mix" && (tk as IdentToken).val == "in") { - // (token as ColorToken).cal = "mix"; - // } else if ((token as ColorToken).val == "color") { - // (token as ColorToken).cal = "col"; - // } - // return token; - // } - // // @ts-ignore - // if (((token as ColorToken).chi as Token[])[0].typ == EnumToken.IdenTokenType) { - // // @ts-ignore - // if (((token as ColorToken).chi as Token[])[0].val == "from") { - // // @ts-ignore - // (token as ColorToken).cal = "rel"; - // } - // // @ts-ignore - // else if ((token as ColorToken).val == "color-mix" && ((token as ColorToken).chi as Token[])[0].val == "in") { - // // @ts-ignore - // (token as ColorToken).cal = "mix"; - // } else { - // // @ts-ignore - // if ((token as ColorToken).val == "color") { - // // @ts-ignore - // (token as ColorToken).cal = "col"; - // } - // } - // } return token; } function isLetter(codepoint) { @@ -15868,6 +15825,9 @@ const isIdent = memoize(function (name) { return false; } if (codepoint == REVERSE_SOLIDUS) { + if (i + 1 > j) { + return false; + } codepoint = name.charCodeAt(i + 1); // if (!isIdentCodepoint(codepoint)) { // return false; @@ -15895,18 +15855,24 @@ const isIdent = memoize(function (name) { } return true; }); -function isPseudo(name) { +function isNonPrintable(codepoint) { + // null -> backspace + return ((codepoint >= 0 && codepoint <= 0x8) || + // tab + codepoint == 0xb || + // delete + codepoint == 0x7f || + (codepoint >= 0xe && codepoint <= 0x1f)); +} +function isPseudo$1(name) { return (name.charAt(0) == ":" && ((name.endsWith("(") && isIdent(name.charAt(1) == ":" ? name.slice(2, -1) : name.slice(1, -1))) || isIdent(name.charAt(1) == ":" ? name.slice(2) : name.slice(1)))); } function isHash(name) { - return name.charAt(0) == "#" && isIdent(name.charAt(1)); + return name.charAt(0) == "#" && isIdentStart(name.charCodeAt(1)); } const isNumber = memoize(function (name) { - // if (name.length == 0) { - // return false; - // } let codepoint = name.charCodeAt(0); let i = 0; const j = name.length; @@ -16252,16 +16218,19 @@ function replaceAstNodes(tokens, root) { if (key in pseudoAliasMap) { const isPseudClass = pseudoAliasMap[key].startsWith("::"); value.val = pseudoAliasMap[key]; - if (value.typ == exports.EnumToken.IdenTokenType && - ["min-resolution", "max-resolution"].includes(value.val) && - parent?.typ == exports.EnumToken.MediaQueryConditionTokenType && - parent.r?.[0]?.typ == exports.EnumToken.NumberTokenType) { - Object.assign(parent.r?.[0], { - typ: exports.EnumToken.ResolutionTokenType, - unit: "x", - }); - } - else if (isPseudClass && value.typ == exports.EnumToken.PseudoElementTokenType) { + // if ( + // value.typ == EnumToken.IdenTokenType && + // ["min-resolution", "max-resolution"].includes((value as IdentToken).val) && + // parent?.typ == EnumToken.MediaQueryConditionTokenType && + // (parent as MediaQueryConditionToken).r?.[0]?.typ == EnumToken.NumberTokenType + // ) { + // Object.assign((parent as MediaQueryConditionToken).r?.[0], { + // typ: EnumToken.ResolutionTokenType, + // unit: "x", + // }); + // } + // else + if (isPseudClass && value.typ == exports.EnumToken.PseudoElementTokenType) { // @ts-ignore value.typ = exports.EnumToken.PseudoClassTokenType; } @@ -16474,32 +16443,11 @@ class ComputePrefixFeature { let commaCount; let type = ""; let tokens = token.chi.slice(); - // while ( - // i < tokens.length && - // (tokens[i].typ === EnumToken.WhitespaceTokenType || tokens[i].typ === EnumToken.CommentTokenType) - // ) { - // i++; - // } - // if (i >= tokens.length || tokens[i].typ !== EnumToken.IdenTokenType) { - // return; - // } // linear or radial if (equalsIgnoreCase(tokens[i].val, "linear")) { type = "linear-gradient"; i++; } - // else { - // return; - // } - // while ( - // i < tokens.length && - // (tokens[i].typ === EnumToken.WhitespaceTokenType || tokens[i].typ === EnumToken.CommentTokenType) - // ) { - // i++; - // } - // if (tokens[i].typ !== EnumToken.CommaTokenType) { - // return; - // } tokens.splice(0, i + 1); commaCount = 0; for (i = 0; i < tokens.length; i++) { @@ -18915,12 +18863,21 @@ 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 +19595,7 @@ class ComputeShorthandFeature { accept = new Set([ exports.EnumToken.RuleNodeType, exports.EnumToken.AtRuleNodeType, - exports.EnumToken.KeyFramesRuleNodeType, + exports.EnumToken.KeyframesRuleNodeType, ]); get ordering() { return 10; @@ -19795,7 +19752,8 @@ class ComputeCalcExpressionFeature { // @ts-ignore const children = parent.typ == exports.EnumToken.DeclarationNodeType ? parent.val - : parent.chi; + : // @ts-ignore + parent.chi; if (values.length == 1 && values[0].typ != exports.EnumToken.BinaryExpressionTokenType) { for (let i = 0; i < children.length; i++) { if (children[i] == value) { @@ -20979,7 +20937,11 @@ function splitTransformList(transformList) { } class TransformCssFeature { - accept = new Set([exports.EnumToken.RuleNodeType, exports.EnumToken.KeyFramesRuleNodeType]); + accept = new Set([ + exports.EnumToken.RuleNodeType, + exports.EnumToken.AtRuleNodeType, + exports.EnumToken.KeyframesRuleNodeType, + ]); get ordering() { return 3; } @@ -21016,7 +20978,6 @@ class TransformCssFeature { ? minifyTransformFunctions(child) : child); } - // consumeWhitespace(children); let { matrix, cumulative, minified } = compute(children) ?? { matrix: null, cumulative: null, @@ -21046,7 +21007,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 +21046,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 +21076,6 @@ button { console.log({node, value}); ``` - * - * @param ast - * @param matcher - * @returns */ function findByValue(ast, matcher) { let source; @@ -21130,6 +21092,7 @@ function findByValue(ast, matcher) { } for (const { value, parent, root: rootNode, parents } of walkValues(source, node)) { if (matcher(value, node)) { + // @ts-ignore return { node, value: { node: value, parent, root: rootNode, parents } }; } } @@ -21238,6 +21201,7 @@ function substituteIfElseNode(declaration, node, wrapper, parentWrapper, cache) exports.EnumToken.SemiColonTokenType ? trimArray(node.r.r.slice(0, -1)) : node.r.r); + // @ts-expect-error if (targetParentWrapper.typ != exports.EnumToken.DeclarationNodeType) { let index = targetParentWrapper.chi.indexOf(targetWrapper); if (index != -1) { @@ -21259,6 +21223,7 @@ function substituteIfElseNode(declaration, node, wrapper, parentWrapper, cache) .r.r.slice(0, -1)) : siblingWrapper.chi[k] .r.r); + // @ts-ignore cache.add(siblingWrapper.chi[k].l); } } @@ -21282,7 +21247,9 @@ function substituteIfElseNode(declaration, node, wrapper, parentWrapper, cache) } if (left.typ === exports.EnumToken.IdenTokenType && equalsIgnoreCase("else", left.val)) { clonedDeclaration = cloneNode(declaration, true, nodeMap); - replaceNodeOrValue(nodeMap.get(parentWrapper), nodeMap.get(targetWrapper.typ === exports.EnumToken.DeclarationNodeType ? node : targetWrapper), node.r.at(-1)?.typ === exports.EnumToken.SemiColonTokenType ? trimArray(node.r.slice(0, -1)) : node.r); + replaceNodeOrValue(nodeMap.get(parentWrapper), + // @ts-expect-error + nodeMap.get(targetWrapper.typ === exports.EnumToken.DeclarationNodeType ? node : targetWrapper), node.r.at(-1)?.typ === exports.EnumToken.SemiColonTokenType ? trimArray(node.r.slice(0, -1)) : node.r); result.push(clonedDeclaration); } else if (left?.typ === exports.EnumToken.WhenElseFunctionTokenType) { @@ -21291,6 +21258,9 @@ function substituteIfElseNode(declaration, node, wrapper, parentWrapper, cache) nam: left.val, chi: [], }); + if (declaration[PARENT] != null) { + atRule[LOC] = declaration[PARENT][LOC]; + } atRule[TOKENS] = [{ typ: exports.EnumToken.ParensTokenType, chi: left.chi.slice() }]; const minify = atRule.nam !== "supports"; const options = { @@ -21313,6 +21283,9 @@ function substituteIfElseNode(declaration, node, wrapper, parentWrapper, cache) }); atRule[TOKENS] = [left]; atRule.val = atRule[TOKENS].reduce((acc, curr) => acc + renderValue(curr), ""); + if (declaration[PARENT] != null) { + atRule[LOC] = declaration[PARENT][LOC]; + } clonedDeclaration = cloneNode(declaration, true, nodeMap); replaceNodeOrValue(nodeMap.get(targetWrapper.typ === exports.EnumToken.WildCardFunctionTokenType ? targetParentWrapper : targetWrapper), nodeMap.get(targetWrapper.typ === exports.EnumToken.WildCardFunctionTokenType ? targetWrapper : node), node.r.at(-1)?.typ === exports.EnumToken.SemiColonTokenType ? trimArray(node.r.slice(0, -1)) @@ -21343,17 +21316,27 @@ function processNode(declarationNode, cache) { const parentWrapper = node.parent ?? parents.find((node) => !nodeMatcher(node)); if (node.node.typ === exports.EnumToken.WildCardFunctionTokenType) { for (i = 0; i < node.node.chi.length; i++) { - stack.push(...substituteIfElseNode(declaration, node.node.chi[i], node.node, parentWrapper, cache)); + stack.push( + // @ts-expect-error + ...substituteIfElseNode( + // @ts-expect-error + declaration, node.node.chi[i], node.node, parentWrapper, cache)); } } else { - stack.push(...substituteIfElseNode(declaration, node.node, parentWrapper, parents[parents.indexOf(parentWrapper) + 1] ?? declaration, cache)); + stack.push( + // @ts-expect-error + ...substituteIfElseNode( + // @ts-expect-error + declaration, node.node, parentWrapper, parents[parents.indexOf(parentWrapper) + 1] ?? declaration, cache)); } } if (result.length > 0) { + // @ts-expect-error replaceNodeOrValue(declarationNode[PARENT], declarationNode, result); } // else remove node? + // @ts-expect-error return result; } class ExpandIfFeature { @@ -21385,96 +21368,425 @@ 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; - } - /** - * add line start - */ - addLineStart(lineStart) { - this.lineStarts.push(lineStart); - } - /** - * clone the linemap - * @returns - */ - clone() { - return new LineMap(this.lineStarts.slice()); } + return result; } - -/** - * Source file ID - */ -let sourceId = 0; /** - * Source file helper class + * + * @param value + * @returns */ -class SourceFile { - /** - * Source file ID - */ - id; - /** +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; +} + +/** + * Generate and parse source map + */ +class SourceMap { + /** + * + * @private + */ + keys = new Set(); + /** + * Last location + */ + lastLocation = null; + /** + * Version + * @private + */ + version = 3; + /** + * Sources map + * @private + */ + sourcesMap = []; + /** + * Sources content + * @private + */ + sourcesContent = []; + /** + * Sources + * @private + */ + sources = []; + /** + * Map + * @private + * + */ + map = new Map(); + /** + * Map + * @private + * + */ + reverseMap = new Map(); + /** + * Line + * @private + */ + line = -1; + /** + * + * @param sourcemaps + */ + constructor(sourcemaps) { + if (typeof sourcemaps === "string") { + if (sourcemaps.startsWith("data:")) { + let encoding = ""; + let offset = sourcemaps.indexOf(",") + 1; + if (offset == 0) { + offset = sourcemaps.lastIndexOf(";") + 1; + } + else { + encoding = sourcemaps.slice(sourcemaps.lastIndexOf(";") + 1, offset - 1); + } + if (encoding == "base64") { + sourcemaps = atob(sourcemaps.slice(offset)); + } + else { + sourcemaps = decodeURIComponent(sourcemaps.slice(offset)); + } + } + 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 source + * @param id + * @param fileName + * @param content + * @returns + */ + addSourceContent(id, fileName, content) { + if (this.sourcesMap.includes(id)) { + return; + } + this.sourcesMap[this.sourcesMap.length] = id; + this.sources[this.sources.length] = fileName || null; + this.sourcesContent[this.sourcesContent.length] = content || null; + } + /** + * Add all location + * @param maps + * @throws + */ + add(...maps) { + let srcIndex; + if (typeof maps[0] === "number") { + maps = [maps]; + } + for (let [newLine, newColumn, srcId, ln, col] of maps) { + const key = `${srcId}:${ln}:${col}:${newLine}:${newColumn}`; + if (this.keys.has(key)) { + continue; + } + this.keys.add(key); + const line = newLine - 1; + let record; + if (line > this.line) { + this.line = line; + } + srcIndex = this.sourcesMap.indexOf(srcId); + if (srcIndex == -1) { + throw new Error(`Source file ${srcId} not added to sourcemap`); + } + if (!this.map.has(line)) { + record = [Math.max(0, newColumn - 1), srcIndex, 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], srcIndex - 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: number = 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); + // nameIndex not needed + // 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.size == 0) { + this.computePositions(); + } + 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(";"), + }; + } +} + +/** + * 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]; + // } + // [line, column] + return [line + 1, offset - this.lineStarts[line] + 1]; + } + /** + * 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); + } +} + +/** + * Source file ID + */ +let sourceId = 0; +/** + * Source file helper class + */ +class SourceFile { + inputSourceMap = null; + /** + * Source file ID + */ + id; + /** * Source file path */ file; @@ -21488,7 +21800,6 @@ class SourceFile { content; /** * Constructor - * @param id * @param content * @param lines * @param file @@ -21502,7 +21813,6 @@ class SourceFile { /** * Update source content * @param content - * @param lines */ append(content) { this.content += content; @@ -21560,6 +21870,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 = { @@ -21694,17 +22018,17 @@ var TokenMap; TokenMap[TokenMap["GREATERTHAN"] = 62] = "GREATERTHAN"; })(TokenMap || (TokenMap = {})); function consumeString(parseInfo) { - const quote = next(parseInfo); - let value; - let buffer = quote; + const quote = next(parseInfo).charCodeAt(0); + let charCode; + let decodeSegments = false; const result = []; - while ((value = parseInfo.stream.charAt(parseInfo.currentPosition - parseInfo.offset + 1))) { - if (value == "\\") { - if ("\\" == parseInfo.stream.charAt(parseInfo.currentPosition - parseInfo.offset + 2)) { - buffer += next(parseInfo, 2); + while ((charCode = parseInfo.stream.charCodeAt(parseInfo.currentPosition - parseInfo.offset)) == charCode) { + if (charCode == 92 /* TokenMap.REVERSE_SOLIDUS */) { + if (charCode == parseInfo.stream.charCodeAt(parseInfo.currentPosition - parseInfo.offset + 1)) { + next(parseInfo, 2); continue; } - const sequence = peek(parseInfo, 6); + const sequence = peek(parseInfo, 7); let escapeSequence = ""; let codepoint; let i; @@ -21723,50 +22047,66 @@ function consumeString(parseInfo) { break; } if (escapeSequence.trimEnd().length > 0) { - const codepoint = parseInt(escapeSequence, 16); - if (codepoint == 0 || - // leading surrogate - (0xd800 <= codepoint && codepoint <= 0xdbff) || - // trailing surrogate - (0xdc00 <= codepoint && codepoint <= 0xdfff)) { - buffer += String.fromCodePoint(0xfffd); - } - else { - buffer += String.fromCodePoint(codepoint); - } - next(parseInfo, escapeSequence.length + + // const codepoint = parseInt(escapeSequence, 16); + // TODO set decode flag ON + // if ( + // codepoint == 0 || + // // leading surrogate + // (0xd800 <= codepoint && codepoint <= 0xdbff) || + // // trailing surrogate + // (0xdc00 <= codepoint && codepoint <= 0xdfff) + // ) { + // buffer += String.fromCodePoint(0xfffd); + // } else { + // buffer += String.fromCodePoint(codepoint); + // } + const length = escapeSequence.length + 1 + - (isWhiteSpace(parseInfo.stream.charAt(parseInfo.currentPosition - parseInfo.offset + 1)?.charCodeAt(0)) + (isWhiteSpace(parseInfo.stream.charAt(parseInfo.currentPosition - parseInfo.offset)?.charCodeAt(0)) ? 1 - : 0)); + : 0); + decodeSegments = true; + next(parseInfo, length); continue; } - buffer += next(parseInfo, 2); + next(parseInfo, 2); continue; } - if (value == quote) { - buffer += value; - result.push(yieldResult(buffer, parseInfo, - /* hasNewLine ? EnumToken.BadStringTokenType : */ exports.EnumToken.StringTokenType)); + if (charCode == quote) { next(parseInfo); - buffer = ""; + result.push(yieldResult(parseInfo, + /* hasNewLine ? EnumToken.BadStringTokenType : */ exports.EnumToken.StringTokenType, decodeSegments ? { decodeSegments } : null)); return result; } - if (isNewLine(value.charCodeAt(0))) { - result.push(yieldResult(buffer + next(parseInfo), parseInfo, exports.EnumToken.BadStringTokenType)); + if (isNewLine(charCode)) { + next(parseInfo); + result.push(yieldResult(parseInfo, exports.EnumToken.BadStringTokenType)); return result; } - buffer += value; next(parseInfo); } // EOF - 'Unclosed-string' fixed - result.push(yieldResult(buffer + quote, parseInfo, exports.EnumToken.StringTokenType)); + result.push(yieldResult(parseInfo, exports.EnumToken.StringTokenType)); return result; } -function yieldResult(val, parseInfo, hint) { +function yieldResult(parseInfo, hint, options) { + let val = parseInfo.stream.slice(parseInfo.position - parseInfo.offset, parseInfo.currentPosition - parseInfo.offset); let token = null; let dimension; - // console.debug(`Yield result: ${val}, ${hint}`); + if (options?.decodeSegments) { + val = val.replace(/\\([0-9a-fA-F]{1,6})(?:\s)?/g, (_, sequence) => { + const codepoint = parseInt(sequence, 16); + if (codepoint == 0 || + // leading surrogate + (0xd800 <= codepoint && codepoint <= 0xdbff) || + // trailing surrogate + (0xdc00 <= codepoint && codepoint <= 0xdfff) || + codepoint > 0x10ffff) { + return "\uFFFD"; + } + return String.fromCodePoint(codepoint); + }); + } if (hint != null) { let searchArray = null; switch (hint) { @@ -21818,7 +22158,12 @@ function yieldResult(val, parseInfo, hint) { else { let slice = val.slice(1); const chr = val.charAt(0); - if (chr == "@" && isIdent(slice)) { + if (chr == "!" && equalsIgnoreCase("!important", val)) { + token = { + typ: exports.EnumToken.ImportantTokenType, + }; + } + else if (chr == "@" && isIdent(slice)) { token = { typ: exports.EnumToken.AtRuleTokenType, nam: slice, @@ -21893,12 +22238,12 @@ function yieldResult(val, parseInfo, hint) { end: parseInfo.currentPosition, }; parseInfo.position = parseInfo.currentPosition; - return { token, bytesIn: parseInfo.currentPosition + 1 }; + return { token, bytesIn: parseInfo.currentPosition }; } function match(parseInfo, input) { let position = parseInfo.currentPosition - parseInfo.offset; for (let i = 0; i < input.length; i++) { - if (parseInfo.stream[position + i + 1] != input.charAt(i)) { + if (parseInfo.stream[position + i] != input.charAt(i)) { return false; } } @@ -21906,14 +22251,14 @@ function match(parseInfo, input) { } function peek(parseInfo, count = 1) { if (count == 1) { - return parseInfo.stream.charAt(parseInfo.currentPosition - parseInfo.offset + 1); + return parseInfo.stream.charAt(parseInfo.currentPosition - parseInfo.offset); } const position = parseInfo.currentPosition - parseInfo.offset; - return parseInfo.stream.slice(position + 1, position + count + 1); + return parseInfo.stream.slice(position, position + count); } function next(parseInfo, count = 1) { let position = parseInfo.currentPosition - parseInfo.offset; - let char = count == 1 ? parseInfo.stream.charAt(position + 1) : parseInfo.stream.slice(position + 1, position + 1 + count); + let char = count == 1 ? parseInfo.stream.charAt(position) : parseInfo.stream.slice(position, position + count); let i = 0; let codepoint; for (; i < char.length; i++) { @@ -21932,198 +22277,295 @@ function next(parseInfo, count = 1) { } } } - parseInfo.currentPosition += char.length; - return char; + parseInfo.currentPosition += char.length; + return char; +} +function isIdentToken(parseInfo, start, end) { + let j = parseInfo.currentPosition - parseInfo.offset; + let i = parseInfo.position - parseInfo.offset; + if (start != null) { + if (end == null) { + if (start < 0) { + j += start; + } + else { + i += start; + } + } + else { + if (end < 0) { + j += end; + } + else { + j = parseInfo.position + end; + } + } + } + j--; + let codepoint = parseInfo.stream.charCodeAt(i); + // - + if (codepoint == 0x2d) { + let nextCodepoint; + if ((nextCodepoint = parseInfo.stream.charCodeAt(i + 1)) != nextCodepoint) { + return false; + } + if (isDigit(nextCodepoint)) { + return false; + } + codepoint = nextCodepoint; + i++; + } + if (codepoint !== 0x2d && !isIdentStart(codepoint)) { + return false; + } + if (codepoint == 92 /* TokenMap.REVERSE_SOLIDUS */) { + codepoint = parseInfo.stream.charCodeAt(i + 1); + // if (!isIdentCodepoint(codepoint)) { + // return false; + // } + i += String.fromCodePoint(codepoint).length; + // if (i < j) { + // codepoint = name.charCodeAt(i) as number; + // if (!isIdentCodepoint(codepoint)) { + // return false; + // } + // } + } + while (i < j) { + i += codepoint < 0x80 ? 1 : String.fromCodePoint(codepoint).length; + codepoint = parseInfo.stream.charCodeAt(i); + if (codepoint == 92 /* TokenMap.REVERSE_SOLIDUS */) { + i += codepoint < 0x80 ? 1 : String.fromCodePoint(codepoint).length; + codepoint = parseInfo.stream.charCodeAt(i); + i += codepoint < 0x80 ? 1 : String.fromCodePoint(codepoint).length; + continue; + } + if (codepoint !== 0x2d && !isIdentCodepoint(codepoint)) { + return false; + } + } + return true; +} +function isPseudo(parseInfo) { + let position = parseInfo.currentPosition - parseInfo.offset; + let endPosition = parseInfo.currentPosition - parseInfo.offset; + return (parseInfo.stream.charAt(position) == ":" && + parseInfo.stream.charAt(endPosition - 1) == "(" && + (parseInfo.stream.charAt(position + 1) == ":" + ? isIdentToken(parseInfo, 2, -1) + : isIdentToken(parseInfo, 1, -1))) || + parseInfo.stream.charAt(position + 1) == ":" + ? isIdentToken(parseInfo, 2) + : isIdentToken(parseInfo, 1); +} +function startsWith(parseInfo, input) { + let i = 0; + let j = input.length; + while (i < j) { + if (parseInfo.stream.charAt(parseInfo.position - parseInfo.offset + i) != input.charAt(i)) { + return false; + } + i++; + } + return true; +} +function isURLToken(parseInfo) { + let i = parseInfo.position - parseInfo.offset; + let c; + while (++i < parseInfo.currentPosition) { + c = parseInfo.stream.charCodeAt(i); + // single quote or double quote or start parenthesis or close parenthesis + if (isNonPrintable(c) || c == 0x27 || c == 0x22 || c == 0x28 || c == 0x29) { + return false; + } + // valid escape + if (c == 92 /* TokenMap.REVERSE_SOLIDUS */) { + i++; + if (i >= parseInfo.currentPosition) { + return false; + } + c = parseInfo.stream.charCodeAt(i); + // c is not '\n' or '\r' or '\f' + if (c == 0x6e || c == 0x72 || c == 0x66) { + return false; + } + continue; + } + // is white space + if (c == 0x20 || c == 0x09) { + break; + } + } + return i == parseInfo.currentPosition; } /** - * Tokenize css string + * Tokenize CSS string * @param parseInfo * @param yieldEOFToken */ function tokenize(parseInfo, yieldEOFToken = true) { if (typeof parseInfo == "string") { parseInfo = { - buffer: "", stream: parseInfo, source: new SourceFile(parseInfo, [], ""), offset: 0, time: 0, position: 0, - currentPosition: -1, + currentPosition: 0, }; } - let value; - let buffer = parseInfo.buffer; let charCode; let nextCharCode; const startTime = performance.now(); const result = []; // allow 10 characters buffer for the streaming parser to avoid incomplete tokens - const endPosition = parseInfo.stream.length - 10; - parseInfo.buffer = ""; - while ((value = peek(parseInfo))) { - charCode = value.charCodeAt(0); - // nextCharCode = nextValue.charCodeAt(0); - // console.debug({value, buffer}); + const endPosition = parseInfo.stream.length - 1; + // NaN is not equal to NaN + while ((charCode = peek(parseInfo).charCodeAt(0)) == charCode) { switch (charCode) { case 61 /* TokenMap.EQUALS */: - if (buffer.length > 0) { - result.push(yieldResult(buffer, parseInfo)); - buffer = ""; + if (parseInfo.position < parseInfo.currentPosition) { + result.push(yieldResult(parseInfo)); } - result.push(yieldResult(next(parseInfo), parseInfo, exports.EnumToken.DelimTokenType)); + next(parseInfo); + result.push(yieldResult(parseInfo, exports.EnumToken.DelimTokenType)); break; // '+' or '-' case 43 /* TokenMap.PLUS */: case 45 /* TokenMap.MINUS */: - next(parseInfo); - if (charCode === 43 /* TokenMap.PLUS */ && !isNumber(peek(parseInfo))) { - if (buffer.length > 0) { - result.push(yieldResult(buffer, parseInfo)); - buffer = ""; - } - result.push(yieldResult(value, parseInfo, SymbolsMapTokens[value])); + nextCharCode = peek(parseInfo).charCodeAt(0); + // not a number + if (charCode === 43 /* TokenMap.PLUS */ && !(nextCharCode >= 0x30 && nextCharCode <= 0x39)) { + if (parseInfo.position < parseInfo.currentPosition) { + result.push(yieldResult(parseInfo)); + } + next(parseInfo); + result.push(yieldResult(parseInfo, SymbolsMapTokens[parseInfo.stream + .slice(parseInfo.position - parseInfo.offset, parseInfo.currentPosition - parseInfo.offset) + .toLowerCase()])); break; } - buffer += value; + next(parseInfo); break; // '{' case 123 /* TokenMap.LEFT_BRACE */: - if (buffer.length > 0) { - result.push(yieldResult(buffer, parseInfo)); - buffer = ""; + if (parseInfo.position < parseInfo.currentPosition) { + result.push(yieldResult(parseInfo)); } - result.push(yieldResult(next(parseInfo), parseInfo, exports.EnumToken.BlockStartTokenType)); + next(parseInfo); + result.push(yieldResult(parseInfo, exports.EnumToken.BlockStartTokenType)); break; // '}' case 125 /* TokenMap.RIGHT_BRACE */: - if (buffer.length > 0) { - result.push(yieldResult(buffer, parseInfo)); - buffer = ""; + if (parseInfo.position < parseInfo.currentPosition) { + result.push(yieldResult(parseInfo)); } - result.push(yieldResult(next(parseInfo), parseInfo, exports.EnumToken.BlockEndTokenType)); + next(parseInfo); + result.push(yieldResult(parseInfo, exports.EnumToken.BlockEndTokenType)); break; // '(' case 40 /* TokenMap.LEFT_PARENTHESIS */: - if (buffer.length > 0) { - if (buffer[0] === ":" && isPseudo(buffer)) { + if (parseInfo.position < parseInfo.currentPosition) { + if (parseInfo.stream[parseInfo.position - parseInfo.offset] === ":" && isPseudo(parseInfo)) { next(parseInfo); - result.push(yieldResult(buffer, parseInfo, exports.EnumToken.PseudoClassFunctionTokenDefType)); - buffer = ""; + result.push(yieldResult(parseInfo, exports.EnumToken.PseudoClassFunctionTokenDefType)); break; } - else if (isIdent(buffer)) { - const hint = buffer.startsWith("--") + else if (isIdentToken(parseInfo)) { + const hint = startsWith(parseInfo, "--") ? exports.EnumToken.CustomFunctionTokenDefType - : (SymbolsMapTokens[buffer.toLowerCase() + "("] ?? exports.EnumToken.FunctionTokenDefType); - result.push(yieldResult(buffer, parseInfo, hint)); + : (SymbolsMapTokens[parseInfo.stream + .slice(parseInfo.position - parseInfo.offset, parseInfo.currentPosition - parseInfo.offset) + .toLowerCase() + "("] ?? exports.EnumToken.FunctionTokenDefType); + result.push(yieldResult(parseInfo, hint)); next(parseInfo); - buffer = ""; + // consume '(' + parseInfo.position = parseInfo.currentPosition; if (hint === exports.EnumToken.UrlFunctionTokenDefType) { - buffer = ""; - value = peek(parseInfo); // consume an - while (isWhiteSpace((charCode = value.charCodeAt(0)))) { - buffer += next(parseInfo); - value = peek(parseInfo); - charCode = value.charCodeAt(0); - if (value === "/" && match(parseInfo, "/*")) { - if (buffer.length > 0) { - result.push(yieldResult(buffer, parseInfo)); - buffer = ""; - } - buffer += next(parseInfo, 2); - while ((value = next(parseInfo))) { - if (value == "*") { - buffer += value; - if (match(parseInfo, "/")) { - result.push(yieldResult(buffer + next(parseInfo), parseInfo, exports.EnumToken.CommentTokenType)); - buffer = ""; - break; - } - } - else { - buffer += value; - } - } - if (buffer.length > 0) { - result.push(yieldResult(buffer, parseInfo, exports.EnumToken.BadCommentTokenType)); - buffer = ""; - } - value = peek(parseInfo); - charCode = value.charCodeAt(0); - } + while (isWhiteSpace(peek(parseInfo).charCodeAt(0))) { + next(parseInfo); } - if (buffer.length > 0) { - result.push(yieldResult(buffer, parseInfo, exports.EnumToken.WhitespaceTokenType)); - buffer = ""; + charCode = peek(parseInfo).charCodeAt(0); + let values = null; + if (charCode == 34 /* TokenMap.DOUBLE_QUOTE */ || charCode == 39 /* TokenMap.SINGLE_QUOTE */) { + values = consumeString(parseInfo); } - if (value === ")" || value === '"' || value === "'") { - break; + else { + do { + next(parseInfo); + // value = peek(parseInfo); + charCode = peek(parseInfo).charCodeAt(0); + } while ( + // !(value === "/" && match(parseInfo, "/*") && + charCode !== 41 /* TokenMap.RIGHT_PARENTHESIS */ && + parseInfo.currentPosition < endPosition); } - do { - buffer += next(parseInfo); - value = peek(parseInfo); - charCode = value.charCodeAt(0); - } while (value !== ")" && - !isWhiteSpace(charCode) && - !(value === "/" && match(parseInfo, "/*"))); - if (buffer.length > 0) { - result.push(yieldResult(buffer, parseInfo, peek(parseInfo) === "" + if (values != null) { + // NaN is not equal to NaN + if ((charCode = peek(parseInfo).charCodeAt(0)) != charCode) { + for (let i = 0; i < values.length; i++) { + values[i].token.typ = exports.EnumToken.BadUrlTokenType; + } + } + result.push(...values); + } + else if (parseInfo.position < parseInfo.currentPosition) { + result.push(yieldResult(parseInfo, + // parseInfo.position < parseInfo.currentPosition + (charCode = peek(parseInfo).charCodeAt(0)) != charCode || !isURLToken(parseInfo) ? exports.EnumToken.BadUrlTokenType : exports.EnumToken.UrlTokenTokenType)); - buffer = ""; } } - // console.debug({value: peek(parseInfo)}); break; } } - result.push(yieldResult(next(parseInfo), parseInfo, exports.EnumToken.StartParensTokenType)); - buffer = ""; + next(parseInfo); + result.push(yieldResult(parseInfo, exports.EnumToken.StartParensTokenType)); break; // ')' case 41 /* TokenMap.RIGHT_PARENTHESIS */: - if (buffer.length > 0) { - result.push(yieldResult(buffer, parseInfo)); - buffer = ""; + if (parseInfo.position < parseInfo.currentPosition) { + result.push(yieldResult(parseInfo)); } - result.push(yieldResult(next(parseInfo), parseInfo, exports.EnumToken.EndParensTokenType)); + next(parseInfo); + result.push(yieldResult(parseInfo, exports.EnumToken.EndParensTokenType)); break; // '[' case 91 /* TokenMap.LEFT_BRACKETS */: - if (buffer.length > 0) { - result.push(yieldResult(buffer, parseInfo)); - buffer = ""; + if (parseInfo.position < parseInfo.currentPosition) { + result.push(yieldResult(parseInfo)); } - result.push(yieldResult(next(parseInfo), parseInfo, exports.EnumToken.AttrStartTokenType)); + next(parseInfo); + result.push(yieldResult(parseInfo, exports.EnumToken.AttrStartTokenType)); break; // ']' case 93 /* TokenMap.RIGHT_BRACKETS */: - if (buffer.length > 0) { - result.push(yieldResult(buffer, parseInfo)); - buffer = ""; + if (parseInfo.position < parseInfo.currentPosition) { + result.push(yieldResult(parseInfo)); } - result.push(yieldResult(next(parseInfo), parseInfo, exports.EnumToken.AttrEndTokenType)); + next(parseInfo); + result.push(yieldResult(parseInfo, exports.EnumToken.AttrEndTokenType)); break; case 59 /* TokenMap.SEMICOLON */: - if (buffer.length > 0) { - result.push(yieldResult(buffer, parseInfo)); - buffer = ""; + if (parseInfo.position < parseInfo.currentPosition) { + result.push(yieldResult(parseInfo)); } - result.push(yieldResult(next(parseInfo), parseInfo, exports.EnumToken.SemiColonTokenType)); + next(parseInfo); + result.push(yieldResult(parseInfo, exports.EnumToken.SemiColonTokenType)); break; case 58 /* TokenMap.COLON */: - if (buffer.length > 0) { - result.push(yieldResult(buffer, parseInfo)); - buffer = ""; + if (parseInfo.position < parseInfo.currentPosition) { + result.push(yieldResult(parseInfo)); } next(parseInfo); if (peek(parseInfo).charCodeAt(0) == 58 /* TokenMap.COLON */) { - result.push(yieldResult(value + next(parseInfo), parseInfo, exports.EnumToken.DoubleColonTokenType)); + next(parseInfo); + result.push(yieldResult(parseInfo, exports.EnumToken.DoubleColonTokenType)); break; } - result.push(yieldResult(value, parseInfo, exports.EnumToken.ColonTokenType)); + result.push(yieldResult(parseInfo, exports.EnumToken.ColonTokenType)); break; // \n \r \f \v \t space case 0x9: @@ -22134,241 +22576,229 @@ function tokenize(parseInfo, yieldEOFToken = true) { case 0xd: case 0x2028: case 0x2029: - if (buffer.length > 0) { - result.push(yieldResult(buffer, parseInfo)); - buffer = ""; + if (parseInfo.position < parseInfo.currentPosition) { + result.push(yieldResult(parseInfo)); } - buffer += next(parseInfo); - nextCharCode = parseInfo.stream.charAt(parseInfo.currentPosition - parseInfo.offset + 1).charCodeAt(0); + next(parseInfo); + nextCharCode = parseInfo.stream.charAt(parseInfo.currentPosition - parseInfo.offset).charCodeAt(0); while (nextCharCode == 0x20 || (nextCharCode >= 0x9 && nextCharCode <= 0xd) || nextCharCode == 0x2028 || nextCharCode == 0x2029) { - value += next(parseInfo); - nextCharCode = parseInfo.stream - .charAt(parseInfo.currentPosition - parseInfo.offset + 1) - .charCodeAt(0); + next(parseInfo); + nextCharCode = parseInfo.stream.charAt(parseInfo.currentPosition - parseInfo.offset).charCodeAt(0); } - result.push(yieldResult(value, parseInfo, exports.EnumToken.WhitespaceTokenType)); - buffer = ""; + result.push(yieldResult(parseInfo, exports.EnumToken.WhitespaceTokenType)); break; case 44 /* TokenMap.COMMA */: - if (buffer.length > 0) { - result.push(yieldResult(buffer, parseInfo)); - buffer = ""; + if (parseInfo.position < parseInfo.currentPosition) { + result.push(yieldResult(parseInfo)); } - result.push(yieldResult(next(parseInfo), parseInfo, exports.EnumToken.CommaTokenType)); + next(parseInfo); + result.push(yieldResult(parseInfo, exports.EnumToken.CommaTokenType)); break; case 36 /* TokenMap.DOLLAR */: - if (buffer.length > 0) { - result.push(yieldResult(buffer, parseInfo)); - buffer = ""; + if (parseInfo.position < parseInfo.currentPosition) { + result.push(yieldResult(parseInfo)); } if (match(parseInfo, "$=")) { - result.push(yieldResult(next(parseInfo, 2), parseInfo, exports.EnumToken.EndMatchTokenType)); + next(parseInfo, 2); + result.push(yieldResult(parseInfo, exports.EnumToken.EndMatchTokenType)); break; } - buffer += next(parseInfo); + next(parseInfo); break; case 126 /* TokenMap.TILDA */: - if (buffer.length > 0) { - result.push(yieldResult(buffer, parseInfo)); - buffer = ""; + if (parseInfo.position < parseInfo.currentPosition) { + result.push(yieldResult(parseInfo)); } if (match(parseInfo, "~=")) { - result.push(yieldResult(next(parseInfo, 2), parseInfo, exports.EnumToken.IncludeMatchTokenType)); + next(parseInfo, 2); + result.push(yieldResult(parseInfo, exports.EnumToken.IncludeMatchTokenType)); break; } - result.push(yieldResult(next(parseInfo), parseInfo, exports.EnumToken.Tilda)); - buffer = ""; + next(parseInfo); + result.push(yieldResult(parseInfo, exports.EnumToken.Tilda)); break; // case '^': case 94 /* TokenMap.CARET */: - if (buffer.length > 0) { - result.push(yieldResult(buffer, parseInfo)); - buffer = ""; + if (parseInfo.position < parseInfo.currentPosition) { + result.push(yieldResult(parseInfo)); } if (match(parseInfo, "^=")) { - result.push(yieldResult(next(parseInfo, 2), parseInfo, exports.EnumToken.StartMatchTokenType)); + next(parseInfo, 2); + result.push(yieldResult(parseInfo, exports.EnumToken.StartMatchTokenType)); break; } - buffer += next(parseInfo); + next(parseInfo); break; case 42 /* TokenMap.STAR */: - if (buffer.length > 0) { - result.push(yieldResult(buffer, parseInfo)); - buffer = ""; + if (parseInfo.position < parseInfo.currentPosition) { + result.push(yieldResult(parseInfo)); } if (match(parseInfo, "*=")) { - result.push(yieldResult(next(parseInfo, 2), parseInfo, exports.EnumToken.ContainMatchTokenType)); + next(parseInfo, 2); + result.push(yieldResult(parseInfo, exports.EnumToken.ContainMatchTokenType)); break; } - result.push(yieldResult(next(parseInfo), parseInfo, exports.EnumToken.Star)); - buffer = ""; + next(parseInfo); + result.push(yieldResult(parseInfo, exports.EnumToken.Star)); break; case 38 /* TokenMap.AMPERSAND */: - if (buffer.length > 0) { - result.push(yieldResult(buffer, parseInfo)); - buffer = ""; + if (parseInfo.position < parseInfo.currentPosition) { + result.push(yieldResult(parseInfo)); } - result.push(yieldResult(next(parseInfo), parseInfo, exports.EnumToken.NestingSelectorTokenType)); - buffer = ""; + next(parseInfo); + result.push(yieldResult(parseInfo, exports.EnumToken.NestingSelectorTokenType)); break; case 124 /* TokenMap.PIPE */: - if (buffer.length > 0) { - result.push(yieldResult(buffer, parseInfo)); - buffer = ""; + if (parseInfo.position < parseInfo.currentPosition) { + result.push(yieldResult(parseInfo)); } // '||' if (match(parseInfo, "||")) { - result.push(yieldResult(next(parseInfo, 2), parseInfo, exports.EnumToken.ColumnCombinatorTokenType)); + next(parseInfo, 2); + result.push(yieldResult(parseInfo, exports.EnumToken.ColumnCombinatorTokenType)); break; } else if (match(parseInfo, "|=")) { - result.push(yieldResult(next(parseInfo, 2), parseInfo, exports.EnumToken.DashMatchTokenType)); + next(parseInfo, 2); + result.push(yieldResult(parseInfo, exports.EnumToken.DashMatchTokenType)); break; } - result.push(yieldResult(next(parseInfo), parseInfo, exports.EnumToken.Pipe)); - buffer = ""; + next(parseInfo); + result.push(yieldResult(parseInfo, exports.EnumToken.Pipe)); break; case 33 /* TokenMap.EXCLAMATION */: - if (buffer.length > 0) { - result.push(yieldResult(buffer, parseInfo)); - buffer = ""; + if (parseInfo.position < parseInfo.currentPosition) { + result.push(yieldResult(parseInfo)); } if (match(parseInfo, "!important")) { - result.push(yieldResult(next(parseInfo, 10), parseInfo, exports.EnumToken.ImportantTokenType)); - buffer = ""; + next(parseInfo, 10); + result.push(yieldResult(parseInfo, exports.EnumToken.ImportantTokenType)); break; } - buffer += next(parseInfo); + next(parseInfo); break; case 47 /* TokenMap.SLASH */: - if (buffer.length > 0) { - result.push(yieldResult(buffer, parseInfo)); - buffer = ""; + if (parseInfo.position < parseInfo.currentPosition) { + result.push(yieldResult(parseInfo)); } if (!match(parseInfo, "/*")) { - result.push(yieldResult(next(parseInfo), parseInfo, SymbolsMapTokens[value])); + next(parseInfo); + result.push(yieldResult(parseInfo, SymbolsMapTokens[parseInfo.stream.slice(parseInfo.position, parseInfo.currentPosition)])); break; } - buffer += next(parseInfo, 2); - while ((value = next(parseInfo))) { - if (value == "*") { - buffer += value; + next(parseInfo, 2); + while ((charCode = next(parseInfo).charCodeAt(0)) == charCode) { + if (charCode == 42 /* TokenMap.STAR */) { if (match(parseInfo, "/")) { - result.push(yieldResult(buffer + next(parseInfo), parseInfo, exports.EnumToken.CommentTokenType)); - buffer = ""; + next(parseInfo); + result.push(yieldResult(parseInfo, exports.EnumToken.CommentTokenType)); break; } } - else { - buffer += value; - } + // else { + // buffer += value; + // } } - if (buffer.length > 0) { - result.push(yieldResult(buffer, parseInfo, exports.EnumToken.BadCommentTokenType)); - buffer = ""; + if (parseInfo.position < parseInfo.currentPosition) { + result.push(yieldResult(parseInfo, exports.EnumToken.BadCommentTokenType)); } break; case 62 /* TokenMap.GREATERTHAN */: - if (buffer.length > 0) { - result.push(yieldResult(buffer, parseInfo)); - buffer = ""; + if (parseInfo.position < parseInfo.currentPosition) { + result.push(yieldResult(parseInfo)); } if (match(parseInfo, ">=")) { - result.push(yieldResult(next(parseInfo, 2), parseInfo, exports.EnumToken.GteTokenType)); + next(parseInfo, 2); + result.push(yieldResult(parseInfo, exports.EnumToken.GteTokenType)); break; } - result.push(yieldResult(next(parseInfo), parseInfo, exports.EnumToken.GtTokenType)); - buffer = ""; + next(parseInfo); + result.push(yieldResult(parseInfo, exports.EnumToken.GtTokenType)); break; case 60 /* TokenMap.LOWERTHAN */: - if (buffer.length > 0) { - result.push(yieldResult(buffer, parseInfo)); - buffer = ""; + if (parseInfo.position < parseInfo.currentPosition) { + result.push(yieldResult(parseInfo)); } if (match(parseInfo, "<=")) { - result.push(yieldResult(next(parseInfo, 2), parseInfo, exports.EnumToken.LteTokenType)); + next(parseInfo, 2); + result.push(yieldResult(parseInfo, exports.EnumToken.LteTokenType)); break; } - buffer += next(parseInfo); + next(parseInfo); if (match(parseInfo, "!--")) { - buffer += next(parseInfo, 3); - while ((value = next(parseInfo))) { - buffer += value; - if (value == "-" && match(parseInfo, "->")) { + next(parseInfo, 3); + while ((charCode = next(parseInfo).charCodeAt(0)) == charCode) { + if (charCode == 45 /* TokenMap.MINUS */ && match(parseInfo, "->")) { break; } } - if (value === "") { - result.push(yieldResult(buffer, parseInfo, exports.EnumToken.BadCdoTokenType)); + if (parseInfo.currentPosition >= endPosition) { + result.push(yieldResult(parseInfo, exports.EnumToken.BadCdoTokenType)); } else { - result.push(yieldResult(buffer + next(parseInfo, 2), parseInfo, exports.EnumToken.CDOCOMMTokenType)); + next(parseInfo, 2); + result.push(yieldResult(parseInfo, exports.EnumToken.CDOCOMMTokenType)); } - buffer = ""; } break; case 35 /* TokenMap.HASH */: - if (buffer.length > 0) { - result.push(yieldResult(buffer, parseInfo)); - buffer = ""; + if (parseInfo.position < parseInfo.currentPosition) { + result.push(yieldResult(parseInfo)); } - buffer += next(parseInfo); + next(parseInfo); break; case 92 /* TokenMap.REVERSE_SOLIDUS */: + if (!yieldEOFToken && parseInfo.stream.length == parseInfo.currentPosition - parseInfo.offset + 1) { + break; + } next(parseInfo); // EOF - if (!(peek(parseInfo))) { + if (!peek(parseInfo)) { + if (!yieldEOFToken) { + break; + } // end of stream ignore \\ - if (buffer.length > 0) { - result.push(yieldResult(buffer, parseInfo)); - buffer = ""; + if (parseInfo.position < parseInfo.currentPosition) { + result.push(yieldResult(parseInfo)); } break; } - buffer += value + next(parseInfo); - // buffer += - // (parseInfo.offset == parseInfo.currentPosition - // ? parseInfo.buffer.slice(-1) - // : parseInfo.stream.charAt(parseInfo.currentPosition - parseInfo.offset - 1)) + value; + next(parseInfo); break; case 39 /* TokenMap.SINGLE_QUOTE */: case 34 /* TokenMap.DOUBLE_QUOTE */: - if (buffer.length > 0) { - result.push(yieldResult(buffer, parseInfo)); - buffer = ""; + if (parseInfo.position < parseInfo.currentPosition) { + result.push(yieldResult(parseInfo)); } result.push(...consumeString(parseInfo)); break; case 46 /* TokenMap.DOT */: const codepoint = parseInfo.stream - .charAt(parseInfo.currentPosition - parseInfo.offset + 2) + .charAt(parseInfo.currentPosition - parseInfo.offset + 1) .charCodeAt(0); - if (!isDigit(codepoint) && buffer !== "") { - result.push(yieldResult(buffer, parseInfo)); - buffer = next(parseInfo, 2); + if (!isDigit(codepoint) && parseInfo.position !== parseInfo.currentPosition) { + result.push(yieldResult(parseInfo)); + next(parseInfo, 2); break; } - buffer += next(parseInfo); + next(parseInfo); break; default: - buffer += next(parseInfo); + next(parseInfo); break; } - if (!yieldEOFToken && endPosition <= parseInfo.stream.length - parseInfo.currentPosition + parseInfo.offset) { + if (!yieldEOFToken && endPosition <= parseInfo.currentPosition - parseInfo.offset + 1) { break; } } if (yieldEOFToken) { - if (buffer.length > 0) { - result.push(yieldResult(buffer, parseInfo)); + if (parseInfo.position < parseInfo.currentPosition) { + result.push(yieldResult(parseInfo)); } - result.push(yieldResult("", parseInfo, exports.EnumToken.EOFTokenType)); - } - else { - parseInfo.buffer = buffer; + result.push(yieldResult(parseInfo, exports.EnumToken.EOFTokenType)); } parseInfo.time += performance.now() - startTime; return result; @@ -22381,19 +22811,17 @@ function tokenize(parseInfo, yieldEOFToken = true) { async function* tokenizeStream(input, parseInfo) { const decoder = new TextDecoder("utf-8"); const reader = input.getReader(); + parseInfo.stream = ""; while (true) { const { done, value } = await reader.read(); const stream = ArrayBuffer.isView(value) ? decoder.decode(value, { stream: true }) : value; if (!done) { parseInfo.source.append(stream); - if (typeof parseInfo.stream != "string") { - parseInfo.stream = stream; - } - else { - parseInfo.stream = (parseInfo.stream.slice(parseInfo.currentPosition - parseInfo.offset + 1) + - stream); - } - parseInfo.offset = parseInfo.currentPosition + 1; + parseInfo.stream = (parseInfo.stream.slice(parseInfo.position - parseInfo.offset) + stream); + parseInfo.offset = parseInfo.offset = parseInfo.position; + } + else { + parseInfo.stream = ""; } yield* tokenize(parseInfo, done); if (done) { @@ -22407,7 +22835,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 +22847,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 +22855,22 @@ function minify(ast, options = {}, recursive = false, errors, nestingContent, co let postprocess = false; let parents; let replacement; - if (!("features" in options)) { - // @ts-ignore - options = { + let { sourcemap, module, ...options2 } = options; + if (!(options2.features != null)) { + 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 +22885,20 @@ 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); + : // @ts-ignore + replacement.nam); } - const result = feature.run(replacement, options, parent[PARENT] ?? ast, context, exports.FeatureWalkMode.Pre); + const result = feature.run(replacement, options2, + // @ts-ignore + parent[PARENT] ?? ast, context, exports.FeatureWalkMode.Pre); if (result != null) { replacement = result; } @@ -22475,24 +22907,26 @@ function minify(ast, options = {}, recursive = false, errors, nestingContent, co (!Array.isArray(replacement) || replacement.length > 0) && replacement != parent && parent[PARENT] != null) { + // @ts-ignore replaceNodeOrValue(parent[PARENT], parent, replacement); } - if ("chi" in replacement) { + // @ts-ignore + if (replacement.chi != null) { // @ts-ignore for (const node of replacement.chi) { - // node[PARENT] = replacement; + node[PARENT] = replacement; parents.add(node); } } } - 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 +22934,14 @@ 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, + // @ts-ignore + parent[PARENT] ?? ast, context, exports.FeatureWalkMode.Post); if (result != null) { replacement = result; } @@ -22518,18 +22954,20 @@ function minify(ast, options = {}, recursive = false, errors, nestingContent, co // @ts-ignore replaceNodeOrValue(parent[PARENT], parent, replacement); } - if ("chi" in replacement) { + // @ts-ignore + if (replacement.chi != null) { + // @ts-ignore for (const node of replacement.chi) { - // node[PARENT] = replacement; + node[PARENT] = replacement; parents.add(node); } } } 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); } } } @@ -22549,6 +22987,7 @@ function transformAtRuleMediaPrelude(values) { values[values.indexOf(value)] = value.l; } else { + // @ts-ignore replaceNodeOrValue(parent, value, value.l); // @ts-ignore value = value.l; @@ -22605,9 +23044,11 @@ function transformAtRuleMediaPrelude(values) { // @ts-expect-error const p = parents?.[parents?.indexOf?.(parent) + 1]; if (p != null) { + // @ts-ignore replaceNodeOrValue(p, parent, replacement); } else { + // @ts-ignore values.splice(values.indexOf(parent), 1, replacement); } hasUpdates = true; @@ -22622,9 +23063,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; @@ -22721,8 +23162,8 @@ function doMinify(ast, options = {}, recursive = false, errors, nestingContent, continue; } while (previous?.typ === exports.EnumToken.CommentNodeType) { + // @ts-ignore previous = ast.chi[--nodeIndex]; - continue; } node = ast.chi[i]; if (node.typ === exports.EnumToken.AtRuleNodeType && node.nam === "font-face") { @@ -22738,12 +23179,13 @@ 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 previous.chi.push(...node.chi); + // @ts-ignore ast.chi.splice(i, 1); previous = ast?.chi?.[nodeIndex] ?? null; i = nodeIndex; @@ -22816,6 +23258,7 @@ function doMinify(ast, options = {}, recursive = false, errors, nestingContent, else if (ast.typ === node.typ && ast.nam === node.nam && ast.val === node.val) { + // @ts-ignore replaceNodeOrValue(ast, node, node.chi); i--; continue; @@ -22909,7 +23352,9 @@ function doMinify(ast, options = {}, recursive = false, errors, nestingContent, ":is(" + node[OPTIMIZED].selector.reduce(reducer, []).join(",") + ")"; - const sel2 = node[OPTIMIZED].selector.reduce((acc, curr) => (acc.length > 0 ? acc + "," : "") + node[OPTIMIZED].optimized[0] + curr.join(""), ""); + const sel2 = node[OPTIMIZED].selector.reduce((acc, curr) => + // @ts-ignore + (acc.length > 0 ? acc + "," : "") + node[OPTIMIZED].optimized[0] + curr.join(""), ""); node.sel = sel1.length < sel2.length ? sel1 : sel2; node[TOKENS] = null; } @@ -22989,7 +23434,9 @@ function doMinify(ast, options = {}, recursive = false, errors, nestingContent, ":is(" + node[OPTIMIZED].selector.reduce(reducer, []).join(",") + ")"; - const sel2 = node[OPTIMIZED].selector.reduce((acc, curr) => (acc.length > 0 ? acc + "," : "") + node[OPTIMIZED].optimized[0] + curr.join(""), ""); + const sel2 = node[OPTIMIZED].selector.reduce((acc, curr) => + // @ts-ignore + (acc.length > 0 ? acc + "," : "") + node[OPTIMIZED].optimized[0] + curr.join(""), ""); node.sel = sel1.length < sel2.length ? sel1 : sel2; node[TOKENS] = null; } @@ -23000,11 +23447,14 @@ function doMinify(ast, options = {}, recursive = false, errors, nestingContent, curr.join(""), ""); node[TOKENS] = null; } + // @ts-ignore } else if (node[OPTIMIZED]?.optimized.length > 0) { + // @ts-ignore const sel = node[OPTIMIZED].optimized.join(""); if (sel.length < node.sel.length) { node.sel = sel; + // @ts-ignore node[RAW] = [node[OPTIMIZED].optimized.slice()]; node[TOKENS] = null; } @@ -23027,11 +23477,14 @@ 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) || + // @ts-ignore (node.typ == exports.EnumToken.AtRuleNodeType && node.nam !== "font-face" && + // @ts-ignore node.nam === previous.nam)) { + // @ts-ignore node.chi.unshift(...previous.chi); doMinify(node, options, recursive, errors, nestingContent, context); ast.chi.splice(nodeIndex, 1); @@ -23040,7 +23493,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 +23646,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 +24030,6 @@ function wrapNodes(previous, node, match, ast, reducer, i, nodeIndex) { * Diff nodes * @param n1 * @param n2 - * @param reducer * @param options * * @private @@ -23694,17 +24148,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) { @@ -23794,12 +24267,24 @@ function reduceRuleSelector(node) { * @private */ function expand(ast) { - const result = { ...ast, chi: [] }; + if (ast[STATE] == exports.EnumAstNodeStatus.Invalid || + ast[STATE] == exports.EnumAstNodeStatus.Disallowed || + ast[STATE] == exports.EnumAstNodeStatus.Unknown || + ast[STATE] == exports.EnumAstNodeStatus.Unparsed || + ast[STATE] == exports.EnumAstNodeStatus.Malformed) { + return ast; + } + const result = Object.assign(cloneNode(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 +24296,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); } @@ -23822,7 +24320,14 @@ function expand(ast) { return result; } function expandRule(node) { - const ast = { ...node, chi: node.chi.slice() }; + if (node[STATE] == exports.EnumAstNodeStatus.Invalid || + node[STATE] == exports.EnumAstNodeStatus.Disallowed || + node[STATE] == exports.EnumAstNodeStatus.Unknown || + node[STATE] == exports.EnumAstNodeStatus.Unparsed || + node[STATE] == exports.EnumAstNodeStatus.Malformed) { + return [node]; + } + const ast = Object.assign(cloneNode(node), { chi: node.chi.slice() }); const result = []; if (ast.typ == exports.EnumToken.RuleNodeType) { let i = 0; @@ -23839,7 +24344,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("")); @@ -24013,163 +24521,27 @@ function replaceCompound(input, replace) { } return tokens.reduce((acc, curr) => acc + renderValue(curr), ""); } -function replaceCompoundLiteral(selector, replace) { - const tokens = [""]; - let i = 0; - for (; i < selector.length; i++) { - if (selector.charAt(i) == "&") { - tokens.push("&", ""); - } - } - return tokens - .sort((a, b) => { - if (a == "&") { - return 1; - } - return b == "&" ? -1 : 0; - }) - .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 - */ -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), "")); - } +function replaceCompoundLiteral(selector, replace) { + const tokens = [""]; + let i = 0; + for (; i < selector.length; i++) { + if (selector.charAt(i) == "&") { + tokens.push("&", ""); } - return { - version: this.version, - sources: this.sources.slice(), - mappings: mappings.join(";"), - }; } + return tokens + .sort((a, b) => { + if (a == "&") { + return 1; + } + return b == "&" ? -1 : 0; + }) + .reduce((acc, curr) => acc + (curr == "&" ? replace : curr), ""); } +/** + * match url + */ const matchUrl = /^(https?:)?\/\//; /** * return the directory name of a path @@ -24181,6 +24553,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 +24579,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 +24588,7 @@ function splitPath(result) { } // else if (chr == "?" || chr == "#") { // break; - // } + // } else { parts[parts.length - 1] += chr; } @@ -24234,6 +24606,8 @@ function splitPath(result) { } /** * Nomalize path + * @param path + * @private */ const normalize = memoize(function (path) { let parts = []; @@ -24262,14 +24636,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 +24672,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("/") || url.match(/^[a-zA-Z]:/) ? 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 +24779,28 @@ function doRender(data, options = {}, mapping) { const startTime = performance.now(); const errors = []; const sourcemap = options.sourcemap ? new SourceMap() : null; + const sourcemaps = options.sourcemap ? { sources: [], maps: [] } : 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 +24812,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 +24827,12 @@ function doRender(data, options = {}, mapping) { }, }; if (sourcemap != null) { + let source; + for (const sourceId of sourcemaps.sources) { + source = options.sourcesMap.get(sourceId); + sourcemap.addSourceContent(source.id, source.getFileName(), source.getContent()); + } + sourcemap.add(...sourcemaps.maps); result.map = sourcemap; if (options.sourcemap === "inline") { result.code += `\n/*# sourceMappingURL=${result.map.toUrl()} */`; @@ -24432,37 +24845,93 @@ 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; + 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]; + 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]; + } + if (!sourcemaps.sources.includes(srcId)) { + sourcemaps.sources.push(srcId); + } + sourcemaps.maps.push([newLine, newColumn, srcId, ...offsets]); } - 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]; + } + if (!sourcemaps.sources.includes(srcId)) { + sourcemaps.sources.push(srcId); + } + sourcemaps.maps.push([newLine, newColumn, srcId, ...offsets]); + } } - 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 +24961,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 +24972,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 +24992,47 @@ 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 lineMapLength = linesMap ? linesMap.getLineStarts().length : 0; + 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 +25041,63 @@ 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); + if (!sourcemaps.sources.includes(node[LOC].srcId)) { + sourcemaps.sources.push(node[LOC].srcId); + } + sourcemaps.maps.push([ + ...linesMap.getOffsets(sourceLocation.end - str.length + options.newLine.length + indentSub.length), + node[LOC].srcId, + ...source.getOffsets(node[LOC].sta), + ]); + } } - return `${css}${options.newLine}${indentSub}${str}`; - }, ""); - if (options.removeEmpty && children === "") { - return ""; } if (children.endsWith(";")) { children = children.slice(0, -1); + sourceLocation.end--; + } + if (options.removeEmpty && children === "") { + if (sourcemaps != null) { + sourceLocation.end -= prelude.length; + linesMap.getLineStarts().length = lineMapLength; + } + return ""; + } + const end = options.newLine + indent + `}`; + if (sourcemaps != null) { + move(sourceLocation, linesMap, 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: + return prelude + children + end; default: return ""; } @@ -24633,6 +25106,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) { @@ -24709,8 +25185,8 @@ function renderValue(token, options = {}, cache = Object.create(null), reducer, return " + "; case exports.EnumToken.Sub: return " - "; - case exports.EnumToken.Star: case exports.EnumToken.UniversalSelectorTokenType: + case exports.EnumToken.Star: case exports.EnumToken.Mul: return "*"; case exports.EnumToken.Div: @@ -25549,11 +26025,11 @@ function renderValue(token, options = {}, cache = Object.create(null), reducer, case exports.EnumToken.OrTokenType: return "or"; case exports.EnumToken.InvalidMediaQueryTokenType: - // case EnumToken.InvalidDeclarationNodeType: case exports.EnumToken.InvalidCommentTokenType: case exports.EnumToken.BadCommentTokenType: case exports.EnumToken.BadCdoTokenType: case exports.EnumToken.BadStringTokenType: + case exports.EnumToken.BadUrlTokenType: case exports.EnumToken.EOFTokenType: return ""; default: @@ -25625,7 +26101,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 }), "")); @@ -25635,7 +26111,7 @@ function parseSelector(tokens, context, options, errors) { chi: [], [LOC]: { ...tokens[0][LOC], - end: tokens[tokens.length - 1]?.[LOC]?.end ?? tokens[0]?.[LOC]?.end + end: tokens[tokens.length - 1]?.[LOC]?.end ?? tokens[0]?.[LOC]?.end, }, [TOKENS]: tokens.length === 0 ? null : tokens, [STATE]: result.success ? exports.EnumAstNodeStatus.Validated : exports.EnumAstNodeStatus.Invalid, @@ -25728,16 +26204,70 @@ function parseSelector(tokens, context, options, errors) { } } if (tokens[i].typ == exports.EnumToken.ColorTokenType) { - // if (isIdent((tokens[i] as ColorToken).val)) { - // Object.assign(tokens[i], { - // typ: EnumToken.IdenTokenType, - // }); - // } else if (isHash(tokens[i].val)) { Object.assign(tokens[i], { typ: exports.EnumToken.HashTokenType, }); } + else { + return { + typ: exports.EnumToken.RuleNodeType, + sel: [ + ...tokens + .reduce((acc, curr, index, array) => { + // if (curr.typ == EnumToken.CommentTokenType) { + // return acc; + // } + if (curr.typ == exports.EnumToken.WhitespaceTokenType) { + if (trimWhiteSpace.includes(array[index - 1]?.typ) || + trimWhiteSpace.includes(array[index + 1]?.typ) || + combinators.includes(array[index - 1]?.val) || + combinators.includes(array[index + 1]?.val)) { + return acc; + } + } + let t = renderValue(curr, { minify: false }); + if (t == ",") { + acc.push([]); + } + else { + acc[acc.length - 1].push(t); + } + return acc; + }, [[]]) + .reduce((acc, curr) => { + let i = 0; + for (; i < curr.length; i++) { + if (i + 1 < curr.length && curr[i] == "*") { + if (curr[i] == "*") { + let index = curr[i + 1] == " " ? 2 : 1; + if (![">", "~", "+"].includes(curr[index])) { + curr.splice(i, index); + } + } + } + } + acc.set(curr.join(""), curr); + return acc; + }, uniq) + .keys(), + ].join(","), + chi: [], + [LOC]: { + ...tokens[0][LOC], + end: tokens[tokens.length - 1][LOC].end, + }, + [TOKENS]: tokens, + [STATE]: exports.EnumAstNodeStatus.Invalid, + [ERRORS]: [ + { + action: "drop", + node: tokens[i], + message: "invalid hash id", + }, + ], + }; + } } } const result = matchSelectorSyntax(tokens, errors, options, nested === true); @@ -25789,6 +26319,29 @@ function parseSelector(tokens, context, options, errors) { func.val == ":nth-last-child" || func.val == ":nth-of-type" || func.val == ":nth-last-of-type") { + const list = []; + let index; + for (index = 0; index < func.chi.length; index++) { + if (func.chi[index].typ == exports.EnumToken.CommentTokenType || func.chi[index].typ == exports.EnumToken.WhitespaceTokenType) { + continue; + } + if (func.chi[index].typ == exports.EnumToken.IdenTokenType && equalsIgnoreCase('of', func.chi[index].val)) { + index--; + break; + } + list.push(func.chi[index]); + } + if (list.length == 3) { + if (list[0].typ == exports.EnumToken.IdenTokenType && ('n' == list[0].val || '-n' == list[0].val || '+n' == list[0].val)) { + if (list[1].typ == exports.EnumToken.NextSiblingCombinatorTokenType) { + if (list[2].typ == exports.EnumToken.NumberTokenType && (0 == list[2].val)) { + list[0].val = 'n'; + func.chi.splice(0, index, list[0]); + break; + } + } + } + } const token = func.chi.find((t) => t.typ != exports.EnumToken.WhitespaceTokenType && t.typ != exports.EnumToken.CommentTokenType); if (token?.typ == exports.EnumToken.IdenTokenType || token?.typ == exports.EnumToken.LiteralTokenType) { if (token.typ == exports.EnumToken.IdenTokenType && @@ -25878,7 +26431,7 @@ function parseSelector(tokens, context, options, errors) { // } else { // Object.assign(token, { typ: EnumToken.NumberTokenType, val: b1 }); // } - // } else + // } else if (b1 === 0) { Object.assign(token, Math.abs(a1) === 1 ? { @@ -25949,7 +26502,7 @@ function parseSelector(tokens, context, options, errors) { // func.chi.splice(0, i); // } // break; - // } else + // } else if (num.val === 0) { func.chi.splice(index + 1, i - index); if (token.val < 0) { @@ -27604,7 +28157,8 @@ function parseAtRuleContainerQueryList(stream, context, options = {}) { success = false; errors.push({ action: "drop", - node: options.source.getSourceLocation(stream[i][LOC].sta), + node: stream[i], + location: options.source.getSourceLocation(stream[i][LOC].sta), message: ` is not allowed outside of parentheses`, }); break; @@ -28074,7 +28628,6 @@ const BadTokensTypes = [ exports.EnumToken.BadStringTokenType, ]; let keyNameCounter = 0; -const forbiddenStartCharacters = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"].map((c) => c.charCodeAt(0)); /** * Short-scoped name generator. * @@ -28085,12 +28638,15 @@ const forbiddenStartCharacters = ["0", "1", "2", "3", "4", "5", "6", "7", "8", " * * @returns string */ -const getShortNameGenerator = memoize((localName, filePath, pattern, hashLength = 5) => { +const getShortNameGenerator = memoize(() => { let value = keyNameCounter.toString(36); + let val = value.charAt(0).charCodeAt(0); keyNameCounter++; - while (forbiddenStartCharacters.includes(value.charCodeAt(0))) { + // starts with'0' - '9' + while (48 <= val && val <= 57) { value = keyNameCounter.toString(36); keyNameCounter++; + val = value.charAt(0).charCodeAt(0); } return value; }); @@ -28272,51 +28828,189 @@ const generateSyncScopedName = memoize((localName, filePath, pattern, hashLength // @ts-ignore [key, hashAlgo, length] = parts; } - if (length != null && !Number.isInteger(+length)) { - throw new Error(`Unsupported hash length: '${length}'. expecting format [hash:length] or [hash:hash-algo:length]`); + if (length != null && !Number.isInteger(+length)) { + throw new Error(`Unsupported hash length: '${length}'. expecting format [hash:length] or [hash:hash-algo:length]`); + } + } + const slice = length != null && length != fileBase.length; + switch (key) { + case "hash": + result += syncHash(hashString, length ?? hashLength, hashAlgo); + break; + case "name": + // @ts-expect-error + result += slice ? fileBase.slice(0, +length) : fileBase; + break; + case "local": + // @ts-expect-error + result += slice ? safeLocal.slice(0, +length) : localName; + break; + case "ext": + // @ts-expect-error + result += slice ? ext.slice(0, +length) : ext; + break; + case "path": + // @ts-expect-error + result += slice ? path.slice(0, +length) : path; + break; + case "folder": + // @ts-expect-error + result += slice ? folder.slice(0, +length) : folder; + break; + default: + throw new Error(`Unsupported key: '${key}'`); + } + key = ""; + continue; + } + if (inParens > 0) { + key += char; + } + else { + result += char; + } + } + // 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") { + if ("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 { + visitors.push(...Object.entries(value)); + } + } + 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") { + // visitors.push(...Object.entries(value)); + 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); } } - const slice = length != null && length != fileBase.length; - switch (key) { - case "hash": - result += syncHash(hashString, length ?? hashLength, hashAlgo); - break; - case "name": - // @ts-expect-error - result += slice ? fileBase.slice(0, +length) : fileBase; - break; - case "local": - // @ts-expect-error - result += slice ? safeLocal.slice(0, +length) : localName; - break; - case "ext": - // @ts-expect-error - result += slice ? ext.slice(0, +length) : ext; - break; - case "path": - // @ts-expect-error - result += slice ? path.slice(0, +length) : path; - break; - case "folder": - // @ts-expect-error - result += slice ? folder.slice(0, +length) : folder; - break; - default: - throw new Error(`Unsupported key: '${key}'`); + else { + errors.push({ action: "ignore", message: `doParse: visitor.${key} is not a valid key name` }); } - key = ""; - continue; - } - if (inParens > 0) { - key += char; } else { - result += char; + errors.push({ action: "ignore", message: `doParse: visitor.${key} is not a valid key name` }); } } - // if leading char is digit, prefix underscore (very rare) - return (/^[0-9]/.test(result) ? "_" : "") + result; -}); + 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 +29076,20 @@ 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, + }; + for (currentItemIndex = 0; currentItemIndex < iter.length; currentItemIndex++) { + item = iter[currentItemIndex]; stats.bytesIn = item.bytesIn; stats.tokensCount++; if (BadTokensTypes.includes(item.token.typ)) { @@ -28534,8 +29117,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 +29132,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 +29187,162 @@ 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; + } + } + // @ts-ignore + if (nodes[i].chi != null) { + // @ts-ignore + 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; + 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); } + // 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]); + // } + // } + // } 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()]; + // @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]) { + // @ts-ignore + replaceNodeOrValue(nodes[i][PARENT], nodes[i], node); + } } + nodes = null; } if (invalidNodes.length > 0) { let count = invalidNodes.length; @@ -28821,19 +29365,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 +29393,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(); @@ -28927,6 +29458,7 @@ function doParseSync(iter, options = {}) { throw new Error("css variable import not supported by parseSync() or transformSync(). use parse() or transform() instead.\nat " + options.source.getSourceLocation(node[LOC].sta).join(":")); } + // @ts-ignore if (node.typ == exports.EnumToken.CssVariableDeclarationMapTokenType) { const from = node.from.find((t) => t.typ == exports.EnumToken.IdenTokenType || isIdentColor(t)); if (!(from.val in cssVariablesMap)) { @@ -28968,10 +29500,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 +29544,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 +29717,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]; @@ -29203,6 +29733,7 @@ function doParseSync(iter, options = {}) { } else if ((value.typ == exports.EnumToken.IdenTokenType || isIdentColor(value)) && value.val in importedCssVariables) { + // @ts-ignore replaceNodeOrValue(parent, value, importedCssVariables[value.val].val); } } @@ -29262,10 +29793,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 @@ -29279,7 +29809,7 @@ function doParseSync(iter, options = {}) { } if (moduleSettings.scoped & exports.ModuleScopeEnumOptions.Pure) { if (!hasIdOrClass) { - throw new Error(`pure module: No id or class found in selector '${node.sel}' at '${node[LOC]?.src ?? ""}':${node[LOC]?.sta?.lin ?? ""}:${node[LOC]?.sta?.col ?? ""}`); + throw new Error(`pure module: No id or class found in selector '${node.sel}' at '${options.source.getOffsets(node[LOC]?.sta).join(":")}'`); } } node.sel = ""; @@ -29298,10 +29828,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 +29848,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; @@ -29405,22 +29935,10 @@ async function doParse(iter, options = {}) { const invalidNodes = []; let ast = { typ: exports.EnumToken.StyleSheetNodeType, - chi: [], - }; - let tokens = []; - let context = ast; - ast[ROOT] = ast; - ast[LOC] = { - sta: 0, - end: 0, - srcId: options.source.id, + chi: [], }; - let valuesHandlers; - let preValuesHandlers; - let postValuesHandlers; - let preVisitorsHandlersMap; - let visitorsHandlersMap; - let postVisitorsHandlersMap; + let tokens = []; + let context = ast; const imports = []; let item; let node; @@ -29428,107 +29946,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](); @@ -29565,8 +29988,6 @@ async function doParse(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 || @@ -29650,7 +30071,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 @@ -29659,11 +30080,10 @@ async function doParse(iter, options = {}) { options.sourcesMap.set(source.id, source); const parseInfo = { stream, - buffer: "", offset: 0, source, position: 0, - currentPosition: -1, + currentPosition: 0, }; const root = await doParse(stream instanceof ReadableStream ? tokenizeStream(stream, parseInfo) : tokenize(parseInfo), Object.assign({}, options, { minify: false, @@ -29674,6 +30094,7 @@ async function doParse(iter, options = {}) { stats.nodesCount += root.stats.nodesCount; stats.tokensCount += root.stats.tokensCount; stats.imports.push(root.stats); + // @ts-ignore node[PARENT].chi.splice(node[PARENT].chi.indexOf(node), 1, ...root.ast.chi); if (root.errors.length > 0) { errors.push(...root.errors); @@ -29690,210 +30111,165 @@ 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; + } + } + // @ts-ignore + if (nodes[i].chi != null) { + // @ts-ignore + 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 instanceof Promise) { - replacement = await replacement; - } - if (replacement == null || replacement == node) { - 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); } + // 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]); + // } + // } + // } 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]) { + // @ts-ignore + replaceNodeOrValue(nodes[i][PARENT], nodes[i], node); } } + nodes = null; } if (invalidNodes.length > 0) { let count = invalidNodes.length; @@ -29916,19 +30292,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; @@ -30034,7 +30397,7 @@ async function doParse(iter, options = {}) { time: 0, source, position: 0, - currentPosition: -1, + currentPosition: 0, }; const root = await doParse(stream instanceof ReadableStream ? tokenizeStream(stream, parseInfo) : tokenize(parseInfo), Object.assign({}, options, { source, @@ -30047,6 +30410,7 @@ async function doParse(iter, options = {}) { parent.chi.splice(parent.chi.indexOf(node), 1); continue; } + // @ts-ignore if (node.typ == exports.EnumToken.CssVariableDeclarationMapTokenType) { const from = node.from.find((t) => t.typ == exports.EnumToken.IdenTokenType || isIdentColor(t)); if (!(from.val in cssVariablesMap)) { @@ -30091,6 +30455,7 @@ async function doParse(iter, options = {}) { let result = moduleSettings.scoped & exports.ModuleScopeEnumOptions.Global ? node.nam : moduleSettings.generateScopedName(node.nam, moduleSettings.filePath, moduleSettings.pattern, moduleSettings.hashLength); + // @ts-ignore let value = result instanceof Promise ? await result : result; mapping[node.nam] = "--" + @@ -30124,6 +30489,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) { @@ -30175,26 +30541,26 @@ async function doParse(iter, options = {}) { : result; const root = await doParse(stream instanceof ReadableStream ? tokenizeStream(stream, { - buffer: "", offset: 0, source: new SourceFile("", [], src.relative), position: 0, - currentPosition: -1, + currentPosition: 0, }) : tokenize({ stream, - buffer: "", offset: 0, position: 0, source: new SourceFile(stream, [], src.relative), - currentPosition: -1, + currentPosition: 0, }), Object.assign({}, options, { minify: false, 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] = {}; } @@ -30395,30 +30761,13 @@ async function doParse(iter, options = {}) { } for (const { value, parent } of walkValues(node.val, node)) { if (value.typ == exports.EnumToken.DashedIdenTokenType) { - // if (!((value as DashedIdentToken).val in mapping)) { - // const result = - // moduleSettings.scoped! & ModuleScopeEnumOptions.Global - // ? (value as DashedIdentToken).val - // : moduleSettings.generateScopedName!( - // (value as DashedIdentToken).val, - // moduleSettings.filePath as string, - // moduleSettings.pattern as string, - // moduleSettings.hashLength, - // ); - // let val: string = result instanceof Promise ? await result : result; - // mapping[(value as DashedIdentToken).val] = - // "--" + - // (moduleSettings.naming! & ModuleCaseTransformEnum.DashCaseOnly || - // moduleSettings.naming! & ModuleCaseTransformEnum.CamelCaseOnly - // ? getKeyName(val, moduleSettings.naming as ModuleCaseTransformEnum) - // : val); - // revMapping[mapping[(value as DashedIdentToken).val]] = (value as DashedIdentToken).val; - // } value.val = mapping[value.val]; } else if ((value.typ == exports.EnumToken.IdenTokenType || isIdentColor(value)) && value.val in importedCssVariables) { - replaceNodeOrValue(parent, value, importedCssVariables[value.val].val); + replaceNodeOrValue( + // @ts-ignore + parent, value, importedCssVariables[value.val].val); } } } @@ -30446,26 +30795,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 +30808,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; } } })) { @@ -30522,7 +30845,7 @@ async function doParse(iter, options = {}) { } if (moduleSettings.scoped & exports.ModuleScopeEnumOptions.Pure) { if (!hasIdOrClass) { - throw new Error(`pure module: No id or class found in selector '${node.sel}' at '${node[LOC]?.src ?? ""}':${node[LOC]?.sta?.lin ?? ""}:${node[LOC]?.sta?.col ?? ""}`); + throw new Error(`pure module: No id or class found in selector '${node.sel}' at '${(options.source?.getOffsets?.(node[LOC]?.sta) ?? []).join(":")}'`); } } node.sel = ""; @@ -30770,6 +31093,8 @@ function parseNode(tokens, context, options, errors, stats, invalidNodes) { return null; } /** + * @param stream + * @param context * @param options * @param errors * @param parseAsBlock @@ -30846,7 +31171,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, @@ -31141,10 +31465,13 @@ function parseAtRule(stream, context, options, errors, parseAsBlock = null) { let definedAfterLastElse = false; if (sibling == null || sibling.typ !== exports.EnumToken.AtRuleNodeType) { missingWhen = true; + // @ts-expect-error } else if (sibling.nam !== "when") { + // @ts-expect-error if (sibling.nam !== "else") { missingWhen = true; + // @ts-expect-error } else if (sibling.val === "") { definedAfterLastElse = true; @@ -31334,7 +31661,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 +31800,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 = []; @@ -31530,11 +31854,10 @@ async function parseDeclarations(declaration) { const stream = `.x{${declaration}}`; return doParse(tokenize({ stream, - buffer: "", offset: 0, position: 0, source: new SourceFile(stream, [], ""), - currentPosition: -1, + currentPosition: 0, }), { setParent: false, minify: false, validation: false }).then((result) => { return result.ast.chi[0].chi.filter((t) => t.typ == exports.EnumToken.DeclarationNodeType || t.typ == exports.EnumToken.CommentNodeType); }); @@ -31564,19 +31887,20 @@ async function parseDeclarations(declaration) { function parseString(src, options = { parseColor: true }, errors) { const parseInfo = { stream: src, - buffer: "", offset: 0, time: 0, source: new SourceFile(src, [], ""), position: 0, - currentPosition: -1, + currentPosition: 0, }; - 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 +31991,6 @@ function parseTokens(tokens, options, errors) { node, location: options.source.getSourceLocation(node[LOC].sta), }); - // return []; continue; } index = tokens.indexOf(stack.at(-1)); @@ -31814,6 +32137,102 @@ 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=")) { + options.source.setInputSourceMap(token.val.slice(21, -2).trim()); + } + } + } + if (options.module) { + const { revMapping, ...res } = result; + return res; + } + return result; +} +/** + * + * @param options + * @param prefix + * @private + */ +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 + "."); + } + } +} + +/** + * + * @param node + * @param key + * @returns + */ +function getNodeProperty(node, key) { + switch (key) { + case "parent": + return node[PARENT]; + case "location": + return node[LOC]; + case "state": + return node[STATE]; + case "errors": + return node[ERRORS]; + case "tokens": + return node[TOKENS]; + } + return undefined; +} +/** + * + * @param node + * @param key + * @param value + */ +function setNodeProperty(node, key, value) { + switch (key) { + case "parent": + node[PARENT] = value; + break; + case "location": + node[LOC] = value; + break; + case "state": + node[STATE] = value; + break; + case "errors": + node[ERRORS] = value; + break; + case "tokens": + node[TOKENS] = value; + break; + } +} + /** * Load file or url * @param url @@ -31897,7 +32316,7 @@ function render(data, options = {}, mapping) { return doRender(data, Object.assign(options, { resolve, dirname, cwd: options.cwd ?? node_path.resolve() }), mapping); } /** - * Parse css file + * Parse CSS file * @param file url or path * @param options * @param asStream load file as stream @@ -31923,17 +32342,17 @@ function render(data, options = {}, mapping) { */ const parseFile = node_util.deprecate(async (file, options = {}, asStream = false) => parse({ file, asStream, ...options }), "parseFile is deprecated, use parse instead as parse({file, asStream, ...options})"); /** - * Parse css + * Parse CSS * @param args * * 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 +32369,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, @@ -31972,26 +32394,24 @@ function parseSync(...args) { time: 0, source: options.source, position: 0, - currentPosition: -1, + currentPosition: 0, }; 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'; * * // css string - * const result = await transform(css); + * const result = transformSync(css); * console.log(result.code); * ``` * + * @param args */ function transformSync(...args) { let options; @@ -32007,7 +32427,15 @@ function transformSync(...args) { stream = input; } options ??= {}; - options = { minify: true, removeEmpty: true, removeCharset: true, ...options }; + if (options.minify == null) { + options.minify = true; + } + if (options.removeEmpty == null) { + options.removeEmpty = true; + } + if (options.removeCharset == null) { + options.removeCharset = true; + } const startTime = performance.now(); const parseResult = parseSync(stream, options); let mapping = null; @@ -32040,7 +32468,7 @@ function transformSync(...args) { }; } /** - * Parse css + * Parse CSS * @param args * * @throws Error file not found @@ -32103,7 +32531,7 @@ async function parse(...args) { } options ??= {}; options.src ??= ""; - options.sourcesMap ??= new Map; + options.sourcesMap ??= new Map(); Object.assign(options, { load, resolve, @@ -32124,15 +32552,12 @@ async function parse(...args) { time: 0, source: options.source, position: 0, - currentPosition: -1, + currentPosition: 0, }; - 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 @@ -32161,9 +32586,7 @@ const transformFile = node_util.deprecate(async (file, options = {}, asStream = ...options, }), "transformFile is deprecated, use transform instead as transform({file, asStream, ...options})"); /** - * Transform css - * @param css - * @param options + * Transform CSS * * Parsing a string * @@ -32202,6 +32625,7 @@ const transformFile = node_util.deprecate(async (file, options = {}, asStream = * * console.log(result.code); * ``` + * @param args */ async function transform(...args) { let options; @@ -32222,7 +32646,15 @@ async function transform(...args) { } } options ??= {}; - options = { minify: true, removeEmpty: true, removeCharset: true, ...options }; + if (options.minify == null) { + options.minify = true; + } + if (options.removeEmpty == null) { + options.removeEmpty = true; + } + if (options.removeCharset == null) { + options.removeCharset = true; + } const startTime = performance.now(); return parse(stream, options).then((parseResult) => { let mapping = null; @@ -32265,6 +32697,7 @@ exports.find = find; exports.findAll = findAll; exports.findByValue = findByValue; exports.findLast = findLast; +exports.getNodeProperty = getNodeProperty; exports.isOkLabClose = isOkLabClose; exports.load = load; exports.minify = minify; @@ -32278,6 +32711,7 @@ exports.render = render; exports.renderToken = renderValue; exports.replaceNodeOrValue = replaceNodeOrValue; exports.resolve = resolve; +exports.setNodeProperty = setNodeProperty; exports.transform = transform; exports.transformFile = transformFile; exports.transformSync = transformSync; diff --git a/dist/index.d.ts b/dist/index.d.ts index 1eae9a4f..e918a136 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; /** @@ -1197,10 +1197,12 @@ export declare interface FunctionToken extends BaseToken { | EnumToken.ImageFunctionTokenType | EnumToken.TimelineFunctionTokenType | EnumToken.TimingFunctionTokenType - | EnumToken.ColorFunctionTokenType + | EnumToken.ColorTokenType | EnumToken.MathFunctionTokenType - | EnumToken.PseudoClassFunctionTokenType - | EnumToken.TransformFunctionTokenType; + | EnumToken.PseudoClassFuncTokenType + | EnumToken.TransformFunctionTokenType + | EnumToken.GeneralEnclosedFunctionTokenType + | EnumToken.WildCardFunctionTokenType; /** * function name */ @@ -1216,7 +1218,7 @@ export declare interface FunctionToken extends BaseToken { */ export declare interface GridTemplateFuncToken extends BaseToken { /** - * function type + * @inheritdoc */ typ: EnumToken.GridTemplateFuncTokenType; /** @@ -1234,7 +1236,7 @@ export declare interface GridTemplateFuncToken extends BaseToken { */ export declare interface FunctionURLToken extends BaseToken { /** - * function type + * @inheritdoc */ typ: EnumToken.UrlFunctionTokenType; /** @@ -1252,7 +1254,7 @@ export declare interface FunctionURLToken extends BaseToken { */ export declare interface FunctionImageToken extends BaseToken { /** - * function type + * @inheritdoc */ typ: EnumToken.ImageFunctionTokenType; /** @@ -1279,7 +1281,7 @@ export declare interface FunctionImageToken extends BaseToken { */ export declare interface TimingFunctionToken extends BaseToken { /** - * timing function type + * @inheritdoc */ typ: EnumToken.TimingFunctionTokenType; /** @@ -1297,7 +1299,7 @@ export declare interface TimingFunctionToken extends BaseToken { */ export declare interface TimelineFunctionToken extends BaseToken { /** - * timeline function type + * @inheritdoc */ typ: EnumToken.TimelineFunctionTokenType; /** @@ -1315,7 +1317,7 @@ export declare interface TimelineFunctionToken extends BaseToken { */ export declare interface StringToken extends BaseToken { /** - * string type + * @inheritdoc */ typ: EnumToken.StringTokenType; /** @@ -1329,7 +1331,7 @@ export declare interface StringToken extends BaseToken { */ export declare interface BadStringToken extends BaseToken { /** - * bad string type + * @inheritdoc */ typ: EnumToken.BadStringTokenType; /** @@ -1343,7 +1345,7 @@ export declare interface BadStringToken extends BaseToken { */ export declare interface UnclosedStringToken extends BaseToken { /** - * unclosed string type + * @inheritdoc */ typ: EnumToken.UnclosedStringTokenType; /** @@ -1357,7 +1359,7 @@ export declare interface UnclosedStringToken extends BaseToken { */ export declare interface DimensionToken extends BaseToken { /** - * dimension type + * @inheritdoc */ typ: EnumToken.DimensionTokenType; /** @@ -1375,7 +1377,7 @@ export declare interface DimensionToken extends BaseToken { */ export declare interface LengthToken extends BaseToken { /** - * length type + * @inheritdoc */ typ: EnumToken.LengthTokenType; /** @@ -1393,7 +1395,7 @@ export declare interface LengthToken extends BaseToken { */ export declare interface AngleToken extends BaseToken { /** - * angle type + * @inheritdoc */ typ: EnumToken.AngleTokenType; /** @@ -1411,7 +1413,7 @@ export declare interface AngleToken extends BaseToken { */ export declare interface TimeToken extends BaseToken { /** - * time type + * @inheritdoc */ typ: EnumToken.TimeTokenType; /** @@ -1419,7 +1421,7 @@ export declare interface TimeToken extends BaseToken { */ val: number | FractionToken; /** - * time unit + * time unit */ unit: "ms" | "s"; } @@ -1429,7 +1431,7 @@ export declare interface TimeToken extends BaseToken { */ export declare interface FrequencyToken extends BaseToken { /** - * frequency type + * @inheritdoc */ typ: EnumToken.FrequencyTokenType; /** @@ -1447,7 +1449,7 @@ export declare interface FrequencyToken extends BaseToken { */ export declare interface ResolutionToken extends BaseToken { /** - * resolution type + * @inheritdoc */ typ: EnumToken.ResolutionTokenType; /** @@ -1465,7 +1467,7 @@ export declare interface ResolutionToken extends BaseToken { */ export declare interface HashToken extends BaseToken { /** - * hash type + * @inheritdoc */ typ: EnumToken.HashTokenType; /** @@ -1479,7 +1481,7 @@ export declare interface HashToken extends BaseToken { */ export declare interface BlockStartToken extends BaseToken { /** - * block start type + * @inheritdoc */ typ: EnumToken.BlockStartTokenType; } @@ -1489,7 +1491,7 @@ export declare interface BlockStartToken extends BaseToken { */ export declare interface BlockEndToken extends BaseToken { /** - * block end type + * @inheritdoc */ typ: EnumToken.BlockEndTokenType; } @@ -1499,7 +1501,7 @@ export declare interface BlockEndToken extends BaseToken { */ export declare interface AttrStartToken extends BaseToken { /** - * attribute start type + * @inheritdoc */ typ: EnumToken.AttrStartTokenType; /** @@ -1513,7 +1515,7 @@ export declare interface AttrStartToken extends BaseToken { */ export declare interface AttrEndToken extends BaseToken { /** - * attribute end type + * @inheritdoc */ typ: EnumToken.AttrEndTokenType; } @@ -1523,7 +1525,7 @@ export declare interface AttrEndToken extends BaseToken { */ export declare interface ParensStartToken extends BaseToken { /** - * parenthesis start type + * @inheritdoc */ typ: EnumToken.StartParensTokenType; } @@ -1533,7 +1535,7 @@ export declare interface ParensStartToken extends BaseToken { */ export declare interface ParensEndToken extends BaseToken { /** - * parenthesis end type + * @inheritdoc */ typ: EnumToken.EndParensTokenType; } @@ -1543,7 +1545,7 @@ export declare interface ParensEndToken extends BaseToken { */ export declare interface ParensToken extends BaseToken { /** - * parenthesis type + * @inheritdoc */ typ: EnumToken.ParensTokenType; /** @@ -1557,7 +1559,7 @@ export declare interface ParensToken extends BaseToken { */ export declare interface WhitespaceToken extends BaseToken { /** - * whitespace type + * @inheritdoc */ typ: EnumToken.WhitespaceTokenType; /** @@ -1571,7 +1573,7 @@ export declare interface WhitespaceToken extends BaseToken { */ export declare interface CommentToken extends BaseToken { /** - * comment type + * @inheritdoc */ typ: EnumToken.CommentTokenType; /** @@ -1585,7 +1587,7 @@ export declare interface CommentToken extends BaseToken { */ export declare interface BadCommentToken extends BaseToken { /** - * bad comment type + * @inheritdoc */ typ: EnumToken.BadCommentTokenType; /** @@ -1598,7 +1600,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 +1615,7 @@ export declare interface CDOCommentToken extends BaseToken { */ export declare interface BadCDOCommentToken extends BaseToken { /** - * bad CDO comment type + * @inheritdoc */ typ: EnumToken.BadCdoTokenType; /** @@ -1621,7 +1629,7 @@ export declare interface BadCDOCommentToken extends BaseToken { */ export declare interface IncludeMatchToken extends BaseToken { /** - * include match type + * @inheritdoc */ typ: EnumToken.IncludeMatchTokenType; // val: '~='; @@ -1632,7 +1640,7 @@ export declare interface IncludeMatchToken extends BaseToken { */ export declare interface DashMatchToken extends BaseToken { /** - * dash match type + * @inheritdoc */ typ: EnumToken.DashMatchTokenType; // val: '|='; @@ -1643,7 +1651,7 @@ export declare interface DashMatchToken extends BaseToken { */ export declare interface EqualMatchToken extends BaseToken { /** - * equal match type + * @inheritdoc */ typ: EnumToken.EqualMatchTokenType; // val: '|='; @@ -1654,7 +1662,7 @@ export declare interface EqualMatchToken extends BaseToken { */ export declare interface StartMatchToken extends BaseToken { /** - * start match type + * @inheritdoc */ typ: EnumToken.StartMatchTokenType; // val: '^='; @@ -1665,7 +1673,7 @@ export declare interface StartMatchToken extends BaseToken { */ export declare interface EndMatchToken extends BaseToken { /** - * end match type + * @inheritdoc */ typ: EnumToken.EndMatchTokenType; // val: '|='; @@ -1676,7 +1684,7 @@ export declare interface EndMatchToken extends BaseToken { */ export declare interface ContainMatchToken extends BaseToken { /** - * contain match type + * @inheritdoc */ typ: EnumToken.ContainMatchTokenType; // val: '|='; @@ -1687,7 +1695,7 @@ export declare interface ContainMatchToken extends BaseToken { */ export declare interface LessThanToken extends BaseToken { /** - * less than type + * @inheritdoc */ typ: EnumToken.LtTokenType; } @@ -1697,7 +1705,7 @@ export declare interface LessThanToken extends BaseToken { */ export declare interface LessThanOrEqualToken extends BaseToken { /** - * less than or equal type + * @inheritdoc */ typ: EnumToken.LteTokenType; } @@ -1707,7 +1715,7 @@ export declare interface LessThanOrEqualToken extends BaseToken { */ export declare interface GreaterThanToken extends BaseToken { /** - * greater than type + * @inheritdoc */ typ: EnumToken.GtTokenType; } @@ -1717,7 +1725,7 @@ export declare interface GreaterThanToken extends BaseToken { */ export declare interface GreaterThanOrEqualToken extends BaseToken { /** - * greater than or equal type + * @inheritdoc */ typ: EnumToken.GteTokenType; } @@ -1727,7 +1735,7 @@ export declare interface GreaterThanOrEqualToken extends BaseToken { */ export declare interface ColumnCombinatorToken extends BaseToken { /** - * column combinator type + * @inheritdoc */ typ: EnumToken.ColumnCombinatorTokenType; } @@ -1737,7 +1745,7 @@ export declare interface ColumnCombinatorToken extends BaseToken { */ export declare interface PseudoClassToken extends BaseToken { /** - * Pseudo class + * @inheritdoc */ typ: EnumToken.PseudoClassTokenType; /** @@ -1751,7 +1759,7 @@ export declare interface PseudoClassToken extends BaseToken { */ export declare interface PseudoElementToken extends BaseToken { /** - * Pseudo element + * @inheritdoc */ typ: EnumToken.PseudoElementTokenType; /** @@ -1765,7 +1773,7 @@ export declare interface PseudoElementToken extends BaseToken { */ export declare interface PseudoPageToken extends BaseToken { /** - * Pseudo page + * @inheritdoc */ typ: EnumToken.PseudoPageTokenType; /** @@ -1779,7 +1787,7 @@ export declare interface PseudoPageToken extends BaseToken { */ export declare interface PseudoClassFunctionToken extends BaseToken { /** - * Pseudo class function + * @inheritdoc */ typ: EnumToken.PseudoClassFuncTokenType; /** @@ -1797,7 +1805,7 @@ export declare interface PseudoClassFunctionToken extends BaseToken { */ export declare interface DelimToken extends BaseToken { /** - * Delimiter token type + * @inheritdoc */ typ: EnumToken.DelimTokenType; } @@ -1807,7 +1815,7 @@ export declare interface DelimToken extends BaseToken { */ export declare interface BadUrlToken extends BaseToken { /** - * Bad URL + * @inheritdoc */ typ: EnumToken.BadUrlTokenType; /** @@ -1821,7 +1829,7 @@ export declare interface BadUrlToken extends BaseToken { */ export declare interface UrlToken extends BaseToken { /** - * URL + * @inheritdoc */ typ: EnumToken.UrlTokenTokenType; /** @@ -1835,7 +1843,7 @@ export declare interface UrlToken extends BaseToken { */ export declare interface EOFToken extends BaseToken { /** - * End of file + * @inheritdoc */ typ: EnumToken.EOFTokenType; } @@ -1845,7 +1853,7 @@ export declare interface EOFToken extends BaseToken { */ export declare interface ImportantToken extends BaseToken { /** - * Important + * @inheritdoc */ typ: EnumToken.ImportantTokenType; } @@ -1855,7 +1863,7 @@ export declare interface ImportantToken extends BaseToken { */ export declare interface ColorToken extends BaseToken { /** - * Color type + * @inheritdoc */ typ: EnumToken.ColorTokenType; /** @@ -1881,7 +1889,7 @@ export declare interface ColorToken extends BaseToken { */ export declare interface AttrToken extends BaseToken { /** - * Attribute type + * @inheritdoc */ typ: EnumToken.AttrTokenType; /** @@ -1895,7 +1903,7 @@ export declare interface AttrToken extends BaseToken { */ export declare interface InvalidAttrToken extends BaseToken { /** - * Attribute type + * @inheritdoc */ typ: EnumToken.InvalidAttrTokenType; /** @@ -1908,6 +1916,9 @@ export declare interface InvalidAttrToken extends BaseToken { * Child combinator token */ export declare interface ChildCombinatorToken extends BaseToken { + /** + * @inheritdoc + */ typ: EnumToken.ChildCombinatorTokenType; } @@ -1916,7 +1927,7 @@ export declare interface ChildCombinatorToken extends BaseToken { */ export declare interface MediaFeatureToken extends BaseToken { /** - * Media feature type + * @inheritdoc */ typ: EnumToken.MediaFeatureTokenType; /** @@ -1930,7 +1941,7 @@ export declare interface MediaFeatureToken extends BaseToken { */ export declare interface NotToken extends BaseToken { /** - * Media feature not type + * @inheritdoc */ typ: EnumToken.NotTokenType; /** @@ -1944,7 +1955,7 @@ export declare interface NotToken extends BaseToken { */ export declare interface MediaFeatureOnlyToken extends BaseToken { /** - * Media feature only type + * @inheritdoc */ typ: EnumToken.OnlyTokenType; /** @@ -1958,7 +1969,7 @@ export declare interface MediaFeatureOnlyToken extends BaseToken { */ export declare interface AndToken extends BaseToken { /** - * Media feature and type + * @inheritdoc */ typ: EnumToken.AndTokenType; } @@ -1968,7 +1979,7 @@ export declare interface AndToken extends BaseToken { */ export declare interface OrToken extends BaseToken { /** - * Media feature or type + * @inheritdoc */ typ: EnumToken.OrTokenType; } @@ -1978,7 +1989,7 @@ export declare interface OrToken extends BaseToken { */ export declare interface MediaQueryUnaryFeatureToken extends BaseToken { /** - * Media query condition type + * @inheritdoc */ typ: EnumToken.MediaQueryUnaryFeatureTokenType; /** @@ -1993,7 +2004,7 @@ export declare interface MediaQueryUnaryFeatureToken extends BaseToken { export declare interface SupportsQueryUnaryConditionToken extends BaseToken { /** - * Supports query condition type + * @inheritdoc */ typ: EnumToken.SupportsQueryUnaryConditionTokenType; /** @@ -2008,7 +2019,7 @@ export declare interface SupportsQueryUnaryConditionToken extends BaseToken { export declare interface SupportsQueryConditionToken extends BaseToken { /** - * Supports query condition type + * @inheritdoc */ typ: EnumToken.SupportsQueryConditionTokenType; /** @@ -2027,7 +2038,7 @@ export declare interface SupportsQueryConditionToken extends BaseToken { export declare interface WhenElseQueryConditionToken extends BaseToken { /** - * When else query condition type + * @inheritdoc */ typ: EnumToken.WhenElseQueryConditionTokenType; /** @@ -2046,7 +2057,7 @@ export declare interface WhenElseQueryConditionToken extends BaseToken { export declare interface WhenElseUnaryConditionToken extends BaseToken { /** - * When else query condition type + * @inheritdoc */ typ: EnumToken.WhenElseUnaryConditionTokenType; /** @@ -2061,7 +2072,7 @@ export declare interface WhenElseUnaryConditionToken extends BaseToken { export declare interface MediaQueryConditionToken extends BaseToken { /** - * Media query condition type + * @inheritdoc */ typ: EnumToken.MediaQueryConditionTokenType; /** @@ -2088,7 +2099,7 @@ export declare interface MediaQueryConditionToken extends BaseToken { export declare interface IfConditionToken extends BaseToken { /** - * If condition type + * @inheritdoc */ typ: EnumToken.IfConditionTokenType; /** @@ -2103,7 +2114,7 @@ export declare interface IfConditionToken extends BaseToken { export declare interface IfElseConditionToken extends BaseToken { /** - * If else condition type + * @inheritdoc */ typ: EnumToken.IfElseConditionTokenType; /** @@ -2117,23 +2128,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 +2196,9 @@ export declare interface InvalidMediaQueryToken extends BaseToken { * Descendant combinator token */ export declare interface DescendantCombinatorToken extends BaseToken { + /** + * @inheritdoc + */ typ: EnumToken.DescendantCombinatorTokenType; } @@ -2148,6 +2206,9 @@ export declare interface DescendantCombinatorToken extends BaseToken { * Next sibling combinator token */ export declare interface NextSiblingCombinatorToken extends BaseToken { + /** + * @inheritdoc + */ typ: EnumToken.NextSiblingCombinatorTokenType; } @@ -2155,6 +2216,9 @@ export declare interface NextSiblingCombinatorToken extends BaseToken { * Subsequent sibling combinator token */ export declare interface SubsequentCombinatorToken extends BaseToken { + /** + * @inheritdoc + */ typ: EnumToken.SubsequentSiblingCombinatorTokenType; } @@ -2162,6 +2226,9 @@ export declare interface SubsequentCombinatorToken extends BaseToken { * Add token */ export declare interface AddToken extends BaseToken { + /** + * @inheritdoc + */ typ: EnumToken.Add; } @@ -2169,6 +2236,9 @@ export declare interface AddToken extends BaseToken { * Sub token */ export declare interface SubToken extends BaseToken { + /** + * @inheritdoc + */ typ: EnumToken.Sub; } @@ -2176,6 +2246,9 @@ export declare interface SubToken extends BaseToken { * Div token */ export declare interface DivToken extends BaseToken { + /** + * @inheritdoc + */ typ: EnumToken.Div; } @@ -2184,7 +2257,7 @@ export declare interface DivToken extends BaseToken { */ export declare interface MulToken extends BaseToken { /** - * Type + * @inheritdoc */ typ: EnumToken.Mul; } @@ -2194,7 +2267,7 @@ export declare interface MulToken extends BaseToken { */ export declare interface WrappedValuesToken extends BaseToken { /** - * Type + * @inheritdoc */ typ: EnumToken.WrappedValuesTokenType; /** @@ -2208,7 +2281,7 @@ export declare interface WrappedValuesToken extends BaseToken { */ export declare interface UnaryExpression extends BaseToken { /** - * Type + * @inheritdoc */ typ: EnumToken.UnaryExpressionTokenType; /** @@ -2225,8 +2298,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 +2316,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 +2338,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 +2364,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 +2382,13 @@ export declare interface NameSpaceAttributeToken extends BaseToken { * List token */ export declare interface ListToken extends BaseToken { + /** + * @inheritdoc + */ typ: EnumToken.ListToken; + /** + * Children + */ chi: Token$1[]; } @@ -2272,29 +2396,71 @@ 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; } /** * Css variable token */ -export declare interface CssVariableToken$1 extends BaseToken { +export declare interface CssVariableToken extends BaseToken { + /** + * @inheritdoc + */ typ: EnumToken.CssVariableTokenType; + /** + * Name + */ nam: string; + /** + * Value + */ val: Token$1[]; } -export declare interface CssVariableImportTokenType$1 extends BaseToken { +/** + * Css variable import token + */ +export declare interface CssVariableImportTokenType 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 +2468,9 @@ export declare interface CssVariableMapTokenType extends BaseToken { * Function definition token */ export declare interface FunctionDefToken extends BaseToken { + /** + * @inheritdoc + */ typ: | EnumToken.FunctionDefTokenType | EnumToken.UrlFunctionTokenDefType @@ -2313,7 +2482,13 @@ export declare interface FunctionDefToken extends BaseToken { | EnumToken.MathFunctionTokenDefType | EnumToken.PseudoClassFunctionTokenDefType | EnumToken.TransformFunctionTokenDefType; + /** + * Name + */ nam: string; + /** + * Value + */ val: string; } @@ -2321,7 +2496,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[]; } @@ -2431,7 +2612,7 @@ export declare type Token$1 = | MatchExpressionToken | NameSpaceAttributeToken | ComposesSelectorToken - | CssVariableToken$1 + | CssVariableToken | DashMatchToken | EqualMatchToken | LessThanToken @@ -2491,16 +2672,16 @@ export declare interface BaseToken { * location info * @private */ - [LOC]?: SourceLocation; + [LOC]?: SourceLocation | null; /** * parent node * @private */ - [PARENT]?: AstNode$1; + [PARENT]?: AstNode$1 | Token$1 | null; /** * root node */ - [ROOT]?: AstStyleSheet; + [ROOT]?: AstStyleSheet | null; /** * prelude or selector tokens * @private @@ -2510,12 +2691,12 @@ export declare interface BaseToken { * node state * @private */ - [STATE]?: EnumAstNodeStatus; + [STATE]?: EnumAstNodeStatus | null; /** * node syntax errors * @private */ - [ERRORS]?: ErrorDescription[]; + [ERRORS]?: ErrorDescription[] | null; /** * property name * @private @@ -2534,7 +2715,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 +2857,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 +2939,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 +3018,7 @@ export declare type AstRuleList = | AstAtRule | AstRule | AstKeyframesAtRule - | AstKeyFrameRule + | AstKeyframesRule | AstInvalidRule; /** @@ -2865,12 +3046,13 @@ export declare type AstNode$1 = | AstRule | AstDeclaration | AstKeyframesAtRule - | AstKeyFrameRule + | AstKeyframesRule | AstInvalidRule | AstInvalidAtRule | AstInvalidDeclaration | CssVariableToken - | CssVariableImportTokenType; + | CssVariableImportTokenType + | WhitespaceToken; /** * token search result @@ -2992,7 +3174,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 +3189,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 +3315,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 +3544,14 @@ export declare interface VisitorSyncNodeMap { */ Rule?: GenericVisitorAstNodeSyncHandlerMap; + /** + * keyframes rule visitor + */ KeyframesRule?: GenericVisitorAstNodeSyncHandlerMap; + /** + * keyframes at-rule visitor + */ KeyframesAtRule?: GenericVisitorAstNodeSyncHandlerMap; /** @@ -3329,22 +3606,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 +3915,14 @@ export declare interface VisitorNodeMap { } /** - * Source map class - * @internal + * Generate and parse source map */ declare class SourceMap { + /** + * + * @private + */ + private keys; /** * Last location */ @@ -3645,28 +3936,76 @@ declare class SourceMap { * Sources map * @private */ - private sourcesMap; + private sourcesMap; + /** + * Sources content + * @private + */ + private readonly sourcesContent; + /** + * Sources + * @private + */ + private readonly sources; + /** + * Map + * @private + * + */ + private map; + /** + * Map + * @private + * + */ + private reverseMap; + /** + * Line + * @private + */ + private line; + /** + * Constructor + */ + constructor(); + /** + * Constructor + * @param sourcemaps + */ + constructor(sourcemaps: string | SourceMapObject); + /** + * add source + * @param id + * @param fileName + * @param content + * @returns + */ + addSourceContent(id: number, fileName: string | null, content: string | null): void; /** - * Sources - * @private + * Add sourcemap + * @param newLine + * @param newColumn + * @param srcId + * @param ln + * @param col */ - private sources; + add(newLine: number, newColumn: number, srcId: number, ln: number, col: number): void; /** - * Map - * @private + * Add multiple sourcemaps + * @param maps + * @throws */ - private map; + add(...maps: Array<[newLine: number, newColumn: number, srcId: number, ln: number, col: number]>): void; /** - * Line - * @private + * compute original positions */ - private line; + computePositions(): void; /** - * Add a location - * @param source - * @param original + * 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 */ @@ -3689,7 +4028,7 @@ declare class LineMap { * Constructor * @param lines */ - constructor(lines: number[]); + constructor(lines?: number[]); /** * Compute line and column of the offset * @param offset @@ -3711,17 +4050,13 @@ declare class LineMap { * add line start */ addLineStart(lineStart: number): void; - /** - * clone the linemap - * @returns - */ - clone(): LineMap; } /** * Source file helper class */ declare class SourceFile { + private inputSourceMap; /** * Source file ID */ @@ -3740,7 +4075,6 @@ declare class SourceFile { private content; /** * Constructor - * @param id * @param content * @param lines * @param file @@ -3749,7 +4083,6 @@ declare class SourceFile { /** * Update source content * @param content - * @param lines */ append(content: string): void; /** @@ -3791,10 +4124,19 @@ 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 { - removeDuplicateDeclarations?: boolean | string | string[]; computeShorthand?: boolean; } @@ -3803,11 +4145,6 @@ export declare interface PropertyListOptions { * parse info */ export declare interface ParseInfo$1 { - - /** - * read buffer - */ - buffer: string; /** * stream */ @@ -3817,7 +4154,7 @@ export declare interface ParseInfo$1 { * Source file */ source: SourceFile; - + /** * last token position */ @@ -4567,6 +4904,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 +4955,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 +5433,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 +5536,7 @@ export declare interface ParserSyncOptions * Node visitor * {@link VisitorSyncNodeMap | VisitorSyncNodeMap[]} */ - visitor?: VisitorSyncNodeMap | VisitorSyncNodeMap[]; + visitor?: GenericVisitorAstNodeSyncHandlerMap | VisitorSyncNodeMap | VisitorSyncNodeMap[]; /** * Abort signal * @@ -5177,7 +5601,11 @@ export declare interface ParserOptions extends ParserSyncOptions, ModuleAsyncOpt * Node visitor * {@link VisitorNodeMap | VisitorNodeMap[]} */ - visitor?: VisitorNodeMap | VisitorNodeMap[]; + visitor?: + | GenericVisitorAstNodeSyncHandlerMap + | GenericVisitorAstNodeHandlerMap + | VisitorNodeMap + | VisitorNodeMap[]; } /** @@ -5256,6 +5684,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. */ @@ -5429,7 +5862,7 @@ export declare interface ParseResult { * CSS module variables * @private */ - cssModuleVariables?: Record; + cssModuleVariables?: Record; /** * css module import mapping @@ -5665,69 +6098,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 +6265,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 +6308,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 +6340,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 +6370,6 @@ button { console.log({node, value}); ``` - * - * @param ast - * @param matcher - * @returns */ declare function findByValue(ast: AstNode$1, matcher: AstValueMatcher): { node: AstNode$1; @@ -5988,8 +6464,78 @@ declare function replaceNodeOrValue(parent: BinaryExpressionToken | (AstNode$1 & chi: Token$1[]; } | { val: Token$1[]; +})) | (Token$1 & ({ + chi: Token$1[]; +} | { + val: Token$1[]; })), node: Token$1, replacement: Token$1 | Token$1[]): boolean; +/** + * + * @param node + * @param key + */ +declare function getNodeProperty(node: AstNode$1, key: 'parent'): AstNode$1 | Token$1 | null; +/** + * + * @param node + * @param key + */ +declare function getNodeProperty(node: AstNode$1, key: 'location'): SourceLocation | null; +/** + * + * @param node + * @param key + */ +declare function getNodeProperty(node: AstNode$1, key: 'state'): EnumAstNodeStatus$1 | null; +/** + * + * @param node + * @param key + */ +declare function getNodeProperty(node: AstNode$1, key: 'errors'): ErrorDescription$1[] | null; +/** + * + * @param node + * @param key + */ +declare function getNodeProperty(node: AstNode$1, key: 'tokens'): Token$1[] | null; +/** + * + * @param node + * @param key + * @param value + */ +declare function setNodeProperty(node: AstNode$1, key: 'parent', value: AstNode$1 | Token$1 | null): void; +/** + * + * @param node + * @param key + * @param value + */ +declare function setNodeProperty(node: AstNode$1, key: 'location', value: SourceLocation | null): void; +/** + * + * @param node + * @param key + * @param value + */ +declare function setNodeProperty(node: AstNode$1, key: 'state', value: EnumAstNodeStatus$1 | null): void; +/** + * + * @param node + * @param key + * @param value + */ +declare function setNodeProperty(node: AstNode$1, key: 'errors', value: ErrorDescription$1[] | null): void; +/** + * + * @param node + * @param key + * @param value + */ +declare function setNodeProperty(node: AstNode$1, key: 'tokens', value: Token$1[] | null): void; + /** * Load file or url * @param url @@ -6040,7 +6586,7 @@ declare function render(data: AstNode$1, options?: RenderOptions, mapping?: { importMapping: Record> | null; } | null): RenderResult; /** - * Parse css file + * Parse CSS file * @param file url or path * @param options * @param asStream load file as stream @@ -6066,7 +6612,7 @@ declare function render(data: AstNode$1, options?: RenderOptions, mapping?: { */ declare const parseFile: (file: string, options?: ParserOptions, asStream?: boolean) => Promise; /** - * Parse css string + * Parse CSS string * @param stream * @param options * @@ -6074,72 +6620,69 @@ 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); * ``` * */ declare function parseSync(stream: string, options?: ParserSyncOptions): ParseResult; /** - * Parse css string - * @param stream + * Parse CSS string * @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); * ``` * */ declare function parseSync(options: ParseInputOptions & ParserSyncOptions): ParseResult; /** - * Transform css + * 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, {beautify: true}); + * const result = transformSync(css, {beautify: true}); * console.log(result.code); * ``` * */ declare function transformSync(css: string, options?: TransformSyncOptions): TransformResult; /** - * Transform css + * Transform CSS * @param options * * ```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 +6694,7 @@ declare function transformSync(options: ParseInputOptions & TransformSyncOptions * console.log(result.ast); * ``` * - * parsing a Readable stream + * parsing a ReadableStream * * ```ts * @@ -6166,7 +6709,7 @@ declare function transformSync(options: ParseInputOptions & TransformSyncOptions * console.log(result.ast); * ``` * - * Example using fetch and readable stream + * Parsing a file as a ReadableStream * * ```ts * @@ -6180,8 +6723,7 @@ declare function transformSync(options: ParseInputOptions & TransformSyncOptions */ declare function parse(stream: string | ReadableStream, options?: ParserOptions): Promise; /** - * Parse css - * @param stream + * Parse CSS * @param options * * @throws Error file not found @@ -6213,8 +6755,7 @@ declare function parse(stream: string | ReadableStream, options?: Pa */ declare function parse(options: ParseInputFileOptions & ParserOptions): Promise; /** - * Parse css - * @param stream + * Parse CSS * @param options * * Parsing a string @@ -6255,7 +6796,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 +6821,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 * @@ -6324,8 +6865,7 @@ declare const transformFile: (file: string, options?: TransformOptions, asStream */ declare function transform(css: string | ReadableStream, options?: TransformOptions): Promise; /** - * Transform css - * @param css + * Transform CSS * @param options * * Parsing a string @@ -6354,7 +6894,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 +6908,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 { 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, getNodeProperty, isOkLabClose, load, minify, okLabDistance, parse, parseDeclarations, parseFile, parseString, parseSync, render, renderValue as renderToken, replaceNodeOrValue, resolve, setNodeProperty, transform, transformFile, transformSync, walk, walkValues }; +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, CssVariableMapTokenType, 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..37b9563a 100644 --- a/dist/lib/ast/clone.js +++ b/dist/lib/ast/clone.js @@ -14,23 +14,28 @@ function cloneNode(node, cloneChildren = false, cloneMap = null) { cloneMap?.set?.(node, clone); for (const [name, value] of Object.entries(node)) { if (value == null || typeof value != "object") { + // @ts-ignore 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; - }); + // @ts-ignore + clone[name] = []; + if (cloneChildren || name !== checkNode) { + for (const c of value) { + const newObj = cloneNode(c, cloneChildren, cloneMap); + cloneMap?.set?.(c, newObj); + // @ts-ignore + clone[name].push(newObj); + } + } } else { + // @ts-ignore clone[name] = { ...value }; } } for (const symbol of Object.getOwnPropertySymbols(node)) { + // @ts-ignore clone[symbol] = node[symbol]; } return clone; diff --git a/dist/lib/ast/expand.js b/dist/lib/ast/expand.js index 3b4461d3..08a2c2d3 100644 --- a/dist/lib/ast/expand.js +++ b/dist/lib/ast/expand.js @@ -1,9 +1,10 @@ import { splitRule } from './minify.js'; -import { combinators, RAW } from '../syntax/constants.js'; +import { STATE, PARENT, combinators, RAW } from '../syntax/constants.js'; import { parseString } from '../parser/parse.js'; import { walkValues } from './walk.js'; import { renderValue } from '../renderer/render.js'; -import { EnumToken } from './types.js'; +import { EnumAstNodeStatus, EnumToken } from './types.js'; +import { cloneNode } from './clone.js'; /** * expand css nesting ast nodes @@ -12,12 +13,24 @@ import { EnumToken } from './types.js'; * @private */ function expand(ast) { - const result = { ...ast, chi: [] }; + if (ast[STATE] == EnumAstNodeStatus.Invalid || + ast[STATE] == EnumAstNodeStatus.Disallowed || + ast[STATE] == EnumAstNodeStatus.Unknown || + ast[STATE] == EnumAstNodeStatus.Unparsed || + ast[STATE] == EnumAstNodeStatus.Malformed) { + return ast; + } + const result = Object.assign(cloneNode(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 +42,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); } @@ -40,7 +66,14 @@ function expand(ast) { return result; } function expandRule(node) { - const ast = { ...node, chi: node.chi.slice() }; + if (node[STATE] == EnumAstNodeStatus.Invalid || + node[STATE] == EnumAstNodeStatus.Disallowed || + node[STATE] == EnumAstNodeStatus.Unknown || + node[STATE] == EnumAstNodeStatus.Unparsed || + node[STATE] == EnumAstNodeStatus.Malformed) { + return [node]; + } + const ast = Object.assign(cloneNode(node), { chi: node.chi.slice() }); const result = []; if (ast.typ == EnumToken.RuleNodeType) { let i = 0; @@ -57,7 +90,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/calc.js b/dist/lib/ast/features/calc.js index c7d639f0..53efdac9 100644 --- a/dist/lib/ast/features/calc.js +++ b/dist/lib/ast/features/calc.js @@ -109,7 +109,8 @@ class ComputeCalcExpressionFeature { // @ts-ignore const children = parent.typ == EnumToken.DeclarationNodeType ? parent.val - : parent.chi; + : // @ts-ignore + parent.chi; if (values.length == 1 && values[0].typ != EnumToken.BinaryExpressionTokenType) { for (let i = 0; i < children.length; i++) { if (children[i] == value) { diff --git a/dist/lib/ast/features/if.js b/dist/lib/ast/features/if.js index af655d3f..5c58b2a5 100644 --- a/dist/lib/ast/features/if.js +++ b/dist/lib/ast/features/if.js @@ -1,7 +1,7 @@ import { EnumToken } from '../types.js'; import { renderValue } from '../../renderer/render.js'; import { FeatureWalkMode } from './type.js'; -import { PARENT, TOKENS } from '../../syntax/constants.js'; +import { PARENT, LOC, TOKENS } from '../../syntax/constants.js'; import { equalsIgnoreCase } from '../../parser/utils/text.js'; import { replaceNodeOrValue } from '../../parser/utils/token.js'; import { cloneNode } from '../clone.js'; @@ -29,6 +29,7 @@ function substituteIfElseNode(declaration, node, wrapper, parentWrapper, cache) EnumToken.SemiColonTokenType ? trimArray(node.r.r.slice(0, -1)) : node.r.r); + // @ts-expect-error if (targetParentWrapper.typ != EnumToken.DeclarationNodeType) { let index = targetParentWrapper.chi.indexOf(targetWrapper); if (index != -1) { @@ -50,6 +51,7 @@ function substituteIfElseNode(declaration, node, wrapper, parentWrapper, cache) .r.r.slice(0, -1)) : siblingWrapper.chi[k] .r.r); + // @ts-ignore cache.add(siblingWrapper.chi[k].l); } } @@ -73,7 +75,9 @@ function substituteIfElseNode(declaration, node, wrapper, parentWrapper, cache) } if (left.typ === EnumToken.IdenTokenType && equalsIgnoreCase("else", left.val)) { clonedDeclaration = cloneNode(declaration, true, nodeMap); - replaceNodeOrValue(nodeMap.get(parentWrapper), nodeMap.get(targetWrapper.typ === EnumToken.DeclarationNodeType ? node : targetWrapper), node.r.at(-1)?.typ === EnumToken.SemiColonTokenType ? trimArray(node.r.slice(0, -1)) : node.r); + replaceNodeOrValue(nodeMap.get(parentWrapper), + // @ts-expect-error + nodeMap.get(targetWrapper.typ === EnumToken.DeclarationNodeType ? node : targetWrapper), node.r.at(-1)?.typ === EnumToken.SemiColonTokenType ? trimArray(node.r.slice(0, -1)) : node.r); result.push(clonedDeclaration); } else if (left?.typ === EnumToken.WhenElseFunctionTokenType) { @@ -82,6 +86,9 @@ function substituteIfElseNode(declaration, node, wrapper, parentWrapper, cache) nam: left.val, chi: [], }); + if (declaration[PARENT] != null) { + atRule[LOC] = declaration[PARENT][LOC]; + } atRule[TOKENS] = [{ typ: EnumToken.ParensTokenType, chi: left.chi.slice() }]; const minify = atRule.nam !== "supports"; const options = { @@ -104,6 +111,9 @@ function substituteIfElseNode(declaration, node, wrapper, parentWrapper, cache) }); atRule[TOKENS] = [left]; atRule.val = atRule[TOKENS].reduce((acc, curr) => acc + renderValue(curr), ""); + if (declaration[PARENT] != null) { + atRule[LOC] = declaration[PARENT][LOC]; + } clonedDeclaration = cloneNode(declaration, true, nodeMap); replaceNodeOrValue(nodeMap.get(targetWrapper.typ === EnumToken.WildCardFunctionTokenType ? targetParentWrapper : targetWrapper), nodeMap.get(targetWrapper.typ === EnumToken.WildCardFunctionTokenType ? targetWrapper : node), node.r.at(-1)?.typ === EnumToken.SemiColonTokenType ? trimArray(node.r.slice(0, -1)) @@ -134,17 +144,27 @@ function processNode(declarationNode, cache) { const parentWrapper = node.parent ?? parents.find((node) => !nodeMatcher(node)); if (node.node.typ === EnumToken.WildCardFunctionTokenType) { for (i = 0; i < node.node.chi.length; i++) { - stack.push(...substituteIfElseNode(declaration, node.node.chi[i], node.node, parentWrapper, cache)); + stack.push( + // @ts-expect-error + ...substituteIfElseNode( + // @ts-expect-error + declaration, node.node.chi[i], node.node, parentWrapper, cache)); } } else { - stack.push(...substituteIfElseNode(declaration, node.node, parentWrapper, parents[parents.indexOf(parentWrapper) + 1] ?? declaration, cache)); + stack.push( + // @ts-expect-error + ...substituteIfElseNode( + // @ts-expect-error + declaration, node.node, parentWrapper, parents[parents.indexOf(parentWrapper) + 1] ?? declaration, cache)); } } if (result.length > 0) { + // @ts-expect-error replaceNodeOrValue(declarationNode[PARENT], declarationNode, result); } // else remove node? + // @ts-expect-error return result; } class ExpandIfFeature { diff --git a/dist/lib/ast/features/prefix.js b/dist/lib/ast/features/prefix.js index d55df621..44b5031f 100644 --- a/dist/lib/ast/features/prefix.js +++ b/dist/lib/ast/features/prefix.js @@ -57,16 +57,19 @@ function replaceAstNodes(tokens, root) { if (key in pseudoAliasMap) { const isPseudClass = pseudoAliasMap[key].startsWith("::"); value.val = pseudoAliasMap[key]; - if (value.typ == EnumToken.IdenTokenType && - ["min-resolution", "max-resolution"].includes(value.val) && - parent?.typ == EnumToken.MediaQueryConditionTokenType && - parent.r?.[0]?.typ == EnumToken.NumberTokenType) { - Object.assign(parent.r?.[0], { - typ: EnumToken.ResolutionTokenType, - unit: "x", - }); - } - else if (isPseudClass && value.typ == EnumToken.PseudoElementTokenType) { + // if ( + // value.typ == EnumToken.IdenTokenType && + // ["min-resolution", "max-resolution"].includes((value as IdentToken).val) && + // parent?.typ == EnumToken.MediaQueryConditionTokenType && + // (parent as MediaQueryConditionToken).r?.[0]?.typ == EnumToken.NumberTokenType + // ) { + // Object.assign((parent as MediaQueryConditionToken).r?.[0], { + // typ: EnumToken.ResolutionTokenType, + // unit: "x", + // }); + // } + // else + if (isPseudClass && value.typ == EnumToken.PseudoElementTokenType) { // @ts-ignore value.typ = EnumToken.PseudoClassTokenType; } @@ -279,32 +282,11 @@ class ComputePrefixFeature { let commaCount; let type = ""; let tokens = token.chi.slice(); - // while ( - // i < tokens.length && - // (tokens[i].typ === EnumToken.WhitespaceTokenType || tokens[i].typ === EnumToken.CommentTokenType) - // ) { - // i++; - // } - // if (i >= tokens.length || tokens[i].typ !== EnumToken.IdenTokenType) { - // return; - // } // linear or radial if (equalsIgnoreCase(tokens[i].val, "linear")) { type = "linear-gradient"; i++; } - // else { - // return; - // } - // while ( - // i < tokens.length && - // (tokens[i].typ === EnumToken.WhitespaceTokenType || tokens[i].typ === EnumToken.CommentTokenType) - // ) { - // i++; - // } - // if (tokens[i].typ !== EnumToken.CommaTokenType) { - // return; - // } tokens.splice(0, i + 1); commaCount = 0; for (i = 0; i < tokens.length; i++) { 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..a0306f8a 100644 --- a/dist/lib/ast/features/transform.js +++ b/dist/lib/ast/features/transform.js @@ -6,7 +6,11 @@ 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.AtRuleNodeType, + EnumToken.KeyframesRuleNodeType, + ]); get ordering() { return 3; } @@ -43,7 +47,6 @@ class TransformCssFeature { ? minifyTransformFunctions(child) : child); } - // consumeWhitespace(children); let { matrix, cumulative, minified } = compute(children) ?? { matrix: null, cumulative: null, diff --git a/dist/lib/ast/find.js b/dist/lib/ast/find.js index 57b3cdb8..8c46228f 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; @@ -87,6 +88,7 @@ function findByValue(ast, matcher) { } for (const { value, parent, root: rootNode, parents } of walkValues(source, node)) { if (matcher(value, node)) { + // @ts-ignore return { node, value: { node: value, parent, root: rootNode, parents } }; } } diff --git a/dist/lib/ast/minify.js b/dist/lib/ast/minify.js index dc44fd30..fb0a66aa 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,22 @@ function minify(ast, options = {}, recursive = false, errors, nestingContent, co let postprocess = false; let parents; let replacement; - if (!("features" in options)) { - // @ts-ignore - options = { + let { sourcemap, module, ...options2 } = options; + if (!(options2.features != null)) { + 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 +67,20 @@ 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); + : // @ts-ignore + replacement.nam); } - const result = feature.run(replacement, options, parent[PARENT] ?? ast, context, FeatureWalkMode.Pre); + const result = feature.run(replacement, options2, + // @ts-ignore + parent[PARENT] ?? ast, context, FeatureWalkMode.Pre); if (result != null) { replacement = result; } @@ -85,24 +89,26 @@ function minify(ast, options = {}, recursive = false, errors, nestingContent, co (!Array.isArray(replacement) || replacement.length > 0) && replacement != parent && parent[PARENT] != null) { + // @ts-ignore replaceNodeOrValue(parent[PARENT], parent, replacement); } - if ("chi" in replacement) { + // @ts-ignore + if (replacement.chi != null) { // @ts-ignore for (const node of replacement.chi) { - // node[PARENT] = replacement; + node[PARENT] = replacement; parents.add(node); } } } - 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 +116,14 @@ 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, + // @ts-ignore + parent[PARENT] ?? ast, context, FeatureWalkMode.Post); if (result != null) { replacement = result; } @@ -128,18 +136,20 @@ function minify(ast, options = {}, recursive = false, errors, nestingContent, co // @ts-ignore replaceNodeOrValue(parent[PARENT], parent, replacement); } - if ("chi" in replacement) { + // @ts-ignore + if (replacement.chi != null) { + // @ts-ignore for (const node of replacement.chi) { - // node[PARENT] = replacement; + node[PARENT] = replacement; parents.add(node); } } } 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); } } } @@ -159,6 +169,7 @@ function transformAtRuleMediaPrelude(values) { values[values.indexOf(value)] = value.l; } else { + // @ts-ignore replaceNodeOrValue(parent, value, value.l); // @ts-ignore value = value.l; @@ -215,9 +226,11 @@ function transformAtRuleMediaPrelude(values) { // @ts-expect-error const p = parents?.[parents?.indexOf?.(parent) + 1]; if (p != null) { + // @ts-ignore replaceNodeOrValue(p, parent, replacement); } else { + // @ts-ignore values.splice(values.indexOf(parent), 1, replacement); } hasUpdates = true; @@ -232,9 +245,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; @@ -331,8 +344,8 @@ function doMinify(ast, options = {}, recursive = false, errors, nestingContent, continue; } while (previous?.typ === EnumToken.CommentNodeType) { + // @ts-ignore previous = ast.chi[--nodeIndex]; - continue; } node = ast.chi[i]; if (node.typ === EnumToken.AtRuleNodeType && node.nam === "font-face") { @@ -348,12 +361,13 @@ 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 previous.chi.push(...node.chi); + // @ts-ignore ast.chi.splice(i, 1); previous = ast?.chi?.[nodeIndex] ?? null; i = nodeIndex; @@ -426,6 +440,7 @@ function doMinify(ast, options = {}, recursive = false, errors, nestingContent, else if (ast.typ === node.typ && ast.nam === node.nam && ast.val === node.val) { + // @ts-ignore replaceNodeOrValue(ast, node, node.chi); i--; continue; @@ -519,7 +534,9 @@ function doMinify(ast, options = {}, recursive = false, errors, nestingContent, ":is(" + node[OPTIMIZED].selector.reduce(reducer, []).join(",") + ")"; - const sel2 = node[OPTIMIZED].selector.reduce((acc, curr) => (acc.length > 0 ? acc + "," : "") + node[OPTIMIZED].optimized[0] + curr.join(""), ""); + const sel2 = node[OPTIMIZED].selector.reduce((acc, curr) => + // @ts-ignore + (acc.length > 0 ? acc + "," : "") + node[OPTIMIZED].optimized[0] + curr.join(""), ""); node.sel = sel1.length < sel2.length ? sel1 : sel2; node[TOKENS] = null; } @@ -599,7 +616,9 @@ function doMinify(ast, options = {}, recursive = false, errors, nestingContent, ":is(" + node[OPTIMIZED].selector.reduce(reducer, []).join(",") + ")"; - const sel2 = node[OPTIMIZED].selector.reduce((acc, curr) => (acc.length > 0 ? acc + "," : "") + node[OPTIMIZED].optimized[0] + curr.join(""), ""); + const sel2 = node[OPTIMIZED].selector.reduce((acc, curr) => + // @ts-ignore + (acc.length > 0 ? acc + "," : "") + node[OPTIMIZED].optimized[0] + curr.join(""), ""); node.sel = sel1.length < sel2.length ? sel1 : sel2; node[TOKENS] = null; } @@ -610,11 +629,14 @@ function doMinify(ast, options = {}, recursive = false, errors, nestingContent, curr.join(""), ""); node[TOKENS] = null; } + // @ts-ignore } else if (node[OPTIMIZED]?.optimized.length > 0) { + // @ts-ignore const sel = node[OPTIMIZED].optimized.join(""); if (sel.length < node.sel.length) { node.sel = sel; + // @ts-ignore node[RAW] = [node[OPTIMIZED].optimized.slice()]; node[TOKENS] = null; } @@ -637,11 +659,14 @@ 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) || + // @ts-ignore (node.typ == EnumToken.AtRuleNodeType && node.nam !== "font-face" && + // @ts-ignore node.nam === previous.nam)) { + // @ts-ignore node.chi.unshift(...previous.chi); doMinify(node, options, recursive, errors, nestingContent, context); ast.chi.splice(nodeIndex, 1); @@ -650,7 +675,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 +828,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 +1212,6 @@ function wrapNodes(previous, node, match, ast, reducer, i, nodeIndex) { * Diff nodes * @param n1 * @param n2 - * @param reducer * @param options * * @private @@ -1304,17 +1330,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/node.js b/dist/lib/ast/node.js new file mode 100644 index 00000000..134448da --- /dev/null +++ b/dist/lib/ast/node.js @@ -0,0 +1,50 @@ +import { TOKENS, ERRORS, STATE, LOC, PARENT } from '../syntax/constants.js'; + +/** + * + * @param node + * @param key + * @returns + */ +function getNodeProperty(node, key) { + switch (key) { + case "parent": + return node[PARENT]; + case "location": + return node[LOC]; + case "state": + return node[STATE]; + case "errors": + return node[ERRORS]; + case "tokens": + return node[TOKENS]; + } + return undefined; +} +/** + * + * @param node + * @param key + * @param value + */ +function setNodeProperty(node, key, value) { + switch (key) { + case "parent": + node[PARENT] = value; + break; + case "location": + node[LOC] = value; + break; + case "state": + node[STATE] = value; + break; + case "errors": + node[ERRORS] = value; + break; + case "tokens": + node[TOKENS] = value; + break; + } +} + +export { getNodeProperty, setNodeProperty }; 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..007b0984 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,21 @@ 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) { + // @ts-ignore + parents.splice(i, 0, ...(reverse ? node[TOKENS].toReversed() : node[TOKENS])); + // @ts-ignore + } + else if (Array.isArray(node.val)) { + // @ts-ignore + parents.splice(i, 0, ...(reverse ? node.val.toReversed() : node.val)); + } + } + // @ts-ignore + if (node["chi"] != null && (!isNumeric || (option & WalkerOptionEnum.IgnoreChildren) === 0)) { + // @ts-ignore + parents.splice(i, 0, ...(reverse ? node.chi.toReversed() : node.chi)); for (const child of node.chi) { map.set(child, node); } @@ -228,11 +253,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..f1f9255a 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("/") || url.match(/^[a-zA-Z]:/) ? 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..1f703256 100644 --- a/dist/lib/parser/declaration/map.js +++ b/dist/lib/parser/declaration/map.js @@ -286,12 +286,21 @@ 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..54d8aec1 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); } @@ -23,12 +23,11 @@ class LineMap { */ getOffsets(offset) { const line = this.search(offset); - if (offset < 0 || line < 0) { - return [1, 1]; - } - const column = offset - this.lineStarts[line]; + // if (offset < 0 || line < 0) { + // return [1, 1]; + // } // [line, column] - return [line + 1, column === 0 ? 1 : column]; + return [line + 1, offset - this.lineStarts[line] + 1]; } /** * search the greatest index of the value less than or equal to offset @@ -66,13 +65,6 @@ class LineMap { addLineStart(lineStart) { this.lineStarts.push(lineStart); } - /** - * clone the linemap - * @returns - */ - clone() { - return new LineMap(this.lineStarts.slice()); - } } export { LineMap }; diff --git a/dist/lib/parser/parse.js b/dist/lib/parser/parse.js index 8325511f..95d367d8 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) @@ -47,7 +48,6 @@ const BadTokensTypes = [ EnumToken.BadStringTokenType, ]; let keyNameCounter = 0; -const forbiddenStartCharacters = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"].map((c) => c.charCodeAt(0)); /** * Short-scoped name generator. * @@ -58,12 +58,15 @@ const forbiddenStartCharacters = ["0", "1", "2", "3", "4", "5", "6", "7", "8", " * * @returns string */ -const getShortNameGenerator = memoize((localName, filePath, pattern, hashLength = 5) => { +const getShortNameGenerator = memoize(() => { let value = keyNameCounter.toString(36); + let val = value.charAt(0).charCodeAt(0); keyNameCounter++; - while (forbiddenStartCharacters.includes(value.charCodeAt(0))) { + // starts with'0' - '9' + while (48 <= val && val <= 57) { value = keyNameCounter.toString(36); keyNameCounter++; + val = value.charAt(0).charCodeAt(0); } return value; }); @@ -290,6 +293,144 @@ 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") { + if ("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 { + visitors.push(...Object.entries(value)); + } + } + 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") { + // visitors.push(...Object.entries(value)); + 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 +496,20 @@ 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, + }; + for (currentItemIndex = 0; currentItemIndex < iter.length; currentItemIndex++) { + item = iter[currentItemIndex]; stats.bytesIn = item.bytesIn; stats.tokensCount++; if (BadTokensTypes.includes(item.token.typ)) { @@ -507,8 +537,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 +552,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 +607,162 @@ 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; + } + } + // @ts-ignore + if (nodes[i].chi != null) { + // @ts-ignore + 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()]; - } - yield* parens[Symbol.iterator](); - }); - if (replacement == null) { - continue; - } - if (replacement == null || replacement == node) { - continue; - } + 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]); + // } + // } + // } 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]) { + // @ts-ignore + replaceNodeOrValue(nodes[i][PARENT], nodes[i], node); + } } + nodes = null; } if (invalidNodes.length > 0) { let count = invalidNodes.length; @@ -794,19 +785,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 +813,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(); @@ -900,6 +878,7 @@ function doParseSync(iter, options = {}) { throw new Error("css variable import not supported by parseSync() or transformSync(). use parse() or transform() instead.\nat " + options.source.getSourceLocation(node[LOC].sta).join(":")); } + // @ts-ignore if (node.typ == EnumToken.CssVariableDeclarationMapTokenType) { const from = node.from.find((t) => t.typ == EnumToken.IdenTokenType || isIdentColor(t)); if (!(from.val in cssVariablesMap)) { @@ -941,10 +920,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 +964,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 +1137,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]; @@ -1176,6 +1153,7 @@ function doParseSync(iter, options = {}) { } else if ((value.typ == EnumToken.IdenTokenType || isIdentColor(value)) && value.val in importedCssVariables) { + // @ts-ignore replaceNodeOrValue(parent, value, importedCssVariables[value.val].val); } } @@ -1235,10 +1213,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 @@ -1252,7 +1229,7 @@ function doParseSync(iter, options = {}) { } if (moduleSettings.scoped & ModuleScopeEnumOptions.Pure) { if (!hasIdOrClass) { - throw new Error(`pure module: No id or class found in selector '${node.sel}' at '${node[LOC]?.src ?? ""}':${node[LOC]?.sta?.lin ?? ""}:${node[LOC]?.sta?.col ?? ""}`); + throw new Error(`pure module: No id or class found in selector '${node.sel}' at '${options.source.getOffsets(node[LOC]?.sta).join(":")}'`); } } node.sel = ""; @@ -1271,10 +1248,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 +1268,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 +1359,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 +1366,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](); @@ -1538,8 +1408,6 @@ async function doParse(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 || @@ -1623,7 +1491,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 @@ -1632,11 +1500,10 @@ async function doParse(iter, options = {}) { options.sourcesMap.set(source.id, source); const parseInfo = { stream, - buffer: "", offset: 0, source, position: 0, - currentPosition: -1, + currentPosition: 0, }; const root = await doParse(stream instanceof ReadableStream ? tokenizeStream(stream, parseInfo) : tokenize(parseInfo), Object.assign({}, options, { minify: false, @@ -1647,6 +1514,7 @@ async function doParse(iter, options = {}) { stats.nodesCount += root.stats.nodesCount; stats.tokensCount += root.stats.tokensCount; stats.imports.push(root.stats); + // @ts-ignore node[PARENT].chi.splice(node[PARENT].chi.indexOf(node), 1, ...root.ast.chi); if (root.errors.length > 0) { errors.push(...root.errors); @@ -1663,210 +1531,165 @@ 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; + } + } + // @ts-ignore + if (nodes[i].chi != null) { + // @ts-ignore + 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()]; - } - yield* parens[Symbol.iterator](); - }); - if (replacement == null) { - continue; - } - if (replacement instanceof Promise) { - replacement = await replacement; - } - if (replacement == null || replacement == node) { - continue; - } + 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]); + // } + // } + // } 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]) { + // @ts-ignore + replaceNodeOrValue(nodes[i][PARENT], nodes[i], node); + } } + nodes = null; } if (invalidNodes.length > 0) { let count = invalidNodes.length; @@ -1889,19 +1712,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; @@ -2007,7 +1817,7 @@ async function doParse(iter, options = {}) { time: 0, source, position: 0, - currentPosition: -1, + currentPosition: 0, }; const root = await doParse(stream instanceof ReadableStream ? tokenizeStream(stream, parseInfo) : tokenize(parseInfo), Object.assign({}, options, { source, @@ -2020,6 +1830,7 @@ async function doParse(iter, options = {}) { parent.chi.splice(parent.chi.indexOf(node), 1); continue; } + // @ts-ignore if (node.typ == EnumToken.CssVariableDeclarationMapTokenType) { const from = node.from.find((t) => t.typ == EnumToken.IdenTokenType || isIdentColor(t)); if (!(from.val in cssVariablesMap)) { @@ -2064,6 +1875,7 @@ async function doParse(iter, options = {}) { let result = moduleSettings.scoped & ModuleScopeEnumOptions.Global ? node.nam : moduleSettings.generateScopedName(node.nam, moduleSettings.filePath, moduleSettings.pattern, moduleSettings.hashLength); + // @ts-ignore let value = result instanceof Promise ? await result : result; mapping[node.nam] = "--" + @@ -2097,6 +1909,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) { @@ -2148,26 +1961,26 @@ async function doParse(iter, options = {}) { : result; const root = await doParse(stream instanceof ReadableStream ? tokenizeStream(stream, { - buffer: "", offset: 0, source: new SourceFile("", [], src.relative), position: 0, - currentPosition: -1, + currentPosition: 0, }) : tokenize({ stream, - buffer: "", offset: 0, position: 0, source: new SourceFile(stream, [], src.relative), - currentPosition: -1, + currentPosition: 0, }), Object.assign({}, options, { minify: false, 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] = {}; } @@ -2368,30 +2181,13 @@ async function doParse(iter, options = {}) { } for (const { value, parent } of walkValues(node.val, node)) { if (value.typ == EnumToken.DashedIdenTokenType) { - // if (!((value as DashedIdentToken).val in mapping)) { - // const result = - // moduleSettings.scoped! & ModuleScopeEnumOptions.Global - // ? (value as DashedIdentToken).val - // : moduleSettings.generateScopedName!( - // (value as DashedIdentToken).val, - // moduleSettings.filePath as string, - // moduleSettings.pattern as string, - // moduleSettings.hashLength, - // ); - // let val: string = result instanceof Promise ? await result : result; - // mapping[(value as DashedIdentToken).val] = - // "--" + - // (moduleSettings.naming! & ModuleCaseTransformEnum.DashCaseOnly || - // moduleSettings.naming! & ModuleCaseTransformEnum.CamelCaseOnly - // ? getKeyName(val, moduleSettings.naming as ModuleCaseTransformEnum) - // : val); - // revMapping[mapping[(value as DashedIdentToken).val]] = (value as DashedIdentToken).val; - // } value.val = mapping[value.val]; } else if ((value.typ == EnumToken.IdenTokenType || isIdentColor(value)) && value.val in importedCssVariables) { - replaceNodeOrValue(parent, value, importedCssVariables[value.val].val); + replaceNodeOrValue( + // @ts-ignore + parent, value, importedCssVariables[value.val].val); } } } @@ -2419,26 +2215,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 +2228,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; } } })) { @@ -2495,7 +2265,7 @@ async function doParse(iter, options = {}) { } if (moduleSettings.scoped & ModuleScopeEnumOptions.Pure) { if (!hasIdOrClass) { - throw new Error(`pure module: No id or class found in selector '${node.sel}' at '${node[LOC]?.src ?? ""}':${node[LOC]?.sta?.lin ?? ""}:${node[LOC]?.sta?.col ?? ""}`); + throw new Error(`pure module: No id or class found in selector '${node.sel}' at '${(options.source?.getOffsets?.(node[LOC]?.sta) ?? []).join(":")}'`); } } node.sel = ""; @@ -2743,6 +2513,8 @@ function parseNode(tokens, context, options, errors, stats, invalidNodes) { return null; } /** + * @param stream + * @param context * @param options * @param errors * @param parseAsBlock @@ -2819,7 +2591,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, @@ -3114,10 +2885,13 @@ function parseAtRule(stream, context, options, errors, parseAsBlock = null) { let definedAfterLastElse = false; if (sibling == null || sibling.typ !== EnumToken.AtRuleNodeType) { missingWhen = true; + // @ts-expect-error } else if (sibling.nam !== "when") { + // @ts-expect-error if (sibling.nam !== "else") { missingWhen = true; + // @ts-expect-error } else if (sibling.val === "") { definedAfterLastElse = true; @@ -3307,7 +3081,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 +3220,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 = []; @@ -3503,11 +3274,10 @@ async function parseDeclarations(declaration) { const stream = `.x{${declaration}}`; return doParse(tokenize({ stream, - buffer: "", offset: 0, position: 0, source: new SourceFile(stream, [], ""), - currentPosition: -1, + currentPosition: 0, }), { setParent: false, minify: false, validation: false }).then((result) => { return result.ast.chi[0].chi.filter((t) => t.typ == EnumToken.DeclarationNodeType || t.typ == EnumToken.CommentNodeType); }); @@ -3537,19 +3307,20 @@ async function parseDeclarations(declaration) { function parseString(src, options = { parseColor: true }, errors) { const parseInfo = { stream: src, - buffer: "", offset: 0, time: 0, source: new SourceFile(src, [], ""), position: 0, - currentPosition: -1, + currentPosition: 0, }; - 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 +3411,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..efd932cb 100644 --- a/dist/lib/parser/tokenize.js +++ b/dist/lib/parser/tokenize.js @@ -1,6 +1,6 @@ import { EnumToken, ColorType } from '../ast/types.js'; import { LOC, wildCardFuncs, whenElseFunc, transformFunctions, mathFuncs, colorsFunc, timingFunc, supportFunc, timelineFunc, imageFunc, gridTemplateFunc, urlFunc, containerFunc, pseudoElements } from '../syntax/constants.js'; -import { isDigit, isPseudo, isIdent, isWhiteSpace, isNumber, isHexColor, isHash, isPercentage, parseDimension, isNewLine } from '../syntax/syntax.js'; +import { isDigit, isWhiteSpace, isIdent, isHexColor, isHash, isNumber, isPercentage, parseDimension, isNewLine, isIdentStart, isIdentCodepoint, isNonPrintable } from '../syntax/syntax.js'; import { SourceFile } from './source.js'; import { equalsIgnoreCase } from './utils/text.js'; @@ -136,17 +136,17 @@ var TokenMap; TokenMap[TokenMap["GREATERTHAN"] = 62] = "GREATERTHAN"; })(TokenMap || (TokenMap = {})); function consumeString(parseInfo) { - const quote = next(parseInfo); - let value; - let buffer = quote; + const quote = next(parseInfo).charCodeAt(0); + let charCode; + let decodeSegments = false; const result = []; - while ((value = parseInfo.stream.charAt(parseInfo.currentPosition - parseInfo.offset + 1))) { - if (value == "\\") { - if ("\\" == parseInfo.stream.charAt(parseInfo.currentPosition - parseInfo.offset + 2)) { - buffer += next(parseInfo, 2); + while ((charCode = parseInfo.stream.charCodeAt(parseInfo.currentPosition - parseInfo.offset)) == charCode) { + if (charCode == 92 /* TokenMap.REVERSE_SOLIDUS */) { + if (charCode == parseInfo.stream.charCodeAt(parseInfo.currentPosition - parseInfo.offset + 1)) { + next(parseInfo, 2); continue; } - const sequence = peek(parseInfo, 6); + const sequence = peek(parseInfo, 7); let escapeSequence = ""; let codepoint; let i; @@ -165,50 +165,66 @@ function consumeString(parseInfo) { break; } if (escapeSequence.trimEnd().length > 0) { - const codepoint = parseInt(escapeSequence, 16); - if (codepoint == 0 || - // leading surrogate - (0xd800 <= codepoint && codepoint <= 0xdbff) || - // trailing surrogate - (0xdc00 <= codepoint && codepoint <= 0xdfff)) { - buffer += String.fromCodePoint(0xfffd); - } - else { - buffer += String.fromCodePoint(codepoint); - } - next(parseInfo, escapeSequence.length + + // const codepoint = parseInt(escapeSequence, 16); + // TODO set decode flag ON + // if ( + // codepoint == 0 || + // // leading surrogate + // (0xd800 <= codepoint && codepoint <= 0xdbff) || + // // trailing surrogate + // (0xdc00 <= codepoint && codepoint <= 0xdfff) + // ) { + // buffer += String.fromCodePoint(0xfffd); + // } else { + // buffer += String.fromCodePoint(codepoint); + // } + const length = escapeSequence.length + 1 + - (isWhiteSpace(parseInfo.stream.charAt(parseInfo.currentPosition - parseInfo.offset + 1)?.charCodeAt(0)) + (isWhiteSpace(parseInfo.stream.charAt(parseInfo.currentPosition - parseInfo.offset)?.charCodeAt(0)) ? 1 - : 0)); + : 0); + decodeSegments = true; + next(parseInfo, length); continue; } - buffer += next(parseInfo, 2); + next(parseInfo, 2); continue; } - if (value == quote) { - buffer += value; - result.push(yieldResult(buffer, parseInfo, - /* hasNewLine ? EnumToken.BadStringTokenType : */ EnumToken.StringTokenType)); + if (charCode == quote) { next(parseInfo); - buffer = ""; + result.push(yieldResult(parseInfo, + /* hasNewLine ? EnumToken.BadStringTokenType : */ EnumToken.StringTokenType, decodeSegments ? { decodeSegments } : null)); return result; } - if (isNewLine(value.charCodeAt(0))) { - result.push(yieldResult(buffer + next(parseInfo), parseInfo, EnumToken.BadStringTokenType)); + if (isNewLine(charCode)) { + next(parseInfo); + result.push(yieldResult(parseInfo, EnumToken.BadStringTokenType)); return result; } - buffer += value; next(parseInfo); } // EOF - 'Unclosed-string' fixed - result.push(yieldResult(buffer + quote, parseInfo, EnumToken.StringTokenType)); + result.push(yieldResult(parseInfo, EnumToken.StringTokenType)); return result; } -function yieldResult(val, parseInfo, hint) { +function yieldResult(parseInfo, hint, options) { + let val = parseInfo.stream.slice(parseInfo.position - parseInfo.offset, parseInfo.currentPosition - parseInfo.offset); let token = null; let dimension; - // console.debug(`Yield result: ${val}, ${hint}`); + if (options?.decodeSegments) { + val = val.replace(/\\([0-9a-fA-F]{1,6})(?:\s)?/g, (_, sequence) => { + const codepoint = parseInt(sequence, 16); + if (codepoint == 0 || + // leading surrogate + (0xd800 <= codepoint && codepoint <= 0xdbff) || + // trailing surrogate + (0xdc00 <= codepoint && codepoint <= 0xdfff) || + codepoint > 0x10ffff) { + return "\uFFFD"; + } + return String.fromCodePoint(codepoint); + }); + } if (hint != null) { let searchArray = null; switch (hint) { @@ -260,7 +276,12 @@ function yieldResult(val, parseInfo, hint) { else { let slice = val.slice(1); const chr = val.charAt(0); - if (chr == "@" && isIdent(slice)) { + if (chr == "!" && equalsIgnoreCase("!important", val)) { + token = { + typ: EnumToken.ImportantTokenType, + }; + } + else if (chr == "@" && isIdent(slice)) { token = { typ: EnumToken.AtRuleTokenType, nam: slice, @@ -335,12 +356,12 @@ function yieldResult(val, parseInfo, hint) { end: parseInfo.currentPosition, }; parseInfo.position = parseInfo.currentPosition; - return { token, bytesIn: parseInfo.currentPosition + 1 }; + return { token, bytesIn: parseInfo.currentPosition }; } function match(parseInfo, input) { let position = parseInfo.currentPosition - parseInfo.offset; for (let i = 0; i < input.length; i++) { - if (parseInfo.stream[position + i + 1] != input.charAt(i)) { + if (parseInfo.stream[position + i] != input.charAt(i)) { return false; } } @@ -348,14 +369,14 @@ function match(parseInfo, input) { } function peek(parseInfo, count = 1) { if (count == 1) { - return parseInfo.stream.charAt(parseInfo.currentPosition - parseInfo.offset + 1); + return parseInfo.stream.charAt(parseInfo.currentPosition - parseInfo.offset); } const position = parseInfo.currentPosition - parseInfo.offset; - return parseInfo.stream.slice(position + 1, position + count + 1); + return parseInfo.stream.slice(position, position + count); } function next(parseInfo, count = 1) { let position = parseInfo.currentPosition - parseInfo.offset; - let char = count == 1 ? parseInfo.stream.charAt(position + 1) : parseInfo.stream.slice(position + 1, position + 1 + count); + let char = count == 1 ? parseInfo.stream.charAt(position) : parseInfo.stream.slice(position, position + count); let i = 0; let codepoint; for (; i < char.length; i++) { @@ -377,195 +398,292 @@ function next(parseInfo, count = 1) { parseInfo.currentPosition += char.length; return char; } +function isIdentToken(parseInfo, start, end) { + let j = parseInfo.currentPosition - parseInfo.offset; + let i = parseInfo.position - parseInfo.offset; + if (start != null) { + if (end == null) { + if (start < 0) { + j += start; + } + else { + i += start; + } + } + else { + if (end < 0) { + j += end; + } + else { + j = parseInfo.position + end; + } + } + } + j--; + let codepoint = parseInfo.stream.charCodeAt(i); + // - + if (codepoint == 0x2d) { + let nextCodepoint; + if ((nextCodepoint = parseInfo.stream.charCodeAt(i + 1)) != nextCodepoint) { + return false; + } + if (isDigit(nextCodepoint)) { + return false; + } + codepoint = nextCodepoint; + i++; + } + if (codepoint !== 0x2d && !isIdentStart(codepoint)) { + return false; + } + if (codepoint == 92 /* TokenMap.REVERSE_SOLIDUS */) { + codepoint = parseInfo.stream.charCodeAt(i + 1); + // if (!isIdentCodepoint(codepoint)) { + // return false; + // } + i += String.fromCodePoint(codepoint).length; + // if (i < j) { + // codepoint = name.charCodeAt(i) as number; + // if (!isIdentCodepoint(codepoint)) { + // return false; + // } + // } + } + while (i < j) { + i += codepoint < 0x80 ? 1 : String.fromCodePoint(codepoint).length; + codepoint = parseInfo.stream.charCodeAt(i); + if (codepoint == 92 /* TokenMap.REVERSE_SOLIDUS */) { + i += codepoint < 0x80 ? 1 : String.fromCodePoint(codepoint).length; + codepoint = parseInfo.stream.charCodeAt(i); + i += codepoint < 0x80 ? 1 : String.fromCodePoint(codepoint).length; + continue; + } + if (codepoint !== 0x2d && !isIdentCodepoint(codepoint)) { + return false; + } + } + return true; +} +function isPseudo(parseInfo) { + let position = parseInfo.currentPosition - parseInfo.offset; + let endPosition = parseInfo.currentPosition - parseInfo.offset; + return (parseInfo.stream.charAt(position) == ":" && + parseInfo.stream.charAt(endPosition - 1) == "(" && + (parseInfo.stream.charAt(position + 1) == ":" + ? isIdentToken(parseInfo, 2, -1) + : isIdentToken(parseInfo, 1, -1))) || + parseInfo.stream.charAt(position + 1) == ":" + ? isIdentToken(parseInfo, 2) + : isIdentToken(parseInfo, 1); +} +function startsWith(parseInfo, input) { + let i = 0; + let j = input.length; + while (i < j) { + if (parseInfo.stream.charAt(parseInfo.position - parseInfo.offset + i) != input.charAt(i)) { + return false; + } + i++; + } + return true; +} +function isURLToken(parseInfo) { + let i = parseInfo.position - parseInfo.offset; + let c; + while (++i < parseInfo.currentPosition) { + c = parseInfo.stream.charCodeAt(i); + // single quote or double quote or start parenthesis or close parenthesis + if (isNonPrintable(c) || c == 0x27 || c == 0x22 || c == 0x28 || c == 0x29) { + return false; + } + // valid escape + if (c == 92 /* TokenMap.REVERSE_SOLIDUS */) { + i++; + if (i >= parseInfo.currentPosition) { + return false; + } + c = parseInfo.stream.charCodeAt(i); + // c is not '\n' or '\r' or '\f' + if (c == 0x6e || c == 0x72 || c == 0x66) { + return false; + } + continue; + } + // is white space + if (c == 0x20 || c == 0x09) { + break; + } + } + return i == parseInfo.currentPosition; +} /** - * Tokenize css string + * Tokenize CSS string * @param parseInfo * @param yieldEOFToken */ function tokenize(parseInfo, yieldEOFToken = true) { if (typeof parseInfo == "string") { parseInfo = { - buffer: "", stream: parseInfo, source: new SourceFile(parseInfo, [], ""), offset: 0, time: 0, position: 0, - currentPosition: -1, + currentPosition: 0, }; } - let value; - let buffer = parseInfo.buffer; let charCode; let nextCharCode; const startTime = performance.now(); const result = []; // allow 10 characters buffer for the streaming parser to avoid incomplete tokens - const endPosition = parseInfo.stream.length - 10; - parseInfo.buffer = ""; - while ((value = peek(parseInfo))) { - charCode = value.charCodeAt(0); - // nextCharCode = nextValue.charCodeAt(0); - // console.debug({value, buffer}); + const endPosition = parseInfo.stream.length - 1; + // NaN is not equal to NaN + while ((charCode = peek(parseInfo).charCodeAt(0)) == charCode) { switch (charCode) { case 61 /* TokenMap.EQUALS */: - if (buffer.length > 0) { - result.push(yieldResult(buffer, parseInfo)); - buffer = ""; + if (parseInfo.position < parseInfo.currentPosition) { + result.push(yieldResult(parseInfo)); } - result.push(yieldResult(next(parseInfo), parseInfo, EnumToken.DelimTokenType)); + next(parseInfo); + result.push(yieldResult(parseInfo, EnumToken.DelimTokenType)); break; // '+' or '-' case 43 /* TokenMap.PLUS */: case 45 /* TokenMap.MINUS */: - next(parseInfo); - if (charCode === 43 /* TokenMap.PLUS */ && !isNumber(peek(parseInfo))) { - if (buffer.length > 0) { - result.push(yieldResult(buffer, parseInfo)); - buffer = ""; + nextCharCode = peek(parseInfo).charCodeAt(0); + // not a number + if (charCode === 43 /* TokenMap.PLUS */ && !(nextCharCode >= 0x30 && nextCharCode <= 0x39)) { + if (parseInfo.position < parseInfo.currentPosition) { + result.push(yieldResult(parseInfo)); } - result.push(yieldResult(value, parseInfo, SymbolsMapTokens[value])); + next(parseInfo); + result.push(yieldResult(parseInfo, SymbolsMapTokens[parseInfo.stream + .slice(parseInfo.position - parseInfo.offset, parseInfo.currentPosition - parseInfo.offset) + .toLowerCase()])); break; } - buffer += value; + next(parseInfo); break; // '{' case 123 /* TokenMap.LEFT_BRACE */: - if (buffer.length > 0) { - result.push(yieldResult(buffer, parseInfo)); - buffer = ""; + if (parseInfo.position < parseInfo.currentPosition) { + result.push(yieldResult(parseInfo)); } - result.push(yieldResult(next(parseInfo), parseInfo, EnumToken.BlockStartTokenType)); + next(parseInfo); + result.push(yieldResult(parseInfo, EnumToken.BlockStartTokenType)); break; // '}' case 125 /* TokenMap.RIGHT_BRACE */: - if (buffer.length > 0) { - result.push(yieldResult(buffer, parseInfo)); - buffer = ""; + if (parseInfo.position < parseInfo.currentPosition) { + result.push(yieldResult(parseInfo)); } - result.push(yieldResult(next(parseInfo), parseInfo, EnumToken.BlockEndTokenType)); + next(parseInfo); + result.push(yieldResult(parseInfo, EnumToken.BlockEndTokenType)); break; // '(' case 40 /* TokenMap.LEFT_PARENTHESIS */: - if (buffer.length > 0) { - if (buffer[0] === ":" && isPseudo(buffer)) { + if (parseInfo.position < parseInfo.currentPosition) { + if (parseInfo.stream[parseInfo.position - parseInfo.offset] === ":" && isPseudo(parseInfo)) { next(parseInfo); - result.push(yieldResult(buffer, parseInfo, EnumToken.PseudoClassFunctionTokenDefType)); - buffer = ""; + result.push(yieldResult(parseInfo, EnumToken.PseudoClassFunctionTokenDefType)); break; } - else if (isIdent(buffer)) { - const hint = buffer.startsWith("--") + else if (isIdentToken(parseInfo)) { + const hint = startsWith(parseInfo, "--") ? EnumToken.CustomFunctionTokenDefType - : (SymbolsMapTokens[buffer.toLowerCase() + "("] ?? EnumToken.FunctionTokenDefType); - result.push(yieldResult(buffer, parseInfo, hint)); + : (SymbolsMapTokens[parseInfo.stream + .slice(parseInfo.position - parseInfo.offset, parseInfo.currentPosition - parseInfo.offset) + .toLowerCase() + "("] ?? EnumToken.FunctionTokenDefType); + result.push(yieldResult(parseInfo, hint)); next(parseInfo); - buffer = ""; + // consume '(' + parseInfo.position = parseInfo.currentPosition; if (hint === EnumToken.UrlFunctionTokenDefType) { - buffer = ""; - value = peek(parseInfo); // consume an - while (isWhiteSpace((charCode = value.charCodeAt(0)))) { - buffer += next(parseInfo); - value = peek(parseInfo); - charCode = value.charCodeAt(0); - if (value === "/" && match(parseInfo, "/*")) { - if (buffer.length > 0) { - result.push(yieldResult(buffer, parseInfo)); - buffer = ""; - } - buffer += next(parseInfo, 2); - while ((value = next(parseInfo))) { - if (value == "*") { - buffer += value; - if (match(parseInfo, "/")) { - result.push(yieldResult(buffer + next(parseInfo), parseInfo, EnumToken.CommentTokenType)); - buffer = ""; - break; - } - } - else { - buffer += value; - } - } - if (buffer.length > 0) { - result.push(yieldResult(buffer, parseInfo, EnumToken.BadCommentTokenType)); - buffer = ""; - } - value = peek(parseInfo); - charCode = value.charCodeAt(0); - } + while (isWhiteSpace(peek(parseInfo).charCodeAt(0))) { + next(parseInfo); } - if (buffer.length > 0) { - result.push(yieldResult(buffer, parseInfo, EnumToken.WhitespaceTokenType)); - buffer = ""; + charCode = peek(parseInfo).charCodeAt(0); + let values = null; + if (charCode == 34 /* TokenMap.DOUBLE_QUOTE */ || charCode == 39 /* TokenMap.SINGLE_QUOTE */) { + values = consumeString(parseInfo); } - if (value === ")" || value === '"' || value === "'") { - break; + else { + do { + next(parseInfo); + // value = peek(parseInfo); + charCode = peek(parseInfo).charCodeAt(0); + } while ( + // !(value === "/" && match(parseInfo, "/*") && + charCode !== 41 /* TokenMap.RIGHT_PARENTHESIS */ && + parseInfo.currentPosition < endPosition); } - do { - buffer += next(parseInfo); - value = peek(parseInfo); - charCode = value.charCodeAt(0); - } while (value !== ")" && - !isWhiteSpace(charCode) && - !(value === "/" && match(parseInfo, "/*"))); - if (buffer.length > 0) { - result.push(yieldResult(buffer, parseInfo, peek(parseInfo) === "" + if (values != null) { + // NaN is not equal to NaN + if ((charCode = peek(parseInfo).charCodeAt(0)) != charCode) { + for (let i = 0; i < values.length; i++) { + values[i].token.typ = EnumToken.BadUrlTokenType; + } + } + result.push(...values); + } + else if (parseInfo.position < parseInfo.currentPosition) { + result.push(yieldResult(parseInfo, + // parseInfo.position < parseInfo.currentPosition + (charCode = peek(parseInfo).charCodeAt(0)) != charCode || !isURLToken(parseInfo) ? EnumToken.BadUrlTokenType : EnumToken.UrlTokenTokenType)); - buffer = ""; } } - // console.debug({value: peek(parseInfo)}); break; } } - result.push(yieldResult(next(parseInfo), parseInfo, EnumToken.StartParensTokenType)); - buffer = ""; + next(parseInfo); + result.push(yieldResult(parseInfo, EnumToken.StartParensTokenType)); break; // ')' case 41 /* TokenMap.RIGHT_PARENTHESIS */: - if (buffer.length > 0) { - result.push(yieldResult(buffer, parseInfo)); - buffer = ""; + if (parseInfo.position < parseInfo.currentPosition) { + result.push(yieldResult(parseInfo)); } - result.push(yieldResult(next(parseInfo), parseInfo, EnumToken.EndParensTokenType)); + next(parseInfo); + result.push(yieldResult(parseInfo, EnumToken.EndParensTokenType)); break; // '[' case 91 /* TokenMap.LEFT_BRACKETS */: - if (buffer.length > 0) { - result.push(yieldResult(buffer, parseInfo)); - buffer = ""; + if (parseInfo.position < parseInfo.currentPosition) { + result.push(yieldResult(parseInfo)); } - result.push(yieldResult(next(parseInfo), parseInfo, EnumToken.AttrStartTokenType)); + next(parseInfo); + result.push(yieldResult(parseInfo, EnumToken.AttrStartTokenType)); break; // ']' case 93 /* TokenMap.RIGHT_BRACKETS */: - if (buffer.length > 0) { - result.push(yieldResult(buffer, parseInfo)); - buffer = ""; + if (parseInfo.position < parseInfo.currentPosition) { + result.push(yieldResult(parseInfo)); } - result.push(yieldResult(next(parseInfo), parseInfo, EnumToken.AttrEndTokenType)); + next(parseInfo); + result.push(yieldResult(parseInfo, EnumToken.AttrEndTokenType)); break; case 59 /* TokenMap.SEMICOLON */: - if (buffer.length > 0) { - result.push(yieldResult(buffer, parseInfo)); - buffer = ""; + if (parseInfo.position < parseInfo.currentPosition) { + result.push(yieldResult(parseInfo)); } - result.push(yieldResult(next(parseInfo), parseInfo, EnumToken.SemiColonTokenType)); + next(parseInfo); + result.push(yieldResult(parseInfo, EnumToken.SemiColonTokenType)); break; case 58 /* TokenMap.COLON */: - if (buffer.length > 0) { - result.push(yieldResult(buffer, parseInfo)); - buffer = ""; + if (parseInfo.position < parseInfo.currentPosition) { + result.push(yieldResult(parseInfo)); } next(parseInfo); if (peek(parseInfo).charCodeAt(0) == 58 /* TokenMap.COLON */) { - result.push(yieldResult(value + next(parseInfo), parseInfo, EnumToken.DoubleColonTokenType)); + next(parseInfo); + result.push(yieldResult(parseInfo, EnumToken.DoubleColonTokenType)); break; } - result.push(yieldResult(value, parseInfo, EnumToken.ColonTokenType)); + result.push(yieldResult(parseInfo, EnumToken.ColonTokenType)); break; // \n \r \f \v \t space case 0x9: @@ -576,241 +694,229 @@ function tokenize(parseInfo, yieldEOFToken = true) { case 0xd: case 0x2028: case 0x2029: - if (buffer.length > 0) { - result.push(yieldResult(buffer, parseInfo)); - buffer = ""; + if (parseInfo.position < parseInfo.currentPosition) { + result.push(yieldResult(parseInfo)); } - buffer += next(parseInfo); - nextCharCode = parseInfo.stream.charAt(parseInfo.currentPosition - parseInfo.offset + 1).charCodeAt(0); + next(parseInfo); + nextCharCode = parseInfo.stream.charAt(parseInfo.currentPosition - parseInfo.offset).charCodeAt(0); while (nextCharCode == 0x20 || (nextCharCode >= 0x9 && nextCharCode <= 0xd) || nextCharCode == 0x2028 || nextCharCode == 0x2029) { - value += next(parseInfo); - nextCharCode = parseInfo.stream - .charAt(parseInfo.currentPosition - parseInfo.offset + 1) - .charCodeAt(0); + next(parseInfo); + nextCharCode = parseInfo.stream.charAt(parseInfo.currentPosition - parseInfo.offset).charCodeAt(0); } - result.push(yieldResult(value, parseInfo, EnumToken.WhitespaceTokenType)); - buffer = ""; + result.push(yieldResult(parseInfo, EnumToken.WhitespaceTokenType)); break; case 44 /* TokenMap.COMMA */: - if (buffer.length > 0) { - result.push(yieldResult(buffer, parseInfo)); - buffer = ""; + if (parseInfo.position < parseInfo.currentPosition) { + result.push(yieldResult(parseInfo)); } - result.push(yieldResult(next(parseInfo), parseInfo, EnumToken.CommaTokenType)); + next(parseInfo); + result.push(yieldResult(parseInfo, EnumToken.CommaTokenType)); break; case 36 /* TokenMap.DOLLAR */: - if (buffer.length > 0) { - result.push(yieldResult(buffer, parseInfo)); - buffer = ""; + if (parseInfo.position < parseInfo.currentPosition) { + result.push(yieldResult(parseInfo)); } if (match(parseInfo, "$=")) { - result.push(yieldResult(next(parseInfo, 2), parseInfo, EnumToken.EndMatchTokenType)); + next(parseInfo, 2); + result.push(yieldResult(parseInfo, EnumToken.EndMatchTokenType)); break; } - buffer += next(parseInfo); + next(parseInfo); break; case 126 /* TokenMap.TILDA */: - if (buffer.length > 0) { - result.push(yieldResult(buffer, parseInfo)); - buffer = ""; + if (parseInfo.position < parseInfo.currentPosition) { + result.push(yieldResult(parseInfo)); } if (match(parseInfo, "~=")) { - result.push(yieldResult(next(parseInfo, 2), parseInfo, EnumToken.IncludeMatchTokenType)); + next(parseInfo, 2); + result.push(yieldResult(parseInfo, EnumToken.IncludeMatchTokenType)); break; } - result.push(yieldResult(next(parseInfo), parseInfo, EnumToken.Tilda)); - buffer = ""; + next(parseInfo); + result.push(yieldResult(parseInfo, EnumToken.Tilda)); break; // case '^': case 94 /* TokenMap.CARET */: - if (buffer.length > 0) { - result.push(yieldResult(buffer, parseInfo)); - buffer = ""; + if (parseInfo.position < parseInfo.currentPosition) { + result.push(yieldResult(parseInfo)); } if (match(parseInfo, "^=")) { - result.push(yieldResult(next(parseInfo, 2), parseInfo, EnumToken.StartMatchTokenType)); + next(parseInfo, 2); + result.push(yieldResult(parseInfo, EnumToken.StartMatchTokenType)); break; } - buffer += next(parseInfo); + next(parseInfo); break; case 42 /* TokenMap.STAR */: - if (buffer.length > 0) { - result.push(yieldResult(buffer, parseInfo)); - buffer = ""; + if (parseInfo.position < parseInfo.currentPosition) { + result.push(yieldResult(parseInfo)); } if (match(parseInfo, "*=")) { - result.push(yieldResult(next(parseInfo, 2), parseInfo, EnumToken.ContainMatchTokenType)); + next(parseInfo, 2); + result.push(yieldResult(parseInfo, EnumToken.ContainMatchTokenType)); break; } - result.push(yieldResult(next(parseInfo), parseInfo, EnumToken.Star)); - buffer = ""; + next(parseInfo); + result.push(yieldResult(parseInfo, EnumToken.Star)); break; case 38 /* TokenMap.AMPERSAND */: - if (buffer.length > 0) { - result.push(yieldResult(buffer, parseInfo)); - buffer = ""; + if (parseInfo.position < parseInfo.currentPosition) { + result.push(yieldResult(parseInfo)); } - result.push(yieldResult(next(parseInfo), parseInfo, EnumToken.NestingSelectorTokenType)); - buffer = ""; + next(parseInfo); + result.push(yieldResult(parseInfo, EnumToken.NestingSelectorTokenType)); break; case 124 /* TokenMap.PIPE */: - if (buffer.length > 0) { - result.push(yieldResult(buffer, parseInfo)); - buffer = ""; + if (parseInfo.position < parseInfo.currentPosition) { + result.push(yieldResult(parseInfo)); } // '||' if (match(parseInfo, "||")) { - result.push(yieldResult(next(parseInfo, 2), parseInfo, EnumToken.ColumnCombinatorTokenType)); + next(parseInfo, 2); + result.push(yieldResult(parseInfo, EnumToken.ColumnCombinatorTokenType)); break; } else if (match(parseInfo, "|=")) { - result.push(yieldResult(next(parseInfo, 2), parseInfo, EnumToken.DashMatchTokenType)); + next(parseInfo, 2); + result.push(yieldResult(parseInfo, EnumToken.DashMatchTokenType)); break; } - result.push(yieldResult(next(parseInfo), parseInfo, EnumToken.Pipe)); - buffer = ""; + next(parseInfo); + result.push(yieldResult(parseInfo, EnumToken.Pipe)); break; case 33 /* TokenMap.EXCLAMATION */: - if (buffer.length > 0) { - result.push(yieldResult(buffer, parseInfo)); - buffer = ""; + if (parseInfo.position < parseInfo.currentPosition) { + result.push(yieldResult(parseInfo)); } if (match(parseInfo, "!important")) { - result.push(yieldResult(next(parseInfo, 10), parseInfo, EnumToken.ImportantTokenType)); - buffer = ""; + next(parseInfo, 10); + result.push(yieldResult(parseInfo, EnumToken.ImportantTokenType)); break; } - buffer += next(parseInfo); + next(parseInfo); break; case 47 /* TokenMap.SLASH */: - if (buffer.length > 0) { - result.push(yieldResult(buffer, parseInfo)); - buffer = ""; + if (parseInfo.position < parseInfo.currentPosition) { + result.push(yieldResult(parseInfo)); } if (!match(parseInfo, "/*")) { - result.push(yieldResult(next(parseInfo), parseInfo, SymbolsMapTokens[value])); + next(parseInfo); + result.push(yieldResult(parseInfo, SymbolsMapTokens[parseInfo.stream.slice(parseInfo.position, parseInfo.currentPosition)])); break; } - buffer += next(parseInfo, 2); - while ((value = next(parseInfo))) { - if (value == "*") { - buffer += value; + next(parseInfo, 2); + while ((charCode = next(parseInfo).charCodeAt(0)) == charCode) { + if (charCode == 42 /* TokenMap.STAR */) { if (match(parseInfo, "/")) { - result.push(yieldResult(buffer + next(parseInfo), parseInfo, EnumToken.CommentTokenType)); - buffer = ""; + next(parseInfo); + result.push(yieldResult(parseInfo, EnumToken.CommentTokenType)); break; } } - else { - buffer += value; - } + // else { + // buffer += value; + // } } - if (buffer.length > 0) { - result.push(yieldResult(buffer, parseInfo, EnumToken.BadCommentTokenType)); - buffer = ""; + if (parseInfo.position < parseInfo.currentPosition) { + result.push(yieldResult(parseInfo, EnumToken.BadCommentTokenType)); } break; case 62 /* TokenMap.GREATERTHAN */: - if (buffer.length > 0) { - result.push(yieldResult(buffer, parseInfo)); - buffer = ""; + if (parseInfo.position < parseInfo.currentPosition) { + result.push(yieldResult(parseInfo)); } if (match(parseInfo, ">=")) { - result.push(yieldResult(next(parseInfo, 2), parseInfo, EnumToken.GteTokenType)); + next(parseInfo, 2); + result.push(yieldResult(parseInfo, EnumToken.GteTokenType)); break; } - result.push(yieldResult(next(parseInfo), parseInfo, EnumToken.GtTokenType)); - buffer = ""; + next(parseInfo); + result.push(yieldResult(parseInfo, EnumToken.GtTokenType)); break; case 60 /* TokenMap.LOWERTHAN */: - if (buffer.length > 0) { - result.push(yieldResult(buffer, parseInfo)); - buffer = ""; + if (parseInfo.position < parseInfo.currentPosition) { + result.push(yieldResult(parseInfo)); } if (match(parseInfo, "<=")) { - result.push(yieldResult(next(parseInfo, 2), parseInfo, EnumToken.LteTokenType)); + next(parseInfo, 2); + result.push(yieldResult(parseInfo, EnumToken.LteTokenType)); break; } - buffer += next(parseInfo); + next(parseInfo); if (match(parseInfo, "!--")) { - buffer += next(parseInfo, 3); - while ((value = next(parseInfo))) { - buffer += value; - if (value == "-" && match(parseInfo, "->")) { + next(parseInfo, 3); + while ((charCode = next(parseInfo).charCodeAt(0)) == charCode) { + if (charCode == 45 /* TokenMap.MINUS */ && match(parseInfo, "->")) { break; } } - if (value === "") { - result.push(yieldResult(buffer, parseInfo, EnumToken.BadCdoTokenType)); + if (parseInfo.currentPosition >= endPosition) { + result.push(yieldResult(parseInfo, EnumToken.BadCdoTokenType)); } else { - result.push(yieldResult(buffer + next(parseInfo, 2), parseInfo, EnumToken.CDOCOMMTokenType)); + next(parseInfo, 2); + result.push(yieldResult(parseInfo, EnumToken.CDOCOMMTokenType)); } - buffer = ""; } break; case 35 /* TokenMap.HASH */: - if (buffer.length > 0) { - result.push(yieldResult(buffer, parseInfo)); - buffer = ""; + if (parseInfo.position < parseInfo.currentPosition) { + result.push(yieldResult(parseInfo)); } - buffer += next(parseInfo); + next(parseInfo); break; case 92 /* TokenMap.REVERSE_SOLIDUS */: + if (!yieldEOFToken && parseInfo.stream.length == parseInfo.currentPosition - parseInfo.offset + 1) { + break; + } next(parseInfo); // EOF - if (!(peek(parseInfo))) { + if (!peek(parseInfo)) { + if (!yieldEOFToken) { + break; + } // end of stream ignore \\ - if (buffer.length > 0) { - result.push(yieldResult(buffer, parseInfo)); - buffer = ""; + if (parseInfo.position < parseInfo.currentPosition) { + result.push(yieldResult(parseInfo)); } break; } - buffer += value + next(parseInfo); - // buffer += - // (parseInfo.offset == parseInfo.currentPosition - // ? parseInfo.buffer.slice(-1) - // : parseInfo.stream.charAt(parseInfo.currentPosition - parseInfo.offset - 1)) + value; + next(parseInfo); break; case 39 /* TokenMap.SINGLE_QUOTE */: case 34 /* TokenMap.DOUBLE_QUOTE */: - if (buffer.length > 0) { - result.push(yieldResult(buffer, parseInfo)); - buffer = ""; + if (parseInfo.position < parseInfo.currentPosition) { + result.push(yieldResult(parseInfo)); } result.push(...consumeString(parseInfo)); break; case 46 /* TokenMap.DOT */: const codepoint = parseInfo.stream - .charAt(parseInfo.currentPosition - parseInfo.offset + 2) + .charAt(parseInfo.currentPosition - parseInfo.offset + 1) .charCodeAt(0); - if (!isDigit(codepoint) && buffer !== "") { - result.push(yieldResult(buffer, parseInfo)); - buffer = next(parseInfo, 2); + if (!isDigit(codepoint) && parseInfo.position !== parseInfo.currentPosition) { + result.push(yieldResult(parseInfo)); + next(parseInfo, 2); break; } - buffer += next(parseInfo); + next(parseInfo); break; default: - buffer += next(parseInfo); + next(parseInfo); break; } - if (!yieldEOFToken && endPosition <= parseInfo.stream.length - parseInfo.currentPosition + parseInfo.offset) { + if (!yieldEOFToken && endPosition <= parseInfo.currentPosition - parseInfo.offset + 1) { break; } } if (yieldEOFToken) { - if (buffer.length > 0) { - result.push(yieldResult(buffer, parseInfo)); + if (parseInfo.position < parseInfo.currentPosition) { + result.push(yieldResult(parseInfo)); } - result.push(yieldResult("", parseInfo, EnumToken.EOFTokenType)); - } - else { - parseInfo.buffer = buffer; + result.push(yieldResult(parseInfo, EnumToken.EOFTokenType)); } parseInfo.time += performance.now() - startTime; return result; @@ -823,19 +929,17 @@ function tokenize(parseInfo, yieldEOFToken = true) { async function* tokenizeStream(input, parseInfo) { const decoder = new TextDecoder("utf-8"); const reader = input.getReader(); + parseInfo.stream = ""; while (true) { const { done, value } = await reader.read(); const stream = ArrayBuffer.isView(value) ? decoder.decode(value, { stream: true }) : value; if (!done) { parseInfo.source.append(stream); - if (typeof parseInfo.stream != "string") { - parseInfo.stream = stream; - } - else { - parseInfo.stream = (parseInfo.stream.slice(parseInfo.currentPosition - parseInfo.offset + 1) + - stream); - } - parseInfo.offset = parseInfo.currentPosition + 1; + parseInfo.stream = (parseInfo.stream.slice(parseInfo.position - parseInfo.offset) + stream); + parseInfo.offset = parseInfo.offset = parseInfo.position; + } + else { + parseInfo.stream = ""; } yield* tokenize(parseInfo, done); if (done) { diff --git a/dist/lib/parser/utils/at-rule-container.js b/dist/lib/parser/utils/at-rule-container.js index ed6c5c06..3fab097b 100644 --- a/dist/lib/parser/utils/at-rule-container.js +++ b/dist/lib/parser/utils/at-rule-container.js @@ -138,7 +138,8 @@ function parseAtRuleContainerQueryList(stream, context, options = {}) { success = false; errors.push({ action: "drop", - node: options.source.getSourceLocation(stream[i][LOC].sta), + node: stream[i], + location: options.source.getSourceLocation(stream[i][LOC].sta), message: ` is not allowed outside of parentheses`, }); break; diff --git a/dist/lib/parser/utils/selector.js b/dist/lib/parser/utils/selector.js index 090bf0c1..d3650aac 100644 --- a/dist/lib/parser/utils/selector.js +++ b/dist/lib/parser/utils/selector.js @@ -1,12 +1,13 @@ import { EnumToken, EnumAstNodeStatus } from '../../ast/types.js'; import { renderValue } from '../../renderer/render.js'; -import { LOC, ERRORS, STATE, TOKENS, pseudoElements, tokensfuncDefMap, combinators, PARENT } from '../../syntax/constants.js'; +import { LOC, ERRORS, STATE, TOKENS, pseudoElements, combinators, tokensfuncDefMap, PARENT } from '../../syntax/constants.js'; import { isHash } from '../../syntax/syntax.js'; import { getParsedSyntax, getSyntaxRule, getSyntaxConfig } from '../../validation/config.js'; import { matchAllSyntaxes, createValidationContext, trimArray, matchSelectorSyntax } from '../../validation/match.js'; import { ValidationSyntaxGroupEnum, ValidationTokenEnum } from '../../validation/parser/typedef.js'; import { splitTokenList } from '../../validation/utils/list.js'; import { trimWhiteSpace } from '../parse.js'; +import { equalsIgnoreCase } from './text.js'; /** * parse selector @@ -48,7 +49,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 }), "")); @@ -58,7 +59,7 @@ function parseSelector(tokens, context, options, errors) { chi: [], [LOC]: { ...tokens[0][LOC], - end: tokens[tokens.length - 1]?.[LOC]?.end ?? tokens[0]?.[LOC]?.end + end: tokens[tokens.length - 1]?.[LOC]?.end ?? tokens[0]?.[LOC]?.end, }, [TOKENS]: tokens.length === 0 ? null : tokens, [STATE]: result.success ? EnumAstNodeStatus.Validated : EnumAstNodeStatus.Invalid, @@ -151,16 +152,70 @@ function parseSelector(tokens, context, options, errors) { } } if (tokens[i].typ == EnumToken.ColorTokenType) { - // if (isIdent((tokens[i] as ColorToken).val)) { - // Object.assign(tokens[i], { - // typ: EnumToken.IdenTokenType, - // }); - // } else if (isHash(tokens[i].val)) { Object.assign(tokens[i], { typ: EnumToken.HashTokenType, }); } + else { + return { + typ: EnumToken.RuleNodeType, + sel: [ + ...tokens + .reduce((acc, curr, index, array) => { + // if (curr.typ == EnumToken.CommentTokenType) { + // return acc; + // } + if (curr.typ == EnumToken.WhitespaceTokenType) { + if (trimWhiteSpace.includes(array[index - 1]?.typ) || + trimWhiteSpace.includes(array[index + 1]?.typ) || + combinators.includes(array[index - 1]?.val) || + combinators.includes(array[index + 1]?.val)) { + return acc; + } + } + let t = renderValue(curr, { minify: false }); + if (t == ",") { + acc.push([]); + } + else { + acc[acc.length - 1].push(t); + } + return acc; + }, [[]]) + .reduce((acc, curr) => { + let i = 0; + for (; i < curr.length; i++) { + if (i + 1 < curr.length && curr[i] == "*") { + if (curr[i] == "*") { + let index = curr[i + 1] == " " ? 2 : 1; + if (![">", "~", "+"].includes(curr[index])) { + curr.splice(i, index); + } + } + } + } + acc.set(curr.join(""), curr); + return acc; + }, uniq) + .keys(), + ].join(","), + chi: [], + [LOC]: { + ...tokens[0][LOC], + end: tokens[tokens.length - 1][LOC].end, + }, + [TOKENS]: tokens, + [STATE]: EnumAstNodeStatus.Invalid, + [ERRORS]: [ + { + action: "drop", + node: tokens[i], + message: "invalid hash id", + }, + ], + }; + } } } const result = matchSelectorSyntax(tokens, errors, options, nested === true); @@ -212,6 +267,29 @@ function parseSelector(tokens, context, options, errors) { func.val == ":nth-last-child" || func.val == ":nth-of-type" || func.val == ":nth-last-of-type") { + const list = []; + let index; + for (index = 0; index < func.chi.length; index++) { + if (func.chi[index].typ == EnumToken.CommentTokenType || func.chi[index].typ == EnumToken.WhitespaceTokenType) { + continue; + } + if (func.chi[index].typ == EnumToken.IdenTokenType && equalsIgnoreCase('of', func.chi[index].val)) { + index--; + break; + } + list.push(func.chi[index]); + } + if (list.length == 3) { + if (list[0].typ == EnumToken.IdenTokenType && ('n' == list[0].val || '-n' == list[0].val || '+n' == list[0].val)) { + if (list[1].typ == EnumToken.NextSiblingCombinatorTokenType) { + if (list[2].typ == EnumToken.NumberTokenType && (0 == list[2].val)) { + list[0].val = 'n'; + func.chi.splice(0, index, list[0]); + break; + } + } + } + } const token = func.chi.find((t) => t.typ != EnumToken.WhitespaceTokenType && t.typ != EnumToken.CommentTokenType); if (token?.typ == EnumToken.IdenTokenType || token?.typ == EnumToken.LiteralTokenType) { if (token.typ == EnumToken.IdenTokenType && @@ -301,7 +379,7 @@ function parseSelector(tokens, context, options, errors) { // } else { // Object.assign(token, { typ: EnumToken.NumberTokenType, val: b1 }); // } - // } else + // } else if (b1 === 0) { Object.assign(token, Math.abs(a1) === 1 ? { @@ -372,7 +450,7 @@ function parseSelector(tokens, context, options, errors) { // func.chi.splice(0, i); // } // break; - // } else + // } else if (num.val === 0) { func.chi.splice(index + 1, i - index); if (token.val < 0) { diff --git a/dist/lib/renderer/render.js b/dist/lib/renderer/render.js index 23d1108e..fe00492a 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 ? { sources: [], maps: [] } : 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,12 @@ function doRender(data, options = {}, mapping) { }, }; if (sourcemap != null) { + let source; + for (const sourceId of sourcemaps.sources) { + source = options.sourcesMap.get(sourceId); + sourcemap.addSourceContent(source.id, source.getFileName(), source.getContent()); + } + sourcemap.add(...sourcemaps.maps); result.map = sourcemap; if (options.sourcemap === "inline") { result.code += `\n/*# sourceMappingURL=${result.map.toUrl()} */`; @@ -115,37 +127,93 @@ 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; + 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]; + 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]; + } + if (!sourcemaps.sources.includes(srcId)) { + sourcemaps.sources.push(srcId); + } + sourcemaps.maps.push([newLine, newColumn, srcId, ...offsets]); } - 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]; + } + if (!sourcemaps.sources.includes(srcId)) { + sourcemaps.sources.push(srcId); + } + sourcemaps.maps.push([newLine, newColumn, srcId, ...offsets]); + } } - 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 +243,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 +254,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 +274,47 @@ 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 lineMapLength = linesMap ? linesMap.getLineStarts().length : 0; + 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 +323,63 @@ 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); + if (!sourcemaps.sources.includes(node[LOC].srcId)) { + sourcemaps.sources.push(node[LOC].srcId); + } + sourcemaps.maps.push([ + ...linesMap.getOffsets(sourceLocation.end - str.length + options.newLine.length + indentSub.length), + node[LOC].srcId, + ...source.getOffsets(node[LOC].sta), + ]); + } } - return `${css}${options.newLine}${indentSub}${str}`; - }, ""); - if (options.removeEmpty && children === "") { - return ""; } if (children.endsWith(";")) { children = children.slice(0, -1); + sourceLocation.end--; + } + if (options.removeEmpty && children === "") { + if (sourcemaps != null) { + sourceLocation.end -= prelude.length; + linesMap.getLineStarts().length = lineMapLength; + } + return ""; } - 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); + const end = options.newLine + indent + `}`; + if (sourcemaps != null) { + move(sourceLocation, linesMap, end); } - 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: + return prelude + children + end; default: return ""; } @@ -316,6 +388,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) { @@ -392,8 +467,8 @@ function renderValue(token, options = {}, cache = Object.create(null), reducer, return " + "; case EnumToken.Sub: return " - "; - case EnumToken.Star: case EnumToken.UniversalSelectorTokenType: + case EnumToken.Star: case EnumToken.Mul: return "*"; case EnumToken.Div: @@ -1232,11 +1307,11 @@ function renderValue(token, options = {}, cache = Object.create(null), reducer, case EnumToken.OrTokenType: return "or"; case EnumToken.InvalidMediaQueryTokenType: - // case EnumToken.InvalidDeclarationNodeType: case EnumToken.InvalidCommentTokenType: case EnumToken.BadCommentTokenType: case EnumToken.BadCdoTokenType: case EnumToken.BadStringTokenType: + case EnumToken.BadUrlTokenType: case EnumToken.EOFTokenType: return ""; default: diff --git a/dist/lib/renderer/sourcemap/lib/codec.js b/dist/lib/renderer/sourcemap/lib/codec.js new file mode 100644 index 00000000..00fb734a --- /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..ff28f482 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,198 @@ 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 + */ + constructor(sourcemaps) { + if (typeof sourcemaps === "string") { + if (sourcemaps.startsWith("data:")) { + let encoding = ""; + let offset = sourcemaps.indexOf(",") + 1; + if (offset == 0) { + offset = sourcemaps.lastIndexOf(";") + 1; + } + else { + encoding = sourcemaps.slice(sourcemaps.lastIndexOf(";") + 1, offset - 1); + } + if (encoding == "base64") { + sourcemaps = atob(sourcemaps.slice(offset)); + } + else { + sourcemaps = decodeURIComponent(sourcemaps.slice(offset)); + } + } + 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 source + * @param id + * @param fileName + * @param content + * @returns + */ + addSourceContent(id, fileName, content) { + if (this.sourcesMap.includes(id)) { + return; + } + this.sourcesMap[this.sourcesMap.length] = id; + this.sources[this.sources.length] = fileName || null; + this.sourcesContent[this.sourcesContent.length] = content || null; + } + /** + * Add all location + * @param maps + * @throws */ - 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); + add(...maps) { + let srcIndex; + if (typeof maps[0] === "number") { + maps = [maps]; + } + for (let [newLine, newColumn, srcId, ln, col] of maps) { + const key = `${srcId}:${ln}:${col}:${newLine}:${newColumn}`; + if (this.keys.has(key)) { + continue; + } + this.keys.add(key); + const line = newLine - 1; + let record; + if (line > this.line) { + this.line = line; + } + srcIndex = this.sourcesMap.indexOf(srcId); + if (srcIndex == -1) { + throw new Error(`Source file ${srcId} not added to sourcemap`); } - this.sourcesMap.push(srcId); - this.sources.push(sourceFileName || null); + if (!this.map.has(line)) { + record = [Math.max(0, newColumn - 1), srcIndex, 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], srcIndex - 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; } - const line = newLine - 1; - let record; - if (line > this.line) { - this.line = line; + } + /** + * compute original positions + */ + computePositions() { + this.reverseMap.clear(); + let sourceFileIndex = 0; // second field + let sourceCodeLine = 0; // third field + let sourceCodeColumn = 0; // fourth field + // let nameIndex: number = 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); + // nameIndex not needed + // 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.map.has(line)) { - record = [Math.max(0, newColumn - 1), this.sourcesMap.indexOf(srcId), ln - 1, col - 1]; - this.map.set(line, [record]); + } + /** + * retrieve original sources, lines and columns + * @param line generated line + * @param column generated column + */ + find(line, column) { + if (this.reverseMap.size == 0) { + this.computePositions(); } - 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.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,6 +253,7 @@ class SourceMap { return { version: this.version, sources: this.sources.slice(), + sourcesContent: this.sourcesContent?.slice(), mappings: mappings.join(";"), }; } diff --git a/dist/lib/syntax/syntax.js b/dist/lib/syntax/syntax.js index 726c3421..aed77b24 100644 --- a/dist/lib/syntax/syntax.js +++ b/dist/lib/syntax/syntax.js @@ -591,7 +591,6 @@ function isColor(token, errors) { action: "drop", message: `Invalid color`, node: token, - // location: options.source!.getSourLocation(token[LOC]!.sta), }); return false; } @@ -614,31 +613,6 @@ function isColor(token, errors) { if (children.length == 4 || (isRelative && children.length == 6)) { return true; } - if (children.length == 8 || children.length == 6) { - children.at(-2); - children.at(-1); - // @ts-ignore - // if ( - // ((children.length > 6 || !isRelative) && sep.typ != EnumToken.LiteralTokenType) || - // (sep as LiteralToken).val != "/" - // ) { - // return false; - // } - // if (alpha.typ == EnumToken.IdenTokenType && (alpha as IdentToken).val != "none") { - // return false; - // } else { - // // @ts-ignore - // if (alpha.typ == EnumToken.PercentageTokenType) { - // if (+(alpha as PercentageToken).val < 0 || +(alpha as PercentageToken).val > 100) { - // return false; - // } - // } else if (alpha.typ == EnumToken.NumberTokenType) { - // if (+(alpha as NumberToken).val < 0 || +(alpha as NumberToken).val > 1) { - // return false; - // } - // } - // } - } return true; } // @ts-ignore @@ -655,9 +629,6 @@ function isColor(token, errors) { } return acc; }, [[]]); - // if (children.length === 0 || children[0].length === 0) { - // return false; - // } let j = 0; let k = 0; if (children[j][0].typ === EnumToken.IdenTokenType && @@ -691,50 +662,13 @@ function isColor(token, errors) { k++; } } - // else { - // return false; - // } } else { k++; } } - // else { - // return false; - // } - // if (k != children[j].length) { - // return false; - // } j++; } - // while (j < children.length) { - // if (children[j].length > 2) { - // return false; - // } - // if ( - // !isColor(children[j][0]) && - // !( - // children[j][0].typ == EnumToken.WildCardFunctionTokenType && - // equalsIgnoreCase("calc", (children[j][0] as FunctionToken).val) - // ) - // ) { - // return false; - // } - // if (children[j][0].typ == EnumToken.WildCardFunctionTokenType) { - // const result = matchAllSyntaxes( - // getParsedSyntax(ValidationSyntaxGroupEnum.Syntaxes, "calc()") as ValidationFunctionToken[], - // createValidationContext([children[j][0]]), - // {}, - // ); - // if (!result.success) { - // return false; - // } - // } - // if (children[j].length > 1 && !isPercentageToken(children[j][1])) { - // return false; - // } - // j++; - // } return true; } else { @@ -747,21 +681,6 @@ function isColor(token, errors) { // @ts-ignore for (const v of token.chi) { if (v.typ == EnumToken.IdenTokenType) { - // if (isColor(v)) { - // continue; - // } - // if (!(keywords.includes(v.val) || COLORS_NAMES[v.val.toLowerCase()] != null)) { - // return false; - // } - // if (keywords.includes(v.val)) { - // if (isLegacySyntax) { - // return false; - // } - // // @ts-ignore - // if (v.val == "from" && ["rgba", "hsla"].includes((token as ColorToken).val)) { - // return false; - // } - // } continue; } if (v.typ === EnumToken.MathFunctionTokenType || @@ -769,20 +688,6 @@ function isColor(token, errors) { colorsFunc.includes(v.val)) { continue; } - // if ( - // ![ - // EnumToken.ColorTokenType, - // EnumToken.IdenTokenType, - // EnumToken.NumberTokenType, - // EnumToken.AngleTokenType, - // EnumToken.PercentageTokenType, - // EnumToken.CommaTokenType, - // EnumToken.WhitespaceTokenType, - // EnumToken.LiteralTokenType, - // ].includes(v.typ) - // ) { - // return false; - // } } } return true; @@ -838,63 +743,12 @@ function parseColor(token) { } if (token.val == "color") { let index = token.chi.indexOf(tk); - // if ((token as ColorToken).cal == "rel") { - // for (let k = 0; k < (token as ColorToken).chi!.length; k++) { - // if (EnumToken.DashedIdenTokenType == (token as ColorToken).chi![k].typ) { - // index = k; - // break; - // } - // } - // } if (EnumToken.DashedIdenTokenType == token?.chi?.[index]?.typ) { token.kin = ColorType.CUSTOM_COLOR; } } } - // return token; } - // @ts-ignore - // token.typ = EnumToken.ColorTokenType; - // // @ts-ignore - // (token as ColorToken).kin = ColorType[token.val.replaceAll("-", "_").toUpperCase()]; - // if (!("chi" in token)) { - // const val: string = (token as ColorToken).val.toLowerCase(); - // if (val == "currentcolor" || val == "transparent" || val in COLORS_NAMES) { - // (token as ColorToken).kin = ColorType.LIT; - // } else if (isHexColor(val)) { - // (token as ColorToken).kin = ColorType.HEX; - // } - // const tk = (token as ColorToken).chi?.find( - // (t) => t.typ !== EnumToken.WhitespaceTokenType && t.typ !== EnumToken.CommentTokenType, - // ); - // if (tk?.typ === EnumToken.IdenTokenType && (tk as IdentToken).val === "from") { - // (token as ColorToken).cal = "rel"; - // } else if ((token as ColorToken).val == "color-mix" && (tk as IdentToken).val == "in") { - // (token as ColorToken).cal = "mix"; - // } else if ((token as ColorToken).val == "color") { - // (token as ColorToken).cal = "col"; - // } - // return token; - // } - // // @ts-ignore - // if (((token as ColorToken).chi as Token[])[0].typ == EnumToken.IdenTokenType) { - // // @ts-ignore - // if (((token as ColorToken).chi as Token[])[0].val == "from") { - // // @ts-ignore - // (token as ColorToken).cal = "rel"; - // } - // // @ts-ignore - // else if ((token as ColorToken).val == "color-mix" && ((token as ColorToken).chi as Token[])[0].val == "in") { - // // @ts-ignore - // (token as ColorToken).cal = "mix"; - // } else { - // // @ts-ignore - // if ((token as ColorToken).val == "color") { - // // @ts-ignore - // (token as ColorToken).cal = "col"; - // } - // } - // } return token; } function isLetter(codepoint) { @@ -940,6 +794,9 @@ const isIdent = memoize(function (name) { return false; } if (codepoint == REVERSE_SOLIDUS) { + if (i + 1 > j) { + return false; + } codepoint = name.charCodeAt(i + 1); // if (!isIdentCodepoint(codepoint)) { // return false; @@ -967,18 +824,24 @@ const isIdent = memoize(function (name) { } return true; }); +function isNonPrintable(codepoint) { + // null -> backspace + return ((codepoint >= 0 && codepoint <= 0x8) || + // tab + codepoint == 0xb || + // delete + codepoint == 0x7f || + (codepoint >= 0xe && codepoint <= 0x1f)); +} function isPseudo(name) { return (name.charAt(0) == ":" && ((name.endsWith("(") && isIdent(name.charAt(1) == ":" ? name.slice(2, -1) : name.slice(1, -1))) || isIdent(name.charAt(1) == ":" ? name.slice(2) : name.slice(1)))); } function isHash(name) { - return name.charAt(0) == "#" && isIdent(name.charAt(1)); + return name.charAt(0) == "#" && isIdentStart(name.charCodeAt(1)); } const isNumber = memoize(function (name) { - // if (name.length == 0) { - // return false; - // } let codepoint = name.charCodeAt(0); let i = 0; const j = name.length; @@ -1225,4 +1088,4 @@ function toPrecisionAngle(angle, precision = colorPrecision, correctValue = true return angle; } -export { dimensionUnits, isAngle, isColor, isDigit, isFlex, isFrequency, isFunction, isHash, isHexColor, isIdent, isIdentCodepoint, isIdentColor, isIdentStart, isLength, isLetter, isNewLine, isNumber, isPercentage, isPolarColorspace, isPseudo, isRectangularOrthogonalColorspace, isResolution, isTime, isWhiteSpace, length2Px, minifyNumber, parseColor, parseDimension, pseudoAliasMap, reduceColorStops, reduceConicColorStops, reducegradientBackgroundPosition, renamedStandardProperties, toPrecisionAngle, toPrecisionValue }; +export { dimensionUnits, isAngle, isColor, isDigit, isFlex, isFrequency, isFunction, isHash, isHexColor, isIdent, isIdentCodepoint, isIdentColor, isIdentStart, isLength, isLetter, isNewLine, isNonPrintable, isNumber, isPercentage, isPolarColorspace, isPseudo, isRectangularOrthogonalColorspace, isResolution, isTime, isWhiteSpace, length2Px, minifyNumber, parseColor, parseDimension, pseudoAliasMap, reduceColorStops, reduceConicColorStops, reducegradientBackgroundPosition, renamedStandardProperties, toPrecisionAngle, toPrecisionValue }; diff --git a/dist/lib/validation/match.js b/dist/lib/validation/match.js index 34ca59ac..6ee6bc51 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 { @@ -941,6 +990,7 @@ function matchSyntax(syntaxes, context, options) { return result; } if (tokensfuncDefMap.has(token.typ) && + // @ts-ignore token.typ === EnumToken.WildCardFunctionTokenDefType) { const range = trimArray(context.peekRange()); result = matchSyntax(getParsedSyntax(ValidationSyntaxGroupEnum.Syntaxes, token.val + "()")?.[0]?.chi, createValidationContext(range.slice(1, -1)), options); @@ -971,7 +1021,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 +1529,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 +1572,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 +1597,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; @@ -1920,7 +1991,9 @@ function matchProperty(property, context, options) { // ) // ) { const newRange = range.map((t) => cloneNode(t, true)); + // @ts-ignore parseTokens(newRange, { parseColor: true }, errors); + // @ts-ignore success = newRange.length == 1 && isColor(newRange[0], errors); if (success) { context.update(range.at(-1)); @@ -2265,6 +2338,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..4433f7b4 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'; @@ -24,6 +25,7 @@ export { cloneNode } from './lib/ast/clone.js'; export { replaceNodeOrValue } from './lib/parser/utils/token.js'; export { SourceMap } from './lib/renderer/sourcemap/sourcemap.js'; export { FeatureWalkMode } from './lib/ast/features/type.js'; +export { getNodeProperty, setNodeProperty } from './lib/ast/node.js'; /** * Load file or url @@ -108,7 +110,7 @@ function render(data, options = {}, mapping) { return doRender(data, Object.assign(options, { resolve, dirname, cwd: options.cwd ?? resolve$1() }), mapping); } /** - * Parse css file + * Parse CSS file * @param file url or path * @param options * @param asStream load file as stream @@ -134,17 +136,17 @@ function render(data, options = {}, mapping) { */ const parseFile = deprecate(async (file, options = {}, asStream = false) => parse({ file, asStream, ...options }), "parseFile is deprecated, use parse instead as parse({file, asStream, ...options})"); /** - * Parse css + * Parse CSS * @param args * * 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, @@ -183,26 +188,24 @@ function parseSync(...args) { time: 0, source: options.source, position: 0, - currentPosition: -1, + currentPosition: 0, }; 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'; * * // css string - * const result = await transform(css); + * const result = transformSync(css); * console.log(result.code); * ``` * + * @param args */ function transformSync(...args) { let options; @@ -218,7 +221,15 @@ function transformSync(...args) { stream = input; } options ??= {}; - options = { minify: true, removeEmpty: true, removeCharset: true, ...options }; + if (options.minify == null) { + options.minify = true; + } + if (options.removeEmpty == null) { + options.removeEmpty = true; + } + if (options.removeCharset == null) { + options.removeCharset = true; + } const startTime = performance.now(); const parseResult = parseSync(stream, options); let mapping = null; @@ -251,7 +262,7 @@ function transformSync(...args) { }; } /** - * Parse css + * Parse CSS * @param args * * @throws Error file not found @@ -314,7 +325,7 @@ async function parse(...args) { } options ??= {}; options.src ??= ""; - options.sourcesMap ??= new Map; + options.sourcesMap ??= new Map(); Object.assign(options, { load, resolve, @@ -335,15 +346,12 @@ async function parse(...args) { time: 0, source: options.source, position: 0, - currentPosition: -1, + currentPosition: 0, }; - 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 @@ -372,9 +380,7 @@ const transformFile = deprecate(async (file, options = {}, asStream = false) => ...options, }), "transformFile is deprecated, use transform instead as transform({file, asStream, ...options})"); /** - * Transform css - * @param css - * @param options + * Transform CSS * * Parsing a string * @@ -413,6 +419,7 @@ const transformFile = deprecate(async (file, options = {}, asStream = false) => * * console.log(result.code); * ``` + * @param args */ async function transform(...args) { let options; @@ -433,7 +440,15 @@ async function transform(...args) { } } options ??= {}; - options = { minify: true, removeEmpty: true, removeCharset: true, ...options }; + if (options.minify == null) { + options.minify = true; + } + if (options.removeEmpty == null) { + options.removeEmpty = true; + } + if (options.removeCharset == null) { + options.removeCharset = true; + } const startTime = performance.now(); return parse(stream, options).then((parseResult) => { let mapping = null; diff --git a/dist/utils/sync.d.ts b/dist/utils/sync.d.ts new file mode 100644 index 00000000..64da9f8a --- /dev/null +++ b/dist/utils/sync.d.ts @@ -0,0 +1,16 @@ +import type { ParseResult, ParserOptions, ParserSyncOptions } from "../@types/index.d.ts"; +/** + * parse result. process input sourcemap + * @param result + * @param options + * @returns + * @private + */ +export declare function parseResult(result: ParseResult, options: ParserOptions): ParseResult; +/** + * + * @param options + * @param prefix + * @private + */ +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..04900102 --- /dev/null +++ b/dist/utils/sync.js @@ -0,0 +1,52 @@ +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=")) { + options.source.setInputSourceMap(token.val.slice(21, -2).trim()); + } + } + } + if (options.module) { + const { revMapping, ...res } = result; + return res; + } + return result; +} +/** + * + * @param options + * @param prefix + * @private + */ +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..53779329 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'; @@ -18,6 +19,7 @@ export { cloneNode } from './lib/ast/clone.js'; export { replaceNodeOrValue } from './lib/parser/utils/token.js'; export { SourceMap } from './lib/renderer/sourcemap/sourcemap.js'; export { FeatureWalkMode } from './lib/ast/features/type.js'; +export { getNodeProperty, setNodeProperty } from './lib/ast/node.js'; /** * Load file or url @@ -27,7 +29,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 +71,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); @@ -98,7 +100,7 @@ function render(data, options = {}, mapping) { }), mapping); } /** - * Parse css file + * Parse CSS file * @param file url or path * @param options * @param asStream load file as stream @@ -126,17 +128,17 @@ async function parseFile(file, options = {}, asStream = false) { return parse({ file, asStream, ...options }); } /** - * Parse css + * Parse CSS * @param args * * 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, @@ -177,26 +182,24 @@ function parseSync(...args) { time: 0, source: options.source, position: 0, - currentPosition: -1, + currentPosition: 0, }; 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 */ function transformSync(...args) { let options; @@ -245,9 +248,7 @@ function transformSync(...args) { }; } /** - * Parse css - * @param stream - * @param options + * Parse CSS * * Example: * @@ -271,6 +272,7 @@ function transformSync(...args) { * * console.log(result.ast); * ``` + * @param args */ async function parse(...args) { let options; @@ -292,7 +294,7 @@ async function parse(...args) { } options ??= {}; options.src ??= ""; - options.sourcesMap ??= new Map; + options.sourcesMap ??= new Map(); Object.assign(options, { load, resolve, @@ -314,15 +316,12 @@ async function parse(...args) { time: 0, source: options.source, position: 0, - currentPosition: -1, + currentPosition: 0, }; - 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 @@ -352,9 +351,7 @@ async function transformFile(file, options = {}, asStream = false) { }); } /** - * Transform css - * @param css - * @param options + * Transform CSS * * Example: * @@ -372,6 +369,7 @@ async function transformFile(file, options = {}, asStream = false) { * * console.log(result.code); * ``` + * @param args */ async function transform(...args) { let options; diff --git a/files/assets/typedoc-custom.css b/files/assets/typedoc-custom.css index 01821819..2cb11e7a 100644 --- a/files/assets/typedoc-custom.css +++ b/files/assets/typedoc-custom.css @@ -78,6 +78,22 @@ 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; + } } + + +.tsd-kind-icon { + width: 16px; +} \ No newline at end of file diff --git a/files/ast.md b/files/ast.md index d57df1a0..7e9197f0 100644 --- a/files/ast.md +++ b/files/ast.md @@ -416,4 +416,4 @@ button { ``` ------ -[← Syntax Lowering](./syntax-lowering.md) | [Utility Functions →](./utilities.md) \ No newline at end of file +[← Prefix Removal](./prefix-removal.md) | [Utility Functions →](./utilities.md) \ No newline at end of file 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..42e3493e 100644 --- a/files/index.md +++ b/files/index.md @@ -9,7 +9,10 @@ children: - ./css-module.md - ./minification.md - ./transform.md + - ./sourcemap.md + - ./plugins.md - ./syntax-lowering.md + - ./prefix-removal.md - ./ast.md - ./utilities.md --- @@ -22,7 +25,10 @@ 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) +- [Prefix Removal](./prefix-removal.md) - [Ast Manipulation](./ast.md) - [Utility Functions](./utilities.md) diff --git a/files/minification.md b/files/minification.md index f58f8b7d..2d0c7dc0 100644 --- a/files/minification.md +++ b/files/minification.md @@ -657,202 +657,6 @@ Output: } ``` -### CSS prefix removal (Experimental) - -This feature is disabled by default. - -```ts - -import {transform} from '@tbela99/css-parser'; - -const css = ` - -::-webkit-input-placeholder { - color: gray; - } - - ::-moz-placeholder { - color: gray; - } - - :-ms-input-placeholder { - color: gray; - } - - ::-ms-input-placeholder { - color: gray; - } - - ::placeholder { - color: gray; - } - - @supports selector(:-ms-input-placeholder) { - - - :-ms-input-placeholder { - color: gray; - } - } - -@media (-webkit-min-device-pixel-ratio: 2), (-o-min-device-pixel-ratio: 2/1), (min-resolution: 2dppx) { - .image { - background-image: url(image@2x.png); - } - - } - - - @-webkit-keyframes bar { - - from, 0% { - - height: 10px; - } - } - - @keyframes bar { - - from, 0% { - - height: 10px; - } - } - .example { - - -moz-animation: bar 1s infinite; - display: -ms-grid; - display: grid; - -webkit-transition: all .5s; - -o-transition: all .5s; - transition: all .5s; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; - background: -o-linear-gradient(top, white, black); - background: -webkit-gradient(linear, left top, left bottom, from(white), to(black)); - background: linear-gradient(to bottom, white, black); - } - - .site{ - display:-ms-grid; - display:grid; -ms-grid-columns:2fr 1fr; - grid-template-columns:2fr 1fr; - grid-template-areas:"header header" - "title sidebar" - "main sidebar" - "footer footer"; - } - .site > *{padding:30px; color:#fff; font-size:20px;} - .mastheader{ - -ms-grid-row:1; - -ms-grid-column:1; - -ms-grid-column-span:2; - grid-area:header; - } - .page-title{ - -ms-grid-row:2; - -ms-grid-column:1; - grid-area:title; - } - .main-content{ - -ms-grid-row:3; - -ms-grid-column:1; - grid-area:main; - } - .sidebar{ - -ms-grid-row:2; - -ms-grid-row-span:2; - -ms-grid-column:2; - grid-area:sidebar; - } - .footer{ - -ms-grid-row:4; - -ms-grid-column:1; - -ms-grid-column-span:2; - grid-area:footer; - } -`; -const result = await transform(css, { - - beautify: true, - removePrefix: true - } -); - -console.log(result.code); -``` - -Output: - -```css -::placeholder { - color: grey -} -@supports selector(::placeholder) { - ::placeholder { - color: grey - } -} -@media (min-resolution:2x) { - .image { - background-image: url(image@2x.png) - } -} -@keyframes bar { - 0% { - height: 10px - } -} -.site,.example { - display: grid -} -.site { - grid-template-columns: 2fr 1fr; - grid-template-areas: "header header""title sidebar""main sidebar""footer footer" -} -.example { - animation: bar 1s infinite; - transition: .5s; - user-select: none; - background: linear-gradient(#fff,#000) -} -.site>* { - padding: 30px; - color: #fff; - font-size: 20px -} -.mastheader { - grid-row: 1; - grid-column: 1; - grid-column-end: 2; - grid-area: header -} -.page-title { - grid-row: 2; - grid-column: 1; - grid-area: title -} -.main-content { - grid-row: 3; - grid-column: 1; - grid-area: main -} -.sidebar { - grid-row: 2; - grid-row-end: 2; - grid-column: 2; - grid-area: sidebar -} -.footer { - grid-row: 4; - grid-column: 1; - grid-column-end: 2; - grid-area: footer -} -``` - ### Shorthands Shorthand properties are computed and default values are removed. @@ -891,7 +695,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/prefix-removal.md b/files/prefix-removal.md new file mode 100644 index 00000000..d88aac49 --- /dev/null +++ b/files/prefix-removal.md @@ -0,0 +1,205 @@ +--- +title: Prefix Removal +group: Documents +category: Guides +--- + +## Prefix Removal + +Vendor prefixes can be removed by enabling the `removePrefix` flag. + +```ts + +import {transformSync} from '@tbela99/css-parser'; + +const css = ` + +::-webkit-input-placeholder { + color: gray; + } + + ::-moz-placeholder { + color: gray; + } + + :-ms-input-placeholder { + color: gray; + } + + ::-ms-input-placeholder { + color: gray; + } + + ::placeholder { + color: gray; + } + + @supports selector(:-ms-input-placeholder) { + + + :-ms-input-placeholder { + color: gray; + } + } + +@media (-webkit-min-device-pixel-ratio: 2), (-o-min-device-pixel-ratio: 2/1), (min-resolution: 2dppx) { + .image { + background-image: url(image@2x.png); + } + + } + + + @-webkit-keyframes bar { + + from, 0% { + + height: 10px; + } + } + + @keyframes bar { + + from, 0% { + + height: 10px; + } + } + .example { + + -moz-animation: bar 1s infinite; + display: -ms-grid; + display: grid; + -webkit-transition: all .5s; + -o-transition: all .5s; + transition: all .5s; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + background: -o-linear-gradient(top, white, black); + background: -webkit-gradient(linear, left top, left bottom, from(white), to(black)); + background: linear-gradient(to bottom, white, black); + } + + .site{ + display:-ms-grid; + display:grid; + -ms-grid-columns:2fr 1fr; + grid-template-columns:2fr 1fr; + grid-template-areas:"header header" + "title sidebar" + "main sidebar" + "footer footer"; + } + .site > *{padding:30px; color:#fff; font-size:20px;} + .mastheader{ + -ms-grid-row:1; + -ms-grid-column:1; + -ms-grid-column-span:2; + grid-area:header; + } + .page-title{ + -ms-grid-row:2; + -ms-grid-column:1; + grid-area:title; + } + .main-content{ + -ms-grid-row:3; + -ms-grid-column:1; + grid-area:main; + } + .sidebar{ + -ms-grid-row:2; + -ms-grid-row-span:2; + -ms-grid-column:2; + grid-area:sidebar; + } + .footer{ + -ms-grid-row:4; + -ms-grid-column:1; + -ms-grid-column-span:2; + grid-area:footer; + } +`; +const result = await transformSync(css, { + + beautify: true, + removePrefix: true + } +); + +console.log(result.code); +``` + +Output: + +```css +::placeholder { + color: grey +} +@supports selector(::placeholder) { + ::placeholder { + color: grey + } +} +@media (min-resolution:2x) { + .image { + background-image: url(image@2x.png) + } +} +@keyframes bar { + 0% { + height: 10px + } +} +.site,.example { + display: grid +} +.site { + grid-template-columns: 2fr 1fr; + grid-template-areas: "header header""title sidebar""main sidebar""footer footer" +} +.example { + animation: bar 1s infinite; + transition: .5s; + user-select: none; + background: linear-gradient(#fff,#000) +} +.site>* { + padding: 30px; + color: #fff; + font-size: 20px +} +.mastheader { + grid-row: 1; + grid-column: 1; + grid-column-end: 2; + grid-area: header +} +.page-title { + grid-row: 2; + grid-column: 1; + grid-area: title +} +.main-content { + grid-row: 3; + grid-column: 1; + grid-area: main +} +.sidebar { + grid-row: 2; + grid-row-end: 2; + grid-column: 2; + grid-area: sidebar +} +.footer { + grid-row: 4; + grid-column: 1; + grid-column-end: 2; + grid-area: footer +} +``` + +------ +[← Syntax Lowering](./syntax-lowering.md) | [Ast Manipulation →](./ast.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..e85b7f7d 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) | [Prefix Removal →](./prefix-removal.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/files/validation.md b/files/validation.md index 665fe144..6ddd983b 100644 --- a/files/validation.md +++ b/files/validation.md @@ -70,24 +70,25 @@ The following example prints the validation state of each node, along with any v ```ts -import {EnumToken, EnumAstNodeStatus, transform} from "@tbela99/css-parser"; +import {EnumToken, EnumAstNodeStatus, transformSync, getNodeProperty} from "@tbela99/css-parser"; import type {TransformOptions, VisitorNodeMap, AstAtRule, AstRule, AstDeclaration} from "@tbela99/css-parser"; -const options: TransformOptions = { + +const options: TransformSyncOptions = { beautify: true, validation: true, visitor: { AtRule: (node: AstAtRule) => { - console.debug('>>> ' + node.nam, '\n state: ' +EnumAstNodeStatus[node.state], '\n errors:', node.errors) + console.debug('>>> ' + node.nam, '\n state: ' + EnumAstNodeStatus[getNodeProperty(node, 'state')], '\n errors:', getNodeProperty(node, 'errors')) }, Rule: (node: AstRule) => { - console.debug('>>> ' + node.sel, '\n state: ' + EnumAstNodeStatus[node.state], '\n errors:', node.errors) + console.debug('>>> ' + node.sel, '\n state: ' + EnumAstNodeStatus[getNodeProperty(node, 'state')], '\n errors:', getNodeProperty(node, 'errors')) }, Declaration: (node: AstDeclaration) => { - console.debug('>>> ' + node.nam, '\n state: ' + EnumAstNodeStatus[node.state], '\n errors:', node.errors) + console.debug('>>> ' + node.nam, '\n state: ' + EnumAstNodeStatus[getNodeProperty(node, 'state')], '\n errors:', getNodeProperty(node, 'errors')) } } as VisitorNodeMap }; @@ -112,7 +113,7 @@ const css = ` } `; -const result = await transform(css, options); +const result = transformSync(css, options); // > >>> .xl\:stats-horizontal:where([dir=rtl],[dir=rtl] *),.prose :where(tbody tr,thead):not(:where([class~=not-prose] *)),.prose :where(code):not(:where([class~=not-prose] *,pre *)) // > state: Validated @@ -161,7 +162,7 @@ const result = await transform(css, options); ## Overriding Node Validation State -A node's validation state can be overridden using node visitors. The `state` property is an [`EnumAstNodeStatus`](../enums/node.EnumAstNodeStatus.html) value. +A node's validation state can be overridden using node visitors. The `state` property type is [`EnumAstNodeStatus`](../enums/node.EnumAstNodeStatus.html) value. By default, nodes with any of the following states are discarded unless their state is overridden by a visitor: @@ -175,18 +176,20 @@ In the example below, the node's validation state is changed from `EnumAstNodeSt ```ts -import {EnumToken, EnumAstNodeStatus, transform} from "@tbela99/css-parser"; -import type {TransformOptions, VisitorNodeMap, AstDeclaration} from "@tbela99/css-parser"; -const options: TransformOptions = { +import {EnumToken, EnumAstNodeStatus, transformSync, getNodeProperty, setNodeProperty} from "@tbela99/css-parser"; +import type {TransformSyncOptions, VisitorNodeMap, AstDeclaration} from "@tbela99/css-parser"; + + +const options: TransformSyncOptions = { beautify: true, validation: true, visitor: { AtRule: (node: AstAtRule) => { - if (node.state == EnumAstNodeStatus.Invalid) { + if (getNodeProperty(node, "state") == EnumAstNodeStatus.Invalid) { - node.state = EnumAstNodeStatus.ValidationFailed; + setNodeProperty(node, "state", EnumAstNodeStatus.ValidationFailed); } }, } as VisitorNodeMap @@ -205,7 +208,7 @@ const css = ` } `; -const result = await transform(css, options); +const result = transformSync(css, options); console.debug(result.code); ``` 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..39e6b399 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, CssVariableToken, CssVariableImportTokenType, WhitespaceToken } from "./token.d.ts"; /** * token or node location @@ -32,16 +32,16 @@ export declare interface BaseToken { * location info * @private */ - [LOC]?: SourceLocation; + [LOC]?: SourceLocation | null; /** * parent node * @private */ - [PARENT]?: AstNode; + [PARENT]?: AstNode | Token | null; /** * root node */ - [ROOT]?: AstStyleSheet; + [ROOT]?: AstStyleSheet | null; /** * prelude or selector tokens * @private @@ -51,12 +51,12 @@ export declare interface BaseToken { * node state * @private */ - [STATE]?: EnumAstNodeStatus; + [STATE]?: EnumAstNodeStatus | null; /** * node syntax errors * @private */ - [ERRORS]?: ErrorDescription[]; + [ERRORS]?: ErrorDescription[] | null; /** * property name * @private @@ -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,12 +406,13 @@ export declare type AstNode = | AstRule | AstDeclaration | AstKeyframesAtRule - | AstKeyFrameRule + | AstKeyframesRule | AstInvalidRule | AstInvalidAtRule | AstInvalidDeclaration | CssVariableToken - | CssVariableImportTokenType; + | CssVariableImportTokenType + | WhitespaceToken; /** * token search result 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/parse.d.ts b/src/@types/parse.d.ts index 6ed10243..0cee2f0b 100644 --- a/src/@types/parse.d.ts +++ b/src/@types/parse.d.ts @@ -1,7 +1,6 @@ -import {SourceFile} from "../lib/parser/source.ts"; +import { SourceFile } from "../lib/parser/source.ts"; export declare interface PropertyListOptions { - removeDuplicateDeclarations?: boolean | string | string[]; computeShorthand?: boolean; } @@ -10,11 +9,6 @@ export declare interface PropertyListOptions { * parse info */ export declare interface ParseInfo { - - /** - * read buffer - */ - buffer: string; /** * stream */ @@ -24,7 +18,7 @@ export declare interface ParseInfo { * Source file */ source: SourceFile; - + /** * last token position */ diff --git a/src/@types/token.d.ts b/src/@types/token.d.ts index 1c800e3e..f05ec4f4 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; /** @@ -223,10 +223,12 @@ export declare interface FunctionToken extends BaseToken { | EnumToken.ImageFunctionTokenType | EnumToken.TimelineFunctionTokenType | EnumToken.TimingFunctionTokenType - | EnumToken.ColorFunctionTokenType + | EnumToken.ColorTokenType | EnumToken.MathFunctionTokenType - | EnumToken.PseudoClassFunctionTokenType - | EnumToken.TransformFunctionTokenType; + | EnumToken.PseudoClassFuncTokenType + | EnumToken.TransformFunctionTokenType + | EnumToken.GeneralEnclosedFunctionTokenType + | EnumToken.WildCardFunctionTokenType; /** * function name */ @@ -242,7 +244,7 @@ export declare interface FunctionToken extends BaseToken { */ export declare interface GridTemplateFuncToken extends BaseToken { /** - * function type + * @inheritdoc */ typ: EnumToken.GridTemplateFuncTokenType; /** @@ -260,7 +262,7 @@ export declare interface GridTemplateFuncToken extends BaseToken { */ export declare interface FunctionURLToken extends BaseToken { /** - * function type + * @inheritdoc */ typ: EnumToken.UrlFunctionTokenType; /** @@ -278,7 +280,7 @@ export declare interface FunctionURLToken extends BaseToken { */ export declare interface FunctionImageToken extends BaseToken { /** - * function type + * @inheritdoc */ typ: EnumToken.ImageFunctionTokenType; /** @@ -305,7 +307,7 @@ export declare interface FunctionImageToken extends BaseToken { */ export declare interface TimingFunctionToken extends BaseToken { /** - * timing function type + * @inheritdoc */ typ: EnumToken.TimingFunctionTokenType; /** @@ -323,7 +325,7 @@ export declare interface TimingFunctionToken extends BaseToken { */ export declare interface TimelineFunctionToken extends BaseToken { /** - * timeline function type + * @inheritdoc */ typ: EnumToken.TimelineFunctionTokenType; /** @@ -341,7 +343,7 @@ export declare interface TimelineFunctionToken extends BaseToken { */ export declare interface StringToken extends BaseToken { /** - * string type + * @inheritdoc */ typ: EnumToken.StringTokenType; /** @@ -355,7 +357,7 @@ export declare interface StringToken extends BaseToken { */ export declare interface BadStringToken extends BaseToken { /** - * bad string type + * @inheritdoc */ typ: EnumToken.BadStringTokenType; /** @@ -369,7 +371,7 @@ export declare interface BadStringToken extends BaseToken { */ export declare interface UnclosedStringToken extends BaseToken { /** - * unclosed string type + * @inheritdoc */ typ: EnumToken.UnclosedStringTokenType; /** @@ -383,7 +385,7 @@ export declare interface UnclosedStringToken extends BaseToken { */ export declare interface DimensionToken extends BaseToken { /** - * dimension type + * @inheritdoc */ typ: EnumToken.DimensionTokenType; /** @@ -401,7 +403,7 @@ export declare interface DimensionToken extends BaseToken { */ export declare interface LengthToken extends BaseToken { /** - * length type + * @inheritdoc */ typ: EnumToken.LengthTokenType; /** @@ -419,7 +421,7 @@ export declare interface LengthToken extends BaseToken { */ export declare interface AngleToken extends BaseToken { /** - * angle type + * @inheritdoc */ typ: EnumToken.AngleTokenType; /** @@ -437,7 +439,7 @@ export declare interface AngleToken extends BaseToken { */ export declare interface TimeToken extends BaseToken { /** - * time type + * @inheritdoc */ typ: EnumToken.TimeTokenType; /** @@ -445,7 +447,7 @@ export declare interface TimeToken extends BaseToken { */ val: number | FractionToken; /** - * time unit + * time unit */ unit: "ms" | "s"; } @@ -455,7 +457,7 @@ export declare interface TimeToken extends BaseToken { */ export declare interface FrequencyToken extends BaseToken { /** - * frequency type + * @inheritdoc */ typ: EnumToken.FrequencyTokenType; /** @@ -473,7 +475,7 @@ export declare interface FrequencyToken extends BaseToken { */ export declare interface ResolutionToken extends BaseToken { /** - * resolution type + * @inheritdoc */ typ: EnumToken.ResolutionTokenType; /** @@ -491,7 +493,7 @@ export declare interface ResolutionToken extends BaseToken { */ export declare interface HashToken extends BaseToken { /** - * hash type + * @inheritdoc */ typ: EnumToken.HashTokenType; /** @@ -505,7 +507,7 @@ export declare interface HashToken extends BaseToken { */ export declare interface BlockStartToken extends BaseToken { /** - * block start type + * @inheritdoc */ typ: EnumToken.BlockStartTokenType; } @@ -515,7 +517,7 @@ export declare interface BlockStartToken extends BaseToken { */ export declare interface BlockEndToken extends BaseToken { /** - * block end type + * @inheritdoc */ typ: EnumToken.BlockEndTokenType; } @@ -525,7 +527,7 @@ export declare interface BlockEndToken extends BaseToken { */ export declare interface AttrStartToken extends BaseToken { /** - * attribute start type + * @inheritdoc */ typ: EnumToken.AttrStartTokenType; /** @@ -539,7 +541,7 @@ export declare interface AttrStartToken extends BaseToken { */ export declare interface AttrEndToken extends BaseToken { /** - * attribute end type + * @inheritdoc */ typ: EnumToken.AttrEndTokenType; } @@ -549,7 +551,7 @@ export declare interface AttrEndToken extends BaseToken { */ export declare interface ParensStartToken extends BaseToken { /** - * parenthesis start type + * @inheritdoc */ typ: EnumToken.StartParensTokenType; } @@ -559,7 +561,7 @@ export declare interface ParensStartToken extends BaseToken { */ export declare interface ParensEndToken extends BaseToken { /** - * parenthesis end type + * @inheritdoc */ typ: EnumToken.EndParensTokenType; } @@ -569,7 +571,7 @@ export declare interface ParensEndToken extends BaseToken { */ export declare interface ParensToken extends BaseToken { /** - * parenthesis type + * @inheritdoc */ typ: EnumToken.ParensTokenType; /** @@ -583,7 +585,7 @@ export declare interface ParensToken extends BaseToken { */ export declare interface WhitespaceToken extends BaseToken { /** - * whitespace type + * @inheritdoc */ typ: EnumToken.WhitespaceTokenType; /** @@ -597,7 +599,7 @@ export declare interface WhitespaceToken extends BaseToken { */ export declare interface CommentToken extends BaseToken { /** - * comment type + * @inheritdoc */ typ: EnumToken.CommentTokenType; /** @@ -611,7 +613,7 @@ export declare interface CommentToken extends BaseToken { */ export declare interface BadCommentToken extends BaseToken { /** - * bad comment type + * @inheritdoc */ typ: EnumToken.BadCommentTokenType; /** @@ -624,7 +626,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 +641,7 @@ export declare interface CDOCommentToken extends BaseToken { */ export declare interface BadCDOCommentToken extends BaseToken { /** - * bad CDO comment type + * @inheritdoc */ typ: EnumToken.BadCdoTokenType; /** @@ -647,7 +655,7 @@ export declare interface BadCDOCommentToken extends BaseToken { */ export declare interface IncludeMatchToken extends BaseToken { /** - * include match type + * @inheritdoc */ typ: EnumToken.IncludeMatchTokenType; // val: '~='; @@ -658,7 +666,7 @@ export declare interface IncludeMatchToken extends BaseToken { */ export declare interface DashMatchToken extends BaseToken { /** - * dash match type + * @inheritdoc */ typ: EnumToken.DashMatchTokenType; // val: '|='; @@ -669,7 +677,7 @@ export declare interface DashMatchToken extends BaseToken { */ export declare interface EqualMatchToken extends BaseToken { /** - * equal match type + * @inheritdoc */ typ: EnumToken.EqualMatchTokenType; // val: '|='; @@ -680,7 +688,7 @@ export declare interface EqualMatchToken extends BaseToken { */ export declare interface StartMatchToken extends BaseToken { /** - * start match type + * @inheritdoc */ typ: EnumToken.StartMatchTokenType; // val: '^='; @@ -691,7 +699,7 @@ export declare interface StartMatchToken extends BaseToken { */ export declare interface EndMatchToken extends BaseToken { /** - * end match type + * @inheritdoc */ typ: EnumToken.EndMatchTokenType; // val: '|='; @@ -702,7 +710,7 @@ export declare interface EndMatchToken extends BaseToken { */ export declare interface ContainMatchToken extends BaseToken { /** - * contain match type + * @inheritdoc */ typ: EnumToken.ContainMatchTokenType; // val: '|='; @@ -713,7 +721,7 @@ export declare interface ContainMatchToken extends BaseToken { */ export declare interface LessThanToken extends BaseToken { /** - * less than type + * @inheritdoc */ typ: EnumToken.LtTokenType; } @@ -723,7 +731,7 @@ export declare interface LessThanToken extends BaseToken { */ export declare interface LessThanOrEqualToken extends BaseToken { /** - * less than or equal type + * @inheritdoc */ typ: EnumToken.LteTokenType; } @@ -733,7 +741,7 @@ export declare interface LessThanOrEqualToken extends BaseToken { */ export declare interface GreaterThanToken extends BaseToken { /** - * greater than type + * @inheritdoc */ typ: EnumToken.GtTokenType; } @@ -743,7 +751,7 @@ export declare interface GreaterThanToken extends BaseToken { */ export declare interface GreaterThanOrEqualToken extends BaseToken { /** - * greater than or equal type + * @inheritdoc */ typ: EnumToken.GteTokenType; } @@ -753,7 +761,7 @@ export declare interface GreaterThanOrEqualToken extends BaseToken { */ export declare interface ColumnCombinatorToken extends BaseToken { /** - * column combinator type + * @inheritdoc */ typ: EnumToken.ColumnCombinatorTokenType; } @@ -763,7 +771,7 @@ export declare interface ColumnCombinatorToken extends BaseToken { */ export declare interface PseudoClassToken extends BaseToken { /** - * Pseudo class + * @inheritdoc */ typ: EnumToken.PseudoClassTokenType; /** @@ -777,7 +785,7 @@ export declare interface PseudoClassToken extends BaseToken { */ export declare interface PseudoElementToken extends BaseToken { /** - * Pseudo element + * @inheritdoc */ typ: EnumToken.PseudoElementTokenType; /** @@ -791,7 +799,7 @@ export declare interface PseudoElementToken extends BaseToken { */ export declare interface PseudoPageToken extends BaseToken { /** - * Pseudo page + * @inheritdoc */ typ: EnumToken.PseudoPageTokenType; /** @@ -805,7 +813,7 @@ export declare interface PseudoPageToken extends BaseToken { */ export declare interface PseudoClassFunctionToken extends BaseToken { /** - * Pseudo class function + * @inheritdoc */ typ: EnumToken.PseudoClassFuncTokenType; /** @@ -823,7 +831,7 @@ export declare interface PseudoClassFunctionToken extends BaseToken { */ export declare interface DelimToken extends BaseToken { /** - * Delimiter token type + * @inheritdoc */ typ: EnumToken.DelimTokenType; } @@ -833,7 +841,7 @@ export declare interface DelimToken extends BaseToken { */ export declare interface BadUrlToken extends BaseToken { /** - * Bad URL + * @inheritdoc */ typ: EnumToken.BadUrlTokenType; /** @@ -847,7 +855,7 @@ export declare interface BadUrlToken extends BaseToken { */ export declare interface UrlToken extends BaseToken { /** - * URL + * @inheritdoc */ typ: EnumToken.UrlTokenTokenType; /** @@ -861,7 +869,7 @@ export declare interface UrlToken extends BaseToken { */ export declare interface EOFToken extends BaseToken { /** - * End of file + * @inheritdoc */ typ: EnumToken.EOFTokenType; } @@ -871,7 +879,7 @@ export declare interface EOFToken extends BaseToken { */ export declare interface ImportantToken extends BaseToken { /** - * Important + * @inheritdoc */ typ: EnumToken.ImportantTokenType; } @@ -881,7 +889,7 @@ export declare interface ImportantToken extends BaseToken { */ export declare interface ColorToken extends BaseToken { /** - * Color type + * @inheritdoc */ typ: EnumToken.ColorTokenType; /** @@ -907,7 +915,7 @@ export declare interface ColorToken extends BaseToken { */ export declare interface AttrToken extends BaseToken { /** - * Attribute type + * @inheritdoc */ typ: EnumToken.AttrTokenType; /** @@ -921,7 +929,7 @@ export declare interface AttrToken extends BaseToken { */ export declare interface InvalidAttrToken extends BaseToken { /** - * Attribute type + * @inheritdoc */ typ: EnumToken.InvalidAttrTokenType; /** @@ -934,6 +942,9 @@ export declare interface InvalidAttrToken extends BaseToken { * Child combinator token */ export declare interface ChildCombinatorToken extends BaseToken { + /** + * @inheritdoc + */ typ: EnumToken.ChildCombinatorTokenType; } @@ -942,7 +953,7 @@ export declare interface ChildCombinatorToken extends BaseToken { */ export declare interface MediaFeatureToken extends BaseToken { /** - * Media feature type + * @inheritdoc */ typ: EnumToken.MediaFeatureTokenType; /** @@ -956,7 +967,7 @@ export declare interface MediaFeatureToken extends BaseToken { */ export declare interface NotToken extends BaseToken { /** - * Media feature not type + * @inheritdoc */ typ: EnumToken.NotTokenType; /** @@ -970,7 +981,7 @@ export declare interface NotToken extends BaseToken { */ export declare interface MediaFeatureOnlyToken extends BaseToken { /** - * Media feature only type + * @inheritdoc */ typ: EnumToken.OnlyTokenType; /** @@ -984,7 +995,7 @@ export declare interface MediaFeatureOnlyToken extends BaseToken { */ export declare interface AndToken extends BaseToken { /** - * Media feature and type + * @inheritdoc */ typ: EnumToken.AndTokenType; } @@ -994,7 +1005,7 @@ export declare interface AndToken extends BaseToken { */ export declare interface OrToken extends BaseToken { /** - * Media feature or type + * @inheritdoc */ typ: EnumToken.OrTokenType; } @@ -1004,7 +1015,7 @@ export declare interface OrToken extends BaseToken { */ export declare interface MediaQueryUnaryFeatureToken extends BaseToken { /** - * Media query condition type + * @inheritdoc */ typ: EnumToken.MediaQueryUnaryFeatureTokenType; /** @@ -1019,7 +1030,7 @@ export declare interface MediaQueryUnaryFeatureToken extends BaseToken { export declare interface SupportsQueryUnaryConditionToken extends BaseToken { /** - * Supports query condition type + * @inheritdoc */ typ: EnumToken.SupportsQueryUnaryConditionTokenType; /** @@ -1034,7 +1045,7 @@ export declare interface SupportsQueryUnaryConditionToken extends BaseToken { export declare interface SupportsQueryConditionToken extends BaseToken { /** - * Supports query condition type + * @inheritdoc */ typ: EnumToken.SupportsQueryConditionTokenType; /** @@ -1053,7 +1064,7 @@ export declare interface SupportsQueryConditionToken extends BaseToken { export declare interface WhenElseQueryConditionToken extends BaseToken { /** - * When else query condition type + * @inheritdoc */ typ: EnumToken.WhenElseQueryConditionTokenType; /** @@ -1072,7 +1083,7 @@ export declare interface WhenElseQueryConditionToken extends BaseToken { export declare interface WhenElseUnaryConditionToken extends BaseToken { /** - * When else query condition type + * @inheritdoc */ typ: EnumToken.WhenElseUnaryConditionTokenType; /** @@ -1087,7 +1098,7 @@ export declare interface WhenElseUnaryConditionToken extends BaseToken { export declare interface MediaQueryConditionToken extends BaseToken { /** - * Media query condition type + * @inheritdoc */ typ: EnumToken.MediaQueryConditionTokenType; /** @@ -1114,7 +1125,7 @@ export declare interface MediaQueryConditionToken extends BaseToken { export declare interface IfConditionToken extends BaseToken { /** - * If condition type + * @inheritdoc */ typ: EnumToken.IfConditionTokenType; /** @@ -1129,7 +1140,7 @@ export declare interface IfConditionToken extends BaseToken { export declare interface IfElseConditionToken extends BaseToken { /** - * If else condition type + * @inheritdoc */ typ: EnumToken.IfElseConditionTokenType; /** @@ -1143,23 +1154,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 +1222,9 @@ export declare interface InvalidMediaQueryToken extends BaseToken { * Descendant combinator token */ export declare interface DescendantCombinatorToken extends BaseToken { + /** + * @inheritdoc + */ typ: EnumToken.DescendantCombinatorTokenType; } @@ -1174,6 +1232,9 @@ export declare interface DescendantCombinatorToken extends BaseToken { * Next sibling combinator token */ export declare interface NextSiblingCombinatorToken extends BaseToken { + /** + * @inheritdoc + */ typ: EnumToken.NextSiblingCombinatorTokenType; } @@ -1181,6 +1242,9 @@ export declare interface NextSiblingCombinatorToken extends BaseToken { * Subsequent sibling combinator token */ export declare interface SubsequentCombinatorToken extends BaseToken { + /** + * @inheritdoc + */ typ: EnumToken.SubsequentSiblingCombinatorTokenType; } @@ -1188,6 +1252,9 @@ export declare interface SubsequentCombinatorToken extends BaseToken { * Add token */ export declare interface AddToken extends BaseToken { + /** + * @inheritdoc + */ typ: EnumToken.Add; } @@ -1195,6 +1262,9 @@ export declare interface AddToken extends BaseToken { * Sub token */ export declare interface SubToken extends BaseToken { + /** + * @inheritdoc + */ typ: EnumToken.Sub; } @@ -1202,6 +1272,9 @@ export declare interface SubToken extends BaseToken { * Div token */ export declare interface DivToken extends BaseToken { + /** + * @inheritdoc + */ typ: EnumToken.Div; } @@ -1210,7 +1283,7 @@ export declare interface DivToken extends BaseToken { */ export declare interface MulToken extends BaseToken { /** - * Type + * @inheritdoc */ typ: EnumToken.Mul; } @@ -1220,7 +1293,7 @@ export declare interface MulToken extends BaseToken { */ export declare interface WrappedValuesToken extends BaseToken { /** - * Type + * @inheritdoc */ typ: EnumToken.WrappedValuesTokenType; /** @@ -1234,7 +1307,7 @@ export declare interface WrappedValuesToken extends BaseToken { */ export declare interface UnaryExpression extends BaseToken { /** - * Type + * @inheritdoc */ typ: EnumToken.UnaryExpressionTokenType; /** @@ -1251,8 +1324,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 +1342,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 +1364,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 +1390,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 +1408,13 @@ export declare interface NameSpaceAttributeToken extends BaseToken { * List token */ export declare interface ListToken extends BaseToken { + /** + * @inheritdoc + */ typ: EnumToken.ListToken; + /** + * Children + */ chi: Token[]; } @@ -1298,8 +1422,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 +1440,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 +1494,9 @@ export declare interface CssVariableMapTokenType extends BaseToken { * Function definition token */ export declare interface FunctionDefToken extends BaseToken { + /** + * @inheritdoc + */ typ: | EnumToken.FunctionDefTokenType | EnumToken.UrlFunctionTokenDefType @@ -1339,7 +1508,13 @@ export declare interface FunctionDefToken extends BaseToken { | EnumToken.MathFunctionTokenDefType | EnumToken.PseudoClassFunctionTokenDefType | EnumToken.TransformFunctionTokenDefType; + /** + * Name + */ nam: string; + /** + * Value + */ val: string; } @@ -1347,7 +1522,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/config.json b/src/config.json index efd48706..486e67fb 100644 --- a/src/config.json +++ b/src/config.json @@ -1 +1,1506 @@ -{"properties":{"gap":{"shorthand":"gap","properties":["row-gap","column-gap"],"types":["Length","Perc"],"multiple":false,"separator":null,"keywords":["normal"]},"row-gap":{"shorthand":"gap"},"column-gap":{"shorthand":"gap"},"inset":{"shorthand":"inset","properties":["top","right","bottom","left"],"types":["Length","Perc"],"multiple":false,"separator":null,"keywords":["auto"]},"top":{"shorthand":"inset"},"right":{"shorthand":"inset"},"bottom":{"shorthand":"inset"},"left":{"shorthand":"inset"},"margin":{"shorthand":"margin","properties":["margin-top","margin-right","margin-bottom","margin-left"],"types":["Length","Perc"],"multiple":false,"separator":null,"keywords":["auto"]},"margin-top":{"shorthand":"margin"},"margin-right":{"shorthand":"margin"},"margin-bottom":{"shorthand":"margin"},"margin-left":{"shorthand":"margin"},"padding":{"shorthand":"padding","properties":["padding-top","padding-right","padding-bottom","padding-left"],"types":["Length","Perc"],"keywords":[]},"padding-top":{"shorthand":"padding"},"padding-right":{"shorthand":"padding"},"padding-bottom":{"shorthand":"padding"},"padding-left":{"shorthand":"padding"},"border-radius":{"shorthand":"border-radius","properties":["border-top-left-radius","border-top-right-radius","border-bottom-right-radius","border-bottom-left-radius"],"types":["Length","Perc"],"multiple":true,"separator":{"typ":"Literal","val":"/"},"keywords":[]},"border-top-left-radius":{"shorthand":"border-radius"},"border-top-right-radius":{"shorthand":"border-radius"},"border-bottom-right-radius":{"shorthand":"border-radius"},"border-bottom-left-radius":{"shorthand":"border-radius"},"border-width":{"shorthand":"border-width","map":"border","properties":["border-top-width","border-right-width","border-bottom-width","border-left-width"],"types":["Length","Perc"],"default":["medium"],"keywords":["thin","medium","thick"]},"border-top-width":{"map":"border","shorthand":"border-width"},"border-right-width":{"map":"border","shorthand":"border-width"},"border-bottom-width":{"map":"border","shorthand":"border-width"},"border-left-width":{"map":"border","shorthand":"border-width"},"border-style":{"shorthand":"border-style","map":"border","properties":["border-top-style","border-right-style","border-bottom-style","border-left-style"],"types":[],"default":["none"],"keywords":["none","hidden","dotted","dashed","solid","double","groove","ridge","inset","outset"]},"border-top-style":{"map":"border","shorthand":"border-style"},"border-right-style":{"map":"border","shorthand":"border-style"},"border-bottom-style":{"map":"border","shorthand":"border-style"},"border-left-style":{"map":"border","shorthand":"border-style"},"border-color":{"shorthand":"border-color","map":"border","properties":["border-top-color","border-right-color","border-bottom-color","border-left-color"],"types":["Color"],"default":["currentcolor"],"keywords":[]},"border-top-color":{"map":"border","shorthand":"border-color"},"border-right-color":{"map":"border","shorthand":"border-color"},"border-bottom-color":{"map":"border","shorthand":"border-color"},"border-left-color":{"map":"border","shorthand":"border-color"},"grid-row":{"shorthand":"grid-row","properties":["grid-row-start","grid-row-end"],"types":["Iden","Number"],"multiple":true,"valueSeparator":{"typ":"Literal","val":"/"},"default":["auto"],"keywords":["auto","span"]},"grid-row-start":{"shorthand":"grid-row"},"grid-row-end":{"shorthand":"grid-row"}},"map":{"flex-flow":{"shorthand":"flex-flow","pattern":"flex-direction flex-wrap","keywords":[],"default":["row","nowrap"],"properties":{"flex-direction":{"keywords":["row","row-reverse","column","column-reverse"],"default":["row"],"types":[]},"flex-wrap":{"keywords":["wrap","nowrap","wrap-reverse"],"default":["nowrap"],"types":[]}}},"flex-direction":{"shorthand":"flex-flow"},"flex-wrap":{"shorthand":"flex-flow"},"container":{"shorthand":"container","pattern":"container-name container-type","keywords":[],"default":[],"properties":{"container-name":{"required":true,"multiple":true,"keywords":["none"],"default":["none"],"types":["Iden","DashedIden"]},"container-type":{"previous":"container-name","prefix":{"typ":"Literal","val":"/"},"keywords":["size","inline-size","normal"],"default":["normal"],"types":[]}}},"container-name":{"shorthand":"container"},"container-type":{"shorthand":"container"},"flex":{"shorthand":"flex","pattern":"flex-grow flex-shrink flex-basis","keywords":["auto","none","initial"],"default":[],"mapping":{"0 1 auto":"initial","0 0 auto":"none","1 1 auto":"auto"},"properties":{"flex-grow":{"required":true,"keywords":[],"default":[],"types":["Number"]},"flex-shrink":{"keywords":[],"default":[],"types":["Number"]},"flex-basis":{"keywords":["max-content","min-content","fit-content","fit-content","content","auto"],"default":[],"types":["Length","Perc"]}}},"flex-grow":{"shorthand":"flex"},"flex-shrink":{"shorthand":"flex"},"flex-basis":{"shorthand":"flex"},"columns":{"shorthand":"columns","pattern":"column-count column-width","keywords":["auto"],"default":["auto","auto auto"],"properties":{"column-count":{"keywords":["auto"],"default":["auto"],"types":["Number"]},"column-width":{"keywords":["auto"],"default":["auto"],"types":["Length"]}}},"column-count":{"shorthand":"columns"},"column-width":{"shorthand":"columns"},"transition":{"shorthand":"transition","multiple":true,"separator":{"typ":"Comma"},"pattern":"transition-property transition-duration transition-timing-function transition-delay transition-behavior","keywords":["none","all"],"default":["0s","0ms","all","ease","none","normal"],"mapping":{"cubic-bezier(.25,.1,.25,1)":"ease","cubic-bezier(0,0,1,1)":"linear","cubic-bezier(.42,0,1,1)":"ease-in","cubic-bezier(0,0,.58,1)":"ease-out","cubic-bezier(.42,0,.58,.42)":"ease-in-out"},"properties":{"transition-property":{"keywords":["none","all"],"default":["all"],"types":["Iden"]},"transition-duration":{"keywords":[],"default":["0s","0ms","normal"],"types":["Time"]},"transition-timing-function":{"keywords":["ease","ease-in","ease-out","ease-in-out","linear","step-start","step-end"],"default":["ease"],"types":["TimingFunction"],"mapping":{"cubic-bezier(.25,.1,.25,1)":"ease","cubic-bezier(0,0,1,1)":"linear","cubic-bezier(.42,0,1,1)":"ease-in","cubic-bezier(0,0,.58,1)":"ease-out","cubic-bezier(.42,0,.58,.42)":"ease-in-out"}},"transition-delay":{"keywords":[],"default":["0s"],"types":["Time"]},"transition-behavior":{"keywords":["normal","allow-discrete"],"default":["normal"],"types":[]}}},"transition-property":{"shorthand":"transition"},"transition-duration":{"shorthand":"transition"},"transition-timing-function":{"shorthand":"transition"},"transition-delay":{"shorthand":"transition"},"transition-behavior":{"shorthand":"transition"},"animation":{"shorthand":"animation","separator":{"typ":"Comma"},"pattern":"animation-name animation-duration animation-timing-function animation-delay animation-iteration-count animation-direction animation-fill-mode animation-play-state animation-timeline","default":["1","0s","0ms","none","ease","normal","running","auto"],"properties":{"animation-name":{"keywords":["none"],"default":["none"],"types":["Iden"]},"animation-duration":{"keywords":["auto"],"default":["0s","0ms","auto"],"types":["Time"],"mapping":{"auto":"0s"}},"animation-timing-function":{"keywords":["ease","ease-in","ease-out","ease-in-out","linear","step-start","step-end"],"default":["ease"],"types":["TimingFunction"],"mapping":{"cubic-bezier(.25,.1,.25,1)":"ease","cubic-bezier(0,0,1,1)":"linear","cubic-bezier(.42,0,1,1)":"ease-in","cubic-bezier(0,0,.58,1)":"ease-out","cubic-bezier(.42,0,.58,.42)":"ease-in-out"}},"animation-delay":{"keywords":[],"default":["0s","0ms"],"types":["Time"]},"animation-iteration-count":{"keywords":["infinite"],"default":["1"],"types":["Number"]},"animation-direction":{"keywords":["normal","reverse","alternate","alternate-reverse"],"default":["normal"],"types":[]},"animation-fill-mode":{"keywords":["none","forwards","backwards","both"],"default":["none"],"types":[]},"animation-play-state":{"keywords":["running","paused"],"default":["running"],"types":[]},"animation-timeline":{"keywords":["none","auto"],"default":["auto"],"types":["DashedIden","TimelineFunction"]}}},"animation-name":{"shorthand":"animation"},"animation-duration":{"shorthand":"animation"},"animation-timing-function":{"shorthand":"animation"},"animation-delay":{"shorthand":"animation"},"animation-iteration-count":{"shorthand":"animation"},"animation-direction":{"shorthand":"animation"},"animation-fill-mode":{"shorthand":"animation"},"animation-play-state":{"shorthand":"animation"},"animation-timeline":{"shorthand":"animation"},"text-emphasis":{"shorthand":"text-emphasis","pattern":"text-emphasis-color text-emphasis-style","default":["none","currentcolor"],"properties":{"text-emphasis-style":{"keywords":["none","filled","open","dot","circle","double-circle","triangle","sesame"],"default":["none"],"types":["String"]},"text-emphasis-color":{"default":["currentcolor"],"types":["Color"]}}},"text-emphasis-style":{"shorthand":"text-emphasis"},"text-emphasis-color":{"shorthand":"text-emphasis"},"border":{"shorthand":"border","pattern":"border-color border-style border-width","keywords":["none"],"default":["0","none"],"properties":{"border-color":{"types":["Color"],"default":["currentcolor"],"keywords":[]},"border-style":{"types":[],"default":["none"],"keywords":["none","hidden","dotted","dashed","solid","double","groove","ridge","inset","outset"]},"border-width":{"types":["Length","Perc"],"default":["medium"],"keywords":["thin","medium","thick"]}}},"border-color":{"shorthand":"border"},"border-style":{"shorthand":"border"},"border-width":{"shorthand":"border"},"list-style":{"shorthand":"list-style","pattern":"list-style-type list-style-position list-style-image","keywords":["none","outside"],"default":["none","outside"],"properties":{"list-style-position":{"types":[],"default":["outside"],"keywords":["inside","outside"]},"list-style-image":{"default":["none"],"keywords":["node"],"types":["UrlFunc","ImageFunc"]},"list-style-type":{"types":["String","Iden","Symbols"],"default":["disc"],"keywords":["disc","circle","square","decimal","decimal-leading-zero","lower-roman","upper-roman","lower-greek","lower-latin","upper-latin","none"]}}},"list-style-position":{"shorthand":"list-style"},"list-style-image":{"shorthand":"list-style"},"list-style-type":{"shorthand":"list-style"},"overflow":{"shorthand":"overflow","pattern":"overflow-x overflow-y","keywords":["auto","visible","hidden","clip","scroll"],"default":[],"mapping":{"visible visible":"visible","auto auto":"auto","hidden hidden":"hidden","scroll scroll":"scroll"},"properties":{"overflow-x":{"default":[],"types":[],"keywords":["auto","visible","hidden","clip","scroll"]},"overflow-y":{"default":[],"types":[],"keywords":["auto","visible","hidden","clip","scroll"]}}},"overflow-x":{"shorthand":"overflow"},"overflow-y":{"shorthand":"overflow"},"outline":{"shorthand":"outline","pattern":"outline-color outline-style outline-width","keywords":["none"],"default":["0","none","currentcolor"],"properties":{"outline-color":{"types":["Color"],"default":["currentcolor"],"keywords":["currentcolor"]},"outline-style":{"types":[],"default":["none"],"keywords":["auto","none","dotted","dashed","solid","double","groove","ridge","inset","outset"]},"outline-width":{"types":["Length","Perc"],"default":["medium"],"keywords":["thin","medium","thick"]}}},"outline-color":{"shorthand":"outline"},"outline-style":{"shorthand":"outline"},"outline-width":{"shorthand":"outline"},"font":{"shorthand":"font","pattern":"font-weight font-style font-size line-height font-stretch font-variant font-family","keywords":["caption","icon","menu","message-box","small-caption","status-bar","-moz-window, ","-moz-document, ","-moz-desktop, ","-moz-info, ","-moz-dialog","-moz-button","-moz-pull-down-menu","-moz-list","-moz-field"],"default":[],"properties":{"font-weight":{"types":["Number"],"default":["400","normal"],"keywords":["normal","bold","lighter","bolder"],"constraints":{"value":{"min":"1","max":"1000"}},"mapping":{"thin":"100","hairline":"100","extra light":"200","ultra light":"200","light":"300","normal":"400","regular":"400","medium":"500","semi bold":"600","demi bold":"600","bold":"700","extra bold":"800","ultra bold":"800","black":"900","heavy":"900","extra black":"950","ultra black":"950"}},"font-style":{"types":["Angle"],"default":["normal"],"keywords":["normal","italic","oblique"]},"font-size":{"types":["Length","Perc"],"default":[],"keywords":["xx-small","x-small","small","medium","large","x-large","xx-large","xxx-large","larger","smaller"],"required":true},"line-height":{"types":["Length","Perc","Number"],"default":["normal"],"keywords":["normal"],"previous":"font-size","prefix":{"typ":"Literal","val":"/"}},"font-stretch":{"types":["Perc"],"default":["normal"],"keywords":["ultra-condensed","extra-condensed","condensed","semi-condensed","normal","semi-expanded","expanded","extra-expanded","ultra-expanded"],"mapping":{"ultra-condensed":"50%","extra-condensed":"62.5%","condensed":"75%","semi-condensed":"87.5%","normal":"100%","semi-expanded":"112.5%","expanded":"125%","extra-expanded":"150%","ultra-expanded":"200%"}},"font-variant":{"types":[],"default":["normal"],"keywords":["normal","none","common-ligatures","no-common-ligatures","discretionary-ligatures","no-discretionary-ligatures","historical-ligatures","no-historical-ligatures","contextual","no-contextual","historical-forms","small-caps","all-small-caps","petite-caps","all-petite-caps","unicase","titling-caps","ordinal","slashed-zero","lining-nums","oldstyle-nums","proportional-nums","tabular-nums","diagonal-fractions","stacked-fractions","ordinal","slashed-zero","ruby","jis78","jis83","jis90","jis04","simplified","traditional","full-width","proportional-width","ruby","sub","super","text","emoji","unicode"]},"font-family":{"types":["String","Iden"],"default":[],"keywords":["serif","sans-serif","monospace","cursive","fantasy","system-ui","ui-serif","ui-sans-serif","ui-monospace","ui-rounded","math","emoji","fangsong"],"required":true,"multiple":true,"separator":{"typ":"Comma"}}}},"font-weight":{"shorthand":"font"},"font-style":{"shorthand":"font"},"font-size":{"shorthand":"font"},"line-height":{"shorthand":"font"},"font-stretch":{"shorthand":"font"},"font-variant":{"shorthand":"font"},"font-family":{"shorthand":"font"},"background":{"shorthand":"background","pattern":"background-attachment background-origin background-clip background-color background-image background-repeat background-position background-size","keywords":["none"],"default":["0 0","none","auto","repeat","transparent","#0000","scroll","padding-box","border-box"],"multiple":true,"set":{"background-origin":["background-clip"]},"separator":{"typ":"Comma"},"properties":{"background-repeat":{"types":[],"default":["repeat"],"multiple":true,"keywords":["repeat-x","repeat-y","repeat","space","round","no-repeat"],"mapping":{"repeat no-repeat":"repeat-x","no-repeat repeat":"repeat-y","repeat repeat":"repeat","space space":"space","round round":"round","no-repeat no-repeat":"no-repeat"}},"background-color":{"types":["Color"],"default":["#0000","transparent"],"multiple":true,"keywords":[]},"background-image":{"types":["UrlFunc","ImageFunc"],"default":["none"],"keywords":["none"]},"background-attachment":{"types":[],"default":["scroll"],"multiple":true,"keywords":["scroll","fixed","local"]},"background-clip":{"types":[],"default":["border-box"],"multiple":true,"keywords":["border-box","padding-box","content-box","text"]},"background-origin":{"types":[],"default":["padding-box"],"multiple":true,"keywords":["border-box","padding-box","content-box"]},"background-position":{"multiple":true,"types":["Perc","Length"],"default":["0 0","top left","left top"],"keywords":["top","left","center","bottom","right"],"mapping":{"left":"0","top":"0","center":"50%","center center":"50%","50% 50%":"50%","bottom":"100%","right":"100%"},"constraints":{"mapping":{"max":2}}},"background-size":{"multiple":true,"previous":"background-position","prefix":{"typ":"Literal","val":"/"},"types":["Perc","Length"],"default":["auto","auto auto"],"keywords":["auto","cover","contain"],"mapping":{"auto auto":"auto"}}}},"background-repeat":{"shorthand":"background"},"background-color":{"shorthand":"background"},"background-image":{"shorthand":"background"},"background-attachment":{"shorthand":"background"},"background-clip":{"shorthand":"background"},"background-origin":{"shorthand":"background"},"background-position":{"shorthand":"background"},"background-size":{"shorthand":"background"}},"property":{"transform-origin":{"pattern":[["left|center|right","top|center|bottom",""],["left|center|right","top|center|bottom"],["center|left|right|center|top|bottom|"]],"mapping":{"center":{"typ":"Perc","val":50},"left":{"typ":"Perc","val":0},"right":{"typ":"Perc","val":100},"top":{"typ":"Perc","val":0},"bottom":{"typ":"Perc","val":100}}}}} \ No newline at end of file +{ + "properties": { + "gap": { + "shorthand": "gap", + "properties": [ + "row-gap", + "column-gap" + ], + "types": [ + "Length", + "Perc" + ], + "multiple": false, + "separator": null, + "keywords": [ + "normal" + ] + }, + "row-gap": { + "shorthand": "gap" + }, + "column-gap": { + "shorthand": "gap" + }, + "inset": { + "shorthand": "inset", + "properties": [ + "top", + "right", + "bottom", + "left" + ], + "types": [ + "Length", + "Perc" + ], + "multiple": false, + "separator": null, + "keywords": [ + "auto" + ] + }, + "top": { + "shorthand": "inset" + }, + "right": { + "shorthand": "inset" + }, + "bottom": { + "shorthand": "inset" + }, + "left": { + "shorthand": "inset" + }, + "margin": { + "shorthand": "margin", + "properties": [ + "margin-top", + "margin-right", + "margin-bottom", + "margin-left" + ], + "types": [ + "Length", + "Perc" + ], + "multiple": false, + "separator": null, + "keywords": [ + "auto" + ] + }, + "margin-top": { + "shorthand": "margin" + }, + "margin-right": { + "shorthand": "margin" + }, + "margin-bottom": { + "shorthand": "margin" + }, + "margin-left": { + "shorthand": "margin" + }, + "padding": { + "shorthand": "padding", + "properties": [ + "padding-top", + "padding-right", + "padding-bottom", + "padding-left" + ], + "types": [ + "Length", + "Perc" + ], + "keywords": [] + }, + "padding-top": { + "shorthand": "padding" + }, + "padding-right": { + "shorthand": "padding" + }, + "padding-bottom": { + "shorthand": "padding" + }, + "padding-left": { + "shorthand": "padding" + }, + "border-radius": { + "shorthand": "border-radius", + "properties": [ + "border-top-left-radius", + "border-top-right-radius", + "border-bottom-right-radius", + "border-bottom-left-radius" + ], + "types": [ + "Length", + "Perc" + ], + "multiple": true, + "separator": { + "typ": "Literal", + "val": "/" + }, + "keywords": [] + }, + "border-top-left-radius": { + "shorthand": "border-radius" + }, + "border-top-right-radius": { + "shorthand": "border-radius" + }, + "border-bottom-right-radius": { + "shorthand": "border-radius" + }, + "border-bottom-left-radius": { + "shorthand": "border-radius" + }, + "border-width": { + "shorthand": "border-width", + "map": "border", + "properties": [ + "border-top-width", + "border-right-width", + "border-bottom-width", + "border-left-width" + ], + "types": [ + "Length", + "Perc" + ], + "default": [ + "medium" + ], + "keywords": [ + "thin", + "medium", + "thick" + ] + }, + "border-top-width": { + "map": "border", + "shorthand": "border-width" + }, + "border-right-width": { + "map": "border", + "shorthand": "border-width" + }, + "border-bottom-width": { + "map": "border", + "shorthand": "border-width" + }, + "border-left-width": { + "map": "border", + "shorthand": "border-width" + }, + "border-style": { + "shorthand": "border-style", + "map": "border", + "properties": [ + "border-top-style", + "border-right-style", + "border-bottom-style", + "border-left-style" + ], + "types": [], + "default": [ + "none" + ], + "keywords": [ + "none", + "hidden", + "dotted", + "dashed", + "solid", + "double", + "groove", + "ridge", + "inset", + "outset" + ] + }, + "border-top-style": { + "map": "border", + "shorthand": "border-style" + }, + "border-right-style": { + "map": "border", + "shorthand": "border-style" + }, + "border-bottom-style": { + "map": "border", + "shorthand": "border-style" + }, + "border-left-style": { + "map": "border", + "shorthand": "border-style" + }, + "border-color": { + "shorthand": "border-color", + "map": "border", + "properties": [ + "border-top-color", + "border-right-color", + "border-bottom-color", + "border-left-color" + ], + "types": [ + "Color" + ], + "default": [ + "currentcolor" + ], + "keywords": [] + }, + "border-top-color": { + "map": "border", + "shorthand": "border-color" + }, + "border-right-color": { + "map": "border", + "shorthand": "border-color" + }, + "border-bottom-color": { + "map": "border", + "shorthand": "border-color" + }, + "border-left-color": { + "map": "border", + "shorthand": "border-color" + }, + "grid-row": { + "shorthand": "grid-row", + "properties": [ + "grid-row-start", + "grid-row-end" + ], + "types": [ + "Iden", + "Number" + ], + "multiple": true, + "valueSeparator": { + "typ": "Literal", + "val": "/" + }, + "default": [ + "auto" + ], + "keywords": [ + "auto", + "span" + ] + }, + "grid-row-start": { + "shorthand": "grid-row" + }, + "grid-row-end": { + "shorthand": "grid-row" + } + }, + "map": { + "flex-flow": { + "shorthand": "flex-flow", + "pattern": "flex-direction flex-wrap", + "keywords": [], + "default": [ + "row", + "nowrap" + ], + "properties": { + "flex-direction": { + "keywords": [ + "row", + "row-reverse", + "column", + "column-reverse" + ], + "default": [ + "row" + ], + "types": [] + }, + "flex-wrap": { + "keywords": [ + "wrap", + "nowrap", + "wrap-reverse" + ], + "default": [ + "nowrap" + ], + "types": [] + } + } + }, + "flex-direction": { + "shorthand": "flex-flow" + }, + "flex-wrap": { + "shorthand": "flex-flow" + }, + "container": { + "shorthand": "container", + "pattern": "container-name container-type", + "keywords": [], + "default": [], + "properties": { + "container-name": { + "required": true, + "multiple": true, + "keywords": [ + "none" + ], + "default": [ + "none" + ], + "types": [ + "Iden", + "DashedIden" + ] + }, + "container-type": { + "previous": "container-name", + "prefix": { + "typ": "Literal", + "val": "/" + }, + "keywords": [ + "size", + "inline-size", + "normal" + ], + "default": [ + "normal" + ], + "types": [] + } + } + }, + "container-name": { + "shorthand": "container" + }, + "container-type": { + "shorthand": "container" + }, + "flex": { + "shorthand": "flex", + "pattern": "flex-grow flex-shrink flex-basis", + "keywords": [ + "auto", + "none", + "initial" + ], + "default": [], + "mapping": { + "0 1 auto": "initial", + "0 0 auto": "none", + "1 1 auto": "auto" + }, + "properties": { + "flex-grow": { + "required": true, + "keywords": [], + "default": [], + "types": [ + "Number" + ] + }, + "flex-shrink": { + "keywords": [], + "default": [], + "types": [ + "Number" + ] + }, + "flex-basis": { + "keywords": [ + "max-content", + "min-content", + "fit-content", + "fit-content", + "content", + "auto" + ], + "default": [], + "types": [ + "Length", + "Perc" + ] + } + } + }, + "flex-grow": { + "shorthand": "flex" + }, + "flex-shrink": { + "shorthand": "flex" + }, + "flex-basis": { + "shorthand": "flex" + }, + "columns": { + "shorthand": "columns", + "pattern": "column-count column-width", + "keywords": [ + "auto" + ], + "default": [ + "auto", + "auto auto" + ], + "properties": { + "column-count": { + "keywords": [ + "auto" + ], + "default": [ + "auto" + ], + "types": [ + "Number" + ] + }, + "column-width": { + "keywords": [ + "auto" + ], + "default": [ + "auto" + ], + "types": [ + "Length" + ] + } + } + }, + "column-count": { + "shorthand": "columns" + }, + "column-width": { + "shorthand": "columns" + }, + "transition": { + "shorthand": "transition", + "multiple": true, + "separator": { + "typ": "Comma" + }, + "pattern": "transition-property transition-duration transition-timing-function transition-delay transition-behavior", + "keywords": [ + "none", + "all" + ], + "default": [ + "0s", + "0ms", + "all", + "ease", + "none", + "normal" + ], + "mapping": { + "cubic-bezier(.25,.1,.25,1)": "ease", + "cubic-bezier(0,0,1,1)": "linear", + "cubic-bezier(.42,0,1,1)": "ease-in", + "cubic-bezier(0,0,.58,1)": "ease-out", + "cubic-bezier(.42,0,.58,.42)": "ease-in-out" + }, + "properties": { + "transition-property": { + "keywords": [ + "none", + "all" + ], + "default": [ + "all" + ], + "types": [ + "Iden" + ] + }, + "transition-duration": { + "keywords": [], + "default": [ + "0s", + "0ms", + "normal" + ], + "types": [ + "Time" + ] + }, + "transition-timing-function": { + "keywords": [ + "ease", + "ease-in", + "ease-out", + "ease-in-out", + "linear", + "step-start", + "step-end" + ], + "default": [ + "ease" + ], + "types": [ + "TimingFunction" + ], + "mapping": { + "cubic-bezier(.25,.1,.25,1)": "ease", + "cubic-bezier(0,0,1,1)": "linear", + "cubic-bezier(.42,0,1,1)": "ease-in", + "cubic-bezier(0,0,.58,1)": "ease-out", + "cubic-bezier(.42,0,.58,.42)": "ease-in-out" + } + }, + "transition-delay": { + "keywords": [], + "default": [ + "0s" + ], + "types": [ + "Time" + ] + }, + "transition-behavior": { + "keywords": [ + "normal", + "allow-discrete" + ], + "default": [ + "normal" + ], + "types": [] + } + } + }, + "transition-property": { + "shorthand": "transition" + }, + "transition-duration": { + "shorthand": "transition" + }, + "transition-timing-function": { + "shorthand": "transition" + }, + "transition-delay": { + "shorthand": "transition" + }, + "transition-behavior": { + "shorthand": "transition" + }, + "animation": { + "shorthand": "animation", + "separator": { + "typ": "Comma" + }, + "pattern": "animation-name animation-duration animation-timing-function animation-delay animation-iteration-count animation-direction animation-fill-mode animation-play-state animation-timeline", + "default": [ + "1", + "0s", + "0ms", + "none", + "ease", + "normal", + "running", + "auto" + ], + "properties": { + "animation-name": { + "keywords": [ + "none" + ], + "default": [ + "none" + ], + "types": [ + "Iden" + ] + }, + "animation-duration": { + "keywords": [ + "auto" + ], + "default": [ + "0s", + "0ms", + "auto" + ], + "types": [ + "Time" + ], + "mapping": { + "auto": "0s" + } + }, + "animation-timing-function": { + "keywords": [ + "ease", + "ease-in", + "ease-out", + "ease-in-out", + "linear", + "step-start", + "step-end" + ], + "default": [ + "ease" + ], + "types": [ + "TimingFunction" + ], + "mapping": { + "cubic-bezier(.25,.1,.25,1)": "ease", + "cubic-bezier(0,0,1,1)": "linear", + "cubic-bezier(.42,0,1,1)": "ease-in", + "cubic-bezier(0,0,.58,1)": "ease-out", + "cubic-bezier(.42,0,.58,.42)": "ease-in-out" + } + }, + "animation-delay": { + "keywords": [], + "default": [ + "0s", + "0ms" + ], + "types": [ + "Time" + ] + }, + "animation-iteration-count": { + "keywords": [ + "infinite" + ], + "default": [ + "1" + ], + "types": [ + "Number" + ] + }, + "animation-direction": { + "keywords": [ + "normal", + "reverse", + "alternate", + "alternate-reverse" + ], + "default": [ + "normal" + ], + "types": [] + }, + "animation-fill-mode": { + "keywords": [ + "none", + "forwards", + "backwards", + "both" + ], + "default": [ + "none" + ], + "types": [] + }, + "animation-play-state": { + "keywords": [ + "running", + "paused" + ], + "default": [ + "running" + ], + "types": [] + }, + "animation-timeline": { + "keywords": [ + "none", + "auto" + ], + "default": [ + "auto" + ], + "types": [ + "DashedIden", + "TimelineFunction" + ] + } + } + }, + "animation-name": { + "shorthand": "animation" + }, + "animation-duration": { + "shorthand": "animation" + }, + "animation-timing-function": { + "shorthand": "animation" + }, + "animation-delay": { + "shorthand": "animation" + }, + "animation-iteration-count": { + "shorthand": "animation" + }, + "animation-direction": { + "shorthand": "animation" + }, + "animation-fill-mode": { + "shorthand": "animation" + }, + "animation-play-state": { + "shorthand": "animation" + }, + "animation-timeline": { + "shorthand": "animation" + }, + "text-emphasis": { + "shorthand": "text-emphasis", + "pattern": "text-emphasis-color text-emphasis-style", + "default": [ + "none", + "currentcolor" + ], + "properties": { + "text-emphasis-style": { + "keywords": [ + "none", + "filled", + "open", + "dot", + "circle", + "double-circle", + "triangle", + "sesame" + ], + "default": [ + "none" + ], + "types": [ + "String" + ] + }, + "text-emphasis-color": { + "default": [ + "currentcolor" + ], + "types": [ + "Color" + ] + } + } + }, + "text-emphasis-style": { + "shorthand": "text-emphasis" + }, + "text-emphasis-color": { + "shorthand": "text-emphasis" + }, + "border": { + "shorthand": "border", + "pattern": "border-color border-style border-width", + "keywords": [ + "none" + ], + "default": [ + "0", + "none" + ], + "properties": { + "border-color": { + "types": [ + "Color" + ], + "default": [ + "currentcolor" + ], + "keywords": [] + }, + "border-style": { + "types": [], + "default": [ + "none" + ], + "keywords": [ + "none", + "hidden", + "dotted", + "dashed", + "solid", + "double", + "groove", + "ridge", + "inset", + "outset" + ] + }, + "border-width": { + "types": [ + "Length", + "Perc" + ], + "default": [ + "medium" + ], + "keywords": [ + "thin", + "medium", + "thick" + ] + } + } + }, + "border-color": { + "shorthand": "border" + }, + "border-style": { + "shorthand": "border" + }, + "border-width": { + "shorthand": "border" + }, + "list-style": { + "shorthand": "list-style", + "pattern": "list-style-type list-style-position list-style-image", + "keywords": [ + "none", + "outside" + ], + "default": [ + "none", + "outside" + ], + "properties": { + "list-style-position": { + "types": [], + "default": [ + "outside" + ], + "keywords": [ + "inside", + "outside" + ] + }, + "list-style-image": { + "default": [ + "none" + ], + "keywords": [ + "node" + ], + "types": [ + "UrlFunc", + "ImageFunc" + ] + }, + "list-style-type": { + "types": [ + "String", + "Iden", + "Symbols" + ], + "default": [ + "disc" + ], + "keywords": [ + "disc", + "circle", + "square", + "decimal", + "decimal-leading-zero", + "lower-roman", + "upper-roman", + "lower-greek", + "lower-latin", + "upper-latin", + "none" + ] + } + } + }, + "list-style-position": { + "shorthand": "list-style" + }, + "list-style-image": { + "shorthand": "list-style" + }, + "list-style-type": { + "shorthand": "list-style" + }, + "overflow": { + "shorthand": "overflow", + "pattern": "overflow-x overflow-y", + "keywords": [ + "auto", + "visible", + "hidden", + "clip", + "scroll" + ], + "default": [], + "mapping": { + "visible visible": "visible", + "auto auto": "auto", + "hidden hidden": "hidden", + "scroll scroll": "scroll" + }, + "properties": { + "overflow-x": { + "default": [], + "types": [], + "keywords": [ + "auto", + "visible", + "hidden", + "clip", + "scroll" + ] + }, + "overflow-y": { + "default": [], + "types": [], + "keywords": [ + "auto", + "visible", + "hidden", + "clip", + "scroll" + ] + } + } + }, + "overflow-x": { + "shorthand": "overflow" + }, + "overflow-y": { + "shorthand": "overflow" + }, + "outline": { + "shorthand": "outline", + "pattern": "outline-color outline-style outline-width", + "keywords": [ + "none" + ], + "default": [ + "0", + "none", + "currentcolor" + ], + "properties": { + "outline-color": { + "types": [ + "Color" + ], + "default": [ + "currentcolor" + ], + "keywords": [ + "currentcolor" + ] + }, + "outline-style": { + "types": [], + "default": [ + "none" + ], + "keywords": [ + "auto", + "none", + "dotted", + "dashed", + "solid", + "double", + "groove", + "ridge", + "inset", + "outset" + ] + }, + "outline-width": { + "types": [ + "Length", + "Perc" + ], + "default": [ + "medium" + ], + "keywords": [ + "thin", + "medium", + "thick" + ] + } + } + }, + "outline-color": { + "shorthand": "outline" + }, + "outline-style": { + "shorthand": "outline" + }, + "outline-width": { + "shorthand": "outline" + }, + "font": { + "shorthand": "font", + "pattern": "font-weight font-style font-size line-height font-stretch font-variant font-family", + "keywords": [ + "caption", + "icon", + "menu", + "message-box", + "small-caption", + "status-bar", + "-moz-window, ", + "-moz-document, ", + "-moz-desktop, ", + "-moz-info, ", + "-moz-dialog", + "-moz-button", + "-moz-pull-down-menu", + "-moz-list", + "-moz-field" + ], + "default": [], + "properties": { + "font-weight": { + "types": [ + "Number" + ], + "default": [ + "400", + "normal" + ], + "keywords": [ + "normal", + "bold", + "lighter", + "bolder" + ], + "constraints": { + "value": { + "min": "1", + "max": "1000" + } + }, + "mapping": { + "thin": "100", + "hairline": "100", + "extra light": "200", + "ultra light": "200", + "light": "300", + "normal": "400", + "regular": "400", + "medium": "500", + "semi bold": "600", + "demi bold": "600", + "bold": "700", + "extra bold": "800", + "ultra bold": "800", + "black": "900", + "heavy": "900", + "extra black": "950", + "ultra black": "950" + } + }, + "font-style": { + "types": [ + "Angle" + ], + "default": [ + "normal" + ], + "keywords": [ + "normal", + "italic", + "oblique" + ] + }, + "font-size": { + "types": [ + "Length", + "Perc" + ], + "default": [], + "keywords": [ + "xx-small", + "x-small", + "small", + "medium", + "large", + "x-large", + "xx-large", + "xxx-large", + "larger", + "smaller" + ], + "required": true + }, + "line-height": { + "types": [ + "Length", + "Perc", + "Number" + ], + "default": [ + "normal" + ], + "keywords": [ + "normal" + ], + "previous": "font-size", + "prefix": { + "typ": "Literal", + "val": "/" + } + }, + "font-stretch": { + "types": [ + "Perc" + ], + "default": [ + "normal" + ], + "keywords": [ + "ultra-condensed", + "extra-condensed", + "condensed", + "semi-condensed", + "normal", + "semi-expanded", + "expanded", + "extra-expanded", + "ultra-expanded" + ], + "mapping": { + "ultra-condensed": "50%", + "extra-condensed": "62.5%", + "condensed": "75%", + "semi-condensed": "87.5%", + "normal": "100%", + "semi-expanded": "112.5%", + "expanded": "125%", + "extra-expanded": "150%", + "ultra-expanded": "200%" + } + }, + "font-variant": { + "types": [], + "default": [ + "normal" + ], + "keywords": [ + "normal", + "none", + "common-ligatures", + "no-common-ligatures", + "discretionary-ligatures", + "no-discretionary-ligatures", + "historical-ligatures", + "no-historical-ligatures", + "contextual", + "no-contextual", + "historical-forms", + "small-caps", + "all-small-caps", + "petite-caps", + "all-petite-caps", + "unicase", + "titling-caps", + "ordinal", + "slashed-zero", + "lining-nums", + "oldstyle-nums", + "proportional-nums", + "tabular-nums", + "diagonal-fractions", + "stacked-fractions", + "ordinal", + "slashed-zero", + "ruby", + "jis78", + "jis83", + "jis90", + "jis04", + "simplified", + "traditional", + "full-width", + "proportional-width", + "ruby", + "sub", + "super", + "text", + "emoji", + "unicode" + ] + }, + "font-family": { + "types": [ + "String", + "Iden" + ], + "default": [], + "keywords": [ + "serif", + "sans-serif", + "monospace", + "cursive", + "fantasy", + "system-ui", + "ui-serif", + "ui-sans-serif", + "ui-monospace", + "ui-rounded", + "math", + "emoji", + "fangsong" + ], + "required": true, + "multiple": true, + "separator": { + "typ": "Comma" + } + } + } + }, + "font-weight": { + "shorthand": "font" + }, + "font-style": { + "shorthand": "font" + }, + "font-size": { + "shorthand": "font" + }, + "line-height": { + "shorthand": "font" + }, + "font-stretch": { + "shorthand": "font" + }, + "font-variant": { + "shorthand": "font" + }, + "font-family": { + "shorthand": "font" + }, + "background": { + "shorthand": "background", + "pattern": "background-attachment background-origin background-clip background-color background-image background-repeat background-position background-size", + "keywords": [ + "none" + ], + "default": [ + "0 0", + "none", + "auto", + "repeat", + "transparent", + "#0000", + "scroll", + "padding-box", + "border-box" + ], + "multiple": true, + "set": { + "background-origin": [ + "background-clip" + ] + }, + "separator": { + "typ": "Comma" + }, + "properties": { + "background-repeat": { + "types": [], + "default": [ + "repeat" + ], + "multiple": true, + "keywords": [ + "repeat-x", + "repeat-y", + "repeat", + "space", + "round", + "no-repeat" + ], + "mapping": { + "repeat no-repeat": "repeat-x", + "no-repeat repeat": "repeat-y", + "repeat repeat": "repeat", + "space space": "space", + "round round": "round", + "no-repeat no-repeat": "no-repeat" + } + }, + "background-color": { + "types": [ + "Color" + ], + "default": [ + "#0000", + "transparent" + ], + "multiple": true, + "keywords": [] + }, + "background-image": { + "types": [ + "UrlFunc", + "ImageFunc" + ], + "default": [ + "none" + ], + "keywords": [ + "none" + ] + }, + "background-attachment": { + "types": [], + "default": [ + "scroll" + ], + "multiple": true, + "keywords": [ + "scroll", + "fixed", + "local" + ] + }, + "background-clip": { + "types": [], + "default": [ + "border-box" + ], + "multiple": true, + "keywords": [ + "border-box", + "padding-box", + "content-box", + "text" + ] + }, + "background-origin": { + "types": [], + "default": [ + "padding-box" + ], + "multiple": true, + "keywords": [ + "border-box", + "padding-box", + "content-box" + ] + }, + "background-position": { + "multiple": true, + "types": [ + "Perc", + "Length" + ], + "default": [ + "0 0", + "top left", + "left top" + ], + "keywords": [ + "top", + "left", + "center", + "bottom", + "right" + ], + "mapping": { + "left": "0", + "top": "0", + "center": "50%", + "center center": "50%", + "50% 50%": "50%", + "bottom": "100%", + "right": "100%" + }, + "constraints": { + "mapping": { + "max": 2 + } + } + }, + "background-size": { + "multiple": true, + "previous": "background-position", + "prefix": { + "typ": "Literal", + "val": "/" + }, + "types": [ + "Perc", + "Length" + ], + "default": [ + "auto", + "auto auto" + ], + "keywords": [ + "auto", + "cover", + "contain" + ], + "mapping": { + "auto auto": "auto" + } + } + } + }, + "background-repeat": { + "shorthand": "background" + }, + "background-color": { + "shorthand": "background" + }, + "background-image": { + "shorthand": "background" + }, + "background-attachment": { + "shorthand": "background" + }, + "background-clip": { + "shorthand": "background" + }, + "background-origin": { + "shorthand": "background" + }, + "background-position": { + "shorthand": "background" + }, + "background-size": { + "shorthand": "background" + } + }, + "property": { + "transform-origin": { + "pattern": [ + [ + "left|center|right", + "top|center|bottom", + "" + ], + [ + "left|center|right", + "top|center|bottom" + ], + [ + "center|left|right|center|top|bottom|" + ] + ], + "mapping": { + "center": { + "typ": "Perc", + "val": 50 + }, + "left": { + "typ": "Perc", + "val": 0 + }, + "right": { + "typ": "Perc", + "val": 100 + }, + "top": { + "typ": "Perc", + "val": 0 + }, + "bottom": { + "typ": "Perc", + "val": 100 + } + } + } + } +} \ No newline at end of file diff --git a/src/lib/ast/clone.ts b/src/lib/ast/clone.ts index 9389a67e..c2fef793 100644 --- a/src/lib/ast/clone.ts +++ b/src/lib/ast/clone.ts @@ -22,23 +22,28 @@ export function cloneNode( for (const [name, value] of Object.entries(node)) { if (value == null || typeof value != "object") { + // @ts-ignore clone[name] = value; } else if (Array.isArray(value)) { - clone[name] = - !cloneChildren && name == checkNode - ? [] - : value.map((c) => { - const newObj = cloneNode(c, cloneChildren, cloneMap); + // @ts-ignore + clone[name] = []; - cloneMap?.set?.(c, newObj); - return newObj; - }); + if (cloneChildren || name !== checkNode) { + for (const c of value) { + const newObj = cloneNode(c, cloneChildren, cloneMap); + cloneMap?.set?.(c, newObj); + // @ts-ignore + clone[name].push(newObj); + } + } } else { + // @ts-ignore clone[name] = { ...value }; } } for (const symbol of Object.getOwnPropertySymbols(node)) { + // @ts-ignore clone[symbol] = node[symbol]; } diff --git a/src/lib/ast/expand.ts b/src/lib/ast/expand.ts index 42a9f6ec..015d1382 100644 --- a/src/lib/ast/expand.ts +++ b/src/lib/ast/expand.ts @@ -1,10 +1,11 @@ import { splitRule } from "./minify.ts"; -import { combinators, RAW } from "../syntax/constants.ts"; +import { combinators, PARENT, RAW, STATE } from "../syntax/constants.ts"; import { parseString } from "../parser/parse.ts"; import { walkValues } from "./walk.ts"; import { renderValue } from "../renderer/render.ts"; import type { AstAtRule, AstNode, AstRule, AstStyleSheet, LiteralToken, Token } from "../../@types/index.d.ts"; -import { EnumToken } from "./types.ts"; +import { EnumAstNodeStatus, EnumToken } from "./types.ts"; +import { cloneNode } from "./clone.ts"; /** * expand css nesting ast nodes @@ -13,14 +14,32 @@ import { EnumToken } from "./types.ts"; * @private */ export function expand(ast: AstStyleSheet | AstAtRule | AstRule): AstNode { - const result = { ...ast, chi: [] }; + + if( + (ast as AstNode)[STATE] == EnumAstNodeStatus.Invalid || + (ast as AstNode)[STATE] == EnumAstNodeStatus.Disallowed || + (ast as AstNode)[STATE] == EnumAstNodeStatus.Unknown || + (ast as AstNode)[STATE] == EnumAstNodeStatus.Unparsed || + (ast as AstNode)[STATE] == EnumAstNodeStatus.Malformed + ) { + return ast; + } + + const result = Object.assign(cloneNode(ast), { chi: [] }) as AstStyleSheet | AstAtRule; + 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 +52,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); } } @@ -45,7 +79,18 @@ export function expand(ast: AstStyleSheet | AstAtRule | AstRule): AstNode { } function expandRule(node: AstRule): Array { - const ast: AstRule = { ...node, chi: node.chi.slice() }; + + if( + (node as AstNode)[STATE] == EnumAstNodeStatus.Invalid || + (node as AstNode)[STATE] == EnumAstNodeStatus.Disallowed || + (node as AstNode)[STATE] == EnumAstNodeStatus.Unknown || + (node as AstNode)[STATE] == EnumAstNodeStatus.Unparsed || + (node as AstNode)[STATE] == EnumAstNodeStatus.Malformed + ) { + return [node]; + } + + const ast: AstRule = Object.assign(cloneNode(node), {chi: node.chi.slice() }) as AstRule; const result: Array = []; if (ast.typ == EnumToken.RuleNodeType) { @@ -70,9 +115,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 +130,7 @@ function expandRule(node: AstRule): Array { [], ) .join(","); - - } else { + } else { let childSelectorCompound: string[] = []; let withCompound: string[] = []; let withoutCompound: string[] = []; @@ -102,7 +147,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/calc.ts b/src/lib/ast/features/calc.ts index 3f1405b7..c7709d31 100644 --- a/src/lib/ast/features/calc.ts +++ b/src/lib/ast/features/calc.ts @@ -160,7 +160,8 @@ export class ComputeCalcExpressionFeature { const children: Token[] = parent.typ == EnumToken.DeclarationNodeType ? (parent).val - : parent.chi; + : // @ts-ignore + parent.chi; if (values.length == 1 && values[0].typ != EnumToken.BinaryExpressionTokenType) { for (let i = 0; i < children.length; i++) { diff --git a/src/lib/ast/features/if.ts b/src/lib/ast/features/if.ts index 825bd9a0..3d853b66 100644 --- a/src/lib/ast/features/if.ts +++ b/src/lib/ast/features/if.ts @@ -13,7 +13,7 @@ import type { import { EnumToken } from "../types.ts"; import { renderValue } from "../../renderer/render.ts"; import { FeatureWalkMode } from "./type.ts"; -import { PARENT, TOKENS } from "../../syntax/constants.ts"; +import { LOC, PARENT, TOKENS } from "../../syntax/constants.ts"; import { equalsIgnoreCase } from "../../parser/utils/text.ts"; import { replaceNodeOrValue } from "../../parser/utils/token.ts"; import { cloneNode } from "../../ast/clone.ts"; @@ -32,7 +32,7 @@ function substituteIfElseNode( parentWrapper: FunctionToken, cache: Set, ): AstNode[] { - const result: AstNode = []; + const result: AstNode[] = [] as AstNode[]; let nodeMap = new Map(); let clonedDeclaration; @@ -60,6 +60,7 @@ function substituteIfElseNode( : ((node as IfElseConditionToken).r as IfConditionToken).r, ); + // @ts-expect-error if (targetParentWrapper.typ != EnumToken.DeclarationNodeType) { let index: number = (targetParentWrapper as FunctionToken).chi.indexOf(targetWrapper); if (index != -1) { @@ -104,6 +105,7 @@ function substituteIfElseNode( ).r, ); + // @ts-ignore cache.add((siblingWrapper.chi[k] as IfElseConditionToken).l); } } @@ -140,6 +142,7 @@ function substituteIfElseNode( replaceNodeOrValue( nodeMap.get(parentWrapper), + // @ts-expect-error nodeMap.get(targetWrapper.typ === EnumToken.DeclarationNodeType ? node : targetWrapper), node.r.at(-1)?.typ === EnumToken.SemiColonTokenType ? trimArray(node.r.slice(0, -1)) : node.r, ); @@ -152,6 +155,10 @@ function substituteIfElseNode( chi: [] as Token[], }) as AstAtRule; + if (declaration[PARENT] != null) { + atRule[LOC] = declaration[PARENT][LOC]!; + } + atRule[TOKENS] = [{ typ: EnumToken.ParensTokenType, chi: (left as FunctionToken).chi.slice() }]; const minify: boolean = atRule.nam !== "supports"; @@ -185,6 +192,10 @@ function substituteIfElseNode( atRule[TOKENS] = [left]; atRule.val = atRule[TOKENS]!.reduce((acc: string, curr: Token) => acc + renderValue(curr), ""); + if (declaration[PARENT] != null) { + atRule[LOC] = declaration[PARENT][LOC]!; + } + clonedDeclaration = cloneNode(declaration, true, nodeMap) as AstDeclaration; replaceNodeOrValue( @@ -220,7 +231,7 @@ function processNode(declarationNode: AstDeclaration, cache: Set): AstN const { node: declaration, value: node } = findByValue(astNode, nodeMatcher) ?? {}; if (declaration == null || node == null) { - result.push(astNode); + result.push(astNode as Token); continue; } @@ -231,7 +242,9 @@ function processNode(declarationNode: AstDeclaration, cache: Set): AstN if (node!.node!.typ === EnumToken.WildCardFunctionTokenType) { for (i = 0; i < (node!.node as FunctionToken).chi.length; i++) { stack.push( + // @ts-expect-error ...substituteIfElseNode( + // @ts-expect-error declaration, (node!.node as FunctionToken).chi[i] as IfConditionToken | IfElseConditionToken, node!.node as FunctionToken, @@ -242,7 +255,9 @@ function processNode(declarationNode: AstDeclaration, cache: Set): AstN } } else { stack.push( + // @ts-expect-error ...substituteIfElseNode( + // @ts-expect-error declaration, node!.node as IfConditionToken, parentWrapper as FunctionToken, @@ -254,9 +269,11 @@ function processNode(declarationNode: AstDeclaration, cache: Set): AstN } if (result.length > 0) { + // @ts-expect-error replaceNodeOrValue(declarationNode[PARENT], declarationNode, result); } // else remove node? + // @ts-expect-error return result; } @@ -278,7 +295,7 @@ export class ExpandIfFeature { } } - run(declaration: AstDeclaration): AstNode | null { - return processNode(declaration, new Set()); + run(declaration: AstDeclaration): AstNode | AstNode[] | null { + return processNode(declaration, new Set()) as AstNode[]; } } diff --git a/src/lib/ast/features/prefix.ts b/src/lib/ast/features/prefix.ts index 12450329..debe40d6 100644 --- a/src/lib/ast/features/prefix.ts +++ b/src/lib/ast/features/prefix.ts @@ -90,17 +90,19 @@ function replaceAstNodes(tokens: Token[], root?: AstNode): boolean { const isPseudClass: boolean = pseudoAliasMap[key].startsWith("::"); (value as PseudoClassToken).val = pseudoAliasMap[key]; - if ( - value.typ == EnumToken.IdenTokenType && - ["min-resolution", "max-resolution"].includes((value as IdentToken).val) && - parent?.typ == EnumToken.MediaQueryConditionTokenType && - (parent as MediaQueryConditionToken).r?.[0]?.typ == EnumToken.NumberTokenType - ) { - Object.assign((parent as MediaQueryConditionToken).r?.[0], { - typ: EnumToken.ResolutionTokenType, - unit: "x", - }); - } else if (isPseudClass && value.typ == EnumToken.PseudoElementTokenType) { + // if ( + // value.typ == EnumToken.IdenTokenType && + // ["min-resolution", "max-resolution"].includes((value as IdentToken).val) && + // parent?.typ == EnumToken.MediaQueryConditionTokenType && + // (parent as MediaQueryConditionToken).r?.[0]?.typ == EnumToken.NumberTokenType + // ) { + // Object.assign((parent as MediaQueryConditionToken).r?.[0], { + // typ: EnumToken.ResolutionTokenType, + // unit: "x", + // }); + // } + // else + if (isPseudClass && value.typ == EnumToken.PseudoElementTokenType) { // @ts-ignore value.typ = EnumToken.PseudoClassTokenType; } @@ -386,36 +388,11 @@ export class ComputePrefixFeature { let type: string = ""; let tokens = token.chi.slice(); - // while ( - // i < tokens.length && - // (tokens[i].typ === EnumToken.WhitespaceTokenType || tokens[i].typ === EnumToken.CommentTokenType) - // ) { - // i++; - // } - - // if (i >= tokens.length || tokens[i].typ !== EnumToken.IdenTokenType) { - // return; - // } - // linear or radial if (equalsIgnoreCase((tokens[i] as IdentToken).val, "linear")) { type = "linear-gradient"; i++; } - // else { - // return; - // } - - // while ( - // i < tokens.length && - // (tokens[i].typ === EnumToken.WhitespaceTokenType || tokens[i].typ === EnumToken.CommentTokenType) - // ) { - // i++; - // } - - // if (tokens[i].typ !== EnumToken.CommaTokenType) { - // return; - // } tokens.splice(0, i + 1); 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..88d5a1bf 100644 --- a/src/lib/ast/features/transform.ts +++ b/src/lib/ast/features/transform.ts @@ -15,7 +15,11 @@ 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.AtRuleNodeType, + EnumToken.KeyframesRuleNodeType, + ]); get ordering(): number { return 3; @@ -66,8 +70,6 @@ export class TransformCssFeature { ); } - // consumeWhitespace(children); - let { matrix, cumulative, minified } = compute(children as Token[]) ?? { matrix: null, cumulative: null, diff --git a/src/lib/ast/find.ts b/src/lib/ast/find.ts index 16e55e27..23bd1b84 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, @@ -97,6 +98,7 @@ export function findByValue( for (const { value, parent, root: rootNode, parents } of walkValues(source, node)) { if (matcher(value, node)) { + // @ts-ignore return { node, value: { node: value, parent, root: rootNode, parents } }; } } @@ -238,7 +240,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])) { @@ -247,18 +249,24 @@ export function findValue( } else if (ast?.typ === EnumToken.DeclarationNodeType) { tokens = (ast as AstDeclaration).val; } else if (ast != null) { + // @ts-ignore if (matcher(ast, ast?.[PARENT])) { + // @ts-ignore return { node: ast, parent: ast?.[PARENT], root: null, parents: null }; } + // @ts-ignore if (Array.isArray(ast.chi)) { + // @ts-ignore tokens = ast.chi; } } if (Array.isArray(tokens)) { for (const { value, parent, root: rootNode, parents } of walkValues(tokens, ast)) { + // @ts-ignore if (matcher(value, parent, rootNode, parents)) { + // @ts-ignore return { node: value, parent, root: rootNode, parents }; } } diff --git a/src/lib/ast/minify.ts b/src/lib/ast/minify.ts index 26c93106..d232bd67 100644 --- a/src/lib/ast/minify.ts +++ b/src/lib/ast/minify.ts @@ -5,8 +5,8 @@ import { walkValues } from "./walk.ts"; import type { AstAtRule, AstDeclaration, - AstKeyFrameRule, AstKeyframesAtRule, + AstKeyframesRule, AstNode, AstRule, AstStyleSheet, @@ -39,7 +39,7 @@ 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,26 @@ export function minify( let parents: Set; let replacement: AstNode | null; - if (!("features" in options)) { - // @ts-ignore - options = { + let { sourcemap, module, ...options2 } = options as ParserOptions; + + if (!((options2 as MinifyFeatureOptions).features != null)) { + options2 = { removeDuplicateDeclarations: true, computeShorthand: true, computeCalcExpression: true, removePrefix: false, - features: [], - ...options, - }; + features: [] as Function[], + ...options2, + } as MinifyFeatureOptions; 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 +129,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,16 +139,18 @@ 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, + : // @ts-ignore + replacement.nam, ); } const result = feature.run( - replacement, - options, - parent[PARENT] ?? ast, + replacement as AstRule | AstAtRule, + options2, + // @ts-ignore + parent[PARENT] ?? (ast as AstRule | AstAtRule | AstStyleSheet), context, FeatureWalkMode.Pre, ); @@ -162,28 +166,29 @@ export function minify( replacement != parent && parent[PARENT] != null ) { + // @ts-ignore replaceNodeOrValue(parent[PARENT] as AstRule | AstAtRule | AstStyleSheet, parent, replacement); } - if ("chi" in replacement) { + // @ts-ignore + if (replacement.chi != null) { // @ts-ignore for (const node of replacement.chi) { - // node[PARENT] = replacement; + node[PARENT] = replacement; parents.add(node as AstNode); } } } - 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 as AstStyleSheet, 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) { @@ -194,7 +199,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 +209,9 @@ export function minify( const result = feature.run( replacement as AstRule | AstAtRule, - options, - parent[PARENT] ?? ast, + options2, + // @ts-ignore + parent[PARENT] ?? (ast as AstRule | AstAtRule | AstStyleSheet), context, FeatureWalkMode.Post, ); @@ -226,19 +232,21 @@ export function minify( replaceNodeOrValue(parent[PARENT], parent, replacement); } - if ("chi" in replacement) { + // @ts-ignore + if (replacement.chi != null) { + // @ts-ignore for (const node of replacement.chi!) { - // node[PARENT] = replacement; + node[PARENT] = replacement; parents.add(node as AstNode); } } } 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 as AstStyleSheet, options2, context, FeatureWalkMode.Post); } } } @@ -262,6 +270,7 @@ function transformAtRuleMediaPrelude(values: Token[]) { // @ts-ignore values[values.indexOf(value)] = (value as MediaQueryConditionToken).l; } else { + // @ts-ignore replaceNodeOrValue(parent, value, (value as MediaQueryConditionToken).l); // @ts-ignore value = (value as MediaQueryConditionToken).l; @@ -338,8 +347,10 @@ function transformAtRuleMediaPrelude(values: Token[]) { const p = parents?.[parents?.indexOf?.(parent) + 1]; if (p != null) { + // @ts-ignore replaceNodeOrValue(p, parent, replacement); } else { + // @ts-ignore values.splice(values.indexOf(parent), 1, replacement as Token); } @@ -357,9 +368,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; @@ -495,8 +506,8 @@ function doMinify( } while (previous?.typ === EnumToken.CommentNodeType) { + // @ts-ignore previous = ast.chi[--nodeIndex]; - continue; } node = ast.chi![i] as AstNode; @@ -517,15 +528,16 @@ 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); + // @ts-ignore ast.chi.splice(i, 1); previous = (ast?.chi?.[nodeIndex] as AstNode) ?? null; i = nodeIndex; @@ -535,22 +547,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; @@ -625,6 +637,7 @@ function doMinify( (ast as AstAtRule).nam === (node as AstAtRule).nam && (ast as AstAtRule).val === (node as AstAtRule).val ) { + // @ts-ignore replaceNodeOrValue(ast as AstAtRule, node as AstAtRule, (node as AstAtRule).chi!); i--; continue; @@ -751,6 +764,7 @@ function doMinify( ")"; const sel2 = node[OPTIMIZED].selector.reduce( (acc: string, curr: string[]) => + // @ts-ignore (acc.length > 0 ? acc + "," : "") + node[OPTIMIZED].optimized[0] + curr.join(""), "", ); @@ -850,6 +864,7 @@ function doMinify( ")"; const sel2 = node[OPTIMIZED].selector.reduce( (acc: string, curr: string[]) => + // @ts-ignore (acc.length > 0 ? acc + "," : "") + node[OPTIMIZED].optimized[0] + curr.join(""), "", ); @@ -867,11 +882,14 @@ function doMinify( ); node[TOKENS] = null; } + // @ts-ignore } else if (node[OPTIMIZED]?.optimized.length > 0) { + // @ts-ignore const sel = node[OPTIMIZED].optimized.join(""); if (sel.length < node.sel.length) { node.sel = sel; + // @ts-ignore node[RAW] = [node[OPTIMIZED].optimized.slice()]; node[TOKENS] = null; } @@ -902,12 +920,15 @@ 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) || + // @ts-ignore (node.typ == EnumToken.AtRuleNodeType && (node as AstAtRule).nam !== "font-face" && + // @ts-ignore (node as AstAtRule).nam === (previous as AstAtRule).nam) ) { + // @ts-ignore node.chi.unshift(...previous.chi); doMinify(node, options, recursive, errors, nestingContent, context); @@ -920,7 +941,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 +1128,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 +1614,6 @@ function wrapNodes( * Diff nodes * @param n1 * @param n2 - * @param reducer * @param options * * @private @@ -1744,20 +1766,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] as AstNode; + + 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] as AstNode; + + 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/node.ts b/src/lib/ast/node.ts new file mode 100644 index 00000000..7d98c19c --- /dev/null +++ b/src/lib/ast/node.ts @@ -0,0 +1,120 @@ +import type { AstNode, ErrorDescription, SourceLocation, Token } from "../../@types/index.d.ts"; +import { ERRORS, LOC, PARENT, STATE, TOKENS } from "../syntax/constants.ts"; +import { AstNodePropertyType, EnumAstNodeStatus } from "./types.ts"; + +/** + * + * @param node + * @param key + */ +export function getNodeProperty(node: AstNode, key: 'parent'): AstNode | Token | null; +/** + * + * @param node + * @param key + */ +export function getNodeProperty(node: AstNode, key: 'location'): SourceLocation | null; +/** + * + * @param node + * @param key + */ +export function getNodeProperty(node: AstNode, key: 'state'): EnumAstNodeStatus | null; +/** + * + * @param node + * @param key + */ +export function getNodeProperty(node: AstNode, key: 'errors'): ErrorDescription[] | null; +/** + * + * @param node + * @param key + */ +export function getNodeProperty(node: AstNode, key: 'tokens'): Token[] | null; + +/** + * + * @param node + * @param key + * @returns + */ +export function getNodeProperty(node: AstNode, key: AstNodePropertyType): any { + + switch (key) { + + case "parent": + return node[PARENT]; + case "location": + return node[LOC]; + case "state": + return node[STATE]; + case "errors": + return node[ERRORS]; + case "tokens": + return node[TOKENS]; + } + return undefined; +} + +/** + * + * @param node + * @param key + * @param value + */ +export function setNodeProperty(node: AstNode, key: 'parent', value: AstNode | Token | null): void; +/** + * + * @param node + * @param key + * @param value + */ +export function setNodeProperty(node: AstNode, key: 'location', value: SourceLocation | null): void; +/** + * + * @param node + * @param key + * @param value + */ +export function setNodeProperty(node: AstNode, key: 'state', value: EnumAstNodeStatus | null): void; +/** + * + * @param node + * @param key + * @param value + */ +export function setNodeProperty(node: AstNode, key: 'errors', value: ErrorDescription[] | null): void; +/** + * + * @param node + * @param key + * @param value + */ +export function setNodeProperty(node: AstNode, key: 'tokens', value: Token[] | null): void; + +/** + * + * @param node + * @param key + * @param value + */ +export function setNodeProperty(node: AstNode, key: AstNodePropertyType, value: any): void { + switch (key) { + case "parent": + node[PARENT] = value; + break; + case "location": + node[LOC] = value; + break; + case "state": + node[STATE] = value; + break; + case "errors": + node[ERRORS] = value; + break; + case "tokens": + node[TOKENS] = value; + break; + } +} \ No newline at end of file diff --git a/src/lib/ast/types.ts b/src/lib/ast/types.ts index 200c50a9..67a98ee3 100644 --- a/src/lib/ast/types.ts +++ b/src/lib/ast/types.ts @@ -48,6 +48,8 @@ export enum EnumAstNodeStatus { Malformed, } +export declare type AstNodePropertyType = "state" | "errors" | "location" | "tokens" | "parent" | "selector"; + /** * Enum of validation levels * @deprecated @@ -386,7 +388,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..df5c7825 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,21 @@ 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) { + // @ts-ignore + parents.splice(i, 0, ...(reverse ? node[TOKENS]!.toReversed() : node[TOKENS])); + // @ts-ignore + } else if (Array.isArray(node.val)) { + // @ts-ignore + parents.splice(i, 0, ...(reverse ? node.val.toReversed() : node.val)); + } + } + + // @ts-ignore + if (node["chi"] != null && (!isNumeric || ((option as number) & WalkerOptionEnum.IgnoreChildren) === 0)) { + // @ts-ignore + parents.splice(i, 0, ...(reverse ? node.chi!.toReversed() : node.chi)); for (const child of (node).chi) { map.set(child, node); @@ -270,12 +449,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 = @@ -339,7 +512,7 @@ export function* walkValues( do { yield result; - next = map.get(result) ?? root; + next = map.get(result as Token) ?? root; if (next == result) { break; diff --git a/src/lib/fs/resolve.ts b/src/lib/fs/resolve.ts index e39004d8..f863f743 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,62 @@ 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("/") || url.match(/^[a-zA-Z]:/) ? 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..c6d1a79e 100644 --- a/src/lib/parser/declaration/map.ts +++ b/src/lib/parser/declaration/map.ts @@ -382,16 +382,27 @@ 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..219a10ca 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); } @@ -27,14 +27,12 @@ export class LineMap { getOffsets(offset: number): [number, number] { const line: number = this.search(offset); - if (offset < 0 || line < 0) { - return [1, 1]; - } - - const column: number = offset - this.lineStarts[line]; + // if (offset < 0 || line < 0) { + // return [1, 1]; + // } // [line, column] - return [line + 1, column === 0 ? 1 : column]; + return [line + 1, offset - this.lineStarts[line] + 1]; } /** @@ -76,12 +74,4 @@ export class LineMap { addLineStart(lineStart: number) { this.lineStarts.push(lineStart); } - - /** - * clone the linemap - * @returns - */ - clone(): LineMap { - return new LineMap(this.lineStarts.slice()); - } } diff --git a/src/lib/parser/parse.ts b/src/lib/parser/parse.ts index d62791a9..2742b020 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,13 +92,8 @@ 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), -); /** * Short-scoped name generator. @@ -108,21 +105,20 @@ const forbiddenStartCharacters: number[] = ["0", "1", "2", "3", "4", "5", "6", " * * @returns string */ -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); +export const getShortNameGenerator = memoize((): string => { + let value: string = keyNameCounter!.toString(36); + let val: number = value.charAt(0).charCodeAt(0); + keyNameCounter!++; + + // starts with'0' - '9' + while (48 <= val && val <= 57) { + value = keyNameCounter!.toString(36); keyNameCounter!++; + val = value.charAt(0).charCodeAt(0); + } - while (forbiddenStartCharacters.includes(value.charCodeAt(0))) { - value = keyNameCounter!.toString(36); - keyNameCounter!++; - } - - return value; - }, -); + return value; +}); function reject(reason?: any) { throw new Error(reason ?? "Parsing aborted"); @@ -416,6 +412,210 @@ 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") { + if ("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 { + visitors.push(...Object.entries(value)); + } + } 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") { + // visitors.push(...Object.entries(value)); + + 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 +693,23 @@ 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` }); - } - } - } - - if (Array.isArray(iter)) { - // @ts-expect-error - iter = iter[Symbol.iterator]() as Iterator; - } + // ast[ROOT] = ast; + ast[LOC] = { + sta: 0, + end: 0, + srcId: options.source!.id, + }; - 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 +741,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 +751,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 +814,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 +828,193 @@ export function doParseSync( } let replacement: GenericVisitorResult; - let callable: GenericVisitorHandler; if (options.visitor != null) { + const handlers = [] as Array>; + const visitors = parseVisitors(options.visitor, errors); + + const subNodes: Array = []; 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>), - ); - } + 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 (visitorsHandlersMap!.has(key)) { - // @ts-ignore - handlers.push(...visitorsHandlersMap.get(key)!); - } + for (i = 0; i < nodes.length; i++) { + if (nodes[i] == null) { + break; + } - if (postVisitorsHandlersMap!.has(key)) { - // @ts-ignore - handlers.push(...postVisitorsHandlersMap.get(key)); - } + 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]!, + ); + break; + case EnumToken.DeclarationNodeType: + subNodes.push(...(nodes[i] as AstDeclaration).val); + break; + } + } - 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-ignore + if (nodes[i].chi != null) { + // @ts-ignore + subNodes.push(...nodes[i].chi); + } - // @ts-expect-error - replacement = callable(node, result[PARENT], ast, function* () { - if (parens == null) { - // @ts-expect-error - parens = [...result.parents()]; - } + if (subNodes.length > 0) { + if (freeBlock <= i) { + freeBlock = i + 1; + } - yield* parens[Symbol.iterator](); - }); + 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 == null || replacement == node) { - 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); + // } + + // // @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; - - if (Array.isArray(node)) { - break; + else if (typeof handler[keyName]! == "function") { + // @ts-ignore + handlers.push(handler[keyName]); } } + } - if (node != result.node) { - replaceNodeOrValue( - result.parent as AstRule | AstAtRule | AstKeyframesAtRule | AstKeyFrameRule | AstStyleSheet, - 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 = [] 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>)); - } - - let node = result.node; - - 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()]; + // @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 as GenericVisitorHandler); + } else if (Array.isArray(handler)) { + for (const h of handler) { + if (typeof h == "function") { + handlers.push(h); } - - yield* parens[Symbol.iterator](); - }, - ) as GenericVisitorResult; - - if (replacement == null) { - continue; - } - - if (replacement == null || replacement == node) { - continue; + } + } 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 (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()]; - } + if (handlers.length == 0) { + continue; + } - yield* parens[Symbol.iterator](); - }, - ); + let node = nodes[i]; - if (replacement == null) { - continue; - } + 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; - if (replacement != null && replacement != node) { - node = replacement as AstNode; + while (node != null) { + yield node; + node = node[PARENT] as AstNode; } } - } - - 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[])); - } - - 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: 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()]; - } - - yield* parens[Symbol.iterator](); - }); + }, + ) 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]) { + // @ts-ignore + replaceNodeOrValue(nodes[i][PARENT], nodes[i], node); + } } + + nodes = null; } if (invalidNodes.length > 0) { @@ -1095,24 +1044,6 @@ export function doParseSync( } } - 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); @@ -1145,7 +1076,7 @@ export function doParseSync( scoped: ModuleScopeEnumOptions.Local, naming: ModuleCaseTransformEnum.IgnoreCase, pattern: "", - generateScopedName, + generateScopedName: generateSyncScopedName, ...(typeof options.module != "object" ? {} : options.module), } as ModuleSyncOptions; @@ -1222,6 +1153,7 @@ export function doParseSync( ); } + // @ts-ignore if (node.typ == EnumToken.CssVariableDeclarationMapTokenType) { const from = (node as CssVariableMapTokenType).from.find( (t) => t.typ == EnumToken.IdenTokenType || isIdentColor(t), @@ -1273,7 +1205,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 +1214,6 @@ export function doParseSync( moduleSettings.pattern as string, moduleSettings.hashLength, ); - let value: string = result; mapping[node.nam] = "--" + @@ -1336,7 +1267,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 +1276,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 +1482,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 +1491,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; } @@ -1577,6 +1506,7 @@ export function doParseSync( (value.typ == EnumToken.IdenTokenType || isIdentColor(value)) && (value as IdentToken).val in importedCssVariables ) { + // @ts-ignore replaceNodeOrValue(parent, value, importedCssVariables[(value as IdentToken).val].val); } } @@ -1658,7 +1588,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 +1597,6 @@ export function doParseSync( moduleSettings.pattern as string, moduleSettings.hashLength, ); - let value: string = result; mapping[val] = moduleSettings.naming! & ModuleCaseTransformEnum.DashCaseOnly || @@ -1685,7 +1614,7 @@ export function doParseSync( if (moduleSettings.scoped! & ModuleScopeEnumOptions.Pure) { if (!hasIdOrClass) { throw new Error( - `pure module: No id or class found in selector '${node.sel}' at '${node[LOC]?.src ?? ""}':${node[LOC]?.sta?.lin ?? ""}:${node[LOC]?.sta?.col ?? ""}`, + `pure module: No id or class found in selector '${node.sel}' at '${options.source!.getOffsets(node[LOC]?.sta as number).join(":")}'`, ); } } @@ -1711,7 +1640,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 +1649,6 @@ export function doParseSync( moduleSettings.pattern as string, moduleSettings.hashLength, ); - let val: string = result; mapping[(value as DashedIdentToken | IdentToken).val] = prefix + @@ -1742,17 +1670,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; @@ -1853,178 +1779,26 @@ export async function doParse( chi: [], }; - 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>>> - >; + let tokens: Token[] = []; + let context: AstRuleList = ast; 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 @@ -2069,9 +1843,6 @@ export async function doParse( tokens.push(item.token); - // console.debug([item.token, {parensMatch, curlyBracketMatch}]); - - // if (parensMatch === 0) { if ( parensMatch === 0 && (item.token.typ === EnumToken.SemiColonTokenType || @@ -2082,7 +1853,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 +1944,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" @@ -2184,11 +1958,10 @@ export async function doParse( options.sourcesMap!.set(source.id, source); const parseInfo = { stream, - buffer: "", offset: 0, source, position: 0, - currentPosition: -1, + currentPosition: 0, } as ParseInfo; const root: ParseResult = await doParse( stream instanceof ReadableStream ? tokenizeStream(stream, parseInfo) : tokenize(parseInfo), @@ -2203,7 +1976,8 @@ export async function doParse( stats.nodesCount += root.stats.nodesCount; stats.tokensCount += root.stats.tokensCount; stats.imports.push(root.stats); - node[PARENT]!.chi.splice(node[PARENT]!.chi.indexOf(node), 1, ...root.ast.chi); + // @ts-ignore + node[PARENT]!.chi!.splice(node[PARENT]!.chi!.indexOf(node), 1, ...root.ast.chi); if (root.errors.length > 0) { errors.push(...root.errors); @@ -2223,289 +1997,198 @@ 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; + // @ts-ignore + if (nodes[i].chi != null) { + // @ts-ignore + 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); } - if (replacement == null || replacement == node) { - continue; - } + // 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]); + // } + // } + // } 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]) { + // @ts-ignore + replaceNodeOrValue(nodes[i]![PARENT] as AstNode, nodes[i], node); + } } + + nodes = null; } if (invalidNodes.length > 0) { @@ -2535,24 +2218,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); @@ -2678,7 +2343,7 @@ export async function doParse( time: 0, source, position: 0, - currentPosition: -1, + currentPosition: 0, } as ParseInfo; const root: ParseResult = await doParse( @@ -2698,6 +2363,7 @@ export async function doParse( continue; } + // @ts-ignore if (node.typ == EnumToken.CssVariableDeclarationMapTokenType) { const from = (node as CssVariableMapTokenType).from.find( (t) => t.typ == EnumToken.IdenTokenType || isIdentColor(t), @@ -2758,6 +2424,7 @@ export async function doParse( moduleSettings.pattern as string, moduleSettings.hashLength, ); + // @ts-ignore let value: string = result instanceof Promise ? await result : result; mapping[node.nam] = @@ -2800,6 +2467,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) { @@ -2868,19 +2537,17 @@ export async function doParse( const root: ParseResult = await doParse( stream instanceof ReadableStream ? tokenizeStream(stream, { - buffer: "", offset: 0, source: new SourceFile("", [], src.relative), position: 0, - currentPosition: -1, + currentPosition: 0, } as ParseInfo) : tokenize({ stream, - buffer: "", offset: 0, position: 0, source: new SourceFile(stream, [], src.relative), - currentPosition: -1, + currentPosition: 0, } as ParseInfo), Object.assign({}, options, { minify: false, @@ -2889,9 +2556,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; @@ -3154,33 +2823,17 @@ export async function doParse( for (const { value, parent } of walkValues(node.val, node)) { if (value.typ == EnumToken.DashedIdenTokenType) { - // if (!((value as DashedIdentToken).val in mapping)) { - // const result = - // moduleSettings.scoped! & ModuleScopeEnumOptions.Global - // ? (value as DashedIdentToken).val - // : moduleSettings.generateScopedName!( - // (value as DashedIdentToken).val, - // moduleSettings.filePath as string, - // moduleSettings.pattern as string, - // moduleSettings.hashLength, - // ); - // let val: string = result instanceof Promise ? await result : result; - - // mapping[(value as DashedIdentToken).val] = - // "--" + - // (moduleSettings.naming! & ModuleCaseTransformEnum.DashCaseOnly || - // moduleSettings.naming! & ModuleCaseTransformEnum.CamelCaseOnly - // ? getKeyName(val, moduleSettings.naming as ModuleCaseTransformEnum) - // : val); - // revMapping[mapping[(value as DashedIdentToken).val]] = (value as DashedIdentToken).val; - // } - (value as DashedIdentToken).val = mapping[(value as DashedIdentToken).val]; } else if ( (value.typ == EnumToken.IdenTokenType || isIdentColor(value)) && (value as IdentToken).val in importedCssVariables ) { - replaceNodeOrValue(parent, value, importedCssVariables[(value as IdentToken).val].val); + replaceNodeOrValue( + // @ts-ignore + parent as AstRule, + value, + importedCssVariables[(value as IdentToken).val].val, + ); } } } else if (node.typ == EnumToken.RuleNodeType) { @@ -3218,28 +2871,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 +2890,6 @@ export async function doParse( ); break; - // (parent as AstRule)[TOKENS]!.splice( - // (parent as AstRule)[TOKENS]!.indexOf(value), - // 1, - // ...(value as FunctionToken).chi, - // ); - - // break; } } }, @@ -3319,7 +2943,7 @@ export async function doParse( if (moduleSettings.scoped! & ModuleScopeEnumOptions.Pure) { if (!hasIdOrClass) { throw new Error( - `pure module: No id or class found in selector '${node.sel}' at '${node[LOC]?.src ?? ""}':${node[LOC]?.sta?.lin ?? ""}:${node[LOC]?.sta?.col ?? ""}`, + `pure module: No id or class found in selector '${node.sel}' at '${(options.source?.getOffsets?.(node[LOC]?.sta as number) ?? []).join(":")}'`, ); } } @@ -3443,7 +3067,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) { @@ -3510,7 +3134,9 @@ function parseNode( } if (i > 0) { - context.chi!.push(...(tokens.splice(0, i) as AstNode[]).filter((n) => n.typ !== EnumToken.WhitespaceTokenType)); + context.chi!.push( + ...(tokens.splice(0, i) as AstNode[]).filter((n: AstNode) => n.typ !== EnumToken.WhitespaceTokenType), + ); i = 0; } @@ -3566,7 +3192,7 @@ function parseNode( break; } - parent = parent[PARENT]; + parent = parent[PARENT] as AstNode; } node = parseAtRule( @@ -3645,7 +3271,7 @@ function parseNode( (node as AstNode)[STATE] == EnumAstNodeStatus.Unparsed || (node as AstNode)[STATE] == EnumAstNodeStatus.Malformed ) { - invalidNodes.push(node); + invalidNodes.push(node as AstDeclaration); } } } @@ -3654,6 +3280,8 @@ function parseNode( } /** + * @param stream + * @param context * @param options * @param errors * @param parseAsBlock @@ -3752,7 +3380,6 @@ export function parseAtRule( } if (syntax != null && atRule.nam !== "layer" && parseAsBlock !== blockAllowed) { - success = false; errors.push({ action: "drop", node: atRule, @@ -4102,8 +3729,8 @@ export function parseAtRule( let success: boolean = result.success; if (atRule.nam === "else") { - const siblings = (context as AstAtRule | AstStyleSheet).chi as AstNode[]; - let sibling: AstNode | null = null; + const siblings = (context as AstAtRule | AstStyleSheet).chi as AstNode[] | Token[]; + let sibling: AstNode | Token | null = null; let l: number = siblings.length; while (l--) { @@ -4124,9 +3751,12 @@ export function parseAtRule( if (sibling == null || sibling.typ !== EnumToken.AtRuleNodeType) { missingWhen = true; + // @ts-expect-error } else if (sibling.nam !== "when") { + // @ts-expect-error if (sibling.nam !== "else") { missingWhen = true; + // @ts-expect-error } else if (sibling.val === "") { definedAfterLastElse = true; } @@ -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; @@ -4565,11 +4192,10 @@ export async function parseDeclarations(declaration: string): Promise { @@ -4608,27 +4234,25 @@ export function parseString( ): Token[] { const parseInfo: ParseInfo = { stream: src, - buffer: "", offset: 0, time: 0, source: new SourceFile(src, [], ""), position: 0, - currentPosition: -1, + currentPosition: 0, }; - 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 +4369,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..e4a6e4dc 100644 --- a/src/lib/parser/tokenize.ts +++ b/src/lib/parser/tokenize.ts @@ -37,10 +37,12 @@ import { isHash, isHexColor, isIdent, + isIdentCodepoint, + isIdentStart, isNewLine, + isNonPrintable, isNumber, isPercentage, - isPseudo, isWhiteSpace, parseDimension, } from "../syntax/syntax.ts"; @@ -180,20 +182,20 @@ export const enum TokenMap { GREATERTHAN = 62, // '>', GREATER THAN } export function consumeString(parseInfo: ParseInfo): Array { - const quote = next(parseInfo); - let value: string; - let buffer: string = quote; + const quote: number = next(parseInfo).charCodeAt(0); + let charCode: number; + let decodeSegments: boolean = false; const result: Array = []; - while ((value = parseInfo.stream.charAt(parseInfo.currentPosition - parseInfo.offset + 1))) { - if (value == "\\") { - if ("\\" == parseInfo.stream.charAt(parseInfo.currentPosition - parseInfo.offset + 2)) { - buffer += next(parseInfo, 2); + while ((charCode = parseInfo.stream.charCodeAt(parseInfo.currentPosition - parseInfo.offset)) == charCode) { + if (charCode == TokenMap.REVERSE_SOLIDUS) { + if (charCode == parseInfo.stream.charCodeAt(parseInfo.currentPosition - parseInfo.offset + 1)) { + next(parseInfo, 2); continue; } - const sequence: string = peek(parseInfo, 6); + const sequence: string = peek(parseInfo, 7); let escapeSequence: string = ""; let codepoint: number; let i; @@ -220,68 +222,77 @@ export function consumeString(parseInfo: ParseInfo): Array { } if (escapeSequence.trimEnd().length > 0) { - const codepoint = parseInt(escapeSequence, 16); + // const codepoint = parseInt(escapeSequence, 16); + + // TODO set decode flag ON + // if ( + // codepoint == 0 || + // // leading surrogate + // (0xd800 <= codepoint && codepoint <= 0xdbff) || + // // trailing surrogate + // (0xdc00 <= codepoint && codepoint <= 0xdfff) + // ) { + // buffer += String.fromCodePoint(0xfffd); + // } else { + // buffer += String.fromCodePoint(codepoint); + // } + + const length: number = + escapeSequence.length + + 1 + + (isWhiteSpace(parseInfo.stream.charAt(parseInfo.currentPosition - parseInfo.offset)?.charCodeAt(0)) + ? 1 + : 0); - if ( - codepoint == 0 || - // leading surrogate - (0xd800 <= codepoint && codepoint <= 0xdbff) || - // trailing surrogate - (0xdc00 <= codepoint && codepoint <= 0xdfff) - ) { - buffer += String.fromCodePoint(0xfffd); - } else { - buffer += String.fromCodePoint(codepoint); - } + decodeSegments = true; - next( - parseInfo, - escapeSequence.length + - 1 + - (isWhiteSpace( - parseInfo.stream.charAt(parseInfo.currentPosition - parseInfo.offset + 1)?.charCodeAt(0), - ) - ? 1 - : 0), - ); + next(parseInfo, length); continue; } - buffer += next(parseInfo, 2); + next(parseInfo, 2); continue; } - if (value == quote) { - buffer += value; + if (charCode == quote) { + next(parseInfo); result.push( yieldResult( - buffer, parseInfo, /* hasNewLine ? EnumToken.BadStringTokenType : */ EnumToken.StringTokenType, + decodeSegments ? { decodeSegments } : null, ), ); - next(parseInfo); - buffer = ""; + return result; } - if (isNewLine(value.charCodeAt(0))) { - result.push(yieldResult(buffer + next(parseInfo), parseInfo, EnumToken.BadStringTokenType)); + if (isNewLine(charCode)) { + next(parseInfo); + result.push(yieldResult(parseInfo, EnumToken.BadStringTokenType)); return result; } - buffer += value; next(parseInfo); } // EOF - 'Unclosed-string' fixed - result.push(yieldResult(buffer + quote, parseInfo, EnumToken.StringTokenType)); + result.push(yieldResult(parseInfo, EnumToken.StringTokenType)); return result; } -export function yieldResult(val: string, parseInfo: ParseInfo, hint?: EnumToken): TokenizeResult { +export function yieldResult( + parseInfo: ParseInfo, + hint?: EnumToken, + options?: { decodeSegments: boolean } | null, +): TokenizeResult { + let val: string = parseInfo.stream.slice( + parseInfo.position - parseInfo.offset, + parseInfo.currentPosition - parseInfo.offset, + ); + let token: Token | null = null; let dimension: | DimensionToken @@ -293,7 +304,24 @@ export function yieldResult(val: string, parseInfo: ParseInfo, hint?: EnumToken) | FrequencyToken | null; - // console.debug(`Yield result: ${val}, ${hint}`); + if (options?.decodeSegments) { + val = val.replace(/\\([0-9a-fA-F]{1,6})(?:\s)?/g, (_, sequence) => { + const codepoint = parseInt(sequence, 16); + + if ( + codepoint == 0 || + // leading surrogate + (0xd800 <= codepoint && codepoint <= 0xdbff) || + // trailing surrogate + (0xdc00 <= codepoint && codepoint <= 0xdfff) || + codepoint > 0x10ffff + ) { + return "\uFFFD"; + } + + return String.fromCodePoint(codepoint); + }); + } if (hint != null) { let searchArray: string[] | null = null; @@ -349,7 +377,11 @@ export function yieldResult(val: string, parseInfo: ParseInfo, hint?: EnumToken) let slice: string = val.slice(1); const chr: string = val.charAt(0); - if (chr == "@" && isIdent(slice)) { + if (chr == "!" && equalsIgnoreCase("!important", val)) { + token = { + typ: EnumToken.ImportantTokenType, + } as Token; + } else if (chr == "@" && isIdent(slice)) { token = { typ: EnumToken.AtRuleTokenType, nam: slice, @@ -420,14 +452,14 @@ export function yieldResult(val: string, parseInfo: ParseInfo, hint?: EnumToken) parseInfo.position = parseInfo.currentPosition; - return { token, bytesIn: parseInfo.currentPosition + 1 }; + return { token, bytesIn: parseInfo.currentPosition }; } export function match(parseInfo: ParseInfo, input: string): boolean { let position: number = parseInfo.currentPosition - parseInfo.offset; for (let i: number = 0; i < input.length; i++) { - if (parseInfo.stream[position + i + 1] != input.charAt(i)) { + if (parseInfo.stream[position + i] != input.charAt(i)) { return false; } } @@ -437,23 +469,23 @@ export function match(parseInfo: ParseInfo, input: string): boolean { export function peek(parseInfo: ParseInfo, count: number = 1): string { if (count == 1) { - return parseInfo.stream.charAt(parseInfo.currentPosition - parseInfo.offset + 1); + return parseInfo.stream.charAt(parseInfo.currentPosition - parseInfo.offset); } const position = parseInfo.currentPosition - parseInfo.offset; - return parseInfo.stream.slice(position + 1, position + count + 1); + return parseInfo.stream.slice(position, position + count); } export function next(parseInfo: ParseInfo, count: number = 1): string { let position = parseInfo.currentPosition - parseInfo.offset; let char: string = - count == 1 ? parseInfo.stream.charAt(position + 1) : parseInfo.stream.slice(position + 1, position + 1 + count); + count == 1 ? parseInfo.stream.charAt(position) : parseInfo.stream.slice(position, position + count); let i: number = 0; let codepoint: number; for (; i < char.length; i++) { - codepoint = char[i].charCodeAt(0); + codepoint = char[i].charCodeAt(0); if ( codepoint == 0xa || // \n @@ -475,250 +507,375 @@ export function next(parseInfo: ParseInfo, count: number = 1): string { parseInfo.currentPosition += char.length; return char; } +function isIdentToken(parseInfo: ParseInfo, start?: number, end?: number): boolean { + let j: number = parseInfo.currentPosition - parseInfo.offset; + let i: number = parseInfo.position - parseInfo.offset; + + if (start != null) { + if (end == null) { + if (start < 0) { + j += start; + } else { + i += start; + } + } else { + if (end < 0) { + j += end; + } else { + j = parseInfo.position + end; + } + } + } + + j--; + + let codepoint: number = parseInfo.stream.charCodeAt(i) as number; + + // - + if (codepoint == 0x2d) { + let nextCodepoint: number; + + if ((nextCodepoint = parseInfo.stream.charCodeAt(i + 1)) != nextCodepoint) { + return false; + } + + if (isDigit(nextCodepoint)) { + return false; + } + + codepoint = nextCodepoint; + i++; + } + + if (codepoint !== 0x2d && !isIdentStart(codepoint)) { + return false; + } + + if (codepoint == TokenMap.REVERSE_SOLIDUS) { + codepoint = parseInfo.stream.charCodeAt(i + 1) as number; + + // if (!isIdentCodepoint(codepoint)) { + // return false; + // } + i += String.fromCodePoint(codepoint).length; + + // if (i < j) { + // codepoint = name.charCodeAt(i) as number; + + // if (!isIdentCodepoint(codepoint)) { + // return false; + // } + // } + } + + while (i < j) { + i += codepoint < 0x80 ? 1 : String.fromCodePoint(codepoint).length; + codepoint = parseInfo.stream.charCodeAt(i) as number; + + if (codepoint == TokenMap.REVERSE_SOLIDUS) { + i += codepoint < 0x80 ? 1 : String.fromCodePoint(codepoint).length; + codepoint = parseInfo.stream.charCodeAt(i) as number; + i += codepoint < 0x80 ? 1 : String.fromCodePoint(codepoint).length; + + continue; + } + + if (codepoint !== 0x2d && !isIdentCodepoint(codepoint)) { + return false; + } + } + + return true; +} + +function isPseudo(parseInfo: ParseInfo): boolean { + let position: number = parseInfo.currentPosition - parseInfo.offset; + let endPosition: number = parseInfo.currentPosition - parseInfo.offset; + return (parseInfo.stream.charAt(position) == ":" && + parseInfo.stream.charAt(endPosition - 1) == "(" && + (parseInfo.stream.charAt(position + 1) == ":" + ? isIdentToken(parseInfo, 2, -1) + : isIdentToken(parseInfo, 1, -1))) || + parseInfo.stream.charAt(position + 1) == ":" + ? isIdentToken(parseInfo, 2) + : isIdentToken(parseInfo, 1); +} + +function startsWith(parseInfo: ParseInfo, input: string): boolean { + let i: number = 0; + let j: number = input.length; + + while (i < j) { + if (parseInfo.stream.charAt(parseInfo.position - parseInfo.offset + i) != input.charAt(i)) { + return false; + } + i++; + } + + return true; +} + +function isURLToken(parseInfo: ParseInfo): boolean { + let i: number = parseInfo.position - parseInfo.offset; + let c: number; + + while (++i < parseInfo.currentPosition) { + c = parseInfo.stream.charCodeAt(i) as number; + + // single quote or double quote or start parenthesis or close parenthesis + if (isNonPrintable(c) || c == 0x27 || c == 0x22 || c == 0x28 || c == 0x29) { + return false; + } + + // valid escape + if (c == TokenMap.REVERSE_SOLIDUS) { + i++; + + if (i >= parseInfo.currentPosition) { + return false; + } + + c = parseInfo.stream.charCodeAt(i) as number; + + // c is not '\n' or '\r' or '\f' + if (c == 0x6e || c == 0x72 || c == 0x66) { + return false; + } + + continue; + } + + // is white space + if (c == 0x20 || c == 0x09) { + break; + } + } + + return i == parseInfo.currentPosition; +} /** - * Tokenize css string + * Tokenize CSS string * @param parseInfo * @param yieldEOFToken */ export function tokenize(parseInfo: ParseInfo | string, yieldEOFToken: boolean = true): Array { if (typeof parseInfo == "string") { parseInfo = { - buffer: "", stream: parseInfo, source: new SourceFile(parseInfo, [], ""), offset: 0, time: 0, position: 0, - currentPosition: -1, + currentPosition: 0, }; } - let value: string; - let nextValue: string; - let buffer: string = parseInfo.buffer; let charCode: number; let nextCharCode: number; const startTime: number = performance.now(); const result: TokenizeResult[] = []; // allow 10 characters buffer for the streaming parser to avoid incomplete tokens - const endPosition: number = parseInfo.stream.length - 10; - - parseInfo.buffer = ""; - - while ((value = peek(parseInfo))) { - charCode = value.charCodeAt(0); - // nextCharCode = nextValue.charCodeAt(0); - - // console.debug({value, buffer}); + const endPosition: number = parseInfo.stream.length - 1; + // NaN is not equal to NaN + while ((charCode = peek(parseInfo).charCodeAt(0)) == charCode) { switch (charCode) { case TokenMap.EQUALS: - if (buffer.length > 0) { - result.push(yieldResult(buffer, parseInfo)); - buffer = ""; + if (parseInfo.position < parseInfo.currentPosition) { + result.push(yieldResult(parseInfo)); } - result.push(yieldResult(next(parseInfo), parseInfo, EnumToken.DelimTokenType)); + next(parseInfo); + result.push(yieldResult(parseInfo, EnumToken.DelimTokenType)); break; // '+' or '-' case TokenMap.PLUS: case TokenMap.MINUS: - next(parseInfo); + nextCharCode = peek(parseInfo).charCodeAt(0); - if (charCode === TokenMap.PLUS && !isNumber(peek(parseInfo))) { - if (buffer.length > 0) { - result.push(yieldResult(buffer, parseInfo)); - buffer = ""; + // not a number + if (charCode === TokenMap.PLUS && !(nextCharCode >= 0x30 && nextCharCode <= 0x39)) { + if (parseInfo.position < parseInfo.currentPosition) { + result.push(yieldResult(parseInfo)); } - result.push(yieldResult(value, parseInfo, SymbolsMapTokens[value])); + next(parseInfo); + + result.push( + yieldResult( + parseInfo, + SymbolsMapTokens[ + parseInfo.stream + .slice( + parseInfo.position - parseInfo.offset, + parseInfo.currentPosition - parseInfo.offset, + ) + .toLowerCase() + ], + ), + ); break; } - buffer += value; + next(parseInfo); + break; // '{' case TokenMap.LEFT_BRACE: - if (buffer.length > 0) { - result.push(yieldResult(buffer, parseInfo)); - buffer = ""; + if (parseInfo.position < parseInfo.currentPosition) { + result.push(yieldResult(parseInfo)); } - result.push(yieldResult(next(parseInfo), parseInfo, EnumToken.BlockStartTokenType)); + next(parseInfo); + result.push(yieldResult(parseInfo, EnumToken.BlockStartTokenType)); break; // '}' case TokenMap.RIGHT_BRACE: - if (buffer.length > 0) { - result.push(yieldResult(buffer, parseInfo)); - buffer = ""; + if (parseInfo.position < parseInfo.currentPosition) { + result.push(yieldResult(parseInfo)); } - result.push(yieldResult(next(parseInfo), parseInfo, EnumToken.BlockEndTokenType)); + next(parseInfo); + result.push(yieldResult(parseInfo, EnumToken.BlockEndTokenType)); break; // '(' case TokenMap.LEFT_PARENTHESIS: - if (buffer.length > 0) { - if (buffer[0] === ":" && isPseudo(buffer)) { + if (parseInfo.position < parseInfo.currentPosition) { + if (parseInfo.stream[parseInfo.position - parseInfo.offset] === ":" && isPseudo(parseInfo)) { next(parseInfo); - result.push(yieldResult(buffer, parseInfo, EnumToken.PseudoClassFunctionTokenDefType)); - buffer = ""; + result.push(yieldResult(parseInfo, EnumToken.PseudoClassFunctionTokenDefType)); + break; - } else if (isIdent(buffer)) { - const hint: EnumToken = buffer.startsWith("--") + } else if (isIdentToken(parseInfo)) { + const hint: EnumToken = startsWith(parseInfo, "--") ? EnumToken.CustomFunctionTokenDefType - : (SymbolsMapTokens[buffer.toLowerCase() + "("] ?? EnumToken.FunctionTokenDefType); - - result.push(yieldResult(buffer, parseInfo, hint)); + : (SymbolsMapTokens[ + parseInfo.stream + .slice( + parseInfo.position - parseInfo.offset, + parseInfo.currentPosition - parseInfo.offset, + ) + .toLowerCase() + "(" + ] ?? EnumToken.FunctionTokenDefType); + + result.push(yieldResult(parseInfo, hint)); next(parseInfo); - buffer = ""; - if (hint === EnumToken.UrlFunctionTokenDefType) { - buffer = ""; - value = peek(parseInfo); + // consume '(' + parseInfo.position = parseInfo.currentPosition; + if (hint === EnumToken.UrlFunctionTokenDefType) { // consume an - while (isWhiteSpace((charCode = value.charCodeAt(0)))) { - buffer += next(parseInfo); - value = peek(parseInfo); - charCode = value.charCodeAt(0); - - if (value === "/" && match(parseInfo, "/*")) { - if (buffer.length > 0) { - result.push(yieldResult(buffer, parseInfo)); - buffer = ""; - } - - buffer += next(parseInfo, 2); - - while ((value = next(parseInfo))) { - if (value == "*") { - buffer += value; - - if (match(parseInfo, "/")) { - result.push( - yieldResult( - buffer + next(parseInfo), - parseInfo, - EnumToken.CommentTokenType, - ), - ); - buffer = ""; - break; - } - } else { - buffer += value; - } - } - - if (buffer.length > 0) { - result.push(yieldResult(buffer, parseInfo, EnumToken.BadCommentTokenType)); - buffer = ""; - } - - value = peek(parseInfo); - charCode = value.charCodeAt(0); - } + while (isWhiteSpace(peek(parseInfo).charCodeAt(0))) { + next(parseInfo); } - if (buffer.length > 0) { - result.push(yieldResult(buffer, parseInfo, EnumToken.WhitespaceTokenType)); - buffer = ""; + charCode = peek(parseInfo).charCodeAt(0); + + let values: Array | null = null; + + if (charCode == TokenMap.DOUBLE_QUOTE || charCode == TokenMap.SINGLE_QUOTE) { + values = consumeString(parseInfo); + } else { + do { + next(parseInfo); + // value = peek(parseInfo); + charCode = peek(parseInfo).charCodeAt(0); + } while ( + // !(value === "/" && match(parseInfo, "/*") && + charCode !== TokenMap.RIGHT_PARENTHESIS && + parseInfo.currentPosition < endPosition + ); } - if (value === ")" || value === '"' || value === "'") { - break; - } + if (values != null) { + // NaN is not equal to NaN + if ((charCode = peek(parseInfo).charCodeAt(0)) != charCode) { + for (let i = 0; i < values.length; i++) { + values[i].token.typ = EnumToken.BadUrlTokenType; + } + } - do { - buffer += next(parseInfo); - value = peek(parseInfo); - charCode = value.charCodeAt(0); - } while ( - value !== ")" && - !isWhiteSpace(charCode) && - !(value === "/" && match(parseInfo, "/*")) - ); - - if (buffer.length > 0) { + result.push(...values); + } else if (parseInfo.position < parseInfo.currentPosition) { result.push( yieldResult( - buffer, parseInfo, - peek(parseInfo) === "" + // parseInfo.position < parseInfo.currentPosition + (charCode = peek(parseInfo).charCodeAt(0)) != charCode || !isURLToken(parseInfo) ? EnumToken.BadUrlTokenType : EnumToken.UrlTokenTokenType, ), ); - buffer = ""; } } - // console.debug({value: peek(parseInfo)}); - break; } } - result.push(yieldResult(next(parseInfo), parseInfo, EnumToken.StartParensTokenType)); - buffer = ""; + next(parseInfo); + result.push(yieldResult(parseInfo, EnumToken.StartParensTokenType)); + break; // ')' case TokenMap.RIGHT_PARENTHESIS: - if (buffer.length > 0) { - result.push(yieldResult(buffer, parseInfo)); - buffer = ""; + if (parseInfo.position < parseInfo.currentPosition) { + result.push(yieldResult(parseInfo)); } - result.push(yieldResult(next(parseInfo), parseInfo, EnumToken.EndParensTokenType)); + next(parseInfo); + result.push(yieldResult(parseInfo, EnumToken.EndParensTokenType)); break; // '[' case TokenMap.LEFT_BRACKETS: - if (buffer.length > 0) { - result.push(yieldResult(buffer, parseInfo)); - buffer = ""; + if (parseInfo.position < parseInfo.currentPosition) { + result.push(yieldResult(parseInfo)); } - result.push(yieldResult(next(parseInfo), parseInfo, EnumToken.AttrStartTokenType)); + next(parseInfo); + result.push(yieldResult(parseInfo, EnumToken.AttrStartTokenType)); break; // ']' case TokenMap.RIGHT_BRACKETS: - if (buffer.length > 0) { - result.push(yieldResult(buffer, parseInfo)); - buffer = ""; + if (parseInfo.position < parseInfo.currentPosition) { + result.push(yieldResult(parseInfo)); } - result.push(yieldResult(next(parseInfo), parseInfo, EnumToken.AttrEndTokenType)); + next(parseInfo); + result.push(yieldResult(parseInfo, EnumToken.AttrEndTokenType)); break; case TokenMap.SEMICOLON: - if (buffer.length > 0) { - result.push(yieldResult(buffer, parseInfo)); - buffer = ""; + if (parseInfo.position < parseInfo.currentPosition) { + result.push(yieldResult(parseInfo)); } - result.push(yieldResult(next(parseInfo), parseInfo, EnumToken.SemiColonTokenType)); + + next(parseInfo); + result.push(yieldResult(parseInfo, EnumToken.SemiColonTokenType)); break; case TokenMap.COLON: - if (buffer.length > 0) { - result.push(yieldResult(buffer, parseInfo)); - buffer = ""; + if (parseInfo.position < parseInfo.currentPosition) { + result.push(yieldResult(parseInfo)); } next(parseInfo); if (peek(parseInfo).charCodeAt(0) == TokenMap.COLON) { - result.push(yieldResult(value + next(parseInfo), parseInfo, EnumToken.DoubleColonTokenType)); + next(parseInfo); + result.push(yieldResult(parseInfo, EnumToken.DoubleColonTokenType)); break; } - result.push(yieldResult(value, parseInfo, EnumToken.ColonTokenType)); + result.push(yieldResult(parseInfo, EnumToken.ColonTokenType)); break; // \n \r \f \v \t space @@ -730,13 +887,12 @@ export function tokenize(parseInfo: ParseInfo | string, yieldEOFToken: boolean = case 0xd: case 0x2028: case 0x2029: - if (buffer.length > 0) { - result.push(yieldResult(buffer, parseInfo)); - buffer = ""; + if (parseInfo.position < parseInfo.currentPosition) { + result.push(yieldResult(parseInfo)); } - buffer += next(parseInfo); - nextCharCode = parseInfo.stream.charAt(parseInfo.currentPosition - parseInfo.offset + 1).charCodeAt(0); + next(parseInfo); + nextCharCode = parseInfo.stream.charAt(parseInfo.currentPosition - parseInfo.offset).charCodeAt(0); while ( nextCharCode == 0x20 || @@ -744,247 +900,252 @@ export function tokenize(parseInfo: ParseInfo | string, yieldEOFToken: boolean = nextCharCode == 0x2028 || nextCharCode == 0x2029 ) { - value += next(parseInfo); - nextCharCode = parseInfo.stream - .charAt(parseInfo.currentPosition - parseInfo.offset + 1) - .charCodeAt(0); + next(parseInfo); + nextCharCode = parseInfo.stream.charAt(parseInfo.currentPosition - parseInfo.offset).charCodeAt(0); } - result.push(yieldResult(value, parseInfo, EnumToken.WhitespaceTokenType)); - buffer = ""; + result.push(yieldResult(parseInfo, EnumToken.WhitespaceTokenType)); + break; case TokenMap.COMMA: - if (buffer.length > 0) { - result.push(yieldResult(buffer, parseInfo)); - buffer = ""; + if (parseInfo.position < parseInfo.currentPosition) { + result.push(yieldResult(parseInfo)); } - result.push(yieldResult(next(parseInfo), parseInfo, EnumToken.CommaTokenType)); + next(parseInfo); + result.push(yieldResult(parseInfo, EnumToken.CommaTokenType)); break; case TokenMap.DOLLAR: - if (buffer.length > 0) { - result.push(yieldResult(buffer, parseInfo)); - buffer = ""; + if (parseInfo.position < parseInfo.currentPosition) { + result.push(yieldResult(parseInfo)); } if (match(parseInfo, "$=")) { - result.push(yieldResult(next(parseInfo, 2), parseInfo, EnumToken.EndMatchTokenType)); + next(parseInfo, 2); + result.push(yieldResult(parseInfo, EnumToken.EndMatchTokenType)); break; } - buffer += next(parseInfo); + next(parseInfo); break; case TokenMap.TILDA: - if (buffer.length > 0) { - result.push(yieldResult(buffer, parseInfo)); - buffer = ""; + if (parseInfo.position < parseInfo.currentPosition) { + result.push(yieldResult(parseInfo)); } if (match(parseInfo, "~=")) { - result.push(yieldResult(next(parseInfo, 2), parseInfo, EnumToken.IncludeMatchTokenType)); + next(parseInfo, 2); + result.push(yieldResult(parseInfo, EnumToken.IncludeMatchTokenType)); break; } - result.push(yieldResult(next(parseInfo), parseInfo, EnumToken.Tilda)); - buffer = ""; + next(parseInfo); + result.push(yieldResult(parseInfo, EnumToken.Tilda)); + break; // case '^': case TokenMap.CARET: - if (buffer.length > 0) { - result.push(yieldResult(buffer, parseInfo)); - buffer = ""; + if (parseInfo.position < parseInfo.currentPosition) { + result.push(yieldResult(parseInfo)); } if (match(parseInfo, "^=")) { - result.push(yieldResult(next(parseInfo, 2), parseInfo, EnumToken.StartMatchTokenType)); + next(parseInfo, 2); + result.push(yieldResult(parseInfo, EnumToken.StartMatchTokenType)); break; } - buffer += next(parseInfo); + next(parseInfo); break; case TokenMap.STAR: - if (buffer.length > 0) { - result.push(yieldResult(buffer, parseInfo)); - buffer = ""; + if (parseInfo.position < parseInfo.currentPosition) { + result.push(yieldResult(parseInfo)); } if (match(parseInfo, "*=")) { - result.push(yieldResult(next(parseInfo, 2), parseInfo, EnumToken.ContainMatchTokenType)); + next(parseInfo, 2); + result.push(yieldResult(parseInfo, EnumToken.ContainMatchTokenType)); break; } - result.push(yieldResult(next(parseInfo), parseInfo, EnumToken.Star)); - buffer = ""; + next(parseInfo); + result.push(yieldResult(parseInfo, EnumToken.Star)); + break; case TokenMap.AMPERSAND: - if (buffer.length > 0) { - result.push(yieldResult(buffer, parseInfo)); - buffer = ""; + if (parseInfo.position < parseInfo.currentPosition) { + result.push(yieldResult(parseInfo)); } - result.push(yieldResult(next(parseInfo), parseInfo, EnumToken.NestingSelectorTokenType)); - buffer = ""; + next(parseInfo); + result.push(yieldResult(parseInfo, EnumToken.NestingSelectorTokenType)); + break; case TokenMap.PIPE: - if (buffer.length > 0) { - result.push(yieldResult(buffer, parseInfo)); - buffer = ""; + if (parseInfo.position < parseInfo.currentPosition) { + result.push(yieldResult(parseInfo)); } // '||' if (match(parseInfo, "||")) { - result.push(yieldResult(next(parseInfo, 2), parseInfo, EnumToken.ColumnCombinatorTokenType)); + next(parseInfo, 2); + result.push(yieldResult(parseInfo, EnumToken.ColumnCombinatorTokenType)); break; } else if (match(parseInfo, "|=")) { - result.push(yieldResult(next(parseInfo, 2), parseInfo, EnumToken.DashMatchTokenType)); + next(parseInfo, 2); + result.push(yieldResult(parseInfo, EnumToken.DashMatchTokenType)); break; } - result.push(yieldResult(next(parseInfo), parseInfo, EnumToken.Pipe)); - buffer = ""; + next(parseInfo); + result.push(yieldResult(parseInfo, EnumToken.Pipe)); + break; case TokenMap.EXCLAMATION: - if (buffer.length > 0) { - result.push(yieldResult(buffer, parseInfo)); - buffer = ""; + if (parseInfo.position < parseInfo.currentPosition) { + result.push(yieldResult(parseInfo)); } if (match(parseInfo, "!important")) { - result.push(yieldResult(next(parseInfo, 10), parseInfo, EnumToken.ImportantTokenType)); - buffer = ""; + next(parseInfo, 10); + result.push(yieldResult(parseInfo, EnumToken.ImportantTokenType)); + break; } - buffer += next(parseInfo); + next(parseInfo); break; case TokenMap.SLASH: - if (buffer.length > 0) { - result.push(yieldResult(buffer, parseInfo)); - buffer = ""; + if (parseInfo.position < parseInfo.currentPosition) { + result.push(yieldResult(parseInfo)); } if (!match(parseInfo, "/*")) { - result.push(yieldResult(next(parseInfo), parseInfo, SymbolsMapTokens[value])); + next(parseInfo); + result.push( + yieldResult( + parseInfo, + SymbolsMapTokens[parseInfo.stream.slice(parseInfo.position, parseInfo.currentPosition)], + ), + ); break; } - buffer += next(parseInfo, 2); - - while ((value = next(parseInfo))) { - if (value == "*") { - buffer += value; + next(parseInfo, 2); + while ((charCode = next(parseInfo).charCodeAt(0)) == charCode) { + if (charCode == TokenMap.STAR) { if (match(parseInfo, "/")) { - result.push(yieldResult(buffer + next(parseInfo), parseInfo, EnumToken.CommentTokenType)); - buffer = ""; + next(parseInfo); + result.push(yieldResult(parseInfo, EnumToken.CommentTokenType)); + break; } - } else { - buffer += value; } + // else { + // buffer += value; + // } } - if (buffer.length > 0) { - result.push(yieldResult(buffer, parseInfo, EnumToken.BadCommentTokenType)); - buffer = ""; + if (parseInfo.position < parseInfo.currentPosition) { + result.push(yieldResult(parseInfo, EnumToken.BadCommentTokenType)); } break; case TokenMap.GREATERTHAN: - if (buffer.length > 0) { - result.push(yieldResult(buffer, parseInfo)); - buffer = ""; + if (parseInfo.position < parseInfo.currentPosition) { + result.push(yieldResult(parseInfo)); } if (match(parseInfo, ">=")) { - result.push(yieldResult(next(parseInfo, 2), parseInfo, EnumToken.GteTokenType)); + next(parseInfo, 2); + result.push(yieldResult(parseInfo, EnumToken.GteTokenType)); break; } - result.push(yieldResult(next(parseInfo), parseInfo, EnumToken.GtTokenType)); - buffer = ""; + next(parseInfo); + result.push(yieldResult(parseInfo, EnumToken.GtTokenType)); + break; case TokenMap.LOWERTHAN: - if (buffer.length > 0) { - result.push(yieldResult(buffer, parseInfo)); - buffer = ""; + if (parseInfo.position < parseInfo.currentPosition) { + result.push(yieldResult(parseInfo)); } if (match(parseInfo, "<=")) { - result.push(yieldResult(next(parseInfo, 2), parseInfo, EnumToken.LteTokenType)); + next(parseInfo, 2); + result.push(yieldResult(parseInfo, EnumToken.LteTokenType)); break; } - buffer += next(parseInfo); + next(parseInfo); if (match(parseInfo, "!--")) { - buffer += next(parseInfo, 3); + next(parseInfo, 3); - while ((value = next(parseInfo))) { - buffer += value; - if (value == "-" && match(parseInfo, "->")) { + while ((charCode = next(parseInfo).charCodeAt(0)) == charCode) { + if (charCode == TokenMap.MINUS && match(parseInfo, "->")) { break; } } - if (value === "") { - result.push(yieldResult(buffer, parseInfo, EnumToken.BadCdoTokenType)); + if (parseInfo.currentPosition >= endPosition) { + result.push(yieldResult(parseInfo, EnumToken.BadCdoTokenType)); } else { - result.push(yieldResult(buffer + next(parseInfo, 2), parseInfo, EnumToken.CDOCOMMTokenType)); + next(parseInfo, 2); + result.push(yieldResult(parseInfo, EnumToken.CDOCOMMTokenType)); } - - buffer = ""; } break; case TokenMap.HASH: - if (buffer.length > 0) { - result.push(yieldResult(buffer, parseInfo)); - buffer = ""; + if (parseInfo.position < parseInfo.currentPosition) { + result.push(yieldResult(parseInfo)); } - buffer += next(parseInfo); + next(parseInfo); break; case TokenMap.REVERSE_SOLIDUS: + if (!yieldEOFToken && parseInfo.stream.length == parseInfo.currentPosition - parseInfo.offset + 1) { + break; + } + next(parseInfo); // EOF - if (!(nextValue = peek(parseInfo))) { + if (!peek(parseInfo)) { + if (!yieldEOFToken) { + break; + } + // end of stream ignore \\ - if (buffer.length > 0) { - result.push(yieldResult(buffer, parseInfo)); - buffer = ""; + if (parseInfo.position < parseInfo.currentPosition) { + result.push(yieldResult(parseInfo)); } break; } - buffer += value + next(parseInfo); - - // buffer += - // (parseInfo.offset == parseInfo.currentPosition - // ? parseInfo.buffer.slice(-1) - // : parseInfo.stream.charAt(parseInfo.currentPosition - parseInfo.offset - 1)) + value; + next(parseInfo); break; case TokenMap.SINGLE_QUOTE: case TokenMap.DOUBLE_QUOTE: - if (buffer.length > 0) { - result.push(yieldResult(buffer, parseInfo)); - buffer = ""; + if (parseInfo.position < parseInfo.currentPosition) { + result.push(yieldResult(parseInfo)); } result.push(...consumeString(parseInfo)); @@ -992,35 +1153,33 @@ export function tokenize(parseInfo: ParseInfo | string, yieldEOFToken: boolean = case TokenMap.DOT: const codepoint = parseInfo.stream - .charAt(parseInfo.currentPosition - parseInfo.offset + 2) + .charAt(parseInfo.currentPosition - parseInfo.offset + 1) .charCodeAt(0); - if (!isDigit(codepoint) && buffer !== "") { - result.push(yieldResult(buffer, parseInfo)); - buffer = next(parseInfo, 2); + if (!isDigit(codepoint) && parseInfo.position !== parseInfo.currentPosition) { + result.push(yieldResult(parseInfo)); + next(parseInfo, 2); break; } - buffer += next(parseInfo); + next(parseInfo); break; default: - buffer += next(parseInfo); + next(parseInfo); break; } - if (!yieldEOFToken && endPosition <= parseInfo.stream.length - parseInfo.currentPosition + parseInfo.offset) { + if (!yieldEOFToken && endPosition <= parseInfo.currentPosition - parseInfo.offset + 1) { break; } } if (yieldEOFToken) { - if (buffer.length > 0) { - result.push(yieldResult(buffer, parseInfo)); + if (parseInfo.position < parseInfo.currentPosition) { + result.push(yieldResult(parseInfo)); } - result.push(yieldResult("", parseInfo, EnumToken.EOFTokenType)); - } else { - parseInfo.buffer = buffer; + result.push(yieldResult(parseInfo, EnumToken.EOFTokenType)); } parseInfo.time += performance.now() - startTime; @@ -1039,6 +1198,8 @@ export async function* tokenizeStream( const decoder = new TextDecoder("utf-8"); const reader = input.getReader(); + parseInfo.stream = ""; + while (true) { const { done, value } = await reader.read(); const stream = ArrayBuffer.isView(value) ? decoder.decode(value, { stream: true }) : value; @@ -1046,14 +1207,11 @@ export async function* tokenizeStream( if (!done) { parseInfo.source.append(stream as string); - if (typeof parseInfo.stream != "string") { - parseInfo.stream = stream as string; - } else { - parseInfo.stream = (parseInfo.stream.slice(parseInfo.currentPosition - parseInfo.offset + 1) + - stream) as string; - } + parseInfo.stream = (parseInfo.stream.slice(parseInfo.position - parseInfo.offset) + stream) as string; - parseInfo.offset = parseInfo.currentPosition + 1; + parseInfo.offset = parseInfo.offset = parseInfo.position; + } else { + parseInfo.stream = ""; } yield* tokenize(parseInfo, done); diff --git a/src/lib/parser/utils/at-rule-container.ts b/src/lib/parser/utils/at-rule-container.ts index e559713a..91a56293 100644 --- a/src/lib/parser/utils/at-rule-container.ts +++ b/src/lib/parser/utils/at-rule-container.ts @@ -193,7 +193,8 @@ export function parseAtRuleContainerQueryList( success = false; errors.push({ action: "drop", - node: options.source!.getSourceLocation(stream[i][LOC]!.sta), + node: stream[i], + location: options.source!.getSourceLocation(stream[i][LOC]!.sta), message: ` is not allowed outside of parentheses`, }); 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..27ba815b 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, @@ -17,10 +17,20 @@ import type { PercentageToken, AtRuleToken, ColorToken, + AstNode, } from "../../../@types/index.d.ts"; import { EnumAstNodeStatus, EnumToken } from "../../ast/types.ts"; import { renderValue } from "../../renderer/render.ts"; -import { combinators, ERRORS, LOC, PARENT, pseudoElements, STATE, TOKENS, tokensfuncDefMap } from "../../syntax/constants.ts"; +import { + combinators, + ERRORS, + LOC, + PARENT, + pseudoElements, + STATE, + TOKENS, + tokensfuncDefMap, +} from "../../syntax/constants.ts"; import { isHash } from "../../syntax/syntax.ts"; import { getParsedSyntax, getSyntaxConfig, getSyntaxRule } from "../../validation/config.ts"; import { createValidationContext, matchAllSyntaxes, matchSelectorSyntax, trimArray } from "../../validation/match.ts"; @@ -29,6 +39,7 @@ import { ValidationSyntaxGroupEnum, ValidationTokenEnum } from "../../validation import type { ValidationPropertyToken } from "../../validation/parser/types.d.ts"; import { splitTokenList } from "../../validation/utils/list.ts"; import { trimWhiteSpace } from "../parse.ts"; +import { equalsIgnoreCase } from "./text.ts"; /** * parse selector @@ -36,10 +47,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 +108,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 }), "")); @@ -107,12 +118,12 @@ export function parseSelector( chi: [], [LOC]: { ...tokens[0][LOC], - end: tokens[tokens.length - 1]?.[LOC]?.end ?? tokens[0]?.[LOC]?.end + end: tokens[tokens.length - 1]?.[LOC]?.end ?? tokens[0]?.[LOC]?.end, }, [TOKENS]: tokens.length === 0 ? null : tokens, [STATE]: result.success ? EnumAstNodeStatus.Validated : EnumAstNodeStatus.Invalid, [ERRORS]: result.errors, - } as AstKeyFrameRule; + } as AstKeyframesRule; } const stack: Token[] = []; @@ -155,12 +166,12 @@ export function parseSelector( do { if (parent?.typ === EnumToken.AtRuleNodeType && "media" === (parent as AstAtRule).nam) { - parent = parent[PARENT]; + parent = parent[PARENT] as AstRuleList; continue; } nested = parent?.typ == EnumToken.RuleNodeType; - parent = parent?.[PARENT]; + parent = parent?.[PARENT] as AstRuleList; } while (!nested && parent != null); for (; i < tokens.length; i++) { @@ -223,15 +234,80 @@ export function parseSelector( } if (tokens[i].typ == EnumToken.ColorTokenType) { - // if (isIdent((tokens[i] as ColorToken).val)) { - // Object.assign(tokens[i], { - // typ: EnumToken.IdenTokenType, - // }); - // } else - if (isHash((tokens[i] as ColorToken).val)) { + + if (isHash((tokens[i] as ColorToken).val)) { Object.assign(tokens[i], { typ: EnumToken.HashTokenType, }); + } else { + + return { + typ: EnumToken.RuleNodeType, + sel: [ + ...tokens + .reduce( + (acc: string[][], curr: Token, index: number, array: Token[]) => { + // if (curr.typ == EnumToken.CommentTokenType) { + // return acc; + // } + + if (curr.typ == EnumToken.WhitespaceTokenType) { + if ( + trimWhiteSpace.includes(array[index - 1]?.typ) || + trimWhiteSpace.includes(array[index + 1]?.typ) || + combinators.includes((array[index - 1])?.val) || + combinators.includes((array[index + 1])?.val) + ) { + return acc; + } + } + + let t: string = renderValue(curr, { minify: false }); + + if (t == ",") { + acc.push([]); + } else { + acc[acc.length - 1].push(t); + } + return acc; + }, + [[]], + ) + .reduce((acc: Map, curr: string[]) => { + let i: number = 0; + + for (; i < curr.length; i++) { + if (i + 1 < curr.length && curr[i] == "*") { + if (curr[i] == "*") { + let index: number = curr[i + 1] == " " ? 2 : 1; + + if (![">", "~", "+"].includes(curr[index])) { + curr.splice(i, index); + } + } + } + } + + acc.set(curr.join(""), curr); + return acc; + }, uniq) + .keys(), + ].join(","), + chi: [], + [LOC]: { + ...tokens[0][LOC], + end: tokens[tokens.length - 1][LOC]!.end, + }, + [TOKENS]: tokens, + [STATE]: EnumAstNodeStatus.Invalid, + [ERRORS]: [ + { + action: "drop", + node: tokens[i], + message: "invalid hash id", + }, + ], + } as AstRule; } } } @@ -298,6 +374,41 @@ export function parseSelector( func.val == ":nth-of-type" || func.val == ":nth-last-of-type" ) { + + const list: Token[] = []; + let index: number; + + for ( index = 0; index < func.chi.length; index++) { + + if (func.chi[index].typ == EnumToken.CommentTokenType || func.chi[index].typ == EnumToken.WhitespaceTokenType) { + continue; + } + + if (func.chi[index].typ == EnumToken.IdenTokenType && equalsIgnoreCase('of', (func.chi[index] as IdentToken).val)) { + + index--; + break; + } + + list.push(func.chi[index]); + } + + if (list.length == 3) { + + if (list[0].typ == EnumToken.IdenTokenType && ('n' == (list[0] as IdentToken).val || '-n' == (list[0] as IdentToken).val || '+n' == (list[0] as IdentToken).val)) { + + if (list[1].typ == EnumToken.NextSiblingCombinatorTokenType) { + + if (list[2].typ == EnumToken.NumberTokenType && (0 == (list[2] as NumberToken).val)) { + + (list[0] as IdentToken).val = 'n'; + func.chi.splice(0, index, list[0]); + break; + } + } + } + } + const token = func.chi.find( (t) => t.typ != EnumToken.WhitespaceTokenType && t.typ != EnumToken.CommentTokenType, @@ -407,8 +518,8 @@ export function parseSelector( // } else { // Object.assign(token, { typ: EnumToken.NumberTokenType, val: b1 }); // } - // } else - if (b1 === 0) { + // } else + if (b1 === 0) { Object.assign( token, Math.abs(a1) === 1 @@ -422,7 +533,7 @@ export function parseSelector( unit: "n", }, ); - } + } // else if (Math.abs(a1) === 2) { // if (b1 === 0) { // Object.assign(token, { @@ -493,8 +604,8 @@ export function parseSelector( // } // break; - // } else - if (num.val === 0) { + // } else + if (num.val === 0) { func.chi.splice(index + 1, i - index); if (((token as DimensionToken).val as number) < 0) { diff --git a/src/lib/parser/utils/token.ts b/src/lib/parser/utils/token.ts index f792cff7..27dac94e 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"; @@ -44,6 +43,13 @@ export function replaceNodeOrValue( parent: | BinaryExpressionToken | (AstNode & + ( + | { chi: Token[] } + | { + val: Token[]; + } + )) + | (Token & ( | { chi: Token[] } | { @@ -81,7 +87,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..7e95e7cb 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: { sources: number[]; maps: Array<[number, number, number, number, number]> } | null = + options.sourcemap ? { sources: [], maps: [] } : 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,13 @@ export function doRender( }; if (sourcemap != null) { + let source: SourceFile; + for (const sourceId of sourcemaps!.sources) { + source = options!.sourcesMap!.get(sourceId)! as SourceFile; + sourcemap.addSourceContent(source.id, source.getFileName(), source.getContent()); + } + + sourcemap.add(...(sourcemaps!.maps! as Array<[number, number, number, number, number]>)); result.map = sourcemap; if (options.sourcemap === "inline") { @@ -221,8 +244,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 +257,113 @@ function updateSourceMap( cache: { [p: string]: any; }, - sourcemap: SourceMap, + sourcemaps: { sources: number[]; maps: Array<[number, number, number, number, number]> }, 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; + } + + sourceFileName = cache[sourceFileName] as string; + } + + if (!sourcemaps.sources.includes(srcId)) { + sourcemaps.sources.push(srcId); + } - let sourceFileName: string | null = (options.sourcesMap?.get(srcId)?.getFileName?.() as string) || null; + sourcemaps.maps.push([newLine, newColumn, srcId, ...offsets]); + } + } 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; + } - if (sourceFileName != null && options.output != null) { - if (cache[sourceFileName] == null) { - cache[sourceFileName] = options.resolve!(sourceFileName, dirname(options.output)).relative as string; + sourceFileName = cache[sourceFileName] as string; } - sourceFileName = cache[sourceFileName] as string; - } + if (!sourcemaps.sources.includes(srcId)) { + sourcemaps.sources.push(srcId); + } - // @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.maps.push([newLine, newColumn, srcId, ...offsets]); + } } - 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 +399,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 +413,9 @@ export function move(sourceLocation: SourceLocation, linesMap: LinesMap, str: st function renderAstNode( data: AstNode, options: RenderOptions, - sourcemap: SourceMap | null, + sourcemaps: { sources: number[]; maps: Array<[number, number, number, number, number]> } | null, sourceLocation: SourceLocation, - linesMap: LinesMap, + linesMap: LinesMap | null, errors: ErrorDescription[], reducer: (acc: string, curr: Token) => string, cache: { @@ -342,6 +432,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 +449,7 @@ function renderAstNode( case EnumToken.CommentNodeType: case EnumToken.CDOCOMMNodeType: if ((data).val.startsWith("/*# sourceMappingURL=")) { - // ignore sourcemap + // ignore sourcemap comment return ""; } @@ -364,13 +458,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 +473,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 +499,24 @@ function renderAstNode( };`; } - // @ts-ignore - let children: string = (data).chi.reduce((css: string, node: AstNode) => { - let str: string; + const lineMapLength = linesMap ? linesMap.getLineStarts().length : 0; + 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,31 +524,17 @@ 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) : (node).val ) .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 +543,70 @@ 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}`; - }, ""); + str = options.newLine + indentSub + str; + children += str; - if (options.removeEmpty && children === "") { - return ""; + 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; + + if (!sourcemaps.sources.includes(node[LOC]!.srcId as number)) { + sourcemaps.sources.push(node[LOC]!.srcId as number); + } + + sourcemaps.maps.push([ + ...linesMap!.getOffsets( + sourceLocation.end - str.length + options.newLine!.length + indentSub.length, + ), + node[LOC]!.srcId, + ...source!.getOffsets(node![LOC]!.sta), + ]); + } + } } 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 === "") { + if (sourcemaps != null) { + sourceLocation.end -= prelude.length; + linesMap!.getLineStarts().length = lineMapLength; + } + return ""; + } + + const end: string = options.newLine + indent + `}`; + + if (sourcemaps != null) { + move(sourceLocation, linesMap!, end); } - 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: + return prelude + children + end; default: return ""; @@ -535,6 +617,9 @@ function renderAstNode( * render ast token * @param token * @param options + * @param cache + * @param reducer + * @param errors * @private */ export function renderValue( @@ -653,8 +738,8 @@ export function renderValue( case EnumToken.Sub: return " - "; - case EnumToken.Star: case EnumToken.UniversalSelectorTokenType: + case EnumToken.Star: case EnumToken.Mul: return "*"; @@ -1858,11 +1943,11 @@ export function renderValue( return "or"; case EnumToken.InvalidMediaQueryTokenType: - // case EnumToken.InvalidDeclarationNodeType: case EnumToken.InvalidCommentTokenType: case EnumToken.BadCommentTokenType: case EnumToken.BadCdoTokenType: case EnumToken.BadStringTokenType: + case EnumToken.BadUrlTokenType: case EnumToken.EOFTokenType: return ""; diff --git a/src/lib/renderer/sourcemap/lib/encode.ts b/src/lib/renderer/sourcemap/lib/codec.ts similarity index 51% rename from src/lib/renderer/sourcemap/lib/encode.ts rename to src/lib/renderer/sourcemap/lib/codec.ts index ead4a1f1..8c06845c 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..fc57cecf 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 */ @@ -22,17 +27,32 @@ export class SourceMap { */ private sourcesMap: number[] = []; + /** + * 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,252 @@ 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 + */ + constructor(); + /** + * Constructor + * @param sourcemaps + */ + constructor(sourcemaps: string | SourceMapObject); + /** + * + * @param sourcemaps + */ + constructor(sourcemaps?: SourceMapObject | string) { + if (typeof sourcemaps === "string") { + if (sourcemaps.startsWith("data:")) { + let encoding: string = ""; + let offset: number = sourcemaps.indexOf(",") + 1; + + if (offset == 0) { + offset = sourcemaps.lastIndexOf(";") + 1; + } else { + encoding = sourcemaps.slice(sourcemaps.lastIndexOf(";") + 1, offset - 1); + } + + if (encoding == "base64") { + sourcemaps = atob(sourcemaps.slice(offset)); + } else { + sourcemaps = decodeURIComponent(sourcemaps.slice(offset)); + } + } + + 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 source + * @param id + * @param fileName + * @param content + * @returns + */ + addSourceContent(id: number, fileName: string | null, content: string | null): void { + if (this.sourcesMap.includes(id)) { + return; + } + + this.sourcesMap[this.sourcesMap.length] = id; + this.sources[this.sources.length] = fileName || null; + this.sourcesContent[this.sourcesContent.length] = content || null; + } + + /** + * Add sourcemap + * @param newLine + * @param newColumn + * @param srcId + * @param ln + * @param col + */ + add(newLine: number, newColumn: number, srcId: number, ln: number, col: number): void; + + /** + * Add multiple sourcemaps + * @param maps + * @throws + */ + add(...maps: Array<[newLine: number, newColumn: number, srcId: number, ln: number, col: number]>): void; + + /** + * Add all location + * @param maps + * @throws + */ + add(...maps: Array<[number, number, number, number, number]> | [number, number, number, number, number]): void { + let srcIndex: number; + + if (typeof maps[0] === "number") { + maps = [maps as [number, number, number, number, number]]; } - const line = newLine - 1; - let record: number[]; + for (let [newLine, newColumn, srcId, ln, col] of maps as Array<[number, number, number, number, number]>) { + const key = `${srcId}:${ln}:${col}:${newLine}:${newColumn}`; + + if (this.keys.has(key)) { + continue; + } + + this.keys.add(key); + + const line: number = newLine - 1; + let record: number[]; + + if (line > this.line) { + this.line = line; + } + + srcIndex = this.sourcesMap.indexOf(srcId); + + if (srcIndex == -1) { + throw new Error(`Source file ${srcId} not added to sourcemap`); + } + + if (!this.map.has(line)) { + record = [Math.max(0, newColumn - 1), srcIndex, 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], srcIndex - arr[0][1], ln - 1, col - 1]; + arr.push(record); + } - if (line > this.line) { - this.line = line; + 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[]; - if (!this.map.has(line)) { - record = [Math.max(0, newColumn - 1), this.sourcesMap.indexOf(srcId), ln - 1, col - 1]; + // 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; + } + + sourceFileIndex = index == 0 ? segment[1] : segment[1] + array[0][1]; + sourceCodeLine += segment[2]; + sourceCodeColumn += segment[3]; + + result.push(sourceFileIndex, sourceCodeLine, sourceCodeColumn); + + // nameIndex not needed + // 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]; + }); - this.map.set(line, [record]); - } else { - const arr: number[][] = this.map.get(line); + if (line.length == 0 || (line.length == 1 && line[0].length == 0)) { + continue; + } + + this.reverseMap.set(i, 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: number, column: number): Array<[string | null, number, number, string | null]> | null { + if (this.reverseMap.size == 0) { + this.computePositions(); } - if (this.lastLocation != null) { - record[2] -= this.lastLocation.ln - 1; - record[3] -= this.lastLocation.col - 1; + if (!this.reverseMap.has(--line)) { + return null; } - this.lastLocation ??= { ln, col }; + column--; + const result: Array<[string | null, number, number, string | null]> = []; + + 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 = ln; - this.lastLocation.col = col; + return result.length == 0 ? null : result; } /** @@ -128,6 +340,7 @@ export class SourceMap { return { version: this.version, sources: this.sources.slice(), + sourcesContent: this.sourcesContent?.slice(), mappings: mappings.join(";"), }; } diff --git a/src/lib/syntax/color/alpha.ts b/src/lib/syntax/color/alpha.ts index 51438b2f..2fd05fb2 100644 --- a/src/lib/syntax/color/alpha.ts +++ b/src/lib/syntax/color/alpha.ts @@ -49,7 +49,7 @@ export function alpha(color: ColorToken, alpha: Token): ColorToken | null { alpha.typ === EnumToken.MathFunctionTokenType && equalsIgnoreCase((alpha as FunctionToken).val, "calc") ) { - alpha = cloneNode(alpha, true); + alpha = cloneNode(alpha, true) as FunctionToken; const alphaValue = components[3] ?? { typ: EnumToken.NumberTokenType, @@ -58,7 +58,7 @@ export function alpha(color: ColorToken, alpha: Token): ColorToken | null { for (const { value, parent } of walkValues((alpha as FunctionToken).chi, alpha)) { if (value.typ === EnumToken.IdenTokenType && equalsIgnoreCase((value as IdentToken).val, "alpha")) { - replaceNodeOrValue(parent, value, alphaValue); + replaceNodeOrValue(parent as FunctionToken, value, alphaValue); } } diff --git a/src/lib/syntax/syntax.ts b/src/lib/syntax/syntax.ts index 799cfa68..8d73f86b 100644 --- a/src/lib/syntax/syntax.ts +++ b/src/lib/syntax/syntax.ts @@ -1081,7 +1081,6 @@ export function isColor(token: Token, errors?: ErrorDescription[]): boolean { action: "drop", message: `Invalid color`, node: token, - // location: options.source!.getSourLocation(token[LOC]!.sta), }); return false; } @@ -1107,76 +1106,10 @@ export function isColor(token: Token, errors?: ErrorDescription[]): boolean { return true; } - // if (children.length < 4 || children.length > 8) { - // return false; - // } - - // if (!isRelative && !isColorspace(children[0])) { - // return false; - // } - - for (let i = 1; i < children.length - 2; i++) { - // if (children[i].typ == EnumToken.IdenTokenType) { - // if (isColor(children[i])) { - // continue; - // } - // if ( - // (children[i] as IdentToken).val != "none" && - // !( - // (isRelative && - // (["alpha", "r", "g", "b", "x", "y", "z"] as string[]).includes( - // (children[i] as IdentToken).val, - // )) || - // isColorspace(children[i]) - // ) - // ) { - // return false; - // } - // } - // if (children[i].typ === EnumToken.WildCardFunctionTokenType) { - // continue; - // } - // if ( - // children[i].typ === EnumToken.FunctionTokenType || - // children[i].typ === EnumToken.MathFunctionTokenType - // ) { - // if (!mathFuncs.includes((children[i] as FunctionToken).val)) { - // return false; - // } - // } - } - if (children.length == 4 || (isRelative && children.length == 6)) { return true; } - if (children.length == 8 || children.length == 6) { - const sep: Token = children.at(-2) as Token; - const alpha: Token = children.at(-1) as Token; - // @ts-ignore - // if ( - // ((children.length > 6 || !isRelative) && sep.typ != EnumToken.LiteralTokenType) || - // (sep as LiteralToken).val != "/" - // ) { - // return false; - // } - - // if (alpha.typ == EnumToken.IdenTokenType && (alpha as IdentToken).val != "none") { - // return false; - // } else { - // // @ts-ignore - // if (alpha.typ == EnumToken.PercentageTokenType) { - // if (+(alpha as PercentageToken).val < 0 || +(alpha as PercentageToken).val > 100) { - // return false; - // } - // } else if (alpha.typ == EnumToken.NumberTokenType) { - // if (+(alpha as NumberToken).val < 0 || +(alpha as NumberToken).val > 1) { - // return false; - // } - // } - // } - } - return true; } // @ts-ignore @@ -1197,10 +1130,6 @@ export function isColor(token: Token, errors?: ErrorDescription[]): boolean { [[]] as Token[][], ); - // if (children.length === 0 || children[0].length === 0) { - // return false; - // } - let j: number = 0; let k: number = 0; @@ -1239,58 +1168,14 @@ export function isColor(token: Token, errors?: ErrorDescription[]): boolean { k++; } } - // else { - // return false; - // } } else { k++; } } - // else { - // return false; - // } - - // if (k != children[j].length) { - // return false; - // } j++; } - // while (j < children.length) { - // if (children[j].length > 2) { - // return false; - // } - - // if ( - // !isColor(children[j][0]) && - // !( - // children[j][0].typ == EnumToken.WildCardFunctionTokenType && - // equalsIgnoreCase("calc", (children[j][0] as FunctionToken).val) - // ) - // ) { - // return false; - // } - - // if (children[j][0].typ == EnumToken.WildCardFunctionTokenType) { - // const result = matchAllSyntaxes( - // getParsedSyntax(ValidationSyntaxGroupEnum.Syntaxes, "calc()") as ValidationFunctionToken[], - // createValidationContext([children[j][0]]), - // {}, - // ); - - // if (!result.success) { - // return false; - // } - // } - - // if (children[j].length > 1 && !isPercentageToken(children[j][1])) { - // return false; - // } - - // j++; - // } - return true; } else { const keywords: string[] = ["from", "none"]; @@ -1312,25 +1197,6 @@ export function isColor(token: Token, errors?: ErrorDescription[]): boolean { } if (v.typ == EnumToken.IdenTokenType) { - // if (isColor(v)) { - // continue; - // } - - // if (!(keywords.includes(v.val) || COLORS_NAMES[v.val.toLowerCase()] != null)) { - // return false; - // } - - // if (keywords.includes(v.val)) { - // if (isLegacySyntax) { - // return false; - // } - - // // @ts-ignore - // if (v.val == "from" && ["rgba", "hsla"].includes((token as ColorToken).val)) { - // return false; - // } - // } - continue; } @@ -1341,21 +1207,6 @@ export function isColor(token: Token, errors?: ErrorDescription[]): boolean { ) { continue; } - - // if ( - // ![ - // EnumToken.ColorTokenType, - // EnumToken.IdenTokenType, - // EnumToken.NumberTokenType, - // EnumToken.AngleTokenType, - // EnumToken.PercentageTokenType, - // EnumToken.CommaTokenType, - // EnumToken.WhitespaceTokenType, - // EnumToken.LiteralTokenType, - // ].includes(v.typ) - // ) { - // return false; - // } } } @@ -1424,75 +1275,13 @@ export function parseColor(token: Token) { if ((token as ColorToken).val == "color") { let index: number = (token as ColorToken).chi!.indexOf(tk) as number; - // if ((token as ColorToken).cal == "rel") { - // for (let k = 0; k < (token as ColorToken).chi!.length; k++) { - // if (EnumToken.DashedIdenTokenType == (token as ColorToken).chi![k].typ) { - // index = k; - // break; - // } - // } - // } - if (EnumToken.DashedIdenTokenType == (token as ColorToken)?.chi?.[index]?.typ) { (token as ColorToken).kin = ColorType.CUSTOM_COLOR; } } } - - // return token; } - // @ts-ignore - // token.typ = EnumToken.ColorTokenType; - - // // @ts-ignore - // (token as ColorToken).kin = ColorType[token.val.replaceAll("-", "_").toUpperCase()]; - - // if (!("chi" in token)) { - // const val: string = (token as ColorToken).val.toLowerCase(); - - // if (val == "currentcolor" || val == "transparent" || val in COLORS_NAMES) { - // (token as ColorToken).kin = ColorType.LIT; - // } else if (isHexColor(val)) { - // (token as ColorToken).kin = ColorType.HEX; - // } - - // const tk = (token as ColorToken).chi?.find( - // (t) => t.typ !== EnumToken.WhitespaceTokenType && t.typ !== EnumToken.CommentTokenType, - // ); - - // if (tk?.typ === EnumToken.IdenTokenType && (tk as IdentToken).val === "from") { - // (token as ColorToken).cal = "rel"; - // } else if ((token as ColorToken).val == "color-mix" && (tk as IdentToken).val == "in") { - // (token as ColorToken).cal = "mix"; - // } else if ((token as ColorToken).val == "color") { - // (token as ColorToken).cal = "col"; - // } - - // return token; - // } - - // // @ts-ignore - // if (((token as ColorToken).chi as Token[])[0].typ == EnumToken.IdenTokenType) { - // // @ts-ignore - // if (((token as ColorToken).chi as Token[])[0].val == "from") { - // // @ts-ignore - // (token as ColorToken).cal = "rel"; - // } - - // // @ts-ignore - // else if ((token as ColorToken).val == "color-mix" && ((token as ColorToken).chi as Token[])[0].val == "in") { - // // @ts-ignore - // (token as ColorToken).cal = "mix"; - // } else { - // // @ts-ignore - // if ((token as ColorToken).val == "color") { - // // @ts-ignore - // (token as ColorToken).cal = "col"; - // } - // } - // } - return token; } @@ -1553,6 +1342,10 @@ export const isIdent = memoize(function (name: string): boolean { } if (codepoint == REVERSE_SOLIDUS) { + if (i + 1 > j) { + return false; + } + codepoint = name.charCodeAt(i + 1) as number; // if (!isIdentCodepoint(codepoint)) { @@ -1576,6 +1369,7 @@ export const isIdent = memoize(function (name: string): boolean { if (codepoint == REVERSE_SOLIDUS) { i += codepoint < 0x80 ? 1 : String.fromCodePoint(codepoint).length; codepoint = name.charCodeAt(i) as number; + i += codepoint < 0x80 ? 1 : String.fromCodePoint(codepoint).length; continue; @@ -1601,6 +1395,45 @@ export function isNonPrintable(codepoint: number): boolean { ); } +export function isURLToken(str: string): boolean { + let i: number = -1; + let c: number; + + while (++i < str.length) { + c = str.charCodeAt(i) as number; + + // single quote or double quote or start parenthesis or close parenthesis + if (isNonPrintable(c) || c == 0x27 || c == 0x22 || c == 0x28 || c == 0x29) { + return false; + } + + // valid escape + if (c == REVERSE_SOLIDUS) { + i++; + + if (i >= str.length) { + return false; + } + + c = str.charCodeAt(i) as number; + + // c is not '\n' or '\r' or '\f' + if (c == 0x6e || c == 0x72 || c == 0x66) { + return false; + } + + continue; + } + + // is white space + if (c == 0x20 || c == 0x09) { + break; + } + } + + return i == str.length; +} + export function isPseudo(name: string): boolean { return ( name.charAt(0) == ":" && @@ -1610,14 +1443,10 @@ export function isPseudo(name: string): boolean { } export function isHash(name: string): boolean { - return name.charAt(0) == "#" && isIdent(name.charAt(1)); + return name.charAt(0) == "#" && isIdentStart(name.charCodeAt(1)); } export const isNumber = memoize(function (name: string): boolean { - // if (name.length == 0) { - // return false; - // } - let codepoint: number = name.charCodeAt(0) as number; let i: number = 0; const j: number = name.length; diff --git a/src/lib/validation/match.ts b/src/lib/validation/match.ts index a32f0795..9bf8fe58 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, @@ -1271,6 +1325,7 @@ function matchSyntax( if ( tokensfuncDefMap.has(token.typ) && + // @ts-ignore (token as FunctionToken).typ === EnumToken.WildCardFunctionTokenDefType ) { const range = trimArray(context.peekRange()); @@ -1323,7 +1378,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(); @@ -1816,6 +1871,7 @@ function matchSyntax( }; case ValidationTokenEnum.FunctionDefinition: + if ( equalsIgnoreCase( (token as FunctionToken).val, @@ -1967,6 +2023,13 @@ function matchSyntax( }; } +/** + * matches a column of syntaxes + * @param syntax + * @param context + * @param options + * @returns + */ function matchColumnSyntax( syntax: ValidationColumnToken, context: ValidationContext, @@ -2017,6 +2080,13 @@ function matchColumnSyntax( }; } +/** + * matches an ampersand of syntaxes + * @param syntax + * @param context + * @param options + * @returns + */ function matchAmpersandSyntax( syntax: ValidationAmpersandToken, context: ValidationContext, @@ -2046,6 +2116,13 @@ function matchAmpersandSyntax( return result!; } +/** + * matches a property + * @param property + * @param context + * @param options + * @returns + */ function matchProperty( property: ValidationPropertyToken, context: ValidationContext, @@ -2571,8 +2648,10 @@ function matchProperty( // ) { const newRange = range.map((t) => cloneNode(t, true)); + // @ts-ignore parseTokens(newRange, { parseColor: true }, errors); + // @ts-ignore success = newRange.length == 1 && isColor(newRange[0], errors); if (success) { @@ -2998,6 +3077,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..26d58168 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"; @@ -68,6 +69,7 @@ export type { ValidationToken } from "./lib/validation/parser/types.d.ts"; export { FeatureWalkMode } from "./lib/ast/features/type.ts"; export { dirname, resolve, ResponseType }; +export { getNodeProperty, setNodeProperty } from "./lib/ast/node.ts"; /** * Load file or url @@ -177,7 +179,7 @@ export function render( } /** - * Parse css file + * Parse CSS file * @param file url or path * @param options * @param asStream load file as stream @@ -209,7 +211,7 @@ export const parseFile = deprecate( ) as (file: string, options?: ParserOptions, asStream?: boolean) => Promise; /** - * Parse css string + * Parse CSS string * @param stream * @param options * @@ -217,10 +219,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); * ``` * @@ -229,18 +231,17 @@ export const parseFile = deprecate( export function parseSync(stream: string, options?: ParserSyncOptions): ParseResult; /** - * Parse css string - * @param stream + * Parse CSS string * @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); * ``` * @@ -249,17 +250,17 @@ export function parseSync(stream: string, options?: ParserSyncOptions): ParseRes export function parseSync(options: ParseInputOptions & ParserSyncOptions): ParseResult; /** - * Parse css + * Parse CSS * @param args * * 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, @@ -307,27 +313,25 @@ export function parseSync( time: 0, source: options.source, position: 0, - currentPosition: -1, + currentPosition: 0, } 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); } /** - * Transform css + * 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, {beautify: true}); + * const result = transformSync(css, {beautify: true}); * console.log(result.code); * ``` * @@ -335,15 +339,15 @@ export function parseSync( export function transformSync(css: string, options?: TransformSyncOptions): TransformResult; /** - * Transform css + * Transform CSS * @param options * * ```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); * ``` * @@ -351,24 +355,23 @@ 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'; * * // css string - * const result = await transform(css); + * const result = transformSync(css); * console.log(result.code); * ``` * + * @param args */ 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") { @@ -383,7 +386,18 @@ export function transformSync( } options ??= {}; - options = { minify: true, removeEmpty: true, removeCharset: true, ...options }; + + if (options.minify == null) { + options.minify = true; + } + + if (options.removeEmpty == null) { + options.removeEmpty = true; + } + + if (options.removeCharset == null) { + options.removeCharset = true; + } const startTime: number = performance.now(); const parseResult: ParseResult = parseSync(stream, options); @@ -427,12 +441,10 @@ export function transformSync( } /** - * Parse css + * Parse CSS * @param stream * @param options * - * @throws Error file not found - * * Example: * * ```ts @@ -444,7 +456,7 @@ export function transformSync( * console.log(result.ast); * ``` * - * parsing a Readable stream + * parsing a ReadableStream * * ```ts * @@ -459,7 +471,7 @@ export function transformSync( * console.log(result.ast); * ``` * - * Example using fetch and readable stream + * Parsing a file as a ReadableStream * * ```ts * @@ -475,8 +487,7 @@ export function transformSync( export async function parse(stream: string | ReadableStream, options?: ParserOptions): Promise; /** - * Parse css - * @param stream + * Parse CSS * @param options * * @throws Error file not found @@ -510,8 +521,7 @@ export async function parse(stream: string | ReadableStream, options export async function parse(options: ParseInputFileOptions & ParserOptions): Promise; /** - * Parse css - * @param stream + * Parse CSS * @param options * * Parsing a string @@ -554,7 +564,7 @@ export async function parse(options: ParseInputFileOptions & ParserOptions): Pro export async function parse(options: ParseInputStreamOptions & ParserOptions): Promise; /** - * Parse css + * Parse CSS * @param args * * @throws Error file not found @@ -629,7 +639,7 @@ export async function parse( options ??= {}; options.src ??= ""; - options.sourcesMap ??= new Map; + options.sourcesMap ??= new Map(); Object.assign(options, { load, @@ -641,7 +651,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; @@ -655,20 +664,17 @@ export async function parse( time: 0, source: options.source, position: 0, - currentPosition: -1, + currentPosition: 0, } as ParseInfo; 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 +709,7 @@ export const transformFile = deprecate( ) as (file: string, options?: TransformOptions, asStream?: boolean) => Promise; /** - * Transform css + * Transform CSS * @param css * @param options * @@ -751,8 +757,7 @@ export async function transform( ): Promise; /** - * Transform css - * @param css + * Transform CSS * @param options * * Parsing a string @@ -781,7 +786,7 @@ export async function transform( * console.log(result.code); * ``` * - * Example using fetch + * Parse a file as a ReadableStream * * ```ts * @@ -797,44 +802,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); * ``` */ @@ -842,9 +819,7 @@ export async function transform(options: ParseInputStreamOptions & TransformOpti export async function transform(options: ParseInputFileOptions & TransformOptions): Promise; /** - * Transform css - * @param css - * @param options + * Transform CSS * * Parsing a string * @@ -883,6 +858,7 @@ export async function transform(options: ParseInputFileOptions & TransformOption * * console.log(result.code); * ``` + * @param args */ export async function transform( ...args: @@ -914,7 +890,18 @@ export async function transform( } options ??= {}; - options = { minify: true, removeEmpty: true, removeCharset: true, ...options }; + + if (options.minify == null) { + options.minify = true; + } + + if (options.removeEmpty == null) { + options.removeEmpty = true; + } + + if (options.removeCharset == null) { + options.removeCharset = true; + } const startTime: number = performance.now(); return parse(stream, options).then((parseResult: ParseResult) => { diff --git a/src/utils/sync.ts b/src/utils/sync.ts new file mode 100644 index 00000000..ac006a31 --- /dev/null +++ b/src/utils/sync.ts @@ -0,0 +1,61 @@ +import type { AstComment, AstNode, ParseResult, ParserOptions, ParserSyncOptions } from "../@types/index.d.ts"; +import { AstNodePropertyType, EnumToken } from "../lib/ast/types.ts"; +import { ERRORS, LOC, PARENT, STATE, TOKENS } from "../lib/syntax/constants.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=") + ) { + options!.source!.setInputSourceMap((token as AstComment).val.slice(21, -2).trim()); + } + } + } + + if (options.module) { + const { revMapping, ...res } = result; + return res as ParseResult; + } + + return result; +} + +/** + * + * @param options + * @param prefix + * @private + */ +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..7a238d93 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"; @@ -63,6 +64,7 @@ export type { ValidationToken } from "./lib/validation/parser/types.d.ts"; export { FeatureWalkMode } from "./lib/ast/features/type.ts"; export { dirname, resolve, ResponseType }; +export { getNodeProperty, setNodeProperty } from "./lib/ast/node.ts"; /** * Load file or url @@ -72,7 +74,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 +124,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); @@ -164,7 +166,7 @@ export function render( } /** - * Parse css file + * Parse CSS file * @param file url or path * @param options * @param asStream load file as stream @@ -199,7 +201,7 @@ export async function parseFile( } /** - * Parse css string + * Parse CSS string * @param stream * @param options * @@ -207,49 +209,74 @@ 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 + * Parse CSS string * @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; /** - * Parse css + * Parse CSS * @param args * * 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; } @@ -300,27 +332,25 @@ export function parseSync( time: 0, source: options.source, position: 0, - currentPosition: -1, + currentPosition: 0, } 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 + * Transform CSS * @param css * @param options * * * ```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); * ``` * @@ -328,15 +358,17 @@ export function parseSync( export function transformSync(css: string, options?: TransformSyncOptions): TransformResult; /** - * Transform css + * 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 +376,24 @@ 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 */ 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 +450,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 + * Parse CSS * @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 +569,7 @@ export async function parse(options: ParseInputStreamOptions & ParserOptions): P * * console.log(result.ast); * ``` + * @param args */ export async function parse( ...args: @@ -484,7 +604,7 @@ export async function parse( options ??= {}; options.src ??= ""; - options.sourcesMap ??= new Map; + options.sourcesMap ??= new Map(); Object.assign(options, { load, @@ -499,7 +619,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; } @@ -511,20 +631,17 @@ export async function parse( time: 0, source: options.source, position: 0, - currentPosition: -1, + currentPosition: 0, } as ParseInfo; 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 +678,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 +776,7 @@ export async function transform(options: ParseInputStreamOptions & TransformOpti * * console.log(result.code); * ``` + * @param args */ export async function transform( ...args: diff --git a/test/allFiles.js b/test/allFiles.js index 841590ef..819b7efc 100644 --- a/test/allFiles.js +++ b/test/allFiles.js @@ -7,7 +7,7 @@ const units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB'], e = Math.floor(Math.log(this) function toFileSize(value) { const e = Math.floor(Math.log(value) / Math.log(1024)); - const result = value === 0 ? 0 : (value / Math.pow(1024, Math.floor(e))); + const result = (value / Math.pow(1024, Math.floor(e))); return (Number.isInteger(result) ? result : result.toFixed(2)) + units[e]; } @@ -20,7 +20,7 @@ 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, diff --git a/test/inspect.js b/test/inspect.js index ba49247c..bd928715 100644 --- a/test/inspect.js +++ b/test/inspect.js @@ -1,7 +1,9 @@ -import {dirname} from "node:path"; -import {transformFile} from '../dist/node.js'; +import { dirname } from "node:path"; +import { transform } from "../dist/node.js"; -const {code, stats} = await transformFile(dirname(new URL(import.meta.url).pathname) + '/files/css/tailwind.css'); +const { code, stats } = await transform({ + file: dirname(new URL(import.meta.url).pathname) + "/files/css/tailwind.css", +}); console.debug(code); -console.debug({stats}); \ No newline at end of file +console.debug({ stats }); diff --git a/test/specs/code/block.js b/test/specs/code/block.js index eb12a38c..dc02514c 100644 --- a/test/specs/code/block.js +++ b/test/specs/code/block.js @@ -1,4 +1,6 @@ export function run(describe, expect, it, transform, parse, render, dirname, readFile, resolve) { + const root = new URL(dirname(import.meta.url) + "/../../../"); + describe("doParse block", function () { it("similar rules #1", function () { const file = ` @@ -1134,10 +1136,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.replace(root.pathname, ""), beautify: true, }; @@ -1149,10 +1155,11 @@ 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.replace(root.pathname, ""), beautify: true, }; diff --git a/test/specs/code/import1.js b/test/specs/code/import1.js index 891f287e..c97434e6 100644 --- a/test/specs/code/import1.js +++ b/test/specs/code/import1.js @@ -1,8 +1,10 @@ export function run(describe, expect, it, transform, parse, render, dirname) { + const root = new URL(dirname(import.meta.url) + '/../../../'); + 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.replace(root.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..68a05349 100644 --- a/test/specs/code/modules.js +++ b/test/specs/code/modules.js @@ -1,6 +1,24 @@ 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, +) { + const root = new URL(dirname(import.meta.url) + "/../../../"); + describe("css modules", function () { it("module #1", function () { return transform( @@ -99,6 +117,8 @@ 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 +127,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.replace(root.pathname, "")}"; color: white; } `, { @@ -119,7 +139,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 +630,7 @@ a span { }); it("module mode ICSS #17", function () { + const url = new URL(dirname(import.meta.url) + "/../../css-modules/mixins.css"); return transform( ` @@ -626,7 +647,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.replace(root.pathname, "")}"; color: white; } `, { @@ -667,63 +688,64 @@ a span { }); }); -// it("module export variables #18", function () { -// return transform( -// ` - -// @value blue: #0c77f8; -// @value red: #ff0000; -// @value green: #aaf200; -// `, -// { -// module: ModuleScopeEnumOptions.ICSS, -// beautify: true, -// }, -// ).then((result) => -// expect(result.cssModuleVariables).deep.equals({ -// blue: { -// typ: EnumToken.CssVariableTokenType, -// nam: "blue", -// val: [ -// { -// typ: EnumToken.ColorTokenType, -// val: "#0c77f8", -// kin: ColorType.HEX, -// }, -// ], -// }, -// red: { -// typ: EnumToken.CssVariableTokenType, -// nam: "red", -// val: [ -// { -// typ: EnumToken.ColorTokenType, -// val: "#ff0000", -// kin: ColorType.HEX, -// }, -// ], -// }, -// green: { -// typ: EnumToken.CssVariableTokenType, -// nam: "green", -// val: [ -// { -// typ: EnumToken.ColorTokenType, -// val: "#aaf200", -// kin: ColorType.HEX, -// }, -// ], -// }, -// }), -// ); -// }); + // it("module export variables #18", function () { + // return transform( + // ` + + // @value blue: #0c77f8; + // @value red: #ff0000; + // @value green: #aaf200; + // `, + // { + // module: ModuleScopeEnumOptions.ICSS, + // beautify: true, + // }, + // ).then((result) => + // expect(result.cssModuleVariables).deep.equals({ + // blue: { + // typ: EnumToken.CssVariableTokenType, + // nam: "blue", + // val: [ + // { + // typ: EnumToken.ColorTokenType, + // val: "#0c77f8", + // kin: ColorType.HEX, + // }, + // ], + // }, + // red: { + // typ: EnumToken.CssVariableTokenType, + // nam: "red", + // val: [ + // { + // typ: EnumToken.ColorTokenType, + // val: "#ff0000", + // kin: ColorType.HEX, + // }, + // ], + // }, + // green: { + // typ: EnumToken.CssVariableTokenType, + // nam: "green", + // val: [ + // { + // typ: EnumToken.ColorTokenType, + // val: "#aaf200", + // kin: ColorType.HEX, + // }, + // ], + // }, + // }), + // ); + // }); 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.replace(root.pathname, "")}"; @value blue, red, green from colors; .button { @@ -858,9 +880,9 @@ a span { }); it("module grid #22", function () { - - return expect(transform( - ` + return expect( + transform( + ` .grid { grid-template-areas: 'nav main'; @@ -873,15 +895,16 @@ a span { } `, - { - module: { - pattern: "[local]-[hash:bogus]", + { + module: { + pattern: "[local]-[hash:bogus]", + }, + beautify: true, }, - beautify: true, - }, - )).to.be.rejectedWith( - `Unsupported hash length: 'bogus'. expecting format [hash:length] or [hash:hash-algo:length]`, - ); + ), + ).to.be.rejectedWith( + `Unsupported hash length: 'bogus'. expecting format [hash:length] or [hash:hash-algo:length]`, + ); }); it("module grid #23", function () { @@ -922,5 +945,68 @@ 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 +}`); + }); + + it("module grid #25", function () { + return expect( + Promise.resolve( + (async () => + transformSync( + ` + +.grid { + grid-template-areas: 'nav main'; + grid-template-columns: [line-name1] 100px [line-name2 line-name3]; +} + +.nav { + grid-column-start: nav-start; + grid-column-end: nav-end; +} + +`, + { + module: { + pattern: "[local]-[hash:sha1]", + }, + beautify: true, + }, + ))(), + ), + ).to.be.rejectedWith( + `Unsupported hash algorithm: 'sha1'. Not supported by parseSync() or transformSync(). Use parse() or transform().`, + ); + }); }); } diff --git a/test/specs/code/prefix.js b/test/specs/code/prefix.js index 0d8e9056..ac9431d8 100644 --- a/test/specs/code/prefix.js +++ b/test/specs/code/prefix.js @@ -1,10 +1,24 @@ - -export function run(describe, expect, it, transform, parse, render, dirname, readFile) { - - describe('prefix removal', function () { - - it('selector prefix #1', function () { - return transform(` +export function run( + describe, + expect, + it, + transform, + parse, + render, + dirname, + readFile, + resolve, + ColorType, + EnumToken, + ModuleCaseTransformEnum, + ModuleScopeEnumOptions, + transformSync, + parseSync, +) { + describe("prefix removal", function () { + it("selector prefix #1", function () { + return transform( + ` @media screen { @@ -12,15 +26,20 @@ export function run(describe, expect, it, transform, parse, render, dirname, rea height: calc(100px * 2/ 15); } } -`, {removePrefix: true}).then(result => expect(render(result.ast, {minify: false}).code).equals(`@media screen { +`, + { removePrefix: true }, + ).then((result) => + expect(render(result.ast, { minify: false }).code).equals(`@media screen { .foo:autofill:not(:hover) { height: calc(40px/3) } -}`)); +}`), + ); }); - it('selector prefix #2', function () { - return transform(` + it("selector prefix #2", function () { + return transform( + ` @media screen { @@ -29,15 +48,20 @@ export function run(describe, expect, it, transform, parse, render, dirname, rea display: none; } } -`, {removePrefix: true, nestingRules: false}).then(result => expect(render(result.ast, {minify: false}).code).equals(`@media screen { +`, + { removePrefix: true, nestingRules: false }, + ).then((result) => + expect(render(result.ast, { minify: false }).code).equals(`@media screen { .foo:is(:autofill:not(:hover),a,b) { display: none } -}`)); +}`), + ); }); - it('selector unknown prefix #3', function () { - return transform(` + it("selector unknown prefix #3", function () { + return transform( + ` @media screen { @@ -45,15 +69,20 @@ export function run(describe, expect, it, transform, parse, render, dirname, rea height: calc(100px * 2/ 15); } } -`, {removePrefix: true}).then(result => expect(render(result.ast, {minify: false}).code).equals(`@media screen { +`, + { removePrefix: true }, + ).then((result) => + expect(render(result.ast, { minify: false }).code).equals(`@media screen { .foo:autofill:not(:hover) { height: calc(40px/3) } -}`)); +}`), + ); }); - it('selector invalid prefix #4', function () { - return transform(` + it("selector invalid prefix #4", function () { + return transform( + ` @media screen { @@ -61,11 +90,14 @@ export function run(describe, expect, it, transform, parse, render, dirname, rea height: calc(100px * 2/ 15); } } -`, {removePrefix: true}).then(result => expect(result.code).equals(``)); +`, + { removePrefix: true }, + ).then((result) => expect(result.code).equals(``)); }); - it('prefixed properties #4', function () { - return transform(` + it("prefixed properties #4", function () { + return transform( + ` a:any-link { border: 1px solid blue; @@ -89,7 +121,10 @@ a:-webkit-any-link { -webkit-appearance: none;; } } -`, {removePrefix: true, beautify: true}).then(result => expect(result.code).equals(`a:any-link { +`, + { removePrefix: true, beautify: true }, + ).then((result) => + expect(result.code).equals(`a:any-link { border: 1px solid blue; color: orange } @@ -103,11 +138,13 @@ a:-webkit-any-link { appearance: none } } -}`)); +}`), + ); }); - it('all prefixed #5', function () { - return transform(` + it("all prefixed #5", function () { + return transform( + ` ::-webkit-input-placeholder { color: gray; @@ -226,11 +263,14 @@ a { .xl\\:origin-bottom-left2 { background: -o-linear-gradient(-90deg,#fff,#000); } -`, { - removePrefix: true, - removeDuplicateDeclarations: false, - beautify: true - }).then(result => expect(result.code).equals(`::placeholder { +`, + { + removePrefix: true, + removeDuplicateDeclarations: false, + beautify: true, + }, + ).then((result) => + expect(result.code).equals(`::placeholder { color: grey } @supports selector(::placeholder) { @@ -304,11 +344,13 @@ a { } .xl\\:origin-bottom-left2 { background: linear-gradient(#fff,#000) -}`)); +}`), + ); }); - it('do not mix #6', function () { - return transform(` + it("do not mix #6", function () { + return transform( + ` ::moz-selection { opacity: .25; @@ -325,9 +367,12 @@ a { filter: blur(25px); } } -`, { - beautify: true - }).then(result => expect(result.code).equals(`::moz-selection { +`, + { + beautify: true, + }, + ).then((result) => + expect(result.code).equals(`::moz-selection { opacity: .25; filter: blur(25px) } @@ -338,8 +383,75 @@ a { ::selection { opacity: .25; filter: blur(25px) -}`)); +}`), + ); }); + it("do not mix #7", function () { + const result = transformSync( + ` + +@media (-webkit-max-device-pixel-ratio: 2) { +#converted-text { + color: #00b400; + background: -webkit-gradient(linear,left bottom, left top,from(#fff),to(#000)); +} + +.hsl { + color: #00b400; + background: -webkit-gradient(linear,left top, right top,from(#fff),to(#000)); +} +.bg { + color: #00b400; + background: -webkit-gradient(linear,right top, left top,from(#fff),to(#000)); +} +.bg2 { + color: #00b400; + background: -webkit-gradient(linear,right top, left bottom,from(#fff),to(#000)); +} +.bg3 { + color: #00b400; + background: -webkit-gradient(linear,left bottom, right top,from(#fff),to(#000)); +} +.bg4 { + color: #00b400; + background: -webkit-gradient(linear,right bottom, left top,from(#fff),to(#000)); +} +`, + { + beautify: true, + removePrefix: true, + }, + ); + + expect(result.code).equals(`@media (max-resolution:2x) { + #converted-text { + color: #00b400; + background: linear-gradient(0,#fff,#000) + } + .hsl,.bg { + color: #00b400 + } + .bg { + background: linear-gradient(270deg,#fff,#000) + } + .hsl { + background: linear-gradient(90deg,#fff,#000) + } + .bg2,.bg3 { + color: #00b400 + } + .bg3 { + background: linear-gradient(to top right,#fff,#000) + } + .bg2 { + background: linear-gradient(to bottom left,#fff,#000) + } + .bg4 { + color: #00b400; + background: linear-gradient(to top left,#fff,#000) + } +}`); + }); }); -} \ No newline at end of file +} diff --git a/test/specs/code/sourcemaps.js b/test/specs/code/sourcemaps.js index 9badb040..7a4f3038 100644 --- a/test/specs/code/sourcemaps.js +++ b/test/specs/code/sourcemaps.js @@ -1,20 +1,108 @@ -export function run(describe, expect, it, transform, parse, render, dirname, readFile, resolve) { - - // 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 +import { ColorType, EnumToken, ModuleCaseTransformEnum, ModuleScopeEnumOptions } from "../../../dist/lib/ast/types.js"; + +export function run( + describe, + expect, + it, + transform, + parse, + render, + dirname, + readFile, + resolve, + ColorType, + EnumToken, + ModuleCaseTransformEnum, + ModuleScopeEnumOptions, + transformSync, + parseSync, +) { + const root = new URL(dirname(import.meta.url) + "/../../../"); + + 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.replace(root.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", + expandNestingRules: true, + 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(40, 2); + expect(positions?.length == 1 && positions[0].slice(0, 3)).deep.equals([null, 6, 2]); + }); + }); + + it("sourcemap minified #2", async () => { + return transform(options).then(async (result) => { + const result2 = transformSync({ + input: result.code, + nestingRules: false, + sourcemap: "inline", + output: "test/sourcemap.html", + }); + + // result2.map.computePositions(); + let positions = result2.map.find(1, 254); + expect(positions?.[0]?.slice?.(0, 3)).deep.equals([null, 19, 2]); + + positions = result2.map.find(1, 255); + expect(positions).equals(null); + + positions = result2.map.find(100, 255); + expect(positions).equals(null); + }); + }); + + it("input sourcemap minified #3", async () => { + return transform({ ...options, sourcemap: true }).then(async (result) => { + const result2 = transformSync({ + input: result.code, + nestingRules: false, + sourcemap: "inline", + inputSourceMap: result.map.toJSON(), + output: "test/sourcemap.html", + }); + + // result2.map.computePositions(); + const positions = result2.map.find(1, 254); + expect(positions?.[0]?.slice?.(0, 3)).deep.equals([null, 19, 2]); + }); + }); + + it("input sourcemap minified #3", async () => { + return transform({ ...options, sourcemap: true }).then(async (result) => { + const result2 = transformSync({ + input: result.code, + nestingRules: false, + sourcemap: "inline", + inputSourceMap: `data:application/json;charset=utf-8;${encodeURIComponent(JSON.stringify(result.map.toJSON()))}`, + output: "test/sourcemap.html", + }); + + // result2.map.computePositions(); + const positions = result2.map.find(1, 254); + expect(positions?.[0]?.slice?.(0, 3)).deep.equals([null, 19, 2]); + }); + }); + }); +} diff --git a/test/specs/code/validation.js b/test/specs/code/validation.js index 13592a9c..ead711fc 100644 --- a/test/specs/code/validation.js +++ b/test/specs/code/validation.js @@ -1,5 +1,7 @@ export function run(describe, expect, it, transform, parse, render, dirname, readFile) { + const root = new URL(dirname(import.meta.url) + '/../../../'); + describe('selector validation', function () { it('selector validation #1', function () { @@ -505,13 +507,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.replace(root.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.replace(root.pathname, '')}'; `, { validation: true, resolveImport: true @@ -520,7 +524,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.replace(root.pathname, '')}'; `, { validation: true, resolveImport: true @@ -529,7 +534,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.replace(root.pathname, '')}'; `, { validation: true, resolveImport: true @@ -538,7 +544,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.replace(root.pathname, '')}'; `, { validation: true, resolveImport: true @@ -547,7 +554,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.replace(root.pathname, '')}'; `, { validation: true, resolveImport: true @@ -556,7 +565,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.replace(root.pathname, '')}'; `, { validation: true, resolveImport: true diff --git a/test/specs/code/visitors.js b/test/specs/code/visitors.js index 148c338a..90d0c90f 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,433 @@ html,body { } `; const options = { - removePrefix: true, beautify: true, visitor: { KeyframesAtRule: { slideIn(node) { + 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% + } +}`), + ); + }); - node.val = 'slide-in-out'; + 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; +} + +.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 = ` + + .ruler { + + height: 10px; + } + +@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: { + Rule: { + type: WalkerEvent.Enter, + handler(node) { + + node.sel = ".ruled"; + // node.val = "slide-in-out"; + // return node; + }, + }, + Declaration: { + type: WalkerEvent.Leave, + handler(node) { + + // node.sel = ".ruled"; + // node.val = "slide-in-out"; + // return node; + }, + }, + KeyframesAtRule: { + slideIn(node) { + node.val = "slide-in-out"; return node; + }, + }, + }, + }; + + const result = transformSync(css, options); + + expect(result.code).equals(`.ruled { + height: 10px +} +@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 #9", 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: [ + function DeclarationNodeType(declaration) { + if (declaration.nam == "height") { + declaration.nam = "width"; } - } - } + }, + { + ColorTokenType: { + type: WalkerEvent.Enter, + handler: function (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 +}`), + ); + }); + + + + it("visitor #10", function () { + const css = ` + + .ruler { + + height: 10px; } - return transform(css, options).then(result => expect(result.code).equals(`@keyframes slide-in-out { +@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: [ + { + Rule: { + type: WalkerEvent.Enter, + handler(node) { + node.sel = ".ruled"; + // node.val = "slide-in-out"; + // return node; + }, + }, + }, + { + KeyframesAtRule: { + slideIn(node) { + node.val = "slide-in-out"; + return node; + }, + }, + }, + ], + }; + + const result = transformSync(css, options); + + expect(result.code).equals(`.ruled { + height: 10px +} +@keyframes slide-in-out { 0% { transform: translateX(0) } @@ -252,9 +662,32 @@ html,body { top: 100px; left: 100% } -}`)); +}`); }); - }); + it("visitor #11", function () { + const css = ` -} \ No newline at end of file + .ruler { + + height: 10px; + } +`; + const options = { + removePrefix: true, + beautify: true, + visitor: function Rule(node) { + node.sel = ".ruled"; + // node.val = "slide-in-out"; + // return node; + }, +}; + + const result = transformSync(css, options); + + expect(result.code).equals(`.ruled { + height: 10px +}`); + }); + }); +} 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'],