diff --git a/package-lock.json b/package-lock.json index 40750c169..b45f8052f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "workspaces": [ "packages/css-tokenizer", "packages/css-parser-algorithms", + "packages/cascade-layer-name-parser", "packages/media-query-list-parser", "packages/*", "plugins/postcss-progressive-custom-properties", @@ -1884,6 +1885,10 @@ "resolved": "packages/base-cli", "link": true }, + "node_modules/@csstools/cascade-layer-name-parser": { + "resolved": "packages/cascade-layer-name-parser", + "link": true + }, "node_modules/@csstools/css-has-pseudo-experimental": { "resolved": "experimental/css-has-pseudo", "link": true @@ -6908,6 +6913,22 @@ "postcss": "^8.4" } }, + "packages/cascade-layer-name-parser": { + "name": "@csstools/cascade-layer-name-parser", + "version": "1.0.0", + "license": "MIT", + "engines": { + "node": "^14 || ^16 || >=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^1.0.0", + "@csstools/css-tokenizer": "^1.0.0" + } + }, "packages/css-parser-algorithms": { "name": "@csstools/css-parser-algorithms", "version": "1.0.0", @@ -7284,6 +7305,7 @@ "version": "9.0.1", "license": "MIT", "dependencies": { + "@csstools/cascade-layer-name-parser": "^1.0.0", "@csstools/css-parser-algorithms": "^1.0.0", "@csstools/css-tokenizer": "^1.0.0", "@csstools/media-query-list-parser": "^1.0.0" @@ -9062,6 +9084,10 @@ "version": "file:packages/base-cli", "requires": {} }, + "@csstools/cascade-layer-name-parser": { + "version": "file:packages/cascade-layer-name-parser", + "requires": {} + }, "@csstools/css-has-pseudo-experimental": { "version": "file:experimental/css-has-pseudo", "requires": {} @@ -11761,6 +11787,7 @@ "postcss-custom-media": { "version": "file:plugins/postcss-custom-media", "requires": { + "@csstools/cascade-layer-name-parser": "^1.0.0", "@csstools/css-parser-algorithms": "^1.0.0", "@csstools/css-tokenizer": "^1.0.0", "@csstools/media-query-list-parser": "^1.0.0" diff --git a/package.json b/package.json index 03a1c0037..396ae0e04 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "workspaces": [ "packages/css-tokenizer", "packages/css-parser-algorithms", + "packages/cascade-layer-name-parser", "packages/media-query-list-parser", "packages/*", "plugins/postcss-progressive-custom-properties", diff --git a/packages/cascade-layer-name-parser/.gitignore b/packages/cascade-layer-name-parser/.gitignore new file mode 100644 index 000000000..8b84de034 --- /dev/null +++ b/packages/cascade-layer-name-parser/.gitignore @@ -0,0 +1,6 @@ +node_modules +package-lock.json +yarn.lock +*.result.css +*.result.css.map +*.result.json diff --git a/packages/cascade-layer-name-parser/.nvmrc b/packages/cascade-layer-name-parser/.nvmrc new file mode 100644 index 000000000..f0b10f153 --- /dev/null +++ b/packages/cascade-layer-name-parser/.nvmrc @@ -0,0 +1 @@ +v16.13.1 diff --git a/packages/cascade-layer-name-parser/CHANGELOG.md b/packages/cascade-layer-name-parser/CHANGELOG.md new file mode 100644 index 000000000..1cceb26a8 --- /dev/null +++ b/packages/cascade-layer-name-parser/CHANGELOG.md @@ -0,0 +1,3 @@ +### 1.0.0 (Unreleased) + +- Initial version diff --git a/packages/cascade-layer-name-parser/LICENSE.md b/packages/cascade-layer-name-parser/LICENSE.md new file mode 100644 index 000000000..af5411fa2 --- /dev/null +++ b/packages/cascade-layer-name-parser/LICENSE.md @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright 2022 Romain Menke, Antonio Laguna + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/packages/cascade-layer-name-parser/README.md b/packages/cascade-layer-name-parser/README.md new file mode 100644 index 000000000..ab897613f --- /dev/null +++ b/packages/cascade-layer-name-parser/README.md @@ -0,0 +1,32 @@ +# Cascade Layer Name Parser + +[npm version][npm-url] +[Build Status][cli-url] +[Discord][discord] + +## Usage + +Add [Cascade Layer Name Parser] to your project: + +```bash +npm install postcss @csstools/cascade-layer-name-parser @csstools/css-parser-algorithms @csstools/css-tokenizer --save-dev +``` + +[Cascade Layer Name Parser] depends on our CSS tokenizer and parser algorithms. +It must be used together with `@csstools/css-tokenizer` and `@csstools/css-parser-algorithms`. + +```ts +import { parse } from '@csstools/cascade-layer-name-parser'; + +const layerNames = parse('layer-name, foo.bar'); +layerNames.forEach((layerName) => { + console.log(layerName.name()) // "foo.bar" + console.log(layerName.segments()) // ["foo", "bar"] +}); +``` + +[cli-url]: https://github.com/csstools/postcss-plugins/actions/workflows/test.yml?query=workflow/test +[discord]: https://discord.gg/bUadyRwkJS +[npm-url]: https://www.npmjs.com/package/@csstools/cascade-layer-name-parser + +[Cascade Layer Name Parser]: https://github.com/csstools/postcss-plugins/tree/main/packages/cascade-layer-name-parser diff --git a/packages/cascade-layer-name-parser/dist/index.cjs b/packages/cascade-layer-name-parser/dist/index.cjs new file mode 100644 index 000000000..bc1f8c66a --- /dev/null +++ b/packages/cascade-layer-name-parser/dist/index.cjs @@ -0,0 +1 @@ +"use strict";var e=require("@csstools/css-tokenizer"),t=require("@csstools/css-parser-algorithms");class LayerName{parts;constructor(e){this.parts=e}tokens(){return[...this.parts]}slice(t,n){const r=[];for(let t=0;tt[0]===e.TokenType.Ident||t[0]===e.TokenType.Delim)),n,...t.parts.filter((t=>t[0]===e.TokenType.Ident||t[0]===e.TokenType.Delim))])}segments(){return this.parts.filter((t=>t[0]===e.TokenType.Ident)).map((e=>e[4].value))}name(){return this.parts.filter((t=>t[0]===e.TokenType.Ident||t[0]===e.TokenType.Delim)).map((e=>e[1])).join("")}equal(e){const t=this.segments(),n=e.segments();if(t.length!==n.length)return!1;for(let e=0;e{}),genericError=e=>({message:`Invalid cascade layer name. ${e}`,start:n[0][2],end:n[n.length-1][3],state:["6.4.2. Layer Naming and Nesting","Layer name syntax"," = [ '.' ]*"]}),a=[];for(let n=0;ne.tokens()));let p,l=!1,m=!1;for(let t=0;t{const n=t.segments();e:for(let r=0;r=a&&(o=t,a=r)}-1===o?e.push(n):e.splice(o+1,0,n)}})),e},exports.parse=function parse(t,n){const r=e.tokenizer({css:t},{commentsAreTokens:!0,onParseError:null==n?void 0:n.onParseError}),s=[];for(;!r.endOfFile();)s.push(r.nextToken());return s.push(r.nextToken()),parseFromTokens(s,n)},exports.parseFromTokens=parseFromTokens; diff --git a/packages/cascade-layer-name-parser/dist/index.d.ts b/packages/cascade-layer-name-parser/dist/index.d.ts new file mode 100644 index 000000000..2592bc2e1 --- /dev/null +++ b/packages/cascade-layer-name-parser/dist/index.d.ts @@ -0,0 +1,3 @@ +export { LayerName } from './nodes/layer-name'; +export { addLayerToModel } from './util/model'; +export { parse, parseFromTokens } from './parser/parse'; diff --git a/packages/cascade-layer-name-parser/dist/index.mjs b/packages/cascade-layer-name-parser/dist/index.mjs new file mode 100644 index 000000000..6dc6faf7e --- /dev/null +++ b/packages/cascade-layer-name-parser/dist/index.mjs @@ -0,0 +1 @@ +import{TokenType as e,stringify as t,tokenizer as n}from"@csstools/css-tokenizer";import{parseCommaSeparatedListOfComponentValues as r,isTokenNode as s,isCommentNode as a,isWhitespaceNode as o}from"@csstools/css-parser-algorithms";class LayerName{parts;constructor(e){this.parts=e}tokens(){return[...this.parts]}slice(t,n){const r=[];for(let t=0;tt[0]===e.Ident||t[0]===e.Delim)),n,...t.parts.filter((t=>t[0]===e.Ident||t[0]===e.Delim))])}segments(){return this.parts.filter((t=>t[0]===e.Ident)).map((e=>e[4].value))}name(){return this.parts.filter((t=>t[0]===e.Ident||t[0]===e.Delim)).map((e=>e[1])).join("")}equal(e){const t=this.segments(),n=e.segments();if(t.length!==n.length)return!1;for(let e=0;e{const n=t.segments();e:for(let r=0;r=o&&(a=t,o=r)}-1===a?e.push(n):e.splice(a+1,0,n)}})),e}function parseFromTokens(t,n){const i=r(t,{onParseError:null==n?void 0:n.onParseError}),l=(null==n?void 0:n.onParseError)??(()=>{}),genericError=e=>({message:`Invalid cascade layer name. ${e}`,start:t[0][2],end:t[t.length-1][3],state:["6.4.2. Layer Naming and Nesting","Layer name syntax"," = [ '.' ]*"]}),m=[];for(let t=0;te.tokens()));let c,u=!1,p=!1;for(let t=0;t; + constructor(parts: Array); + tokens(): Array; + slice(start: number, end: number): LayerName; + concat(other: LayerName): LayerName; + segments(): Array; + name(): string; + equal(other: LayerName): boolean; + toString(): string; + toJSON(): { + parts: CSSToken[]; + segments: string[]; + name: string; + }; +} diff --git a/packages/cascade-layer-name-parser/dist/parser/parse.d.ts b/packages/cascade-layer-name-parser/dist/parser/parse.d.ts new file mode 100644 index 000000000..ed13df9f3 --- /dev/null +++ b/packages/cascade-layer-name-parser/dist/parser/parse.d.ts @@ -0,0 +1,8 @@ +import { ParserError } from '@csstools/css-parser-algorithms/dist/interfaces/error'; +import { CSSToken } from '@csstools/css-tokenizer'; +import { LayerName } from '../nodes/layer-name'; +export type Options = { + onParseError?: (error: ParserError) => void; +}; +export declare function parseFromTokens(tokens: Array, options?: Options): LayerName[]; +export declare function parse(source: string, options?: Options): LayerName[]; diff --git a/packages/cascade-layer-name-parser/dist/util/model.d.ts b/packages/cascade-layer-name-parser/dist/util/model.d.ts new file mode 100644 index 000000000..7347ce264 --- /dev/null +++ b/packages/cascade-layer-name-parser/dist/util/model.d.ts @@ -0,0 +1,2 @@ +import { LayerName } from '../nodes/layer-name'; +export declare function addLayerToModel(layers: Array, currentLayerNames: Array): LayerName[]; diff --git a/packages/cascade-layer-name-parser/package.json b/packages/cascade-layer-name-parser/package.json new file mode 100644 index 000000000..9464189f9 --- /dev/null +++ b/packages/cascade-layer-name-parser/package.json @@ -0,0 +1,72 @@ +{ + "name": "@csstools/cascade-layer-name-parser", + "description": "Parse CSS Cascade Layer names.", + "version": "1.0.0", + "contributors": [ + { + "name": "Antonio Laguna", + "email": "antonio@laguna.es", + "url": "https://antonio.laguna.es" + }, + { + "name": "Romain Menke", + "email": "romainmenke@gmail.com" + } + ], + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "engines": { + "node": "^14 || ^16 || >=18" + }, + "main": "dist/index.cjs", + "module": "dist/index.mjs", + "types": "dist/index.d.ts", + "exports": { + ".": { + "import": "./dist/index.mjs", + "require": "./dist/index.cjs", + "default": "./dist/index.mjs" + } + }, + "files": [ + "CHANGELOG.md", + "LICENSE.md", + "README.md", + "dist" + ], + "peerDependencies": { + "@csstools/css-parser-algorithms": "^1.0.0", + "@csstools/css-tokenizer": "^1.0.0" + }, + "scripts": { + "prebuild": "npm run clean", + "build": "rollup -c ../../rollup/default.mjs", + "clean": "node -e \"fs.rmSync('./dist', { recursive: true, force: true }); fs.mkdirSync('./dist');\"", + "lint": "npm run lint:eslint && npm run lint:package-json", + "lint:eslint": "eslint ./src --ext .js --ext .ts --ext .mjs --no-error-on-unmatched-pattern", + "lint:package-json": "node ../../.github/bin/format-package-json.mjs", + "prepublishOnly": "npm run clean && npm run build && npm run test", + "stryker": "stryker run --logLevel error", + "test": "node ./test/test.mjs", + "test:exports": "node ./test/_import.mjs && node ./test/_require.cjs", + "test:rewrite-expects": "REWRITE_EXPECTS=true node ./test/test.mjs" + }, + "homepage": "https://github.com/csstools/postcss-plugins/tree/main/packages/cascade-layer-name-parser#readme", + "repository": { + "type": "git", + "url": "https://github.com/csstools/postcss-plugins.git", + "directory": "packages/cascade-layer-name-parser" + }, + "bugs": "https://github.com/csstools/postcss-plugins/issues", + "keywords": [ + "cascade-layer", + "css", + "parser" + ], + "volta": { + "extends": "../../package.json" + } +} diff --git a/packages/cascade-layer-name-parser/src/index.ts b/packages/cascade-layer-name-parser/src/index.ts new file mode 100644 index 000000000..2592bc2e1 --- /dev/null +++ b/packages/cascade-layer-name-parser/src/index.ts @@ -0,0 +1,3 @@ +export { LayerName } from './nodes/layer-name'; +export { addLayerToModel } from './util/model'; +export { parse, parseFromTokens } from './parser/parse'; diff --git a/packages/cascade-layer-name-parser/src/nodes/layer-name.ts b/packages/cascade-layer-name-parser/src/nodes/layer-name.ts new file mode 100644 index 000000000..7878c714a --- /dev/null +++ b/packages/cascade-layer-name-parser/src/nodes/layer-name.ts @@ -0,0 +1,94 @@ +import { TokenDelim } from '@csstools/css-tokenizer'; +import { CSSToken, stringify, TokenIdent, TokenType } from '@csstools/css-tokenizer'; + +export class LayerName { + parts: Array; + + constructor(parts: Array) { + this.parts = parts; + } + + tokens(): Array { + return [ + ...this.parts, + ]; + } + + slice(start: number, end: number): LayerName { + const indices = []; + for (let i = 0; i < this.parts.length; i++) { + if (this.parts[i][0] === TokenType.Ident) { + indices.push(i); + } + } + + const slice = indices.slice(start, end); + return new LayerName(this.parts.slice(slice[0], slice[slice.length-1]+1)); + } + + concat(other: LayerName): LayerName { + const dot: TokenDelim = [ + TokenType.Delim, + '.', + -1, + -1, + { value: '.' }, + ]; + + return new LayerName([ + ...this.parts.filter((x) => { + return x[0] === TokenType.Ident || x[0] === TokenType.Delim; + }), + dot, + ...other.parts.filter((x) => { + return x[0] === TokenType.Ident || x[0] === TokenType.Delim; + }), + ]); + } + + segments(): Array { + return this.parts.filter((x) => { + return x[0] === TokenType.Ident; + }).map((x: TokenIdent) => { + return x[4].value; + }); + } + + name(): string { + return this.parts.filter((x) => { + return x[0] === TokenType.Ident || x[0] === TokenType.Delim; + }).map((x: TokenIdent) => { + return x[1]; + }).join(''); + } + + equal(other: LayerName): boolean { + const a = this.segments(); + const b = other.segments(); + if (a.length !== b.length) { + return false; + } + + for (let i = 0; i < a.length; i++) { + const aa = a[i]; + const bb = b[i]; + if (aa !== bb) { + return false; + } + } + + return true; + } + + toString(): string { + return stringify(...this.parts); + } + + toJSON() { + return { + parts: this.parts, + segments: this.segments(), + name: this.name(), + }; + } +} diff --git a/packages/cascade-layer-name-parser/src/parser/parse.ts b/packages/cascade-layer-name-parser/src/parser/parse.ts new file mode 100644 index 000000000..238dc0a22 --- /dev/null +++ b/packages/cascade-layer-name-parser/src/parser/parse.ts @@ -0,0 +1,147 @@ +import { isCommentNode, isTokenNode, isWhitespaceNode } from '@csstools/css-parser-algorithms'; +import { parseCommaSeparatedListOfComponentValues } from '@csstools/css-parser-algorithms'; +import { ParserError } from '@csstools/css-parser-algorithms/dist/interfaces/error'; +import { CSSToken, tokenizer, TokenType } from '@csstools/css-tokenizer'; +import { LayerName } from '../nodes/layer-name'; + +export type Options = { + onParseError?: (error: ParserError) => void +} + +export function parseFromTokens(tokens: Array, options?: Options) { + const componentValuesLists = parseCommaSeparatedListOfComponentValues(tokens, { + onParseError: options?.onParseError, + }); + + const onParseError = options?.onParseError ?? (() => { + // noop; + }); + + // There is no error recovery when parsing layer names. + // They are either fully valid or fully invalid. + + const genericError = (message) => { + return { + message: `Invalid cascade layer name. ${message}`, + start: tokens[0][2], + end: tokens[tokens.length - 1][3], + state: [ + '6.4.2. Layer Naming and Nesting', + 'Layer name syntax', + ' = [ \'.\' ]*', + ], + }; + }; + + const result: Array = []; + + for (let i = 0; i < componentValuesLists.length; i++) { + const componentValuesList = componentValuesLists[i]; + for (let j = 0; j < componentValuesList.length; j++) { + const componentValue = componentValuesList[j]; + if (!isTokenNode(componentValue) && !isCommentNode(componentValue) && !isWhitespaceNode(componentValue)) { + onParseError(genericError(`Invalid layer name part "${componentValue.toString()}"`)); + return []; + } + } + + const componentValueTokens = componentValuesList.flatMap((x) => x.tokens()); + + let inLayerNameSequence = false; + let sawWhiteSpaceAfterIdent = false; + let lastToken: CSSToken; + for (let j = 0; j < componentValueTokens.length; j++) { + const token = componentValueTokens[j]; + if (!( + token[0] === TokenType.Comment || + token[0] === TokenType.Whitespace || + token[0] === TokenType.Ident || + ( + token[0] === TokenType.Delim && + token[4].value === '.' + ) + )) { + onParseError(genericError(`Invalid character "${token[1]}"`)); + return []; + } + + if (!inLayerNameSequence) { + if (token[0] === TokenType.Delim) { + onParseError(genericError('Layer names can not start with a dot.')); + return []; + } + } + + if (inLayerNameSequence) { + if (token[0] === TokenType.Whitespace) { + sawWhiteSpaceAfterIdent = true; + continue; + } + + if (sawWhiteSpaceAfterIdent && token[0] === TokenType.Comment) { + continue; + } + + if (sawWhiteSpaceAfterIdent) { + onParseError(genericError('Encountered unexpected whitespace between layer name parts.')); + return []; + } + + if (lastToken[0] === TokenType.Ident) { + if (token[0] === TokenType.Ident) { + onParseError(genericError('Layer name parts must be separated by dots.')); + return []; + } + } + + if (lastToken[0] === TokenType.Delim) { + if (token[0] === TokenType.Delim) { + onParseError(genericError('Layer name parts must not be empty.')); + return []; + } + } + } + + if (token[0] === TokenType.Ident) { + inLayerNameSequence = true; + } + + if (token[0] === TokenType.Ident || token[0] === TokenType.Delim) { + lastToken = token; + } + } + + if (!lastToken) { + onParseError(genericError('Empty layer name.')); + return []; + } + + if (lastToken[0] === TokenType.Delim) { + onParseError(genericError('Layer name must not end with a dot.')); + return []; + } + + result.push(new LayerName(componentValueTokens)); + } + + return result; +} + +export function parse(source: string, options?: Options) { + const t = tokenizer({ css: source }, { + commentsAreTokens: true, + onParseError: options?.onParseError, + }); + + const tokens = []; + + { + while (!t.endOfFile()) { + tokens.push(t.nextToken()); + } + + tokens.push(t.nextToken()); // EOF-token + } + + return parseFromTokens(tokens, options); +} diff --git a/packages/cascade-layer-name-parser/src/util/model.ts b/packages/cascade-layer-name-parser/src/util/model.ts new file mode 100644 index 000000000..e9bc966f9 --- /dev/null +++ b/packages/cascade-layer-name-parser/src/util/model.ts @@ -0,0 +1,66 @@ +import { LayerName } from '../nodes/layer-name'; + +// Insert new items after the most similar current item +// +// [["a", "b"]] +// insert "a.first" +// [["a", "a.first", "b"]] +// +// [["a", "a.first", "a.second", "b"]] +// insert "a.first.foo" +// [["a", "a.first", "a.first.foo", "a.second", "b"]] +// +// [["a", "b"]] +// insert "c" + +// [["a", "b", "c"]] +export function addLayerToModel(layers: Array, currentLayerNames: Array) { + currentLayerNames.forEach((layerName) => { + const allLayerNameParts = layerName.segments(); + + ALL_LAYER_NAME_PARTS_LOOP: for (let x = 0; x < allLayerNameParts.length; x++) { + const layerNameSlice = layerName.slice(0, x + 1); + const layerNameParts = layerNameSlice.segments(); + + let layerWithMostEqualSegments = -1; + let mostEqualSegments = 0; + + for (let i = 0; i < layers.length; i++) { + const existingLayerParts = layers[i].segments(); + + let numberOfEqualSegments = 0; + + LAYER_PARTS_LOOP: for (let j = 0; j < existingLayerParts.length; j++) { + const existingLayerPart = existingLayerParts[j]; + const layerPart = layerNameParts[j]; + + if (layerPart === existingLayerPart && (j + 1) === layerNameParts.length) { + continue ALL_LAYER_NAME_PARTS_LOOP; // layer already exists in model + } + + if (layerPart === existingLayerPart) { + numberOfEqualSegments++; + continue; + } + + if (layerPart !== existingLayerPart) { + break LAYER_PARTS_LOOP; + } + } + + if (numberOfEqualSegments >= mostEqualSegments) { + layerWithMostEqualSegments = i; + mostEqualSegments = numberOfEqualSegments; + } + } + + if (layerWithMostEqualSegments === -1) { + layers.push(layerNameSlice); + } else { + layers.splice(layerWithMostEqualSegments + 1, 0, layerNameSlice); + } + } + }); + + return layers; +} diff --git a/packages/cascade-layer-name-parser/stryker.conf.json b/packages/cascade-layer-name-parser/stryker.conf.json new file mode 100644 index 000000000..015ebbb73 --- /dev/null +++ b/packages/cascade-layer-name-parser/stryker.conf.json @@ -0,0 +1,19 @@ +{ + "$schema": "../../node_modules/@stryker-mutator/core/schema/stryker-schema.json", + "mutate": [ + "src/**/*.ts" + ], + "buildCommand": "npm run build", + "testRunner": "command", + "coverageAnalysis": "perTest", + "tempDirName": "../../.stryker-tmp", + "commandRunner": { + "command": "npm run test" + }, + "thresholds": { + "high": 100, + "low": 100, + "break": 100 + }, + "inPlace": true +} diff --git a/packages/cascade-layer-name-parser/test/_import.mjs b/packages/cascade-layer-name-parser/test/_import.mjs new file mode 100644 index 000000000..8929e4112 --- /dev/null +++ b/packages/cascade-layer-name-parser/test/_import.mjs @@ -0,0 +1,3 @@ +import { parse } from '@csstools/cascade-layer-name-parser'; + +parse('layer-name, foo.bar'); diff --git a/packages/cascade-layer-name-parser/test/_require.cjs b/packages/cascade-layer-name-parser/test/_require.cjs new file mode 100644 index 000000000..226da4fdf --- /dev/null +++ b/packages/cascade-layer-name-parser/test/_require.cjs @@ -0,0 +1,3 @@ +const { parse } = require('@csstools/cascade-layer-name-parser'); + +parse('layer-name, foo.bar'); diff --git a/packages/cascade-layer-name-parser/test/cases/various/0001.expect.json b/packages/cascade-layer-name-parser/test/cases/various/0001.expect.json new file mode 100644 index 000000000..23eb516a9 --- /dev/null +++ b/packages/cascade-layer-name-parser/test/cases/various/0001.expect.json @@ -0,0 +1,19 @@ +[ + { + "parts": [ + [ + "ident-token", + "layer-name", + 0, + 9, + { + "value": "layer-name" + } + ] + ], + "segments": [ + "layer-name" + ], + "name": "layer-name" + } +] \ No newline at end of file diff --git a/packages/cascade-layer-name-parser/test/cases/various/0001.mjs b/packages/cascade-layer-name-parser/test/cases/various/0001.mjs new file mode 100644 index 000000000..32a008cc8 --- /dev/null +++ b/packages/cascade-layer-name-parser/test/cases/various/0001.mjs @@ -0,0 +1,13 @@ +import assert from 'assert'; +import { runTest } from '../../util/run-test.mjs'; + +runTest( + 'layer-name', + 'various/0001', + (actual, expected) => { + assert.deepStrictEqual( + actual, + expected, + ); + }, +); diff --git a/packages/cascade-layer-name-parser/test/cases/various/0002.expect.json b/packages/cascade-layer-name-parser/test/cases/various/0002.expect.json new file mode 100644 index 000000000..f0d4b3384 --- /dev/null +++ b/packages/cascade-layer-name-parser/test/cases/various/0002.expect.json @@ -0,0 +1,38 @@ +[ + { + "parts": [ + [ + "ident-token", + "layer-name", + 0, + 9, + { + "value": "layer-name" + } + ], + [ + "delim-token", + ".", + 10, + 10, + { + "value": "." + } + ], + [ + "ident-token", + "sub-part", + 11, + 18, + { + "value": "sub-part" + } + ] + ], + "segments": [ + "layer-name", + "sub-part" + ], + "name": "layer-name.sub-part" + } +] \ No newline at end of file diff --git a/packages/cascade-layer-name-parser/test/cases/various/0002.mjs b/packages/cascade-layer-name-parser/test/cases/various/0002.mjs new file mode 100644 index 000000000..a649e0dd6 --- /dev/null +++ b/packages/cascade-layer-name-parser/test/cases/various/0002.mjs @@ -0,0 +1,13 @@ +import assert from 'assert'; +import { runTest } from '../../util/run-test.mjs'; + +runTest( + 'layer-name.sub-part', + 'various/0002', + (actual, expected) => { + assert.deepStrictEqual( + actual, + expected, + ); + }, +); diff --git a/packages/cascade-layer-name-parser/test/cases/various/0003.expect.json b/packages/cascade-layer-name-parser/test/cases/various/0003.expect.json new file mode 100644 index 000000000..5bccd10b3 --- /dev/null +++ b/packages/cascade-layer-name-parser/test/cases/various/0003.expect.json @@ -0,0 +1,93 @@ +[ + { + "parts": [ + [ + "ident-token", + "abc", + 0, + 2, + { + "value": "abc" + } + ], + [ + "delim-token", + ".", + 3, + 3, + { + "value": "." + } + ], + [ + "ident-token", + "def", + 4, + 6, + { + "value": "def" + } + ] + ], + "segments": [ + "abc", + "def" + ], + "name": "abc.def" + }, + { + "parts": [ + [ + "ident-token", + "ghi", + 8, + 10, + { + "value": "ghi" + } + ], + [ + "delim-token", + ".", + 11, + 11, + { + "value": "." + } + ], + [ + "ident-token", + "jkl", + 12, + 14, + { + "value": "jkl" + } + ], + [ + "delim-token", + ".", + 15, + 15, + { + "value": "." + } + ], + [ + "ident-token", + "mno", + 16, + 18, + { + "value": "mno" + } + ] + ], + "segments": [ + "ghi", + "jkl", + "mno" + ], + "name": "ghi.jkl.mno" + } +] \ No newline at end of file diff --git a/packages/cascade-layer-name-parser/test/cases/various/0003.mjs b/packages/cascade-layer-name-parser/test/cases/various/0003.mjs new file mode 100644 index 000000000..259bf1008 --- /dev/null +++ b/packages/cascade-layer-name-parser/test/cases/various/0003.mjs @@ -0,0 +1,13 @@ +import assert from 'assert'; +import { runTest } from '../../util/run-test.mjs'; + +runTest( + 'abc.def,ghi.jkl.mno', + 'various/0003', + (actual, expected) => { + assert.deepStrictEqual( + actual, + expected, + ); + }, +); diff --git a/packages/cascade-layer-name-parser/test/cases/various/0004.expect.json b/packages/cascade-layer-name-parser/test/cases/various/0004.expect.json new file mode 100644 index 000000000..0637a088a --- /dev/null +++ b/packages/cascade-layer-name-parser/test/cases/various/0004.expect.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/packages/cascade-layer-name-parser/test/cases/various/0004.mjs b/packages/cascade-layer-name-parser/test/cases/various/0004.mjs new file mode 100644 index 000000000..8d01c2486 --- /dev/null +++ b/packages/cascade-layer-name-parser/test/cases/various/0004.mjs @@ -0,0 +1,14 @@ +import assert from 'assert'; +import { runTest } from '../../util/run-test.mjs'; + +runTest( + 'ab/* a comment */c.def,ghi.jkl.mno', + 'various/0004', + (actual, expected) => { + assert.deepStrictEqual( + actual, + expected, + ); + }, + false, +); diff --git a/packages/cascade-layer-name-parser/test/cases/various/0005.expect.json b/packages/cascade-layer-name-parser/test/cases/various/0005.expect.json new file mode 100644 index 000000000..563107eac --- /dev/null +++ b/packages/cascade-layer-name-parser/test/cases/various/0005.expect.json @@ -0,0 +1,156 @@ +[ + { + "parts": [ + [ + "comment", + "/* a comment */", + 0, + 14, + null + ], + [ + "ident-token", + "abc", + 15, + 17, + { + "value": "abc" + } + ], + [ + "comment", + "/* a comment */", + 18, + 32, + null + ], + [ + "delim-token", + ".", + 33, + 33, + { + "value": "." + } + ], + [ + "comment", + "/* a comment */", + 34, + 48, + null + ], + [ + "ident-token", + "def", + 49, + 51, + { + "value": "def" + } + ], + [ + "comment", + "/* a comment */", + 52, + 66, + null + ] + ], + "segments": [ + "abc", + "def" + ], + "name": "abc.def" + }, + { + "parts": [ + [ + "comment", + "/* a comment */", + 68, + 82, + null + ], + [ + "ident-token", + "ghi", + 83, + 85, + { + "value": "ghi" + } + ], + [ + "comment", + "/* a comment */", + 86, + 100, + null + ], + [ + "delim-token", + ".", + 101, + 101, + { + "value": "." + } + ], + [ + "comment", + "/* a comment */", + 102, + 116, + null + ], + [ + "ident-token", + "jkl", + 117, + 119, + { + "value": "jkl" + } + ], + [ + "comment", + "/* a comment */", + 120, + 134, + null + ], + [ + "delim-token", + ".", + 135, + 135, + { + "value": "." + } + ], + [ + "comment", + "/* a comment */", + 136, + 150, + null + ], + [ + "ident-token", + "mno", + 151, + 153, + { + "value": "mno" + } + ] + ], + "segments": [ + "ghi", + "jkl", + "mno" + ], + "name": "ghi.jkl.mno" + } +] \ No newline at end of file diff --git a/packages/cascade-layer-name-parser/test/cases/various/0005.mjs b/packages/cascade-layer-name-parser/test/cases/various/0005.mjs new file mode 100644 index 000000000..6e77c95da --- /dev/null +++ b/packages/cascade-layer-name-parser/test/cases/various/0005.mjs @@ -0,0 +1,13 @@ +import assert from 'assert'; +import { runTest } from '../../util/run-test.mjs'; + +runTest( + '/* a comment */abc/* a comment */./* a comment */def/* a comment */,/* a comment */ghi/* a comment */./* a comment */jkl/* a comment */./* a comment */mno', + 'various/0005', + (actual, expected) => { + assert.deepStrictEqual( + actual, + expected, + ); + }, +); diff --git a/packages/cascade-layer-name-parser/test/cases/various/0006.expect.json b/packages/cascade-layer-name-parser/test/cases/various/0006.expect.json new file mode 100644 index 000000000..0637a088a --- /dev/null +++ b/packages/cascade-layer-name-parser/test/cases/various/0006.expect.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/packages/cascade-layer-name-parser/test/cases/various/0006.mjs b/packages/cascade-layer-name-parser/test/cases/various/0006.mjs new file mode 100644 index 000000000..138d6056e --- /dev/null +++ b/packages/cascade-layer-name-parser/test/cases/various/0006.mjs @@ -0,0 +1,14 @@ +import assert from 'assert'; +import { runTest } from '../../util/run-test.mjs'; + +runTest( + 'layer..part', + 'various/0006', + (actual, expected) => { + assert.deepStrictEqual( + actual, + expected, + ); + }, + false, +); diff --git a/packages/cascade-layer-name-parser/test/cases/various/0007.expect.json b/packages/cascade-layer-name-parser/test/cases/various/0007.expect.json new file mode 100644 index 000000000..0637a088a --- /dev/null +++ b/packages/cascade-layer-name-parser/test/cases/various/0007.expect.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/packages/cascade-layer-name-parser/test/cases/various/0007.mjs b/packages/cascade-layer-name-parser/test/cases/various/0007.mjs new file mode 100644 index 000000000..f869ff134 --- /dev/null +++ b/packages/cascade-layer-name-parser/test/cases/various/0007.mjs @@ -0,0 +1,14 @@ +import assert from 'assert'; +import { runTest } from '../../util/run-test.mjs'; + +runTest( + 'layer . part', + 'various/0007', + (actual, expected) => { + assert.deepStrictEqual( + actual, + expected, + ); + }, + false, +); diff --git a/packages/cascade-layer-name-parser/test/cases/various/0008.expect.json b/packages/cascade-layer-name-parser/test/cases/various/0008.expect.json new file mode 100644 index 000000000..0637a088a --- /dev/null +++ b/packages/cascade-layer-name-parser/test/cases/various/0008.expect.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/packages/cascade-layer-name-parser/test/cases/various/0008.mjs b/packages/cascade-layer-name-parser/test/cases/various/0008.mjs new file mode 100644 index 000000000..3970b75cc --- /dev/null +++ b/packages/cascade-layer-name-parser/test/cases/various/0008.mjs @@ -0,0 +1,14 @@ +import assert from 'assert'; +import { runTest } from '../../util/run-test.mjs'; + +runTest( + 'layer part', + 'various/0008', + (actual, expected) => { + assert.deepStrictEqual( + actual, + expected, + ); + }, + false, +); diff --git a/packages/cascade-layer-name-parser/test/cases/various/0009.expect.json b/packages/cascade-layer-name-parser/test/cases/various/0009.expect.json new file mode 100644 index 000000000..95e50fc30 --- /dev/null +++ b/packages/cascade-layer-name-parser/test/cases/various/0009.expect.json @@ -0,0 +1,38 @@ +[ + { + "parts": [ + [ + "ident-token", + "l\\61 yer", + 0, + 7, + { + "value": "layer" + } + ], + [ + "delim-token", + ".", + 8, + 8, + { + "value": "." + } + ], + [ + "ident-token", + "part", + 9, + 12, + { + "value": "part" + } + ] + ], + "segments": [ + "layer", + "part" + ], + "name": "l\\61 yer.part" + } +] \ No newline at end of file diff --git a/packages/cascade-layer-name-parser/test/cases/various/0009.mjs b/packages/cascade-layer-name-parser/test/cases/various/0009.mjs new file mode 100644 index 000000000..586990df4 --- /dev/null +++ b/packages/cascade-layer-name-parser/test/cases/various/0009.mjs @@ -0,0 +1,13 @@ +import assert from 'assert'; +import { runTest } from '../../util/run-test.mjs'; + +runTest( + 'l\\61 yer.part', + 'various/0009', + (actual, expected) => { + assert.deepStrictEqual( + actual, + expected, + ); + }, +); diff --git a/packages/cascade-layer-name-parser/test/cases/various/0010.expect.json b/packages/cascade-layer-name-parser/test/cases/various/0010.expect.json new file mode 100644 index 000000000..0637a088a --- /dev/null +++ b/packages/cascade-layer-name-parser/test/cases/various/0010.expect.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/packages/cascade-layer-name-parser/test/cases/various/0010.mjs b/packages/cascade-layer-name-parser/test/cases/various/0010.mjs new file mode 100644 index 000000000..e7d15d7fe --- /dev/null +++ b/packages/cascade-layer-name-parser/test/cases/various/0010.mjs @@ -0,0 +1,14 @@ +import assert from 'assert'; +import { runTest } from '../../util/run-test.mjs'; + +runTest( + 'layer, (not-a-layer), sub.layer', + 'various/0010', + (actual, expected) => { + assert.deepStrictEqual( + actual, + expected, + ); + }, + false, +); diff --git a/packages/cascade-layer-name-parser/test/cases/various/0011.expect.json b/packages/cascade-layer-name-parser/test/cases/various/0011.expect.json new file mode 100644 index 000000000..0637a088a --- /dev/null +++ b/packages/cascade-layer-name-parser/test/cases/various/0011.expect.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/packages/cascade-layer-name-parser/test/cases/various/0011.mjs b/packages/cascade-layer-name-parser/test/cases/various/0011.mjs new file mode 100644 index 000000000..f88b0a7b9 --- /dev/null +++ b/packages/cascade-layer-name-parser/test/cases/various/0011.mjs @@ -0,0 +1,14 @@ +import assert from 'assert'; +import { runTest } from '../../util/run-test.mjs'; + +runTest( + 'layer, calc(not-a-layer), sub.layer', + 'various/0011', + (actual, expected) => { + assert.deepStrictEqual( + actual, + expected, + ); + }, + false, +); diff --git a/packages/cascade-layer-name-parser/test/cases/various/0012.expect.json b/packages/cascade-layer-name-parser/test/cases/various/0012.expect.json new file mode 100644 index 000000000..0637a088a --- /dev/null +++ b/packages/cascade-layer-name-parser/test/cases/various/0012.expect.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/packages/cascade-layer-name-parser/test/cases/various/0012.mjs b/packages/cascade-layer-name-parser/test/cases/various/0012.mjs new file mode 100644 index 000000000..7b3b0e00a --- /dev/null +++ b/packages/cascade-layer-name-parser/test/cases/various/0012.mjs @@ -0,0 +1,14 @@ +import assert from 'assert'; +import { runTest } from '../../util/run-test.mjs'; + +runTest( + 'layer, sub/layer , sub.layer', + 'various/0012', + (actual, expected) => { + assert.deepStrictEqual( + actual, + expected, + ); + }, + false, +); diff --git a/packages/cascade-layer-name-parser/test/cases/various/0013.expect.json b/packages/cascade-layer-name-parser/test/cases/various/0013.expect.json new file mode 100644 index 000000000..0637a088a --- /dev/null +++ b/packages/cascade-layer-name-parser/test/cases/various/0013.expect.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/packages/cascade-layer-name-parser/test/cases/various/0013.mjs b/packages/cascade-layer-name-parser/test/cases/various/0013.mjs new file mode 100644 index 000000000..6097361c6 --- /dev/null +++ b/packages/cascade-layer-name-parser/test/cases/various/0013.mjs @@ -0,0 +1,14 @@ +import assert from 'assert'; +import { runTest } from '../../util/run-test.mjs'; + +runTest( + 'layer, sub+layer , sub.layer', + 'various/0013', + (actual, expected) => { + assert.deepStrictEqual( + actual, + expected, + ); + }, + false, +); diff --git a/packages/cascade-layer-name-parser/test/cases/various/0014.expect.json b/packages/cascade-layer-name-parser/test/cases/various/0014.expect.json new file mode 100644 index 000000000..0637a088a --- /dev/null +++ b/packages/cascade-layer-name-parser/test/cases/various/0014.expect.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/packages/cascade-layer-name-parser/test/cases/various/0014.mjs b/packages/cascade-layer-name-parser/test/cases/various/0014.mjs new file mode 100644 index 000000000..6aa53cf73 --- /dev/null +++ b/packages/cascade-layer-name-parser/test/cases/various/0014.mjs @@ -0,0 +1,14 @@ +import assert from 'assert'; +import { runTest } from '../../util/run-test.mjs'; + +runTest( + 'layer., other-layer', + 'various/0014', + (actual, expected) => { + assert.deepStrictEqual( + actual, + expected, + ); + }, + false, +); diff --git a/packages/cascade-layer-name-parser/test/cases/various/0015.expect.json b/packages/cascade-layer-name-parser/test/cases/various/0015.expect.json new file mode 100644 index 000000000..0637a088a --- /dev/null +++ b/packages/cascade-layer-name-parser/test/cases/various/0015.expect.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/packages/cascade-layer-name-parser/test/cases/various/0015.mjs b/packages/cascade-layer-name-parser/test/cases/various/0015.mjs new file mode 100644 index 000000000..c726c33a7 --- /dev/null +++ b/packages/cascade-layer-name-parser/test/cases/various/0015.mjs @@ -0,0 +1,14 @@ +import assert from 'assert'; +import { runTest } from '../../util/run-test.mjs'; + +runTest( + 'layer,,other-layer', + 'various/0015', + (actual, expected) => { + assert.deepStrictEqual( + actual, + expected, + ); + }, + false, +); diff --git a/packages/cascade-layer-name-parser/test/cases/various/0016.expect.json b/packages/cascade-layer-name-parser/test/cases/various/0016.expect.json new file mode 100644 index 000000000..5a8e9362b --- /dev/null +++ b/packages/cascade-layer-name-parser/test/cases/various/0016.expect.json @@ -0,0 +1,88 @@ +[ + { + "parts": [ + [ + "whitespace-token", + " \t ", + 0, + 2, + null + ], + [ + "ident-token", + "layer", + 3, + 7, + { + "value": "layer" + } + ] + ], + "segments": [ + "layer" + ], + "name": "layer" + }, + { + "parts": [ + [ + "whitespace-token", + " \t ", + 9, + 11, + null + ], + [ + "ident-token", + "other-layer", + 12, + 22, + { + "value": "other-layer" + } + ], + [ + "whitespace-token", + " \t ", + 23, + 25, + null + ] + ], + "segments": [ + "other-layer" + ], + "name": "other-layer" + }, + { + "parts": [ + [ + "whitespace-token", + "\n\n", + 27, + 28, + null + ], + [ + "ident-token", + "last-layer", + 29, + 38, + { + "value": "last-layer" + } + ], + [ + "whitespace-token", + "\n\n", + 39, + 40, + null + ] + ], + "segments": [ + "last-layer" + ], + "name": "last-layer" + } +] \ No newline at end of file diff --git a/packages/cascade-layer-name-parser/test/cases/various/0016.mjs b/packages/cascade-layer-name-parser/test/cases/various/0016.mjs new file mode 100644 index 000000000..05baab87e --- /dev/null +++ b/packages/cascade-layer-name-parser/test/cases/various/0016.mjs @@ -0,0 +1,17 @@ +import assert from 'assert'; +import { runTest } from '../../util/run-test.mjs'; + +runTest( + ` layer, other-layer , + +last-layer + +`, + 'various/0016', + (actual, expected) => { + assert.deepStrictEqual( + actual, + expected, + ); + }, +); diff --git a/packages/cascade-layer-name-parser/test/cases/various/0017.expect.json b/packages/cascade-layer-name-parser/test/cases/various/0017.expect.json new file mode 100644 index 000000000..d98f090f8 --- /dev/null +++ b/packages/cascade-layer-name-parser/test/cases/various/0017.expect.json @@ -0,0 +1,55 @@ +[ + { + "parts": [ + [ + "ident-token", + "lay\\,er", + 0, + 6, + { + "value": "lay,er" + } + ] + ], + "segments": [ + "lay,er" + ], + "name": "lay\\,er" + }, + { + "parts": [ + [ + "ident-token", + "other-la\\.yer", + 8, + 20, + { + "value": "other-la.yer" + } + ], + [ + "delim-token", + ".", + 21, + 21, + { + "value": "." + } + ], + [ + "ident-token", + "sub-layer", + 22, + 30, + { + "value": "sub-layer" + } + ] + ], + "segments": [ + "other-la.yer", + "sub-layer" + ], + "name": "other-la\\.yer.sub-layer" + } +] \ No newline at end of file diff --git a/packages/cascade-layer-name-parser/test/cases/various/0017.mjs b/packages/cascade-layer-name-parser/test/cases/various/0017.mjs new file mode 100644 index 000000000..ecca237e8 --- /dev/null +++ b/packages/cascade-layer-name-parser/test/cases/various/0017.mjs @@ -0,0 +1,13 @@ +import assert from 'assert'; +import { runTest } from '../../util/run-test.mjs'; + +runTest( + 'lay\\,er,other-la\\.yer.sub-layer', + 'various/0017', + (actual, expected) => { + assert.deepStrictEqual( + actual, + expected, + ); + }, +); diff --git a/packages/cascade-layer-name-parser/test/cases/various/0018.expect.json b/packages/cascade-layer-name-parser/test/cases/various/0018.expect.json new file mode 100644 index 000000000..69662f344 --- /dev/null +++ b/packages/cascade-layer-name-parser/test/cases/various/0018.expect.json @@ -0,0 +1,205 @@ +[ + { + "parts": [ + [ + "whitespace-token", + " ", + 0, + 1, + null + ], + [ + "comment", + "/* a comment */", + 2, + 16, + null + ], + [ + "whitespace-token", + " ", + 17, + 18, + null + ], + [ + "ident-token", + "abc", + 19, + 21, + { + "value": "abc" + } + ], + [ + "comment", + "/* a comment */", + 22, + 36, + null + ], + [ + "delim-token", + ".", + 37, + 37, + { + "value": "." + } + ], + [ + "comment", + "/* a comment */", + 38, + 52, + null + ], + [ + "ident-token", + "def", + 53, + 55, + { + "value": "def" + } + ], + [ + "whitespace-token", + " ", + 56, + 57, + null + ], + [ + "comment", + "/* a comment */", + 58, + 72, + null + ], + [ + "whitespace-token", + " ", + 73, + 74, + null + ] + ], + "segments": [ + "abc", + "def" + ], + "name": "abc.def" + }, + { + "parts": [ + [ + "whitespace-token", + " ", + 76, + 77, + null + ], + [ + "comment", + "/* a comment */", + 78, + 92, + null + ], + [ + "whitespace-token", + " ", + 93, + 94, + null + ], + [ + "ident-token", + "ghi", + 95, + 97, + { + "value": "ghi" + } + ], + [ + "comment", + "/* a comment */", + 98, + 112, + null + ], + [ + "delim-token", + ".", + 113, + 113, + { + "value": "." + } + ], + [ + "comment", + "/* a comment */", + 114, + 128, + null + ], + [ + "ident-token", + "jkl", + 129, + 131, + { + "value": "jkl" + } + ], + [ + "comment", + "/* a comment */", + 132, + 146, + null + ], + [ + "delim-token", + ".", + 147, + 147, + { + "value": "." + } + ], + [ + "comment", + "/* a comment */", + 148, + 162, + null + ], + [ + "ident-token", + "mno", + 163, + 165, + { + "value": "mno" + } + ], + [ + "whitespace-token", + " ", + 166, + 167, + null + ] + ], + "segments": [ + "ghi", + "jkl", + "mno" + ], + "name": "ghi.jkl.mno" + } +] \ No newline at end of file diff --git a/packages/cascade-layer-name-parser/test/cases/various/0018.mjs b/packages/cascade-layer-name-parser/test/cases/various/0018.mjs new file mode 100644 index 000000000..e7dc77b39 --- /dev/null +++ b/packages/cascade-layer-name-parser/test/cases/various/0018.mjs @@ -0,0 +1,13 @@ +import assert from 'assert'; +import { runTest } from '../../util/run-test.mjs'; + +runTest( + ' /* a comment */ abc/* a comment */./* a comment */def /* a comment */ , /* a comment */ ghi/* a comment */./* a comment */jkl/* a comment */./* a comment */mno ', + 'various/0018', + (actual, expected) => { + assert.deepStrictEqual( + actual, + expected, + ); + }, +); diff --git a/packages/cascade-layer-name-parser/test/concat.mjs b/packages/cascade-layer-name-parser/test/concat.mjs new file mode 100644 index 000000000..fb25e4016 --- /dev/null +++ b/packages/cascade-layer-name-parser/test/concat.mjs @@ -0,0 +1,51 @@ +import { parse } from '@csstools/cascade-layer-name-parser'; +import assert from 'assert'; + +{ + const a = parse(' some-layer./* a comment */sub-layer ')[0]; + const b = parse(' another-part /* trailing comment */')[0]; + + assert.equal(a.concat(b).toString(), 'some-layer.sub-layer.another-part'); + assert.deepEqual(a.concat(b).toJSON(), { + parts: [ + ['ident-token', 'some-layer', 1, 10, { value: 'some-layer' }], + ['delim-token', '.', 11, 11, { value: '.' }], + ['ident-token', 'sub-layer', 27, 35, { value: 'sub-layer' }], + ['delim-token', '.', -1, -1, { value: '.' }], + [ + 'ident-token', + 'another-part', + 1, + 12, + { value: 'another-part' }, + ], + ], + segments: ['some-layer', 'sub-layer', 'another-part'], + name: 'some-layer.sub-layer.another-part', + }); +} + +{ + const a = parse(' another-part /* trailing comment */')[0]; + const b = parse(' some-layer./* a comment */sub-layer ')[0]; + + assert.equal(a.concat(b).toString(), 'another-part.some-layer.sub-layer'); + assert.deepEqual(a.concat(b).toJSON(), { + parts: [ + [ + 'ident-token', + 'another-part', + 1, + 12, + { value: 'another-part' }, + ], + ['delim-token', '.', -1, -1, { value: '.' }], + ['ident-token', 'some-layer', 1, 10, { value: 'some-layer' }], + ['delim-token', '.', 11, 11, { value: '.' }], + ['ident-token', 'sub-layer', 27, 35, { value: 'sub-layer' }], + ], + segments: ['another-part', 'some-layer', 'sub-layer'], + name: 'another-part.some-layer.sub-layer', + }); +} + diff --git a/packages/cascade-layer-name-parser/test/equal.mjs b/packages/cascade-layer-name-parser/test/equal.mjs new file mode 100644 index 000000000..c89ea8388 --- /dev/null +++ b/packages/cascade-layer-name-parser/test/equal.mjs @@ -0,0 +1,26 @@ +import { parse } from '@csstools/cascade-layer-name-parser'; +import assert from 'assert'; + +{ + const layerNames = [ + ...parse('alpha'), + ...parse('beta'), + ...parse('alpha.one'), + ...parse('alpha.two.other2'), + ...parse('alpha.two'), + ...parse('alpha.one.other1'), + ...parse('delta'), + ...parse('gamma.sub-layer.foo'), + ]; + + for (let i = 0; i < layerNames.length; i++) { + const ii = layerNames[i]; + + + for (let j = 0; j < layerNames.length; j++) { + const jj = layerNames[j]; + + assert.equal(ii.equal(jj), ii === jj); + } + } +} diff --git a/packages/cascade-layer-name-parser/test/model.mjs b/packages/cascade-layer-name-parser/test/model.mjs new file mode 100644 index 000000000..2901caf3d --- /dev/null +++ b/packages/cascade-layer-name-parser/test/model.mjs @@ -0,0 +1,35 @@ +import { addLayerToModel, parse } from '@csstools/cascade-layer-name-parser'; +import assert from 'assert'; + +{ + const model = []; + + addLayerToModel(model, parse('alpha')); + addLayerToModel(model, parse('beta')); + + addLayerToModel(model, parse('alpha.one')); + addLayerToModel(model, parse('alpha.two.other2')); + addLayerToModel(model, parse('alpha.two')); + addLayerToModel(model, parse('alpha.one.other1')); + + addLayerToModel(model, parse('delta')); + addLayerToModel(model, parse('alpha')); + + addLayerToModel(model, parse('gamma.sub-layer.foo')); + + assert.deepEqual( + model.map(x => x.toString()), + [ + 'alpha', + 'alpha.one', + 'alpha.one.other1', + 'alpha.two', + 'alpha.two.other2', + 'beta', + 'delta', + 'gamma', + 'gamma.sub-layer', + 'gamma.sub-layer.foo', + ], + ); +} diff --git a/packages/cascade-layer-name-parser/test/slice.mjs b/packages/cascade-layer-name-parser/test/slice.mjs new file mode 100644 index 000000000..9d6697957 --- /dev/null +++ b/packages/cascade-layer-name-parser/test/slice.mjs @@ -0,0 +1,19 @@ +import { parse } from '@csstools/cascade-layer-name-parser'; +import assert from 'assert'; + +{ + const layerName = parse('alpha./* a comment */beta.gamma.delta')[0]; + assert.equal(layerName.toString(), 'alpha./* a comment */beta.gamma.delta'); + + const a = layerName.slice(1); + assert.equal(a.toString(), 'beta.gamma.delta'); + + const b = layerName.slice(-1); + assert.equal(b.toString(), 'delta'); + + const c = layerName.slice(0, -1); + assert.equal(c.toString(), 'alpha./* a comment */beta.gamma'); + + const d = layerName.slice(1, 2); + assert.equal(d.toString(), 'beta'); +} diff --git a/packages/cascade-layer-name-parser/test/test.mjs b/packages/cascade-layer-name-parser/test/test.mjs new file mode 100644 index 000000000..590f4ebe6 --- /dev/null +++ b/packages/cascade-layer-name-parser/test/test.mjs @@ -0,0 +1,24 @@ +import './cases/various/0001.mjs'; +import './cases/various/0002.mjs'; +import './cases/various/0003.mjs'; +import './cases/various/0004.mjs'; +import './cases/various/0005.mjs'; +import './cases/various/0006.mjs'; +import './cases/various/0007.mjs'; +import './cases/various/0008.mjs'; +import './cases/various/0009.mjs'; +import './cases/various/0010.mjs'; +import './cases/various/0011.mjs'; +import './cases/various/0012.mjs'; +import './cases/various/0013.mjs'; +import './cases/various/0014.mjs'; +import './cases/various/0015.mjs'; +import './cases/various/0016.mjs'; +import './cases/various/0017.mjs'; +import './cases/various/0018.mjs'; + +import './concat.mjs'; +import './equal.mjs'; +import './model.mjs'; +import './slice.mjs'; +import './tokens.mjs'; diff --git a/packages/cascade-layer-name-parser/test/tokens.mjs b/packages/cascade-layer-name-parser/test/tokens.mjs new file mode 100644 index 000000000..d99bf2273 --- /dev/null +++ b/packages/cascade-layer-name-parser/test/tokens.mjs @@ -0,0 +1,29 @@ +import { parse } from '@csstools/cascade-layer-name-parser'; +import assert from 'assert'; + +// The "tokens" function exists for API parity with other parser tools. +// It's value is the same as "parts". +function tokensAndPartsAreEqual(layerNames) { + for (let i = 0; i < layerNames.length; i++) { + const layerName = layerNames[i]; + assert.deepEqual( + layerName.tokens(), + layerName.parts, + ); + } +} + +{ + tokensAndPartsAreEqual(parse('alpha')); + tokensAndPartsAreEqual(parse('beta')); + + tokensAndPartsAreEqual(parse('alpha.one')); + tokensAndPartsAreEqual(parse('alpha.two.other2')); + tokensAndPartsAreEqual(parse('alpha.two')); + tokensAndPartsAreEqual(parse('alpha.one.other1')); + + tokensAndPartsAreEqual(parse('delta')); + tokensAndPartsAreEqual(parse('alpha')); + + tokensAndPartsAreEqual(parse('gamma.sub-layer.foo')); +} diff --git a/packages/cascade-layer-name-parser/test/util/run-test.mjs b/packages/cascade-layer-name-parser/test/util/run-test.mjs new file mode 100644 index 000000000..fc45c7a3d --- /dev/null +++ b/packages/cascade-layer-name-parser/test/util/run-test.mjs @@ -0,0 +1,34 @@ +import fs from 'fs'; +import path from 'path'; +import { parse } from '@csstools/cascade-layer-name-parser'; + +export function runTest(source, testPath, assertEqual, expectSuccess = true) { + let err; + const resultAST = parse(source, { + onParseError: (parseError) => { + err = parseError; + }, + }); + + const resultAST_JSON = JSON.stringify(resultAST, null, '\t'); + + if (process.env['REWRITE_EXPECTS'] === 'true') { + fs.writeFileSync(path.join(process.cwd(), `./test/cases/${testPath}.expect.json`), resultAST_JSON); + fs.writeFileSync(path.join(process.cwd(), `./test/cases/${testPath}.result.json`), resultAST_JSON); + } else { + if (expectSuccess) { + if (err) { + throw new Error(JSON.stringify(err)); + } + } else { + assertEqual(!!err, true); + } + + const expectData = JSON.parse(fs.readFileSync(path.join(process.cwd(), `./test/cases/${testPath}.expect.json`)).toString()); + + assertEqual( + JSON.parse(resultAST_JSON), + expectData, + ); + } +} diff --git a/packages/cascade-layer-name-parser/tsconfig.json b/packages/cascade-layer-name-parser/tsconfig.json new file mode 100644 index 000000000..e0d06239c --- /dev/null +++ b/packages/cascade-layer-name-parser/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "dist", + "declarationDir": "." + }, + "include": ["./src/**/*"], + "exclude": ["dist"], +} diff --git a/packages/css-parser-algorithms/CHANGELOG.md b/packages/css-parser-algorithms/CHANGELOG.md index cd7fc18e7..6e25f75aa 100644 --- a/packages/css-parser-algorithms/CHANGELOG.md +++ b/packages/css-parser-algorithms/CHANGELOG.md @@ -1,3 +1,7 @@ +### Unreleased + +- Fix: Do not discard empty items in comma separated lists. + ### 1.0.0 (November 14, 2022) - Initial version diff --git a/packages/css-parser-algorithms/dist/index.cjs b/packages/css-parser-algorithms/dist/index.cjs index 91788ca8d..2ad9b2515 100644 --- a/packages/css-parser-algorithms/dist/index.cjs +++ b/packages/css-parser-algorithms/dist/index.cjs @@ -1 +1 @@ -"use strict";var e,n=require("@csstools/css-tokenizer");function consumeComponentValue(e,o){const t=o[0];if(t[0]===n.TokenType.OpenParen||t[0]===n.TokenType.OpenCurly||t[0]===n.TokenType.OpenSquare){const n=consumeSimpleBlock(e,o);return{advance:n.advance,node:n.node}}if(t[0]===n.TokenType.Function){const n=consumeFunction(e,o);return{advance:n.advance,node:n.node}}if(t[0]===n.TokenType.Whitespace){const n=consumeWhitespace(e,o);return{advance:n.advance,node:n.node}}if(t[0]===n.TokenType.Comment){const n=consumeComment(e,o);return{advance:n.advance,node:n.node}}return{advance:1,node:new TokenNode(t)}}exports.ComponentValueType=void 0,(e=exports.ComponentValueType||(exports.ComponentValueType={})).Function="function",e.SimpleBlock="simple-block",e.Whitespace="whitespace",e.Comment="comment",e.Token="token",e.UnclosedFunction="unclosed-function",e.UnclosedSimpleBlock="unclosed-simple-block";class FunctionNode{type=exports.ComponentValueType.Function;name;endToken;value;constructor(e,n,o){this.name=e,this.endToken=n,this.value=o}nameTokenValue(){return this.name[4].value}tokens(){return[this.name,...this.value.flatMap((e=>n.isToken(e)?e:e.tokens())),this.endToken]}toString(){const e=this.value.map((e=>n.isToken(e)?n.stringify(e):e.toString())).join("");return n.stringify(this.name)+e+n.stringify(this.endToken)}indexOf(e){return this.value.indexOf(e)}at(e){if("number"==typeof e)return e<0&&(e=this.value.length+e),this.value[e]}walk(e){let n=!1;if(this.value.forEach(((o,t)=>{n||(!1!==e({node:o,parent:this},t)?"walk"in o&&!1===o.walk(e)&&(n=!0):n=!0)})),n)return!1}toJSON(){return{type:this.type,name:this.nameTokenValue(),tokens:this.tokens(),value:this.value.map((e=>e.toJSON()))}}isFunctionNode(){return FunctionNode.isFunctionNode(this)}static isFunctionNode(e){return!!e&&(e instanceof FunctionNode&&e.type===exports.ComponentValueType.Function)}}function consumeFunction(e,o){const t=[];let s=1;for(;;){const i=o[s];if(!i||i[0]===n.TokenType.EOF)return e.onParseError({message:"Unexpected EOF while consuming a function.",start:o[0][2],end:o[o.length-1][3],state:["5.4.9. Consume a function","Unexpected EOF"]}),{advance:o.length,node:new UnclosedFunctionNode(o)};if(i[0]===n.TokenType.CloseParen)return{advance:s+1,node:new FunctionNode(o[0],i,t)};if(i[0]===n.TokenType.Comment||i[0]===n.TokenType.Whitespace){const n=consumeAllCommentsAndWhitespace(e,o.slice(s));s+=n.advance,t.push(...n.nodes);continue}const c=consumeComponentValue(e,o.slice(s));s+=c.advance,t.push(c.node)}}class SimpleBlockNode{type=exports.ComponentValueType.SimpleBlock;startToken;endToken;value;constructor(e,n,o){this.startToken=e,this.endToken=n,this.value=o}tokens(){return[this.startToken,...this.value.flatMap((e=>n.isToken(e)?e:e.tokens())),this.endToken]}toString(){const e=this.value.map((e=>n.isToken(e)?n.stringify(e):e.toString())).join("");return n.stringify(this.startToken)+e+n.stringify(this.endToken)}indexOf(e){return this.value.indexOf(e)}at(e){if("number"==typeof e)return e<0&&(e=this.value.length+e),this.value[e]}walk(e){let n=!1;if(this.value.forEach(((o,t)=>{n||(!1!==e({node:o,parent:this},t)?"walk"in o&&!1===o.walk(e)&&(n=!0):n=!0)})),n)return!1}toJSON(){return{type:this.type,startToken:this.startToken,tokens:this.tokens(),value:this.value.map((e=>e.toJSON()))}}isSimpleBlockNode(){return SimpleBlockNode.isSimpleBlockNode(this)}static isSimpleBlockNode(e){return!!e&&(e instanceof SimpleBlockNode&&e.type===exports.ComponentValueType.SimpleBlock)}}function consumeSimpleBlock(e,o){const t=n.mirrorVariantType(o[0][0]);if(!t)throw new Error("Failed to parse, a mirror variant must exist for all block open tokens.");const s=[];let i=1;for(;;){const c=o[i];if(!c||c[0]===n.TokenType.EOF)return e.onParseError({message:"Unexpected EOF while consuming a simple block.",start:o[0][2],end:o[o.length-1][3],state:["5.4.8. Consume a simple block","Unexpected EOF"]}),{advance:o.length,node:new UnclosedSimpleBlockNode(o)};if(c[0]===t)return{advance:i+1,node:new SimpleBlockNode(o[0],c,s)};if(c[0]===n.TokenType.Comment||c[0]===n.TokenType.Whitespace){const n=consumeAllCommentsAndWhitespace(e,o.slice(i));i+=n.advance,s.push(...n.nodes);continue}const r=consumeComponentValue(e,o.slice(i));i+=r.advance,s.push(r.node)}}class WhitespaceNode{type=exports.ComponentValueType.Whitespace;value;constructor(e){this.value=e}tokens(){return this.value}toString(){return n.stringify(...this.value)}toJSON(){return{type:this.type,tokens:this.tokens()}}isWhitespaceNode(){return WhitespaceNode.isWhitespaceNode(this)}static isWhitespaceNode(e){return!!e&&(e instanceof WhitespaceNode&&e.type===exports.ComponentValueType.Whitespace)}}function consumeWhitespace(e,o){let t=0;for(;;){if(o[t][0]!==n.TokenType.Whitespace)return{advance:t,node:new WhitespaceNode(o.slice(0,t))};t++}}class CommentNode{type=exports.ComponentValueType.Comment;value;constructor(e){this.value=e}tokens(){return[this.value]}toString(){return n.stringify(this.value)}toJSON(){return{type:this.type,tokens:this.tokens()}}isCommentNode(){return CommentNode.isCommentNode(this)}static isCommentNode(e){return!!e&&(e instanceof CommentNode&&e.type===exports.ComponentValueType.Comment)}}function consumeComment(e,n){return{advance:1,node:new CommentNode(n[0])}}function consumeAllCommentsAndWhitespace(e,o){const t=[];let s=0;for(;;)if(o[s][0]!==n.TokenType.Whitespace){if(o[s][0]!==n.TokenType.Comment)return{advance:s,nodes:t};t.push(new CommentNode(o[s])),s++}else{const e=consumeWhitespace(0,o.slice(s));s+=e.advance,t.push(e.node)}}class TokenNode{type=exports.ComponentValueType.Token;value;constructor(e){this.value=e}tokens(){return[this.value]}toString(){return n.stringify(this.value)}toJSON(){return{type:this.type,tokens:this.tokens()}}isTokenNode(){return TokenNode.isTokenNode(this)}static isTokenNode(e){return!!e&&(e instanceof TokenNode&&e.type===exports.ComponentValueType.Token)}}class UnclosedFunctionNode{type=exports.ComponentValueType.UnclosedFunction;value;constructor(e){this.value=e}tokens(){return this.value}toString(){return n.stringify(...this.value)}toJSON(){return{type:this.type,tokens:this.tokens()}}isUnclosedFunctionNode(){return UnclosedFunctionNode.isUnclosedFunctionNode(this)}static isUnclosedFunctionNode(e){return!!e&&(e instanceof UnclosedFunctionNode&&e.type===exports.ComponentValueType.UnclosedFunction)}}class UnclosedSimpleBlockNode{type=exports.ComponentValueType.UnclosedSimpleBlock;value;constructor(e){this.value=e}tokens(){return this.value}toString(){return n.stringify(...this.value)}toJSON(){return{type:this.type,tokens:this.tokens()}}isUnclosedSimpleBlockNode(){return UnclosedSimpleBlockNode.isUnclosedSimpleBlockNode(this)}static isUnclosedSimpleBlockNode(e){return!!e&&(e instanceof UnclosedSimpleBlockNode&&e.type===exports.ComponentValueType.UnclosedSimpleBlock)}}exports.CommentNode=CommentNode,exports.FunctionNode=FunctionNode,exports.SimpleBlockNode=SimpleBlockNode,exports.TokenNode=TokenNode,exports.UnclosedFunctionNode=UnclosedFunctionNode,exports.UnclosedSimpleBlockNode=UnclosedSimpleBlockNode,exports.WhitespaceNode=WhitespaceNode,exports.consumeAllCommentsAndWhitespace=consumeAllCommentsAndWhitespace,exports.consumeComment=consumeComment,exports.consumeComponentValue=consumeComponentValue,exports.consumeFunction=consumeFunction,exports.consumeSimpleBlock=consumeSimpleBlock,exports.consumeWhitespace=consumeWhitespace,exports.gatherNodeAncestry=function gatherNodeAncestry(e){const n=new Map;return e.walk((e=>{Array.isArray(e.node)?e.node.forEach((o=>{n.set(o,e.parent)})):n.set(e.node,e.parent)})),n},exports.isCommentNode=function isCommentNode(e){return CommentNode.isCommentNode(e)},exports.isFunctionNode=function isFunctionNode(e){return FunctionNode.isFunctionNode(e)},exports.isSimpleBlockNode=function isSimpleBlockNode(e){return SimpleBlockNode.isSimpleBlockNode(e)},exports.isTokenNode=function isTokenNode(e){return TokenNode.isTokenNode(e)},exports.isUnclosedFunctionNode=function isUnclosedFunctionNode(e){return UnclosedFunctionNode.isUnclosedFunctionNode(e)},exports.isUnclosedSimpleBlockNode=function isUnclosedSimpleBlockNode(e){return UnclosedSimpleBlockNode.isUnclosedSimpleBlockNode(e)},exports.isWhitespaceNode=function isWhitespaceNode(e){return WhitespaceNode.isWhitespaceNode(e)},exports.parseCommaSeparatedListOfComponentValues=function parseCommaSeparatedListOfComponentValues(e,o){const t={onParseError:(null==o?void 0:o.onParseError)??(()=>{})},s=[...e];if(0===e.length)return[];s[s.length-1][0]!==n.TokenType.EOF&&s.push([n.TokenType.EOF,"",s[s.length-1][2],s[s.length-1][3],void 0]);const i=[];let c=[],r=0;for(;;){if(!s[r]||s[r][0]===n.TokenType.EOF)return c.length&&i.push(c),i;if(s[r][0]===n.TokenType.Comma){c.length&&i.push(c),c=[],r++;continue}const o=consumeComponentValue(t,e.slice(r));c.push(o.node),r+=o.advance}},exports.parseComponentValue=function parseComponentValue(e,o){const t={onParseError:(null==o?void 0:o.onParseError)??(()=>{})},s=[...e];s[s.length-1][0]!==n.TokenType.EOF&&s.push([n.TokenType.EOF,"",s[s.length-1][2],s[s.length-1][3],void 0]);const i=consumeComponentValue(t,s);if(s[Math.min(i.advance,s.length-1)][0]===n.TokenType.EOF)return i.node;t.onParseError({message:"Expected EOF after parsing a component value.",start:e[0][2],end:e[e.length-1][3],state:["5.3.9. Parse a component value","Expected EOF"]})},exports.parseListOfComponentValues=function parseListOfComponentValues(e,o){const t={onParseError:(null==o?void 0:o.onParseError)??(()=>{})},s=[...e];s[s.length-1][0]!==n.TokenType.EOF&&s.push([n.TokenType.EOF,"",s[s.length-1][2],s[s.length-1][3],void 0]);const i=[];let c=0;for(;;){if(!s[c]||s[c][0]===n.TokenType.EOF)return i;const e=consumeComponentValue(t,s.slice(c));i.push(e.node),c+=e.advance}}; +"use strict";var e,n=require("@csstools/css-tokenizer");function consumeComponentValue(e,o){const t=o[0];if(t[0]===n.TokenType.OpenParen||t[0]===n.TokenType.OpenCurly||t[0]===n.TokenType.OpenSquare){const n=consumeSimpleBlock(e,o);return{advance:n.advance,node:n.node}}if(t[0]===n.TokenType.Function){const n=consumeFunction(e,o);return{advance:n.advance,node:n.node}}if(t[0]===n.TokenType.Whitespace){const n=consumeWhitespace(e,o);return{advance:n.advance,node:n.node}}if(t[0]===n.TokenType.Comment){const n=consumeComment(e,o);return{advance:n.advance,node:n.node}}return{advance:1,node:new TokenNode(t)}}exports.ComponentValueType=void 0,(e=exports.ComponentValueType||(exports.ComponentValueType={})).Function="function",e.SimpleBlock="simple-block",e.Whitespace="whitespace",e.Comment="comment",e.Token="token",e.UnclosedFunction="unclosed-function",e.UnclosedSimpleBlock="unclosed-simple-block";class FunctionNode{type=exports.ComponentValueType.Function;name;endToken;value;constructor(e,n,o){this.name=e,this.endToken=n,this.value=o}nameTokenValue(){return this.name[4].value}tokens(){return[this.name,...this.value.flatMap((e=>n.isToken(e)?e:e.tokens())),this.endToken]}toString(){const e=this.value.map((e=>n.isToken(e)?n.stringify(e):e.toString())).join("");return n.stringify(this.name)+e+n.stringify(this.endToken)}indexOf(e){return this.value.indexOf(e)}at(e){if("number"==typeof e)return e<0&&(e=this.value.length+e),this.value[e]}walk(e){let n=!1;if(this.value.forEach(((o,t)=>{n||(!1!==e({node:o,parent:this},t)?"walk"in o&&!1===o.walk(e)&&(n=!0):n=!0)})),n)return!1}toJSON(){return{type:this.type,name:this.nameTokenValue(),tokens:this.tokens(),value:this.value.map((e=>e.toJSON()))}}isFunctionNode(){return FunctionNode.isFunctionNode(this)}static isFunctionNode(e){return!!e&&(e instanceof FunctionNode&&e.type===exports.ComponentValueType.Function)}}function consumeFunction(e,o){const t=[];let s=1;for(;;){const i=o[s];if(!i||i[0]===n.TokenType.EOF)return e.onParseError({message:"Unexpected EOF while consuming a function.",start:o[0][2],end:o[o.length-1][3],state:["5.4.9. Consume a function","Unexpected EOF"]}),{advance:o.length,node:new UnclosedFunctionNode(o)};if(i[0]===n.TokenType.CloseParen)return{advance:s+1,node:new FunctionNode(o[0],i,t)};if(i[0]===n.TokenType.Comment||i[0]===n.TokenType.Whitespace){const n=consumeAllCommentsAndWhitespace(e,o.slice(s));s+=n.advance,t.push(...n.nodes);continue}const c=consumeComponentValue(e,o.slice(s));s+=c.advance,t.push(c.node)}}class SimpleBlockNode{type=exports.ComponentValueType.SimpleBlock;startToken;endToken;value;constructor(e,n,o){this.startToken=e,this.endToken=n,this.value=o}tokens(){return[this.startToken,...this.value.flatMap((e=>n.isToken(e)?e:e.tokens())),this.endToken]}toString(){const e=this.value.map((e=>n.isToken(e)?n.stringify(e):e.toString())).join("");return n.stringify(this.startToken)+e+n.stringify(this.endToken)}indexOf(e){return this.value.indexOf(e)}at(e){if("number"==typeof e)return e<0&&(e=this.value.length+e),this.value[e]}walk(e){let n=!1;if(this.value.forEach(((o,t)=>{n||(!1!==e({node:o,parent:this},t)?"walk"in o&&!1===o.walk(e)&&(n=!0):n=!0)})),n)return!1}toJSON(){return{type:this.type,startToken:this.startToken,tokens:this.tokens(),value:this.value.map((e=>e.toJSON()))}}isSimpleBlockNode(){return SimpleBlockNode.isSimpleBlockNode(this)}static isSimpleBlockNode(e){return!!e&&(e instanceof SimpleBlockNode&&e.type===exports.ComponentValueType.SimpleBlock)}}function consumeSimpleBlock(e,o){const t=n.mirrorVariantType(o[0][0]);if(!t)throw new Error("Failed to parse, a mirror variant must exist for all block open tokens.");const s=[];let i=1;for(;;){const c=o[i];if(!c||c[0]===n.TokenType.EOF)return e.onParseError({message:"Unexpected EOF while consuming a simple block.",start:o[0][2],end:o[o.length-1][3],state:["5.4.8. Consume a simple block","Unexpected EOF"]}),{advance:o.length,node:new UnclosedSimpleBlockNode(o)};if(c[0]===t)return{advance:i+1,node:new SimpleBlockNode(o[0],c,s)};if(c[0]===n.TokenType.Comment||c[0]===n.TokenType.Whitespace){const n=consumeAllCommentsAndWhitespace(e,o.slice(i));i+=n.advance,s.push(...n.nodes);continue}const r=consumeComponentValue(e,o.slice(i));i+=r.advance,s.push(r.node)}}class WhitespaceNode{type=exports.ComponentValueType.Whitespace;value;constructor(e){this.value=e}tokens(){return this.value}toString(){return n.stringify(...this.value)}toJSON(){return{type:this.type,tokens:this.tokens()}}isWhitespaceNode(){return WhitespaceNode.isWhitespaceNode(this)}static isWhitespaceNode(e){return!!e&&(e instanceof WhitespaceNode&&e.type===exports.ComponentValueType.Whitespace)}}function consumeWhitespace(e,o){let t=0;for(;;){if(o[t][0]!==n.TokenType.Whitespace)return{advance:t,node:new WhitespaceNode(o.slice(0,t))};t++}}class CommentNode{type=exports.ComponentValueType.Comment;value;constructor(e){this.value=e}tokens(){return[this.value]}toString(){return n.stringify(this.value)}toJSON(){return{type:this.type,tokens:this.tokens()}}isCommentNode(){return CommentNode.isCommentNode(this)}static isCommentNode(e){return!!e&&(e instanceof CommentNode&&e.type===exports.ComponentValueType.Comment)}}function consumeComment(e,n){return{advance:1,node:new CommentNode(n[0])}}function consumeAllCommentsAndWhitespace(e,o){const t=[];let s=0;for(;;)if(o[s][0]!==n.TokenType.Whitespace){if(o[s][0]!==n.TokenType.Comment)return{advance:s,nodes:t};t.push(new CommentNode(o[s])),s++}else{const e=consumeWhitespace(0,o.slice(s));s+=e.advance,t.push(e.node)}}class TokenNode{type=exports.ComponentValueType.Token;value;constructor(e){this.value=e}tokens(){return[this.value]}toString(){return n.stringify(this.value)}toJSON(){return{type:this.type,tokens:this.tokens()}}isTokenNode(){return TokenNode.isTokenNode(this)}static isTokenNode(e){return!!e&&(e instanceof TokenNode&&e.type===exports.ComponentValueType.Token)}}class UnclosedFunctionNode{type=exports.ComponentValueType.UnclosedFunction;value;constructor(e){this.value=e}tokens(){return this.value}toString(){return n.stringify(...this.value)}toJSON(){return{type:this.type,tokens:this.tokens()}}isUnclosedFunctionNode(){return UnclosedFunctionNode.isUnclosedFunctionNode(this)}static isUnclosedFunctionNode(e){return!!e&&(e instanceof UnclosedFunctionNode&&e.type===exports.ComponentValueType.UnclosedFunction)}}class UnclosedSimpleBlockNode{type=exports.ComponentValueType.UnclosedSimpleBlock;value;constructor(e){this.value=e}tokens(){return this.value}toString(){return n.stringify(...this.value)}toJSON(){return{type:this.type,tokens:this.tokens()}}isUnclosedSimpleBlockNode(){return UnclosedSimpleBlockNode.isUnclosedSimpleBlockNode(this)}static isUnclosedSimpleBlockNode(e){return!!e&&(e instanceof UnclosedSimpleBlockNode&&e.type===exports.ComponentValueType.UnclosedSimpleBlock)}}exports.CommentNode=CommentNode,exports.FunctionNode=FunctionNode,exports.SimpleBlockNode=SimpleBlockNode,exports.TokenNode=TokenNode,exports.UnclosedFunctionNode=UnclosedFunctionNode,exports.UnclosedSimpleBlockNode=UnclosedSimpleBlockNode,exports.WhitespaceNode=WhitespaceNode,exports.consumeAllCommentsAndWhitespace=consumeAllCommentsAndWhitespace,exports.consumeComment=consumeComment,exports.consumeComponentValue=consumeComponentValue,exports.consumeFunction=consumeFunction,exports.consumeSimpleBlock=consumeSimpleBlock,exports.consumeWhitespace=consumeWhitespace,exports.gatherNodeAncestry=function gatherNodeAncestry(e){const n=new Map;return e.walk((e=>{Array.isArray(e.node)?e.node.forEach((o=>{n.set(o,e.parent)})):n.set(e.node,e.parent)})),n},exports.isCommentNode=function isCommentNode(e){return CommentNode.isCommentNode(e)},exports.isFunctionNode=function isFunctionNode(e){return FunctionNode.isFunctionNode(e)},exports.isSimpleBlockNode=function isSimpleBlockNode(e){return SimpleBlockNode.isSimpleBlockNode(e)},exports.isTokenNode=function isTokenNode(e){return TokenNode.isTokenNode(e)},exports.isUnclosedFunctionNode=function isUnclosedFunctionNode(e){return UnclosedFunctionNode.isUnclosedFunctionNode(e)},exports.isUnclosedSimpleBlockNode=function isUnclosedSimpleBlockNode(e){return UnclosedSimpleBlockNode.isUnclosedSimpleBlockNode(e)},exports.isWhitespaceNode=function isWhitespaceNode(e){return WhitespaceNode.isWhitespaceNode(e)},exports.parseCommaSeparatedListOfComponentValues=function parseCommaSeparatedListOfComponentValues(e,o){const t={onParseError:(null==o?void 0:o.onParseError)??(()=>{})},s=[...e];if(0===e.length)return[];s[s.length-1][0]!==n.TokenType.EOF&&s.push([n.TokenType.EOF,"",s[s.length-1][2],s[s.length-1][3],void 0]);const i=[];let c=[],r=0;for(;;){if(!s[r]||s[r][0]===n.TokenType.EOF)return c.length&&i.push(c),i;if(s[r][0]===n.TokenType.Comma){i.push(c),c=[],r++;continue}const o=consumeComponentValue(t,e.slice(r));c.push(o.node),r+=o.advance}},exports.parseComponentValue=function parseComponentValue(e,o){const t={onParseError:(null==o?void 0:o.onParseError)??(()=>{})},s=[...e];s[s.length-1][0]!==n.TokenType.EOF&&s.push([n.TokenType.EOF,"",s[s.length-1][2],s[s.length-1][3],void 0]);const i=consumeComponentValue(t,s);if(s[Math.min(i.advance,s.length-1)][0]===n.TokenType.EOF)return i.node;t.onParseError({message:"Expected EOF after parsing a component value.",start:e[0][2],end:e[e.length-1][3],state:["5.3.9. Parse a component value","Expected EOF"]})},exports.parseListOfComponentValues=function parseListOfComponentValues(e,o){const t={onParseError:(null==o?void 0:o.onParseError)??(()=>{})},s=[...e];s[s.length-1][0]!==n.TokenType.EOF&&s.push([n.TokenType.EOF,"",s[s.length-1][2],s[s.length-1][3],void 0]);const i=[];let c=0;for(;;){if(!s[c]||s[c][0]===n.TokenType.EOF)return i;const e=consumeComponentValue(t,s.slice(c));i.push(e.node),c+=e.advance}}; diff --git a/packages/css-parser-algorithms/dist/index.mjs b/packages/css-parser-algorithms/dist/index.mjs index 2864b392c..cb6b5c249 100644 --- a/packages/css-parser-algorithms/dist/index.mjs +++ b/packages/css-parser-algorithms/dist/index.mjs @@ -1 +1 @@ -import{TokenType as e,isToken as n,stringify as o,mirrorVariantType as t}from"@csstools/css-tokenizer";var s;function consumeComponentValue(n,o){const t=o[0];if(t[0]===e.OpenParen||t[0]===e.OpenCurly||t[0]===e.OpenSquare){const e=consumeSimpleBlock(n,o);return{advance:e.advance,node:e.node}}if(t[0]===e.Function){const e=consumeFunction(n,o);return{advance:e.advance,node:e.node}}if(t[0]===e.Whitespace){const e=consumeWhitespace(n,o);return{advance:e.advance,node:e.node}}if(t[0]===e.Comment){const e=consumeComment(n,o);return{advance:e.advance,node:e.node}}return{advance:1,node:new TokenNode(t)}}!function(e){e.Function="function",e.SimpleBlock="simple-block",e.Whitespace="whitespace",e.Comment="comment",e.Token="token",e.UnclosedFunction="unclosed-function",e.UnclosedSimpleBlock="unclosed-simple-block"}(s||(s={}));class FunctionNode{type=s.Function;name;endToken;value;constructor(e,n,o){this.name=e,this.endToken=n,this.value=o}nameTokenValue(){return this.name[4].value}tokens(){return[this.name,...this.value.flatMap((e=>n(e)?e:e.tokens())),this.endToken]}toString(){const e=this.value.map((e=>n(e)?o(e):e.toString())).join("");return o(this.name)+e+o(this.endToken)}indexOf(e){return this.value.indexOf(e)}at(e){if("number"==typeof e)return e<0&&(e=this.value.length+e),this.value[e]}walk(e){let n=!1;if(this.value.forEach(((o,t)=>{n||(!1!==e({node:o,parent:this},t)?"walk"in o&&!1===o.walk(e)&&(n=!0):n=!0)})),n)return!1}toJSON(){return{type:this.type,name:this.nameTokenValue(),tokens:this.tokens(),value:this.value.map((e=>e.toJSON()))}}isFunctionNode(){return FunctionNode.isFunctionNode(this)}static isFunctionNode(e){return!!e&&(e instanceof FunctionNode&&e.type===s.Function)}}function consumeFunction(n,o){const t=[];let s=1;for(;;){const i=o[s];if(!i||i[0]===e.EOF)return n.onParseError({message:"Unexpected EOF while consuming a function.",start:o[0][2],end:o[o.length-1][3],state:["5.4.9. Consume a function","Unexpected EOF"]}),{advance:o.length,node:new UnclosedFunctionNode(o)};if(i[0]===e.CloseParen)return{advance:s+1,node:new FunctionNode(o[0],i,t)};if(i[0]===e.Comment||i[0]===e.Whitespace){const e=consumeAllCommentsAndWhitespace(n,o.slice(s));s+=e.advance,t.push(...e.nodes);continue}const c=consumeComponentValue(n,o.slice(s));s+=c.advance,t.push(c.node)}}class SimpleBlockNode{type=s.SimpleBlock;startToken;endToken;value;constructor(e,n,o){this.startToken=e,this.endToken=n,this.value=o}tokens(){return[this.startToken,...this.value.flatMap((e=>n(e)?e:e.tokens())),this.endToken]}toString(){const e=this.value.map((e=>n(e)?o(e):e.toString())).join("");return o(this.startToken)+e+o(this.endToken)}indexOf(e){return this.value.indexOf(e)}at(e){if("number"==typeof e)return e<0&&(e=this.value.length+e),this.value[e]}walk(e){let n=!1;if(this.value.forEach(((o,t)=>{n||(!1!==e({node:o,parent:this},t)?"walk"in o&&!1===o.walk(e)&&(n=!0):n=!0)})),n)return!1}toJSON(){return{type:this.type,startToken:this.startToken,tokens:this.tokens(),value:this.value.map((e=>e.toJSON()))}}isSimpleBlockNode(){return SimpleBlockNode.isSimpleBlockNode(this)}static isSimpleBlockNode(e){return!!e&&(e instanceof SimpleBlockNode&&e.type===s.SimpleBlock)}}function consumeSimpleBlock(n,o){const s=t(o[0][0]);if(!s)throw new Error("Failed to parse, a mirror variant must exist for all block open tokens.");const i=[];let c=1;for(;;){const t=o[c];if(!t||t[0]===e.EOF)return n.onParseError({message:"Unexpected EOF while consuming a simple block.",start:o[0][2],end:o[o.length-1][3],state:["5.4.8. Consume a simple block","Unexpected EOF"]}),{advance:o.length,node:new UnclosedSimpleBlockNode(o)};if(t[0]===s)return{advance:c+1,node:new SimpleBlockNode(o[0],t,i)};if(t[0]===e.Comment||t[0]===e.Whitespace){const e=consumeAllCommentsAndWhitespace(n,o.slice(c));c+=e.advance,i.push(...e.nodes);continue}const r=consumeComponentValue(n,o.slice(c));c+=r.advance,i.push(r.node)}}class WhitespaceNode{type=s.Whitespace;value;constructor(e){this.value=e}tokens(){return this.value}toString(){return o(...this.value)}toJSON(){return{type:this.type,tokens:this.tokens()}}isWhitespaceNode(){return WhitespaceNode.isWhitespaceNode(this)}static isWhitespaceNode(e){return!!e&&(e instanceof WhitespaceNode&&e.type===s.Whitespace)}}function consumeWhitespace(n,o){let t=0;for(;;){if(o[t][0]!==e.Whitespace)return{advance:t,node:new WhitespaceNode(o.slice(0,t))};t++}}class CommentNode{type=s.Comment;value;constructor(e){this.value=e}tokens(){return[this.value]}toString(){return o(this.value)}toJSON(){return{type:this.type,tokens:this.tokens()}}isCommentNode(){return CommentNode.isCommentNode(this)}static isCommentNode(e){return!!e&&(e instanceof CommentNode&&e.type===s.Comment)}}function consumeComment(e,n){return{advance:1,node:new CommentNode(n[0])}}function consumeAllCommentsAndWhitespace(n,o){const t=[];let s=0;for(;;)if(o[s][0]!==e.Whitespace){if(o[s][0]!==e.Comment)return{advance:s,nodes:t};t.push(new CommentNode(o[s])),s++}else{const e=consumeWhitespace(0,o.slice(s));s+=e.advance,t.push(e.node)}}class TokenNode{type=s.Token;value;constructor(e){this.value=e}tokens(){return[this.value]}toString(){return o(this.value)}toJSON(){return{type:this.type,tokens:this.tokens()}}isTokenNode(){return TokenNode.isTokenNode(this)}static isTokenNode(e){return!!e&&(e instanceof TokenNode&&e.type===s.Token)}}class UnclosedFunctionNode{type=s.UnclosedFunction;value;constructor(e){this.value=e}tokens(){return this.value}toString(){return o(...this.value)}toJSON(){return{type:this.type,tokens:this.tokens()}}isUnclosedFunctionNode(){return UnclosedFunctionNode.isUnclosedFunctionNode(this)}static isUnclosedFunctionNode(e){return!!e&&(e instanceof UnclosedFunctionNode&&e.type===s.UnclosedFunction)}}class UnclosedSimpleBlockNode{type=s.UnclosedSimpleBlock;value;constructor(e){this.value=e}tokens(){return this.value}toString(){return o(...this.value)}toJSON(){return{type:this.type,tokens:this.tokens()}}isUnclosedSimpleBlockNode(){return UnclosedSimpleBlockNode.isUnclosedSimpleBlockNode(this)}static isUnclosedSimpleBlockNode(e){return!!e&&(e instanceof UnclosedSimpleBlockNode&&e.type===s.UnclosedSimpleBlock)}}function parseComponentValue(n,o){const t={onParseError:(null==o?void 0:o.onParseError)??(()=>{})},s=[...n];s[s.length-1][0]!==e.EOF&&s.push([e.EOF,"",s[s.length-1][2],s[s.length-1][3],void 0]);const i=consumeComponentValue(t,s);if(s[Math.min(i.advance,s.length-1)][0]===e.EOF)return i.node;t.onParseError({message:"Expected EOF after parsing a component value.",start:n[0][2],end:n[n.length-1][3],state:["5.3.9. Parse a component value","Expected EOF"]})}function parseListOfComponentValues(n,o){const t={onParseError:(null==o?void 0:o.onParseError)??(()=>{})},s=[...n];s[s.length-1][0]!==e.EOF&&s.push([e.EOF,"",s[s.length-1][2],s[s.length-1][3],void 0]);const i=[];let c=0;for(;;){if(!s[c]||s[c][0]===e.EOF)return i;const n=consumeComponentValue(t,s.slice(c));i.push(n.node),c+=n.advance}}function parseCommaSeparatedListOfComponentValues(n,o){const t={onParseError:(null==o?void 0:o.onParseError)??(()=>{})},s=[...n];if(0===n.length)return[];s[s.length-1][0]!==e.EOF&&s.push([e.EOF,"",s[s.length-1][2],s[s.length-1][3],void 0]);const i=[];let c=[],r=0;for(;;){if(!s[r]||s[r][0]===e.EOF)return c.length&&i.push(c),i;if(s[r][0]===e.Comma){c.length&&i.push(c),c=[],r++;continue}const o=consumeComponentValue(t,n.slice(r));c.push(o.node),r+=o.advance}}function gatherNodeAncestry(e){const n=new Map;return e.walk((e=>{Array.isArray(e.node)?e.node.forEach((o=>{n.set(o,e.parent)})):n.set(e.node,e.parent)})),n}function isSimpleBlockNode(e){return SimpleBlockNode.isSimpleBlockNode(e)}function isFunctionNode(e){return FunctionNode.isFunctionNode(e)}function isUnclosedSimpleBlockNode(e){return UnclosedSimpleBlockNode.isUnclosedSimpleBlockNode(e)}function isUnclosedFunctionNode(e){return UnclosedFunctionNode.isUnclosedFunctionNode(e)}function isWhitespaceNode(e){return WhitespaceNode.isWhitespaceNode(e)}function isCommentNode(e){return CommentNode.isCommentNode(e)}function isTokenNode(e){return TokenNode.isTokenNode(e)}export{CommentNode,s as ComponentValueType,FunctionNode,SimpleBlockNode,TokenNode,UnclosedFunctionNode,UnclosedSimpleBlockNode,WhitespaceNode,consumeAllCommentsAndWhitespace,consumeComment,consumeComponentValue,consumeFunction,consumeSimpleBlock,consumeWhitespace,gatherNodeAncestry,isCommentNode,isFunctionNode,isSimpleBlockNode,isTokenNode,isUnclosedFunctionNode,isUnclosedSimpleBlockNode,isWhitespaceNode,parseCommaSeparatedListOfComponentValues,parseComponentValue,parseListOfComponentValues}; +import{TokenType as e,isToken as n,stringify as o,mirrorVariantType as t}from"@csstools/css-tokenizer";var s;function consumeComponentValue(n,o){const t=o[0];if(t[0]===e.OpenParen||t[0]===e.OpenCurly||t[0]===e.OpenSquare){const e=consumeSimpleBlock(n,o);return{advance:e.advance,node:e.node}}if(t[0]===e.Function){const e=consumeFunction(n,o);return{advance:e.advance,node:e.node}}if(t[0]===e.Whitespace){const e=consumeWhitespace(n,o);return{advance:e.advance,node:e.node}}if(t[0]===e.Comment){const e=consumeComment(n,o);return{advance:e.advance,node:e.node}}return{advance:1,node:new TokenNode(t)}}!function(e){e.Function="function",e.SimpleBlock="simple-block",e.Whitespace="whitespace",e.Comment="comment",e.Token="token",e.UnclosedFunction="unclosed-function",e.UnclosedSimpleBlock="unclosed-simple-block"}(s||(s={}));class FunctionNode{type=s.Function;name;endToken;value;constructor(e,n,o){this.name=e,this.endToken=n,this.value=o}nameTokenValue(){return this.name[4].value}tokens(){return[this.name,...this.value.flatMap((e=>n(e)?e:e.tokens())),this.endToken]}toString(){const e=this.value.map((e=>n(e)?o(e):e.toString())).join("");return o(this.name)+e+o(this.endToken)}indexOf(e){return this.value.indexOf(e)}at(e){if("number"==typeof e)return e<0&&(e=this.value.length+e),this.value[e]}walk(e){let n=!1;if(this.value.forEach(((o,t)=>{n||(!1!==e({node:o,parent:this},t)?"walk"in o&&!1===o.walk(e)&&(n=!0):n=!0)})),n)return!1}toJSON(){return{type:this.type,name:this.nameTokenValue(),tokens:this.tokens(),value:this.value.map((e=>e.toJSON()))}}isFunctionNode(){return FunctionNode.isFunctionNode(this)}static isFunctionNode(e){return!!e&&(e instanceof FunctionNode&&e.type===s.Function)}}function consumeFunction(n,o){const t=[];let s=1;for(;;){const i=o[s];if(!i||i[0]===e.EOF)return n.onParseError({message:"Unexpected EOF while consuming a function.",start:o[0][2],end:o[o.length-1][3],state:["5.4.9. Consume a function","Unexpected EOF"]}),{advance:o.length,node:new UnclosedFunctionNode(o)};if(i[0]===e.CloseParen)return{advance:s+1,node:new FunctionNode(o[0],i,t)};if(i[0]===e.Comment||i[0]===e.Whitespace){const e=consumeAllCommentsAndWhitespace(n,o.slice(s));s+=e.advance,t.push(...e.nodes);continue}const c=consumeComponentValue(n,o.slice(s));s+=c.advance,t.push(c.node)}}class SimpleBlockNode{type=s.SimpleBlock;startToken;endToken;value;constructor(e,n,o){this.startToken=e,this.endToken=n,this.value=o}tokens(){return[this.startToken,...this.value.flatMap((e=>n(e)?e:e.tokens())),this.endToken]}toString(){const e=this.value.map((e=>n(e)?o(e):e.toString())).join("");return o(this.startToken)+e+o(this.endToken)}indexOf(e){return this.value.indexOf(e)}at(e){if("number"==typeof e)return e<0&&(e=this.value.length+e),this.value[e]}walk(e){let n=!1;if(this.value.forEach(((o,t)=>{n||(!1!==e({node:o,parent:this},t)?"walk"in o&&!1===o.walk(e)&&(n=!0):n=!0)})),n)return!1}toJSON(){return{type:this.type,startToken:this.startToken,tokens:this.tokens(),value:this.value.map((e=>e.toJSON()))}}isSimpleBlockNode(){return SimpleBlockNode.isSimpleBlockNode(this)}static isSimpleBlockNode(e){return!!e&&(e instanceof SimpleBlockNode&&e.type===s.SimpleBlock)}}function consumeSimpleBlock(n,o){const s=t(o[0][0]);if(!s)throw new Error("Failed to parse, a mirror variant must exist for all block open tokens.");const i=[];let c=1;for(;;){const t=o[c];if(!t||t[0]===e.EOF)return n.onParseError({message:"Unexpected EOF while consuming a simple block.",start:o[0][2],end:o[o.length-1][3],state:["5.4.8. Consume a simple block","Unexpected EOF"]}),{advance:o.length,node:new UnclosedSimpleBlockNode(o)};if(t[0]===s)return{advance:c+1,node:new SimpleBlockNode(o[0],t,i)};if(t[0]===e.Comment||t[0]===e.Whitespace){const e=consumeAllCommentsAndWhitespace(n,o.slice(c));c+=e.advance,i.push(...e.nodes);continue}const r=consumeComponentValue(n,o.slice(c));c+=r.advance,i.push(r.node)}}class WhitespaceNode{type=s.Whitespace;value;constructor(e){this.value=e}tokens(){return this.value}toString(){return o(...this.value)}toJSON(){return{type:this.type,tokens:this.tokens()}}isWhitespaceNode(){return WhitespaceNode.isWhitespaceNode(this)}static isWhitespaceNode(e){return!!e&&(e instanceof WhitespaceNode&&e.type===s.Whitespace)}}function consumeWhitespace(n,o){let t=0;for(;;){if(o[t][0]!==e.Whitespace)return{advance:t,node:new WhitespaceNode(o.slice(0,t))};t++}}class CommentNode{type=s.Comment;value;constructor(e){this.value=e}tokens(){return[this.value]}toString(){return o(this.value)}toJSON(){return{type:this.type,tokens:this.tokens()}}isCommentNode(){return CommentNode.isCommentNode(this)}static isCommentNode(e){return!!e&&(e instanceof CommentNode&&e.type===s.Comment)}}function consumeComment(e,n){return{advance:1,node:new CommentNode(n[0])}}function consumeAllCommentsAndWhitespace(n,o){const t=[];let s=0;for(;;)if(o[s][0]!==e.Whitespace){if(o[s][0]!==e.Comment)return{advance:s,nodes:t};t.push(new CommentNode(o[s])),s++}else{const e=consumeWhitespace(0,o.slice(s));s+=e.advance,t.push(e.node)}}class TokenNode{type=s.Token;value;constructor(e){this.value=e}tokens(){return[this.value]}toString(){return o(this.value)}toJSON(){return{type:this.type,tokens:this.tokens()}}isTokenNode(){return TokenNode.isTokenNode(this)}static isTokenNode(e){return!!e&&(e instanceof TokenNode&&e.type===s.Token)}}class UnclosedFunctionNode{type=s.UnclosedFunction;value;constructor(e){this.value=e}tokens(){return this.value}toString(){return o(...this.value)}toJSON(){return{type:this.type,tokens:this.tokens()}}isUnclosedFunctionNode(){return UnclosedFunctionNode.isUnclosedFunctionNode(this)}static isUnclosedFunctionNode(e){return!!e&&(e instanceof UnclosedFunctionNode&&e.type===s.UnclosedFunction)}}class UnclosedSimpleBlockNode{type=s.UnclosedSimpleBlock;value;constructor(e){this.value=e}tokens(){return this.value}toString(){return o(...this.value)}toJSON(){return{type:this.type,tokens:this.tokens()}}isUnclosedSimpleBlockNode(){return UnclosedSimpleBlockNode.isUnclosedSimpleBlockNode(this)}static isUnclosedSimpleBlockNode(e){return!!e&&(e instanceof UnclosedSimpleBlockNode&&e.type===s.UnclosedSimpleBlock)}}function parseComponentValue(n,o){const t={onParseError:(null==o?void 0:o.onParseError)??(()=>{})},s=[...n];s[s.length-1][0]!==e.EOF&&s.push([e.EOF,"",s[s.length-1][2],s[s.length-1][3],void 0]);const i=consumeComponentValue(t,s);if(s[Math.min(i.advance,s.length-1)][0]===e.EOF)return i.node;t.onParseError({message:"Expected EOF after parsing a component value.",start:n[0][2],end:n[n.length-1][3],state:["5.3.9. Parse a component value","Expected EOF"]})}function parseListOfComponentValues(n,o){const t={onParseError:(null==o?void 0:o.onParseError)??(()=>{})},s=[...n];s[s.length-1][0]!==e.EOF&&s.push([e.EOF,"",s[s.length-1][2],s[s.length-1][3],void 0]);const i=[];let c=0;for(;;){if(!s[c]||s[c][0]===e.EOF)return i;const n=consumeComponentValue(t,s.slice(c));i.push(n.node),c+=n.advance}}function parseCommaSeparatedListOfComponentValues(n,o){const t={onParseError:(null==o?void 0:o.onParseError)??(()=>{})},s=[...n];if(0===n.length)return[];s[s.length-1][0]!==e.EOF&&s.push([e.EOF,"",s[s.length-1][2],s[s.length-1][3],void 0]);const i=[];let c=[],r=0;for(;;){if(!s[r]||s[r][0]===e.EOF)return c.length&&i.push(c),i;if(s[r][0]===e.Comma){i.push(c),c=[],r++;continue}const o=consumeComponentValue(t,n.slice(r));c.push(o.node),r+=o.advance}}function gatherNodeAncestry(e){const n=new Map;return e.walk((e=>{Array.isArray(e.node)?e.node.forEach((o=>{n.set(o,e.parent)})):n.set(e.node,e.parent)})),n}function isSimpleBlockNode(e){return SimpleBlockNode.isSimpleBlockNode(e)}function isFunctionNode(e){return FunctionNode.isFunctionNode(e)}function isUnclosedSimpleBlockNode(e){return UnclosedSimpleBlockNode.isUnclosedSimpleBlockNode(e)}function isUnclosedFunctionNode(e){return UnclosedFunctionNode.isUnclosedFunctionNode(e)}function isWhitespaceNode(e){return WhitespaceNode.isWhitespaceNode(e)}function isCommentNode(e){return CommentNode.isCommentNode(e)}function isTokenNode(e){return TokenNode.isTokenNode(e)}export{CommentNode,s as ComponentValueType,FunctionNode,SimpleBlockNode,TokenNode,UnclosedFunctionNode,UnclosedSimpleBlockNode,WhitespaceNode,consumeAllCommentsAndWhitespace,consumeComment,consumeComponentValue,consumeFunction,consumeSimpleBlock,consumeWhitespace,gatherNodeAncestry,isCommentNode,isFunctionNode,isSimpleBlockNode,isTokenNode,isUnclosedFunctionNode,isUnclosedSimpleBlockNode,isWhitespaceNode,parseCommaSeparatedListOfComponentValues,parseComponentValue,parseListOfComponentValues}; diff --git a/packages/css-parser-algorithms/src/parse/parse-comma-separated-list-of-component-values.ts b/packages/css-parser-algorithms/src/parse/parse-comma-separated-list-of-component-values.ts index a4136fcb8..67b5425c9 100644 --- a/packages/css-parser-algorithms/src/parse/parse-comma-separated-list-of-component-values.ts +++ b/packages/css-parser-algorithms/src/parse/parse-comma-separated-list-of-component-values.ts @@ -43,10 +43,7 @@ export function parseCommaSeparatedListOfComponentValues(tokens: Array } if (tokensCopy[i][0] === TokenType.Comma) { - if (list.length) { - listOfCvls.push(list); - } - + listOfCvls.push(list); list = []; i++; continue; diff --git a/packages/css-parser-algorithms/test/cases/various/0020.list-comma.expect.json b/packages/css-parser-algorithms/test/cases/various/0020.list-comma.expect.json new file mode 100644 index 000000000..dc86ff5de --- /dev/null +++ b/packages/css-parser-algorithms/test/cases/various/0020.list-comma.expect.json @@ -0,0 +1,35 @@ +[ + [ + { + "type": "token", + "tokens": [ + [ + "ident-token", + "a", + 0, + 0, + { + "value": "a" + } + ] + ] + } + ], + [], + [ + { + "type": "token", + "tokens": [ + [ + "ident-token", + "c", + 3, + 3, + { + "value": "c" + } + ] + ] + } + ] +] \ No newline at end of file diff --git a/packages/css-parser-algorithms/test/cases/various/0020.list-space.expect.json b/packages/css-parser-algorithms/test/cases/various/0020.list-space.expect.json new file mode 100644 index 000000000..afd6027d1 --- /dev/null +++ b/packages/css-parser-algorithms/test/cases/various/0020.list-space.expect.json @@ -0,0 +1,54 @@ +[ + { + "type": "token", + "tokens": [ + [ + "ident-token", + "a", + 0, + 0, + { + "value": "a" + } + ] + ] + }, + { + "type": "token", + "tokens": [ + [ + "comma-token", + ",", + 1, + 1, + null + ] + ] + }, + { + "type": "token", + "tokens": [ + [ + "comma-token", + ",", + 2, + 2, + null + ] + ] + }, + { + "type": "token", + "tokens": [ + [ + "ident-token", + "c", + 3, + 3, + { + "value": "c" + } + ] + ] + } +] \ No newline at end of file diff --git a/packages/css-parser-algorithms/test/cases/various/0020.mjs b/packages/css-parser-algorithms/test/cases/various/0020.mjs new file mode 100644 index 000000000..040771614 --- /dev/null +++ b/packages/css-parser-algorithms/test/cases/various/0020.mjs @@ -0,0 +1,13 @@ +import assert from 'assert'; +import { runTest } from '../../util/run-test.mjs'; + +runTest( + 'a,,c', + 'various/0020', + (actual, expected) => { + assert.deepStrictEqual( + actual, + expected, + ); + }, +); diff --git a/packages/css-parser-algorithms/test/test.mjs b/packages/css-parser-algorithms/test/test.mjs index fa184c135..2eef7a9a5 100644 --- a/packages/css-parser-algorithms/test/test.mjs +++ b/packages/css-parser-algorithms/test/test.mjs @@ -35,3 +35,4 @@ import './cases/various/0016.mjs'; import './cases/various/0017.mjs'; import './cases/various/0018.mjs'; import './cases/various/0019.mjs'; +import './cases/various/0020.mjs'; diff --git a/plugins/postcss-custom-media/dist/index.cjs b/plugins/postcss-custom-media/dist/index.cjs index 38e06987f..fad0ad4ae 100644 --- a/plugins/postcss-custom-media/dist/index.cjs +++ b/plugins/postcss-custom-media/dist/index.cjs @@ -1 +1 @@ -"use strict";var e=require("@csstools/css-tokenizer"),r=require("@csstools/media-query-list-parser");function collectCascadeLayerOrder(e){const r=new Map,t=new Map,n=[];e.walkAtRules((e=>{if("layer"!==e.name.toLowerCase())return;{let r=e.parent;for(;r;){if("atrule"!==r.type||"layer"!==r.name.toLowerCase()){if(r===e.root())break;return}r=r.parent}}let o=[];if(e.nodes)o.push((a=e.params,s=1,a.trim()||"csstools-anon-layer--"+s++));else{if(!e.params.trim())return;o=e.params.split(",").map((e=>e.trim()))}var a,s;{let r=e.parent;for(;r&&"atrule"===r.type&&"layer"===r.name.toLowerCase();){const e=t.get(r);e?(o=o.map((r=>e+"."+r)),r=r.parent):r=r.parent}}if(addLayerToModel(n,o),e.nodes){const n=o[0]+".csstools-implicit-layer";r.set(e,n),t.set(e,o[0])}}));for(const e of r.values())addLayerToModel(n,[e]);const o=n.map((e=>e.join("."))),a=new WeakMap;for(const[e,t]of r)a.set(e,o.indexOf(t));return a}function addLayerToModel(e,r){return r.forEach((r=>{const t=r.split(".");e:for(let r=0;r=a&&(o=r,a=s)}-1===o?e.push(n):e.splice(o+1,0,n)}})),e}const t=new Set(["scope","container","layer"]);function isProcessableCustomMediaRule(e){if("custom-media"!==e.name.toLowerCase())return!1;if(!e.params||!e.params.includes("--"))return!1;if(e.nodes&&e.nodes.length>0)return!1;let r=e.parent;for(;r;){if("atrule"===r.type&&!t.has(r.name.toLowerCase()))return!1;r=r.parent}return!0}function removeCyclicReferences(e,r){const t=new Set;let n=r;for(;e.size>0;)try{toposort(Array.from(e.keys()),n);break}catch(r){if(!r._graphNode)throw r;e.delete(r._graphNode),t.add(r._graphNode),n=n.filter((e=>-1===e.indexOf(r._graphNode)))}return t}function toposort(e,r){let t=e.length;const n=new Array(t),o={};let a=t;const s=makeOutgoingEdges(r),i=makeNodesHash(e);for(r.forEach((function(e){if(!i.has(e[0])||!i.has(e[1]))throw new Error("Unknown token. Make sure to provide all tokens used in aliases.")}));a--;)o[a]||visit(e[a],a,new Set);return n;function visit(e,r,a){if(a.has(e)){const r=new Error("Cyclic dependency"+JSON.stringify(e));throw r._graphNode=e,r}if(!i.has(e))throw new Error("Found unknown token. Make sure to provided all involved tokens. Unknown token: "+JSON.stringify(e));if(o[r])return;o[r]=!0;let l=s.get(e)||new Set;if(l=Array.from(l),r=l.length){a.add(e);do{const e=l[--r];visit(e,i.get(e),a)}while(r);a.delete(e)}n[--t]=e}}function makeOutgoingEdges(e){const r=new Map;for(let t=0,n=e.length;t{throw new Error(`Unable to parse media query "${r}"`)}}),n=[];for(;!t.endOfFile();)n.push(t.nextToken());return n}const n=[[e.TokenType.Ident,"max-color",0,0,{value:"max-color"}],[e.TokenType.Colon,":",0,0,void 0],[e.TokenType.Number,"2147477350",0,0,{value:2147477350,type:e.NumberType.Integer}]],o=[[e.TokenType.Ident,"color",0,0,{value:"color"}],[e.TokenType.Colon,":",0,0,void 0],[e.TokenType.Number,"2147477350",0,0,{value:2147477350,type:e.NumberType.Integer}]];function replaceTrueAndFalseTokens(r){let t,a;for(let n=0;n{throw new Error(`Unable to parse media query "${e.stringify(...s)}"`)}}),l=r.parseFromTokens(e.cloneTokens(s),{preserveInvalidMediaQueries:!0,onParseError:()=>{throw new Error(`Unable to parse media query "${e.stringify(...s)}"`)}});for(let e=0;e[e,a]))}}function getCustomMedia(e,r,t){const n=new Map,o=new Map,a=[],s=collectCascadeLayerOrder(e);e.walkAtRules((e=>{if(!isProcessableCustomMediaRule(e))return;const r=parseCustomMedia(e.params);if(!r)return;if(0===r.truthy.length)return;const i=(u=s,(l=e).parent&&"atrule"===l.parent.type&&"layer"===l.parent.name.toLowerCase()?u.has(l.parent)?u.get(l.parent):-1:1/0);var l,u;if(i>=(o.get(r.name)??-1)&&(o.set(r.name,i),n.set(r.name,{truthy:r.truthy,falsy:r.falsy}),a.push(...r.dependsOn)),!t.preserve){const r=e.parent;e.remove(),removeEmptyAncestorBlocks(r)}}));const i=removeCyclicReferences(n,a);for(const t of i.values())e.warn(r,`@custom-media rules have cyclic dependencies for "${t}"`);return n}function removeEmptyAncestorBlocks(e){let r=e;for(;r;){if(r.nodes&&r.nodes.length>0)return;const e=r.parent;r.remove(),r=e}}function transformAtMediaListTokens(e,t){const n=r.parse(e,{preserveInvalidMediaQueries:!0,onParseError:()=>{throw new Error(`Unable to parse media query "${e}"`)}}),o=n.map((e=>e.toString()));for(let e=0;et===e?n:{replaceWith:r}))}const s=transformComplexMediaQuery(r,t);if(s&&0!==s.length&&s[0].replaceWith!==a)return o.flatMap(((r,t)=>t===e?s:[{replaceWith:r}]))}return[]}function transformSimpleMediaQuery(e,t){if(!mediaQueryIsSimple(e))return null;let n=null;return e.walk((e=>{const o=e.node;if(!r.isMediaFeatureBoolean(o))return;const a=o.getName();if(!a.startsWith("--"))return!1;const s=t.get(a);return s?(n={replaceWith:s.truthy.map((e=>e.toString().trim())).join(",")},!1):void 0})),n}function transformComplexMediaQuery(e,t){let a=[];return e.walk((s=>{const i=s.node;if(!r.isMediaFeatureBoolean(i))return;const l=s.parent;if(!r.isMediaFeature(l))return;const u=i.getName();if(!u.startsWith("--"))return!1;const p=t.get(u);if(p){if(1===p.truthy.length&&mediaQueryIsSimple(p.truthy[0])){let t=null;if(p.truthy[0].walk((e=>{if(r.isMediaFeature(e.node))return t=e.node,!1})),t&&t.feature)return l.feature=t.feature,a=[{replaceWith:e.toString()}],!1}const t=r.newMediaFeaturePlain(n[0][4].value,n[2]);l.feature=t.feature;const s=e.toString(),i=r.newMediaFeaturePlain(o[0][4].value,o[2]);l.feature=i.feature;const u=e.toString();return a=[{replaceWith:s,encapsulateWith:p.truthy.map((e=>e.toString().trim())).join(",")},{replaceWith:u,encapsulateWith:p.falsy.map((e=>e.toString().trim())).join(",")}],!1}})),a}function mediaQueryIsSimple(e){if(r.isMediaQueryInvalid(e))return!1;if(r.isMediaQueryWithType(e))return!1;let t=!0;return e.walk((e=>{if(r.isMediaAnd(e.node)||r.isMediaOr(e.node)||r.isMediaNot(e.node)||r.isMediaConditionList(e.node)||r.isGeneralEnclosed(e.node))return t=!1,!1})),t}const creator=e=>{const r=Boolean(Object(e).preserve);if("importFrom"in Object(e))throw new Error('[postcss-custom-media] "importFrom" is no longer supported');if("exportTo"in Object(e))throw new Error('[postcss-custom-media] "exportTo" is no longer supported');return{postcssPlugin:"postcss-custom-media",prepare(){let e=new Map;return{Once:(t,{result:n})=>{e=getCustomMedia(t,n,{preserve:r})},AtRule:(t,{result:n})=>{if("media"!==t.name.toLowerCase())return;if(!t.params)return;if(!t.params.includes("--"))return;let o=[];try{o=transformAtMediaListTokens(t.params,e)}catch(e){return void t.warn(n,`Failed to parse @custom-media params with error message: "${e.message}"`)}if(!o||0===o.length)return;if(1===o.length){if(t.params.trim()===o[0].replaceWith.trim())return;return t.cloneBefore({params:o[0].replaceWith.trim()}),r?void 0:void t.remove()}if(!!!o.find((e=>!!e.encapsulateWith)))return t.cloneBefore({params:o.map((e=>e.replaceWith)).join(",").trim()}),void(r||t.remove());o.forEach((e=>{if(!e.encapsulateWith)return void t.cloneBefore({params:e.replaceWith.trim()});const r=t.clone({params:e.replaceWith}),n=t.clone({params:e.encapsulateWith.trim(),nodes:[]});r.parent=null,n.parent=null,n.append(r),t.before(n)})),r||t.remove()}}}}};creator.postcss=!0,module.exports=creator; +"use strict";var e=require("@csstools/cascade-layer-name-parser"),r=require("@csstools/css-tokenizer"),t=require("@csstools/media-query-list-parser");const n=e.parse("csstools-implicit-layer")[0];function collectCascadeLayerOrder(r){const t=new Map,o=new Map,a=[];r.walkAtRules((r=>{var s;if("layer"!==r.name.toLowerCase())return;{let e=r.parent;for(;e;){if("atrule"!==e.type||"layer"!==e.name.toLowerCase()){if(e===r.root())break;return}e=e.parent}}let i;if(r.nodes)i=normalizeLayerName(r.params,1);else{if(!r.params.trim())return;i=r.params}let l=e.parse(i);if(null!=(s=l)&&s.length){{let e=r.parent;for(;e&&"atrule"===e.type&&"layer"===e.name.toLowerCase();){const r=o.get(e);r?(l=l.map((e=>r.concat(e))),e=e.parent):e=e.parent}}if(e.addLayerToModel(a,l),r.nodes){const e=l[0].concat(n);t.set(r,e),o.set(r,l[0])}}}));for(const r of t.values())e.addLayerToModel(a,[r]);const s=new WeakMap;for(const[e,r]of t)s.set(e,a.findIndex((e=>r.equal(e))));return s}function normalizeLayerName(e,r){return e.trim()?e:"csstools-anon-layer--"+r++}const o=new Set(["scope","container","layer"]);function isProcessableCustomMediaRule(e){if("custom-media"!==e.name.toLowerCase())return!1;if(!e.params||!e.params.includes("--"))return!1;if(e.nodes&&e.nodes.length>0)return!1;let r=e.parent;for(;r;){if("atrule"===r.type&&!o.has(r.name.toLowerCase()))return!1;r=r.parent}return!0}function removeCyclicReferences(e,r){const t=new Set;let n=r;for(;e.size>0;)try{toposort(Array.from(e.keys()),n);break}catch(r){if(!r._graphNode)throw r;e.delete(r._graphNode),t.add(r._graphNode),n=n.filter((e=>-1===e.indexOf(r._graphNode)))}return t}function toposort(e,r){let t=e.length;const n=new Array(t),o={};let a=t;const s=makeOutgoingEdges(r),i=makeNodesHash(e);for(r.forEach((function(e){if(!i.has(e[0])||!i.has(e[1]))throw new Error("Unknown token. Make sure to provide all tokens used in aliases.")}));a--;)o[a]||visit(e[a],a,new Set);return n;function visit(e,r,a){if(a.has(e)){const r=new Error("Cyclic dependency"+JSON.stringify(e));throw r._graphNode=e,r}if(!i.has(e))throw new Error("Found unknown token. Make sure to provided all involved tokens. Unknown token: "+JSON.stringify(e));if(o[r])return;o[r]=!0;let l=s.get(e)||new Set;if(l=Array.from(l),r=l.length){a.add(e);do{const e=l[--r];visit(e,i.get(e),a)}while(r);a.delete(e)}n[--t]=e}}function makeOutgoingEdges(e){const r=new Map;for(let t=0,n=e.length;t{throw new Error(`Unable to parse media query "${e}"`)}}),n=[];for(;!t.endOfFile();)n.push(t.nextToken());return n}const a=[[r.TokenType.Ident,"max-color",0,0,{value:"max-color"}],[r.TokenType.Colon,":",0,0,void 0],[r.TokenType.Number,"2147477350",0,0,{value:2147477350,type:r.NumberType.Integer}]],s=[[r.TokenType.Ident,"color",0,0,{value:"color"}],[r.TokenType.Colon,":",0,0,void 0],[r.TokenType.Number,"2147477350",0,0,{value:2147477350,type:r.NumberType.Integer}]];function replaceTrueAndFalseTokens(e){let t,n;for(let o=0;o{throw new Error(`Unable to parse media query "${r.stringify(...s)}"`)}}),l=t.parseFromTokens(r.cloneTokens(s),{preserveInvalidMediaQueries:!0,onParseError:()=>{throw new Error(`Unable to parse media query "${r.stringify(...s)}"`)}});for(let e=0;e[e,a]))}}function getCustomMedia(e,r,t){const n=new Map,o=new Map,a=[],s=collectCascadeLayerOrder(e);e.walkAtRules((e=>{if(!isProcessableCustomMediaRule(e))return;const r=parseCustomMedia(e.params);if(!r)return;if(0===r.truthy.length)return;const i=(u=s,(l=e).parent&&"atrule"===l.parent.type&&"layer"===l.parent.name.toLowerCase()?u.has(l.parent)?u.get(l.parent):-1:1/0);var l,u;if(i>=(o.get(r.name)??-1)&&(o.set(r.name,i),n.set(r.name,{truthy:r.truthy,falsy:r.falsy}),a.push(...r.dependsOn)),!t.preserve){const r=e.parent;e.remove(),removeEmptyAncestorBlocks(r)}}));const i=removeCyclicReferences(n,a);for(const t of i.values())e.warn(r,`@custom-media rules have cyclic dependencies for "${t}"`);return n}function removeEmptyAncestorBlocks(e){let r=e;for(;r;){if(r.nodes&&r.nodes.length>0)return;const e=r.parent;r.remove(),r=e}}function transformAtMediaListTokens(e,r){const n=t.parse(e,{preserveInvalidMediaQueries:!0,onParseError:()=>{throw new Error(`Unable to parse media query "${e}"`)}}),o=n.map((e=>e.toString()));for(let e=0;et===e?n:{replaceWith:r}))}const s=transformComplexMediaQuery(t,r);if(s&&0!==s.length&&s[0].replaceWith!==a)return o.flatMap(((r,t)=>t===e?s:[{replaceWith:r}]))}return[]}function transformSimpleMediaQuery(e,r){if(!mediaQueryIsSimple(e))return null;let n=null;return e.walk((e=>{const o=e.node;if(!t.isMediaFeatureBoolean(o))return;const a=o.getName();if(!a.startsWith("--"))return!1;const s=r.get(a);return s?(n={replaceWith:s.truthy.map((e=>e.toString().trim())).join(",")},!1):void 0})),n}function transformComplexMediaQuery(e,r){let n=[];return e.walk((o=>{const i=o.node;if(!t.isMediaFeatureBoolean(i))return;const l=o.parent;if(!t.isMediaFeature(l))return;const u=i.getName();if(!u.startsWith("--"))return!1;const c=r.get(u);if(c){if(1===c.truthy.length&&mediaQueryIsSimple(c.truthy[0])){let r=null;if(c.truthy[0].walk((e=>{if(t.isMediaFeature(e.node))return r=e.node,!1})),r&&r.feature)return l.feature=r.feature,n=[{replaceWith:e.toString()}],!1}const r=t.newMediaFeaturePlain(a[0][4].value,a[2]);l.feature=r.feature;const o=e.toString(),i=t.newMediaFeaturePlain(s[0][4].value,s[2]);l.feature=i.feature;const u=e.toString();return n=[{replaceWith:o,encapsulateWith:c.truthy.map((e=>e.toString().trim())).join(",")},{replaceWith:u,encapsulateWith:c.falsy.map((e=>e.toString().trim())).join(",")}],!1}})),n}function mediaQueryIsSimple(e){if(t.isMediaQueryInvalid(e))return!1;if(t.isMediaQueryWithType(e))return!1;let r=!0;return e.walk((e=>{if(t.isMediaAnd(e.node)||t.isMediaOr(e.node)||t.isMediaNot(e.node)||t.isMediaConditionList(e.node)||t.isGeneralEnclosed(e.node))return r=!1,!1})),r}const creator=e=>{const r=Boolean(Object(e).preserve);if("importFrom"in Object(e))throw new Error('[postcss-custom-media] "importFrom" is no longer supported');if("exportTo"in Object(e))throw new Error('[postcss-custom-media] "exportTo" is no longer supported');return{postcssPlugin:"postcss-custom-media",prepare(){let e=new Map;return{Once:(t,{result:n})=>{e=getCustomMedia(t,n,{preserve:r})},AtRule:(t,{result:n})=>{if("media"!==t.name.toLowerCase())return;if(!t.params)return;if(!t.params.includes("--"))return;let o=[];try{o=transformAtMediaListTokens(t.params,e)}catch(e){return void t.warn(n,`Failed to parse @custom-media params with error message: "${e.message}"`)}if(!o||0===o.length)return;if(1===o.length){if(t.params.trim()===o[0].replaceWith.trim())return;return t.cloneBefore({params:o[0].replaceWith.trim()}),r?void 0:void t.remove()}if(!!!o.find((e=>!!e.encapsulateWith)))return t.cloneBefore({params:o.map((e=>e.replaceWith)).join(",").trim()}),void(r||t.remove());o.forEach((e=>{if(!e.encapsulateWith)return void t.cloneBefore({params:e.replaceWith.trim()});const r=t.clone({params:e.replaceWith}),n=t.clone({params:e.encapsulateWith.trim(),nodes:[]});r.parent=null,n.parent=null,n.append(r),t.before(n)})),r||t.remove()}}}}};creator.postcss=!0,module.exports=creator; diff --git a/plugins/postcss-custom-media/dist/index.mjs b/plugins/postcss-custom-media/dist/index.mjs index fee0e5809..2d96a7061 100644 --- a/plugins/postcss-custom-media/dist/index.mjs +++ b/plugins/postcss-custom-media/dist/index.mjs @@ -1 +1 @@ -import{tokenizer as e,TokenType as r,NumberType as t,cloneTokens as n,stringify as o}from"@csstools/css-tokenizer";import{parseFromTokens as a,parse as s,isMediaFeatureBoolean as i,isMediaFeature as l,newMediaFeaturePlain as u,isMediaQueryInvalid as c,isMediaQueryWithType as p,isMediaAnd as f,isMediaOr as m,isMediaNot as d,isMediaConditionList as h,isGeneralEnclosed as g}from"@csstools/media-query-list-parser";function collectCascadeLayerOrder(e){const r=new Map,t=new Map,n=[];e.walkAtRules((e=>{if("layer"!==e.name.toLowerCase())return;{let r=e.parent;for(;r;){if("atrule"!==r.type||"layer"!==r.name.toLowerCase()){if(r===e.root())break;return}r=r.parent}}let o=[];if(e.nodes)o.push((a=e.params,s=1,a.trim()||"csstools-anon-layer--"+s++));else{if(!e.params.trim())return;o=e.params.split(",").map((e=>e.trim()))}var a,s;{let r=e.parent;for(;r&&"atrule"===r.type&&"layer"===r.name.toLowerCase();){const e=t.get(r);e?(o=o.map((r=>e+"."+r)),r=r.parent):r=r.parent}}if(addLayerToModel(n,o),e.nodes){const n=o[0]+".csstools-implicit-layer";r.set(e,n),t.set(e,o[0])}}));for(const e of r.values())addLayerToModel(n,[e]);const o=n.map((e=>e.join("."))),a=new WeakMap;for(const[e,t]of r)a.set(e,o.indexOf(t));return a}function addLayerToModel(e,r){return r.forEach((r=>{const t=r.split(".");e:for(let r=0;r=a&&(o=r,a=s)}-1===o?e.push(n):e.splice(o+1,0,n)}})),e}const y=new Set(["scope","container","layer"]);function isProcessableCustomMediaRule(e){if("custom-media"!==e.name.toLowerCase())return!1;if(!e.params||!e.params.includes("--"))return!1;if(e.nodes&&e.nodes.length>0)return!1;let r=e.parent;for(;r;){if("atrule"===r.type&&!y.has(r.name.toLowerCase()))return!1;r=r.parent}return!0}function removeCyclicReferences(e,r){const t=new Set;let n=r;for(;e.size>0;)try{toposort(Array.from(e.keys()),n);break}catch(r){if(!r._graphNode)throw r;e.delete(r._graphNode),t.add(r._graphNode),n=n.filter((e=>-1===e.indexOf(r._graphNode)))}return t}function toposort(e,r){let t=e.length;const n=new Array(t),o={};let a=t;const s=makeOutgoingEdges(r),i=makeNodesHash(e);for(r.forEach((function(e){if(!i.has(e[0])||!i.has(e[1]))throw new Error("Unknown token. Make sure to provide all tokens used in aliases.")}));a--;)o[a]||visit(e[a],a,new Set);return n;function visit(e,r,a){if(a.has(e)){const r=new Error("Cyclic dependency"+JSON.stringify(e));throw r._graphNode=e,r}if(!i.has(e))throw new Error("Found unknown token. Make sure to provided all involved tokens. Unknown token: "+JSON.stringify(e));if(o[r])return;o[r]=!0;let l=s.get(e)||new Set;if(l=Array.from(l),r=l.length){a.add(e);do{const e=l[--r];visit(e,i.get(e),a)}while(r);a.delete(e)}n[--t]=e}}function makeOutgoingEdges(e){const r=new Map;for(let t=0,n=e.length;t{throw new Error(`Unable to parse media query "${r}"`)}}),n=[];for(;!t.endOfFile();)n.push(t.nextToken());return n}const w=[[r.Ident,"max-color",0,0,{value:"max-color"}],[r.Colon,":",0,0,void 0],[r.Number,"2147477350",0,0,{value:2147477350,type:t.Integer}]],v=[[r.Ident,"color",0,0,{value:"color"}],[r.Colon,":",0,0,void 0],[r.Number,"2147477350",0,0,{value:2147477350,type:t.Integer}]];function replaceTrueAndFalseTokens(e){let t,n;for(let o=0;o{throw new Error(`Unable to parse media query "${o(...l)}"`)}}),c=a(n(l),{preserveInvalidMediaQueries:!0,onParseError:()=>{throw new Error(`Unable to parse media query "${o(...l)}"`)}});for(let e=0;e[e,i]))}}function getCustomMedia(e,r,t){const n=new Map,o=new Map,a=[],s=collectCascadeLayerOrder(e);e.walkAtRules((e=>{if(!isProcessableCustomMediaRule(e))return;const r=parseCustomMedia(e.params);if(!r)return;if(0===r.truthy.length)return;const i=(u=s,(l=e).parent&&"atrule"===l.parent.type&&"layer"===l.parent.name.toLowerCase()?u.has(l.parent)?u.get(l.parent):-1:1/0);var l,u;if(i>=(o.get(r.name)??-1)&&(o.set(r.name,i),n.set(r.name,{truthy:r.truthy,falsy:r.falsy}),a.push(...r.dependsOn)),!t.preserve){const r=e.parent;e.remove(),removeEmptyAncestorBlocks(r)}}));const i=removeCyclicReferences(n,a);for(const t of i.values())e.warn(r,`@custom-media rules have cyclic dependencies for "${t}"`);return n}function removeEmptyAncestorBlocks(e){let r=e;for(;r;){if(r.nodes&&r.nodes.length>0)return;const e=r.parent;r.remove(),r=e}}function transformAtMediaListTokens(e,r){const t=s(e,{preserveInvalidMediaQueries:!0,onParseError:()=>{throw new Error(`Unable to parse media query "${e}"`)}}),n=t.map((e=>e.toString()));for(let e=0;en===e?t:{replaceWith:r}))}const s=transformComplexMediaQuery(o,r);if(s&&0!==s.length&&s[0].replaceWith!==a)return n.flatMap(((r,t)=>t===e?s:[{replaceWith:r}]))}return[]}function transformSimpleMediaQuery(e,r){if(!mediaQueryIsSimple(e))return null;let t=null;return e.walk((e=>{const n=e.node;if(!i(n))return;const o=n.getName();if(!o.startsWith("--"))return!1;const a=r.get(o);return a?(t={replaceWith:a.truthy.map((e=>e.toString().trim())).join(",")},!1):void 0})),t}function transformComplexMediaQuery(e,r){let t=[];return e.walk((n=>{const o=n.node;if(!i(o))return;const a=n.parent;if(!l(a))return;const s=o.getName();if(!s.startsWith("--"))return!1;const c=r.get(s);if(c){if(1===c.truthy.length&&mediaQueryIsSimple(c.truthy[0])){let r=null;if(c.truthy[0].walk((e=>{if(l(e.node))return r=e.node,!1})),r&&r.feature)return a.feature=r.feature,t=[{replaceWith:e.toString()}],!1}const r=u(w[0][4].value,w[2]);a.feature=r.feature;const n=e.toString(),o=u(v[0][4].value,v[2]);a.feature=o.feature;const s=e.toString();return t=[{replaceWith:n,encapsulateWith:c.truthy.map((e=>e.toString().trim())).join(",")},{replaceWith:s,encapsulateWith:c.falsy.map((e=>e.toString().trim())).join(",")}],!1}})),t}function mediaQueryIsSimple(e){if(c(e))return!1;if(p(e))return!1;let r=!0;return e.walk((e=>{if(f(e.node)||m(e.node)||d(e.node)||h(e.node)||g(e.node))return r=!1,!1})),r}const creator=e=>{const r=Boolean(Object(e).preserve);if("importFrom"in Object(e))throw new Error('[postcss-custom-media] "importFrom" is no longer supported');if("exportTo"in Object(e))throw new Error('[postcss-custom-media] "exportTo" is no longer supported');return{postcssPlugin:"postcss-custom-media",prepare(){let e=new Map;return{Once:(t,{result:n})=>{e=getCustomMedia(t,n,{preserve:r})},AtRule:(t,{result:n})=>{if("media"!==t.name.toLowerCase())return;if(!t.params)return;if(!t.params.includes("--"))return;let o=[];try{o=transformAtMediaListTokens(t.params,e)}catch(e){return void t.warn(n,`Failed to parse @custom-media params with error message: "${e.message}"`)}if(!o||0===o.length)return;if(1===o.length){if(t.params.trim()===o[0].replaceWith.trim())return;return t.cloneBefore({params:o[0].replaceWith.trim()}),r?void 0:void t.remove()}if(!!!o.find((e=>!!e.encapsulateWith)))return t.cloneBefore({params:o.map((e=>e.replaceWith)).join(",").trim()}),void(r||t.remove());o.forEach((e=>{if(!e.encapsulateWith)return void t.cloneBefore({params:e.replaceWith.trim()});const r=t.clone({params:e.replaceWith}),n=t.clone({params:e.encapsulateWith.trim(),nodes:[]});r.parent=null,n.parent=null,n.append(r),t.before(n)})),r||t.remove()}}}}};creator.postcss=!0;export{creator as default}; +import{parse as e,addLayerToModel as r}from"@csstools/cascade-layer-name-parser";import{tokenizer as t,TokenType as n,NumberType as o,cloneTokens as a,stringify as s}from"@csstools/css-tokenizer";import{parseFromTokens as i,parse as l,isMediaFeatureBoolean as u,isMediaFeature as c,newMediaFeaturePlain as p,isMediaQueryInvalid as f,isMediaQueryWithType as m,isMediaAnd as d,isMediaOr as h,isMediaNot as y,isMediaConditionList as g,isGeneralEnclosed as w}from"@csstools/media-query-list-parser";const v=e("csstools-implicit-layer")[0];function collectCascadeLayerOrder(t){const n=new Map,o=new Map,a=[];t.walkAtRules((t=>{var s;if("layer"!==t.name.toLowerCase())return;{let e=t.parent;for(;e;){if("atrule"!==e.type||"layer"!==e.name.toLowerCase()){if(e===t.root())break;return}e=e.parent}}let i;if(t.nodes)i=normalizeLayerName(t.params,1);else{if(!t.params.trim())return;i=t.params}let l=e(i);if(null!=(s=l)&&s.length){{let e=t.parent;for(;e&&"atrule"===e.type&&"layer"===e.name.toLowerCase();){const r=o.get(e);r?(l=l.map((e=>r.concat(e))),e=e.parent):e=e.parent}}if(r(a,l),t.nodes){const e=l[0].concat(v);n.set(t,e),o.set(t,l[0])}}}));for(const e of n.values())r(a,[e]);const s=new WeakMap;for(const[e,r]of n)s.set(e,a.findIndex((e=>r.equal(e))));return s}function normalizeLayerName(e,r){return e.trim()?e:"csstools-anon-layer--"+r++}const k=new Set(["scope","container","layer"]);function isProcessableCustomMediaRule(e){if("custom-media"!==e.name.toLowerCase())return!1;if(!e.params||!e.params.includes("--"))return!1;if(e.nodes&&e.nodes.length>0)return!1;let r=e.parent;for(;r;){if("atrule"===r.type&&!k.has(r.name.toLowerCase()))return!1;r=r.parent}return!0}function removeCyclicReferences(e,r){const t=new Set;let n=r;for(;e.size>0;)try{toposort(Array.from(e.keys()),n);break}catch(r){if(!r._graphNode)throw r;e.delete(r._graphNode),t.add(r._graphNode),n=n.filter((e=>-1===e.indexOf(r._graphNode)))}return t}function toposort(e,r){let t=e.length;const n=new Array(t),o={};let a=t;const s=makeOutgoingEdges(r),i=makeNodesHash(e);for(r.forEach((function(e){if(!i.has(e[0])||!i.has(e[1]))throw new Error("Unknown token. Make sure to provide all tokens used in aliases.")}));a--;)o[a]||visit(e[a],a,new Set);return n;function visit(e,r,a){if(a.has(e)){const r=new Error("Cyclic dependency"+JSON.stringify(e));throw r._graphNode=e,r}if(!i.has(e))throw new Error("Found unknown token. Make sure to provided all involved tokens. Unknown token: "+JSON.stringify(e));if(o[r])return;o[r]=!0;let l=s.get(e)||new Set;if(l=Array.from(l),r=l.length){a.add(e);do{const e=l[--r];visit(e,i.get(e),a)}while(r);a.delete(e)}n[--t]=e}}function makeOutgoingEdges(e){const r=new Map;for(let t=0,n=e.length;t{throw new Error(`Unable to parse media query "${e}"`)}}),n=[];for(;!r.endOfFile();)n.push(r.nextToken());return n}const C=[[n.Ident,"max-color",0,0,{value:"max-color"}],[n.Colon,":",0,0,void 0],[n.Number,"2147477350",0,0,{value:2147477350,type:o.Integer}]],M=[[n.Ident,"color",0,0,{value:"color"}],[n.Colon,":",0,0,void 0],[n.Number,"2147477350",0,0,{value:2147477350,type:o.Integer}]];function replaceTrueAndFalseTokens(e){let r,t;for(let o=0;o{throw new Error(`Unable to parse media query "${s(...l)}"`)}}),c=i(a(l),{preserveInvalidMediaQueries:!0,onParseError:()=>{throw new Error(`Unable to parse media query "${s(...l)}"`)}});for(let e=0;e[e,o]))}}function getCustomMedia(e,r,t){const n=new Map,o=new Map,a=[],s=collectCascadeLayerOrder(e);e.walkAtRules((e=>{if(!isProcessableCustomMediaRule(e))return;const r=parseCustomMedia(e.params);if(!r)return;if(0===r.truthy.length)return;const i=(u=s,(l=e).parent&&"atrule"===l.parent.type&&"layer"===l.parent.name.toLowerCase()?u.has(l.parent)?u.get(l.parent):-1:1/0);var l,u;if(i>=(o.get(r.name)??-1)&&(o.set(r.name,i),n.set(r.name,{truthy:r.truthy,falsy:r.falsy}),a.push(...r.dependsOn)),!t.preserve){const r=e.parent;e.remove(),removeEmptyAncestorBlocks(r)}}));const i=removeCyclicReferences(n,a);for(const t of i.values())e.warn(r,`@custom-media rules have cyclic dependencies for "${t}"`);return n}function removeEmptyAncestorBlocks(e){let r=e;for(;r;){if(r.nodes&&r.nodes.length>0)return;const e=r.parent;r.remove(),r=e}}function transformAtMediaListTokens(e,r){const t=l(e,{preserveInvalidMediaQueries:!0,onParseError:()=>{throw new Error(`Unable to parse media query "${e}"`)}}),n=t.map((e=>e.toString()));for(let e=0;en===e?t:{replaceWith:r}))}const s=transformComplexMediaQuery(o,r);if(s&&0!==s.length&&s[0].replaceWith!==a)return n.flatMap(((r,t)=>t===e?s:[{replaceWith:r}]))}return[]}function transformSimpleMediaQuery(e,r){if(!mediaQueryIsSimple(e))return null;let t=null;return e.walk((e=>{const n=e.node;if(!u(n))return;const o=n.getName();if(!o.startsWith("--"))return!1;const a=r.get(o);return a?(t={replaceWith:a.truthy.map((e=>e.toString().trim())).join(",")},!1):void 0})),t}function transformComplexMediaQuery(e,r){let t=[];return e.walk((n=>{const o=n.node;if(!u(o))return;const a=n.parent;if(!c(a))return;const s=o.getName();if(!s.startsWith("--"))return!1;const i=r.get(s);if(i){if(1===i.truthy.length&&mediaQueryIsSimple(i.truthy[0])){let r=null;if(i.truthy[0].walk((e=>{if(c(e.node))return r=e.node,!1})),r&&r.feature)return a.feature=r.feature,t=[{replaceWith:e.toString()}],!1}const r=p(C[0][4].value,C[2]);a.feature=r.feature;const n=e.toString(),o=p(M[0][4].value,M[2]);a.feature=o.feature;const s=e.toString();return t=[{replaceWith:n,encapsulateWith:i.truthy.map((e=>e.toString().trim())).join(",")},{replaceWith:s,encapsulateWith:i.falsy.map((e=>e.toString().trim())).join(",")}],!1}})),t}function mediaQueryIsSimple(e){if(f(e))return!1;if(m(e))return!1;let r=!0;return e.walk((e=>{if(d(e.node)||h(e.node)||y(e.node)||g(e.node)||w(e.node))return r=!1,!1})),r}const creator=e=>{const r=Boolean(Object(e).preserve);if("importFrom"in Object(e))throw new Error('[postcss-custom-media] "importFrom" is no longer supported');if("exportTo"in Object(e))throw new Error('[postcss-custom-media] "exportTo" is no longer supported');return{postcssPlugin:"postcss-custom-media",prepare(){let e=new Map;return{Once:(t,{result:n})=>{e=getCustomMedia(t,n,{preserve:r})},AtRule:(t,{result:n})=>{if("media"!==t.name.toLowerCase())return;if(!t.params)return;if(!t.params.includes("--"))return;let o=[];try{o=transformAtMediaListTokens(t.params,e)}catch(e){return void t.warn(n,`Failed to parse @custom-media params with error message: "${e.message}"`)}if(!o||0===o.length)return;if(1===o.length){if(t.params.trim()===o[0].replaceWith.trim())return;return t.cloneBefore({params:o[0].replaceWith.trim()}),r?void 0:void t.remove()}if(!!!o.find((e=>!!e.encapsulateWith)))return t.cloneBefore({params:o.map((e=>e.replaceWith)).join(",").trim()}),void(r||t.remove());o.forEach((e=>{if(!e.encapsulateWith)return void t.cloneBefore({params:e.replaceWith.trim()});const r=t.clone({params:e.replaceWith}),n=t.clone({params:e.encapsulateWith.trim(),nodes:[]});r.parent=null,n.parent=null,n.append(r),t.before(n)})),r||t.remove()}}}}};creator.postcss=!0;export{creator as default}; diff --git a/plugins/postcss-custom-media/package.json b/plugins/postcss-custom-media/package.json index fe297c1aa..5cc7b0c50 100644 --- a/plugins/postcss-custom-media/package.json +++ b/plugins/postcss-custom-media/package.json @@ -45,6 +45,7 @@ "dist" ], "dependencies": { + "@csstools/cascade-layer-name-parser": "^1.0.0", "@csstools/css-parser-algorithms": "^1.0.0", "@csstools/css-tokenizer": "^1.0.0", "@csstools/media-query-list-parser": "^1.0.0" diff --git a/plugins/postcss-custom-media/src/cascade-layers.ts b/plugins/postcss-custom-media/src/cascade-layers.ts index 4d60ebb98..3b95579cb 100644 --- a/plugins/postcss-custom-media/src/cascade-layers.ts +++ b/plugins/postcss-custom-media/src/cascade-layers.ts @@ -1,10 +1,13 @@ import type { AtRule, Container, Document, Node, Root } from 'postcss'; +import { LayerName, parse as parseCascadeLayerNames, addLayerToModel } from '@csstools/cascade-layer-name-parser'; + +const implicitLayerNameForCloning = parseCascadeLayerNames('csstools-implicit-layer')[0]; export function collectCascadeLayerOrder(root: Root) { - const references: Map = new Map(); - const referencesForLayerNames: Map = new Map(); + const references: Map = new Map(); + const referencesForLayerNames: Map = new Map(); - const layers: Array> = []; + const layers: Array = []; const anonLayerCounter = 1; root.walkAtRules((node) => { @@ -32,17 +35,20 @@ export function collectCascadeLayerOrder(root: Root) { } } - let currentLayerNames = []; + let layerParams; if (node.nodes) { // @layer { .foo {} } - currentLayerNames.push(normalizeLayerName(node.params, anonLayerCounter)); + layerParams = normalizeLayerName(node.params, anonLayerCounter); } else if (node.params.trim()) { // @layer a, b; - currentLayerNames = node.params.split(',').map((layerName) => { - return layerName.trim(); - }); + layerParams = node.params; } else { // @layer; return; } + let currentLayerNames = parseCascadeLayerNames(layerParams); + if (!currentLayerNames?.length) { + return; + } + { // Stitch the layer names of the current node together with those of ancestors. // @layer foo { @layer bar { .any {} } } @@ -56,7 +62,7 @@ export function collectCascadeLayerOrder(root: Root) { } currentLayerNames = currentLayerNames.map((layerName) => { - return parentLayerName + '.' + layerName; + return parentLayerName.concat(layerName); }); parent = parent.parent; @@ -75,7 +81,7 @@ export function collectCascadeLayerOrder(root: Root) { // 3. use the real layer to resolve other real layer names // 4. use the implicit layer later - const implicitLayerName = currentLayerNames[0] + '.' + 'csstools-implicit-layer'; + const implicitLayerName = currentLayerNames[0].concat(implicitLayerNameForCloning); references.set(node, implicitLayerName); referencesForLayerNames.set(node, currentLayerNames[0]); } @@ -87,11 +93,11 @@ export function collectCascadeLayerOrder(root: Root) { addLayerToModel(layers, [layerName]); } - const finalLayers = layers.map((x) => x.join('.')); - const out: WeakMap = new WeakMap(); for (const [node, layerName] of references) { - out.set(node, finalLayers.indexOf(layerName)); + out.set(node, layers.findIndex((x) => { + return layerName.equal(x); + })); } return out; @@ -113,68 +119,9 @@ export function cascadeLayerNumberForNode(node: Node, layers: WeakMap { - const allLayerNameParts = layerName.split('.'); - - ALL_LAYER_NAME_PARTS_LOOP: for (let x = 0; x < allLayerNameParts.length; x++) { - const layerNameParts = allLayerNameParts.slice(0, x + 1); - - let layerWithMostEqualSegments = -1; - let mostEqualSegments = 0; - - for (let i = 0; i < layers.length; i++) { - const existingLayerParts = layers[i]; - - let numberOfEqualSegments = 0; - - LAYER_PARTS_LOOP: for (let j = 0; j < existingLayerParts.length; j++) { - const existingLayerPart = existingLayerParts[j]; - const layerPart = layerNameParts[j]; - - if (layerPart === existingLayerPart && (j + 1) === layerNameParts.length) { - continue ALL_LAYER_NAME_PARTS_LOOP; // layer already exists in model - } - - if (layerPart === existingLayerPart) { - numberOfEqualSegments++; - continue; - } - - if (layerPart !== existingLayerPart) { - break LAYER_PARTS_LOOP; - } - } - - if (numberOfEqualSegments >= mostEqualSegments) { - layerWithMostEqualSegments = i; - mostEqualSegments = numberOfEqualSegments; - } - } - - if (layerWithMostEqualSegments === -1) { - layers.push(layerNameParts); - } else { - layers.splice(layerWithMostEqualSegments+1, 0, layerNameParts); - } - } - }); + if (layerName.trim()) { + return layerName; + } - return layers; + return `csstools-anon-layer--${counter++}`; } diff --git a/plugins/postcss-custom-media/test/cascade-layers.css b/plugins/postcss-custom-media/test/cascade-layers.css index 235fb35c4..b7b511491 100644 --- a/plugins/postcss-custom-media/test/cascade-layers.css +++ b/plugins/postcss-custom-media/test/cascade-layers.css @@ -1,4 +1,5 @@ @layer extensions, components; +@layer extensions.one,,invalid-layer-names..foo; @custom-media --desktop (min-width:1280px) and (debug-layer: unlayered); diff --git a/plugins/postcss-custom-media/test/cascade-layers.expect.css b/plugins/postcss-custom-media/test/cascade-layers.expect.css index 8519dc032..95f7e37c3 100644 --- a/plugins/postcss-custom-media/test/cascade-layers.expect.css +++ b/plugins/postcss-custom-media/test/cascade-layers.expect.css @@ -1,4 +1,5 @@ @layer extensions, components; +@layer extensions.one,,invalid-layer-names..foo; @layer extensions { @layer one, two diff --git a/plugins/postcss-custom-properties/dist/index.cjs b/plugins/postcss-custom-properties/dist/index.cjs index 398f7670b..e3d9e55be 100644 --- a/plugins/postcss-custom-properties/dist/index.cjs +++ b/plugins/postcss-custom-properties/dist/index.cjs @@ -1 +1 @@ -"use strict";var e=require("postcss-value-parser");function collectCascadeLayerOrder(e){const r=new Map,t=new Map,o=[];e.walkAtRules((e=>{if("layer"!==e.name.toLowerCase())return;{let r=e.parent;for(;r;){if("atrule"!==r.type||"layer"!==r.name.toLowerCase()){if(r===e.root())break;return}r=r.parent}}let s=[];if(e.nodes)s.push((n=e.params,a=1,n.trim()||"csstools-anon-layer--"+a++));else{if(!e.params.trim())return;s=e.params.split(",").map((e=>e.trim()))}var n,a;{let r=e.parent;for(;r&&"atrule"===r.type&&"layer"===r.name.toLowerCase();){const e=t.get(r);e?(s=s.map((r=>e+"."+r)),r=r.parent):r=r.parent}}if(addLayerToModel(o,s),e.nodes){const o=s[0]+".csstools-implicit-layer";r.set(e,o),t.set(e,s[0])}}));for(const e of r.values())addLayerToModel(o,[e]);const s=o.map((e=>e.join("."))),n=new WeakMap;for(const[e,t]of r)n.set(e,s.indexOf(t));return n}function cascadeLayerNumberForNode(e,r){return e.parent&&"atrule"===e.parent.type&&"layer"===e.parent.name.toLowerCase()?r.has(e.parent)?r.get(e.parent):-1:1/0}function addLayerToModel(e,r){return r.forEach((r=>{const t=r.split(".");e:for(let r=0;r=n&&(s=r,n=a)}-1===s?e.push(o):e.splice(s+1,0,o)}})),e}const r=/(!\s*)?postcss-custom-properties:\s*off\b/i,t=new WeakMap;function isBlockIgnored(e){if(!e||!e.nodes)return!1;if(t.has(e))return t.get(e);const o=e.some((e=>isIgnoreComment(e,r)));return t.set(e,o),o}const o=/(!\s*)?postcss-custom-properties:\s*ignore\s+next\b/i;function isDeclarationIgnored(e){return!!e&&(!!isBlockIgnored(e.parent)||isIgnoreComment(e.prev(),o))}function isIgnoreComment(e,r){return e&&"comment"===e.type&&r.test(e.text)}const s=new Set(["layer"]);function isProcessableRule(e){if(!isHtmlRule(e)&&!isRootRule(e))return!1;let r=e.parent;for(;r;){if("atrule"===r.type&&!s.has(r.name.toLowerCase()))return!1;r=r.parent}return!0}const n=/^html$/i,a=/^:root$/i;function isHtmlRule(e){return e.selectors.some((e=>n.test(e)))&&e.nodes&&e.nodes.length}function isRootRule(e){return e.selectors.some((e=>a.test(e)))&&e.nodes&&e.nodes.length}function getCustomPropertiesFromRoot(r){const t=new Map,o=new Map,s=new Map,n=new Map,a=new Map,l=collectCascadeLayerOrder(r);r.walkRules((e=>{isProcessableRule(e)&&(isBlockIgnored(e)||(isHtmlRule(e)?e.each((e=>{if("decl"!==e.type)return;if(!e.variable||isDeclarationIgnored(e))return;if("initial"===e.value.toLowerCase().trim())return;const r=cascadeLayerNumberForNode(e,l);r>=(n.get(e.prop)??-1)&&(n.set(e.prop,r),t.set(e.prop,e.value))})):isRootRule(e)&&e.each((e=>{if("decl"!==e.type)return;if(!e.variable||isDeclarationIgnored(e))return;if("initial"===e.value.toLowerCase().trim())return;const r=cascadeLayerNumberForNode(e,l);r>=(a.get(e.prop)??-1)&&(a.set(e.prop,r),o.set(e.prop,e.value))}))))}));for(const[e,r]of t.entries())s.set(e,r);for(const[e,r]of o.entries())s.set(e,r);const i=new Map;for(const[r,t]of s.entries())i.set(r,e(t));return i}function transformValueAST(e,r){return e.nodes&&e.nodes.length&&e.nodes.slice().forEach((t=>{if(isVarFunction(t)){const[o,...s]=t.nodes.filter((e=>"div"!==e.type)),{value:n}=o,a=e.nodes.indexOf(t);if(r.has(n)){const t=r.get(n).nodes;reTransformValueAST({nodes:t},r,n),a>-1&&e.nodes.splice(a,1,...t)}else s.length&&(a>-1&&e.nodes.splice(a,1,...t.nodes.slice(t.nodes.indexOf(s[0]))),transformValueAST(e,r))}else transformValueAST(t,r)})),e.toString()}function reTransformValueAST(e,r,t){const o=new Map(r);return o.delete(t),transformValueAST(e,o)}const l=/^var$/i,isVarFunction=e=>"function"===e.type&&l.test(e.value)&&Object(e.nodes).length>0;var transformProperties=(r,t,o)=>{if(isTransformableDecl(r)&&!isDeclarationIgnored(r)){const s=r.value;let n=transformValueAST(e(s),t);const a=new Set;for(;n.includes("--")&&n.toLowerCase().includes("var(")&&!a.has(n);){a.add(n);n=transformValueAST(e(n),t)}if(n!==s){if(parentHasExactFallback(r,n))return void(o.preserve||r.remove());if(o.preserve){const e=r.cloneBefore({value:n});hasTrailingComment(e)&&(e.raws.value.value=e.value.replace(i,"$1"),e.raws.value.raw=e.raws.value.value+e.raws.value.raw.replace(i,"$2"))}else r.value=n,hasTrailingComment(r)&&(r.raws.value.value=r.value.replace(i,"$1"),r.raws.value.raw=r.raws.value.value+r.raws.value.raw.replace(i,"$2"))}}};const isTransformableDecl=e=>!e.variable&&e.value.includes("--")&&e.value.toLowerCase().includes("var("),hasTrailingComment=e=>"value"in Object(Object(e.raws).value)&&"raw"in e.raws.value&&i.test(e.raws.value.raw),i=/^([\W\w]+)(\s*\/\*[\W\w]+?\*\/)$/;function parentHasExactFallback(e,r){if(!e||!e.parent)return!1;let t=!1;const o=e.parent.index(e);return e.parent.each(((s,n)=>s!==e&&(!(n>=o)&&void("decl"===s.type&&s.prop.toLowerCase()===e.prop.toLowerCase()&&s.value===r&&(t=!0))))),t}const creator=r=>{const t=!("preserve"in Object(r))||Boolean(r.preserve);if("importFrom"in Object(r))throw new Error('[postcss-custom-properties] "importFrom" is no longer supported');if("exportTo"in Object(r))throw new Error('[postcss-custom-properties] "exportTo" is no longer supported');return{postcssPlugin:"postcss-custom-properties",prepare:()=>{let r=new Map;return{Once:e=>{r=getCustomPropertiesFromRoot(e)},Declaration:o=>{let s=r;if(t&&o.parent){let t=!1;o.parent.each((n=>{o!==n&&"decl"===n.type&&n.variable&&!isDeclarationIgnored(n)&&(t||(s=new Map(r),t=!0),"initial"!==n.value.toLowerCase().trim()?s.set(n.prop,e(n.value)):s.delete(n.prop))}))}transformProperties(o,s,{preserve:t})}}}}};creator.postcss=!0,module.exports=creator; +"use strict";var e=require("postcss-value-parser"),r=require("@csstools/cascade-layer-name-parser");const t=r.parse("csstools-implicit-layer")[0];function collectCascadeLayerOrder(e){const o=new Map,a=new Map,s=[];e.walkAtRules((e=>{var n;if("layer"!==e.name.toLowerCase())return;{let r=e.parent;for(;r;){if("atrule"!==r.type||"layer"!==r.name.toLowerCase()){if(r===e.root())break;return}r=r.parent}}let l;if(e.nodes)l=normalizeLayerName(e.params,1);else{if(!e.params.trim())return;l=e.params}let i=r.parse(l);if(null!=(n=i)&&n.length){{let r=e.parent;for(;r&&"atrule"===r.type&&"layer"===r.name.toLowerCase();){const e=a.get(r);e?(i=i.map((r=>e.concat(r))),r=r.parent):r=r.parent}}if(r.addLayerToModel(s,i),e.nodes){const r=i[0].concat(t);o.set(e,r),a.set(e,i[0])}}}));for(const e of o.values())r.addLayerToModel(s,[e]);const n=new WeakMap;for(const[e,r]of o)n.set(e,s.findIndex((e=>r.equal(e))));return n}function cascadeLayerNumberForNode(e,r){return e.parent&&"atrule"===e.parent.type&&"layer"===e.parent.name.toLowerCase()?r.has(e.parent)?r.get(e.parent):-1:1/0}function normalizeLayerName(e,r){return e.trim()?e:"csstools-anon-layer--"+r++}const o=/(!\s*)?postcss-custom-properties:\s*off\b/i,a=new WeakMap;function isBlockIgnored(e){if(!e||!e.nodes)return!1;if(a.has(e))return a.get(e);const r=e.some((e=>isIgnoreComment(e,o)));return a.set(e,r),r}const s=/(!\s*)?postcss-custom-properties:\s*ignore\s+next\b/i;function isDeclarationIgnored(e){return!!e&&(!!isBlockIgnored(e.parent)||isIgnoreComment(e.prev(),s))}function isIgnoreComment(e,r){return e&&"comment"===e.type&&r.test(e.text)}const n=new Set(["layer"]);function isProcessableRule(e){if(!isHtmlRule(e)&&!isRootRule(e))return!1;let r=e.parent;for(;r;){if("atrule"===r.type&&!n.has(r.name.toLowerCase()))return!1;r=r.parent}return!0}const l=/^html$/i,i=/^:root$/i;function isHtmlRule(e){return e.selectors.some((e=>l.test(e)))&&e.nodes&&e.nodes.length}function isRootRule(e){return e.selectors.some((e=>i.test(e)))&&e.nodes&&e.nodes.length}function getCustomPropertiesFromRoot(r){const t=new Map,o=new Map,a=new Map,s=new Map,n=new Map,l=collectCascadeLayerOrder(r);r.walkRules((e=>{isProcessableRule(e)&&(isBlockIgnored(e)||(isHtmlRule(e)?e.each((e=>{if("decl"!==e.type)return;if(!e.variable||isDeclarationIgnored(e))return;if("initial"===e.value.toLowerCase().trim())return;const r=cascadeLayerNumberForNode(e,l);r>=(s.get(e.prop)??-1)&&(s.set(e.prop,r),t.set(e.prop,e.value))})):isRootRule(e)&&e.each((e=>{if("decl"!==e.type)return;if(!e.variable||isDeclarationIgnored(e))return;if("initial"===e.value.toLowerCase().trim())return;const r=cascadeLayerNumberForNode(e,l);r>=(n.get(e.prop)??-1)&&(n.set(e.prop,r),o.set(e.prop,e.value))}))))}));for(const[e,r]of t.entries())a.set(e,r);for(const[e,r]of o.entries())a.set(e,r);const i=new Map;for(const[r,t]of a.entries())i.set(r,e(t));return i}function transformValueAST(e,r){return e.nodes&&e.nodes.length&&e.nodes.slice().forEach((t=>{if(isVarFunction(t)){const[o,...a]=t.nodes.filter((e=>"div"!==e.type)),{value:s}=o,n=e.nodes.indexOf(t);if(r.has(s)){const t=r.get(s).nodes;reTransformValueAST({nodes:t},r,s),n>-1&&e.nodes.splice(n,1,...t)}else a.length&&(n>-1&&e.nodes.splice(n,1,...t.nodes.slice(t.nodes.indexOf(a[0]))),transformValueAST(e,r))}else transformValueAST(t,r)})),e.toString()}function reTransformValueAST(e,r,t){const o=new Map(r);return o.delete(t),transformValueAST(e,o)}const c=/^var$/i,isVarFunction=e=>"function"===e.type&&c.test(e.value)&&Object(e.nodes).length>0;var transformProperties=(r,t,o)=>{if(isTransformableDecl(r)&&!isDeclarationIgnored(r)){const a=r.value;let s=transformValueAST(e(a),t);const n=new Set;for(;s.includes("--")&&s.toLowerCase().includes("var(")&&!n.has(s);){n.add(s);s=transformValueAST(e(s),t)}if(s!==a){if(parentHasExactFallback(r,s))return void(o.preserve||r.remove());if(o.preserve){const e=r.cloneBefore({value:s});hasTrailingComment(e)&&(e.raws.value.value=e.value.replace(u,"$1"),e.raws.value.raw=e.raws.value.value+e.raws.value.raw.replace(u,"$2"))}else r.value=s,hasTrailingComment(r)&&(r.raws.value.value=r.value.replace(u,"$1"),r.raws.value.raw=r.raws.value.value+r.raws.value.raw.replace(u,"$2"))}}};const isTransformableDecl=e=>!e.variable&&e.value.includes("--")&&e.value.toLowerCase().includes("var("),hasTrailingComment=e=>"value"in Object(Object(e.raws).value)&&"raw"in e.raws.value&&u.test(e.raws.value.raw),u=/^([\W\w]+)(\s*\/\*[\W\w]+?\*\/)$/;function parentHasExactFallback(e,r){if(!e||!e.parent)return!1;let t=!1;const o=e.parent.index(e);return e.parent.each(((a,s)=>a!==e&&(!(s>=o)&&void("decl"===a.type&&a.prop.toLowerCase()===e.prop.toLowerCase()&&a.value===r&&(t=!0))))),t}const creator=r=>{const t=!("preserve"in Object(r))||Boolean(r.preserve);if("importFrom"in Object(r))throw new Error('[postcss-custom-properties] "importFrom" is no longer supported');if("exportTo"in Object(r))throw new Error('[postcss-custom-properties] "exportTo" is no longer supported');return{postcssPlugin:"postcss-custom-properties",prepare:()=>{let r=new Map;return{Once:e=>{r=getCustomPropertiesFromRoot(e)},Declaration:o=>{let a=r;if(t&&o.parent){let t=!1;o.parent.each((s=>{o!==s&&"decl"===s.type&&s.variable&&!isDeclarationIgnored(s)&&(t||(a=new Map(r),t=!0),"initial"!==s.value.toLowerCase().trim()?a.set(s.prop,e(s.value)):a.delete(s.prop))}))}transformProperties(o,a,{preserve:t})}}}}};creator.postcss=!0,module.exports=creator; diff --git a/plugins/postcss-custom-properties/dist/index.mjs b/plugins/postcss-custom-properties/dist/index.mjs index 43011d851..71d58176a 100644 --- a/plugins/postcss-custom-properties/dist/index.mjs +++ b/plugins/postcss-custom-properties/dist/index.mjs @@ -1 +1 @@ -import e from"postcss-value-parser";function collectCascadeLayerOrder(e){const r=new Map,t=new Map,o=[];e.walkAtRules((e=>{if("layer"!==e.name.toLowerCase())return;{let r=e.parent;for(;r;){if("atrule"!==r.type||"layer"!==r.name.toLowerCase()){if(r===e.root())break;return}r=r.parent}}let s=[];if(e.nodes)s.push((n=e.params,a=1,n.trim()||"csstools-anon-layer--"+a++));else{if(!e.params.trim())return;s=e.params.split(",").map((e=>e.trim()))}var n,a;{let r=e.parent;for(;r&&"atrule"===r.type&&"layer"===r.name.toLowerCase();){const e=t.get(r);e?(s=s.map((r=>e+"."+r)),r=r.parent):r=r.parent}}if(addLayerToModel(o,s),e.nodes){const o=s[0]+".csstools-implicit-layer";r.set(e,o),t.set(e,s[0])}}));for(const e of r.values())addLayerToModel(o,[e]);const s=o.map((e=>e.join("."))),n=new WeakMap;for(const[e,t]of r)n.set(e,s.indexOf(t));return n}function cascadeLayerNumberForNode(e,r){return e.parent&&"atrule"===e.parent.type&&"layer"===e.parent.name.toLowerCase()?r.has(e.parent)?r.get(e.parent):-1:1/0}function addLayerToModel(e,r){return r.forEach((r=>{const t=r.split(".");e:for(let r=0;r=n&&(s=r,n=a)}-1===s?e.push(o):e.splice(s+1,0,o)}})),e}const r=/(!\s*)?postcss-custom-properties:\s*off\b/i,t=new WeakMap;function isBlockIgnored(e){if(!e||!e.nodes)return!1;if(t.has(e))return t.get(e);const o=e.some((e=>isIgnoreComment(e,r)));return t.set(e,o),o}const o=/(!\s*)?postcss-custom-properties:\s*ignore\s+next\b/i;function isDeclarationIgnored(e){return!!e&&(!!isBlockIgnored(e.parent)||isIgnoreComment(e.prev(),o))}function isIgnoreComment(e,r){return e&&"comment"===e.type&&r.test(e.text)}const s=new Set(["layer"]);function isProcessableRule(e){if(!isHtmlRule(e)&&!isRootRule(e))return!1;let r=e.parent;for(;r;){if("atrule"===r.type&&!s.has(r.name.toLowerCase()))return!1;r=r.parent}return!0}const n=/^html$/i,a=/^:root$/i;function isHtmlRule(e){return e.selectors.some((e=>n.test(e)))&&e.nodes&&e.nodes.length}function isRootRule(e){return e.selectors.some((e=>a.test(e)))&&e.nodes&&e.nodes.length}function getCustomPropertiesFromRoot(r){const t=new Map,o=new Map,s=new Map,n=new Map,a=new Map,l=collectCascadeLayerOrder(r);r.walkRules((e=>{isProcessableRule(e)&&(isBlockIgnored(e)||(isHtmlRule(e)?e.each((e=>{if("decl"!==e.type)return;if(!e.variable||isDeclarationIgnored(e))return;if("initial"===e.value.toLowerCase().trim())return;const r=cascadeLayerNumberForNode(e,l);r>=(n.get(e.prop)??-1)&&(n.set(e.prop,r),t.set(e.prop,e.value))})):isRootRule(e)&&e.each((e=>{if("decl"!==e.type)return;if(!e.variable||isDeclarationIgnored(e))return;if("initial"===e.value.toLowerCase().trim())return;const r=cascadeLayerNumberForNode(e,l);r>=(a.get(e.prop)??-1)&&(a.set(e.prop,r),o.set(e.prop,e.value))}))))}));for(const[e,r]of t.entries())s.set(e,r);for(const[e,r]of o.entries())s.set(e,r);const i=new Map;for(const[r,t]of s.entries())i.set(r,e(t));return i}function transformValueAST(e,r){return e.nodes&&e.nodes.length&&e.nodes.slice().forEach((t=>{if(isVarFunction(t)){const[o,...s]=t.nodes.filter((e=>"div"!==e.type)),{value:n}=o,a=e.nodes.indexOf(t);if(r.has(n)){const t=r.get(n).nodes;reTransformValueAST({nodes:t},r,n),a>-1&&e.nodes.splice(a,1,...t)}else s.length&&(a>-1&&e.nodes.splice(a,1,...t.nodes.slice(t.nodes.indexOf(s[0]))),transformValueAST(e,r))}else transformValueAST(t,r)})),e.toString()}function reTransformValueAST(e,r,t){const o=new Map(r);return o.delete(t),transformValueAST(e,o)}const l=/^var$/i,isVarFunction=e=>"function"===e.type&&l.test(e.value)&&Object(e.nodes).length>0;var transformProperties=(r,t,o)=>{if(isTransformableDecl(r)&&!isDeclarationIgnored(r)){const s=r.value;let n=transformValueAST(e(s),t);const a=new Set;for(;n.includes("--")&&n.toLowerCase().includes("var(")&&!a.has(n);){a.add(n);n=transformValueAST(e(n),t)}if(n!==s){if(parentHasExactFallback(r,n))return void(o.preserve||r.remove());if(o.preserve){const e=r.cloneBefore({value:n});hasTrailingComment(e)&&(e.raws.value.value=e.value.replace(i,"$1"),e.raws.value.raw=e.raws.value.value+e.raws.value.raw.replace(i,"$2"))}else r.value=n,hasTrailingComment(r)&&(r.raws.value.value=r.value.replace(i,"$1"),r.raws.value.raw=r.raws.value.value+r.raws.value.raw.replace(i,"$2"))}}};const isTransformableDecl=e=>!e.variable&&e.value.includes("--")&&e.value.toLowerCase().includes("var("),hasTrailingComment=e=>"value"in Object(Object(e.raws).value)&&"raw"in e.raws.value&&i.test(e.raws.value.raw),i=/^([\W\w]+)(\s*\/\*[\W\w]+?\*\/)$/;function parentHasExactFallback(e,r){if(!e||!e.parent)return!1;let t=!1;const o=e.parent.index(e);return e.parent.each(((s,n)=>s!==e&&(!(n>=o)&&void("decl"===s.type&&s.prop.toLowerCase()===e.prop.toLowerCase()&&s.value===r&&(t=!0))))),t}const creator=r=>{const t=!("preserve"in Object(r))||Boolean(r.preserve);if("importFrom"in Object(r))throw new Error('[postcss-custom-properties] "importFrom" is no longer supported');if("exportTo"in Object(r))throw new Error('[postcss-custom-properties] "exportTo" is no longer supported');return{postcssPlugin:"postcss-custom-properties",prepare:()=>{let r=new Map;return{Once:e=>{r=getCustomPropertiesFromRoot(e)},Declaration:o=>{let s=r;if(t&&o.parent){let t=!1;o.parent.each((n=>{o!==n&&"decl"===n.type&&n.variable&&!isDeclarationIgnored(n)&&(t||(s=new Map(r),t=!0),"initial"!==n.value.toLowerCase().trim()?s.set(n.prop,e(n.value)):s.delete(n.prop))}))}transformProperties(o,s,{preserve:t})}}}}};creator.postcss=!0;export{creator as default}; +import e from"postcss-value-parser";import{parse as r,addLayerToModel as t}from"@csstools/cascade-layer-name-parser";const o=r("csstools-implicit-layer")[0];function collectCascadeLayerOrder(e){const a=new Map,s=new Map,n=[];e.walkAtRules((e=>{var l;if("layer"!==e.name.toLowerCase())return;{let r=e.parent;for(;r;){if("atrule"!==r.type||"layer"!==r.name.toLowerCase()){if(r===e.root())break;return}r=r.parent}}let i;if(e.nodes)i=normalizeLayerName(e.params,1);else{if(!e.params.trim())return;i=e.params}let c=r(i);if(null!=(l=c)&&l.length){{let r=e.parent;for(;r&&"atrule"===r.type&&"layer"===r.name.toLowerCase();){const e=s.get(r);e?(c=c.map((r=>e.concat(r))),r=r.parent):r=r.parent}}if(t(n,c),e.nodes){const r=c[0].concat(o);a.set(e,r),s.set(e,c[0])}}}));for(const e of a.values())t(n,[e]);const l=new WeakMap;for(const[e,r]of a)l.set(e,n.findIndex((e=>r.equal(e))));return l}function cascadeLayerNumberForNode(e,r){return e.parent&&"atrule"===e.parent.type&&"layer"===e.parent.name.toLowerCase()?r.has(e.parent)?r.get(e.parent):-1:1/0}function normalizeLayerName(e,r){return e.trim()?e:"csstools-anon-layer--"+r++}const a=/(!\s*)?postcss-custom-properties:\s*off\b/i,s=new WeakMap;function isBlockIgnored(e){if(!e||!e.nodes)return!1;if(s.has(e))return s.get(e);const r=e.some((e=>isIgnoreComment(e,a)));return s.set(e,r),r}const n=/(!\s*)?postcss-custom-properties:\s*ignore\s+next\b/i;function isDeclarationIgnored(e){return!!e&&(!!isBlockIgnored(e.parent)||isIgnoreComment(e.prev(),n))}function isIgnoreComment(e,r){return e&&"comment"===e.type&&r.test(e.text)}const l=new Set(["layer"]);function isProcessableRule(e){if(!isHtmlRule(e)&&!isRootRule(e))return!1;let r=e.parent;for(;r;){if("atrule"===r.type&&!l.has(r.name.toLowerCase()))return!1;r=r.parent}return!0}const i=/^html$/i,c=/^:root$/i;function isHtmlRule(e){return e.selectors.some((e=>i.test(e)))&&e.nodes&&e.nodes.length}function isRootRule(e){return e.selectors.some((e=>c.test(e)))&&e.nodes&&e.nodes.length}function getCustomPropertiesFromRoot(r){const t=new Map,o=new Map,a=new Map,s=new Map,n=new Map,l=collectCascadeLayerOrder(r);r.walkRules((e=>{isProcessableRule(e)&&(isBlockIgnored(e)||(isHtmlRule(e)?e.each((e=>{if("decl"!==e.type)return;if(!e.variable||isDeclarationIgnored(e))return;if("initial"===e.value.toLowerCase().trim())return;const r=cascadeLayerNumberForNode(e,l);r>=(s.get(e.prop)??-1)&&(s.set(e.prop,r),t.set(e.prop,e.value))})):isRootRule(e)&&e.each((e=>{if("decl"!==e.type)return;if(!e.variable||isDeclarationIgnored(e))return;if("initial"===e.value.toLowerCase().trim())return;const r=cascadeLayerNumberForNode(e,l);r>=(n.get(e.prop)??-1)&&(n.set(e.prop,r),o.set(e.prop,e.value))}))))}));for(const[e,r]of t.entries())a.set(e,r);for(const[e,r]of o.entries())a.set(e,r);const i=new Map;for(const[r,t]of a.entries())i.set(r,e(t));return i}function transformValueAST(e,r){return e.nodes&&e.nodes.length&&e.nodes.slice().forEach((t=>{if(isVarFunction(t)){const[o,...a]=t.nodes.filter((e=>"div"!==e.type)),{value:s}=o,n=e.nodes.indexOf(t);if(r.has(s)){const t=r.get(s).nodes;reTransformValueAST({nodes:t},r,s),n>-1&&e.nodes.splice(n,1,...t)}else a.length&&(n>-1&&e.nodes.splice(n,1,...t.nodes.slice(t.nodes.indexOf(a[0]))),transformValueAST(e,r))}else transformValueAST(t,r)})),e.toString()}function reTransformValueAST(e,r,t){const o=new Map(r);return o.delete(t),transformValueAST(e,o)}const u=/^var$/i,isVarFunction=e=>"function"===e.type&&u.test(e.value)&&Object(e.nodes).length>0;var transformProperties=(r,t,o)=>{if(isTransformableDecl(r)&&!isDeclarationIgnored(r)){const a=r.value;let s=transformValueAST(e(a),t);const n=new Set;for(;s.includes("--")&&s.toLowerCase().includes("var(")&&!n.has(s);){n.add(s);s=transformValueAST(e(s),t)}if(s!==a){if(parentHasExactFallback(r,s))return void(o.preserve||r.remove());if(o.preserve){const e=r.cloneBefore({value:s});hasTrailingComment(e)&&(e.raws.value.value=e.value.replace(p,"$1"),e.raws.value.raw=e.raws.value.value+e.raws.value.raw.replace(p,"$2"))}else r.value=s,hasTrailingComment(r)&&(r.raws.value.value=r.value.replace(p,"$1"),r.raws.value.raw=r.raws.value.value+r.raws.value.raw.replace(p,"$2"))}}};const isTransformableDecl=e=>!e.variable&&e.value.includes("--")&&e.value.toLowerCase().includes("var("),hasTrailingComment=e=>"value"in Object(Object(e.raws).value)&&"raw"in e.raws.value&&p.test(e.raws.value.raw),p=/^([\W\w]+)(\s*\/\*[\W\w]+?\*\/)$/;function parentHasExactFallback(e,r){if(!e||!e.parent)return!1;let t=!1;const o=e.parent.index(e);return e.parent.each(((a,s)=>a!==e&&(!(s>=o)&&void("decl"===a.type&&a.prop.toLowerCase()===e.prop.toLowerCase()&&a.value===r&&(t=!0))))),t}const creator=r=>{const t=!("preserve"in Object(r))||Boolean(r.preserve);if("importFrom"in Object(r))throw new Error('[postcss-custom-properties] "importFrom" is no longer supported');if("exportTo"in Object(r))throw new Error('[postcss-custom-properties] "exportTo" is no longer supported');return{postcssPlugin:"postcss-custom-properties",prepare:()=>{let r=new Map;return{Once:e=>{r=getCustomPropertiesFromRoot(e)},Declaration:o=>{let a=r;if(t&&o.parent){let t=!1;o.parent.each((s=>{o!==s&&"decl"===s.type&&s.variable&&!isDeclarationIgnored(s)&&(t||(a=new Map(r),t=!0),"initial"!==s.value.toLowerCase().trim()?a.set(s.prop,e(s.value)):a.delete(s.prop))}))}transformProperties(o,a,{preserve:t})}}}}};creator.postcss=!0;export{creator as default}; diff --git a/plugins/postcss-custom-properties/package.json b/plugins/postcss-custom-properties/package.json index e68d7003b..e833c9b13 100644 --- a/plugins/postcss-custom-properties/package.json +++ b/plugins/postcss-custom-properties/package.json @@ -32,6 +32,9 @@ "index.d.ts" ], "dependencies": { + "@csstools/cascade-layer-name-parser": "^1.0.0", + "@csstools/css-parser-algorithms": "^1.0.0", + "@csstools/css-tokenizer": "^1.0.0", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { diff --git a/plugins/postcss-custom-properties/src/cascade-layers.ts b/plugins/postcss-custom-properties/src/cascade-layers.ts index 4d60ebb98..3b95579cb 100644 --- a/plugins/postcss-custom-properties/src/cascade-layers.ts +++ b/plugins/postcss-custom-properties/src/cascade-layers.ts @@ -1,10 +1,13 @@ import type { AtRule, Container, Document, Node, Root } from 'postcss'; +import { LayerName, parse as parseCascadeLayerNames, addLayerToModel } from '@csstools/cascade-layer-name-parser'; + +const implicitLayerNameForCloning = parseCascadeLayerNames('csstools-implicit-layer')[0]; export function collectCascadeLayerOrder(root: Root) { - const references: Map = new Map(); - const referencesForLayerNames: Map = new Map(); + const references: Map = new Map(); + const referencesForLayerNames: Map = new Map(); - const layers: Array> = []; + const layers: Array = []; const anonLayerCounter = 1; root.walkAtRules((node) => { @@ -32,17 +35,20 @@ export function collectCascadeLayerOrder(root: Root) { } } - let currentLayerNames = []; + let layerParams; if (node.nodes) { // @layer { .foo {} } - currentLayerNames.push(normalizeLayerName(node.params, anonLayerCounter)); + layerParams = normalizeLayerName(node.params, anonLayerCounter); } else if (node.params.trim()) { // @layer a, b; - currentLayerNames = node.params.split(',').map((layerName) => { - return layerName.trim(); - }); + layerParams = node.params; } else { // @layer; return; } + let currentLayerNames = parseCascadeLayerNames(layerParams); + if (!currentLayerNames?.length) { + return; + } + { // Stitch the layer names of the current node together with those of ancestors. // @layer foo { @layer bar { .any {} } } @@ -56,7 +62,7 @@ export function collectCascadeLayerOrder(root: Root) { } currentLayerNames = currentLayerNames.map((layerName) => { - return parentLayerName + '.' + layerName; + return parentLayerName.concat(layerName); }); parent = parent.parent; @@ -75,7 +81,7 @@ export function collectCascadeLayerOrder(root: Root) { // 3. use the real layer to resolve other real layer names // 4. use the implicit layer later - const implicitLayerName = currentLayerNames[0] + '.' + 'csstools-implicit-layer'; + const implicitLayerName = currentLayerNames[0].concat(implicitLayerNameForCloning); references.set(node, implicitLayerName); referencesForLayerNames.set(node, currentLayerNames[0]); } @@ -87,11 +93,11 @@ export function collectCascadeLayerOrder(root: Root) { addLayerToModel(layers, [layerName]); } - const finalLayers = layers.map((x) => x.join('.')); - const out: WeakMap = new WeakMap(); for (const [node, layerName] of references) { - out.set(node, finalLayers.indexOf(layerName)); + out.set(node, layers.findIndex((x) => { + return layerName.equal(x); + })); } return out; @@ -113,68 +119,9 @@ export function cascadeLayerNumberForNode(node: Node, layers: WeakMap { - const allLayerNameParts = layerName.split('.'); - - ALL_LAYER_NAME_PARTS_LOOP: for (let x = 0; x < allLayerNameParts.length; x++) { - const layerNameParts = allLayerNameParts.slice(0, x + 1); - - let layerWithMostEqualSegments = -1; - let mostEqualSegments = 0; - - for (let i = 0; i < layers.length; i++) { - const existingLayerParts = layers[i]; - - let numberOfEqualSegments = 0; - - LAYER_PARTS_LOOP: for (let j = 0; j < existingLayerParts.length; j++) { - const existingLayerPart = existingLayerParts[j]; - const layerPart = layerNameParts[j]; - - if (layerPart === existingLayerPart && (j + 1) === layerNameParts.length) { - continue ALL_LAYER_NAME_PARTS_LOOP; // layer already exists in model - } - - if (layerPart === existingLayerPart) { - numberOfEqualSegments++; - continue; - } - - if (layerPart !== existingLayerPart) { - break LAYER_PARTS_LOOP; - } - } - - if (numberOfEqualSegments >= mostEqualSegments) { - layerWithMostEqualSegments = i; - mostEqualSegments = numberOfEqualSegments; - } - } - - if (layerWithMostEqualSegments === -1) { - layers.push(layerNameParts); - } else { - layers.splice(layerWithMostEqualSegments+1, 0, layerNameParts); - } - } - }); + if (layerName.trim()) { + return layerName; + } - return layers; + return `csstools-anon-layer--${counter++}`; } diff --git a/plugins/postcss-custom-properties/test/cascade-layers.css b/plugins/postcss-custom-properties/test/cascade-layers.css index f560bc574..b6cb1a95b 100644 --- a/plugins/postcss-custom-properties/test/cascade-layers.css +++ b/plugins/postcss-custom-properties/test/cascade-layers.css @@ -1,4 +1,5 @@ @layer extensions, components; +@layer extensions.one,,invalid-layer-names..foo; :root{ --desktop: unlayered; diff --git a/plugins/postcss-custom-properties/test/cascade-layers.expect.css b/plugins/postcss-custom-properties/test/cascade-layers.expect.css index 3b884a0c5..9595e2232 100644 --- a/plugins/postcss-custom-properties/test/cascade-layers.expect.css +++ b/plugins/postcss-custom-properties/test/cascade-layers.expect.css @@ -1,4 +1,5 @@ @layer extensions, components; +@layer extensions.one,,invalid-layer-names..foo; :root{ --desktop: unlayered; diff --git a/plugins/postcss-custom-selectors/dist/index.cjs b/plugins/postcss-custom-selectors/dist/index.cjs index c68729dc6..bcf35bb12 100644 --- a/plugins/postcss-custom-selectors/dist/index.cjs +++ b/plugins/postcss-custom-selectors/dist/index.cjs @@ -1 +1 @@ -"use strict";var e=require("postcss-selector-parser");function collectCascadeLayerOrder(e){const t=new Map,r=new Map,s=[];e.walkAtRules((e=>{if("layer"!==e.name.toLowerCase())return;{let t=e.parent;for(;t;){if("atrule"!==t.type||"layer"!==t.name.toLowerCase()){if(t===e.root())break;return}t=t.parent}}let o=[];if(e.nodes)o.push((n=e.params,a=1,n.trim()||"csstools-anon-layer--"+a++));else{if(!e.params.trim())return;o=e.params.split(",").map((e=>e.trim()))}var n,a;{let t=e.parent;for(;t&&"atrule"===t.type&&"layer"===t.name.toLowerCase();){const e=r.get(t);e?(o=o.map((t=>e+"."+t)),t=t.parent):t=t.parent}}if(addLayerToModel(s,o),e.nodes){const s=o[0]+".csstools-implicit-layer";t.set(e,s),r.set(e,o[0])}}));for(const e of t.values())addLayerToModel(s,[e]);const o=s.map((e=>e.join("."))),n=new WeakMap;for(const[e,r]of t)n.set(e,o.indexOf(r));return n}function addLayerToModel(e,t){return t.forEach((t=>{const r=t.split(".");e:for(let t=0;t=n&&(o=t,n=a)}-1===o?e.push(s):e.splice(o+1,0,s)}})),e}const t=new Set(["scope","container","layer"]);function isProcessableCustomSelectorRule(e){if("atrule"!==e.type)return!1;if("custom-selector"!==e.name.toLowerCase())return!1;if(!e.params||!e.params.includes(":--"))return!1;if(e.nodes&&e.nodes.length>0)return!1;let r=e.parent;for(;r;){if("rule"===r.type)return!1;if("atrule"===r.type&&!t.has(r.name.toLowerCase()))return!1;r=r.parent}return!0}function getCustomSelectors(t,r,s){const o=new Map,n=new Map,a=collectCascadeLayerOrder(t);return t.walkAtRules((t=>{var l,c;if(isProcessableCustomSelectorRule(t))try{var p,u,i;const r=t.params.trim(),m=e().astSync(r),f=null==m||null==(p=m.nodes)||null==(u=p[0])||null==(i=u.nodes)?void 0:i[0];if(!f||"pseudo"!==f.type||!f.value.startsWith(":--"))return;const d=f.toString(),y=(c=a,(l=t).parent&&"atrule"===l.parent.type&&"layer"===l.parent.name.toLowerCase()?c.has(l.parent)?c.get(l.parent):-1:1/0);if(y>=(n.get(d)??-1)&&(n.set(d,y),o.set(d,e().astSync(r.slice(d.length).trim()))),!s.preserve){const e=t.parent;t.remove(),removeEmptyAncestorBlocks(e)}}catch(e){t.warn(r,`Failed to parse custom selector : "${t.params}" with message: "${e.message}"`)}})),o}function removeEmptyAncestorBlocks(e){let t=e;for(;t;){if(t.nodes&&t.nodes.length>0)return;const e=t.parent;t.remove(),t=e}}var transformRule=(t,r,s,o)=>{let n=t.selector;try{n=e((t=>{t.walkPseudos((t=>{if(!s.has(t.value))return;const r=e.pseudo({value:":is",nodes:[]});s.get(t.value).each((e=>{r.append(e.clone({}))})),t.replaceWith(r)}))})).processSync(t.selector)}catch(e){return void t.warn(r,`Failed to parse selector : "${n}" with message: "${e.message}"`)}n!==t.selector&&(t.cloneBefore({selector:n}),o.preserve||t.remove())};const creator=e=>{const t=Boolean(Object(e).preserve);if("importFrom"in Object(e))throw new Error('[postcss-custom-selectors] "importFrom" is no longer supported');if("exportTo"in Object(e))throw new Error('[postcss-custom-selectors] "exportTo" is no longer supported');return{postcssPlugin:"postcss-custom-selectors",prepare(){let e=new Map;return{Once:(r,{result:s})=>{e=getCustomSelectors(r,s,{preserve:t})},Rule:(r,{result:s})=>{r.selector.includes(":--")&&transformRule(r,s,e,{preserve:t})}}}}};creator.postcss=!0,module.exports=creator; +"use strict";var e=require("postcss-selector-parser"),r=require("@csstools/cascade-layer-name-parser");const t=r.parse("csstools-implicit-layer")[0];function collectCascadeLayerOrder(e){const s=new Map,o=new Map,a=[];e.walkAtRules((e=>{var n;if("layer"!==e.name.toLowerCase())return;{let r=e.parent;for(;r;){if("atrule"!==r.type||"layer"!==r.name.toLowerCase()){if(r===e.root())break;return}r=r.parent}}let l;if(e.nodes)l=normalizeLayerName(e.params,1);else{if(!e.params.trim())return;l=e.params}let c=r.parse(l);if(null!=(n=c)&&n.length){{let r=e.parent;for(;r&&"atrule"===r.type&&"layer"===r.name.toLowerCase();){const e=o.get(r);e?(c=c.map((r=>e.concat(r))),r=r.parent):r=r.parent}}if(r.addLayerToModel(a,c),e.nodes){const r=c[0].concat(t);s.set(e,r),o.set(e,c[0])}}}));for(const e of s.values())r.addLayerToModel(a,[e]);const n=new WeakMap;for(const[e,r]of s)n.set(e,a.findIndex((e=>r.equal(e))));return n}function normalizeLayerName(e,r){return e.trim()?e:"csstools-anon-layer--"+r++}const s=new Set(["scope","container","layer"]);function isProcessableCustomSelectorRule(e){if("atrule"!==e.type)return!1;if("custom-selector"!==e.name.toLowerCase())return!1;if(!e.params||!e.params.includes(":--"))return!1;if(e.nodes&&e.nodes.length>0)return!1;let r=e.parent;for(;r;){if("rule"===r.type)return!1;if("atrule"===r.type&&!s.has(r.name.toLowerCase()))return!1;r=r.parent}return!0}function getCustomSelectors(r,t,s){const o=new Map,a=new Map,n=collectCascadeLayerOrder(r);return r.walkAtRules((r=>{var l,c;if(isProcessableCustomSelectorRule(r))try{var p,u,i;const t=r.params.trim(),m=e().astSync(t),d=null==m||null==(p=m.nodes)||null==(u=p[0])||null==(i=u.nodes)?void 0:i[0];if(!d||"pseudo"!==d.type||!d.value.startsWith(":--"))return;const f=d.toString(),y=(c=n,(l=r).parent&&"atrule"===l.parent.type&&"layer"===l.parent.name.toLowerCase()?c.has(l.parent)?c.get(l.parent):-1:1/0);if(y>=(a.get(f)??-1)&&(a.set(f,y),o.set(f,e().astSync(t.slice(f.length).trim()))),!s.preserve){const e=r.parent;r.remove(),removeEmptyAncestorBlocks(e)}}catch(e){r.warn(t,`Failed to parse custom selector : "${r.params}" with message: "${e.message}"`)}})),o}function removeEmptyAncestorBlocks(e){let r=e;for(;r;){if(r.nodes&&r.nodes.length>0)return;const e=r.parent;r.remove(),r=e}}var transformRule=(r,t,s,o)=>{let a=r.selector;try{a=e((r=>{r.walkPseudos((r=>{if(!s.has(r.value))return;const t=e.pseudo({value:":is",nodes:[]});s.get(r.value).each((e=>{t.append(e.clone({}))})),r.replaceWith(t)}))})).processSync(r.selector)}catch(e){return void r.warn(t,`Failed to parse selector : "${a}" with message: "${e.message}"`)}a!==r.selector&&(r.cloneBefore({selector:a}),o.preserve||r.remove())};const creator=e=>{const r=Boolean(Object(e).preserve);if("importFrom"in Object(e))throw new Error('[postcss-custom-selectors] "importFrom" is no longer supported');if("exportTo"in Object(e))throw new Error('[postcss-custom-selectors] "exportTo" is no longer supported');return{postcssPlugin:"postcss-custom-selectors",prepare(){let e=new Map;return{Once:(t,{result:s})=>{e=getCustomSelectors(t,s,{preserve:r})},Rule:(t,{result:s})=>{t.selector.includes(":--")&&transformRule(t,s,e,{preserve:r})}}}}};creator.postcss=!0,module.exports=creator; diff --git a/plugins/postcss-custom-selectors/dist/index.mjs b/plugins/postcss-custom-selectors/dist/index.mjs index a8bbe2e70..0c61da99f 100644 --- a/plugins/postcss-custom-selectors/dist/index.mjs +++ b/plugins/postcss-custom-selectors/dist/index.mjs @@ -1 +1 @@ -import e from"postcss-selector-parser";function collectCascadeLayerOrder(e){const t=new Map,r=new Map,s=[];e.walkAtRules((e=>{if("layer"!==e.name.toLowerCase())return;{let t=e.parent;for(;t;){if("atrule"!==t.type||"layer"!==t.name.toLowerCase()){if(t===e.root())break;return}t=t.parent}}let o=[];if(e.nodes)o.push((n=e.params,a=1,n.trim()||"csstools-anon-layer--"+a++));else{if(!e.params.trim())return;o=e.params.split(",").map((e=>e.trim()))}var n,a;{let t=e.parent;for(;t&&"atrule"===t.type&&"layer"===t.name.toLowerCase();){const e=r.get(t);e?(o=o.map((t=>e+"."+t)),t=t.parent):t=t.parent}}if(addLayerToModel(s,o),e.nodes){const s=o[0]+".csstools-implicit-layer";t.set(e,s),r.set(e,o[0])}}));for(const e of t.values())addLayerToModel(s,[e]);const o=s.map((e=>e.join("."))),n=new WeakMap;for(const[e,r]of t)n.set(e,o.indexOf(r));return n}function addLayerToModel(e,t){return t.forEach((t=>{const r=t.split(".");e:for(let t=0;t=n&&(o=t,n=a)}-1===o?e.push(s):e.splice(o+1,0,s)}})),e}const t=new Set(["scope","container","layer"]);function isProcessableCustomSelectorRule(e){if("atrule"!==e.type)return!1;if("custom-selector"!==e.name.toLowerCase())return!1;if(!e.params||!e.params.includes(":--"))return!1;if(e.nodes&&e.nodes.length>0)return!1;let r=e.parent;for(;r;){if("rule"===r.type)return!1;if("atrule"===r.type&&!t.has(r.name.toLowerCase()))return!1;r=r.parent}return!0}function getCustomSelectors(t,r,s){const o=new Map,n=new Map,a=collectCascadeLayerOrder(t);return t.walkAtRules((t=>{var l,c;if(isProcessableCustomSelectorRule(t))try{var p,u,i;const r=t.params.trim(),m=e().astSync(r),f=null==m||null==(p=m.nodes)||null==(u=p[0])||null==(i=u.nodes)?void 0:i[0];if(!f||"pseudo"!==f.type||!f.value.startsWith(":--"))return;const d=f.toString(),y=(c=a,(l=t).parent&&"atrule"===l.parent.type&&"layer"===l.parent.name.toLowerCase()?c.has(l.parent)?c.get(l.parent):-1:1/0);if(y>=(n.get(d)??-1)&&(n.set(d,y),o.set(d,e().astSync(r.slice(d.length).trim()))),!s.preserve){const e=t.parent;t.remove(),removeEmptyAncestorBlocks(e)}}catch(e){t.warn(r,`Failed to parse custom selector : "${t.params}" with message: "${e.message}"`)}})),o}function removeEmptyAncestorBlocks(e){let t=e;for(;t;){if(t.nodes&&t.nodes.length>0)return;const e=t.parent;t.remove(),t=e}}var transformRule=(t,r,s,o)=>{let n=t.selector;try{n=e((t=>{t.walkPseudos((t=>{if(!s.has(t.value))return;const r=e.pseudo({value:":is",nodes:[]});s.get(t.value).each((e=>{r.append(e.clone({}))})),t.replaceWith(r)}))})).processSync(t.selector)}catch(e){return void t.warn(r,`Failed to parse selector : "${n}" with message: "${e.message}"`)}n!==t.selector&&(t.cloneBefore({selector:n}),o.preserve||t.remove())};const creator=e=>{const t=Boolean(Object(e).preserve);if("importFrom"in Object(e))throw new Error('[postcss-custom-selectors] "importFrom" is no longer supported');if("exportTo"in Object(e))throw new Error('[postcss-custom-selectors] "exportTo" is no longer supported');return{postcssPlugin:"postcss-custom-selectors",prepare(){let e=new Map;return{Once:(r,{result:s})=>{e=getCustomSelectors(r,s,{preserve:t})},Rule:(r,{result:s})=>{r.selector.includes(":--")&&transformRule(r,s,e,{preserve:t})}}}}};creator.postcss=!0;export{creator as default}; +import e from"postcss-selector-parser";import{parse as r,addLayerToModel as t}from"@csstools/cascade-layer-name-parser";const s=r("csstools-implicit-layer")[0];function collectCascadeLayerOrder(e){const o=new Map,n=new Map,a=[];e.walkAtRules((e=>{var l;if("layer"!==e.name.toLowerCase())return;{let r=e.parent;for(;r;){if("atrule"!==r.type||"layer"!==r.name.toLowerCase()){if(r===e.root())break;return}r=r.parent}}let c;if(e.nodes)c=normalizeLayerName(e.params,1);else{if(!e.params.trim())return;c=e.params}let p=r(c);if(null!=(l=p)&&l.length){{let r=e.parent;for(;r&&"atrule"===r.type&&"layer"===r.name.toLowerCase();){const e=n.get(r);e?(p=p.map((r=>e.concat(r))),r=r.parent):r=r.parent}}if(t(a,p),e.nodes){const r=p[0].concat(s);o.set(e,r),n.set(e,p[0])}}}));for(const e of o.values())t(a,[e]);const l=new WeakMap;for(const[e,r]of o)l.set(e,a.findIndex((e=>r.equal(e))));return l}function normalizeLayerName(e,r){return e.trim()?e:"csstools-anon-layer--"+r++}const o=new Set(["scope","container","layer"]);function isProcessableCustomSelectorRule(e){if("atrule"!==e.type)return!1;if("custom-selector"!==e.name.toLowerCase())return!1;if(!e.params||!e.params.includes(":--"))return!1;if(e.nodes&&e.nodes.length>0)return!1;let r=e.parent;for(;r;){if("rule"===r.type)return!1;if("atrule"===r.type&&!o.has(r.name.toLowerCase()))return!1;r=r.parent}return!0}function getCustomSelectors(r,t,s){const o=new Map,n=new Map,a=collectCascadeLayerOrder(r);return r.walkAtRules((r=>{var l,c;if(isProcessableCustomSelectorRule(r))try{var p,u,i;const t=r.params.trim(),m=e().astSync(t),f=null==m||null==(p=m.nodes)||null==(u=p[0])||null==(i=u.nodes)?void 0:i[0];if(!f||"pseudo"!==f.type||!f.value.startsWith(":--"))return;const d=f.toString(),y=(c=a,(l=r).parent&&"atrule"===l.parent.type&&"layer"===l.parent.name.toLowerCase()?c.has(l.parent)?c.get(l.parent):-1:1/0);if(y>=(n.get(d)??-1)&&(n.set(d,y),o.set(d,e().astSync(t.slice(d.length).trim()))),!s.preserve){const e=r.parent;r.remove(),removeEmptyAncestorBlocks(e)}}catch(e){r.warn(t,`Failed to parse custom selector : "${r.params}" with message: "${e.message}"`)}})),o}function removeEmptyAncestorBlocks(e){let r=e;for(;r;){if(r.nodes&&r.nodes.length>0)return;const e=r.parent;r.remove(),r=e}}var transformRule=(r,t,s,o)=>{let n=r.selector;try{n=e((r=>{r.walkPseudos((r=>{if(!s.has(r.value))return;const t=e.pseudo({value:":is",nodes:[]});s.get(r.value).each((e=>{t.append(e.clone({}))})),r.replaceWith(t)}))})).processSync(r.selector)}catch(e){return void r.warn(t,`Failed to parse selector : "${n}" with message: "${e.message}"`)}n!==r.selector&&(r.cloneBefore({selector:n}),o.preserve||r.remove())};const creator=e=>{const r=Boolean(Object(e).preserve);if("importFrom"in Object(e))throw new Error('[postcss-custom-selectors] "importFrom" is no longer supported');if("exportTo"in Object(e))throw new Error('[postcss-custom-selectors] "exportTo" is no longer supported');return{postcssPlugin:"postcss-custom-selectors",prepare(){let e=new Map;return{Once:(t,{result:s})=>{e=getCustomSelectors(t,s,{preserve:r})},Rule:(t,{result:s})=>{t.selector.includes(":--")&&transformRule(t,s,e,{preserve:r})}}}}};creator.postcss=!0;export{creator as default}; diff --git a/plugins/postcss-custom-selectors/package.json b/plugins/postcss-custom-selectors/package.json index 29c6fd1c6..b31f38493 100644 --- a/plugins/postcss-custom-selectors/package.json +++ b/plugins/postcss-custom-selectors/package.json @@ -48,6 +48,9 @@ "dist" ], "dependencies": { + "@csstools/cascade-layer-name-parser": "^1.0.0", + "@csstools/css-parser-algorithms": "^1.0.0", + "@csstools/css-tokenizer": "^1.0.0", "postcss-selector-parser": "^6.0.4" }, "peerDependencies": { diff --git a/plugins/postcss-custom-selectors/src/cascade-layers.ts b/plugins/postcss-custom-selectors/src/cascade-layers.ts index 4d60ebb98..3b95579cb 100644 --- a/plugins/postcss-custom-selectors/src/cascade-layers.ts +++ b/plugins/postcss-custom-selectors/src/cascade-layers.ts @@ -1,10 +1,13 @@ import type { AtRule, Container, Document, Node, Root } from 'postcss'; +import { LayerName, parse as parseCascadeLayerNames, addLayerToModel } from '@csstools/cascade-layer-name-parser'; + +const implicitLayerNameForCloning = parseCascadeLayerNames('csstools-implicit-layer')[0]; export function collectCascadeLayerOrder(root: Root) { - const references: Map = new Map(); - const referencesForLayerNames: Map = new Map(); + const references: Map = new Map(); + const referencesForLayerNames: Map = new Map(); - const layers: Array> = []; + const layers: Array = []; const anonLayerCounter = 1; root.walkAtRules((node) => { @@ -32,17 +35,20 @@ export function collectCascadeLayerOrder(root: Root) { } } - let currentLayerNames = []; + let layerParams; if (node.nodes) { // @layer { .foo {} } - currentLayerNames.push(normalizeLayerName(node.params, anonLayerCounter)); + layerParams = normalizeLayerName(node.params, anonLayerCounter); } else if (node.params.trim()) { // @layer a, b; - currentLayerNames = node.params.split(',').map((layerName) => { - return layerName.trim(); - }); + layerParams = node.params; } else { // @layer; return; } + let currentLayerNames = parseCascadeLayerNames(layerParams); + if (!currentLayerNames?.length) { + return; + } + { // Stitch the layer names of the current node together with those of ancestors. // @layer foo { @layer bar { .any {} } } @@ -56,7 +62,7 @@ export function collectCascadeLayerOrder(root: Root) { } currentLayerNames = currentLayerNames.map((layerName) => { - return parentLayerName + '.' + layerName; + return parentLayerName.concat(layerName); }); parent = parent.parent; @@ -75,7 +81,7 @@ export function collectCascadeLayerOrder(root: Root) { // 3. use the real layer to resolve other real layer names // 4. use the implicit layer later - const implicitLayerName = currentLayerNames[0] + '.' + 'csstools-implicit-layer'; + const implicitLayerName = currentLayerNames[0].concat(implicitLayerNameForCloning); references.set(node, implicitLayerName); referencesForLayerNames.set(node, currentLayerNames[0]); } @@ -87,11 +93,11 @@ export function collectCascadeLayerOrder(root: Root) { addLayerToModel(layers, [layerName]); } - const finalLayers = layers.map((x) => x.join('.')); - const out: WeakMap = new WeakMap(); for (const [node, layerName] of references) { - out.set(node, finalLayers.indexOf(layerName)); + out.set(node, layers.findIndex((x) => { + return layerName.equal(x); + })); } return out; @@ -113,68 +119,9 @@ export function cascadeLayerNumberForNode(node: Node, layers: WeakMap { - const allLayerNameParts = layerName.split('.'); - - ALL_LAYER_NAME_PARTS_LOOP: for (let x = 0; x < allLayerNameParts.length; x++) { - const layerNameParts = allLayerNameParts.slice(0, x + 1); - - let layerWithMostEqualSegments = -1; - let mostEqualSegments = 0; - - for (let i = 0; i < layers.length; i++) { - const existingLayerParts = layers[i]; - - let numberOfEqualSegments = 0; - - LAYER_PARTS_LOOP: for (let j = 0; j < existingLayerParts.length; j++) { - const existingLayerPart = existingLayerParts[j]; - const layerPart = layerNameParts[j]; - - if (layerPart === existingLayerPart && (j + 1) === layerNameParts.length) { - continue ALL_LAYER_NAME_PARTS_LOOP; // layer already exists in model - } - - if (layerPart === existingLayerPart) { - numberOfEqualSegments++; - continue; - } - - if (layerPart !== existingLayerPart) { - break LAYER_PARTS_LOOP; - } - } - - if (numberOfEqualSegments >= mostEqualSegments) { - layerWithMostEqualSegments = i; - mostEqualSegments = numberOfEqualSegments; - } - } - - if (layerWithMostEqualSegments === -1) { - layers.push(layerNameParts); - } else { - layers.splice(layerWithMostEqualSegments+1, 0, layerNameParts); - } - } - }); + if (layerName.trim()) { + return layerName; + } - return layers; + return `csstools-anon-layer--${counter++}`; } diff --git a/plugins/postcss-custom-selectors/test/cascade-layers.css b/plugins/postcss-custom-selectors/test/cascade-layers.css index 7c5d4fbaa..ffc8afbb5 100644 --- a/plugins/postcss-custom-selectors/test/cascade-layers.css +++ b/plugins/postcss-custom-selectors/test/cascade-layers.css @@ -1,4 +1,5 @@ @layer extensions, components; +@layer extensions.one,,invalid-layer-names..foo; @custom-selector :--desktop :desktop(unlayered); diff --git a/plugins/postcss-custom-selectors/test/cascade-layers.expect.css b/plugins/postcss-custom-selectors/test/cascade-layers.expect.css index abb93defe..3a168eb56 100644 --- a/plugins/postcss-custom-selectors/test/cascade-layers.expect.css +++ b/plugins/postcss-custom-selectors/test/cascade-layers.expect.css @@ -1,4 +1,5 @@ @layer extensions, components; +@layer extensions.one,,invalid-layer-names..foo; @layer extensions { @layer one, two diff --git a/rollup/configs/externals.mjs b/rollup/configs/externals.mjs index 65bc7a1c1..76d60711f 100644 --- a/rollup/configs/externals.mjs +++ b/rollup/configs/externals.mjs @@ -9,6 +9,7 @@ export const externalsForCLI = [ /^postcss\/lib\/*/, 'postcss-html', + '@csstools/cascade-layer-name-parser', '@csstools/css-parser-algorithms', '@csstools/css-tokenizer', '@csstools/media-query-list-parser', @@ -79,6 +80,7 @@ export const externalsForPlugin = [ /^postcss\/lib\/*/, 'postcss-html', + '@csstools/cascade-layer-name-parser', '@csstools/css-parser-algorithms', '@csstools/css-tokenizer', '@csstools/media-query-list-parser',