From 4a1c07dec39167e109a133a59bf572fbacceb61e Mon Sep 17 00:00:00 2001 From: Thierry Bela Nanga Date: Wed, 12 Aug 2026 09:41:00 -0400 Subject: [PATCH 01/11] update link to llms.txt --- src/lib/fs/resolve.ts | 7 +++---- typedoc.config.js | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/lib/fs/resolve.ts b/src/lib/fs/resolve.ts index e39004d8..08428858 100644 --- a/src/lib/fs/resolve.ts +++ b/src/lib/fs/resolve.ts @@ -149,10 +149,6 @@ export const resolve = memoize(function ( cwd?: string, ): { absolute: string; relative: string } { - - cwd ??= ""; - currentDirectory ??= ""; - if (matchUrl.test(url)) { return { absolute: url, @@ -160,6 +156,9 @@ export const resolve = memoize(function ( }; } + cwd ??= ""; + currentDirectory ??= ""; + url = normalize(url); 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'], From d758f20530c1eaa7ecac47b567eb46f9f722e09b Mon Sep 17 00:00:00 2001 From: Thierry Bela Nanga Date: Wed, 12 Aug 2026 13:59:07 -0400 Subject: [PATCH 02/11] fix documentation --- dist/index-umd-web.js | 63 ++++++++++++++++++------------ dist/index.cjs | 63 ++++++++++++++++++------------ dist/index.d.ts | 16 ++++---- dist/lib/ast/clone.js | 16 ++++---- dist/lib/fs/resolve.js | 4 +- dist/lib/parser/declaration/map.js | 17 +++++--- dist/lib/parser/parse.js | 18 ++++++--- dist/node.js | 8 ++-- dist/web.js | 8 ++-- files/usage.md | 13 +++--- llms.txt | 6 +-- src/lib/ast/clone.ts | 16 ++++---- src/lib/parser/declaration/map.ts | 27 ++++++++----- src/lib/parser/parse.ts | 29 +++++++------- src/node.ts | 24 ++++++------ src/web.ts | 24 ++++++------ 16 files changed, 202 insertions(+), 150 deletions(-) diff --git a/dist/index-umd-web.js b/dist/index-umd-web.js index 1792fbfb..3148a197 100644 --- a/dist/index-umd-web.js +++ b/dist/index-umd-web.js @@ -11683,14 +11683,14 @@ clone[name] = value; } else if (Array.isArray(value)) { - clone[name] = - !cloneChildren && name == checkNode - ? [] - : value.map((c) => { - const newObj = cloneNode(c, cloneChildren, cloneMap); - cloneMap?.set?.(c, newObj); - return newObj; - }); + clone[name] = []; + if (cloneChildren || name !== checkNode) { + for (const c of value) { + const newObj = cloneNode(c, cloneChildren, cloneMap); + cloneMap?.set?.(c, newObj); + clone[name].push(newObj); + } + } } else { clone[name] = { ...value }; @@ -18912,12 +18912,19 @@ return acc; }, []); let isImportant = false; - const filtered = values.map(removeDefaults).filter((x) => x.val.filter((t) => { - if (t.typ == exports.EnumToken.ImportantTokenType) { - isImportant = true; + let dec; + const filtered = []; + for (const declaration of values) { + dec = removeDefaults(declaration); + for (const t of dec.val) { + if (t.typ == exports.EnumToken.ImportantTokenType) { + isImportant = true; + } + if (filtered.length == 0 && t.typ != exports.EnumToken.WhitespaceTokenType && t.typ != exports.EnumToken.ImportantTokenType) { + filtered.push(dec); + } } - return ![exports.EnumToken.WhitespaceTokenType, exports.EnumToken.ImportantTokenType].includes(t.typ); - }).length > 0); + } if (filtered.length == 0 && this.config.default.length > 0) { filtered.push({ typ: exports.EnumToken.DeclarationNodeType, @@ -24289,14 +24296,14 @@ * @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 (currentDirectory !== "") { currentDirectory = normalize(currentDirectory); @@ -29316,12 +29323,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; @@ -31568,7 +31576,12 @@ position: 0, currentPosition: -1, }; - const result = parseTokens([...tokenize(parseInfo)].map((t) => t.token), options, errors); + 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.pop(); if (result.at(-1)?.typ === exports.EnumToken.WhitespaceTokenType) { @@ -31925,10 +31938,10 @@ * * ```ts * - * import {parse} from '@tbela99/css-parser'; + * import {parseSync} from '@tbela99/css-parser'; * * // css string - * let result = await parse(css, {nestingRules: true}); + * let result = await parseSync(css, {nestingRules: true}); * console.log(result.ast); * ``` * @@ -31982,10 +31995,10 @@ * * ```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); * ``` * diff --git a/dist/index.cjs b/dist/index.cjs index b3a4b908..2f51323c 100644 --- a/dist/index.cjs +++ b/dist/index.cjs @@ -11686,14 +11686,14 @@ function cloneNode(node, cloneChildren = false, cloneMap = null) { clone[name] = value; } else if (Array.isArray(value)) { - clone[name] = - !cloneChildren && name == checkNode - ? [] - : value.map((c) => { - const newObj = cloneNode(c, cloneChildren, cloneMap); - cloneMap?.set?.(c, newObj); - return newObj; - }); + clone[name] = []; + if (cloneChildren || name !== checkNode) { + for (const c of value) { + const newObj = cloneNode(c, cloneChildren, cloneMap); + cloneMap?.set?.(c, newObj); + clone[name].push(newObj); + } + } } else { clone[name] = { ...value }; @@ -18915,12 +18915,19 @@ class PropertyMap { return acc; }, []); let isImportant = false; - const filtered = values.map(removeDefaults).filter((x) => x.val.filter((t) => { - if (t.typ == exports.EnumToken.ImportantTokenType) { - isImportant = true; + let dec; + const filtered = []; + for (const declaration of values) { + dec = removeDefaults(declaration); + for (const t of dec.val) { + if (t.typ == exports.EnumToken.ImportantTokenType) { + isImportant = true; + } + if (filtered.length == 0 && t.typ != exports.EnumToken.WhitespaceTokenType && t.typ != exports.EnumToken.ImportantTokenType) { + filtered.push(dec); + } } - return ![exports.EnumToken.WhitespaceTokenType, exports.EnumToken.ImportantTokenType].includes(t.typ); - }).length > 0); + } if (filtered.length == 0 && this.config.default.length > 0) { filtered.push({ typ: exports.EnumToken.DeclarationNodeType, @@ -24292,14 +24299,14 @@ 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 (currentDirectory !== "") { currentDirectory = normalize(currentDirectory); @@ -29319,12 +29326,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; @@ -31571,7 +31579,12 @@ function parseString(src, options = { parseColor: true }, errors) { position: 0, currentPosition: -1, }; - const result = parseTokens([...tokenize(parseInfo)].map((t) => t.token), options, errors); + 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.pop(); if (result.at(-1)?.typ === exports.EnumToken.WhitespaceTokenType) { @@ -31930,10 +31943,10 @@ const parseFile = node_util.deprecate(async (file, options = {}, asStream = fals * * ```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); * ``` * @@ -31985,10 +31998,10 @@ function parseSync(...args) { * * ```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); * ``` * diff --git a/dist/index.d.ts b/dist/index.d.ts index 1eae9a4f..c60324b9 100644 --- a/dist/index.d.ts +++ b/dist/index.d.ts @@ -6074,10 +6074,10 @@ declare const parseFile: (file: string, options?: ParserOptions, asStream?: bool * * ```ts * - * import {parse} from '@tbela99/css-parser'; + * import {parseSync} from '@tbela99/css-parser'; * * // css string - * let result = await parse(css, {nestingRules: true}); + * let result = parseSync(css, {nestingRules: true}); * console.log(result.ast); * ``` * @@ -6092,10 +6092,10 @@ declare function parseSync(stream: string, options?: ParserSyncOptions): ParseRe * * ```ts * - * import {parse} from '@tbela99/css-parser'; + * import {parseSync} from '@tbela99/css-parser'; * * // css string - * let result = await parse({input: css, nestingRules: true}); + * let result = parseSync({input: css, nestingRules: true}); * console.log(result.ast); * ``` * @@ -6109,10 +6109,10 @@ declare function parseSync(options: ParseInputOptions & ParserSyncOptions): Pars * * ```ts * - * import {transform} from '@tbela99/css-parser'; + * import {transformSync} from '@tbela99/css-parser'; * * // css string - * const result = await transform(css, {beautify: true}); + * const result = transformSync(css, {beautify: true}); * console.log(result.code); * ``` * @@ -6124,10 +6124,10 @@ declare function transformSync(css: string, options?: TransformSyncOptions): Tra * * ```ts * - * import {transform} from '@tbela99/css-parser'; + * import {transformSync} from '@tbela99/css-parser'; * * // css string - * const result = await transform({input: css, beautify: true}); + * const result = transformSync({input: css, beautify: true}); * console.log(result.code); * ``` * diff --git a/dist/lib/ast/clone.js b/dist/lib/ast/clone.js index b5a3537a..3842583d 100644 --- a/dist/lib/ast/clone.js +++ b/dist/lib/ast/clone.js @@ -17,14 +17,14 @@ function cloneNode(node, cloneChildren = false, cloneMap = null) { clone[name] = value; } else if (Array.isArray(value)) { - clone[name] = - !cloneChildren && name == checkNode - ? [] - : value.map((c) => { - const newObj = cloneNode(c, cloneChildren, cloneMap); - cloneMap?.set?.(c, newObj); - return newObj; - }); + clone[name] = []; + if (cloneChildren || name !== checkNode) { + for (const c of value) { + const newObj = cloneNode(c, cloneChildren, cloneMap); + cloneMap?.set?.(c, newObj); + clone[name].push(newObj); + } + } } else { clone[name] = { ...value }; diff --git a/dist/lib/fs/resolve.js b/dist/lib/fs/resolve.js index 7a2d8b5a..9ae24cae 100644 --- a/dist/lib/fs/resolve.js +++ b/dist/lib/fs/resolve.js @@ -122,14 +122,14 @@ 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 (currentDirectory !== "") { currentDirectory = normalize(currentDirectory); diff --git a/dist/lib/parser/declaration/map.js b/dist/lib/parser/declaration/map.js index 736cf11f..d1cfc62c 100644 --- a/dist/lib/parser/declaration/map.js +++ b/dist/lib/parser/declaration/map.js @@ -286,12 +286,19 @@ class PropertyMap { return acc; }, []); let isImportant = false; - const filtered = values.map(removeDefaults).filter((x) => x.val.filter((t) => { - if (t.typ == EnumToken.ImportantTokenType) { - isImportant = true; + let dec; + const filtered = []; + for (const declaration of values) { + dec = removeDefaults(declaration); + for (const t of dec.val) { + if (t.typ == EnumToken.ImportantTokenType) { + isImportant = true; + } + if (filtered.length == 0 && t.typ != EnumToken.WhitespaceTokenType && t.typ != EnumToken.ImportantTokenType) { + filtered.push(dec); + } } - return ![EnumToken.WhitespaceTokenType, EnumToken.ImportantTokenType].includes(t.typ); - }).length > 0); + } if (filtered.length == 0 && this.config.default.length > 0) { filtered.push({ typ: EnumToken.DeclarationNodeType, diff --git a/dist/lib/parser/parse.js b/dist/lib/parser/parse.js index 8325511f..cd5df1f8 100644 --- a/dist/lib/parser/parse.js +++ b/dist/lib/parser/parse.js @@ -1292,12 +1292,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; @@ -3544,7 +3545,12 @@ function parseString(src, options = { parseColor: true }, errors) { position: 0, currentPosition: -1, }; - const result = parseTokens([...tokenize(parseInfo)].map((t) => t.token), options, errors); + 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.pop(); if (result.at(-1)?.typ === EnumToken.WhitespaceTokenType) { diff --git a/dist/node.js b/dist/node.js index 47ce863a..21d0e975 100644 --- a/dist/node.js +++ b/dist/node.js @@ -141,10 +141,10 @@ const parseFile = deprecate(async (file, options = {}, asStream = false) => pars * * ```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); * ``` * @@ -196,10 +196,10 @@ function parseSync(...args) { * * ```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); * ``` * diff --git a/dist/web.js b/dist/web.js index a6194266..63d9f568 100644 --- a/dist/web.js +++ b/dist/web.js @@ -133,10 +133,10 @@ async function parseFile(file, options = {}, asStream = false) { * * ```ts * - * import {parse} from '@tbela99/css-parser'; + * import {parseSync} from '@tbela99/css-parser'; * * // css string - * let result = await parse(css, {nestingRules: true}); + * let result = await parseSync(css, {nestingRules: true}); * console.log(result.ast); * ``` * @@ -190,10 +190,10 @@ function parseSync(...args) { * * ```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); * ``` * diff --git a/files/usage.md b/files/usage.md index b5a603b3..ffbc6107 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 diff --git a/llms.txt b/llms.txt index ddef377c..50cb7811 100644 --- a/llms.txt +++ b/llms.txt @@ -43,15 +43,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/src/lib/ast/clone.ts b/src/lib/ast/clone.ts index 9389a67e..cba96bb2 100644 --- a/src/lib/ast/clone.ts +++ b/src/lib/ast/clone.ts @@ -24,15 +24,17 @@ export function cloneNode( if (value == null || typeof value != "object") { clone[name] = value; } else if (Array.isArray(value)) { - clone[name] = - !cloneChildren && name == checkNode - ? [] - : value.map((c) => { - const newObj = cloneNode(c, cloneChildren, cloneMap); + clone[name] = []; + if (cloneChildren || name !== checkNode) { + + for (const c of value) { + const newObj = cloneNode(c, cloneChildren, cloneMap); cloneMap?.set?.(c, newObj); - return newObj; - }); + clone[name].push(newObj); + } + } + } else { clone[name] = { ...value }; } diff --git a/src/lib/parser/declaration/map.ts b/src/lib/parser/declaration/map.ts index e6140a44..23f02650 100644 --- a/src/lib/parser/declaration/map.ts +++ b/src/lib/parser/declaration/map.ts @@ -382,16 +382,25 @@ export class PropertyMap { ); let isImportant: boolean = false; - const filtered: AstDeclaration[] = values.map(removeDefaults).filter( - (x: AstDeclaration): boolean => - x.val.filter((t: Token) => { - if (t.typ == EnumToken.ImportantTokenType) { - isImportant = true; - } + let dec: AstDeclaration; - return ![EnumToken.WhitespaceTokenType, EnumToken.ImportantTokenType].includes(t.typ); - }).length > 0, - ); + const filtered: AstDeclaration[] = []; + + for (const declaration of values) { + + dec = removeDefaults(declaration); + + for (const t of dec.val) { + if (t.typ == EnumToken.ImportantTokenType) { + isImportant = true; + } + + if (filtered.length == 0 && t.typ != EnumToken.WhitespaceTokenType && t.typ != EnumToken.ImportantTokenType) { + + filtered.push(dec); + } + } + } if (filtered.length == 0 && this.config.default.length > 0) { filtered.push({ diff --git a/src/lib/parser/parse.ts b/src/lib/parser/parse.ts index d62791a9..2285cb55 100644 --- a/src/lib/parser/parse.ts +++ b/src/lib/parser/parse.ts @@ -1742,17 +1742,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; @@ -4616,11 +4614,14 @@ export function parseString( currentPosition: -1, }; - const result = parseTokens( - [...tokenize(parseInfo)].map((t) => t.token), - options, - errors, - ); + 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.pop(); diff --git a/src/node.ts b/src/node.ts index 401b4403..b762c3bd 100644 --- a/src/node.ts +++ b/src/node.ts @@ -217,10 +217,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); * ``` * @@ -237,10 +237,10 @@ export function parseSync(stream: string, options?: ParserSyncOptions): ParseRes * * ```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); * ``` * @@ -256,10 +256,10 @@ export function parseSync(options: ParseInputOptions & ParserSyncOptions): Parse * * ```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); * ``` * @@ -324,10 +324,10 @@ export function parseSync( * * ```ts * - * import {transform} from '@tbela99/css-parser'; + * import {transformSync} from '@tbela99/css-parser'; * * // css string - * const result = await transform(css, {beautify: true}); + * const result = transformSync(css, {beautify: true}); * console.log(result.code); * ``` * @@ -340,10 +340,10 @@ export function transformSync(css: string, options?: TransformSyncOptions): Tran * * ```ts * - * import {transform} from '@tbela99/css-parser'; + * import {transformSync} from '@tbela99/css-parser'; * * // css string - * const result = await transform({input: css, beautify: true}); + * const result = transformSync({input: css, beautify: true}); * console.log(result.code); * ``` * @@ -357,10 +357,10 @@ export function transformSync(options: ParseInputOptions & TransformSyncOptions) * * ```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); * ``` * diff --git a/src/web.ts b/src/web.ts index 059a280e..4f961d59 100644 --- a/src/web.ts +++ b/src/web.ts @@ -207,10 +207,10 @@ export async function parseFile( * * ```ts * - * import {parse} from '@tbela99/css-parser'; + * import {parseSync} from '@tbela99/css-parser'; * * // css string - * let result = await parse(css, {nestingRules: true}); + * let result = await parseSync(css, {nestingRules: true}); * console.log(result.ast); * ``` * @@ -227,10 +227,10 @@ export function parseSync(stream: string, options?: ParserSyncOptions): ParseRes * * ```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 = await parseSync({input: css, nestingRules: true}); * console.log(result.ast); * ``` * @@ -246,10 +246,10 @@ export function parseSync(options: ParseInputOptions & ParserSyncOptions): Parse * * ```ts * - * import {parse} from '@tbela99/css-parser'; + * import {parseSync} from '@tbela99/css-parser'; * * // css string - * let result = await parse(css, {nestingRules: true}); + * let result = await parseSync(css, {nestingRules: true}); * console.log(result.ast); * ``` * @@ -317,10 +317,10 @@ export function parseSync( * * ```ts * - * import {transform} from '@tbela99/css-parser'; + * import {transformSync} from '@tbela99/css-parser'; * * // css string - * const result = await transform(css, {beautify: true}); + * const result = transformSync(css, {beautify: true}); * console.log(result.code); * ``` * @@ -333,10 +333,10 @@ export function transformSync(css: string, options?: TransformSyncOptions): Tran * * ```ts * - * import {transform} from '@tbela99/css-parser'; + * import {transformSync} from '@tbela99/css-parser'; * * // css string - * const result = await transform({input: css, beautify: true}); + * const result = transformSync({input: css, beautify: true}); * console.log(result.code); * ``` * @@ -350,10 +350,10 @@ export function transformSync(options: ParseInputOptions & TransformSyncOptions) * * ```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); * ``` * From 1d419ab634bff648c6dd05b4edcd68bfa1521571 Mon Sep 17 00:00:00 2001 From: Thierry Bela Nanga Date: Sat, 15 Aug 2026 10:19:26 -0400 Subject: [PATCH 03/11] implement inputSourceMap support #146 --- CHANGELOG.md | 6 + README.md | 8 +- dist/index-umd-web.js | 947 ++++++++++++------ dist/index.cjs | 944 +++++++++++------ dist/index.d.ts | 111 +- dist/lib/ast/expand.js | 5 +- dist/lib/ast/find.js | 2 +- dist/lib/ast/minify.js | 36 +- dist/lib/fs/resolve.js | 84 +- dist/lib/parser/linesmap.js | 4 +- dist/lib/parser/parse.js | 72 +- dist/lib/parser/source.js | 18 +- dist/lib/parser/tokenize.js | 8 +- dist/lib/renderer/render.js | 205 ++-- dist/lib/renderer/sourcemap/lib/codec.js | 78 ++ dist/lib/renderer/sourcemap/lib/encode.js | 37 - dist/lib/renderer/sourcemap/sourcemap.js | 197 +++- dist/lib/validation/match.js | 2 +- dist/node.js | 19 +- dist/utils.d.ts | 9 + dist/utils.js | 49 + dist/web.js | 22 +- files/getting-started.md | 8 +- files/transform.md | 24 + llms.txt | 11 +- src/@types/index.d.ts | 32 +- src/lib/ast/expand.ts | 21 +- src/lib/ast/find.ts | 12 +- src/lib/ast/minify.ts | 70 +- src/lib/fs/resolve.ts | 101 +- src/lib/parser/linesmap.ts | 5 +- src/lib/parser/parse.ts | 94 +- src/lib/parser/source.ts | 58 +- src/lib/parser/tokenize.ts | 13 +- src/lib/renderer/render.ts | 261 +++-- .../sourcemap/lib/{encode.ts => codec.ts} | 50 +- src/lib/renderer/sourcemap/sourcemap.ts | 253 ++++- src/lib/validation/match.ts | 2 +- src/node.ts | 31 +- src/utils.ts | 54 + src/web.ts | 28 +- test/specs/code/block.js | 16 +- test/specs/code/import1.js | 3 +- test/specs/code/modules.js | 16 +- test/specs/code/sourcemaps.js | 50 +- test/specs/code/validation.js | 23 +- 46 files changed, 2796 insertions(+), 1303 deletions(-) create mode 100644 dist/lib/renderer/sourcemap/lib/codec.js delete mode 100644 dist/lib/renderer/sourcemap/lib/encode.js create mode 100644 dist/utils.d.ts create mode 100644 dist/utils.js rename src/lib/renderer/sourcemap/lib/{encode.ts => codec.ts} (52%) create mode 100644 src/utils.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..8833e01f 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. diff --git a/dist/index-umd-web.js b/dist/index-umd-web.js index 3148a197..c71e7413 100644 --- a/dist/index-umd-web.js +++ b/dist/index-umd-web.js @@ -12666,7 +12666,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 { @@ -21050,7 +21050,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' @@ -21389,6 +21389,315 @@ 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; + } + /** + * @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; + } + + /** + * 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") { + sourcemaps = JSON.parse(sourcemaps); + } + if (sourcemaps != null) { + this.sources = sourcemaps.sources?.slice() ?? []; + this.sourcesContent = sourcemaps.sourcesContent?.slice() ?? []; + const decodedMappings = sourcemaps.mappings + .split(";") + .map((mapping) => mapping.split(",").map((mapping) => decode(mapping))); + this.line = decodedMappings.length - 1; + for (let index = 0; index < decodedMappings.length; index++) { + if (decodedMappings[index].length == 0 || + (decodedMappings[index].length == 1 && decodedMappings[index][0].length == 0)) { + continue; + } + this.map.set(index, decodedMappings[index]); + } + this.computePositions(); + } + } + /** + * Add all location + * @param maps + */ + addAll(maps) { + for (let [newLine, newColumn, srcId, ln, col, sourceFileName, sourceContent] of maps) { + const key = `${srcId}:${ln}:${sourceFileName}:${col}:${newLine}:${newColumn}:${sourceContent}`; + const sourcemap = `${srcId}:${sourceFileName}:${sourceContent}`; + if (this.keys.has(key)) { + continue; + } + this.keys.add(key); + if (!this.sourcesMap.includes(sourcemap)) { + this.sourcesMap.push(sourcemap); + this.sources.push(sourceFileName || null); + this.sourcesContent.push((sourceFileName != null ? null : sourceContent) || null); + } + const line = newLine - 1; + let record; + if (line > this.line) { + this.line = line; + } + if (!this.map.has(line)) { + record = [Math.max(0, newColumn - 1), this.sourcesMap.indexOf(sourcemap), ln - 1, col - 1]; + this.map.set(line, [record]); + } + else { + const arr = this.map.get(line); + record = [ + Math.max(0, newColumn - 1) - arr[0][0], + this.sourcesMap.indexOf(sourcemap) - arr[0][1], + ln - 1, + col - 1, + ]; + arr.push(record); + } + if (this.lastLocation != null) { + record[2] -= this.lastLocation.ln - 1; + record[3] -= this.lastLocation.col - 1; + } + this.lastLocation ??= { ln, col }; + this.lastLocation.ln = ln; + this.lastLocation.col = col; + } + } + /** + * compute original positions + */ + computePositions() { + this.reverseMap.clear(); + let sourceFileIndex = 0; // second field + let sourceCodeLine = 0; // third field + let sourceCodeColumn = 0; // fourth field + let nameIndex = 0; // fifth field + let generatedCodeColumn; + let result; + // mappings to original source + for (let [i, line] of this.map.entries()) { + if (line.length === 0 || (line.length === 1 && line[0].length === 0)) { + continue; + } + generatedCodeColumn = line[0][0]; // first field - reset each time + line = line + .map((segment, index, array) => { + if (segment.length === 0) { + return []; + } + generatedCodeColumn = index == 0 ? segment[0] : segment[0] + array[0][0]; + result = [generatedCodeColumn]; + if (segment.length <= 1) { + return result; + } + sourceFileIndex = index == 0 ? segment[1] : segment[1] + array[0][1]; + sourceCodeLine += segment[2]; + sourceCodeColumn += segment[3]; + result.push(sourceFileIndex, sourceCodeLine, sourceCodeColumn); + if (segment.length === 5) { + nameIndex += segment[4]; + result.push(nameIndex); + } + return result; + }) + .sort((a, b) => { + if (a[1] !== b[1]) { + return a[1] - b[1]; + } + return a[0] - b[0]; + }); + if (line.length == 0 || (line.length == 1 && line[0].length == 0)) { + continue; + } + this.reverseMap.set(i, line); + } + } + /** + * retrieve original sources, lines and columns + * @param line generated line + * @param column generated column + */ + find(line, column) { + if (!this.reverseMap.has(--line)) { + return null; + } + column--; + const result = []; + for (const record of this.reverseMap.get(line)) { + if (record.length == 0 || record[0] < column) { + continue; + } + if (record[0] > column) { + break; + } + result.push([ + this.sources?.[record[1]] ?? null, + record[2] + 1, + record[3] + 1, + this.sourcesContent?.[record[1]] ?? null, + ]); + } + return result.length == 0 ? null : result; + } + /** + * Convert to URL encoded string + */ + toUrl() { + // /*# sourceMappingURL = ${url} */ + return `data:application/json;charset=utf-8;base64,${btoa(JSON.stringify(this.toJSON()))}`; + } + /** + * Convert to JSON object + */ + toJSON() { + const mappings = []; + let i = 0; + for (; i <= this.line; i++) { + if (!this.map.has(i)) { + mappings.push(""); + } + else { + mappings.push(this.map.get(i).reduce((acc, curr) => acc + (acc === "" ? "" : ",") + encode(curr), "")); + } + } + return { + version: this.version, + sources: this.sources.slice(), + sourcesContent: this.sourcesContent?.slice(), + mappings: mappings.join(";"), + }; + } + /** + * to string + */ + toString() { + return JSON.stringify(this); + } + } + /** * Compute line and column of the offset */ @@ -21401,7 +21710,7 @@ * Constructor * @param lines */ - constructor(lines) { + constructor(lines = []) { if (lines.length === 0) { lines.push(0); } @@ -21419,7 +21728,7 @@ } const column = offset - this.lineStarts[line]; // [line, column] - return [line + 1, column === 0 ? 1 : column]; + return [line + 1, line === 0 ? column + 1 : column]; } /** * search the greatest index of the value less than or equal to offset @@ -21474,6 +21783,7 @@ * Source file helper class */ class SourceFile { + inputSourceMap = null; /** * Source file ID */ @@ -21492,7 +21802,6 @@ content; /** * Constructor - * @param id * @param content * @param lines * @param file @@ -21506,7 +21815,6 @@ /** * Update source content * @param content - * @param lines */ append(content) { this.content += content; @@ -21564,6 +21872,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 = { @@ -21940,7 +22262,7 @@ return char; } /** - * Tokenize css string + * Tokenize CSS string * @param parseInfo * @param yieldEOFToken */ @@ -21967,8 +22289,6 @@ parseInfo.buffer = ""; while ((value = peek(parseInfo))) { charCode = value.charCodeAt(0); - // nextCharCode = nextValue.charCodeAt(0); - // console.debug({value, buffer}); switch (charCode) { case 61 /* TokenMap.EQUALS */: if (buffer.length > 0) { @@ -22333,10 +22653,6 @@ break; } buffer += value + next(parseInfo); - // buffer += - // (parseInfo.offset == parseInfo.currentPosition - // ? parseInfo.buffer.slice(-1) - // : parseInfo.stream.charAt(parseInfo.currentPosition - parseInfo.offset - 1)) + value; break; case 39 /* TokenMap.SINGLE_QUOTE */: case 34 /* TokenMap.DOUBLE_QUOTE */: @@ -22423,13 +22739,16 @@ * @param errors * @param nestingContent * + * @param context * @private */ - function minify(ast, options = {}, recursive = false, errors, nestingContent, context = {}) { + function minify(ast, opt = {}, recursive = false, errors, nestingContent, context = {}) { let preprocess = false; let postprocess = false; let parents; let replacement; + // @ts-ignore + let { sourcemap, module, ...options } = opt; if (!("features" in options)) { // @ts-ignore options = { @@ -22626,9 +22945,9 @@ * Minify at-rule media * - remove redundant tokens * - generate range queries - * @param ast * * @private + * @param tokens */ function minifyAtRuleMedia(tokens) { let hasUpdates = false; @@ -22726,7 +23045,6 @@ } while (previous?.typ === exports.EnumToken.CommentNodeType) { previous = ast.chi[--nodeIndex]; - continue; } node = ast.chi[i]; if (node.typ === exports.EnumToken.AtRuleNodeType && node.nam === "font-face") { @@ -23197,7 +23515,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] == " ") { @@ -23579,7 +23899,6 @@ * Diff nodes * @param n1 * @param n2 - * @param reducer * @param options * * @private @@ -23698,17 +24017,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) { @@ -23843,7 +24181,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("")); @@ -24035,145 +24376,9 @@ .reduce((acc, curr) => acc + (curr == "&" ? replace : curr), ""); } - // from https://github.com/Rich-Harris/vlq/tree/master - // credit: Rich Harris - const integer_to_char = {}; - let i = 0; - for (const char of 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=') { - integer_to_char[i++] = char; - } - function encode(value) { - if (typeof value === 'number') { - return encode_integer(value); - } - let result = ''; - for (let i = 0; i < value.length; i += 1) { - result += encode_integer(value[i]); - } - return result; - } - function encode_integer(num) { - let result = ''; - if (num < 0) { - num = (-num << 1) | 1; - } - else { - num <<= 1; - } - do { - let clamped = num & 31; - num >>>= 5; - if (num > 0) { - clamped |= 32; - } - result += integer_to_char[clamped]; - } while (num > 0); - return result; - } - /** - * Source map class - * @internal + * match url */ - class SourceMap { - /** - * Last location - */ - lastLocation = null; - /** - * Version - * @private - */ - version = 3; - /** - * Sources map - * @private - */ - sourcesMap = []; - /** - * Sources - * @private - */ - sources = []; - /** - * Map - * @private - */ - map = new Map(); - /** - * Line - * @private - */ - line = -1; - /** - * Add a location - * @param source - * @param original - */ - add(newLine, newColumn, srcId, ln, col, sourceFileName, sourceContent) { - if (!this.sourcesMap.includes(srcId)) { - if (sourceFileName == null && sourceContent != null) { - sourceFileName = "data:text/css;charset=utf-8;base64," + btoa(sourceContent); - } - this.sourcesMap.push(srcId); - this.sources.push(sourceFileName || null); - } - const line = newLine - 1; - let record; - if (line > this.line) { - this.line = line; - } - if (!this.map.has(line)) { - record = [Math.max(0, newColumn - 1), this.sourcesMap.indexOf(srcId), ln - 1, col - 1]; - this.map.set(line, [record]); - } - else { - const arr = this.map.get(line); - record = [ - Math.max(0, newColumn - 1 - arr[0][0]), - this.sourcesMap.indexOf(srcId) - arr[0][1], - ln - 1, - col - 1, - ]; - arr.push(record); - } - if (this.lastLocation != null) { - record[2] -= this.lastLocation.ln - 1; - record[3] -= this.lastLocation.col - 1; - } - this.lastLocation ??= { ln, col }; - this.lastLocation.ln = ln; - this.lastLocation.col = col; - } - /** - * Convert to URL encoded string - */ - toUrl() { - // /*# sourceMappingURL = ${url} */ - return `data:application/json;charset=utf-8;base64,${btoa(JSON.stringify(this.toJSON()))}`; - } - /** - * Convert to JSON object - */ - toJSON() { - const mappings = []; - let i = 0; - for (; i <= this.line; i++) { - if (!this.map.has(i)) { - mappings.push(""); - } - else { - mappings.push(this.map.get(i).reduce((acc, curr) => acc + (acc === "" ? "" : ",") + encode(curr), "")); - } - } - return { - version: this.version, - sources: this.sources.slice(), - mappings: mappings.join(";"), - }; - } - } - const matchUrl = /^(https?:)?\/\//; /** * return the directory name of a path @@ -24185,6 +24390,9 @@ if (path === "") { return ""; } + if (path.startsWith("data:")) { + return path; + } let i = 0; let parts = [""]; for (; i < path.length; i++) { @@ -24208,10 +24416,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); @@ -24220,7 +24425,7 @@ } // else if (chr == "?" || chr == "#") { // break; - // } + // } else { parts[parts.length - 1] += chr; } @@ -24238,6 +24443,8 @@ } /** * Nomalize path + * @param path + * @private */ const normalize = memoize(function (path) { let parts = []; @@ -24266,14 +24473,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); @@ -24305,31 +24518,52 @@ cwd ??= ""; currentDirectory ??= ""; url = normalize(url); + if (cwd !== "") { + cwd = normalize(cwd); + } if (currentDirectory !== "") { currentDirectory = normalize(currentDirectory); - if (url.startsWith(currentDirectory + "/")) { - return { - absolute: url, - relative: url.slice(currentDirectory.length + 1), - }; - } - } - if ((currentDirectory === "" || currentDirectory === ".") && cwd !== "") { - cwd = normalize(cwd); - if (url.startsWith(cwd == "/" ? cwd : cwd + "/")) { - const absolute = url; - const prefix = cwd == "/" ? cwd : cwd + "/"; - return { - absolute, - relative: absolute.startsWith(prefix) ? absolute.slice(prefix.length) : diff(absolute, cwd), - }; - } } + const dir = cwd || currentDirectory; + const absolute = dir == "" || url.startsWith("/") ? resolvePath(url) : resolvePath(dir, url); return { - absolute: url, - relative: url === "" ? "" : diff(url, cwd || currentDirectory), + absolute, + relative: dir === "" ? absolute : diff(absolute, dir), }; }); + /** + * + * @param parts + * @returns + * @private + */ + function resolvePath(...parts) { + const path = parts.filter(Boolean).join("/"); + const isAbsolute = /^[\\/]/.test(path); + const segments = path.split(/[\\/]+/); + const resolved = []; + for (const segment of segments) { + if (!segment || segment === ".") { + continue; + } + if (segment === "..") { + if (resolved.length && resolved[resolved.length - 1] !== "..") { + resolved.pop(); + } + else if (!isAbsolute) { + resolved.push(".."); + } + } + else { + resolved.push(segment); + } + } + let result = resolved.join("/"); + if (isAbsolute) { + result = "/" + result; + } + return result || (isAbsolute ? "/" : "."); + } /** * render ast @@ -24382,22 +24616,28 @@ const startTime = performance.now(); const errors = []; const sourcemap = options.sourcemap ? new SourceMap() : null; + const sourcemaps = options.sourcemap ? [] : null; const cache = Object.create(null); const sourceLocation = { - srcId: 0, - sta: 0, end: 0, }; - const linesMap = new LineMap([]); + const linesMap = options.sourcemap ? new LineMap() : null; let code = ""; if (mapping != null) { if (mapping.importMapping != null) { - for (const [key, value] of Object.entries(mapping.importMapping)) { + const absolutePath = options.resolve(options.output != null ? dirname(options.output) : dirname(options.src), options.cwd).absolute; + for (let [key, value] of Object.entries(mapping.importMapping)) { + key = options.resolve(options.resolve(key, options.cwd).absolute, absolutePath).relative; + if (!key.startsWith("/") && !key.startsWith(".")) { + key = "./" + key; + } code += `:import("${key}")${options.indent}{${options.newLine}${Object.entries(value).reduce((acc, [k, v]) => acc + (acc.length > 0 ? options.newLine : "") + `${options.indent}${v}:${options.indent}${k};`, "")}${options.newLine}}${options.newLine}`; } } code += `:export${options.indent}{${options.newLine}${Object.entries(mapping.mapping).reduce((acc, [k, v]) => acc + (acc.length > 0 ? options.newLine : "") + `${options.indent}${k}:${options.indent}${v};`, "")}${options.newLine}}${options.newLine}`; - move(sourceLocation, linesMap, code); + if (sourcemap != null) { + move(sourceLocation, linesMap, code); + } } if (options.output != null) { // @ts-ignore @@ -24409,7 +24649,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; @@ -24424,6 +24664,7 @@ }, }; if (sourcemap != null) { + sourcemap.addAll(sourcemaps); result.map = sourcemap; if (options.sourcemap === "inline") { result.code += `\n/*# sourceMappingURL=${result.map.toUrl()} */`; @@ -24436,37 +24677,88 @@ * @param node * @param options * @param cache - * @param sourcemap - * @param position + * @param sourcemaps + * @param sourceLocation + * @param linesMap * @param str * * @internal */ - function updateSourceMap(node, options, cache, sourcemap, sourceLocation, linesMap, str) { - if ([ - exports.EnumToken.RuleNodeType, - exports.EnumToken.AtRuleNodeType, - exports.EnumToken.KeyFramesRuleNodeType, - exports.EnumToken.KeyframesAtRuleNodeType, - ].includes(node.typ)) { - let srcId = node[LOC]?.srcId ?? 0; - let sourceFileName = options.sourcesMap?.get(srcId)?.getFileName?.() || null; - if (sourceFileName != null && options.output != null) { - if (cache[sourceFileName] == null) { - cache[sourceFileName] = options.resolve(sourceFileName, dirname(options.output)).relative; + function updateSourceMap(node, options, cache, sourcemaps, sourceLocation, linesMap, str) { + let offset = 0; + while (true) { + if (str.charAt(offset) == options.newLine) { + offset += options.newLine.length; + continue; + } + if (str.charAt(offset) == options.indent) { + offset += options.indent.length; + continue; + } + break; + } + if (offset > 0) { + move(sourceLocation, linesMap, str.slice(0, offset)); + } + if (node[LOC] != null && + [ + exports.EnumToken.RuleNodeType, + exports.EnumToken.AtRuleNodeType, + exports.EnumToken.KeyFramesRuleNodeType, + exports.EnumToken.KeyframesAtRuleNodeType, + ].includes(node.typ)) { + const source = options.sourcesMap.get(node[LOC].srcId); + const inputSourceMap = source.getInputSourceMap(); + const offsets = source.getOffsets(node[LOC].sta); + const [newLine, newColumn] = linesMap.getOffsets(sourceLocation.end); + let records = null; + let srcId = node[LOC].srcId; + let sourceFileName = source.getFileName() || null; + let sourceContent = source.getContent() || null; + if (inputSourceMap != null && (records = inputSourceMap.find(offsets[0], offsets[1])) != null) { + for (const record of records) { + // @ts-ignore + sourceFileName = record[0] || null; + // @ts-ignore + offsets[0] = record[1]; + // @ts-ignore + offsets[1] = record[2]; + sourceContent = record[3] || null; + if (sourceFileName != null && options.output != null && !sourceFileName.startsWith("data:")) { + if (cache[sourceFileName] == null) { + const absolute = options.resolve(dirname(options.output), options.cwd) + .absolute; + const absoluteSourcePath = options.resolve(dirname(options.src || ""), options.cwd).absolute; + // resolution is relative to the source file + const absoluteSourceFileName = options.resolve(sourceFileName, absoluteSourcePath) + .absolute; + cache[sourceFileName] = options.resolve(absoluteSourceFileName, absolute).relative; + } + sourceFileName = cache[sourceFileName]; + } + sourcemaps.push([newLine, newColumn, srcId, ...offsets, sourceFileName, sourceContent]); } - sourceFileName = cache[sourceFileName]; } - // @ts-ignore - sourcemap.add(...linesMap.getOffsets(sourceLocation.end), srcId, - // @ts-ignore - ...options.sourcesMap?.get(srcId)?.getOffsets(sourceLocation.sta), sourceFileName, options.sourcesMap?.get(srcId)?.getContent?.()); + else { + if (sourceFileName != null && options.output != null && !sourceFileName.startsWith("data:")) { + if (cache[sourceFileName] == null) { + const absolute = options.resolve(dirname(options.output), options.cwd) + .absolute; + const absoluteSourceFileName = options.resolve(sourceFileName, options.cwd) + .absolute; + cache[sourceFileName] = options.resolve(absoluteSourceFileName, absolute).relative; + } + sourceFileName = cache[sourceFileName]; + } + sourcemaps.push([newLine, newColumn, srcId, ...offsets, sourceFileName, sourceContent]); + } } - move(sourceLocation, linesMap, str); + move(sourceLocation, linesMap, offset > 0 ? str.slice(offset) : str); } /** * Update position - * @param position + * @param sourceLocation + * @param linesMap * @param str */ function move(sourceLocation, linesMap, str) { @@ -24496,8 +24788,9 @@ * render ast node * @param data * @param options - * @param sourcemap - * @param position + * @param sourcemaps + * @param sourceLocation + * @param linesMap * @param errors * @param reducer * @param cache @@ -24506,13 +24799,17 @@ * * @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 = ""; + let previousStr = ""; const indent = indents[level]; const indentSub = indents[level + 1]; switch (data.typ) { @@ -24530,20 +24827,17 @@ ? 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; - } - if (sourcemap != null && node[LOC] != null) { - updateSourceMap(node, options, cache, sourcemap, sourceLocation, linesMap, (hasPreviousContent ? options.newLine : "") + str); + continue; } - if (!hasPreviousContent) { - return str; + if (children.length > 0) { + str = options.newLine + str; } - return `${css}${options.newLine}${str}`; - }, ""); + children += str; + } + return children; case exports.EnumToken.AtRuleNodeType: case exports.EnumToken.RuleNodeType: case exports.EnumToken.KeyFramesRuleNodeType: @@ -24551,9 +24845,15 @@ if ([exports.EnumToken.AtRuleNodeType, exports.EnumToken.KeyframesAtRuleNodeType].includes(data.typ) && !("chi" in data)) { return `${indent}@${data.nam}${data.val === "" ? "" : options.indent || " "}${data.val};`; } - // @ts-ignore - let children = data.chi.reduce((css, node) => { - let str; + const prelude = [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; + for (let i = 0; i < data.chi.length; i++) { + node = data.chi[i]; if (node.typ == exports.EnumToken.CommentNodeType) { str = options.removeComments && @@ -24576,41 +24876,45 @@ : node.val) .reduce(reducer, "") .trimEnd()};`; + if (sourcemaps != null) { + if (previousStr.length > 0) { + move(sourceLocation, linesMap, previousStr); + } + } + previousStr = str === "" ? "" : options.newLine + indentSub + str; } // 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; + if (sourcemaps != null) { + if (previousStr.length > 0) { + move(sourceLocation, linesMap, previousStr); + } + } + str = renderAstNode(node, options, sourcemaps, sourceLocation, linesMap, errors, reducer, cache, level + 1, indents); + previousStr = ""; } if (str === "") { - return css; + continue; } - return `${css}${options.newLine}${indentSub}${str}`; - }, ""); - if (options.removeEmpty && children === "") { - return ""; + str = options.newLine + indentSub + str; + children += str; + } + if (sourcemaps != null && str !== "") { + move(sourceLocation, linesMap, str.endsWith(";") ? str.slice(0, -1) : str); } if (children.endsWith(";")) { children = children.slice(0, -1); } - 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; + if (options.removeEmpty && children === "") { + return ""; + } + const end = options.newLine + indent + `}`; + if (sourcemaps != null) { + move(sourceLocation, linesMap, end); + } + return prelude + children + end; // case EnumToken.CssVariableTokenType: // case EnumToken.CssVariableImportTokenType: // return `@value ${(data).val}:${options.indent}${filterValues( @@ -24637,6 +24941,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) { @@ -28674,7 +28981,7 @@ if (replacement == null) { continue; } - if (replacement == null || replacement == node) { + if (replacement == node) { continue; } // @ts-ignore @@ -28720,7 +29027,7 @@ if (replacement == null) { continue; } - if (replacement == null || replacement == node) { + if (replacement == node) { continue; } // @ts-ignore @@ -28754,7 +29061,7 @@ if (replacement == null) { continue; } - if (replacement != null && replacement != node) { + if (replacement != node) { node = replacement; } } @@ -28787,7 +29094,7 @@ if (result == null) { continue; } - if (result != null && result != node) { + if (result != node) { node = result; } if (Array.isArray(node)) { @@ -28972,10 +29279,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 || @@ -29017,10 +29323,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 || @@ -29191,10 +29496,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]; @@ -29266,10 +29571,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 @@ -29302,10 +29606,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 || @@ -29655,7 +29958,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 @@ -30129,6 +30432,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) { @@ -30198,8 +30502,10 @@ setParent: false, src: src.relative, })); - const srcIndex = (src.relative.startsWith("/") || src.relative.startsWith("../") ? "" : "./") + - src.relative; + let srcIndex = options.resolve(src.absolute, resolvedSrc.absolute).relative; + if (!srcIndex.startsWith("/") && !srcIndex.startsWith("../")) { + srcIndex = `./${srcIndex}`; + } if (Object.keys(root.mapping).length > 0) { importMapping[srcIndex] = {}; } @@ -30451,26 +30757,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; } @@ -30484,12 +30770,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; } } })) { @@ -30775,6 +31055,8 @@ return null; } /** + * @param stream + * @param context * @param options * @param errors * @param parseAsBlock @@ -30851,7 +31133,6 @@ parseAsBlock = blockAllowed; } if (syntax != null && atRule.nam !== "layer" && parseAsBlock !== blockAllowed) { - success = false; errors.push({ action: "drop", node: atRule, @@ -31339,7 +31620,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 { @@ -31478,9 +31759,6 @@ if (result.errors.length > 0) { errors.push(...result.errors); } - // else if (atRuleName === "document") { - // parseUrlToken(stream); - // } if (result.success) { let i = 0; const stack = []; @@ -31824,6 +32102,52 @@ ResponseType[ResponseType["ArrayBuffer"] = 2] = "ArrayBuffer"; })(exports.ResponseType || (exports.ResponseType = {})); + /** + * parse result. process input sourcemap + * @param result + * @param options + * @returns + * @private + */ + function parseResult(result, options) { + if (options.sourcemap != null && options.source.getInputSourceMap() == null) { + if (options.inputSourceMap != null) { + options.source.setInputSourceMap(options.inputSourceMap); + } + else { + // extract inline source map from the input CSS + const token = result.ast.chi.at(-1); + if (token?.typ == exports.EnumToken.CommentTokenType && + token.val.startsWith("/*# sourceMappingURL=")) { + const data = token.val.slice(21, -2).trim(); + let sourcemap; + let encoding = ""; + if (data.startsWith("data:")) { + let offset = data.indexOf(",") + 1; + if (offset == 0) { + offset = data.lastIndexOf(";") + 1; + } + else { + encoding = data.slice(data.lastIndexOf(";") + 1, offset - 1); + } + if (encoding == "base64") { + sourcemap = atob(data.slice(offset)); + } + else { + sourcemap = decodeURIComponent(data.slice(offset)); + } + options.source.setInputSourceMap(sourcemap); + } + } + } + } + if (options.module) { + const { revMapping, ...res } = result; + return res; + } + return result; + } + /** * Load file or url * @param url @@ -31960,7 +32284,7 @@ } options ??= {}; options.src ??= ""; - options.sourcesMap ??= new Map; + options.sourcesMap ??= new Map(); Object.assign(options, { resolve, dirname, @@ -31985,13 +32309,10 @@ currentPosition: -1, }; const result = doParseSync(tokenize(options.parseInfo), options); - const { revMapping, ...res } = result; - return res; + return !options.module && !options.inputSourceMap ? result : parseResult(result, options); } /** * Transform css - * @param css - * @param options * * ```ts * @@ -32002,6 +32323,7 @@ * console.log(result.code); * ``` * + * @param args */ function transformSync(...args) { let options; @@ -32051,8 +32373,6 @@ } /** * Parse css - * @param stream - * @param options * * Example: * @@ -32076,6 +32396,7 @@ * * console.log(result.ast); * ``` + * @param args */ async function parse(...args) { let options; @@ -32097,7 +32418,7 @@ } options ??= {}; options.src ??= ""; - options.sourcesMap ??= new Map; + options.sourcesMap ??= new Map(); Object.assign(options, { load, resolve, @@ -32121,10 +32442,7 @@ position: 0, currentPosition: -1, }; - return doParse(stream instanceof ReadableStream ? tokenizeStream(stream, options.parseInfo) : tokenize(options.parseInfo), options).then((result) => { - const { revMapping, ...res } = result; - return res; - }); + return doParse(stream instanceof ReadableStream ? tokenizeStream(stream, options.parseInfo) : tokenize(options.parseInfo), options).then((result) => (!options.module && !options.inputSourceMap ? result : parseResult(result, options))); } /** * Transform css file @@ -32158,8 +32476,6 @@ } /** * Transform css - * @param css - * @param options * * Example: * @@ -32177,6 +32493,7 @@ * * console.log(result.code); * ``` + * @param args */ async function transform(...args) { let options; diff --git a/dist/index.cjs b/dist/index.cjs index 2f51323c..fb531363 100644 --- a/dist/index.cjs +++ b/dist/index.cjs @@ -12669,7 +12669,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 { @@ -21053,7 +21053,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' @@ -21392,6 +21392,315 @@ 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; +} +/** + * @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; +} + +/** + * 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") { + sourcemaps = JSON.parse(sourcemaps); + } + if (sourcemaps != null) { + this.sources = sourcemaps.sources?.slice() ?? []; + this.sourcesContent = sourcemaps.sourcesContent?.slice() ?? []; + const decodedMappings = sourcemaps.mappings + .split(";") + .map((mapping) => mapping.split(",").map((mapping) => decode(mapping))); + this.line = decodedMappings.length - 1; + for (let index = 0; index < decodedMappings.length; index++) { + if (decodedMappings[index].length == 0 || + (decodedMappings[index].length == 1 && decodedMappings[index][0].length == 0)) { + continue; + } + this.map.set(index, decodedMappings[index]); + } + this.computePositions(); + } + } + /** + * Add all location + * @param maps + */ + addAll(maps) { + for (let [newLine, newColumn, srcId, ln, col, sourceFileName, sourceContent] of maps) { + const key = `${srcId}:${ln}:${sourceFileName}:${col}:${newLine}:${newColumn}:${sourceContent}`; + const sourcemap = `${srcId}:${sourceFileName}:${sourceContent}`; + if (this.keys.has(key)) { + continue; + } + this.keys.add(key); + if (!this.sourcesMap.includes(sourcemap)) { + this.sourcesMap.push(sourcemap); + this.sources.push(sourceFileName || null); + this.sourcesContent.push((sourceFileName != null ? null : sourceContent) || null); + } + const line = newLine - 1; + let record; + if (line > this.line) { + this.line = line; + } + if (!this.map.has(line)) { + record = [Math.max(0, newColumn - 1), this.sourcesMap.indexOf(sourcemap), ln - 1, col - 1]; + this.map.set(line, [record]); + } + else { + const arr = this.map.get(line); + record = [ + Math.max(0, newColumn - 1) - arr[0][0], + this.sourcesMap.indexOf(sourcemap) - arr[0][1], + ln - 1, + col - 1, + ]; + arr.push(record); + } + if (this.lastLocation != null) { + record[2] -= this.lastLocation.ln - 1; + record[3] -= this.lastLocation.col - 1; + } + this.lastLocation ??= { ln, col }; + this.lastLocation.ln = ln; + this.lastLocation.col = col; + } + } + /** + * compute original positions + */ + computePositions() { + this.reverseMap.clear(); + let sourceFileIndex = 0; // second field + let sourceCodeLine = 0; // third field + let sourceCodeColumn = 0; // fourth field + let nameIndex = 0; // fifth field + let generatedCodeColumn; + let result; + // mappings to original source + for (let [i, line] of this.map.entries()) { + if (line.length === 0 || (line.length === 1 && line[0].length === 0)) { + continue; + } + generatedCodeColumn = line[0][0]; // first field - reset each time + line = line + .map((segment, index, array) => { + if (segment.length === 0) { + return []; + } + generatedCodeColumn = index == 0 ? segment[0] : segment[0] + array[0][0]; + result = [generatedCodeColumn]; + if (segment.length <= 1) { + return result; + } + sourceFileIndex = index == 0 ? segment[1] : segment[1] + array[0][1]; + sourceCodeLine += segment[2]; + sourceCodeColumn += segment[3]; + result.push(sourceFileIndex, sourceCodeLine, sourceCodeColumn); + if (segment.length === 5) { + nameIndex += segment[4]; + result.push(nameIndex); + } + return result; + }) + .sort((a, b) => { + if (a[1] !== b[1]) { + return a[1] - b[1]; + } + return a[0] - b[0]; + }); + if (line.length == 0 || (line.length == 1 && line[0].length == 0)) { + continue; + } + this.reverseMap.set(i, line); + } + } + /** + * retrieve original sources, lines and columns + * @param line generated line + * @param column generated column + */ + find(line, column) { + if (!this.reverseMap.has(--line)) { + return null; + } + column--; + const result = []; + for (const record of this.reverseMap.get(line)) { + if (record.length == 0 || record[0] < column) { + continue; + } + if (record[0] > column) { + break; + } + result.push([ + this.sources?.[record[1]] ?? null, + record[2] + 1, + record[3] + 1, + this.sourcesContent?.[record[1]] ?? null, + ]); + } + return result.length == 0 ? null : result; + } + /** + * Convert to URL encoded string + */ + toUrl() { + // /*# sourceMappingURL = ${url} */ + return `data:application/json;charset=utf-8;base64,${btoa(JSON.stringify(this.toJSON()))}`; + } + /** + * Convert to JSON object + */ + toJSON() { + const mappings = []; + let i = 0; + for (; i <= this.line; i++) { + if (!this.map.has(i)) { + mappings.push(""); + } + else { + mappings.push(this.map.get(i).reduce((acc, curr) => acc + (acc === "" ? "" : ",") + encode(curr), "")); + } + } + return { + version: this.version, + sources: this.sources.slice(), + sourcesContent: this.sourcesContent?.slice(), + mappings: mappings.join(";"), + }; + } + /** + * to string + */ + toString() { + return JSON.stringify(this); + } +} + /** * Compute line and column of the offset */ @@ -21404,7 +21713,7 @@ class LineMap { * Constructor * @param lines */ - constructor(lines) { + constructor(lines = []) { if (lines.length === 0) { lines.push(0); } @@ -21422,7 +21731,7 @@ class LineMap { } const column = offset - this.lineStarts[line]; // [line, column] - return [line + 1, column === 0 ? 1 : column]; + return [line + 1, line === 0 ? column + 1 : column]; } /** * search the greatest index of the value less than or equal to offset @@ -21477,6 +21786,7 @@ let sourceId = 0; * Source file helper class */ class SourceFile { + inputSourceMap = null; /** * Source file ID */ @@ -21495,7 +21805,6 @@ class SourceFile { content; /** * Constructor - * @param id * @param content * @param lines * @param file @@ -21509,7 +21818,6 @@ class SourceFile { /** * Update source content * @param content - * @param lines */ append(content) { this.content += content; @@ -21567,6 +21875,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 = { @@ -21943,7 +22265,7 @@ function next(parseInfo, count = 1) { return char; } /** - * Tokenize css string + * Tokenize CSS string * @param parseInfo * @param yieldEOFToken */ @@ -21970,8 +22292,6 @@ function tokenize(parseInfo, yieldEOFToken = true) { parseInfo.buffer = ""; while ((value = peek(parseInfo))) { charCode = value.charCodeAt(0); - // nextCharCode = nextValue.charCodeAt(0); - // console.debug({value, buffer}); switch (charCode) { case 61 /* TokenMap.EQUALS */: if (buffer.length > 0) { @@ -22336,10 +22656,6 @@ function tokenize(parseInfo, yieldEOFToken = true) { break; } buffer += value + next(parseInfo); - // buffer += - // (parseInfo.offset == parseInfo.currentPosition - // ? parseInfo.buffer.slice(-1) - // : parseInfo.stream.charAt(parseInfo.currentPosition - parseInfo.offset - 1)) + value; break; case 39 /* TokenMap.SINGLE_QUOTE */: case 34 /* TokenMap.DOUBLE_QUOTE */: @@ -22426,13 +22742,16 @@ 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 = {}) { +function minify(ast, opt = {}, recursive = false, errors, nestingContent, context = {}) { let preprocess = false; let postprocess = false; let parents; let replacement; + // @ts-ignore + let { sourcemap, module, ...options } = opt; if (!("features" in options)) { // @ts-ignore options = { @@ -22629,9 +22948,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; @@ -22729,7 +23048,6 @@ function doMinify(ast, options = {}, recursive = false, errors, nestingContent, } while (previous?.typ === exports.EnumToken.CommentNodeType) { previous = ast.chi[--nodeIndex]; - continue; } node = ast.chi[i]; if (node.typ === exports.EnumToken.AtRuleNodeType && node.nam === "font-face") { @@ -23200,7 +23518,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] == " ") { @@ -23582,7 +23902,6 @@ function wrapNodes(previous, node, match, ast, reducer, i, nodeIndex) { * Diff nodes * @param n1 * @param n2 - * @param reducer * @param options * * @private @@ -23701,17 +24020,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) { @@ -23846,7 +24184,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("")); @@ -24038,145 +24379,9 @@ function replaceCompoundLiteral(selector, replace) { .reduce((acc, curr) => acc + (curr == "&" ? replace : curr), ""); } -// from https://github.com/Rich-Harris/vlq/tree/master -// credit: Rich Harris -const integer_to_char = {}; -let i = 0; -for (const char of 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=') { - integer_to_char[i++] = char; -} -function encode(value) { - if (typeof value === 'number') { - return encode_integer(value); - } - let result = ''; - for (let i = 0; i < value.length; i += 1) { - result += encode_integer(value[i]); - } - return result; -} -function encode_integer(num) { - let result = ''; - if (num < 0) { - num = (-num << 1) | 1; - } - else { - num <<= 1; - } - do { - let clamped = num & 31; - num >>>= 5; - if (num > 0) { - clamped |= 32; - } - result += integer_to_char[clamped]; - } while (num > 0); - return result; -} - /** - * Source map class - * @internal + * match url */ -class SourceMap { - /** - * Last location - */ - lastLocation = null; - /** - * Version - * @private - */ - version = 3; - /** - * Sources map - * @private - */ - sourcesMap = []; - /** - * Sources - * @private - */ - sources = []; - /** - * Map - * @private - */ - map = new Map(); - /** - * Line - * @private - */ - line = -1; - /** - * Add a location - * @param source - * @param original - */ - add(newLine, newColumn, srcId, ln, col, sourceFileName, sourceContent) { - if (!this.sourcesMap.includes(srcId)) { - if (sourceFileName == null && sourceContent != null) { - sourceFileName = "data:text/css;charset=utf-8;base64," + btoa(sourceContent); - } - this.sourcesMap.push(srcId); - this.sources.push(sourceFileName || null); - } - const line = newLine - 1; - let record; - if (line > this.line) { - this.line = line; - } - if (!this.map.has(line)) { - record = [Math.max(0, newColumn - 1), this.sourcesMap.indexOf(srcId), ln - 1, col - 1]; - this.map.set(line, [record]); - } - else { - const arr = this.map.get(line); - record = [ - Math.max(0, newColumn - 1 - arr[0][0]), - this.sourcesMap.indexOf(srcId) - arr[0][1], - ln - 1, - col - 1, - ]; - arr.push(record); - } - if (this.lastLocation != null) { - record[2] -= this.lastLocation.ln - 1; - record[3] -= this.lastLocation.col - 1; - } - this.lastLocation ??= { ln, col }; - this.lastLocation.ln = ln; - this.lastLocation.col = col; - } - /** - * Convert to URL encoded string - */ - toUrl() { - // /*# sourceMappingURL = ${url} */ - return `data:application/json;charset=utf-8;base64,${btoa(JSON.stringify(this.toJSON()))}`; - } - /** - * Convert to JSON object - */ - toJSON() { - const mappings = []; - let i = 0; - for (; i <= this.line; i++) { - if (!this.map.has(i)) { - mappings.push(""); - } - else { - mappings.push(this.map.get(i).reduce((acc, curr) => acc + (acc === "" ? "" : ",") + encode(curr), "")); - } - } - return { - version: this.version, - sources: this.sources.slice(), - mappings: mappings.join(";"), - }; - } -} - const matchUrl = /^(https?:)?\/\//; /** * return the directory name of a path @@ -24188,6 +24393,9 @@ function dirname(path) { if (path === "") { return ""; } + if (path.startsWith("data:")) { + return path; + } let i = 0; let parts = [""]; for (; i < path.length; i++) { @@ -24211,10 +24419,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); @@ -24223,7 +24428,7 @@ function splitPath(result) { } // else if (chr == "?" || chr == "#") { // break; - // } + // } else { parts[parts.length - 1] += chr; } @@ -24241,6 +24446,8 @@ function splitPath(result) { } /** * Nomalize path + * @param path + * @private */ const normalize = memoize(function (path) { let parts = []; @@ -24269,14 +24476,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); @@ -24308,31 +24521,52 @@ const resolve = memoize(function (url, currentDirectory, cwd) { cwd ??= ""; currentDirectory ??= ""; url = normalize(url); + if (cwd !== "") { + cwd = normalize(cwd); + } if (currentDirectory !== "") { currentDirectory = normalize(currentDirectory); - if (url.startsWith(currentDirectory + "/")) { - return { - absolute: url, - relative: url.slice(currentDirectory.length + 1), - }; - } - } - if ((currentDirectory === "" || currentDirectory === ".") && cwd !== "") { - cwd = normalize(cwd); - if (url.startsWith(cwd == "/" ? cwd : cwd + "/")) { - const absolute = url; - const prefix = cwd == "/" ? cwd : cwd + "/"; - return { - absolute, - relative: absolute.startsWith(prefix) ? absolute.slice(prefix.length) : diff(absolute, cwd), - }; - } } + const dir = cwd || currentDirectory; + const absolute = dir == "" || url.startsWith("/") ? resolvePath(url) : resolvePath(dir, url); return { - absolute: url, - relative: url === "" ? "" : diff(url, cwd || currentDirectory), + absolute, + relative: dir === "" ? absolute : diff(absolute, dir), }; }); +/** + * + * @param parts + * @returns + * @private + */ +function resolvePath(...parts) { + const path = parts.filter(Boolean).join("/"); + const isAbsolute = /^[\\/]/.test(path); + const segments = path.split(/[\\/]+/); + const resolved = []; + for (const segment of segments) { + if (!segment || segment === ".") { + continue; + } + if (segment === "..") { + if (resolved.length && resolved[resolved.length - 1] !== "..") { + resolved.pop(); + } + else if (!isAbsolute) { + resolved.push(".."); + } + } + else { + resolved.push(segment); + } + } + let result = resolved.join("/"); + if (isAbsolute) { + result = "/" + result; + } + return result || (isAbsolute ? "/" : "."); +} /** * render ast @@ -24385,22 +24619,28 @@ function doRender(data, options = {}, mapping) { const startTime = performance.now(); const errors = []; const sourcemap = options.sourcemap ? new SourceMap() : null; + const sourcemaps = options.sourcemap ? [] : null; const cache = Object.create(null); const sourceLocation = { - srcId: 0, - sta: 0, end: 0, }; - const linesMap = new LineMap([]); + const linesMap = options.sourcemap ? new LineMap() : null; let code = ""; if (mapping != null) { if (mapping.importMapping != null) { - for (const [key, value] of Object.entries(mapping.importMapping)) { + const absolutePath = options.resolve(options.output != null ? dirname(options.output) : dirname(options.src), options.cwd).absolute; + for (let [key, value] of Object.entries(mapping.importMapping)) { + key = options.resolve(options.resolve(key, options.cwd).absolute, absolutePath).relative; + if (!key.startsWith("/") && !key.startsWith(".")) { + key = "./" + key; + } code += `:import("${key}")${options.indent}{${options.newLine}${Object.entries(value).reduce((acc, [k, v]) => acc + (acc.length > 0 ? options.newLine : "") + `${options.indent}${v}:${options.indent}${k};`, "")}${options.newLine}}${options.newLine}`; } } code += `:export${options.indent}{${options.newLine}${Object.entries(mapping.mapping).reduce((acc, [k, v]) => acc + (acc.length > 0 ? options.newLine : "") + `${options.indent}${k}:${options.indent}${v};`, "")}${options.newLine}}${options.newLine}`; - move(sourceLocation, linesMap, code); + if (sourcemap != null) { + move(sourceLocation, linesMap, code); + } } if (options.output != null) { // @ts-ignore @@ -24412,7 +24652,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; @@ -24427,6 +24667,7 @@ function doRender(data, options = {}, mapping) { }, }; if (sourcemap != null) { + sourcemap.addAll(sourcemaps); result.map = sourcemap; if (options.sourcemap === "inline") { result.code += `\n/*# sourceMappingURL=${result.map.toUrl()} */`; @@ -24439,37 +24680,88 @@ function doRender(data, options = {}, mapping) { * @param node * @param options * @param cache - * @param sourcemap - * @param position + * @param sourcemaps + * @param sourceLocation + * @param linesMap * @param str * * @internal */ -function updateSourceMap(node, options, cache, sourcemap, sourceLocation, linesMap, str) { - if ([ - exports.EnumToken.RuleNodeType, - exports.EnumToken.AtRuleNodeType, - exports.EnumToken.KeyFramesRuleNodeType, - exports.EnumToken.KeyframesAtRuleNodeType, - ].includes(node.typ)) { - let srcId = node[LOC]?.srcId ?? 0; - let sourceFileName = options.sourcesMap?.get(srcId)?.getFileName?.() || null; - if (sourceFileName != null && options.output != null) { - if (cache[sourceFileName] == null) { - cache[sourceFileName] = options.resolve(sourceFileName, dirname(options.output)).relative; +function updateSourceMap(node, options, cache, sourcemaps, sourceLocation, linesMap, str) { + let offset = 0; + while (true) { + if (str.charAt(offset) == options.newLine) { + offset += options.newLine.length; + continue; + } + if (str.charAt(offset) == options.indent) { + offset += options.indent.length; + continue; + } + break; + } + if (offset > 0) { + move(sourceLocation, linesMap, str.slice(0, offset)); + } + if (node[LOC] != null && + [ + exports.EnumToken.RuleNodeType, + exports.EnumToken.AtRuleNodeType, + exports.EnumToken.KeyFramesRuleNodeType, + exports.EnumToken.KeyframesAtRuleNodeType, + ].includes(node.typ)) { + const source = options.sourcesMap.get(node[LOC].srcId); + const inputSourceMap = source.getInputSourceMap(); + const offsets = source.getOffsets(node[LOC].sta); + const [newLine, newColumn] = linesMap.getOffsets(sourceLocation.end); + let records = null; + let srcId = node[LOC].srcId; + let sourceFileName = source.getFileName() || null; + let sourceContent = source.getContent() || null; + if (inputSourceMap != null && (records = inputSourceMap.find(offsets[0], offsets[1])) != null) { + for (const record of records) { + // @ts-ignore + sourceFileName = record[0] || null; + // @ts-ignore + offsets[0] = record[1]; + // @ts-ignore + offsets[1] = record[2]; + sourceContent = record[3] || null; + if (sourceFileName != null && options.output != null && !sourceFileName.startsWith("data:")) { + if (cache[sourceFileName] == null) { + const absolute = options.resolve(dirname(options.output), options.cwd) + .absolute; + const absoluteSourcePath = options.resolve(dirname(options.src || ""), options.cwd).absolute; + // resolution is relative to the source file + const absoluteSourceFileName = options.resolve(sourceFileName, absoluteSourcePath) + .absolute; + cache[sourceFileName] = options.resolve(absoluteSourceFileName, absolute).relative; + } + sourceFileName = cache[sourceFileName]; + } + sourcemaps.push([newLine, newColumn, srcId, ...offsets, sourceFileName, sourceContent]); } - sourceFileName = cache[sourceFileName]; } - // @ts-ignore - sourcemap.add(...linesMap.getOffsets(sourceLocation.end), srcId, - // @ts-ignore - ...options.sourcesMap?.get(srcId)?.getOffsets(sourceLocation.sta), sourceFileName, options.sourcesMap?.get(srcId)?.getContent?.()); + else { + if (sourceFileName != null && options.output != null && !sourceFileName.startsWith("data:")) { + if (cache[sourceFileName] == null) { + const absolute = options.resolve(dirname(options.output), options.cwd) + .absolute; + const absoluteSourceFileName = options.resolve(sourceFileName, options.cwd) + .absolute; + cache[sourceFileName] = options.resolve(absoluteSourceFileName, absolute).relative; + } + sourceFileName = cache[sourceFileName]; + } + sourcemaps.push([newLine, newColumn, srcId, ...offsets, sourceFileName, sourceContent]); + } } - move(sourceLocation, linesMap, str); + move(sourceLocation, linesMap, offset > 0 ? str.slice(offset) : str); } /** * Update position - * @param position + * @param sourceLocation + * @param linesMap * @param str */ function move(sourceLocation, linesMap, str) { @@ -24499,8 +24791,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 @@ -24509,13 +24802,17 @@ 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 = ""; + let previousStr = ""; const indent = indents[level]; const indentSub = indents[level + 1]; switch (data.typ) { @@ -24533,20 +24830,17 @@ function renderAstNode(data, options, sourcemap, sourceLocation, linesMap, error ? 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; - } - if (sourcemap != null && node[LOC] != null) { - updateSourceMap(node, options, cache, sourcemap, sourceLocation, linesMap, (hasPreviousContent ? options.newLine : "") + str); + continue; } - if (!hasPreviousContent) { - return str; + if (children.length > 0) { + str = options.newLine + str; } - return `${css}${options.newLine}${str}`; - }, ""); + children += str; + } + return children; case exports.EnumToken.AtRuleNodeType: case exports.EnumToken.RuleNodeType: case exports.EnumToken.KeyFramesRuleNodeType: @@ -24554,9 +24848,15 @@ function renderAstNode(data, options, sourcemap, sourceLocation, linesMap, error if ([exports.EnumToken.AtRuleNodeType, exports.EnumToken.KeyframesAtRuleNodeType].includes(data.typ) && !("chi" in data)) { return `${indent}@${data.nam}${data.val === "" ? "" : options.indent || " "}${data.val};`; } - // @ts-ignore - let children = data.chi.reduce((css, node) => { - let str; + const prelude = [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; + for (let i = 0; i < data.chi.length; i++) { + node = data.chi[i]; if (node.typ == exports.EnumToken.CommentNodeType) { str = options.removeComments && @@ -24579,41 +24879,45 @@ function renderAstNode(data, options, sourcemap, sourceLocation, linesMap, error : node.val) .reduce(reducer, "") .trimEnd()};`; + if (sourcemaps != null) { + if (previousStr.length > 0) { + move(sourceLocation, linesMap, previousStr); + } + } + previousStr = str === "" ? "" : options.newLine + indentSub + str; } // 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; + if (sourcemaps != null) { + if (previousStr.length > 0) { + move(sourceLocation, linesMap, previousStr); + } + } + str = renderAstNode(node, options, sourcemaps, sourceLocation, linesMap, errors, reducer, cache, level + 1, indents); + previousStr = ""; } if (str === "") { - return css; + continue; } - return `${css}${options.newLine}${indentSub}${str}`; - }, ""); - if (options.removeEmpty && children === "") { - return ""; + str = options.newLine + indentSub + str; + children += str; + } + if (sourcemaps != null && str !== "") { + move(sourceLocation, linesMap, str.endsWith(";") ? str.slice(0, -1) : str); } if (children.endsWith(";")) { children = children.slice(0, -1); } - 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; + if (options.removeEmpty && children === "") { + return ""; + } + const end = options.newLine + indent + `}`; + if (sourcemaps != null) { + move(sourceLocation, linesMap, end); + } + return prelude + children + end; // case EnumToken.CssVariableTokenType: // case EnumToken.CssVariableImportTokenType: // return `@value ${(data).val}:${options.indent}${filterValues( @@ -24640,6 +24944,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) { @@ -28677,7 +28984,7 @@ function doParseSync(iter, options = {}) { if (replacement == null) { continue; } - if (replacement == null || replacement == node) { + if (replacement == node) { continue; } // @ts-ignore @@ -28723,7 +29030,7 @@ function doParseSync(iter, options = {}) { if (replacement == null) { continue; } - if (replacement == null || replacement == node) { + if (replacement == node) { continue; } // @ts-ignore @@ -28757,7 +29064,7 @@ function doParseSync(iter, options = {}) { if (replacement == null) { continue; } - if (replacement != null && replacement != node) { + if (replacement != node) { node = replacement; } } @@ -28790,7 +29097,7 @@ function doParseSync(iter, options = {}) { if (result == null) { continue; } - if (result != null && result != node) { + if (result != node) { node = result; } if (Array.isArray(node)) { @@ -28975,10 +29282,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 || @@ -29020,10 +29326,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 || @@ -29194,10 +29499,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]; @@ -29269,10 +29574,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 @@ -29305,10 +29609,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 || @@ -29658,7 +29961,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 @@ -30132,6 +30435,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) { @@ -30201,8 +30505,10 @@ async function doParse(iter, options = {}) { setParent: false, src: src.relative, })); - const srcIndex = (src.relative.startsWith("/") || src.relative.startsWith("../") ? "" : "./") + - src.relative; + let srcIndex = options.resolve(src.absolute, resolvedSrc.absolute).relative; + if (!srcIndex.startsWith("/") && !srcIndex.startsWith("../")) { + srcIndex = `./${srcIndex}`; + } if (Object.keys(root.mapping).length > 0) { importMapping[srcIndex] = {}; } @@ -30454,26 +30760,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; } @@ -30487,12 +30773,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; } } })) { @@ -30778,6 +31058,8 @@ function parseNode(tokens, context, options, errors, stats, invalidNodes) { return null; } /** + * @param stream + * @param context * @param options * @param errors * @param parseAsBlock @@ -30854,7 +31136,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, @@ -31342,7 +31623,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 { @@ -31481,9 +31762,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 = []; @@ -31827,6 +32105,52 @@ exports.ResponseType = void 0; ResponseType[ResponseType["ArrayBuffer"] = 2] = "ArrayBuffer"; })(exports.ResponseType || (exports.ResponseType = {})); +/** + * parse result. process input sourcemap + * @param result + * @param options + * @returns + * @private + */ +function parseResult(result, options) { + if (options.sourcemap != null && options.source.getInputSourceMap() == null) { + if (options.inputSourceMap != null) { + options.source.setInputSourceMap(options.inputSourceMap); + } + else { + // extract inline source map from the input CSS + const token = result.ast.chi.at(-1); + if (token?.typ == exports.EnumToken.CommentTokenType && + token.val.startsWith("/*# sourceMappingURL=")) { + const data = token.val.slice(21, -2).trim(); + let sourcemap; + let encoding = ""; + if (data.startsWith("data:")) { + let offset = data.indexOf(",") + 1; + if (offset == 0) { + offset = data.lastIndexOf(";") + 1; + } + else { + encoding = data.slice(data.lastIndexOf(";") + 1, offset - 1); + } + if (encoding == "base64") { + sourcemap = atob(data.slice(offset)); + } + else { + sourcemap = decodeURIComponent(data.slice(offset)); + } + options.source.setInputSourceMap(sourcemap); + } + } + } + } + if (options.module) { + const { revMapping, ...res } = result; + return res; + } + return result; +} + /** * Load file or url * @param url @@ -31965,7 +32289,7 @@ function parseSync(...args) { } options ??= {}; options.src ??= ""; - options.sourcesMap ??= new Map; + options.sourcesMap ??= new Map(); Object.assign(options, { resolve, dirname, @@ -31988,13 +32312,10 @@ function parseSync(...args) { currentPosition: -1, }; const result = doParseSync(tokenize(options.parseInfo), options); - const { revMapping, ...res } = result; - return res; + return !options.module && !options.inputSourceMap ? result : parseResult(result, options); } /** * Transform css - * @param css - * @param options * * ```ts * @@ -32005,6 +32326,7 @@ function parseSync(...args) { * console.log(result.code); * ``` * + * @param args */ function transformSync(...args) { let options; @@ -32116,7 +32438,7 @@ async function parse(...args) { } options ??= {}; options.src ??= ""; - options.sourcesMap ??= new Map; + options.sourcesMap ??= new Map(); Object.assign(options, { load, resolve, @@ -32139,10 +32461,7 @@ async function parse(...args) { position: 0, currentPosition: -1, }; - return doParse(stream instanceof ReadableStream ? tokenizeStream(stream, options.parseInfo) : tokenize(options.parseInfo), options).then((result) => { - const { revMapping, ...res } = result; - return res; - }); + return doParse(stream instanceof ReadableStream ? tokenizeStream(stream, options.parseInfo) : tokenize(options.parseInfo), options).then((result) => (!options.module && !options.inputSourceMap ? result : parseResult(result, options))); } /** * Transform css file @@ -32175,8 +32494,6 @@ const transformFile = node_util.deprecate(async (file, options = {}, asStream = }), "transformFile is deprecated, use transform instead as transform({file, asStream, ...options})"); /** * Transform css - * @param css - * @param options * * Parsing a string * @@ -32215,6 +32532,7 @@ const transformFile = node_util.deprecate(async (file, options = {}, asStream = * * console.log(result.code); * ``` + * @param args */ async function transform(...args) { let options; diff --git a/dist/index.d.ts b/dist/index.d.ts index c60324b9..22524cb1 100644 --- a/dist/index.d.ts +++ b/dist/index.d.ts @@ -3628,10 +3628,14 @@ export declare interface VisitorNodeMap { } /** - * Source map class - * @internal + * Generate and parse source map */ declare class SourceMap { + /** + * + * @private + */ + private keys; /** * Last location */ @@ -3646,27 +3650,57 @@ declare class SourceMap { * @private */ private sourcesMap; + /** + * Sources content + * @private + */ + private readonly sourcesContent; /** * Sources * @private */ - private sources; + private readonly sources; /** * Map * @private + * */ private map; + /** + * Map + * @private + * + */ + private reverseMap; /** * Line * @private */ private line; /** - * Add a location - * @param source - * @param original + * */ - add(newLine: number, newColumn: number, srcId: number, ln: number, col: number, sourceFileName: string, sourceContent: string): void; + constructor(); + /** + * + * @param sourcemaps + */ + constructor(sourcemaps: string | SourceMapObject); + /** + * Add all location + * @param maps + */ + addAll(maps: Array<[number, number, number, number, number, string | null, string | null]>): void; + /** + * compute original positions + */ + computePositions(): void; + /** + * retrieve original sources, lines and columns + * @param line generated line + * @param column generated column + */ + find(line: number, column: number): Array<[string | null, number, number, string | null]> | null; /** * Convert to URL encoded string */ @@ -3675,6 +3709,10 @@ declare class SourceMap { * Convert to JSON object */ toJSON(): SourceMapObject; + /** + * to string + */ + toString(): string; } /** @@ -3689,7 +3727,7 @@ declare class LineMap { * Constructor * @param lines */ - constructor(lines: number[]); + constructor(lines?: number[]); /** * Compute line and column of the offset * @param offset @@ -3722,6 +3760,7 @@ declare class LineMap { * Source file helper class */ declare class SourceFile { + private inputSourceMap; /** * Source file ID */ @@ -3740,7 +3779,6 @@ declare class SourceFile { private content; /** * Constructor - * @param id * @param content * @param lines * @param file @@ -3749,7 +3787,6 @@ declare class SourceFile { /** * Update source content * @param content - * @param lines */ append(content: string): void; /** @@ -3791,6 +3828,16 @@ declare class SourceFile { * @param lineStart */ addLineStart(lineStart: number): void; + /** + * set input source map + * @param inputSourceMap + */ + setInputSourceMap(inputSourceMap: SourceMapObject | string | null): void; + /** + * return input source map + * @returns + */ + getInputSourceMap(): SourceMap | null; } export declare interface PropertyListOptions { @@ -5040,21 +5087,38 @@ export declare interface ParseInputStreamOptions { input: string | ReadableStream; } +/** + * Input options for string or stream + * @internal + */ export declare interface ParseSourceOptions { sourcesMap?: Map; source?: SourceFile | null; } +export declare interface ParserSourceMapOptions { + /** + * Include sourcemap in the ast. Sourcemap info is always generated + */ + sourcemap?: boolean | "inline"; + /** + * Input source map + */ + inputSourceMap?: SourceMapObject | string; +} + 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 */ @@ -5256,6 +5320,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. */ @@ -5793,6 +5862,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 +5905,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' @@ -6085,7 +6157,6 @@ declare const parseFile: (file: string, options?: ParserOptions, asStream?: bool declare function parseSync(stream: string, options?: ParserSyncOptions): ParseResult; /** * Parse css string - * @param stream * @param options * * Parsing a string @@ -6181,7 +6252,6 @@ declare function transformSync(options: ParseInputOptions & TransformSyncOptions declare function parse(stream: string | ReadableStream, options?: ParserOptions): Promise; /** * Parse css - * @param stream * @param options * * @throws Error file not found @@ -6214,7 +6284,6 @@ declare function parse(stream: string | ReadableStream, options?: Pa declare function parse(options: ParseInputFileOptions & ParserOptions): Promise; /** * Parse css - * @param stream * @param options * * Parsing a string @@ -6325,7 +6394,6 @@ declare const transformFile: (file: string, options?: TransformOptions, asStream declare function transform(css: string | ReadableStream, options?: TransformOptions): Promise; /** * Transform css - * @param css * @param options * * Parsing a string @@ -6369,7 +6437,6 @@ declare function transform(css: string | ReadableStream, options?: T declare function transform(options: ParseInputStreamOptions & TransformOptions): Promise; /** * Transform css - * @param css * @param options * * Parsing a string @@ -6412,4 +6479,4 @@ declare function transform(options: ParseInputStreamOptions & TransformOptions): declare function transform(options: ParseInputFileOptions & TransformOptions): Promise; export { ColorType$1 as ColorType, EnumAstNodeStatus$1 as EnumAstNodeStatus, EnumToken, FeatureWalkMode, ModuleCaseTransformEnum, ModuleScopeEnumOptions, ResponseType$1 as ResponseType, SourceMap, ValidationLevel, WalkerEvent, WalkerOptionEnum, cloneNode, convertColor, dirname, expand, find, findAll, findByValue, findLast, isOkLabClose, load, minify, okLabDistance, parse, parseDeclarations, parseFile, parseString, parseSync, render, renderValue as renderToken, replaceNodeOrValue, resolve, transform, transformFile, transformSync, walk, walkValues }; -export type { AddToken, AndToken, AngleToken, AstAtRule, AstComment, AstDeclaration, AstInvalidAtRule, AstInvalidDeclaration, AstInvalidRule, AstKeyFrameRule, AstKeyframesAtRule, AstKeyframesRule, AstNode$1 as AstNode, AstNodeStatus, AstRule, AstRuleList, AstStyleSheet, AstValueMatcher, AtRuleToken, AtRuleVisitorHandler, AttrEndToken, AttrStartToken, AttrToken, Background, BackgroundAttachmentMapping, BackgroundPosition, BackgroundPositionClass, BackgroundPositionConstraints, BackgroundPositionMapping, BackgroundProperties, BackgroundRepeat, BackgroundRepeatMapping, BackgroundSize, BackgroundSizeMapping, BadCDOCommentToken, BadCommentToken, BadStringToken, BadUrlToken, BaseToken, BinaryExpressionNode, BinaryExpressionToken, BlockEndToken, BlockStartToken, Border, BorderColor, BorderColorClass, BorderProperties, BorderRadius, CDOCommentToken, ChildCombinatorToken, ClassSelectorToken, ColonToken, ColorToken, ColumnCombinatorToken, CommaToken, CommentToken, ComposesSelectorToken, ConstraintsMapping, ContainMatchToken, ContainerStyleRangeToken, Context, CssVariableImportTokenType$1 as CssVariableImportTokenType, CssVariableMapTokenType, CssVariableToken$1 as CssVariableToken, DashMatchToken, DashedIdentToken, DeclarationVisitorHandler, DelimToken, DescendantCombinatorToken, DimensionToken, DivToken, DoubleColonToken, EOFToken, EndMatchToken, EqualMatchToken, ErrorDescription$1 as ErrorDescription, FlexToken, Font, FontFamily, FontProperties, FontWeight, FontWeightConstraints, FontWeightMapping, FractionToken, FrequencyToken, FunctionDefToken, FunctionImageToken, FunctionToken, FunctionURLToken, GenericVisitorAstNodeHandlerMap, GenericVisitorAstNodeSyncHandlerMap, GenericVisitorAsyncResult, GenericVisitorHandler, GenericVisitorResult, GenericVisitorSyncHandler, GenericVisitorSyncResult, GreaterThanOrEqualToken, GreaterThanToken, GridTemplateFuncToken, HashToken, IdentListToken, IdentToken, IfConditionToken, IfElseConditionToken, ImportantToken, IncludeMatchToken, InvalidAttrToken, InvalidClassSelectorToken, InvalidMediaQueryToken, LengthToken, LessThanOrEqualToken, LessThanToken, LineHeight, ListToken, LiteralToken, LoadResult, Map$1 as Map, MatchExpressionToken, MatchedSelector, MediaFeatureOnlyToken, MediaFeatureToken, MediaQueryConditionToken, MediaQueryUnaryFeatureToken, MediaRangeQueryToken, MinifyFeature, MinifyFeatureOptions, MinifyOptions, ModuleAsyncOptions, ModuleSyncOptions, MulToken, NameSpaceAttributeToken, NestingSelectorToken, NextSiblingCombinatorToken, NotToken, NumberToken, OptimizedSelector, OptimizedSelectorToken, OrToken, Outline, OutlineProperties, ParensEndToken, ParensStartToken, ParensToken, ParseInfo$1 as ParseInfo, ParseInputFileOptions, ParseInputOptions, ParseInputStreamOptions, ParseResult, ParseResultStats, ParseSourceOptions, ParseTokenOptions, ParserOptions, ParserSyncOptions, PercentageToken, Prefix, PropertiesConfig, PropertiesConfigProperties, PropertyListOptions, PropertyMapType, PropertySetType, PropertyType, PseudoClassFunctionToken, PseudoClassToken, PseudoElementToken, PseudoPageToken, PurpleBackgroundAttachment, RawNodeToken, RawSelectorTokens, RenderOptions, RenderResult, ResolutionToken, ResolvedPath, RuleVisitorHandler, SemiColonToken, Separator, ShorthandDef, ShorthandMapType, ShorthandProperties, ShorthandPropertyType, ShorthandType, SinglePropertyType, SinglePropertyTypeMapping, SourceLocation, SourceMapObject, StartMatchToken, StringToken, SubToken, SubsequentCombinatorToken, SupportsQueryConditionToken, SupportsQueryUnaryConditionToken, TimeToken, TimelineFunctionToken, TimingFunctionToken, Token$1 as Token, TokenSearchResult, TokenizeResult, TransformOptions, TransformResult, TransformSyncOptions, UnaryExpression, UnaryExpressionNode, UnclosedStringToken, UniversalSelectorToken, UrlToken, ValidationConfiguration, ValidationMediaFeature, ValidationOptions, ValidationResult, ValidationSelectorOptions, ValidationSyntaxNode, ValidationSyntaxResult, ValidationToken$1 as ValidationToken, Value, ValueVisitorHandler, ValueVisitorSyncHandler, VariableScopeInfo, VisitorNodeMap, VisitorSyncNodeMap, WalkAttributesResult, WalkResult, WalkerFilter, WalkerOption, WalkerValueFilter, WhenElseQueryConditionToken, WhenElseUnaryConditionToken, WhitespaceToken, WrappedValuesToken }; +export type { AddToken, AndToken, AngleToken, AstAtRule, AstComment, AstDeclaration, AstInvalidAtRule, AstInvalidDeclaration, AstInvalidRule, 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, 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, WalkerValueFilter, WhenElseQueryConditionToken, WhenElseUnaryConditionToken, WhitespaceToken, WrappedValuesToken }; diff --git a/dist/lib/ast/expand.js b/dist/lib/ast/expand.js index 3b4461d3..577eefeb 100644 --- a/dist/lib/ast/expand.js +++ b/dist/lib/ast/expand.js @@ -57,7 +57,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/find.js b/dist/lib/ast/find.js index 57b3cdb8..d70ac623 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' diff --git a/dist/lib/ast/minify.js b/dist/lib/ast/minify.js index dc44fd30..5830747b 100644 --- a/dist/lib/ast/minify.js +++ b/dist/lib/ast/minify.js @@ -29,13 +29,16 @@ 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 = {}) { +function minify(ast, opt = {}, recursive = false, errors, nestingContent, context = {}) { let preprocess = false; let postprocess = false; let parents; let replacement; + // @ts-ignore + let { sourcemap, module, ...options } = opt; if (!("features" in options)) { // @ts-ignore options = { @@ -232,9 +235,9 @@ function transformAtRuleMediaPrelude(values) { * Minify at-rule media * - remove redundant tokens * - generate range queries - * @param ast * * @private + * @param tokens */ function minifyAtRuleMedia(tokens) { let hasUpdates = false; @@ -332,7 +335,6 @@ function doMinify(ast, options = {}, recursive = false, errors, nestingContent, } while (previous?.typ === EnumToken.CommentNodeType) { previous = ast.chi[--nodeIndex]; - continue; } node = ast.chi[i]; if (node.typ === EnumToken.AtRuleNodeType && node.nam === "font-face") { @@ -803,7 +805,9 @@ function optimizeSelector(selector) { } break; } - selector.forEach((selector) => selector.splice(0, optimized.length)); + for (let i1 = 0; i1 < selector.length; i1++) { + selector[i1].splice(0, optimized.length); + } let reducible = optimized.length == 1; if (optimized[0] == "&") { if (optimized[1] == " ") { @@ -1185,7 +1189,6 @@ function wrapNodes(previous, node, match, ast, reducer, i, nodeIndex) { * Diff nodes * @param n1 * @param n2 - * @param reducer * @param options * * @private @@ -1304,17 +1307,36 @@ function diff(n1, n2, options = {}) { // @ts-ignore chi: intersect.reverse(), }; + let op = { level: 0, ...options }; if (result == null || [n1, n2].reduce((acc, curr) => { let css = options.cache.get(curr); if (css == null) { - css = doRender(curr, options).code; + let level = 0; + let parent = curr[PARENT]; + while (parent != null && parent.typ != EnumToken.StyleSheetNodeType) { + level++; + parent = parent[PARENT]; + } + op.level = level; + css = doRender(curr, op).code; options.cache.set(curr, css); } return curr.chi.length == 0 ? acc : acc + css.length; }, 0) <= [node1, node2, result].reduce((acc, curr) => { - const css = doRender(curr, options).code; + let css = options.cache.get(curr); + if (css != null) { + return curr.chi.length == 0 ? acc : acc + css.length; + } + let level = 0; + let parent = curr[PARENT]; + while (parent != null && parent.typ != EnumToken.StyleSheetNodeType) { + level++; + parent = parent[PARENT]; + } + op.level = level; + css = doRender(curr, op).code; return curr.chi.length == 0 ? acc : acc + css.length; }, 0)) { if (node1.chi.length != 0 && node2.chi.length != 0) { diff --git a/dist/lib/fs/resolve.js b/dist/lib/fs/resolve.js index 9ae24cae..51da577d 100644 --- a/dist/lib/fs/resolve.js +++ b/dist/lib/fs/resolve.js @@ -1,5 +1,8 @@ import { memoize } from '../parser/utils/cache.js'; +/** + * match url + */ const matchUrl = /^(https?:)?\/\//; /** * return the directory name of a path @@ -11,6 +14,9 @@ function dirname(path) { if (path === "") { return ""; } + if (path.startsWith("data:")) { + return path; + } let i = 0; let parts = [""]; for (; i < path.length; i++) { @@ -34,10 +40,7 @@ function splitPath(result) { if (result.length == 0) { return { parts: [], i: 0 }; } - // if (result === "/") { - // return { parts: ["/"], i: 0 }; - // } - const parts = [""]; + const parts = result == "/" ? [] : [""]; let i = 0; for (; i < result.length; i++) { const chr = result.charAt(i); @@ -46,7 +49,7 @@ function splitPath(result) { } // else if (chr == "?" || chr == "#") { // break; - // } + // } else { parts[parts.length - 1] += chr; } @@ -64,6 +67,8 @@ function splitPath(result) { } /** * Nomalize path + * @param path + * @private */ const normalize = memoize(function (path) { let parts = []; @@ -92,14 +97,20 @@ const normalize = memoize(function (path) { while (++k < parts.length) { // if (parts[k] == ".") { // parts.splice(k--, 1); - // } else - if (parts[k] == "..") { + // } else + if (k > 0 && parts[k] == "..") { parts.splice(k - 1, 2); k -= 2; } } return (path.charAt(0) == "/" ? "/" : "") + parts.join("/"); }); +/** + * diff path + * @param path1 + * @param path2 + * @private + */ const diff = memoize(function (path1, path2) { let { parts } = splitPath(path1); const { parts: dirs } = splitPath(path2); @@ -131,30 +142,51 @@ const resolve = memoize(function (url, currentDirectory, cwd) { cwd ??= ""; currentDirectory ??= ""; url = normalize(url); + if (cwd !== "") { + cwd = normalize(cwd); + } if (currentDirectory !== "") { currentDirectory = normalize(currentDirectory); - if (url.startsWith(currentDirectory + "/")) { - return { - absolute: url, - relative: url.slice(currentDirectory.length + 1), - }; - } - } - if ((currentDirectory === "" || currentDirectory === ".") && cwd !== "") { - cwd = normalize(cwd); - if (url.startsWith(cwd == "/" ? cwd : cwd + "/")) { - const absolute = url; - const prefix = cwd == "/" ? cwd : cwd + "/"; - return { - absolute, - relative: absolute.startsWith(prefix) ? absolute.slice(prefix.length) : diff(absolute, cwd), - }; - } } + const dir = cwd || currentDirectory; + const absolute = dir == "" || url.startsWith("/") ? resolvePath(url) : resolvePath(dir, url); return { - absolute: url, - relative: url === "" ? "" : diff(url, cwd || currentDirectory), + absolute, + relative: dir === "" ? absolute : diff(absolute, dir), }; }); +/** + * + * @param parts + * @returns + * @private + */ +function resolvePath(...parts) { + const path = parts.filter(Boolean).join("/"); + const isAbsolute = /^[\\/]/.test(path); + const segments = path.split(/[\\/]+/); + const resolved = []; + for (const segment of segments) { + if (!segment || segment === ".") { + continue; + } + if (segment === "..") { + if (resolved.length && resolved[resolved.length - 1] !== "..") { + resolved.pop(); + } + else if (!isAbsolute) { + resolved.push(".."); + } + } + else { + resolved.push(segment); + } + } + let result = resolved.join("/"); + if (isAbsolute) { + result = "/" + result; + } + return result || (isAbsolute ? "/" : "."); +} export { diff, dirname, matchUrl, normalize, resolve }; diff --git a/dist/lib/parser/linesmap.js b/dist/lib/parser/linesmap.js index 0365942d..38daa8f7 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); } @@ -28,7 +28,7 @@ class LineMap { } const column = offset - this.lineStarts[line]; // [line, column] - return [line + 1, column === 0 ? 1 : column]; + return [line + 1, line === 0 ? column + 1 : column]; } /** * search the greatest index of the value less than or equal to offset diff --git a/dist/lib/parser/parse.js b/dist/lib/parser/parse.js index cd5df1f8..99b36382 100644 --- a/dist/lib/parser/parse.js +++ b/dist/lib/parser/parse.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) @@ -643,7 +644,7 @@ function doParseSync(iter, options = {}) { if (replacement == null) { continue; } - if (replacement == null || replacement == node) { + if (replacement == node) { continue; } // @ts-ignore @@ -689,7 +690,7 @@ function doParseSync(iter, options = {}) { if (replacement == null) { continue; } - if (replacement == null || replacement == node) { + if (replacement == node) { continue; } // @ts-ignore @@ -723,7 +724,7 @@ function doParseSync(iter, options = {}) { if (replacement == null) { continue; } - if (replacement != null && replacement != node) { + if (replacement != node) { node = replacement; } } @@ -756,7 +757,7 @@ function doParseSync(iter, options = {}) { if (result == null) { continue; } - if (result != null && result != node) { + if (result != node) { node = result; } if (Array.isArray(node)) { @@ -941,10 +942,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 +986,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 +1159,10 @@ function doParseSync(iter, options = {}) { "unset", ].includes(value.val)) { if (!(value.val in mapping)) { - const result = moduleSettings.scoped & ModuleScopeEnumOptions.Global - ? value.val - : moduleSettings.generateScopedName(value.val, moduleSettings.filePath, moduleSettings.pattern, moduleSettings.hashLength); - mapping[value.val] = result; + mapping[value.val] = + moduleSettings.scoped & ModuleScopeEnumOptions.Global + ? value.val + : moduleSettings.generateScopedName(value.val, moduleSettings.filePath, moduleSettings.pattern, moduleSettings.hashLength); revMapping[mapping[value.val]] = value.val; } value.val = mapping[value.val]; @@ -1235,10 +1234,9 @@ function doParseSync(iter, options = {}) { if (value.typ == EnumToken.ClassSelectorTokenType) { const val = value.val.slice(1); if (!(val in mapping)) { - const result = moduleSettings.scoped & ModuleScopeEnumOptions.Global + let value = moduleSettings.scoped & ModuleScopeEnumOptions.Global ? val : moduleSettings.generateScopedName(val, moduleSettings.filePath, moduleSettings.pattern, moduleSettings.hashLength); - let value = result; mapping[val] = moduleSettings.naming & ModuleCaseTransformEnum.DashCaseOnly || moduleSettings.naming & ModuleCaseTransformEnum.CamelCaseOnly @@ -1271,10 +1269,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 || @@ -1624,7 +1621,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 @@ -2098,6 +2095,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) { @@ -2167,8 +2165,10 @@ async function doParse(iter, options = {}) { setParent: false, src: src.relative, })); - const srcIndex = (src.relative.startsWith("/") || src.relative.startsWith("../") ? "" : "./") + - src.relative; + let srcIndex = options.resolve(src.absolute, resolvedSrc.absolute).relative; + if (!srcIndex.startsWith("/") && !srcIndex.startsWith("../")) { + srcIndex = `./${srcIndex}`; + } if (Object.keys(root.mapping).length > 0) { importMapping[srcIndex] = {}; } @@ -2420,26 +2420,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; } @@ -2453,12 +2433,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; } } })) { @@ -2744,6 +2718,8 @@ function parseNode(tokens, context, options, errors, stats, invalidNodes) { return null; } /** + * @param stream + * @param context * @param options * @param errors * @param parseAsBlock @@ -2820,7 +2796,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, @@ -3308,7 +3283,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 { @@ -3447,9 +3422,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 = []; diff --git a/dist/lib/parser/source.js b/dist/lib/parser/source.js index 6f758722..04d7d7e0 100644 --- a/dist/lib/parser/source.js +++ b/dist/lib/parser/source.js @@ -1,3 +1,4 @@ +import { SourceMap } from '../renderer/sourcemap/sourcemap.js'; import { LineMap } from './linesmap.js'; /** @@ -8,6 +9,7 @@ let sourceId = 0; * Source file helper class */ class SourceFile { + inputSourceMap = null; /** * Source file ID */ @@ -26,7 +28,6 @@ class SourceFile { content; /** * Constructor - * @param id * @param content * @param lines * @param file @@ -40,7 +41,6 @@ class SourceFile { /** * Update source content * @param content - * @param lines */ append(content) { this.content += content; @@ -98,6 +98,20 @@ class SourceFile { addLineStart(lineStart) { this.lineStarts.addLineStart(lineStart); } + /** + * set input source map + * @param inputSourceMap + */ + setInputSourceMap(inputSourceMap) { + this.inputSourceMap = inputSourceMap == null ? null : new SourceMap(inputSourceMap); + } + /** + * return input source map + * @returns + */ + getInputSourceMap() { + return this.inputSourceMap; + } } export { SourceFile }; diff --git a/dist/lib/parser/tokenize.js b/dist/lib/parser/tokenize.js index e0cb381b..2145962b 100644 --- a/dist/lib/parser/tokenize.js +++ b/dist/lib/parser/tokenize.js @@ -378,7 +378,7 @@ function next(parseInfo, count = 1) { return char; } /** - * Tokenize css string + * Tokenize CSS string * @param parseInfo * @param yieldEOFToken */ @@ -405,8 +405,6 @@ function tokenize(parseInfo, yieldEOFToken = true) { parseInfo.buffer = ""; while ((value = peek(parseInfo))) { charCode = value.charCodeAt(0); - // nextCharCode = nextValue.charCodeAt(0); - // console.debug({value, buffer}); switch (charCode) { case 61 /* TokenMap.EQUALS */: if (buffer.length > 0) { @@ -771,10 +769,6 @@ function tokenize(parseInfo, yieldEOFToken = true) { break; } buffer += value + next(parseInfo); - // buffer += - // (parseInfo.offset == parseInfo.currentPosition - // ? parseInfo.buffer.slice(-1) - // : parseInfo.stream.charAt(parseInfo.currentPosition - parseInfo.offset - 1)) + value; break; case 39 /* TokenMap.SINGLE_QUOTE */: case 34 /* TokenMap.DOUBLE_QUOTE */: diff --git a/dist/lib/renderer/render.js b/dist/lib/renderer/render.js index 23d1108e..d96ed529 100644 --- a/dist/lib/renderer/render.js +++ b/dist/lib/renderer/render.js @@ -61,22 +61,28 @@ function doRender(data, options = {}, mapping) { const startTime = performance.now(); const errors = []; const sourcemap = options.sourcemap ? new SourceMap() : null; + const sourcemaps = options.sourcemap ? [] : null; const cache = Object.create(null); const sourceLocation = { - srcId: 0, - sta: 0, end: 0, }; - const linesMap = new LineMap([]); + const linesMap = options.sourcemap ? new LineMap() : null; let code = ""; if (mapping != null) { if (mapping.importMapping != null) { - for (const [key, value] of Object.entries(mapping.importMapping)) { + const absolutePath = options.resolve(options.output != null ? dirname(options.output) : dirname(options.src), options.cwd).absolute; + for (let [key, value] of Object.entries(mapping.importMapping)) { + key = options.resolve(options.resolve(key, options.cwd).absolute, absolutePath).relative; + if (!key.startsWith("/") && !key.startsWith(".")) { + key = "./" + key; + } code += `:import("${key}")${options.indent}{${options.newLine}${Object.entries(value).reduce((acc, [k, v]) => acc + (acc.length > 0 ? options.newLine : "") + `${options.indent}${v}:${options.indent}${k};`, "")}${options.newLine}}${options.newLine}`; } } code += `:export${options.indent}{${options.newLine}${Object.entries(mapping.mapping).reduce((acc, [k, v]) => acc + (acc.length > 0 ? options.newLine : "") + `${options.indent}${k}:${options.indent}${v};`, "")}${options.newLine}}${options.newLine}`; - move(sourceLocation, linesMap, code); + if (sourcemap != null) { + move(sourceLocation, linesMap, code); + } } if (options.output != null) { // @ts-ignore @@ -88,7 +94,7 @@ function doRender(data, options = {}, mapping) { [EnumToken.StyleSheetNodeType, EnumToken.AtRuleNodeType, EnumToken.RuleNodeType].includes(data.typ) && "chi" in data ? expand(data) - : data, options, sourcemap, sourceLocation, linesMap, errors, function reducer(acc, curr) { + : data, options, sourcemaps, sourceLocation, linesMap, errors, function reducer(acc, curr) { if (curr.typ == EnumToken.CommentTokenType && options.removeComments) { if (!options.preserveLicense || !curr.val.startsWith("/*!")) { return acc; @@ -103,6 +109,7 @@ function doRender(data, options = {}, mapping) { }, }; if (sourcemap != null) { + sourcemap.addAll(sourcemaps); result.map = sourcemap; if (options.sourcemap === "inline") { result.code += `\n/*# sourceMappingURL=${result.map.toUrl()} */`; @@ -115,37 +122,88 @@ function doRender(data, options = {}, mapping) { * @param node * @param options * @param cache - * @param sourcemap - * @param position + * @param sourcemaps + * @param sourceLocation + * @param linesMap * @param str * * @internal */ -function updateSourceMap(node, options, cache, sourcemap, sourceLocation, linesMap, str) { - if ([ - EnumToken.RuleNodeType, - EnumToken.AtRuleNodeType, - EnumToken.KeyFramesRuleNodeType, - EnumToken.KeyframesAtRuleNodeType, - ].includes(node.typ)) { - let srcId = node[LOC]?.srcId ?? 0; - let sourceFileName = options.sourcesMap?.get(srcId)?.getFileName?.() || null; - if (sourceFileName != null && options.output != null) { - if (cache[sourceFileName] == null) { - cache[sourceFileName] = options.resolve(sourceFileName, dirname(options.output)).relative; +function updateSourceMap(node, options, cache, sourcemaps, sourceLocation, linesMap, str) { + let offset = 0; + while (true) { + if (str.charAt(offset) == options.newLine) { + offset += options.newLine.length; + continue; + } + if (str.charAt(offset) == options.indent) { + offset += options.indent.length; + continue; + } + break; + } + if (offset > 0) { + move(sourceLocation, linesMap, str.slice(0, offset)); + } + if (node[LOC] != null && + [ + EnumToken.RuleNodeType, + EnumToken.AtRuleNodeType, + EnumToken.KeyFramesRuleNodeType, + EnumToken.KeyframesAtRuleNodeType, + ].includes(node.typ)) { + const source = options.sourcesMap.get(node[LOC].srcId); + const inputSourceMap = source.getInputSourceMap(); + const offsets = source.getOffsets(node[LOC].sta); + const [newLine, newColumn] = linesMap.getOffsets(sourceLocation.end); + let records = null; + let srcId = node[LOC].srcId; + let sourceFileName = source.getFileName() || null; + let sourceContent = source.getContent() || null; + if (inputSourceMap != null && (records = inputSourceMap.find(offsets[0], offsets[1])) != null) { + for (const record of records) { + // @ts-ignore + sourceFileName = record[0] || null; + // @ts-ignore + offsets[0] = record[1]; + // @ts-ignore + offsets[1] = record[2]; + sourceContent = record[3] || null; + if (sourceFileName != null && options.output != null && !sourceFileName.startsWith("data:")) { + if (cache[sourceFileName] == null) { + const absolute = options.resolve(dirname(options.output), options.cwd) + .absolute; + const absoluteSourcePath = options.resolve(dirname(options.src || ""), options.cwd).absolute; + // resolution is relative to the source file + const absoluteSourceFileName = options.resolve(sourceFileName, absoluteSourcePath) + .absolute; + cache[sourceFileName] = options.resolve(absoluteSourceFileName, absolute).relative; + } + sourceFileName = cache[sourceFileName]; + } + sourcemaps.push([newLine, newColumn, srcId, ...offsets, sourceFileName, sourceContent]); } - sourceFileName = cache[sourceFileName]; } - // @ts-ignore - sourcemap.add(...linesMap.getOffsets(sourceLocation.end), srcId, - // @ts-ignore - ...options.sourcesMap?.get(srcId)?.getOffsets(sourceLocation.sta), sourceFileName, options.sourcesMap?.get(srcId)?.getContent?.()); + else { + if (sourceFileName != null && options.output != null && !sourceFileName.startsWith("data:")) { + if (cache[sourceFileName] == null) { + const absolute = options.resolve(dirname(options.output), options.cwd) + .absolute; + const absoluteSourceFileName = options.resolve(sourceFileName, options.cwd) + .absolute; + cache[sourceFileName] = options.resolve(absoluteSourceFileName, absolute).relative; + } + sourceFileName = cache[sourceFileName]; + } + sourcemaps.push([newLine, newColumn, srcId, ...offsets, sourceFileName, sourceContent]); + } } - move(sourceLocation, linesMap, str); + move(sourceLocation, linesMap, offset > 0 ? str.slice(offset) : str); } /** * Update position - * @param position + * @param sourceLocation + * @param linesMap * @param str */ function move(sourceLocation, linesMap, str) { @@ -175,8 +233,9 @@ function move(sourceLocation, linesMap, str) { * render ast node * @param data * @param options - * @param sourcemap - * @param position + * @param sourcemaps + * @param sourceLocation + * @param linesMap * @param errors * @param reducer * @param cache @@ -185,13 +244,17 @@ 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 = ""; + let previousStr = ""; const indent = indents[level]; const indentSub = indents[level + 1]; switch (data.typ) { @@ -209,20 +272,17 @@ function renderAstNode(data, options, sourcemap, sourceLocation, linesMap, error ? 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; - } - if (sourcemap != null && node[LOC] != null) { - updateSourceMap(node, options, cache, sourcemap, sourceLocation, linesMap, (hasPreviousContent ? options.newLine : "") + str); + continue; } - if (!hasPreviousContent) { - return str; + if (children.length > 0) { + str = options.newLine + str; } - return `${css}${options.newLine}${str}`; - }, ""); + children += str; + } + return children; case EnumToken.AtRuleNodeType: case EnumToken.RuleNodeType: case EnumToken.KeyFramesRuleNodeType: @@ -230,9 +290,15 @@ function renderAstNode(data, options, sourcemap, sourceLocation, linesMap, error if ([EnumToken.AtRuleNodeType, EnumToken.KeyframesAtRuleNodeType].includes(data.typ) && !("chi" in data)) { return `${indent}@${data.nam}${data.val === "" ? "" : options.indent || " "}${data.val};`; } - // @ts-ignore - let children = data.chi.reduce((css, node) => { - let str; + const prelude = [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; + for (let i = 0; i < data.chi.length; i++) { + node = data.chi[i]; if (node.typ == EnumToken.CommentNodeType) { str = options.removeComments && @@ -255,41 +321,45 @@ function renderAstNode(data, options, sourcemap, sourceLocation, linesMap, error : node.val) .reduce(reducer, "") .trimEnd()};`; + if (sourcemaps != null) { + if (previousStr.length > 0) { + move(sourceLocation, linesMap, previousStr); + } + } + previousStr = str === "" ? "" : options.newLine + indentSub + str; } // 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; + if (sourcemaps != null) { + if (previousStr.length > 0) { + move(sourceLocation, linesMap, previousStr); + } + } + str = renderAstNode(node, options, sourcemaps, sourceLocation, linesMap, errors, reducer, cache, level + 1, indents); + previousStr = ""; } if (str === "") { - return css; + continue; } - return `${css}${options.newLine}${indentSub}${str}`; - }, ""); - if (options.removeEmpty && children === "") { - return ""; + str = options.newLine + indentSub + str; + children += str; + } + if (sourcemaps != null && str !== "") { + move(sourceLocation, linesMap, str.endsWith(";") ? str.slice(0, -1) : str); } if (children.endsWith(";")) { children = children.slice(0, -1); } - const rendered = [EnumToken.AtRuleNodeType, EnumToken.KeyframesAtRuleNodeType].includes(data.typ) - ? `@${data.nam}${data.val === "" ? "" : options.indent || " "}${data.val}${options.indent}{${options.newLine}` + - (children === "" ? "" : indentSub + children + options.newLine) + - indent + - `}` - : data.sel + - `${options.indent}{${options.newLine}` + - (children === "" ? "" : indentSub + children + options.newLine) + - indent + - `}`; - if (sourcemap != null && data[LOC] != null) { - updateSourceMap(data, options, cache, sourcemap, { ...sourceLocation }, linesMap.clone(), rendered); + if (options.removeEmpty && children === "") { + return ""; + } + const end = options.newLine + indent + `}`; + if (sourcemaps != null) { + move(sourceLocation, linesMap, end); } - return rendered; + return prelude + children + end; // case EnumToken.CssVariableTokenType: // case EnumToken.CssVariableImportTokenType: // return `@value ${(data).val}:${options.indent}${filterValues( @@ -316,6 +386,9 @@ function renderAstNode(data, options, sourcemap, sourceLocation, linesMap, error * render ast token * @param token * @param options + * @param cache + * @param reducer + * @param errors * @private */ function renderValue(token, options = {}, cache = Object.create(null), reducer, errors) { diff --git a/dist/lib/renderer/sourcemap/lib/codec.js b/dist/lib/renderer/sourcemap/lib/codec.js new file mode 100644 index 00000000..ab152589 --- /dev/null +++ b/dist/lib/renderer/sourcemap/lib/codec.js @@ -0,0 +1,78 @@ +// from https://github.com/Rich-Harris/vlq/tree/master +// credit: Rich Harris +const integer_to_char = {}; +const char_to_integer = {}; +let i = 0; +for (const char of 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=') { + char_to_integer[char] = i; + integer_to_char[i++] = char; +} +/** + * @param {string} str + */ +function decode(str) { + /** @type {number[]} */ + let result = []; + let shift = 0; + let value = 0; + for (let i = 0; i < str.length; i += 1) { + let integer = char_to_integer[str[i]]; + if (integer === undefined) { + throw new Error('Invalid character (' + str[i] + ')'); + } + const has_continuation_bit = integer & 32; + integer &= 31; + value += integer << shift; + if (has_continuation_bit) { + shift += 5; + } + else { + const should_negate = value & 1; + value >>>= 1; + if (should_negate) { + result.push(value === 0 ? -2147483648 : -value); + } + else { + result.push(value); + } + // reset + value = shift = 0; + } + } + return result; +} +/** + * + * @param value + * @returns + */ +function encode(value) { + if (typeof value === 'number') { + return encode_integer(value); + } + let result = ''; + for (let i = 0; i < value.length; i += 1) { + result += encode_integer(value[i]); + } + return result; +} +function encode_integer(num) { + let result = ''; + if (num < 0) { + num = (-num << 1) | 1; + } + else { + num <<= 1; + } + do { + let clamped = num & 31; + num >>>= 5; + if (num > 0) { + clamped |= 32; + } + result += integer_to_char[clamped]; + } while (num > 0); + return result; +} + +export { decode, encode }; diff --git a/dist/lib/renderer/sourcemap/lib/encode.js b/dist/lib/renderer/sourcemap/lib/encode.js deleted file mode 100644 index 9484f762..00000000 --- a/dist/lib/renderer/sourcemap/lib/encode.js +++ /dev/null @@ -1,37 +0,0 @@ -// from https://github.com/Rich-Harris/vlq/tree/master -// credit: Rich Harris -const integer_to_char = {}; -let i = 0; -for (const char of 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=') { - integer_to_char[i++] = char; -} -function encode(value) { - if (typeof value === 'number') { - return encode_integer(value); - } - let result = ''; - for (let i = 0; i < value.length; i += 1) { - result += encode_integer(value[i]); - } - return result; -} -function encode_integer(num) { - let result = ''; - if (num < 0) { - num = (-num << 1) | 1; - } - else { - num <<= 1; - } - do { - let clamped = num & 31; - num >>>= 5; - if (num > 0) { - clamped |= 32; - } - result += integer_to_char[clamped]; - } while (num > 0); - return result; -} - -export { encode }; diff --git a/dist/lib/renderer/sourcemap/sourcemap.js b/dist/lib/renderer/sourcemap/sourcemap.js index 8fb6c4ad..0099b322 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,165 @@ 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 */ - add(newLine, newColumn, srcId, ln, col, sourceFileName, sourceContent) { - if (!this.sourcesMap.includes(srcId)) { - if (sourceFileName == null && sourceContent != null) { - sourceFileName = "data:text/css;charset=utf-8;base64," + btoa(sourceContent); + constructor(sourcemaps) { + if (typeof sourcemaps === "string") { + sourcemaps = JSON.parse(sourcemaps); + } + if (sourcemaps != null) { + this.sources = sourcemaps.sources?.slice() ?? []; + this.sourcesContent = sourcemaps.sourcesContent?.slice() ?? []; + const decodedMappings = sourcemaps.mappings + .split(";") + .map((mapping) => mapping.split(",").map((mapping) => decode(mapping))); + this.line = decodedMappings.length - 1; + for (let index = 0; index < decodedMappings.length; index++) { + if (decodedMappings[index].length == 0 || + (decodedMappings[index].length == 1 && decodedMappings[index][0].length == 0)) { + continue; + } + this.map.set(index, decodedMappings[index]); } - this.sourcesMap.push(srcId); - this.sources.push(sourceFileName || null); + this.computePositions(); } - const line = newLine - 1; - let record; - if (line > this.line) { - this.line = line; + } + /** + * Add all location + * @param maps + */ + addAll(maps) { + for (let [newLine, newColumn, srcId, ln, col, sourceFileName, sourceContent] of maps) { + const key = `${srcId}:${ln}:${sourceFileName}:${col}:${newLine}:${newColumn}:${sourceContent}`; + const sourcemap = `${srcId}:${sourceFileName}:${sourceContent}`; + if (this.keys.has(key)) { + continue; + } + this.keys.add(key); + if (!this.sourcesMap.includes(sourcemap)) { + this.sourcesMap.push(sourcemap); + this.sources.push(sourceFileName || null); + this.sourcesContent.push((sourceFileName != null ? null : sourceContent) || null); + } + const line = newLine - 1; + let record; + if (line > this.line) { + this.line = line; + } + if (!this.map.has(line)) { + record = [Math.max(0, newColumn - 1), this.sourcesMap.indexOf(sourcemap), ln - 1, col - 1]; + this.map.set(line, [record]); + } + else { + const arr = this.map.get(line); + record = [ + Math.max(0, newColumn - 1) - arr[0][0], + this.sourcesMap.indexOf(sourcemap) - arr[0][1], + ln - 1, + col - 1, + ]; + arr.push(record); + } + if (this.lastLocation != null) { + record[2] -= this.lastLocation.ln - 1; + record[3] -= this.lastLocation.col - 1; + } + this.lastLocation ??= { ln, col }; + this.lastLocation.ln = ln; + this.lastLocation.col = col; } - if (!this.map.has(line)) { - record = [Math.max(0, newColumn - 1), this.sourcesMap.indexOf(srcId), ln - 1, col - 1]; - this.map.set(line, [record]); + } + /** + * compute original positions + */ + computePositions() { + this.reverseMap.clear(); + let sourceFileIndex = 0; // second field + let sourceCodeLine = 0; // third field + let sourceCodeColumn = 0; // fourth field + let nameIndex = 0; // fifth field + let generatedCodeColumn; + let result; + // mappings to original source + for (let [i, line] of this.map.entries()) { + if (line.length === 0 || (line.length === 1 && line[0].length === 0)) { + continue; + } + generatedCodeColumn = line[0][0]; // first field - reset each time + line = line + .map((segment, index, array) => { + if (segment.length === 0) { + return []; + } + generatedCodeColumn = index == 0 ? segment[0] : segment[0] + array[0][0]; + result = [generatedCodeColumn]; + if (segment.length <= 1) { + return result; + } + sourceFileIndex = index == 0 ? segment[1] : segment[1] + array[0][1]; + sourceCodeLine += segment[2]; + sourceCodeColumn += segment[3]; + result.push(sourceFileIndex, sourceCodeLine, sourceCodeColumn); + if (segment.length === 5) { + nameIndex += segment[4]; + result.push(nameIndex); + } + return result; + }) + .sort((a, b) => { + if (a[1] !== b[1]) { + return a[1] - b[1]; + } + return a[0] - b[0]; + }); + if (line.length == 0 || (line.length == 1 && line[0].length == 0)) { + continue; + } + this.reverseMap.set(i, line); } - else { - const arr = this.map.get(line); - record = [ - Math.max(0, newColumn - 1 - arr[0][0]), - this.sourcesMap.indexOf(srcId) - arr[0][1], - ln - 1, - col - 1, - ]; - arr.push(record); + } + /** + * retrieve original sources, lines and columns + * @param line generated line + * @param column generated column + */ + find(line, column) { + if (!this.reverseMap.has(--line)) { + return null; } - if (this.lastLocation != null) { - record[2] -= this.lastLocation.ln - 1; - record[3] -= this.lastLocation.col - 1; + column--; + const result = []; + for (const record of this.reverseMap.get(line)) { + if (record.length == 0 || record[0] < column) { + continue; + } + if (record[0] > column) { + break; + } + result.push([ + this.sources?.[record[1]] ?? null, + record[2] + 1, + record[3] + 1, + this.sourcesContent?.[record[1]] ?? null, + ]); } - this.lastLocation ??= { ln, col }; - this.lastLocation.ln = ln; - this.lastLocation.col = col; + return result.length == 0 ? null : result; } /** * Convert to URL encoded string @@ -98,9 +220,16 @@ class SourceMap { return { version: this.version, sources: this.sources.slice(), + sourcesContent: this.sourcesContent?.slice(), mappings: mappings.join(";"), }; } + /** + * to string + */ + toString() { + return JSON.stringify(this); + } } export { SourceMap }; diff --git a/dist/lib/validation/match.js b/dist/lib/validation/match.js index 34ca59ac..dbed9b43 100644 --- a/dist/lib/validation/match.js +++ b/dist/lib/validation/match.js @@ -971,7 +971,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 { diff --git a/dist/node.js b/dist/node.js index 21d0e975..ca818d15 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 } from './utils.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'; @@ -163,7 +164,7 @@ function parseSync(...args) { } options ??= {}; options.src ??= ""; - options.sourcesMap ??= new Map; + options.sourcesMap ??= new Map(); Object.assign(options, { resolve, dirname, @@ -186,13 +187,10 @@ function parseSync(...args) { currentPosition: -1, }; const result = doParseSync(tokenize(options.parseInfo), options); - const { revMapping, ...res } = result; - return res; + return !options.module && !options.inputSourceMap ? result : parseResult(result, options); } /** * Transform css - * @param css - * @param options * * ```ts * @@ -203,6 +201,7 @@ function parseSync(...args) { * console.log(result.code); * ``` * + * @param args */ function transformSync(...args) { let options; @@ -314,7 +313,7 @@ async function parse(...args) { } options ??= {}; options.src ??= ""; - options.sourcesMap ??= new Map; + options.sourcesMap ??= new Map(); Object.assign(options, { load, resolve, @@ -337,10 +336,7 @@ async function parse(...args) { position: 0, currentPosition: -1, }; - return doParse(stream instanceof ReadableStream ? tokenizeStream(stream, options.parseInfo) : tokenize(options.parseInfo), options).then((result) => { - const { revMapping, ...res } = result; - return res; - }); + return doParse(stream instanceof ReadableStream ? tokenizeStream(stream, options.parseInfo) : tokenize(options.parseInfo), options).then((result) => (!options.module && !options.inputSourceMap ? result : parseResult(result, options))); } /** * Transform css file @@ -373,8 +369,6 @@ const transformFile = deprecate(async (file, options = {}, asStream = false) => }), "transformFile is deprecated, use transform instead as transform({file, asStream, ...options})"); /** * Transform css - * @param css - * @param options * * Parsing a string * @@ -413,6 +407,7 @@ const transformFile = deprecate(async (file, options = {}, asStream = false) => * * console.log(result.code); * ``` + * @param args */ async function transform(...args) { let options; diff --git a/dist/utils.d.ts b/dist/utils.d.ts new file mode 100644 index 00000000..17fbc10f --- /dev/null +++ b/dist/utils.d.ts @@ -0,0 +1,9 @@ +import type { ParseResult, ParserOptions } 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; diff --git a/dist/utils.js b/dist/utils.js new file mode 100644 index 00000000..e95f0443 --- /dev/null +++ b/dist/utils.js @@ -0,0 +1,49 @@ +import { EnumToken } from './lib/ast/types.js'; + +/** + * parse result. process input sourcemap + * @param result + * @param options + * @returns + * @private + */ +function parseResult(result, options) { + if (options.sourcemap != null && options.source.getInputSourceMap() == null) { + if (options.inputSourceMap != null) { + options.source.setInputSourceMap(options.inputSourceMap); + } + else { + // extract inline source map from the input CSS + const token = result.ast.chi.at(-1); + if (token?.typ == EnumToken.CommentTokenType && + token.val.startsWith("/*# sourceMappingURL=")) { + const data = token.val.slice(21, -2).trim(); + let sourcemap; + let encoding = ""; + if (data.startsWith("data:")) { + let offset = data.indexOf(",") + 1; + if (offset == 0) { + offset = data.lastIndexOf(";") + 1; + } + else { + encoding = data.slice(data.lastIndexOf(";") + 1, offset - 1); + } + if (encoding == "base64") { + sourcemap = atob(data.slice(offset)); + } + else { + sourcemap = decodeURIComponent(data.slice(offset)); + } + options.source.setInputSourceMap(sourcemap); + } + } + } + } + if (options.module) { + const { revMapping, ...res } = result; + return res; + } + return result; +} + +export { parseResult }; diff --git a/dist/web.js b/dist/web.js index 63d9f568..f0ce3778 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 } from './utils.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'; @@ -155,7 +156,7 @@ function parseSync(...args) { } options ??= {}; options.src ??= ""; - options.sourcesMap ??= new Map; + options.sourcesMap ??= new Map(); Object.assign(options, { resolve, dirname, @@ -180,13 +181,10 @@ function parseSync(...args) { currentPosition: -1, }; const result = doParseSync(tokenize(options.parseInfo), options); - const { revMapping, ...res } = result; - return res; + return !options.module && !options.inputSourceMap ? result : parseResult(result, options); } /** * Transform css - * @param css - * @param options * * ```ts * @@ -197,6 +195,7 @@ function parseSync(...args) { * console.log(result.code); * ``` * + * @param args */ function transformSync(...args) { let options; @@ -246,8 +245,6 @@ function transformSync(...args) { } /** * Parse css - * @param stream - * @param options * * Example: * @@ -271,6 +268,7 @@ function transformSync(...args) { * * console.log(result.ast); * ``` + * @param args */ async function parse(...args) { let options; @@ -292,7 +290,7 @@ async function parse(...args) { } options ??= {}; options.src ??= ""; - options.sourcesMap ??= new Map; + options.sourcesMap ??= new Map(); Object.assign(options, { load, resolve, @@ -316,10 +314,7 @@ async function parse(...args) { position: 0, currentPosition: -1, }; - return doParse(stream instanceof ReadableStream ? tokenizeStream(stream, options.parseInfo) : tokenize(options.parseInfo), options).then((result) => { - const { revMapping, ...res } = result; - return res; - }); + return doParse(stream instanceof ReadableStream ? tokenizeStream(stream, options.parseInfo) : tokenize(options.parseInfo), options).then((result) => (!options.module && !options.inputSourceMap ? result : parseResult(result, options))); } /** * Transform css file @@ -353,8 +348,6 @@ async function transformFile(file, options = {}, asStream = false) { } /** * Transform css - * @param css - * @param options * * Example: * @@ -372,6 +365,7 @@ async function transformFile(file, options = {}, asStream = false) { * * console.log(result.code); * ``` + * @param args */ async function transform(...args) { let options; diff --git a/files/getting-started.md b/files/getting-started.md index 13e6c9a5..a1702631 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. diff --git a/files/transform.md b/files/transform.md index 236970b3..3545bc28 100644 --- a/files/transform.md +++ b/files/transform.md @@ -8,6 +8,30 @@ category: Guides 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) +## Plugin support through the visitor API + +The CSS parser supports plugin-style extensions through its visitor API. 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. + +```ts +import {transform, type ParserOptions} from '@tbela99/css-parser'; + +const options: ParserOptions = { + visitor: { + Rule: { + '.card': (node) => { + node.selector = '.card, .panel'; + return node; + } + } + } +}; + +const result = await transform('.card { color: red; }', options); +console.log(result.code); +``` + ## Visitors execution order Visitors can be called when the node is entered, visited or left. diff --git a/llms.txt b/llms.txt index 50cb7811..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'; diff --git a/src/@types/index.d.ts b/src/@types/index.d.ts index fde50dcf..d32373ff 100644 --- a/src/@types/index.d.ts +++ b/src/@types/index.d.ts @@ -442,21 +442,38 @@ export declare interface ParseInputStreamOptions { input: string | ReadableStream; } +/** + * Input options for string or stream + * @internal + */ export declare interface ParseSourceOptions { sourcesMap?: Map; source?: SourceFile | null; } +export declare interface ParserSourceMapOptions { + /** + * Include sourcemap in the ast. Sourcemap info is always generated + */ + sourcemap?: boolean | "inline"; + /** + * Input source map + */ + inputSourceMap?: SourceMapObject | string; +} + 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 */ @@ -658,6 +675,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/lib/ast/expand.ts b/src/lib/ast/expand.ts index 42a9f6ec..d975052d 100644 --- a/src/lib/ast/expand.ts +++ b/src/lib/ast/expand.ts @@ -1,10 +1,10 @@ -import { splitRule } from "./minify.ts"; -import { combinators, RAW } 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 {splitRule} from "./minify.ts"; +import {combinators, RAW} 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"; /** * expand css nesting ast nodes @@ -70,9 +70,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( diff --git a/src/lib/ast/find.ts b/src/lib/ast/find.ts index 16e55e27..89647f2a 100644 --- a/src/lib/ast/find.ts +++ b/src/lib/ast/find.ts @@ -1,11 +1,11 @@ -import type { Token } from "../../@types/token.d.ts"; -import type { AstDeclaration, AstNode, AstValueMatcher, TokenSearchResult } from "../../@types/ast.d.ts"; -import { EnumToken } from "./types.ts"; -import { walk, walkValues } from "./walk.ts"; -import { PARENT, TOKENS } from "../syntax/constants.ts"; +import type {Token} from "../../@types/token.d.ts"; +import type {AstDeclaration, AstNode, AstValueMatcher, TokenSearchResult} from "../../@types/ast.d.ts"; +import {EnumToken} from "./types.ts"; +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' diff --git a/src/lib/ast/minify.ts b/src/lib/ast/minify.ts index 26c93106..5498a5b0 100644 --- a/src/lib/ast/minify.ts +++ b/src/lib/ast/minify.ts @@ -1,7 +1,7 @@ -import { eq } from "../parser/utils/eq.ts"; -import { doRender, renderValue } from "../renderer/render.ts"; +import {eq} from "../parser/utils/eq.ts"; +import {doRender, renderValue} from "../renderer/render.ts"; import * as allFeatures from "./features/index.ts"; -import { walkValues } from "./walk.ts"; +import {walkValues} from "./walk.ts"; import type { AstAtRule, AstDeclaration, @@ -24,15 +24,15 @@ import type { RawSelectorTokens, Token, } from "../../@types/index.d.ts"; -import { EnumToken } from "./types.ts"; -import { isFunction, isIdent, isIdentStart, isWhiteSpace } from "../syntax/syntax.ts"; -import { FeatureWalkMode } from "./features/type.ts"; -import { trimArray } from "../validation/match.ts"; -import { combinators, LOC, OPTIMIZED, PARENT, RAW, TOKENS } from "../syntax/constants.ts"; -import { replaceNodeOrValue } from "../parser/utils/token.ts"; -import { parseString } from "../parser/parse.ts"; -import { tokenize } from "../parser/tokenize.ts"; -import { replaceCompound } from "./expand.ts"; +import {EnumToken} from "./types.ts"; +import {isFunction, isIdent, isIdentStart, isWhiteSpace} from "../syntax/syntax.ts"; +import {FeatureWalkMode} from "./features/type.ts"; +import {trimArray} from "../validation/match.ts"; +import {combinators, LOC, OPTIMIZED, PARENT, RAW, TOKENS} from "../syntax/constants.ts"; +import {replaceNodeOrValue} from "../parser/utils/token.ts"; +import {parseString} from "../parser/parse.ts"; +import {tokenize} from "../parser/tokenize.ts"; +import {replaceCompound} from "./expand.ts"; const notEndingWith: string[] = ["(", "["].concat(combinators); const rules: EnumToken[] = [ @@ -72,11 +72,12 @@ export function minify( * @param errors * @param nestingContent * + * @param context * @private */ export function minify( ast: AstNode, - options: ParserOptions | MinifyFeatureOptions = {}, + opt: ParserOptions | MinifyFeatureOptions = {}, recursive: boolean = false, errors?: ErrorDescription[], nestingContent?: boolean, @@ -89,6 +90,9 @@ export function minify( let parents: Set; let replacement: AstNode | null; + // @ts-ignore + let {sourcemap, module, ...options} = opt; + if (!("features" in options)) { // @ts-ignore options = { @@ -357,9 +361,9 @@ function transformAtRuleMediaPrelude(values: Token[]) { * Minify at-rule media * - remove redundant tokens * - generate range queries - * @param ast * * @private + * @param tokens */ function minifyAtRuleMedia(tokens: Token[]): Token[] { let hasUpdates: boolean = false; @@ -496,7 +500,6 @@ function doMinify( while (previous?.typ === EnumToken.CommentNodeType) { previous = ast.chi[--nodeIndex]; - continue; } node = ast.chi![i] as AstNode; @@ -1107,7 +1110,9 @@ export function optimizeSelector(selector: string[][]): OptimizedSelector | null break; } - selector.forEach((selector: string[]) => selector.splice(0, optimized.length)); + for (let i1 = 0; i1 < selector.length; i1++) { + selector[i1].splice(0, optimized.length); + } let reducible: boolean = optimized.length == 1; @@ -1591,7 +1596,6 @@ function wrapNodes( * Diff nodes * @param n1 * @param n2 - * @param reducer * @param options * * @private @@ -1744,20 +1748,48 @@ function diff(n1: AstRule, n2: AstRule, options: ParserOptions = {}) { chi: intersect.reverse(), }; + let op = {level: 0, ...options}; + if ( result == null || [n1, n2].reduce((acc: number, curr: AstRule): number => { let css: string = options.cache!.get(curr) as string; if (css == null) { - css = doRender(curr, options).code; + let level: number = 0; + let parent: AstNode | null = curr[PARENT]; + + while (parent != null && parent.typ != EnumToken.StyleSheetNodeType) { + level++; + parent = parent[PARENT] as AstRule; + } + + op.level = level; + + css = doRender(curr, op).code; options.cache!.set(curr, css); } return curr.chi.length == 0 ? acc : acc + css.length; }, 0) <= [node1, node2, result].reduce((acc: number, curr: AstRule): number => { - const css = doRender(curr, options).code; + + let css: string = options.cache!.get(curr) as string; + + if (css != null) { + return curr.chi.length == 0 ? acc : acc + css.length + } + + let level: number = 0; + let parent: AstNode | null = curr[PARENT]; + + while (parent != null && parent.typ != EnumToken.StyleSheetNodeType) { + level++; + parent = parent[PARENT] as AstRule; + } + + op.level = level; + css = doRender(curr, op).code; return curr.chi.length == 0 ? acc : acc + css.length; }, 0) diff --git a/src/lib/fs/resolve.ts b/src/lib/fs/resolve.ts index 08428858..057e0874 100644 --- a/src/lib/fs/resolve.ts +++ b/src/lib/fs/resolve.ts @@ -1,5 +1,8 @@ import { memoize } from "../parser/utils/cache.ts"; +/** + * match url + */ export const matchUrl: RegExp = /^(https?:)?\/\//; /** @@ -13,6 +16,10 @@ export function dirname(path: string): string { return ""; } + if (path.startsWith("data:")) { + return path; + } + let i: number = 0; let parts: string[] = [""]; @@ -42,11 +49,7 @@ function splitPath(result: string): { i: number; parts: string[] } { return { parts: [], i: 0 }; } - // if (result === "/") { - // return { parts: ["/"], i: 0 }; - // } - - const parts: string[] = [""]; + const parts: string[] = result == "/" ? [] : [""]; let i: number = 0; for (; i < result.length; i++) { @@ -54,10 +57,10 @@ function splitPath(result: string): { i: number; parts: string[] } { if (chr == "/") { parts.push(""); - } + } // else if (chr == "?" || chr == "#") { // break; - // } + // } else { parts[parts.length - 1] += chr; } @@ -79,6 +82,8 @@ function splitPath(result: string): { i: number; parts: string[] } { /** * Nomalize path + * @param path + * @private */ export const normalize = memoize(function (path: string) { let parts: string[] = []; @@ -111,8 +116,8 @@ export const normalize = memoize(function (path: string) { while (++k < parts.length) { // if (parts[k] == ".") { // parts.splice(k--, 1); - // } else - if (parts[k] == "..") { + // } else + if (k > 0 && parts[k] == "..") { parts.splice(k - 1, 2); k -= 2; } @@ -121,9 +126,16 @@ export const normalize = memoize(function (path: string) { return (path.charAt(0) == "/" ? "/" : "") + parts.join("/"); }); +/** + * diff path + * @param path1 + * @param path2 + * @private + */ export const diff = memoize(function (path1: string, path2: string) { let { parts } = splitPath(path1); const { parts: dirs } = splitPath(path2); + for (const p of dirs) { if (parts[0] == p) { parts.shift(); @@ -148,7 +160,6 @@ export const resolve = memoize(function ( currentDirectory: string, cwd?: string, ): { absolute: string; relative: string } { - if (matchUrl.test(url)) { return { absolute: url, @@ -160,39 +171,57 @@ export const resolve = memoize(function ( currentDirectory ??= ""; url = normalize(url); - + + if (cwd !== "") { + cwd = normalize(cwd); + } if (currentDirectory !== "") { currentDirectory = normalize(currentDirectory); - - if (url.startsWith(currentDirectory + "/")) { - return { - absolute: url, - relative: url.slice(currentDirectory.length + 1), - }; - } } - if ((currentDirectory === "" || currentDirectory === ".") && cwd !== "") { - cwd = normalize(cwd); + const dir = cwd || currentDirectory; + const absolute = dir == "" || url.startsWith("/") ? resolvePath(url) : resolvePath(dir, url); - if (url.startsWith(cwd == "/" ? cwd : cwd + "/")) { - const absolute: string = url; - const prefix: string = cwd == "/" ? cwd : cwd + "/"; + return { + absolute, + relative: dir === "" ? absolute : diff(absolute, dir), + }; +}) as (url: string, currentDirectory?: string, cwd?: string) => { absolute: string; relative: string }; - return { - absolute, - relative: absolute.startsWith(prefix) ? absolute.slice(prefix.length) : diff(absolute, cwd), - }; +/** + * + * @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; + } + + 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/linesmap.ts b/src/lib/parser/linesmap.ts index c59070b6..8f5fa34b 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); } @@ -32,9 +32,8 @@ export class LineMap { } const column: number = offset - this.lineStarts[line]; - // [line, column] - return [line + 1, column === 0 ? 1 : column]; + return [line + 1, line === 0 ? column + 1 : column]; } /** diff --git a/src/lib/parser/parse.ts b/src/lib/parser/parse.ts index 2285cb55..6e85e4d9 100644 --- a/src/lib/parser/parse.ts +++ b/src/lib/parser/parse.ts @@ -29,23 +29,23 @@ import type { FunctionToken, GenericVisitorAstNodeHandlerMap, GenericVisitorHandler, + GenericVisitorResult, IdentToken, LoadResult, - SourceLocation, ModuleSyncOptions, ParseInfo, ParseResult, ParseResultStats, ParserOptions, + ParserSyncOptions, PseudoClassToken, ResolvedPath, + SourceLocation, StringToken, Token, TokenizeResult, UrlToken, 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 +67,7 @@ import { parseAtRuleFontFeatureValues } from "./utils/at-rule-font-feature-value import { matchGenericSyntax } from "./utils/at-rule-generic.ts"; import { memoize } from "./utils/cache.ts"; import { SourceFile } from "./source.ts"; +import { dirname } from "../fs/resolve.ts"; function renderTokens(tokens: Token[] | null | undefined, options?: any): string { if (tokens == null || tokens.length === 0) return ""; @@ -90,9 +91,7 @@ const BadTokensTypes: EnumToken[] = [ EnumToken.BadUrlTokenType, EnumToken.BadStringTokenType, ]; - -export const atRulesMap: Map = new Map([["keyframes", EnumToken.KeyframesAtRuleNodeType]]); - +new Map([["keyframes", EnumToken.KeyframesAtRuleNodeType]]); let keyNameCounter: number = 0; const forbiddenStartCharacters: number[] = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"].map((c) => c.charCodeAt(0), @@ -110,8 +109,6 @@ const forbiddenStartCharacters: number[] = ["0", "1", "2", "3", "4", "5", "6", " */ export const getShortNameGenerator = memoize( (localName: string, filePath: string, pattern: string, hashLength = 5): string => { - const key = `${localName}_${filePath}_${pattern}_${hashLength}`; - let value: string = keyNameCounter!.toString(36); keyNameCounter!++; @@ -517,8 +514,6 @@ export function doParseSync( Array | Record>>> >; - const imports: AstAtRule[] = []; - let item: TokenizeResult; let node: AstAtRule | AstRule | AstKeyFrameRule | AstKeyframesAtRule | AstDeclaration | AstComment | null; @@ -717,8 +712,6 @@ export function doParseSync( if ("chi" in node) { stack.push(node as AstAtRule | AstRule | AstKeyFrameRule); 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; @@ -781,10 +774,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; @@ -889,7 +878,7 @@ export function doParseSync( continue; } - if (replacement == null || replacement == node) { + if (replacement == node) { continue; } @@ -961,7 +950,7 @@ export function doParseSync( continue; } - if (replacement == null || replacement == node) { + if (replacement == node) { continue; } @@ -1006,7 +995,7 @@ export function doParseSync( continue; } - if (replacement != null && replacement != node) { + if (replacement != node) { node = replacement as AstNode; } } @@ -1048,7 +1037,7 @@ export function doParseSync( continue; } - if (result != null && result != node) { + if (result != node) { node = result as Token; } @@ -1273,7 +1262,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 +1271,6 @@ export function doParseSync( moduleSettings.pattern as string, moduleSettings.hashLength, ); - let value: string = result; mapping[node.nam] = "--" + @@ -1336,7 +1324,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 +1333,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 +1539,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 +1548,6 @@ export function doParseSync( moduleSettings.pattern as string, moduleSettings.hashLength, ); - mapping[(value as IdentToken).val] = result; revMapping[mapping[(value as IdentToken).val]] = (value as IdentToken).val; } @@ -1658,7 +1644,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 +1653,6 @@ export function doParseSync( moduleSettings.pattern as string, moduleSettings.hashLength, ); - let value: string = result; mapping[val] = moduleSettings.naming! & ModuleCaseTransformEnum.DashCaseOnly || @@ -1711,7 +1696,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 +1705,6 @@ export function doParseSync( moduleSettings.pattern as string, moduleSettings.hashLength, ); - let val: string = result; mapping[(value as DashedIdentToken | IdentToken).val] = prefix + @@ -2036,6 +2020,7 @@ export async function doParse( : // @ts-expect-error ((iter as Iterator).next().value as TokenizeResult)) ) { + stats.bytesIn = item.bytesIn; stats.tokensCount++; @@ -2171,7 +2156,7 @@ 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" @@ -2798,6 +2783,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) { @@ -2887,9 +2874,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; @@ -3216,28 +3205,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; @@ -3257,13 +3224,6 @@ export async function doParse( ); break; - // (parent as AstRule)[TOKENS]!.splice( - // (parent as AstRule)[TOKENS]!.indexOf(value), - // 1, - // ...(value as FunctionToken).chi, - // ); - - // break; } } }, @@ -3652,6 +3612,8 @@ function parseNode( } /** + * @param stream + * @param context * @param options * @param errors * @param parseAsBlock @@ -3750,7 +3712,6 @@ export function parseAtRule( } if (syntax != null && atRule.nam !== "layer" && parseAsBlock !== blockAllowed) { - success = false; errors.push({ action: "drop", node: atRule, @@ -4333,7 +4294,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); @@ -4498,9 +4459,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; diff --git a/src/lib/parser/source.ts b/src/lib/parser/source.ts index 9ef29a73..a65ee98d 100644 --- a/src/lib/parser/source.ts +++ b/src/lib/parser/source.ts @@ -1,5 +1,7 @@ +import type { SourceMapObject } from "../../@types/index.d.ts"; +import { SourceMap } from "../renderer/sourcemap/sourcemap.ts"; import { LineMap } from "./linesmap.ts"; -import type {SourceLocation} from "../../@types/ast.d.ts"; + /** * Source file ID */ @@ -9,6 +11,7 @@ let sourceId: number = 0; * Source file helper class */ export class SourceFile { + private inputSourceMap: SourceMap | null = null; /** * Source file ID @@ -29,10 +32,9 @@ export class SourceFile { /** * Constructor - * @param id - * @param content - * @param lines - * @param file + * @param content + * @param lines + * @param file */ constructor(content: string, lines: number[], file: string | null = null) { this.id = sourceId++; @@ -43,8 +45,7 @@ export class SourceFile { /** * Update source content - * @param content - * @param lines + * @param content */ append(content: string) { this.content += content; @@ -52,16 +53,15 @@ export class SourceFile { /** * get file name - * @returns + * @returns */ getFileName(): string | null { - return this.file; } /** * get content - * @returns + * @returns */ getContent(): string { return this.content; @@ -69,9 +69,9 @@ export class SourceFile { /** * get text - * @param start - * @param length - * @returns + * @param start + * @param length + * @returns */ getText(start: number, length: number): string { return this.content.slice(start, start + length); @@ -79,8 +79,8 @@ export class SourceFile { /** * Compute line and column of the offset - * @param offset - * @returns + * @param offset + * @returns */ getOffsets(offset: number): [number, number] { return this.lineStarts.getOffsets(offset); @@ -88,26 +88,42 @@ export class SourceFile { /** * get source location - * @param offset - * @returns + * @param offset + * @returns */ getSourceLocation(offset: number): [string | null, number, number] { - return [this.file, ... this.getOffsets(offset)]; + return [this.file, ...this.getOffsets(offset)]; } /** * get line starts - * @returns + * @returns */ getLineStarts(): number[] { return this.lineStarts.getLineStarts(); } - + /** * add line start - * @param lineStart + * @param lineStart */ addLineStart(lineStart: number) { this.lineStarts.addLineStart(lineStart); } + + /** + * set input source map + * @param inputSourceMap + */ + setInputSourceMap(inputSourceMap: SourceMapObject | string | null) { + this.inputSourceMap = inputSourceMap == null ? null : new SourceMap(inputSourceMap as SourceMapObject | string); + } + + /** + * return input source map + * @returns + */ + getInputSourceMap(): SourceMap | null { + return this.inputSourceMap; + } } diff --git a/src/lib/parser/tokenize.ts b/src/lib/parser/tokenize.ts index 42071314..865ecc9e 100644 --- a/src/lib/parser/tokenize.ts +++ b/src/lib/parser/tokenize.ts @@ -477,7 +477,7 @@ export function next(parseInfo: ParseInfo, count: number = 1): string { } /** - * Tokenize css string + * Tokenize CSS string * @param parseInfo * @param yieldEOFToken */ @@ -495,7 +495,6 @@ export function tokenize(parseInfo: ParseInfo | string, yieldEOFToken: boolean = } let value: string; - let nextValue: string; let buffer: string = parseInfo.buffer; let charCode: number; let nextCharCode: number; @@ -509,9 +508,6 @@ export function tokenize(parseInfo: ParseInfo | string, yieldEOFToken: boolean = while ((value = peek(parseInfo))) { charCode = value.charCodeAt(0); - // nextCharCode = nextValue.charCodeAt(0); - - // console.debug({value, buffer}); switch (charCode) { case TokenMap.EQUALS: @@ -962,7 +958,7 @@ export function tokenize(parseInfo: ParseInfo | string, yieldEOFToken: boolean = next(parseInfo); // EOF - if (!(nextValue = peek(parseInfo))) { + if (!(peek(parseInfo))) { // end of stream ignore \\ if (buffer.length > 0) { result.push(yieldResult(buffer, parseInfo)); @@ -973,11 +969,6 @@ export function tokenize(parseInfo: ParseInfo | string, yieldEOFToken: boolean = } buffer += value + next(parseInfo); - - // buffer += - // (parseInfo.offset == parseInfo.currentPosition - // ? parseInfo.buffer.slice(-1) - // : parseInfo.stream.charAt(parseInfo.currentPosition - parseInfo.offset - 1)) + value; break; case TokenMap.SINGLE_QUOTE: diff --git a/src/lib/renderer/render.ts b/src/lib/renderer/render.ts index 1c1387da..c87f1742 100644 --- a/src/lib/renderer/render.ts +++ b/src/lib/renderer/render.ts @@ -28,7 +28,6 @@ import type { LengthToken, ListToken, LiteralToken, - SourceLocation, MediaFeatureToken, MediaQueryConditionToken, MediaQueryUnaryFeatureToken, @@ -39,6 +38,7 @@ import type { PseudoPageToken, RenderOptions, RenderResult, + SourceLocation, StringToken, SupportsQueryConditionToken, SupportsQueryUnaryConditionToken, @@ -47,19 +47,18 @@ import type { WhenElseUnaryConditionToken, WrappedValuesToken, } from "../../@types/index.d.ts"; -import { convertColor } from "../syntax/color/color.ts"; -import { getAngle } from "../syntax/color/color.ts"; +import { convertColor, getAngle } from "../syntax/color/color.ts"; import { reduceHexValue } from "../syntax/color/hex.ts"; import { ColorType, EnumToken } from "../ast/types.ts"; import { expand } from "../ast/expand.ts"; import { SourceMap } from "./sourcemap/sourcemap.ts"; import { colorPrecision, LOC, PARENT, pseudoElements, tokensfuncSet, urlTokenMatcher } from "../syntax/constants.ts"; import { + minifyNumber, parseColor, - reducegradientBackgroundPosition, reduceColorStops, reduceConicColorStops, - minifyNumber, + reducegradientBackgroundPosition, toPrecisionAngle, toPrecisionValue, } from "../syntax/syntax.ts"; @@ -67,6 +66,7 @@ import { equalsIgnoreCase } from "../parser/utils/text.ts"; import { toDegrees } from "../parser/utils/angle.ts"; import { LineMap as LinesMap } from "../parser/linesmap.ts"; import { dirname } from "../fs/resolve.ts"; +import { SourceFile } from "../parser/source.ts"; /** * render ast @@ -133,6 +133,8 @@ export function doRender( const startTime: number = performance.now(); const errors: ErrorDescription[] = []; const sourcemap: SourceMap | null = options.sourcemap ? new SourceMap() : null; + const sourcemaps: Array<[number, number, number, number, number, string | null, string | null]> | null = + options.sourcemap ? [] : null; const cache: { [key: string]: any; } = Object.create(null); @@ -142,13 +144,24 @@ export function doRender( sta: 0, end: 0, } as SourceLocation; - const linesMap = new LinesMap([]); + const linesMap: LinesMap | null = options.sourcemap ? new LinesMap() : null; let code: string = ""; if (mapping != null) { if (mapping.importMapping != null) { - for (const [key, value] of Object.entries(mapping.importMapping)) { + const absolutePath = options.resolve!( + options.output != null ? dirname(options.output as string) : dirname(options.src as string), + options.cwd as string, + ).absolute; + + for (let [key, value] of Object.entries(mapping.importMapping)) { + key = options.resolve!(options.resolve!(key, options.cwd as string).absolute, absolutePath).relative; + + if (!key.startsWith("/") && !key.startsWith(".")) { + key = "./" + key; + } + code += `:import("${key}")${options.indent}{${options.newLine}${Object.entries(value).reduce( (acc, [k, v]) => acc + (acc.length > 0 ? options.newLine : "") + `${options.indent}${v}:${options.indent}${k};`, @@ -162,7 +175,10 @@ export function doRender( acc + (acc.length > 0 ? options.newLine : "") + `${options.indent}${k}:${options.indent}${v};`, "", )}${options.newLine}}${options.newLine}`; - move(sourceLocation, linesMap, code); + + if (sourcemap != null) { + move(sourceLocation, linesMap!, code); + } } if (options.output != null) { @@ -182,7 +198,7 @@ export function doRender( ? expand(data as AstStyleSheet | AstAtRule | AstRule) : data, options, - sourcemap, + sourcemaps, sourceLocation, linesMap, errors, @@ -206,6 +222,7 @@ export function doRender( }; if (sourcemap != null) { + sourcemap.addAll(sourcemaps!); result.map = sourcemap; if (options.sourcemap === "inline") { @@ -221,8 +238,9 @@ export function doRender( * @param node * @param options * @param cache - * @param sourcemap - * @param position + * @param sourcemaps + * @param sourceLocation + * @param linesMap * @param str * * @internal @@ -233,12 +251,33 @@ function updateSourceMap( cache: { [p: string]: any; }, - sourcemap: SourceMap, + sourcemaps: Array<[number, number, number, number, number, string | null, string | null]>, sourceLocation: SourceLocation, linesMap: LinesMap, str: string, ) { + let offset: number = 0; + + while (true) { + if (str.charAt(offset) == options.newLine) { + offset += options.newLine.length; + continue; + } + + if (str.charAt(offset) == options.indent) { + offset += options.indent.length; + continue; + } + + break; + } + + if (offset > 0) { + move(sourceLocation, linesMap, str.slice(0, offset)); + } + if ( + node[LOC] != null && [ EnumToken.RuleNodeType, EnumToken.AtRuleNodeType, @@ -246,35 +285,71 @@ function updateSourceMap( 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; + } - let sourceFileName: string | null = (options.sourcesMap?.get(srcId)?.getFileName?.() as string) || null; + sourcemaps.push([newLine, newColumn, srcId, ...offsets, sourceFileName as string, sourceContent]); + } + } else { + if (sourceFileName != null && options.output != null && !sourceFileName.startsWith("data:")) { + if (cache[sourceFileName] == null) { + const absolute = options.resolve!(dirname(options.output as string), options.cwd as string) + .absolute as string; + const absoluteSourceFileName = options.resolve!(sourceFileName, options.cwd as string) + .absolute as string; + + cache[sourceFileName] = options.resolve!(absoluteSourceFileName, absolute).relative as string; + } - 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; + sourcemaps.push([newLine, newColumn, srcId, ...offsets, sourceFileName as string, sourceContent]); } - - // @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, - ); } - move(sourceLocation, linesMap, str); + move(sourceLocation, linesMap, offset > 0 ? str.slice(offset) : str); } /** * Update position - * @param position + * @param sourceLocation + * @param linesMap * @param str */ export function move(sourceLocation: SourceLocation, linesMap: LinesMap, str: string) { @@ -310,8 +385,9 @@ export function move(sourceLocation: SourceLocation, linesMap: LinesMap, str: st * render ast node * @param data * @param options - * @param sourcemap - * @param position + * @param sourcemaps + * @param sourceLocation + * @param linesMap * @param errors * @param reducer * @param cache @@ -323,9 +399,9 @@ export function move(sourceLocation: SourceLocation, linesMap: LinesMap, str: st function renderAstNode( data: AstNode, options: RenderOptions, - sourcemap: SourceMap | null, + sourcemaps: Array<[number, number, number, number, number, string | null, string | null]> | null, sourceLocation: SourceLocation, - linesMap: LinesMap, + linesMap: LinesMap | null, errors: ErrorDescription[], reducer: (acc: string, curr: Token) => string, cache: { @@ -342,6 +418,11 @@ function renderAstNode( indents.push((options.indent).repeat(level + 1)); } + // @ts-ignore + let children: string = ""; + let str: string = ""; + let previousStr: string = ""; + const indent: string = indents[level]; const indentSub: string = indents[level + 1]; @@ -364,13 +445,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,27 +460,17 @@ 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; + } - return `${css}${options.newLine}${str}`; - }, ""); + return children; case EnumToken.AtRuleNodeType: case EnumToken.RuleNodeType: @@ -413,10 +482,20 @@ function renderAstNode( };`; } - // @ts-ignore - let children: string = (data).chi.reduce((css: string, node: AstNode) => { - let str: string; + const prelude = [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 k: number = (data as AstRule | AstAtRule).chi!.length - 1; + 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 && @@ -440,15 +519,29 @@ function renderAstNode( ) .reduce(reducer, "") .trimEnd()};`; + + if (sourcemaps != null) { + if (previousStr.length > 0) { + move(sourceLocation, linesMap!, previousStr); + } + } + + previousStr = str === "" ? "" : options.newLine + indentSub + str; } // else if (node.typ == EnumToken.AtRuleNodeType && !("chi" in node)) { // str = `${(node).val === "" ? "" : options.indent || " "}${(node).val};`; // } else { + if (sourcemaps != null) { + if (previousStr.length > 0) { + move(sourceLocation, linesMap!, previousStr); + } + } + str = renderAstNode( node, options, - sourcemap, + sourcemaps, sourceLocation, linesMap, errors, @@ -457,53 +550,36 @@ function renderAstNode( level + 1, indents, ); - } - if (css === "") { - return str; + previousStr = ""; } 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.endsWith(";") ? str.slice(0, -1) : str); } if (children.endsWith(";")) { children = children.slice(0, -1); } + if (options.removeEmpty && children === "") { + 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 as AstRuleList, - options, - cache, - sourcemap, - { ...sourceLocation }, - linesMap.clone(), - rendered, - ); + const end: string = options.newLine + indent + `}`; + + if (sourcemaps != null) { + move(sourceLocation, linesMap!, end); } - return rendered; + return prelude + children + end; // case EnumToken.CssVariableTokenType: // case EnumToken.CssVariableImportTokenType: @@ -535,6 +611,9 @@ function renderAstNode( * render ast token * @param token * @param options + * @param cache + * @param reducer + * @param errors * @private */ export function renderValue( diff --git a/src/lib/renderer/sourcemap/lib/encode.ts b/src/lib/renderer/sourcemap/lib/codec.ts similarity index 52% rename from src/lib/renderer/sourcemap/lib/encode.ts rename to src/lib/renderer/sourcemap/lib/codec.ts index ead4a1f1..fd45e13a 100644 --- a/src/lib/renderer/sourcemap/lib/encode.ts +++ b/src/lib/renderer/sourcemap/lib/codec.ts @@ -1,13 +1,61 @@ // from https://github.com/Rich-Harris/vlq/tree/master // credit: Rich Harris const integer_to_char: { [key: number]: string } = {}; - +const char_to_integer: { [key: string]: number } = {}; let i = 0; for (const char of 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=') { + char_to_integer[char] = i; integer_to_char[i++] = char; } +/** + * @param {string} str + */ +export function decode(str: string) { + /** @type {number[]} */ + let result = []; + + let shift = 0; + let value = 0; + + for (let i = 0; i < str.length; i += 1) { + let integer = char_to_integer[str[i]]; + + if (integer === undefined) { + throw new Error('Invalid character (' + str[i] + ')'); + } + + const has_continuation_bit = integer & 32; + + integer &= 31; + value += integer << shift; + + if (has_continuation_bit) { + shift += 5; + } else { + const should_negate = value & 1; + value >>>= 1; + + if (should_negate) { + result.push(value === 0 ? -0x80000000 : -value); + } else { + result.push(value); + } + + // reset + value = shift = 0; + } + } + + return result; +} + +/** + * + * @param value + * @returns + */ export function encode(value: number | number[]) { if (typeof value === 'number') { return encode_integer(value); diff --git a/src/lib/renderer/sourcemap/sourcemap.ts b/src/lib/renderer/sourcemap/sourcemap.ts index d846ccd5..a53747fc 100644 --- a/src/lib/renderer/sourcemap/sourcemap.ts +++ b/src/lib/renderer/sourcemap/sourcemap.ts @@ -1,11 +1,16 @@ import type { SourceMapObject } from "../../../@types/index.d.ts"; -import { encode } from "./lib/encode.ts"; +import { decode, encode } from "./lib/codec.ts"; /** - * Source map class - * @internal + * Generate and parse source map */ export class SourceMap { + /** + * + * @private + */ + private keys: Set = new Set(); + /** * Last location */ @@ -20,19 +25,34 @@ export class SourceMap { * Sources map * @private */ - private sourcesMap: number[] = []; + private sourcesMap: string[] = []; + + /** + * Sources content + * @private + */ + private readonly sourcesContent: Array = []; /** * Sources * @private */ - private sources: Array = []; + private readonly sources: Array = []; /** * Map * @private + * */ private map: Map = new Map(); + + /** + * Map + * @private + * + */ + private reverseMap: Map = new Map(); + /** * Line * @private @@ -40,60 +60,195 @@ export class SourceMap { private line: number = -1; /** - * Add a location - * @param source - * @param original - */ - add( - newLine: number, - newColumn: number, - srcId: number, - ln: number, - col: number, - sourceFileName: string, - sourceContent: string, - ) { - if (!this.sourcesMap.includes(srcId)) { - if (sourceFileName == null && sourceContent != null) { - sourceFileName = "data:text/css;charset=utf-8;base64," + btoa(sourceContent); + * + */ + constructor(); + /** + * + * @param sourcemaps + */ + constructor(sourcemaps: string | SourceMapObject); + /** + * + * @param sourcemaps + */ + constructor(sourcemaps?: SourceMapObject | string) { + if (typeof sourcemaps === "string") { + sourcemaps = JSON.parse(sourcemaps) as SourceMapObject; + } + + if (sourcemaps != null) { + this.sources = sourcemaps.sources?.slice() ?? []; + this.sourcesContent = sourcemaps.sourcesContent?.slice() ?? []; + const decodedMappings = sourcemaps.mappings + .split(";") + .map((mapping) => mapping.split(",").map((mapping) => decode(mapping))) as number[][][]; + + this.line = decodedMappings.length - 1; + + for (let index = 0; index < decodedMappings.length; index++) { + if ( + decodedMappings[index].length == 0 || + (decodedMappings[index].length == 1 && decodedMappings[index][0].length == 0) + ) { + continue; + } + + this.map.set(index, decodedMappings[index]); } - this.sourcesMap.push(srcId); - this.sources.push((sourceFileName as string) || null); + this.computePositions(); } + } + + /** + * Add all location + * @param maps + */ + addAll(maps: Array<[number, number, number, number, number, string | null, string | null]>): void { + for (let [newLine, newColumn, srcId, ln, col, sourceFileName, sourceContent] of maps) { + const key = `${srcId}:${ln}:${sourceFileName}:${col}:${newLine}:${newColumn}:${sourceContent}`; + const sourcemap = `${srcId}:${sourceFileName}:${sourceContent}`; + + if (this.keys.has(key)) { + continue; + } + + this.keys.add(key); + + if (!this.sourcesMap.includes(sourcemap)) { + this.sourcesMap.push(sourcemap); + this.sources.push((sourceFileName as string) || null); + this.sourcesContent.push((sourceFileName != null ? null : sourceContent) || null); + } + + const line: number = newLine - 1; + let record: number[]; + + if (line > this.line) { + this.line = line; + } + + if (!this.map.has(line)) { + record = [Math.max(0, newColumn - 1), this.sourcesMap.indexOf(sourcemap), ln - 1, col - 1]; - const line = newLine - 1; - let record: number[]; + this.map.set(line, [record]); + } else { + const arr: number[][] = this.map.get(line) as number[][]; + + record = [ + Math.max(0, newColumn - 1) - arr[0][0], + this.sourcesMap.indexOf(sourcemap) - arr[0][1], + ln - 1, + col - 1, + ]; + arr.push(record); + } - if (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[]; + + // mappings to original source + for (let [i, line] of this.map.entries()) { + if (line.length === 0 || (line.length === 1 && line[0].length === 0)) { + continue; + } + + generatedCodeColumn = line[0][0]; // first field - reset each time + + line = line + .map((segment: number[], index: number, array: number[][]) => { + if (segment.length === 0) { + return []; + } + + generatedCodeColumn = index == 0 ? segment[0] : segment[0] + array[0][0]; + + result = [generatedCodeColumn]; + + if (segment.length <= 1) { + return result; + } - if (!this.map.has(line)) { - record = [Math.max(0, newColumn - 1), this.sourcesMap.indexOf(srcId), ln - 1, col - 1]; + sourceFileIndex = index == 0 ? segment[1] : segment[1] + array[0][1]; + sourceCodeLine += segment[2]; + sourceCodeColumn += segment[3]; - this.map.set(line, [record]); - } else { - const arr: number[][] = this.map.get(line); + result.push(sourceFileIndex, sourceCodeLine, sourceCodeColumn); - record = [ - Math.max(0, newColumn - 1 - arr[0][0]), - this.sourcesMap.indexOf(srcId) - arr[0][1], - ln - 1, - col - 1, - ]; - arr.push(record); + if (segment.length === 5) { + nameIndex += segment[4]; + result.push(nameIndex); + } + + return result; + }) + .sort((a, b) => { + if (a[1] !== b[1]) { + return a[1] - b[1]; + } + + return a[0] - b[0]; + }); + + if (line.length == 0 || (line.length == 1 && line[0].length == 0)) { + continue; + } + + this.reverseMap.set(i, line); } + } - if (this.lastLocation != null) { - record[2] -= this.lastLocation.ln - 1; - record[3] -= this.lastLocation.col - 1; + /** + * retrieve original sources, lines and columns + * @param line generated line + * @param column generated column + */ + find(line: number, column: number): Array<[string | null, number, number, string | null]> | null { + if (!this.reverseMap.has(--line)) { + return null; } - this.lastLocation ??= { ln, col }; + column--; + const result: Array<[string | null, number, number, string | null]> = []; - this.lastLocation.ln = ln; - this.lastLocation.col = col; + for (const record of this.reverseMap.get(line)!) { + if (record.length == 0 || record[0] < column) { + continue; + } + if (record[0] > column) { + break; + } + + result.push([ + this.sources?.[record[1]] ?? null, + record[2] + 1, + record[3] + 1, + this.sourcesContent?.[record[1]] ?? null, + ]); + } + + return result.length == 0 ? null : result; } /** @@ -128,7 +283,15 @@ export class SourceMap { return { version: this.version, sources: this.sources.slice(), + sourcesContent: this.sourcesContent?.slice(), mappings: mappings.join(";"), }; } + + /** + * to string + */ + toString(): string { + return JSON.stringify(this); + } } diff --git a/src/lib/validation/match.ts b/src/lib/validation/match.ts index a32f0795..0458949b 100644 --- a/src/lib/validation/match.ts +++ b/src/lib/validation/match.ts @@ -1323,7 +1323,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(); diff --git a/src/node.ts b/src/node.ts index b762c3bd..56ee1a39 100644 --- a/src/node.ts +++ b/src/node.ts @@ -1,4 +1,5 @@ import type { + AstComment, AstNode, LoadResult, ParseInfo, @@ -20,13 +21,14 @@ import { createReadStream } from "node:fs"; import { lstat, readFile } from "node:fs/promises"; import { doParse, doParseSync } from "./lib/parser/parse.ts"; import { doRender } from "./lib/renderer/render.ts"; -import { ModuleScopeEnumOptions } from "./lib/ast/types.ts"; +import { EnumToken, ModuleScopeEnumOptions } from "./lib/ast/types.ts"; import { tokenize, tokenizeStream } from "./lib/parser/tokenize.ts"; import { dirname, matchUrl, resolve } from "./lib/fs/resolve.ts"; 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 } from "./utils.ts"; export type * from "./@types/index.d.ts"; export type * from "./@types/ast.d.ts"; @@ -230,7 +232,6 @@ export function parseSync(stream: string, options?: ParserSyncOptions): ParseRes /** * Parse css string - * @param stream * @param options * * Parsing a string @@ -283,7 +284,7 @@ export function parseSync( options ??= {}; options.src ??= ""; - options.sourcesMap ??= new Map; + options.sourcesMap ??= new Map(); Object.assign(options, { resolve, @@ -310,10 +311,8 @@ export function parseSync( currentPosition: -1, } as ParseInfo; - const result = doParseSync(tokenize(options.parseInfo), options); - - const { revMapping, ...res } = result; - return res as ParseResult; + const result = doParseSync(tokenize(options.parseInfo), options) as ParseResult; + return !options.module && !options.inputSourceMap ? result : parseResult(result, options); } /** @@ -352,8 +351,6 @@ export function transformSync(options: ParseInputOptions & TransformSyncOptions) /** * Transform css - * @param css - * @param options * * ```ts * @@ -364,6 +361,7 @@ export function transformSync(options: ParseInputOptions & TransformSyncOptions) * console.log(result.code); * ``` * + * @param args */ export function transformSync( ...args: [string, TransformSyncOptions?] | [ParseInputOptions & TransformSyncOptions] @@ -476,7 +474,6 @@ export async function parse(stream: string | ReadableStream, options /** * Parse css - * @param stream * @param options * * @throws Error file not found @@ -511,7 +508,6 @@ export async function parse(options: ParseInputFileOptions & ParserOptions): Pro /** * Parse css - * @param stream * @param options * * Parsing a string @@ -629,7 +625,7 @@ export async function parse( options ??= {}; options.src ??= ""; - options.sourcesMap ??= new Map; + options.sourcesMap ??= new Map(); Object.assign(options, { load, @@ -641,7 +637,6 @@ export async function parse( options.src = resolve(options.src!, options.cwd).relative; if (options.source == null) { - const source = new SourceFile(typeof stream == "string" ? stream : "", [], options.src); options.sourcesMap.set(source.id, source); options.source = source; @@ -661,10 +656,7 @@ export async function parse( return doParse( stream instanceof ReadableStream ? tokenizeStream(stream, options.parseInfo) : tokenize(options.parseInfo), options, - ).then((result) => { - const { revMapping, ...res } = result; - return res as ParseResult; - }); + ).then((result) => (!options.module && !options.inputSourceMap ? result : parseResult(result, options))); } /** @@ -752,7 +744,6 @@ export async function transform( /** * Transform css - * @param css * @param options * * Parsing a string @@ -798,7 +789,6 @@ export async function transform(options: ParseInputStreamOptions & TransformOpti /** * Transform css - * @param css * @param options * * Parsing a string @@ -843,8 +833,6 @@ export async function transform(options: ParseInputFileOptions & TransformOption /** * Transform css - * @param css - * @param options * * Parsing a string * @@ -883,6 +871,7 @@ export async function transform(options: ParseInputFileOptions & TransformOption * * console.log(result.code); * ``` + * @param args */ export async function transform( ...args: diff --git a/src/utils.ts b/src/utils.ts new file mode 100644 index 00000000..11387fff --- /dev/null +++ b/src/utils.ts @@ -0,0 +1,54 @@ +import type { AstComment, ParseResult, ParserOptions } from "./@types/index.d.ts"; +import { EnumToken } from "./lib/ast/types.ts"; + +/** + * parse result. process input sourcemap + * @param result + * @param options + * @returns + * @private + */ +export function parseResult(result: ParseResult, options: ParserOptions): ParseResult { + if (options.sourcemap != null && options!.source!.getInputSourceMap() == null) { + if (options.inputSourceMap != null) { + options!.source!.setInputSourceMap(options.inputSourceMap); + } else { + // extract inline source map from the input CSS + const token = result.ast.chi.at(-1); + + if ( + token?.typ == EnumToken.CommentTokenType && + (token as AstComment).val.startsWith("/*# sourceMappingURL=") + ) { + const data = (token as AstComment).val.slice(21, -2).trim(); + let sourcemap: string; + let encoding: string = ""; + + if (data.startsWith("data:")) { + let offset: number = data.indexOf(",") + 1; + + if (offset == 0) { + offset = data.lastIndexOf(";") + 1; + } else { + encoding = data.slice(data.lastIndexOf(";") + 1, offset - 1); + } + + if (encoding == "base64") { + sourcemap = atob(data.slice(offset)); + } else { + sourcemap = decodeURIComponent(data.slice(offset)); + } + + options!.source!.setInputSourceMap(sourcemap); + } + } + } + } + + if (options.module) { + const { revMapping, ...res } = result; + return res as ParseResult; + } + + return result; +} diff --git a/src/web.ts b/src/web.ts index 4f961d59..11781e44 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 } from "./utils.ts"; export type * from "./@types/index.d.ts"; export type * from "./@types/ast.d.ts"; @@ -220,7 +221,6 @@ export function parseSync(stream: string, options?: ParserSyncOptions): ParseRes /** * Parse css string - * @param stream * @param options * * Parsing a string @@ -273,7 +273,7 @@ export function parseSync( options ??= {}; options.src ??= ""; - options.sourcesMap ??= new Map; + options.sourcesMap ??= new Map(); Object.assign(options, { resolve, @@ -287,7 +287,7 @@ export function parseSync( options.src = resolve(options.src!, options.cwd).relative; if (options.source == null) { - const source = new SourceFile(typeof stream == "string" ? stream : "", [], options.src) + const source = new SourceFile(typeof stream == "string" ? stream : "", [], options.src); options.sourcesMap.set(source.id, source); options.source = source; } @@ -304,9 +304,7 @@ export function parseSync( } as ParseInfo; const result = doParseSync(tokenize(options.parseInfo), options); - - const { revMapping, ...res } = result; - return res as ParseResult; + return !options.module && !options.inputSourceMap ? result : parseResult(result, options); } /** @@ -345,8 +343,6 @@ export function transformSync(options: ParseInputOptions & TransformSyncOptions) /** * Transform css - * @param css - * @param options * * ```ts * @@ -357,6 +353,7 @@ export function transformSync(options: ParseInputOptions & TransformSyncOptions) * console.log(result.code); * ``` * + * @param args */ export function transformSync( ...args: [string, TransformSyncOptions?] | [ParseInputOptions & TransformSyncOptions] @@ -425,8 +422,6 @@ export async function parse(options: ParseInputStreamOptions & ParserOptions): P /** * Parse css - * @param stream - * @param options * * Example: * @@ -450,6 +445,7 @@ export async function parse(options: ParseInputStreamOptions & ParserOptions): P * * console.log(result.ast); * ``` + * @param args */ export async function parse( ...args: @@ -484,7 +480,7 @@ export async function parse( options ??= {}; options.src ??= ""; - options.sourcesMap ??= new Map; + options.sourcesMap ??= new Map(); Object.assign(options, { load, @@ -499,7 +495,7 @@ export async function parse( options.src = resolve(options.src!, options.cwd).relative; if (options.source == null) { - const source = new SourceFile(typeof stream === "string" ? stream : "", [], options.src) + const source = new SourceFile(typeof stream === "string" ? stream : "", [], options.src); options.sourcesMap.set(source.id, source); options.source = source; } @@ -517,10 +513,7 @@ export async function parse( return doParse( stream instanceof ReadableStream ? tokenizeStream(stream, options.parseInfo) : tokenize(options.parseInfo), options, - ).then((result) => { - const { revMapping, ...res } = result; - return res as ParseResult; - }); + ).then((result) => (!options.module && !options.inputSourceMap ? result : parseResult(result, options))); } /** @@ -571,8 +564,6 @@ export async function transform(options: ParseInputStreamOptions & TransformOpti /** * Transform css - * @param css - * @param options * * Example: * @@ -590,6 +581,7 @@ export async function transform(options: ParseInputStreamOptions & TransformOpti * * console.log(result.code); * ``` + * @param args */ export async function transform( ...args: diff --git a/test/specs/code/block.js b/test/specs/code/block.js index eb12a38c..5250c7bb 100644 --- a/test/specs/code/block.js +++ b/test/specs/code/block.js @@ -1134,10 +1134,14 @@ font-family: random-item(--x, {Times, serif}, {Arial, sans-serif}, {Courier, mon }); it("stream file #50", async () => { - const dir = resolve((import.meta.dirname ?? dirname(new URL(import.meta.url).pathname)) + "/../..").absolute; + // const dir = resolve((import.meta.dirname ?? dirname(new URL(import.meta.url).pathname)) + "/../..").absolute; // const file = `@import '${dir}/files/css/line-awesome.css`; + + const url = new URL(import.meta.url); + url.pathname = dirname(url.pathname) + "/../../files/css/bootstrap-4.css"; + const options = { - file: `${dir}/files/css/bootstrap-4.css`, + file: url.pathname , beautify: true, }; @@ -1149,10 +1153,12 @@ font-family: random-item(--x, {Times, serif}, {Arial, sans-serif}, {Courier, mon }); it("stream file #51", async () => { - const dir = resolve((import.meta.dirname ?? dirname(new URL(import.meta.url).pathname)) + "/../..").absolute; - // const file = `@import '${dir}/files/css/line-awesome.css`; + + const url = new URL(import.meta.url); + url.pathname = dirname(url.pathname) + "/../../files/css/tailwind.css"; + const options = { - file: `${dir}/files/css/tailwind.css`, + file: url.pathname, beautify: true, }; diff --git a/test/specs/code/import1.js b/test/specs/code/import1.js index 891f287e..485d7bd6 100644 --- a/test/specs/code/import1.js +++ b/test/specs/code/import1.js @@ -1,8 +1,9 @@ export function run(describe, expect, it, transform, parse, render, dirname) { + const url = new URL(dirname(import.meta.url) + '/../../files/css/color.css?v=1'); const atRule = ` -@import '${(import.meta.dirname ?? dirname(new URL(import.meta.url).pathname)).replace(/\\/g, '/') + '/../../files/css/color.css?v=1'}'; +@import '${url.pathname}'; abbr[title], abbr[data-original-title] { text-decoration: underline dotted; -webkit-text-decoration: underline dotted; diff --git a/test/specs/code/modules.js b/test/specs/code/modules.js index 3ca56f6b..f73093b5 100644 --- a/test/specs/code/modules.js +++ b/test/specs/code/modules.js @@ -99,6 +99,9 @@ export function run(describe, expect, it, transform, parse, render, dirname, rea }); it("module #4", function () { + + const url = new URL(dirname(import.meta.url) + '/../../css-modules/mixins.css'); + return transform( ` .goal .bg-indigo { @@ -107,7 +110,7 @@ export function run(describe, expect, it, transform, parse, render, dirname, rea .indigo-white { composes: bg-indigo; -composes: button cell title from "${(import.meta.dirname ?? dirname(new URL(import.meta.url).pathname)).replaceAll("\\", "/")}/../../css-modules/mixins.css"; color: white; +composes: button cell title from "${url.pathname}"; color: white; } `, { @@ -119,7 +122,7 @@ composes: button cell title from "${(import.meta.dirname ?? dirname(new URL(impo goal: "goal_r7bhp", "bg-indigo": "bg-indigo_gy28g", "indigo-white": - "indigo-white_wims0 bg-indigo_gy28g button_rptz7_mixins cell_dptz7_mixins title_fnrx5_mixins", + "indigo-white_wims0 bg-indigo_gy28g button_egkqy_mixins cell_s04ai_mixins title_seiow_mixins", }); expect(result.code).equals(`.goal_r7bhp .bg-indigo_gy28g { @@ -610,6 +613,9 @@ a span { }); it("module mode ICSS #17", function () { + + + const url = new URL(dirname(import.meta.url) + '/../../css-modules/mixins.css'); return transform( ` @@ -626,7 +632,7 @@ a span { .indigo-white { composes: bg-indigo; - composes: button cell title from "${(import.meta.dirname ?? dirname(new URL(import.meta.url).pathname)).replaceAll("\\", "/")}/../../css-modules/mixins.css"; color: white; + composes: button cell title from "${url.pathname}"; color: white; } `, { @@ -719,11 +725,13 @@ a span { // }); it("module import variables #19", function () { + + const url = new URL(dirname(import.meta.url) + '/../../css-modules/color.css'); return transform( ` /* import your colors... */ - @value colors: "${(import.meta.dirname ?? dirname(new URL(import.meta.url).pathname)).replaceAll("\\", "/")}/../../css-modules/color.css"; + @value colors: "${url.pathname}"; @value blue, red, green from colors; .button { diff --git a/test/specs/code/sourcemaps.js b/test/specs/code/sourcemaps.js index 9badb040..ec49c0ce 100644 --- a/test/specs/code/sourcemaps.js +++ b/test/specs/code/sourcemaps.js @@ -1,20 +1,44 @@ +import { ColorType, EnumToken, ModuleCaseTransformEnum, ModuleScopeEnumOptions } from "../../../dist/lib/ast/types.js"; + export function run(describe, expect, it, transform, parse, render, dirname, readFile, resolve) { - // describe('sourcemap', function () { + 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', - // }; + + const url = new URL(dirname(import.meta.url) + '/../../css-modules/mixins.css'); // const file = `@import '${dir}/files/css/line-awesome.css`; + const options = { + input: ` + + .goal .bg-indigo { + background: indigo; + } + + + .indigo-white { + composes: bg-indigo; + composes: title block ruler from global; + color: white; + } + + .indigo-white { + composes: bg-indigo; + composes: button cell title from "${url.pathname}"; color: white; + } + `, + beautify: true, + sourcemap: 'inline', + module: ModuleScopeEnumOptions.ICSS, + output: 'test/sourcemap.html' + }; - // it('sourcemap file #1', async () => { + it('sourcemap file #1', async () => { - // return transform(options).then(async result => { + 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())); - // }); - // }); - // }); + result.map.computePositions(); + const positions = result.map.find(11, 1); + return expect(positions.length == 1 && positions[0].slice(0, 3)).deep.equals([null, 3, 15]) + }); + }); + }); } \ No newline at end of file diff --git a/test/specs/code/validation.js b/test/specs/code/validation.js index 13592a9c..8124a458 100644 --- a/test/specs/code/validation.js +++ b/test/specs/code/validation.js @@ -505,13 +505,15 @@ html, body, div, span, applet, object, iframe, it('file validation #21', function () { - return transform(`@import '${(import.meta.dirname ?? dirname(new URL(import.meta.url).pathname)).replaceAll('\\', '/')}/../../files/css/full.css'; + const url = new URL(dirname(import.meta.url) + '/../../files/css/full.css'); + return transform(`@import '${url.pathname}'; `, {validation: true, resolveImport: true}).then(result => expect(result.errors.length).equals(5)); }); it('file validation #22', function () { - transform(`@import '${(import.meta.dirname ?? dirname(new URL(import.meta.url).pathname)).replaceAll('\\', '/')}/../../files/css/bootstrap.css'; + const url = new URL(dirname(import.meta.url) + '/../../files/css/bootstrap.css'); + transform(`@import '${url.pathname}'; `, { validation: true, resolveImport: true @@ -520,7 +522,8 @@ html, body, div, span, applet, object, iframe, it('file validation #23', function () { - return transform(`@import '${(import.meta.dirname ?? dirname(new URL(import.meta.url).pathname)).replaceAll('\\', '/')}/../../files/css/bootstrap-4.css'; + const url = new URL(dirname(import.meta.url) + '/../../files/css/bootstrap-4.css'); + return transform(`@import '${url.pathname}'; `, { validation: true, resolveImport: true @@ -529,7 +532,8 @@ html, body, div, span, applet, object, iframe, it('file validation #24', function () { - return transform(`@import '${(import.meta.dirname ?? dirname(new URL(import.meta.url).pathname)).replaceAll('\\', '/')}/../../files/css/bootstrap-5.css'; + const url = new URL(dirname(import.meta.url) + '/../../files/css/bootstrap-5.css'); + return transform(`@import '${url.pathname}'; `, { validation: true, resolveImport: true @@ -538,7 +542,8 @@ html, body, div, span, applet, object, iframe, it('file validation #25', function () { - return transform(`@import '${(import.meta.dirname ?? dirname(new URL(import.meta.url).pathname)).replaceAll('\\', '/')}/../../files/css/tailwind.css'; + const url = new URL(dirname(import.meta.url) + '/../../files/css/tailwind.css'); + return transform(`@import '${url.pathname}'; `, { validation: true, resolveImport: true @@ -547,7 +552,9 @@ html, body, div, span, applet, object, iframe, it('file validation #26', function () { - return transform(`@import '${(import.meta.dirname ?? dirname(new URL(import.meta.url).pathname)).replaceAll('\\', '/')}/../../files/css/tailwind-2.0.4.css'; + const url = new URL(dirname(import.meta.url) + '/../../files/css/tailwind-2.0.4.css'); + + return transform(`@import '${url.pathname}'; `, { validation: true, resolveImport: true @@ -556,7 +563,9 @@ html, body, div, span, applet, object, iframe, it('file validation #27', function () { - return transform(`@import '${(import.meta.dirname ?? dirname(new URL(import.meta.url).pathname)).replaceAll('\\', '/')}/../../files/css/github-markdown.css'; + const url = new URL(dirname(import.meta.url) + '/../../files/css/github-markdown.css'); + + return transform(`@import '${url.pathname}'; `, { validation: true, resolveImport: true From aff9f3b914b3fe151915627d0e8dab24027292ef Mon Sep 17 00:00:00 2001 From: Thierry Bela Nanga Date: Sun, 16 Aug 2026 17:59:06 -0400 Subject: [PATCH 04/11] fix sourcemap bugs #146 --- .gitattributes | 2 + README.md | 1 + dist/index-umd-web.js | 118 ++++++++++++++++++++++------------ dist/index.cjs | 118 ++++++++++++++++++++++------------ dist/lib/ast/expand.js | 28 ++++++-- dist/lib/parser/linesmap.js | 4 +- dist/lib/parser/parse.js | 31 ++++----- dist/lib/renderer/render.js | 59 ++++++++++------- files/index.md | 2 + files/minification.md | 2 +- files/sourcemap.md | 111 ++++++++++++++++++++++++++++++++ files/syntax-lowering.md | 2 +- files/transform.md | 111 ++++++++++++++++---------------- files/usage.md | 29 --------- jsr.json | 2 +- package.json | 2 +- src/lib/ast/expand.ts | 49 ++++++++++---- src/lib/ast/minify.ts | 37 ++++++----- src/lib/parser/linesmap.ts | 4 +- src/lib/parser/parse.ts | 46 +++++++------ src/lib/renderer/render.ts | 77 +++++++++++++--------- test/specs/code/sourcemaps.js | 88 +++++++++++++++---------- 22 files changed, 589 insertions(+), 334 deletions(-) create mode 100644 files/sourcemap.md 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/README.md b/README.md index 8833e01f..a9b6e6af 100644 --- a/README.md +++ b/README.md @@ -85,6 +85,7 @@ 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) +- [Sourcema](https://tbela99.github.io/css-parser/docs/documents/Guide.Sourcemap.html) - [Syntax Lowering](https://tbela99.github.io/css-parser/docs/documents/Guide.Syntax_Lowering.html) - [Ast Manipulation](https://tbela99.github.io/css-parser/docs/documents/Guide.Ast_Manipulation.html) - [Utility Functions](https://tbela99.github.io/css-parser/docs/documents/Guide.Utility_Functions.html) diff --git a/dist/index-umd-web.js b/dist/index-umd-web.js index c71e7413..7c67416e 100644 --- a/dist/index-umd-web.js +++ b/dist/index-umd-web.js @@ -21726,9 +21726,9 @@ if (offset < 0 || line < 0) { return [1, 1]; } - const column = offset - this.lineStarts[line]; + const column = offset - this.lineStarts[line] + 1; // [line, column] - return [line + 1, line === 0 ? column + 1 : column]; + return [line + 1, column == 0 ? 1 : column]; } /** * search the greatest index of the value less than or equal to offset @@ -24137,11 +24137,16 @@ */ function expand(ast) { const result = { ...ast, chi: [] }; + let children; for (let i = 0; i < ast.chi.length; i++) { - const node = ast.chi[i]; + let node = ast.chi[i]; if (node.typ === exports.EnumToken.RuleNodeType) { + children = expandRule(node); + for (const child of children) { + child[PARENT] = result; + } // @ts-ignore - result.chi.push(...expandRule(node)); + result.chi.push(...children); } else if (node.typ == exports.EnumToken.AtRuleNodeType && "chi" in node) { let hasRule = false; @@ -24153,10 +24158,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); } @@ -24809,7 +24827,7 @@ // @ts-ignore let children = ""; let str = ""; - let previousStr = ""; + // let previousStr: string = ""; const indent = indents[level]; const indentSub = indents[level + 1]; switch (data.typ) { @@ -24836,6 +24854,9 @@ str = options.newLine + str; } children += str; + if (sourcemaps != null && str !== "" && options.newLine) { + move(sourceLocation, linesMap, options.newLine); + } } return children; case exports.EnumToken.AtRuleNodeType: @@ -24845,13 +24866,16 @@ if ([exports.EnumToken.AtRuleNodeType, exports.EnumToken.KeyframesAtRuleNodeType].includes(data.typ) && !("chi" in data)) { return `${indent}@${data.nam}${data.val === "" ? "" : options.indent || " "}${data.val};`; } - const prelude = [exports.EnumToken.AtRuleNodeType, exports.EnumToken.KeyframesAtRuleNodeType].includes(data.typ) - ? `@${data.nam}${data.val === "" ? "" : options.indent || " "}${data.val}${options.indent}{` - : data.sel + `${options.indent}{`; + 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) { @@ -24876,38 +24900,47 @@ : node.val) .reduce(reducer, "") .trimEnd()};`; - if (sourcemaps != null) { - if (previousStr.length > 0) { - move(sourceLocation, linesMap, previousStr); - } - } - previousStr = str === "" ? "" : options.newLine + indentSub + str; } - // else if (node.typ == EnumToken.AtRuleNodeType && !("chi" in node)) { - // str = `${(node).val === "" ? "" : options.indent || " "}${(node).val};`; - // } else { - if (sourcemaps != null) { - if (previousStr.length > 0) { - move(sourceLocation, linesMap, previousStr); - } - } str = renderAstNode(node, options, sourcemaps, sourceLocation, linesMap, errors, reducer, cache, level + 1, indents); - previousStr = ""; + if (str === "") { + continue; + } + children += str; + str = ""; + continue; } if (str === "") { continue; } str = options.newLine + indentSub + str; children += str; - } - if (sourcemaps != null && str !== "") { - move(sourceLocation, linesMap, str.endsWith(";") ? str.slice(0, -1) : str); + if (sourcemaps != null && str !== "") { + move(sourceLocation, linesMap, str); + if (node.typ == exports.EnumToken.DeclarationNodeType && recordDeclarationSourceMap) { + // if declaration is child of at-rule, then record it + // .rule { + // @media screen { + // color: red; + // } + // } + const source = options.sourcesMap.get(node[LOC].srcId); + sourcemaps.push([ + ...linesMap.getOffsets(sourceLocation.end - str.length + options.newLine.length + indentSub.length), + node[LOC].srcId, + ...source.getOffsets(node[LOC].sta), + source.getFileName(), + source.getContent(), + ]); + } + } } if (children.endsWith(";")) { children = children.slice(0, -1); + sourceLocation.end--; } if (options.removeEmpty && children === "") { + sourceLocation.end -= prelude.length; return ""; } const end = options.newLine + indent + `}`; @@ -29717,7 +29750,7 @@ }; let tokens = []; let context = ast; - ast[ROOT] = ast; + // ast[ROOT] = ast; ast[LOC] = { sta: 0, end: 0, @@ -29999,6 +30032,20 @@ } let replacement; let callable; + while (stack.length > 0 && context != ast) { + const previousNode = stack.pop(); + context = (stack[stack.length - 1] ?? ast); + previousNode[PARENT] = context; + // 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.visitor != null) { let parens; for (const result of walk(ast)) { @@ -30224,19 +30271,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; diff --git a/dist/index.cjs b/dist/index.cjs index fb531363..91dd92da 100644 --- a/dist/index.cjs +++ b/dist/index.cjs @@ -21729,9 +21729,9 @@ class LineMap { if (offset < 0 || line < 0) { return [1, 1]; } - const column = offset - this.lineStarts[line]; + const column = offset - this.lineStarts[line] + 1; // [line, column] - return [line + 1, line === 0 ? column + 1 : column]; + return [line + 1, column == 0 ? 1 : column]; } /** * search the greatest index of the value less than or equal to offset @@ -24140,11 +24140,16 @@ function reduceRuleSelector(node) { */ function expand(ast) { const result = { ...ast, chi: [] }; + let children; for (let i = 0; i < ast.chi.length; i++) { - const node = ast.chi[i]; + let node = ast.chi[i]; if (node.typ === exports.EnumToken.RuleNodeType) { + children = expandRule(node); + for (const child of children) { + child[PARENT] = result; + } // @ts-ignore - result.chi.push(...expandRule(node)); + result.chi.push(...children); } else if (node.typ == exports.EnumToken.AtRuleNodeType && "chi" in node) { let hasRule = false; @@ -24156,10 +24161,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); } @@ -24812,7 +24830,7 @@ function renderAstNode(data, options, sourcemaps, sourceLocation, linesMap, erro // @ts-ignore let children = ""; let str = ""; - let previousStr = ""; + // let previousStr: string = ""; const indent = indents[level]; const indentSub = indents[level + 1]; switch (data.typ) { @@ -24839,6 +24857,9 @@ function renderAstNode(data, options, sourcemaps, sourceLocation, linesMap, erro str = options.newLine + str; } children += str; + if (sourcemaps != null && str !== "" && options.newLine) { + move(sourceLocation, linesMap, options.newLine); + } } return children; case exports.EnumToken.AtRuleNodeType: @@ -24848,13 +24869,16 @@ function renderAstNode(data, options, sourcemaps, sourceLocation, linesMap, erro if ([exports.EnumToken.AtRuleNodeType, exports.EnumToken.KeyframesAtRuleNodeType].includes(data.typ) && !("chi" in data)) { return `${indent}@${data.nam}${data.val === "" ? "" : options.indent || " "}${data.val};`; } - const prelude = [exports.EnumToken.AtRuleNodeType, exports.EnumToken.KeyframesAtRuleNodeType].includes(data.typ) - ? `@${data.nam}${data.val === "" ? "" : options.indent || " "}${data.val}${options.indent}{` - : data.sel + `${options.indent}{`; + 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) { @@ -24879,38 +24903,47 @@ function renderAstNode(data, options, sourcemaps, sourceLocation, linesMap, erro : node.val) .reduce(reducer, "") .trimEnd()};`; - if (sourcemaps != null) { - if (previousStr.length > 0) { - move(sourceLocation, linesMap, previousStr); - } - } - previousStr = str === "" ? "" : options.newLine + indentSub + str; } - // else if (node.typ == EnumToken.AtRuleNodeType && !("chi" in node)) { - // str = `${(node).val === "" ? "" : options.indent || " "}${(node).val};`; - // } else { - if (sourcemaps != null) { - if (previousStr.length > 0) { - move(sourceLocation, linesMap, previousStr); - } - } str = renderAstNode(node, options, sourcemaps, sourceLocation, linesMap, errors, reducer, cache, level + 1, indents); - previousStr = ""; + if (str === "") { + continue; + } + children += str; + str = ""; + continue; } if (str === "") { continue; } str = options.newLine + indentSub + str; children += str; - } - if (sourcemaps != null && str !== "") { - move(sourceLocation, linesMap, str.endsWith(";") ? str.slice(0, -1) : str); + if (sourcemaps != null && str !== "") { + move(sourceLocation, linesMap, str); + if (node.typ == exports.EnumToken.DeclarationNodeType && recordDeclarationSourceMap) { + // if declaration is child of at-rule, then record it + // .rule { + // @media screen { + // color: red; + // } + // } + const source = options.sourcesMap.get(node[LOC].srcId); + sourcemaps.push([ + ...linesMap.getOffsets(sourceLocation.end - str.length + options.newLine.length + indentSub.length), + node[LOC].srcId, + ...source.getOffsets(node[LOC].sta), + source.getFileName(), + source.getContent(), + ]); + } + } } if (children.endsWith(";")) { children = children.slice(0, -1); + sourceLocation.end--; } if (options.removeEmpty && children === "") { + sourceLocation.end -= prelude.length; return ""; } const end = options.newLine + indent + `}`; @@ -29720,7 +29753,7 @@ async function doParse(iter, options = {}) { }; let tokens = []; let context = ast; - ast[ROOT] = ast; + // ast[ROOT] = ast; ast[LOC] = { sta: 0, end: 0, @@ -30002,6 +30035,20 @@ async function doParse(iter, options = {}) { } let replacement; let callable; + while (stack.length > 0 && context != ast) { + const previousNode = stack.pop(); + context = (stack[stack.length - 1] ?? ast); + previousNode[PARENT] = context; + // 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.visitor != null) { let parens; for (const result of walk(ast)) { @@ -30227,19 +30274,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; diff --git a/dist/lib/ast/expand.js b/dist/lib/ast/expand.js index 577eefeb..4da8070f 100644 --- a/dist/lib/ast/expand.js +++ b/dist/lib/ast/expand.js @@ -1,5 +1,5 @@ import { splitRule } from './minify.js'; -import { combinators, RAW } from '../syntax/constants.js'; +import { PARENT, combinators, RAW } from '../syntax/constants.js'; import { parseString } from '../parser/parse.js'; import { walkValues } from './walk.js'; import { renderValue } from '../renderer/render.js'; @@ -13,11 +13,16 @@ import { EnumToken } from './types.js'; */ function expand(ast) { const result = { ...ast, chi: [] }; + let children; for (let i = 0; i < ast.chi.length; i++) { - const node = ast.chi[i]; + let node = ast.chi[i]; if (node.typ === EnumToken.RuleNodeType) { + children = expandRule(node); + for (const child of children) { + child[PARENT] = result; + } // @ts-ignore - result.chi.push(...expandRule(node)); + result.chi.push(...children); } else if (node.typ == EnumToken.AtRuleNodeType && "chi" in node) { let hasRule = false; @@ -29,10 +34,23 @@ function expand(ast) { break; } } - // @ts-ignore - result.chi.push({ ...(hasRule ? expand(node) : node) }); + if (hasRule) { + node = expand(node); + for (const child of node.chi) { + child[PARENT] = result; + } + node[PARENT] = result; + // @ts-ignore + result.chi.push(node); + } + else { + node[PARENT] = result; + // @ts-ignore + result.chi.push(node); + } } else { + node[PARENT] = result; // @ts-ignore result.chi.push(node); } diff --git a/dist/lib/parser/linesmap.js b/dist/lib/parser/linesmap.js index 38daa8f7..38c9fad1 100644 --- a/dist/lib/parser/linesmap.js +++ b/dist/lib/parser/linesmap.js @@ -26,9 +26,9 @@ class LineMap { if (offset < 0 || line < 0) { return [1, 1]; } - const column = offset - this.lineStarts[line]; + const column = offset - this.lineStarts[line] + 1; // [line, column] - return [line + 1, line === 0 ? column + 1 : column]; + return [line + 1, column == 0 ? 1 : column]; } /** * search the greatest index of the value less than or equal to offset diff --git a/dist/lib/parser/parse.js b/dist/lib/parser/parse.js index 99b36382..64cc0afa 100644 --- a/dist/lib/parser/parse.js +++ b/dist/lib/parser/parse.js @@ -6,7 +6,7 @@ import { minify } from '../ast/minify.js'; import { expand } from '../ast/expand.js'; import { WalkerEvent, walk, walkValues } 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'; @@ -1380,7 +1380,7 @@ async function doParse(iter, options = {}) { }; let tokens = []; let context = ast; - ast[ROOT] = ast; + // ast[ROOT] = ast; ast[LOC] = { sta: 0, end: 0, @@ -1662,6 +1662,20 @@ async function doParse(iter, options = {}) { } let replacement; let callable; + while (stack.length > 0 && context != ast) { + const previousNode = stack.pop(); + context = (stack[stack.length - 1] ?? ast); + previousNode[PARENT] = context; + // 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.visitor != null) { let parens; for (const result of walk(ast)) { @@ -1887,19 +1901,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; diff --git a/dist/lib/renderer/render.js b/dist/lib/renderer/render.js index d96ed529..f7710816 100644 --- a/dist/lib/renderer/render.js +++ b/dist/lib/renderer/render.js @@ -254,7 +254,7 @@ function renderAstNode(data, options, sourcemaps, sourceLocation, linesMap, erro // @ts-ignore let children = ""; let str = ""; - let previousStr = ""; + // let previousStr: string = ""; const indent = indents[level]; const indentSub = indents[level + 1]; switch (data.typ) { @@ -281,6 +281,9 @@ function renderAstNode(data, options, sourcemaps, sourceLocation, linesMap, erro str = options.newLine + str; } children += str; + if (sourcemaps != null && str !== "" && options.newLine) { + move(sourceLocation, linesMap, options.newLine); + } } return children; case EnumToken.AtRuleNodeType: @@ -290,13 +293,16 @@ function renderAstNode(data, options, sourcemaps, sourceLocation, linesMap, erro if ([EnumToken.AtRuleNodeType, EnumToken.KeyframesAtRuleNodeType].includes(data.typ) && !("chi" in data)) { return `${indent}@${data.nam}${data.val === "" ? "" : options.indent || " "}${data.val};`; } - const prelude = [EnumToken.AtRuleNodeType, EnumToken.KeyframesAtRuleNodeType].includes(data.typ) - ? `@${data.nam}${data.val === "" ? "" : options.indent || " "}${data.val}${options.indent}{` - : data.sel + `${options.indent}{`; + 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) { @@ -321,38 +327,47 @@ function renderAstNode(data, options, sourcemaps, sourceLocation, linesMap, erro : node.val) .reduce(reducer, "") .trimEnd()};`; - if (sourcemaps != null) { - if (previousStr.length > 0) { - move(sourceLocation, linesMap, previousStr); - } - } - previousStr = str === "" ? "" : options.newLine + indentSub + str; } - // else if (node.typ == EnumToken.AtRuleNodeType && !("chi" in node)) { - // str = `${(node).val === "" ? "" : options.indent || " "}${(node).val};`; - // } else { - if (sourcemaps != null) { - if (previousStr.length > 0) { - move(sourceLocation, linesMap, previousStr); - } - } str = renderAstNode(node, options, sourcemaps, sourceLocation, linesMap, errors, reducer, cache, level + 1, indents); - previousStr = ""; + if (str === "") { + continue; + } + children += str; + str = ""; + continue; } if (str === "") { continue; } str = options.newLine + indentSub + str; children += str; - } - if (sourcemaps != null && str !== "") { - move(sourceLocation, linesMap, str.endsWith(";") ? str.slice(0, -1) : str); + if (sourcemaps != null && str !== "") { + move(sourceLocation, linesMap, str); + if (node.typ == EnumToken.DeclarationNodeType && recordDeclarationSourceMap) { + // if declaration is child of at-rule, then record it + // .rule { + // @media screen { + // color: red; + // } + // } + const source = options.sourcesMap.get(node[LOC].srcId); + sourcemaps.push([ + ...linesMap.getOffsets(sourceLocation.end - str.length + options.newLine.length + indentSub.length), + node[LOC].srcId, + ...source.getOffsets(node[LOC].sta), + source.getFileName(), + source.getContent(), + ]); + } + } } if (children.endsWith(";")) { children = children.slice(0, -1); + sourceLocation.end--; } if (options.removeEmpty && children === "") { + sourceLocation.end -= prelude.length; return ""; } const end = options.newLine + indent + `}`; diff --git a/files/index.md b/files/index.md index 9b88907c..3bd1279c 100644 --- a/files/index.md +++ b/files/index.md @@ -9,6 +9,7 @@ children: - ./css-module.md - ./minification.md - ./transform.md + - ./sourcemap.md - ./syntax-lowering.md - ./ast.md - ./utilities.md @@ -22,6 +23,7 @@ children: - [CSS Modules](./css-module.md) - [Minification](./minification.md) - [Custom Transform](./transform.md) +- [Sourcemap](./sourcemap.md) - [Syntax Lowering](./syntax-lowering.md) - [Ast Manipulation](./ast.md) - [Utility Functions](./utilities.md) diff --git a/files/minification.md b/files/minification.md index f58f8b7d..4d329fac 100644 --- a/files/minification.md +++ b/files/minification.md @@ -891,7 +891,7 @@ Output: ### Computed shorthands properties -Below is the list of computed shorthands properties: +Below is the list of computed shorthands properties. Minification is fully supported for the propertie with a checkmark. - [ ] ~all~ - [x] animation diff --git a/files/sourcemap.md b/files/sourcemap.md new file mode 100644 index 00000000..157f55a1 --- /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) | [Syntax Lowering →](./syntax-lowering.md) \ No newline at end of file diff --git a/files/syntax-lowering.md b/files/syntax-lowering.md index 8f4c6bed..f070397f 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 +[← Custom Transform](./sourcemap.md) | [Ast Manipulation →](./ast.md) \ No newline at end of file diff --git a/files/transform.md b/files/transform.md index 3545bc28..92b10cc5 100644 --- a/files/transform.md +++ b/files/transform.md @@ -446,9 +446,9 @@ console.debug(await transform(css, options)); // body {color:#f3fff0} ``` -### Example of visitor that inlines images +### Example of plugin -A visitor that inlines all images under a specific size +A plugin implemented as visitor that inlines all images under a specific size. ```ts import { @@ -463,76 +463,79 @@ import { 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:')) { +function toBase64(arraybuffer: Uint8Array) { + // @ts-ignore + if (typeof Uint8Array.prototype.toBase64! == "function") { + // @ts-ignore + return arraybuffer.toBase64(); + } - return; - } + let binary = ""; + for (const byte of arraybuffer) { + binary += String.fromCharCode(byte); + } - const matches = /(.*?\/)?([^/.]+)\.([^?#]+)([?#].*)?$/.exec(url); + return btoa(binary); +} - if (matches == null || !extensions.includes(matches[3].toLowerCase())) { +function inlineImagesPlugin(maxSize: number, extensions: string[]) { + return async function (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; - return; - } + if (t == null) { + return; + } - const buffer = await load(url, '.', ResponseType.ArrayBuffer) as ArrayBuffer ; + const url = t.typ == EnumToken.StringTokenType ? (t as StringToken).val.slice(1, -1) : (t as UrlToken).val; - if (buffer.byteLength > maxSize) { + if (url.startsWith("data:")) { + return; + } - return - } + const matches = /(.*?\/)?([^/.]+)\.([^?#]+)([?#].*)?$/.exec(url); - Object.assign(t, {typ: EnumToken.StringTokenType, val: `"data:image/${matches[3].toLowerCase()};base64,${toBase64(new Uint8Array(buffer))}"`}) + if (matches == null || !extensions.includes(matches[3].toLowerCase())) { + return; } - } - } -}); - -function toBase64(arraybuffer: Uint8Array) { - // @ts-ignore - if (typeof Uint8Array.prototype.toBase64! == 'function') { + const buffer = (await load(url, ".", ResponseType.ArrayBuffer)) as ArrayBuffer; - // @ts-ignore - return arraybuffer.toBase64(); - } + if (buffer.byteLength > maxSize) { + return; + } - let binary = ''; - for (const byte of arraybuffer) { - binary += String.fromCharCode( byte); - } + // change node type to EnumToken.String + Object.assign(t, { + typ: EnumToken.StringTokenType, + val: `"data:image/${matches[3].toLowerCase()};base64,${toBase64(new Uint8Array(buffer))}"`, + }); + } + }; +} - return btoa( binary ); +// 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: { + UrlFunctionTokenType: inlineImagesPlugin(maxSize, extensions), + }, +}); 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 ffbc6107..9640acf6 100644 --- a/files/usage.md +++ b/files/usage.md @@ -380,35 +380,6 @@ 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 diff --git a/jsr.json b/jsr.json index 34aff9f9..3cf6e541 100644 --- a/jsr.json +++ b/jsr.json @@ -1,6 +1,6 @@ { "name": "@tbela99/css-parser", - "version": "1.4.11", + "version": "1.5.0-alpha.1", "publish": { "include": [ "src", diff --git a/package.json b/package.json index 3c22ae62..165717ee 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-alpha.1", "exports": { ".": "./dist/node.js", "./node": "./dist/node.js", diff --git a/src/lib/ast/expand.ts b/src/lib/ast/expand.ts index d975052d..b2687841 100644 --- a/src/lib/ast/expand.ts +++ b/src/lib/ast/expand.ts @@ -1,10 +1,10 @@ -import {splitRule} from "./minify.ts"; -import {combinators, RAW} 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 { splitRule } from "./minify.ts"; +import { combinators, PARENT, RAW } from "../syntax/constants.ts"; +import { parseString } from "../parser/parse.ts"; +import { walkValues } from "./walk.ts"; +import { renderValue } from "../renderer/render.ts"; +import type { AstAtRule, AstNode, AstRule, AstStyleSheet, LiteralToken, Token } from "../../@types/index.d.ts"; +import { EnumToken } from "./types.ts"; /** * expand css nesting ast nodes @@ -14,13 +14,20 @@ import {EnumToken} from "./types.ts"; */ export function expand(ast: AstStyleSheet | AstAtRule | AstRule): AstNode { const result = { ...ast, chi: [] }; + let children: AstNode[]; for (let i = 0; i < ast.chi!.length; i++) { - const node = ast.chi![i]; + let node = ast.chi![i]; if (node.typ === EnumToken.RuleNodeType) { + children = expandRule(node as AstRule); + + for (const child of children) { + child[PARENT] = result; + } + // @ts-ignore - result.chi.push(...expandRule(node)); + result.chi.push(...children); } else if (node.typ == EnumToken.AtRuleNodeType && "chi" in node) { let hasRule: boolean = false; let j: number = node!.chi!.length; @@ -33,10 +40,25 @@ export function expand(ast: AstStyleSheet | AstAtRule | AstRule): AstNode { } } - // @ts-ignore - result.chi.push({ ...(hasRule ? expand(node) : node) }); + if (hasRule) { + node = expand(node as AstRule); + + for (const child of node.chi) { + child[PARENT] = result; + } + + node[PARENT] = result; + // @ts-ignore + result.chi.push(node); + } else { + node[PARENT] = result; + // @ts-ignore + result.chi.push(node); + } } else { + node[PARENT] = result; // @ts-ignore + result.chi!.push(node); } } @@ -85,8 +107,7 @@ function expandRule(node: AstRule): Array { [], ) .join(","); - - } else { + } else { let childSelectorCompound: string[] = []; let withCompound: string[] = []; let withoutCompound: string[] = []; @@ -103,7 +124,7 @@ function expandRule(node: AstRule): Array { continue; } - for (const sel of rule[RAW]?? splitRule(rule.sel)) { + for (const sel of rule[RAW] ?? splitRule(rule.sel)) { const s: string = sel.join(""); if (s.includes("&") || parentSelector) { diff --git a/src/lib/ast/minify.ts b/src/lib/ast/minify.ts index 5498a5b0..f281a946 100644 --- a/src/lib/ast/minify.ts +++ b/src/lib/ast/minify.ts @@ -1,7 +1,7 @@ -import {eq} from "../parser/utils/eq.ts"; -import {doRender, renderValue} from "../renderer/render.ts"; +import { eq } from "../parser/utils/eq.ts"; +import { doRender, renderValue } from "../renderer/render.ts"; import * as allFeatures from "./features/index.ts"; -import {walkValues} from "./walk.ts"; +import { walkValues } from "./walk.ts"; import type { AstAtRule, AstDeclaration, @@ -24,15 +24,15 @@ import type { RawSelectorTokens, Token, } from "../../@types/index.d.ts"; -import {EnumToken} from "./types.ts"; -import {isFunction, isIdent, isIdentStart, isWhiteSpace} from "../syntax/syntax.ts"; -import {FeatureWalkMode} from "./features/type.ts"; -import {trimArray} from "../validation/match.ts"; -import {combinators, LOC, OPTIMIZED, PARENT, RAW, TOKENS} from "../syntax/constants.ts"; -import {replaceNodeOrValue} from "../parser/utils/token.ts"; -import {parseString} from "../parser/parse.ts"; -import {tokenize} from "../parser/tokenize.ts"; -import {replaceCompound} from "./expand.ts"; +import { EnumToken } from "./types.ts"; +import { isFunction, isIdent, isIdentStart, isWhiteSpace } from "../syntax/syntax.ts"; +import { FeatureWalkMode } from "./features/type.ts"; +import { trimArray } from "../validation/match.ts"; +import { combinators, LOC, OPTIMIZED, PARENT, RAW, TOKENS } from "../syntax/constants.ts"; +import { replaceNodeOrValue } from "../parser/utils/token.ts"; +import { parseString } from "../parser/parse.ts"; +import { tokenize } from "../parser/tokenize.ts"; +import { replaceCompound } from "./expand.ts"; const notEndingWith: string[] = ["(", "["].concat(combinators); const rules: EnumToken[] = [ @@ -91,7 +91,7 @@ export function minify( let replacement: AstNode | null; // @ts-ignore - let {sourcemap, module, ...options} = opt; + let { sourcemap, module, ...options } = opt; if (!("features" in options)) { // @ts-ignore @@ -209,7 +209,7 @@ export function minify( const result = feature.run( replacement as AstRule | AstAtRule, options, - parent[PARENT] ?? ast, + parent[PARENT] ?? (ast as AstRule | AstAtRule | AstStyleSheet), context, FeatureWalkMode.Post, ); @@ -1748,7 +1748,7 @@ function diff(n1: AstRule, n2: AstRule, options: ParserOptions = {}) { chi: intersect.reverse(), }; - let op = {level: 0, ...options}; + let op = { level: 0, ...options }; if ( result == null || @@ -1773,11 +1773,10 @@ function diff(n1: AstRule, n2: AstRule, options: ParserOptions = {}) { return curr.chi.length == 0 ? acc : acc + css.length; }, 0) <= [node1, node2, result].reduce((acc: number, curr: AstRule): number => { - let css: string = options.cache!.get(curr) as string; if (css != null) { - return curr.chi.length == 0 ? acc : acc + css.length + return curr.chi.length == 0 ? acc : acc + css.length; } let level: number = 0; @@ -1788,8 +1787,8 @@ function diff(n1: AstRule, n2: AstRule, options: ParserOptions = {}) { parent = parent[PARENT] as AstRule; } - op.level = level; - css = doRender(curr, op).code; + op.level = level; + css = doRender(curr, op).code; return curr.chi.length == 0 ? acc : acc + css.length; }, 0) diff --git a/src/lib/parser/linesmap.ts b/src/lib/parser/linesmap.ts index 8f5fa34b..9356d0ea 100644 --- a/src/lib/parser/linesmap.ts +++ b/src/lib/parser/linesmap.ts @@ -31,9 +31,9 @@ export class LineMap { return [1, 1]; } - const column: number = offset - this.lineStarts[line]; + const column: number = offset - this.lineStarts[line] + 1; // [line, column] - return [line + 1, line === 0 ? column + 1 : column]; + return [line + 1, column == 0 ? 1 : column]; } /** diff --git a/src/lib/parser/parse.ts b/src/lib/parser/parse.ts index 6e85e4d9..3bfa7bad 100644 --- a/src/lib/parser/parse.ts +++ b/src/lib/parser/parse.ts @@ -1838,7 +1838,7 @@ export async function doParse( let tokens: Token[] = []; let context: AstRuleList = ast; - ast[ROOT] = ast; + // ast[ROOT] = ast; ast[LOC] = { sta: 0, @@ -2020,7 +2020,6 @@ export async function doParse( : // @ts-expect-error ((iter as Iterator).next().value as TokenizeResult)) ) { - stats.bytesIn = item.bytesIn; stats.tokensCount++; @@ -2156,7 +2155,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 ? dirname(options.src as string) : (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" @@ -2208,6 +2210,26 @@ export async function doParse( let replacement: GenericVisitorResult; let callable: GenericVisitorHandler; + while (stack.length > 0 && context != ast) { + const previousNode: AstAtRule | AstRule = stack.pop() as AstAtRule | AstRule; + context = (stack[stack.length - 1] ?? ast) as AstRuleList; + + previousNode[PARENT] = context; + + // 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.visitor != null) { let parens: Token[] | null; for (const result of walk(ast)) { @@ -2518,24 +2540,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); diff --git a/src/lib/renderer/render.ts b/src/lib/renderer/render.ts index c87f1742..aed9f15a 100644 --- a/src/lib/renderer/render.ts +++ b/src/lib/renderer/render.ts @@ -421,7 +421,7 @@ function renderAstNode( // @ts-ignore let children: string = ""; let str: string = ""; - let previousStr: string = ""; + // let previousStr: string = ""; const indent: string = indents[level]; const indentSub: string = indents[level + 1]; @@ -468,6 +468,10 @@ function renderAstNode( } children += str; + + if (sourcemaps != null && str !== "" && options.newLine) { + move(sourceLocation, linesMap!, options.newLine as string); + } } return children; @@ -482,18 +486,21 @@ function renderAstNode( };`; } - const prelude = [EnumToken.AtRuleNodeType, EnumToken.KeyframesAtRuleNodeType].includes(data.typ) - ? `@${(data).nam}${(data).val === "" ? "" : options.indent || " "}${ - (data).val - }${options.indent}{` - : (data).sel + `${options.indent}{`; + 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 k: number = (data as AstRule | AstAtRule).chi!.length - 1; + 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) { @@ -519,25 +526,7 @@ function renderAstNode( ) .reduce(reducer, "") .trimEnd()};`; - - if (sourcemaps != null) { - if (previousStr.length > 0) { - move(sourceLocation, linesMap!, previousStr); - } - } - - previousStr = str === "" ? "" : options.newLine + indentSub + str; - } - // else if (node.typ == EnumToken.AtRuleNodeType && !("chi" in node)) { - // str = `${(node).val === "" ? "" : options.indent || " "}${(node).val};`; - // } - else { - if (sourcemaps != null) { - if (previousStr.length > 0) { - move(sourceLocation, linesMap!, previousStr); - } - } - + } else { str = renderAstNode( node, options, @@ -551,7 +540,13 @@ function renderAstNode( indents, ); - previousStr = ""; + if (str === "") { + continue; + } + + children += str; + str = ""; + continue; } if (str === "") { @@ -560,16 +555,38 @@ function renderAstNode( str = options.newLine + indentSub + str; children += str; - } - if (sourcemaps != null && str !== "") { - move(sourceLocation, linesMap!, str.endsWith(";") ? str.slice(0, -1) : str); + if (sourcemaps != null && str !== "") { + move(sourceLocation, linesMap!, str); + + if (node.typ == EnumToken.DeclarationNodeType && recordDeclarationSourceMap) { + // if declaration is child of at-rule, then record it + // .rule { + // @media screen { + // color: red; + // } + // } + const source = options.sourcesMap!.get(node[LOC]!.srcId) as SourceFile; + sourcemaps.push([ + ...linesMap!.getOffsets( + sourceLocation.end - str.length + options.newLine!.length + indentSub.length, + ), + node[LOC]!.srcId, + ...source!.getOffsets(node[LOC].sta), + source.getFileName(), + source.getContent(), + ]); + } + } } if (children.endsWith(";")) { children = children.slice(0, -1); + sourceLocation.end--; } + if (options.removeEmpty && children === "") { + sourceLocation.end -= prelude.length; return ""; } diff --git a/test/specs/code/sourcemaps.js b/test/specs/code/sourcemaps.js index ec49c0ce..3080b219 100644 --- a/test/specs/code/sourcemaps.js +++ b/test/specs/code/sourcemaps.js @@ -1,44 +1,66 @@ import { ColorType, EnumToken, ModuleCaseTransformEnum, ModuleScopeEnumOptions } from "../../../dist/lib/ast/types.js"; -export function run(describe, expect, it, transform, parse, render, dirname, readFile, resolve) { +export function run( + describe, + expect, + it, + transform, + parse, + render, + dirname, + readFile, + resolve, + ColorType, + EnumToken, + ModuleCaseTransformEnum, + ModuleScopeEnumOptions, + transformSync, + parseSync, +) { + describe("sourcemap", function () { + const url = new URL(dirname(import.meta.url) + "/../../files/css/nested.css"); // const file = `@import '${dir}/files/css/line-awesome.css`; - describe('sourcemap', function () { - - - const url = new URL(dirname(import.meta.url) + '/../../css-modules/mixins.css'); // const file = `@import '${dir}/files/css/line-awesome.css`; const options = { input: ` - - .goal .bg-indigo { - background: indigo; - } - - - .indigo-white { - composes: bg-indigo; - composes: title block ruler from global; - color: white; - } - - .indigo-white { - composes: bg-indigo; - composes: button cell title from "${url.pathname}"; color: white; - } +@import '${url.pathname}'; +h1 { + text-transform: uppercase; +} +button { + background: linear-gradient( + if(media(min-width: 768px): to right; else: to bottom), + if(style(--dark-mode): #333; else: #fff), + if(style(--dark-mode): #000; else: #ccc) + ); +} `, - beautify: true, - sourcemap: 'inline', - module: ModuleScopeEnumOptions.ICSS, - output: 'test/sourcemap.html' + beautify: true, + sourcemap: "inline", + expandIfSyntax: true, + resolveImport: true, + output: "test/sourcemap.html", }; - - it('sourcemap file #1', async () => { - - return transform(options).then(async result => { - + + it("sourcemap unminified #1", async () => { + return transform(options).then(async (result) => { result.map.computePositions(); - const positions = result.map.find(11, 1); - return expect(positions.length == 1 && positions[0].slice(0, 3)).deep.equals([null, 3, 15]) + let positions = result.map.find(39, 3); + expect(positions?.length == 1 && positions[0].slice(0, 3)).deep.equals([null, 7, 3]); + }); + }); + + it("sourcemap minified #2", async () => { + return transform(options).then(async (result) => { + const result2 = transformSync({ + input: result.code, + sourcemap: "inline", + output: "test/sourcemap.html", + }); + + result2.map.computePositions(); + const positions = result2.map.find(1, 255); + expect(positions?.length == 1 && positions[0].slice(0, 3)).deep.equals([null, 23, 2]); }); }); }); -} \ No newline at end of file +} From ada7a3fb0e25a003ef5638f312bf4e96a47d6b14 Mon Sep 17 00:00:00 2001 From: Thierry Bela Nanga Date: Sun, 16 Aug 2026 21:52:48 -0400 Subject: [PATCH 05/11] bump version #146 --- README.md | 5 +- dist/index-umd-web.js | 46 +--- dist/index.cjs | 36 +-- dist/index.d.ts | 47 +--- dist/lib/renderer/render.js | 30 +-- dist/node.js | 6 +- dist/web.js | 16 +- files/getting-started.md | 2 +- files/index.md | 2 + files/minification.md | 4 +- files/plugins.md | 103 +++++++++ files/sourcemap.md | 2 +- files/syntax-lowering.md | 2 +- files/transform.md | 116 +--------- jsr.json | 2 +- package.json | 2 +- src/@types/ast.d.ts | 15 +- src/@types/index.d.ts | 15 ++ src/@types/token.d.ts | 343 +++++++++++++++++++++------- src/@types/validation.d.ts | 97 +++++--- src/@types/visitor.d.ts | 42 +++- src/@types/walker.d.ts | 36 +++ src/lib/ast/find.ts | 19 +- src/lib/ast/minify.ts | 76 +++--- src/lib/parser/parse.ts | 29 ++- src/lib/parser/utils/declaration.ts | 4 +- src/lib/parser/utils/selector.ts | 8 +- src/lib/parser/utils/token.ts | 5 +- src/lib/renderer/render.ts | 33 +-- src/lib/validation/match.ts | 82 +++++++ src/node.ts | 56 ++--- src/web.ts | 220 ++++++++++++++++-- 32 files changed, 963 insertions(+), 538 deletions(-) create mode 100644 files/plugins.md diff --git a/README.md b/README.md index a9b6e6af..ac470844 100644 --- a/README.md +++ b/README.md @@ -43,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: @@ -85,7 +85,8 @@ Try it [online](https://tbela99.github.io/css-parser/playground/) - [CSS Modules](https://tbela99.github.io/css-parser/docs/documents/Guide.CSS_Modules.html) - [Minification](https://tbela99.github.io/css-parser/docs/documents/Guide.Minification.html) - [Custom Transform](https://tbela99.github.io/css-parser/docs/documents/Guide.Custom_Transform.html) -- [Sourcema](https://tbela99.github.io/css-parser/docs/documents/Guide.Sourcemap.html) +- [Sourcemap](https://tbela99.github.io/css-parser/docs/documents/Guide.Sourcemap.html) +- [Plugins API](https://tbela99.github.io/css-parser/docs/documents/Guide.Plugins_API.html) - [Syntax Lowering](https://tbela99.github.io/css-parser/docs/documents/Guide.Syntax_Lowering.html) - [Ast Manipulation](https://tbela99.github.io/css-parser/docs/documents/Guide.Ast_Manipulation.html) - [Utility Functions](https://tbela99.github.io/css-parser/docs/documents/Guide.Utility_Functions.html) diff --git a/dist/index-umd-web.js b/dist/index-umd-web.js index 7c67416e..5171a65e 100644 --- a/dist/index-umd-web.js +++ b/dist/index-umd-web.js @@ -24827,7 +24827,6 @@ // @ts-ignore let children = ""; let str = ""; - // let previousStr: string = ""; const indent = indents[level]; const indentSub = indents[level + 1]; switch (data.typ) { @@ -24838,7 +24837,7 @@ 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("/*!")) @@ -24886,15 +24885,6 @@ : 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) @@ -24948,24 +24938,6 @@ move(sourceLocation, linesMap, end); } return prelude + children + end; - // 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: default: return ""; } @@ -32190,7 +32162,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; * ``` */ @@ -32232,7 +32204,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); @@ -32291,12 +32263,13 @@ /** * Parse css * @param args + * @private * * Parsing a string * * ```ts * - * import {parseSync} from '@tbela99/css-parser'; + * import {parseSync} from '@tbela99/css-parser/web'; * * // css string * let result = await parseSync(css, {nestingRules: true}); @@ -32346,11 +32319,11 @@ return !options.module && !options.inputSourceMap ? result : parseResult(result, options); } /** - * Transform css + * Transform CSS * * ```ts * - * import {transformSync} from '@tbela99/css-parser'; + * import {transformSync} from '@tbela99/css-parser/web'; * * // css string * const result = transformSync(css); @@ -32358,6 +32331,7 @@ * ``` * * @param args + * @private */ function transformSync(...args) { let options; @@ -32431,6 +32405,7 @@ * console.log(result.ast); * ``` * @param args + * @private */ async function parse(...args) { let options; @@ -32479,7 +32454,7 @@ 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 @@ -32528,6 +32503,7 @@ * console.log(result.code); * ``` * @param args + * @private */ async function transform(...args) { let options; diff --git a/dist/index.cjs b/dist/index.cjs index 91dd92da..c60b7ab5 100644 --- a/dist/index.cjs +++ b/dist/index.cjs @@ -24830,7 +24830,6 @@ function renderAstNode(data, options, sourcemaps, sourceLocation, linesMap, erro // @ts-ignore let children = ""; let str = ""; - // let previousStr: string = ""; const indent = indents[level]; const indentSub = indents[level + 1]; switch (data.typ) { @@ -24841,7 +24840,7 @@ function renderAstNode(data, options, sourcemaps, sourceLocation, linesMap, erro 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("/*!")) @@ -24889,15 +24888,6 @@ function renderAstNode(data, options, sourcemaps, sourceLocation, linesMap, erro : 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) @@ -24951,24 +24941,6 @@ function renderAstNode(data, options, sourcemaps, sourceLocation, linesMap, erro move(sourceLocation, linesMap, end); } return prelude + children + end; - // 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: default: return ""; } @@ -32296,6 +32268,7 @@ const parseFile = node_util.deprecate(async (file, options = {}, asStream = fals /** * Parse css * @param args + * @private * * Parsing a string * @@ -32361,6 +32334,7 @@ function parseSync(...args) { * ``` * * @param args + * @private */ function transformSync(...args) { let options; @@ -32413,6 +32387,7 @@ function transformSync(...args) { * @param args * * @throws Error file not found + * @private * * Parsing a string * @@ -32498,7 +32473,7 @@ async function parse(...args) { 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 @@ -32567,6 +32542,7 @@ const transformFile = node_util.deprecate(async (file, options = {}, asStream = * console.log(result.code); * ``` * @param args + * @private */ async function transform(...args) { let options; diff --git a/dist/index.d.ts b/dist/index.d.ts index 22524cb1..c1160d41 100644 --- a/dist/index.d.ts +++ b/dist/index.d.ts @@ -6205,12 +6205,10 @@ declare function transformSync(css: string, options?: TransformSyncOptions): Tra */ declare function transformSync(options: ParseInputOptions & TransformSyncOptions): TransformResult; /** - * Parse css + * Parse CSS * @param stream * @param options * - * @throws Error file not found - * * Example: * * ```ts @@ -6222,7 +6220,7 @@ declare function transformSync(options: ParseInputOptions & TransformSyncOptions * console.log(result.ast); * ``` * - * parsing a Readable stream + * parsing a ReadableStream * * ```ts * @@ -6237,7 +6235,7 @@ declare function transformSync(options: ParseInputOptions & TransformSyncOptions * console.log(result.ast); * ``` * - * Example using fetch and readable stream + * Parsing a file as a ReadableStream * * ```ts * @@ -6324,7 +6322,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 @@ -6349,7 +6347,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 * @@ -6422,7 +6420,7 @@ declare function transform(css: string | ReadableStream, options?: T * console.log(result.code); * ``` * - * Example using fetch + * Parse a file as a ReadableStream * * ```ts * @@ -6436,43 +6434,16 @@ declare function transform(css: string | ReadableStream, options?: T */ declare function transform(options: ParseInputStreamOptions & TransformOptions): Promise; /** - * Transform 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); * ``` */ diff --git a/dist/lib/renderer/render.js b/dist/lib/renderer/render.js index f7710816..faeb33b9 100644 --- a/dist/lib/renderer/render.js +++ b/dist/lib/renderer/render.js @@ -254,7 +254,6 @@ function renderAstNode(data, options, sourcemaps, sourceLocation, linesMap, erro // @ts-ignore let children = ""; let str = ""; - // let previousStr: string = ""; const indent = indents[level]; const indentSub = indents[level + 1]; switch (data.typ) { @@ -265,7 +264,7 @@ function renderAstNode(data, options, sourcemaps, sourceLocation, linesMap, erro 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("/*!")) @@ -313,15 +312,6 @@ function renderAstNode(data, options, sourcemaps, sourceLocation, linesMap, erro : 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) @@ -375,24 +365,6 @@ function renderAstNode(data, options, sourcemaps, sourceLocation, linesMap, erro move(sourceLocation, linesMap, end); } return prelude + children + end; - // 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: default: return ""; } diff --git a/dist/node.js b/dist/node.js index ca818d15..e30a1e25 100644 --- a/dist/node.js +++ b/dist/node.js @@ -137,6 +137,7 @@ const parseFile = deprecate(async (file, options = {}, asStream = false) => pars /** * Parse css * @param args + * @private * * Parsing a string * @@ -202,6 +203,7 @@ function parseSync(...args) { * ``` * * @param args + * @private */ function transformSync(...args) { let options; @@ -254,6 +256,7 @@ function transformSync(...args) { * @param args * * @throws Error file not found + * @private * * Parsing a string * @@ -339,7 +342,7 @@ async function parse(...args) { 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 @@ -408,6 +411,7 @@ const transformFile = deprecate(async (file, options = {}, asStream = false) => * console.log(result.code); * ``` * @param args + * @private */ async function transform(...args) { let options; diff --git a/dist/web.js b/dist/web.js index f0ce3778..68753355 100644 --- a/dist/web.js +++ b/dist/web.js @@ -28,7 +28,7 @@ export { FeatureWalkMode } from './lib/ast/features/type.js'; * @throws Error file not found * * ```ts - * import {load, ResponseType} from '@tbela99/css-parser'; + * import {load, ResponseType} from '@tbela99/css-parser/web'; * const result = await load(file, '.', ResponseType.ArrayBuffer) as ArrayBuffer; * ``` */ @@ -70,7 +70,7 @@ async function load(url, currentDirectory = ".", responseType = false) { * * ```ts * - * import {render, ColorType} from '@tbela99/css-parser'; + * import {render, ColorType} from '@tbela99/css-parser/web'; * * const css = 'body { color: color(from hsl(0 100% 50%) xyz x y z); }'; * const parseResult = await parse(css); @@ -129,12 +129,13 @@ async function parseFile(file, options = {}, asStream = false) { /** * Parse css * @param args + * @private * * Parsing a string * * ```ts * - * import {parseSync} from '@tbela99/css-parser'; + * import {parseSync} from '@tbela99/css-parser/web'; * * // css string * let result = await parseSync(css, {nestingRules: true}); @@ -184,11 +185,11 @@ function parseSync(...args) { return !options.module && !options.inputSourceMap ? result : parseResult(result, options); } /** - * Transform css + * Transform CSS * * ```ts * - * import {transformSync} from '@tbela99/css-parser'; + * import {transformSync} from '@tbela99/css-parser/web'; * * // css string * const result = transformSync(css); @@ -196,6 +197,7 @@ function parseSync(...args) { * ``` * * @param args + * @private */ function transformSync(...args) { let options; @@ -269,6 +271,7 @@ function transformSync(...args) { * console.log(result.ast); * ``` * @param args + * @private */ async function parse(...args) { let options; @@ -317,7 +320,7 @@ async function parse(...args) { 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 @@ -366,6 +369,7 @@ async function transformFile(file, options = {}, asStream = false) { * console.log(result.code); * ``` * @param args + * @private */ async function transform(...args) { let options; diff --git a/files/getting-started.md b/files/getting-started.md index a1702631..1aafb051 100644 --- a/files/getting-started.md +++ b/files/getting-started.md @@ -40,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 3bd1279c..91b6094b 100644 --- a/files/index.md +++ b/files/index.md @@ -10,6 +10,7 @@ children: - ./minification.md - ./transform.md - ./sourcemap.md + - ./plugins.md - ./syntax-lowering.md - ./ast.md - ./utilities.md @@ -24,6 +25,7 @@ children: - [Minification](./minification.md) - [Custom Transform](./transform.md) - [Sourcemap](./sourcemap.md) +- [Plugins API](./plugins.md) - [Syntax Lowering](./syntax-lowering.md) - [Ast Manipulation](./ast.md) - [Utility Functions](./utilities.md) diff --git a/files/minification.md b/files/minification.md index 4d329fac..56a418c6 100644 --- a/files/minification.md +++ b/files/minification.md @@ -657,7 +657,7 @@ Output: } ``` -### CSS prefix removal (Experimental) +### CSS prefix removal This feature is disabled by default. @@ -891,7 +891,7 @@ Output: ### Computed shorthands properties -Below is the list of computed shorthands properties. Minification is fully supported for the propertie with a checkmark. +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..6a003d31 --- /dev/null +++ b/files/plugins.md @@ -0,0 +1,103 @@ +--- +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 (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; + } + + // change node type to EnumToken.String + 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: { + UrlFunctionTokenType: inlineImagesPlugin(maxSize, extensions), + }, +}); + +console.error(result.code); +// .goal .bg-indigo{background:url("data:image/jpg;base64,/9j/4AAQSkZJRgABAQEASABIAAD/4QugRXhpZgAA ...")} +``` + +------ +[← Sourcemap](./sourcemap.md) | [Syntax Lowering →](./syntax-lowering.md) \ No newline at end of file diff --git a/files/sourcemap.md b/files/sourcemap.md index 157f55a1..c8568696 100644 --- a/files/sourcemap.md +++ b/files/sourcemap.md @@ -108,4 +108,4 @@ console.log(result.map.toJSON()); ``` ------ -[← Custom Transform](./transform.md) | [Syntax Lowering →](./syntax-lowering.md) \ No newline at end of file +[← 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 f070397f..e13fe447 100644 --- a/files/syntax-lowering.md +++ b/files/syntax-lowering.md @@ -135,4 +135,4 @@ table.colortable th { ```` ------ -[← Custom Transform](./sourcemap.md) | [Ast Manipulation →](./ast.md) \ No newline at end of file +[← Plugins API](./plugins.md) | [Ast Manipulation →](./ast.md) \ No newline at end of file diff --git a/files/transform.md b/files/transform.md index 92b10cc5..cd3f7471 100644 --- a/files/transform.md +++ b/files/transform.md @@ -6,32 +6,10 @@ 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) - -## Plugin support through the visitor API - -The CSS parser supports plugin-style extensions through its visitor API. 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. +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. -```ts -import {transform, type ParserOptions} from '@tbela99/css-parser'; - -const options: ParserOptions = { - visitor: { - Rule: { - '.card': (node) => { - node.selector = '.card, .panel'; - return node; - } - } - } -}; - -const result = await transform('.card { color: red; }', options); -console.log(result.code); -``` - ## Visitors execution order Visitors can be called when the node is entered, visited or left. @@ -445,97 +423,5 @@ console.debug(await transform(css, options)); // body {color:#f3fff0} ``` - -### Example of plugin - -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 (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; - } - - // change node type to EnumToken.String - 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: { - UrlFunctionTokenType: inlineImagesPlugin(maxSize, extensions), - }, -}); - -console.error(result.code); -// .goal .bg-indigo{background:url("data:image/jpg;base64,/9j/4AAQSkZJRgABAQEASABIAAD/4QugRXhpZgAA ...")} -``` - ------ [← Minification](./minification.md) | [Sourcemap →](./sourcemap.md) \ No newline at end of file diff --git a/jsr.json b/jsr.json index 3cf6e541..2828ebed 100644 --- a/jsr.json +++ b/jsr.json @@ -1,6 +1,6 @@ { "name": "@tbela99/css-parser", - "version": "1.5.0-alpha.1", + "version": "1.5.0", "publish": { "include": [ "src", diff --git a/package.json b/package.json index 165717ee..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.5.0-alpha.1", + "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..5a577a90 100644 --- a/src/@types/ast.d.ts +++ b/src/@types/ast.d.ts @@ -1,6 +1,7 @@ -import {EnumToken} from "../lib/ast/types.ts"; -import {ERRORS, LOC, OPTIMIZED, PARENT, RAW, ROOT, STATE, TOKENS} from "../lib/syntax/constants.ts"; -import type {Token} from "./token.d.ts"; +import { EnumToken } from "../lib/ast/types.ts"; +import { ERRORS, LOC, OPTIMIZED, PARENT, RAW, ROOT, STATE, TOKENS } from "../lib/syntax/constants.ts"; +import type { Token } from "./token.d.ts"; +import type { AstNode } from "./ast.d.ts"; /** * token or node location @@ -75,7 +76,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,7 +221,7 @@ 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 */ @@ -378,7 +379,7 @@ export declare type AstRuleList = | AstAtRule | AstRule | AstKeyframesAtRule - | AstKeyFrameRule + | AstKeyframesRule | AstInvalidRule; /** @@ -406,7 +407,7 @@ export declare type AstNode = | AstRule | AstDeclaration | AstKeyframesAtRule - | AstKeyFrameRule + | AstKeyframesRule | AstInvalidRule | AstInvalidAtRule | AstInvalidDeclaration diff --git a/src/@types/index.d.ts b/src/@types/index.d.ts index d32373ff..70fcdea3 100644 --- a/src/@types/index.d.ts +++ b/src/@types/index.d.ts @@ -7,6 +7,7 @@ import type { CssVariableToken, Token } from "./token.d.ts"; import { FeatureWalkMode } from "../lib/ast/features/type.ts"; import { ValidationToken } from "../lib/validation/parser/types"; import { SourceFile } from "../lib/parser/source.ts"; +import type { VisitorSyncNodeMap, VisitorNodeMap } from "./visitor.d.ts"; export * from "./ast.d.ts"; export * from "./token.d.ts"; @@ -447,10 +448,21 @@ export declare interface ParseInputStreamOptions { * @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 @@ -462,6 +474,9 @@ export declare interface ParserSourceMapOptions { inputSourceMap?: SourceMapObject | string; } +/** + * Sync parseroptions + */ export declare interface ParserSyncOptions extends MinifyOptions, diff --git a/src/@types/token.d.ts b/src/@types/token.d.ts index 1c800e3e..667ebd2e 100644 --- a/src/@types/token.d.ts +++ b/src/@types/token.d.ts @@ -6,7 +6,7 @@ import { ColorType, EnumToken, EnumAstNodeStatus } from "../lib/ast/types.ts"; */ export declare interface LiteralToken extends BaseToken { /** - * literal type + * @inheritdoc */ typ: EnumToken.LiteralTokenType; /** @@ -20,7 +20,7 @@ export declare interface LiteralToken extends BaseToken { */ export declare interface ClassSelectorToken extends BaseToken { /** - * class selector type + * @inheritdoc */ typ: EnumToken.ClassSelectorTokenType; /** @@ -34,7 +34,7 @@ export declare interface ClassSelectorToken extends BaseToken { */ export declare interface InvalidClassSelectorToken extends BaseToken { /** - * invalid class selector type + * @inheritdoc */ typ: EnumToken.InvalidClassSelectorTokenType; /** @@ -48,7 +48,7 @@ export declare interface InvalidClassSelectorToken extends BaseToken { */ export declare interface UniversalSelectorToken extends BaseToken { /** - * universal selector type + * @inheritdoc */ typ: EnumToken.UniversalSelectorTokenType; } @@ -58,7 +58,7 @@ export declare interface UniversalSelectorToken extends BaseToken { */ export declare interface IdentToken extends BaseToken { /** - * ident type + * @inheritdoc */ typ: EnumToken.IdenTokenType; /** @@ -72,7 +72,7 @@ export declare interface IdentToken extends BaseToken { */ export declare interface IdentListToken extends BaseToken { /** - * ident list type + * @inheritdoc */ typ: EnumToken.IdenListTokenType; /** @@ -86,7 +86,7 @@ export declare interface IdentListToken extends BaseToken { */ export declare interface DashedIdentToken extends BaseToken { /** - * ident type + * @inheritdoc */ typ: EnumToken.DashedIdenTokenType; /** @@ -100,7 +100,7 @@ export declare interface DashedIdentToken extends BaseToken { */ export declare interface CommaToken extends BaseToken { /** - * comma type + * @inheritdoc */ typ: EnumToken.CommaTokenType; } @@ -110,7 +110,7 @@ export declare interface CommaToken extends BaseToken { */ export declare interface ColonToken extends BaseToken { /** - * colon type ':' + * @inheritdoc */ typ: EnumToken.ColonTokenType; } @@ -120,7 +120,7 @@ export declare interface ColonToken extends BaseToken { */ export declare interface DoubleColonToken extends BaseToken { /** - * double colon type '::' + * @inheritdoc */ typ: EnumToken.DoubleColonTokenType; } @@ -130,7 +130,7 @@ export declare interface DoubleColonToken extends BaseToken { */ export declare interface SemiColonToken extends BaseToken { /** - * semicolon type + * @inheritdoc */ typ: EnumToken.SemiColonTokenType; } @@ -140,7 +140,7 @@ export declare interface SemiColonToken extends BaseToken { */ export declare interface NestingSelectorToken extends BaseToken { /** - * nesting selector type + * @inheritdoc */ typ: EnumToken.NestingSelectorTokenType; } @@ -150,7 +150,7 @@ export declare interface NestingSelectorToken extends BaseToken { */ export declare interface NumberToken extends BaseToken { /** - * number type + * @inheritdoc */ typ: EnumToken.NumberTokenType; /** @@ -168,7 +168,7 @@ export declare interface NumberToken extends BaseToken { */ export declare interface AtRuleToken extends BaseToken { /** - * at rule type + * @inheritdoc */ typ: EnumToken.AtRuleTokenType; /** @@ -186,7 +186,7 @@ export declare interface AtRuleToken extends BaseToken { */ export declare interface PercentageToken extends BaseToken { /** - * percentage type + * @inheritdoc */ typ: EnumToken.PercentageTokenType; /** @@ -200,7 +200,7 @@ export declare interface PercentageToken extends BaseToken { */ export declare interface FlexToken extends BaseToken { /** - * flex type + * @inheritdoc */ typ: EnumToken.FlexTokenType; /** @@ -242,7 +242,7 @@ export declare interface FunctionToken extends BaseToken { */ export declare interface GridTemplateFuncToken extends BaseToken { /** - * function type + * @inheritdoc */ typ: EnumToken.GridTemplateFuncTokenType; /** @@ -260,7 +260,7 @@ export declare interface GridTemplateFuncToken extends BaseToken { */ export declare interface FunctionURLToken extends BaseToken { /** - * function type + * @inheritdoc */ typ: EnumToken.UrlFunctionTokenType; /** @@ -278,7 +278,7 @@ export declare interface FunctionURLToken extends BaseToken { */ export declare interface FunctionImageToken extends BaseToken { /** - * function type + * @inheritdoc */ typ: EnumToken.ImageFunctionTokenType; /** @@ -305,7 +305,7 @@ export declare interface FunctionImageToken extends BaseToken { */ export declare interface TimingFunctionToken extends BaseToken { /** - * timing function type + * @inheritdoc */ typ: EnumToken.TimingFunctionTokenType; /** @@ -323,7 +323,7 @@ export declare interface TimingFunctionToken extends BaseToken { */ export declare interface TimelineFunctionToken extends BaseToken { /** - * timeline function type + * @inheritdoc */ typ: EnumToken.TimelineFunctionTokenType; /** @@ -341,7 +341,7 @@ export declare interface TimelineFunctionToken extends BaseToken { */ export declare interface StringToken extends BaseToken { /** - * string type + * @inheritdoc */ typ: EnumToken.StringTokenType; /** @@ -355,7 +355,7 @@ export declare interface StringToken extends BaseToken { */ export declare interface BadStringToken extends BaseToken { /** - * bad string type + * @inheritdoc */ typ: EnumToken.BadStringTokenType; /** @@ -369,7 +369,7 @@ export declare interface BadStringToken extends BaseToken { */ export declare interface UnclosedStringToken extends BaseToken { /** - * unclosed string type + * @inheritdoc */ typ: EnumToken.UnclosedStringTokenType; /** @@ -383,7 +383,7 @@ export declare interface UnclosedStringToken extends BaseToken { */ export declare interface DimensionToken extends BaseToken { /** - * dimension type + * @inheritdoc */ typ: EnumToken.DimensionTokenType; /** @@ -401,7 +401,7 @@ export declare interface DimensionToken extends BaseToken { */ export declare interface LengthToken extends BaseToken { /** - * length type + * @inheritdoc */ typ: EnumToken.LengthTokenType; /** @@ -419,7 +419,7 @@ export declare interface LengthToken extends BaseToken { */ export declare interface AngleToken extends BaseToken { /** - * angle type + * @inheritdoc */ typ: EnumToken.AngleTokenType; /** @@ -437,7 +437,7 @@ export declare interface AngleToken extends BaseToken { */ export declare interface TimeToken extends BaseToken { /** - * time type + * @inheritdoc */ typ: EnumToken.TimeTokenType; /** @@ -445,7 +445,7 @@ export declare interface TimeToken extends BaseToken { */ val: number | FractionToken; /** - * time unit + * time unit */ unit: "ms" | "s"; } @@ -455,7 +455,7 @@ export declare interface TimeToken extends BaseToken { */ export declare interface FrequencyToken extends BaseToken { /** - * frequency type + * @inheritdoc */ typ: EnumToken.FrequencyTokenType; /** @@ -473,7 +473,7 @@ export declare interface FrequencyToken extends BaseToken { */ export declare interface ResolutionToken extends BaseToken { /** - * resolution type + * @inheritdoc */ typ: EnumToken.ResolutionTokenType; /** @@ -491,7 +491,7 @@ export declare interface ResolutionToken extends BaseToken { */ export declare interface HashToken extends BaseToken { /** - * hash type + * @inheritdoc */ typ: EnumToken.HashTokenType; /** @@ -505,7 +505,7 @@ export declare interface HashToken extends BaseToken { */ export declare interface BlockStartToken extends BaseToken { /** - * block start type + * @inheritdoc */ typ: EnumToken.BlockStartTokenType; } @@ -515,7 +515,7 @@ export declare interface BlockStartToken extends BaseToken { */ export declare interface BlockEndToken extends BaseToken { /** - * block end type + * @inheritdoc */ typ: EnumToken.BlockEndTokenType; } @@ -525,7 +525,7 @@ export declare interface BlockEndToken extends BaseToken { */ export declare interface AttrStartToken extends BaseToken { /** - * attribute start type + * @inheritdoc */ typ: EnumToken.AttrStartTokenType; /** @@ -539,7 +539,7 @@ export declare interface AttrStartToken extends BaseToken { */ export declare interface AttrEndToken extends BaseToken { /** - * attribute end type + * @inheritdoc */ typ: EnumToken.AttrEndTokenType; } @@ -549,7 +549,7 @@ export declare interface AttrEndToken extends BaseToken { */ export declare interface ParensStartToken extends BaseToken { /** - * parenthesis start type + * @inheritdoc */ typ: EnumToken.StartParensTokenType; } @@ -559,7 +559,7 @@ export declare interface ParensStartToken extends BaseToken { */ export declare interface ParensEndToken extends BaseToken { /** - * parenthesis end type + * @inheritdoc */ typ: EnumToken.EndParensTokenType; } @@ -569,7 +569,7 @@ export declare interface ParensEndToken extends BaseToken { */ export declare interface ParensToken extends BaseToken { /** - * parenthesis type + * @inheritdoc */ typ: EnumToken.ParensTokenType; /** @@ -583,7 +583,7 @@ export declare interface ParensToken extends BaseToken { */ export declare interface WhitespaceToken extends BaseToken { /** - * whitespace type + * @inheritdoc */ typ: EnumToken.WhitespaceTokenType; /** @@ -597,7 +597,7 @@ export declare interface WhitespaceToken extends BaseToken { */ export declare interface CommentToken extends BaseToken { /** - * comment type + * @inheritdoc */ typ: EnumToken.CommentTokenType; /** @@ -611,7 +611,7 @@ export declare interface CommentToken extends BaseToken { */ export declare interface BadCommentToken extends BaseToken { /** - * bad comment type + * @inheritdoc */ typ: EnumToken.BadCommentTokenType; /** @@ -624,7 +624,13 @@ export declare interface BadCommentToken extends BaseToken { * CDO comment token */ export declare interface CDOCommentToken extends BaseToken { + /** + * @inheritdoc + */ typ: EnumToken.CDOCOMMTokenType; + /** + * CDO comment value + */ val: string; } @@ -633,7 +639,7 @@ export declare interface CDOCommentToken extends BaseToken { */ export declare interface BadCDOCommentToken extends BaseToken { /** - * bad CDO comment type + * @inheritdoc */ typ: EnumToken.BadCdoTokenType; /** @@ -647,7 +653,7 @@ export declare interface BadCDOCommentToken extends BaseToken { */ export declare interface IncludeMatchToken extends BaseToken { /** - * include match type + * @inheritdoc */ typ: EnumToken.IncludeMatchTokenType; // val: '~='; @@ -658,7 +664,7 @@ export declare interface IncludeMatchToken extends BaseToken { */ export declare interface DashMatchToken extends BaseToken { /** - * dash match type + * @inheritdoc */ typ: EnumToken.DashMatchTokenType; // val: '|='; @@ -669,7 +675,7 @@ export declare interface DashMatchToken extends BaseToken { */ export declare interface EqualMatchToken extends BaseToken { /** - * equal match type + * @inheritdoc */ typ: EnumToken.EqualMatchTokenType; // val: '|='; @@ -680,7 +686,7 @@ export declare interface EqualMatchToken extends BaseToken { */ export declare interface StartMatchToken extends BaseToken { /** - * start match type + * @inheritdoc */ typ: EnumToken.StartMatchTokenType; // val: '^='; @@ -691,7 +697,7 @@ export declare interface StartMatchToken extends BaseToken { */ export declare interface EndMatchToken extends BaseToken { /** - * end match type + * @inheritdoc */ typ: EnumToken.EndMatchTokenType; // val: '|='; @@ -702,7 +708,7 @@ export declare interface EndMatchToken extends BaseToken { */ export declare interface ContainMatchToken extends BaseToken { /** - * contain match type + * @inheritdoc */ typ: EnumToken.ContainMatchTokenType; // val: '|='; @@ -713,7 +719,7 @@ export declare interface ContainMatchToken extends BaseToken { */ export declare interface LessThanToken extends BaseToken { /** - * less than type + * @inheritdoc */ typ: EnumToken.LtTokenType; } @@ -723,7 +729,7 @@ export declare interface LessThanToken extends BaseToken { */ export declare interface LessThanOrEqualToken extends BaseToken { /** - * less than or equal type + * @inheritdoc */ typ: EnumToken.LteTokenType; } @@ -733,7 +739,7 @@ export declare interface LessThanOrEqualToken extends BaseToken { */ export declare interface GreaterThanToken extends BaseToken { /** - * greater than type + * @inheritdoc */ typ: EnumToken.GtTokenType; } @@ -743,7 +749,7 @@ export declare interface GreaterThanToken extends BaseToken { */ export declare interface GreaterThanOrEqualToken extends BaseToken { /** - * greater than or equal type + * @inheritdoc */ typ: EnumToken.GteTokenType; } @@ -753,7 +759,7 @@ export declare interface GreaterThanOrEqualToken extends BaseToken { */ export declare interface ColumnCombinatorToken extends BaseToken { /** - * column combinator type + * @inheritdoc */ typ: EnumToken.ColumnCombinatorTokenType; } @@ -763,7 +769,7 @@ export declare interface ColumnCombinatorToken extends BaseToken { */ export declare interface PseudoClassToken extends BaseToken { /** - * Pseudo class + * @inheritdoc */ typ: EnumToken.PseudoClassTokenType; /** @@ -777,7 +783,7 @@ export declare interface PseudoClassToken extends BaseToken { */ export declare interface PseudoElementToken extends BaseToken { /** - * Pseudo element + * @inheritdoc */ typ: EnumToken.PseudoElementTokenType; /** @@ -791,7 +797,7 @@ export declare interface PseudoElementToken extends BaseToken { */ export declare interface PseudoPageToken extends BaseToken { /** - * Pseudo page + * @inheritdoc */ typ: EnumToken.PseudoPageTokenType; /** @@ -805,7 +811,7 @@ export declare interface PseudoPageToken extends BaseToken { */ export declare interface PseudoClassFunctionToken extends BaseToken { /** - * Pseudo class function + * @inheritdoc */ typ: EnumToken.PseudoClassFuncTokenType; /** @@ -823,7 +829,7 @@ export declare interface PseudoClassFunctionToken extends BaseToken { */ export declare interface DelimToken extends BaseToken { /** - * Delimiter token type + * @inheritdoc */ typ: EnumToken.DelimTokenType; } @@ -833,7 +839,7 @@ export declare interface DelimToken extends BaseToken { */ export declare interface BadUrlToken extends BaseToken { /** - * Bad URL + * @inheritdoc */ typ: EnumToken.BadUrlTokenType; /** @@ -847,7 +853,7 @@ export declare interface BadUrlToken extends BaseToken { */ export declare interface UrlToken extends BaseToken { /** - * URL + * @inheritdoc */ typ: EnumToken.UrlTokenTokenType; /** @@ -861,7 +867,7 @@ export declare interface UrlToken extends BaseToken { */ export declare interface EOFToken extends BaseToken { /** - * End of file + * @inheritdoc */ typ: EnumToken.EOFTokenType; } @@ -871,7 +877,7 @@ export declare interface EOFToken extends BaseToken { */ export declare interface ImportantToken extends BaseToken { /** - * Important + * @inheritdoc */ typ: EnumToken.ImportantTokenType; } @@ -881,7 +887,7 @@ export declare interface ImportantToken extends BaseToken { */ export declare interface ColorToken extends BaseToken { /** - * Color type + * @inheritdoc */ typ: EnumToken.ColorTokenType; /** @@ -907,7 +913,7 @@ export declare interface ColorToken extends BaseToken { */ export declare interface AttrToken extends BaseToken { /** - * Attribute type + * @inheritdoc */ typ: EnumToken.AttrTokenType; /** @@ -921,7 +927,7 @@ export declare interface AttrToken extends BaseToken { */ export declare interface InvalidAttrToken extends BaseToken { /** - * Attribute type + * @inheritdoc */ typ: EnumToken.InvalidAttrTokenType; /** @@ -934,6 +940,9 @@ export declare interface InvalidAttrToken extends BaseToken { * Child combinator token */ export declare interface ChildCombinatorToken extends BaseToken { + /** + * @inheritdoc + */ typ: EnumToken.ChildCombinatorTokenType; } @@ -942,7 +951,7 @@ export declare interface ChildCombinatorToken extends BaseToken { */ export declare interface MediaFeatureToken extends BaseToken { /** - * Media feature type + * @inheritdoc */ typ: EnumToken.MediaFeatureTokenType; /** @@ -956,7 +965,7 @@ export declare interface MediaFeatureToken extends BaseToken { */ export declare interface NotToken extends BaseToken { /** - * Media feature not type + * @inheritdoc */ typ: EnumToken.NotTokenType; /** @@ -970,7 +979,7 @@ export declare interface NotToken extends BaseToken { */ export declare interface MediaFeatureOnlyToken extends BaseToken { /** - * Media feature only type + * @inheritdoc */ typ: EnumToken.OnlyTokenType; /** @@ -984,7 +993,7 @@ export declare interface MediaFeatureOnlyToken extends BaseToken { */ export declare interface AndToken extends BaseToken { /** - * Media feature and type + * @inheritdoc */ typ: EnumToken.AndTokenType; } @@ -994,7 +1003,7 @@ export declare interface AndToken extends BaseToken { */ export declare interface OrToken extends BaseToken { /** - * Media feature or type + * @inheritdoc */ typ: EnumToken.OrTokenType; } @@ -1004,7 +1013,7 @@ export declare interface OrToken extends BaseToken { */ export declare interface MediaQueryUnaryFeatureToken extends BaseToken { /** - * Media query condition type + * @inheritdoc */ typ: EnumToken.MediaQueryUnaryFeatureTokenType; /** @@ -1019,7 +1028,7 @@ export declare interface MediaQueryUnaryFeatureToken extends BaseToken { export declare interface SupportsQueryUnaryConditionToken extends BaseToken { /** - * Supports query condition type + * @inheritdoc */ typ: EnumToken.SupportsQueryUnaryConditionTokenType; /** @@ -1034,7 +1043,7 @@ export declare interface SupportsQueryUnaryConditionToken extends BaseToken { export declare interface SupportsQueryConditionToken extends BaseToken { /** - * Supports query condition type + * @inheritdoc */ typ: EnumToken.SupportsQueryConditionTokenType; /** @@ -1053,7 +1062,7 @@ export declare interface SupportsQueryConditionToken extends BaseToken { export declare interface WhenElseQueryConditionToken extends BaseToken { /** - * When else query condition type + * @inheritdoc */ typ: EnumToken.WhenElseQueryConditionTokenType; /** @@ -1072,7 +1081,7 @@ export declare interface WhenElseQueryConditionToken extends BaseToken { export declare interface WhenElseUnaryConditionToken extends BaseToken { /** - * When else query condition type + * @inheritdoc */ typ: EnumToken.WhenElseUnaryConditionTokenType; /** @@ -1087,7 +1096,7 @@ export declare interface WhenElseUnaryConditionToken extends BaseToken { export declare interface MediaQueryConditionToken extends BaseToken { /** - * Media query condition type + * @inheritdoc */ typ: EnumToken.MediaQueryConditionTokenType; /** @@ -1114,7 +1123,7 @@ export declare interface MediaQueryConditionToken extends BaseToken { export declare interface IfConditionToken extends BaseToken { /** - * If condition type + * @inheritdoc */ typ: EnumToken.IfConditionTokenType; /** @@ -1129,7 +1138,7 @@ export declare interface IfConditionToken extends BaseToken { export declare interface IfElseConditionToken extends BaseToken { /** - * If else condition type + * @inheritdoc */ typ: EnumToken.IfElseConditionTokenType; /** @@ -1143,23 +1152,67 @@ export declare interface IfElseConditionToken extends BaseToken { } export declare interface ContainerStyleRangeToken extends BaseToken { + /** + * @inheritdoc + */ typ: EnumToken.ContainerStyleRangeTokenType; + /** + * condition left handle + */ l: Token[]; + /** + * condition operator + */ op: Token[]; + /** + * condition value + */ r: Token[]; } +// (20px <= width < 30px) +/** + * @inheritdoc + */ export declare interface MediaRangeQueryToken extends BaseToken { + /** + * @inheritdoc + */ typ: EnumToken.MediaRangeQueryTokenType; + /** + * left hanle + * */ l: Token[]; + /** + * media feature name + */ val: Token[]; + /** + * first comparator + */ op1: LessThanToken | GreaterThanToken | LessThanOrEqualToken | GreaterThanOrEqualToken; + /** + * second comparator + */ op2: LessThanToken | GreaterThanToken | LessThanOrEqualToken | GreaterThanOrEqualToken; + /** + * right handle + */ r: Token[]; } +/** + * @inheritdoc + */ export declare interface InvalidMediaQueryToken extends BaseToken { + /** + * @inheritdoc + */ typ: EnumToken.InvalidMediaQueryTokenType; + + /** + * children + */ chi: Token[]; } @@ -1167,6 +1220,9 @@ export declare interface InvalidMediaQueryToken extends BaseToken { * Descendant combinator token */ export declare interface DescendantCombinatorToken extends BaseToken { + /** + * @inheritdoc + */ typ: EnumToken.DescendantCombinatorTokenType; } @@ -1174,6 +1230,9 @@ export declare interface DescendantCombinatorToken extends BaseToken { * Next sibling combinator token */ export declare interface NextSiblingCombinatorToken extends BaseToken { + /** + * @inheritdoc + */ typ: EnumToken.NextSiblingCombinatorTokenType; } @@ -1181,6 +1240,9 @@ export declare interface NextSiblingCombinatorToken extends BaseToken { * Subsequent sibling combinator token */ export declare interface SubsequentCombinatorToken extends BaseToken { + /** + * @inheritdoc + */ typ: EnumToken.SubsequentSiblingCombinatorTokenType; } @@ -1188,6 +1250,9 @@ export declare interface SubsequentCombinatorToken extends BaseToken { * Add token */ export declare interface AddToken extends BaseToken { + /** + * @inheritdoc + */ typ: EnumToken.Add; } @@ -1195,6 +1260,9 @@ export declare interface AddToken extends BaseToken { * Sub token */ export declare interface SubToken extends BaseToken { + /** + * @inheritdoc + */ typ: EnumToken.Sub; } @@ -1202,6 +1270,9 @@ export declare interface SubToken extends BaseToken { * Div token */ export declare interface DivToken extends BaseToken { + /** + * @inheritdoc + */ typ: EnumToken.Div; } @@ -1210,7 +1281,7 @@ export declare interface DivToken extends BaseToken { */ export declare interface MulToken extends BaseToken { /** - * Type + * @inheritdoc */ typ: EnumToken.Mul; } @@ -1220,7 +1291,7 @@ export declare interface MulToken extends BaseToken { */ export declare interface WrappedValuesToken extends BaseToken { /** - * Type + * @inheritdoc */ typ: EnumToken.WrappedValuesTokenType; /** @@ -1234,7 +1305,7 @@ export declare interface WrappedValuesToken extends BaseToken { */ export declare interface UnaryExpression extends BaseToken { /** - * Type + * @inheritdoc */ typ: EnumToken.UnaryExpressionTokenType; /** @@ -1251,8 +1322,17 @@ export declare interface UnaryExpression extends BaseToken { * Fraction token */ export declare interface FractionToken extends BaseToken { + /** + * @inheritdoc + */ typ: EnumToken.FractionTokenType; + /** + * Left handle + */ l: NumberToken; + /** + * Right handle + */ r: NumberToken; } @@ -1260,9 +1340,21 @@ export declare interface FractionToken extends BaseToken { * Binary expression token */ export declare interface BinaryExpressionToken extends BaseToken { + /** + * @inheritdoc + */ typ: EnumToken.BinaryExpressionTokenType; + /** + * Operator + */ op: EnumToken.Add | EnumToken.Sub | EnumToken.Div | EnumToken.Mul; + /** + * Left handle + */ l: BinaryExpressionNode | Token; + /** + * Right handle + */ r: BinaryExpressionNode | Token; } @@ -1270,10 +1362,25 @@ export declare interface BinaryExpressionToken extends BaseToken { * Match expression token */ export declare interface MatchExpressionToken extends BaseToken { + /** + * @inheritdoc + */ typ: EnumToken.MatchExpressionTokenType; + /** + * Operator + */ op: EqualMatchToken | DashMatchToken | StartMatchToken | ContainMatchToken | EndMatchToken | IncludeMatchToken; + /** + * Left handle + */ l: Token; + /** + * Right handle + */ r: Token; + /** + * Flags + */ attr?: "i" | "s"; } @@ -1281,8 +1388,17 @@ export declare interface MatchExpressionToken extends BaseToken { * Name space attribute token */ export declare interface NameSpaceAttributeToken extends BaseToken { + /** + * @inheritdoc + */ typ: EnumToken.NameSpaceAttributeTokenType; + /** + * Left handle + */ l?: Token; + /** + * Right handle + */ r: Token; } @@ -1290,7 +1406,13 @@ export declare interface NameSpaceAttributeToken extends BaseToken { * List token */ export declare interface ListToken extends BaseToken { + /** + * @inheritdoc + */ typ: EnumToken.ListToken; + /** + * Children + */ chi: Token[]; } @@ -1298,8 +1420,17 @@ export declare interface ListToken extends BaseToken { * Composes selector token */ export declare interface ComposesSelectorToken extends BaseToken { + /** + * @inheritdoc + */ typ: EnumToken.ComposesSelectorTokenType; + /** + * Left handle + */ l: Token[]; + /** + * Right handle + */ r: Token | null; } @@ -1307,20 +1438,53 @@ export declare interface ComposesSelectorToken extends BaseToken { * Css variable token */ export declare interface CssVariableToken extends BaseToken { + /** + * @inheritdoc + */ typ: EnumToken.CssVariableTokenType; + /** + * Name + */ nam: string; + /** + * Value + */ val: Token[]; } +/** + * Css variable import token + */ export declare interface CssVariableImportTokenType extends BaseToken { + /** + * @inheritdoc + */ typ: EnumToken.CssVariableImportTokenType; + /** + * Name + */ nam: string; + /** + * Value + */ val: Token[]; } +/** + * Css variable map token + */ export declare interface CssVariableMapTokenType extends BaseToken { + /** + * @inheritdoc + */ typ: EnumToken.CssVariableDeclarationMapTokenType; + /** + * CSS Variables + */ vars: Token[]; + /** + * From clause + */ from: Token[]; } @@ -1328,6 +1492,9 @@ export declare interface CssVariableMapTokenType extends BaseToken { * Function definition token */ export declare interface FunctionDefToken extends BaseToken { + /** + * @inheritdoc + */ typ: | EnumToken.FunctionDefTokenType | EnumToken.UrlFunctionTokenDefType @@ -1339,7 +1506,13 @@ export declare interface FunctionDefToken extends BaseToken { | EnumToken.MathFunctionTokenDefType | EnumToken.PseudoClassFunctionTokenDefType | EnumToken.TransformFunctionTokenDefType; + /** + * Name + */ nam: string; + /** + * Value + */ val: string; } @@ -1347,7 +1520,13 @@ export declare interface FunctionDefToken extends BaseToken { * Raw node token */ export declare interface RawNodeToken extends BaseToken, EnumAstNodeStatus { + /** + * @inheritdoc + */ typ: EnumToken.RawNodeTokenType; + /** + * Value + */ val: Token[]; } diff --git a/src/@types/validation.d.ts b/src/@types/validation.d.ts index aa630143..b7b29f38 100644 --- a/src/@types/validation.d.ts +++ b/src/@types/validation.d.ts @@ -4,67 +4,106 @@ import type { Token } from "./token.d.ts"; import type { ValidationOptions } from "./index.d.ts"; import { MediaFeatureType, ValidationSyntaxGroupEnum } from "../lib/validation/parser/typedef.ts"; +/** + * Validation syntax + * @internal + */ export declare interface ValidationSyntaxNode { + /** + * mdn data syntax + */ syntax: string; + /** + * validation tokens + */ ast?: ValidationToken[]; + /** + * descriptors + */ descriptors?: Record>; } +/** + * Validation selector options + * @internal + */ export interface ValidationSelectorOptions extends ValidationOptions { + /** + * nested selector + */ nestedSelector?: boolean; } +/** + * Validation media feature + * @internal + */ export declare interface ValidationMediaFeature { + /** + * media feature type + */ type: MediaFeatureType; + /** + * media feature status + */ status?: string; + /** + * media feature category + */ category: string; + /** + * media feature values + */ values?: Array | Array; } +/** + * Validation configuration + * @internal + */ export declare type ValidationConfiguration = Record< ValidationSyntaxGroupEnum, ValidationSyntaxNode | Record | Record >; +/** + * Validation result + * @internal + */ export interface ValidationResult { + /** + * validation result + */ valid: SyntaxValidationResult; + /** + * node + */ node: AstNode | Token | null; + /** + * syntax + */ syntax: ValidationToken | string | null; + /** + * error + */ error: string; + /** + * cycle + */ cycle?: boolean; } +/** + * Validation syntax result + * @internal + */ export interface ValidationSyntaxResult extends ValidationResult { + /** + * syntax + */ syntax: ValidationToken | string | null; - context: Context | Token[]; -} - -export interface Context { - index: number; - /** - * The length of the context tokens to be consumed + * context */ - - readonly length: number; - - current(): Type | null; - - update(context: Context): void; - - consume(token: Type, howMany?: number): boolean; - - peek(): Type | null; - - // tokens(): Type[]; - - next(): Type | null; - - consume(token: Type, howMany?: number): boolean; - - slice(): Type[]; - - clone(): Context; - - done(): boolean; + context: ValidationContext | Token[]; } diff --git a/src/@types/visitor.d.ts b/src/@types/visitor.d.ts index 321a2450..8fc8d380 100644 --- a/src/@types/visitor.d.ts +++ b/src/@types/visitor.d.ts @@ -2,24 +2,40 @@ import type { AstAtRule, AstDeclaration, AstKeyframesAtRule, AstKeyframesRule, A import { WalkerEvent } from "../lib/ast/walk.ts"; import { EnumToken } from "../lib/ast/types.ts"; +/** + * Generic visitor result + */ export declare type GenericVisitorSyncResult = T | T[] | null; -export declare type GenericVisitorAsyncResult = Promise | Promise| Promise; +/** + * Generic visitor result + */ +export declare type GenericVisitorAsyncResult = Promise | Promise | Promise; +/** + * Generic visitor result + */ export declare type GenericVisitorResult = GenericVisitorSyncResult | GenericVisitorAsyncResult; - - +/** + * Generic visitor handler + */ export declare type GenericVisitorSyncHandler = ( node: T, parent?: AstNode | Token, root?: AstNode | Token, ) => GenericVisitorSyncResult; +/** + * Generic visitor handler + */ export declare type GenericVisitorAstNodeSyncHandlerMap = | Record> | GenericVisitorSyncHandler | { type: WalkerEvent; handler: GenericVisitorSyncHandler } | { type: WalkerEvent; handler: Record> }; +/** + * Generic visitor handler + */ export declare type ValueVisitorSyncHandler = GenericVisitorSyncHandler; /** @@ -229,8 +245,14 @@ export declare interface VisitorSyncNodeMap { */ Rule?: GenericVisitorAstNodeSyncHandlerMap; + /** + * keyframes rule visitor + */ KeyframesRule?: GenericVisitorAstNodeSyncHandlerMap; + /** + * keyframes at-rule visitor + */ KeyframesAtRule?: GenericVisitorAstNodeSyncHandlerMap; /** @@ -285,22 +307,32 @@ export declare interface VisitorSyncNodeMap { * // body {color:#f3fff0} * ``` */ - [key: keyof typeof EnumToken]: GenericVisitorAstNodeSyncHandlerMap | GenericVisitorAstNodeSyncHandlerMap; + [key: keyof typeof EnumToken]: + | GenericVisitorAstNodeSyncHandlerMap + | GenericVisitorAstNodeSyncHandlerMap; } - +/** + * Generic visitor handler + */ export declare type GenericVisitorHandler = ( node: T, parent?: AstNode | Token, root?: AstNode | Token, ) => GenericVisitorSyncResult | GenericVisitorAsyncResult; +/** + * Generic visitor handler + */ export declare type GenericVisitorAstNodeHandlerMap = | Record> | GenericVisitorHandler | { type: WalkerEvent; handler: GenericVisitorHandler } | { type: WalkerEvent; handler: Record> }; +/** + * Generic visitor handler + */ export declare type ValueVisitorHandler = GenericVisitorHandler; /** diff --git a/src/@types/walker.d.ts b/src/@types/walker.d.ts index 8eedabc6..c8868e69 100644 --- a/src/@types/walker.d.ts +++ b/src/@types/walker.d.ts @@ -33,18 +33,54 @@ export declare type WalkerValueFilter = ( parents?: Generator, ) => WalkerOption | null; +/** + * walker result + */ export declare interface WalkResult { + /** + * current node + */ node: AstNode; + /** + * parent node + */ parent?: AstRuleList; + /** + * root node + */ root?: AstNode; + /** + * parent nodes + */ parents: Generator; } +/** + * walker result + */ export declare interface WalkAttributesResult { + /** + * current node + */ value: Token; + /** + * previous node + */ previousValue: Token | null; + /** + * next node + */ nextValue: Token | null; + /** + * root node + */ root?: AstNode | Token | null; + /** + * parent node + */ parent: AstNode | Token | null; + /** + * parent nodes + */ parents: Generator; } diff --git a/src/lib/ast/find.ts b/src/lib/ast/find.ts index 89647f2a..c9933a11 100644 --- a/src/lib/ast/find.ts +++ b/src/lib/ast/find.ts @@ -1,8 +1,8 @@ -import type {Token} from "../../@types/token.d.ts"; -import type {AstDeclaration, AstNode, AstValueMatcher, TokenSearchResult} from "../../@types/ast.d.ts"; -import {EnumToken} from "./types.ts"; -import {walk, walkValues} from "./walk.ts"; -import {PARENT, TOKENS} from "../syntax/constants.ts"; +import type { Token } from "../../@types/token.d.ts"; +import type { AstDeclaration, AstNode, AstValueMatcher, TokenSearchResult } from "../../@types/ast.d.ts"; +import { EnumToken } from "./types.ts"; +import { walk, walkValues } from "./walk.ts"; +import { PARENT, TOKENS } from "../syntax/constants.ts"; /** * Search the ast subtree and return the first match @@ -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, diff --git a/src/lib/ast/minify.ts b/src/lib/ast/minify.ts index f281a946..b5f85ae7 100644 --- a/src/lib/ast/minify.ts +++ b/src/lib/ast/minify.ts @@ -1,12 +1,12 @@ -import { eq } from "../parser/utils/eq.ts"; -import { doRender, renderValue } from "../renderer/render.ts"; +import {eq} from "../parser/utils/eq.ts"; +import {doRender, renderValue} from "../renderer/render.ts"; import * as allFeatures from "./features/index.ts"; -import { walkValues } from "./walk.ts"; +import {walkValues} from "./walk.ts"; import type { AstAtRule, AstDeclaration, - AstKeyFrameRule, AstKeyframesAtRule, + AstKeyframesRule, AstNode, AstRule, AstStyleSheet, @@ -24,15 +24,15 @@ import type { RawSelectorTokens, Token, } from "../../@types/index.d.ts"; -import { EnumToken } from "./types.ts"; -import { isFunction, isIdent, isIdentStart, isWhiteSpace } from "../syntax/syntax.ts"; -import { FeatureWalkMode } from "./features/type.ts"; -import { trimArray } from "../validation/match.ts"; -import { combinators, LOC, OPTIMIZED, PARENT, RAW, TOKENS } from "../syntax/constants.ts"; -import { replaceNodeOrValue } from "../parser/utils/token.ts"; -import { parseString } from "../parser/parse.ts"; -import { tokenize } from "../parser/tokenize.ts"; -import { replaceCompound } from "./expand.ts"; +import {EnumToken} from "./types.ts"; +import {isFunction, isIdent, isIdentStart, isWhiteSpace} from "../syntax/syntax.ts"; +import {FeatureWalkMode} from "./features/type.ts"; +import {trimArray} from "../validation/match.ts"; +import {combinators, LOC, OPTIMIZED, PARENT, RAW, TOKENS} from "../syntax/constants.ts"; +import {replaceNodeOrValue} from "../parser/utils/token.ts"; +import {parseString} from "../parser/parse.ts"; +import {tokenize} from "../parser/tokenize.ts"; +import {replaceCompound} from "./expand.ts"; const notEndingWith: string[] = ["(", "["].concat(combinators); const rules: EnumToken[] = [ @@ -77,7 +77,7 @@ export function minify( */ export function minify( ast: AstNode, - opt: ParserOptions | MinifyFeatureOptions = {}, + options: ParserOptions | MinifyFeatureOptions = {}, recursive: boolean = false, errors?: ErrorDescription[], nestingContent?: boolean, @@ -91,27 +91,27 @@ export function minify( let replacement: AstNode | null; // @ts-ignore - let { sourcemap, module, ...options } = opt; + let { sourcemap, module, ...options2 } = options; - if (!("features" in options)) { + if (!("features" in options2)) { // @ts-ignore - options = { + options2 = { removeDuplicateDeclarations: true, computeShorthand: true, computeCalcExpression: true, removePrefix: false, features: [], - ...options, + ...options2, }; for (const feature of features) { - feature.register(options); + feature.register(options2); } - options.features!.sort((a: MinifyFeature, b: MinifyFeature): number => a.ordering - b.ordering); + options2.features!.sort((a: MinifyFeature, b: MinifyFeature): number => a.ordering - b.ordering); } - for (const feature of options!.features as MinifyFeature[]) { + for (const feature of options2!.features as MinifyFeature[]) { if (feature.processMode & FeatureWalkMode.Pre) { preprocess = true; } @@ -131,7 +131,7 @@ export function minify( replacement = parent; - for (const feature of options.features as MinifyFeature[]) { + for (const feature of options2.features as MinifyFeature[]) { if ( (feature.processMode & FeatureWalkMode.Pre) === 0 || (feature.accept != null && !feature.accept.has(parent.typ)) @@ -149,7 +149,7 @@ export function minify( const result = feature.run( replacement, - options, + options2, parent[PARENT] ?? ast, context, FeatureWalkMode.Pre, @@ -178,15 +178,15 @@ export function minify( } } - for (const feature of options.features as MinifyFeature[]) { + for (const feature of options2.features as MinifyFeature[]) { if (feature.processMode & FeatureWalkMode.Pre && "cleanup" in feature) { // @ts-ignore - feature.cleanup(ast, options, context, FeatureWalkMode.Pre); + feature.cleanup(ast, options2, context, FeatureWalkMode.Pre); } } } - doMinify(ast, options, recursive, errors, nestingContent, context); + doMinify(ast, options2, recursive, errors, nestingContent, context); parents = new Set([ast]); @@ -198,7 +198,7 @@ export function minify( replacement = parent; if (postprocess) { - for (const feature of options.features as MinifyFeature[]) { + for (const feature of options2.features as MinifyFeature[]) { if ( (feature.processMode & FeatureWalkMode.Post) === 0 || (feature.accept != null && !feature.accept.has(parent.typ)) @@ -208,7 +208,7 @@ export function minify( const result = feature.run( replacement as AstRule | AstAtRule, - options, + options2, parent[PARENT] ?? (ast as AstRule | AstAtRule | AstStyleSheet), context, FeatureWalkMode.Post, @@ -239,10 +239,10 @@ export function minify( } if (postprocess) { - for (const feature of options.features as MinifyFeature[]) { + for (const feature of options2.features as MinifyFeature[]) { if (feature.processMode & FeatureWalkMode.Post && "cleanup" in feature) { // @ts-ignore - feature.cleanup(ast, options, context, FeatureWalkMode.Post); + feature.cleanup(ast, options2, context, FeatureWalkMode.Post); } } } @@ -523,11 +523,11 @@ function doMinify( } else if (node.typ === EnumToken.KeyFramesRuleNodeType) { if ( previous?.typ === EnumToken.KeyFramesRuleNodeType && - (node).sel === (previous).sel + (node).sel === (previous).sel ) { // do not merge keyframes // https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/At-rules/@keyframes#resolving_duplicates - (previous).chi.push(...(node).chi); + (previous).chi.push(...(node).chi); ast.chi.splice(i, 1); previous = (ast?.chi?.[nodeIndex] as AstNode) ?? null; @@ -538,22 +538,22 @@ function doMinify( let k: number; - for (k = 0; k < (node as AstKeyFrameRule).chi.length; k++) { - if ((node as AstKeyFrameRule).chi[k].typ == EnumToken.DeclarationNodeType) { - let l: number = ((node as AstKeyFrameRule).chi[k] as AstDeclaration).val.length; + for (k = 0; k < (node as AstKeyframesRule).chi.length; k++) { + if ((node as AstKeyframesRule).chi[k].typ == EnumToken.DeclarationNodeType) { + let l: number = ((node as AstKeyframesRule).chi[k] as AstDeclaration).val.length; while (l--) { if ( - ((node as AstKeyFrameRule).chi[k] as AstDeclaration).val[l].typ == + ((node as AstKeyframesRule).chi[k] as AstDeclaration).val[l].typ == EnumToken.ImportantTokenType ) { - (node as AstKeyFrameRule).chi.splice(k--, 1); + (node as AstKeyframesRule).chi.splice(k--, 1); break; } if ( [EnumToken.WhitespaceTokenType, EnumToken.CommentTokenType].includes( - ((node as AstKeyFrameRule).chi[k] as AstDeclaration).val[l].typ, + ((node as AstKeyframesRule).chi[k] as AstDeclaration).val[l].typ, ) ) { continue; diff --git a/src/lib/parser/parse.ts b/src/lib/parser/parse.ts index 3bfa7bad..719ad5fb 100644 --- a/src/lib/parser/parse.ts +++ b/src/lib/parser/parse.ts @@ -10,9 +10,8 @@ import type { AstAtRule, AstComment, AstDeclaration, - AstKeyFrameRule, - AstKeyframesAtRule, AstKeyframesRule, + AstKeyframesAtRule, AstNode, AstRule, AstRuleList, @@ -515,7 +514,7 @@ export function doParseSync( >; 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; @@ -710,7 +709,7 @@ 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 (item.token.typ == EnumToken.BlockStartTokenType) { @@ -892,7 +891,12 @@ export function doParseSync( if (node != result.node) { replaceNodeOrValue( - result.parent as AstRule | AstAtRule | AstKeyframesAtRule | AstKeyFrameRule | AstStyleSheet, + result.parent as + | AstRule + | AstAtRule + | AstKeyframesAtRule + | AstKeyframesRule + | AstStyleSheet, result.node, node, ); @@ -1865,7 +1869,7 @@ export async function doParse( 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"; @@ -2064,7 +2068,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); @@ -2229,7 +2233,7 @@ export async function doParse( break; } - + if (options.visitor != null) { let parens: Token[] | null; for (const result of walk(ast)) { @@ -2336,7 +2340,12 @@ export async function doParse( if (node != result.node) { replaceNodeOrValue( - result.parent as AstRule | AstAtRule | AstKeyframesAtRule | AstKeyFrameRule | AstStyleSheet, + result.parent as + | AstRule + | AstAtRule + | AstKeyframesAtRule + | AstKeyframesRule + | AstStyleSheet, result.node, node, ); @@ -3405,7 +3414,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) { 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..456a88c9 100644 --- a/src/lib/parser/utils/selector.ts +++ b/src/lib/parser/utils/selector.ts @@ -2,7 +2,7 @@ import type { Token, AstRule, AstAtRule, - AstKeyFrameRule, + AstKeyframesRule, AstKeyframesAtRule, AstStyleSheet, ParserOptions, @@ -36,10 +36,10 @@ import { trimWhiteSpace } from "../parse.ts"; export function parseSelector( tokens: Token[], - context: AtRuleToken | AstRule | AstAtRule | AstKeyFrameRule | AstKeyframesAtRule | AstStyleSheet | null, + context: AtRuleToken | AstRule | AstAtRule | AstKeyframesRule | AstKeyframesAtRule | AstStyleSheet | null, options: ParserOptions, errors: ErrorDescription[], -): AstRule | AstKeyFrameRule { +): AstRule | AstKeyframesRule { if (context?.typ === EnumToken.KeyframesAtRuleNodeType) { const result = matchAllSyntaxes( getParsedSyntax(ValidationSyntaxGroupEnum.Syntaxes, "keyframe-selectors"), @@ -112,7 +112,7 @@ export function parseSelector( [TOKENS]: tokens.length === 0 ? null : tokens, [STATE]: result.success ? EnumAstNodeStatus.Validated : EnumAstNodeStatus.Invalid, [ERRORS]: result.errors, - } as AstKeyFrameRule; + } as AstKeyframesRule; } const stack: Token[] = []; diff --git a/src/lib/parser/utils/token.ts b/src/lib/parser/utils/token.ts index f792cff7..134340eb 100644 --- a/src/lib/parser/utils/token.ts +++ b/src/lib/parser/utils/token.ts @@ -1,9 +1,8 @@ import type { AstAtRule, AstDeclaration, - AstKeyFrameRule, - AstKeyframesAtRule, AstKeyframesRule, + AstKeyframesAtRule, AstNode, AstRule, } from "../../../@types/ast.d.ts"; @@ -81,7 +80,7 @@ export function replaceNodeOrValue( | ParensToken | AstAtRule | AstKeyframesAtRule - | AstKeyFrameRule + | AstKeyframesRule | AstRule | AstKeyframesRule ).chi as Token[]) ?? parent); diff --git a/src/lib/renderer/render.ts b/src/lib/renderer/render.ts index aed9f15a..0e2db5fb 100644 --- a/src/lib/renderer/render.ts +++ b/src/lib/renderer/render.ts @@ -421,7 +421,6 @@ function renderAstNode( // @ts-ignore let children: string = ""; let str: string = ""; - // let previousStr: string = ""; const indent: string = indents[level]; const indentSub: string = indents[level + 1]; @@ -436,7 +435,7 @@ function renderAstNode( case EnumToken.CommentNodeType: case EnumToken.CDOCOMMNodeType: if ((data).val.startsWith("/*# sourceMappingURL=")) { - // ignore sourcemap + // ignore sourcemap comment return ""; } @@ -510,15 +509,6 @@ function renderAstNode( ? "" : (node).val; } else if (node.typ == EnumToken.DeclarationNodeType) { - // if (!(node).nam.startsWith("--") && (node).val.length === 0) { - // // @ts-ignore - // errors.push({ - // action: "ignore", - // message: `render: invalid declaration ${JSON.stringify(node)}`, - // location: node[LOC], - // }); - // return ""; - // } str = `${(node).nam}:${options.indent}${(options.minify ? filterValues((node).val) @@ -598,27 +588,6 @@ function renderAstNode( return prelude + children + end; - // 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: - default: return ""; } diff --git a/src/lib/validation/match.ts b/src/lib/validation/match.ts index 0458949b..44164031 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, @@ -1967,6 +2021,13 @@ function matchSyntax( }; } +/** + * matches a column of syntaxes + * @param syntax + * @param context + * @param options + * @returns + */ function matchColumnSyntax( syntax: ValidationColumnToken, context: ValidationContext, @@ -2017,6 +2078,13 @@ function matchColumnSyntax( }; } +/** + * matches an ampersand of syntaxes + * @param syntax + * @param context + * @param options + * @returns + */ function matchAmpersandSyntax( syntax: ValidationAmpersandToken, context: ValidationContext, @@ -2046,6 +2114,13 @@ function matchAmpersandSyntax( return result!; } +/** + * matches a property + * @param property + * @param context + * @param options + * @returns + */ function matchProperty( property: ValidationPropertyToken, context: ValidationContext, @@ -2998,6 +3073,13 @@ function matchProperty( }; } +/** + * matches a repeatable syntax + * @param syntax + * @param context + * @param options + * @returns + */ function matchRepeatableSyntax( syntax: ValidationToken, context: ValidationContext, diff --git a/src/node.ts b/src/node.ts index 56ee1a39..85787661 100644 --- a/src/node.ts +++ b/src/node.ts @@ -1,5 +1,4 @@ import type { - AstComment, AstNode, LoadResult, ParseInfo, @@ -21,7 +20,7 @@ import { createReadStream } from "node:fs"; import { lstat, readFile } from "node:fs/promises"; import { doParse, doParseSync } from "./lib/parser/parse.ts"; import { doRender } from "./lib/renderer/render.ts"; -import { EnumToken, ModuleScopeEnumOptions } from "./lib/ast/types.ts"; +import { ModuleScopeEnumOptions } from "./lib/ast/types.ts"; import { tokenize, tokenizeStream } from "./lib/parser/tokenize.ts"; import { dirname, matchUrl, resolve } from "./lib/fs/resolve.ts"; import { ResponseType } from "./types.ts"; @@ -252,6 +251,7 @@ export function parseSync(options: ParseInputOptions & ParserSyncOptions): Parse /** * Parse css * @param args + * @private * * Parsing a string * @@ -362,11 +362,12 @@ export function transformSync(options: ParseInputOptions & TransformSyncOptions) * ``` * * @param args + * @private */ export function transformSync( ...args: [string, TransformSyncOptions?] | [ParseInputOptions & TransformSyncOptions] ): TransformResult { - let options: (ParseInputOptions & TransformSyncOptions) | TransformSyncOptions; + let options: (ParseInputStreamOptions & TransformSyncOptions) | TransformSyncOptions; let stream: string; if (typeof args[0] === "string") { @@ -425,12 +426,10 @@ export function transformSync( } /** - * Parse css + * Parse CSS * @param stream * @param options * - * @throws Error file not found - * * Example: * * ```ts @@ -442,7 +441,7 @@ export function transformSync( * console.log(result.ast); * ``` * - * parsing a Readable stream + * parsing a ReadableStream * * ```ts * @@ -457,7 +456,7 @@ export function transformSync( * console.log(result.ast); * ``` * - * Example using fetch and readable stream + * Parsing a file as a ReadableStream * * ```ts * @@ -554,6 +553,7 @@ export async function parse(options: ParseInputStreamOptions & ParserOptions): P * @param args * * @throws Error file not found + * @private * * Parsing a string * @@ -660,7 +660,7 @@ export async function parse( } /** - * Transform css file + * Transform CSS file * @param file url or path * @param options * @param asStream load file as stream @@ -695,7 +695,7 @@ export const transformFile = deprecate( ) as (file: string, options?: TransformOptions, asStream?: boolean) => Promise; /** - * Transform css + * Transform CSS * @param css * @param options * @@ -772,7 +772,7 @@ export async function transform( * console.log(result.code); * ``` * - * Example using fetch + * Parse a file as a ReadableStream * * ```ts * @@ -788,43 +788,16 @@ export async function transform( export async function transform(options: ParseInputStreamOptions & TransformOptions): Promise; /** - * Transform 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); * ``` */ @@ -872,6 +845,7 @@ export async function transform(options: ParseInputFileOptions & TransformOption * console.log(result.code); * ``` * @param args + * @private */ export async function transform( ...args: diff --git a/src/web.ts b/src/web.ts index 11781e44..9d1b92e7 100644 --- a/src/web.ts +++ b/src/web.ts @@ -73,7 +73,7 @@ export { dirname, resolve, ResponseType }; * @throws Error file not found * * ```ts - * import {load, ResponseType} from '@tbela99/css-parser'; + * import {load, ResponseType} from '@tbela99/css-parser/web'; * const result = await load(file, '.', ResponseType.ArrayBuffer) as ArrayBuffer; * ``` */ @@ -123,7 +123,7 @@ export async function load( * * ```ts * - * import {render, ColorType} from '@tbela99/css-parser'; + * import {render, ColorType} from '@tbela99/css-parser/web'; * * const css = 'body { color: color(from hsl(0 100% 50%) xyz x y z); }'; * const parseResult = await parse(css); @@ -208,13 +208,26 @@ export async function parseFile( * * ```ts * - * import {parseSync} from '@tbela99/css-parser'; + * import {parseSync} from '@tbela99/css-parser/web'; * * // css string * 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; @@ -227,13 +240,26 @@ export function parseSync(stream: string, options?: ParserSyncOptions): ParseRes * * ```ts * - * import {parseSync} from '@tbela99/css-parser'; + * import {parseSync} from '@tbela99/css-parser/web'; * * // css string - * let result = await parseSync({input: css, nestingRules: true}); + * let result = parseSync({input: css, nestingRules: true}); * console.log(result.ast); * ``` * + * parsing a ReadableStream + * + * ```ts + * + * import {parseSync} from '@tbela99/css-parser/web'; + * + * const response = await fetch('https://docs.deno.com/styles.css'); + * + * // css string + * const result = parseSync({input: response.body, beautify: true}); + * console.log(result.code); + * ``` + * */ export function parseSync(options: ParseInputOptions & ParserSyncOptions): ParseResult; @@ -241,12 +267,13 @@ export function parseSync(options: ParseInputOptions & ParserSyncOptions): Parse /** * Parse css * @param args + * @private * * Parsing a string * * ```ts * - * import {parseSync} from '@tbela99/css-parser'; + * import {parseSync} from '@tbela99/css-parser/web'; * * // css string * let result = await parseSync(css, {nestingRules: true}); @@ -311,11 +338,12 @@ export function parseSync( * Transform css * @param css * @param options + * @private * * * ```ts * - * import {transformSync} from '@tbela99/css-parser'; + * import {transformSync} from '@tbela99/css-parser/web'; * * // css string * const result = transformSync(css, {beautify: true}); @@ -329,9 +357,11 @@ export function transformSync(css: string, options?: TransformSyncOptions): Tran * Transform css * @param options * + * parsing a string + * * ```ts * - * import {transformSync} from '@tbela99/css-parser'; + * import {transformSync} from '@tbela99/css-parser/web'; * * // css string * const result = transformSync({input: css, beautify: true}); @@ -342,11 +372,11 @@ export function transformSync(css: string, options?: TransformSyncOptions): Tran export function transformSync(options: ParseInputOptions & TransformSyncOptions): TransformResult; /** - * Transform css + * Transform CSS * * ```ts * - * import {transformSync} from '@tbela99/css-parser'; + * import {transformSync} from '@tbela99/css-parser/web'; * * // css string * const result = transformSync(css); @@ -354,12 +384,13 @@ export function transformSync(options: ParseInputOptions & TransformSyncOptions) * ``` * * @param args + * @private */ export function transformSync( ...args: [string, TransformSyncOptions?] | [ParseInputOptions & TransformSyncOptions] ): TransformResult { let options: (ParseInputOptions & TransformSyncOptions) | TransformSyncOptions; - let stream: string; + let stream: string | ReadableStream; if (typeof args[0] === "string") { stream = args[0]; @@ -416,8 +447,98 @@ 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; + +/** + * 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; /** @@ -446,6 +567,7 @@ export async function parse(options: ParseInputStreamOptions & ParserOptions): P * console.log(result.ast); * ``` * @param args + * @private */ export async function parse( ...args: @@ -517,7 +639,7 @@ export async function parse( } /** - * Transform css file + * Transform CSS file * @param file url or path * @param options * @param asStream load file as stream @@ -554,14 +676,85 @@ 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 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 * @@ -582,6 +775,7 @@ export async function transform(options: ParseInputStreamOptions & TransformOpti * console.log(result.code); * ``` * @param args + * @private */ export async function transform( ...args: From ee7b8b9d9b6db3b9163c3355e3b609a969a53b86 Mon Sep 17 00:00:00 2001 From: Thierry Bela Nanga Date: Sun, 16 Aug 2026 22:01:54 -0400 Subject: [PATCH 06/11] bump version #146 --- .gitignore | 1 + benchmark/package.json | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) 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/benchmark/package.json b/benchmark/package.json index 84d10d42..a7e23016 100644 --- a/benchmark/package.json +++ b/benchmark/package.json @@ -10,7 +10,7 @@ "all": "npm run sizes && npm run bench && npm run report" }, "dependencies": { - "@tbela99/css-parser": "^1.4.9", + "@tbela99/css-parser": "^1.5.0", "@tbela99/css-parser2": "github:tbela99/css-parser#2279484", "clean-css": "^5.3.3", "css-tree": "^3.2.1", From 65f3c22e7bae22f3825e60e9142c8beffe741524 Mon Sep 17 00:00:00 2001 From: Thierry Bela Nanga Date: Sun, 16 Aug 2026 22:22:51 -0400 Subject: [PATCH 07/11] delete incorrect import #146 --- dist/index-umd-web.js | 120 +++++-- dist/index.cjs | 120 +++++-- dist/index.d.ts | 609 ++++++++++++++++++++++++++--------- dist/lib/ast/find.js | 9 +- dist/lib/ast/minify.js | 34 +- dist/lib/validation/match.js | 77 +++++ src/@types/ast.d.ts | 1 - src/@types/index.d.ts | 1 - 8 files changed, 752 insertions(+), 219 deletions(-) diff --git a/dist/index-umd-web.js b/dist/index-umd-web.js index 5171a65e..768c4117 100644 --- a/dist/index-umd-web.js +++ b/dist/index-umd-web.js @@ -11705,11 +11705,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 +11818,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 +11986,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 +12432,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 +12477,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 +12528,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 +12576,13 @@ } return result; } + /** + * matches a list of syntaxes + * @param syntaxes + * @param context + * @param options + * @returns + */ function matchSyntax(syntaxes, context, options) { if (syntaxes == null) { return { @@ -13174,6 +13223,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 +13266,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 +13291,13 @@ } return result; } + /** + * matches a property + * @param property + * @param context + * @param options + * @returns + */ function matchProperty(property, context, options) { let success = false; let t = context.peek()?.typ; @@ -13960,6 +14030,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; @@ -21089,6 +21166,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 @@ -21114,10 +21196,6 @@ console.log({node, value}); ``` - * - * @param ast - * @param matcher - * @returns */ function findByValue(ast, matcher) { let source; @@ -22742,29 +22820,29 @@ * @param context * @private */ - function minify(ast, opt = {}, recursive = false, errors, nestingContent, context = {}) { + function minify(ast, options = {}, recursive = false, errors, nestingContent, context = {}) { let preprocess = false; let postprocess = false; let parents; let replacement; // @ts-ignore - let { sourcemap, module, ...options } = opt; - if (!("features" in options)) { + let { sourcemap, module, ...options2 } = options; + if (!("features" in options2)) { // @ts-ignore - options = { + options2 = { removeDuplicateDeclarations: true, computeShorthand: true, computeCalcExpression: true, removePrefix: false, features: [], - ...options, + ...options2, }; for (const feature of features) { - feature.register(options); + feature.register(options2); } - options.features.sort((a, b) => a.ordering - b.ordering); + options2.features.sort((a, b) => a.ordering - b.ordering); } - for (const feature of options.features) { + for (const feature of options2.features) { if (feature.processMode & exports.FeatureWalkMode.Pre) { preprocess = true; } @@ -22779,7 +22857,7 @@ 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; @@ -22789,7 +22867,7 @@ ? replacement.sel : replacement.nam); } - const result = feature.run(replacement, options, parent[PARENT] ?? ast, context, exports.FeatureWalkMode.Pre); + const result = feature.run(replacement, options2, parent[PARENT] ?? ast, context, exports.FeatureWalkMode.Pre); if (result != null) { replacement = result; } @@ -22808,14 +22886,14 @@ } } } - for (const feature of options.features) { + for (const feature of options2.features) { if (feature.processMode & exports.FeatureWalkMode.Pre && "cleanup" in feature) { // @ts-ignore - feature.cleanup(ast, options, context, exports.FeatureWalkMode.Pre); + feature.cleanup(ast, options2, context, exports.FeatureWalkMode.Pre); } } } - doMinify(ast, options, recursive, errors, nestingContent, context); + doMinify(ast, options2, recursive, errors, nestingContent, context); parents = new Set([ast]); for (const parent of parents) { if (parent.typ == exports.EnumToken.CommentTokenType || parent.typ == exports.EnumToken.CDOCOMMTokenType) { @@ -22823,12 +22901,12 @@ } replacement = parent; if (postprocess) { - for (const feature of options.features) { + for (const feature of options2.features) { if ((feature.processMode & exports.FeatureWalkMode.Post) === 0 || (feature.accept != null && !feature.accept.has(parent.typ))) { continue; } - const result = feature.run(replacement, options, parent[PARENT] ?? ast, context, exports.FeatureWalkMode.Post); + const result = feature.run(replacement, options2, parent[PARENT] ?? ast, context, exports.FeatureWalkMode.Post); if (result != null) { replacement = result; } @@ -22849,10 +22927,10 @@ } } if (postprocess) { - for (const feature of options.features) { + for (const feature of options2.features) { if (feature.processMode & exports.FeatureWalkMode.Post && "cleanup" in feature) { // @ts-ignore - feature.cleanup(ast, options, context, exports.FeatureWalkMode.Post); + feature.cleanup(ast, options2, context, exports.FeatureWalkMode.Post); } } } diff --git a/dist/index.cjs b/dist/index.cjs index c60b7ab5..481a526d 100644 --- a/dist/index.cjs +++ b/dist/index.cjs @@ -11708,11 +11708,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 +11821,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 +11989,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 +12435,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 +12480,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 +12531,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 +12579,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 { @@ -13177,6 +13226,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 +13269,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 +13294,13 @@ function matchAmpersandSyntax(syntax, context, options) { } return result; } +/** + * matches a property + * @param property + * @param context + * @param options + * @returns + */ function matchProperty(property, context, options) { let success = false; let t = context.peek()?.typ; @@ -13963,6 +14033,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; @@ -21092,6 +21169,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 @@ -21117,10 +21199,6 @@ button { console.log({node, value}); ``` - * - * @param ast - * @param matcher - * @returns */ function findByValue(ast, matcher) { let source; @@ -22745,29 +22823,29 @@ const features = Object.values(allFeatures).sort((a, b) => a.ordering - b.orderi * @param context * @private */ -function minify(ast, opt = {}, recursive = false, errors, nestingContent, context = {}) { +function minify(ast, options = {}, recursive = false, errors, nestingContent, context = {}) { let preprocess = false; let postprocess = false; let parents; let replacement; // @ts-ignore - let { sourcemap, module, ...options } = opt; - if (!("features" in options)) { + let { sourcemap, module, ...options2 } = options; + if (!("features" in options2)) { // @ts-ignore - options = { + options2 = { removeDuplicateDeclarations: true, computeShorthand: true, computeCalcExpression: true, removePrefix: false, features: [], - ...options, + ...options2, }; for (const feature of features) { - feature.register(options); + feature.register(options2); } - options.features.sort((a, b) => a.ordering - b.ordering); + options2.features.sort((a, b) => a.ordering - b.ordering); } - for (const feature of options.features) { + for (const feature of options2.features) { if (feature.processMode & exports.FeatureWalkMode.Pre) { preprocess = true; } @@ -22782,7 +22860,7 @@ function minify(ast, opt = {}, recursive = false, errors, nestingContent, contex 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; @@ -22792,7 +22870,7 @@ function minify(ast, opt = {}, recursive = false, errors, nestingContent, contex ? replacement.sel : replacement.nam); } - const result = feature.run(replacement, options, parent[PARENT] ?? ast, context, exports.FeatureWalkMode.Pre); + const result = feature.run(replacement, options2, parent[PARENT] ?? ast, context, exports.FeatureWalkMode.Pre); if (result != null) { replacement = result; } @@ -22811,14 +22889,14 @@ function minify(ast, opt = {}, recursive = false, errors, nestingContent, contex } } } - 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) { @@ -22826,12 +22904,12 @@ function minify(ast, opt = {}, recursive = false, errors, nestingContent, contex } replacement = parent; if (postprocess) { - for (const feature of options.features) { + for (const feature of options2.features) { if ((feature.processMode & exports.FeatureWalkMode.Post) === 0 || (feature.accept != null && !feature.accept.has(parent.typ))) { continue; } - const result = feature.run(replacement, options, parent[PARENT] ?? ast, context, exports.FeatureWalkMode.Post); + const result = feature.run(replacement, options2, parent[PARENT] ?? ast, context, exports.FeatureWalkMode.Post); if (result != null) { replacement = result; } @@ -22852,10 +22930,10 @@ function minify(ast, opt = {}, recursive = false, errors, nestingContent, contex } } 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); } } } diff --git a/dist/index.d.ts b/dist/index.d.ts index c1160d41..ae5c71fd 100644 --- a/dist/index.d.ts +++ b/dist/index.d.ts @@ -980,7 +980,7 @@ declare const OPTIMIZED: unique symbol; */ export declare interface LiteralToken extends BaseToken { /** - * literal type + * @inheritdoc */ typ: EnumToken.LiteralTokenType; /** @@ -994,7 +994,7 @@ export declare interface LiteralToken extends BaseToken { */ export declare interface ClassSelectorToken extends BaseToken { /** - * class selector type + * @inheritdoc */ typ: EnumToken.ClassSelectorTokenType; /** @@ -1008,7 +1008,7 @@ export declare interface ClassSelectorToken extends BaseToken { */ export declare interface InvalidClassSelectorToken extends BaseToken { /** - * invalid class selector type + * @inheritdoc */ typ: EnumToken.InvalidClassSelectorTokenType; /** @@ -1022,7 +1022,7 @@ export declare interface InvalidClassSelectorToken extends BaseToken { */ export declare interface UniversalSelectorToken extends BaseToken { /** - * universal selector type + * @inheritdoc */ typ: EnumToken.UniversalSelectorTokenType; } @@ -1032,7 +1032,7 @@ export declare interface UniversalSelectorToken extends BaseToken { */ export declare interface IdentToken extends BaseToken { /** - * ident type + * @inheritdoc */ typ: EnumToken.IdenTokenType; /** @@ -1046,7 +1046,7 @@ export declare interface IdentToken extends BaseToken { */ export declare interface IdentListToken extends BaseToken { /** - * ident list type + * @inheritdoc */ typ: EnumToken.IdenListTokenType; /** @@ -1060,7 +1060,7 @@ export declare interface IdentListToken extends BaseToken { */ export declare interface DashedIdentToken extends BaseToken { /** - * ident type + * @inheritdoc */ typ: EnumToken.DashedIdenTokenType; /** @@ -1074,7 +1074,7 @@ export declare interface DashedIdentToken extends BaseToken { */ export declare interface CommaToken extends BaseToken { /** - * comma type + * @inheritdoc */ typ: EnumToken.CommaTokenType; } @@ -1084,7 +1084,7 @@ export declare interface CommaToken extends BaseToken { */ export declare interface ColonToken extends BaseToken { /** - * colon type ':' + * @inheritdoc */ typ: EnumToken.ColonTokenType; } @@ -1094,7 +1094,7 @@ export declare interface ColonToken extends BaseToken { */ export declare interface DoubleColonToken extends BaseToken { /** - * double colon type '::' + * @inheritdoc */ typ: EnumToken.DoubleColonTokenType; } @@ -1104,7 +1104,7 @@ export declare interface DoubleColonToken extends BaseToken { */ export declare interface SemiColonToken extends BaseToken { /** - * semicolon type + * @inheritdoc */ typ: EnumToken.SemiColonTokenType; } @@ -1114,7 +1114,7 @@ export declare interface SemiColonToken extends BaseToken { */ export declare interface NestingSelectorToken extends BaseToken { /** - * nesting selector type + * @inheritdoc */ typ: EnumToken.NestingSelectorTokenType; } @@ -1124,7 +1124,7 @@ export declare interface NestingSelectorToken extends BaseToken { */ export declare interface NumberToken extends BaseToken { /** - * number type + * @inheritdoc */ typ: EnumToken.NumberTokenType; /** @@ -1142,7 +1142,7 @@ export declare interface NumberToken extends BaseToken { */ export declare interface AtRuleToken extends BaseToken { /** - * at rule type + * @inheritdoc */ typ: EnumToken.AtRuleTokenType; /** @@ -1160,7 +1160,7 @@ export declare interface AtRuleToken extends BaseToken { */ export declare interface PercentageToken extends BaseToken { /** - * percentage type + * @inheritdoc */ typ: EnumToken.PercentageTokenType; /** @@ -1174,7 +1174,7 @@ export declare interface PercentageToken extends BaseToken { */ export declare interface FlexToken extends BaseToken { /** - * flex type + * @inheritdoc */ typ: EnumToken.FlexTokenType; /** @@ -1216,7 +1216,7 @@ export declare interface FunctionToken extends BaseToken { */ export declare interface GridTemplateFuncToken extends BaseToken { /** - * function type + * @inheritdoc */ typ: EnumToken.GridTemplateFuncTokenType; /** @@ -1234,7 +1234,7 @@ export declare interface GridTemplateFuncToken extends BaseToken { */ export declare interface FunctionURLToken extends BaseToken { /** - * function type + * @inheritdoc */ typ: EnumToken.UrlFunctionTokenType; /** @@ -1252,7 +1252,7 @@ export declare interface FunctionURLToken extends BaseToken { */ export declare interface FunctionImageToken extends BaseToken { /** - * function type + * @inheritdoc */ typ: EnumToken.ImageFunctionTokenType; /** @@ -1279,7 +1279,7 @@ export declare interface FunctionImageToken extends BaseToken { */ export declare interface TimingFunctionToken extends BaseToken { /** - * timing function type + * @inheritdoc */ typ: EnumToken.TimingFunctionTokenType; /** @@ -1297,7 +1297,7 @@ export declare interface TimingFunctionToken extends BaseToken { */ export declare interface TimelineFunctionToken extends BaseToken { /** - * timeline function type + * @inheritdoc */ typ: EnumToken.TimelineFunctionTokenType; /** @@ -1315,7 +1315,7 @@ export declare interface TimelineFunctionToken extends BaseToken { */ export declare interface StringToken extends BaseToken { /** - * string type + * @inheritdoc */ typ: EnumToken.StringTokenType; /** @@ -1329,7 +1329,7 @@ export declare interface StringToken extends BaseToken { */ export declare interface BadStringToken extends BaseToken { /** - * bad string type + * @inheritdoc */ typ: EnumToken.BadStringTokenType; /** @@ -1343,7 +1343,7 @@ export declare interface BadStringToken extends BaseToken { */ export declare interface UnclosedStringToken extends BaseToken { /** - * unclosed string type + * @inheritdoc */ typ: EnumToken.UnclosedStringTokenType; /** @@ -1357,7 +1357,7 @@ export declare interface UnclosedStringToken extends BaseToken { */ export declare interface DimensionToken extends BaseToken { /** - * dimension type + * @inheritdoc */ typ: EnumToken.DimensionTokenType; /** @@ -1375,7 +1375,7 @@ export declare interface DimensionToken extends BaseToken { */ export declare interface LengthToken extends BaseToken { /** - * length type + * @inheritdoc */ typ: EnumToken.LengthTokenType; /** @@ -1393,7 +1393,7 @@ export declare interface LengthToken extends BaseToken { */ export declare interface AngleToken extends BaseToken { /** - * angle type + * @inheritdoc */ typ: EnumToken.AngleTokenType; /** @@ -1411,7 +1411,7 @@ export declare interface AngleToken extends BaseToken { */ export declare interface TimeToken extends BaseToken { /** - * time type + * @inheritdoc */ typ: EnumToken.TimeTokenType; /** @@ -1419,7 +1419,7 @@ export declare interface TimeToken extends BaseToken { */ val: number | FractionToken; /** - * time unit + * time unit */ unit: "ms" | "s"; } @@ -1429,7 +1429,7 @@ export declare interface TimeToken extends BaseToken { */ export declare interface FrequencyToken extends BaseToken { /** - * frequency type + * @inheritdoc */ typ: EnumToken.FrequencyTokenType; /** @@ -1447,7 +1447,7 @@ export declare interface FrequencyToken extends BaseToken { */ export declare interface ResolutionToken extends BaseToken { /** - * resolution type + * @inheritdoc */ typ: EnumToken.ResolutionTokenType; /** @@ -1465,7 +1465,7 @@ export declare interface ResolutionToken extends BaseToken { */ export declare interface HashToken extends BaseToken { /** - * hash type + * @inheritdoc */ typ: EnumToken.HashTokenType; /** @@ -1479,7 +1479,7 @@ export declare interface HashToken extends BaseToken { */ export declare interface BlockStartToken extends BaseToken { /** - * block start type + * @inheritdoc */ typ: EnumToken.BlockStartTokenType; } @@ -1489,7 +1489,7 @@ export declare interface BlockStartToken extends BaseToken { */ export declare interface BlockEndToken extends BaseToken { /** - * block end type + * @inheritdoc */ typ: EnumToken.BlockEndTokenType; } @@ -1499,7 +1499,7 @@ export declare interface BlockEndToken extends BaseToken { */ export declare interface AttrStartToken extends BaseToken { /** - * attribute start type + * @inheritdoc */ typ: EnumToken.AttrStartTokenType; /** @@ -1513,7 +1513,7 @@ export declare interface AttrStartToken extends BaseToken { */ export declare interface AttrEndToken extends BaseToken { /** - * attribute end type + * @inheritdoc */ typ: EnumToken.AttrEndTokenType; } @@ -1523,7 +1523,7 @@ export declare interface AttrEndToken extends BaseToken { */ export declare interface ParensStartToken extends BaseToken { /** - * parenthesis start type + * @inheritdoc */ typ: EnumToken.StartParensTokenType; } @@ -1533,7 +1533,7 @@ export declare interface ParensStartToken extends BaseToken { */ export declare interface ParensEndToken extends BaseToken { /** - * parenthesis end type + * @inheritdoc */ typ: EnumToken.EndParensTokenType; } @@ -1543,7 +1543,7 @@ export declare interface ParensEndToken extends BaseToken { */ export declare interface ParensToken extends BaseToken { /** - * parenthesis type + * @inheritdoc */ typ: EnumToken.ParensTokenType; /** @@ -1557,7 +1557,7 @@ export declare interface ParensToken extends BaseToken { */ export declare interface WhitespaceToken extends BaseToken { /** - * whitespace type + * @inheritdoc */ typ: EnumToken.WhitespaceTokenType; /** @@ -1571,7 +1571,7 @@ export declare interface WhitespaceToken extends BaseToken { */ export declare interface CommentToken extends BaseToken { /** - * comment type + * @inheritdoc */ typ: EnumToken.CommentTokenType; /** @@ -1585,7 +1585,7 @@ export declare interface CommentToken extends BaseToken { */ export declare interface BadCommentToken extends BaseToken { /** - * bad comment type + * @inheritdoc */ typ: EnumToken.BadCommentTokenType; /** @@ -1598,7 +1598,13 @@ export declare interface BadCommentToken extends BaseToken { * CDO comment token */ export declare interface CDOCommentToken extends BaseToken { + /** + * @inheritdoc + */ typ: EnumToken.CDOCOMMTokenType; + /** + * CDO comment value + */ val: string; } @@ -1607,7 +1613,7 @@ export declare interface CDOCommentToken extends BaseToken { */ export declare interface BadCDOCommentToken extends BaseToken { /** - * bad CDO comment type + * @inheritdoc */ typ: EnumToken.BadCdoTokenType; /** @@ -1621,7 +1627,7 @@ export declare interface BadCDOCommentToken extends BaseToken { */ export declare interface IncludeMatchToken extends BaseToken { /** - * include match type + * @inheritdoc */ typ: EnumToken.IncludeMatchTokenType; // val: '~='; @@ -1632,7 +1638,7 @@ export declare interface IncludeMatchToken extends BaseToken { */ export declare interface DashMatchToken extends BaseToken { /** - * dash match type + * @inheritdoc */ typ: EnumToken.DashMatchTokenType; // val: '|='; @@ -1643,7 +1649,7 @@ export declare interface DashMatchToken extends BaseToken { */ export declare interface EqualMatchToken extends BaseToken { /** - * equal match type + * @inheritdoc */ typ: EnumToken.EqualMatchTokenType; // val: '|='; @@ -1654,7 +1660,7 @@ export declare interface EqualMatchToken extends BaseToken { */ export declare interface StartMatchToken extends BaseToken { /** - * start match type + * @inheritdoc */ typ: EnumToken.StartMatchTokenType; // val: '^='; @@ -1665,7 +1671,7 @@ export declare interface StartMatchToken extends BaseToken { */ export declare interface EndMatchToken extends BaseToken { /** - * end match type + * @inheritdoc */ typ: EnumToken.EndMatchTokenType; // val: '|='; @@ -1676,7 +1682,7 @@ export declare interface EndMatchToken extends BaseToken { */ export declare interface ContainMatchToken extends BaseToken { /** - * contain match type + * @inheritdoc */ typ: EnumToken.ContainMatchTokenType; // val: '|='; @@ -1687,7 +1693,7 @@ export declare interface ContainMatchToken extends BaseToken { */ export declare interface LessThanToken extends BaseToken { /** - * less than type + * @inheritdoc */ typ: EnumToken.LtTokenType; } @@ -1697,7 +1703,7 @@ export declare interface LessThanToken extends BaseToken { */ export declare interface LessThanOrEqualToken extends BaseToken { /** - * less than or equal type + * @inheritdoc */ typ: EnumToken.LteTokenType; } @@ -1707,7 +1713,7 @@ export declare interface LessThanOrEqualToken extends BaseToken { */ export declare interface GreaterThanToken extends BaseToken { /** - * greater than type + * @inheritdoc */ typ: EnumToken.GtTokenType; } @@ -1717,7 +1723,7 @@ export declare interface GreaterThanToken extends BaseToken { */ export declare interface GreaterThanOrEqualToken extends BaseToken { /** - * greater than or equal type + * @inheritdoc */ typ: EnumToken.GteTokenType; } @@ -1727,7 +1733,7 @@ export declare interface GreaterThanOrEqualToken extends BaseToken { */ export declare interface ColumnCombinatorToken extends BaseToken { /** - * column combinator type + * @inheritdoc */ typ: EnumToken.ColumnCombinatorTokenType; } @@ -1737,7 +1743,7 @@ export declare interface ColumnCombinatorToken extends BaseToken { */ export declare interface PseudoClassToken extends BaseToken { /** - * Pseudo class + * @inheritdoc */ typ: EnumToken.PseudoClassTokenType; /** @@ -1751,7 +1757,7 @@ export declare interface PseudoClassToken extends BaseToken { */ export declare interface PseudoElementToken extends BaseToken { /** - * Pseudo element + * @inheritdoc */ typ: EnumToken.PseudoElementTokenType; /** @@ -1765,7 +1771,7 @@ export declare interface PseudoElementToken extends BaseToken { */ export declare interface PseudoPageToken extends BaseToken { /** - * Pseudo page + * @inheritdoc */ typ: EnumToken.PseudoPageTokenType; /** @@ -1779,7 +1785,7 @@ export declare interface PseudoPageToken extends BaseToken { */ export declare interface PseudoClassFunctionToken extends BaseToken { /** - * Pseudo class function + * @inheritdoc */ typ: EnumToken.PseudoClassFuncTokenType; /** @@ -1797,7 +1803,7 @@ export declare interface PseudoClassFunctionToken extends BaseToken { */ export declare interface DelimToken extends BaseToken { /** - * Delimiter token type + * @inheritdoc */ typ: EnumToken.DelimTokenType; } @@ -1807,7 +1813,7 @@ export declare interface DelimToken extends BaseToken { */ export declare interface BadUrlToken extends BaseToken { /** - * Bad URL + * @inheritdoc */ typ: EnumToken.BadUrlTokenType; /** @@ -1821,7 +1827,7 @@ export declare interface BadUrlToken extends BaseToken { */ export declare interface UrlToken extends BaseToken { /** - * URL + * @inheritdoc */ typ: EnumToken.UrlTokenTokenType; /** @@ -1835,7 +1841,7 @@ export declare interface UrlToken extends BaseToken { */ export declare interface EOFToken extends BaseToken { /** - * End of file + * @inheritdoc */ typ: EnumToken.EOFTokenType; } @@ -1845,7 +1851,7 @@ export declare interface EOFToken extends BaseToken { */ export declare interface ImportantToken extends BaseToken { /** - * Important + * @inheritdoc */ typ: EnumToken.ImportantTokenType; } @@ -1855,7 +1861,7 @@ export declare interface ImportantToken extends BaseToken { */ export declare interface ColorToken extends BaseToken { /** - * Color type + * @inheritdoc */ typ: EnumToken.ColorTokenType; /** @@ -1881,7 +1887,7 @@ export declare interface ColorToken extends BaseToken { */ export declare interface AttrToken extends BaseToken { /** - * Attribute type + * @inheritdoc */ typ: EnumToken.AttrTokenType; /** @@ -1895,7 +1901,7 @@ export declare interface AttrToken extends BaseToken { */ export declare interface InvalidAttrToken extends BaseToken { /** - * Attribute type + * @inheritdoc */ typ: EnumToken.InvalidAttrTokenType; /** @@ -1908,6 +1914,9 @@ export declare interface InvalidAttrToken extends BaseToken { * Child combinator token */ export declare interface ChildCombinatorToken extends BaseToken { + /** + * @inheritdoc + */ typ: EnumToken.ChildCombinatorTokenType; } @@ -1916,7 +1925,7 @@ export declare interface ChildCombinatorToken extends BaseToken { */ export declare interface MediaFeatureToken extends BaseToken { /** - * Media feature type + * @inheritdoc */ typ: EnumToken.MediaFeatureTokenType; /** @@ -1930,7 +1939,7 @@ export declare interface MediaFeatureToken extends BaseToken { */ export declare interface NotToken extends BaseToken { /** - * Media feature not type + * @inheritdoc */ typ: EnumToken.NotTokenType; /** @@ -1944,7 +1953,7 @@ export declare interface NotToken extends BaseToken { */ export declare interface MediaFeatureOnlyToken extends BaseToken { /** - * Media feature only type + * @inheritdoc */ typ: EnumToken.OnlyTokenType; /** @@ -1958,7 +1967,7 @@ export declare interface MediaFeatureOnlyToken extends BaseToken { */ export declare interface AndToken extends BaseToken { /** - * Media feature and type + * @inheritdoc */ typ: EnumToken.AndTokenType; } @@ -1968,7 +1977,7 @@ export declare interface AndToken extends BaseToken { */ export declare interface OrToken extends BaseToken { /** - * Media feature or type + * @inheritdoc */ typ: EnumToken.OrTokenType; } @@ -1978,7 +1987,7 @@ export declare interface OrToken extends BaseToken { */ export declare interface MediaQueryUnaryFeatureToken extends BaseToken { /** - * Media query condition type + * @inheritdoc */ typ: EnumToken.MediaQueryUnaryFeatureTokenType; /** @@ -1993,7 +2002,7 @@ export declare interface MediaQueryUnaryFeatureToken extends BaseToken { export declare interface SupportsQueryUnaryConditionToken extends BaseToken { /** - * Supports query condition type + * @inheritdoc */ typ: EnumToken.SupportsQueryUnaryConditionTokenType; /** @@ -2008,7 +2017,7 @@ export declare interface SupportsQueryUnaryConditionToken extends BaseToken { export declare interface SupportsQueryConditionToken extends BaseToken { /** - * Supports query condition type + * @inheritdoc */ typ: EnumToken.SupportsQueryConditionTokenType; /** @@ -2027,7 +2036,7 @@ export declare interface SupportsQueryConditionToken extends BaseToken { export declare interface WhenElseQueryConditionToken extends BaseToken { /** - * When else query condition type + * @inheritdoc */ typ: EnumToken.WhenElseQueryConditionTokenType; /** @@ -2046,7 +2055,7 @@ export declare interface WhenElseQueryConditionToken extends BaseToken { export declare interface WhenElseUnaryConditionToken extends BaseToken { /** - * When else query condition type + * @inheritdoc */ typ: EnumToken.WhenElseUnaryConditionTokenType; /** @@ -2061,7 +2070,7 @@ export declare interface WhenElseUnaryConditionToken extends BaseToken { export declare interface MediaQueryConditionToken extends BaseToken { /** - * Media query condition type + * @inheritdoc */ typ: EnumToken.MediaQueryConditionTokenType; /** @@ -2088,7 +2097,7 @@ export declare interface MediaQueryConditionToken extends BaseToken { export declare interface IfConditionToken extends BaseToken { /** - * If condition type + * @inheritdoc */ typ: EnumToken.IfConditionTokenType; /** @@ -2103,7 +2112,7 @@ export declare interface IfConditionToken extends BaseToken { export declare interface IfElseConditionToken extends BaseToken { /** - * If else condition type + * @inheritdoc */ typ: EnumToken.IfElseConditionTokenType; /** @@ -2117,23 +2126,67 @@ export declare interface IfElseConditionToken extends BaseToken { } export declare interface ContainerStyleRangeToken extends BaseToken { + /** + * @inheritdoc + */ typ: EnumToken.ContainerStyleRangeTokenType; + /** + * condition left handle + */ l: Token$1[]; + /** + * condition operator + */ op: Token$1[]; + /** + * condition value + */ r: Token$1[]; } +// (20px <= width < 30px) +/** + * @inheritdoc + */ export declare interface MediaRangeQueryToken extends BaseToken { + /** + * @inheritdoc + */ typ: EnumToken.MediaRangeQueryTokenType; + /** + * left hanle + * */ l: Token$1[]; + /** + * media feature name + */ val: Token$1[]; + /** + * first comparator + */ op1: LessThanToken | GreaterThanToken | LessThanOrEqualToken | GreaterThanOrEqualToken; + /** + * second comparator + */ op2: LessThanToken | GreaterThanToken | LessThanOrEqualToken | GreaterThanOrEqualToken; + /** + * right handle + */ r: Token$1[]; } +/** + * @inheritdoc + */ export declare interface InvalidMediaQueryToken extends BaseToken { + /** + * @inheritdoc + */ typ: EnumToken.InvalidMediaQueryTokenType; + + /** + * children + */ chi: Token$1[]; } @@ -2141,6 +2194,9 @@ export declare interface InvalidMediaQueryToken extends BaseToken { * Descendant combinator token */ export declare interface DescendantCombinatorToken extends BaseToken { + /** + * @inheritdoc + */ typ: EnumToken.DescendantCombinatorTokenType; } @@ -2148,6 +2204,9 @@ export declare interface DescendantCombinatorToken extends BaseToken { * Next sibling combinator token */ export declare interface NextSiblingCombinatorToken extends BaseToken { + /** + * @inheritdoc + */ typ: EnumToken.NextSiblingCombinatorTokenType; } @@ -2155,6 +2214,9 @@ export declare interface NextSiblingCombinatorToken extends BaseToken { * Subsequent sibling combinator token */ export declare interface SubsequentCombinatorToken extends BaseToken { + /** + * @inheritdoc + */ typ: EnumToken.SubsequentSiblingCombinatorTokenType; } @@ -2162,6 +2224,9 @@ export declare interface SubsequentCombinatorToken extends BaseToken { * Add token */ export declare interface AddToken extends BaseToken { + /** + * @inheritdoc + */ typ: EnumToken.Add; } @@ -2169,6 +2234,9 @@ export declare interface AddToken extends BaseToken { * Sub token */ export declare interface SubToken extends BaseToken { + /** + * @inheritdoc + */ typ: EnumToken.Sub; } @@ -2176,6 +2244,9 @@ export declare interface SubToken extends BaseToken { * Div token */ export declare interface DivToken extends BaseToken { + /** + * @inheritdoc + */ typ: EnumToken.Div; } @@ -2184,7 +2255,7 @@ export declare interface DivToken extends BaseToken { */ export declare interface MulToken extends BaseToken { /** - * Type + * @inheritdoc */ typ: EnumToken.Mul; } @@ -2194,7 +2265,7 @@ export declare interface MulToken extends BaseToken { */ export declare interface WrappedValuesToken extends BaseToken { /** - * Type + * @inheritdoc */ typ: EnumToken.WrappedValuesTokenType; /** @@ -2208,7 +2279,7 @@ export declare interface WrappedValuesToken extends BaseToken { */ export declare interface UnaryExpression extends BaseToken { /** - * Type + * @inheritdoc */ typ: EnumToken.UnaryExpressionTokenType; /** @@ -2225,8 +2296,17 @@ export declare interface UnaryExpression extends BaseToken { * Fraction token */ export declare interface FractionToken extends BaseToken { + /** + * @inheritdoc + */ typ: EnumToken.FractionTokenType; + /** + * Left handle + */ l: NumberToken; + /** + * Right handle + */ r: NumberToken; } @@ -2234,9 +2314,21 @@ export declare interface FractionToken extends BaseToken { * Binary expression token */ export declare interface BinaryExpressionToken extends BaseToken { + /** + * @inheritdoc + */ typ: EnumToken.BinaryExpressionTokenType; + /** + * Operator + */ op: EnumToken.Add | EnumToken.Sub | EnumToken.Div | EnumToken.Mul; + /** + * Left handle + */ l: BinaryExpressionNode | Token$1; + /** + * Right handle + */ r: BinaryExpressionNode | Token$1; } @@ -2244,10 +2336,25 @@ export declare interface BinaryExpressionToken extends BaseToken { * Match expression token */ export declare interface MatchExpressionToken extends BaseToken { + /** + * @inheritdoc + */ typ: EnumToken.MatchExpressionTokenType; + /** + * Operator + */ op: EqualMatchToken | DashMatchToken | StartMatchToken | ContainMatchToken | EndMatchToken | IncludeMatchToken; + /** + * Left handle + */ l: Token$1; + /** + * Right handle + */ r: Token$1; + /** + * Flags + */ attr?: "i" | "s"; } @@ -2255,8 +2362,17 @@ export declare interface MatchExpressionToken extends BaseToken { * Name space attribute token */ export declare interface NameSpaceAttributeToken extends BaseToken { + /** + * @inheritdoc + */ typ: EnumToken.NameSpaceAttributeTokenType; + /** + * Left handle + */ l?: Token$1; + /** + * Right handle + */ r: Token$1; } @@ -2264,7 +2380,13 @@ export declare interface NameSpaceAttributeToken extends BaseToken { * List token */ export declare interface ListToken extends BaseToken { + /** + * @inheritdoc + */ typ: EnumToken.ListToken; + /** + * Children + */ chi: Token$1[]; } @@ -2272,8 +2394,17 @@ export declare interface ListToken extends BaseToken { * Composes selector token */ export declare interface ComposesSelectorToken extends BaseToken { + /** + * @inheritdoc + */ typ: EnumToken.ComposesSelectorTokenType; + /** + * Left handle + */ l: Token$1[]; + /** + * Right handle + */ r: Token$1 | null; } @@ -2281,20 +2412,53 @@ export declare interface ComposesSelectorToken extends BaseToken { * Css variable token */ export declare interface CssVariableToken$1 extends BaseToken { + /** + * @inheritdoc + */ typ: EnumToken.CssVariableTokenType; + /** + * Name + */ nam: string; + /** + * Value + */ val: Token$1[]; } +/** + * Css variable import token + */ export declare interface CssVariableImportTokenType$1 extends BaseToken { + /** + * @inheritdoc + */ typ: EnumToken.CssVariableImportTokenType; + /** + * Name + */ nam: string; + /** + * Value + */ val: Token$1[]; } +/** + * Css variable map token + */ export declare interface CssVariableMapTokenType extends BaseToken { + /** + * @inheritdoc + */ typ: EnumToken.CssVariableDeclarationMapTokenType; + /** + * CSS Variables + */ vars: Token$1[]; + /** + * From clause + */ from: Token$1[]; } @@ -2302,6 +2466,9 @@ export declare interface CssVariableMapTokenType extends BaseToken { * Function definition token */ export declare interface FunctionDefToken extends BaseToken { + /** + * @inheritdoc + */ typ: | EnumToken.FunctionDefTokenType | EnumToken.UrlFunctionTokenDefType @@ -2313,7 +2480,13 @@ export declare interface FunctionDefToken extends BaseToken { | EnumToken.MathFunctionTokenDefType | EnumToken.PseudoClassFunctionTokenDefType | EnumToken.TransformFunctionTokenDefType; + /** + * Name + */ nam: string; + /** + * Value + */ val: string; } @@ -2321,7 +2494,13 @@ export declare interface FunctionDefToken extends BaseToken { * Raw node token */ export declare interface RawNodeToken extends BaseToken, EnumAstNodeStatus$1 { + /** + * @inheritdoc + */ typ: EnumToken.RawNodeTokenType; + /** + * Value + */ val: Token$1[]; } @@ -2534,7 +2713,7 @@ export declare interface BaseToken { /** * parent node */ - parent?: AstAtRule | astRule | AstKeyframesAtRule | AstKeyFrameRule | AstInvalidRule | AstInvalidAtRule | null; + parent?: AstAtRule | astRule | AstKeyframesAtRule | AstKeyframesRule | AstInvalidRule | AstInvalidAtRule | null; /** * @private */ @@ -2676,36 +2855,6 @@ export declare interface AstInvalidAtRule extends BaseToken, AstNodeStatus { chi?: Array; } -/** - * keyframe rule node - */ -export declare interface AstKeyFrameRule extends BaseToken, AstNodeStatus { - /** - * token type - */ - typ: EnumToken.KeyFramesRuleNodeType; - /** - * selector - */ - sel: string; - /** - * child nodes - */ - chi: Array; - /** - * optimized selector - */ - optimized?: OptimizedSelector; - /** - * raw selector - */ - raw?: RawSelectorTokens; - /** - * tokens - */ - tokens?: Token$1[]; -} - /** * raw selector tokens */ @@ -2781,6 +2930,36 @@ export declare interface AstAtRule extends BaseToken, AstNodeStatus { chi?: Array; } +/** + * keyframe rule node + */ +export declare interface AstKeyframesRule 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[]; +} + /** * keyframe rule node */ @@ -2837,7 +3016,7 @@ export declare type AstRuleList = | AstAtRule | AstRule | AstKeyframesAtRule - | AstKeyFrameRule + | AstKeyframesRule | AstInvalidRule; /** @@ -2865,7 +3044,7 @@ export declare type AstNode$1 = | AstRule | AstDeclaration | AstKeyframesAtRule - | AstKeyFrameRule + | AstKeyframesRule | AstInvalidRule | AstInvalidAtRule | AstInvalidDeclaration @@ -3060,24 +3239,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 +3468,14 @@ export declare interface VisitorSyncNodeMap { */ Rule?: GenericVisitorAstNodeSyncHandlerMap; + /** + * keyframes rule visitor + */ KeyframesRule?: GenericVisitorAstNodeSyncHandlerMap; + /** + * keyframes at-rule visitor + */ KeyframesAtRule?: GenericVisitorAstNodeSyncHandlerMap; /** @@ -3329,22 +3530,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; /** @@ -4645,19 +4856,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; } @@ -5092,10 +5339,21 @@ export declare interface ParseInputStreamOptions { * @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 @@ -5107,6 +5365,9 @@ export declare interface ParserSourceMapOptions { inputSourceMap?: SourceMapObject | string; } +/** + * Sync parseroptions + */ export declare interface ParserSyncOptions extends MinifyOptions, @@ -5734,69 +5995,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[]; } /** @@ -5937,6 +6237,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 @@ -5962,10 +6267,6 @@ button { console.log({node, value}); ``` - * - * @param ast - * @param matcher - * @returns */ declare function findByValue(ast: AstNode$1, matcher: AstValueMatcher): { node: AstNode$1; @@ -6450,4 +6751,4 @@ declare function transform(options: ParseInputStreamOptions & TransformOptions): 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, 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, WalkerValueFilter, WhenElseQueryConditionToken, WhenElseUnaryConditionToken, WhitespaceToken, WrappedValuesToken }; +export type { AddToken, AndToken, AngleToken, AstAtRule, AstComment, AstDeclaration, AstInvalidAtRule, AstInvalidDeclaration, AstInvalidRule, AstKeyframesAtRule, AstKeyframesRule, AstNode$1 as AstNode, AstNodeStatus, AstRule, AstRuleList, AstStyleSheet, AstValueMatcher, AtRuleToken, AtRuleVisitorHandler, AttrEndToken, AttrStartToken, AttrToken, Background, BackgroundAttachmentMapping, BackgroundPosition, BackgroundPositionClass, BackgroundPositionConstraints, BackgroundPositionMapping, BackgroundProperties, BackgroundRepeat, BackgroundRepeatMapping, BackgroundSize, BackgroundSizeMapping, BadCDOCommentToken, BadCommentToken, BadStringToken, BadUrlToken, BaseToken, BinaryExpressionNode, BinaryExpressionToken, BlockEndToken, BlockStartToken, Border, BorderColor, BorderColorClass, BorderProperties, BorderRadius, CDOCommentToken, ChildCombinatorToken, ClassSelectorToken, ColonToken, ColorToken, ColumnCombinatorToken, CommaToken, CommentToken, ComposesSelectorToken, ConstraintsMapping, ContainMatchToken, ContainerStyleRangeToken, CssVariableImportTokenType$1 as CssVariableImportTokenType, CssVariableMapTokenType, CssVariableToken$1 as CssVariableToken, DashMatchToken, DashedIdentToken, DeclarationVisitorHandler, DelimToken, DescendantCombinatorToken, DimensionToken, DivToken, DoubleColonToken, EOFToken, EndMatchToken, EqualMatchToken, ErrorDescription$1 as ErrorDescription, FlexToken, Font, FontFamily, FontProperties, FontWeight, FontWeightConstraints, FontWeightMapping, FractionToken, FrequencyToken, FunctionDefToken, FunctionImageToken, FunctionToken, FunctionURLToken, GenericVisitorAstNodeHandlerMap, GenericVisitorAstNodeSyncHandlerMap, GenericVisitorAsyncResult, GenericVisitorHandler, GenericVisitorResult, GenericVisitorSyncHandler, GenericVisitorSyncResult, GreaterThanOrEqualToken, GreaterThanToken, GridTemplateFuncToken, HashToken, IdentListToken, IdentToken, IfConditionToken, IfElseConditionToken, ImportantToken, IncludeMatchToken, InvalidAttrToken, InvalidClassSelectorToken, InvalidMediaQueryToken, LengthToken, LessThanOrEqualToken, LessThanToken, LineHeight, ListToken, LiteralToken, LoadResult, Map$1 as Map, MatchExpressionToken, MatchedSelector, MediaFeatureOnlyToken, MediaFeatureToken, MediaQueryConditionToken, MediaQueryUnaryFeatureToken, MediaRangeQueryToken, MinifyFeature, MinifyFeatureOptions, MinifyOptions, ModuleAsyncOptions, ModuleSyncOptions, MulToken, NameSpaceAttributeToken, NestingSelectorToken, NextSiblingCombinatorToken, NotToken, NumberToken, OptimizedSelector, OptimizedSelectorToken, OrToken, Outline, OutlineProperties, ParensEndToken, ParensStartToken, ParensToken, ParseInfo$1 as ParseInfo, ParseInputFileOptions, ParseInputOptions, ParseInputStreamOptions, ParseResult, ParseResultStats, ParseSourceOptions, ParseTokenOptions, ParserOptions, ParserSourceMapOptions, ParserSyncOptions, PercentageToken, Prefix, PropertiesConfig, PropertiesConfigProperties, PropertyListOptions, PropertyMapType, PropertySetType, PropertyType, PseudoClassFunctionToken, PseudoClassToken, PseudoElementToken, PseudoPageToken, PurpleBackgroundAttachment, RawNodeToken, RawSelectorTokens, RenderOptions, RenderResult, ResolutionToken, ResolvedPath, RuleVisitorHandler, SemiColonToken, Separator, ShorthandDef, ShorthandMapType, ShorthandProperties, ShorthandPropertyType, ShorthandType, SinglePropertyType, SinglePropertyTypeMapping, SourceLocation, SourceMapObject, StartMatchToken, StringToken, SubToken, SubsequentCombinatorToken, SupportsQueryConditionToken, SupportsQueryUnaryConditionToken, TimeToken, TimelineFunctionToken, TimingFunctionToken, Token$1 as Token, TokenSearchResult, TokenizeResult, TransformOptions, TransformResult, TransformSyncOptions, UnaryExpression, UnaryExpressionNode, UnclosedStringToken, UniversalSelectorToken, UrlToken, ValidationConfiguration, ValidationMediaFeature, ValidationOptions, ValidationResult, ValidationSelectorOptions, ValidationSyntaxNode, ValidationSyntaxResult, ValidationToken$1 as ValidationToken, Value, ValueVisitorHandler, ValueVisitorSyncHandler, VariableScopeInfo, VisitorNodeMap, VisitorSyncNodeMap, WalkAttributesResult, WalkResult, WalkerFilter, WalkerOption, WalkerValueFilter, WhenElseQueryConditionToken, WhenElseUnaryConditionToken, WhitespaceToken, WrappedValuesToken }; diff --git a/dist/lib/ast/find.js b/dist/lib/ast/find.js index d70ac623..99a59a98 100644 --- a/dist/lib/ast/find.js +++ b/dist/lib/ast/find.js @@ -42,6 +42,11 @@ function find(ast, matcher) { return null; } /** + * + * @param ast + * @param matcher + * @returns + * * Search the ast sub-tree by checking each node's value token and return the first match * ```ts @@ -67,10 +72,6 @@ button { console.log({node, value}); ``` - * - * @param ast - * @param matcher - * @returns */ function findByValue(ast, matcher) { let source; diff --git a/dist/lib/ast/minify.js b/dist/lib/ast/minify.js index 5830747b..896e8ec6 100644 --- a/dist/lib/ast/minify.js +++ b/dist/lib/ast/minify.js @@ -32,29 +32,29 @@ const features = Object.values(index).sort((a, b) => a.ordering - b.ordering); * @param context * @private */ -function minify(ast, opt = {}, recursive = false, errors, nestingContent, context = {}) { +function minify(ast, options = {}, recursive = false, errors, nestingContent, context = {}) { let preprocess = false; let postprocess = false; let parents; let replacement; // @ts-ignore - let { sourcemap, module, ...options } = opt; - if (!("features" in options)) { + let { sourcemap, module, ...options2 } = options; + if (!("features" in options2)) { // @ts-ignore - options = { + options2 = { removeDuplicateDeclarations: true, computeShorthand: true, computeCalcExpression: true, removePrefix: false, features: [], - ...options, + ...options2, }; for (const feature of features) { - feature.register(options); + feature.register(options2); } - options.features.sort((a, b) => a.ordering - b.ordering); + options2.features.sort((a, b) => a.ordering - b.ordering); } - for (const feature of options.features) { + for (const feature of options2.features) { if (feature.processMode & FeatureWalkMode.Pre) { preprocess = true; } @@ -69,7 +69,7 @@ function minify(ast, opt = {}, recursive = false, errors, nestingContent, contex 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; @@ -79,7 +79,7 @@ function minify(ast, opt = {}, recursive = false, errors, nestingContent, contex ? replacement.sel : replacement.nam); } - const result = feature.run(replacement, options, parent[PARENT] ?? ast, context, FeatureWalkMode.Pre); + const result = feature.run(replacement, options2, parent[PARENT] ?? ast, context, FeatureWalkMode.Pre); if (result != null) { replacement = result; } @@ -98,14 +98,14 @@ function minify(ast, opt = {}, recursive = false, errors, nestingContent, contex } } } - 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) { @@ -113,12 +113,12 @@ function minify(ast, opt = {}, recursive = false, errors, nestingContent, contex } replacement = parent; if (postprocess) { - for (const feature of options.features) { + for (const feature of options2.features) { if ((feature.processMode & FeatureWalkMode.Post) === 0 || (feature.accept != null && !feature.accept.has(parent.typ))) { continue; } - const result = feature.run(replacement, options, parent[PARENT] ?? ast, context, FeatureWalkMode.Post); + const result = feature.run(replacement, options2, parent[PARENT] ?? ast, context, FeatureWalkMode.Post); if (result != null) { replacement = result; } @@ -139,10 +139,10 @@ function minify(ast, opt = {}, recursive = false, errors, nestingContent, contex } } 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); } } } diff --git a/dist/lib/validation/match.js b/dist/lib/validation/match.js index dbed9b43..23f06fbc 100644 --- a/dist/lib/validation/match.js +++ b/dist/lib/validation/match.js @@ -10,11 +10,19 @@ import { parseTokens } from '../parser/parse.js'; const config = getSyntaxConfig(); // @ts-expect-error const allValues = config.declarations.all.syntax.split(/[\s|]+/g); +/** + * @type {Array.} + */ const funcTypes = [ ...tokensfuncDefMap.values(), EnumToken.FunctionTokenType, EnumToken.PseudoClassFuncTokenType, ]; +/** + * trim leading and trailing whitespace + * @param tokens + * @returns + */ function trimArray(tokens) { while (tokens[0]?.typ === EnumToken.WhitespaceTokenType) { tokens.shift(); @@ -115,6 +123,11 @@ function isMFValue(featureName, tokens, isMFRange) { success: true, }; } +/** + * create validation context + * @param tokens + * @returns + */ function createValidationContext(tokens) { tokens = trimArray(tokens.filter((t) => t.typ !== EnumToken.CommentTokenType)); if (tokens.at(-1)?.typ === EnumToken.ImportantTokenType) { @@ -278,6 +291,14 @@ function createValidationContext(tokens) { }; return token; } +/** + * match selector syntax + * @param stream + * @param errors + * @param options + * @param nested + * @returns + */ function matchSelectorSyntax(stream, errors, options, nested = true) { const stack = []; const tokens = []; @@ -716,6 +737,13 @@ function matchSelectorSyntax(stream, errors, options, nested = true) { stream.push(...tokens); return { success, errors }; } +/** + * matches all syntaxes + * @param syntaxes + * @param context + * @param options + * @returns + */ function matchAllSyntaxes(syntaxes, context, options) { const result = matchSyntax(syntaxes, context, { ...options, @@ -754,6 +782,13 @@ function matchAllSyntaxes(syntaxes, context, options) { syntaxToken: !result.success ? result.syntaxToken : null, }; } +/** + * matches a list of syntaxes + * @param syntax + * @param context + * @param options + * @returns + */ function matchListSyntax(syntax, context, options) { const { isList, match, isOptional, ...rest } = syntax; let success = true; @@ -798,6 +833,13 @@ function matchListSyntax(syntax, context, options) { token: context.peek(), }; } +/** + * matches a list of syntaxes + * @param syntax + * @param context + * @param options + * @returns + */ function matchOccurenceSyntax(syntax, context, options) { const { match, ...rest } = syntax; let result = null; @@ -839,6 +881,13 @@ function matchOccurenceSyntax(syntax, context, options) { } return result; } +/** + * matches a list of syntaxes + * @param syntaxes + * @param context + * @param options + * @returns + */ function matchSyntax(syntaxes, context, options) { if (syntaxes == null) { return { @@ -1479,6 +1528,13 @@ function matchSyntax(syntaxes, context, options) { errors: [], }; } +/** + * matches a column of syntaxes + * @param syntax + * @param context + * @param options + * @returns + */ function matchColumnSyntax(syntax, context, options) { let syntaxes = syntax.chi.slice(); let i = 0; @@ -1515,6 +1571,13 @@ function matchColumnSyntax(syntax, context, options) { errors: [], }; } +/** + * matches an ampersand of syntaxes + * @param syntax + * @param context + * @param options + * @returns + */ function matchAmpersandSyntax(syntax, context, options) { const syntaxes = [syntax.l, syntax.r]; let result; @@ -1533,6 +1596,13 @@ function matchAmpersandSyntax(syntax, context, options) { } return result; } +/** + * matches a property + * @param property + * @param context + * @param options + * @returns + */ function matchProperty(property, context, options) { let success = false; let t = context.peek()?.typ; @@ -2265,6 +2335,13 @@ function matchProperty(property, context, options) { errors: [], }; } +/** + * matches a repeatable syntax + * @param syntax + * @param context + * @param options + * @returns + */ function matchRepeatableSyntax(syntax, context, options) { const { isRepeatable, isOptional, isMandatatoryGroup, isRepeatableAtLeastOnce, ...rest } = syntax; let result = null; diff --git a/src/@types/ast.d.ts b/src/@types/ast.d.ts index 5a577a90..1f5efeb3 100644 --- a/src/@types/ast.d.ts +++ b/src/@types/ast.d.ts @@ -1,7 +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 type { AstNode } from "./ast.d.ts"; /** * token or node location diff --git a/src/@types/index.d.ts b/src/@types/index.d.ts index 70fcdea3..9e323ba4 100644 --- a/src/@types/index.d.ts +++ b/src/@types/index.d.ts @@ -7,7 +7,6 @@ import type { CssVariableToken, Token } from "./token.d.ts"; import { FeatureWalkMode } from "../lib/ast/features/type.ts"; import { ValidationToken } from "../lib/validation/parser/types"; import { SourceFile } from "../lib/parser/source.ts"; -import type { VisitorSyncNodeMap, VisitorNodeMap } from "./visitor.d.ts"; export * from "./ast.d.ts"; export * from "./token.d.ts"; From 75cd05f851c213f616216264780fec4c3a3d0c55 Mon Sep 17 00:00:00 2001 From: Thierry Bela Nanga Date: Sun, 16 Aug 2026 22:27:06 -0400 Subject: [PATCH 08/11] remove sourcemap flag #146 --- test/allFiles.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/allFiles.js b/test/allFiles.js index 841590ef..184efb15 100644 --- a/test/allFiles.js +++ b/test/allFiles.js @@ -20,11 +20,11 @@ for (const file of await readdir(baseDir)) { message = ''; const result = await load(baseDir + file, import.meta.dirname).then(css => transform(css, { - src: baseDir + file, minify: true, sourcemap: true, + src: baseDir + file, minify: true, removePrefix: true, nestingRules: true, resolveImport: true, - sourcemap: true, + // sourcemap: true, validation: true })); From c7b46acb88c219c78d7fdc9a3f53e668a6e78a95 Mon Sep 17 00:00:00 2001 From: Thierry Bela Nanga Date: Sun, 16 Aug 2026 22:29:50 -0400 Subject: [PATCH 09/11] remove sourcemap flag #146 --- test/allFiles.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/allFiles.js b/test/allFiles.js index 184efb15..e82da112 100644 --- a/test/allFiles.js +++ b/test/allFiles.js @@ -31,7 +31,7 @@ for (const file of await readdir(baseDir)) { message += `[inputSize]: ${toFileSize(result.stats.bytesIn)}\n `; message += `[outputSize]: ${toFileSize(result.stats.bytesOut)}\n `; message += `[ratio]: ${(100 * (1 - result.stats.bytesOut / result.stats.bytesIn)).toFixed(2)}%\n `; - message += `[sourcemap]: ${JSON.stringify(result.map.toJSON()).length}\n `; + // message += `[sourcemap]: ${JSON.stringify(result.map.toJSON()).length}\n `; for (const key in result.stats) { From 2628ebcef9e1c9034e16af85a65f78a0e2fb3a97 Mon Sep 17 00:00:00 2001 From: Thierry Bela Nanga Date: Mon, 17 Aug 2026 02:53:39 -0400 Subject: [PATCH 10/11] rewrite visitors handling #146 --- dist/index-umd-web.js | 1007 +++++++++------------ dist/index.cjs | 1007 +++++++++------------ dist/index.d.ts | 6 +- dist/lib/ast/features/shorthand.js | 2 +- dist/lib/ast/features/transform.js | 2 +- dist/lib/ast/minify.js | 12 +- dist/lib/ast/types.js | 2 +- dist/lib/parser/parse.js | 993 +++++++++------------ dist/lib/parser/utils/selector.js | 2 +- dist/lib/renderer/render.js | 4 +- src/@types/ast.d.ts | 4 +- src/lib/ast/features/shorthand.ts | 2 +- src/lib/ast/features/transform.ts | 2 +- src/lib/ast/find.ts | 2 +- src/lib/ast/minify.ts | 12 +- src/lib/ast/types.ts | 2 +- src/lib/parser/parse.ts | 1309 ++++++++++++---------------- src/lib/parser/utils/selector.ts | 2 +- src/lib/renderer/render.ts | 4 +- test/specs/code/modules.js | 34 +- test/specs/code/visitors.js | 390 +++++++-- 21 files changed, 2215 insertions(+), 2585 deletions(-) diff --git a/dist/index-umd-web.js b/dist/index-umd-web.js index 768c4117..ffe67ee0 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 */ @@ -19719,7 +19719,7 @@ accept = new Set([ exports.EnumToken.RuleNodeType, exports.EnumToken.AtRuleNodeType, - exports.EnumToken.KeyFramesRuleNodeType, + exports.EnumToken.KeyframesRuleNodeType, ]); get ordering() { return 10; @@ -21060,7 +21060,7 @@ } class TransformCssFeature { - accept = new Set([exports.EnumToken.RuleNodeType, exports.EnumToken.KeyFramesRuleNodeType]); + accept = new Set([exports.EnumToken.RuleNodeType, exports.EnumToken.KeyframesRuleNodeType]); get ordering() { return 3; } @@ -22805,7 +22805,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); @@ -22863,7 +22863,7 @@ 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); } @@ -23138,8 +23138,8 @@ continue; } } - else if (node.typ === exports.EnumToken.KeyFramesRuleNodeType) { - if (previous?.typ === exports.EnumToken.KeyFramesRuleNodeType && + else if (node.typ === exports.EnumToken.KeyframesRuleNodeType) { + if (previous?.typ === exports.EnumToken.KeyframesRuleNodeType && node.sel === previous.sel) { // do not merge keyframes // https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/At-rules/@keyframes#resolving_duplicates @@ -23427,7 +23427,7 @@ } if (shouldMerge) { if (((node.typ === exports.EnumToken.RuleNodeType || - node.typ === exports.EnumToken.KeyFramesRuleNodeType) && + node.typ === exports.EnumToken.KeyframesRuleNodeType) && node.sel === previous.sel) || (node.typ == exports.EnumToken.AtRuleNodeType && node.nam !== "font-face" && @@ -23440,7 +23440,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) { @@ -24800,7 +24800,7 @@ [ exports.EnumToken.RuleNodeType, exports.EnumToken.AtRuleNodeType, - exports.EnumToken.KeyFramesRuleNodeType, + exports.EnumToken.KeyframesRuleNodeType, exports.EnumToken.KeyframesAtRuleNodeType, ].includes(node.typ)) { const source = options.sourcesMap.get(node[LOC].srcId); @@ -24938,7 +24938,7 @@ 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};`; @@ -26019,7 +26019,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 }), "")); @@ -28711,6 +28711,99 @@ // if leading char is digit, prefix underscore (very rare) return (/^[0-9]/.test(result) ? "_" : "") + result; }); + function parseVisitors(options, valuesHandlers, preValuesHandlers, postValuesHandlers, errors, visitorsHandlersMap, preVisitorsHandlersMap, postVisitorsHandlersMap) { + 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)) { + if (Array.isArray(value)) { + visitors.splice(i + 1, 0, ...Object.entries(value)); + continue; + } + if (typeof value == "function") { + key = value.name; + } + } + if (Array.isArray(value)) { + // @ts-ignore + visitors.splice(i + 1, 0, ...value.map((item) => [key, item])); + continue; + } + if (key in exports.EnumToken) { + if (typeof value == "function") { + if (!valuesHandlers.has(exports.EnumToken[key])) { + valuesHandlers.set(exports.EnumToken[key], []); + } + valuesHandlers.get(exports.EnumToken[key]).push(value); + } + else if (typeof value == "object" && "type" in value && "handler" in value && value.type in exports.WalkerEvent) { + if (value.type == exports.WalkerEvent.Enter) { + if (!preValuesHandlers.has(exports.EnumToken[key])) { + preValuesHandlers.set(exports.EnumToken[key], []); + } + preValuesHandlers.get(exports.EnumToken[key]).push(value.handler); + } + else if (value.type == exports.WalkerEvent.Leave) { + if (!postValuesHandlers.has(exports.EnumToken[key])) { + postValuesHandlers.set(exports.EnumToken[key], []); + } + postValuesHandlers.get(exports.EnumToken[key]).push(value.handler); + } + } + else { + errors.push({ action: "ignore", message: `doParse: visitor.${key} is not a valid key name` }); + } + } + else if (["Declaration", "Rule", "AtRule", "KeyframesRule", "KeyframesAtRule"].includes(key)) { + if (typeof value == "function") { + if (!visitorsHandlersMap.has(key)) { + visitorsHandlersMap.set(key, []); + } + visitorsHandlersMap + .get(key) + .push(value); + } + else if (typeof value == "object") { + if ("type" in value && "handler" in value && value.type in exports.WalkerEvent) { + if (value.type == exports.WalkerEvent.Enter) { + if (!preVisitorsHandlersMap.has(key)) { + preVisitorsHandlersMap.set(key, []); + } + preVisitorsHandlersMap + .get(key) + .push(value.handler); + } + else if (value.type == exports.WalkerEvent.Leave) { + if (!postVisitorsHandlersMap.has(key)) { + postVisitorsHandlersMap.set(key, []); + } + postVisitorsHandlersMap + .get(key) + .push(value.handler); + } + } + else { + if (!visitorsHandlersMap.has(key)) { + visitorsHandlersMap.set(key, []); + } + visitorsHandlersMap + .get(key) + .push(value); + } + } + else { + errors.push({ action: "ignore", message: `doParse: visitor.${key} is not a valid key name` }); + } + } + else { + errors.push({ action: "ignore", message: `doParse: visitor.${key} is not a valid key name` }); + } + } + } /** * Parse css string * @param iter @@ -28793,114 +28886,13 @@ // @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; + // if (Array.isArray(iter)) { + // // @ts-expect-error + // iter = iter[Symbol.iterator]() as Iterator; + // } + for (currentItemIndex = 0; currentItemIndex < iter.length; currentItemIndex++) { + item = iter[currentItemIndex]; stats.bytesIn = item.bytesIn; stats.tokensCount++; if (BadTokensTypes.includes(item.token.typ)) { @@ -28928,8 +28920,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 || @@ -28945,8 +28935,7 @@ let inBlock = 1; tokens = [item.token]; do { - // @ts-expect-error - item = iter.next().value; + item = iter[++currentItemIndex]; if (item == null) { break; } @@ -29001,198 +28990,186 @@ ast = expand(ast); } let replacement; - let callable; if (options.visitor != null) { + valuesHandlers = new Map(); + preValuesHandlers = new Map(); + postValuesHandlers = new Map(); + preVisitorsHandlersMap = new Map(); + visitorsHandlersMap = new Map(); + postVisitorsHandlersMap = new Map(); + parseVisitors(options, valuesHandlers, preValuesHandlers, postValuesHandlers, errors, visitorsHandlersMap, preVisitorsHandlersMap, postVisitorsHandlersMap); let parens; - for (const result of walk(ast)) { + let genericKey; + const handlers = []; + 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); + } + let nodes = new Array(stats.tokensCount); + const subNodes = []; + let i; + let k; + let j; + let freeBlock = 1; + const includeTokens = preValuesHandlers.size > 0 || valuesHandlers.size > 0 || postValuesHandlers.size > 0; + nodes[0] = ast; + for (i = 0; i < nodes.length; i++) { + if (nodes[i] == null) { + break; + } + subNodes.length = 0; + if (includeTokens) { + switch (nodes[i].typ) { + case exports.EnumToken.RuleNodeType: + case exports.EnumToken.AtRuleNodeType: + case exports.EnumToken.KeyframesRuleNodeType: + case exports.EnumToken.KeyframesAtRuleNodeType: + subNodes.push(...nodes[i][TOKENS]); + break; + case exports.EnumToken.DeclarationNodeType: + subNodes.push(...nodes[i].val); + break; + } + } + if (nodes[i].chi != null) { + subNodes.push(...nodes[i].chi); + } + if (subNodes.length > 0) { + if (freeBlock <= i) { + freeBlock = i + 1; + } + for (k = 0; k < subNodes.length; k++) { + j = k + freeBlock; + nodes[j] = subNodes[k]; + nodes[j][PARENT] = nodes[i]; + } + freeBlock += subNodes.length; + } parens = null; - if (valuesHandlers.size > 0 || - preVisitorsHandlersMap.size > 0 || - visitorsHandlersMap.size > 0 || - postVisitorsHandlersMap.size > 0) { - if ((result.node.typ == exports.EnumToken.DeclarationNodeType && - (preVisitorsHandlersMap.has("Declaration") || - visitorsHandlersMap.has("Declaration") || - postVisitorsHandlersMap.has("Declaration"))) || - (result.node.typ == exports.EnumToken.AtRuleNodeType && - (preVisitorsHandlersMap.has("AtRule") || - visitorsHandlersMap.has("AtRule") || - postVisitorsHandlersMap.has("AtRule"))) || - (result.node.typ == exports.EnumToken.KeyframesAtRuleNodeType && - (preVisitorsHandlersMap.has("KeyframesAtRule") || - visitorsHandlersMap.has("KeyframesAtRule") || - postVisitorsHandlersMap.has("KeyframesAtRule")))) { - const handlers = []; - const key = result.node.typ == exports.EnumToken.DeclarationNodeType - ? "Declaration" - : result.node.typ == exports.EnumToken.AtRuleNodeType - ? "AtRule" - : "KeyframesAtRule"; - if (preVisitorsHandlersMap.has(key)) { - handlers.push( - // @ts-expect-error - ...preVisitorsHandlersMap.get(key)); - } - if (visitorsHandlersMap.has(key)) { - // @ts-ignore - handlers.push(...visitorsHandlersMap.get(key)); - } - if (postVisitorsHandlersMap.has(key)) { - // @ts-ignore - handlers.push(...postVisitorsHandlersMap.get(key)); - } - let node = result.node; - for (const handler of handlers) { - callable = - typeof handler == "function" - ? handler - : handler[camelize(node.typ === exports.EnumToken.DeclarationNodeType || - node.typ === exports.EnumToken.AtRuleNodeType - ? node.nam - : node.val)]; - if (callable == null) { - continue; + handlers.length = 0; + genericKey = + nodes[i].typ == exports.EnumToken.DeclarationNodeType + ? "Declaration" + : nodes[i].typ == exports.EnumToken.AtRuleNodeType + ? "AtRule" + : nodes[i].typ == exports.EnumToken.KeyframesAtRuleNodeType + ? "KeyframesAtRule" + : nodes[i].typ === exports.EnumToken.KeyframesRuleNodeType + ? "KeyframesRule" + : nodes[i].typ == exports.EnumToken.RuleNodeType + ? "Rule" + : nodes[i].typ == exports.EnumToken.KeyframesRuleNodeType + ? "KeyframesRule" + : null; + let keyName = nodes[i].typ == exports.EnumToken.DeclarationNodeType || nodes[i].typ == exports.EnumToken.AtRuleNodeType + ? camelize(nodes[i].nam) + : nodes[i].typ == exports.EnumToken.KeyframesAtRuleNodeType + ? camelize(nodes[i].val) + : null; + for (const map of allHandlers) { + // @ts-ignore + if (genericKey != null && map.has(genericKey)) { + // @ts-ignore + for (const handler of map.get(genericKey)) { + if (typeof handler == "function") { + handlers.push(handler); } - // @ts-expect-error - replacement = callable(node, result[PARENT], ast, function* () { - if (parens == null) { - // @ts-expect-error - parens = [...result.parents()]; + else if (Array.isArray(handler)) { + for (const h of handler) { + if (typeof h == "function") { + handlers.push(h); + } + // @ts-ignore + else if (h[keyName] != null) { + // @ts-ignore + handlers.push(h[keyName]); + } } - yield* parens[Symbol.iterator](); - }); - if (replacement == null) { - continue; } - if (replacement == node) { - continue; + else if (typeof handler.handler == "function") { + handlers.push(handler.handler); } // @ts-ignore - node = replacement; - if (Array.isArray(node)) { - break; + else if (typeof handler[keyName] == "function") { + // @ts-ignore + handlers.push(handler[keyName]); } } - if (node != result.node) { - replaceNodeOrValue(result.parent, result.node, node); - } } - else if ((result.node.typ == exports.EnumToken.RuleNodeType && - (preVisitorsHandlersMap.has("Rule") || - visitorsHandlersMap.has("Rule") || - postVisitorsHandlersMap.has("Rule"))) || - (result.node.typ == exports.EnumToken.KeyFramesRuleNodeType && - (preVisitorsHandlersMap.has("KeyframesRule") || - visitorsHandlersMap.has("KeyframesRule") || - postVisitorsHandlersMap.has("KeyframesRule")))) { - const handlers = []; - const key = result.node.typ == exports.EnumToken.RuleNodeType ? "Rule" : "KeyframesRule"; - if (preVisitorsHandlersMap.has(key)) { - handlers.push(...preVisitorsHandlersMap.get(key)); - } - if (visitorsHandlersMap.has(key)) { - handlers.push(...visitorsHandlersMap.get(key)); - } - if (postVisitorsHandlersMap.has(key)) { - handlers.push(...postVisitorsHandlersMap.get(key)); - } - let node = result.node; - for (const callable of handlers) { - replacement = callable(node, result.parent, result.root, - // @ts-expect-error - function* () { - if (parens == null) { - // @ts-expect-error - parens = [...result.parents()]; + // @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 == 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 != 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 != node) { - node = result; - } - if (Array.isArray(node)) { - break; - } - } - } - if (node != value) { - // @ts-ignore - replaceNodeOrValue(parent, value, node); + } + if (handlers.length == 0) { + continue; + } + let node = nodes[i]; + for (const callable of handlers) { + replacement = callable(node, nodes[i][PARENT], ast, + // @ts-expect-error + function* () { + if (parens == null) { + let node = nodes[i][PARENT]; + while (node != null) { + yield node; + node = node[PARENT]; } } + }); + if (replacement == null) { + continue; + } + if (replacement == node) { + continue; } + // @ts-ignore + node = replacement; + // + if (Array.isArray(node)) { + break; + } + } + if (node != nodes[i]) { + replaceNodeOrValue(nodes[i][PARENT], nodes[i], node); } } + nodes = null; } if (invalidNodes.length > 0) { let count = invalidNodes.length; @@ -29256,7 +29233,7 @@ scoped: exports.ModuleScopeEnumOptions.Local, naming: exports.ModuleCaseTransformEnum.IgnoreCase, pattern: "", - generateScopedName, + generateScopedName: generateSyncScopedName, ...(typeof options.module != "object" ? {} : options.module), }; const parseModuleTime = performance.now(); @@ -29819,107 +29796,6 @@ 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` }); - } - } - } if (Array.isArray(iter)) { // @ts-expect-error iter = iter[Symbol.iterator](); @@ -30081,7 +29957,6 @@ ast = expand(ast); } let replacement; - let callable; while (stack.length > 0 && context != ast) { const previousNode = stack.pop(); context = (stack[stack.length - 1] ?? ast); @@ -30097,208 +29972,188 @@ break; } if (options.visitor != null) { + valuesHandlers = new Map(); + preValuesHandlers = new Map(); + postValuesHandlers = new Map(); + preVisitorsHandlersMap = new Map(); + visitorsHandlersMap = new Map(); + postVisitorsHandlersMap = new Map(); + parseVisitors(options, valuesHandlers, preValuesHandlers, postValuesHandlers, errors, visitorsHandlersMap, preVisitorsHandlersMap, postVisitorsHandlersMap); let parens; - for (const result of walk(ast)) { + let genericKey; + const handlers = []; + 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); + } + let nodes = new Array(stats.tokensCount); + const subNodes = []; + let i; + let k; + let j; + let freeblock = 1; + const includeTokens = preValuesHandlers.size > 0 || valuesHandlers.size > 0 || postValuesHandlers.size > 0; + nodes[0] = ast; + for (i = 0; i < nodes.length; i++) { + if (nodes[i] == null) { + break; + } + subNodes.length = 0; + if (includeTokens) { + switch (nodes[i].typ) { + case exports.EnumToken.RuleNodeType: + case exports.EnumToken.AtRuleNodeType: + case exports.EnumToken.KeyframesRuleNodeType: + case exports.EnumToken.KeyframesAtRuleNodeType: + subNodes.push(...nodes[i][TOKENS]); + break; + case exports.EnumToken.DeclarationNodeType: + subNodes.push(...nodes[i].val); + break; + } + } + if (nodes[i].chi != null) { + subNodes.push(...nodes[i].chi); + } + if (subNodes.length > 0) { + if (freeblock <= i) { + freeblock = i + 1; + } + for (k = 0; k < subNodes.length; k++) { + j = k + freeblock; + nodes[j] = subNodes[k]; + nodes[j][PARENT] = nodes[i]; + } + freeblock += subNodes.length; + } parens = null; - if (valuesHandlers.size > 0 || - preVisitorsHandlersMap.size > 0 || - visitorsHandlersMap.size > 0 || - postVisitorsHandlersMap.size > 0) { - if ((result.node.typ == exports.EnumToken.DeclarationNodeType && - (preVisitorsHandlersMap.has("Declaration") || - visitorsHandlersMap.has("Declaration") || - postVisitorsHandlersMap.has("Declaration"))) || - (result.node.typ == exports.EnumToken.AtRuleNodeType && - (preVisitorsHandlersMap.has("AtRule") || - visitorsHandlersMap.has("AtRule") || - postVisitorsHandlersMap.has("AtRule"))) || - (result.node.typ == exports.EnumToken.KeyframesAtRuleNodeType && - (preVisitorsHandlersMap.has("KeyframesAtRule") || - visitorsHandlersMap.has("KeyframesAtRule") || - postVisitorsHandlersMap.has("KeyframesAtRule")))) { - const handlers = []; - const key = result.node.typ == exports.EnumToken.DeclarationNodeType - ? "Declaration" - : result.node.typ == exports.EnumToken.AtRuleNodeType - ? "AtRule" - : "KeyframesAtRule"; - if (preVisitorsHandlersMap.has(key)) { - handlers.push( - // @ts-expect-error - ...preVisitorsHandlersMap.get(key)); - } - if (visitorsHandlersMap.has(key)) { - // @ts-ignore - handlers.push(...visitorsHandlersMap.get(key)); - } - if (postVisitorsHandlersMap.has(key)) { - // @ts-ignore - handlers.push(...postVisitorsHandlersMap.get(key)); - } - let node = result.node; - for (const handler of handlers) { - callable = - typeof handler == "function" - ? handler - : handler[camelize(node.typ === exports.EnumToken.DeclarationNodeType || - node.typ === exports.EnumToken.AtRuleNodeType - ? node.nam - : node.val)]; - if (callable == null) { - continue; + handlers.length = 0; + genericKey = + nodes[i].typ == exports.EnumToken.DeclarationNodeType + ? "Declaration" + : nodes[i].typ == exports.EnumToken.AtRuleNodeType + ? "AtRule" + : nodes[i].typ == exports.EnumToken.KeyframesAtRuleNodeType + ? "KeyframesAtRule" + : nodes[i].typ === exports.EnumToken.KeyframesRuleNodeType + ? "KeyframesRule" + : nodes[i].typ == exports.EnumToken.RuleNodeType + ? "Rule" + : nodes[i].typ == exports.EnumToken.KeyframesRuleNodeType + ? "KeyframesRule" + : null; + let keyName = nodes[i].typ == exports.EnumToken.DeclarationNodeType || nodes[i].typ == exports.EnumToken.AtRuleNodeType + ? camelize(nodes[i].nam) + : nodes[i].typ == exports.EnumToken.KeyframesAtRuleNodeType + ? camelize(nodes[i].val) + : null; + for (const map of allHandlers) { + // @ts-ignore + if (genericKey != null && map.has(genericKey)) { + // @ts-ignore + for (const handler of map.get(genericKey)) { + if (typeof handler == "function") { + handlers.push(handler); } - // @ts-expect-error - replacement = callable(node, result[PARENT], ast, function* () { - if (parens == null) { - // @ts-expect-error - parens = [...result.parents()]; + else if (Array.isArray(handler)) { + for (const h of handler) { + if (typeof h == "function") { + handlers.push(h); + } + // @ts-ignore + else if (h[keyName] != null) { + // @ts-ignore + handlers.push(h[keyName]); + } } - yield* parens[Symbol.iterator](); - }); - if (replacement == null) { - continue; - } - if (replacement instanceof Promise) { - replacement = await replacement; } - if (replacement == null || replacement == node) { - continue; + else if (typeof handler.handler == "function") { + handlers.push(handler.handler); } // @ts-ignore - node = replacement; - if (Array.isArray(node)) { - break; + else if (typeof handler[keyName] == "function") { + // @ts-ignore + handlers.push(handler[keyName]); } } - if (node != result.node) { - replaceNodeOrValue(result.parent, result.node, node); - } } - else if ((result.node.typ == exports.EnumToken.RuleNodeType && - (preVisitorsHandlersMap.has("Rule") || - visitorsHandlersMap.has("Rule") || - postVisitorsHandlersMap.has("Rule"))) || - (result.node.typ == exports.EnumToken.KeyFramesRuleNodeType && - (preVisitorsHandlersMap.has("KeyframesRule") || - visitorsHandlersMap.has("KeyframesRule") || - postVisitorsHandlersMap.has("KeyframesRule")))) { - const handlers = []; - const key = result.node.typ == exports.EnumToken.RuleNodeType ? "Rule" : "KeyframesRule"; - if (preVisitorsHandlersMap.has(key)) { - handlers.push(...preVisitorsHandlersMap.get(key)); - } - if (visitorsHandlersMap.has(key)) { - handlers.push(...visitorsHandlersMap.get(key)); - } - if (postVisitorsHandlersMap.has(key)) { - handlers.push(...postVisitorsHandlersMap.get(key)); - } - let node = result.node; - for (const callable of handlers) { - replacement = callable(node, result.parent, result.root, - // @ts-expect-error - function* () { - if (parens == null) { - // @ts-expect-error - parens = [...result.parents()]; - } - yield* parens[Symbol.iterator](); - }); - if (replacement == null) { - continue; + // @ts-ignore + if (map.has(nodes[i].typ)) { + // @ts-ignore + for (const handler of map.get(nodes[i].typ)) { + if (typeof handler == "function") { + handlers.push(handler); } - if (replacement instanceof Promise) { - replacement = await replacement; + else if (Array.isArray(handler)) { + for (const h of handler) { + if (typeof h == "function") { + handlers.push(h); + } + } } - if (replacement == null || replacement == node) { - continue; + else if (typeof handler.handler == "function") { + handlers.push(handler.handler); } // @ts-ignore - node = replacement; - // - if (Array.isArray(node)) { - break; + else if (typeof handler[keyName] == "function") { + // @ts-ignore + handlers.push(handler[keyName]); } } - // @ts-ignore - if (node != result.node) { - // @ts-ignore - replaceNodeOrValue(result.parent, result.node, node); - } } - else if (valuesHandlers.size > 0) { - let node = null; - node = result.node; - if (valuesHandlers.has(node.typ)) { - for (const valueHandler of valuesHandlers.get(node.typ)) { - callable = valueHandler; - replacement = callable(node, result.parent, ast, - // @ts-expect-error - function* () { - if (parens == null) { - // @ts-expect-error - parens = [...result.parents()]; - } - yield* parens[Symbol.iterator](); - }); - if (replacement == null) { - continue; - } - if (replacement instanceof Promise) { - replacement = await replacement; - } - if (replacement != null && replacement != node) { - node = replacement; - } - } - } - if (node != result.node) { - // @ts-ignore - replaceNodeOrValue(result[PARENT], value, node); - } - const tokens = Array.isArray(result.node[TOKENS]) ? result.node[TOKENS] : []; - if (Array.isArray(result.node.val)) { - tokens.push(...result.node.val); - } - if (tokens.length == 0) { - continue; - } - for (const { value, parent, root, parents } of walkValues(tokens, result.node)) { - node = value; - if (valuesHandlers.has(node.typ)) { - let parens = null; - for (const valueHandler of valuesHandlers.get(node.typ)) { - callable = valueHandler; - // @ts-expect-error - let result = callable(node, parent, root, function* () { - if (parens == null) { - // @ts-expect-error - parens = [...parents()]; - } - yield* parens[Symbol.iterator](); - }); - if (result == null) { - continue; - } - if (result instanceof Promise) { - result = await result; - } - if (result != null && result != node) { - node = result; - } - if (Array.isArray(node)) { - break; - } - } - } - if (node != value) { - // @ts-ignore - replaceNodeOrValue(parent, value, node); + } + if (handlers.length == 0) { + continue; + } + let node = nodes[i]; + for (const callable of handlers) { + replacement = callable(node, nodes[i][PARENT], ast, + // @ts-expect-error + function* () { + if (parens == null) { + let node = nodes[i][PARENT]; + while (node != null) { + yield node; + node = node[PARENT]; } } + }); + if (replacement == null) { + continue; + } + if (replacement instanceof Promise) { + replacement = await replacement; + } + if (replacement == null || replacement == node) { + continue; + } + // @ts-ignore + node = replacement; + // + if (Array.isArray(node)) { + break; } } + if (node != nodes[i]) { + replaceNodeOrValue(nodes[i][PARENT], nodes[i], node); + } } + nodes = null; } if (invalidNodes.length > 0) { let count = invalidNodes.length; diff --git a/dist/index.cjs b/dist/index.cjs index 481a526d..f62fb5f0 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 */ @@ -19722,7 +19722,7 @@ class ComputeShorthandFeature { accept = new Set([ exports.EnumToken.RuleNodeType, exports.EnumToken.AtRuleNodeType, - exports.EnumToken.KeyFramesRuleNodeType, + exports.EnumToken.KeyframesRuleNodeType, ]); get ordering() { return 10; @@ -21063,7 +21063,7 @@ function splitTransformList(transformList) { } class TransformCssFeature { - accept = new Set([exports.EnumToken.RuleNodeType, exports.EnumToken.KeyFramesRuleNodeType]); + accept = new Set([exports.EnumToken.RuleNodeType, exports.EnumToken.KeyframesRuleNodeType]); get ordering() { return 3; } @@ -22808,7 +22808,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); @@ -22866,7 +22866,7 @@ function minify(ast, options = {}, recursive = false, errors, nestingContent, co 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); } @@ -23141,8 +23141,8 @@ function doMinify(ast, options = {}, recursive = false, errors, nestingContent, continue; } } - else if (node.typ === exports.EnumToken.KeyFramesRuleNodeType) { - if (previous?.typ === exports.EnumToken.KeyFramesRuleNodeType && + else if (node.typ === exports.EnumToken.KeyframesRuleNodeType) { + if (previous?.typ === exports.EnumToken.KeyframesRuleNodeType && node.sel === previous.sel) { // do not merge keyframes // https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/At-rules/@keyframes#resolving_duplicates @@ -23430,7 +23430,7 @@ function doMinify(ast, options = {}, recursive = false, errors, nestingContent, } if (shouldMerge) { if (((node.typ === exports.EnumToken.RuleNodeType || - node.typ === exports.EnumToken.KeyFramesRuleNodeType) && + node.typ === exports.EnumToken.KeyframesRuleNodeType) && node.sel === previous.sel) || (node.typ == exports.EnumToken.AtRuleNodeType && node.nam !== "font-face" && @@ -23443,7 +23443,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) { @@ -24803,7 +24803,7 @@ function updateSourceMap(node, options, cache, sourcemaps, sourceLocation, lines [ exports.EnumToken.RuleNodeType, exports.EnumToken.AtRuleNodeType, - exports.EnumToken.KeyFramesRuleNodeType, + exports.EnumToken.KeyframesRuleNodeType, exports.EnumToken.KeyframesAtRuleNodeType, ].includes(node.typ)) { const source = options.sourcesMap.get(node[LOC].srcId); @@ -24941,7 +24941,7 @@ function renderAstNode(data, options, sourcemaps, sourceLocation, linesMap, erro 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};`; @@ -26022,7 +26022,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 }), "")); @@ -28714,6 +28714,99 @@ const generateSyncScopedName = memoize((localName, filePath, pattern, hashLength // if leading char is digit, prefix underscore (very rare) return (/^[0-9]/.test(result) ? "_" : "") + result; }); +function parseVisitors(options, valuesHandlers, preValuesHandlers, postValuesHandlers, errors, visitorsHandlersMap, preVisitorsHandlersMap, postVisitorsHandlersMap) { + 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)) { + if (Array.isArray(value)) { + visitors.splice(i + 1, 0, ...Object.entries(value)); + continue; + } + if (typeof value == "function") { + key = value.name; + } + } + if (Array.isArray(value)) { + // @ts-ignore + visitors.splice(i + 1, 0, ...value.map((item) => [key, item])); + continue; + } + if (key in exports.EnumToken) { + if (typeof value == "function") { + if (!valuesHandlers.has(exports.EnumToken[key])) { + valuesHandlers.set(exports.EnumToken[key], []); + } + valuesHandlers.get(exports.EnumToken[key]).push(value); + } + else if (typeof value == "object" && "type" in value && "handler" in value && value.type in exports.WalkerEvent) { + if (value.type == exports.WalkerEvent.Enter) { + if (!preValuesHandlers.has(exports.EnumToken[key])) { + preValuesHandlers.set(exports.EnumToken[key], []); + } + preValuesHandlers.get(exports.EnumToken[key]).push(value.handler); + } + else if (value.type == exports.WalkerEvent.Leave) { + if (!postValuesHandlers.has(exports.EnumToken[key])) { + postValuesHandlers.set(exports.EnumToken[key], []); + } + postValuesHandlers.get(exports.EnumToken[key]).push(value.handler); + } + } + else { + errors.push({ action: "ignore", message: `doParse: visitor.${key} is not a valid key name` }); + } + } + else if (["Declaration", "Rule", "AtRule", "KeyframesRule", "KeyframesAtRule"].includes(key)) { + if (typeof value == "function") { + if (!visitorsHandlersMap.has(key)) { + visitorsHandlersMap.set(key, []); + } + visitorsHandlersMap + .get(key) + .push(value); + } + else if (typeof value == "object") { + if ("type" in value && "handler" in value && value.type in exports.WalkerEvent) { + if (value.type == exports.WalkerEvent.Enter) { + if (!preVisitorsHandlersMap.has(key)) { + preVisitorsHandlersMap.set(key, []); + } + preVisitorsHandlersMap + .get(key) + .push(value.handler); + } + else if (value.type == exports.WalkerEvent.Leave) { + if (!postVisitorsHandlersMap.has(key)) { + postVisitorsHandlersMap.set(key, []); + } + postVisitorsHandlersMap + .get(key) + .push(value.handler); + } + } + else { + if (!visitorsHandlersMap.has(key)) { + visitorsHandlersMap.set(key, []); + } + visitorsHandlersMap + .get(key) + .push(value); + } + } + else { + errors.push({ action: "ignore", message: `doParse: visitor.${key} is not a valid key name` }); + } + } + else { + errors.push({ action: "ignore", message: `doParse: visitor.${key} is not a valid key name` }); + } + } +} /** * Parse css string * @param iter @@ -28796,114 +28889,13 @@ function doParseSync(iter, options = {}) { // @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; + // if (Array.isArray(iter)) { + // // @ts-expect-error + // iter = iter[Symbol.iterator]() as Iterator; + // } + for (currentItemIndex = 0; currentItemIndex < iter.length; currentItemIndex++) { + item = iter[currentItemIndex]; stats.bytesIn = item.bytesIn; stats.tokensCount++; if (BadTokensTypes.includes(item.token.typ)) { @@ -28931,8 +28923,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 || @@ -28948,8 +28938,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; } @@ -29004,198 +28993,186 @@ function doParseSync(iter, options = {}) { ast = expand(ast); } let replacement; - let callable; if (options.visitor != null) { + valuesHandlers = new Map(); + preValuesHandlers = new Map(); + postValuesHandlers = new Map(); + preVisitorsHandlersMap = new Map(); + visitorsHandlersMap = new Map(); + postVisitorsHandlersMap = new Map(); + parseVisitors(options, valuesHandlers, preValuesHandlers, postValuesHandlers, errors, visitorsHandlersMap, preVisitorsHandlersMap, postVisitorsHandlersMap); let parens; - for (const result of walk(ast)) { + let genericKey; + const handlers = []; + 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); + } + let nodes = new Array(stats.tokensCount); + const subNodes = []; + let i; + let k; + let j; + let freeBlock = 1; + const includeTokens = preValuesHandlers.size > 0 || valuesHandlers.size > 0 || postValuesHandlers.size > 0; + nodes[0] = ast; + for (i = 0; i < nodes.length; i++) { + if (nodes[i] == null) { + break; + } + subNodes.length = 0; + if (includeTokens) { + switch (nodes[i].typ) { + case exports.EnumToken.RuleNodeType: + case exports.EnumToken.AtRuleNodeType: + case exports.EnumToken.KeyframesRuleNodeType: + case exports.EnumToken.KeyframesAtRuleNodeType: + subNodes.push(...nodes[i][TOKENS]); + break; + case exports.EnumToken.DeclarationNodeType: + subNodes.push(...nodes[i].val); + break; + } + } + if (nodes[i].chi != null) { + subNodes.push(...nodes[i].chi); + } + if (subNodes.length > 0) { + if (freeBlock <= i) { + freeBlock = i + 1; + } + for (k = 0; k < subNodes.length; k++) { + j = k + freeBlock; + nodes[j] = subNodes[k]; + nodes[j][PARENT] = nodes[i]; + } + freeBlock += subNodes.length; + } parens = null; - if (valuesHandlers.size > 0 || - preVisitorsHandlersMap.size > 0 || - visitorsHandlersMap.size > 0 || - postVisitorsHandlersMap.size > 0) { - if ((result.node.typ == exports.EnumToken.DeclarationNodeType && - (preVisitorsHandlersMap.has("Declaration") || - visitorsHandlersMap.has("Declaration") || - postVisitorsHandlersMap.has("Declaration"))) || - (result.node.typ == exports.EnumToken.AtRuleNodeType && - (preVisitorsHandlersMap.has("AtRule") || - visitorsHandlersMap.has("AtRule") || - postVisitorsHandlersMap.has("AtRule"))) || - (result.node.typ == exports.EnumToken.KeyframesAtRuleNodeType && - (preVisitorsHandlersMap.has("KeyframesAtRule") || - visitorsHandlersMap.has("KeyframesAtRule") || - postVisitorsHandlersMap.has("KeyframesAtRule")))) { - const handlers = []; - const key = result.node.typ == exports.EnumToken.DeclarationNodeType - ? "Declaration" - : result.node.typ == exports.EnumToken.AtRuleNodeType - ? "AtRule" - : "KeyframesAtRule"; - if (preVisitorsHandlersMap.has(key)) { - handlers.push( - // @ts-expect-error - ...preVisitorsHandlersMap.get(key)); - } - if (visitorsHandlersMap.has(key)) { - // @ts-ignore - handlers.push(...visitorsHandlersMap.get(key)); - } - if (postVisitorsHandlersMap.has(key)) { - // @ts-ignore - handlers.push(...postVisitorsHandlersMap.get(key)); - } - let node = result.node; - for (const handler of handlers) { - callable = - typeof handler == "function" - ? handler - : handler[camelize(node.typ === exports.EnumToken.DeclarationNodeType || - node.typ === exports.EnumToken.AtRuleNodeType - ? node.nam - : node.val)]; - if (callable == null) { - continue; + handlers.length = 0; + genericKey = + nodes[i].typ == exports.EnumToken.DeclarationNodeType + ? "Declaration" + : nodes[i].typ == exports.EnumToken.AtRuleNodeType + ? "AtRule" + : nodes[i].typ == exports.EnumToken.KeyframesAtRuleNodeType + ? "KeyframesAtRule" + : nodes[i].typ === exports.EnumToken.KeyframesRuleNodeType + ? "KeyframesRule" + : nodes[i].typ == exports.EnumToken.RuleNodeType + ? "Rule" + : nodes[i].typ == exports.EnumToken.KeyframesRuleNodeType + ? "KeyframesRule" + : null; + let keyName = nodes[i].typ == exports.EnumToken.DeclarationNodeType || nodes[i].typ == exports.EnumToken.AtRuleNodeType + ? camelize(nodes[i].nam) + : nodes[i].typ == exports.EnumToken.KeyframesAtRuleNodeType + ? camelize(nodes[i].val) + : null; + for (const map of allHandlers) { + // @ts-ignore + if (genericKey != null && map.has(genericKey)) { + // @ts-ignore + for (const handler of map.get(genericKey)) { + if (typeof handler == "function") { + handlers.push(handler); } - // @ts-expect-error - replacement = callable(node, result[PARENT], ast, function* () { - if (parens == null) { - // @ts-expect-error - parens = [...result.parents()]; + else if (Array.isArray(handler)) { + for (const h of handler) { + if (typeof h == "function") { + handlers.push(h); + } + // @ts-ignore + else if (h[keyName] != null) { + // @ts-ignore + handlers.push(h[keyName]); + } } - yield* parens[Symbol.iterator](); - }); - if (replacement == null) { - continue; } - if (replacement == node) { - continue; + else if (typeof handler.handler == "function") { + handlers.push(handler.handler); } // @ts-ignore - node = replacement; - if (Array.isArray(node)) { - break; + else if (typeof handler[keyName] == "function") { + // @ts-ignore + handlers.push(handler[keyName]); } } - if (node != result.node) { - replaceNodeOrValue(result.parent, result.node, node); - } } - else if ((result.node.typ == exports.EnumToken.RuleNodeType && - (preVisitorsHandlersMap.has("Rule") || - visitorsHandlersMap.has("Rule") || - postVisitorsHandlersMap.has("Rule"))) || - (result.node.typ == exports.EnumToken.KeyFramesRuleNodeType && - (preVisitorsHandlersMap.has("KeyframesRule") || - visitorsHandlersMap.has("KeyframesRule") || - postVisitorsHandlersMap.has("KeyframesRule")))) { - const handlers = []; - const key = result.node.typ == exports.EnumToken.RuleNodeType ? "Rule" : "KeyframesRule"; - if (preVisitorsHandlersMap.has(key)) { - handlers.push(...preVisitorsHandlersMap.get(key)); - } - if (visitorsHandlersMap.has(key)) { - handlers.push(...visitorsHandlersMap.get(key)); - } - if (postVisitorsHandlersMap.has(key)) { - handlers.push(...postVisitorsHandlersMap.get(key)); - } - let node = result.node; - for (const callable of handlers) { - replacement = callable(node, result.parent, result.root, - // @ts-expect-error - function* () { - if (parens == null) { - // @ts-expect-error - parens = [...result.parents()]; + // @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 == 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 != 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 != node) { - node = result; - } - if (Array.isArray(node)) { - break; - } - } - } - if (node != value) { - // @ts-ignore - replaceNodeOrValue(parent, value, node); + } + if (handlers.length == 0) { + continue; + } + let node = nodes[i]; + for (const callable of handlers) { + replacement = callable(node, nodes[i][PARENT], ast, + // @ts-expect-error + function* () { + if (parens == null) { + let node = nodes[i][PARENT]; + while (node != null) { + yield node; + node = node[PARENT]; } } + }); + if (replacement == null) { + continue; + } + if (replacement == node) { + continue; } + // @ts-ignore + node = replacement; + // + if (Array.isArray(node)) { + break; + } + } + if (node != nodes[i]) { + replaceNodeOrValue(nodes[i][PARENT], nodes[i], node); } } + nodes = null; } if (invalidNodes.length > 0) { let count = invalidNodes.length; @@ -29259,7 +29236,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(); @@ -29822,107 +29799,6 @@ 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` }); - } - } - } if (Array.isArray(iter)) { // @ts-expect-error iter = iter[Symbol.iterator](); @@ -30084,7 +29960,6 @@ async function doParse(iter, options = {}) { ast = expand(ast); } let replacement; - let callable; while (stack.length > 0 && context != ast) { const previousNode = stack.pop(); context = (stack[stack.length - 1] ?? ast); @@ -30100,208 +29975,188 @@ async function doParse(iter, options = {}) { break; } if (options.visitor != null) { + valuesHandlers = new Map(); + preValuesHandlers = new Map(); + postValuesHandlers = new Map(); + preVisitorsHandlersMap = new Map(); + visitorsHandlersMap = new Map(); + postVisitorsHandlersMap = new Map(); + parseVisitors(options, valuesHandlers, preValuesHandlers, postValuesHandlers, errors, visitorsHandlersMap, preVisitorsHandlersMap, postVisitorsHandlersMap); let parens; - for (const result of walk(ast)) { + let genericKey; + const handlers = []; + 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); + } + let nodes = new Array(stats.tokensCount); + const subNodes = []; + let i; + let k; + let j; + let freeblock = 1; + const includeTokens = preValuesHandlers.size > 0 || valuesHandlers.size > 0 || postValuesHandlers.size > 0; + nodes[0] = ast; + for (i = 0; i < nodes.length; i++) { + if (nodes[i] == null) { + break; + } + subNodes.length = 0; + if (includeTokens) { + switch (nodes[i].typ) { + case exports.EnumToken.RuleNodeType: + case exports.EnumToken.AtRuleNodeType: + case exports.EnumToken.KeyframesRuleNodeType: + case exports.EnumToken.KeyframesAtRuleNodeType: + subNodes.push(...nodes[i][TOKENS]); + break; + case exports.EnumToken.DeclarationNodeType: + subNodes.push(...nodes[i].val); + break; + } + } + if (nodes[i].chi != null) { + subNodes.push(...nodes[i].chi); + } + if (subNodes.length > 0) { + if (freeblock <= i) { + freeblock = i + 1; + } + for (k = 0; k < subNodes.length; k++) { + j = k + freeblock; + nodes[j] = subNodes[k]; + nodes[j][PARENT] = nodes[i]; + } + freeblock += subNodes.length; + } parens = null; - if (valuesHandlers.size > 0 || - preVisitorsHandlersMap.size > 0 || - visitorsHandlersMap.size > 0 || - postVisitorsHandlersMap.size > 0) { - if ((result.node.typ == exports.EnumToken.DeclarationNodeType && - (preVisitorsHandlersMap.has("Declaration") || - visitorsHandlersMap.has("Declaration") || - postVisitorsHandlersMap.has("Declaration"))) || - (result.node.typ == exports.EnumToken.AtRuleNodeType && - (preVisitorsHandlersMap.has("AtRule") || - visitorsHandlersMap.has("AtRule") || - postVisitorsHandlersMap.has("AtRule"))) || - (result.node.typ == exports.EnumToken.KeyframesAtRuleNodeType && - (preVisitorsHandlersMap.has("KeyframesAtRule") || - visitorsHandlersMap.has("KeyframesAtRule") || - postVisitorsHandlersMap.has("KeyframesAtRule")))) { - const handlers = []; - const key = result.node.typ == exports.EnumToken.DeclarationNodeType - ? "Declaration" - : result.node.typ == exports.EnumToken.AtRuleNodeType - ? "AtRule" - : "KeyframesAtRule"; - if (preVisitorsHandlersMap.has(key)) { - handlers.push( - // @ts-expect-error - ...preVisitorsHandlersMap.get(key)); - } - if (visitorsHandlersMap.has(key)) { - // @ts-ignore - handlers.push(...visitorsHandlersMap.get(key)); - } - if (postVisitorsHandlersMap.has(key)) { - // @ts-ignore - handlers.push(...postVisitorsHandlersMap.get(key)); - } - let node = result.node; - for (const handler of handlers) { - callable = - typeof handler == "function" - ? handler - : handler[camelize(node.typ === exports.EnumToken.DeclarationNodeType || - node.typ === exports.EnumToken.AtRuleNodeType - ? node.nam - : node.val)]; - if (callable == null) { - continue; + handlers.length = 0; + genericKey = + nodes[i].typ == exports.EnumToken.DeclarationNodeType + ? "Declaration" + : nodes[i].typ == exports.EnumToken.AtRuleNodeType + ? "AtRule" + : nodes[i].typ == exports.EnumToken.KeyframesAtRuleNodeType + ? "KeyframesAtRule" + : nodes[i].typ === exports.EnumToken.KeyframesRuleNodeType + ? "KeyframesRule" + : nodes[i].typ == exports.EnumToken.RuleNodeType + ? "Rule" + : nodes[i].typ == exports.EnumToken.KeyframesRuleNodeType + ? "KeyframesRule" + : null; + let keyName = nodes[i].typ == exports.EnumToken.DeclarationNodeType || nodes[i].typ == exports.EnumToken.AtRuleNodeType + ? camelize(nodes[i].nam) + : nodes[i].typ == exports.EnumToken.KeyframesAtRuleNodeType + ? camelize(nodes[i].val) + : null; + for (const map of allHandlers) { + // @ts-ignore + if (genericKey != null && map.has(genericKey)) { + // @ts-ignore + for (const handler of map.get(genericKey)) { + if (typeof handler == "function") { + handlers.push(handler); } - // @ts-expect-error - replacement = callable(node, result[PARENT], ast, function* () { - if (parens == null) { - // @ts-expect-error - parens = [...result.parents()]; + else if (Array.isArray(handler)) { + for (const h of handler) { + if (typeof h == "function") { + handlers.push(h); + } + // @ts-ignore + else if (h[keyName] != null) { + // @ts-ignore + handlers.push(h[keyName]); + } } - yield* parens[Symbol.iterator](); - }); - if (replacement == null) { - continue; - } - if (replacement instanceof Promise) { - replacement = await replacement; } - if (replacement == null || replacement == node) { - continue; + else if (typeof handler.handler == "function") { + handlers.push(handler.handler); } // @ts-ignore - node = replacement; - if (Array.isArray(node)) { - break; + else if (typeof handler[keyName] == "function") { + // @ts-ignore + handlers.push(handler[keyName]); } } - if (node != result.node) { - replaceNodeOrValue(result.parent, result.node, node); - } } - else if ((result.node.typ == exports.EnumToken.RuleNodeType && - (preVisitorsHandlersMap.has("Rule") || - visitorsHandlersMap.has("Rule") || - postVisitorsHandlersMap.has("Rule"))) || - (result.node.typ == exports.EnumToken.KeyFramesRuleNodeType && - (preVisitorsHandlersMap.has("KeyframesRule") || - visitorsHandlersMap.has("KeyframesRule") || - postVisitorsHandlersMap.has("KeyframesRule")))) { - const handlers = []; - const key = result.node.typ == exports.EnumToken.RuleNodeType ? "Rule" : "KeyframesRule"; - if (preVisitorsHandlersMap.has(key)) { - handlers.push(...preVisitorsHandlersMap.get(key)); - } - if (visitorsHandlersMap.has(key)) { - handlers.push(...visitorsHandlersMap.get(key)); - } - if (postVisitorsHandlersMap.has(key)) { - handlers.push(...postVisitorsHandlersMap.get(key)); - } - let node = result.node; - for (const callable of handlers) { - replacement = callable(node, result.parent, result.root, - // @ts-expect-error - function* () { - if (parens == null) { - // @ts-expect-error - parens = [...result.parents()]; - } - yield* parens[Symbol.iterator](); - }); - if (replacement == null) { - continue; + // @ts-ignore + if (map.has(nodes[i].typ)) { + // @ts-ignore + for (const handler of map.get(nodes[i].typ)) { + if (typeof handler == "function") { + handlers.push(handler); } - if (replacement instanceof Promise) { - replacement = await replacement; + else if (Array.isArray(handler)) { + for (const h of handler) { + if (typeof h == "function") { + handlers.push(h); + } + } } - if (replacement == null || replacement == node) { - continue; + else if (typeof handler.handler == "function") { + handlers.push(handler.handler); } // @ts-ignore - node = replacement; - // - if (Array.isArray(node)) { - break; + else if (typeof handler[keyName] == "function") { + // @ts-ignore + handlers.push(handler[keyName]); } } - // @ts-ignore - if (node != result.node) { - // @ts-ignore - replaceNodeOrValue(result.parent, result.node, node); - } } - else if (valuesHandlers.size > 0) { - let node = null; - node = result.node; - if (valuesHandlers.has(node.typ)) { - for (const valueHandler of valuesHandlers.get(node.typ)) { - callable = valueHandler; - replacement = callable(node, result.parent, ast, - // @ts-expect-error - function* () { - if (parens == null) { - // @ts-expect-error - parens = [...result.parents()]; - } - yield* parens[Symbol.iterator](); - }); - if (replacement == null) { - continue; - } - if (replacement instanceof Promise) { - replacement = await replacement; - } - if (replacement != null && replacement != node) { - node = replacement; - } - } - } - if (node != result.node) { - // @ts-ignore - replaceNodeOrValue(result[PARENT], value, node); - } - const tokens = Array.isArray(result.node[TOKENS]) ? result.node[TOKENS] : []; - if (Array.isArray(result.node.val)) { - tokens.push(...result.node.val); - } - if (tokens.length == 0) { - continue; - } - for (const { value, parent, root, parents } of walkValues(tokens, result.node)) { - node = value; - if (valuesHandlers.has(node.typ)) { - let parens = null; - for (const valueHandler of valuesHandlers.get(node.typ)) { - callable = valueHandler; - // @ts-expect-error - let result = callable(node, parent, root, function* () { - if (parens == null) { - // @ts-expect-error - parens = [...parents()]; - } - yield* parens[Symbol.iterator](); - }); - if (result == null) { - continue; - } - if (result instanceof Promise) { - result = await result; - } - if (result != null && result != node) { - node = result; - } - if (Array.isArray(node)) { - break; - } - } - } - if (node != value) { - // @ts-ignore - replaceNodeOrValue(parent, value, node); + } + if (handlers.length == 0) { + continue; + } + let node = nodes[i]; + for (const callable of handlers) { + replacement = callable(node, nodes[i][PARENT], ast, + // @ts-expect-error + function* () { + if (parens == null) { + let node = nodes[i][PARENT]; + while (node != null) { + yield node; + node = node[PARENT]; } } + }); + if (replacement == null) { + continue; + } + if (replacement instanceof Promise) { + replacement = await replacement; + } + if (replacement == null || replacement == node) { + continue; + } + // @ts-ignore + node = replacement; + // + if (Array.isArray(node)) { + break; } } + if (node != nodes[i]) { + replaceNodeOrValue(nodes[i][PARENT], nodes[i], node); + } } + nodes = null; } if (invalidNodes.length > 0) { let count = invalidNodes.length; diff --git a/dist/index.d.ts b/dist/index.d.ts index ae5c71fd..549223a7 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 */ @@ -2937,7 +2937,7 @@ export declare interface AstKeyframesRule extends BaseToken, AstNodeStatus { /** * token type */ - typ: EnumToken.KeyFramesRuleNodeType; + typ: EnumToken.KeyframesRuleNodeType; /** * selector */ @@ -2967,7 +2967,7 @@ export declare interface AstKeyframesRule extends BaseToken, AstNodeStatus { /** * token type */ - typ: EnumToken.KeyFramesRuleNodeType; + typ: EnumToken.KeyframesRuleNodeType; /** * selector */ diff --git a/dist/lib/ast/features/shorthand.js b/dist/lib/ast/features/shorthand.js index 4c3fcfee..2843a483 100644 --- a/dist/lib/ast/features/shorthand.js +++ b/dist/lib/ast/features/shorthand.js @@ -6,7 +6,7 @@ class ComputeShorthandFeature { accept = new Set([ EnumToken.RuleNodeType, EnumToken.AtRuleNodeType, - EnumToken.KeyFramesRuleNodeType, + EnumToken.KeyframesRuleNodeType, ]); get ordering() { return 10; diff --git a/dist/lib/ast/features/transform.js b/dist/lib/ast/features/transform.js index 63eed655..0009cfe3 100644 --- a/dist/lib/ast/features/transform.js +++ b/dist/lib/ast/features/transform.js @@ -6,7 +6,7 @@ import { FeatureWalkMode } from './type.js'; import { STATE } from '../../syntax/constants.js'; class TransformCssFeature { - accept = new Set([EnumToken.RuleNodeType, EnumToken.KeyFramesRuleNodeType]); + accept = new Set([EnumToken.RuleNodeType, EnumToken.KeyframesRuleNodeType]); get ordering() { return 3; } diff --git a/dist/lib/ast/minify.js b/dist/lib/ast/minify.js index 896e8ec6..11b2dac5 100644 --- a/dist/lib/ast/minify.js +++ b/dist/lib/ast/minify.js @@ -17,7 +17,7 @@ const rules = [ EnumToken.AtRuleNodeType, EnumToken.RuleNodeType, EnumToken.AtRuleTokenType, - EnumToken.KeyFramesRuleNodeType, + EnumToken.KeyframesRuleNodeType, ]; // @ts-ignore const features = Object.values(index).sort((a, b) => a.ordering - b.ordering); @@ -75,7 +75,7 @@ function minify(ast, options = {}, recursive = false, errors, nestingContent, co 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); } @@ -350,8 +350,8 @@ function doMinify(ast, options = {}, recursive = false, errors, nestingContent, continue; } } - else if (node.typ === EnumToken.KeyFramesRuleNodeType) { - if (previous?.typ === EnumToken.KeyFramesRuleNodeType && + else if (node.typ === EnumToken.KeyframesRuleNodeType) { + if (previous?.typ === EnumToken.KeyframesRuleNodeType && node.sel === previous.sel) { // do not merge keyframes // https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/At-rules/@keyframes#resolving_duplicates @@ -639,7 +639,7 @@ function doMinify(ast, options = {}, recursive = false, errors, nestingContent, } if (shouldMerge) { if (((node.typ === EnumToken.RuleNodeType || - node.typ === EnumToken.KeyFramesRuleNodeType) && + node.typ === EnumToken.KeyframesRuleNodeType) && node.sel === previous.sel) || (node.typ == EnumToken.AtRuleNodeType && node.nam !== "font-face" && @@ -652,7 +652,7 @@ function doMinify(ast, options = {}, recursive = false, errors, nestingContent, continue; } else if (node.typ == previous?.typ && - [EnumToken.KeyFramesRuleNodeType, EnumToken.RuleNodeType].includes(node.typ)) { + [EnumToken.KeyframesRuleNodeType, EnumToken.RuleNodeType].includes(node.typ)) { const intersect = diff(previous, node, options); if (intersect != null) { if (intersect.node1.chi.length == 0) { 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/parser/parse.js b/dist/lib/parser/parse.js index 64cc0afa..3dffa43c 100644 --- a/dist/lib/parser/parse.js +++ b/dist/lib/parser/parse.js @@ -4,7 +4,7 @@ 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 { LOC, tokensfuncDefMap, STATE, PARENT, TOKENS, ROOT, ERRORS, pageMarginBoxType } from '../syntax/constants.js'; import { hashAlgorithms, hash, syncHash } from './utils/hash.js'; @@ -291,6 +291,99 @@ const generateSyncScopedName = memoize((localName, filePath, pattern, hashLength // if leading char is digit, prefix underscore (very rare) return (/^[0-9]/.test(result) ? "_" : "") + result; }); +function parseVisitors(options, valuesHandlers, preValuesHandlers, postValuesHandlers, errors, visitorsHandlersMap, preVisitorsHandlersMap, postVisitorsHandlersMap) { + 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)) { + if (Array.isArray(value)) { + visitors.splice(i + 1, 0, ...Object.entries(value)); + continue; + } + if (typeof value == "function") { + key = value.name; + } + } + if (Array.isArray(value)) { + // @ts-ignore + visitors.splice(i + 1, 0, ...value.map((item) => [key, item])); + continue; + } + if (key in EnumToken) { + if (typeof value == "function") { + if (!valuesHandlers.has(EnumToken[key])) { + valuesHandlers.set(EnumToken[key], []); + } + valuesHandlers.get(EnumToken[key]).push(value); + } + else if (typeof value == "object" && "type" in value && "handler" in value && value.type in WalkerEvent) { + if (value.type == WalkerEvent.Enter) { + if (!preValuesHandlers.has(EnumToken[key])) { + preValuesHandlers.set(EnumToken[key], []); + } + preValuesHandlers.get(EnumToken[key]).push(value.handler); + } + else if (value.type == WalkerEvent.Leave) { + if (!postValuesHandlers.has(EnumToken[key])) { + postValuesHandlers.set(EnumToken[key], []); + } + postValuesHandlers.get(EnumToken[key]).push(value.handler); + } + } + else { + errors.push({ action: "ignore", message: `doParse: visitor.${key} is not a valid key name` }); + } + } + else if (["Declaration", "Rule", "AtRule", "KeyframesRule", "KeyframesAtRule"].includes(key)) { + if (typeof value == "function") { + if (!visitorsHandlersMap.has(key)) { + visitorsHandlersMap.set(key, []); + } + visitorsHandlersMap + .get(key) + .push(value); + } + else if (typeof value == "object") { + if ("type" in value && "handler" in value && value.type in WalkerEvent) { + if (value.type == WalkerEvent.Enter) { + if (!preVisitorsHandlersMap.has(key)) { + preVisitorsHandlersMap.set(key, []); + } + preVisitorsHandlersMap + .get(key) + .push(value.handler); + } + else if (value.type == WalkerEvent.Leave) { + if (!postVisitorsHandlersMap.has(key)) { + postVisitorsHandlersMap.set(key, []); + } + postVisitorsHandlersMap + .get(key) + .push(value.handler); + } + } + else { + if (!visitorsHandlersMap.has(key)) { + visitorsHandlersMap.set(key, []); + } + visitorsHandlersMap + .get(key) + .push(value); + } + } + else { + errors.push({ action: "ignore", message: `doParse: visitor.${key} is not a valid key name` }); + } + } + else { + errors.push({ action: "ignore", message: `doParse: visitor.${key} is not a valid key name` }); + } + } +} /** * Parse css string * @param iter @@ -373,114 +466,13 @@ function doParseSync(iter, options = {}) { // @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; + // if (Array.isArray(iter)) { + // // @ts-expect-error + // iter = iter[Symbol.iterator]() as Iterator; + // } + for (currentItemIndex = 0; currentItemIndex < iter.length; currentItemIndex++) { + item = iter[currentItemIndex]; stats.bytesIn = item.bytesIn; stats.tokensCount++; if (BadTokensTypes.includes(item.token.typ)) { @@ -508,8 +500,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 || @@ -525,8 +515,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; } @@ -581,198 +570,186 @@ function doParseSync(iter, options = {}) { ast = expand(ast); } let replacement; - let callable; if (options.visitor != null) { + valuesHandlers = new Map(); + preValuesHandlers = new Map(); + postValuesHandlers = new Map(); + preVisitorsHandlersMap = new Map(); + visitorsHandlersMap = new Map(); + postVisitorsHandlersMap = new Map(); + parseVisitors(options, valuesHandlers, preValuesHandlers, postValuesHandlers, errors, visitorsHandlersMap, preVisitorsHandlersMap, postVisitorsHandlersMap); let parens; - for (const result of walk(ast)) { + let genericKey; + const handlers = []; + 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); + } + let nodes = new Array(stats.tokensCount); + const subNodes = []; + let i; + let k; + let j; + let freeBlock = 1; + const includeTokens = preValuesHandlers.size > 0 || valuesHandlers.size > 0 || postValuesHandlers.size > 0; + nodes[0] = ast; + for (i = 0; i < nodes.length; i++) { + if (nodes[i] == null) { + break; + } + subNodes.length = 0; + if (includeTokens) { + switch (nodes[i].typ) { + case EnumToken.RuleNodeType: + case EnumToken.AtRuleNodeType: + case EnumToken.KeyframesRuleNodeType: + case EnumToken.KeyframesAtRuleNodeType: + subNodes.push(...nodes[i][TOKENS]); + break; + case EnumToken.DeclarationNodeType: + subNodes.push(...nodes[i].val); + break; + } + } + if (nodes[i].chi != null) { + subNodes.push(...nodes[i].chi); + } + if (subNodes.length > 0) { + if (freeBlock <= i) { + freeBlock = i + 1; + } + for (k = 0; k < subNodes.length; k++) { + j = k + freeBlock; + nodes[j] = subNodes[k]; + nodes[j][PARENT] = nodes[i]; + } + freeBlock += subNodes.length; + } parens = null; - if (valuesHandlers.size > 0 || - preVisitorsHandlersMap.size > 0 || - visitorsHandlersMap.size > 0 || - postVisitorsHandlersMap.size > 0) { - if ((result.node.typ == EnumToken.DeclarationNodeType && - (preVisitorsHandlersMap.has("Declaration") || - visitorsHandlersMap.has("Declaration") || - postVisitorsHandlersMap.has("Declaration"))) || - (result.node.typ == EnumToken.AtRuleNodeType && - (preVisitorsHandlersMap.has("AtRule") || - visitorsHandlersMap.has("AtRule") || - postVisitorsHandlersMap.has("AtRule"))) || - (result.node.typ == EnumToken.KeyframesAtRuleNodeType && - (preVisitorsHandlersMap.has("KeyframesAtRule") || - visitorsHandlersMap.has("KeyframesAtRule") || - postVisitorsHandlersMap.has("KeyframesAtRule")))) { - const handlers = []; - const key = result.node.typ == EnumToken.DeclarationNodeType - ? "Declaration" - : result.node.typ == EnumToken.AtRuleNodeType - ? "AtRule" - : "KeyframesAtRule"; - if (preVisitorsHandlersMap.has(key)) { - handlers.push( - // @ts-expect-error - ...preVisitorsHandlersMap.get(key)); - } - if (visitorsHandlersMap.has(key)) { - // @ts-ignore - handlers.push(...visitorsHandlersMap.get(key)); - } - if (postVisitorsHandlersMap.has(key)) { - // @ts-ignore - handlers.push(...postVisitorsHandlersMap.get(key)); - } - let node = result.node; - for (const handler of handlers) { - callable = - typeof handler == "function" - ? handler - : handler[camelize(node.typ === EnumToken.DeclarationNodeType || - node.typ === EnumToken.AtRuleNodeType - ? node.nam - : node.val)]; - if (callable == null) { - continue; - } - // @ts-expect-error - replacement = callable(node, result[PARENT], ast, function* () { - if (parens == null) { - // @ts-expect-error - parens = [...result.parents()]; + handlers.length = 0; + genericKey = + nodes[i].typ == EnumToken.DeclarationNodeType + ? "Declaration" + : nodes[i].typ == EnumToken.AtRuleNodeType + ? "AtRule" + : nodes[i].typ == EnumToken.KeyframesAtRuleNodeType + ? "KeyframesAtRule" + : nodes[i].typ === EnumToken.KeyframesRuleNodeType + ? "KeyframesRule" + : nodes[i].typ == EnumToken.RuleNodeType + ? "Rule" + : nodes[i].typ == EnumToken.KeyframesRuleNodeType + ? "KeyframesRule" + : null; + let keyName = nodes[i].typ == EnumToken.DeclarationNodeType || nodes[i].typ == EnumToken.AtRuleNodeType + ? camelize(nodes[i].nam) + : nodes[i].typ == EnumToken.KeyframesAtRuleNodeType + ? camelize(nodes[i].val) + : null; + for (const map of allHandlers) { + // @ts-ignore + if (genericKey != null && map.has(genericKey)) { + // @ts-ignore + for (const handler of map.get(genericKey)) { + if (typeof handler == "function") { + handlers.push(handler); + } + else if (Array.isArray(handler)) { + for (const h of handler) { + if (typeof h == "function") { + handlers.push(h); + } + // @ts-ignore + else if (h[keyName] != null) { + // @ts-ignore + handlers.push(h[keyName]); + } } - yield* parens[Symbol.iterator](); - }); - if (replacement == null) { - continue; } - if (replacement == node) { - continue; + else if (typeof handler.handler == "function") { + handlers.push(handler.handler); } // @ts-ignore - node = replacement; - if (Array.isArray(node)) { - break; + else if (typeof handler[keyName] == "function") { + // @ts-ignore + handlers.push(handler[keyName]); } } - if (node != result.node) { - replaceNodeOrValue(result.parent, result.node, node); - } - } - else if ((result.node.typ == EnumToken.RuleNodeType && - (preVisitorsHandlersMap.has("Rule") || - visitorsHandlersMap.has("Rule") || - postVisitorsHandlersMap.has("Rule"))) || - (result.node.typ == EnumToken.KeyFramesRuleNodeType && - (preVisitorsHandlersMap.has("KeyframesRule") || - visitorsHandlersMap.has("KeyframesRule") || - postVisitorsHandlersMap.has("KeyframesRule")))) { - const handlers = []; - const key = result.node.typ == EnumToken.RuleNodeType ? "Rule" : "KeyframesRule"; - if (preVisitorsHandlersMap.has(key)) { - handlers.push(...preVisitorsHandlersMap.get(key)); - } - if (visitorsHandlersMap.has(key)) { - handlers.push(...visitorsHandlersMap.get(key)); - } - if (postVisitorsHandlersMap.has(key)) { - handlers.push(...postVisitorsHandlersMap.get(key)); - } - let node = result.node; - for (const callable of handlers) { - replacement = callable(node, result.parent, result.root, - // @ts-expect-error - function* () { - if (parens == null) { - // @ts-expect-error - parens = [...result.parents()]; + } + // @ts-ignore + if (map.has(nodes[i].typ)) { + // @ts-ignore + for (const handler of map.get(nodes[i].typ)) { + if (typeof handler == "function") { + handlers.push(handler); + } + else if (Array.isArray(handler)) { + for (const h of handler) { + if (typeof h == "function") { + handlers.push(h); + } } - yield* parens[Symbol.iterator](); - }); - if (replacement == null) { - continue; } - if (replacement == 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 != 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 != node) { - node = result; - } - if (Array.isArray(node)) { - break; - } - } - } - if (node != value) { - // @ts-ignore - replaceNodeOrValue(parent, value, node); + } + if (handlers.length == 0) { + continue; + } + let node = nodes[i]; + for (const callable of handlers) { + replacement = callable(node, nodes[i][PARENT], ast, + // @ts-expect-error + function* () { + if (parens == null) { + let node = nodes[i][PARENT]; + while (node != null) { + yield node; + node = node[PARENT]; } } + }); + if (replacement == null) { + continue; } + if (replacement == node) { + continue; + } + // @ts-ignore + node = replacement; + // + if (Array.isArray(node)) { + break; + } + } + if (node != nodes[i]) { + replaceNodeOrValue(nodes[i][PARENT], nodes[i], node); } } + nodes = null; } if (invalidNodes.length > 0) { let count = invalidNodes.length; @@ -836,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(); @@ -1399,107 +1376,6 @@ 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` }); - } - } - } if (Array.isArray(iter)) { // @ts-expect-error iter = iter[Symbol.iterator](); @@ -1661,7 +1537,6 @@ async function doParse(iter, options = {}) { ast = expand(ast); } let replacement; - let callable; while (stack.length > 0 && context != ast) { const previousNode = stack.pop(); context = (stack[stack.length - 1] ?? ast); @@ -1677,208 +1552,188 @@ async function doParse(iter, options = {}) { break; } if (options.visitor != null) { + valuesHandlers = new Map(); + preValuesHandlers = new Map(); + postValuesHandlers = new Map(); + preVisitorsHandlersMap = new Map(); + visitorsHandlersMap = new Map(); + postVisitorsHandlersMap = new Map(); + parseVisitors(options, valuesHandlers, preValuesHandlers, postValuesHandlers, errors, visitorsHandlersMap, preVisitorsHandlersMap, postVisitorsHandlersMap); let parens; - for (const result of walk(ast)) { + let genericKey; + const handlers = []; + 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); + } + let nodes = new Array(stats.tokensCount); + const subNodes = []; + let i; + let k; + let j; + let freeblock = 1; + const includeTokens = preValuesHandlers.size > 0 || valuesHandlers.size > 0 || postValuesHandlers.size > 0; + nodes[0] = ast; + for (i = 0; i < nodes.length; i++) { + if (nodes[i] == null) { + break; + } + subNodes.length = 0; + if (includeTokens) { + switch (nodes[i].typ) { + case EnumToken.RuleNodeType: + case EnumToken.AtRuleNodeType: + case EnumToken.KeyframesRuleNodeType: + case EnumToken.KeyframesAtRuleNodeType: + subNodes.push(...nodes[i][TOKENS]); + break; + case EnumToken.DeclarationNodeType: + subNodes.push(...nodes[i].val); + break; + } + } + if (nodes[i].chi != null) { + subNodes.push(...nodes[i].chi); + } + if (subNodes.length > 0) { + if (freeblock <= i) { + freeblock = i + 1; + } + for (k = 0; k < subNodes.length; k++) { + j = k + freeblock; + nodes[j] = subNodes[k]; + nodes[j][PARENT] = nodes[i]; + } + freeblock += subNodes.length; + } parens = null; - if (valuesHandlers.size > 0 || - preVisitorsHandlersMap.size > 0 || - visitorsHandlersMap.size > 0 || - postVisitorsHandlersMap.size > 0) { - if ((result.node.typ == EnumToken.DeclarationNodeType && - (preVisitorsHandlersMap.has("Declaration") || - visitorsHandlersMap.has("Declaration") || - postVisitorsHandlersMap.has("Declaration"))) || - (result.node.typ == EnumToken.AtRuleNodeType && - (preVisitorsHandlersMap.has("AtRule") || - visitorsHandlersMap.has("AtRule") || - postVisitorsHandlersMap.has("AtRule"))) || - (result.node.typ == EnumToken.KeyframesAtRuleNodeType && - (preVisitorsHandlersMap.has("KeyframesAtRule") || - visitorsHandlersMap.has("KeyframesAtRule") || - postVisitorsHandlersMap.has("KeyframesAtRule")))) { - const handlers = []; - const key = result.node.typ == EnumToken.DeclarationNodeType - ? "Declaration" - : result.node.typ == EnumToken.AtRuleNodeType - ? "AtRule" - : "KeyframesAtRule"; - if (preVisitorsHandlersMap.has(key)) { - handlers.push( - // @ts-expect-error - ...preVisitorsHandlersMap.get(key)); - } - if (visitorsHandlersMap.has(key)) { - // @ts-ignore - handlers.push(...visitorsHandlersMap.get(key)); - } - if (postVisitorsHandlersMap.has(key)) { - // @ts-ignore - handlers.push(...postVisitorsHandlersMap.get(key)); - } - let node = result.node; - for (const handler of handlers) { - callable = - typeof handler == "function" - ? handler - : handler[camelize(node.typ === EnumToken.DeclarationNodeType || - node.typ === EnumToken.AtRuleNodeType - ? node.nam - : node.val)]; - if (callable == null) { - continue; - } - // @ts-expect-error - replacement = callable(node, result[PARENT], ast, function* () { - if (parens == null) { - // @ts-expect-error - parens = [...result.parents()]; + handlers.length = 0; + genericKey = + nodes[i].typ == EnumToken.DeclarationNodeType + ? "Declaration" + : nodes[i].typ == EnumToken.AtRuleNodeType + ? "AtRule" + : nodes[i].typ == EnumToken.KeyframesAtRuleNodeType + ? "KeyframesAtRule" + : nodes[i].typ === EnumToken.KeyframesRuleNodeType + ? "KeyframesRule" + : nodes[i].typ == EnumToken.RuleNodeType + ? "Rule" + : nodes[i].typ == EnumToken.KeyframesRuleNodeType + ? "KeyframesRule" + : null; + let keyName = nodes[i].typ == EnumToken.DeclarationNodeType || nodes[i].typ == EnumToken.AtRuleNodeType + ? camelize(nodes[i].nam) + : nodes[i].typ == EnumToken.KeyframesAtRuleNodeType + ? camelize(nodes[i].val) + : null; + for (const map of allHandlers) { + // @ts-ignore + if (genericKey != null && map.has(genericKey)) { + // @ts-ignore + for (const handler of map.get(genericKey)) { + if (typeof handler == "function") { + handlers.push(handler); + } + else if (Array.isArray(handler)) { + for (const h of handler) { + if (typeof h == "function") { + handlers.push(h); + } + // @ts-ignore + else if (h[keyName] != null) { + // @ts-ignore + handlers.push(h[keyName]); + } } - yield* parens[Symbol.iterator](); - }); - if (replacement == null) { - continue; - } - if (replacement instanceof Promise) { - replacement = await replacement; } - if (replacement == null || replacement == node) { - continue; + else if (typeof handler.handler == "function") { + handlers.push(handler.handler); } // @ts-ignore - node = replacement; - if (Array.isArray(node)) { - break; + else if (typeof handler[keyName] == "function") { + // @ts-ignore + handlers.push(handler[keyName]); } } - if (node != result.node) { - replaceNodeOrValue(result.parent, result.node, node); - } - } - else if ((result.node.typ == EnumToken.RuleNodeType && - (preVisitorsHandlersMap.has("Rule") || - visitorsHandlersMap.has("Rule") || - postVisitorsHandlersMap.has("Rule"))) || - (result.node.typ == EnumToken.KeyFramesRuleNodeType && - (preVisitorsHandlersMap.has("KeyframesRule") || - visitorsHandlersMap.has("KeyframesRule") || - postVisitorsHandlersMap.has("KeyframesRule")))) { - const handlers = []; - const key = result.node.typ == EnumToken.RuleNodeType ? "Rule" : "KeyframesRule"; - if (preVisitorsHandlersMap.has(key)) { - handlers.push(...preVisitorsHandlersMap.get(key)); - } - if (visitorsHandlersMap.has(key)) { - handlers.push(...visitorsHandlersMap.get(key)); - } - if (postVisitorsHandlersMap.has(key)) { - handlers.push(...postVisitorsHandlersMap.get(key)); - } - let node = result.node; - for (const callable of handlers) { - replacement = callable(node, result.parent, result.root, - // @ts-expect-error - function* () { - if (parens == null) { - // @ts-expect-error - parens = [...result.parents()]; + } + // @ts-ignore + if (map.has(nodes[i].typ)) { + // @ts-ignore + for (const handler of map.get(nodes[i].typ)) { + if (typeof handler == "function") { + handlers.push(handler); + } + else if (Array.isArray(handler)) { + for (const h of handler) { + if (typeof h == "function") { + handlers.push(h); + } } - yield* parens[Symbol.iterator](); - }); - if (replacement == null) { - continue; - } - if (replacement instanceof Promise) { - replacement = await replacement; } - if (replacement == null || replacement == node) { - continue; + else if (typeof handler.handler == "function") { + handlers.push(handler.handler); } // @ts-ignore - node = replacement; - // - if (Array.isArray(node)) { - break; + else if (typeof handler[keyName] == "function") { + // @ts-ignore + handlers.push(handler[keyName]); } } - // @ts-ignore - if (node != result.node) { - // @ts-ignore - replaceNodeOrValue(result.parent, result.node, node); - } } - else if (valuesHandlers.size > 0) { - let node = null; - node = result.node; - if (valuesHandlers.has(node.typ)) { - for (const valueHandler of valuesHandlers.get(node.typ)) { - callable = valueHandler; - replacement = callable(node, result.parent, ast, - // @ts-expect-error - function* () { - if (parens == null) { - // @ts-expect-error - parens = [...result.parents()]; - } - yield* parens[Symbol.iterator](); - }); - if (replacement == null) { - continue; - } - if (replacement instanceof Promise) { - replacement = await replacement; - } - if (replacement != null && replacement != node) { - node = replacement; - } - } - } - if (node != result.node) { - // @ts-ignore - replaceNodeOrValue(result[PARENT], value, node); - } - const tokens = Array.isArray(result.node[TOKENS]) ? result.node[TOKENS] : []; - if (Array.isArray(result.node.val)) { - tokens.push(...result.node.val); - } - if (tokens.length == 0) { - continue; - } - for (const { value, parent, root, parents } of walkValues(tokens, result.node)) { - node = value; - if (valuesHandlers.has(node.typ)) { - let parens = null; - for (const valueHandler of valuesHandlers.get(node.typ)) { - callable = valueHandler; - // @ts-expect-error - let result = callable(node, parent, root, function* () { - if (parens == null) { - // @ts-expect-error - parens = [...parents()]; - } - yield* parens[Symbol.iterator](); - }); - if (result == null) { - continue; - } - if (result instanceof Promise) { - result = await result; - } - if (result != null && result != node) { - node = result; - } - if (Array.isArray(node)) { - break; - } - } - } - if (node != value) { - // @ts-ignore - replaceNodeOrValue(parent, value, node); + } + if (handlers.length == 0) { + continue; + } + let node = nodes[i]; + for (const callable of handlers) { + replacement = callable(node, nodes[i][PARENT], ast, + // @ts-expect-error + function* () { + if (parens == null) { + let node = nodes[i][PARENT]; + while (node != null) { + yield node; + node = node[PARENT]; } } + }); + if (replacement == null) { + continue; + } + if (replacement instanceof Promise) { + replacement = await replacement; + } + if (replacement == null || replacement == node) { + continue; } + // @ts-ignore + node = replacement; + // + if (Array.isArray(node)) { + break; + } + } + if (node != nodes[i]) { + replaceNodeOrValue(nodes[i][PARENT], nodes[i], node); } } + nodes = null; } if (invalidNodes.length > 0) { let count = invalidNodes.length; diff --git a/dist/lib/parser/utils/selector.js b/dist/lib/parser/utils/selector.js index 090bf0c1..04b911a1 100644 --- a/dist/lib/parser/utils/selector.js +++ b/dist/lib/parser/utils/selector.js @@ -48,7 +48,7 @@ function parseSelector(tokens, context, options, errors) { return acc; }, [])); return { - typ: EnumToken.KeyFramesRuleNodeType, + typ: EnumToken.KeyframesRuleNodeType, sel: [ ...splitTokenList(trimArray(tokens)).reduce((acc, curr) => { acc.add(curr.reduce((acc, curr) => acc + renderValue(curr, { minify: false }), "")); diff --git a/dist/lib/renderer/render.js b/dist/lib/renderer/render.js index faeb33b9..6c90b117 100644 --- a/dist/lib/renderer/render.js +++ b/dist/lib/renderer/render.js @@ -149,7 +149,7 @@ function updateSourceMap(node, options, cache, sourcemaps, sourceLocation, lines [ EnumToken.RuleNodeType, EnumToken.AtRuleNodeType, - EnumToken.KeyFramesRuleNodeType, + EnumToken.KeyframesRuleNodeType, EnumToken.KeyframesAtRuleNodeType, ].includes(node.typ)) { const source = options.sourcesMap.get(node[LOC].srcId); @@ -287,7 +287,7 @@ function renderAstNode(data, options, sourcemaps, sourceLocation, linesMap, erro 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};`; diff --git a/src/@types/ast.d.ts b/src/@types/ast.d.ts index 1f5efeb3..e018ba9d 100644 --- a/src/@types/ast.d.ts +++ b/src/@types/ast.d.ts @@ -224,7 +224,7 @@ 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 */ diff --git a/src/lib/ast/features/shorthand.ts b/src/lib/ast/features/shorthand.ts index 20df72b3..daf97e2c 100644 --- a/src/lib/ast/features/shorthand.ts +++ b/src/lib/ast/features/shorthand.ts @@ -14,7 +14,7 @@ export class ComputeShorthandFeature { public accept: Set = new Set([ EnumToken.RuleNodeType, EnumToken.AtRuleNodeType, - EnumToken.KeyFramesRuleNodeType, + EnumToken.KeyframesRuleNodeType, ]); get ordering() { diff --git a/src/lib/ast/features/transform.ts b/src/lib/ast/features/transform.ts index 5cb304ed..1c525520 100644 --- a/src/lib/ast/features/transform.ts +++ b/src/lib/ast/features/transform.ts @@ -15,7 +15,7 @@ import { FeatureWalkMode } from "./type.ts"; import { STATE } from "../../syntax/constants.ts"; export class TransformCssFeature { - public accept: Set = new Set([EnumToken.RuleNodeType, EnumToken.KeyFramesRuleNodeType]); + public accept: Set = new Set([EnumToken.RuleNodeType, EnumToken.KeyframesRuleNodeType]); get ordering(): number { return 3; diff --git a/src/lib/ast/find.ts b/src/lib/ast/find.ts index c9933a11..d283ced2 100644 --- a/src/lib/ast/find.ts +++ b/src/lib/ast/find.ts @@ -239,7 +239,7 @@ export function findValue( (ast.typ === EnumToken.StyleSheetNodeType || ast.typ === EnumToken.RuleNodeType || ast.typ === EnumToken.AtRuleNodeType || - ast.typ === EnumToken.KeyFramesRuleNodeType || + ast.typ === EnumToken.KeyframesRuleNodeType || ast.typ === EnumToken.KeyframesAtRuleNodeType) ) { if (Array.isArray(ast[TOKENS])) { diff --git a/src/lib/ast/minify.ts b/src/lib/ast/minify.ts index b5f85ae7..00b4acd3 100644 --- a/src/lib/ast/minify.ts +++ b/src/lib/ast/minify.ts @@ -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( @@ -141,7 +141,7 @@ export function minify( if (rules.includes(replacement.typ) && !Array.isArray(replacement[TOKENS])) { replacement[TOKENS] = parseString( - replacement.typ == EnumToken.RuleNodeType || replacement.typ === EnumToken.KeyFramesRuleNodeType + replacement.typ == EnumToken.RuleNodeType || replacement.typ === EnumToken.KeyframesRuleNodeType ? replacement.sel : replacement.nam, ); @@ -520,9 +520,9 @@ function doMinify( continue; } - } else if (node.typ === EnumToken.KeyFramesRuleNodeType) { + } else if (node.typ === EnumToken.KeyframesRuleNodeType) { if ( - previous?.typ === EnumToken.KeyFramesRuleNodeType && + previous?.typ === EnumToken.KeyframesRuleNodeType && (node).sel === (previous).sel ) { // do not merge keyframes @@ -905,7 +905,7 @@ function doMinify( if (shouldMerge) { if ( ((node.typ === EnumToken.RuleNodeType || - node.typ === EnumToken.KeyFramesRuleNodeType) && + node.typ === EnumToken.KeyframesRuleNodeType) && (node as AstRule).sel === (previous as AstRule).sel) || (node.typ == EnumToken.AtRuleNodeType && (node as AstAtRule).nam !== "font-face" && @@ -923,7 +923,7 @@ function doMinify( continue; } else if ( node.typ == previous?.typ && - [EnumToken.KeyFramesRuleNodeType, EnumToken.RuleNodeType].includes(node.typ) + [EnumToken.KeyframesRuleNodeType, EnumToken.RuleNodeType].includes(node.typ) ) { const intersect = diff(previous as AstRule, node as AstRule, options); diff --git a/src/lib/ast/types.ts b/src/lib/ast/types.ts index 200c50a9..8e9d4c6c 100644 --- a/src/lib/ast/types.ts +++ b/src/lib/ast/types.ts @@ -386,7 +386,7 @@ export enum EnumToken { /** * keyframe rule node type */ - KeyFramesRuleNodeType, + KeyframesRuleNodeType, /** * class selector token type */ diff --git a/src/lib/parser/parse.ts b/src/lib/parser/parse.ts index 719ad5fb..fb993136 100644 --- a/src/lib/parser/parse.ts +++ b/src/lib/parser/parse.ts @@ -412,6 +412,149 @@ export const generateSyncScopedName = memoize( }, ) as (localName: string, filePath: string, pattern: string, hashLength?: number) => string; +function parseVisitors( + options: ParserSyncOptions | ParserOptions, + valuesHandlers: Map>>, + preValuesHandlers: Map>>, + postValuesHandlers: Map>>, + errors: ErrorDescription[], + visitorsHandlersMap: Map< + "Declaration" | "Rule" | "AtRule" | "KeyframesRule" | "KeyframesAtRule", + Array | Record>> + >, + preVisitorsHandlersMap: Map< + "Declaration" | "Rule" | "AtRule" | "KeyframesRule" | "KeyframesAtRule", + Array | Record>>> + >, + postVisitorsHandlersMap: Map< + "Declaration" | "Rule" | "AtRule" | "KeyframesRule" | "KeyframesAtRule", + Array | Record>>> + >, +) { + 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)) { + if (Array.isArray(value)) { + visitors.splice(i + 1, 0, ...Object.entries(value)); + continue; + } + + if (typeof value == "function") { + key = value.name; + } + } + + if (Array.isArray(value)) { + // @ts-ignore + visitors.splice(i + 1, 0, ...value.map((item) => [key, item])); + continue; + } + + if (key in EnumToken) { + if (typeof value == "function") { + if (!valuesHandlers.has(EnumToken[key as keyof typeof EnumToken] as EnumToken)) { + valuesHandlers.set(EnumToken[key as keyof typeof EnumToken] as EnumToken, []); + } + + valuesHandlers.get(EnumToken[key as keyof typeof EnumToken] as EnumToken)!.push(value); + } else if (typeof value == "object" && "type" in value && "handler" in value && value.type in WalkerEvent) { + if (value.type == WalkerEvent.Enter) { + if (!preValuesHandlers.has(EnumToken[key as keyof typeof EnumToken] as EnumToken)) { + preValuesHandlers.set(EnumToken[key as keyof typeof EnumToken] as EnumToken, []); + } + + preValuesHandlers.get(EnumToken[key as keyof typeof EnumToken] as EnumToken)!.push(value.handler); + } else if (value.type == WalkerEvent.Leave) { + if (!postValuesHandlers.has(EnumToken[key as keyof typeof EnumToken] as EnumToken)) { + postValuesHandlers.set(EnumToken[key as keyof typeof EnumToken] as EnumToken, []); + } + + postValuesHandlers.get(EnumToken[key as keyof typeof EnumToken] as EnumToken)!.push(value.handler); + } + } else { + errors.push({ action: "ignore", message: `doParse: visitor.${key} is not a valid key name` }); + } + } else if (["Declaration", "Rule", "AtRule", "KeyframesRule", "KeyframesAtRule"].includes(key)) { + if (typeof value == "function") { + if ( + !visitorsHandlersMap.has( + key as "Declaration" | "Rule" | "AtRule" | "KeyframesRule" | "KeyframesAtRule", + ) + ) { + visitorsHandlersMap.set( + key as "Declaration" | "Rule" | "AtRule" | "KeyframesRule" | "KeyframesAtRule", + [], + ); + } + + visitorsHandlersMap + .get(key as "Declaration" | "Rule" | "AtRule" | "KeyframesRule" | "KeyframesAtRule")! + .push(value); + } else if (typeof value == "object") { + if ("type" in value && "handler" in value && value.type in WalkerEvent) { + if (value.type == WalkerEvent.Enter) { + if ( + !preVisitorsHandlersMap.has( + key as "Declaration" | "Rule" | "AtRule" | "KeyframesRule" | "KeyframesAtRule", + ) + ) { + preVisitorsHandlersMap.set( + key as "Declaration" | "Rule" | "AtRule" | "KeyframesRule" | "KeyframesAtRule", + [], + ); + } + + preVisitorsHandlersMap + .get(key as "Declaration" | "Rule" | "AtRule" | "KeyframesRule" | "KeyframesAtRule")! + .push(value.handler); + } else if (value.type == WalkerEvent.Leave) { + if ( + !postVisitorsHandlersMap.has( + key as "Declaration" | "Rule" | "AtRule" | "KeyframesRule" | "KeyframesAtRule", + ) + ) { + postVisitorsHandlersMap.set( + key as "Declaration" | "Rule" | "AtRule" | "KeyframesRule" | "KeyframesAtRule", + [], + ); + } + + postVisitorsHandlersMap + .get(key as "Declaration" | "Rule" | "AtRule" | "KeyframesRule" | "KeyframesAtRule")! + .push(value.handler); + } + } else { + if ( + !visitorsHandlersMap.has( + key as "Declaration" | "Rule" | "AtRule" | "KeyframesRule" | "KeyframesAtRule", + ) + ) { + visitorsHandlersMap.set( + key as "Declaration" | "Rule" | "AtRule" | "KeyframesRule" | "KeyframesAtRule", + [], + ); + } + + visitorsHandlersMap + .get(key as "Declaration" | "Rule" | "AtRule" | "KeyframesRule" | "KeyframesAtRule")! + .push(value); + } + } else { + errors.push({ action: "ignore", message: `doParse: visitor.${key} is not a valid key name` }); + } + } else { + errors.push({ action: "ignore", message: `doParse: visitor.${key} is not a valid key name` }); + } + } +} + /** * Parse css string * @param iter @@ -520,151 +663,16 @@ export function doParseSync( 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", - [], - ); - } - - 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` }); - } - } - } + let currentItemIndex: number; - if (Array.isArray(iter)) { - // @ts-expect-error - iter = iter[Symbol.iterator]() as Iterator; - } + // if (Array.isArray(iter)) { + // // @ts-expect-error + // iter = iter[Symbol.iterator]() as Iterator; + // } - while ( - // @ts-expect-error - (item = (iter as Iterator).next().value as TokenizeResult) + for (currentItemIndex = 0; currentItemIndex < (iter as Array).length; currentItemIndex++ ) { + item = (iter as Array)[currentItemIndex]; stats.bytesIn = item.bytesIn; stats.tokensCount++; @@ -696,9 +704,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 || @@ -717,8 +722,7 @@ export function doParseSync( tokens = [item.token]; do { - // @ts-expect-error - item = (iter as Iterator).next().value as TokenizeResult; + item = (iter as Array)[++currentItemIndex]; if (item == null) { break; @@ -787,278 +791,248 @@ export function doParseSync( } let replacement: GenericVisitorResult; - let callable: GenericVisitorHandler; if (options.visitor != null) { - let parens: Token[] | null; - for (const result of walk(ast)) { - parens = null; + valuesHandlers = new Map() as Map>>; + preValuesHandlers = new Map() as Map>>; + postValuesHandlers = new Map() as Map>>; - 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>), - ); - } + preVisitorsHandlersMap = new Map(); + visitorsHandlersMap = new Map(); + postVisitorsHandlersMap = new Map(); + parseVisitors( + options, + valuesHandlers, + preValuesHandlers, + postValuesHandlers, + errors, + visitorsHandlersMap, + preVisitorsHandlersMap, + postVisitorsHandlersMap, + ); - if (visitorsHandlersMap!.has(key)) { - // @ts-ignore - handlers.push(...visitorsHandlersMap.get(key)!); - } + let parens: Token[] | null; - if (postVisitorsHandlersMap!.has(key)) { - // @ts-ignore - handlers.push(...postVisitorsHandlersMap.get(key)); - } + let genericKey: string | null; + const handlers = [] as Array>; + 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!); + } - 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; - } + if (preValuesHandlers!.size > 0) { + allHandlers.push(preValuesHandlers!); + } - // @ts-expect-error - replacement = callable(node, result[PARENT], ast, function* () { - if (parens == null) { - // @ts-expect-error - parens = [...result.parents()]; - } + if (visitorsHandlersMap!.size > 0) { + allHandlers.push(visitorsHandlersMap!); + } - yield* parens[Symbol.iterator](); - }); + if (valuesHandlers!.size > 0) { + allHandlers.push(valuesHandlers!); + } - if (replacement == null) { - continue; - } + if (postVisitorsHandlersMap!.size > 0) { + allHandlers.push(postVisitorsHandlersMap!); + } - if (replacement == node) { - continue; - } + if (postValuesHandlers!.size > 0) { + allHandlers.push(postValuesHandlers!); + } - // @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; + const includeTokens: boolean = + preValuesHandlers!.size > 0 || valuesHandlers!.size > 0 || postValuesHandlers!.size > 0; - if (Array.isArray(node)) { - break; - } - } + nodes[0] = ast; - if (node != result.node) { - replaceNodeOrValue( - result.parent as - | AstRule - | AstAtRule - | AstKeyframesAtRule - | AstKeyframesRule - | 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>)); - } + for (i = 0; i < nodes.length; i++) { + if (nodes[i] == null) { + break; + } - if (visitorsHandlersMap!.has(key)) { - handlers.push(...(visitorsHandlersMap!.get(key)! as Array>)); - } + subNodes.length = 0; + if (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; + } + } - if (postVisitorsHandlersMap!.has(key)) { - handlers.push(...(postVisitorsHandlersMap!.get(key)! as Array>)); - } + if (nodes[i].chi != null) { + subNodes.push(...nodes[i].chi); + } - let node = result.node; + if (subNodes.length > 0) { + if (freeBlock <= i) { + freeBlock = i + 1; + } - for (const callable of handlers) { - replacement = (callable as GenericVisitorHandler)( - node as T, - result.parent, - result.root, - // @ts-expect-error - function* () { - if (parens == null) { - // @ts-expect-error - parens = [...result.parents()]; - } + for (k = 0; k < subNodes.length; k++) { + j = k + freeBlock; + nodes[j] = subNodes[k] as AstNode; + nodes[j][PARENT] = nodes[i]; + } - yield* parens[Symbol.iterator](); - }, - ) as GenericVisitorResult; + freeBlock += subNodes.length; + } - if (replacement == null) { - continue; - } + parens = null; + handlers.length = 0; + + genericKey = + nodes[i].typ == EnumToken.DeclarationNodeType + ? "Declaration" + : nodes[i].typ == EnumToken.AtRuleNodeType + ? "AtRule" + : nodes[i].typ == EnumToken.KeyframesAtRuleNodeType + ? "KeyframesAtRule" + : nodes[i].typ === EnumToken.KeyframesRuleNodeType + ? "KeyframesRule" + : nodes[i].typ == EnumToken.RuleNodeType + ? "Rule" + : nodes[i].typ == EnumToken.KeyframesRuleNodeType + ? "KeyframesRule" + : null; + let keyName: string | null = + nodes[i].typ == EnumToken.DeclarationNodeType || nodes[i].typ == EnumToken.AtRuleNodeType + ? camelize((nodes[i] as AstDeclaration | AstAtRule).nam) + : nodes[i].typ == EnumToken.KeyframesAtRuleNodeType + ? camelize((nodes[i] as AstKeyframesAtRule).val) + : null; + + for (const map of allHandlers) { + // @ts-ignore + if (genericKey != null && map!.has(genericKey)) { + // @ts-ignore + for (const handler of map!.get(genericKey)!) { + if (typeof handler == "function") { + handlers.push(handler as GenericVisitorHandler); + } else if (Array.isArray(handler)) { + for (const h of handler) { + if (typeof h == "function") { + handlers.push(h); + } - if (replacement == node) { - continue; + // @ts-ignore + else if (h[keyName] != null) { + // @ts-ignore + handlers.push(h[keyName]); + } + } + } else if (typeof handler.handler! == "function") { + handlers.push(handler.handler); } // @ts-ignore - node = replacement as AstNode; - - // - if (Array.isArray(node)) { - break; + else if (typeof handler[keyName]! == "function") { + // @ts-ignore + handlers.push(handler[keyName]); } } + } + // @ts-ignore + if (map!.has(nodes[i].typ)) { // @ts-ignore - if (node != result.node) { - // @ts-ignore - replaceNodeOrValue(result.parent, result.node, node); - } - } else if (valuesHandlers!.size > 0) { - let node: Token | AstNode | null = null; - - node = result.node; - - if (valuesHandlers!.has(node.typ)) { - for (const valueHandler of valuesHandlers!.get(node.typ)!) { - callable = valueHandler as GenericVisitorHandler; - replacement = callable( - node as T, - result.parent, - ast, - // @ts-expect-error - function* () { - if (parens == null) { - // @ts-expect-error - parens = [...result.parents()]; - } - - yield* parens[Symbol.iterator](); - }, - ); - - if (replacement == null) { - continue; - } - - if (replacement != node) { - node = replacement as AstNode; + for (const handler of map!.get(nodes[i].typ)!) { + if (typeof handler == "function") { + handlers.push(handler as GenericVisitorHandler); + } else if (Array.isArray(handler)) { + for (const h of handler) { + if (typeof h == "function") { + handlers.push(h); + } } + } else if (typeof handler.handler! == "function") { + handlers.push(handler.handler); } - } - if (node != result.node) { // @ts-ignore - replaceNodeOrValue(result[PARENT], value, node); - } - - const tokens: Token[] = Array.isArray(result.node[TOKENS]) ? (result.node[TOKENS] as Token[]) : []; - - if (Array.isArray(result.node.val)) { - tokens.push(...(result.node.val as Token[])); + else if (typeof handler[keyName]! == "function") { + // @ts-ignore + handlers.push(handler[keyName]); + } } + } + } - if (tokens.length == 0) { - continue; - } + if (handlers.length == 0) { + continue; + } - for (const { value, parent, root, parents } of walkValues(tokens, result.node)) { - node = value; + let node = nodes[i]; - if (valuesHandlers!.has(node!.typ)) { - let parens: Token[] | null = null; - for (const valueHandler of valuesHandlers!.get(node!.typ)!) { - callable = valueHandler as GenericVisitorHandler; - // @ts-expect-error - let result: GenericVisitorResult = callable(node as T, parent, root, function* () { - if (parens == null) { - // @ts-expect-error - parens = [...parents()]; - } + for (const callable of handlers) { + replacement = (callable as GenericVisitorHandler)( + node as T, + nodes[i][PARENT] as AstNode, + ast as AstStyleSheet, + // @ts-expect-error + function* () { + if (parens == null) { + let node = nodes![i][PARENT] as AstNode; - yield* parens[Symbol.iterator](); - }); + while (node != null) { + yield node; + node = node[PARENT] as AstNode; + } + } + }, + ) as GenericVisitorResult; - if (result == null) { - continue; - } + if (replacement == null) { + continue; + } - if (result != node) { - node = result as Token; - } + if (replacement == node) { + continue; + } - if (Array.isArray(node)) { - break; - } - } - } + // @ts-ignore + node = replacement as AstNode; - if (node != value) { - // @ts-ignore - replaceNodeOrValue(parent, value, node); - } - } + // + if (Array.isArray(node)) { + break; } } + + if (node != nodes[i]) { + replaceNodeOrValue(nodes[i][PARENT], nodes[i], node); + } } + + nodes = null; } if (invalidNodes.length > 0) { @@ -1138,7 +1112,7 @@ export function doParseSync( scoped: ModuleScopeEnumOptions.Local, naming: ModuleCaseTransformEnum.IgnoreCase, pattern: "", - generateScopedName, + generateScopedName: generateSyncScopedName, ...(typeof options.module != "object" ? {} : options.module), } as ModuleSyncOptions; @@ -1876,142 +1850,6 @@ export async function doParse( 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", - [], - ); - } - - 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; @@ -2212,7 +2050,6 @@ export async function doParse( } let replacement: GenericVisitorResult; - let callable: GenericVisitorHandler; while (stack.length > 0 && context != ast) { const previousNode: AstAtRule | AstRule = stack.pop() as AstAtRule | AstRule; @@ -2235,291 +2072,251 @@ export async function doParse( } 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>), - ); - } + valuesHandlers = new Map() as Map>>; + preValuesHandlers = new Map() as Map>>; + postValuesHandlers = new Map() as Map>>; - if (visitorsHandlersMap!.has(key)) { - // @ts-ignore - handlers.push(...visitorsHandlersMap.get(key)!); - } + preVisitorsHandlersMap = new Map(); + visitorsHandlersMap = new Map(); + postVisitorsHandlersMap = new Map(); - if (postVisitorsHandlersMap!.has(key)) { - // @ts-ignore - handlers.push(...postVisitorsHandlersMap.get(key)); - } + parseVisitors( + options as ParserSyncOptions, + valuesHandlers, + preValuesHandlers, + postValuesHandlers, + errors, + visitorsHandlersMap, + preVisitorsHandlersMap, + postVisitorsHandlersMap, + ); - 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; - } + let parens: Token[] | null; - // @ts-expect-error - replacement = callable(node, result[PARENT], ast, function* () { - if (parens == null) { - // @ts-expect-error - parens = [...result.parents()]; - } + let genericKey: string | null; + const handlers = [] as Array>; + 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!); + } - yield* parens[Symbol.iterator](); - }); + if (preValuesHandlers!.size > 0) { + allHandlers.push(preValuesHandlers!); + } - if (replacement == null) { - continue; - } + if (visitorsHandlersMap!.size > 0) { + allHandlers.push(visitorsHandlersMap!); + } - if (replacement instanceof Promise) { - replacement = await replacement; - } + if (valuesHandlers!.size > 0) { + allHandlers.push(valuesHandlers!); + } - if (replacement == null || replacement == node) { - continue; - } + if (postVisitorsHandlersMap!.size > 0) { + allHandlers.push(postVisitorsHandlersMap!); + } - // @ts-ignore - node = replacement; + if (postValuesHandlers!.size > 0) { + allHandlers.push(postValuesHandlers!); + } - if (Array.isArray(node)) { - break; - } - } + let nodes: AstNode[] | null = new Array(stats.tokensCount); + const subNodes: Array = []; + let i: number; + let k: number; + let j: number; + let freeblock: number = 1; + const includeTokens: boolean = + preValuesHandlers!.size > 0 || valuesHandlers!.size > 0 || postValuesHandlers!.size > 0; - if (node != result.node) { - replaceNodeOrValue( - result.parent as - | AstRule - | AstAtRule - | AstKeyframesAtRule - | AstKeyframesRule - | 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>)); - } + nodes[0] = ast; - if (visitorsHandlersMap!.has(key)) { - handlers.push(...(visitorsHandlersMap!.get(key)! as Array>)); - } + for (i = 0; i < nodes.length; i++) { + if (nodes[i] == null) { + break; + } - if (postVisitorsHandlersMap!.has(key)) { - handlers.push(...(postVisitorsHandlersMap!.get(key)! as Array>)); - } + subNodes.length = 0; + if (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 = result.node; + if (nodes[i].chi != null) { + subNodes.push(...nodes[i].chi); + } - for (const callable of handlers) { - replacement = (callable as GenericVisitorHandler)( - node as T, - result.parent, - result.root, - // @ts-expect-error - function* () { - if (parens == null) { - // @ts-expect-error - parens = [...result.parents()]; - } + if (subNodes.length > 0) { + if (freeblock <= i) { + freeblock = i + 1; + } - yield* parens[Symbol.iterator](); - }, - ) as GenericVisitorResult; + for (k = 0; k < subNodes.length; k++) { + j = k + freeblock; + nodes[j] = subNodes[k] as AstNode; + nodes[j][PARENT] = nodes[i]; + } - if (replacement == null) { - continue; - } + freeblock += subNodes.length; + } - if (replacement instanceof Promise) { - replacement = await replacement; - } + parens = null; + handlers.length = 0; + + genericKey = + nodes[i].typ == EnumToken.DeclarationNodeType + ? "Declaration" + : nodes[i].typ == EnumToken.AtRuleNodeType + ? "AtRule" + : nodes[i].typ == EnumToken.KeyframesAtRuleNodeType + ? "KeyframesAtRule" + : nodes[i].typ === EnumToken.KeyframesRuleNodeType + ? "KeyframesRule" + : nodes[i].typ == EnumToken.RuleNodeType + ? "Rule" + : nodes[i].typ == EnumToken.KeyframesRuleNodeType + ? "KeyframesRule" + : null; + let keyName: string | null = + nodes[i].typ == EnumToken.DeclarationNodeType || nodes[i].typ == EnumToken.AtRuleNodeType + ? camelize((nodes[i] as AstDeclaration | AstAtRule).nam) + : nodes[i].typ == EnumToken.KeyframesAtRuleNodeType + ? camelize((nodes[i] as AstKeyframesAtRule).val) + : null; + + for (const map of allHandlers) { + // @ts-ignore + if (genericKey != null && map!.has(genericKey)) { + // @ts-ignore + for (const handler of map!.get(genericKey)!) { + if (typeof handler == "function") { + handlers.push(handler as GenericVisitorHandler); + } else if (Array.isArray(handler)) { + for (const h of handler) { + if (typeof h == "function") { + handlers.push(h); + } - if (replacement == null || replacement == node) { - continue; + // @ts-ignore + else if (h[keyName] != null) { + // @ts-ignore + handlers.push(h[keyName]); + } + } + } else if (typeof handler.handler! == "function") { + handlers.push(handler.handler); } // @ts-ignore - node = replacement as AstNode; - - // - if (Array.isArray(node)) { - break; + else if (typeof handler[keyName]! == "function") { + // @ts-ignore + handlers.push(handler[keyName]); } } + } + // @ts-ignore + if (map!.has(nodes[i].typ)) { // @ts-ignore - if (node != result.node) { - // @ts-ignore - replaceNodeOrValue(result.parent, result.node, node); - } - } else if (valuesHandlers!.size > 0) { - let node: Token | AstNode | null = null; - - node = result.node; - - if (valuesHandlers!.has(node.typ)) { - for (const valueHandler of valuesHandlers!.get(node.typ)!) { - callable = valueHandler as GenericVisitorHandler; - replacement = callable( - node as T, - result.parent, - ast, - // @ts-expect-error - function* () { - if (parens == null) { - // @ts-expect-error - parens = [...result.parents()]; - } - - yield* parens[Symbol.iterator](); - }, - ); - - if (replacement == null) { - continue; - } - - if (replacement instanceof Promise) { - replacement = await replacement; - } - - if (replacement != null && replacement != node) { - node = replacement as AstNode; + for (const handler of map!.get(nodes[i].typ)!) { + if (typeof handler == "function") { + handlers.push(handler as GenericVisitorHandler); + } else if (Array.isArray(handler)) { + for (const h of handler) { + if (typeof h == "function") { + handlers.push(h); + } } + } else if (typeof handler.handler! == "function") { + handlers.push(handler.handler); } - } - if (node != result.node) { // @ts-ignore - replaceNodeOrValue(result[PARENT], value, node); - } - - const tokens: Token[] = Array.isArray(result.node[TOKENS]) ? (result.node[TOKENS] as Token[]) : []; - - if (Array.isArray(result.node.val)) { - tokens.push(...(result.node.val as Token[])); + else if (typeof handler[keyName]! == "function") { + // @ts-ignore + handlers.push(handler[keyName]); + } } + } + } - if (tokens.length == 0) { - continue; - } + if (handlers.length == 0) { + continue; + } - for (const { value, parent, root, parents } of walkValues(tokens, result.node)) { - node = value; + let node = nodes[i] as AstNode; - if (valuesHandlers!.has(node!.typ)) { - let parens: Token[] | null = null; - for (const valueHandler of valuesHandlers!.get(node!.typ)!) { - callable = valueHandler as GenericVisitorHandler; - // @ts-expect-error - let result: GenericVisitorResult = callable(node as T, parent, root, function* () { - if (parens == null) { - // @ts-expect-error - parens = [...parents()]; - } + for (const callable of handlers) { + replacement = (callable as GenericVisitorHandler)( + node as T, + nodes[i][PARENT] as AstNode, + ast as AstStyleSheet, + // @ts-expect-error + function* () { + if (parens == null) { + let node = nodes![i][PARENT] as AstNode; - yield* parens[Symbol.iterator](); - }); + while (node != null) { + yield node; + node = node[PARENT] as AstNode; + } + } + }, + ) as GenericVisitorResult; - if (result == null) { - continue; - } + if (replacement == null) { + continue; + } - if (result instanceof Promise) { - result = await result; - } + if (replacement instanceof Promise) { + replacement = await replacement; + } - if (result != null && result != node) { - node = result as Token; - } + if (replacement == null || replacement == node) { + continue; + } - if (Array.isArray(node)) { - break; - } - } - } + // @ts-ignore + node = replacement as AstNode; - if (node != value) { - // @ts-ignore - replaceNodeOrValue(parent, value, node); - } - } + // + if (Array.isArray(node)) { + break; } } + + if (node != nodes[i]) { + replaceNodeOrValue(nodes[i][PARENT], nodes[i], node); + } } + + nodes = null; } if (invalidNodes.length > 0) { diff --git a/src/lib/parser/utils/selector.ts b/src/lib/parser/utils/selector.ts index 456a88c9..4dfa2340 100644 --- a/src/lib/parser/utils/selector.ts +++ b/src/lib/parser/utils/selector.ts @@ -97,7 +97,7 @@ export function parseSelector( ); return { - typ: EnumToken.KeyFramesRuleNodeType, + typ: EnumToken.KeyframesRuleNodeType, sel: [ ...splitTokenList(trimArray(tokens)).reduce((acc, curr: Token[]) => { acc.add(curr.reduce((acc, curr) => acc + renderValue(curr, { minify: false }), "")); diff --git a/src/lib/renderer/render.ts b/src/lib/renderer/render.ts index 0e2db5fb..ee0d6106 100644 --- a/src/lib/renderer/render.ts +++ b/src/lib/renderer/render.ts @@ -281,7 +281,7 @@ function updateSourceMap( [ EnumToken.RuleNodeType, EnumToken.AtRuleNodeType, - EnumToken.KeyFramesRuleNodeType, + EnumToken.KeyframesRuleNodeType, EnumToken.KeyframesAtRuleNodeType, ].includes(node.typ) ) { @@ -477,7 +477,7 @@ function renderAstNode( 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 || " "}${ diff --git a/test/specs/code/modules.js b/test/specs/code/modules.js index f73093b5..d038667e 100644 --- a/test/specs/code/modules.js +++ b/test/specs/code/modules.js @@ -1,6 +1,6 @@ import { ColorType, EnumToken, ModuleCaseTransformEnum, ModuleScopeEnumOptions } from "../../../dist/lib/ast/types.js"; -export function run(describe, expect, it, transform, parse, render, dirname, readFile) { +export function run(describe, expect, it, transform, parse, render, dirname, readFile, resolve, ColorType, EnumToken, ModuleCaseTransformEnum, ModuleScopeEnumOptions, transformSync, parseSync) { describe("css modules", function () { it("module #1", function () { return transform( @@ -930,5 +930,37 @@ a span { }`); }); }); + + it("module #24", function () { + const result = transformSync( + ` +.goal .bg-indigo { + background: indigo; +} + +.indigo-white { + composes: bg-indigo title; + color: white; +} +`, + { + module: true, + beautify: true, + }, + ); + + expect(result.mapping).deep.equals({ + goal: "goal_r7bhp", + "bg-indigo": "bg-indigo_gy28g", + "indigo-white": "indigo-white_wims0 bg-indigo_gy28g title_qw06e", + title: "title_qw06e", + }); + expect(result.code).equals(`.goal_r7bhp .bg-indigo_gy28g { + background: indigo +} +.indigo-white_wims0 { + color: #fff +}`); + }); }); } diff --git a/test/specs/code/visitors.js b/test/specs/code/visitors.js index 148c338a..bda89685 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,20 @@ html,body { } `; const options = { - removePrefix: true, beautify: true, visitor: { KeyframesAtRule: { slideIn(node) { - - node.val = 'slide-in-out'; + node.val = "slide-in-out"; return node; - } - } - } - } + }, + }, + }, + }; - return transform(css, options).then(result => expect(result.code).equals(`@keyframes slide-in-out { + return transform(css, options).then((result) => + expect(result.code).equals(`@keyframes slide-in-out { 0% { transform: translateX(0) } @@ -252,9 +249,248 @@ html,body { top: 100px; left: 100% } -}`)); +}`), + ); }); - }); + it("visitor #5", function () { + const css = ` + +body { color: color(from var(--base-color) display-p3 r calc(g + 0.24) calc(b + 0.15)); } + +html, +body { + line-height: 1.474; +} + +.ruler { + + height: 10px; + background-color: orange +} +`; + const options = { + beautify: true, + visitor: { + DeclarationNodeType: { + type: WalkerEvent.Enter, + handler: (declaration) => { + if (declaration.nam == "height") { + declaration.nam = "width"; + } + }, + }, + ColorTokenType: (color) => { + return { + typ: EnumToken.Color, + val: "red", + kin: ColorType.HEX, + }; + }, + }, + }; + + const result = transformSync(css, options); + + expect(result.code).equals(`body { + color: red +} +html,body { + line-height: 1.474 +} +.ruler { + width: 10px; + background-color: red +}`); + }); + + it("visitor #6", function () { + const css = ` + +body { color: color(from var(--base-color) display-p3 r calc(g + 0.24) calc(b + 0.15)); } + +html, +body { + line-height: 1.474; +} + +.ruler { + + height: 10px; + background-color: orange +} +`; + const options = { + beautify: true, + visitor: { + DeclarationNodeType: (declaration) => { + if (declaration.nam == "height") { + declaration.nam = "width"; + } + }, + ColorTokenType: (color) => { + return { + typ: EnumToken.Color, + val: "red", + kin: ColorType.HEX, + }; + }, + }, + }; + + const result = transformSync(css, options); + + expect(result.code).equals(`body { + color: red +} +html,body { + line-height: 1.474 +} +.ruler { + width: 10px; + background-color: red +}`); + }); + + it("visitor #7", function () { + const css = ` + +@media screen { + + .foo:-webkit-autofill { + height: calc(100px * 2/ 15); + } +} + + +body { color: color(from var(--base-color) display-p3 r calc(g + 0.24) calc(b + 0.15)); } + +html, +body { + line-height: 1.474; +} + +.ruler { + + height: 10px; + background-color: orange +} +`; + const options = { + beautify: true, + inlineCssVariables: true, + resolveImport: true, + visitor: { + StyleSheetNodeType: { + type: WalkerEvent.Leave, + handler: (node) => { + // insert a new rule + node.chi.unshift(parseSync("html {--base-color: pink}").ast.chi[0]); + }, + }, + ColorTokenType: (node) => { + // dump all color tokens + // console.debug(node); + }, + FunctionTokenType: (node) => { + // dump all function tokens + // console.debug(node); + }, + DeclarationNodeType: (node) => { + // dump all declaration nodes + // console.debug(node); + }, + }, + }; + + const result = transformSync(css, options); + + expect(result.code).equals(`@media screen { + .foo:-webkit-autofill { + height: calc(40px/3) + } +} +body { + color: #f3fff0 +} +html,body { + line-height: 1.474 +} +.ruler { + height: 10px; + background-color: orange +}`); + }); + + it("visitor #8", function () { + const css = ` + +@keyframes slide-in { + from { + transform: translateX(0%); + } -} \ No newline at end of file + to { + transform: translateX(100%); + } +} +@keyframes identifier { + 0% { + top: 0; + left: 0; + } + 30% { + top: 50px; + } + 68%, + 72% { + left: 50px; + } + 100% { + top: 100px; + left: 100%; + } +} +`; + const options = { + removePrefix: true, + beautify: true, + visitor: { + KeyframesAtRule: { + slideIn(node) { + node.val = "slide-in-out"; + return node; + }, + }, + }, + }; + + const result = transformSync(css, options); + + expect(result.code).equals(`@keyframes slide-in-out { + 0% { + transform: translateX(0) + } + to { + transform: translateX(100%) + } +} +@keyframes identifier { + 0% { + top: 0; + left: 0 + } + 30% { + top: 50px + } + 68%,72% { + left: 50px + } + to { + top: 100px; + left: 100% + } +}`); + }); + }); +} From f9bab6a95723f28e13c2dd09278a4ad96876e403 Mon Sep 17 00:00:00 2001 From: Thierry Bela Nanga Date: Mon, 17 Aug 2026 17:26:17 -0400 Subject: [PATCH 11/11] throw an error when async parameter is passed to sync function #146 --- benchmark/package.json | 6 +- dist/index-umd-web.js | 224 +++++++-------- dist/index.cjs | 224 +++++++-------- dist/index.d.ts | 105 ++++++- dist/lib/ast/walk.js | 29 +- dist/lib/parser/parse.js | 176 ++++-------- dist/lib/renderer/sourcemap/sourcemap.js | 1 + dist/node.js | 7 +- dist/{utils.d.ts => utils/sync.d.ts} | 3 +- dist/{utils.js => utils/sync.js} | 19 +- dist/web.js | 7 +- files/assets/typedoc-custom.css | 15 +- files/plugins.md | 7 +- files/usage.md | 35 ++- src/@types/index.d.ts | 15 +- src/@types/walker.d.ts | 20 ++ src/lib/ast/walk.ts | 186 ++++++++++++- src/lib/parser/parse.ts | 338 +++++++---------------- src/lib/renderer/sourcemap/sourcemap.ts | 1 + src/lib/validation/match.ts | 82 +++--- src/node.ts | 9 +- src/{utils.ts => utils/sync.ts} | 24 +- src/web.ts | 9 +- test/specs/code/visitors.js | 48 ++++ 24 files changed, 873 insertions(+), 717 deletions(-) rename dist/{utils.d.ts => utils/sync.d.ts} (52%) rename dist/{utils.js => utils/sync.js} (67%) rename src/{utils.ts => utils/sync.ts} (65%) diff --git a/benchmark/package.json b/benchmark/package.json index a7e23016..dafde208 100644 --- a/benchmark/package.json +++ b/benchmark/package.json @@ -10,11 +10,11 @@ "all": "npm run sizes && npm run bench && npm run report" }, "dependencies": { - "@tbela99/css-parser": "^1.5.0", - "@tbela99/css-parser2": "github:tbela99/css-parser#2279484", + "@tbela99/css-parser": "^1.4.11", + "@tbela99/css-parser2": "github:tbela99/css-parser#2628ebce", "clean-css": "^5.3.3", "css-tree": "^3.2.1", - "cssnano": "^8.0.5", + "cssnano": "^8.0.6", "csso": "^5.0.5", "esbuild": "^0.28.2", "lightningcss": "^1.33.0", diff --git a/dist/index-umd-web.js b/dist/index-umd-web.js index ffe67ee0..0fa56d1e 100644 --- a/dist/index-umd-web.js +++ b/dist/index-umd-web.js @@ -9435,6 +9435,8 @@ * @param filter control the walk process * @param reverse walk in reverse order * + * @private + * * ```ts * * import {walk} from '@tbela99/css-parser'; @@ -9506,11 +9508,19 @@ const parents = [node]; const root = node; const map = new Map(); + let options = filter; let isNumeric = false; + let includeValues = false; let i = 0; + if (options != null && typeof options == "object") { + filter = options.filter; + reverse = options.reverse; + includeValues = options.inludeValues; + } while ((node = parents[i++])) { let option = null; if (filter != null) { + // @ts-ignore option = filter(node); isNumeric = typeof option == "number"; if (isNumeric) { @@ -9538,8 +9548,16 @@ }, }; } - if ("chi" in node && (!isNumeric || (option & exports.WalkerOptionEnum.IgnoreChildren) === 0)) { - parents.splice(i, 0, ...node.chi[reverse ? "toReversed" : "slice"]()); + if (includeValues) { + if (node[TOKENS] != null) { + parents.splice(i, 0, ...(reverse ? node[TOKENS].toReversed() : node[TOKENS])); + } + else if (Array.isArray(node.val)) { + parents.splice(i, 0, ...(reverse ? node.val.toReversed() : node.val)); + } + } + if (node["chi"] != null && (!isNumeric || (option & exports.WalkerOptionEnum.IgnoreChildren) === 0)) { + parents.splice(i, 0, ...(reverse ? node.chi.toReversed() : node.chi)); for (const child of node.chi) { map.set(child, node); } @@ -9623,11 +9641,6 @@ continue; } used.add(value); - // parents.length = 0; - // while (node != null) { - // parents.push(node); - // node = map.get(node) ?? null; - // } if (filter.fn != null && eventType & exports.WalkerEvent.Enter) { const isValid = filter.type == null || value.typ == filter.type || @@ -21597,6 +21610,7 @@ /** * * @param sourcemaps + * @private */ constructor(sourcemaps) { if (typeof sourcemaps === "string") { @@ -28711,11 +28725,23 @@ // if leading char is digit, prefix underscore (very rare) return (/^[0-9]/.test(result) ? "_" : "") + result; }); - function parseVisitors(options, valuesHandlers, preValuesHandlers, postValuesHandlers, errors, visitorsHandlersMap, preVisitorsHandlersMap, postVisitorsHandlersMap) { - const visitors = Object.entries(options.visitor); + /** + * + * @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]; @@ -28803,6 +28829,29 @@ 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 @@ -28869,24 +28918,18 @@ }; 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; let currentItemIndex; + // ast[ROOT] = ast; + ast[LOC] = { + sta: 0, + end: 0, + srcId: options.source.id, + }; // if (Array.isArray(iter)) { // // @ts-expect-error // iter = iter[Symbol.iterator]() as Iterator; @@ -28991,49 +29034,23 @@ } let replacement; if (options.visitor != null) { - valuesHandlers = new Map(); - preValuesHandlers = new Map(); - postValuesHandlers = new Map(); - preVisitorsHandlersMap = new Map(); - visitorsHandlersMap = new Map(); - postVisitorsHandlersMap = new Map(); - parseVisitors(options, valuesHandlers, preValuesHandlers, postValuesHandlers, errors, visitorsHandlersMap, preVisitorsHandlersMap, postVisitorsHandlersMap); + const handlers = []; + const visitors = parseVisitors(options.visitor, errors); + const subNodes = []; let parens; let genericKey; - const handlers = []; - 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); - } let nodes = new Array(stats.tokensCount); - const subNodes = []; let i; let k; let j; let freeBlock = 1; - const includeTokens = preValuesHandlers.size > 0 || valuesHandlers.size > 0 || postValuesHandlers.size > 0; nodes[0] = ast; for (i = 0; i < nodes.length; i++) { if (nodes[i] == null) { break; } subNodes.length = 0; - if (includeTokens) { + if (visitors.includeTokens) { switch (nodes[i].typ) { case exports.EnumToken.RuleNodeType: case exports.EnumToken.AtRuleNodeType: @@ -29081,7 +29098,7 @@ : nodes[i].typ == exports.EnumToken.KeyframesAtRuleNodeType ? camelize(nodes[i].val) : null; - for (const map of allHandlers) { + for (const map of visitors.allHandlers) { // @ts-ignore if (genericKey != null && map.has(genericKey)) { // @ts-ignore @@ -29192,19 +29209,6 @@ } } } - while (stack.length > 0 && context != ast) { - const previousNode = stack.pop(); - context = (stack[stack.length - 1] ?? ast); - // remove empty nodes - if (options.removeEmpty && - previousNode != null && - previousNode.chi.length == 0 && - context.chi[context.chi.length - 1] == previousNode) { - context.chi.pop(); - continue; - } - break; - } if (options.minify) { if (ast.chi.length > 0) { let passes = options.pass ?? 1; @@ -29777,18 +29781,6 @@ }; let tokens = []; let context = ast; - // ast[ROOT] = ast; - ast[LOC] = { - sta: 0, - end: 0, - srcId: options.source.id, - }; - let valuesHandlers; - let preValuesHandlers; - let postValuesHandlers; - let preVisitorsHandlersMap; - let visitorsHandlersMap; - let postVisitorsHandlersMap; const imports = []; let item; let node; @@ -29796,6 +29788,12 @@ let isAsync = typeof iter[Symbol.asyncIterator] === "function"; let parensMatch = 0; let curlyBracketMatch = 0; + // ast[ROOT] = ast; + ast[LOC] = { + sta: 0, + end: 0, + srcId: options.source.id, + }; if (Array.isArray(iter)) { // @ts-expect-error iter = iter[Symbol.iterator](); @@ -29957,64 +29955,24 @@ ast = expand(ast); } let replacement; - while (stack.length > 0 && context != ast) { - const previousNode = stack.pop(); - context = (stack[stack.length - 1] ?? ast); - previousNode[PARENT] = context; - // 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.visitor != null) { - valuesHandlers = new Map(); - preValuesHandlers = new Map(); - postValuesHandlers = new Map(); - preVisitorsHandlersMap = new Map(); - visitorsHandlersMap = new Map(); - postVisitorsHandlersMap = new Map(); - parseVisitors(options, valuesHandlers, preValuesHandlers, postValuesHandlers, errors, visitorsHandlersMap, preVisitorsHandlersMap, postVisitorsHandlersMap); let parens; let genericKey; const handlers = []; - 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); - } + const visitors = parseVisitors(options.visitor, errors); let nodes = new Array(stats.tokensCount); const subNodes = []; let i; let k; let j; let freeblock = 1; - const includeTokens = preValuesHandlers.size > 0 || valuesHandlers.size > 0 || postValuesHandlers.size > 0; nodes[0] = ast; for (i = 0; i < nodes.length; i++) { if (nodes[i] == null) { break; } subNodes.length = 0; - if (includeTokens) { + if (visitors.includeTokens) { switch (nodes[i].typ) { case exports.EnumToken.RuleNodeType: case exports.EnumToken.AtRuleNodeType: @@ -30062,7 +30020,7 @@ : nodes[i].typ == exports.EnumToken.KeyframesAtRuleNodeType ? camelize(nodes[i].val) : null; - for (const map of allHandlers) { + for (const map of visitors.allHandlers) { // @ts-ignore if (genericKey != null && map.has(genericKey)) { // @ts-ignore @@ -31800,10 +31758,7 @@ } const result = parseTokens(mapped, options, errors); // remove EOF token - result.pop(); - if (result.at(-1)?.typ === exports.EnumToken.WhitespaceTokenType) { - result.pop(); - } + result.splice(result.length - (result[result.length - 2]?.typ === exports.EnumToken.WhitespaceTokenType ? 2 : 1), 2); return result; } /** @@ -31894,7 +31849,6 @@ node, location: options.source.getSourceLocation(node[LOC].sta), }); - // return []; continue; } index = tokens.indexOf(stack.at(-1)); @@ -32086,6 +32040,21 @@ } return result; } + function validateSyncArguments(options, prefix = "options.") { + const args = Object.entries(options); + let i; + for (i = 0; i < args.length; i++) { + const [key, value] = args[i]; + if (typeof value == 'function') { + if (value instanceof Promise || Object.getPrototypeOf(value).constructor.name == "AsyncFunction") { + throw new Error(`[${prefix + key}]: Async functions are not supported in sync mode. Use parse() or transform() instead.`); + } + } + else if (value != null && typeof value == 'object') { + validateSyncArguments(value, prefix + key + "."); + } + } + } /** * Load file or url @@ -32222,6 +32191,9 @@ options = opt; stream = input; } + if (options != null) { + validateSyncArguments(options); + } options ??= {}; options.src ??= ""; options.sourcesMap ??= new Map(); @@ -32249,7 +32221,7 @@ currentPosition: -1, }; const result = doParseSync(tokenize(options.parseInfo), options); - return !options.module && !options.inputSourceMap ? result : parseResult(result, options); + return !options.module && !options.inputSourceMap && !options.sourcemap ? result : parseResult(result, options); } /** * Transform CSS diff --git a/dist/index.cjs b/dist/index.cjs index f62fb5f0..8ba8ff59 100644 --- a/dist/index.cjs +++ b/dist/index.cjs @@ -9438,6 +9438,8 @@ exports.WalkerEvent = void 0; * @param filter control the walk process * @param reverse walk in reverse order * + * @private + * * ```ts * * import {walk} from '@tbela99/css-parser'; @@ -9509,11 +9511,19 @@ function* walk(node, filter, reverse) { const parents = [node]; const root = node; const map = new Map(); + let options = filter; let isNumeric = false; + let includeValues = false; let i = 0; + if (options != null && typeof options == "object") { + filter = options.filter; + reverse = options.reverse; + includeValues = options.inludeValues; + } while ((node = parents[i++])) { let option = null; if (filter != null) { + // @ts-ignore option = filter(node); isNumeric = typeof option == "number"; if (isNumeric) { @@ -9541,8 +9551,16 @@ function* walk(node, filter, reverse) { }, }; } - if ("chi" in node && (!isNumeric || (option & exports.WalkerOptionEnum.IgnoreChildren) === 0)) { - parents.splice(i, 0, ...node.chi[reverse ? "toReversed" : "slice"]()); + if (includeValues) { + if (node[TOKENS] != null) { + parents.splice(i, 0, ...(reverse ? node[TOKENS].toReversed() : node[TOKENS])); + } + else if (Array.isArray(node.val)) { + parents.splice(i, 0, ...(reverse ? node.val.toReversed() : node.val)); + } + } + if (node["chi"] != null && (!isNumeric || (option & exports.WalkerOptionEnum.IgnoreChildren) === 0)) { + parents.splice(i, 0, ...(reverse ? node.chi.toReversed() : node.chi)); for (const child of node.chi) { map.set(child, node); } @@ -9626,11 +9644,6 @@ function* walkValues(values, root = null, filter, reverse) { continue; } used.add(value); - // parents.length = 0; - // while (node != null) { - // parents.push(node); - // node = map.get(node) ?? null; - // } if (filter.fn != null && eventType & exports.WalkerEvent.Enter) { const isValid = filter.type == null || value.typ == filter.type || @@ -21600,6 +21613,7 @@ class SourceMap { /** * * @param sourcemaps + * @private */ constructor(sourcemaps) { if (typeof sourcemaps === "string") { @@ -28714,11 +28728,23 @@ const generateSyncScopedName = memoize((localName, filePath, pattern, hashLength // if leading char is digit, prefix underscore (very rare) return (/^[0-9]/.test(result) ? "_" : "") + result; }); -function parseVisitors(options, valuesHandlers, preValuesHandlers, postValuesHandlers, errors, visitorsHandlersMap, preVisitorsHandlersMap, postVisitorsHandlersMap) { - const visitors = Object.entries(options.visitor); +/** + * + * @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]; @@ -28806,6 +28832,29 @@ function parseVisitors(options, valuesHandlers, preValuesHandlers, postValuesHan 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 @@ -28872,24 +28921,18 @@ 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; let currentItemIndex; + // ast[ROOT] = ast; + ast[LOC] = { + sta: 0, + end: 0, + srcId: options.source.id, + }; // if (Array.isArray(iter)) { // // @ts-expect-error // iter = iter[Symbol.iterator]() as Iterator; @@ -28994,49 +29037,23 @@ function doParseSync(iter, options = {}) { } let replacement; if (options.visitor != null) { - valuesHandlers = new Map(); - preValuesHandlers = new Map(); - postValuesHandlers = new Map(); - preVisitorsHandlersMap = new Map(); - visitorsHandlersMap = new Map(); - postVisitorsHandlersMap = new Map(); - parseVisitors(options, valuesHandlers, preValuesHandlers, postValuesHandlers, errors, visitorsHandlersMap, preVisitorsHandlersMap, postVisitorsHandlersMap); + const handlers = []; + const visitors = parseVisitors(options.visitor, errors); + const subNodes = []; let parens; let genericKey; - const handlers = []; - 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); - } let nodes = new Array(stats.tokensCount); - const subNodes = []; let i; let k; let j; let freeBlock = 1; - const includeTokens = preValuesHandlers.size > 0 || valuesHandlers.size > 0 || postValuesHandlers.size > 0; nodes[0] = ast; for (i = 0; i < nodes.length; i++) { if (nodes[i] == null) { break; } subNodes.length = 0; - if (includeTokens) { + if (visitors.includeTokens) { switch (nodes[i].typ) { case exports.EnumToken.RuleNodeType: case exports.EnumToken.AtRuleNodeType: @@ -29084,7 +29101,7 @@ function doParseSync(iter, options = {}) { : nodes[i].typ == exports.EnumToken.KeyframesAtRuleNodeType ? camelize(nodes[i].val) : null; - for (const map of allHandlers) { + for (const map of visitors.allHandlers) { // @ts-ignore if (genericKey != null && map.has(genericKey)) { // @ts-ignore @@ -29195,19 +29212,6 @@ function doParseSync(iter, options = {}) { } } } - while (stack.length > 0 && context != ast) { - const previousNode = stack.pop(); - context = (stack[stack.length - 1] ?? ast); - // remove empty nodes - if (options.removeEmpty && - previousNode != null && - previousNode.chi.length == 0 && - context.chi[context.chi.length - 1] == previousNode) { - context.chi.pop(); - continue; - } - break; - } if (options.minify) { if (ast.chi.length > 0) { let passes = options.pass ?? 1; @@ -29780,18 +29784,6 @@ async function doParse(iter, options = {}) { }; let tokens = []; let context = ast; - // ast[ROOT] = ast; - ast[LOC] = { - sta: 0, - end: 0, - srcId: options.source.id, - }; - let valuesHandlers; - let preValuesHandlers; - let postValuesHandlers; - let preVisitorsHandlersMap; - let visitorsHandlersMap; - let postVisitorsHandlersMap; const imports = []; let item; let node; @@ -29799,6 +29791,12 @@ async function doParse(iter, options = {}) { let isAsync = typeof iter[Symbol.asyncIterator] === "function"; let parensMatch = 0; let curlyBracketMatch = 0; + // ast[ROOT] = ast; + ast[LOC] = { + sta: 0, + end: 0, + srcId: options.source.id, + }; if (Array.isArray(iter)) { // @ts-expect-error iter = iter[Symbol.iterator](); @@ -29960,64 +29958,24 @@ async function doParse(iter, options = {}) { ast = expand(ast); } let replacement; - while (stack.length > 0 && context != ast) { - const previousNode = stack.pop(); - context = (stack[stack.length - 1] ?? ast); - previousNode[PARENT] = context; - // 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.visitor != null) { - valuesHandlers = new Map(); - preValuesHandlers = new Map(); - postValuesHandlers = new Map(); - preVisitorsHandlersMap = new Map(); - visitorsHandlersMap = new Map(); - postVisitorsHandlersMap = new Map(); - parseVisitors(options, valuesHandlers, preValuesHandlers, postValuesHandlers, errors, visitorsHandlersMap, preVisitorsHandlersMap, postVisitorsHandlersMap); let parens; let genericKey; const handlers = []; - 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); - } + const visitors = parseVisitors(options.visitor, errors); let nodes = new Array(stats.tokensCount); const subNodes = []; let i; let k; let j; let freeblock = 1; - const includeTokens = preValuesHandlers.size > 0 || valuesHandlers.size > 0 || postValuesHandlers.size > 0; nodes[0] = ast; for (i = 0; i < nodes.length; i++) { if (nodes[i] == null) { break; } subNodes.length = 0; - if (includeTokens) { + if (visitors.includeTokens) { switch (nodes[i].typ) { case exports.EnumToken.RuleNodeType: case exports.EnumToken.AtRuleNodeType: @@ -30065,7 +30023,7 @@ async function doParse(iter, options = {}) { : nodes[i].typ == exports.EnumToken.KeyframesAtRuleNodeType ? camelize(nodes[i].val) : null; - for (const map of allHandlers) { + for (const map of visitors.allHandlers) { // @ts-ignore if (genericKey != null && map.has(genericKey)) { // @ts-ignore @@ -31803,10 +31761,7 @@ function parseString(src, options = { parseColor: true }, errors) { } const result = parseTokens(mapped, options, errors); // remove EOF token - result.pop(); - if (result.at(-1)?.typ === exports.EnumToken.WhitespaceTokenType) { - result.pop(); - } + result.splice(result.length - (result[result.length - 2]?.typ === exports.EnumToken.WhitespaceTokenType ? 2 : 1), 2); return result; } /** @@ -31897,7 +31852,6 @@ function parseTokens(tokens, options, errors) { node, location: options.source.getSourceLocation(node[LOC].sta), }); - // return []; continue; } index = tokens.indexOf(stack.at(-1)); @@ -32089,6 +32043,21 @@ function parseResult(result, options) { } return result; } +function validateSyncArguments(options, prefix = "options.") { + const args = Object.entries(options); + let i; + for (i = 0; i < args.length; i++) { + const [key, value] = args[i]; + if (typeof value == 'function') { + if (value instanceof Promise || Object.getPrototypeOf(value).constructor.name == "AsyncFunction") { + throw new Error(`[${prefix + key}]: Async functions are not supported in sync mode. Use parse() or transform() instead.`); + } + } + else if (value != null && typeof value == 'object') { + validateSyncArguments(value, prefix + key + "."); + } + } +} /** * Load file or url @@ -32227,6 +32196,9 @@ function parseSync(...args) { options = opt; stream = input; } + if (options != null) { + validateSyncArguments(options); + } options ??= {}; options.src ??= ""; options.sourcesMap ??= new Map(); @@ -32252,7 +32224,7 @@ function parseSync(...args) { currentPosition: -1, }; const result = doParseSync(tokenize(options.parseInfo), options); - return !options.module && !options.inputSourceMap ? result : parseResult(result, options); + return !options.module && !options.inputSourceMap && !options.sourcemap ? result : parseResult(result, options); } /** * Transform css diff --git a/dist/index.d.ts b/dist/index.d.ts index 549223a7..695682dd 100644 --- a/dist/index.d.ts +++ b/dist/index.d.ts @@ -3171,7 +3171,7 @@ declare enum WalkerEvent { * } * * const result = await transform(css); - * for (const {node} of walk(result.ast, filter)) { + * for (const {node} of walk(result.ast, filter, false)) { * * console.error([EnumToken[node.typ]]); * } @@ -3186,6 +3186,79 @@ declare enum WalkerEvent { * ``` */ declare function walk(node: AstNode$1, filter?: WalkerFilter | null, reverse?: boolean): Generator; +/** + * Walk ast nodes + * @param node initial node + * @param filter control the walk process + * + * ```ts + * + * import {walk} from '@tbela99/css-parser'; + * + * const css = ` + * body { color: color(from var(--base-color) display-p3 r calc(g + 0.24) calc(b + 0.15)); } + * + * html, + * body { + * line-height: 1.474; + * } + * + * .ruler { + * + * height: 10px; + * } + * `; + * + * for (const {node, parent, root} of walk(ast)) { + * + * // do something with node + * } + * ``` + * + * Using a {@link filter} function to control the ast traversal. the filter function returns a value of type {@link WalkerOption}. + * + * ```ts + * import {EnumToken, transform, walk, WalkerOptionEnum} from '@tbela99/css-parser'; + * + * const css = ` + * body { color: color(from var(--base-color) display-p3 r calc(g + 0.24) calc(b + 0.15)); } + * + * html, + * body { + * line-height: 1.474; + * } + * + * .ruler { + * + * height: 10px; + * } + * `; + * + * function filter(node) { + * + * if (node.typ == EnumToken.AstRule && node.sel.includes('html')) { + * + * // skip the children of the current node + * return WalkerOptionEnum.IgnoreChildren; + * } + * } + * + * const result = await transform(css); + * for (const {node} of walk(result.ast, {filter, reverse: false})) { + * + * console.error([EnumToken[node.typ]]); + * } + * + * // [ "StyleSheetNodeType" ] + * // [ "RuleNodeType" ] + * // [ "DeclarationNodeType" ] + * // [ "RuleNodeType" ] + * // [ "DeclarationNodeType" ] + * // [ "RuleNodeType" ] + * // [ "DeclarationNodeType" ] + * ``` + */ +declare function walk(node: AstNode$1, filter?: WalkerOptions | null): Generator; /** * Walk ast node value tokens * @param values @@ -4825,6 +4898,26 @@ interface BorderRadius { keywords: string[]; } +/** + * node walker options + */ +export declare interface WalkerOptions { + + /** + * walk in reverse + */ + reverse?: boolean; + + /** + * Traverse node value tokens. If false, only traverse node children + */ + inludeValues?: boolean; + /** + * filter function to control the walk + */ + filter?: WalkerFilter; +} + /** * node walker option */ @@ -5437,7 +5530,7 @@ export declare interface ParserSyncOptions * Node visitor * {@link VisitorSyncNodeMap | VisitorSyncNodeMap[]} */ - visitor?: VisitorSyncNodeMap | VisitorSyncNodeMap[]; + visitor?: GenericVisitorAstNodeSyncHandlerMap | VisitorSyncNodeMap | VisitorSyncNodeMap[]; /** * Abort signal * @@ -5502,7 +5595,11 @@ export declare interface ParserOptions extends ParserSyncOptions, ModuleAsyncOpt * Node visitor * {@link VisitorNodeMap | VisitorNodeMap[]} */ - visitor?: VisitorNodeMap | VisitorNodeMap[]; + visitor?: + | GenericVisitorAstNodeSyncHandlerMap + | GenericVisitorAstNodeHandlerMap + | VisitorNodeMap + | VisitorNodeMap[]; } /** @@ -6751,4 +6848,4 @@ declare function transform(options: ParseInputStreamOptions & TransformOptions): 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, AstKeyframesAtRule, AstKeyframesRule, AstNode$1 as AstNode, AstNodeStatus, AstRule, AstRuleList, AstStyleSheet, AstValueMatcher, AtRuleToken, AtRuleVisitorHandler, AttrEndToken, AttrStartToken, AttrToken, Background, BackgroundAttachmentMapping, BackgroundPosition, BackgroundPositionClass, BackgroundPositionConstraints, BackgroundPositionMapping, BackgroundProperties, BackgroundRepeat, BackgroundRepeatMapping, BackgroundSize, BackgroundSizeMapping, BadCDOCommentToken, BadCommentToken, BadStringToken, BadUrlToken, BaseToken, BinaryExpressionNode, BinaryExpressionToken, BlockEndToken, BlockStartToken, Border, BorderColor, BorderColorClass, BorderProperties, BorderRadius, CDOCommentToken, ChildCombinatorToken, ClassSelectorToken, ColonToken, ColorToken, ColumnCombinatorToken, CommaToken, CommentToken, ComposesSelectorToken, ConstraintsMapping, ContainMatchToken, ContainerStyleRangeToken, CssVariableImportTokenType$1 as CssVariableImportTokenType, CssVariableMapTokenType, CssVariableToken$1 as CssVariableToken, DashMatchToken, DashedIdentToken, DeclarationVisitorHandler, DelimToken, DescendantCombinatorToken, DimensionToken, DivToken, DoubleColonToken, EOFToken, EndMatchToken, EqualMatchToken, ErrorDescription$1 as ErrorDescription, FlexToken, Font, FontFamily, FontProperties, FontWeight, FontWeightConstraints, FontWeightMapping, FractionToken, FrequencyToken, FunctionDefToken, FunctionImageToken, FunctionToken, FunctionURLToken, GenericVisitorAstNodeHandlerMap, GenericVisitorAstNodeSyncHandlerMap, GenericVisitorAsyncResult, GenericVisitorHandler, GenericVisitorResult, GenericVisitorSyncHandler, GenericVisitorSyncResult, GreaterThanOrEqualToken, GreaterThanToken, GridTemplateFuncToken, HashToken, IdentListToken, IdentToken, IfConditionToken, IfElseConditionToken, ImportantToken, IncludeMatchToken, InvalidAttrToken, InvalidClassSelectorToken, InvalidMediaQueryToken, LengthToken, LessThanOrEqualToken, LessThanToken, LineHeight, ListToken, LiteralToken, LoadResult, Map$1 as Map, MatchExpressionToken, MatchedSelector, MediaFeatureOnlyToken, MediaFeatureToken, MediaQueryConditionToken, MediaQueryUnaryFeatureToken, MediaRangeQueryToken, MinifyFeature, MinifyFeatureOptions, MinifyOptions, ModuleAsyncOptions, ModuleSyncOptions, MulToken, NameSpaceAttributeToken, NestingSelectorToken, NextSiblingCombinatorToken, NotToken, NumberToken, OptimizedSelector, OptimizedSelectorToken, OrToken, Outline, OutlineProperties, ParensEndToken, ParensStartToken, ParensToken, ParseInfo$1 as ParseInfo, ParseInputFileOptions, ParseInputOptions, ParseInputStreamOptions, ParseResult, ParseResultStats, ParseSourceOptions, ParseTokenOptions, ParserOptions, ParserSourceMapOptions, ParserSyncOptions, PercentageToken, Prefix, PropertiesConfig, PropertiesConfigProperties, PropertyListOptions, PropertyMapType, PropertySetType, PropertyType, PseudoClassFunctionToken, PseudoClassToken, PseudoElementToken, PseudoPageToken, PurpleBackgroundAttachment, RawNodeToken, RawSelectorTokens, RenderOptions, RenderResult, ResolutionToken, ResolvedPath, RuleVisitorHandler, SemiColonToken, Separator, ShorthandDef, ShorthandMapType, ShorthandProperties, ShorthandPropertyType, ShorthandType, SinglePropertyType, SinglePropertyTypeMapping, SourceLocation, SourceMapObject, StartMatchToken, StringToken, SubToken, SubsequentCombinatorToken, SupportsQueryConditionToken, SupportsQueryUnaryConditionToken, TimeToken, TimelineFunctionToken, TimingFunctionToken, Token$1 as Token, TokenSearchResult, TokenizeResult, TransformOptions, TransformResult, TransformSyncOptions, UnaryExpression, UnaryExpressionNode, UnclosedStringToken, UniversalSelectorToken, UrlToken, ValidationConfiguration, ValidationMediaFeature, ValidationOptions, ValidationResult, ValidationSelectorOptions, ValidationSyntaxNode, ValidationSyntaxResult, ValidationToken$1 as ValidationToken, Value, ValueVisitorHandler, ValueVisitorSyncHandler, VariableScopeInfo, VisitorNodeMap, VisitorSyncNodeMap, WalkAttributesResult, WalkResult, WalkerFilter, WalkerOption, WalkerValueFilter, WhenElseQueryConditionToken, WhenElseUnaryConditionToken, WhitespaceToken, WrappedValuesToken }; +export type { AddToken, AndToken, AngleToken, AstAtRule, AstComment, AstDeclaration, AstInvalidAtRule, AstInvalidDeclaration, AstInvalidRule, AstKeyframesAtRule, AstKeyframesRule, AstNode$1 as AstNode, AstNodeStatus, AstRule, AstRuleList, AstStyleSheet, AstValueMatcher, AtRuleToken, AtRuleVisitorHandler, AttrEndToken, AttrStartToken, AttrToken, Background, BackgroundAttachmentMapping, BackgroundPosition, BackgroundPositionClass, BackgroundPositionConstraints, BackgroundPositionMapping, BackgroundProperties, BackgroundRepeat, BackgroundRepeatMapping, BackgroundSize, BackgroundSizeMapping, BadCDOCommentToken, BadCommentToken, BadStringToken, BadUrlToken, BaseToken, BinaryExpressionNode, BinaryExpressionToken, BlockEndToken, BlockStartToken, Border, BorderColor, BorderColorClass, BorderProperties, BorderRadius, CDOCommentToken, ChildCombinatorToken, ClassSelectorToken, ColonToken, ColorToken, ColumnCombinatorToken, CommaToken, CommentToken, ComposesSelectorToken, ConstraintsMapping, ContainMatchToken, ContainerStyleRangeToken, CssVariableImportTokenType$1 as CssVariableImportTokenType, CssVariableMapTokenType, CssVariableToken$1 as CssVariableToken, DashMatchToken, DashedIdentToken, DeclarationVisitorHandler, DelimToken, DescendantCombinatorToken, DimensionToken, DivToken, DoubleColonToken, EOFToken, EndMatchToken, EqualMatchToken, ErrorDescription$1 as ErrorDescription, FlexToken, Font, FontFamily, FontProperties, FontWeight, FontWeightConstraints, FontWeightMapping, FractionToken, FrequencyToken, FunctionDefToken, FunctionImageToken, FunctionToken, FunctionURLToken, GenericVisitorAstNodeHandlerMap, GenericVisitorAstNodeSyncHandlerMap, GenericVisitorAsyncResult, GenericVisitorHandler, GenericVisitorResult, GenericVisitorSyncHandler, GenericVisitorSyncResult, GreaterThanOrEqualToken, GreaterThanToken, GridTemplateFuncToken, HashToken, IdentListToken, IdentToken, IfConditionToken, IfElseConditionToken, ImportantToken, IncludeMatchToken, InvalidAttrToken, InvalidClassSelectorToken, InvalidMediaQueryToken, LengthToken, LessThanOrEqualToken, LessThanToken, LineHeight, ListToken, LiteralToken, LoadResult, Map$1 as Map, MatchExpressionToken, MatchedSelector, MediaFeatureOnlyToken, MediaFeatureToken, MediaQueryConditionToken, MediaQueryUnaryFeatureToken, MediaRangeQueryToken, MinifyFeature, MinifyFeatureOptions, MinifyOptions, ModuleAsyncOptions, ModuleSyncOptions, MulToken, NameSpaceAttributeToken, NestingSelectorToken, NextSiblingCombinatorToken, NotToken, NumberToken, OptimizedSelector, OptimizedSelectorToken, OrToken, Outline, OutlineProperties, ParensEndToken, ParensStartToken, ParensToken, ParseInfo$1 as ParseInfo, ParseInputFileOptions, ParseInputOptions, ParseInputStreamOptions, ParseResult, ParseResultStats, ParseSourceOptions, ParseTokenOptions, ParserOptions, ParserSourceMapOptions, ParserSyncOptions, PercentageToken, Prefix, PropertiesConfig, PropertiesConfigProperties, PropertyListOptions, PropertyMapType, PropertySetType, PropertyType, PseudoClassFunctionToken, PseudoClassToken, PseudoElementToken, PseudoPageToken, PurpleBackgroundAttachment, RawNodeToken, RawSelectorTokens, RenderOptions, RenderResult, ResolutionToken, ResolvedPath, RuleVisitorHandler, SemiColonToken, Separator, ShorthandDef, ShorthandMapType, ShorthandProperties, ShorthandPropertyType, ShorthandType, SinglePropertyType, SinglePropertyTypeMapping, SourceLocation, SourceMapObject, StartMatchToken, StringToken, SubToken, SubsequentCombinatorToken, SupportsQueryConditionToken, SupportsQueryUnaryConditionToken, TimeToken, TimelineFunctionToken, TimingFunctionToken, Token$1 as Token, TokenSearchResult, TokenizeResult, TransformOptions, TransformResult, TransformSyncOptions, UnaryExpression, UnaryExpressionNode, UnclosedStringToken, UniversalSelectorToken, UrlToken, ValidationConfiguration, ValidationMediaFeature, ValidationOptions, ValidationResult, ValidationSelectorOptions, ValidationSyntaxNode, ValidationSyntaxResult, ValidationToken$1 as ValidationToken, Value, ValueVisitorHandler, ValueVisitorSyncHandler, VariableScopeInfo, VisitorNodeMap, VisitorSyncNodeMap, WalkAttributesResult, WalkResult, WalkerFilter, WalkerOption, WalkerOptions, WalkerValueFilter, WhenElseQueryConditionToken, WhenElseUnaryConditionToken, WhitespaceToken, WrappedValuesToken }; diff --git a/dist/lib/ast/walk.js b/dist/lib/ast/walk.js index d593519c..c7260b95 100644 --- a/dist/lib/ast/walk.js +++ b/dist/lib/ast/walk.js @@ -1,3 +1,5 @@ +import { TOKENS } from '../syntax/constants.js'; + /** * Options for the walk function */ @@ -40,6 +42,8 @@ var WalkerEvent; * @param filter control the walk process * @param reverse walk in reverse order * + * @private + * * ```ts * * import {walk} from '@tbela99/css-parser'; @@ -111,11 +115,19 @@ function* walk(node, filter, reverse) { const parents = [node]; const root = node; const map = new Map(); + let options = filter; let isNumeric = false; + let includeValues = false; let i = 0; + if (options != null && typeof options == "object") { + filter = options.filter; + reverse = options.reverse; + includeValues = options.inludeValues; + } while ((node = parents[i++])) { let option = null; if (filter != null) { + // @ts-ignore option = filter(node); isNumeric = typeof option == "number"; if (isNumeric) { @@ -143,8 +155,16 @@ function* walk(node, filter, reverse) { }, }; } - if ("chi" in node && (!isNumeric || (option & WalkerOptionEnum.IgnoreChildren) === 0)) { - parents.splice(i, 0, ...node.chi[reverse ? "toReversed" : "slice"]()); + if (includeValues) { + if (node[TOKENS] != null) { + parents.splice(i, 0, ...(reverse ? node[TOKENS].toReversed() : node[TOKENS])); + } + else if (Array.isArray(node.val)) { + parents.splice(i, 0, ...(reverse ? node.val.toReversed() : node.val)); + } + } + if (node["chi"] != null && (!isNumeric || (option & WalkerOptionEnum.IgnoreChildren) === 0)) { + parents.splice(i, 0, ...(reverse ? node.chi.toReversed() : node.chi)); for (const child of node.chi) { map.set(child, node); } @@ -228,11 +248,6 @@ function* walkValues(values, root = null, filter, reverse) { continue; } used.add(value); - // parents.length = 0; - // while (node != null) { - // parents.push(node); - // node = map.get(node) ?? null; - // } if (filter.fn != null && eventType & WalkerEvent.Enter) { const isValid = filter.type == null || value.typ == filter.type || diff --git a/dist/lib/parser/parse.js b/dist/lib/parser/parse.js index 3dffa43c..642a7b1d 100644 --- a/dist/lib/parser/parse.js +++ b/dist/lib/parser/parse.js @@ -291,11 +291,23 @@ const generateSyncScopedName = memoize((localName, filePath, pattern, hashLength // if leading char is digit, prefix underscore (very rare) return (/^[0-9]/.test(result) ? "_" : "") + result; }); -function parseVisitors(options, valuesHandlers, preValuesHandlers, postValuesHandlers, errors, visitorsHandlersMap, preVisitorsHandlersMap, postVisitorsHandlersMap) { - const visitors = Object.entries(options.visitor); +/** + * + * @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]; @@ -383,6 +395,29 @@ function parseVisitors(options, valuesHandlers, preValuesHandlers, postValuesHan 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 @@ -449,24 +484,18 @@ 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; let currentItemIndex; + // ast[ROOT] = ast; + ast[LOC] = { + sta: 0, + end: 0, + srcId: options.source.id, + }; // if (Array.isArray(iter)) { // // @ts-expect-error // iter = iter[Symbol.iterator]() as Iterator; @@ -571,49 +600,23 @@ function doParseSync(iter, options = {}) { } let replacement; if (options.visitor != null) { - valuesHandlers = new Map(); - preValuesHandlers = new Map(); - postValuesHandlers = new Map(); - preVisitorsHandlersMap = new Map(); - visitorsHandlersMap = new Map(); - postVisitorsHandlersMap = new Map(); - parseVisitors(options, valuesHandlers, preValuesHandlers, postValuesHandlers, errors, visitorsHandlersMap, preVisitorsHandlersMap, postVisitorsHandlersMap); + const handlers = []; + const visitors = parseVisitors(options.visitor, errors); + const subNodes = []; let parens; let genericKey; - const handlers = []; - 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); - } let nodes = new Array(stats.tokensCount); - const subNodes = []; let i; let k; let j; let freeBlock = 1; - const includeTokens = preValuesHandlers.size > 0 || valuesHandlers.size > 0 || postValuesHandlers.size > 0; nodes[0] = ast; for (i = 0; i < nodes.length; i++) { if (nodes[i] == null) { break; } subNodes.length = 0; - if (includeTokens) { + if (visitors.includeTokens) { switch (nodes[i].typ) { case EnumToken.RuleNodeType: case EnumToken.AtRuleNodeType: @@ -661,7 +664,7 @@ function doParseSync(iter, options = {}) { : nodes[i].typ == EnumToken.KeyframesAtRuleNodeType ? camelize(nodes[i].val) : null; - for (const map of allHandlers) { + for (const map of visitors.allHandlers) { // @ts-ignore if (genericKey != null && map.has(genericKey)) { // @ts-ignore @@ -772,19 +775,6 @@ function doParseSync(iter, options = {}) { } } } - while (stack.length > 0 && context != ast) { - const previousNode = stack.pop(); - context = (stack[stack.length - 1] ?? ast); - // remove empty nodes - if (options.removeEmpty && - previousNode != null && - previousNode.chi.length == 0 && - context.chi[context.chi.length - 1] == previousNode) { - context.chi.pop(); - continue; - } - break; - } if (options.minify) { if (ast.chi.length > 0) { let passes = options.pass ?? 1; @@ -1357,18 +1347,6 @@ async function doParse(iter, options = {}) { }; let tokens = []; let context = ast; - // ast[ROOT] = ast; - ast[LOC] = { - sta: 0, - end: 0, - srcId: options.source.id, - }; - let valuesHandlers; - let preValuesHandlers; - let postValuesHandlers; - let preVisitorsHandlersMap; - let visitorsHandlersMap; - let postVisitorsHandlersMap; const imports = []; let item; let node; @@ -1376,6 +1354,12 @@ async function doParse(iter, options = {}) { let isAsync = typeof iter[Symbol.asyncIterator] === "function"; let parensMatch = 0; let curlyBracketMatch = 0; + // ast[ROOT] = ast; + ast[LOC] = { + sta: 0, + end: 0, + srcId: options.source.id, + }; if (Array.isArray(iter)) { // @ts-expect-error iter = iter[Symbol.iterator](); @@ -1537,64 +1521,24 @@ async function doParse(iter, options = {}) { ast = expand(ast); } let replacement; - while (stack.length > 0 && context != ast) { - const previousNode = stack.pop(); - context = (stack[stack.length - 1] ?? ast); - previousNode[PARENT] = context; - // 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.visitor != null) { - valuesHandlers = new Map(); - preValuesHandlers = new Map(); - postValuesHandlers = new Map(); - preVisitorsHandlersMap = new Map(); - visitorsHandlersMap = new Map(); - postVisitorsHandlersMap = new Map(); - parseVisitors(options, valuesHandlers, preValuesHandlers, postValuesHandlers, errors, visitorsHandlersMap, preVisitorsHandlersMap, postVisitorsHandlersMap); let parens; let genericKey; const handlers = []; - 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); - } + const visitors = parseVisitors(options.visitor, errors); let nodes = new Array(stats.tokensCount); const subNodes = []; let i; let k; let j; let freeblock = 1; - const includeTokens = preValuesHandlers.size > 0 || valuesHandlers.size > 0 || postValuesHandlers.size > 0; nodes[0] = ast; for (i = 0; i < nodes.length; i++) { if (nodes[i] == null) { break; } subNodes.length = 0; - if (includeTokens) { + if (visitors.includeTokens) { switch (nodes[i].typ) { case EnumToken.RuleNodeType: case EnumToken.AtRuleNodeType: @@ -1642,7 +1586,7 @@ async function doParse(iter, options = {}) { : nodes[i].typ == EnumToken.KeyframesAtRuleNodeType ? camelize(nodes[i].val) : null; - for (const map of allHandlers) { + for (const map of visitors.allHandlers) { // @ts-ignore if (genericKey != null && map.has(genericKey)) { // @ts-ignore @@ -3380,10 +3324,7 @@ function parseString(src, options = { parseColor: true }, errors) { } const result = parseTokens(mapped, options, errors); // remove EOF token - result.pop(); - if (result.at(-1)?.typ === EnumToken.WhitespaceTokenType) { - result.pop(); - } + result.splice(result.length - (result[result.length - 2]?.typ === EnumToken.WhitespaceTokenType ? 2 : 1), 2); return result; } /** @@ -3474,7 +3415,6 @@ function parseTokens(tokens, options, errors) { node, location: options.source.getSourceLocation(node[LOC].sta), }); - // return []; continue; } index = tokens.indexOf(stack.at(-1)); diff --git a/dist/lib/renderer/sourcemap/sourcemap.js b/dist/lib/renderer/sourcemap/sourcemap.js index 0099b322..ba38bf05 100644 --- a/dist/lib/renderer/sourcemap/sourcemap.js +++ b/dist/lib/renderer/sourcemap/sourcemap.js @@ -53,6 +53,7 @@ class SourceMap { /** * * @param sourcemaps + * @private */ constructor(sourcemaps) { if (typeof sourcemaps === "string") { diff --git a/dist/node.js b/dist/node.js index e30a1e25..269a3bd5 100644 --- a/dist/node.js +++ b/dist/node.js @@ -14,7 +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 } from './utils.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'; @@ -163,6 +163,9 @@ function parseSync(...args) { options = opt; stream = input; } + if (options != null) { + validateSyncArguments(options); + } options ??= {}; options.src ??= ""; options.sourcesMap ??= new Map(); @@ -188,7 +191,7 @@ function parseSync(...args) { currentPosition: -1, }; const result = doParseSync(tokenize(options.parseInfo), options); - return !options.module && !options.inputSourceMap ? result : parseResult(result, options); + return !options.module && !options.inputSourceMap && !options.sourcemap ? result : parseResult(result, options); } /** * Transform css diff --git a/dist/utils.d.ts b/dist/utils/sync.d.ts similarity index 52% rename from dist/utils.d.ts rename to dist/utils/sync.d.ts index 17fbc10f..0242b12d 100644 --- a/dist/utils.d.ts +++ b/dist/utils/sync.d.ts @@ -1,4 +1,4 @@ -import type { ParseResult, ParserOptions } from "./@types/index.d.ts"; +import type { ParseResult, ParserOptions, ParserSyncOptions } from "../@types/index.js"; /** * parse result. process input sourcemap * @param result @@ -7,3 +7,4 @@ import type { ParseResult, ParserOptions } from "./@types/index.d.ts"; * @private */ export declare function parseResult(result: ParseResult, options: ParserOptions): ParseResult; +export declare function validateSyncArguments(options: ParserSyncOptions, prefix?: string): void; diff --git a/dist/utils.js b/dist/utils/sync.js similarity index 67% rename from dist/utils.js rename to dist/utils/sync.js index e95f0443..d8b3dff7 100644 --- a/dist/utils.js +++ b/dist/utils/sync.js @@ -1,4 +1,4 @@ -import { EnumToken } from './lib/ast/types.js'; +import { EnumToken } from '../lib/ast/types.js'; /** * parse result. process input sourcemap @@ -45,5 +45,20 @@ function parseResult(result, options) { } return result; } +function validateSyncArguments(options, prefix = "options.") { + const args = Object.entries(options); + let i; + for (i = 0; i < args.length; i++) { + const [key, value] = args[i]; + if (typeof value == 'function') { + if (value instanceof Promise || Object.getPrototypeOf(value).constructor.name == "AsyncFunction") { + throw new Error(`[${prefix + key}]: Async functions are not supported in sync mode. Use parse() or transform() instead.`); + } + } + else if (value != null && typeof value == 'object') { + validateSyncArguments(value, prefix + key + "."); + } + } +} -export { parseResult }; +export { parseResult, validateSyncArguments }; diff --git a/dist/web.js b/dist/web.js index 68753355..eafdd719 100644 --- a/dist/web.js +++ b/dist/web.js @@ -8,7 +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 } from './utils.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'; @@ -155,6 +155,9 @@ function parseSync(...args) { options = opt; stream = input; } + if (options != null) { + validateSyncArguments(options); + } options ??= {}; options.src ??= ""; options.sourcesMap ??= new Map(); @@ -182,7 +185,7 @@ function parseSync(...args) { currentPosition: -1, }; const result = doParseSync(tokenize(options.parseInfo), options); - return !options.module && !options.inputSourceMap ? result : parseResult(result, options); + return !options.module && !options.inputSourceMap && !options.sourcemap ? result : parseResult(result, options); } /** * Transform CSS diff --git a/files/assets/typedoc-custom.css b/files/assets/typedoc-custom.css index 01821819..979dc2d2 100644 --- a/files/assets/typedoc-custom.css +++ b/files/assets/typedoc-custom.css @@ -78,6 +78,17 @@ html[data-theme="dark"] { } } -#main-function-differences ~ table td { - text-align: center; +#main-function-differences ~ table tbody tr { + &:hover { + background-color: var(--transparent-blue) !important; + color: var(--blue-dark) !important; + } + + td { + &:first-child { + text-align: left; + } + + text-align: center; + } } diff --git a/files/plugins.md b/files/plugins.md index 6a003d31..ff9a9048 100644 --- a/files/plugins.md +++ b/files/plugins.md @@ -42,7 +42,7 @@ function toBase64(arraybuffer: Uint8Array) { } function inlineImagesPlugin(maxSize: number, extensions: string[]) { - return async function (node: FunctionURLToken, parent: AstNode) { + 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, @@ -70,7 +70,6 @@ function inlineImagesPlugin(maxSize: number, extensions: string[]) { return; } - // change node type to EnumToken.String Object.assign(t, { typ: EnumToken.StringTokenType, val: `"data:image/${matches[3].toLowerCase()};base64,${toBase64(new Uint8Array(buffer))}"`, @@ -90,9 +89,7 @@ const css = ` `; const result = await transform(css, { - visitor: { - UrlFunctionTokenType: inlineImagesPlugin(maxSize, extensions), - }, + visitor: inlineImagesPlugin(maxSize, extensions), }); console.error(result.code); diff --git a/files/usage.md b/files/usage.md index 9640acf6..eabdc87a 100644 --- a/files/usage.md +++ b/files/usage.md @@ -383,25 +383,22 @@ button { ## 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. - -### Unsupported CSS Module Features - -* The `pattern` parameter does not support the following algorithms: - - * `sha1` - * `sha256` - * `sha384` - * `sha512` -* CSS `composes` does not support composing from a file. -* Importing CSS variables from a file using `@value` is not supported. +### Parsing features comparison + +| Feature | parse() | transform() | transformSync() | ParseSync() | +| ----------------------- | ------- | ----------- | --------------- | ----------- | +| Parse from stream | ✅ | ✅ | ❌ | ❌ | +| Parse from file | ✅ | ✅ | ❌ | ❌ | +| Flatten @import at-rule | ✅ | ✅ | ❌ | ❌ | +| transformSync() | ✅ | ✅ | ❌ | ❌ | + +### CSS Module features comparison + +| 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/src/@types/index.d.ts b/src/@types/index.d.ts index 9e323ba4..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"; @@ -545,7 +550,7 @@ export declare interface ParserSyncOptions * Node visitor * {@link VisitorSyncNodeMap | VisitorSyncNodeMap[]} */ - visitor?: VisitorSyncNodeMap | VisitorSyncNodeMap[]; + visitor?: GenericVisitorAstNodeSyncHandlerMap | VisitorSyncNodeMap | VisitorSyncNodeMap[]; /** * Abort signal * @@ -610,7 +615,11 @@ export declare interface ParserOptions extends ParserSyncOptions, ModuleAsyncOpt * Node visitor * {@link VisitorNodeMap | VisitorNodeMap[]} */ - visitor?: VisitorNodeMap | VisitorNodeMap[]; + visitor?: + | GenericVisitorAstNodeSyncHandlerMap + | GenericVisitorAstNodeHandlerMap + | VisitorNodeMap + | VisitorNodeMap[]; } /** diff --git a/src/@types/walker.d.ts b/src/@types/walker.d.ts index c8868e69..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 */ diff --git a/src/lib/ast/walk.ts b/src/lib/ast/walk.ts index e656fdba..b8e3bc52 100644 --- a/src/lib/ast/walk.ts +++ b/src/lib/ast/walk.ts @@ -8,9 +8,11 @@ import type { WalkAttributesResult, WalkerFilter, WalkerOption, + WalkerOptions, WalkerValueFilter, WalkResult, } from "../../@types/index.d.ts"; +import { TOKENS } from "../syntax/constants.ts"; import { EnumToken } from "./types.ts"; /** @@ -108,6 +110,157 @@ export enum WalkerEvent { * } * * const result = await transform(css); + * for (const {node} of walk(result.ast, filter, false)) { + * + * console.error([EnumToken[node.typ]]); + * } + * + * // [ "StyleSheetNodeType" ] + * // [ "RuleNodeType" ] + * // [ "DeclarationNodeType" ] + * // [ "RuleNodeType" ] + * // [ "DeclarationNodeType" ] + * // [ "RuleNodeType" ] + * // [ "DeclarationNodeType" ] + * ``` + */ +export function walk(node: AstNode, filter?: WalkerFilter | null, reverse?: boolean): Generator; + +/** + * Walk ast nodes + * @param node initial node + * @param filter control the walk process + * + * ```ts + * + * import {walk} from '@tbela99/css-parser'; + * + * const css = ` + * body { color: color(from var(--base-color) display-p3 r calc(g + 0.24) calc(b + 0.15)); } + * + * html, + * body { + * line-height: 1.474; + * } + * + * .ruler { + * + * height: 10px; + * } + * `; + * + * for (const {node, parent, root} of walk(ast)) { + * + * // do something with node + * } + * ``` + * + * Using a {@link filter} function to control the ast traversal. the filter function returns a value of type {@link WalkerOption}. + * + * ```ts + * import {EnumToken, transform, walk, WalkerOptionEnum} from '@tbela99/css-parser'; + * + * const css = ` + * body { color: color(from var(--base-color) display-p3 r calc(g + 0.24) calc(b + 0.15)); } + * + * html, + * body { + * line-height: 1.474; + * } + * + * .ruler { + * + * height: 10px; + * } + * `; + * + * function filter(node) { + * + * if (node.typ == EnumToken.AstRule && node.sel.includes('html')) { + * + * // skip the children of the current node + * return WalkerOptionEnum.IgnoreChildren; + * } + * } + * + * const result = await transform(css); + * for (const {node} of walk(result.ast, {filter, reverse: false})) { + * + * console.error([EnumToken[node.typ]]); + * } + * + * // [ "StyleSheetNodeType" ] + * // [ "RuleNodeType" ] + * // [ "DeclarationNodeType" ] + * // [ "RuleNodeType" ] + * // [ "DeclarationNodeType" ] + * // [ "RuleNodeType" ] + * // [ "DeclarationNodeType" ] + * ``` + */ +export function walk(node: AstNode, filter?: WalkerOptions | null): Generator; + +/** + * Walk ast nodes + * @param node initial node + * @param filter control the walk process + * @param reverse walk in reverse order + * + * @private + * + * ```ts + * + * import {walk} from '@tbela99/css-parser'; + * + * const css = ` + * body { color: color(from var(--base-color) display-p3 r calc(g + 0.24) calc(b + 0.15)); } + * + * html, + * body { + * line-height: 1.474; + * } + * + * .ruler { + * + * height: 10px; + * } + * `; + * + * for (const {node, parent, root} of walk(ast)) { + * + * // do something with node + * } + * ``` + * + * Using a {@link filter} function to control the ast traversal. the filter function returns a value of type {@link WalkerOption}. + * + * ```ts + * import {EnumToken, transform, walk, WalkerOptionEnum} from '@tbela99/css-parser'; + * + * const css = ` + * body { color: color(from var(--base-color) display-p3 r calc(g + 0.24) calc(b + 0.15)); } + * + * html, + * body { + * line-height: 1.474; + * } + * + * .ruler { + * + * height: 10px; + * } + * `; + * + * function filter(node) { + * + * if (node.typ == EnumToken.AstRule && node.sel.includes('html')) { + * + * // skip the children of the current node + * return WalkerOptionEnum.IgnoreChildren; + * } + * } + * + * const result = await transform(css); * for (const {node} of walk(result.ast, filter)) { * * console.error([EnumToken[node.typ]]); @@ -122,18 +275,31 @@ export enum WalkerEvent { * // [ "DeclarationNodeType" ] * ``` */ -export function* walk(node: AstNode, filter?: WalkerFilter | null, reverse?: boolean): Generator { +export function* walk( + node: AstNode, + filter?: WalkerFilter | WalkerOptions | null, + reverse?: boolean, +): Generator { const parents: AstNode[] = [node]; const root: AstRuleList = node; const map: Map = new Map(); + let options: WalkerOptions | null = filter as WalkerOptions | null; let isNumeric: boolean = false; + let includeValues: boolean = false; let i: number = 0; + if (options != null && typeof options == "object") { + filter = options.filter as WalkerFilter; + reverse = options.reverse; + includeValues = options.inludeValues as boolean; + } + while ((node = parents[i++])) { let option: WalkerOption = null; if (filter != null) { + // @ts-ignore option = filter(node); isNumeric = typeof option == "number"; @@ -166,8 +332,16 @@ export function* walk(node: AstNode, filter?: WalkerFilter | null, reverse?: boo }; } - if ("chi" in node && (!isNumeric || ((option as number) & WalkerOptionEnum.IgnoreChildren) === 0)) { - parents.splice(i, 0, ...((node).chi![reverse ? "toReversed" : "slice"]())); + if (includeValues) { + if (node[TOKENS] != null) { + parents.splice(i, 0, ...(reverse ? node[TOKENS]!.toReversed() : node[TOKENS])); + } else if (Array.isArray(node.val)) { + parents.splice(i, 0, ...(reverse ? node.val.toReversed() : node.val)); + } + } + + if (node["chi"] != null && (!isNumeric || ((option as number) & WalkerOptionEnum.IgnoreChildren) === 0)) { + parents.splice(i, 0, ...(reverse ? node.chi!.toReversed() : node.chi)); for (const child of (node).chi) { map.set(child, node); @@ -270,12 +444,6 @@ export function* walkValues( } used.add(value); - // parents.length = 0; - - // while (node != null) { - // parents.push(node); - // node = map.get(node) ?? null; - // } if (filter.fn != null && eventType & WalkerEvent.Enter) { const isValid: boolean = diff --git a/src/lib/parser/parse.ts b/src/lib/parser/parse.ts index fb993136..c8f5d95f 100644 --- a/src/lib/parser/parse.ts +++ b/src/lib/parser/parse.ts @@ -10,8 +10,8 @@ import type { AstAtRule, AstComment, AstDeclaration, - AstKeyframesRule, AstKeyframesAtRule, + AstKeyframesRule, AstNode, AstRule, AstRuleList, @@ -27,6 +27,7 @@ import type { ErrorDescription, FunctionToken, GenericVisitorAstNodeHandlerMap, + GenericVisitorAstNodeSyncHandlerMap, GenericVisitorHandler, GenericVisitorResult, IdentToken, @@ -44,6 +45,7 @@ import type { Token, TokenizeResult, UrlToken, + VisitorNodeMap, WhitespaceToken, } from "../../@types/index.d.ts"; import { ERRORS, LOC, pageMarginBoxType, PARENT, ROOT, STATE, TOKENS, tokensfuncDefMap } from "../syntax/constants.ts"; @@ -412,29 +414,36 @@ export const generateSyncScopedName = memoize( }, ) as (localName: string, filePath: string, pattern: string, hashLength?: number) => string; +/** + * + * @param visitorsDef + * @param errors + * @private + */ function parseVisitors( - options: ParserSyncOptions | ParserOptions, - valuesHandlers: Map>>, - preValuesHandlers: Map>>, - postValuesHandlers: Map>>, + visitorsDef: GenericVisitorHandler | GenericVisitorAstNodeSyncHandlerMap | VisitorNodeMap | VisitorNodeMap[], errors: ErrorDescription[], - visitorsHandlersMap: Map< +) { + 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>> - >, - preVisitorsHandlersMap: Map< + > = new Map(); + const preVisitorsHandlersMap: Map< "Declaration" | "Rule" | "AtRule" | "KeyframesRule" | "KeyframesAtRule", Array | Record>>> - >, - postVisitorsHandlersMap: Map< + > = new Map(); + const postVisitorsHandlersMap: Map< "Declaration" | "Rule" | "AtRule" | "KeyframesRule" | "KeyframesAtRule", Array | Record>>> - >, -) { - const visitors = Object.entries(options.visitor!); - let key: string; - let value: any; - let i: number; + > = new Map(); for (i = 0; i < visitors.length; i++) { key = visitors[i][0]; @@ -553,6 +562,50 @@ function parseVisitors( 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, + }; } /** @@ -632,46 +685,27 @@ 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>>> - >; - let item: TokenizeResult; let node: AstAtRule | AstRule | AstKeyframesRule | AstKeyframesAtRule | AstDeclaration | AstComment | null; // @ts-ignore ignore error let parensMatch: number = 0; let curlyBracketMatch: number = 0; - let currentItemIndex: number; + // ast[ROOT] = ast; + ast[LOC] = { + sta: 0, + end: 0, + srcId: options.source!.id, + }; + // if (Array.isArray(iter)) { // // @ts-expect-error // iter = iter[Symbol.iterator]() as Iterator; // } - for (currentItemIndex = 0; currentItemIndex < (iter as Array).length; currentItemIndex++ - ) { + for (currentItemIndex = 0; currentItemIndex < (iter as Array).length; currentItemIndex++) { item = (iter as Array)[currentItemIndex]; stats.bytesIn = item.bytesIn; stats.tokensCount++; @@ -793,77 +827,18 @@ export function doParseSync( let replacement: GenericVisitorResult; 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(); - parseVisitors( - options, - valuesHandlers, - preValuesHandlers, - postValuesHandlers, - errors, - visitorsHandlersMap, - preVisitorsHandlersMap, - postVisitorsHandlersMap, - ); + const handlers = [] as Array>; + const visitors = parseVisitors(options.visitor, errors); + const subNodes: Array = []; let parens: Token[] | null; let genericKey: string | null; - const handlers = [] as Array>; - 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!); - } - let nodes: AstNode[] | null = new Array(stats.tokensCount); - const subNodes: Array = []; let i: number; let k: number; let j: number; let freeBlock: number = 1; - const includeTokens: boolean = - preValuesHandlers!.size > 0 || valuesHandlers!.size > 0 || postValuesHandlers!.size > 0; - nodes[0] = ast; for (i = 0; i < nodes.length; i++) { @@ -872,7 +847,7 @@ export function doParseSync( } subNodes.length = 0; - if (includeTokens) { + if (visitors.includeTokens) { switch (nodes[i].typ) { case EnumToken.RuleNodeType: case EnumToken.AtRuleNodeType: @@ -930,7 +905,7 @@ export function doParseSync( ? camelize((nodes[i] as AstKeyframesAtRule).val) : null; - for (const map of allHandlers) { + for (const map of visitors.allHandlers) { // @ts-ignore if (genericKey != null && map!.has(genericKey)) { // @ts-ignore @@ -1062,24 +1037,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); @@ -1816,30 +1773,6 @@ export async function doParse( let tokens: Token[] = []; let context: AstRuleList = ast; - // ast[ROOT] = ast; - - ast[LOC] = { - sta: 0, - end: 0, - srcId: options.source!.id, - }; - - let valuesHandlers: Map>>; - let preValuesHandlers: Map>>; - let postValuesHandlers: Map>>; - let preVisitorsHandlersMap: Map< - "Declaration" | "Rule" | "AtRule" | "KeyframesRule" | "KeyframesAtRule", - Array | Record>>> - >; - let visitorsHandlersMap: Map< - "Declaration" | "Rule" | "AtRule" | "KeyframesRule" | "KeyframesAtRule", - Array | Record>> - >; - let postVisitorsHandlersMap: Map< - "Declaration" | "Rule" | "AtRule" | "KeyframesRule" | "KeyframesAtRule", - Array | Record>>> - >; - const imports: AstAtRule[] = []; let item: TokenizeResult; @@ -1850,6 +1783,14 @@ export async function doParse( let parensMatch: number = 0; let curlyBracketMatch: number = 0; + // ast[ROOT] = ast; + + ast[LOC] = { + sta: 0, + end: 0, + srcId: options.source!.id, + }; + if (Array.isArray(iter)) { // @ts-expect-error iter = iter[Symbol.iterator]() as Iterator; @@ -2051,89 +1992,12 @@ export async function doParse( let replacement: GenericVisitorResult; - while (stack.length > 0 && context != ast) { - const previousNode: AstAtRule | AstRule = stack.pop() as AstAtRule | AstRule; - context = (stack[stack.length - 1] ?? ast) as AstRuleList; - - previousNode[PARENT] = context; - - // 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.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(); - - parseVisitors( - options as ParserSyncOptions, - valuesHandlers, - preValuesHandlers, - postValuesHandlers, - errors, - visitorsHandlersMap, - preVisitorsHandlersMap, - postVisitorsHandlersMap, - ); - let parens: Token[] | null; let genericKey: string | null; const handlers = [] as Array>; - 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!); - } + const visitors = parseVisitors(options.visitor, errors); let nodes: AstNode[] | null = new Array(stats.tokensCount); const subNodes: Array = []; @@ -2141,9 +2005,6 @@ export async function doParse( let k: number; let j: number; let freeblock: number = 1; - const includeTokens: boolean = - preValuesHandlers!.size > 0 || valuesHandlers!.size > 0 || postValuesHandlers!.size > 0; - nodes[0] = ast; for (i = 0; i < nodes.length; i++) { @@ -2152,7 +2013,7 @@ export async function doParse( } subNodes.length = 0; - if (includeTokens) { + if (visitors.includeTokens) { switch (nodes[i].typ) { case EnumToken.RuleNodeType: case EnumToken.AtRuleNodeType: @@ -2210,7 +2071,7 @@ export async function doParse( ? camelize((nodes[i] as AstKeyframesAtRule).val) : null; - for (const map of allHandlers) { + for (const map of visitors.allHandlers) { // @ts-ignore if (genericKey != null && map!.has(genericKey)) { // @ts-ignore @@ -4382,21 +4243,17 @@ export function parseString( currentPosition: -1, }; - const tokenResults = tokenize(parseInfo); - const mapped = []; + const tokenResults: TokenizeResult[] = tokenize(parseInfo); + const mapped: Token[] = []; for (const token of tokenResults) { mapped.push(token.token); } - const result = parseTokens(mapped, options, errors); + const result: Token[] = parseTokens(mapped, options, errors); // remove EOF token - result.pop(); - - if (result.at(-1)?.typ === EnumToken.WhitespaceTokenType) { - result.pop(); - } + result.splice(result.length - (result[result.length - 2]?.typ === EnumToken.WhitespaceTokenType ? 2 : 1), 2); return result; } @@ -4514,7 +4371,6 @@ export function parseTokens( node, location: options.source!.getSourceLocation(node[LOC]!.sta), }); - // return []; continue; } diff --git a/src/lib/renderer/sourcemap/sourcemap.ts b/src/lib/renderer/sourcemap/sourcemap.ts index a53747fc..c6dc31ca 100644 --- a/src/lib/renderer/sourcemap/sourcemap.ts +++ b/src/lib/renderer/sourcemap/sourcemap.ts @@ -71,6 +71,7 @@ export class SourceMap { /** * * @param sourcemaps + * @private */ constructor(sourcemaps?: SourceMapObject | string) { if (typeof sourcemaps === "string") { diff --git a/src/lib/validation/match.ts b/src/lib/validation/match.ts index 44164031..9341109d 100644 --- a/src/lib/validation/match.ts +++ b/src/lib/validation/match.ts @@ -60,8 +60,8 @@ export const funcTypes: EnumToken[] = [ /** * trim leading and trailing whitespace - * @param tokens - * @returns + * @param tokens + * @returns */ export function trimArray(tokens: Token[]): Token[] { while (tokens[0]?.typ === EnumToken.WhitespaceTokenType) { @@ -77,8 +77,8 @@ export function trimArray(tokens: Token[]): Token[] { /** * is a media feature - * @param featureName - * @returns + * @param featureName + * @returns */ export function isMFName(featureName: string): boolean { // @ts-expect-error @@ -206,8 +206,8 @@ export function isMFValue( /** * create validation context - * @param tokens - * @returns + * @param tokens + * @returns */ export function createValidationContext(tokens: Token[]): ValidationContext { tokens = trimArray(tokens.filter((t) => t.typ !== EnumToken.CommentTokenType)); @@ -412,11 +412,11 @@ export function createValidationContext(tokens: Token[]): ValidationContext { /** * match selector syntax - * @param stream - * @param errors - * @param options - * @param nested - * @returns + * @param stream + * @param errors + * @param options + * @param nested + * @returns */ export function matchSelectorSyntax( stream: Token[], @@ -994,10 +994,10 @@ export function matchSelectorSyntax( /** * matches all syntaxes - * @param syntaxes - * @param context - * @param options - * @returns + * @param syntaxes + * @param context + * @param options + * @returns */ export function matchAllSyntaxes( syntaxes: ValidationToken[] | null, @@ -1053,9 +1053,9 @@ export function matchAllSyntaxes( /** * matches a list of syntaxes - * @param syntax - * @param context - * @param options + * @param syntax + * @param context + * @param options * @returns */ function matchListSyntax( @@ -1121,9 +1121,9 @@ function matchListSyntax( /** * matches a list of syntaxes - * @param syntax - * @param context - * @param options + * @param syntax + * @param context + * @param options * @returns */ export function matchOccurenceSyntax( @@ -1183,10 +1183,10 @@ export function matchOccurenceSyntax( /** * matches a list of syntaxes - * @param syntaxes - * @param context - * @param options - * @returns + * @param syntaxes + * @param context + * @param options + * @returns */ function matchSyntax( syntaxes: ValidationToken[] | null, @@ -2023,10 +2023,10 @@ function matchSyntax( /** * matches a column of syntaxes - * @param syntax - * @param context - * @param options - * @returns + * @param syntax + * @param context + * @param options + * @returns */ function matchColumnSyntax( syntax: ValidationColumnToken, @@ -2080,10 +2080,10 @@ function matchColumnSyntax( /** * matches an ampersand of syntaxes - * @param syntax - * @param context - * @param options - * @returns + * @param syntax + * @param context + * @param options + * @returns */ function matchAmpersandSyntax( syntax: ValidationAmpersandToken, @@ -2116,10 +2116,10 @@ function matchAmpersandSyntax( /** * matches a property - * @param property - * @param context - * @param options - * @returns + * @param property + * @param context + * @param options + * @returns */ function matchProperty( property: ValidationPropertyToken, @@ -3075,10 +3075,10 @@ function matchProperty( /** * matches a repeatable syntax - * @param syntax - * @param context - * @param options - * @returns + * @param syntax + * @param context + * @param options + * @returns */ function matchRepeatableSyntax( syntax: ValidationToken, diff --git a/src/node.ts b/src/node.ts index 85787661..49fdddcc 100644 --- a/src/node.ts +++ b/src/node.ts @@ -27,7 +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 } from "./utils.ts"; +import { parseResult, validateSyncArguments } from "./utils/sync.ts"; export type * from "./@types/index.d.ts"; export type * from "./@types/ast.d.ts"; @@ -282,7 +282,12 @@ export function parseSync( stream = input; } + if (options != null) { + validateSyncArguments(options); + } + options ??= {}; + options.src ??= ""; options.sourcesMap ??= new Map(); @@ -312,7 +317,7 @@ export function parseSync( } as ParseInfo; const result = doParseSync(tokenize(options.parseInfo), options) as ParseResult; - return !options.module && !options.inputSourceMap ? result : parseResult(result, options); + return !options.module && !options.inputSourceMap && !options.sourcemap ? result : parseResult(result, options); } /** diff --git a/src/utils.ts b/src/utils/sync.ts similarity index 65% rename from src/utils.ts rename to src/utils/sync.ts index 11387fff..e21ea207 100644 --- a/src/utils.ts +++ b/src/utils/sync.ts @@ -1,5 +1,5 @@ -import type { AstComment, ParseResult, ParserOptions } from "./@types/index.d.ts"; -import { EnumToken } from "./lib/ast/types.ts"; +import type { AstComment, ParseResult, ParserOptions, ParserSyncOptions } from "../@types/index.js"; +import { EnumToken } from "../lib/ast/types.ts"; /** * parse result. process input sourcemap @@ -52,3 +52,23 @@ export function parseResult(result: ParseResult, options: ParserOptions): ParseR return result; } + +export function validateSyncArguments(options: ParserSyncOptions, prefix: string = "options."): void { + const args = Object.entries(options); + + let i: number; + + for (i = 0; i < args.length; i++) { + const [key, value] = args[i]; + + if (typeof value == "function") { + if (value instanceof Promise || Object.getPrototypeOf(value).constructor.name == "AsyncFunction") { + throw new Error( + `[${prefix + key}]: Async functions are not supported in sync mode. Use parse() or transform() instead.`, + ); + } + } else if (value != null && typeof value == "object") { + validateSyncArguments(value, prefix + key + "."); + } + } +} diff --git a/src/web.ts b/src/web.ts index 9d1b92e7..69abccab 100644 --- a/src/web.ts +++ b/src/web.ts @@ -22,7 +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 } from "./utils.ts"; +import { parseResult, validateSyncArguments } from "./utils/sync.ts"; export type * from "./@types/index.d.ts"; export type * from "./@types/ast.d.ts"; @@ -298,7 +298,12 @@ export function parseSync( stream = input; } + if (options != null) { + validateSyncArguments(options); + } + options ??= {}; + options.src ??= ""; options.sourcesMap ??= new Map(); @@ -331,7 +336,7 @@ export function parseSync( } as ParseInfo; const result = doParseSync(tokenize(options.parseInfo), options); - return !options.module && !options.inputSourceMap ? result : parseResult(result, options); + return !options.module && !options.inputSourceMap && !options.sourcemap ? result : parseResult(result, options); } /** diff --git a/test/specs/code/visitors.js b/test/specs/code/visitors.js index bda89685..66a99817 100644 --- a/test/specs/code/visitors.js +++ b/test/specs/code/visitors.js @@ -492,5 +492,53 @@ html,body { } }`); }); + + 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"; + } + }, + function ColorTokenType(color) { + return { + typ: EnumToken.Color, + val: "red", + kin: ColorType.HEX, + }; + }, + ], + }; + + return transform(css, options).then((result) => + expect(result.code).equals(`body { + color: red +} +html,body { + line-height: 1.474 +} +.ruler { + width: 10px; + background-color: red +}`), + ); + }); }); }