From df439119373d0a59bb6de9757534d1dd1925ed84 Mon Sep 17 00:00:00 2001 From: Romain Menke Date: Thu, 13 Oct 2022 14:13:01 +0200 Subject: [PATCH 01/35] wip --- package-lock.json | 36 ++++ package.json | 2 + packages/css-parser/.gitignore | 6 + packages/css-parser/.nvmrc | 1 + packages/css-parser/CHANGELOG.md | 3 + packages/css-parser/LICENSE.md | 20 ++ packages/css-parser/README.md | 129 ++++++++++++ packages/css-parser/package.json | 68 +++++++ .../consume-component-block-function.ts | 152 ++++++++++++++ packages/css-parser/src/index.ts | 0 packages/css-parser/stryker.conf.json | 19 ++ packages/css-parser/test/_import.mjs | 5 + packages/css-parser/test/_require.cjs | 5 + packages/css-parser/test/test.mjs | 13 ++ packages/css-parser/tsconfig.json | 9 + packages/css-tokenizer/src/index.ts | 2 +- .../css-tokenizer/src/interfaces/token.ts | 51 +++++ .../.gitignore | 6 + .../postcss-media-query-list-parser/.nvmrc | 1 + .../CHANGELOG.md | 3 + .../LICENSE.md | 20 ++ .../postcss-media-query-list-parser/README.md | 1 + .../package.json | 63 ++++++ .../src/index.ts | 1 + .../src/nodes/general-enclosed.ts | 15 ++ .../src/nodes/media-and.ts | 15 ++ .../src/nodes/media-condition-list.ts | 45 +++++ .../nodes/media-condition-without-or-or.ts | 16 ++ .../src/nodes/media-condition.ts | 16 ++ .../src/nodes/media-feature-boolean.ts | 5 + .../src/nodes/media-feature-comparison.ts | 49 +++++ .../src/nodes/media-feature-name.ts | 21 ++ .../src/nodes/media-feature-plain.ts | 18 ++ .../src/nodes/media-feature-range.ts | 138 +++++++++++++ .../src/nodes/media-feature-value.ts | 15 ++ .../src/nodes/media-feature.ts | 17 ++ .../src/nodes/media-in-parens.ts | 29 +++ .../src/nodes/media-not.ts | 15 ++ .../src/nodes/media-or.ts | 15 ++ .../src/nodes/media-query-modifier.ts | 4 + .../src/nodes/media-query.ts | 50 +++++ .../src/nodes/media-type.ts | 22 ++ .../src/parser/advance/advance.ts | 58 ++++++ .../src/parser/consume/consume-boolean.ts | 46 +++++ .../src/parser/consume/consume-plain.ts | 44 ++++ .../src/parser/consume/consume-value.ts | 12 ++ .../src/parser/parse.ts | 97 +++++++++ .../test/test.mjs | 3 + .../tsconfig.json | 9 + packages/virtual-media/.gitignore | 6 + packages/virtual-media/.nvmrc | 1 + packages/virtual-media/CHANGELOG.md | 3 + packages/virtual-media/LICENSE.md | 20 ++ packages/virtual-media/README.md | 1 + packages/virtual-media/package.json | 63 ++++++ packages/virtual-media/src/index.ts | 188 ++++++++++++++++++ packages/virtual-media/src/range/add.ts | 67 +++++++ packages/virtual-media/src/range/compare.ts | 33 +++ packages/virtual-media/src/range/range.ts | 4 + packages/virtual-media/test/test.mjs | 28 +++ packages/virtual-media/tsconfig.json | 9 + 61 files changed, 1812 insertions(+), 1 deletion(-) create mode 100644 packages/css-parser/.gitignore create mode 100644 packages/css-parser/.nvmrc create mode 100644 packages/css-parser/CHANGELOG.md create mode 100644 packages/css-parser/LICENSE.md create mode 100644 packages/css-parser/README.md create mode 100644 packages/css-parser/package.json create mode 100644 packages/css-parser/src/consume/consume-component-block-function.ts create mode 100644 packages/css-parser/src/index.ts create mode 100644 packages/css-parser/stryker.conf.json create mode 100644 packages/css-parser/test/_import.mjs create mode 100644 packages/css-parser/test/_require.cjs create mode 100644 packages/css-parser/test/test.mjs create mode 100644 packages/css-parser/tsconfig.json create mode 100644 packages/postcss-media-query-list-parser/.gitignore create mode 100644 packages/postcss-media-query-list-parser/.nvmrc create mode 100644 packages/postcss-media-query-list-parser/CHANGELOG.md create mode 100644 packages/postcss-media-query-list-parser/LICENSE.md create mode 100644 packages/postcss-media-query-list-parser/README.md create mode 100644 packages/postcss-media-query-list-parser/package.json create mode 100644 packages/postcss-media-query-list-parser/src/index.ts create mode 100644 packages/postcss-media-query-list-parser/src/nodes/general-enclosed.ts create mode 100644 packages/postcss-media-query-list-parser/src/nodes/media-and.ts create mode 100644 packages/postcss-media-query-list-parser/src/nodes/media-condition-list.ts create mode 100644 packages/postcss-media-query-list-parser/src/nodes/media-condition-without-or-or.ts create mode 100644 packages/postcss-media-query-list-parser/src/nodes/media-condition.ts create mode 100644 packages/postcss-media-query-list-parser/src/nodes/media-feature-boolean.ts create mode 100644 packages/postcss-media-query-list-parser/src/nodes/media-feature-comparison.ts create mode 100644 packages/postcss-media-query-list-parser/src/nodes/media-feature-name.ts create mode 100644 packages/postcss-media-query-list-parser/src/nodes/media-feature-plain.ts create mode 100644 packages/postcss-media-query-list-parser/src/nodes/media-feature-range.ts create mode 100644 packages/postcss-media-query-list-parser/src/nodes/media-feature-value.ts create mode 100644 packages/postcss-media-query-list-parser/src/nodes/media-feature.ts create mode 100644 packages/postcss-media-query-list-parser/src/nodes/media-in-parens.ts create mode 100644 packages/postcss-media-query-list-parser/src/nodes/media-not.ts create mode 100644 packages/postcss-media-query-list-parser/src/nodes/media-or.ts create mode 100644 packages/postcss-media-query-list-parser/src/nodes/media-query-modifier.ts create mode 100644 packages/postcss-media-query-list-parser/src/nodes/media-query.ts create mode 100644 packages/postcss-media-query-list-parser/src/nodes/media-type.ts create mode 100644 packages/postcss-media-query-list-parser/src/parser/advance/advance.ts create mode 100644 packages/postcss-media-query-list-parser/src/parser/consume/consume-boolean.ts create mode 100644 packages/postcss-media-query-list-parser/src/parser/consume/consume-plain.ts create mode 100644 packages/postcss-media-query-list-parser/src/parser/consume/consume-value.ts create mode 100644 packages/postcss-media-query-list-parser/src/parser/parse.ts create mode 100644 packages/postcss-media-query-list-parser/test/test.mjs create mode 100644 packages/postcss-media-query-list-parser/tsconfig.json create mode 100644 packages/virtual-media/.gitignore create mode 100644 packages/virtual-media/.nvmrc create mode 100644 packages/virtual-media/CHANGELOG.md create mode 100644 packages/virtual-media/LICENSE.md create mode 100644 packages/virtual-media/README.md create mode 100644 packages/virtual-media/package.json create mode 100644 packages/virtual-media/src/index.ts create mode 100644 packages/virtual-media/src/range/add.ts create mode 100644 packages/virtual-media/src/range/compare.ts create mode 100644 packages/virtual-media/src/range/range.ts create mode 100644 packages/virtual-media/test/test.mjs create mode 100644 packages/virtual-media/tsconfig.json diff --git a/package-lock.json b/package-lock.json index 6651725c2..c93065d75 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1848,6 +1848,10 @@ "resolved": "plugins/postcss-is-pseudo-class", "link": true }, + "node_modules/@csstools/postcss-media-query-list-parser": { + "resolved": "packages/postcss-media-query-list-parser", + "link": true + }, "node_modules/@csstools/postcss-nested-calc": { "resolved": "plugins/postcss-nested-calc", "link": true @@ -1888,6 +1892,10 @@ "resolved": "packages/selector-specificity", "link": true }, + "node_modules/@csstools/virtual-media": { + "resolved": "packages/virtual-media", + "link": true + }, "node_modules/@eslint/eslintrc": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.2.tgz", @@ -6844,6 +6852,17 @@ "url": "https://opencollective.com/csstools" } }, + "packages/postcss-media-query-list-parser": { + "version": "1.0.0", + "license": "MIT", + "engines": { + "node": "^14 || ^16 || >=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + }, "packages/postcss-tape": { "name": "@csstools/postcss-tape", "version": "1.0.0", @@ -6880,6 +6899,17 @@ "postcss-selector-parser": "^6.0.10" } }, + "packages/virtual-media": { + "version": "1.0.0", + "license": "MIT", + "engines": { + "node": "^14 || ^16 || >=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + }, "plugin-packs/postcss-preset-env": { "version": "8.0.0-alpha.0", "license": "CC0-1.0", @@ -9007,6 +9037,9 @@ "puppeteer": "^18.0.0" } }, + "@csstools/postcss-media-query-list-parser": { + "version": "file:packages/postcss-media-query-list-parser" + }, "@csstools/postcss-nested-calc": { "version": "file:plugins/postcss-nested-calc", "requires": { @@ -9069,6 +9102,9 @@ "postcss-selector-parser": "^6.0.10" } }, + "@csstools/virtual-media": { + "version": "file:packages/virtual-media" + }, "@eslint/eslintrc": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.2.tgz", diff --git a/package.json b/package.json index e3228ae16..ededd4c7b 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,8 @@ "node": "^14 || ^16 || >=18" }, "workspaces": [ + "packages/css-tokenizer", + "packages/css-parser", "packages/*", "plugins/postcss-progressive-custom-properties", "plugins/*", diff --git a/packages/css-parser/.gitignore b/packages/css-parser/.gitignore new file mode 100644 index 000000000..7172b04f1 --- /dev/null +++ b/packages/css-parser/.gitignore @@ -0,0 +1,6 @@ +node_modules +package-lock.json +yarn.lock +*.result.css +*.result.css.map +dist/* diff --git a/packages/css-parser/.nvmrc b/packages/css-parser/.nvmrc new file mode 100644 index 000000000..f0b10f153 --- /dev/null +++ b/packages/css-parser/.nvmrc @@ -0,0 +1 @@ +v16.13.1 diff --git a/packages/css-parser/CHANGELOG.md b/packages/css-parser/CHANGELOG.md new file mode 100644 index 000000000..b0ff6b082 --- /dev/null +++ b/packages/css-parser/CHANGELOG.md @@ -0,0 +1,3 @@ +### 1.0.0 + +- Initial version diff --git a/packages/css-parser/LICENSE.md b/packages/css-parser/LICENSE.md new file mode 100644 index 000000000..af5411fa2 --- /dev/null +++ b/packages/css-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/css-parser/README.md b/packages/css-parser/README.md new file mode 100644 index 000000000..a9dc1b02e --- /dev/null +++ b/packages/css-parser/README.md @@ -0,0 +1,129 @@ +# CSS Parser + +[npm version][npm-url] +[Build Status][cli-url] +[Discord][discord] + +Implemented from : https://www.w3.org/TR/2021/CRD-css-syntax-3-20211224/ + +## Usage + +Add [CSS Parser] to your project: + +```bash +npm install postcss @csstools/css-parser --save-dev +``` + +```js +import { tokenizer, TokenType } from '@csstools/css-parser'; + +const myCSS = `@media only screen and (min-width: 768rem) { + .foo { + content: 'Some content!' !important; + } +} +`; + +const t = tokenizer({ + css: myCSS, +}); + +while (true) { + const token = t.nextToken(); + if (token[0] === TokenType.EOF) { + break; + } + + console.log(token); +} +``` + +### Options + +```ts +{ + commentsAreTokens?: false, + onParseError?: (error: ParserError) => void +} +``` + +#### `commentsAreTokens` + +Following the CSS specification comments are never returned by the tokenizer. +For many tools however it is desirable to be able to convert tokens back to a string. + +```js +import { tokenizer, TokenType } from '@csstools/css-tokenizer'; + +const t = tokenizer({ + css: `/* a comment */`, +}, { commentsAreTokens: true }); + +while (true) { + const token = t.nextToken(); + if (token[0] === TokenType.EOF) { + break; + } + + console.log(token); +} +``` + +logs : `['comment', '/* a comment */', , , undefined]` + + +#### `onParseError` + +The tokenizer is forgiving and won't stop when a parse error is encountered. +Parse errors also aren't tokens. + +To receive parsing error information you can set a callback. + +```js +import { tokenizer, TokenType } from '@csstools/css-tokenizer'; + +const t = tokenizer({ + css: '\\', +}, { onParseError: (err) => console.warn(err) }); + +while (true) { + const token = t.nextToken(); + if (token[0] === TokenType.EOF) { + break; + } +} +``` + +logs : + +```js +{ + message: 'Unexpected EOF while consuming an escaped code point.', + start: 0, + end: 0, + state: ['4.3.7. Consume an escaped code point', 'Unexpected EOF'], +} +``` + +Parser errors will try to inform you about the point in the tokenizer logic the error happened. +This tells you the kind of error. + +`start` and `end` are the location in your CSS source code. + +## Goals and non-goals + +Things this package aims to be: +- specification compliant CSS parser +- a reliable low level package to be used in CSS sub-grammars + +What it is not: +- opinionated +- fast +- small +- a replacement for PostCSS (PostCSS is fast and also an ecosystem) + +[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/css-parser + +[CSS Parser]: https://github.com/csstools/postcss-plugins/tree/main/packages/css-parser diff --git a/packages/css-parser/package.json b/packages/css-parser/package.json new file mode 100644 index 000000000..d495fdd38 --- /dev/null +++ b/packages/css-parser/package.json @@ -0,0 +1,68 @@ +{ + "name": "@csstools/css-parser", + "description": "Parse CSS", + "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" + ], + "dependencies": { + "@csstools/css-tokenizer": "^1.0.0" + }, + "scripts": { + "build": "rollup -c ../../rollup/default.js", + "clean": "node -e \"fs.rmSync('./dist', { recursive: true, force: true });\"", + "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": "npm run test:exports && node ./test/test.mjs", + "test:exports": "node ./test/_import.mjs && node ./test/_require.cjs" + }, + "homepage": "https://github.com/csstools/postcss-plugins/tree/main/packages/css-parser#readme", + "repository": { + "type": "git", + "url": "https://github.com/csstools/postcss-plugins.git", + "directory": "packages/css-parser" + }, + "bugs": "https://github.com/csstools/postcss-plugins/issues", + "keywords": [ + "css", + "parser" + ], + "volta": { + "extends": "../../package.json" + } +} diff --git a/packages/css-parser/src/consume/consume-component-block-function.ts b/packages/css-parser/src/consume/consume-component-block-function.ts new file mode 100644 index 000000000..e5d1dc408 --- /dev/null +++ b/packages/css-parser/src/consume/consume-component-block-function.ts @@ -0,0 +1,152 @@ +import { CSSToken, mirrorVariant, stringify, TokenType, isToken, TokenIdent, TokenFunction } from '@csstools/css-tokenizer'; + +export type ComponentValue = FunctionNode | SimpleBlockNode | CSSToken; + +export class ComponentValueNode { + type = 'component-value'; + + value: ComponentValue; + before: Array; + after: Array; + + constructor(value: ComponentValue, before: Array, after: Array) { + this.value = value; + this.before = before; + this.after = after; + } + + toString() { + if (isToken(this.value)) { + return stringify( + ...this.before, + this.value, + ...this.after, + ); + } + + return stringify(...this.before) + this.value.toString() + stringify(...this.after); + } +} + +// https://www.w3.org/TR/css-syntax-3/#consume-a-component-value +export function consumeComponentValue(tokens: Array): { advance: number, node: ComponentValue } { + for (let i = 0; i < tokens.length; i++) { + const token = tokens[i]; + if ( + token[0] === TokenType.OpenParen || + token[0] === TokenType.OpenCurly || + token[0] === TokenType.OpenSquare + ) { + const r = consumeSimpleBlock(tokens.slice(i)); + return { + advance: r.advance + i, + node: r.node, + }; + } + + if (token[0] === TokenType.Function) { + const r = consumeFunction(tokens.slice(i)); + return { + advance: r.advance + i, + node: r.node, + }; + } + + return { + advance: i, + node: token, + }; + } +} + +export class FunctionNode { + type = 'function'; + + name: TokenFunction; + value: Array; + after: Array; + + constructor(name: TokenFunction, value: Array, after: Array) { + this.name = name; + this.value = value; + this.after = after; + } + + get nameTokenValue(): string { + return this.name[4].value; + } + + toString() { + return stringify(this.name) + this.value.map((x) => x.toString()).join('') + stringify(...this.after); + } +} + +// https://www.w3.org/TR/css-syntax-3/#consume-function +export function consumeFunction(tokens: Array): { advance: number, node: FunctionNode } { + const valueList: Array = []; + let lastValueIndex = -1; + + for (let i = 1; i < tokens.length; i++) { + const token = tokens[i]; + + if (token[0] === TokenType.CloseParen) { + return { + advance: i, + node: new FunctionNode( + tokens[0] as TokenFunction, + valueList, + tokens.slice(lastValueIndex, i+1), + ), + }; + } + + const r = consumeComponentValue(tokens.slice(i)); + i += r.advance; + lastValueIndex = i; + valueList.push(r.node); + } + + throw new Error('Failed to parse'); +} + +export class SimpleBlockNode { + type = 'simple-block'; + + name: Array; + value: Array; + + constructor(name: Array, value: Array) { + this.name = name; + this.value = value; + } + + get nameIdentIndex(): number { + return this.name.findIndex((x) => { + return x[0] === TokenType.Ident; + }); + } + + toString() { + return stringify(...this.name) + this.value.map((x) => x.toString()).join(''); + } +} + +/** https://www.w3.org/TR/css-syntax-3/#consume-simple-block */ +export function consumeSimpleBlock(tokens: Array): { advance: number, node: SimpleBlockNode } { + const endingToken = mirrorVariant(tokens[0][0]); + if (!endingToken) { + throw new Error('Failed to parse'); + } + + for (let i = 1; i < tokens.length; i++) { + const token = tokens[i]; + + if (token[0] === endingToken) { + return i; + } + + i += consumeComponentValue(tokens.slice(i)); + } + + throw new Error('Failed to parse'); +} diff --git a/packages/css-parser/src/index.ts b/packages/css-parser/src/index.ts new file mode 100644 index 000000000..e69de29bb diff --git a/packages/css-parser/stryker.conf.json b/packages/css-parser/stryker.conf.json new file mode 100644 index 000000000..015ebbb73 --- /dev/null +++ b/packages/css-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/css-parser/test/_import.mjs b/packages/css-parser/test/_import.mjs new file mode 100644 index 000000000..c1c61bf2b --- /dev/null +++ b/packages/css-parser/test/_import.mjs @@ -0,0 +1,5 @@ +import { tokenizer } from '@csstools/css-tokenizer'; + +tokenizer({ + css: '.some { css: ""; }', +}); diff --git a/packages/css-parser/test/_require.cjs b/packages/css-parser/test/_require.cjs new file mode 100644 index 000000000..abc74a96e --- /dev/null +++ b/packages/css-parser/test/_require.cjs @@ -0,0 +1,5 @@ +const { tokenizer } = require('@csstools/css-tokenizer'); + +tokenizer({ + css: '.some { css: ""; }', +}); diff --git a/packages/css-parser/test/test.mjs b/packages/css-parser/test/test.mjs new file mode 100644 index 000000000..71d21591a --- /dev/null +++ b/packages/css-parser/test/test.mjs @@ -0,0 +1,13 @@ +// Reader +import './test-reader.mjs'; +// Code points +import './code-points/code-points.mjs'; +import './code-points/ranges.mjs'; +// Tokens +import './token/basic.mjs'; +import './token/comment.mjs'; +import './token/numeric.mjs'; +import './token/url.mjs'; +// Complex +import './complex/at-media-params.mjs'; +import './complex/parse-error.mjs'; diff --git a/packages/css-parser/tsconfig.json b/packages/css-parser/tsconfig.json new file mode 100644 index 000000000..e0d06239c --- /dev/null +++ b/packages/css-parser/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "dist", + "declarationDir": "." + }, + "include": ["./src/**/*"], + "exclude": ["dist"], +} diff --git a/packages/css-tokenizer/src/index.ts b/packages/css-tokenizer/src/index.ts index 54e28b122..992bdf3bc 100644 --- a/packages/css-tokenizer/src/index.ts +++ b/packages/css-tokenizer/src/index.ts @@ -1,6 +1,6 @@ export type { CSSToken } from './interfaces/token'; export { Reader } from './reader'; -export { TokenType, NumberType } from './interfaces/token'; +export { TokenType, NumberType, mirrorVariant, isToken } from './interfaces/token'; export { stringify } from './stringify'; export { tokenizer } from './tokenizer'; diff --git a/packages/css-tokenizer/src/interfaces/token.ts b/packages/css-tokenizer/src/interfaces/token.ts index 184be7ce9..90d259f01 100644 --- a/packages/css-tokenizer/src/interfaces/token.ts +++ b/packages/css-tokenizer/src/interfaces/token.ts @@ -132,3 +132,54 @@ export type Token = [ /** Extra data */ U, ] + +export function mirrorVariant(type: TokenType): TokenType|null { + switch (type) { + case TokenType.OpenParen: + return TokenType.CloseParen; + case TokenType.CloseParen: + return TokenType.OpenParen; + + case TokenType.OpenCurly: + return TokenType.CloseCurly; + case TokenType.CloseCurly: + return TokenType.OpenCurly; + + case TokenType.OpenSquare: + return TokenType.CloseSquare; + case TokenType.CloseSquare: + return TokenType.OpenSquare; + + default: + return null; + } +} + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export function isToken(x: any): x is CSSToken { + if (!Array.isArray(x)) { + return false; + } + + if (x.length < 4) { + return false; + } + + if (!(x[0] in TokenType)) { + return false; + } + + if (typeof x[1] !== 'string') { + return false; + } + + if (typeof x[2] !== 'number') { + return false; + } + + if (typeof x[3] !== 'number') { + return false; + } + + return true; +} diff --git a/packages/postcss-media-query-list-parser/.gitignore b/packages/postcss-media-query-list-parser/.gitignore new file mode 100644 index 000000000..7172b04f1 --- /dev/null +++ b/packages/postcss-media-query-list-parser/.gitignore @@ -0,0 +1,6 @@ +node_modules +package-lock.json +yarn.lock +*.result.css +*.result.css.map +dist/* diff --git a/packages/postcss-media-query-list-parser/.nvmrc b/packages/postcss-media-query-list-parser/.nvmrc new file mode 100644 index 000000000..f0b10f153 --- /dev/null +++ b/packages/postcss-media-query-list-parser/.nvmrc @@ -0,0 +1 @@ +v16.13.1 diff --git a/packages/postcss-media-query-list-parser/CHANGELOG.md b/packages/postcss-media-query-list-parser/CHANGELOG.md new file mode 100644 index 000000000..b0ff6b082 --- /dev/null +++ b/packages/postcss-media-query-list-parser/CHANGELOG.md @@ -0,0 +1,3 @@ +### 1.0.0 + +- Initial version diff --git a/packages/postcss-media-query-list-parser/LICENSE.md b/packages/postcss-media-query-list-parser/LICENSE.md new file mode 100644 index 000000000..af5411fa2 --- /dev/null +++ b/packages/postcss-media-query-list-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/postcss-media-query-list-parser/README.md b/packages/postcss-media-query-list-parser/README.md new file mode 100644 index 000000000..361096774 --- /dev/null +++ b/packages/postcss-media-query-list-parser/README.md @@ -0,0 +1 @@ +# TODO diff --git a/packages/postcss-media-query-list-parser/package.json b/packages/postcss-media-query-list-parser/package.json new file mode 100644 index 000000000..c1f53a38c --- /dev/null +++ b/packages/postcss-media-query-list-parser/package.json @@ -0,0 +1,63 @@ +{ + "name": "@csstools/postcss-media-query-list-parser", + "description": "Tokenize CSS", + "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" + ], + "scripts": { + "build": "rollup -c ../../rollup/default.js", + "clean": "node -e \"fs.rmSync('./dist', { recursive: true, force: true });\"", + "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", + "test": "node ./test/test.mjs" + }, + "homepage": "https://github.com/csstools/postcss-plugins/tree/main/packages/postcss-media-query-list-parser#readme", + "repository": { + "type": "git", + "url": "https://github.com/csstools/postcss-plugins.git", + "directory": "packages/postcss-media-query-list-parser" + }, + "bugs": "https://github.com/csstools/postcss-plugins/issues", + "keywords": [ + "css", + "tokenizer" + ], + "volta": { + "extends": "../../package.json" + } +} diff --git a/packages/postcss-media-query-list-parser/src/index.ts b/packages/postcss-media-query-list-parser/src/index.ts new file mode 100644 index 000000000..6c0dde8d4 --- /dev/null +++ b/packages/postcss-media-query-list-parser/src/index.ts @@ -0,0 +1 @@ +export { parse } from './parser/parse'; diff --git a/packages/postcss-media-query-list-parser/src/nodes/general-enclosed.ts b/packages/postcss-media-query-list-parser/src/nodes/general-enclosed.ts new file mode 100644 index 000000000..ef15f9f37 --- /dev/null +++ b/packages/postcss-media-query-list-parser/src/nodes/general-enclosed.ts @@ -0,0 +1,15 @@ +import { CSSToken, stringify } from '@csstools/css-tokenizer'; + +export class GeneralEnclosed { + type = 'general-enclosed'; + + raw: Array; + + constructor(raw: Array) { + this.raw = raw; + } + + toString() { + return stringify(...this.raw); + } +} diff --git a/packages/postcss-media-query-list-parser/src/nodes/media-and.ts b/packages/postcss-media-query-list-parser/src/nodes/media-and.ts new file mode 100644 index 000000000..c6fd5c87e --- /dev/null +++ b/packages/postcss-media-query-list-parser/src/nodes/media-and.ts @@ -0,0 +1,15 @@ +import { MediaInParens } from './media-in-parens'; + +export class MediaAnd { + type = 'media-and'; + + media: MediaInParens; + + constructor(media: MediaInParens) { + this.media = media; + } + + toString() { + return 'and' + this.media.toString(); + } +} diff --git a/packages/postcss-media-query-list-parser/src/nodes/media-condition-list.ts b/packages/postcss-media-query-list-parser/src/nodes/media-condition-list.ts new file mode 100644 index 000000000..563eebae3 --- /dev/null +++ b/packages/postcss-media-query-list-parser/src/nodes/media-condition-list.ts @@ -0,0 +1,45 @@ +import { MediaAnd } from './media-and'; +import { MediaInParens } from './media-in-parens'; +import { MediaOr } from './media-or'; + +export type MediaConditionList = MediaConditionListWithAnd | MediaConditionListWithOr; + +export class MediaConditionListWithAnd { + type = 'media-condition-list-and'; + + leading: MediaInParens; + list: Array; + + constructor(leading: MediaInParens, list: Array) { + this.leading = leading; + this.list = list; + } + + toString() { + if (this.list.length === 0) { + return this.leading.toString(); + } + + return this.leading.toString() + ' ' + this.list.map((x) => x.toString()).join(' '); + } +} + +export class MediaConditionListWithOr { + type = 'media-condition-list-or'; + + leading: MediaInParens; + list: Array; + + constructor(leading: MediaInParens, list: Array) { + this.leading = leading; + this.list = list; + } + + toString() { + if (this.list.length === 0) { + return this.leading.toString(); + } + + return this.leading.toString() + ' ' + this.list.map((x) => x.toString()).join(' '); + } +} diff --git a/packages/postcss-media-query-list-parser/src/nodes/media-condition-without-or-or.ts b/packages/postcss-media-query-list-parser/src/nodes/media-condition-without-or-or.ts new file mode 100644 index 000000000..8cb02ade0 --- /dev/null +++ b/packages/postcss-media-query-list-parser/src/nodes/media-condition-without-or-or.ts @@ -0,0 +1,16 @@ +import { MediaConditionListWithAnd } from './media-condition-list'; +import { MediaNot } from './media-not'; + +export class MediaConditionWithoutOr { + type = 'media-condition-without-or'; + + media: MediaNot | MediaConditionListWithAnd; + + constructor(media: MediaNot | MediaConditionListWithAnd) { + this.media = media; + } + + toString() { + return this.media.toString(); + } +} diff --git a/packages/postcss-media-query-list-parser/src/nodes/media-condition.ts b/packages/postcss-media-query-list-parser/src/nodes/media-condition.ts new file mode 100644 index 000000000..fa276ee92 --- /dev/null +++ b/packages/postcss-media-query-list-parser/src/nodes/media-condition.ts @@ -0,0 +1,16 @@ +import { MediaConditionListWithAnd, MediaConditionListWithOr } from './media-condition-list'; +import { MediaNot } from './media-not'; + +export class MediaCondition { + type = 'media-condition'; + + media: MediaNot | MediaConditionListWithAnd | MediaConditionListWithOr; + + constructor(media: MediaNot | MediaConditionListWithAnd | MediaConditionListWithOr) { + this.media = media; + } + + toString() { + return this.media.toString(); + } +} diff --git a/packages/postcss-media-query-list-parser/src/nodes/media-feature-boolean.ts b/packages/postcss-media-query-list-parser/src/nodes/media-feature-boolean.ts new file mode 100644 index 000000000..bb960da2a --- /dev/null +++ b/packages/postcss-media-query-list-parser/src/nodes/media-feature-boolean.ts @@ -0,0 +1,5 @@ +import { MediaFeatureName } from './media-feature-name'; + +export class MediaFeatureBoolean extends MediaFeatureName { + type = 'mf-boolean'; +} diff --git a/packages/postcss-media-query-list-parser/src/nodes/media-feature-comparison.ts b/packages/postcss-media-query-list-parser/src/nodes/media-feature-comparison.ts new file mode 100644 index 000000000..e172c73ef --- /dev/null +++ b/packages/postcss-media-query-list-parser/src/nodes/media-feature-comparison.ts @@ -0,0 +1,49 @@ +export enum MediaFeatureLT { + LT = '<', + LT_OR_EQ = '<=', +} + +export enum MediaFeatureGT { + GT = '>', + GT_OR_EQ = '>=', +} + +export enum MediaFeatureEQ { + EQ = '=', +} + +export type MediaFeatureComparison = MediaFeatureLT | MediaFeatureGT | MediaFeatureEQ + +export function invertComparison(operator: MediaFeatureComparison): MediaFeatureComparison { + switch (operator) { + case MediaFeatureEQ.EQ: + return MediaFeatureEQ.EQ; + case MediaFeatureLT.LT: + return MediaFeatureGT.GT; + case MediaFeatureLT.LT_OR_EQ: + return MediaFeatureGT.GT_OR_EQ; + case MediaFeatureGT.GT: + return MediaFeatureLT.LT; + case MediaFeatureGT.GT_OR_EQ: + return MediaFeatureLT.LT_OR_EQ; + default: + throw new Error('Unknown range syntax operator'); + } +} + +export function comparisonToString(operator: MediaFeatureComparison): string { + switch (operator) { + case MediaFeatureEQ.EQ: + return '='; + case MediaFeatureLT.LT: + return '<'; + case MediaFeatureLT.LT_OR_EQ: + return '<='; + case MediaFeatureGT.GT: + return '>'; + case MediaFeatureGT.GT_OR_EQ: + return '>='; + default: + throw new Error('Unknown range syntax operator'); + } +} diff --git a/packages/postcss-media-query-list-parser/src/nodes/media-feature-name.ts b/packages/postcss-media-query-list-parser/src/nodes/media-feature-name.ts new file mode 100644 index 000000000..86349b095 --- /dev/null +++ b/packages/postcss-media-query-list-parser/src/nodes/media-feature-name.ts @@ -0,0 +1,21 @@ +import { CSSToken, stringify, TokenType } from '@csstools/css-tokenizer'; + +export class MediaFeatureName { + type = 'mf-name'; + + tokens: Array; + + constructor(tokens: Array) { + this.tokens = tokens; + } + + get nameIndex(): number { + return this.tokens.findIndex((x) => { + return x[0] === TokenType.Ident; + }); + } + + toString() { + return stringify(...this.tokens); + } +} diff --git a/packages/postcss-media-query-list-parser/src/nodes/media-feature-plain.ts b/packages/postcss-media-query-list-parser/src/nodes/media-feature-plain.ts new file mode 100644 index 000000000..f8d898b14 --- /dev/null +++ b/packages/postcss-media-query-list-parser/src/nodes/media-feature-plain.ts @@ -0,0 +1,18 @@ +import { MediaFeatureName } from './media-feature-name'; +import { MediaFeatureValue } from './media-feature-value'; + +export class MediaFeaturePlain { + type = 'mf-plain'; + + name: MediaFeatureName; + value: MediaFeatureValue; + + constructor(name: MediaFeatureName, value: MediaFeatureValue) { + this.name = name; + this.value = value; + } + + toString() { + return this.name.toString() + ':' + this.value.toString(); + } +} diff --git a/packages/postcss-media-query-list-parser/src/nodes/media-feature-range.ts b/packages/postcss-media-query-list-parser/src/nodes/media-feature-range.ts new file mode 100644 index 000000000..a804d26df --- /dev/null +++ b/packages/postcss-media-query-list-parser/src/nodes/media-feature-range.ts @@ -0,0 +1,138 @@ +import { comparisonToString, MediaFeatureComparison, MediaFeatureGT, MediaFeatureLT } from './media-feature-comparison'; +import { MediaFeatureName } from './media-feature-name'; +import { MediaFeatureValue } from './media-feature-value'; + +export type MediaFeatureRange = MediaFeatureRangeNameValue | + MediaFeatureRangeValueName | + MediaFeatureRangeLowHigh | + MediaFeatureRangeHighLow; + +export class MediaFeatureRangeNameValue { + type = 'mf-range-name-value'; + + name: MediaFeatureName; + operator: MediaFeatureComparison; + value: MediaFeatureValue; + + constructor(name: MediaFeatureName, operator: MediaFeatureComparison, value: MediaFeatureValue) { + this.name = name; + this.operator = operator; + this.value = value; + } + + toString() { + let result = ''; + + result += this.name.toString(); + result += ' '; + + result += comparisonToString(this.operator); + + result += ' '; + result += this.value.toString(); + + return result; + } +} + +export class MediaFeatureRangeValueName { + type = 'mf-range-value-range'; + + name: MediaFeatureName; + operator: MediaFeatureComparison; + value: MediaFeatureValue; + + constructor(value: MediaFeatureValue, operator: MediaFeatureComparison, name: MediaFeatureName) { + this.name = name; + this.operator = operator; + this.value = value; + } + + toString() { + let result = ''; + + result += this.value.toString(); + result += ' '; + + result += comparisonToString(this.operator); + + result += ' '; + result += this.name.toString(); + + return result; + } +} + +export class MediaFeatureRangeLowHigh { + type = 'mf-range-low-high'; + + name: MediaFeatureName; + low: MediaFeatureValue; + lowOperator: MediaFeatureLT; + high: MediaFeatureValue; + highOperator: MediaFeatureLT; + + constructor(name: MediaFeatureName, low: MediaFeatureValue, lowOperator: MediaFeatureLT, high: MediaFeatureValue, highOperator: MediaFeatureLT) { + this.name = name; + this.low = low; + this.lowOperator = lowOperator; + this.high = high; + this.highOperator = highOperator; + } + + toString() { + let result = ''; + + result += this.low.toString(); + + result += ' '; + result += comparisonToString(this.lowOperator); + result += ' '; + + result += this.name.toString(); + + result += ' '; + result += comparisonToString(this.highOperator); + result += ' '; + + result += this.high.toString(); + return result; + } +} + +export class MediaFeatureRangeHighLow { + type = 'mf-range-high-low'; + + name: MediaFeatureName; + low: MediaFeatureValue; + lowOperator: MediaFeatureGT; + high: MediaFeatureValue; + highOperator: MediaFeatureGT; + + constructor(name: MediaFeatureName, high: MediaFeatureValue, highOperator: MediaFeatureGT, low: MediaFeatureValue, lowOperator: MediaFeatureGT) { + this.name = name; + this.low = low; + this.lowOperator = lowOperator; + this.high = high; + this.highOperator = highOperator; + } + + toString() { + let result = ''; + + result += this.high.toString(); + + result += ' '; + result += comparisonToString(this.highOperator); + result += ' '; + + result += this.name.toString(); + + result += ' '; + result += comparisonToString(this.lowOperator); + result += ' '; + + result += this.low.toString(); + return result; + } +} diff --git a/packages/postcss-media-query-list-parser/src/nodes/media-feature-value.ts b/packages/postcss-media-query-list-parser/src/nodes/media-feature-value.ts new file mode 100644 index 000000000..83302072c --- /dev/null +++ b/packages/postcss-media-query-list-parser/src/nodes/media-feature-value.ts @@ -0,0 +1,15 @@ +import { CSSToken, stringify} from '@csstools/css-tokenizer'; + +export class MediaFeatureValue { + type = 'mf-value'; + + tokens: Array; + + constructor(tokens: Array) { + this.tokens = tokens; + } + + toString() { + return stringify(...this.tokens); + } +} diff --git a/packages/postcss-media-query-list-parser/src/nodes/media-feature.ts b/packages/postcss-media-query-list-parser/src/nodes/media-feature.ts new file mode 100644 index 000000000..28e55bb18 --- /dev/null +++ b/packages/postcss-media-query-list-parser/src/nodes/media-feature.ts @@ -0,0 +1,17 @@ +import { MediaFeatureBoolean } from './media-feature-boolean'; +import { MediaFeaturePlain } from './media-feature-plain'; +import { MediaFeatureRange } from './media-feature-range'; + +export class MediaFeature { + type = 'media-feature'; + + feature: MediaFeaturePlain | MediaFeatureBoolean | MediaFeatureRange; + + constructor(feature: MediaFeaturePlain | MediaFeatureBoolean | MediaFeatureRange) { + this.feature = feature; + } + + toString() { + return '(' + this.feature.toString() + ')'; + } +} diff --git a/packages/postcss-media-query-list-parser/src/nodes/media-in-parens.ts b/packages/postcss-media-query-list-parser/src/nodes/media-in-parens.ts new file mode 100644 index 000000000..609b5210c --- /dev/null +++ b/packages/postcss-media-query-list-parser/src/nodes/media-in-parens.ts @@ -0,0 +1,29 @@ +import { GeneralEnclosed } from './general-enclosed'; +import { MediaCondition } from './media-condition'; +import { MediaFeature } from './media-feature'; + +export class MediaInParens { + type = 'media-in-parens'; + + media: MediaCondition | MediaFeature | GeneralEnclosed; + + constructor(media: MediaCondition | MediaFeature | GeneralEnclosed) { + this.media = media; + } + + toString() { + if (this.media.type === 'general-enclosed') { + return this.media.toString(); + } + + if (this.media.type === 'media-feature') { + return this.media.toString(); + } + + if (this.media.type === 'media-condition') { + return '(' + this.media.toString() + ')'; + } + + return this.media.toString(); + } +} diff --git a/packages/postcss-media-query-list-parser/src/nodes/media-not.ts b/packages/postcss-media-query-list-parser/src/nodes/media-not.ts new file mode 100644 index 000000000..c6b8d9b88 --- /dev/null +++ b/packages/postcss-media-query-list-parser/src/nodes/media-not.ts @@ -0,0 +1,15 @@ +import { MediaInParens } from './media-in-parens'; + +export class MediaNot { + type = 'media-not'; + + media: MediaInParens; + + constructor(media: MediaInParens) { + this.media = media; + } + + toString() { + return 'not ' + this.media.toString(); + } +} diff --git a/packages/postcss-media-query-list-parser/src/nodes/media-or.ts b/packages/postcss-media-query-list-parser/src/nodes/media-or.ts new file mode 100644 index 000000000..f2b6779d3 --- /dev/null +++ b/packages/postcss-media-query-list-parser/src/nodes/media-or.ts @@ -0,0 +1,15 @@ +import { MediaInParens } from './media-in-parens'; + +export class MediaOr { + type = 'media-or'; + + media: MediaInParens; + + constructor(media: MediaInParens) { + this.media = media; + } + + toString() { + return 'or ' + this.media.toString(); + } +} diff --git a/packages/postcss-media-query-list-parser/src/nodes/media-query-modifier.ts b/packages/postcss-media-query-list-parser/src/nodes/media-query-modifier.ts new file mode 100644 index 000000000..d30ae315d --- /dev/null +++ b/packages/postcss-media-query-list-parser/src/nodes/media-query-modifier.ts @@ -0,0 +1,4 @@ +export enum MediaQueryModifier { + Not = 'not', + Only = 'only' +} diff --git a/packages/postcss-media-query-list-parser/src/nodes/media-query.ts b/packages/postcss-media-query-list-parser/src/nodes/media-query.ts new file mode 100644 index 000000000..5ab50cfec --- /dev/null +++ b/packages/postcss-media-query-list-parser/src/nodes/media-query.ts @@ -0,0 +1,50 @@ +import { MediaCondition } from './media-condition'; +import { MediaConditionWithoutOr } from './media-condition-without-or-or'; +import { MediaQueryModifier } from './media-query-modifier'; +import { MediaType } from './media-type'; + +export type MediaQuery = MediaQueryWithType | MediaQueryWithoutType; + +export class MediaQueryWithType { + type = 'media-query-with-type'; + + modifier?: MediaQueryModifier; + mediaType: MediaType; + media?: MediaConditionWithoutOr; + + constructor(modifier: MediaQueryModifier | null, mediaType: MediaType, media: MediaConditionWithoutOr | null) { + this.modifier = modifier; + this.mediaType = mediaType; + this.media = media; + } + + toString() { + if (this.modifier && this.media) { + return `${this.modifier} ${this.mediaType} and ${this.media.toString()}`; + } + + if (this.modifier) { + return `${this.modifier} ${this.mediaType}`; + } + + if (this.media) { + return `${this.mediaType} and ${this.media.toString()}`; + } + + return this.mediaType; + } +} + +export class MediaQueryWithoutType { + type = 'media-query-without-type'; + + media: MediaCondition; + + constructor(media: MediaCondition) { + this.media = media; + } + + toString() { + return this.media.toString(); + } +} diff --git a/packages/postcss-media-query-list-parser/src/nodes/media-type.ts b/packages/postcss-media-query-list-parser/src/nodes/media-type.ts new file mode 100644 index 000000000..341a7e474 --- /dev/null +++ b/packages/postcss-media-query-list-parser/src/nodes/media-type.ts @@ -0,0 +1,22 @@ +export enum MediaType { + /** Always matches */ + All = 'all', + Print = 'print', + Screen = 'screen', + /** Never matches */ + Tty = 'tty', + /** Never matches */ + Tv = 'tv', + /** Never matches */ + Projection = 'projection', + /** Never matches */ + Handheld = 'handheld', + /** Never matches */ + Braille = 'braille', + /** Never matches */ + Embossed = 'embossed', + /** Never matches */ + Aural = 'aural', + /** Never matches */ + Speech = 'speech', +} diff --git a/packages/postcss-media-query-list-parser/src/parser/advance/advance.ts b/packages/postcss-media-query-list-parser/src/parser/advance/advance.ts new file mode 100644 index 000000000..b66dc51d4 --- /dev/null +++ b/packages/postcss-media-query-list-parser/src/parser/advance/advance.ts @@ -0,0 +1,58 @@ +import { CSSToken, mirrorVariant, TokenType } from '@csstools/css-tokenizer'; + +// https://www.w3.org/TR/css-syntax-3/#consume-a-component-value +export function advanceComponentValue(tokens: Array): number { + for (let i = 0; i < tokens.length; i++) { + const token = tokens[i]; + if ( + token[0] === TokenType.OpenParen || + token[0] === TokenType.OpenCurly || + token[0] === TokenType.OpenSquare + ) { + i += advanceSimpleBlock(tokens.slice(i)); + return i; + } + + if (token[0] === TokenType.Function) { + i += advanceFunction(tokens.slice(i)); + return i; + } + + return i; + } +} + +// https://www.w3.org/TR/css-syntax-3/#consume-function +export function advanceFunction(tokens: Array): number | null { + for (let i = 1; i < tokens.length; i++) { + const token = tokens[i]; + + if (token[0] === TokenType.CloseParen) { + return i; + } + + i += advanceComponentValue(tokens.slice(i)); + } + + throw new Error('Failed to parse'); +} + +/** https://www.w3.org/TR/css-syntax-3/#consume-simple-block */ +export function advanceSimpleBlock(tokens: Array): number | null { + const endingToken = mirrorVariant(tokens[0][0]); + if (!endingToken) { + throw new Error('Failed to parse'); + } + + for (let i = 1; i < tokens.length; i++) { + const token = tokens[i]; + + if (token[0] === endingToken) { + return i; + } + + i += advanceComponentValue(tokens.slice(i)); + } + + throw new Error('Failed to parse'); +} diff --git a/packages/postcss-media-query-list-parser/src/parser/consume/consume-boolean.ts b/packages/postcss-media-query-list-parser/src/parser/consume/consume-boolean.ts new file mode 100644 index 000000000..fc7081d5d --- /dev/null +++ b/packages/postcss-media-query-list-parser/src/parser/consume/consume-boolean.ts @@ -0,0 +1,46 @@ +import { CSSToken, TokenIdent, TokenType } from '@csstools/css-tokenizer'; +import { MediaFeatureBoolean } from '../../nodes/media-feature-boolean'; + +export function consumeBoolean(tokens: Array): { node: MediaFeatureBoolean, tokens: Array } | null { + let ident : TokenIdent|null = null; + + for (let i = 0; i < tokens.length; i++) { + const token = tokens[i]; + + if (i === 0) { + if (token[0] === TokenType.OpenParen) { + continue; + } + + return null; + } + + if (token[0] === TokenType.Comment || token[0] === TokenType.Whitespace) { + continue; + } + + if (token[0] === TokenType.Ident) { + if (!ident) { + ident = token as TokenIdent; + continue; + } + + return null; + } + + if (token[0] === TokenType.CloseParen) { + if (ident) { + const node = new MediaFeatureBoolean(tokens.slice(0, i + 1)); + + return { + node: node, + tokens: tokens.slice(i + 1), + }; + } + + return null; + } + + return null; + } +} diff --git a/packages/postcss-media-query-list-parser/src/parser/consume/consume-plain.ts b/packages/postcss-media-query-list-parser/src/parser/consume/consume-plain.ts new file mode 100644 index 000000000..3a84cd380 --- /dev/null +++ b/packages/postcss-media-query-list-parser/src/parser/consume/consume-plain.ts @@ -0,0 +1,44 @@ +import { CSSToken, TokenIdent, TokenType } from '@csstools/css-tokenizer'; +import { MediaFeatureName } from '../../nodes/media-feature-name'; +import { MediaFeaturePlain } from '../../nodes/media-feature-plain'; +import { consumeValue } from './consume-value'; + +export function consumePlain(tokens: Array): { node: MediaFeaturePlain, tokens: Array } | null { + let name: MediaFeatureName | null = null; + const value: MediaFeatureValue | null = null; + + for (let i = 0; i < tokens.length; i++) { + const token = tokens[i]; + + if (i === 0) { + if (token[0] === TokenType.OpenParen) { + continue; + } else { + return null; + } + } + + if (token[0] === TokenType.CloseParen) { + continue; + } + + if (token[0] === TokenType.Comment || token[0] === TokenType.Whitespace) { + continue; + } + + if (token[0] === TokenType.Ident) { + if (!name) { + name = new MediaFeatureName(tokens.slice(0, i + 1)); + continue; + } else { + return null; + } + } + + if (token[0] === TokenType.Delim && token[1] === ':' && name) { + const value = consumeValue(tokens.slice(i + 1)); + } + + return null; + } +} diff --git a/packages/postcss-media-query-list-parser/src/parser/consume/consume-value.ts b/packages/postcss-media-query-list-parser/src/parser/consume/consume-value.ts new file mode 100644 index 000000000..6d1b21b88 --- /dev/null +++ b/packages/postcss-media-query-list-parser/src/parser/consume/consume-value.ts @@ -0,0 +1,12 @@ +import { CSSToken } from '@csstools/css-tokenizer'; +import { MediaFeatureValue } from '../../nodes/media-feature-value'; +import { advanceComponentValue } from '../advance/advance'; + +export function consumeValue(tokens: Array): { node: MediaFeatureValue, tokens: Array } | null { + const result = advanceComponentValue(tokens); + + return { + node: new MediaFeatureValue(tokens.slice(0, result + 1)), + tokens: tokens.slice(result + 1), + }; +} diff --git a/packages/postcss-media-query-list-parser/src/parser/parse.ts b/packages/postcss-media-query-list-parser/src/parser/parse.ts new file mode 100644 index 000000000..8283ae072 --- /dev/null +++ b/packages/postcss-media-query-list-parser/src/parser/parse.ts @@ -0,0 +1,97 @@ +import { CSSToken, TokenType, tokenizer } from '@csstools/css-tokenizer'; +import { MediaFeatureBoolean } from '../nodes/media-feature-boolean'; +import { advanceSimpleBlock } from './advance/advance'; +import { consumeBoolean } from './consume/consume-boolean'; + +type Tokenizer = { + nextToken: () => CSSToken | undefined, + endOfFile: () => boolean, +} + +export function parse(source: string) { + const t = tokenizer({ css: source }, { + commentsAreTokens: true, + onParseError: (err) => { + console.warn(err); + throw new Error(`Unable to parse "${source}"`); + }, + }); + + const tokenBuffer = []; + while (!t.endOfFile()) { + tokenBuffer.push(t.nextToken()); + } + + // console.log(tokenBuffer); + + const result = consumeBoolean(tokenBuffer); + const remainder = result.tokens; + const node = result.node; + const tokenSlice = node.tokens; + + console.log(tokenSlice); + console.log(node.nameIndex); + console.log(node.tokens[node.nameIndex][4].value); + + console.log(remainder); +} + +// function consumeMediaQuery(t: Tokenizer) { +// let token = t.nextToken(); +// if (t.endOfFile()) { +// return; +// } + +// while (token[0] === TokenType.Whitespace || token[0] === TokenType.Comment) { +// token = t.nextToken(); +// if (t.endOfFile()) { +// return; +// } +// } + +// if (token[0] === TokenType.OpenParen) { +// return consumeMediaQueryWithoutType(t); +// } + +// if (token[0] !== TokenType.Ident) { +// return; +// } + +// if (token[0] === TokenType.Ident && token[4].value.toLowerCase() === 'only') { +// return consumeMediaQueryWithType(t); +// } + +// if (token[0] === TokenType.Ident && token[4].value.toLowerCase() === 'not') { +// token = t.nextToken(); +// if (t.endOfFile()) { +// return; +// } + +// while (token[0] === TokenType.Whitespace || token[0] === TokenType.Comment) { +// token = t.nextToken(); +// if (t.endOfFile()) { +// return; +// } +// } + +// if (token[0] === TokenType.Comma) { +// return; +// } + +// if (token[0] === TokenType.OpenParen) { +// return consumeMediaQueryWithoutType(t); +// } + +// return consumeMediaQueryWithType(t); +// } + +// const modifier = token[] +// } + +// function consumeMediaQueryWithoutType(t: Tokenizer) { + +// } + +// function consumeMediaQueryWithType(t: Tokenizer) { + +// } diff --git a/packages/postcss-media-query-list-parser/test/test.mjs b/packages/postcss-media-query-list-parser/test/test.mjs new file mode 100644 index 000000000..44a4b335d --- /dev/null +++ b/packages/postcss-media-query-list-parser/test/test.mjs @@ -0,0 +1,3 @@ +import { parse } from '@csstools/postcss-media-query-list-parser'; + +parse('(/* a comment */foo ) something else'); diff --git a/packages/postcss-media-query-list-parser/tsconfig.json b/packages/postcss-media-query-list-parser/tsconfig.json new file mode 100644 index 000000000..e0d06239c --- /dev/null +++ b/packages/postcss-media-query-list-parser/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "dist", + "declarationDir": "." + }, + "include": ["./src/**/*"], + "exclude": ["dist"], +} diff --git a/packages/virtual-media/.gitignore b/packages/virtual-media/.gitignore new file mode 100644 index 000000000..7172b04f1 --- /dev/null +++ b/packages/virtual-media/.gitignore @@ -0,0 +1,6 @@ +node_modules +package-lock.json +yarn.lock +*.result.css +*.result.css.map +dist/* diff --git a/packages/virtual-media/.nvmrc b/packages/virtual-media/.nvmrc new file mode 100644 index 000000000..f0b10f153 --- /dev/null +++ b/packages/virtual-media/.nvmrc @@ -0,0 +1 @@ +v16.13.1 diff --git a/packages/virtual-media/CHANGELOG.md b/packages/virtual-media/CHANGELOG.md new file mode 100644 index 000000000..b0ff6b082 --- /dev/null +++ b/packages/virtual-media/CHANGELOG.md @@ -0,0 +1,3 @@ +### 1.0.0 + +- Initial version diff --git a/packages/virtual-media/LICENSE.md b/packages/virtual-media/LICENSE.md new file mode 100644 index 000000000..af5411fa2 --- /dev/null +++ b/packages/virtual-media/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/virtual-media/README.md b/packages/virtual-media/README.md new file mode 100644 index 000000000..361096774 --- /dev/null +++ b/packages/virtual-media/README.md @@ -0,0 +1 @@ +# TODO diff --git a/packages/virtual-media/package.json b/packages/virtual-media/package.json new file mode 100644 index 000000000..84e6aa12d --- /dev/null +++ b/packages/virtual-media/package.json @@ -0,0 +1,63 @@ +{ + "name": "@csstools/virtual-media", + "description": "Virtualized media for media queries.", + "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" + ], + "scripts": { + "build": "rollup -c ../../rollup/default.js", + "clean": "node -e \"fs.rmSync('./dist', { recursive: true, force: true });\"", + "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", + "test": "node ./test/test.mjs" + }, + "homepage": "https://github.com/csstools/postcss-plugins/tree/main/packages/virtual-media#readme", + "repository": { + "type": "git", + "url": "https://github.com/csstools/postcss-plugins.git", + "directory": "packages/virtual-media" + }, + "bugs": "https://github.com/csstools/postcss-plugins/issues", + "keywords": [ + "css", + "tokenizer" + ], + "volta": { + "extends": "../../package.json" + } +} diff --git a/packages/virtual-media/src/index.ts b/packages/virtual-media/src/index.ts new file mode 100644 index 000000000..e2a94869a --- /dev/null +++ b/packages/virtual-media/src/index.ts @@ -0,0 +1,188 @@ +import { addRange } from './range/add'; +import { Range } from './range/range'; + +export type Unknown = 'unknown'; +export type Impossible = 'impossible'; + +export enum Operator { + LT = '<', + LT_OR_EQ = '<=', + GT = '>', + GT_OR_EQ = '>=', + EQ = '=', +} + +const fraction = 1 / Math.pow(2, 32); + +export class VirtualMedia { + #impossibleMedia = false; + + #types = new Set([ + 'print', + 'screen', + ]); + + mustMatchType(type: string) { + if (type.toLowerCase() === 'all') { + return; + } + + if (!this.#types.has(type.toLowerCase())) { + this.#types.clear(); + return; + } + + this.#types.clear(); + this.#types.add(type.toLowerCase()); + } + + mustNotMatchType(type: string) { + if (type.toLowerCase() === 'all') { + this.#types.clear(); + return; + } + + this.#types.delete(type.toLowerCase()); + } + + mustMatchWidth(low: number, lowOperator: Operator, high: number, highOperator: Operator) { + if (this.#impossibleMedia) { + return; + } + + const range = { + low: low, + high: high, + }; + + switch (lowOperator) { + case Operator.LT: + range.low = range.low - fraction; + break; + case Operator.GT: + range.low = range.low + fraction; + break; + } + + switch (highOperator) { + case Operator.LT: + range.high = range.high - fraction; + break; + case Operator.GT: + range.high = range.high + fraction; + break; + } + + this.#width = addRange(this.#width, { + low: range.low, + high: range.high, + }); + + if (this.#width.length === 0) { + this.#impossibleMedia = true; + } + } + + #width: Array> = [ + { + low: Number.MIN_SAFE_INTEGER, + high: Number.MAX_SAFE_INTEGER, + }, + ]; + + /** + * https://www.w3.org/TR/mediaqueries-5/#width + * + */ + get width(): number | Unknown | Impossible { + if (this.#impossibleMedia) { + return 'impossible'; + } + + if (this.#width.length !== 1) { + return 'unknown'; + } + + if (this.#width[0].low !== this.#width[0].high) { + return 'unknown'; + } + + return this.#width[0].low; + } + + #height: Array> = [ + { + low: Number.MIN_SAFE_INTEGER, + high: Number.MAX_SAFE_INTEGER, + }, + ]; + + /** + * https://www.w3.org/TR/mediaqueries-5/#height + * + */ + get height(): number | Unknown | Impossible { + if (this.#impossibleMedia) { + return 'impossible'; + } + + if (this.#height.length !== 1) { + return 'unknown'; + } + + if (this.#height[0].low !== this.#height[0].high) { + return 'unknown'; + } + + return this.#height[0].low; + } + + #aspectRatio: Range<{ + /** dividend / divisor */ + dividend: number, + /** dividend / divisor */ + divisor: number + }> | Unknown = 'unknown'; + + /** + * https://www.w3.org/TR/mediaqueries-5/#aspect-ratio + * + */ + get aspectRatio() { + if (this.#impossibleMedia) { + return 'unknown'; + } + + if (this.#aspectRatio === 'unknown') { + const height = this.height; + const width = this.width; + if (height === 'impossible' || width === 'impossible') { + return 'impossible'; + } + + if (height !== 'unknown' && width !== 'unknown') { + return width / height; + } + + return 'unknown'; + } + + if (this.#aspectRatio.low.dividend !== this.#aspectRatio.high.dividend) { + return 'unknown'; + } + + if (this.#aspectRatio.low.divisor !== this.#aspectRatio.high.divisor) { + return 'unknown'; + } + + if (this.#aspectRatio.low.dividend === 0) { + return 'unknown'; + } + + if (this.#aspectRatio.low.divisor === 0) { + return 'unknown'; + } + + return this.#aspectRatio.low.dividend / this.#aspectRatio.low.divisor; + } +} diff --git a/packages/virtual-media/src/range/add.ts b/packages/virtual-media/src/range/add.ts new file mode 100644 index 000000000..3b0688c89 --- /dev/null +++ b/packages/virtual-media/src/range/add.ts @@ -0,0 +1,67 @@ +import { compare } from './compare'; +import { Range } from './range'; + +export function addRange(existingRanges: Array>, add: Range): Array> { + if (add.low > add.high) { + throw new Error('Inversed range ' + JSON.stringify(add)); + } + + const updated: Array> = []; + + for (let i = 0; i < existingRanges.length; i++) { + const existingRange = existingRanges[i]; + + // The "add" spans an equal range as is currently allowed. + // Return the current part of existing ranges. + if (compare(add.low, existingRange.low) === 0 && compare(existingRange.high, add.high) === 0) { + return [ + existingRange, + ]; + } + + // The "add" spans a range without any overlap with the current part. + // Continue to the next part. + if (compare(add.low, existingRange.high) > 0) { + continue; + } + + // The "add" spans a range without any overlap with the current part. + // Continue to the next part. + if (compare(add.high, existingRange.low) < 0) { + continue; + } + + // The "add" spans a smaller range, but is fully enclosed withing the current range. + // Return the part to add. + if (compare(add.low, existingRange.low) > 0 && compare(existingRange.high, add.high) > 0) { + return [ + add, + ]; + } + + // The "add" spans a larger range than is currently allowed, but it fully encloses the current range. + // Add the current part of the existing ranges to the updated slice. + if (compare(add.low, existingRange.low) < 0 && compare(existingRange.high, add.high) < 0) { + updated.push(existingRange); + continue; + } + + if (compare(add.low, existingRange.low) > 0) { + updated.push({ + low: add.low, + high: existingRange.high, + }); + continue; + } + + if (compare(add.high, existingRange.high) < 0) { + updated.push({ + low: existingRange.low, + high: add.high, + }); + continue; + } + } + + return updated; +} diff --git a/packages/virtual-media/src/range/compare.ts b/packages/virtual-media/src/range/compare.ts new file mode 100644 index 000000000..cb678e037 --- /dev/null +++ b/packages/virtual-media/src/range/compare.ts @@ -0,0 +1,33 @@ +export function compare(a: T, b: T): -1 | 0 | 1 { + if ((typeof a) !== (typeof b)) { + return 0; + } + + if ((typeof a === 'number')) { + const r = a - (b as number); + if (r < 0) { + return -1; + } + + if (r > 0) { + return 1; + } + + return 0; + } + + if ((typeof a === 'string')) { + const r = a.localeCompare(b as string); + if (r < 0) { + return -1; + } + + if (r > 0) { + return 1; + } + + return 0; + } + + return 0; +} diff --git a/packages/virtual-media/src/range/range.ts b/packages/virtual-media/src/range/range.ts new file mode 100644 index 000000000..5eeae7c75 --- /dev/null +++ b/packages/virtual-media/src/range/range.ts @@ -0,0 +1,4 @@ +export type Range = { + low: T, + high: T +}; diff --git a/packages/virtual-media/test/test.mjs b/packages/virtual-media/test/test.mjs new file mode 100644 index 000000000..1832b1a17 --- /dev/null +++ b/packages/virtual-media/test/test.mjs @@ -0,0 +1,28 @@ +import { VirtualMedia, Operator } from '@csstools/virtual-media'; + +// What with units? +// Maybe a map of facts per property +// { px: { low, high }, rem: { low, high } } +// +// Or a map of VirtualMedia and a way to share unitless properties? +// +// How to deal with "not (width: 300px)" + +{ + const media = new VirtualMedia(); + console.log(media.width); + + media.mustMatchWidth(10, Operator.GT, 100, Operator.LT); + console.log(media.width); + + media.mustMatchWidth(1000, Operator.GT, 10000, Operator.LT); + console.log(media.width); +} + +{ + const media = new VirtualMedia(); + console.log(media.width); + + media.mustMatchWidth(10, Operator.EQ, 10, Operator.EQ); + console.log(media.width); +} diff --git a/packages/virtual-media/tsconfig.json b/packages/virtual-media/tsconfig.json new file mode 100644 index 000000000..e0d06239c --- /dev/null +++ b/packages/virtual-media/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "dist", + "declarationDir": "." + }, + "include": ["./src/**/*"], + "exclude": ["dist"], +} From 412d6bee368d1613519acc6d4eabb65eab43a392 Mon Sep 17 00:00:00 2001 From: Romain Menke Date: Mon, 17 Oct 2022 19:27:03 +0200 Subject: [PATCH 02/35] wip --- package-lock.json | 88 +++- package.json | 2 +- .../.gitignore | 0 .../.nvmrc | 0 .../CHANGELOG.md | 0 .../LICENSE.md | 0 packages/css-parser-algorithms/README.md | 102 ++++ .../package.json | 6 +- .../consume-component-block-function.ts | 434 ++++++++++++++++++ packages/css-parser-algorithms/src/index.ts | 4 + .../src/interfaces/context.ts | 5 + .../src/interfaces/error.ts | 6 + ...omma-separated-list-of-component-values.ts | 60 +++ .../src/parse/parse-component-value.ts | 41 ++ .../parse/parse-list-of-component-values.ts | 41 ++ .../stryker.conf.json | 0 .../css-parser-algorithms/test/_import.mjs | 11 + .../css-parser-algorithms/test/_require.cjs | 11 + .../css-parser-algorithms/test/consume.mjs | 121 +++++ ...omma-separated-list-of-component-value.mjs | 38 ++ .../test/parse-component-value.mjs | 32 ++ .../test/parse-list-of-component-value.mjs | 33 ++ packages/css-parser-algorithms/test/parse.mjs | 3 + packages/css-parser-algorithms/test/test.mjs | 3 + .../test/util/collect-tokens.mjs | 11 + .../tsconfig.json | 0 packages/css-parser/README.md | 129 ------ .../consume-component-block-function.ts | 152 ------ packages/css-parser/src/index.ts | 0 packages/css-parser/test/_import.mjs | 5 - packages/css-parser/test/_require.cjs | 5 - packages/css-parser/test/test.mjs | 13 - packages/css-tokenizer/src/index.ts | 2 +- .../css-tokenizer/src/interfaces/token.ts | 2 +- .../.gitignore | 0 .../.nvmrc | 0 .../CHANGELOG.md | 0 .../LICENSE.md | 0 .../README.md | 0 .../package.json | 10 +- .../src/index.ts | 0 .../src/nodes/general-enclosed.ts | 38 ++ .../src/nodes/media-and.ts | 0 .../src/nodes/media-condition-list.ts | 0 .../nodes/media-condition-without-or-or.ts | 0 .../src/nodes/media-condition.ts | 0 .../src/nodes/media-feature-boolean.ts | 0 .../src/nodes/media-feature-comparison.ts | 76 +++ .../src/nodes/media-feature-name.ts | 28 ++ .../src/nodes/media-feature-plain.ts | 38 ++ .../src/nodes/media-feature-range.ts | 214 +++++++++ .../src/nodes/media-feature-value.ts | 38 ++ .../src/nodes/media-feature.ts | 10 +- .../src/nodes/media-in-parens.ts | 73 +++ .../src/nodes/media-not.ts | 0 .../src/nodes/media-or.ts | 0 .../src/nodes/media-query-modifier.ts | 22 + .../src/nodes/media-query.ts | 0 .../src/nodes/media-type.ts | 59 +++ .../src/parser/consume/consume-boolean.ts | 0 .../src/parser/consume/consume-plain.ts | 0 .../src/parser/consume/consume-value.ts | 16 + .../src/parser/parse.ts | 29 ++ .../media-query-list-parser/test/test.mjs | 12 + .../tsconfig.json | 0 .../src/nodes/general-enclosed.ts | 15 - .../src/nodes/media-feature-comparison.ts | 49 -- .../src/nodes/media-feature-name.ts | 21 - .../src/nodes/media-feature-plain.ts | 18 - .../src/nodes/media-feature-range.ts | 138 ------ .../src/nodes/media-feature-value.ts | 15 - .../src/nodes/media-in-parens.ts | 29 -- .../src/nodes/media-query-modifier.ts | 4 - .../src/nodes/media-type.ts | 22 - .../src/parser/advance/advance.ts | 58 --- .../src/parser/consume/consume-value.ts | 12 - .../src/parser/parse.ts | 97 ---- .../test/test.mjs | 3 - packages/virtual-media/.gitignore | 6 - packages/virtual-media/.nvmrc | 1 - packages/virtual-media/CHANGELOG.md | 3 - packages/virtual-media/LICENSE.md | 20 - packages/virtual-media/README.md | 1 - packages/virtual-media/package.json | 63 --- packages/virtual-media/src/index.ts | 188 -------- packages/virtual-media/src/range/add.ts | 67 --- packages/virtual-media/src/range/compare.ts | 33 -- packages/virtual-media/src/range/range.ts | 4 - packages/virtual-media/test/test.mjs | 28 -- packages/virtual-media/tsconfig.json | 9 - rollup/presets/package-typescript.js | 4 +- 91 files changed, 1697 insertions(+), 1234 deletions(-) rename packages/{css-parser => css-parser-algorithms}/.gitignore (100%) rename packages/{css-parser => css-parser-algorithms}/.nvmrc (100%) rename packages/{css-parser => css-parser-algorithms}/CHANGELOG.md (100%) rename packages/{css-parser => css-parser-algorithms}/LICENSE.md (100%) create mode 100644 packages/css-parser-algorithms/README.md rename packages/{css-parser => css-parser-algorithms}/package.json (92%) create mode 100644 packages/css-parser-algorithms/src/consume/consume-component-block-function.ts create mode 100644 packages/css-parser-algorithms/src/index.ts create mode 100644 packages/css-parser-algorithms/src/interfaces/context.ts create mode 100644 packages/css-parser-algorithms/src/interfaces/error.ts create mode 100644 packages/css-parser-algorithms/src/parse/parse-comma-separated-list-of-component-values.ts create mode 100644 packages/css-parser-algorithms/src/parse/parse-component-value.ts create mode 100644 packages/css-parser-algorithms/src/parse/parse-list-of-component-values.ts rename packages/{css-parser => css-parser-algorithms}/stryker.conf.json (100%) create mode 100644 packages/css-parser-algorithms/test/_import.mjs create mode 100644 packages/css-parser-algorithms/test/_require.cjs create mode 100644 packages/css-parser-algorithms/test/consume.mjs create mode 100644 packages/css-parser-algorithms/test/parse-comma-separated-list-of-component-value.mjs create mode 100644 packages/css-parser-algorithms/test/parse-component-value.mjs create mode 100644 packages/css-parser-algorithms/test/parse-list-of-component-value.mjs create mode 100644 packages/css-parser-algorithms/test/parse.mjs create mode 100644 packages/css-parser-algorithms/test/test.mjs create mode 100644 packages/css-parser-algorithms/test/util/collect-tokens.mjs rename packages/{css-parser => css-parser-algorithms}/tsconfig.json (100%) delete mode 100644 packages/css-parser/README.md delete mode 100644 packages/css-parser/src/consume/consume-component-block-function.ts delete mode 100644 packages/css-parser/src/index.ts delete mode 100644 packages/css-parser/test/_import.mjs delete mode 100644 packages/css-parser/test/_require.cjs delete mode 100644 packages/css-parser/test/test.mjs rename packages/{postcss-media-query-list-parser => media-query-list-parser}/.gitignore (100%) rename packages/{postcss-media-query-list-parser => media-query-list-parser}/.nvmrc (100%) rename packages/{postcss-media-query-list-parser => media-query-list-parser}/CHANGELOG.md (100%) rename packages/{postcss-media-query-list-parser => media-query-list-parser}/LICENSE.md (100%) rename packages/{postcss-media-query-list-parser => media-query-list-parser}/README.md (100%) rename packages/{postcss-media-query-list-parser => media-query-list-parser}/package.json (85%) rename packages/{postcss-media-query-list-parser => media-query-list-parser}/src/index.ts (100%) create mode 100644 packages/media-query-list-parser/src/nodes/general-enclosed.ts rename packages/{postcss-media-query-list-parser => media-query-list-parser}/src/nodes/media-and.ts (100%) rename packages/{postcss-media-query-list-parser => media-query-list-parser}/src/nodes/media-condition-list.ts (100%) rename packages/{postcss-media-query-list-parser => media-query-list-parser}/src/nodes/media-condition-without-or-or.ts (100%) rename packages/{postcss-media-query-list-parser => media-query-list-parser}/src/nodes/media-condition.ts (100%) rename packages/{postcss-media-query-list-parser => media-query-list-parser}/src/nodes/media-feature-boolean.ts (100%) create mode 100644 packages/media-query-list-parser/src/nodes/media-feature-comparison.ts create mode 100644 packages/media-query-list-parser/src/nodes/media-feature-name.ts create mode 100644 packages/media-query-list-parser/src/nodes/media-feature-plain.ts create mode 100644 packages/media-query-list-parser/src/nodes/media-feature-range.ts create mode 100644 packages/media-query-list-parser/src/nodes/media-feature-value.ts rename packages/{postcss-media-query-list-parser => media-query-list-parser}/src/nodes/media-feature.ts (56%) create mode 100644 packages/media-query-list-parser/src/nodes/media-in-parens.ts rename packages/{postcss-media-query-list-parser => media-query-list-parser}/src/nodes/media-not.ts (100%) rename packages/{postcss-media-query-list-parser => media-query-list-parser}/src/nodes/media-or.ts (100%) create mode 100644 packages/media-query-list-parser/src/nodes/media-query-modifier.ts rename packages/{postcss-media-query-list-parser => media-query-list-parser}/src/nodes/media-query.ts (100%) create mode 100644 packages/media-query-list-parser/src/nodes/media-type.ts rename packages/{postcss-media-query-list-parser => media-query-list-parser}/src/parser/consume/consume-boolean.ts (100%) rename packages/{postcss-media-query-list-parser => media-query-list-parser}/src/parser/consume/consume-plain.ts (100%) create mode 100644 packages/media-query-list-parser/src/parser/consume/consume-value.ts create mode 100644 packages/media-query-list-parser/src/parser/parse.ts create mode 100644 packages/media-query-list-parser/test/test.mjs rename packages/{postcss-media-query-list-parser => media-query-list-parser}/tsconfig.json (100%) delete mode 100644 packages/postcss-media-query-list-parser/src/nodes/general-enclosed.ts delete mode 100644 packages/postcss-media-query-list-parser/src/nodes/media-feature-comparison.ts delete mode 100644 packages/postcss-media-query-list-parser/src/nodes/media-feature-name.ts delete mode 100644 packages/postcss-media-query-list-parser/src/nodes/media-feature-plain.ts delete mode 100644 packages/postcss-media-query-list-parser/src/nodes/media-feature-range.ts delete mode 100644 packages/postcss-media-query-list-parser/src/nodes/media-feature-value.ts delete mode 100644 packages/postcss-media-query-list-parser/src/nodes/media-in-parens.ts delete mode 100644 packages/postcss-media-query-list-parser/src/nodes/media-query-modifier.ts delete mode 100644 packages/postcss-media-query-list-parser/src/nodes/media-type.ts delete mode 100644 packages/postcss-media-query-list-parser/src/parser/advance/advance.ts delete mode 100644 packages/postcss-media-query-list-parser/src/parser/consume/consume-value.ts delete mode 100644 packages/postcss-media-query-list-parser/src/parser/parse.ts delete mode 100644 packages/postcss-media-query-list-parser/test/test.mjs delete mode 100644 packages/virtual-media/.gitignore delete mode 100644 packages/virtual-media/.nvmrc delete mode 100644 packages/virtual-media/CHANGELOG.md delete mode 100644 packages/virtual-media/LICENSE.md delete mode 100644 packages/virtual-media/README.md delete mode 100644 packages/virtual-media/package.json delete mode 100644 packages/virtual-media/src/index.ts delete mode 100644 packages/virtual-media/src/range/add.ts delete mode 100644 packages/virtual-media/src/range/compare.ts delete mode 100644 packages/virtual-media/src/range/range.ts delete mode 100644 packages/virtual-media/test/test.mjs delete mode 100644 packages/virtual-media/tsconfig.json diff --git a/package-lock.json b/package-lock.json index c93065d75..2cb713f40 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,8 @@ "version": "1.0.0", "license": "CC0-1.0", "workspaces": [ + "packages/css-tokenizer", + "packages/css-parser-algorithms", "packages/*", "plugins/postcss-progressive-custom-properties", "plugins/*", @@ -1796,6 +1798,10 @@ "resolved": "experimental/css-has-pseudo", "link": true }, + "node_modules/@csstools/css-parser-algorithms": { + "resolved": "packages/css-parser-algorithms", + "link": true + }, "node_modules/@csstools/css-tokenizer": { "resolved": "packages/css-tokenizer", "link": true @@ -1808,6 +1814,10 @@ "resolved": "packages/generate-test-cases", "link": true }, + "node_modules/@csstools/media-query-list-parser": { + "resolved": "packages/media-query-list-parser", + "link": true + }, "node_modules/@csstools/postcss-base-plugin": { "resolved": "plugins/postcss-base-plugin", "link": true @@ -1848,10 +1858,6 @@ "resolved": "plugins/postcss-is-pseudo-class", "link": true }, - "node_modules/@csstools/postcss-media-query-list-parser": { - "resolved": "packages/postcss-media-query-list-parser", - "link": true - }, "node_modules/@csstools/postcss-nested-calc": { "resolved": "plugins/postcss-nested-calc", "link": true @@ -1892,10 +1898,6 @@ "resolved": "packages/selector-specificity", "link": true }, - "node_modules/@csstools/virtual-media": { - "resolved": "packages/virtual-media", - "link": true - }, "node_modules/@eslint/eslintrc": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.2.tgz", @@ -6825,6 +6827,37 @@ "url": "https://opencollective.com/csstools" } }, + "packages/css-parser": { + "name": "@csstools/css-parser", + "version": "1.0.0", + "extraneous": true, + "license": "MIT", + "dependencies": { + "@csstools/css-tokenizer": "^1.0.0" + }, + "engines": { + "node": "^14 || ^16 || >=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + }, + "packages/css-parser-algorithms": { + "name": "@csstools/css-parser-algorithms", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "@csstools/css-tokenizer": "^1.0.0" + }, + "engines": { + "node": "^14 || ^16 || >=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + }, "packages/css-tokenizer": { "name": "@csstools/css-tokenizer", "version": "1.0.0", @@ -6852,8 +6885,26 @@ "url": "https://opencollective.com/csstools" } }, + "packages/media-query-list-parser": { + "name": "@csstools/media-query-list-parser", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "@csstools/css-parser-algorithms": "^1.0.0", + "@csstools/css-tokenizer": "^1.0.0" + }, + "engines": { + "node": "^14 || ^16 || >=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + }, "packages/postcss-media-query-list-parser": { + "name": "@csstools/postcss-media-query-list-parser", "version": "1.0.0", + "extraneous": true, "license": "MIT", "engines": { "node": "^14 || ^16 || >=18" @@ -6900,7 +6951,9 @@ } }, "packages/virtual-media": { + "name": "@csstools/virtual-media", "version": "1.0.0", + "extraneous": true, "license": "MIT", "engines": { "node": "^14 || ^16 || >=18" @@ -8917,6 +8970,12 @@ "version": "file:experimental/css-has-pseudo", "requires": {} }, + "@csstools/css-parser-algorithms": { + "version": "file:packages/css-parser-algorithms", + "requires": { + "@csstools/css-tokenizer": "^1.0.0" + } + }, "@csstools/css-tokenizer": { "version": "file:packages/css-tokenizer" }, @@ -8968,6 +9027,13 @@ "mdn-data": "^2.0.28" } }, + "@csstools/media-query-list-parser": { + "version": "file:packages/media-query-list-parser", + "requires": { + "@csstools/css-parser-algorithms": "^1.0.0", + "@csstools/css-tokenizer": "^1.0.0" + } + }, "@csstools/postcss-base-plugin": { "version": "file:plugins/postcss-base-plugin", "requires": {} @@ -9037,9 +9103,6 @@ "puppeteer": "^18.0.0" } }, - "@csstools/postcss-media-query-list-parser": { - "version": "file:packages/postcss-media-query-list-parser" - }, "@csstools/postcss-nested-calc": { "version": "file:plugins/postcss-nested-calc", "requires": { @@ -9102,9 +9165,6 @@ "postcss-selector-parser": "^6.0.10" } }, - "@csstools/virtual-media": { - "version": "file:packages/virtual-media" - }, "@eslint/eslintrc": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.2.tgz", diff --git a/package.json b/package.json index ededd4c7b..6f42b9ecf 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ }, "workspaces": [ "packages/css-tokenizer", - "packages/css-parser", + "packages/css-parser-algorithms", "packages/*", "plugins/postcss-progressive-custom-properties", "plugins/*", diff --git a/packages/css-parser/.gitignore b/packages/css-parser-algorithms/.gitignore similarity index 100% rename from packages/css-parser/.gitignore rename to packages/css-parser-algorithms/.gitignore diff --git a/packages/css-parser/.nvmrc b/packages/css-parser-algorithms/.nvmrc similarity index 100% rename from packages/css-parser/.nvmrc rename to packages/css-parser-algorithms/.nvmrc diff --git a/packages/css-parser/CHANGELOG.md b/packages/css-parser-algorithms/CHANGELOG.md similarity index 100% rename from packages/css-parser/CHANGELOG.md rename to packages/css-parser-algorithms/CHANGELOG.md diff --git a/packages/css-parser/LICENSE.md b/packages/css-parser-algorithms/LICENSE.md similarity index 100% rename from packages/css-parser/LICENSE.md rename to packages/css-parser-algorithms/LICENSE.md diff --git a/packages/css-parser-algorithms/README.md b/packages/css-parser-algorithms/README.md new file mode 100644 index 000000000..ff2753b4d --- /dev/null +++ b/packages/css-parser-algorithms/README.md @@ -0,0 +1,102 @@ +# CSS Parser Algorithms + +[npm version][npm-url] +[Build Status][cli-url] +[Discord][discord] + +Implemented from : https://www.w3.org/TR/2021/CRD-css-syntax-3-20211224/ + +## Usage + +Add [CSS Parser Algorithms] to your project: + +```bash +npm install postcss @csstools/css-parser-algorithms --save-dev +``` + +[CSS Parser Algorithms] only accepts tokenized CSS. +It must be used together with `@csstools/css-tokenizer`. + + +```js +import { tokenizer, TokenType } from '@csstools/css-tokenizer'; +import { parseComponentValue } from '@csstools/css-parser-algorithms'; + +const myCSS = `@media only screen and (min-width: 768rem) { + .foo { + content: 'Some content!' !important; + } +} +`; + +const t = tokenizer({ + css: myCSS, +}); + +const tokens = []; + +{ + while (!t.endOfFile()) { + tokens.push(t.nextToken()); + } + + tokens.push(t.nextToken()); // EOF-token +} + +const options = { + onParseError: ((err) => { + throw new Error(JSON.stringify(err)); + }), +}; + +const result = parseComponentValue(tokens, options); + +console.log(result); +``` + +### Available functions + +- [`parseComponentValue`](https://www.w3.org/TR/css-syntax-3/#parse-component-value) +- [`parseListOfComponentValues`](https://www.w3.org/TR/css-syntax-3/#parse-list-of-component-values) +- [`parseCommaSeparatedListOfComponentValues`](https://www.w3.org/TR/css-syntax-3/#parse-comma-separated-list-of-component-values) + +### Options + +```ts +{ + onParseError?: (error: ParserError) => void +} +``` + +#### `onParseError` + +The parser algorithms are forgiving and won't stop when a parse error is encountered. +Parse errors also aren't tokens. + +To receive parsing error information you can set a callback. + +Parser errors will try to inform you about the point in the parsing logic the error happened. +This tells you the kind of error. + +`start` and `end` are the location in your CSS source code. + +`UnclosedSimpleBlockNode` and `UnclosedFunctionNode` entries will be added to the output. +This allows you to recover from errors and/or show warnings. + +## Goals and non-goals + +Things this package aims to be: +- specification compliant CSS parser +- a reliable low level package to be used in CSS sub-grammars + +What it is not: +- opinionated +- fast +- small +- a replacement for PostCSS (PostCSS is fast and also an ecosystem) + +[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/css-parser-algorithms + +[CSS Parser Algorithms]: https://github.com/csstools/postcss-plugins/tree/main/packages/css-parser diff --git a/packages/css-parser/package.json b/packages/css-parser-algorithms/package.json similarity index 92% rename from packages/css-parser/package.json rename to packages/css-parser-algorithms/package.json index d495fdd38..2db452189 100644 --- a/packages/css-parser/package.json +++ b/packages/css-parser-algorithms/package.json @@ -1,5 +1,5 @@ { - "name": "@csstools/css-parser", + "name": "@csstools/css-parser-algorithms", "description": "Parse CSS", "version": "1.0.0", "contributors": [ @@ -51,11 +51,11 @@ "test": "npm run test:exports && node ./test/test.mjs", "test:exports": "node ./test/_import.mjs && node ./test/_require.cjs" }, - "homepage": "https://github.com/csstools/postcss-plugins/tree/main/packages/css-parser#readme", + "homepage": "https://github.com/csstools/postcss-plugins/tree/main/packages/css-parser-algorithms#readme", "repository": { "type": "git", "url": "https://github.com/csstools/postcss-plugins.git", - "directory": "packages/css-parser" + "directory": "packages/css-parser-algorithms" }, "bugs": "https://github.com/csstools/postcss-plugins/issues", "keywords": [ diff --git a/packages/css-parser-algorithms/src/consume/consume-component-block-function.ts b/packages/css-parser-algorithms/src/consume/consume-component-block-function.ts new file mode 100644 index 000000000..bb9276ba3 --- /dev/null +++ b/packages/css-parser-algorithms/src/consume/consume-component-block-function.ts @@ -0,0 +1,434 @@ +import { CSSToken, mirrorVariantType, stringify, TokenType, isToken, TokenFunction } from '@csstools/css-tokenizer'; +import { Context } from '../interfaces/context'; + +export type ContainerNode = FunctionNode | SimpleBlockNode; + +export type ComponentValue = FunctionNode | SimpleBlockNode | WhitespaceNode | CommentNode | TokenNode | UnclosedSimpleBlockNode | UnclosedFunctionNode; + +// https://www.w3.org/TR/css-syntax-3/#consume-a-component-value +export function consumeComponentValue(ctx: Context, tokens: Array): { advance: number, node: ComponentValue } { + const i = 0; + + // eslint-disable-next-line no-constant-condition + while (true) { + const token = tokens[i]; + if ( + token[0] === TokenType.OpenParen || + token[0] === TokenType.OpenCurly || + token[0] === TokenType.OpenSquare + ) { + const r = consumeSimpleBlock(ctx, tokens.slice(i)); + return { + advance: r.advance + i, + node: r.node, + }; + } + + if (token[0] === TokenType.Function) { + const r = consumeFunction(ctx, tokens.slice(i)); + return { + advance: r.advance + i, + node: r.node, + }; + } + + if (token[0] === TokenType.Whitespace) { + const r = consumeWhitespace(ctx, tokens.slice(i)); + return { + advance: r.advance + i, + node: r.node, + }; + } + + if (token[0] === TokenType.Comment) { + const r = consumeComment(ctx, tokens.slice(i)); + return { + advance: r.advance + i, + node: r.node, + }; + } + + return { + advance: i + 1, + node: new TokenNode(token), + }; + } +} + +export class FunctionNode { + type = 'function'; + + name: TokenFunction; + endToken: CSSToken; + value: Array; + + constructor(name: TokenFunction, endToken: CSSToken, value: Array) { + this.name = name; + this.endToken = endToken; + this.value = value; + } + + get nameTokenValue(): string { + return this.name[4].value; + } + + tokens() { + return [ + this.name, + ...this.value.flatMap((x) => { + if (isToken(x)) { + return x; + } + + return x.tokens(); + }), + this.endToken, + ]; + } + + toString() { + const valueString = this.value.map((x) => { + if (isToken(x)) { + return stringify(x); + } + + return x.toString(); + }).join(''); + + return stringify(this.name) + valueString + stringify(this.endToken); + } + + walk(cb: (entry: { node: ComponentValue, parent: ContainerNode }, index: number) => boolean) { + let aborted = false; + + this.value.forEach((child, index) => { + if (aborted) { + return; + } + + if (cb({ node: child, parent: this }, index) === false) { + aborted = true; + return; + } + + if ('walk' in child) { + if (child.walk(cb) === false) { + aborted = true; + return; + } + } + }); + + if (aborted) { + return false; + } + } +} + +// https://www.w3.org/TR/css-syntax-3/#consume-function +export function consumeFunction(ctx: Context, tokens: Array): { advance: number, node: FunctionNode | UnclosedFunctionNode } { + const value: Array = []; + + let i = 1; + + // eslint-disable-next-line no-constant-condition + while (true) { + const token = tokens[i]; + if (!token || token[0] === TokenType.EOF) { + ctx.onParseError({ + message: 'Unexpected EOF while consuming a function.', + start: tokens[0][2], + end: tokens[tokens.length - 1][3], + state: [ + '5.4.9. Consume a function', + 'Unexpected EOF', + ], + }); + + return { + advance: tokens.length, + node: new UnclosedFunctionNode(tokens), + }; + } + + if (token[0] === TokenType.CloseParen) { + return { + advance: i + 1, + node: new FunctionNode(tokens[0] as TokenFunction, token, value), + }; + } + + if (token[0] === TokenType.Comment || token[0] === TokenType.Whitespace) { + const result = consumeAllCommentsAndWhitespace(ctx, tokens.slice(i)); + i += result.advance; + value.push(...result.nodes); + continue; + } + + const result = consumeComponentValue(ctx, tokens.slice(i)); + i += result.advance; + value.push(result.node); + } +} + +export class SimpleBlockNode { + type = 'simple-block'; + + startToken: CSSToken; + endToken: CSSToken; + value: Array; + + constructor(startToken: CSSToken, endToken: CSSToken, value: Array) { + this.startToken = startToken; + this.endToken = endToken; + this.value = value; + } + + tokens() { + return [ + this.startToken, + ...this.value.flatMap((x) => { + if (isToken(x)) { + return x; + } + + return x.tokens(); + }), + this.endToken, + ]; + } + + toString() { + const valueString = this.value.map((x) => { + if (isToken(x)) { + return stringify(x); + } + + return x.toString(); + }).join(''); + + return stringify(this.startToken) + valueString + stringify(this.endToken); + } + + walk(cb: (entry: { node: ComponentValue, parent: ContainerNode }, index: number) => boolean) { + let aborted = false; + + this.value.forEach((child, index) => { + if (aborted) { + return; + } + + if (cb({ node: child, parent: this }, index) === false) { + aborted = true; + return; + } + + if ('walk' in child) { + if (child.walk(cb) === false) { + aborted = true; + return; + } + } + }); + + if (aborted) { + return false; + } + } +} + +/** https://www.w3.org/TR/css-syntax-3/#consume-simple-block */ +export function consumeSimpleBlock(ctx: Context, tokens: Array): { advance: number, node: SimpleBlockNode | UnclosedSimpleBlockNode } { + const endingTokenType = mirrorVariantType(tokens[0][0]); + if (!endingTokenType) { + throw new Error('Failed to parse, a mirror variant must exist for all block open tokens.'); + } + + const value: Array = []; + + let i = 1; + + // eslint-disable-next-line no-constant-condition + while (true) { + const token = tokens[i]; + if (!token || token[0] === TokenType.EOF) { + ctx.onParseError({ + message: 'Unexpected EOF while consuming a simple block.', + start: tokens[0][2], + end: tokens[tokens.length - 1][3], + state: [ + '5.4.8. Consume a simple block', + 'Unexpected EOF', + ], + }); + + return { + advance: tokens.length, + node: new UnclosedSimpleBlockNode(tokens), + }; + } + + if (token[0] === endingTokenType) { + return { + advance: i + 1, + node: new SimpleBlockNode(tokens[0], token, value), + }; + } + + if (token[0] === TokenType.Comment || token[0] === TokenType.Whitespace) { + const result = consumeAllCommentsAndWhitespace(ctx, tokens.slice(i)); + i += result.advance; + value.push(...result.nodes); + continue; + } + + const result = consumeComponentValue(ctx, tokens.slice(i)); + i += result.advance; + value.push(result.node); + } +} + +export class WhitespaceNode { + type = 'whitespace'; + + value: Array; + + constructor(value: Array) { + this.value = value; + } + + tokens() { + return this.value; + } + + toString() { + return stringify(...this.value); + } +} + +export function consumeWhitespace(ctx: Context, tokens: Array): { advance: number, node: WhitespaceNode } { + let i = 0; + + // eslint-disable-next-line no-constant-condition + while (true) { + const token = tokens[i]; + if (token[0] !== TokenType.Whitespace) { + return { + advance: i, + node: new WhitespaceNode(tokens.slice(0, i)), + }; + } + + i++; + } +} + +export class CommentNode { + type = 'comment'; + + value: CSSToken; + + constructor(value: CSSToken) { + this.value = value; + } + + tokens() { + return [ + this.value, + ]; + } + + toString() { + return stringify(this.value); + } +} + +export function consumeComment(ctx: Context, tokens: Array): { advance: number, node: CommentNode } { + return { + advance: 1, + node: new CommentNode(tokens[0]), + }; +} + +export function consumeAllCommentsAndWhitespace(ctx: Context, tokens: Array): { advance: number, nodes: Array } { + const nodes: Array = []; + + let i = 0; + + // eslint-disable-next-line no-constant-condition + while (true) { + if (tokens[i][0] === TokenType.Whitespace) { + const result = consumeWhitespace(ctx, tokens.slice(i)); + i += result.advance; + nodes.push(result.node); + continue; + } + + if (tokens[i][0] === TokenType.Comment) { + nodes.push(new CommentNode(tokens[i])); + i++; + continue; + } + + return { + advance: i, + nodes: nodes, + }; + } +} + +export class TokenNode { + type = 'token'; + + value: CSSToken; + + constructor(value: CSSToken) { + this.value = value; + } + + tokens() { + return [ + this.value, + ]; + } + + toString() { + return stringify(this.value); + } +} + +export class UnclosedFunctionNode { + type = 'unclosed-function'; + + value: Array; + + constructor(value: Array) { + this.value = value; + } + + tokens() { + return this.value; + } + + toString() { + return stringify(...this.value); + } +} + +export class UnclosedSimpleBlockNode { + type = 'unclosed-simple-block'; + + value: Array; + + constructor(value: Array) { + this.value = value; + } + + tokens() { + return this.value; + } + + toString() { + return stringify(...this.value); + } +} diff --git a/packages/css-parser-algorithms/src/index.ts b/packages/css-parser-algorithms/src/index.ts new file mode 100644 index 000000000..b88beecea --- /dev/null +++ b/packages/css-parser-algorithms/src/index.ts @@ -0,0 +1,4 @@ +export * from './consume/consume-component-block-function'; +export { parseComponentValue } from './parse/parse-component-value'; +export { parseListOfComponentValues } from './parse/parse-list-of-component-values'; +export { parseCommaSeparatedListOfComponentValues } from './parse/parse-comma-separated-list-of-component-values'; diff --git a/packages/css-parser-algorithms/src/interfaces/context.ts b/packages/css-parser-algorithms/src/interfaces/context.ts new file mode 100644 index 000000000..27eb77de6 --- /dev/null +++ b/packages/css-parser-algorithms/src/interfaces/context.ts @@ -0,0 +1,5 @@ +import { ParserError } from './error'; + +export type Context = { + onParseError: (error: ParserError) => void +} diff --git a/packages/css-parser-algorithms/src/interfaces/error.ts b/packages/css-parser-algorithms/src/interfaces/error.ts new file mode 100644 index 000000000..e8c677745 --- /dev/null +++ b/packages/css-parser-algorithms/src/interfaces/error.ts @@ -0,0 +1,6 @@ +export type ParserError = { + message: string, + start: number, + end: number, + state: Array +} 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 new file mode 100644 index 000000000..fd138691d --- /dev/null +++ b/packages/css-parser-algorithms/src/parse/parse-comma-separated-list-of-component-values.ts @@ -0,0 +1,60 @@ +import { ParserError } from '../interfaces/error'; +import { CSSToken, TokenType } from '@csstools/css-tokenizer'; +import { ComponentValue, consumeComponentValue } from '../consume/consume-component-block-function'; + +export function parseCommaSeparatedListOfComponentValues(tokens: Array, options?: { onParseError?: (error: ParserError) => void }) { + const ctx = { + onParseError: options?.onParseError ?? (() => { /* noop */ }), + }; + + const tokensCopy = [ + ...tokens, + ]; + + if (tokens.length === 0) { + return []; + } + + // We expect the last token to be an EOF token. + // Passing slices of tokens to this function can easily cause the EOF token to be missing. + if (tokensCopy[tokensCopy.length - 1][0] !== TokenType.EOF) { + tokensCopy.push([ + TokenType.EOF, + '', + tokensCopy[tokensCopy.length - 1][2], + tokensCopy[tokensCopy.length - 1][3], + undefined, + ]); + } + + const listOfCvls: Array> = []; + let list: Array = []; + + let i = 0; + + // eslint-disable-next-line no-constant-condition + while (true) { + if (!tokensCopy[i] || tokensCopy[i][0] === TokenType.EOF) { + if (list.length) { + listOfCvls.push(list); + } + + return listOfCvls; + } + + if (tokensCopy[i][0] === TokenType.Comma) { + if (list.length) { + listOfCvls.push(list); + } + + list = []; + i++; + continue; + } + + const result = consumeComponentValue(ctx, tokens.slice(i)); + list.push(result.node); + i += result.advance; + } +} + diff --git a/packages/css-parser-algorithms/src/parse/parse-component-value.ts b/packages/css-parser-algorithms/src/parse/parse-component-value.ts new file mode 100644 index 000000000..b837b04cd --- /dev/null +++ b/packages/css-parser-algorithms/src/parse/parse-component-value.ts @@ -0,0 +1,41 @@ +import { ParserError } from '../interfaces/error'; +import { CSSToken, TokenType } from '@csstools/css-tokenizer'; +import { consumeComponentValue } from '../consume/consume-component-block-function'; + +export function parseComponentValue(tokens: Array, options?: { onParseError?: (error: ParserError) => void }) { + const ctx = { + onParseError: options?.onParseError ?? (() => { /* noop */ }), + }; + + const tokensCopy = [ + ...tokens, + ]; + + // We expect the last token to be an EOF token. + // Passing slices of tokens to this function can easily cause the EOF token to be missing. + if (tokensCopy[tokensCopy.length - 1][0] !== TokenType.EOF) { + tokensCopy.push([ + TokenType.EOF, + '', + tokensCopy[tokensCopy.length - 1][2], + tokensCopy[tokensCopy.length - 1][3], + undefined, + ]); + } + + const result = consumeComponentValue(ctx, tokensCopy); + if (tokensCopy[Math.min(result.advance, tokensCopy.length - 1)][0] === TokenType.EOF) { + return result.node; + } + + ctx.onParseError({ + message: 'Expected EOF after parsing a component value.', + start: tokens[0][2], + end: tokens[tokens.length - 1][3], + state: [ + '5.3.9. Parse a component value', + 'Expected EOF', + ], + }); +} + diff --git a/packages/css-parser-algorithms/src/parse/parse-list-of-component-values.ts b/packages/css-parser-algorithms/src/parse/parse-list-of-component-values.ts new file mode 100644 index 000000000..4c167ddc1 --- /dev/null +++ b/packages/css-parser-algorithms/src/parse/parse-list-of-component-values.ts @@ -0,0 +1,41 @@ +import { ParserError } from '../interfaces/error'; +import { CSSToken, TokenType } from '@csstools/css-tokenizer'; +import { ComponentValue, consumeComponentValue } from '../consume/consume-component-block-function'; + +export function parseListOfComponentValues(tokens: Array, options?: { onParseError?: (error: ParserError) => void }) { + const ctx = { + onParseError: options?.onParseError ?? (() => { /* noop */ }), + }; + + const tokensCopy = [ + ...tokens, + ]; + + // We expect the last token to be an EOF token. + // Passing slices of tokens to this function can easily cause the EOF token to be missing. + if (tokensCopy[tokensCopy.length - 1][0] !== TokenType.EOF) { + tokensCopy.push([ + TokenType.EOF, + '', + tokensCopy[tokensCopy.length - 1][2], + tokensCopy[tokensCopy.length - 1][3], + undefined, + ]); + } + + const list: Array = []; + + let i = 0; + + // eslint-disable-next-line no-constant-condition + while (true) { + if (!tokensCopy[i] || tokensCopy[i][0] === TokenType.EOF) { + return list; + } + + const result = consumeComponentValue(ctx, tokensCopy.slice(i)); + list.push(result.node); + i += result.advance; + } +} + diff --git a/packages/css-parser/stryker.conf.json b/packages/css-parser-algorithms/stryker.conf.json similarity index 100% rename from packages/css-parser/stryker.conf.json rename to packages/css-parser-algorithms/stryker.conf.json diff --git a/packages/css-parser-algorithms/test/_import.mjs b/packages/css-parser-algorithms/test/_import.mjs new file mode 100644 index 000000000..44050c1ac --- /dev/null +++ b/packages/css-parser-algorithms/test/_import.mjs @@ -0,0 +1,11 @@ +import { parseComponentValue } from '@csstools/css-parser-algorithms'; +import { TokenType } from '@csstools/css-tokenizer'; + +parseComponentValue([ + [ + TokenType.Ident, 'any', 0, 0, undefined, + ], + [ + TokenType.EOF, '', 0, 0, undefined, + ], +]); diff --git a/packages/css-parser-algorithms/test/_require.cjs b/packages/css-parser-algorithms/test/_require.cjs new file mode 100644 index 000000000..1e519d1d4 --- /dev/null +++ b/packages/css-parser-algorithms/test/_require.cjs @@ -0,0 +1,11 @@ +const { parseComponentValue } = require('@csstools/css-parser-algorithms'); +const { TokenType } = require('@csstools/css-tokenizer'); + +parseComponentValue([ + [ + TokenType.Ident, 'any', 0, 0, undefined, + ], + [ + TokenType.EOF, '', 0, 0, undefined, + ], +]); diff --git a/packages/css-parser-algorithms/test/consume.mjs b/packages/css-parser-algorithms/test/consume.mjs new file mode 100644 index 000000000..30aa0a8f6 --- /dev/null +++ b/packages/css-parser-algorithms/test/consume.mjs @@ -0,0 +1,121 @@ +import { tokenizer, TokenType } from '@csstools/css-tokenizer'; +import assert from 'assert'; +import { collectTokens } from './util/collect-tokens.mjs'; +import { consumeFunction, consumeSimpleBlock } from '@csstools/css-parser-algorithms'; + +{ + const testCases = [ + { + css: '[10px]', + advance: 3, + }, + { + css: '(calc(10px))', + advance: 5, + }, + { + css: '(calc(10px) )', + advance: 6, + }, + { + css: '(calc(10px)/* a comment */)', + advance: 6, + }, + { + css: '((calc(10px)/* a comment */) (other))', + advance: 12, + }, + ]; + + for (const testCase of testCases) { + const t = tokenizer({ + css: testCase.css, + }, { commentsAreTokens: true }); + + const tokens = collectTokens(t); + const ctx = { + onParseError: ((err) => { + throw new Error(JSON.stringify(err)); + }), + }; + + const result = consumeSimpleBlock(ctx, tokens); + + assert.deepEqual( + result.advance, + testCase.advance, + ); + + assert.deepEqual( + tokens[result.advance][0], + TokenType.EOF, + ); + + assert.deepEqual( + result.node.toString(), + testCase.css, + ); + + assert.deepEqual( + result.node.tokens(), + tokens.slice(0, -1), + ); + } +} + +{ + const testCases = [ + { + css: 'calc(10px)', + advance: 3, + }, + { + css: 'calc(10px )', + advance: 4, + }, + { + css: 'calc(10px/* a comment */)', + advance: 4, + }, + { + css: 'calc(10px, /* a comment */, (other calc(more)))', + advance: 15, + }, + ]; + + for (const testCase of testCases) { + const t = tokenizer({ + css: testCase.css, + }, { commentsAreTokens: true }); + + const tokens = collectTokens(t); + + const ctx = { + onParseError: ((err) => { + throw new Error(JSON.stringify(err)); + }), + }; + + const result = consumeFunction(ctx, tokens); + + assert.deepEqual( + result.advance, + testCase.advance, + ); + + assert.deepEqual( + tokens[result.advance][0], + TokenType.EOF, + ); + + assert.deepEqual( + result.node.toString(), + testCase.css, + ); + + assert.deepEqual( + result.node.tokens(), + tokens.slice(0, -1), + ); + } +} diff --git a/packages/css-parser-algorithms/test/parse-comma-separated-list-of-component-value.mjs b/packages/css-parser-algorithms/test/parse-comma-separated-list-of-component-value.mjs new file mode 100644 index 000000000..3c8417a11 --- /dev/null +++ b/packages/css-parser-algorithms/test/parse-comma-separated-list-of-component-value.mjs @@ -0,0 +1,38 @@ +import { tokenizer } from '@csstools/css-tokenizer'; +import assert from 'assert'; +import { collectTokens } from './util/collect-tokens.mjs'; +import { parseCommaSeparatedListOfComponentValues } from '@csstools/css-parser-algorithms'; + +{ + const testCases = [ + { + css: '(10px,12px),(10px)', + result: [['(10px,12px)'], ['(10px)']], + }, + + { + css: '(10px,12px),10px 10px', + result: [['(10px,12px)'], ['10px', ' ', '10px']], + }, + ]; + + for (const testCase of testCases) { + const t = tokenizer({ + css: testCase.css, + }, { commentsAreTokens: true }); + + const tokens = collectTokens(t); + const options = { + onParseError: ((err) => { + throw new Error(JSON.stringify(err)); + }), + }; + + const result = parseCommaSeparatedListOfComponentValues(tokens, options); + + assert.deepEqual( + result.map((x) => x.map((y) => y.toString())), + testCase.result, + ); + } +} diff --git a/packages/css-parser-algorithms/test/parse-component-value.mjs b/packages/css-parser-algorithms/test/parse-component-value.mjs new file mode 100644 index 000000000..e9cd6aa2b --- /dev/null +++ b/packages/css-parser-algorithms/test/parse-component-value.mjs @@ -0,0 +1,32 @@ +import { tokenizer } from '@csstools/css-tokenizer'; +import assert from 'assert'; +import { collectTokens } from './util/collect-tokens.mjs'; +import { parseComponentValue } from '@csstools/css-parser-algorithms'; + +{ + const testCases = [ + { + css: '(calc(10px */* a comment */5))', + }, + ]; + + for (const testCase of testCases) { + const t = tokenizer({ + css: testCase.css, + }, { commentsAreTokens: true }); + + const tokens = collectTokens(t); + const options = { + onParseError: ((err) => { + throw new Error(JSON.stringify(err)); + }), + }; + + const result = parseComponentValue(tokens, options); + + assert.deepEqual( + result.toString(), + testCase.css, + ); + } +} diff --git a/packages/css-parser-algorithms/test/parse-list-of-component-value.mjs b/packages/css-parser-algorithms/test/parse-list-of-component-value.mjs new file mode 100644 index 000000000..d417d8490 --- /dev/null +++ b/packages/css-parser-algorithms/test/parse-list-of-component-value.mjs @@ -0,0 +1,33 @@ +import { tokenizer } from '@csstools/css-tokenizer'; +import assert from 'assert'; +import { collectTokens } from './util/collect-tokens.mjs'; +import { parseListOfComponentValues } from '@csstools/css-parser-algorithms'; + +{ + const testCases = [ + { + css: '10px 15px', + result: ['10px', ' ', '15px'], + }, + ]; + + for (const testCase of testCases) { + const t = tokenizer({ + css: testCase.css, + }, { commentsAreTokens: true }); + + const tokens = collectTokens(t); + const options = { + onParseError: ((err) => { + throw new Error(JSON.stringify(err)); + }), + }; + + const result = parseListOfComponentValues(tokens, options); + + assert.deepEqual( + result.map((x) => x.toString()), + testCase.result, + ); + } +} diff --git a/packages/css-parser-algorithms/test/parse.mjs b/packages/css-parser-algorithms/test/parse.mjs new file mode 100644 index 000000000..c776b8120 --- /dev/null +++ b/packages/css-parser-algorithms/test/parse.mjs @@ -0,0 +1,3 @@ +import './parse-component-value.mjs'; +import './parse-list-of-component-value.mjs'; +import './parse-comma-separated-list-of-component-value.mjs'; diff --git a/packages/css-parser-algorithms/test/test.mjs b/packages/css-parser-algorithms/test/test.mjs new file mode 100644 index 000000000..1f4f59b9c --- /dev/null +++ b/packages/css-parser-algorithms/test/test.mjs @@ -0,0 +1,3 @@ +import './consume.mjs'; +import './parse.mjs'; + diff --git a/packages/css-parser-algorithms/test/util/collect-tokens.mjs b/packages/css-parser-algorithms/test/util/collect-tokens.mjs new file mode 100644 index 000000000..5ab1c998b --- /dev/null +++ b/packages/css-parser-algorithms/test/util/collect-tokens.mjs @@ -0,0 +1,11 @@ +export function collectTokens(t) { + const bag = []; + + while (!t.endOfFile()) { + bag.push(t.nextToken()); + } + + bag.push(t.nextToken()); // EOF-token + + return bag; +} diff --git a/packages/css-parser/tsconfig.json b/packages/css-parser-algorithms/tsconfig.json similarity index 100% rename from packages/css-parser/tsconfig.json rename to packages/css-parser-algorithms/tsconfig.json diff --git a/packages/css-parser/README.md b/packages/css-parser/README.md deleted file mode 100644 index a9dc1b02e..000000000 --- a/packages/css-parser/README.md +++ /dev/null @@ -1,129 +0,0 @@ -# CSS Parser - -[npm version][npm-url] -[Build Status][cli-url] -[Discord][discord] - -Implemented from : https://www.w3.org/TR/2021/CRD-css-syntax-3-20211224/ - -## Usage - -Add [CSS Parser] to your project: - -```bash -npm install postcss @csstools/css-parser --save-dev -``` - -```js -import { tokenizer, TokenType } from '@csstools/css-parser'; - -const myCSS = `@media only screen and (min-width: 768rem) { - .foo { - content: 'Some content!' !important; - } -} -`; - -const t = tokenizer({ - css: myCSS, -}); - -while (true) { - const token = t.nextToken(); - if (token[0] === TokenType.EOF) { - break; - } - - console.log(token); -} -``` - -### Options - -```ts -{ - commentsAreTokens?: false, - onParseError?: (error: ParserError) => void -} -``` - -#### `commentsAreTokens` - -Following the CSS specification comments are never returned by the tokenizer. -For many tools however it is desirable to be able to convert tokens back to a string. - -```js -import { tokenizer, TokenType } from '@csstools/css-tokenizer'; - -const t = tokenizer({ - css: `/* a comment */`, -}, { commentsAreTokens: true }); - -while (true) { - const token = t.nextToken(); - if (token[0] === TokenType.EOF) { - break; - } - - console.log(token); -} -``` - -logs : `['comment', '/* a comment */', , , undefined]` - - -#### `onParseError` - -The tokenizer is forgiving and won't stop when a parse error is encountered. -Parse errors also aren't tokens. - -To receive parsing error information you can set a callback. - -```js -import { tokenizer, TokenType } from '@csstools/css-tokenizer'; - -const t = tokenizer({ - css: '\\', -}, { onParseError: (err) => console.warn(err) }); - -while (true) { - const token = t.nextToken(); - if (token[0] === TokenType.EOF) { - break; - } -} -``` - -logs : - -```js -{ - message: 'Unexpected EOF while consuming an escaped code point.', - start: 0, - end: 0, - state: ['4.3.7. Consume an escaped code point', 'Unexpected EOF'], -} -``` - -Parser errors will try to inform you about the point in the tokenizer logic the error happened. -This tells you the kind of error. - -`start` and `end` are the location in your CSS source code. - -## Goals and non-goals - -Things this package aims to be: -- specification compliant CSS parser -- a reliable low level package to be used in CSS sub-grammars - -What it is not: -- opinionated -- fast -- small -- a replacement for PostCSS (PostCSS is fast and also an ecosystem) - -[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/css-parser - -[CSS Parser]: https://github.com/csstools/postcss-plugins/tree/main/packages/css-parser diff --git a/packages/css-parser/src/consume/consume-component-block-function.ts b/packages/css-parser/src/consume/consume-component-block-function.ts deleted file mode 100644 index e5d1dc408..000000000 --- a/packages/css-parser/src/consume/consume-component-block-function.ts +++ /dev/null @@ -1,152 +0,0 @@ -import { CSSToken, mirrorVariant, stringify, TokenType, isToken, TokenIdent, TokenFunction } from '@csstools/css-tokenizer'; - -export type ComponentValue = FunctionNode | SimpleBlockNode | CSSToken; - -export class ComponentValueNode { - type = 'component-value'; - - value: ComponentValue; - before: Array; - after: Array; - - constructor(value: ComponentValue, before: Array, after: Array) { - this.value = value; - this.before = before; - this.after = after; - } - - toString() { - if (isToken(this.value)) { - return stringify( - ...this.before, - this.value, - ...this.after, - ); - } - - return stringify(...this.before) + this.value.toString() + stringify(...this.after); - } -} - -// https://www.w3.org/TR/css-syntax-3/#consume-a-component-value -export function consumeComponentValue(tokens: Array): { advance: number, node: ComponentValue } { - for (let i = 0; i < tokens.length; i++) { - const token = tokens[i]; - if ( - token[0] === TokenType.OpenParen || - token[0] === TokenType.OpenCurly || - token[0] === TokenType.OpenSquare - ) { - const r = consumeSimpleBlock(tokens.slice(i)); - return { - advance: r.advance + i, - node: r.node, - }; - } - - if (token[0] === TokenType.Function) { - const r = consumeFunction(tokens.slice(i)); - return { - advance: r.advance + i, - node: r.node, - }; - } - - return { - advance: i, - node: token, - }; - } -} - -export class FunctionNode { - type = 'function'; - - name: TokenFunction; - value: Array; - after: Array; - - constructor(name: TokenFunction, value: Array, after: Array) { - this.name = name; - this.value = value; - this.after = after; - } - - get nameTokenValue(): string { - return this.name[4].value; - } - - toString() { - return stringify(this.name) + this.value.map((x) => x.toString()).join('') + stringify(...this.after); - } -} - -// https://www.w3.org/TR/css-syntax-3/#consume-function -export function consumeFunction(tokens: Array): { advance: number, node: FunctionNode } { - const valueList: Array = []; - let lastValueIndex = -1; - - for (let i = 1; i < tokens.length; i++) { - const token = tokens[i]; - - if (token[0] === TokenType.CloseParen) { - return { - advance: i, - node: new FunctionNode( - tokens[0] as TokenFunction, - valueList, - tokens.slice(lastValueIndex, i+1), - ), - }; - } - - const r = consumeComponentValue(tokens.slice(i)); - i += r.advance; - lastValueIndex = i; - valueList.push(r.node); - } - - throw new Error('Failed to parse'); -} - -export class SimpleBlockNode { - type = 'simple-block'; - - name: Array; - value: Array; - - constructor(name: Array, value: Array) { - this.name = name; - this.value = value; - } - - get nameIdentIndex(): number { - return this.name.findIndex((x) => { - return x[0] === TokenType.Ident; - }); - } - - toString() { - return stringify(...this.name) + this.value.map((x) => x.toString()).join(''); - } -} - -/** https://www.w3.org/TR/css-syntax-3/#consume-simple-block */ -export function consumeSimpleBlock(tokens: Array): { advance: number, node: SimpleBlockNode } { - const endingToken = mirrorVariant(tokens[0][0]); - if (!endingToken) { - throw new Error('Failed to parse'); - } - - for (let i = 1; i < tokens.length; i++) { - const token = tokens[i]; - - if (token[0] === endingToken) { - return i; - } - - i += consumeComponentValue(tokens.slice(i)); - } - - throw new Error('Failed to parse'); -} diff --git a/packages/css-parser/src/index.ts b/packages/css-parser/src/index.ts deleted file mode 100644 index e69de29bb..000000000 diff --git a/packages/css-parser/test/_import.mjs b/packages/css-parser/test/_import.mjs deleted file mode 100644 index c1c61bf2b..000000000 --- a/packages/css-parser/test/_import.mjs +++ /dev/null @@ -1,5 +0,0 @@ -import { tokenizer } from '@csstools/css-tokenizer'; - -tokenizer({ - css: '.some { css: ""; }', -}); diff --git a/packages/css-parser/test/_require.cjs b/packages/css-parser/test/_require.cjs deleted file mode 100644 index abc74a96e..000000000 --- a/packages/css-parser/test/_require.cjs +++ /dev/null @@ -1,5 +0,0 @@ -const { tokenizer } = require('@csstools/css-tokenizer'); - -tokenizer({ - css: '.some { css: ""; }', -}); diff --git a/packages/css-parser/test/test.mjs b/packages/css-parser/test/test.mjs deleted file mode 100644 index 71d21591a..000000000 --- a/packages/css-parser/test/test.mjs +++ /dev/null @@ -1,13 +0,0 @@ -// Reader -import './test-reader.mjs'; -// Code points -import './code-points/code-points.mjs'; -import './code-points/ranges.mjs'; -// Tokens -import './token/basic.mjs'; -import './token/comment.mjs'; -import './token/numeric.mjs'; -import './token/url.mjs'; -// Complex -import './complex/at-media-params.mjs'; -import './complex/parse-error.mjs'; diff --git a/packages/css-tokenizer/src/index.ts b/packages/css-tokenizer/src/index.ts index 992bdf3bc..d199143ce 100644 --- a/packages/css-tokenizer/src/index.ts +++ b/packages/css-tokenizer/src/index.ts @@ -1,6 +1,6 @@ export type { CSSToken } from './interfaces/token'; export { Reader } from './reader'; -export { TokenType, NumberType, mirrorVariant, isToken } from './interfaces/token'; +export { TokenType, NumberType, mirrorVariantType, isToken } from './interfaces/token'; export { stringify } from './stringify'; export { tokenizer } from './tokenizer'; diff --git a/packages/css-tokenizer/src/interfaces/token.ts b/packages/css-tokenizer/src/interfaces/token.ts index 90d259f01..81a916036 100644 --- a/packages/css-tokenizer/src/interfaces/token.ts +++ b/packages/css-tokenizer/src/interfaces/token.ts @@ -133,7 +133,7 @@ export type Token = [ U, ] -export function mirrorVariant(type: TokenType): TokenType|null { +export function mirrorVariantType(type: TokenType): TokenType|null { switch (type) { case TokenType.OpenParen: return TokenType.CloseParen; diff --git a/packages/postcss-media-query-list-parser/.gitignore b/packages/media-query-list-parser/.gitignore similarity index 100% rename from packages/postcss-media-query-list-parser/.gitignore rename to packages/media-query-list-parser/.gitignore diff --git a/packages/postcss-media-query-list-parser/.nvmrc b/packages/media-query-list-parser/.nvmrc similarity index 100% rename from packages/postcss-media-query-list-parser/.nvmrc rename to packages/media-query-list-parser/.nvmrc diff --git a/packages/postcss-media-query-list-parser/CHANGELOG.md b/packages/media-query-list-parser/CHANGELOG.md similarity index 100% rename from packages/postcss-media-query-list-parser/CHANGELOG.md rename to packages/media-query-list-parser/CHANGELOG.md diff --git a/packages/postcss-media-query-list-parser/LICENSE.md b/packages/media-query-list-parser/LICENSE.md similarity index 100% rename from packages/postcss-media-query-list-parser/LICENSE.md rename to packages/media-query-list-parser/LICENSE.md diff --git a/packages/postcss-media-query-list-parser/README.md b/packages/media-query-list-parser/README.md similarity index 100% rename from packages/postcss-media-query-list-parser/README.md rename to packages/media-query-list-parser/README.md diff --git a/packages/postcss-media-query-list-parser/package.json b/packages/media-query-list-parser/package.json similarity index 85% rename from packages/postcss-media-query-list-parser/package.json rename to packages/media-query-list-parser/package.json index c1f53a38c..c400c28d4 100644 --- a/packages/postcss-media-query-list-parser/package.json +++ b/packages/media-query-list-parser/package.json @@ -1,5 +1,5 @@ { - "name": "@csstools/postcss-media-query-list-parser", + "name": "@csstools/media-query-list-parser", "description": "Tokenize CSS", "version": "1.0.0", "contributors": [ @@ -46,17 +46,21 @@ "prepublishOnly": "npm run clean && npm run build && npm run test", "test": "node ./test/test.mjs" }, - "homepage": "https://github.com/csstools/postcss-plugins/tree/main/packages/postcss-media-query-list-parser#readme", + "homepage": "https://github.com/csstools/postcss-plugins/tree/main/packages/media-query-list-parser#readme", "repository": { "type": "git", "url": "https://github.com/csstools/postcss-plugins.git", - "directory": "packages/postcss-media-query-list-parser" + "directory": "packages/media-query-list-parser" }, "bugs": "https://github.com/csstools/postcss-plugins/issues", "keywords": [ "css", "tokenizer" ], + "dependencies": { + "@csstools/css-tokenizer": "^1.0.0", + "@csstools/css-parser-algorithms": "^1.0.0" + }, "volta": { "extends": "../../package.json" } diff --git a/packages/postcss-media-query-list-parser/src/index.ts b/packages/media-query-list-parser/src/index.ts similarity index 100% rename from packages/postcss-media-query-list-parser/src/index.ts rename to packages/media-query-list-parser/src/index.ts diff --git a/packages/media-query-list-parser/src/nodes/general-enclosed.ts b/packages/media-query-list-parser/src/nodes/general-enclosed.ts new file mode 100644 index 000000000..bc0c4ca81 --- /dev/null +++ b/packages/media-query-list-parser/src/nodes/general-enclosed.ts @@ -0,0 +1,38 @@ +import { ComponentValue, ContainerNode } from '@csstools/css-parser-algorithms'; +import { isToken, stringify } from '@csstools/css-tokenizer'; + +export class GeneralEnclosed { + type = 'general-enclosed'; + + value: ComponentValue; + + constructor(value: ComponentValue) { + this.value = value; + } + + tokens() { + if (isToken(this.value)) { + return this.value; + } + + return this.value.tokens(); + } + + toString() { + if (isToken(this.value)) { + return stringify(this.value); + } + + return this.value.toString(); + } + + walk(cb: (entry: { node: ComponentValue, parent: ContainerNode | GeneralEnclosed }, index: number) => boolean) { + if (cb({ node: this.value, parent: this }, 0) === false) { + return false; + } + + if ('walk' in this.value) { + return this.value.walk(cb); + } + } +} diff --git a/packages/postcss-media-query-list-parser/src/nodes/media-and.ts b/packages/media-query-list-parser/src/nodes/media-and.ts similarity index 100% rename from packages/postcss-media-query-list-parser/src/nodes/media-and.ts rename to packages/media-query-list-parser/src/nodes/media-and.ts diff --git a/packages/postcss-media-query-list-parser/src/nodes/media-condition-list.ts b/packages/media-query-list-parser/src/nodes/media-condition-list.ts similarity index 100% rename from packages/postcss-media-query-list-parser/src/nodes/media-condition-list.ts rename to packages/media-query-list-parser/src/nodes/media-condition-list.ts diff --git a/packages/postcss-media-query-list-parser/src/nodes/media-condition-without-or-or.ts b/packages/media-query-list-parser/src/nodes/media-condition-without-or-or.ts similarity index 100% rename from packages/postcss-media-query-list-parser/src/nodes/media-condition-without-or-or.ts rename to packages/media-query-list-parser/src/nodes/media-condition-without-or-or.ts diff --git a/packages/postcss-media-query-list-parser/src/nodes/media-condition.ts b/packages/media-query-list-parser/src/nodes/media-condition.ts similarity index 100% rename from packages/postcss-media-query-list-parser/src/nodes/media-condition.ts rename to packages/media-query-list-parser/src/nodes/media-condition.ts diff --git a/packages/postcss-media-query-list-parser/src/nodes/media-feature-boolean.ts b/packages/media-query-list-parser/src/nodes/media-feature-boolean.ts similarity index 100% rename from packages/postcss-media-query-list-parser/src/nodes/media-feature-boolean.ts rename to packages/media-query-list-parser/src/nodes/media-feature-boolean.ts diff --git a/packages/media-query-list-parser/src/nodes/media-feature-comparison.ts b/packages/media-query-list-parser/src/nodes/media-feature-comparison.ts new file mode 100644 index 000000000..79a2ef9f4 --- /dev/null +++ b/packages/media-query-list-parser/src/nodes/media-feature-comparison.ts @@ -0,0 +1,76 @@ +import { TokenDelim, TokenType } from '@csstools/css-tokenizer'; + +export enum MediaFeatureLT { + LT = '<', + LT_OR_EQ = '< =', +} + +export enum MediaFeatureGT { + GT = '>', + GT_OR_EQ = '> =', +} + +export enum MediaFeatureEQ { + EQ = '=', +} + +export type MediaFeatureComparison = MediaFeatureLT | MediaFeatureGT | MediaFeatureEQ + +export function comparisonFromTokens(tokens: [TokenDelim, TokenDelim] | [TokenDelim]): MediaFeatureComparison | false { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + /* @ts-ignore */ + if (tokens.length === 0 || tokens.length > 2) { + return false; + } + + if (tokens[0][0] !== TokenType.Delim) { + return false; + } + + if (tokens.length === 1) { + switch (tokens[0][4].value) { + case MediaFeatureEQ.EQ: + return MediaFeatureEQ.EQ; + case MediaFeatureLT.LT: + return MediaFeatureLT.LT; + case MediaFeatureGT.GT: + return MediaFeatureGT.GT; + default: + return false; + } + } + + if (tokens[1][0] !== TokenType.Delim) { + return false; + } + + if (tokens[1][4].value !== MediaFeatureEQ.EQ) { + return false; + } + + switch (tokens[0][4].value) { + case MediaFeatureLT.LT: + return MediaFeatureLT.LT_OR_EQ; + case MediaFeatureGT.GT: + return MediaFeatureGT.GT_OR_EQ; + default: + return false; + } +} + +export function invertComparison(operator: MediaFeatureComparison): MediaFeatureComparison | false { + switch (operator) { + case MediaFeatureEQ.EQ: + return MediaFeatureEQ.EQ; + case MediaFeatureLT.LT: + return MediaFeatureGT.GT; + case MediaFeatureLT.LT_OR_EQ: + return MediaFeatureGT.GT_OR_EQ; + case MediaFeatureGT.GT: + return MediaFeatureLT.LT; + case MediaFeatureGT.GT_OR_EQ: + return MediaFeatureLT.LT_OR_EQ; + default: + return false; + } +} diff --git a/packages/media-query-list-parser/src/nodes/media-feature-name.ts b/packages/media-query-list-parser/src/nodes/media-feature-name.ts new file mode 100644 index 000000000..f15ccbde0 --- /dev/null +++ b/packages/media-query-list-parser/src/nodes/media-feature-name.ts @@ -0,0 +1,28 @@ +import { ComponentValue } from '@csstools/css-parser-algorithms'; +import { isToken, stringify } from '@csstools/css-tokenizer'; + +export class MediaFeatureName { + type = 'mf-name'; + + value: ComponentValue; + + constructor(value: ComponentValue) { + this.value = value; + } + + tokens() { + if (isToken(this.value)) { + return this.value; + } + + return this.value.tokens(); + } + + toString() { + if (isToken(this.value)) { + return stringify(this.value); + } + + return this.value.toString(); + } +} diff --git a/packages/media-query-list-parser/src/nodes/media-feature-plain.ts b/packages/media-query-list-parser/src/nodes/media-feature-plain.ts new file mode 100644 index 000000000..90fd309e4 --- /dev/null +++ b/packages/media-query-list-parser/src/nodes/media-feature-plain.ts @@ -0,0 +1,38 @@ +import { ComponentValue, ContainerNode } from '@csstools/css-parser-algorithms'; +import { stringify, TokenColon } from '@csstools/css-tokenizer'; +import { MediaFeatureName } from './media-feature-name'; +import { MediaFeatureValue } from './media-feature-value'; + +export class MediaFeaturePlain { + type = 'mf-plain'; + + name: MediaFeatureName; + colon: TokenColon; + value: MediaFeatureValue; + + constructor(name: MediaFeatureName, colon: TokenColon, value: MediaFeatureValue) { + this.name = name; + this.colon = colon; + this.value = value; + } + + tokens() { + return [ + ...this.name.tokens(), + this.colon, + ...this.value.tokens(), + ]; + } + + toString() { + return this.name.toString() + stringify(this.colon) + this.value.toString(); + } + + walk(cb: (entry: { node: ComponentValue | MediaFeatureValue, parent: ContainerNode | MediaFeaturePlain | MediaFeatureValue }, index: number) => boolean) { + if (cb({ node: this.value, parent: this }, 0) === false) { + return false; + } + + return this.value.walk(cb); + } +} diff --git a/packages/media-query-list-parser/src/nodes/media-feature-range.ts b/packages/media-query-list-parser/src/nodes/media-feature-range.ts new file mode 100644 index 000000000..df9cdf053 --- /dev/null +++ b/packages/media-query-list-parser/src/nodes/media-feature-range.ts @@ -0,0 +1,214 @@ +import { ComponentValue, ContainerNode } from '@csstools/css-parser-algorithms'; +import { stringify, TokenDelim } from '@csstools/css-tokenizer'; +import { comparisonFromTokens } from './media-feature-comparison'; +import { MediaFeatureName } from './media-feature-name'; +import { MediaFeatureValue } from './media-feature-value'; + +export type MediaFeatureRange = MediaFeatureRangeNameValue | + MediaFeatureRangeValueName | + MediaFeatureRangeLowHigh | + MediaFeatureRangeHighLow; + +export class MediaFeatureRangeNameValue { + type = 'mf-range-name-value'; + + name: MediaFeatureName; + operator: [TokenDelim, TokenDelim] | [TokenDelim]; + value: MediaFeatureValue; + + constructor(name: MediaFeatureName, operator: [TokenDelim, TokenDelim] | [TokenDelim], value: MediaFeatureValue) { + this.name = name; + this.operator = operator; + this.value = value; + } + + operatorKind() { + return comparisonFromTokens(this.operator); + } + + tokens() { + return [ + ...this.name.tokens(), + ...this.operator, + ...this.value.tokens(), + ]; + } + + toString() { + return this.name.toString() + stringify(...this.operator) + this.value.toString(); + } + + walk(cb: (entry: { node: ComponentValue | MediaFeatureValue, parent: ContainerNode | MediaFeatureValue | MediaFeatureRange }, index: number) => boolean) { + if (cb({ node: this.value, parent: this }, 0) === false) { + return false; + } + + if ('walk' in this.value) { + return this.value.walk(cb); + } + } +} + +export class MediaFeatureRangeValueName { + type = 'mf-range-value-range'; + + name: MediaFeatureName; + operator: [TokenDelim, TokenDelim] | [TokenDelim]; + value: MediaFeatureValue; + + constructor(name: MediaFeatureName, operator: [TokenDelim, TokenDelim] | [TokenDelim], value: MediaFeatureValue) { + this.name = name; + this.operator = operator; + this.value = value; + } + + operatorKind() { + return comparisonFromTokens(this.operator); + } + + tokens() { + return [ + ...this.value.tokens(), + ...this.operator, + ...this.name.tokens(), + ]; + } + + toString() { + return this.value.toString() + stringify(...this.operator) + this.name.toString(); + } + + walk(cb: (entry: { node: ComponentValue | MediaFeatureValue, parent: ContainerNode | MediaFeatureValue | MediaFeatureRange }, index: number) => boolean) { + if (cb({ node: this.value, parent: this }, 0) === false) { + return false; + } + + if ('walk' in this.value) { + return this.value.walk(cb); + } + } +} + +export class MediaFeatureRangeLowHigh { + type = 'mf-range-low-high'; + + name: MediaFeatureName; + low: MediaFeatureValue; + lowOperator: [TokenDelim, TokenDelim] | [TokenDelim]; + high: MediaFeatureValue; + highOperator: [TokenDelim, TokenDelim] | [TokenDelim]; + + constructor(name: MediaFeatureName, low: MediaFeatureValue, lowOperator: [TokenDelim, TokenDelim] | [TokenDelim], high: MediaFeatureValue, highOperator: [TokenDelim, TokenDelim] | [TokenDelim]) { + this.name = name; + this.low = low; + this.lowOperator = lowOperator; + this.high = high; + this.highOperator = highOperator; + } + + lowOperatorKind() { + return comparisonFromTokens(this.lowOperator); + } + + highOperatorKind() { + return comparisonFromTokens(this.highOperator); + } + + tokens() { + return [ + ...this.low.tokens(), + ...this.lowOperator, + ...this.name.tokens(), + ...this.highOperator, + ...this.high.tokens(), + ]; + } + + toString() { + return this.low.toString() + stringify(...this.lowOperator) + this.name.toString() + stringify(...this.highOperator) + this.high.toString(); + } + + walk(cb: (entry: { node: ComponentValue | MediaFeatureValue, parent: ContainerNode | MediaFeatureValue | MediaFeatureRange }, index: number) => boolean) { + if (cb({ node: this.low, parent: this }, 0) === false) { + return false; + } + + if ('walk' in this.low) { + if (this.low.walk(cb) === false) { + return false; + } + } + + if (cb({ node: this.high, parent: this }, 0) === false) { + return false; + } + + if ('walk' in this.high) { + if (this.high.walk(cb) === false) { + return false; + } + } + } +} + +export class MediaFeatureRangeHighLow { + type = 'mf-range-high-low'; + + name: MediaFeatureName; + low: MediaFeatureValue; + lowOperator: [TokenDelim, TokenDelim] | [TokenDelim]; + high: MediaFeatureValue; + highOperator: [TokenDelim, TokenDelim] | [TokenDelim]; + + constructor(name: MediaFeatureName, high: MediaFeatureValue, highOperator: [TokenDelim, TokenDelim] | [TokenDelim], low: MediaFeatureValue, lowOperator: [TokenDelim, TokenDelim] | [TokenDelim]) { + this.name = name; + this.low = low; + this.lowOperator = lowOperator; + this.high = high; + this.highOperator = highOperator; + } + + lowOperatorKind() { + return comparisonFromTokens(this.lowOperator); + } + + highOperatorKind() { + return comparisonFromTokens(this.highOperator); + } + + tokens() { + return [ + ...this.high.tokens(), + ...this.highOperator, + ...this.name.tokens(), + ...this.lowOperator, + ...this.low.tokens(), + ]; + } + + toString() { + return this.high.toString() + stringify(...this.highOperator) + this.name.toString() + stringify(...this.lowOperator) + this.low.toString(); + } + + walk(cb: (entry: { node: ComponentValue | MediaFeatureValue, parent: ContainerNode | MediaFeatureValue | MediaFeatureRange }, index: number) => boolean) { + if (cb({ node: this.high, parent: this }, 0) === false) { + return false; + } + + if ('walk' in this.high) { + if (this.high.walk(cb) === false) { + return false; + } + } + + if (cb({ node: this.low, parent: this }, 0) === false) { + return false; + } + + if ('walk' in this.low) { + if (this.low.walk(cb) === false) { + return false; + } + } + } +} diff --git a/packages/media-query-list-parser/src/nodes/media-feature-value.ts b/packages/media-query-list-parser/src/nodes/media-feature-value.ts new file mode 100644 index 000000000..61e05380d --- /dev/null +++ b/packages/media-query-list-parser/src/nodes/media-feature-value.ts @@ -0,0 +1,38 @@ +import { ComponentValue, ContainerNode } from '@csstools/css-parser-algorithms'; +import { isToken, stringify } from '@csstools/css-tokenizer'; + +export class MediaFeatureValue { + type = 'mf-value'; + + value: ComponentValue; + + constructor(value: ComponentValue) { + this.value = value; + } + + tokens() { + if (isToken(this.value)) { + return this.value; + } + + return this.value.tokens(); + } + + toString() { + if (isToken(this.value)) { + return stringify(this.value); + } + + return this.value.toString(); + } + + walk(cb: (entry: { node: ComponentValue, parent: ContainerNode | MediaFeatureValue }, index: number) => boolean) { + if (cb({ node: this.value, parent: this }, 0) === false) { + return false; + } + + if ('walk' in this.value) { + return this.value.walk(cb); + } + } +} diff --git a/packages/postcss-media-query-list-parser/src/nodes/media-feature.ts b/packages/media-query-list-parser/src/nodes/media-feature.ts similarity index 56% rename from packages/postcss-media-query-list-parser/src/nodes/media-feature.ts rename to packages/media-query-list-parser/src/nodes/media-feature.ts index 28e55bb18..ae69aaa27 100644 --- a/packages/postcss-media-query-list-parser/src/nodes/media-feature.ts +++ b/packages/media-query-list-parser/src/nodes/media-feature.ts @@ -1,3 +1,4 @@ +import { CSSToken, stringify } from '@csstools/css-tokenizer'; import { MediaFeatureBoolean } from './media-feature-boolean'; import { MediaFeaturePlain } from './media-feature-plain'; import { MediaFeatureRange } from './media-feature-range'; @@ -7,11 +8,16 @@ export class MediaFeature { feature: MediaFeaturePlain | MediaFeatureBoolean | MediaFeatureRange; - constructor(feature: MediaFeaturePlain | MediaFeatureBoolean | MediaFeatureRange) { + startToken: CSSToken; + endToken: CSSToken; + + constructor(feature: MediaFeaturePlain | MediaFeatureBoolean | MediaFeatureRange, startToken: CSSToken, endToken: CSSToken) { + this.startToken = startToken; + this.endToken = endToken; this.feature = feature; } toString() { - return '(' + this.feature.toString() + ')'; + return stringify(this.startToken) + this.feature.toString() + stringify(this.endToken); } } diff --git a/packages/media-query-list-parser/src/nodes/media-in-parens.ts b/packages/media-query-list-parser/src/nodes/media-in-parens.ts new file mode 100644 index 000000000..495e89448 --- /dev/null +++ b/packages/media-query-list-parser/src/nodes/media-in-parens.ts @@ -0,0 +1,73 @@ +import { ComponentValue, ContainerNode } from '@csstools/css-parser-algorithms'; +import { CSSToken, stringify } from '@csstools/css-tokenizer'; +import { GeneralEnclosed } from './general-enclosed'; +import { MediaCondition } from './media-condition'; +import { MediaFeature } from './media-feature'; + +export class MediaInParens { + type = 'media-in-parens'; + + startToken?: CSSToken; + endToken?: CSSToken; + media: MediaCondition | MediaFeature | GeneralEnclosed; + + constructor(media: MediaCondition | MediaFeature | GeneralEnclosed, startToken?: CSSToken, endToken?: CSSToken) { + this.startToken = startToken; + this.endToken = endToken; + this.media = media; + } + + tokens() { + if (this.media.type === 'general-enclosed') { + return this.media.tokens(); + } + + if (this.media.type === 'media-feature') { + return this.media.tokens(); + } + + if (this.media.type === 'media-condition') { + if (!this.startToken || !this.endToken) { + throw new Error('Failed to list tokens for "media-in-parens" with "media-condition"'); + } + + return [ + this.startToken, + ...this.media.tokens(), + this.endToken, + ]; + } + + throw new Error('Failed to list tokens for "media-in-parens"'); + } + + toString() { + if (this.media.type === 'general-enclosed') { + return this.media.toString(); + } + + if (this.media.type === 'media-feature') { + return this.media.toString(); + } + + if (this.media.type === 'media-condition') { + if (!this.startToken || !this.endToken) { + throw new Error('Failed to stringify "media-in-parens" with "media-condition"'); + } + + return stringify(this.startToken) + this.media.toString() + stringify(this.endToken); + } + + throw new Error('Failed to stringify "media-in-parens"'); + } + + walk(cb: (entry: { node: ComponentValue | MediaCondition | MediaFeature | GeneralEnclosed, parent: ContainerNode | MediaInParens | GeneralEnclosed }, index: number) => boolean) { + if (cb({ node: this.media, parent: this }, 0) === false) { + return false; + } + + if ('walk' in this.media) { + return this.media.walk(cb); + } + } +} diff --git a/packages/postcss-media-query-list-parser/src/nodes/media-not.ts b/packages/media-query-list-parser/src/nodes/media-not.ts similarity index 100% rename from packages/postcss-media-query-list-parser/src/nodes/media-not.ts rename to packages/media-query-list-parser/src/nodes/media-not.ts diff --git a/packages/postcss-media-query-list-parser/src/nodes/media-or.ts b/packages/media-query-list-parser/src/nodes/media-or.ts similarity index 100% rename from packages/postcss-media-query-list-parser/src/nodes/media-or.ts rename to packages/media-query-list-parser/src/nodes/media-or.ts diff --git a/packages/media-query-list-parser/src/nodes/media-query-modifier.ts b/packages/media-query-list-parser/src/nodes/media-query-modifier.ts new file mode 100644 index 000000000..22d94d092 --- /dev/null +++ b/packages/media-query-list-parser/src/nodes/media-query-modifier.ts @@ -0,0 +1,22 @@ +import { TokenIdent, TokenType } from '@csstools/css-tokenizer'; + +export enum MediaQueryModifier { + Not = 'not', + Only = 'only' +} + +export function modifierFromToken(token: TokenIdent): MediaQueryModifier | false { + if (token[0] !== TokenType.Ident) { + return false; + } + + const matchingValue = token[4].value.toLowerCase(); + switch (matchingValue) { + case MediaQueryModifier.Not: + return MediaQueryModifier.Not; + case MediaQueryModifier.Only: + return MediaQueryModifier.Only; + default: + return false; + } +} diff --git a/packages/postcss-media-query-list-parser/src/nodes/media-query.ts b/packages/media-query-list-parser/src/nodes/media-query.ts similarity index 100% rename from packages/postcss-media-query-list-parser/src/nodes/media-query.ts rename to packages/media-query-list-parser/src/nodes/media-query.ts diff --git a/packages/media-query-list-parser/src/nodes/media-type.ts b/packages/media-query-list-parser/src/nodes/media-type.ts new file mode 100644 index 000000000..2eeebf847 --- /dev/null +++ b/packages/media-query-list-parser/src/nodes/media-type.ts @@ -0,0 +1,59 @@ +import { TokenIdent, TokenType } from '@csstools/css-tokenizer'; + +export enum MediaType { + /** Always matches */ + All = 'all', + Print = 'print', + Screen = 'screen', + /** Never matches */ + Tty = 'tty', + /** Never matches */ + Tv = 'tv', + /** Never matches */ + Projection = 'projection', + /** Never matches */ + Handheld = 'handheld', + /** Never matches */ + Braille = 'braille', + /** Never matches */ + Embossed = 'embossed', + /** Never matches */ + Aural = 'aural', + /** Never matches */ + Speech = 'speech', +} + + +export function typeFromToken(token: TokenIdent): MediaType | false { + if (token[0] !== TokenType.Ident) { + return false; + } + + const matchingValue = token[4].value.toLowerCase(); + switch (matchingValue) { + case MediaType.All: + return MediaType.All; + case MediaType.Print: + return MediaType.Print; + case MediaType.Screen: + return MediaType.Screen; + case MediaType.Tty: + return MediaType.Tty; + case MediaType.Tv: + return MediaType.Tv; + case MediaType.Projection: + return MediaType.Projection; + case MediaType.Handheld: + return MediaType.Handheld; + case MediaType.Braille: + return MediaType.Braille; + case MediaType.Embossed: + return MediaType.Embossed; + case MediaType.Aural: + return MediaType.Aural; + case MediaType.Speech: + return MediaType.Speech; + default: + return false; + } +} diff --git a/packages/postcss-media-query-list-parser/src/parser/consume/consume-boolean.ts b/packages/media-query-list-parser/src/parser/consume/consume-boolean.ts similarity index 100% rename from packages/postcss-media-query-list-parser/src/parser/consume/consume-boolean.ts rename to packages/media-query-list-parser/src/parser/consume/consume-boolean.ts diff --git a/packages/postcss-media-query-list-parser/src/parser/consume/consume-plain.ts b/packages/media-query-list-parser/src/parser/consume/consume-plain.ts similarity index 100% rename from packages/postcss-media-query-list-parser/src/parser/consume/consume-plain.ts rename to packages/media-query-list-parser/src/parser/consume/consume-plain.ts diff --git a/packages/media-query-list-parser/src/parser/consume/consume-value.ts b/packages/media-query-list-parser/src/parser/consume/consume-value.ts new file mode 100644 index 000000000..991bc9b63 --- /dev/null +++ b/packages/media-query-list-parser/src/parser/consume/consume-value.ts @@ -0,0 +1,16 @@ +import { CSSToken } from '@csstools/css-tokenizer'; +import { parseComponentValue } from '@csstools/css-parser-algorithms'; +import { MediaFeatureValue } from '../../nodes/media-feature-value'; + +export function consumeValue(tokens: Array): { node: MediaFeatureValue, tokens: Array } | null { + const result = parseComponentValue(tokens, { + onParseError(err) { + throw new Error(JSON.stringify(err)); + }, + }); + + return { + node: new MediaFeatureValue(result), + tokens: tokens.slice(result.tokens().length + 1), + }; +} diff --git a/packages/media-query-list-parser/src/parser/parse.ts b/packages/media-query-list-parser/src/parser/parse.ts new file mode 100644 index 000000000..e96aa49ef --- /dev/null +++ b/packages/media-query-list-parser/src/parser/parse.ts @@ -0,0 +1,29 @@ +import { parseCommaSeparatedListOfComponentValues } from '@csstools/css-parser-algorithms'; +import { tokenizer } from '@csstools/css-tokenizer'; + +export function parse(source: string) { + const onParseError = (err) => { + console.warn(err); + throw new Error(`Unable to parse "${source}"`); + }; + const t = tokenizer({ css: source }, { + commentsAreTokens: true, + onParseError: onParseError, + }); + + const tokens = []; + + { + while (!t.endOfFile()) { + tokens.push(t.nextToken()); + } + + tokens.push(t.nextToken()); // EOF-token + } + + const result = parseCommaSeparatedListOfComponentValues(tokens, { + onParseError: onParseError, + }); + + return result; +} diff --git a/packages/media-query-list-parser/test/test.mjs b/packages/media-query-list-parser/test/test.mjs new file mode 100644 index 000000000..0ddae5dc7 --- /dev/null +++ b/packages/media-query-list-parser/test/test.mjs @@ -0,0 +1,12 @@ +import { parse } from '@csstools/media-query-list-parser'; + +parse('(/* a comment */foo ) something else'); + +parse('((min-width: 300px) and (prefers-color-scheme:/* a comment */dark))').forEach((mediaQuery) => { + mediaQuery.forEach((args) => { + args.walk((a) => { + console.log(a.node.type); + console.log(a.node.toString()); + }); + }); +}); diff --git a/packages/postcss-media-query-list-parser/tsconfig.json b/packages/media-query-list-parser/tsconfig.json similarity index 100% rename from packages/postcss-media-query-list-parser/tsconfig.json rename to packages/media-query-list-parser/tsconfig.json diff --git a/packages/postcss-media-query-list-parser/src/nodes/general-enclosed.ts b/packages/postcss-media-query-list-parser/src/nodes/general-enclosed.ts deleted file mode 100644 index ef15f9f37..000000000 --- a/packages/postcss-media-query-list-parser/src/nodes/general-enclosed.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { CSSToken, stringify } from '@csstools/css-tokenizer'; - -export class GeneralEnclosed { - type = 'general-enclosed'; - - raw: Array; - - constructor(raw: Array) { - this.raw = raw; - } - - toString() { - return stringify(...this.raw); - } -} diff --git a/packages/postcss-media-query-list-parser/src/nodes/media-feature-comparison.ts b/packages/postcss-media-query-list-parser/src/nodes/media-feature-comparison.ts deleted file mode 100644 index e172c73ef..000000000 --- a/packages/postcss-media-query-list-parser/src/nodes/media-feature-comparison.ts +++ /dev/null @@ -1,49 +0,0 @@ -export enum MediaFeatureLT { - LT = '<', - LT_OR_EQ = '<=', -} - -export enum MediaFeatureGT { - GT = '>', - GT_OR_EQ = '>=', -} - -export enum MediaFeatureEQ { - EQ = '=', -} - -export type MediaFeatureComparison = MediaFeatureLT | MediaFeatureGT | MediaFeatureEQ - -export function invertComparison(operator: MediaFeatureComparison): MediaFeatureComparison { - switch (operator) { - case MediaFeatureEQ.EQ: - return MediaFeatureEQ.EQ; - case MediaFeatureLT.LT: - return MediaFeatureGT.GT; - case MediaFeatureLT.LT_OR_EQ: - return MediaFeatureGT.GT_OR_EQ; - case MediaFeatureGT.GT: - return MediaFeatureLT.LT; - case MediaFeatureGT.GT_OR_EQ: - return MediaFeatureLT.LT_OR_EQ; - default: - throw new Error('Unknown range syntax operator'); - } -} - -export function comparisonToString(operator: MediaFeatureComparison): string { - switch (operator) { - case MediaFeatureEQ.EQ: - return '='; - case MediaFeatureLT.LT: - return '<'; - case MediaFeatureLT.LT_OR_EQ: - return '<='; - case MediaFeatureGT.GT: - return '>'; - case MediaFeatureGT.GT_OR_EQ: - return '>='; - default: - throw new Error('Unknown range syntax operator'); - } -} diff --git a/packages/postcss-media-query-list-parser/src/nodes/media-feature-name.ts b/packages/postcss-media-query-list-parser/src/nodes/media-feature-name.ts deleted file mode 100644 index 86349b095..000000000 --- a/packages/postcss-media-query-list-parser/src/nodes/media-feature-name.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { CSSToken, stringify, TokenType } from '@csstools/css-tokenizer'; - -export class MediaFeatureName { - type = 'mf-name'; - - tokens: Array; - - constructor(tokens: Array) { - this.tokens = tokens; - } - - get nameIndex(): number { - return this.tokens.findIndex((x) => { - return x[0] === TokenType.Ident; - }); - } - - toString() { - return stringify(...this.tokens); - } -} diff --git a/packages/postcss-media-query-list-parser/src/nodes/media-feature-plain.ts b/packages/postcss-media-query-list-parser/src/nodes/media-feature-plain.ts deleted file mode 100644 index f8d898b14..000000000 --- a/packages/postcss-media-query-list-parser/src/nodes/media-feature-plain.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { MediaFeatureName } from './media-feature-name'; -import { MediaFeatureValue } from './media-feature-value'; - -export class MediaFeaturePlain { - type = 'mf-plain'; - - name: MediaFeatureName; - value: MediaFeatureValue; - - constructor(name: MediaFeatureName, value: MediaFeatureValue) { - this.name = name; - this.value = value; - } - - toString() { - return this.name.toString() + ':' + this.value.toString(); - } -} diff --git a/packages/postcss-media-query-list-parser/src/nodes/media-feature-range.ts b/packages/postcss-media-query-list-parser/src/nodes/media-feature-range.ts deleted file mode 100644 index a804d26df..000000000 --- a/packages/postcss-media-query-list-parser/src/nodes/media-feature-range.ts +++ /dev/null @@ -1,138 +0,0 @@ -import { comparisonToString, MediaFeatureComparison, MediaFeatureGT, MediaFeatureLT } from './media-feature-comparison'; -import { MediaFeatureName } from './media-feature-name'; -import { MediaFeatureValue } from './media-feature-value'; - -export type MediaFeatureRange = MediaFeatureRangeNameValue | - MediaFeatureRangeValueName | - MediaFeatureRangeLowHigh | - MediaFeatureRangeHighLow; - -export class MediaFeatureRangeNameValue { - type = 'mf-range-name-value'; - - name: MediaFeatureName; - operator: MediaFeatureComparison; - value: MediaFeatureValue; - - constructor(name: MediaFeatureName, operator: MediaFeatureComparison, value: MediaFeatureValue) { - this.name = name; - this.operator = operator; - this.value = value; - } - - toString() { - let result = ''; - - result += this.name.toString(); - result += ' '; - - result += comparisonToString(this.operator); - - result += ' '; - result += this.value.toString(); - - return result; - } -} - -export class MediaFeatureRangeValueName { - type = 'mf-range-value-range'; - - name: MediaFeatureName; - operator: MediaFeatureComparison; - value: MediaFeatureValue; - - constructor(value: MediaFeatureValue, operator: MediaFeatureComparison, name: MediaFeatureName) { - this.name = name; - this.operator = operator; - this.value = value; - } - - toString() { - let result = ''; - - result += this.value.toString(); - result += ' '; - - result += comparisonToString(this.operator); - - result += ' '; - result += this.name.toString(); - - return result; - } -} - -export class MediaFeatureRangeLowHigh { - type = 'mf-range-low-high'; - - name: MediaFeatureName; - low: MediaFeatureValue; - lowOperator: MediaFeatureLT; - high: MediaFeatureValue; - highOperator: MediaFeatureLT; - - constructor(name: MediaFeatureName, low: MediaFeatureValue, lowOperator: MediaFeatureLT, high: MediaFeatureValue, highOperator: MediaFeatureLT) { - this.name = name; - this.low = low; - this.lowOperator = lowOperator; - this.high = high; - this.highOperator = highOperator; - } - - toString() { - let result = ''; - - result += this.low.toString(); - - result += ' '; - result += comparisonToString(this.lowOperator); - result += ' '; - - result += this.name.toString(); - - result += ' '; - result += comparisonToString(this.highOperator); - result += ' '; - - result += this.high.toString(); - return result; - } -} - -export class MediaFeatureRangeHighLow { - type = 'mf-range-high-low'; - - name: MediaFeatureName; - low: MediaFeatureValue; - lowOperator: MediaFeatureGT; - high: MediaFeatureValue; - highOperator: MediaFeatureGT; - - constructor(name: MediaFeatureName, high: MediaFeatureValue, highOperator: MediaFeatureGT, low: MediaFeatureValue, lowOperator: MediaFeatureGT) { - this.name = name; - this.low = low; - this.lowOperator = lowOperator; - this.high = high; - this.highOperator = highOperator; - } - - toString() { - let result = ''; - - result += this.high.toString(); - - result += ' '; - result += comparisonToString(this.highOperator); - result += ' '; - - result += this.name.toString(); - - result += ' '; - result += comparisonToString(this.lowOperator); - result += ' '; - - result += this.low.toString(); - return result; - } -} diff --git a/packages/postcss-media-query-list-parser/src/nodes/media-feature-value.ts b/packages/postcss-media-query-list-parser/src/nodes/media-feature-value.ts deleted file mode 100644 index 83302072c..000000000 --- a/packages/postcss-media-query-list-parser/src/nodes/media-feature-value.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { CSSToken, stringify} from '@csstools/css-tokenizer'; - -export class MediaFeatureValue { - type = 'mf-value'; - - tokens: Array; - - constructor(tokens: Array) { - this.tokens = tokens; - } - - toString() { - return stringify(...this.tokens); - } -} diff --git a/packages/postcss-media-query-list-parser/src/nodes/media-in-parens.ts b/packages/postcss-media-query-list-parser/src/nodes/media-in-parens.ts deleted file mode 100644 index 609b5210c..000000000 --- a/packages/postcss-media-query-list-parser/src/nodes/media-in-parens.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { GeneralEnclosed } from './general-enclosed'; -import { MediaCondition } from './media-condition'; -import { MediaFeature } from './media-feature'; - -export class MediaInParens { - type = 'media-in-parens'; - - media: MediaCondition | MediaFeature | GeneralEnclosed; - - constructor(media: MediaCondition | MediaFeature | GeneralEnclosed) { - this.media = media; - } - - toString() { - if (this.media.type === 'general-enclosed') { - return this.media.toString(); - } - - if (this.media.type === 'media-feature') { - return this.media.toString(); - } - - if (this.media.type === 'media-condition') { - return '(' + this.media.toString() + ')'; - } - - return this.media.toString(); - } -} diff --git a/packages/postcss-media-query-list-parser/src/nodes/media-query-modifier.ts b/packages/postcss-media-query-list-parser/src/nodes/media-query-modifier.ts deleted file mode 100644 index d30ae315d..000000000 --- a/packages/postcss-media-query-list-parser/src/nodes/media-query-modifier.ts +++ /dev/null @@ -1,4 +0,0 @@ -export enum MediaQueryModifier { - Not = 'not', - Only = 'only' -} diff --git a/packages/postcss-media-query-list-parser/src/nodes/media-type.ts b/packages/postcss-media-query-list-parser/src/nodes/media-type.ts deleted file mode 100644 index 341a7e474..000000000 --- a/packages/postcss-media-query-list-parser/src/nodes/media-type.ts +++ /dev/null @@ -1,22 +0,0 @@ -export enum MediaType { - /** Always matches */ - All = 'all', - Print = 'print', - Screen = 'screen', - /** Never matches */ - Tty = 'tty', - /** Never matches */ - Tv = 'tv', - /** Never matches */ - Projection = 'projection', - /** Never matches */ - Handheld = 'handheld', - /** Never matches */ - Braille = 'braille', - /** Never matches */ - Embossed = 'embossed', - /** Never matches */ - Aural = 'aural', - /** Never matches */ - Speech = 'speech', -} diff --git a/packages/postcss-media-query-list-parser/src/parser/advance/advance.ts b/packages/postcss-media-query-list-parser/src/parser/advance/advance.ts deleted file mode 100644 index b66dc51d4..000000000 --- a/packages/postcss-media-query-list-parser/src/parser/advance/advance.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { CSSToken, mirrorVariant, TokenType } from '@csstools/css-tokenizer'; - -// https://www.w3.org/TR/css-syntax-3/#consume-a-component-value -export function advanceComponentValue(tokens: Array): number { - for (let i = 0; i < tokens.length; i++) { - const token = tokens[i]; - if ( - token[0] === TokenType.OpenParen || - token[0] === TokenType.OpenCurly || - token[0] === TokenType.OpenSquare - ) { - i += advanceSimpleBlock(tokens.slice(i)); - return i; - } - - if (token[0] === TokenType.Function) { - i += advanceFunction(tokens.slice(i)); - return i; - } - - return i; - } -} - -// https://www.w3.org/TR/css-syntax-3/#consume-function -export function advanceFunction(tokens: Array): number | null { - for (let i = 1; i < tokens.length; i++) { - const token = tokens[i]; - - if (token[0] === TokenType.CloseParen) { - return i; - } - - i += advanceComponentValue(tokens.slice(i)); - } - - throw new Error('Failed to parse'); -} - -/** https://www.w3.org/TR/css-syntax-3/#consume-simple-block */ -export function advanceSimpleBlock(tokens: Array): number | null { - const endingToken = mirrorVariant(tokens[0][0]); - if (!endingToken) { - throw new Error('Failed to parse'); - } - - for (let i = 1; i < tokens.length; i++) { - const token = tokens[i]; - - if (token[0] === endingToken) { - return i; - } - - i += advanceComponentValue(tokens.slice(i)); - } - - throw new Error('Failed to parse'); -} diff --git a/packages/postcss-media-query-list-parser/src/parser/consume/consume-value.ts b/packages/postcss-media-query-list-parser/src/parser/consume/consume-value.ts deleted file mode 100644 index 6d1b21b88..000000000 --- a/packages/postcss-media-query-list-parser/src/parser/consume/consume-value.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { CSSToken } from '@csstools/css-tokenizer'; -import { MediaFeatureValue } from '../../nodes/media-feature-value'; -import { advanceComponentValue } from '../advance/advance'; - -export function consumeValue(tokens: Array): { node: MediaFeatureValue, tokens: Array } | null { - const result = advanceComponentValue(tokens); - - return { - node: new MediaFeatureValue(tokens.slice(0, result + 1)), - tokens: tokens.slice(result + 1), - }; -} diff --git a/packages/postcss-media-query-list-parser/src/parser/parse.ts b/packages/postcss-media-query-list-parser/src/parser/parse.ts deleted file mode 100644 index 8283ae072..000000000 --- a/packages/postcss-media-query-list-parser/src/parser/parse.ts +++ /dev/null @@ -1,97 +0,0 @@ -import { CSSToken, TokenType, tokenizer } from '@csstools/css-tokenizer'; -import { MediaFeatureBoolean } from '../nodes/media-feature-boolean'; -import { advanceSimpleBlock } from './advance/advance'; -import { consumeBoolean } from './consume/consume-boolean'; - -type Tokenizer = { - nextToken: () => CSSToken | undefined, - endOfFile: () => boolean, -} - -export function parse(source: string) { - const t = tokenizer({ css: source }, { - commentsAreTokens: true, - onParseError: (err) => { - console.warn(err); - throw new Error(`Unable to parse "${source}"`); - }, - }); - - const tokenBuffer = []; - while (!t.endOfFile()) { - tokenBuffer.push(t.nextToken()); - } - - // console.log(tokenBuffer); - - const result = consumeBoolean(tokenBuffer); - const remainder = result.tokens; - const node = result.node; - const tokenSlice = node.tokens; - - console.log(tokenSlice); - console.log(node.nameIndex); - console.log(node.tokens[node.nameIndex][4].value); - - console.log(remainder); -} - -// function consumeMediaQuery(t: Tokenizer) { -// let token = t.nextToken(); -// if (t.endOfFile()) { -// return; -// } - -// while (token[0] === TokenType.Whitespace || token[0] === TokenType.Comment) { -// token = t.nextToken(); -// if (t.endOfFile()) { -// return; -// } -// } - -// if (token[0] === TokenType.OpenParen) { -// return consumeMediaQueryWithoutType(t); -// } - -// if (token[0] !== TokenType.Ident) { -// return; -// } - -// if (token[0] === TokenType.Ident && token[4].value.toLowerCase() === 'only') { -// return consumeMediaQueryWithType(t); -// } - -// if (token[0] === TokenType.Ident && token[4].value.toLowerCase() === 'not') { -// token = t.nextToken(); -// if (t.endOfFile()) { -// return; -// } - -// while (token[0] === TokenType.Whitespace || token[0] === TokenType.Comment) { -// token = t.nextToken(); -// if (t.endOfFile()) { -// return; -// } -// } - -// if (token[0] === TokenType.Comma) { -// return; -// } - -// if (token[0] === TokenType.OpenParen) { -// return consumeMediaQueryWithoutType(t); -// } - -// return consumeMediaQueryWithType(t); -// } - -// const modifier = token[] -// } - -// function consumeMediaQueryWithoutType(t: Tokenizer) { - -// } - -// function consumeMediaQueryWithType(t: Tokenizer) { - -// } diff --git a/packages/postcss-media-query-list-parser/test/test.mjs b/packages/postcss-media-query-list-parser/test/test.mjs deleted file mode 100644 index 44a4b335d..000000000 --- a/packages/postcss-media-query-list-parser/test/test.mjs +++ /dev/null @@ -1,3 +0,0 @@ -import { parse } from '@csstools/postcss-media-query-list-parser'; - -parse('(/* a comment */foo ) something else'); diff --git a/packages/virtual-media/.gitignore b/packages/virtual-media/.gitignore deleted file mode 100644 index 7172b04f1..000000000 --- a/packages/virtual-media/.gitignore +++ /dev/null @@ -1,6 +0,0 @@ -node_modules -package-lock.json -yarn.lock -*.result.css -*.result.css.map -dist/* diff --git a/packages/virtual-media/.nvmrc b/packages/virtual-media/.nvmrc deleted file mode 100644 index f0b10f153..000000000 --- a/packages/virtual-media/.nvmrc +++ /dev/null @@ -1 +0,0 @@ -v16.13.1 diff --git a/packages/virtual-media/CHANGELOG.md b/packages/virtual-media/CHANGELOG.md deleted file mode 100644 index b0ff6b082..000000000 --- a/packages/virtual-media/CHANGELOG.md +++ /dev/null @@ -1,3 +0,0 @@ -### 1.0.0 - -- Initial version diff --git a/packages/virtual-media/LICENSE.md b/packages/virtual-media/LICENSE.md deleted file mode 100644 index af5411fa2..000000000 --- a/packages/virtual-media/LICENSE.md +++ /dev/null @@ -1,20 +0,0 @@ -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/virtual-media/README.md b/packages/virtual-media/README.md deleted file mode 100644 index 361096774..000000000 --- a/packages/virtual-media/README.md +++ /dev/null @@ -1 +0,0 @@ -# TODO diff --git a/packages/virtual-media/package.json b/packages/virtual-media/package.json deleted file mode 100644 index 84e6aa12d..000000000 --- a/packages/virtual-media/package.json +++ /dev/null @@ -1,63 +0,0 @@ -{ - "name": "@csstools/virtual-media", - "description": "Virtualized media for media queries.", - "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" - ], - "scripts": { - "build": "rollup -c ../../rollup/default.js", - "clean": "node -e \"fs.rmSync('./dist', { recursive: true, force: true });\"", - "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", - "test": "node ./test/test.mjs" - }, - "homepage": "https://github.com/csstools/postcss-plugins/tree/main/packages/virtual-media#readme", - "repository": { - "type": "git", - "url": "https://github.com/csstools/postcss-plugins.git", - "directory": "packages/virtual-media" - }, - "bugs": "https://github.com/csstools/postcss-plugins/issues", - "keywords": [ - "css", - "tokenizer" - ], - "volta": { - "extends": "../../package.json" - } -} diff --git a/packages/virtual-media/src/index.ts b/packages/virtual-media/src/index.ts deleted file mode 100644 index e2a94869a..000000000 --- a/packages/virtual-media/src/index.ts +++ /dev/null @@ -1,188 +0,0 @@ -import { addRange } from './range/add'; -import { Range } from './range/range'; - -export type Unknown = 'unknown'; -export type Impossible = 'impossible'; - -export enum Operator { - LT = '<', - LT_OR_EQ = '<=', - GT = '>', - GT_OR_EQ = '>=', - EQ = '=', -} - -const fraction = 1 / Math.pow(2, 32); - -export class VirtualMedia { - #impossibleMedia = false; - - #types = new Set([ - 'print', - 'screen', - ]); - - mustMatchType(type: string) { - if (type.toLowerCase() === 'all') { - return; - } - - if (!this.#types.has(type.toLowerCase())) { - this.#types.clear(); - return; - } - - this.#types.clear(); - this.#types.add(type.toLowerCase()); - } - - mustNotMatchType(type: string) { - if (type.toLowerCase() === 'all') { - this.#types.clear(); - return; - } - - this.#types.delete(type.toLowerCase()); - } - - mustMatchWidth(low: number, lowOperator: Operator, high: number, highOperator: Operator) { - if (this.#impossibleMedia) { - return; - } - - const range = { - low: low, - high: high, - }; - - switch (lowOperator) { - case Operator.LT: - range.low = range.low - fraction; - break; - case Operator.GT: - range.low = range.low + fraction; - break; - } - - switch (highOperator) { - case Operator.LT: - range.high = range.high - fraction; - break; - case Operator.GT: - range.high = range.high + fraction; - break; - } - - this.#width = addRange(this.#width, { - low: range.low, - high: range.high, - }); - - if (this.#width.length === 0) { - this.#impossibleMedia = true; - } - } - - #width: Array> = [ - { - low: Number.MIN_SAFE_INTEGER, - high: Number.MAX_SAFE_INTEGER, - }, - ]; - - /** - * https://www.w3.org/TR/mediaqueries-5/#width - * - */ - get width(): number | Unknown | Impossible { - if (this.#impossibleMedia) { - return 'impossible'; - } - - if (this.#width.length !== 1) { - return 'unknown'; - } - - if (this.#width[0].low !== this.#width[0].high) { - return 'unknown'; - } - - return this.#width[0].low; - } - - #height: Array> = [ - { - low: Number.MIN_SAFE_INTEGER, - high: Number.MAX_SAFE_INTEGER, - }, - ]; - - /** - * https://www.w3.org/TR/mediaqueries-5/#height - * - */ - get height(): number | Unknown | Impossible { - if (this.#impossibleMedia) { - return 'impossible'; - } - - if (this.#height.length !== 1) { - return 'unknown'; - } - - if (this.#height[0].low !== this.#height[0].high) { - return 'unknown'; - } - - return this.#height[0].low; - } - - #aspectRatio: Range<{ - /** dividend / divisor */ - dividend: number, - /** dividend / divisor */ - divisor: number - }> | Unknown = 'unknown'; - - /** - * https://www.w3.org/TR/mediaqueries-5/#aspect-ratio - * - */ - get aspectRatio() { - if (this.#impossibleMedia) { - return 'unknown'; - } - - if (this.#aspectRatio === 'unknown') { - const height = this.height; - const width = this.width; - if (height === 'impossible' || width === 'impossible') { - return 'impossible'; - } - - if (height !== 'unknown' && width !== 'unknown') { - return width / height; - } - - return 'unknown'; - } - - if (this.#aspectRatio.low.dividend !== this.#aspectRatio.high.dividend) { - return 'unknown'; - } - - if (this.#aspectRatio.low.divisor !== this.#aspectRatio.high.divisor) { - return 'unknown'; - } - - if (this.#aspectRatio.low.dividend === 0) { - return 'unknown'; - } - - if (this.#aspectRatio.low.divisor === 0) { - return 'unknown'; - } - - return this.#aspectRatio.low.dividend / this.#aspectRatio.low.divisor; - } -} diff --git a/packages/virtual-media/src/range/add.ts b/packages/virtual-media/src/range/add.ts deleted file mode 100644 index 3b0688c89..000000000 --- a/packages/virtual-media/src/range/add.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { compare } from './compare'; -import { Range } from './range'; - -export function addRange(existingRanges: Array>, add: Range): Array> { - if (add.low > add.high) { - throw new Error('Inversed range ' + JSON.stringify(add)); - } - - const updated: Array> = []; - - for (let i = 0; i < existingRanges.length; i++) { - const existingRange = existingRanges[i]; - - // The "add" spans an equal range as is currently allowed. - // Return the current part of existing ranges. - if (compare(add.low, existingRange.low) === 0 && compare(existingRange.high, add.high) === 0) { - return [ - existingRange, - ]; - } - - // The "add" spans a range without any overlap with the current part. - // Continue to the next part. - if (compare(add.low, existingRange.high) > 0) { - continue; - } - - // The "add" spans a range without any overlap with the current part. - // Continue to the next part. - if (compare(add.high, existingRange.low) < 0) { - continue; - } - - // The "add" spans a smaller range, but is fully enclosed withing the current range. - // Return the part to add. - if (compare(add.low, existingRange.low) > 0 && compare(existingRange.high, add.high) > 0) { - return [ - add, - ]; - } - - // The "add" spans a larger range than is currently allowed, but it fully encloses the current range. - // Add the current part of the existing ranges to the updated slice. - if (compare(add.low, existingRange.low) < 0 && compare(existingRange.high, add.high) < 0) { - updated.push(existingRange); - continue; - } - - if (compare(add.low, existingRange.low) > 0) { - updated.push({ - low: add.low, - high: existingRange.high, - }); - continue; - } - - if (compare(add.high, existingRange.high) < 0) { - updated.push({ - low: existingRange.low, - high: add.high, - }); - continue; - } - } - - return updated; -} diff --git a/packages/virtual-media/src/range/compare.ts b/packages/virtual-media/src/range/compare.ts deleted file mode 100644 index cb678e037..000000000 --- a/packages/virtual-media/src/range/compare.ts +++ /dev/null @@ -1,33 +0,0 @@ -export function compare(a: T, b: T): -1 | 0 | 1 { - if ((typeof a) !== (typeof b)) { - return 0; - } - - if ((typeof a === 'number')) { - const r = a - (b as number); - if (r < 0) { - return -1; - } - - if (r > 0) { - return 1; - } - - return 0; - } - - if ((typeof a === 'string')) { - const r = a.localeCompare(b as string); - if (r < 0) { - return -1; - } - - if (r > 0) { - return 1; - } - - return 0; - } - - return 0; -} diff --git a/packages/virtual-media/src/range/range.ts b/packages/virtual-media/src/range/range.ts deleted file mode 100644 index 5eeae7c75..000000000 --- a/packages/virtual-media/src/range/range.ts +++ /dev/null @@ -1,4 +0,0 @@ -export type Range = { - low: T, - high: T -}; diff --git a/packages/virtual-media/test/test.mjs b/packages/virtual-media/test/test.mjs deleted file mode 100644 index 1832b1a17..000000000 --- a/packages/virtual-media/test/test.mjs +++ /dev/null @@ -1,28 +0,0 @@ -import { VirtualMedia, Operator } from '@csstools/virtual-media'; - -// What with units? -// Maybe a map of facts per property -// { px: { low, high }, rem: { low, high } } -// -// Or a map of VirtualMedia and a way to share unitless properties? -// -// How to deal with "not (width: 300px)" - -{ - const media = new VirtualMedia(); - console.log(media.width); - - media.mustMatchWidth(10, Operator.GT, 100, Operator.LT); - console.log(media.width); - - media.mustMatchWidth(1000, Operator.GT, 10000, Operator.LT); - console.log(media.width); -} - -{ - const media = new VirtualMedia(); - console.log(media.width); - - media.mustMatchWidth(10, Operator.EQ, 10, Operator.EQ); - console.log(media.width); -} diff --git a/packages/virtual-media/tsconfig.json b/packages/virtual-media/tsconfig.json deleted file mode 100644 index e0d06239c..000000000 --- a/packages/virtual-media/tsconfig.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "extends": "../../tsconfig.json", - "compilerOptions": { - "outDir": "dist", - "declarationDir": "." - }, - "include": ["./src/**/*"], - "exclude": ["dist"], -} diff --git a/rollup/presets/package-typescript.js b/rollup/presets/package-typescript.js index d765bf703..6242f32c5 100644 --- a/rollup/presets/package-typescript.js +++ b/rollup/presets/package-typescript.js @@ -21,7 +21,9 @@ export function packageTypescript() { extensions: ['.js', '.ts'], presets: packageBabelPreset, }), - terser(), + terser({ + keep_classnames: true, + }), ], }, ]; From ccf0b594a90aeffde9fdadc00a2056dae0c47bc3 Mon Sep 17 00:00:00 2001 From: Romain Menke Date: Wed, 19 Oct 2022 22:44:50 +0200 Subject: [PATCH 03/35] wip --- .../consume-component-block-function.ts | 30 +++- .../src/nodes/general-enclosed.ts | 21 ++- .../src/nodes/media-and.ts | 41 ++++- .../src/nodes/media-condition-list.ts | 167 ++++++++++++++++-- .../nodes/media-condition-without-or-or.ts | 16 -- .../src/nodes/media-condition-without-or.ts | 45 +++++ .../src/nodes/media-condition.ts | 33 +++- .../src/nodes/media-feature-name.ts | 32 +++- .../src/nodes/media-feature-plain.ts | 32 +++- .../src/nodes/media-feature-range.ts | 130 ++++++++++++-- .../src/nodes/media-feature-value.ts | 21 ++- .../src/nodes/media-feature.ts | 52 +++++- .../src/nodes/media-in-parens.ts | 65 +++---- .../src/nodes/media-not.ts | 41 ++++- .../src/nodes/media-or.ts | 41 ++++- .../src/nodes/media-query.ts | 90 ++++++++-- .../src/parser/parse.ts | 52 +++++- .../media-query-list-parser/test/test.mjs | 7 +- 18 files changed, 779 insertions(+), 137 deletions(-) delete mode 100644 packages/media-query-list-parser/src/nodes/media-condition-without-or-or.ts create mode 100644 packages/media-query-list-parser/src/nodes/media-condition-without-or.ts diff --git a/packages/css-parser-algorithms/src/consume/consume-component-block-function.ts b/packages/css-parser-algorithms/src/consume/consume-component-block-function.ts index bb9276ba3..20c618ec6 100644 --- a/packages/css-parser-algorithms/src/consume/consume-component-block-function.ts +++ b/packages/css-parser-algorithms/src/consume/consume-component-block-function.ts @@ -98,7 +98,20 @@ export class FunctionNode { return stringify(this.name) + valueString + stringify(this.endToken); } - walk(cb: (entry: { node: ComponentValue, parent: ContainerNode }, index: number) => boolean) { + indexOf(item: ComponentValue): number | string { + return this.value.indexOf(item); + } + + at(index: number | string) { + if (typeof index === 'number') { + if (index < 0) { + index = this.value.length + index; + } + return this.value[index]; + } + } + + walk(cb: (entry: { node: ComponentValue, parent: ContainerNode }, index: number | string) => boolean) { let aborted = false; this.value.forEach((child, index) => { @@ -210,7 +223,20 @@ export class SimpleBlockNode { return stringify(this.startToken) + valueString + stringify(this.endToken); } - walk(cb: (entry: { node: ComponentValue, parent: ContainerNode }, index: number) => boolean) { + indexOf(item: ComponentValue): number | string { + return this.value.indexOf(item); + } + + at(index: number | string) { + if (typeof index === 'number') { + if (index < 0) { + index = this.value.length + index; + } + return this.value[index]; + } + } + + walk(cb: (entry: { node: ComponentValue, parent: ContainerNode }, index: number | string) => boolean) { let aborted = false; this.value.forEach((child, index) => { diff --git a/packages/media-query-list-parser/src/nodes/general-enclosed.ts b/packages/media-query-list-parser/src/nodes/general-enclosed.ts index bc0c4ca81..d2e019ee4 100644 --- a/packages/media-query-list-parser/src/nodes/general-enclosed.ts +++ b/packages/media-query-list-parser/src/nodes/general-enclosed.ts @@ -26,8 +26,22 @@ export class GeneralEnclosed { return this.value.toString(); } - walk(cb: (entry: { node: ComponentValue, parent: ContainerNode | GeneralEnclosed }, index: number) => boolean) { - if (cb({ node: this.value, parent: this }, 0) === false) { + indexOf(item: ComponentValue): number | string { + if (item === this.value) { + return 'value'; + } + + return -1; + } + + at(index: number | string) { + if (index === 'value') { + return this.value; + } + } + + walk(cb: (entry: { node: GeneralEnclosedWalkerEntry, parent: GeneralEnclosedWalkerParent }, index: number | string) => boolean) { + if (cb({ node: this.value, parent: this }, 'value') === false) { return false; } @@ -36,3 +50,6 @@ export class GeneralEnclosed { } } } + +export type GeneralEnclosedWalkerEntry = ComponentValue; +export type GeneralEnclosedWalkerParent = ContainerNode | GeneralEnclosed; diff --git a/packages/media-query-list-parser/src/nodes/media-and.ts b/packages/media-query-list-parser/src/nodes/media-and.ts index c6fd5c87e..b6695c07d 100644 --- a/packages/media-query-list-parser/src/nodes/media-and.ts +++ b/packages/media-query-list-parser/src/nodes/media-and.ts @@ -1,15 +1,50 @@ -import { MediaInParens } from './media-in-parens'; +import { CSSToken, stringify } from '@csstools/css-tokenizer'; +import { MediaInParens, MediaInParensWalkerEntry, MediaInParensWalkerParent } from './media-in-parens'; export class MediaAnd { type = 'media-and'; + modifier: Array; media: MediaInParens; - constructor(media: MediaInParens) { + constructor(modifier: Array, media: MediaInParens) { + this.modifier = modifier; this.media = media; } + tokens() { + return [ + ...this.modifier, + ...this.media.tokens(), + ]; + } + toString() { - return 'and' + this.media.toString(); + return stringify(...this.modifier) + this.media.toString(); + } + + indexOf(item: MediaInParens): number | string { + if (item === this.media) { + return 'media'; + } + + return -1; + } + + at(index: number | string) { + if (index === 'media') { + return this.media; + } + } + + walk(cb: (entry: { node: MediaAndWalkerEntry, parent: MediaAndWalkerParent }, index: number | string) => boolean) { + if (cb({ node: this.media, parent: this }, 'media') === false) { + return false; + } + + return this.media.walk(cb); } } + +export type MediaAndWalkerEntry = MediaInParensWalkerEntry | MediaInParens; +export type MediaAndWalkerParent = MediaInParensWalkerParent | MediaAnd; diff --git a/packages/media-query-list-parser/src/nodes/media-condition-list.ts b/packages/media-query-list-parser/src/nodes/media-condition-list.ts index 563eebae3..588c3565a 100644 --- a/packages/media-query-list-parser/src/nodes/media-condition-list.ts +++ b/packages/media-query-list-parser/src/nodes/media-condition-list.ts @@ -1,6 +1,7 @@ -import { MediaAnd } from './media-and'; +import { CSSToken, stringify } from '@csstools/css-tokenizer'; +import { MediaAnd, MediaAndWalkerEntry, MediaAndWalkerParent } from './media-and'; import { MediaInParens } from './media-in-parens'; -import { MediaOr } from './media-or'; +import { MediaOr, MediaOrWalkerEntry, MediaOrWalkerParent } from './media-or'; export type MediaConditionList = MediaConditionListWithAnd | MediaConditionListWithOr; @@ -9,37 +10,183 @@ export class MediaConditionListWithAnd { leading: MediaInParens; list: Array; + before: Array; + after: Array; - constructor(leading: MediaInParens, list: Array) { + constructor(leading: MediaInParens, list: Array, before: Array = [], after: Array = []) { this.leading = leading; this.list = list; + this.before = before; + this.after = after; + } + + tokens() { + return [ + ...this.before, + this.leading.tokens(), + ...this.list.flatMap((item) => item.tokens()), + ...this.after, + ]; } toString() { - if (this.list.length === 0) { - return this.leading.toString(); + return stringify(...this.before) + this.leading.toString() + this.list.map((item) => item.toString()).join('') + stringify(...this.after); + } + + indexOf(item: MediaInParens | MediaAnd): number | string { + if (item === this.leading) { + return 'leading'; + } + + if (item.type === 'media-and') { + return this.list.indexOf(item as MediaAnd); } - return this.leading.toString() + ' ' + this.list.map((x) => x.toString()).join(' '); + return -1; + } + + at(index: number | string) { + if (index === 'leading') { + return this.leading; + } + + if (typeof index === 'number') { + if (index < 0) { + index = this.list.length + index; + } + return this.list[index]; + } + } + + walk(cb: (entry: { node: MediaConditionListWithAndWalkerEntry, parent: MediaConditionListWithAndWalkerParent }, index: number | string) => boolean) { + if (cb({ node: this.leading, parent: this }, 'leading') === false) { + return false; + } + + if ('walk' in this.leading) { + if (this.leading.walk(cb) === false) { + return false; + } + } + + let aborted = false; + + this.list.forEach((child, index) => { + if (aborted) { + return; + } + + if (cb({ node: child, parent: this }, index) === false) { + aborted = true; + return; + } + + if ('walk' in child) { + if (child.walk(cb) === false) { + aborted = true; + return; + } + } + }); + + if (aborted) { + return false; + } } } +export type MediaConditionListWithAndWalkerEntry = MediaAndWalkerEntry | MediaAnd; +export type MediaConditionListWithAndWalkerParent = MediaAndWalkerParent | MediaConditionListWithAnd; + export class MediaConditionListWithOr { type = 'media-condition-list-or'; leading: MediaInParens; list: Array; + before: Array; + after: Array; - constructor(leading: MediaInParens, list: Array) { + constructor(leading: MediaInParens, list: Array, before: Array = [], after: Array = []) { this.leading = leading; this.list = list; + this.before = before; + this.after = after; + } + + tokens() { + return [ + ...this.before, + this.leading.tokens(), + ...this.list.flatMap((item) => item.tokens()), + ...this.after, + ]; } toString() { - if (this.list.length === 0) { - return this.leading.toString(); + return stringify(...this.before) + this.leading.toString() + this.list.map((item) => item.toString()).join('') + stringify(...this.after); + } + + indexOf(item: MediaInParens | MediaOr): number | string { + if (item === this.leading) { + return 'leading'; + } + + if (item.type === 'media-or') { + return this.list.indexOf(item as MediaOr); } - return this.leading.toString() + ' ' + this.list.map((x) => x.toString()).join(' '); + return -1; + } + + at(index: number | string) { + if (index === 'leading') { + return this.leading; + } + + if (typeof index === 'number') { + if (index < 0) { + index = this.list.length + index; + } + return this.list[index]; + } + } + + walk(cb: (entry: { node: MediaConditionListWithOrWalkerEntry, parent: MediaConditionListWithOrWalkerParent }, index: number | string) => boolean) { + if (cb({ node: this.leading, parent: this }, 'leading') === false) { + return false; + } + + if ('walk' in this.leading) { + if (this.leading.walk(cb) === false) { + return false; + } + } + + let aborted = false; + + this.list.forEach((child, index) => { + if (aborted) { + return; + } + + if (cb({ node: child, parent: this }, index) === false) { + aborted = true; + return; + } + + if ('walk' in child) { + if (child.walk(cb) === false) { + aborted = true; + return; + } + } + }); + + if (aborted) { + return false; + } } } + +export type MediaConditionListWithOrWalkerEntry = MediaOrWalkerEntry | MediaOr; +export type MediaConditionListWithOrWalkerParent = MediaOrWalkerParent | MediaConditionListWithOr; diff --git a/packages/media-query-list-parser/src/nodes/media-condition-without-or-or.ts b/packages/media-query-list-parser/src/nodes/media-condition-without-or-or.ts deleted file mode 100644 index 8cb02ade0..000000000 --- a/packages/media-query-list-parser/src/nodes/media-condition-without-or-or.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { MediaConditionListWithAnd } from './media-condition-list'; -import { MediaNot } from './media-not'; - -export class MediaConditionWithoutOr { - type = 'media-condition-without-or'; - - media: MediaNot | MediaConditionListWithAnd; - - constructor(media: MediaNot | MediaConditionListWithAnd) { - this.media = media; - } - - toString() { - return this.media.toString(); - } -} diff --git a/packages/media-query-list-parser/src/nodes/media-condition-without-or.ts b/packages/media-query-list-parser/src/nodes/media-condition-without-or.ts new file mode 100644 index 000000000..1834898dd --- /dev/null +++ b/packages/media-query-list-parser/src/nodes/media-condition-without-or.ts @@ -0,0 +1,45 @@ +import { MediaConditionListWithAnd, MediaConditionListWithAndWalkerEntry, MediaConditionListWithAndWalkerParent } from './media-condition-list'; +import { MediaNot, MediaNotWalkerEntry, MediaNotWalkerParent } from './media-not'; + +export class MediaConditionWithoutOr { + type = 'media-condition-without-or'; + + media: MediaNot | MediaConditionListWithAnd; + + constructor(media: MediaNot | MediaConditionListWithAnd) { + this.media = media; + } + + tokens() { + return this.media.tokens(); + } + + toString() { + return this.media.toString(); + } + + indexOf(item: MediaNot | MediaConditionListWithAnd): number | string { + if (item === this.media) { + return 'media'; + } + + return -1; + } + + at(index: number | string) { + if (index === 'media') { + return this.media; + } + } + + walk(cb: (entry: { node: MediaConditionWithoutOrWalkerEntry, parent: MediaConditionWithoutOrWalkerParent }, index: number | string) => boolean) { + if (cb({ node: this.media, parent: this }, 'media') === false) { + return false; + } + + return this.media.walk(cb); + } +} + +export type MediaConditionWithoutOrWalkerEntry = MediaConditionListWithAndWalkerEntry | MediaNotWalkerEntry | MediaNot | MediaConditionListWithAnd; +export type MediaConditionWithoutOrWalkerParent = MediaConditionListWithAndWalkerParent | MediaNotWalkerParent | MediaConditionWithoutOr; diff --git a/packages/media-query-list-parser/src/nodes/media-condition.ts b/packages/media-query-list-parser/src/nodes/media-condition.ts index fa276ee92..b29ccd613 100644 --- a/packages/media-query-list-parser/src/nodes/media-condition.ts +++ b/packages/media-query-list-parser/src/nodes/media-condition.ts @@ -1,5 +1,5 @@ -import { MediaConditionListWithAnd, MediaConditionListWithOr } from './media-condition-list'; -import { MediaNot } from './media-not'; +import { MediaConditionListWithAnd, MediaConditionListWithAndWalkerEntry, MediaConditionListWithAndWalkerParent, MediaConditionListWithOr, MediaConditionListWithOrWalkerEntry, MediaConditionListWithOrWalkerParent } from './media-condition-list'; +import { MediaNot, MediaNotWalkerEntry, MediaNotWalkerParent } from './media-not'; export class MediaCondition { type = 'media-condition'; @@ -10,7 +10,36 @@ export class MediaCondition { this.media = media; } + tokens() { + return this.media.tokens(); + } + toString() { return this.media.toString(); } + + indexOf(item: MediaNot | MediaConditionListWithAnd | MediaConditionListWithOr): number | string { + if (item === this.media) { + return 'media'; + } + + return -1; + } + + at(index: number | string) { + if (index === 'media') { + return this.media; + } + } + + walk(cb: (entry: { node: MediaConditionWalkerEntry, parent: MediaConditionWalkerParent }, index: number | string) => boolean) { + if (cb({ node: this.media, parent: this }, 'media') === false) { + return false; + } + + return this.media.walk(cb); + } } + +export type MediaConditionWalkerEntry = MediaNotWalkerEntry | MediaConditionListWithAndWalkerEntry | MediaConditionListWithOrWalkerEntry | MediaNot | MediaConditionListWithAnd | MediaConditionListWithOr; +export type MediaConditionWalkerParent = MediaNotWalkerParent | MediaConditionListWithAndWalkerParent | MediaConditionListWithOrWalkerParent | MediaCondition; diff --git a/packages/media-query-list-parser/src/nodes/media-feature-name.ts b/packages/media-query-list-parser/src/nodes/media-feature-name.ts index f15ccbde0..451adefd4 100644 --- a/packages/media-query-list-parser/src/nodes/media-feature-name.ts +++ b/packages/media-query-list-parser/src/nodes/media-feature-name.ts @@ -4,25 +4,39 @@ import { isToken, stringify } from '@csstools/css-tokenizer'; export class MediaFeatureName { type = 'mf-name'; - value: ComponentValue; + name: ComponentValue; - constructor(value: ComponentValue) { - this.value = value; + constructor(name: ComponentValue) { + this.name = name; } tokens() { - if (isToken(this.value)) { - return this.value; + if (isToken(this.name)) { + return this.name; } - return this.value.tokens(); + return this.name.tokens(); } toString() { - if (isToken(this.value)) { - return stringify(this.value); + if (isToken(this.name)) { + return stringify(this.name); } - return this.value.toString(); + return this.name.toString(); + } + + indexOf(item: ComponentValue): number | string { + if (item === this.name) { + return 'name'; + } + + return -1; + } + + at(index: number | string) { + if (index === 'name') { + return this.name; + } } } diff --git a/packages/media-query-list-parser/src/nodes/media-feature-plain.ts b/packages/media-query-list-parser/src/nodes/media-feature-plain.ts index 90fd309e4..104de5819 100644 --- a/packages/media-query-list-parser/src/nodes/media-feature-plain.ts +++ b/packages/media-query-list-parser/src/nodes/media-feature-plain.ts @@ -1,7 +1,6 @@ -import { ComponentValue, ContainerNode } from '@csstools/css-parser-algorithms'; import { stringify, TokenColon } from '@csstools/css-tokenizer'; import { MediaFeatureName } from './media-feature-name'; -import { MediaFeatureValue } from './media-feature-value'; +import { MediaFeatureValue, MediaFeatureValueWalkerEntry, MediaFeatureValueWalkerParent } from './media-feature-value'; export class MediaFeaturePlain { type = 'mf-plain'; @@ -28,11 +27,36 @@ export class MediaFeaturePlain { return this.name.toString() + stringify(this.colon) + this.value.toString(); } - walk(cb: (entry: { node: ComponentValue | MediaFeatureValue, parent: ContainerNode | MediaFeaturePlain | MediaFeatureValue }, index: number) => boolean) { - if (cb({ node: this.value, parent: this }, 0) === false) { + indexOf(item: MediaFeatureName | MediaFeatureValue): number | string { + if (item === this.name) { + return 'name'; + } + + if (item === this.value) { + return 'value'; + } + + return -1; + } + + at(index: number | string) { + if (index === 'name') { + return this.name; + } + + if (index === 'value') { + return this.value; + } + } + + walk(cb: (entry: { node: MediaFeaturePlainWalkerEntry, parent: MediaFeaturePlainWalkerParent }, index: number | string) => boolean) { + if (cb({ node: this.value, parent: this }, 'value') === false) { return false; } return this.value.walk(cb); } } + +export type MediaFeaturePlainWalkerEntry = MediaFeatureValueWalkerEntry | MediaFeatureValue; +export type MediaFeaturePlainWalkerParent = MediaFeatureValueWalkerParent | MediaFeaturePlain; diff --git a/packages/media-query-list-parser/src/nodes/media-feature-range.ts b/packages/media-query-list-parser/src/nodes/media-feature-range.ts index df9cdf053..45a144df4 100644 --- a/packages/media-query-list-parser/src/nodes/media-feature-range.ts +++ b/packages/media-query-list-parser/src/nodes/media-feature-range.ts @@ -1,8 +1,7 @@ -import { ComponentValue, ContainerNode } from '@csstools/css-parser-algorithms'; import { stringify, TokenDelim } from '@csstools/css-tokenizer'; import { comparisonFromTokens } from './media-feature-comparison'; import { MediaFeatureName } from './media-feature-name'; -import { MediaFeatureValue } from './media-feature-value'; +import { MediaFeatureValue, MediaFeatureValueWalkerEntry, MediaFeatureValueWalkerParent } from './media-feature-value'; export type MediaFeatureRange = MediaFeatureRangeNameValue | MediaFeatureRangeValueName | @@ -38,8 +37,30 @@ export class MediaFeatureRangeNameValue { return this.name.toString() + stringify(...this.operator) + this.value.toString(); } - walk(cb: (entry: { node: ComponentValue | MediaFeatureValue, parent: ContainerNode | MediaFeatureValue | MediaFeatureRange }, index: number) => boolean) { - if (cb({ node: this.value, parent: this }, 0) === false) { + indexOf(item: MediaFeatureName | MediaFeatureValue): number | string { + if (item === this.name) { + return 'name'; + } + + if (item === this.value) { + return 'value'; + } + + return -1; + } + + at(index: number | string) { + if (index === 'name') { + return this.name; + } + + if (index === 'value') { + return this.value; + } + } + + walk(cb: (entry: { node: MediaFeatureRangeWalkerEntry, parent: MediaFeatureRangeWalkerParent }, index: number | string) => boolean) { + if (cb({ node: this.value, parent: this }, 'value') === false) { return false; } @@ -78,8 +99,30 @@ export class MediaFeatureRangeValueName { return this.value.toString() + stringify(...this.operator) + this.name.toString(); } - walk(cb: (entry: { node: ComponentValue | MediaFeatureValue, parent: ContainerNode | MediaFeatureValue | MediaFeatureRange }, index: number) => boolean) { - if (cb({ node: this.value, parent: this }, 0) === false) { + indexOf(item: MediaFeatureName | MediaFeatureValue): number | string { + if (item === this.name) { + return 'name'; + } + + if (item === this.value) { + return 'value'; + } + + return -1; + } + + at(index: number | string) { + if (index === 'name') { + return this.name; + } + + if (index === 'value') { + return this.value; + } + } + + walk(cb: (entry: { node: MediaFeatureRangeWalkerEntry, parent: MediaFeatureRangeWalkerParent }, index: number | string) => boolean) { + if (cb({ node: this.value, parent: this }, 'value') === false) { return false; } @@ -128,8 +171,38 @@ export class MediaFeatureRangeLowHigh { return this.low.toString() + stringify(...this.lowOperator) + this.name.toString() + stringify(...this.highOperator) + this.high.toString(); } - walk(cb: (entry: { node: ComponentValue | MediaFeatureValue, parent: ContainerNode | MediaFeatureValue | MediaFeatureRange }, index: number) => boolean) { - if (cb({ node: this.low, parent: this }, 0) === false) { + indexOf(item: MediaFeatureName | MediaFeatureValue): number | string { + if (item === this.name) { + return 'name'; + } + + if (item === this.low) { + return 'low'; + } + + if (item === this.high) { + return 'high'; + } + + return -1; + } + + at(index: number | string) { + if (index === 'name') { + return this.name; + } + + if (index === 'low') { + return this.low; + } + + if (index === 'high') { + return this.high; + } + } + + walk(cb: (entry: { node: MediaFeatureRangeWalkerEntry, parent: MediaFeatureRangeWalkerParent }, index: number | string) => boolean) { + if (cb({ node: this.low, parent: this }, 'low') === false) { return false; } @@ -139,7 +212,7 @@ export class MediaFeatureRangeLowHigh { } } - if (cb({ node: this.high, parent: this }, 0) === false) { + if (cb({ node: this.high, parent: this }, 'high') === false) { return false; } @@ -190,8 +263,38 @@ export class MediaFeatureRangeHighLow { return this.high.toString() + stringify(...this.highOperator) + this.name.toString() + stringify(...this.lowOperator) + this.low.toString(); } - walk(cb: (entry: { node: ComponentValue | MediaFeatureValue, parent: ContainerNode | MediaFeatureValue | MediaFeatureRange }, index: number) => boolean) { - if (cb({ node: this.high, parent: this }, 0) === false) { + indexOf(item: MediaFeatureName | MediaFeatureValue): number | string { + if (item === this.name) { + return 'name'; + } + + if (item === this.low) { + return 'low'; + } + + if (item === this.high) { + return 'high'; + } + + return -1; + } + + at(index: number | string) { + if (index === 'name') { + return this.name; + } + + if (index === 'low') { + return this.low; + } + + if (index === 'high') { + return this.high; + } + } + + walk(cb: (entry: { node: MediaFeatureRangeWalkerEntry, parent: MediaFeatureRangeWalkerParent }, index: number | string) => boolean) { + if (cb({ node: this.high, parent: this }, 'high') === false) { return false; } @@ -201,7 +304,7 @@ export class MediaFeatureRangeHighLow { } } - if (cb({ node: this.low, parent: this }, 0) === false) { + if (cb({ node: this.low, parent: this }, 'low') === false) { return false; } @@ -212,3 +315,6 @@ export class MediaFeatureRangeHighLow { } } } + +export type MediaFeatureRangeWalkerEntry = MediaFeatureValueWalkerEntry | MediaFeatureValue; +export type MediaFeatureRangeWalkerParent = MediaFeatureValueWalkerParent | MediaFeatureRange; diff --git a/packages/media-query-list-parser/src/nodes/media-feature-value.ts b/packages/media-query-list-parser/src/nodes/media-feature-value.ts index 61e05380d..bf07cbb3c 100644 --- a/packages/media-query-list-parser/src/nodes/media-feature-value.ts +++ b/packages/media-query-list-parser/src/nodes/media-feature-value.ts @@ -26,8 +26,22 @@ export class MediaFeatureValue { return this.value.toString(); } - walk(cb: (entry: { node: ComponentValue, parent: ContainerNode | MediaFeatureValue }, index: number) => boolean) { - if (cb({ node: this.value, parent: this }, 0) === false) { + indexOf(item: ComponentValue): number | string { + if (item === this.value) { + return 'value'; + } + + return -1; + } + + at(index: number | string) { + if (index === 'value') { + return this.value; + } + } + + walk(cb: (entry: { node: MediaFeatureValueWalkerEntry, parent: MediaFeatureValueWalkerParent }, index: number | string) => boolean) { + if (cb({ node: this.value, parent: this }, 'value') === false) { return false; } @@ -36,3 +50,6 @@ export class MediaFeatureValue { } } } + +export type MediaFeatureValueWalkerEntry = ComponentValue; +export type MediaFeatureValueWalkerParent = ContainerNode | MediaFeatureValue; diff --git a/packages/media-query-list-parser/src/nodes/media-feature.ts b/packages/media-query-list-parser/src/nodes/media-feature.ts index ae69aaa27..60fcc21eb 100644 --- a/packages/media-query-list-parser/src/nodes/media-feature.ts +++ b/packages/media-query-list-parser/src/nodes/media-feature.ts @@ -1,23 +1,57 @@ import { CSSToken, stringify } from '@csstools/css-tokenizer'; import { MediaFeatureBoolean } from './media-feature-boolean'; -import { MediaFeaturePlain } from './media-feature-plain'; -import { MediaFeatureRange } from './media-feature-range'; +import { MediaFeaturePlain, MediaFeaturePlainWalkerEntry, MediaFeaturePlainWalkerParent } from './media-feature-plain'; +import { MediaFeatureRange, MediaFeatureRangeWalkerEntry, MediaFeatureRangeWalkerParent } from './media-feature-range'; export class MediaFeature { type = 'media-feature'; feature: MediaFeaturePlain | MediaFeatureBoolean | MediaFeatureRange; + before: Array; + after: Array; - startToken: CSSToken; - endToken: CSSToken; - - constructor(feature: MediaFeaturePlain | MediaFeatureBoolean | MediaFeatureRange, startToken: CSSToken, endToken: CSSToken) { - this.startToken = startToken; - this.endToken = endToken; + constructor(feature: MediaFeaturePlain | MediaFeatureBoolean | MediaFeatureRange, before: Array = [], after: Array = []) { this.feature = feature; + this.before = before; + this.after = after; + } + + tokens() { + return [ + ...this.before, + ...this.feature.tokens(), + ...this.after, + ]; } toString() { - return stringify(this.startToken) + this.feature.toString() + stringify(this.endToken); + return stringify(...this.before) + this.feature.toString() + stringify(...this.after); + } + + indexOf(item: MediaFeaturePlain | MediaFeatureBoolean | MediaFeatureRange): number | string { + if (item === this.feature) { + return 'feature'; + } + + return -1; + } + + at(index: number | string) { + if (index === 'feature') { + return this.feature; + } + } + + walk(cb: (entry: { node: MediaFeatureWalkerEntry, parent: MediaFeatureWalkerParent }, index: number | string) => boolean) { + if (cb({ node: this.feature, parent: this }, 'feature') === false) { + return false; + } + + if ('walk' in this.feature) { + return this.feature.walk(cb); + } } } + +export type MediaFeatureWalkerEntry = MediaFeaturePlainWalkerEntry | MediaFeatureRangeWalkerEntry | MediaFeaturePlain | MediaFeatureBoolean | MediaFeatureRange; +export type MediaFeatureWalkerParent = MediaFeaturePlainWalkerParent | MediaFeatureRangeWalkerParent | MediaFeature; diff --git a/packages/media-query-list-parser/src/nodes/media-in-parens.ts b/packages/media-query-list-parser/src/nodes/media-in-parens.ts index 495e89448..f937357ab 100644 --- a/packages/media-query-list-parser/src/nodes/media-in-parens.ts +++ b/packages/media-query-list-parser/src/nodes/media-in-parens.ts @@ -7,62 +7,44 @@ import { MediaFeature } from './media-feature'; export class MediaInParens { type = 'media-in-parens'; - startToken?: CSSToken; - endToken?: CSSToken; media: MediaCondition | MediaFeature | GeneralEnclosed; + before: Array; + after: Array; - constructor(media: MediaCondition | MediaFeature | GeneralEnclosed, startToken?: CSSToken, endToken?: CSSToken) { - this.startToken = startToken; - this.endToken = endToken; + constructor(media: MediaCondition | MediaFeature | GeneralEnclosed, before: Array = [], after: Array = []) { this.media = media; + this.before = before; + this.after = after; } tokens() { - if (this.media.type === 'general-enclosed') { - return this.media.tokens(); - } - - if (this.media.type === 'media-feature') { - return this.media.tokens(); - } - - if (this.media.type === 'media-condition') { - if (!this.startToken || !this.endToken) { - throw new Error('Failed to list tokens for "media-in-parens" with "media-condition"'); - } - - return [ - this.startToken, - ...this.media.tokens(), - this.endToken, - ]; - } - - throw new Error('Failed to list tokens for "media-in-parens"'); + return [ + ...this.before, + ...this.media.tokens(), + ...this.after, + ]; } toString() { - if (this.media.type === 'general-enclosed') { - return this.media.toString(); - } + return stringify(...this.before) + this.media.toString() + stringify(...this.after); + } - if (this.media.type === 'media-feature') { - return this.media.toString(); + indexOf(item: MediaCondition | MediaFeature | GeneralEnclosed): number | string { + if (item === this.media) { + return 'media'; } - if (this.media.type === 'media-condition') { - if (!this.startToken || !this.endToken) { - throw new Error('Failed to stringify "media-in-parens" with "media-condition"'); - } + return -1; + } - return stringify(this.startToken) + this.media.toString() + stringify(this.endToken); + at(index: number | string) { + if (index === 'media') { + return this.media; } - - throw new Error('Failed to stringify "media-in-parens"'); } - walk(cb: (entry: { node: ComponentValue | MediaCondition | MediaFeature | GeneralEnclosed, parent: ContainerNode | MediaInParens | GeneralEnclosed }, index: number) => boolean) { - if (cb({ node: this.media, parent: this }, 0) === false) { + walk(cb: (entry: { node: MediaInParensWalkerEntry, parent: MediaInParensWalkerParent }, index: number | string) => boolean) { + if (cb({ node: this.media, parent: this }, 'media') === false) { return false; } @@ -71,3 +53,6 @@ export class MediaInParens { } } } + +export type MediaInParensWalkerEntry = ComponentValue | MediaCondition | MediaFeature | GeneralEnclosed; +export type MediaInParensWalkerParent = ContainerNode | MediaInParens | GeneralEnclosed; diff --git a/packages/media-query-list-parser/src/nodes/media-not.ts b/packages/media-query-list-parser/src/nodes/media-not.ts index c6b8d9b88..2a9dae586 100644 --- a/packages/media-query-list-parser/src/nodes/media-not.ts +++ b/packages/media-query-list-parser/src/nodes/media-not.ts @@ -1,15 +1,50 @@ -import { MediaInParens } from './media-in-parens'; +import { CSSToken, stringify } from '@csstools/css-tokenizer'; +import { MediaInParens, MediaInParensWalkerEntry, MediaInParensWalkerParent } from './media-in-parens'; export class MediaNot { type = 'media-not'; + modifier: Array; media: MediaInParens; - constructor(media: MediaInParens) { + constructor(modifier: Array, media: MediaInParens) { + this.modifier = modifier; this.media = media; } + tokens() { + return [ + ...this.modifier, + ...this.media.tokens(), + ]; + } + toString() { - return 'not ' + this.media.toString(); + return stringify(...this.modifier) + this.media.toString(); + } + + indexOf(item: MediaInParens): number | string { + if (item === this.media) { + return 'media'; + } + + return -1; + } + + at(index: number | string) { + if (index === 'media') { + return this.media; + } + } + + walk(cb: (entry: { node: MediaNotWalkerEntry, parent: MediaNotWalkerParent }, index: number | string) => boolean) { + if (cb({ node: this.media, parent: this }, 'media') === false) { + return false; + } + + return this.media.walk(cb); } } + +export type MediaNotWalkerEntry = MediaInParensWalkerEntry | MediaInParens; +export type MediaNotWalkerParent = MediaInParensWalkerParent | MediaNot; diff --git a/packages/media-query-list-parser/src/nodes/media-or.ts b/packages/media-query-list-parser/src/nodes/media-or.ts index f2b6779d3..c3945fe2f 100644 --- a/packages/media-query-list-parser/src/nodes/media-or.ts +++ b/packages/media-query-list-parser/src/nodes/media-or.ts @@ -1,15 +1,50 @@ -import { MediaInParens } from './media-in-parens'; +import { CSSToken, stringify } from '@csstools/css-tokenizer'; +import { MediaInParens, MediaInParensWalkerEntry, MediaInParensWalkerParent } from './media-in-parens'; export class MediaOr { type = 'media-or'; + modifier: Array; media: MediaInParens; - constructor(media: MediaInParens) { + constructor(modifier: Array, media: MediaInParens) { + this.modifier = modifier; this.media = media; } + tokens() { + return [ + ...this.modifier, + ...this.media.tokens(), + ]; + } + toString() { - return 'or ' + this.media.toString(); + return stringify(...this.modifier) + this.media.toString(); + } + + indexOf(item: MediaInParens): number | string { + if (item === this.media) { + return 'media'; + } + + return -1; + } + + at(index: number | string) { + if (index === 'media') { + return this.media; + } + } + + walk(cb: (entry: { node: MediaOrWalkerEntry, parent: MediaOrWalkerParent }, index: number | string) => boolean) { + if (cb({ node: this.media, parent: this }, 'media') === false) { + return false; + } + + return this.media.walk(cb); } } + +export type MediaOrWalkerEntry = MediaInParensWalkerEntry | MediaInParens; +export type MediaOrWalkerParent = MediaInParensWalkerParent | MediaOr; diff --git a/packages/media-query-list-parser/src/nodes/media-query.ts b/packages/media-query-list-parser/src/nodes/media-query.ts index 5ab50cfec..ad2780997 100644 --- a/packages/media-query-list-parser/src/nodes/media-query.ts +++ b/packages/media-query-list-parser/src/nodes/media-query.ts @@ -1,40 +1,71 @@ -import { MediaCondition } from './media-condition'; -import { MediaConditionWithoutOr } from './media-condition-without-or-or'; -import { MediaQueryModifier } from './media-query-modifier'; -import { MediaType } from './media-type'; +import { CSSToken, stringify } from '@csstools/css-tokenizer'; +import { MediaCondition, MediaConditionWalkerEntry, MediaConditionWalkerParent } from './media-condition'; +import { MediaConditionWithoutOr, MediaConditionWithoutOrWalkerEntry, MediaConditionWithoutOrWalkerParent } from './media-condition-without-or'; export type MediaQuery = MediaQueryWithType | MediaQueryWithoutType; export class MediaQueryWithType { type = 'media-query-with-type'; - modifier?: MediaQueryModifier; - mediaType: MediaType; - media?: MediaConditionWithoutOr; + modifier: Array; + mediaType: Array; + media: MediaConditionWithoutOr | null = null; - constructor(modifier: MediaQueryModifier | null, mediaType: MediaType, media: MediaConditionWithoutOr | null) { + constructor(modifier: Array, mediaType: Array, media?: MediaConditionWithoutOr | null) { this.modifier = modifier; this.mediaType = mediaType; this.media = media; } + tokens() { + if (this.media) { + return [ + ...this.modifier, + ...this.mediaType, + ...this.media.tokens(), + ]; + } + + return [ + ...this.modifier, + ...this.mediaType, + ]; + } + toString() { - if (this.modifier && this.media) { - return `${this.modifier} ${this.mediaType} and ${this.media.toString()}`; + if (this.media) { + return stringify(...this.modifier) + stringify(...this.mediaType) + this.media.toString(); } - if (this.modifier) { - return `${this.modifier} ${this.mediaType}`; + return stringify(...this.modifier) + stringify(...this.mediaType); + } + + indexOf(item: MediaConditionWithoutOr): number | string { + if (item === this.media) { + return 'media'; } - if (this.media) { - return `${this.mediaType} and ${this.media.toString()}`; + return -1; + } + + at(index: number | string) { + if (index === 'media') { + return this.media; } + } - return this.mediaType; + walk(cb: (entry: { node: MediaQueryWithTypeWalkerEntry, parent: MediaQueryWithTypeWalkerParent }, index: number | string) => boolean) { + if (cb({ node: this.media, parent: this }, 'media') === false) { + return false; + } + + return this.media.walk(cb); } } +export type MediaQueryWithTypeWalkerEntry = MediaConditionWithoutOrWalkerEntry | MediaConditionWithoutOr; +export type MediaQueryWithTypeWalkerParent = MediaConditionWithoutOrWalkerParent | MediaQueryWithType; + export class MediaQueryWithoutType { type = 'media-query-without-type'; @@ -44,7 +75,36 @@ export class MediaQueryWithoutType { this.media = media; } + tokens() { + return this.media.tokens(); + } + toString() { return this.media.toString(); } + + indexOf(item: MediaCondition): number | string { + if (item === this.media) { + return 'media'; + } + + return -1; + } + + at(index: number | string) { + if (index === 'media') { + return this.media; + } + } + + walk(cb: (entry: { node: MediaQueryWithoutTypeWalkerEntry, parent: MediaQueryWithoutTypeWalkerParent }, index: number | string) => boolean) { + if (cb({ node: this.media, parent: this }, 'media') === false) { + return false; + } + + return this.media.walk(cb); + } } + +export type MediaQueryWithoutTypeWalkerEntry = MediaConditionWalkerEntry | MediaCondition; +export type MediaQueryWithoutTypeWalkerParent = MediaConditionWalkerParent | MediaQueryWithoutType; diff --git a/packages/media-query-list-parser/src/parser/parse.ts b/packages/media-query-list-parser/src/parser/parse.ts index e96aa49ef..c050d65f6 100644 --- a/packages/media-query-list-parser/src/parser/parse.ts +++ b/packages/media-query-list-parser/src/parser/parse.ts @@ -1,5 +1,6 @@ -import { parseCommaSeparatedListOfComponentValues } from '@csstools/css-parser-algorithms'; -import { tokenizer } from '@csstools/css-tokenizer'; +import { ComponentValue, parseCommaSeparatedListOfComponentValues, TokenNode } from '@csstools/css-parser-algorithms'; +import { tokenizer, TokenType } from '@csstools/css-tokenizer'; +import { GeneralEnclosed } from '../nodes/general-enclosed'; export function parse(source: string) { const onParseError = (err) => { @@ -21,9 +22,52 @@ export function parse(source: string) { tokens.push(t.nextToken()); // EOF-token } - const result = parseCommaSeparatedListOfComponentValues(tokens, { + const parsed = parseCommaSeparatedListOfComponentValues(tokens, { onParseError: onParseError, }); - return result; + const mediaQueryList = parsed.map((componentValuesList) => { + const result = []; + + const lastSliceIndex = 0; + for (let i = 0; i < componentValuesList.length; i++) { + const componentValue = componentValuesList[i]; + if (componentValue.type === 'whitespace' || componentValue.type === 'comment') { + continue; + } + + if (componentValue.type === 'function') { + result.push(new GeneralEnclosed(componentValue)); + } + } + + }); + + return mediaQueryList; +} + +function consumeMediaQueryWithType(componentValuesList: Array) { + const modifier: Array = []; + const mediaType: Array = []; + + const lastSliceIndex = 0; + for (let i = 0; i < componentValuesList.length; i++) { + const componentValue = componentValuesList[i]; + if (componentValue.type === 'whitespace' || componentValue.type === 'comment') { + continue; + } + + if (componentValue.type === 'token') { + const token = (componentValue as TokenNode).value; + + switch (token[0]) { + case TokenType.Ident: + + break; + + default: + break; + } + } + } } diff --git a/packages/media-query-list-parser/test/test.mjs b/packages/media-query-list-parser/test/test.mjs index 0ddae5dc7..cd5d20ff0 100644 --- a/packages/media-query-list-parser/test/test.mjs +++ b/packages/media-query-list-parser/test/test.mjs @@ -2,8 +2,13 @@ import { parse } from '@csstools/media-query-list-parser'; parse('(/* a comment */foo ) something else'); -parse('((min-width: 300px) and (prefers-color-scheme:/* a comment */dark))').forEach((mediaQuery) => { +parse('not screen and ((min-width: 300px) and (prefers-color-scheme:/* a comment */dark))').forEach((mediaQuery) => { mediaQuery.forEach((args) => { + if (!('walk' in args)) { + console.log(args); + return; + } + args.walk((a) => { console.log(a.node.type); console.log(a.node.toString()); From f327ff4c6745beab0f07b30ef1cef2b397abbd03 Mon Sep 17 00:00:00 2001 From: Romain Menke Date: Thu, 20 Oct 2022 17:26:35 +0200 Subject: [PATCH 04/35] wip --- packages/css-tokenizer/test/token/numeric.mjs | 20 +++ .../src/nodes/general-enclosed.ts | 9 - .../src/nodes/media-feature-name.ts | 53 ++++-- .../src/nodes/media-feature-plain.ts | 40 ++++- .../src/nodes/media-feature-value.ts | 161 ++++++++++++++++-- .../src/nodes/media-in-parens.ts | 13 +- .../src/parser/parse.ts | 31 +--- .../src/util/component-value-is.ts | 29 ++++ 8 files changed, 290 insertions(+), 66 deletions(-) create mode 100644 packages/media-query-list-parser/src/util/component-value-is.ts diff --git a/packages/css-tokenizer/test/token/numeric.mjs b/packages/css-tokenizer/test/token/numeric.mjs index 801564d6b..0ee6c1f0f 100644 --- a/packages/css-tokenizer/test/token/numeric.mjs +++ b/packages/css-tokenizer/test/token/numeric.mjs @@ -220,6 +220,26 @@ import { collectTokens } from '../util/collect-tokens.mjs'; ); } +{ + const t = tokenizer({ + css: '1e2 ', + }); + + assert.deepEqual( + collectTokens(t).slice(0, -1), + [ + [ + 'number-token', + '1e2', + 0, + 2, + { value: 100, type: 'number' }, + ], + ['whitespace-token', ' ', 3, 3, undefined], + ], + ); +} + { const t = tokenizer({ css: '12rem ', diff --git a/packages/media-query-list-parser/src/nodes/general-enclosed.ts b/packages/media-query-list-parser/src/nodes/general-enclosed.ts index d2e019ee4..eb7ca0774 100644 --- a/packages/media-query-list-parser/src/nodes/general-enclosed.ts +++ b/packages/media-query-list-parser/src/nodes/general-enclosed.ts @@ -1,5 +1,4 @@ import { ComponentValue, ContainerNode } from '@csstools/css-parser-algorithms'; -import { isToken, stringify } from '@csstools/css-tokenizer'; export class GeneralEnclosed { type = 'general-enclosed'; @@ -11,18 +10,10 @@ export class GeneralEnclosed { } tokens() { - if (isToken(this.value)) { - return this.value; - } - return this.value.tokens(); } toString() { - if (isToken(this.value)) { - return stringify(this.value); - } - return this.value.toString(); } diff --git a/packages/media-query-list-parser/src/nodes/media-feature-name.ts b/packages/media-query-list-parser/src/nodes/media-feature-name.ts index 451adefd4..70ec906c3 100644 --- a/packages/media-query-list-parser/src/nodes/media-feature-name.ts +++ b/packages/media-query-list-parser/src/nodes/media-feature-name.ts @@ -1,29 +1,30 @@ import { ComponentValue } from '@csstools/css-parser-algorithms'; -import { isToken, stringify } from '@csstools/css-tokenizer'; +import { CSSToken, stringify, TokenType } from '@csstools/css-tokenizer'; +import { isIdent } from '../util/component-value-is'; export class MediaFeatureName { type = 'mf-name'; name: ComponentValue; + before: Array; + after: Array; - constructor(name: ComponentValue) { + constructor(name: ComponentValue, before: Array = [], after: Array = []) { this.name = name; + this.before = before; + this.after = after; } tokens() { - if (isToken(this.name)) { - return this.name; - } - - return this.name.tokens(); + return [ + ...this.before, + ...this.name.tokens(), + ...this.after, + ]; } toString() { - if (isToken(this.name)) { - return stringify(this.name); - } - - return this.name.toString(); + return stringify(...this.before) + this.name.toString() + stringify(...this.after); } indexOf(item: ComponentValue): number | string { @@ -40,3 +41,31 @@ export class MediaFeatureName { } } } + +export function matchesMediaFeatureName(componentValues: Array) { + let singleIdentTokenIndex = -1; + + for (let i = 0; i < componentValues.length; i++) { + const componentValue = componentValues[i]; + if (componentValue.type === 'whitespace') { + continue; + } + + if (componentValue.type === 'comment') { + continue; + } + + if (isIdent(componentValue)) { + if (singleIdentTokenIndex !== -1) { + return -1; + } + + singleIdentTokenIndex = i; + continue; + } + + return -1; + } + + return singleIdentTokenIndex; +} diff --git a/packages/media-query-list-parser/src/nodes/media-feature-plain.ts b/packages/media-query-list-parser/src/nodes/media-feature-plain.ts index 104de5819..c57b216f2 100644 --- a/packages/media-query-list-parser/src/nodes/media-feature-plain.ts +++ b/packages/media-query-list-parser/src/nodes/media-feature-plain.ts @@ -1,6 +1,7 @@ -import { stringify, TokenColon } from '@csstools/css-tokenizer'; -import { MediaFeatureName } from './media-feature-name'; -import { MediaFeatureValue, MediaFeatureValueWalkerEntry, MediaFeatureValueWalkerParent } from './media-feature-value'; +import { ComponentValue } from '@csstools/css-parser-algorithms'; +import { CSSToken, stringify, TokenColon, TokenType } from '@csstools/css-tokenizer'; +import { matchesMediaFeatureName, MediaFeatureName } from './media-feature-name'; +import { matchesMediaFeatureValue, MediaFeatureValue, MediaFeatureValueWalkerEntry, MediaFeatureValueWalkerParent } from './media-feature-value'; export class MediaFeaturePlain { type = 'mf-plain'; @@ -60,3 +61,36 @@ export class MediaFeaturePlain { export type MediaFeaturePlainWalkerEntry = MediaFeatureValueWalkerEntry | MediaFeatureValue; export type MediaFeaturePlainWalkerParent = MediaFeatureValueWalkerParent | MediaFeaturePlain; + +export function matchesMediaFeaturePlain(componentValues: Array) { + let a: Array = []; + let b: Array = []; + + for (let i = 0; i < componentValues.length; i++) { + const componentValue = componentValues[i]; + if (componentValue.type === 'token') { + const token = componentValue.value as CSSToken; + if (token[0] === TokenType.Colon) { + a = componentValues.slice(0, i); + b = componentValues.slice(i + 1); + break; + } + } + } + + if (!a.length || !b.length) { + return -1; + } + + const aResult = matchesMediaFeatureName(a); + if (aResult === -1) { + return -1; + } + + const bResult = matchesMediaFeatureValue(b); + if (bResult === -1) { + return -1; + } + + return [aResult, bResult]; +} diff --git a/packages/media-query-list-parser/src/nodes/media-feature-value.ts b/packages/media-query-list-parser/src/nodes/media-feature-value.ts index bf07cbb3c..ae0661866 100644 --- a/packages/media-query-list-parser/src/nodes/media-feature-value.ts +++ b/packages/media-query-list-parser/src/nodes/media-feature-value.ts @@ -1,29 +1,42 @@ -import { ComponentValue, ContainerNode } from '@csstools/css-parser-algorithms'; -import { isToken, stringify } from '@csstools/css-tokenizer'; +import { ComponentValue, ContainerNode, FunctionNode, TokenNode } from '@csstools/css-parser-algorithms'; +import { CSSToken, stringify, TokenFunction, TokenType } from '@csstools/css-tokenizer'; +import { isDimension, isIdent, isNumber } from '../util/component-value-is'; export class MediaFeatureValue { type = 'mf-value'; - value: ComponentValue; + value: ComponentValue | Array; + before: Array; + after: Array; - constructor(value: ComponentValue) { + constructor(value: ComponentValue | Array, before: Array = [], after: Array = []) { this.value = value; + this.before = before; + this.after = after; } tokens() { - if (isToken(this.value)) { - return this.value; + if (Array.isArray(this.value)) { + return [ + ...this.before, + ...this.value.flatMap((x) => x.tokens()), + ...this.after, + ]; } - return this.value.tokens(); + return [ + ...this.before, + ...this.value.tokens(), + ...this.after, + ]; } toString() { - if (isToken(this.value)) { - return stringify(this.value); + if (Array.isArray(this.value)) { + return stringify(...this.before) + this.value.map((x) => x.toString()).join('') + stringify(...this.after); } - return this.value.toString(); + return stringify(...this.before) + this.value.toString() + stringify(...this.after); } indexOf(item: ComponentValue): number | string { @@ -51,5 +64,131 @@ export class MediaFeatureValue { } } -export type MediaFeatureValueWalkerEntry = ComponentValue; +export type MediaFeatureValueWalkerEntry = ComponentValue | Array; export type MediaFeatureValueWalkerParent = ContainerNode | MediaFeatureValue; + +export function matchesMediaFeatureValue(componentValues: Array) { + let candidateIndexStart = -1; + let candidateIndexEnd = -1; + + for (let i = 0; i < componentValues.length; i++) { + const componentValue = componentValues[i]; + if (componentValue.type === 'whitespace') { + continue; + } + + if (componentValue.type === 'comment') { + continue; + } + + if (candidateIndexStart !== -1) { + return -1; + } + + if (isNumber(componentValue)) { + const maybeRatio = matchesRatioExactly(componentValues.slice(i)); + if (maybeRatio !== -1) { + candidateIndexStart = maybeRatio[0]; + candidateIndexEnd = maybeRatio[1]; + i += maybeRatio[1] - maybeRatio[0]; + continue; + } + + candidateIndexStart = i; + candidateIndexEnd = i; + continue; + } + + if (isDimension(componentValue)) { + candidateIndexStart = i; + candidateIndexEnd = i; + continue; + } + + if (isIdent(componentValue)) { + candidateIndexStart = i; + candidateIndexEnd = i; + continue; + } + + return -1; + } + + return [candidateIndexStart, candidateIndexEnd]; +} + +export function matchesRatioExactly(componentValues: Array) { + let firstNumber = -1; + let secondNumber = -1; + + const result = matchesRatio(componentValues); + if (result === -1) { + return -1; + } + + firstNumber = result[0]; + secondNumber = result[1]; + + for (let i = secondNumber+1; i < componentValues.length; i++) { + const componentValue = componentValues[i]; + if (componentValue.type === 'whitespace') { + continue; + } + + if (componentValue.type === 'comment') { + continue; + } + + return -1; + } + + return [firstNumber, secondNumber]; +} + +export function matchesRatio(componentValues: Array) { + let firstNumber = -1; + let delim = -1; + + for (let i = 0; i < componentValues.length; i++) { + const componentValue = componentValues[i]; + if (componentValue.type === 'whitespace') { + continue; + } + + if (componentValue.type === 'comment') { + continue; + } + + if (componentValue.type === 'token') { + const token = componentValue.value as CSSToken; + if (token[0] === TokenType.Delim && token[4].value === '/') { + if (firstNumber === -1) { + return -1; + } + + if (delim !== -1) { + return -1; + } + + delim = i; + continue; + } + } + + if (isNumber(componentValue)) { + if (delim !== -1) { + return [firstNumber, i]; + } else if (firstNumber !== -1) { + return -1; + } else { + firstNumber = i; + continue; + } + } + + return -1; + } + + return -1; +} + diff --git a/packages/media-query-list-parser/src/nodes/media-in-parens.ts b/packages/media-query-list-parser/src/nodes/media-in-parens.ts index f937357ab..9d4d07626 100644 --- a/packages/media-query-list-parser/src/nodes/media-in-parens.ts +++ b/packages/media-query-list-parser/src/nodes/media-in-parens.ts @@ -1,8 +1,15 @@ -import { ComponentValue, ContainerNode } from '@csstools/css-parser-algorithms'; +import { ComponentValue, ContainerNode, TokenNode } from '@csstools/css-parser-algorithms'; import { CSSToken, stringify } from '@csstools/css-tokenizer'; import { GeneralEnclosed } from './general-enclosed'; +import { MediaAnd } from './media-and'; import { MediaCondition } from './media-condition'; +import { MediaConditionList } from './media-condition-list'; import { MediaFeature } from './media-feature'; +import { MediaFeatureBoolean } from './media-feature-boolean'; +import { MediaFeatureName } from './media-feature-name'; +import { MediaFeaturePlain } from './media-feature-plain'; +import { MediaFeatureRange } from './media-feature-range'; +import { MediaFeatureValue } from './media-feature-value'; export class MediaInParens { type = 'media-in-parens'; @@ -54,5 +61,5 @@ export class MediaInParens { } } -export type MediaInParensWalkerEntry = ComponentValue | MediaCondition | MediaFeature | GeneralEnclosed; -export type MediaInParensWalkerParent = ContainerNode | MediaInParens | GeneralEnclosed; +export type MediaInParensWalkerEntry = ComponentValue | Array | GeneralEnclosed | MediaAnd | MediaConditionList | MediaCondition | MediaFeatureBoolean | MediaFeatureName | MediaFeaturePlain | MediaFeatureRange | MediaFeatureValue | MediaFeature | GeneralEnclosed | MediaInParens; +export type MediaInParensWalkerParent = ContainerNode | GeneralEnclosed | MediaAnd | MediaConditionList | MediaCondition | MediaFeatureBoolean | MediaFeatureName | MediaFeaturePlain | MediaFeatureRange | MediaFeatureValue | MediaFeature | GeneralEnclosed | MediaInParens; diff --git a/packages/media-query-list-parser/src/parser/parse.ts b/packages/media-query-list-parser/src/parser/parse.ts index c050d65f6..af7834d2e 100644 --- a/packages/media-query-list-parser/src/parser/parse.ts +++ b/packages/media-query-list-parser/src/parser/parse.ts @@ -1,6 +1,7 @@ -import { ComponentValue, parseCommaSeparatedListOfComponentValues, TokenNode } from '@csstools/css-parser-algorithms'; -import { tokenizer, TokenType } from '@csstools/css-tokenizer'; +import { ComponentValue, parseCommaSeparatedListOfComponentValues, SimpleBlockNode, TokenNode } from '@csstools/css-parser-algorithms'; +import { CSSToken, tokenizer, TokenType } from '@csstools/css-tokenizer'; import { GeneralEnclosed } from '../nodes/general-enclosed'; +import { MediaFeatureName } from '../nodes/media-feature-name'; export function parse(source: string) { const onParseError = (err) => { @@ -45,29 +46,3 @@ export function parse(source: string) { return mediaQueryList; } - -function consumeMediaQueryWithType(componentValuesList: Array) { - const modifier: Array = []; - const mediaType: Array = []; - - const lastSliceIndex = 0; - for (let i = 0; i < componentValuesList.length; i++) { - const componentValue = componentValuesList[i]; - if (componentValue.type === 'whitespace' || componentValue.type === 'comment') { - continue; - } - - if (componentValue.type === 'token') { - const token = (componentValue as TokenNode).value; - - switch (token[0]) { - case TokenType.Ident: - - break; - - default: - break; - } - } - } -} diff --git a/packages/media-query-list-parser/src/util/component-value-is.ts b/packages/media-query-list-parser/src/util/component-value-is.ts new file mode 100644 index 000000000..d94b5433f --- /dev/null +++ b/packages/media-query-list-parser/src/util/component-value-is.ts @@ -0,0 +1,29 @@ +import { ComponentValue, FunctionNode } from '@csstools/css-parser-algorithms'; +import { CSSToken, TokenFunction, TokenType } from '@csstools/css-tokenizer'; + +export function isNumber(componentValue: ComponentValue) { + if ( + (componentValue.type === 'token' && (componentValue.value as CSSToken)[0] === TokenType.Number) || + (componentValue.type === 'function' && ((componentValue as FunctionNode).name as TokenFunction)[4].value === 'calc') + ) { + return true; + } + + return false; +} + +export function isDimension(componentValue: ComponentValue) { + if (componentValue.type === 'token' && (componentValue.value as CSSToken)[0] === TokenType.Dimension) { + return true; + } + + return false; +} + +export function isIdent(componentValue: ComponentValue) { + if (componentValue.type === 'token' && (componentValue.value as CSSToken)[0] === TokenType.Ident) { + return true; + } + + return false; +} From 343a2abf4af100100a2035850fa1e83b65ac860d Mon Sep 17 00:00:00 2001 From: Romain Menke Date: Fri, 21 Oct 2022 19:42:21 +0200 Subject: [PATCH 05/35] wip --- packages/css-tokenizer/test/token/basic.mjs | 25 ++++++++ .../src/nodes/media-feature-comparison.ts | 40 ++++++++++++- .../src/nodes/media-feature-name.ts | 22 +++++-- .../src/nodes/media-feature-plain.ts | 24 ++++---- .../src/nodes/media-feature-range.ts | 58 ++++++++++++++++++- .../src/nodes/media-feature-value.ts | 31 +++++++--- .../src/nodes/media-in-parens.ts | 2 +- .../src/util/component-value-is.ts | 26 ++++++++- 8 files changed, 200 insertions(+), 28 deletions(-) diff --git a/packages/css-tokenizer/test/token/basic.mjs b/packages/css-tokenizer/test/token/basic.mjs index 609e80713..46033d7d5 100644 --- a/packages/css-tokenizer/test/token/basic.mjs +++ b/packages/css-tokenizer/test/token/basic.mjs @@ -19,6 +19,31 @@ import { collectTokens } from '../util/collect-tokens.mjs'; ); } +{ + const t = tokenizer({ + css: 'foo { width: calc(-infinity) }', + }); + + assert.deepEqual( + collectTokens(t), + [ + ['ident-token', 'foo', 0, 2, { value: 'foo' }], + ['whitespace-token', ' ', 3, 3, undefined], + ['{-token', '{', 4, 4, undefined], + ['whitespace-token', ' ', 5, 5, undefined], + ['ident-token', 'width', 6, 10, { value: 'width' }], + ['colon-token', ':', 11, 11, undefined], + ['whitespace-token', ' ', 12, 12, undefined], + ['function-token', 'calc(', 13, 17, { value: 'calc' }], + ['ident-token', '-infinity', 18, 26, { value: '-infinity' }], + [')-token', ')', 27, 27, undefined], + ['whitespace-token', ' ', 28, 28, undefined], + ['}-token', '}', 29, 29, undefined], + ['EOF-token', '', -1, -1, undefined], + ], + ); +} + { const t = tokenizer({ css: '@import url(https://example.com/stylesheet.css) layer( base.tokens ) supports( display: grid ) not screen and ((400px <= width < 1024px) and (prefers-color-scheme: dark));', diff --git a/packages/media-query-list-parser/src/nodes/media-feature-comparison.ts b/packages/media-query-list-parser/src/nodes/media-feature-comparison.ts index 79a2ef9f4..cd02d772f 100644 --- a/packages/media-query-list-parser/src/nodes/media-feature-comparison.ts +++ b/packages/media-query-list-parser/src/nodes/media-feature-comparison.ts @@ -1,4 +1,5 @@ -import { TokenDelim, TokenType } from '@csstools/css-tokenizer'; +import { ComponentValue } from '@csstools/css-parser-algorithms'; +import { CSSToken, TokenDelim, TokenType } from '@csstools/css-tokenizer'; export enum MediaFeatureLT { LT = '<', @@ -16,6 +17,43 @@ export enum MediaFeatureEQ { export type MediaFeatureComparison = MediaFeatureLT | MediaFeatureGT | MediaFeatureEQ +export function matchesComparison(componentValues: Array): false | [number, number] { + let firstTokenIndex = -1; + + for (let i = 0; i < componentValues.length; i++) { + const componentValue = componentValues[i]; + if (componentValue.type === 'token') { + const token = componentValue.value as CSSToken; + if (token[0] === TokenType.Delim) { + if (token[4].value === MediaFeatureEQ.EQ) { + if (firstTokenIndex) { + return [firstTokenIndex, i]; + } + + firstTokenIndex = i; + continue; + } + if (token[4].value === MediaFeatureLT.LT) { + firstTokenIndex = i; + continue; + } + if (token[4].value === MediaFeatureGT.GT) { + firstTokenIndex = i; + continue; + } + } + } + + break; + } + + if (firstTokenIndex !== -1) { + return [firstTokenIndex, firstTokenIndex]; + } + + return false; +} + export function comparisonFromTokens(tokens: [TokenDelim, TokenDelim] | [TokenDelim]): MediaFeatureComparison | false { // eslint-disable-next-line @typescript-eslint/ban-ts-comment /* @ts-ignore */ diff --git a/packages/media-query-list-parser/src/nodes/media-feature-name.ts b/packages/media-query-list-parser/src/nodes/media-feature-name.ts index 70ec906c3..b700959b0 100644 --- a/packages/media-query-list-parser/src/nodes/media-feature-name.ts +++ b/packages/media-query-list-parser/src/nodes/media-feature-name.ts @@ -1,5 +1,5 @@ import { ComponentValue } from '@csstools/css-parser-algorithms'; -import { CSSToken, stringify, TokenType } from '@csstools/css-tokenizer'; +import { CSSToken, stringify } from '@csstools/css-tokenizer'; import { isIdent } from '../util/component-value-is'; export class MediaFeatureName { @@ -42,7 +42,7 @@ export class MediaFeatureName { } } -export function matchesMediaFeatureName(componentValues: Array) { +export function parseMediaFeatureName(componentValues: Array) { let singleIdentTokenIndex = -1; for (let i = 0; i < componentValues.length; i++) { @@ -57,15 +57,27 @@ export function matchesMediaFeatureName(componentValues: Array) if (isIdent(componentValue)) { if (singleIdentTokenIndex !== -1) { - return -1; + return false; } singleIdentTokenIndex = i; continue; } - return -1; + return false; + } + + if (singleIdentTokenIndex === -1) { + return false; } - return singleIdentTokenIndex; + return new MediaFeatureName( + componentValues[singleIdentTokenIndex], + componentValues.slice(0, singleIdentTokenIndex).flatMap((x) => { + return x.tokens(); + }), + componentValues.slice(singleIdentTokenIndex + 1).flatMap((x) => { + return x.tokens(); + }), + ); } diff --git a/packages/media-query-list-parser/src/nodes/media-feature-plain.ts b/packages/media-query-list-parser/src/nodes/media-feature-plain.ts index c57b216f2..2672270fb 100644 --- a/packages/media-query-list-parser/src/nodes/media-feature-plain.ts +++ b/packages/media-query-list-parser/src/nodes/media-feature-plain.ts @@ -1,7 +1,7 @@ import { ComponentValue } from '@csstools/css-parser-algorithms'; import { CSSToken, stringify, TokenColon, TokenType } from '@csstools/css-tokenizer'; -import { matchesMediaFeatureName, MediaFeatureName } from './media-feature-name'; -import { matchesMediaFeatureValue, MediaFeatureValue, MediaFeatureValueWalkerEntry, MediaFeatureValueWalkerParent } from './media-feature-value'; +import { parseMediaFeatureName, MediaFeatureName } from './media-feature-name'; +import { parseMediaFeatureValue, MediaFeatureValue, MediaFeatureValueWalkerEntry, MediaFeatureValueWalkerParent } from './media-feature-value'; export class MediaFeaturePlain { type = 'mf-plain'; @@ -62,9 +62,10 @@ export class MediaFeaturePlain { export type MediaFeaturePlainWalkerEntry = MediaFeatureValueWalkerEntry | MediaFeatureValue; export type MediaFeaturePlainWalkerParent = MediaFeatureValueWalkerParent | MediaFeaturePlain; -export function matchesMediaFeaturePlain(componentValues: Array) { +export function parseMediaFeaturePlain(componentValues: Array) { let a: Array = []; let b: Array = []; + let colon: TokenColon | null = null; for (let i = 0; i < componentValues.length; i++) { const componentValue = componentValues[i]; @@ -73,24 +74,25 @@ export function matchesMediaFeaturePlain(componentValues: Array) if (token[0] === TokenType.Colon) { a = componentValues.slice(0, i); b = componentValues.slice(i + 1); + colon = token; break; } } } if (!a.length || !b.length) { - return -1; + return false; } - const aResult = matchesMediaFeatureName(a); - if (aResult === -1) { - return -1; + const name = parseMediaFeatureName(a); + if (name === false) { + return false; } - const bResult = matchesMediaFeatureValue(b); - if (bResult === -1) { - return -1; + const value = parseMediaFeatureValue(b); + if (value === false) { + return false; } - return [aResult, bResult]; + return new MediaFeaturePlain(name, colon , value); } diff --git a/packages/media-query-list-parser/src/nodes/media-feature-range.ts b/packages/media-query-list-parser/src/nodes/media-feature-range.ts index 45a144df4..363906105 100644 --- a/packages/media-query-list-parser/src/nodes/media-feature-range.ts +++ b/packages/media-query-list-parser/src/nodes/media-feature-range.ts @@ -1,5 +1,6 @@ -import { stringify, TokenDelim } from '@csstools/css-tokenizer'; -import { comparisonFromTokens } from './media-feature-comparison'; +import { ComponentValue } from '@csstools/css-parser-algorithms'; +import { CSSToken, stringify, TokenDelim, TokenType } from '@csstools/css-tokenizer'; +import { comparisonFromTokens, matchesComparison } from './media-feature-comparison'; import { MediaFeatureName } from './media-feature-name'; import { MediaFeatureValue, MediaFeatureValueWalkerEntry, MediaFeatureValueWalkerParent } from './media-feature-value'; @@ -318,3 +319,56 @@ export class MediaFeatureRangeHighLow { export type MediaFeatureRangeWalkerEntry = MediaFeatureValueWalkerEntry | MediaFeatureValue; export type MediaFeatureRangeWalkerParent = MediaFeatureValueWalkerParent | MediaFeatureRange; + +export function matchesMediaFeaturePlain(componentValues: Array) { + let comparisonOne: false | [number, number] = false; + let comparisonTwo: false | [number, number] = false; + + for (let i = 0; i < componentValues.length; i++) { + const componentValue = componentValues[i]; + if (componentValue.type === 'token') { + const token = componentValue.value as CSSToken; + if (token[0] === TokenType.Delim) { + const comparison = matchesComparison(componentValues.slice(i)); + if (comparison !== false) { + if (comparisonOne === false) { + comparisonOne = [ + comparison[0] + i, + comparison[i] + i, + ]; + } else { + comparisonTwo = [ + comparison[0] + i, + comparison[i] + i, + ]; + break; + } + } + } + } + } + + if (comparisonOne === -1) { + return false; + } + + if (comparisonTwo === -1) { + return false; + } + + if (!a.length || !b.length) { + return -1; + } + + const aResult = matchesMediaFeatureName(a); + if (aResult === -1) { + return -1; + } + + const bResult = matchesMediaFeatureValue(b); + if (bResult === -1) { + return -1; + } + + return [aResult, bResult]; +} diff --git a/packages/media-query-list-parser/src/nodes/media-feature-value.ts b/packages/media-query-list-parser/src/nodes/media-feature-value.ts index ae0661866..25acb0ec2 100644 --- a/packages/media-query-list-parser/src/nodes/media-feature-value.ts +++ b/packages/media-query-list-parser/src/nodes/media-feature-value.ts @@ -1,5 +1,5 @@ -import { ComponentValue, ContainerNode, FunctionNode, TokenNode } from '@csstools/css-parser-algorithms'; -import { CSSToken, stringify, TokenFunction, TokenType } from '@csstools/css-tokenizer'; +import { ComponentValue, ContainerNode } from '@csstools/css-parser-algorithms'; +import { CSSToken, stringify, TokenType } from '@csstools/css-tokenizer'; import { isDimension, isIdent, isNumber } from '../util/component-value-is'; export class MediaFeatureValue { @@ -10,7 +10,12 @@ export class MediaFeatureValue { after: Array; constructor(value: ComponentValue | Array, before: Array = [], after: Array = []) { - this.value = value; + if (Array.isArray(value) && value.length === 1) { + this.value = value[0]; + } else { + this.value = value; + } + this.before = before; this.after = after; } @@ -67,7 +72,7 @@ export class MediaFeatureValue { export type MediaFeatureValueWalkerEntry = ComponentValue | Array; export type MediaFeatureValueWalkerParent = ContainerNode | MediaFeatureValue; -export function matchesMediaFeatureValue(componentValues: Array) { +export function parseMediaFeatureValue(componentValues: Array) { let candidateIndexStart = -1; let candidateIndexEnd = -1; @@ -82,7 +87,7 @@ export function matchesMediaFeatureValue(componentValues: Array) } if (candidateIndexStart !== -1) { - return -1; + return false; } if (isNumber(componentValue)) { @@ -111,10 +116,22 @@ export function matchesMediaFeatureValue(componentValues: Array) continue; } - return -1; + return false; + } + + if (candidateIndexStart === -1) { + return false; } - return [candidateIndexStart, candidateIndexEnd]; + return new MediaFeatureValue( + componentValues.slice(candidateIndexStart, candidateIndexEnd + 1), + componentValues.slice(0, candidateIndexStart).flatMap((x) => { + return x.tokens(); + }), + componentValues.slice(candidateIndexEnd + 1).flatMap((x) => { + return x.tokens(); + }), + ); } export function matchesRatioExactly(componentValues: Array) { diff --git a/packages/media-query-list-parser/src/nodes/media-in-parens.ts b/packages/media-query-list-parser/src/nodes/media-in-parens.ts index 9d4d07626..5b351b81d 100644 --- a/packages/media-query-list-parser/src/nodes/media-in-parens.ts +++ b/packages/media-query-list-parser/src/nodes/media-in-parens.ts @@ -1,4 +1,4 @@ -import { ComponentValue, ContainerNode, TokenNode } from '@csstools/css-parser-algorithms'; +import { ComponentValue, ContainerNode } from '@csstools/css-parser-algorithms'; import { CSSToken, stringify } from '@csstools/css-tokenizer'; import { GeneralEnclosed } from './general-enclosed'; import { MediaAnd } from './media-and'; diff --git a/packages/media-query-list-parser/src/util/component-value-is.ts b/packages/media-query-list-parser/src/util/component-value-is.ts index d94b5433f..c71b94ab5 100644 --- a/packages/media-query-list-parser/src/util/component-value-is.ts +++ b/packages/media-query-list-parser/src/util/component-value-is.ts @@ -1,5 +1,5 @@ import { ComponentValue, FunctionNode } from '@csstools/css-parser-algorithms'; -import { CSSToken, TokenFunction, TokenType } from '@csstools/css-tokenizer'; +import { CSSToken, TokenFunction, TokenIdent, TokenType } from '@csstools/css-tokenizer'; export function isNumber(componentValue: ComponentValue) { if ( @@ -12,6 +12,30 @@ export function isNumber(componentValue: ComponentValue) { return false; } +export function isNumericConstant(componentValue: ComponentValue) { + if (componentValue.type === 'token' && (componentValue.value as CSSToken)[0] === TokenType.Ident) { + const token = componentValue.value as TokenIdent; + const tokenValue = token[4].value.toLowerCase(); + if (tokenValue === 'infinity') { + return true; + } + if (tokenValue === '-infinity') { + return true; + } + if (tokenValue === 'nan') { + return true; + } + if (tokenValue === 'e') { + return true; + } + if (tokenValue === 'pi') { + return true; + } + } + + return false; +} + export function isDimension(componentValue: ComponentValue) { if (componentValue.type === 'token' && (componentValue.value as CSSToken)[0] === TokenType.Dimension) { return true; From 1c82f160fe54f997857e946e95e53dbc1f162049 Mon Sep 17 00:00:00 2001 From: Romain Menke Date: Sat, 22 Oct 2022 23:08:55 +0200 Subject: [PATCH 06/35] finish range --- .../src/nodes/media-feature-name.ts | 9 +- .../src/nodes/media-feature-range.ts | 289 +++++++++--------- 2 files changed, 153 insertions(+), 145 deletions(-) diff --git a/packages/media-query-list-parser/src/nodes/media-feature-name.ts b/packages/media-query-list-parser/src/nodes/media-feature-name.ts index b700959b0..b7f6612d6 100644 --- a/packages/media-query-list-parser/src/nodes/media-feature-name.ts +++ b/packages/media-query-list-parser/src/nodes/media-feature-name.ts @@ -1,5 +1,5 @@ -import { ComponentValue } from '@csstools/css-parser-algorithms'; -import { CSSToken, stringify } from '@csstools/css-tokenizer'; +import { ComponentValue, TokenNode } from '@csstools/css-parser-algorithms'; +import { CSSToken, stringify, TokenIdent } from '@csstools/css-tokenizer'; import { isIdent } from '../util/component-value-is'; export class MediaFeatureName { @@ -15,6 +15,11 @@ export class MediaFeatureName { this.after = after; } + getName() { + const token = (((this.name as TokenNode).value as CSSToken) as TokenIdent); + return token[4].value; + } + tokens() { return [ ...this.before, diff --git a/packages/media-query-list-parser/src/nodes/media-feature-range.ts b/packages/media-query-list-parser/src/nodes/media-feature-range.ts index 363906105..c1e957add 100644 --- a/packages/media-query-list-parser/src/nodes/media-feature-range.ts +++ b/packages/media-query-list-parser/src/nodes/media-feature-range.ts @@ -1,13 +1,12 @@ -import { ComponentValue } from '@csstools/css-parser-algorithms'; +import { ComponentValue, TokenNode } from '@csstools/css-parser-algorithms'; import { CSSToken, stringify, TokenDelim, TokenType } from '@csstools/css-tokenizer'; import { comparisonFromTokens, matchesComparison } from './media-feature-comparison'; -import { MediaFeatureName } from './media-feature-name'; -import { MediaFeatureValue, MediaFeatureValueWalkerEntry, MediaFeatureValueWalkerParent } from './media-feature-value'; +import { MediaFeatureName, parseMediaFeatureName } from './media-feature-name'; +import { MediaFeatureValue, MediaFeatureValueWalkerEntry, MediaFeatureValueWalkerParent, parseMediaFeatureValue } from './media-feature-value'; export type MediaFeatureRange = MediaFeatureRangeNameValue | MediaFeatureRangeValueName | - MediaFeatureRangeLowHigh | - MediaFeatureRangeHighLow; + MediaFeatureRangeValueNameValue; export class MediaFeatureRangeNameValue { type = 'mf-range-name-value'; @@ -133,43 +132,43 @@ export class MediaFeatureRangeValueName { } } -export class MediaFeatureRangeLowHigh { - type = 'mf-range-low-high'; +export class MediaFeatureRangeValueNameValue { + type = 'mf-range-value-name-value'; name: MediaFeatureName; - low: MediaFeatureValue; - lowOperator: [TokenDelim, TokenDelim] | [TokenDelim]; - high: MediaFeatureValue; - highOperator: [TokenDelim, TokenDelim] | [TokenDelim]; + valueOne: MediaFeatureValue; + valueOneOperator: [TokenDelim, TokenDelim] | [TokenDelim]; + valueTwo: MediaFeatureValue; + valueTwoOperator: [TokenDelim, TokenDelim] | [TokenDelim]; - constructor(name: MediaFeatureName, low: MediaFeatureValue, lowOperator: [TokenDelim, TokenDelim] | [TokenDelim], high: MediaFeatureValue, highOperator: [TokenDelim, TokenDelim] | [TokenDelim]) { + constructor(name: MediaFeatureName, valueOne: MediaFeatureValue, valueOneOperator: [TokenDelim, TokenDelim] | [TokenDelim], valueTwo: MediaFeatureValue, valueTwoOperator: [TokenDelim, TokenDelim] | [TokenDelim]) { this.name = name; - this.low = low; - this.lowOperator = lowOperator; - this.high = high; - this.highOperator = highOperator; + this.valueOne = valueOne; + this.valueOneOperator = valueOneOperator; + this.valueTwo = valueTwo; + this.valueTwoOperator = valueTwoOperator; } - lowOperatorKind() { - return comparisonFromTokens(this.lowOperator); + valueOneOperatorKind() { + return comparisonFromTokens(this.valueOneOperator); } - highOperatorKind() { - return comparisonFromTokens(this.highOperator); + valueTwoOperatorKind() { + return comparisonFromTokens(this.valueTwoOperator); } tokens() { return [ - ...this.low.tokens(), - ...this.lowOperator, + ...this.valueOne.tokens(), + ...this.valueOneOperator, ...this.name.tokens(), - ...this.highOperator, - ...this.high.tokens(), + ...this.valueTwoOperator, + ...this.valueTwo.tokens(), ]; } toString() { - return this.low.toString() + stringify(...this.lowOperator) + this.name.toString() + stringify(...this.highOperator) + this.high.toString(); + return this.valueOne.toString() + stringify(...this.valueOneOperator) + this.name.toString() + stringify(...this.valueTwoOperator) + this.valueTwo.toString(); } indexOf(item: MediaFeatureName | MediaFeatureValue): number | string { @@ -177,12 +176,12 @@ export class MediaFeatureRangeLowHigh { return 'name'; } - if (item === this.low) { - return 'low'; + if (item === this.valueOne) { + return 'valueOne'; } - if (item === this.high) { - return 'high'; + if (item === this.valueTwo) { + return 'valueTwo'; } return -1; @@ -193,124 +192,32 @@ export class MediaFeatureRangeLowHigh { return this.name; } - if (index === 'low') { - return this.low; + if (index === 'valueOne') { + return this.valueOne; } - if (index === 'high') { - return this.high; + if (index === 'valueTwo') { + return this.valueTwo; } } walk(cb: (entry: { node: MediaFeatureRangeWalkerEntry, parent: MediaFeatureRangeWalkerParent }, index: number | string) => boolean) { - if (cb({ node: this.low, parent: this }, 'low') === false) { + if (cb({ node: this.valueOne, parent: this }, 'valueOne') === false) { return false; } - if ('walk' in this.low) { - if (this.low.walk(cb) === false) { + if ('walk' in this.valueOne) { + if (this.valueOne.walk(cb) === false) { return false; } } - if (cb({ node: this.high, parent: this }, 'high') === false) { + if (cb({ node: this.valueTwo, parent: this }, 'valueTwo') === false) { return false; } - if ('walk' in this.high) { - if (this.high.walk(cb) === false) { - return false; - } - } - } -} - -export class MediaFeatureRangeHighLow { - type = 'mf-range-high-low'; - - name: MediaFeatureName; - low: MediaFeatureValue; - lowOperator: [TokenDelim, TokenDelim] | [TokenDelim]; - high: MediaFeatureValue; - highOperator: [TokenDelim, TokenDelim] | [TokenDelim]; - - constructor(name: MediaFeatureName, high: MediaFeatureValue, highOperator: [TokenDelim, TokenDelim] | [TokenDelim], low: MediaFeatureValue, lowOperator: [TokenDelim, TokenDelim] | [TokenDelim]) { - this.name = name; - this.low = low; - this.lowOperator = lowOperator; - this.high = high; - this.highOperator = highOperator; - } - - lowOperatorKind() { - return comparisonFromTokens(this.lowOperator); - } - - highOperatorKind() { - return comparisonFromTokens(this.highOperator); - } - - tokens() { - return [ - ...this.high.tokens(), - ...this.highOperator, - ...this.name.tokens(), - ...this.lowOperator, - ...this.low.tokens(), - ]; - } - - toString() { - return this.high.toString() + stringify(...this.highOperator) + this.name.toString() + stringify(...this.lowOperator) + this.low.toString(); - } - - indexOf(item: MediaFeatureName | MediaFeatureValue): number | string { - if (item === this.name) { - return 'name'; - } - - if (item === this.low) { - return 'low'; - } - - if (item === this.high) { - return 'high'; - } - - return -1; - } - - at(index: number | string) { - if (index === 'name') { - return this.name; - } - - if (index === 'low') { - return this.low; - } - - if (index === 'high') { - return this.high; - } - } - - walk(cb: (entry: { node: MediaFeatureRangeWalkerEntry, parent: MediaFeatureRangeWalkerParent }, index: number | string) => boolean) { - if (cb({ node: this.high, parent: this }, 'high') === false) { - return false; - } - - if ('walk' in this.high) { - if (this.high.walk(cb) === false) { - return false; - } - } - - if (cb({ node: this.low, parent: this }, 'low') === false) { - return false; - } - - if ('walk' in this.low) { - if (this.low.walk(cb) === false) { + if ('walk' in this.valueTwo) { + if (this.valueTwo.walk(cb) === false) { return false; } } @@ -348,27 +255,123 @@ export function matchesMediaFeaturePlain(componentValues: Array) } } - if (comparisonOne === -1) { + if (comparisonOne === false) { return false; } - if (comparisonTwo === -1) { - return false; + const comparisonTokensOne: [TokenDelim, TokenDelim] | [TokenDelim] = [ + (componentValues[comparisonOne[0]] as TokenNode).value as TokenDelim, + ]; + if (comparisonOne[0] !== comparisonOne[1]) { + comparisonTokensOne.push( + (componentValues[comparisonOne[1]] as TokenNode).value as TokenDelim, + ); } - if (!a.length || !b.length) { - return -1; + if (comparisonTwo === false) { + const a = componentValues.slice(0, comparisonOne[0] - 1); + const b = componentValues.slice(comparisonOne[1] + 1); + + const nameA = parseMediaFeatureName(a); + const nameB = parseMediaFeatureName(b); + + if (!nameA && !nameB) { + return false; + } + + if ( + (nameA && !nameB) || + nameA && mediaDescriptors.has(nameA.getName().toLowerCase()) + ) { + const value = parseMediaFeatureValue(b); + if (!value) { + return false; + } + + return new MediaFeatureRangeNameValue(nameA, comparisonTokensOne, value); + } + + if ( + (!nameA && nameB) || + nameB && mediaDescriptors.has(nameB.getName().toLowerCase()) + ) { + const value = parseMediaFeatureValue(a); + if (!value) { + return false; + } + + return new MediaFeatureRangeValueName(nameB, comparisonTokensOne, value); + } + + return false; } - const aResult = matchesMediaFeatureName(a); - if (aResult === -1) { - return -1; + const comparisonTokensTwo: [TokenDelim, TokenDelim] | [TokenDelim] = [ + (componentValues[comparisonTwo[0]] as TokenNode).value as TokenDelim, + ]; + if (comparisonTwo[0] !== comparisonTwo[1]) { + comparisonTokensTwo.push( + (componentValues[comparisonTwo[1]] as TokenNode).value as TokenDelim, + ); } - const bResult = matchesMediaFeatureValue(b); - if (bResult === -1) { - return -1; + const a = componentValues.slice(0, comparisonOne[0] - 1); + const b = componentValues.slice(comparisonOne[1] + 1, comparisonTwo[0] - 1); + const c = componentValues.slice(comparisonTwo[1] + 1); + + const valueA = parseMediaFeatureValue(a); + const nameB = parseMediaFeatureName(b); + const valueC = parseMediaFeatureValue(c); + + if (!valueA || !nameB || !valueC) { + return false; } - return [aResult, bResult]; + return new MediaFeatureRangeValueNameValue( + nameB, + valueA, + comparisonTokensOne, + valueC, + comparisonTokensTwo, + ); } + +export const mediaDescriptors = new Set([ + 'any-hover', + 'any-pointer', + 'aspect-ratio', + 'color', + 'color-gamut', + 'color-index', + 'device-aspect-ratio', + 'device-height', + 'device-width', + 'display-mode', + 'dynamic-range', + 'environment-blending', + 'forced-colors', + 'grid', + 'height', + 'horizontal-viewport-segments', + 'hover', + 'inverted-colors', + 'monochrome', + 'nav-controls', + 'orientation', + 'overflow-block', + 'overflow-inline', + 'pointer', + 'prefers-color-scheme', + 'prefers-contrast', + 'prefers-reduced-data', + 'prefers-reduced-motion', + 'prefers-reduced-transparency', + 'resolution', + 'scan', + 'scripting', + 'update', + 'vertical-viewport-segments', + 'video-color-gamut', + 'video-dynamic-range', + 'width', +]); From 23fd5684bd698b0b26b3923e88193a1f466bc774 Mon Sep 17 00:00:00 2001 From: Romain Menke Date: Sun, 23 Oct 2022 10:29:58 +0200 Subject: [PATCH 07/35] more work --- .../consume-component-block-function.ts | 24 +++++++--- .../src/nodes/media-condition.ts | 5 ++ .../src/nodes/media-feature-boolean.ts | 12 ++++- .../src/nodes/media-feature-comparison.ts | 4 +- .../src/nodes/media-feature-name.ts | 6 +-- .../src/nodes/media-feature-plain.ts | 4 +- .../src/nodes/media-feature-range.ts | 6 +-- .../src/nodes/media-feature-value.ts | 6 +-- .../src/nodes/media-feature.ts | 46 +++++++++++++------ .../src/nodes/media-in-parens.ts | 26 +++++++++-- .../src/parser/consume/consume-boolean.ts | 46 ------------------- .../src/parser/consume/consume-plain.ts | 44 ------------------ .../src/parser/consume/consume-value.ts | 16 ------- .../src/parser/parse.ts | 17 +++---- .../src/util/component-value-is.ts | 12 ++--- .../media-query-list-parser/test/test.mjs | 4 +- rollup/configs/externals.js | 2 + 17 files changed, 115 insertions(+), 165 deletions(-) delete mode 100644 packages/media-query-list-parser/src/parser/consume/consume-boolean.ts delete mode 100644 packages/media-query-list-parser/src/parser/consume/consume-plain.ts delete mode 100644 packages/media-query-list-parser/src/parser/consume/consume-value.ts diff --git a/packages/css-parser-algorithms/src/consume/consume-component-block-function.ts b/packages/css-parser-algorithms/src/consume/consume-component-block-function.ts index 20c618ec6..525b852b6 100644 --- a/packages/css-parser-algorithms/src/consume/consume-component-block-function.ts +++ b/packages/css-parser-algorithms/src/consume/consume-component-block-function.ts @@ -5,6 +5,16 @@ export type ContainerNode = FunctionNode | SimpleBlockNode; export type ComponentValue = FunctionNode | SimpleBlockNode | WhitespaceNode | CommentNode | TokenNode | UnclosedSimpleBlockNode | UnclosedFunctionNode; +export enum ComponentValueType { + Function = 'function', + SimpleBlock = 'simple-block', + Whitespace = 'whitespace', + Comment = 'comment', + Token = 'token', + UnclosedFunction = 'unclosed-function', + UnclosedSimpleBlock = 'unclosed-simple-block' +} + // https://www.w3.org/TR/css-syntax-3/#consume-a-component-value export function consumeComponentValue(ctx: Context, tokens: Array): { advance: number, node: ComponentValue } { const i = 0; @@ -56,7 +66,7 @@ export function consumeComponentValue(ctx: Context, tokens: Array): { } export class FunctionNode { - type = 'function'; + type: ComponentValueType = ComponentValueType.Function; name: TokenFunction; endToken: CSSToken; @@ -185,7 +195,7 @@ export function consumeFunction(ctx: Context, tokens: Array): { advanc } export class SimpleBlockNode { - type = 'simple-block'; + type: ComponentValueType = ComponentValueType.SimpleBlock; startToken: CSSToken; endToken: CSSToken; @@ -315,7 +325,7 @@ export function consumeSimpleBlock(ctx: Context, tokens: Array): { adv } export class WhitespaceNode { - type = 'whitespace'; + type: ComponentValueType = ComponentValueType.Whitespace; value: Array; @@ -350,7 +360,7 @@ export function consumeWhitespace(ctx: Context, tokens: Array): { adva } export class CommentNode { - type = 'comment'; + type: ComponentValueType = ComponentValueType.Comment; value: CSSToken; @@ -404,7 +414,7 @@ export function consumeAllCommentsAndWhitespace(ctx: Context, tokens: Array; @@ -442,7 +452,7 @@ export class UnclosedFunctionNode { } export class UnclosedSimpleBlockNode { - type = 'unclosed-simple-block'; + type: ComponentValueType = ComponentValueType.UnclosedSimpleBlock; value: Array; diff --git a/packages/media-query-list-parser/src/nodes/media-condition.ts b/packages/media-query-list-parser/src/nodes/media-condition.ts index b29ccd613..d3e4d9881 100644 --- a/packages/media-query-list-parser/src/nodes/media-condition.ts +++ b/packages/media-query-list-parser/src/nodes/media-condition.ts @@ -1,3 +1,4 @@ +import { ComponentValue } from '@csstools/css-parser-algorithms'; import { MediaConditionListWithAnd, MediaConditionListWithAndWalkerEntry, MediaConditionListWithAndWalkerParent, MediaConditionListWithOr, MediaConditionListWithOrWalkerEntry, MediaConditionListWithOrWalkerParent } from './media-condition-list'; import { MediaNot, MediaNotWalkerEntry, MediaNotWalkerParent } from './media-not'; @@ -43,3 +44,7 @@ export class MediaCondition { export type MediaConditionWalkerEntry = MediaNotWalkerEntry | MediaConditionListWithAndWalkerEntry | MediaConditionListWithOrWalkerEntry | MediaNot | MediaConditionListWithAnd | MediaConditionListWithOr; export type MediaConditionWalkerParent = MediaNotWalkerParent | MediaConditionListWithAndWalkerParent | MediaConditionListWithOrWalkerParent | MediaCondition; + +export function parseMediaCondition(componentValues: Array): false | MediaCondition { + return false; +} diff --git a/packages/media-query-list-parser/src/nodes/media-feature-boolean.ts b/packages/media-query-list-parser/src/nodes/media-feature-boolean.ts index bb960da2a..ad3ab9e65 100644 --- a/packages/media-query-list-parser/src/nodes/media-feature-boolean.ts +++ b/packages/media-query-list-parser/src/nodes/media-feature-boolean.ts @@ -1,5 +1,15 @@ -import { MediaFeatureName } from './media-feature-name'; +import { ComponentValue } from '@csstools/css-parser-algorithms'; +import { MediaFeatureName, parseMediaFeatureName } from './media-feature-name'; export class MediaFeatureBoolean extends MediaFeatureName { type = 'mf-boolean'; } + +export function parseMediaFeatureBoolean(componentValues: Array) { + const mediaFeatureName = parseMediaFeatureName(componentValues); + if (mediaFeatureName === false) { + return mediaFeatureName; + } + + return new MediaFeatureBoolean(mediaFeatureName.name, mediaFeatureName.before, mediaFeatureName.after); +} diff --git a/packages/media-query-list-parser/src/nodes/media-feature-comparison.ts b/packages/media-query-list-parser/src/nodes/media-feature-comparison.ts index cd02d772f..580a5dce2 100644 --- a/packages/media-query-list-parser/src/nodes/media-feature-comparison.ts +++ b/packages/media-query-list-parser/src/nodes/media-feature-comparison.ts @@ -1,4 +1,4 @@ -import { ComponentValue } from '@csstools/css-parser-algorithms'; +import { ComponentValue, ComponentValueType } from '@csstools/css-parser-algorithms'; import { CSSToken, TokenDelim, TokenType } from '@csstools/css-tokenizer'; export enum MediaFeatureLT { @@ -22,7 +22,7 @@ export function matchesComparison(componentValues: Array): false for (let i = 0; i < componentValues.length; i++) { const componentValue = componentValues[i]; - if (componentValue.type === 'token') { + if (componentValue.type === ComponentValueType.Token) { const token = componentValue.value as CSSToken; if (token[0] === TokenType.Delim) { if (token[4].value === MediaFeatureEQ.EQ) { diff --git a/packages/media-query-list-parser/src/nodes/media-feature-name.ts b/packages/media-query-list-parser/src/nodes/media-feature-name.ts index b7f6612d6..102669bd4 100644 --- a/packages/media-query-list-parser/src/nodes/media-feature-name.ts +++ b/packages/media-query-list-parser/src/nodes/media-feature-name.ts @@ -1,4 +1,4 @@ -import { ComponentValue, TokenNode } from '@csstools/css-parser-algorithms'; +import { ComponentValue, ComponentValueType, TokenNode } from '@csstools/css-parser-algorithms'; import { CSSToken, stringify, TokenIdent } from '@csstools/css-tokenizer'; import { isIdent } from '../util/component-value-is'; @@ -52,11 +52,11 @@ export function parseMediaFeatureName(componentValues: Array) { for (let i = 0; i < componentValues.length; i++) { const componentValue = componentValues[i]; - if (componentValue.type === 'whitespace') { + if (componentValue.type === ComponentValueType.Whitespace) { continue; } - if (componentValue.type === 'comment') { + if (componentValue.type === ComponentValueType.Comment) { continue; } diff --git a/packages/media-query-list-parser/src/nodes/media-feature-plain.ts b/packages/media-query-list-parser/src/nodes/media-feature-plain.ts index 2672270fb..1eaaff61c 100644 --- a/packages/media-query-list-parser/src/nodes/media-feature-plain.ts +++ b/packages/media-query-list-parser/src/nodes/media-feature-plain.ts @@ -1,4 +1,4 @@ -import { ComponentValue } from '@csstools/css-parser-algorithms'; +import { ComponentValue, ComponentValueType } from '@csstools/css-parser-algorithms'; import { CSSToken, stringify, TokenColon, TokenType } from '@csstools/css-tokenizer'; import { parseMediaFeatureName, MediaFeatureName } from './media-feature-name'; import { parseMediaFeatureValue, MediaFeatureValue, MediaFeatureValueWalkerEntry, MediaFeatureValueWalkerParent } from './media-feature-value'; @@ -69,7 +69,7 @@ export function parseMediaFeaturePlain(componentValues: Array) { for (let i = 0; i < componentValues.length; i++) { const componentValue = componentValues[i]; - if (componentValue.type === 'token') { + if (componentValue.type === ComponentValueType.Token) { const token = componentValue.value as CSSToken; if (token[0] === TokenType.Colon) { a = componentValues.slice(0, i); diff --git a/packages/media-query-list-parser/src/nodes/media-feature-range.ts b/packages/media-query-list-parser/src/nodes/media-feature-range.ts index c1e957add..70d1ed1aa 100644 --- a/packages/media-query-list-parser/src/nodes/media-feature-range.ts +++ b/packages/media-query-list-parser/src/nodes/media-feature-range.ts @@ -1,4 +1,4 @@ -import { ComponentValue, TokenNode } from '@csstools/css-parser-algorithms'; +import { ComponentValue, ComponentValueType, TokenNode } from '@csstools/css-parser-algorithms'; import { CSSToken, stringify, TokenDelim, TokenType } from '@csstools/css-tokenizer'; import { comparisonFromTokens, matchesComparison } from './media-feature-comparison'; import { MediaFeatureName, parseMediaFeatureName } from './media-feature-name'; @@ -227,13 +227,13 @@ export class MediaFeatureRangeValueNameValue { export type MediaFeatureRangeWalkerEntry = MediaFeatureValueWalkerEntry | MediaFeatureValue; export type MediaFeatureRangeWalkerParent = MediaFeatureValueWalkerParent | MediaFeatureRange; -export function matchesMediaFeaturePlain(componentValues: Array) { +export function parseMediaFeatureRange(componentValues: Array) { let comparisonOne: false | [number, number] = false; let comparisonTwo: false | [number, number] = false; for (let i = 0; i < componentValues.length; i++) { const componentValue = componentValues[i]; - if (componentValue.type === 'token') { + if (componentValue.type === ComponentValueType.Token) { const token = componentValue.value as CSSToken; if (token[0] === TokenType.Delim) { const comparison = matchesComparison(componentValues.slice(i)); diff --git a/packages/media-query-list-parser/src/nodes/media-feature-value.ts b/packages/media-query-list-parser/src/nodes/media-feature-value.ts index 25acb0ec2..89be76753 100644 --- a/packages/media-query-list-parser/src/nodes/media-feature-value.ts +++ b/packages/media-query-list-parser/src/nodes/media-feature-value.ts @@ -1,4 +1,4 @@ -import { ComponentValue, ContainerNode } from '@csstools/css-parser-algorithms'; +import { ComponentValue, ComponentValueType, ContainerNode } from '@csstools/css-parser-algorithms'; import { CSSToken, stringify, TokenType } from '@csstools/css-tokenizer'; import { isDimension, isIdent, isNumber } from '../util/component-value-is'; @@ -78,11 +78,11 @@ export function parseMediaFeatureValue(componentValues: Array) { for (let i = 0; i < componentValues.length; i++) { const componentValue = componentValues[i]; - if (componentValue.type === 'whitespace') { + if (componentValue.type === ComponentValueType.Whitespace) { continue; } - if (componentValue.type === 'comment') { + if (componentValue.type === ComponentValueType.Comment) { continue; } diff --git a/packages/media-query-list-parser/src/nodes/media-feature.ts b/packages/media-query-list-parser/src/nodes/media-feature.ts index 60fcc21eb..a9b026ce6 100644 --- a/packages/media-query-list-parser/src/nodes/media-feature.ts +++ b/packages/media-query-list-parser/src/nodes/media-feature.ts @@ -1,31 +1,24 @@ -import { CSSToken, stringify } from '@csstools/css-tokenizer'; -import { MediaFeatureBoolean } from './media-feature-boolean'; -import { MediaFeaturePlain, MediaFeaturePlainWalkerEntry, MediaFeaturePlainWalkerParent } from './media-feature-plain'; -import { MediaFeatureRange, MediaFeatureRangeWalkerEntry, MediaFeatureRangeWalkerParent } from './media-feature-range'; +import { SimpleBlockNode } from '@csstools/css-parser-algorithms'; +import { TokenType } from '@csstools/css-tokenizer'; +import { MediaFeatureBoolean, parseMediaFeatureBoolean } from './media-feature-boolean'; +import { MediaFeaturePlain, MediaFeaturePlainWalkerEntry, MediaFeaturePlainWalkerParent, parseMediaFeaturePlain } from './media-feature-plain'; +import { MediaFeatureRange, MediaFeatureRangeWalkerEntry, MediaFeatureRangeWalkerParent, parseMediaFeatureRange } from './media-feature-range'; export class MediaFeature { type = 'media-feature'; feature: MediaFeaturePlain | MediaFeatureBoolean | MediaFeatureRange; - before: Array; - after: Array; - constructor(feature: MediaFeaturePlain | MediaFeatureBoolean | MediaFeatureRange, before: Array = [], after: Array = []) { + constructor(feature: MediaFeaturePlain | MediaFeatureBoolean | MediaFeatureRange) { this.feature = feature; - this.before = before; - this.after = after; } tokens() { - return [ - ...this.before, - ...this.feature.tokens(), - ...this.after, - ]; + return this.feature.tokens(); } toString() { - return stringify(...this.before) + this.feature.toString() + stringify(...this.after); + return this.feature.toString(); } indexOf(item: MediaFeaturePlain | MediaFeatureBoolean | MediaFeatureRange): number | string { @@ -55,3 +48,26 @@ export class MediaFeature { export type MediaFeatureWalkerEntry = MediaFeaturePlainWalkerEntry | MediaFeatureRangeWalkerEntry | MediaFeaturePlain | MediaFeatureBoolean | MediaFeatureRange; export type MediaFeatureWalkerParent = MediaFeaturePlainWalkerParent | MediaFeatureRangeWalkerParent | MediaFeature; + +export function parseMediaFeature(simpleBlock: SimpleBlockNode) { + if (simpleBlock.startToken[0] !== TokenType.OpenParen) { + return false; + } + + const boolean = parseMediaFeatureBoolean(simpleBlock.value); + if (boolean !== false) { + return new MediaFeature(boolean); + } + + const plain = parseMediaFeaturePlain(simpleBlock.value); + if (plain !== false) { + return new MediaFeature(plain); + } + + const range = parseMediaFeatureRange(simpleBlock.value); + if (range !== false) { + return new MediaFeature(range); + } + + return false; +} diff --git a/packages/media-query-list-parser/src/nodes/media-in-parens.ts b/packages/media-query-list-parser/src/nodes/media-in-parens.ts index 5b351b81d..b5da64cee 100644 --- a/packages/media-query-list-parser/src/nodes/media-in-parens.ts +++ b/packages/media-query-list-parser/src/nodes/media-in-parens.ts @@ -1,10 +1,10 @@ -import { ComponentValue, ContainerNode } from '@csstools/css-parser-algorithms'; -import { CSSToken, stringify } from '@csstools/css-tokenizer'; +import { ComponentValue, ContainerNode, SimpleBlockNode } from '@csstools/css-parser-algorithms'; +import { CSSToken, stringify, TokenType } from '@csstools/css-tokenizer'; import { GeneralEnclosed } from './general-enclosed'; import { MediaAnd } from './media-and'; -import { MediaCondition } from './media-condition'; +import { MediaCondition, parseMediaCondition } from './media-condition'; import { MediaConditionList } from './media-condition-list'; -import { MediaFeature } from './media-feature'; +import { MediaFeature, parseMediaFeature } from './media-feature'; import { MediaFeatureBoolean } from './media-feature-boolean'; import { MediaFeatureName } from './media-feature-name'; import { MediaFeaturePlain } from './media-feature-plain'; @@ -63,3 +63,21 @@ export class MediaInParens { export type MediaInParensWalkerEntry = ComponentValue | Array | GeneralEnclosed | MediaAnd | MediaConditionList | MediaCondition | MediaFeatureBoolean | MediaFeatureName | MediaFeaturePlain | MediaFeatureRange | MediaFeatureValue | MediaFeature | GeneralEnclosed | MediaInParens; export type MediaInParensWalkerParent = ContainerNode | GeneralEnclosed | MediaAnd | MediaConditionList | MediaCondition | MediaFeatureBoolean | MediaFeatureName | MediaFeaturePlain | MediaFeatureRange | MediaFeatureValue | MediaFeature | GeneralEnclosed | MediaInParens; + +export function parseMediaInParens(simpleBlock: SimpleBlockNode) { + if (simpleBlock.startToken[0] !== TokenType.OpenParen) { + return false; + } + + const feature = parseMediaFeature(simpleBlock); + if (feature !== false) { + return new MediaInParens(feature); + } + + const condition = parseMediaCondition(simpleBlock.value); + if (condition !== false) { + return new MediaInParens(condition, [simpleBlock.startToken], [simpleBlock.endToken]); + } + + return new GeneralEnclosed(simpleBlock); +} diff --git a/packages/media-query-list-parser/src/parser/consume/consume-boolean.ts b/packages/media-query-list-parser/src/parser/consume/consume-boolean.ts deleted file mode 100644 index fc7081d5d..000000000 --- a/packages/media-query-list-parser/src/parser/consume/consume-boolean.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { CSSToken, TokenIdent, TokenType } from '@csstools/css-tokenizer'; -import { MediaFeatureBoolean } from '../../nodes/media-feature-boolean'; - -export function consumeBoolean(tokens: Array): { node: MediaFeatureBoolean, tokens: Array } | null { - let ident : TokenIdent|null = null; - - for (let i = 0; i < tokens.length; i++) { - const token = tokens[i]; - - if (i === 0) { - if (token[0] === TokenType.OpenParen) { - continue; - } - - return null; - } - - if (token[0] === TokenType.Comment || token[0] === TokenType.Whitespace) { - continue; - } - - if (token[0] === TokenType.Ident) { - if (!ident) { - ident = token as TokenIdent; - continue; - } - - return null; - } - - if (token[0] === TokenType.CloseParen) { - if (ident) { - const node = new MediaFeatureBoolean(tokens.slice(0, i + 1)); - - return { - node: node, - tokens: tokens.slice(i + 1), - }; - } - - return null; - } - - return null; - } -} diff --git a/packages/media-query-list-parser/src/parser/consume/consume-plain.ts b/packages/media-query-list-parser/src/parser/consume/consume-plain.ts deleted file mode 100644 index 3a84cd380..000000000 --- a/packages/media-query-list-parser/src/parser/consume/consume-plain.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { CSSToken, TokenIdent, TokenType } from '@csstools/css-tokenizer'; -import { MediaFeatureName } from '../../nodes/media-feature-name'; -import { MediaFeaturePlain } from '../../nodes/media-feature-plain'; -import { consumeValue } from './consume-value'; - -export function consumePlain(tokens: Array): { node: MediaFeaturePlain, tokens: Array } | null { - let name: MediaFeatureName | null = null; - const value: MediaFeatureValue | null = null; - - for (let i = 0; i < tokens.length; i++) { - const token = tokens[i]; - - if (i === 0) { - if (token[0] === TokenType.OpenParen) { - continue; - } else { - return null; - } - } - - if (token[0] === TokenType.CloseParen) { - continue; - } - - if (token[0] === TokenType.Comment || token[0] === TokenType.Whitespace) { - continue; - } - - if (token[0] === TokenType.Ident) { - if (!name) { - name = new MediaFeatureName(tokens.slice(0, i + 1)); - continue; - } else { - return null; - } - } - - if (token[0] === TokenType.Delim && token[1] === ':' && name) { - const value = consumeValue(tokens.slice(i + 1)); - } - - return null; - } -} diff --git a/packages/media-query-list-parser/src/parser/consume/consume-value.ts b/packages/media-query-list-parser/src/parser/consume/consume-value.ts deleted file mode 100644 index 991bc9b63..000000000 --- a/packages/media-query-list-parser/src/parser/consume/consume-value.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { CSSToken } from '@csstools/css-tokenizer'; -import { parseComponentValue } from '@csstools/css-parser-algorithms'; -import { MediaFeatureValue } from '../../nodes/media-feature-value'; - -export function consumeValue(tokens: Array): { node: MediaFeatureValue, tokens: Array } | null { - const result = parseComponentValue(tokens, { - onParseError(err) { - throw new Error(JSON.stringify(err)); - }, - }); - - return { - node: new MediaFeatureValue(result), - tokens: tokens.slice(result.tokens().length + 1), - }; -} diff --git a/packages/media-query-list-parser/src/parser/parse.ts b/packages/media-query-list-parser/src/parser/parse.ts index af7834d2e..bbabc6c93 100644 --- a/packages/media-query-list-parser/src/parser/parse.ts +++ b/packages/media-query-list-parser/src/parser/parse.ts @@ -1,7 +1,6 @@ -import { ComponentValue, parseCommaSeparatedListOfComponentValues, SimpleBlockNode, TokenNode } from '@csstools/css-parser-algorithms'; -import { CSSToken, tokenizer, TokenType } from '@csstools/css-tokenizer'; -import { GeneralEnclosed } from '../nodes/general-enclosed'; -import { MediaFeatureName } from '../nodes/media-feature-name'; +import { ComponentValueType, parseCommaSeparatedListOfComponentValues, SimpleBlockNode } from '@csstools/css-parser-algorithms'; +import { tokenizer } from '@csstools/css-tokenizer'; +import { parseMediaInParens } from '../nodes/media-in-parens'; export function parse(source: string) { const onParseError = (err) => { @@ -30,18 +29,14 @@ export function parse(source: string) { const mediaQueryList = parsed.map((componentValuesList) => { const result = []; - const lastSliceIndex = 0; for (let i = 0; i < componentValuesList.length; i++) { const componentValue = componentValuesList[i]; - if (componentValue.type === 'whitespace' || componentValue.type === 'comment') { - continue; - } - - if (componentValue.type === 'function') { - result.push(new GeneralEnclosed(componentValue)); + if (componentValue.type === ComponentValueType.SimpleBlock) { + result.push(parseMediaInParens(componentValue as SimpleBlockNode)); } } + return result; }); return mediaQueryList; diff --git a/packages/media-query-list-parser/src/util/component-value-is.ts b/packages/media-query-list-parser/src/util/component-value-is.ts index c71b94ab5..1475eaeeb 100644 --- a/packages/media-query-list-parser/src/util/component-value-is.ts +++ b/packages/media-query-list-parser/src/util/component-value-is.ts @@ -1,10 +1,10 @@ -import { ComponentValue, FunctionNode } from '@csstools/css-parser-algorithms'; +import { ComponentValue, ComponentValueType, FunctionNode } from '@csstools/css-parser-algorithms'; import { CSSToken, TokenFunction, TokenIdent, TokenType } from '@csstools/css-tokenizer'; export function isNumber(componentValue: ComponentValue) { if ( - (componentValue.type === 'token' && (componentValue.value as CSSToken)[0] === TokenType.Number) || - (componentValue.type === 'function' && ((componentValue as FunctionNode).name as TokenFunction)[4].value === 'calc') + (componentValue.type === ComponentValueType.Token && (componentValue.value as CSSToken)[0] === TokenType.Number) || + (componentValue.type === ComponentValueType.Function && ((componentValue as FunctionNode).name as TokenFunction)[4].value === 'calc') ) { return true; } @@ -13,7 +13,7 @@ export function isNumber(componentValue: ComponentValue) { } export function isNumericConstant(componentValue: ComponentValue) { - if (componentValue.type === 'token' && (componentValue.value as CSSToken)[0] === TokenType.Ident) { + if (componentValue.type === ComponentValueType.Token && (componentValue.value as CSSToken)[0] === TokenType.Ident) { const token = componentValue.value as TokenIdent; const tokenValue = token[4].value.toLowerCase(); if (tokenValue === 'infinity') { @@ -37,7 +37,7 @@ export function isNumericConstant(componentValue: ComponentValue) { } export function isDimension(componentValue: ComponentValue) { - if (componentValue.type === 'token' && (componentValue.value as CSSToken)[0] === TokenType.Dimension) { + if (componentValue.type === ComponentValueType.Token && (componentValue.value as CSSToken)[0] === TokenType.Dimension) { return true; } @@ -45,7 +45,7 @@ export function isDimension(componentValue: ComponentValue) { } export function isIdent(componentValue: ComponentValue) { - if (componentValue.type === 'token' && (componentValue.value as CSSToken)[0] === TokenType.Ident) { + if (componentValue.type === ComponentValueType.Token && (componentValue.value as CSSToken)[0] === TokenType.Ident) { return true; } diff --git a/packages/media-query-list-parser/test/test.mjs b/packages/media-query-list-parser/test/test.mjs index cd5d20ff0..64a572311 100644 --- a/packages/media-query-list-parser/test/test.mjs +++ b/packages/media-query-list-parser/test/test.mjs @@ -2,7 +2,7 @@ import { parse } from '@csstools/media-query-list-parser'; parse('(/* a comment */foo ) something else'); -parse('not screen and ((min-width: 300px) and (prefers-color-scheme:/* a comment */dark))').forEach((mediaQuery) => { +parse('not screen and (min-width: 300px) and (prefers-color-scheme:/* a comment */dark)').forEach((mediaQuery) => { mediaQuery.forEach((args) => { if (!('walk' in args)) { console.log(args); @@ -10,7 +10,7 @@ parse('not screen and ((min-width: 300px) and (prefers-color-scheme:/* a comment } args.walk((a) => { - console.log(a.node.type); + console.log(a.node.type, a.parent.type); console.log(a.node.toString()); }); }); diff --git a/rollup/configs/externals.js b/rollup/configs/externals.js index aaf0a70e5..c3a6e57a3 100644 --- a/rollup/configs/externals.js +++ b/rollup/configs/externals.js @@ -4,6 +4,7 @@ export const externalsForCLI = [ 'url', 'vm', + '@csstools/css-parser-algorithms', '@csstools/css-tokenizer', '@csstools/postcss-cascade-layers', '@csstools/postcss-color-function', @@ -70,6 +71,7 @@ export const externalsForPlugin = [ /^postcss\/lib\/*/, 'postcss-html', + '@csstools/css-parser-algorithms', '@csstools/css-tokenizer', '@csstools/postcss-cascade-layers', '@csstools/postcss-color-function', From c18208e427a7057c59b9888be0668f6c499f1ecb Mon Sep 17 00:00:00 2001 From: Romain Menke Date: Sun, 23 Oct 2022 10:39:16 +0200 Subject: [PATCH 08/35] lint --- packages/media-query-list-parser/package.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/media-query-list-parser/package.json b/packages/media-query-list-parser/package.json index c400c28d4..c12761f4c 100644 --- a/packages/media-query-list-parser/package.json +++ b/packages/media-query-list-parser/package.json @@ -37,6 +37,10 @@ "README.md", "dist" ], + "dependencies": { + "@csstools/css-parser-algorithms": "^1.0.0", + "@csstools/css-tokenizer": "^1.0.0" + }, "scripts": { "build": "rollup -c ../../rollup/default.js", "clean": "node -e \"fs.rmSync('./dist', { recursive: true, force: true });\"", @@ -57,10 +61,6 @@ "css", "tokenizer" ], - "dependencies": { - "@csstools/css-tokenizer": "^1.0.0", - "@csstools/css-parser-algorithms": "^1.0.0" - }, "volta": { "extends": "../../package.json" } From 2942574b1f4f78f326f94f664edc6f5f058c59f5 Mon Sep 17 00:00:00 2001 From: Romain Menke Date: Sun, 23 Oct 2022 11:03:21 +0200 Subject: [PATCH 09/35] fix ranges --- .../src/nodes/media-feature-range.ts | 10 +++++----- packages/media-query-list-parser/test/test.mjs | 16 +++++++++++++++- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/packages/media-query-list-parser/src/nodes/media-feature-range.ts b/packages/media-query-list-parser/src/nodes/media-feature-range.ts index 70d1ed1aa..db08fc92e 100644 --- a/packages/media-query-list-parser/src/nodes/media-feature-range.ts +++ b/packages/media-query-list-parser/src/nodes/media-feature-range.ts @@ -241,12 +241,12 @@ export function parseMediaFeatureRange(componentValues: Array) { if (comparisonOne === false) { comparisonOne = [ comparison[0] + i, - comparison[i] + i, + comparison[1] + i, ]; } else { comparisonTwo = [ comparison[0] + i, - comparison[i] + i, + comparison[1] + i, ]; break; } @@ -269,7 +269,7 @@ export function parseMediaFeatureRange(componentValues: Array) { } if (comparisonTwo === false) { - const a = componentValues.slice(0, comparisonOne[0] - 1); + const a = componentValues.slice(0, comparisonOne[0]); const b = componentValues.slice(comparisonOne[1] + 1); const nameA = parseMediaFeatureName(a); @@ -315,8 +315,8 @@ export function parseMediaFeatureRange(componentValues: Array) { ); } - const a = componentValues.slice(0, comparisonOne[0] - 1); - const b = componentValues.slice(comparisonOne[1] + 1, comparisonTwo[0] - 1); + const a = componentValues.slice(0, comparisonOne[0]); + const b = componentValues.slice(comparisonOne[1] + 1, comparisonTwo[0]); const c = componentValues.slice(comparisonTwo[1] + 1); const valueA = parseMediaFeatureValue(a); diff --git a/packages/media-query-list-parser/test/test.mjs b/packages/media-query-list-parser/test/test.mjs index 64a572311..f13936c41 100644 --- a/packages/media-query-list-parser/test/test.mjs +++ b/packages/media-query-list-parser/test/test.mjs @@ -2,7 +2,21 @@ import { parse } from '@csstools/media-query-list-parser'; parse('(/* a comment */foo ) something else'); -parse('not screen and (min-width: 300px) and (prefers-color-scheme:/* a comment */dark)').forEach((mediaQuery) => { +parse('not screen and (min-width: 300px) and (prefers-color-scheme:/* a comment */dark) and (width < 40vw) and (30px < width < 50rem)').forEach((mediaQuery) => { + mediaQuery.forEach((args) => { + if (!('walk' in args)) { + console.log(args); + return; + } + + args.walk((a) => { + console.log(a.node.type, a.parent.type); + console.log(a.node.toString()); + }); + }); +}); + +parse('(resolution < infinite) and (infinite < resolution)').forEach((mediaQuery) => { mediaQuery.forEach((args) => { if (!('walk' in args)) { console.log(args); From ff0fa6441efde37e887c15a89f8423f4976720af Mon Sep 17 00:00:00 2001 From: Romain Menke Date: Sun, 23 Oct 2022 17:02:34 +0200 Subject: [PATCH 10/35] more work --- .../src/nodes/media-and.ts | 56 +++++++- .../src/nodes/media-condition-list.ts | 122 +++++++++++++++++- .../src/nodes/media-in-parens.ts | 4 +- .../src/nodes/media-not.ts | 56 +++++++- .../src/nodes/media-or.ts | 56 +++++++- .../src/parser/parse.ts | 8 +- .../media-query-list-parser/test/test.mjs | 51 +++++--- 7 files changed, 320 insertions(+), 33 deletions(-) diff --git a/packages/media-query-list-parser/src/nodes/media-and.ts b/packages/media-query-list-parser/src/nodes/media-and.ts index b6695c07d..81d378461 100644 --- a/packages/media-query-list-parser/src/nodes/media-and.ts +++ b/packages/media-query-list-parser/src/nodes/media-and.ts @@ -1,5 +1,7 @@ -import { CSSToken, stringify } from '@csstools/css-tokenizer'; -import { MediaInParens, MediaInParensWalkerEntry, MediaInParensWalkerParent } from './media-in-parens'; +import { ComponentValue, ComponentValueType, SimpleBlockNode } from '@csstools/css-parser-algorithms'; +import { CSSToken, stringify, TokenIdent } from '@csstools/css-tokenizer'; +import { isIdent } from '../util/component-value-is'; +import { MediaInParens, MediaInParensWalkerEntry, MediaInParensWalkerParent, parseMediaInParens } from './media-in-parens'; export class MediaAnd { type = 'media-and'; @@ -48,3 +50,53 @@ export class MediaAnd { export type MediaAndWalkerEntry = MediaInParensWalkerEntry | MediaInParens; export type MediaAndWalkerParent = MediaInParensWalkerParent | MediaAnd; + +export function parseMediaAnd(componentValues: Array) { + let sawAnd = false; + + for (let i = 0; i < componentValues.length; i++) { + const componentValue = componentValues[i]; + if (componentValue.type === ComponentValueType.Whitespace) { + continue; + } + + if (componentValue.type === ComponentValueType.Comment) { + continue; + } + + if (isIdent(componentValue)) { + const token = (componentValue.value as TokenIdent); + if (token[4].value.toLowerCase() === 'and') { + if (sawAnd) { + return false; + } + + sawAnd = true; + continue; + } + + return false; + } + + if (sawAnd && componentValue.type === ComponentValueType.SimpleBlock) { + const media = parseMediaInParens(componentValue as SimpleBlockNode); + if (media === false) { + return false; + } + + return { + advance: i, + node: new MediaAnd( + componentValues.slice(0, i).flatMap((x) => { + return x.tokens(); + }), + media, + ), + }; + } + + return false; + } + + return false; +} diff --git a/packages/media-query-list-parser/src/nodes/media-condition-list.ts b/packages/media-query-list-parser/src/nodes/media-condition-list.ts index 588c3565a..56ca8ed0f 100644 --- a/packages/media-query-list-parser/src/nodes/media-condition-list.ts +++ b/packages/media-query-list-parser/src/nodes/media-condition-list.ts @@ -1,7 +1,8 @@ +import { ComponentValue, ComponentValueType, SimpleBlockNode } from '@csstools/css-parser-algorithms'; import { CSSToken, stringify } from '@csstools/css-tokenizer'; -import { MediaAnd, MediaAndWalkerEntry, MediaAndWalkerParent } from './media-and'; -import { MediaInParens } from './media-in-parens'; -import { MediaOr, MediaOrWalkerEntry, MediaOrWalkerParent } from './media-or'; +import { MediaAnd, MediaAndWalkerEntry, MediaAndWalkerParent, parseMediaAnd } from './media-and'; +import { MediaInParens, parseMediaInParens } from './media-in-parens'; +import { MediaOr, MediaOrWalkerEntry, MediaOrWalkerParent, parseMediaOr } from './media-or'; export type MediaConditionList = MediaConditionListWithAnd | MediaConditionListWithOr; @@ -98,6 +99,63 @@ export class MediaConditionListWithAnd { export type MediaConditionListWithAndWalkerEntry = MediaAndWalkerEntry | MediaAnd; export type MediaConditionListWithAndWalkerParent = MediaAndWalkerParent | MediaConditionListWithAnd; +export function parseMediaConditionListWithAnd(componentValues: Array) { + let leading: MediaInParens | false = false; + const list: Array = []; + let firstIndex = -1; + let lastIndex = -1; + + for (let i = 0; i < componentValues.length; i++) { + if (leading) { + const part = parseMediaAnd(componentValues.slice(i)); + if (part !== false) { + i += part.advance; + list.push(part.node); + lastIndex = i; + continue; + } + + return false; + } + + const componentValue = componentValues[i]; + if (componentValue.type === ComponentValueType.Whitespace) { + continue; + } + + if (componentValue.type === ComponentValueType.Comment) { + continue; + } + + if (leading === false && componentValue.type === ComponentValueType.SimpleBlock) { + leading = parseMediaInParens(componentValue as SimpleBlockNode); + if (leading === false) { + return false; + } + + firstIndex = i; + continue; + } + + return false; + } + + if (leading && list.length) { + return new MediaConditionListWithAnd( + leading, + list, + componentValues.slice(0, firstIndex).flatMap((x) => { + return x.tokens(); + }), + componentValues.slice(lastIndex + 1).flatMap((x) => { + return x.tokens(); + }), + ); + } + + return false; +} + export class MediaConditionListWithOr { type = 'media-condition-list-or'; @@ -190,3 +248,61 @@ export class MediaConditionListWithOr { export type MediaConditionListWithOrWalkerEntry = MediaOrWalkerEntry | MediaOr; export type MediaConditionListWithOrWalkerParent = MediaOrWalkerParent | MediaConditionListWithOr; + + +export function parseMediaConditionListWithOr(componentValues: Array) { + let leading: MediaInParens | false = false; + const list: Array = []; + let firstIndex = -1; + let lastIndex = -1; + + for (let i = 0; i < componentValues.length; i++) { + if (leading) { + const part = parseMediaOr(componentValues.slice(i)); + if (part !== false) { + i += part.advance; + list.push(part.node); + lastIndex = i; + continue; + } + + return false; + } + + const componentValue = componentValues[i]; + if (componentValue.type === ComponentValueType.Whitespace) { + continue; + } + + if (componentValue.type === ComponentValueType.Comment) { + continue; + } + + if (leading === false && componentValue.type === ComponentValueType.SimpleBlock) { + leading = parseMediaInParens(componentValue as SimpleBlockNode); + if (leading === false) { + return false; + } + + firstIndex = i; + continue; + } + + return false; + } + + if (leading && list.length) { + return new MediaConditionListWithOr( + leading, + list, + componentValues.slice(0, firstIndex).flatMap((x) => { + return x.tokens(); + }), + componentValues.slice(lastIndex + 1).flatMap((x) => { + return x.tokens(); + }), + ); + } + + return false; +} diff --git a/packages/media-query-list-parser/src/nodes/media-in-parens.ts b/packages/media-query-list-parser/src/nodes/media-in-parens.ts index b5da64cee..daa0089bc 100644 --- a/packages/media-query-list-parser/src/nodes/media-in-parens.ts +++ b/packages/media-query-list-parser/src/nodes/media-in-parens.ts @@ -71,7 +71,7 @@ export function parseMediaInParens(simpleBlock: SimpleBlockNode) { const feature = parseMediaFeature(simpleBlock); if (feature !== false) { - return new MediaInParens(feature); + return new MediaInParens(feature, [simpleBlock.startToken], [simpleBlock.endToken]); } const condition = parseMediaCondition(simpleBlock.value); @@ -79,5 +79,5 @@ export function parseMediaInParens(simpleBlock: SimpleBlockNode) { return new MediaInParens(condition, [simpleBlock.startToken], [simpleBlock.endToken]); } - return new GeneralEnclosed(simpleBlock); + return new MediaInParens(new GeneralEnclosed(simpleBlock)); } diff --git a/packages/media-query-list-parser/src/nodes/media-not.ts b/packages/media-query-list-parser/src/nodes/media-not.ts index 2a9dae586..a703b2930 100644 --- a/packages/media-query-list-parser/src/nodes/media-not.ts +++ b/packages/media-query-list-parser/src/nodes/media-not.ts @@ -1,5 +1,7 @@ -import { CSSToken, stringify } from '@csstools/css-tokenizer'; -import { MediaInParens, MediaInParensWalkerEntry, MediaInParensWalkerParent } from './media-in-parens'; +import { ComponentValue, ComponentValueType, SimpleBlockNode } from '@csstools/css-parser-algorithms'; +import { CSSToken, stringify, TokenIdent } from '@csstools/css-tokenizer'; +import { isIdent } from '../util/component-value-is'; +import { MediaInParens, MediaInParensWalkerEntry, MediaInParensWalkerParent, parseMediaInParens } from './media-in-parens'; export class MediaNot { type = 'media-not'; @@ -48,3 +50,53 @@ export class MediaNot { export type MediaNotWalkerEntry = MediaInParensWalkerEntry | MediaInParens; export type MediaNotWalkerParent = MediaInParensWalkerParent | MediaNot; + +export function parseMediaNot(componentValues: Array) { + let sawNot = false; + + for (let i = 0; i < componentValues.length; i++) { + const componentValue = componentValues[i]; + if (componentValue.type === ComponentValueType.Whitespace) { + continue; + } + + if (componentValue.type === ComponentValueType.Comment) { + continue; + } + + if (isIdent(componentValue)) { + const token = (componentValue.value as TokenIdent); + if (token[4].value.toLowerCase() === 'not') { + if (sawNot) { + return false; + } + + sawNot = true; + continue; + } + + return false; + } + + if (sawNot && componentValue.type === ComponentValueType.SimpleBlock) { + const media = parseMediaInParens(componentValue as SimpleBlockNode); + if (media === false) { + return false; + } + + return { + advance: i, + node: new MediaNot( + componentValues.slice(0, i).flatMap((x) => { + return x.tokens(); + }), + media, + ), + }; + } + + return false; + } + + return false; +} diff --git a/packages/media-query-list-parser/src/nodes/media-or.ts b/packages/media-query-list-parser/src/nodes/media-or.ts index c3945fe2f..1204a32f9 100644 --- a/packages/media-query-list-parser/src/nodes/media-or.ts +++ b/packages/media-query-list-parser/src/nodes/media-or.ts @@ -1,5 +1,7 @@ -import { CSSToken, stringify } from '@csstools/css-tokenizer'; -import { MediaInParens, MediaInParensWalkerEntry, MediaInParensWalkerParent } from './media-in-parens'; +import { ComponentValue, ComponentValueType, SimpleBlockNode } from '@csstools/css-parser-algorithms'; +import { CSSToken, stringify, TokenIdent } from '@csstools/css-tokenizer'; +import { isIdent } from '../util/component-value-is'; +import { MediaInParens, MediaInParensWalkerEntry, MediaInParensWalkerParent, parseMediaInParens } from './media-in-parens'; export class MediaOr { type = 'media-or'; @@ -48,3 +50,53 @@ export class MediaOr { export type MediaOrWalkerEntry = MediaInParensWalkerEntry | MediaInParens; export type MediaOrWalkerParent = MediaInParensWalkerParent | MediaOr; + +export function parseMediaOr(componentValues: Array) { + let sawOr = false; + + for (let i = 0; i < componentValues.length; i++) { + const componentValue = componentValues[i]; + if (componentValue.type === ComponentValueType.Whitespace) { + continue; + } + + if (componentValue.type === ComponentValueType.Comment) { + continue; + } + + if (isIdent(componentValue)) { + const token = (componentValue.value as TokenIdent); + if (token[4].value.toLowerCase() === 'or') { + if (sawOr) { + return false; + } + + sawOr = true; + continue; + } + + return false; + } + + if (sawOr && componentValue.type === ComponentValueType.SimpleBlock) { + const media = parseMediaInParens(componentValue as SimpleBlockNode); + if (media === false) { + return false; + } + + return { + advance: i, + node: new MediaOr( + componentValues.slice(0, i).flatMap((x) => { + return x.tokens(); + }), + media, + ), + }; + } + + return false; + } + + return false; +} diff --git a/packages/media-query-list-parser/src/parser/parse.ts b/packages/media-query-list-parser/src/parser/parse.ts index bbabc6c93..ccbc1d3a3 100644 --- a/packages/media-query-list-parser/src/parser/parse.ts +++ b/packages/media-query-list-parser/src/parser/parse.ts @@ -1,5 +1,6 @@ import { ComponentValueType, parseCommaSeparatedListOfComponentValues, SimpleBlockNode } from '@csstools/css-parser-algorithms'; import { tokenizer } from '@csstools/css-tokenizer'; +import { parseMediaConditionListWithAnd } from '../nodes/media-condition-list'; import { parseMediaInParens } from '../nodes/media-in-parens'; export function parse(source: string) { @@ -36,7 +37,12 @@ export function parse(source: string) { } } - return result; + const x = parseMediaConditionListWithAnd(componentValuesList); + if (x) { + return x; + } + + return {}; }); return mediaQueryList; diff --git a/packages/media-query-list-parser/test/test.mjs b/packages/media-query-list-parser/test/test.mjs index f13936c41..ee54b14ac 100644 --- a/packages/media-query-list-parser/test/test.mjs +++ b/packages/media-query-list-parser/test/test.mjs @@ -3,29 +3,38 @@ import { parse } from '@csstools/media-query-list-parser'; parse('(/* a comment */foo ) something else'); parse('not screen and (min-width: 300px) and (prefers-color-scheme:/* a comment */dark) and (width < 40vw) and (30px < width < 50rem)').forEach((mediaQuery) => { - mediaQuery.forEach((args) => { - if (!('walk' in args)) { - console.log(args); - return; - } - - args.walk((a) => { - console.log(a.node.type, a.parent.type); - console.log(a.node.toString()); - }); + if (!('walk' in mediaQuery)) { + console.log(mediaQuery); + return; + } + + mediaQuery.walk((a) => { + console.log(a.node.type, a.parent.type); + console.log(a.node.toString()); }); }); -parse('(resolution < infinite) and (infinite < resolution)').forEach((mediaQuery) => { - mediaQuery.forEach((args) => { - if (!('walk' in args)) { - console.log(args); - return; - } - - args.walk((a) => { - console.log(a.node.type, a.parent.type); - console.log(a.node.toString()); - }); +parse('(resolution < infinite) and (infinite <= resolution)').forEach((mediaQuery) => { + console.log(mediaQuery.toString()); + + if (!('walk' in mediaQuery)) { + return; + } + + mediaQuery.walk((a) => { + console.log(a.node.type, a.parent.type); + console.log(a.node.toString()); + }); +}); + +parse('(width < calc(50vw - 3rem))').forEach((mediaQuery) => { + if (!('walk' in mediaQuery)) { + console.log(mediaQuery); + return; + } + + mediaQuery.walk((a) => { + console.log(a.node.type, a.parent.type); + console.log(a.node.toString()); }); }); From 48cd40f04b6f3c5c4761b911c73b1ed9b1f5007e Mon Sep 17 00:00:00 2001 From: Romain Menke Date: Sun, 23 Oct 2022 18:58:08 +0200 Subject: [PATCH 11/35] media lists --- .../src/nodes/media-and.ts | 4 +- .../src/nodes/media-condition-list.ts | 6 +- .../src/nodes/media-condition.ts | 33 +++++++-- .../src/nodes/media-in-parens.ts | 73 ++++++++++++++++++- .../src/nodes/media-not.ts | 4 +- .../src/nodes/media-or.ts | 4 +- .../src/parser/parse.ts | 7 +- 7 files changed, 111 insertions(+), 20 deletions(-) diff --git a/packages/media-query-list-parser/src/nodes/media-and.ts b/packages/media-query-list-parser/src/nodes/media-and.ts index 81d378461..e10d6d523 100644 --- a/packages/media-query-list-parser/src/nodes/media-and.ts +++ b/packages/media-query-list-parser/src/nodes/media-and.ts @@ -1,7 +1,7 @@ import { ComponentValue, ComponentValueType, SimpleBlockNode } from '@csstools/css-parser-algorithms'; import { CSSToken, stringify, TokenIdent } from '@csstools/css-tokenizer'; import { isIdent } from '../util/component-value-is'; -import { MediaInParens, MediaInParensWalkerEntry, MediaInParensWalkerParent, parseMediaInParens } from './media-in-parens'; +import { MediaInParens, MediaInParensWalkerEntry, MediaInParensWalkerParent, parseMediaInParensFromSimpleBlock } from './media-in-parens'; export class MediaAnd { type = 'media-and'; @@ -79,7 +79,7 @@ export function parseMediaAnd(componentValues: Array) { } if (sawAnd && componentValue.type === ComponentValueType.SimpleBlock) { - const media = parseMediaInParens(componentValue as SimpleBlockNode); + const media = parseMediaInParensFromSimpleBlock(componentValue as SimpleBlockNode); if (media === false) { return false; } diff --git a/packages/media-query-list-parser/src/nodes/media-condition-list.ts b/packages/media-query-list-parser/src/nodes/media-condition-list.ts index 56ca8ed0f..99eba1dcc 100644 --- a/packages/media-query-list-parser/src/nodes/media-condition-list.ts +++ b/packages/media-query-list-parser/src/nodes/media-condition-list.ts @@ -1,7 +1,7 @@ import { ComponentValue, ComponentValueType, SimpleBlockNode } from '@csstools/css-parser-algorithms'; import { CSSToken, stringify } from '@csstools/css-tokenizer'; import { MediaAnd, MediaAndWalkerEntry, MediaAndWalkerParent, parseMediaAnd } from './media-and'; -import { MediaInParens, parseMediaInParens } from './media-in-parens'; +import { MediaInParens, parseMediaInParensFromSimpleBlock } from './media-in-parens'; import { MediaOr, MediaOrWalkerEntry, MediaOrWalkerParent, parseMediaOr } from './media-or'; export type MediaConditionList = MediaConditionListWithAnd | MediaConditionListWithOr; @@ -128,7 +128,7 @@ export function parseMediaConditionListWithAnd(componentValues: Array): false | MediaCondition { +export function parseMediaCondition(componentValues: Array) { + const mediaNot = parseMediaNot(componentValues); + if (mediaNot !== false) { + return new MediaCondition(mediaNot); + } + + const mediaListAnd = parseMediaConditionListWithAnd(componentValues); + if (mediaListAnd !== false) { + return new MediaCondition(mediaListAnd); + } + + const mediaListOr = parseMediaConditionListWithOr(componentValues); + if (mediaListOr !== false) { + return new MediaCondition(mediaListOr); + } + + const mediaInParens = parseMediaInParens(componentValues); + if (mediaInParens !== false) { + return new MediaCondition(mediaInParens); + } + return false; } diff --git a/packages/media-query-list-parser/src/nodes/media-in-parens.ts b/packages/media-query-list-parser/src/nodes/media-in-parens.ts index daa0089bc..ddac1930c 100644 --- a/packages/media-query-list-parser/src/nodes/media-in-parens.ts +++ b/packages/media-query-list-parser/src/nodes/media-in-parens.ts @@ -1,4 +1,4 @@ -import { ComponentValue, ContainerNode, SimpleBlockNode } from '@csstools/css-parser-algorithms'; +import { ComponentValue, ComponentValueType, ContainerNode, SimpleBlockNode } from '@csstools/css-parser-algorithms'; import { CSSToken, stringify, TokenType } from '@csstools/css-tokenizer'; import { GeneralEnclosed } from './general-enclosed'; import { MediaAnd } from './media-and'; @@ -64,7 +64,76 @@ export class MediaInParens { export type MediaInParensWalkerEntry = ComponentValue | Array | GeneralEnclosed | MediaAnd | MediaConditionList | MediaCondition | MediaFeatureBoolean | MediaFeatureName | MediaFeaturePlain | MediaFeatureRange | MediaFeatureValue | MediaFeature | GeneralEnclosed | MediaInParens; export type MediaInParensWalkerParent = ContainerNode | GeneralEnclosed | MediaAnd | MediaConditionList | MediaCondition | MediaFeatureBoolean | MediaFeatureName | MediaFeaturePlain | MediaFeatureRange | MediaFeatureValue | MediaFeature | GeneralEnclosed | MediaInParens; -export function parseMediaInParens(simpleBlock: SimpleBlockNode) { +export function parseMediaInParens(componentValues: Array) { + let singleSimpleBlockIndex = -1; + + for (let i = 0; i < componentValues.length; i++) { + const componentValue = componentValues[i]; + if (componentValue.type === ComponentValueType.Whitespace) { + continue; + } + + if (componentValue.type === ComponentValueType.Comment) { + continue; + } + + if (componentValue.type === ComponentValueType.SimpleBlock) { + if (singleSimpleBlockIndex !== -1) { + return false; + } + + singleSimpleBlockIndex = i; + continue; + } + + return false; + } + + if (singleSimpleBlockIndex === -1) { + return false; + } + + const simpleBlock = componentValues[singleSimpleBlockIndex] as SimpleBlockNode; + if (simpleBlock.startToken[0] !== TokenType.OpenParen) { + return false; + } + + const before = [ + ...componentValues.slice(0, singleSimpleBlockIndex).flatMap((x) => { + return x.tokens(); + }), + simpleBlock.startToken, + ]; + + const after = [ + simpleBlock.endToken, + ...componentValues.slice(singleSimpleBlockIndex + 1).flatMap((x) => { + return x.tokens(); + }), + ]; + + const feature = parseMediaFeature(simpleBlock); + if (feature !== false) { + return new MediaInParens(feature, before, after); + } + + const condition = parseMediaCondition(simpleBlock.value); + if (condition !== false) { + return new MediaInParens(condition, before, after); + } + + return new MediaInParens( + new GeneralEnclosed(simpleBlock), + componentValues.slice(0, singleSimpleBlockIndex).flatMap((x) => { + return x.tokens(); + }), + componentValues.slice(singleSimpleBlockIndex + 1).flatMap((x) => { + return x.tokens(); + }), + ); +} + +export function parseMediaInParensFromSimpleBlock(simpleBlock: SimpleBlockNode) { if (simpleBlock.startToken[0] !== TokenType.OpenParen) { return false; } diff --git a/packages/media-query-list-parser/src/nodes/media-not.ts b/packages/media-query-list-parser/src/nodes/media-not.ts index a703b2930..4be6d0fff 100644 --- a/packages/media-query-list-parser/src/nodes/media-not.ts +++ b/packages/media-query-list-parser/src/nodes/media-not.ts @@ -1,7 +1,7 @@ import { ComponentValue, ComponentValueType, SimpleBlockNode } from '@csstools/css-parser-algorithms'; import { CSSToken, stringify, TokenIdent } from '@csstools/css-tokenizer'; import { isIdent } from '../util/component-value-is'; -import { MediaInParens, MediaInParensWalkerEntry, MediaInParensWalkerParent, parseMediaInParens } from './media-in-parens'; +import { MediaInParens, MediaInParensWalkerEntry, MediaInParensWalkerParent, parseMediaInParensFromSimpleBlock } from './media-in-parens'; export class MediaNot { type = 'media-not'; @@ -79,7 +79,7 @@ export function parseMediaNot(componentValues: Array) { } if (sawNot && componentValue.type === ComponentValueType.SimpleBlock) { - const media = parseMediaInParens(componentValue as SimpleBlockNode); + const media = parseMediaInParensFromSimpleBlock(componentValue as SimpleBlockNode); if (media === false) { return false; } diff --git a/packages/media-query-list-parser/src/nodes/media-or.ts b/packages/media-query-list-parser/src/nodes/media-or.ts index 1204a32f9..558d10bae 100644 --- a/packages/media-query-list-parser/src/nodes/media-or.ts +++ b/packages/media-query-list-parser/src/nodes/media-or.ts @@ -1,7 +1,7 @@ import { ComponentValue, ComponentValueType, SimpleBlockNode } from '@csstools/css-parser-algorithms'; import { CSSToken, stringify, TokenIdent } from '@csstools/css-tokenizer'; import { isIdent } from '../util/component-value-is'; -import { MediaInParens, MediaInParensWalkerEntry, MediaInParensWalkerParent, parseMediaInParens } from './media-in-parens'; +import { MediaInParens, MediaInParensWalkerEntry, MediaInParensWalkerParent, parseMediaInParensFromSimpleBlock } from './media-in-parens'; export class MediaOr { type = 'media-or'; @@ -79,7 +79,7 @@ export function parseMediaOr(componentValues: Array) { } if (sawOr && componentValue.type === ComponentValueType.SimpleBlock) { - const media = parseMediaInParens(componentValue as SimpleBlockNode); + const media = parseMediaInParensFromSimpleBlock(componentValue as SimpleBlockNode); if (media === false) { return false; } diff --git a/packages/media-query-list-parser/src/parser/parse.ts b/packages/media-query-list-parser/src/parser/parse.ts index ccbc1d3a3..77d9bc1eb 100644 --- a/packages/media-query-list-parser/src/parser/parse.ts +++ b/packages/media-query-list-parser/src/parser/parse.ts @@ -1,7 +1,8 @@ import { ComponentValueType, parseCommaSeparatedListOfComponentValues, SimpleBlockNode } from '@csstools/css-parser-algorithms'; import { tokenizer } from '@csstools/css-tokenizer'; +import { parseMediaCondition } from '../nodes/media-condition'; import { parseMediaConditionListWithAnd } from '../nodes/media-condition-list'; -import { parseMediaInParens } from '../nodes/media-in-parens'; +import { parseMediaInParensFromSimpleBlock } from '../nodes/media-in-parens'; export function parse(source: string) { const onParseError = (err) => { @@ -33,11 +34,11 @@ export function parse(source: string) { for (let i = 0; i < componentValuesList.length; i++) { const componentValue = componentValuesList[i]; if (componentValue.type === ComponentValueType.SimpleBlock) { - result.push(parseMediaInParens(componentValue as SimpleBlockNode)); + result.push(parseMediaInParensFromSimpleBlock(componentValue as SimpleBlockNode)); } } - const x = parseMediaConditionListWithAnd(componentValuesList); + const x = parseMediaCondition(componentValuesList); if (x) { return x; } From 9849e9944e35b02c5178eae6fa58b0e74421cff7 Mon Sep 17 00:00:00 2001 From: Romain Menke Date: Sun, 23 Oct 2022 23:03:34 +0200 Subject: [PATCH 12/35] finish parser --- .../src/nodes/media-and.ts | 56 +- .../src/nodes/media-condition-list.ts | 122 +---- .../src/nodes/media-condition-without-or.ts | 45 -- .../src/nodes/media-condition.ts | 31 +- .../src/nodes/media-in-parens.ts | 95 +--- .../src/nodes/media-not.ts | 56 +- .../src/nodes/media-or.ts | 56 +- .../src/nodes/media-query.ts | 11 +- .../src/parser/parse-media-query.ts | 485 ++++++++++++++++++ .../src/parser/parse.ts | 32 +- .../media-query-list-parser/test/test.mjs | 47 +- 11 files changed, 543 insertions(+), 493 deletions(-) delete mode 100644 packages/media-query-list-parser/src/nodes/media-condition-without-or.ts create mode 100644 packages/media-query-list-parser/src/parser/parse-media-query.ts diff --git a/packages/media-query-list-parser/src/nodes/media-and.ts b/packages/media-query-list-parser/src/nodes/media-and.ts index e10d6d523..b6695c07d 100644 --- a/packages/media-query-list-parser/src/nodes/media-and.ts +++ b/packages/media-query-list-parser/src/nodes/media-and.ts @@ -1,7 +1,5 @@ -import { ComponentValue, ComponentValueType, SimpleBlockNode } from '@csstools/css-parser-algorithms'; -import { CSSToken, stringify, TokenIdent } from '@csstools/css-tokenizer'; -import { isIdent } from '../util/component-value-is'; -import { MediaInParens, MediaInParensWalkerEntry, MediaInParensWalkerParent, parseMediaInParensFromSimpleBlock } from './media-in-parens'; +import { CSSToken, stringify } from '@csstools/css-tokenizer'; +import { MediaInParens, MediaInParensWalkerEntry, MediaInParensWalkerParent } from './media-in-parens'; export class MediaAnd { type = 'media-and'; @@ -50,53 +48,3 @@ export class MediaAnd { export type MediaAndWalkerEntry = MediaInParensWalkerEntry | MediaInParens; export type MediaAndWalkerParent = MediaInParensWalkerParent | MediaAnd; - -export function parseMediaAnd(componentValues: Array) { - let sawAnd = false; - - for (let i = 0; i < componentValues.length; i++) { - const componentValue = componentValues[i]; - if (componentValue.type === ComponentValueType.Whitespace) { - continue; - } - - if (componentValue.type === ComponentValueType.Comment) { - continue; - } - - if (isIdent(componentValue)) { - const token = (componentValue.value as TokenIdent); - if (token[4].value.toLowerCase() === 'and') { - if (sawAnd) { - return false; - } - - sawAnd = true; - continue; - } - - return false; - } - - if (sawAnd && componentValue.type === ComponentValueType.SimpleBlock) { - const media = parseMediaInParensFromSimpleBlock(componentValue as SimpleBlockNode); - if (media === false) { - return false; - } - - return { - advance: i, - node: new MediaAnd( - componentValues.slice(0, i).flatMap((x) => { - return x.tokens(); - }), - media, - ), - }; - } - - return false; - } - - return false; -} diff --git a/packages/media-query-list-parser/src/nodes/media-condition-list.ts b/packages/media-query-list-parser/src/nodes/media-condition-list.ts index 99eba1dcc..588c3565a 100644 --- a/packages/media-query-list-parser/src/nodes/media-condition-list.ts +++ b/packages/media-query-list-parser/src/nodes/media-condition-list.ts @@ -1,8 +1,7 @@ -import { ComponentValue, ComponentValueType, SimpleBlockNode } from '@csstools/css-parser-algorithms'; import { CSSToken, stringify } from '@csstools/css-tokenizer'; -import { MediaAnd, MediaAndWalkerEntry, MediaAndWalkerParent, parseMediaAnd } from './media-and'; -import { MediaInParens, parseMediaInParensFromSimpleBlock } from './media-in-parens'; -import { MediaOr, MediaOrWalkerEntry, MediaOrWalkerParent, parseMediaOr } from './media-or'; +import { MediaAnd, MediaAndWalkerEntry, MediaAndWalkerParent } from './media-and'; +import { MediaInParens } from './media-in-parens'; +import { MediaOr, MediaOrWalkerEntry, MediaOrWalkerParent } from './media-or'; export type MediaConditionList = MediaConditionListWithAnd | MediaConditionListWithOr; @@ -99,63 +98,6 @@ export class MediaConditionListWithAnd { export type MediaConditionListWithAndWalkerEntry = MediaAndWalkerEntry | MediaAnd; export type MediaConditionListWithAndWalkerParent = MediaAndWalkerParent | MediaConditionListWithAnd; -export function parseMediaConditionListWithAnd(componentValues: Array) { - let leading: MediaInParens | false = false; - const list: Array = []; - let firstIndex = -1; - let lastIndex = -1; - - for (let i = 0; i < componentValues.length; i++) { - if (leading) { - const part = parseMediaAnd(componentValues.slice(i)); - if (part !== false) { - i += part.advance; - list.push(part.node); - lastIndex = i; - continue; - } - - return false; - } - - const componentValue = componentValues[i]; - if (componentValue.type === ComponentValueType.Whitespace) { - continue; - } - - if (componentValue.type === ComponentValueType.Comment) { - continue; - } - - if (leading === false && componentValue.type === ComponentValueType.SimpleBlock) { - leading = parseMediaInParensFromSimpleBlock(componentValue as SimpleBlockNode); - if (leading === false) { - return false; - } - - firstIndex = i; - continue; - } - - return false; - } - - if (leading && list.length) { - return new MediaConditionListWithAnd( - leading, - list, - componentValues.slice(0, firstIndex).flatMap((x) => { - return x.tokens(); - }), - componentValues.slice(lastIndex + 1).flatMap((x) => { - return x.tokens(); - }), - ); - } - - return false; -} - export class MediaConditionListWithOr { type = 'media-condition-list-or'; @@ -248,61 +190,3 @@ export class MediaConditionListWithOr { export type MediaConditionListWithOrWalkerEntry = MediaOrWalkerEntry | MediaOr; export type MediaConditionListWithOrWalkerParent = MediaOrWalkerParent | MediaConditionListWithOr; - - -export function parseMediaConditionListWithOr(componentValues: Array) { - let leading: MediaInParens | false = false; - const list: Array = []; - let firstIndex = -1; - let lastIndex = -1; - - for (let i = 0; i < componentValues.length; i++) { - if (leading) { - const part = parseMediaOr(componentValues.slice(i)); - if (part !== false) { - i += part.advance; - list.push(part.node); - lastIndex = i; - continue; - } - - return false; - } - - const componentValue = componentValues[i]; - if (componentValue.type === ComponentValueType.Whitespace) { - continue; - } - - if (componentValue.type === ComponentValueType.Comment) { - continue; - } - - if (leading === false && componentValue.type === ComponentValueType.SimpleBlock) { - leading = parseMediaInParensFromSimpleBlock(componentValue as SimpleBlockNode); - if (leading === false) { - return false; - } - - firstIndex = i; - continue; - } - - return false; - } - - if (leading && list.length) { - return new MediaConditionListWithOr( - leading, - list, - componentValues.slice(0, firstIndex).flatMap((x) => { - return x.tokens(); - }), - componentValues.slice(lastIndex + 1).flatMap((x) => { - return x.tokens(); - }), - ); - } - - return false; -} diff --git a/packages/media-query-list-parser/src/nodes/media-condition-without-or.ts b/packages/media-query-list-parser/src/nodes/media-condition-without-or.ts deleted file mode 100644 index 1834898dd..000000000 --- a/packages/media-query-list-parser/src/nodes/media-condition-without-or.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { MediaConditionListWithAnd, MediaConditionListWithAndWalkerEntry, MediaConditionListWithAndWalkerParent } from './media-condition-list'; -import { MediaNot, MediaNotWalkerEntry, MediaNotWalkerParent } from './media-not'; - -export class MediaConditionWithoutOr { - type = 'media-condition-without-or'; - - media: MediaNot | MediaConditionListWithAnd; - - constructor(media: MediaNot | MediaConditionListWithAnd) { - this.media = media; - } - - tokens() { - return this.media.tokens(); - } - - toString() { - return this.media.toString(); - } - - indexOf(item: MediaNot | MediaConditionListWithAnd): number | string { - if (item === this.media) { - return 'media'; - } - - return -1; - } - - at(index: number | string) { - if (index === 'media') { - return this.media; - } - } - - walk(cb: (entry: { node: MediaConditionWithoutOrWalkerEntry, parent: MediaConditionWithoutOrWalkerParent }, index: number | string) => boolean) { - if (cb({ node: this.media, parent: this }, 'media') === false) { - return false; - } - - return this.media.walk(cb); - } -} - -export type MediaConditionWithoutOrWalkerEntry = MediaConditionListWithAndWalkerEntry | MediaNotWalkerEntry | MediaNot | MediaConditionListWithAnd; -export type MediaConditionWithoutOrWalkerParent = MediaConditionListWithAndWalkerParent | MediaNotWalkerParent | MediaConditionWithoutOr; diff --git a/packages/media-query-list-parser/src/nodes/media-condition.ts b/packages/media-query-list-parser/src/nodes/media-condition.ts index 741e82011..6846d2f65 100644 --- a/packages/media-query-list-parser/src/nodes/media-condition.ts +++ b/packages/media-query-list-parser/src/nodes/media-condition.ts @@ -1,7 +1,6 @@ -import { ComponentValue } from '@csstools/css-parser-algorithms'; -import { MediaConditionListWithAnd, MediaConditionListWithAndWalkerEntry, MediaConditionListWithAndWalkerParent, MediaConditionListWithOr, MediaConditionListWithOrWalkerEntry, MediaConditionListWithOrWalkerParent, parseMediaConditionListWithAnd, parseMediaConditionListWithOr } from './media-condition-list'; -import { MediaInParens, parseMediaInParens } from './media-in-parens'; -import { MediaNot, MediaNotWalkerEntry, MediaNotWalkerParent, parseMediaNot } from './media-not'; +import { MediaConditionListWithAnd, MediaConditionListWithAndWalkerEntry, MediaConditionListWithAndWalkerParent, MediaConditionListWithOr, MediaConditionListWithOrWalkerEntry, MediaConditionListWithOrWalkerParent } from './media-condition-list'; +import { MediaInParens } from './media-in-parens'; +import { MediaNot, MediaNotWalkerEntry, MediaNotWalkerParent } from './media-not'; export class MediaCondition { type = 'media-condition'; @@ -45,27 +44,3 @@ export class MediaCondition { export type MediaConditionWalkerEntry = MediaNotWalkerEntry | MediaConditionListWithAndWalkerEntry | MediaConditionListWithOrWalkerEntry | MediaNot | MediaConditionListWithAnd | MediaConditionListWithOr; export type MediaConditionWalkerParent = MediaNotWalkerParent | MediaConditionListWithAndWalkerParent | MediaConditionListWithOrWalkerParent | MediaCondition; - -export function parseMediaCondition(componentValues: Array) { - const mediaNot = parseMediaNot(componentValues); - if (mediaNot !== false) { - return new MediaCondition(mediaNot); - } - - const mediaListAnd = parseMediaConditionListWithAnd(componentValues); - if (mediaListAnd !== false) { - return new MediaCondition(mediaListAnd); - } - - const mediaListOr = parseMediaConditionListWithOr(componentValues); - if (mediaListOr !== false) { - return new MediaCondition(mediaListOr); - } - - const mediaInParens = parseMediaInParens(componentValues); - if (mediaInParens !== false) { - return new MediaCondition(mediaInParens); - } - - return false; -} diff --git a/packages/media-query-list-parser/src/nodes/media-in-parens.ts b/packages/media-query-list-parser/src/nodes/media-in-parens.ts index ddac1930c..5b351b81d 100644 --- a/packages/media-query-list-parser/src/nodes/media-in-parens.ts +++ b/packages/media-query-list-parser/src/nodes/media-in-parens.ts @@ -1,10 +1,10 @@ -import { ComponentValue, ComponentValueType, ContainerNode, SimpleBlockNode } from '@csstools/css-parser-algorithms'; -import { CSSToken, stringify, TokenType } from '@csstools/css-tokenizer'; +import { ComponentValue, ContainerNode } from '@csstools/css-parser-algorithms'; +import { CSSToken, stringify } from '@csstools/css-tokenizer'; import { GeneralEnclosed } from './general-enclosed'; import { MediaAnd } from './media-and'; -import { MediaCondition, parseMediaCondition } from './media-condition'; +import { MediaCondition } from './media-condition'; import { MediaConditionList } from './media-condition-list'; -import { MediaFeature, parseMediaFeature } from './media-feature'; +import { MediaFeature } from './media-feature'; import { MediaFeatureBoolean } from './media-feature-boolean'; import { MediaFeatureName } from './media-feature-name'; import { MediaFeaturePlain } from './media-feature-plain'; @@ -63,90 +63,3 @@ export class MediaInParens { export type MediaInParensWalkerEntry = ComponentValue | Array | GeneralEnclosed | MediaAnd | MediaConditionList | MediaCondition | MediaFeatureBoolean | MediaFeatureName | MediaFeaturePlain | MediaFeatureRange | MediaFeatureValue | MediaFeature | GeneralEnclosed | MediaInParens; export type MediaInParensWalkerParent = ContainerNode | GeneralEnclosed | MediaAnd | MediaConditionList | MediaCondition | MediaFeatureBoolean | MediaFeatureName | MediaFeaturePlain | MediaFeatureRange | MediaFeatureValue | MediaFeature | GeneralEnclosed | MediaInParens; - -export function parseMediaInParens(componentValues: Array) { - let singleSimpleBlockIndex = -1; - - for (let i = 0; i < componentValues.length; i++) { - const componentValue = componentValues[i]; - if (componentValue.type === ComponentValueType.Whitespace) { - continue; - } - - if (componentValue.type === ComponentValueType.Comment) { - continue; - } - - if (componentValue.type === ComponentValueType.SimpleBlock) { - if (singleSimpleBlockIndex !== -1) { - return false; - } - - singleSimpleBlockIndex = i; - continue; - } - - return false; - } - - if (singleSimpleBlockIndex === -1) { - return false; - } - - const simpleBlock = componentValues[singleSimpleBlockIndex] as SimpleBlockNode; - if (simpleBlock.startToken[0] !== TokenType.OpenParen) { - return false; - } - - const before = [ - ...componentValues.slice(0, singleSimpleBlockIndex).flatMap((x) => { - return x.tokens(); - }), - simpleBlock.startToken, - ]; - - const after = [ - simpleBlock.endToken, - ...componentValues.slice(singleSimpleBlockIndex + 1).flatMap((x) => { - return x.tokens(); - }), - ]; - - const feature = parseMediaFeature(simpleBlock); - if (feature !== false) { - return new MediaInParens(feature, before, after); - } - - const condition = parseMediaCondition(simpleBlock.value); - if (condition !== false) { - return new MediaInParens(condition, before, after); - } - - return new MediaInParens( - new GeneralEnclosed(simpleBlock), - componentValues.slice(0, singleSimpleBlockIndex).flatMap((x) => { - return x.tokens(); - }), - componentValues.slice(singleSimpleBlockIndex + 1).flatMap((x) => { - return x.tokens(); - }), - ); -} - -export function parseMediaInParensFromSimpleBlock(simpleBlock: SimpleBlockNode) { - if (simpleBlock.startToken[0] !== TokenType.OpenParen) { - return false; - } - - const feature = parseMediaFeature(simpleBlock); - if (feature !== false) { - return new MediaInParens(feature, [simpleBlock.startToken], [simpleBlock.endToken]); - } - - const condition = parseMediaCondition(simpleBlock.value); - if (condition !== false) { - return new MediaInParens(condition, [simpleBlock.startToken], [simpleBlock.endToken]); - } - - return new MediaInParens(new GeneralEnclosed(simpleBlock)); -} diff --git a/packages/media-query-list-parser/src/nodes/media-not.ts b/packages/media-query-list-parser/src/nodes/media-not.ts index 4be6d0fff..2a9dae586 100644 --- a/packages/media-query-list-parser/src/nodes/media-not.ts +++ b/packages/media-query-list-parser/src/nodes/media-not.ts @@ -1,7 +1,5 @@ -import { ComponentValue, ComponentValueType, SimpleBlockNode } from '@csstools/css-parser-algorithms'; -import { CSSToken, stringify, TokenIdent } from '@csstools/css-tokenizer'; -import { isIdent } from '../util/component-value-is'; -import { MediaInParens, MediaInParensWalkerEntry, MediaInParensWalkerParent, parseMediaInParensFromSimpleBlock } from './media-in-parens'; +import { CSSToken, stringify } from '@csstools/css-tokenizer'; +import { MediaInParens, MediaInParensWalkerEntry, MediaInParensWalkerParent } from './media-in-parens'; export class MediaNot { type = 'media-not'; @@ -50,53 +48,3 @@ export class MediaNot { export type MediaNotWalkerEntry = MediaInParensWalkerEntry | MediaInParens; export type MediaNotWalkerParent = MediaInParensWalkerParent | MediaNot; - -export function parseMediaNot(componentValues: Array) { - let sawNot = false; - - for (let i = 0; i < componentValues.length; i++) { - const componentValue = componentValues[i]; - if (componentValue.type === ComponentValueType.Whitespace) { - continue; - } - - if (componentValue.type === ComponentValueType.Comment) { - continue; - } - - if (isIdent(componentValue)) { - const token = (componentValue.value as TokenIdent); - if (token[4].value.toLowerCase() === 'not') { - if (sawNot) { - return false; - } - - sawNot = true; - continue; - } - - return false; - } - - if (sawNot && componentValue.type === ComponentValueType.SimpleBlock) { - const media = parseMediaInParensFromSimpleBlock(componentValue as SimpleBlockNode); - if (media === false) { - return false; - } - - return { - advance: i, - node: new MediaNot( - componentValues.slice(0, i).flatMap((x) => { - return x.tokens(); - }), - media, - ), - }; - } - - return false; - } - - return false; -} diff --git a/packages/media-query-list-parser/src/nodes/media-or.ts b/packages/media-query-list-parser/src/nodes/media-or.ts index 558d10bae..c3945fe2f 100644 --- a/packages/media-query-list-parser/src/nodes/media-or.ts +++ b/packages/media-query-list-parser/src/nodes/media-or.ts @@ -1,7 +1,5 @@ -import { ComponentValue, ComponentValueType, SimpleBlockNode } from '@csstools/css-parser-algorithms'; -import { CSSToken, stringify, TokenIdent } from '@csstools/css-tokenizer'; -import { isIdent } from '../util/component-value-is'; -import { MediaInParens, MediaInParensWalkerEntry, MediaInParensWalkerParent, parseMediaInParensFromSimpleBlock } from './media-in-parens'; +import { CSSToken, stringify } from '@csstools/css-tokenizer'; +import { MediaInParens, MediaInParensWalkerEntry, MediaInParensWalkerParent } from './media-in-parens'; export class MediaOr { type = 'media-or'; @@ -50,53 +48,3 @@ export class MediaOr { export type MediaOrWalkerEntry = MediaInParensWalkerEntry | MediaInParens; export type MediaOrWalkerParent = MediaInParensWalkerParent | MediaOr; - -export function parseMediaOr(componentValues: Array) { - let sawOr = false; - - for (let i = 0; i < componentValues.length; i++) { - const componentValue = componentValues[i]; - if (componentValue.type === ComponentValueType.Whitespace) { - continue; - } - - if (componentValue.type === ComponentValueType.Comment) { - continue; - } - - if (isIdent(componentValue)) { - const token = (componentValue.value as TokenIdent); - if (token[4].value.toLowerCase() === 'or') { - if (sawOr) { - return false; - } - - sawOr = true; - continue; - } - - return false; - } - - if (sawOr && componentValue.type === ComponentValueType.SimpleBlock) { - const media = parseMediaInParensFromSimpleBlock(componentValue as SimpleBlockNode); - if (media === false) { - return false; - } - - return { - advance: i, - node: new MediaOr( - componentValues.slice(0, i).flatMap((x) => { - return x.tokens(); - }), - media, - ), - }; - } - - return false; - } - - return false; -} diff --git a/packages/media-query-list-parser/src/nodes/media-query.ts b/packages/media-query-list-parser/src/nodes/media-query.ts index ad2780997..fae90842b 100644 --- a/packages/media-query-list-parser/src/nodes/media-query.ts +++ b/packages/media-query-list-parser/src/nodes/media-query.ts @@ -1,6 +1,5 @@ import { CSSToken, stringify } from '@csstools/css-tokenizer'; import { MediaCondition, MediaConditionWalkerEntry, MediaConditionWalkerParent } from './media-condition'; -import { MediaConditionWithoutOr, MediaConditionWithoutOrWalkerEntry, MediaConditionWithoutOrWalkerParent } from './media-condition-without-or'; export type MediaQuery = MediaQueryWithType | MediaQueryWithoutType; @@ -9,9 +8,9 @@ export class MediaQueryWithType { modifier: Array; mediaType: Array; - media: MediaConditionWithoutOr | null = null; + media: MediaCondition | null = null; - constructor(modifier: Array, mediaType: Array, media?: MediaConditionWithoutOr | null) { + constructor(modifier: Array, mediaType: Array, media?: MediaCondition | null) { this.modifier = modifier; this.mediaType = mediaType; this.media = media; @@ -40,7 +39,7 @@ export class MediaQueryWithType { return stringify(...this.modifier) + stringify(...this.mediaType); } - indexOf(item: MediaConditionWithoutOr): number | string { + indexOf(item: MediaCondition): number | string { if (item === this.media) { return 'media'; } @@ -63,8 +62,8 @@ export class MediaQueryWithType { } } -export type MediaQueryWithTypeWalkerEntry = MediaConditionWithoutOrWalkerEntry | MediaConditionWithoutOr; -export type MediaQueryWithTypeWalkerParent = MediaConditionWithoutOrWalkerParent | MediaQueryWithType; +export type MediaQueryWithTypeWalkerEntry = MediaConditionWalkerEntry | MediaCondition; +export type MediaQueryWithTypeWalkerParent = MediaConditionWalkerParent | MediaQueryWithType; export class MediaQueryWithoutType { type = 'media-query-without-type'; diff --git a/packages/media-query-list-parser/src/parser/parse-media-query.ts b/packages/media-query-list-parser/src/parser/parse-media-query.ts new file mode 100644 index 000000000..727c3da23 --- /dev/null +++ b/packages/media-query-list-parser/src/parser/parse-media-query.ts @@ -0,0 +1,485 @@ +import { ComponentValue, ComponentValueType, SimpleBlockNode, TokenNode } from '@csstools/css-parser-algorithms'; +import { CSSToken, TokenIdent, TokenType } from '@csstools/css-tokenizer'; +import { GeneralEnclosed } from '../nodes/general-enclosed'; +import { MediaAnd } from '../nodes/media-and'; +import { MediaCondition } from '../nodes/media-condition'; +import { MediaConditionListWithAnd, MediaConditionListWithOr } from '../nodes/media-condition-list'; +import { parseMediaFeature } from '../nodes/media-feature'; +import { MediaInParens } from '../nodes/media-in-parens'; +import { MediaNot } from '../nodes/media-not'; +import { MediaOr } from '../nodes/media-or'; +import { MediaQueryWithoutType, MediaQueryWithType } from '../nodes/media-query'; +import { modifierFromToken } from '../nodes/media-query-modifier'; +import { isIdent } from '../util/component-value-is'; + +export function parseMediaQuery(componentValues: Array) { + { + const condition = parseMediaCondition(componentValues); + if (condition !== false) { + return new MediaQueryWithoutType(condition); + } + } + + { + let modifierIndex = -1; + let typeIndex = -1; + + for (let i = 0; i < componentValues.length; i++) { + const componentValue = componentValues[i]; + if (componentValue.type === ComponentValueType.Whitespace) { + continue; + } + + if (componentValue.type === ComponentValueType.Comment) { + continue; + } + + if (componentValue.type === ComponentValueType.Token) { + const token = (componentValue as TokenNode).value; + if (token[0] === TokenType.Ident && modifierFromToken(token)) { + modifierIndex = i; + continue; + } + + if (token[0] === TokenType.Ident) { + typeIndex = i; + continue; + } + } + + break; + } + + const remainder = componentValues.slice(Math.max(modifierIndex, typeIndex) + 1); + const condition = parseMediaConditionWithoutOr(remainder); + if (condition === false) { + return false; + } + + let modifierTokens: Array = []; + let typeTokens: Array = []; + + if (modifierIndex !== -1) { + modifierTokens = componentValues.slice(0, modifierIndex + 1).flatMap((x) => { + return x.tokens(); + }); + + if (typeIndex !== -1) { + typeTokens = componentValues.slice(modifierIndex + 1, typeIndex + 1).flatMap((x) => { + return x.tokens(); + }); + } + } else if (typeIndex !== -1) { + typeTokens = componentValues.slice(0, typeIndex + 1).flatMap((x) => { + return x.tokens(); + }); + } + + return new MediaQueryWithType( + modifierTokens, + typeTokens, + condition, + ); + } + +} + +export function parseMediaConditionListWithOr(componentValues: Array) { + let leading: MediaInParens | false = false; + const list: Array = []; + let firstIndex = -1; + let lastIndex = -1; + + for (let i = 0; i < componentValues.length; i++) { + if (leading) { + const part = parseMediaOr(componentValues.slice(i)); + if (part !== false) { + i += part.advance; + list.push(part.node); + lastIndex = i; + continue; + } + } + + const componentValue = componentValues[i]; + if (componentValue.type === ComponentValueType.Whitespace) { + continue; + } + + if (componentValue.type === ComponentValueType.Comment) { + continue; + } + + if (leading) { + return false; + } + + if (leading === false && componentValue.type === ComponentValueType.SimpleBlock) { + leading = parseMediaInParensFromSimpleBlock(componentValue as SimpleBlockNode); + if (leading === false) { + return false; + } + + firstIndex = i; + continue; + } + + return false; + } + + if (leading && list.length) { + return new MediaConditionListWithOr( + leading, + list, + componentValues.slice(0, firstIndex).flatMap((x) => { + return x.tokens(); + }), + componentValues.slice(lastIndex + 1).flatMap((x) => { + return x.tokens(); + }), + ); + } + + return false; +} + +export function parseMediaConditionListWithAnd(componentValues: Array) { + let leading: MediaInParens | false = false; + const list: Array = []; + let firstIndex = -1; + let lastIndex = -1; + + for (let i = 0; i < componentValues.length; i++) { + if (leading) { + const part = parseMediaAnd(componentValues.slice(i)); + if (part !== false) { + i += part.advance; + list.push(part.node); + lastIndex = i; + continue; + } + } + + const componentValue = componentValues[i]; + if (componentValue.type === ComponentValueType.Whitespace) { + continue; + } + + if (componentValue.type === ComponentValueType.Comment) { + continue; + } + + if (leading) { + return false; + } + + if (leading === false && componentValue.type === ComponentValueType.SimpleBlock) { + leading = parseMediaInParensFromSimpleBlock(componentValue as SimpleBlockNode); + if (leading === false) { + return false; + } + + firstIndex = i; + continue; + } + + return false; + } + + if (leading && list.length) { + return new MediaConditionListWithAnd( + leading, + list, + componentValues.slice(0, firstIndex).flatMap((x) => { + return x.tokens(); + }), + componentValues.slice(lastIndex + 1).flatMap((x) => { + return x.tokens(); + }), + ); + } + + return false; +} + +export function parseMediaCondition(componentValues: Array) { + const mediaNot = parseMediaNot(componentValues); + if (mediaNot !== false) { + return new MediaCondition(mediaNot); + } + + const mediaListAnd = parseMediaConditionListWithAnd(componentValues); + if (mediaListAnd !== false) { + return new MediaCondition(mediaListAnd); + } + + const mediaListOr = parseMediaConditionListWithOr(componentValues); + if (mediaListOr !== false) { + return new MediaCondition(mediaListOr); + } + + const mediaInParens = parseMediaInParens(componentValues); + if (mediaInParens !== false) { + return new MediaCondition(mediaInParens); + } + + return false; +} + +export function parseMediaConditionWithoutOr(componentValues: Array) { + const mediaNot = parseMediaNot(componentValues); + if (mediaNot !== false) { + return new MediaCondition(mediaNot); + } + + const mediaListAnd = parseMediaConditionListWithAnd(componentValues); + if (mediaListAnd !== false) { + return new MediaCondition(mediaListAnd); + } + + const mediaInParens = parseMediaInParens(componentValues); + if (mediaInParens !== false) { + return new MediaCondition(mediaInParens); + } + + return false; +} + +export function parseMediaInParens(componentValues: Array) { + let singleSimpleBlockIndex = -1; + + for (let i = 0; i < componentValues.length; i++) { + const componentValue = componentValues[i]; + if (componentValue.type === ComponentValueType.Whitespace) { + continue; + } + + if (componentValue.type === ComponentValueType.Comment) { + continue; + } + + if (componentValue.type === ComponentValueType.SimpleBlock) { + if (singleSimpleBlockIndex !== -1) { + return false; + } + + singleSimpleBlockIndex = i; + continue; + } + + return false; + } + + if (singleSimpleBlockIndex === -1) { + return false; + } + + const simpleBlock = componentValues[singleSimpleBlockIndex] as SimpleBlockNode; + if (simpleBlock.startToken[0] !== TokenType.OpenParen) { + return false; + } + + const before = [ + ...componentValues.slice(0, singleSimpleBlockIndex).flatMap((x) => { + return x.tokens(); + }), + simpleBlock.startToken, + ]; + + const after = [ + simpleBlock.endToken, + ...componentValues.slice(singleSimpleBlockIndex + 1).flatMap((x) => { + return x.tokens(); + }), + ]; + + const feature = parseMediaFeature(simpleBlock); + if (feature !== false) { + return new MediaInParens(feature, before, after); + } + + const condition = parseMediaCondition(simpleBlock.value); + if (condition !== false) { + return new MediaInParens(condition, before, after); + } + + return new MediaInParens( + new GeneralEnclosed(simpleBlock), + componentValues.slice(0, singleSimpleBlockIndex).flatMap((x) => { + return x.tokens(); + }), + componentValues.slice(singleSimpleBlockIndex + 1).flatMap((x) => { + return x.tokens(); + }), + ); +} + +export function parseMediaInParensFromSimpleBlock(simpleBlock: SimpleBlockNode) { + if (simpleBlock.startToken[0] !== TokenType.OpenParen) { + return false; + } + + const feature = parseMediaFeature(simpleBlock); + if (feature !== false) { + return new MediaInParens(feature, [simpleBlock.startToken], [simpleBlock.endToken]); + } + + const condition = parseMediaCondition(simpleBlock.value); + if (condition !== false) { + return new MediaInParens(condition, [simpleBlock.startToken], [simpleBlock.endToken]); + } + + return new MediaInParens(new GeneralEnclosed(simpleBlock)); +} + +export function parseMediaNot(componentValues: Array) { + let sawNot = false; + let node: MediaNot | null = null; + + for (let i = 0; i < componentValues.length; i++) { + const componentValue = componentValues[i]; + if (componentValue.type === ComponentValueType.Whitespace) { + continue; + } + + if (componentValue.type === ComponentValueType.Comment) { + continue; + } + + if (isIdent(componentValue)) { + const token = (componentValue.value as TokenIdent); + if (token[4].value.toLowerCase() === 'not') { + if (sawNot) { + return false; + } + + sawNot = true; + continue; + } + + return false; + } + + if (sawNot && componentValue.type === ComponentValueType.SimpleBlock) { + const media = parseMediaInParensFromSimpleBlock(componentValue as SimpleBlockNode); + if (media === false) { + return false; + } + + node = new MediaNot( + componentValues.slice(0, i).flatMap((x) => { + return x.tokens(); + }), + media, + ); + } + + return false; + } + + if (node) { + return node; + } + + return false; +} + +export function parseMediaOr(componentValues: Array) { + let sawOr = false; + + for (let i = 0; i < componentValues.length; i++) { + const componentValue = componentValues[i]; + if (componentValue.type === ComponentValueType.Whitespace) { + continue; + } + + if (componentValue.type === ComponentValueType.Comment) { + continue; + } + + if (isIdent(componentValue)) { + const token = (componentValue.value as TokenIdent); + if (token[4].value.toLowerCase() === 'or') { + if (sawOr) { + return false; + } + + sawOr = true; + continue; + } + + return false; + } + + if (sawOr && componentValue.type === ComponentValueType.SimpleBlock) { + const media = parseMediaInParensFromSimpleBlock(componentValue as SimpleBlockNode); + if (media === false) { + return false; + } + + return { + advance: i, + node: new MediaOr( + componentValues.slice(0, i).flatMap((x) => { + return x.tokens(); + }), + media, + ), + }; + } + + return false; + } + + return false; +} + +export function parseMediaAnd(componentValues: Array) { + let sawAnd = false; + + for (let i = 0; i < componentValues.length; i++) { + const componentValue = componentValues[i]; + if (componentValue.type === ComponentValueType.Whitespace) { + continue; + } + + if (componentValue.type === ComponentValueType.Comment) { + continue; + } + + if (isIdent(componentValue)) { + const token = (componentValue.value as TokenIdent); + if (token[4].value.toLowerCase() === 'and') { + if (sawAnd) { + return false; + } + + sawAnd = true; + continue; + } + + return false; + } + + if (sawAnd && componentValue.type === ComponentValueType.SimpleBlock) { + const media = parseMediaInParensFromSimpleBlock(componentValue as SimpleBlockNode); + if (media === false) { + return false; + } + + return { + advance: i, + node: new MediaAnd( + componentValues.slice(0, i).flatMap((x) => { + return x.tokens(); + }), + media, + ), + }; + } + + return false; + } + + return false; +} diff --git a/packages/media-query-list-parser/src/parser/parse.ts b/packages/media-query-list-parser/src/parser/parse.ts index 77d9bc1eb..b958160ef 100644 --- a/packages/media-query-list-parser/src/parser/parse.ts +++ b/packages/media-query-list-parser/src/parser/parse.ts @@ -1,8 +1,6 @@ -import { ComponentValueType, parseCommaSeparatedListOfComponentValues, SimpleBlockNode } from '@csstools/css-parser-algorithms'; +import { parseCommaSeparatedListOfComponentValues } from '@csstools/css-parser-algorithms'; import { tokenizer } from '@csstools/css-tokenizer'; -import { parseMediaCondition } from '../nodes/media-condition'; -import { parseMediaConditionListWithAnd } from '../nodes/media-condition-list'; -import { parseMediaInParensFromSimpleBlock } from '../nodes/media-in-parens'; +import { parseMediaQuery } from './parse-media-query'; export function parse(source: string) { const onParseError = (err) => { @@ -24,27 +22,9 @@ export function parse(source: string) { tokens.push(t.nextToken()); // EOF-token } - const parsed = parseCommaSeparatedListOfComponentValues(tokens, { + return parseCommaSeparatedListOfComponentValues(tokens, { onParseError: onParseError, - }); - - const mediaQueryList = parsed.map((componentValuesList) => { - const result = []; - - for (let i = 0; i < componentValuesList.length; i++) { - const componentValue = componentValuesList[i]; - if (componentValue.type === ComponentValueType.SimpleBlock) { - result.push(parseMediaInParensFromSimpleBlock(componentValue as SimpleBlockNode)); - } - } - - const x = parseMediaCondition(componentValuesList); - if (x) { - return x; - } - - return {}; - }); - - return mediaQueryList; + }).map((componentValuesList) => { + return parseMediaQuery(componentValuesList); + }).filter((x) => !!x); } diff --git a/packages/media-query-list-parser/test/test.mjs b/packages/media-query-list-parser/test/test.mjs index ee54b14ac..4780c553e 100644 --- a/packages/media-query-list-parser/test/test.mjs +++ b/packages/media-query-list-parser/test/test.mjs @@ -3,38 +3,53 @@ import { parse } from '@csstools/media-query-list-parser'; parse('(/* a comment */foo ) something else'); parse('not screen and (min-width: 300px) and (prefers-color-scheme:/* a comment */dark) and (width < 40vw) and (30px < width < 50rem)').forEach((mediaQuery) => { + console.log(JSON.stringify(mediaQuery.toString())); + if (!('walk' in mediaQuery)) { - console.log(mediaQuery); return; } - mediaQuery.walk((a) => { - console.log(a.node.type, a.parent.type); - console.log(a.node.toString()); - }); + // mediaQuery.walk((a) => { + // console.log(a.node.type, a.parent.type); + // console.log(a.node.toString()); + // }); }); -parse('(resolution < infinite) and (infinite <= resolution)').forEach((mediaQuery) => { - console.log(mediaQuery.toString()); +parse('(resolution < infinite) and (infinite <= resolution) ').forEach((mediaQuery) => { + console.log(JSON.stringify(mediaQuery.toString())); if (!('walk' in mediaQuery)) { return; } - mediaQuery.walk((a) => { - console.log(a.node.type, a.parent.type); - console.log(a.node.toString()); - }); + // mediaQuery.walk((a) => { + // console.log(a.node.type, a.parent.type); + // console.log(a.node.toString()); + // }); }); parse('(width < calc(50vw - 3rem))').forEach((mediaQuery) => { + console.log(JSON.stringify(mediaQuery.toString())); + + if (!('walk' in mediaQuery)) { + return; + } + + // mediaQuery.walk((a) => { + // console.log(a.node.type, a.parent.type); + // console.log(a.node.toString()); + // }); +}); + +parse('screen and not (min-width: 300px)').forEach((mediaQuery) => { + console.log(JSON.stringify(mediaQuery.toString())); + if (!('walk' in mediaQuery)) { - console.log(mediaQuery); return; } - mediaQuery.walk((a) => { - console.log(a.node.type, a.parent.type); - console.log(a.node.toString()); - }); + // mediaQuery.walk((a) => { + // console.log(a.node.type, a.parent.type); + // console.log(a.node.toString()); + // }); }); From 2148f11ce24172922b30396609dcda2ee97bc401 Mon Sep 17 00:00:00 2001 From: Romain Menke Date: Sun, 23 Oct 2022 23:30:40 +0200 Subject: [PATCH 13/35] toJSON --- .../src/nodes/general-enclosed.ts | 7 +++++ .../src/nodes/media-and.ts | 8 ++++++ .../src/nodes/media-condition-list.ts | 20 +++++++++++++ .../src/nodes/media-condition.ts | 7 +++++ .../src/nodes/media-feature-name.ts | 8 ++++++ .../src/nodes/media-feature-plain.ts | 9 ++++++ .../src/nodes/media-feature-range.ts | 28 +++++++++++++++++++ .../src/nodes/media-feature-value.ts | 8 ++++++ .../src/nodes/media-feature.ts | 7 +++++ .../src/nodes/media-in-parens.ts | 9 ++++++ .../src/nodes/media-not.ts | 8 ++++++ .../src/nodes/media-or.ts | 8 ++++++ .../src/nodes/media-query.ts | 16 +++++++++++ .../src/parser/parse-media-query.ts | 27 +++++++++++++----- .../media-query-list-parser/test/test.mjs | 21 +++++++++++--- 15 files changed, 180 insertions(+), 11 deletions(-) diff --git a/packages/media-query-list-parser/src/nodes/general-enclosed.ts b/packages/media-query-list-parser/src/nodes/general-enclosed.ts index eb7ca0774..d7ea7b4af 100644 --- a/packages/media-query-list-parser/src/nodes/general-enclosed.ts +++ b/packages/media-query-list-parser/src/nodes/general-enclosed.ts @@ -40,6 +40,13 @@ export class GeneralEnclosed { return this.value.walk(cb); } } + + toJSON() { + return { + type: this.type, + tokens: this.tokens(), + }; + } } export type GeneralEnclosedWalkerEntry = ComponentValue; diff --git a/packages/media-query-list-parser/src/nodes/media-and.ts b/packages/media-query-list-parser/src/nodes/media-and.ts index b6695c07d..f14df64d6 100644 --- a/packages/media-query-list-parser/src/nodes/media-and.ts +++ b/packages/media-query-list-parser/src/nodes/media-and.ts @@ -44,6 +44,14 @@ export class MediaAnd { return this.media.walk(cb); } + + toJSON() { + return { + type: this.type, + modifier: this.modifier, + media: this.media.toJSON(), + }; + } } export type MediaAndWalkerEntry = MediaInParensWalkerEntry | MediaInParens; diff --git a/packages/media-query-list-parser/src/nodes/media-condition-list.ts b/packages/media-query-list-parser/src/nodes/media-condition-list.ts index 588c3565a..641f78cf4 100644 --- a/packages/media-query-list-parser/src/nodes/media-condition-list.ts +++ b/packages/media-query-list-parser/src/nodes/media-condition-list.ts @@ -93,6 +93,16 @@ export class MediaConditionListWithAnd { return false; } } + + toJSON() { + return { + type: this.type, + leading: this.leading.toJSON(), + list: this.list.map((x) => x.toJSON()), + before: this.before, + after: this.after, + }; + } } export type MediaConditionListWithAndWalkerEntry = MediaAndWalkerEntry | MediaAnd; @@ -186,6 +196,16 @@ export class MediaConditionListWithOr { return false; } } + + toJSON() { + return { + type: this.type, + leading: this.leading.toJSON(), + list: this.list.map((x) => x.toJSON()), + before: this.before, + after: this.after, + }; + } } export type MediaConditionListWithOrWalkerEntry = MediaOrWalkerEntry | MediaOr; diff --git a/packages/media-query-list-parser/src/nodes/media-condition.ts b/packages/media-query-list-parser/src/nodes/media-condition.ts index 6846d2f65..fff8842e2 100644 --- a/packages/media-query-list-parser/src/nodes/media-condition.ts +++ b/packages/media-query-list-parser/src/nodes/media-condition.ts @@ -40,6 +40,13 @@ export class MediaCondition { return this.media.walk(cb); } + + toJSON() { + return { + type: this.type, + media: this.media.toJSON(), + }; + } } export type MediaConditionWalkerEntry = MediaNotWalkerEntry | MediaConditionListWithAndWalkerEntry | MediaConditionListWithOrWalkerEntry | MediaNot | MediaConditionListWithAnd | MediaConditionListWithOr; diff --git a/packages/media-query-list-parser/src/nodes/media-feature-name.ts b/packages/media-query-list-parser/src/nodes/media-feature-name.ts index 102669bd4..339fa9683 100644 --- a/packages/media-query-list-parser/src/nodes/media-feature-name.ts +++ b/packages/media-query-list-parser/src/nodes/media-feature-name.ts @@ -45,6 +45,14 @@ export class MediaFeatureName { return this.name; } } + + toJSON() { + return { + type: this.type, + name: this.getName(), + tokens: this.tokens(), + }; + } } export function parseMediaFeatureName(componentValues: Array) { diff --git a/packages/media-query-list-parser/src/nodes/media-feature-plain.ts b/packages/media-query-list-parser/src/nodes/media-feature-plain.ts index 1eaaff61c..db91d3575 100644 --- a/packages/media-query-list-parser/src/nodes/media-feature-plain.ts +++ b/packages/media-query-list-parser/src/nodes/media-feature-plain.ts @@ -57,6 +57,15 @@ export class MediaFeaturePlain { return this.value.walk(cb); } + + toJSON() { + return { + type: this.type, + name: this.name.toJSON(), + value: this.value.toJSON(), + tokens: this.tokens(), + }; + } } export type MediaFeaturePlainWalkerEntry = MediaFeatureValueWalkerEntry | MediaFeatureValue; diff --git a/packages/media-query-list-parser/src/nodes/media-feature-range.ts b/packages/media-query-list-parser/src/nodes/media-feature-range.ts index db08fc92e..d454a7ecd 100644 --- a/packages/media-query-list-parser/src/nodes/media-feature-range.ts +++ b/packages/media-query-list-parser/src/nodes/media-feature-range.ts @@ -68,6 +68,15 @@ export class MediaFeatureRangeNameValue { return this.value.walk(cb); } } + + toJSON() { + return { + type: this.type, + name: this.name.toJSON(), + value: this.value.toJSON(), + tokens: this.tokens(), + }; + } } export class MediaFeatureRangeValueName { @@ -130,6 +139,15 @@ export class MediaFeatureRangeValueName { return this.value.walk(cb); } } + + toJSON() { + return { + type: this.type, + name: this.name.toJSON(), + value: this.value.toJSON(), + tokens: this.tokens(), + }; + } } export class MediaFeatureRangeValueNameValue { @@ -222,6 +240,16 @@ export class MediaFeatureRangeValueNameValue { } } } + + toJSON() { + return { + type: this.type, + name: this.name.toJSON(), + valueOne: this.valueOne.toJSON(), + valueTwo: this.valueTwo.toJSON(), + tokens: this.tokens(), + }; + } } export type MediaFeatureRangeWalkerEntry = MediaFeatureValueWalkerEntry | MediaFeatureValue; diff --git a/packages/media-query-list-parser/src/nodes/media-feature-value.ts b/packages/media-query-list-parser/src/nodes/media-feature-value.ts index 89be76753..f790ea1aa 100644 --- a/packages/media-query-list-parser/src/nodes/media-feature-value.ts +++ b/packages/media-query-list-parser/src/nodes/media-feature-value.ts @@ -67,6 +67,14 @@ export class MediaFeatureValue { return this.value.walk(cb); } } + + toJSON() { + return { + type: this.type, + value: this.value.toString(), + tokens: this.tokens(), + }; + } } export type MediaFeatureValueWalkerEntry = ComponentValue | Array; diff --git a/packages/media-query-list-parser/src/nodes/media-feature.ts b/packages/media-query-list-parser/src/nodes/media-feature.ts index a9b026ce6..b496587c1 100644 --- a/packages/media-query-list-parser/src/nodes/media-feature.ts +++ b/packages/media-query-list-parser/src/nodes/media-feature.ts @@ -44,6 +44,13 @@ export class MediaFeature { return this.feature.walk(cb); } } + + toJSON() { + return { + type: this.type, + feature: this.feature.toJSON(), + }; + } } export type MediaFeatureWalkerEntry = MediaFeaturePlainWalkerEntry | MediaFeatureRangeWalkerEntry | MediaFeaturePlain | MediaFeatureBoolean | MediaFeatureRange; diff --git a/packages/media-query-list-parser/src/nodes/media-in-parens.ts b/packages/media-query-list-parser/src/nodes/media-in-parens.ts index 5b351b81d..f918dcb97 100644 --- a/packages/media-query-list-parser/src/nodes/media-in-parens.ts +++ b/packages/media-query-list-parser/src/nodes/media-in-parens.ts @@ -59,6 +59,15 @@ export class MediaInParens { return this.media.walk(cb); } } + + toJSON() { + return { + type: this.type, + media: this.media.toJSON(), + before: this.before, + after: this.after, + }; + } } export type MediaInParensWalkerEntry = ComponentValue | Array | GeneralEnclosed | MediaAnd | MediaConditionList | MediaCondition | MediaFeatureBoolean | MediaFeatureName | MediaFeaturePlain | MediaFeatureRange | MediaFeatureValue | MediaFeature | GeneralEnclosed | MediaInParens; diff --git a/packages/media-query-list-parser/src/nodes/media-not.ts b/packages/media-query-list-parser/src/nodes/media-not.ts index 2a9dae586..c81cbd4b8 100644 --- a/packages/media-query-list-parser/src/nodes/media-not.ts +++ b/packages/media-query-list-parser/src/nodes/media-not.ts @@ -44,6 +44,14 @@ export class MediaNot { return this.media.walk(cb); } + + toJSON() { + return { + type: this.type, + modifier: this.modifier, + media: this.media.toJSON(), + }; + } } export type MediaNotWalkerEntry = MediaInParensWalkerEntry | MediaInParens; diff --git a/packages/media-query-list-parser/src/nodes/media-or.ts b/packages/media-query-list-parser/src/nodes/media-or.ts index c3945fe2f..ac17c15bb 100644 --- a/packages/media-query-list-parser/src/nodes/media-or.ts +++ b/packages/media-query-list-parser/src/nodes/media-or.ts @@ -44,6 +44,14 @@ export class MediaOr { return this.media.walk(cb); } + + toJSON() { + return { + type: this.type, + modifier: this.modifier, + media: this.media.toJSON(), + }; + } } export type MediaOrWalkerEntry = MediaInParensWalkerEntry | MediaInParens; diff --git a/packages/media-query-list-parser/src/nodes/media-query.ts b/packages/media-query-list-parser/src/nodes/media-query.ts index fae90842b..ed9b03834 100644 --- a/packages/media-query-list-parser/src/nodes/media-query.ts +++ b/packages/media-query-list-parser/src/nodes/media-query.ts @@ -60,6 +60,15 @@ export class MediaQueryWithType { return this.media.walk(cb); } + + toJSON() { + return { + type: this.type, + modifier: this.modifier, + mediaType: this.mediaType, + media: this.media, + }; + } } export type MediaQueryWithTypeWalkerEntry = MediaConditionWalkerEntry | MediaCondition; @@ -103,6 +112,13 @@ export class MediaQueryWithoutType { return this.media.walk(cb); } + + toJSON() { + return { + type: this.type, + media: this.media, + }; + } } export type MediaQueryWithoutTypeWalkerEntry = MediaConditionWalkerEntry | MediaCondition; diff --git a/packages/media-query-list-parser/src/parser/parse-media-query.ts b/packages/media-query-list-parser/src/parser/parse-media-query.ts index 727c3da23..ba449bcfa 100644 --- a/packages/media-query-list-parser/src/parser/parse-media-query.ts +++ b/packages/media-query-list-parser/src/parser/parse-media-query.ts @@ -45,15 +45,14 @@ export function parseMediaQuery(componentValues: Array) { typeIndex = i; continue; } - } - break; - } + return false; + } - const remainder = componentValues.slice(Math.max(modifierIndex, typeIndex) + 1); - const condition = parseMediaConditionWithoutOr(remainder); - if (condition === false) { - return false; + const condition = parseMediaConditionWithoutOr(componentValues.slice(i)); + if (condition === false) { + return false; + } } let modifierTokens: Array = []; @@ -75,6 +74,20 @@ export function parseMediaQuery(componentValues: Array) { }); } + const remainder = componentValues.slice(Math.max(modifierIndex, typeIndex) + 1); + const condition = parseMediaConditionWithoutOr(remainder); + if (condition === false) { + return new MediaQueryWithType( + modifierTokens, + [ + ...typeTokens, + ...componentValues.slice(typeIndex + 1).flatMap((x) => { + return x.tokens(); + }), + ], + ); + } + return new MediaQueryWithType( modifierTokens, typeTokens, diff --git a/packages/media-query-list-parser/test/test.mjs b/packages/media-query-list-parser/test/test.mjs index 4780c553e..c615a485c 100644 --- a/packages/media-query-list-parser/test/test.mjs +++ b/packages/media-query-list-parser/test/test.mjs @@ -3,7 +3,7 @@ import { parse } from '@csstools/media-query-list-parser'; parse('(/* a comment */foo ) something else'); parse('not screen and (min-width: 300px) and (prefers-color-scheme:/* a comment */dark) and (width < 40vw) and (30px < width < 50rem)').forEach((mediaQuery) => { - console.log(JSON.stringify(mediaQuery.toString())); + console.log(JSON.stringify(mediaQuery, null, '\t')); if (!('walk' in mediaQuery)) { return; @@ -16,7 +16,7 @@ parse('not screen and (min-width: 300px) and (prefers-color-scheme:/* a comment }); parse('(resolution < infinite) and (infinite <= resolution) ').forEach((mediaQuery) => { - console.log(JSON.stringify(mediaQuery.toString())); + console.log(JSON.stringify(mediaQuery, null, '\t')); if (!('walk' in mediaQuery)) { return; @@ -29,7 +29,7 @@ parse('(resolution < infinite) and (infinite <= resolution) ').forEach((mediaQue }); parse('(width < calc(50vw - 3rem))').forEach((mediaQuery) => { - console.log(JSON.stringify(mediaQuery.toString())); + console.log(JSON.stringify(mediaQuery, null, '\t')); if (!('walk' in mediaQuery)) { return; @@ -42,7 +42,20 @@ parse('(width < calc(50vw - 3rem))').forEach((mediaQuery) => { }); parse('screen and not (min-width: 300px)').forEach((mediaQuery) => { - console.log(JSON.stringify(mediaQuery.toString())); + console.log(JSON.stringify(mediaQuery, null, '\t')); + + if (!('walk' in mediaQuery)) { + return; + } + + // mediaQuery.walk((a) => { + // console.log(a.node.type, a.parent.type); + // console.log(a.node.toString()); + // }); +}); + +parse('only screen ').forEach((mediaQuery) => { + console.log(JSON.stringify(mediaQuery, null, '\t')); if (!('walk' in mediaQuery)) { return; From 972f24fd6a7ea39d54ec703aa2aa3e2ced55de0d Mon Sep 17 00:00:00 2001 From: Romain Menke Date: Mon, 24 Oct 2022 10:36:40 +0200 Subject: [PATCH 14/35] add a test suite --- .../consume-component-block-function.ts | 51 ++ packages/media-query-list-parser/.gitignore | 1 + packages/media-query-list-parser/package.json | 3 +- .../src/nodes/media-feature-value.ts | 10 +- .../src/nodes/media-query.ts | 2 + .../test/cases/mf-plain/0001.expect.json | 126 +++++ .../test/cases/mf-plain/0001.mjs | 13 + .../test/cases/mf-plain/0002.expect.json | 182 +++++++ .../test/cases/mf-plain/0002.mjs | 13 + .../test/cases/various/0001.expect.json | 1 + .../test/cases/various/0001.mjs | 13 + .../test/cases/various/0002.expect.json | 494 ++++++++++++++++++ .../test/cases/various/0002.mjs | 13 + .../test/cases/various/0003.expect.json | 253 +++++++++ .../test/cases/various/0003.mjs | 13 + .../test/cases/various/0004.expect.json | 293 +++++++++++ .../test/cases/various/0004.mjs | 13 + .../test/cases/various/0005.expect.json | 177 +++++++ .../test/cases/various/0005.mjs | 13 + .../test/cases/various/0006.expect.json | 42 ++ .../test/cases/various/0006.mjs | 13 + .../media-query-list-parser/test/test.mjs | 76 +-- .../test/util/run-test.mjs | 25 + 23 files changed, 1770 insertions(+), 70 deletions(-) create mode 100644 packages/media-query-list-parser/test/cases/mf-plain/0001.expect.json create mode 100644 packages/media-query-list-parser/test/cases/mf-plain/0001.mjs create mode 100644 packages/media-query-list-parser/test/cases/mf-plain/0002.expect.json create mode 100644 packages/media-query-list-parser/test/cases/mf-plain/0002.mjs create mode 100644 packages/media-query-list-parser/test/cases/various/0001.expect.json create mode 100644 packages/media-query-list-parser/test/cases/various/0001.mjs create mode 100644 packages/media-query-list-parser/test/cases/various/0002.expect.json create mode 100644 packages/media-query-list-parser/test/cases/various/0002.mjs create mode 100644 packages/media-query-list-parser/test/cases/various/0003.expect.json create mode 100644 packages/media-query-list-parser/test/cases/various/0003.mjs create mode 100644 packages/media-query-list-parser/test/cases/various/0004.expect.json create mode 100644 packages/media-query-list-parser/test/cases/various/0004.mjs create mode 100644 packages/media-query-list-parser/test/cases/various/0005.expect.json create mode 100644 packages/media-query-list-parser/test/cases/various/0005.mjs create mode 100644 packages/media-query-list-parser/test/cases/various/0006.expect.json create mode 100644 packages/media-query-list-parser/test/cases/various/0006.mjs create mode 100644 packages/media-query-list-parser/test/util/run-test.mjs diff --git a/packages/css-parser-algorithms/src/consume/consume-component-block-function.ts b/packages/css-parser-algorithms/src/consume/consume-component-block-function.ts index 525b852b6..c59a5dd3e 100644 --- a/packages/css-parser-algorithms/src/consume/consume-component-block-function.ts +++ b/packages/css-parser-algorithms/src/consume/consume-component-block-function.ts @@ -146,6 +146,14 @@ export class FunctionNode { return false; } } + + toJSON() { + return { + type: this.type, + name: this.name[4].value, + tokens: this.tokens(), + }; + } } // https://www.w3.org/TR/css-syntax-3/#consume-function @@ -271,6 +279,14 @@ export class SimpleBlockNode { return false; } } + + toJSON() { + return { + type: this.type, + startToken: this.startToken, + tokens: this.tokens(), + }; + } } /** https://www.w3.org/TR/css-syntax-3/#consume-simple-block */ @@ -340,6 +356,13 @@ export class WhitespaceNode { toString() { return stringify(...this.value); } + + toJSON() { + return { + type: this.type, + tokens: this.tokens(), + }; + } } export function consumeWhitespace(ctx: Context, tokens: Array): { advance: number, node: WhitespaceNode } { @@ -377,6 +400,13 @@ export class CommentNode { toString() { return stringify(this.value); } + + toJSON() { + return { + type: this.type, + tokens: this.tokens(), + }; + } } export function consumeComment(ctx: Context, tokens: Array): { advance: number, node: CommentNode } { @@ -431,6 +461,13 @@ export class TokenNode { toString() { return stringify(this.value); } + + toJSON() { + return { + type: this.type, + tokens: this.tokens(), + }; + } } export class UnclosedFunctionNode { @@ -449,6 +486,13 @@ export class UnclosedFunctionNode { toString() { return stringify(...this.value); } + + toJSON() { + return { + type: this.type, + tokens: this.tokens(), + }; + } } export class UnclosedSimpleBlockNode { @@ -467,4 +511,11 @@ export class UnclosedSimpleBlockNode { toString() { return stringify(...this.value); } + + toJSON() { + return { + type: this.type, + tokens: this.tokens(), + }; + } } diff --git a/packages/media-query-list-parser/.gitignore b/packages/media-query-list-parser/.gitignore index 7172b04f1..f548255b0 100644 --- a/packages/media-query-list-parser/.gitignore +++ b/packages/media-query-list-parser/.gitignore @@ -3,4 +3,5 @@ package-lock.json yarn.lock *.result.css *.result.css.map +*.result.json dist/* diff --git a/packages/media-query-list-parser/package.json b/packages/media-query-list-parser/package.json index c12761f4c..f1dfa6c4c 100644 --- a/packages/media-query-list-parser/package.json +++ b/packages/media-query-list-parser/package.json @@ -48,7 +48,8 @@ "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", - "test": "node ./test/test.mjs" + "test": "node ./test/test.mjs", + "test:rewrite-expects": "REWRITE_EXPECTS=true node ./test/test.mjs" }, "homepage": "https://github.com/csstools/postcss-plugins/tree/main/packages/media-query-list-parser#readme", "repository": { diff --git a/packages/media-query-list-parser/src/nodes/media-feature-value.ts b/packages/media-query-list-parser/src/nodes/media-feature-value.ts index f790ea1aa..8b31019de 100644 --- a/packages/media-query-list-parser/src/nodes/media-feature-value.ts +++ b/packages/media-query-list-parser/src/nodes/media-feature-value.ts @@ -69,9 +69,17 @@ export class MediaFeatureValue { } toJSON() { + if (Array.isArray(this.value)) { + return { + type: this.type, + value: this.value.map((x) => x.toJSON()), + tokens: this.tokens(), + }; + } + return { type: this.type, - value: this.value.toString(), + value: this.value.toJSON(), tokens: this.tokens(), }; } diff --git a/packages/media-query-list-parser/src/nodes/media-query.ts b/packages/media-query-list-parser/src/nodes/media-query.ts index ed9b03834..5b3075306 100644 --- a/packages/media-query-list-parser/src/nodes/media-query.ts +++ b/packages/media-query-list-parser/src/nodes/media-query.ts @@ -64,6 +64,7 @@ export class MediaQueryWithType { toJSON() { return { type: this.type, + string: this.toString(), modifier: this.modifier, mediaType: this.mediaType, media: this.media, @@ -116,6 +117,7 @@ export class MediaQueryWithoutType { toJSON() { return { type: this.type, + string: this.toString(), media: this.media, }; } diff --git a/packages/media-query-list-parser/test/cases/mf-plain/0001.expect.json b/packages/media-query-list-parser/test/cases/mf-plain/0001.expect.json new file mode 100644 index 000000000..b1b67c7ca --- /dev/null +++ b/packages/media-query-list-parser/test/cases/mf-plain/0001.expect.json @@ -0,0 +1,126 @@ +[ + { + "type": "media-query-without-type", + "string": "(min-width: 300px)", + "media": { + "type": "media-condition", + "media": { + "type": "media-in-parens", + "media": { + "type": "media-feature", + "feature": { + "type": "mf-plain", + "name": { + "type": "mf-name", + "name": "min-width", + "tokens": [ + [ + "ident-token", + "min-width", + 1, + 9, + { + "value": "min-width" + } + ] + ] + }, + "value": { + "type": "mf-value", + "value": { + "type": "token", + "tokens": [ + [ + "dimension-token", + "300px", + 12, + 16, + { + "value": 300, + "type": "integer", + "unit": "px" + } + ] + ] + }, + "tokens": [ + [ + "whitespace-token", + " ", + 11, + 11, + null + ], + [ + "dimension-token", + "300px", + 12, + 16, + { + "value": 300, + "type": "integer", + "unit": "px" + } + ] + ] + }, + "tokens": [ + [ + "ident-token", + "min-width", + 1, + 9, + { + "value": "min-width" + } + ], + [ + "colon-token", + ":", + 10, + 10, + null + ], + [ + "whitespace-token", + " ", + 11, + 11, + null + ], + [ + "dimension-token", + "300px", + 12, + 16, + { + "value": 300, + "type": "integer", + "unit": "px" + } + ] + ] + } + }, + "before": [ + [ + "(-token", + "(", + 0, + 0, + null + ] + ], + "after": [ + [ + ")-token", + ")", + 17, + 17, + null + ] + ] + } + } + } +] \ No newline at end of file diff --git a/packages/media-query-list-parser/test/cases/mf-plain/0001.mjs b/packages/media-query-list-parser/test/cases/mf-plain/0001.mjs new file mode 100644 index 000000000..b674721b7 --- /dev/null +++ b/packages/media-query-list-parser/test/cases/mf-plain/0001.mjs @@ -0,0 +1,13 @@ +import assert from 'assert'; +import { runTest } from '../../util/run-test.mjs'; + +runTest( + '(min-width: 300px)', + 'mf-plain/0001', + (actual, expected) => { + assert.deepStrictEqual( + actual, + expected, + ); + }, +); diff --git a/packages/media-query-list-parser/test/cases/mf-plain/0002.expect.json b/packages/media-query-list-parser/test/cases/mf-plain/0002.expect.json new file mode 100644 index 000000000..276dc2dec --- /dev/null +++ b/packages/media-query-list-parser/test/cases/mf-plain/0002.expect.json @@ -0,0 +1,182 @@ +[ + { + "type": "media-query-without-type", + "string": "/* comment 1 */(/* comment 2 */min-width/* comment 3 */:/* comment 4 */300px/* comment 5 */)/* comment 6 */", + "media": { + "type": "media-condition", + "media": { + "type": "media-in-parens", + "media": { + "type": "media-feature", + "feature": { + "type": "mf-plain", + "name": { + "type": "mf-name", + "name": "min-width", + "tokens": [ + [ + "comment", + "/* comment 2 */", + 16, + 30, + null + ], + [ + "ident-token", + "min-width", + 31, + 39, + { + "value": "min-width" + } + ], + [ + "comment", + "/* comment 3 */", + 40, + 54, + null + ] + ] + }, + "value": { + "type": "mf-value", + "value": { + "type": "token", + "tokens": [ + [ + "dimension-token", + "300px", + 71, + 75, + { + "value": 300, + "type": "integer", + "unit": "px" + } + ] + ] + }, + "tokens": [ + [ + "comment", + "/* comment 4 */", + 56, + 70, + null + ], + [ + "dimension-token", + "300px", + 71, + 75, + { + "value": 300, + "type": "integer", + "unit": "px" + } + ], + [ + "comment", + "/* comment 5 */", + 76, + 90, + null + ] + ] + }, + "tokens": [ + [ + "comment", + "/* comment 2 */", + 16, + 30, + null + ], + [ + "ident-token", + "min-width", + 31, + 39, + { + "value": "min-width" + } + ], + [ + "comment", + "/* comment 3 */", + 40, + 54, + null + ], + [ + "colon-token", + ":", + 55, + 55, + null + ], + [ + "comment", + "/* comment 4 */", + 56, + 70, + null + ], + [ + "dimension-token", + "300px", + 71, + 75, + { + "value": 300, + "type": "integer", + "unit": "px" + } + ], + [ + "comment", + "/* comment 5 */", + 76, + 90, + null + ] + ] + } + }, + "before": [ + [ + "comment", + "/* comment 1 */", + 0, + 14, + null + ], + [ + "(-token", + "(", + 15, + 15, + null + ] + ], + "after": [ + [ + ")-token", + ")", + 91, + 91, + null + ], + [ + "comment", + "/* comment 6 */", + 92, + 106, + null + ] + ] + } + } + } +] \ No newline at end of file diff --git a/packages/media-query-list-parser/test/cases/mf-plain/0002.mjs b/packages/media-query-list-parser/test/cases/mf-plain/0002.mjs new file mode 100644 index 000000000..90eb3e1f9 --- /dev/null +++ b/packages/media-query-list-parser/test/cases/mf-plain/0002.mjs @@ -0,0 +1,13 @@ +import assert from 'assert'; +import { runTest } from '../../util/run-test.mjs'; + +runTest( + '/* comment 1 */(/* comment 2 */min-width/* comment 3 */:/* comment 4 */300px/* comment 5 */)/* comment 6 */', + 'mf-plain/0002', + (actual, expected) => { + assert.deepStrictEqual( + actual, + expected, + ); + }, +); diff --git a/packages/media-query-list-parser/test/cases/various/0001.expect.json b/packages/media-query-list-parser/test/cases/various/0001.expect.json new file mode 100644 index 000000000..0637a088a --- /dev/null +++ b/packages/media-query-list-parser/test/cases/various/0001.expect.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/packages/media-query-list-parser/test/cases/various/0001.mjs b/packages/media-query-list-parser/test/cases/various/0001.mjs new file mode 100644 index 000000000..c5764560e --- /dev/null +++ b/packages/media-query-list-parser/test/cases/various/0001.mjs @@ -0,0 +1,13 @@ +import assert from 'assert'; +import { runTest } from '../../util/run-test.mjs'; + +runTest( + '(/* a comment */foo ) something else', + 'various/0001', + (actual, expected) => { + assert.deepStrictEqual( + actual, + expected, + ); + }, +); diff --git a/packages/media-query-list-parser/test/cases/various/0002.expect.json b/packages/media-query-list-parser/test/cases/various/0002.expect.json new file mode 100644 index 000000000..6ce2bb672 --- /dev/null +++ b/packages/media-query-list-parser/test/cases/various/0002.expect.json @@ -0,0 +1,494 @@ +[ + { + "type": "media-query-with-type", + "string": "not screen and (min-width: 300px) and (prefers-color-scheme:/* a comment */dark) and (width < 40vw) and (30px < width < 50rem)", + "modifier": [ + [ + "ident-token", + "not", + 0, + 2, + { + "value": "not" + } + ] + ], + "mediaType": [ + [ + "whitespace-token", + " ", + 3, + 3, + null + ], + [ + "ident-token", + "screen", + 4, + 9, + { + "value": "screen" + } + ], + [ + "whitespace-token", + " ", + 10, + 10, + null + ], + [ + "ident-token", + "and", + 11, + 13, + { + "value": "and" + } + ], + [ + "whitespace-token", + " ", + 14, + 14, + null + ], + [ + "(-token", + "(", + 15, + 15, + null + ], + [ + "ident-token", + "min-width", + 16, + 24, + { + "value": "min-width" + } + ], + [ + "colon-token", + ":", + 25, + 25, + null + ], + [ + "whitespace-token", + " ", + 26, + 26, + null + ], + [ + "dimension-token", + "300px", + 27, + 31, + { + "value": 300, + "type": "integer", + "unit": "px" + } + ], + [ + ")-token", + ")", + 32, + 32, + null + ], + [ + "whitespace-token", + " ", + 33, + 33, + null + ], + [ + "ident-token", + "and", + 34, + 36, + { + "value": "and" + } + ], + [ + "whitespace-token", + " ", + 37, + 37, + null + ], + [ + "(-token", + "(", + 38, + 38, + null + ], + [ + "ident-token", + "prefers-color-scheme", + 39, + 58, + { + "value": "prefers-color-scheme" + } + ], + [ + "colon-token", + ":", + 59, + 59, + null + ], + [ + "comment", + "/* a comment */", + 60, + 74, + null + ], + [ + "ident-token", + "dark", + 75, + 78, + { + "value": "dark" + } + ], + [ + ")-token", + ")", + 79, + 79, + null + ], + [ + "whitespace-token", + " ", + 80, + 80, + null + ], + [ + "ident-token", + "and", + 81, + 83, + { + "value": "and" + } + ], + [ + "whitespace-token", + " ", + 84, + 84, + null + ], + [ + "(-token", + "(", + 85, + 85, + null + ], + [ + "ident-token", + "width", + 86, + 90, + { + "value": "width" + } + ], + [ + "whitespace-token", + " ", + 91, + 91, + null + ], + [ + "delim-token", + "<", + 92, + 92, + { + "value": "<" + } + ], + [ + "whitespace-token", + " ", + 93, + 93, + null + ], + [ + "dimension-token", + "40vw", + 94, + 97, + { + "value": 40, + "type": "integer", + "unit": "vw" + } + ], + [ + ")-token", + ")", + 98, + 98, + null + ], + [ + "whitespace-token", + " ", + 99, + 99, + null + ], + [ + "ident-token", + "and", + 100, + 102, + { + "value": "and" + } + ] + ], + "media": { + "type": "media-condition", + "media": { + "type": "media-in-parens", + "media": { + "type": "media-feature", + "feature": { + "type": "mf-range-value-name-value", + "name": { + "type": "mf-name", + "name": "width", + "tokens": [ + [ + "whitespace-token", + " ", + 111, + 111, + null + ], + [ + "ident-token", + "width", + 112, + 116, + { + "value": "width" + } + ], + [ + "whitespace-token", + " ", + 117, + 117, + null + ] + ] + }, + "valueOne": { + "type": "mf-value", + "value": { + "type": "token", + "tokens": [ + [ + "dimension-token", + "30px", + 105, + 108, + { + "value": 30, + "type": "integer", + "unit": "px" + } + ] + ] + }, + "tokens": [ + [ + "dimension-token", + "30px", + 105, + 108, + { + "value": 30, + "type": "integer", + "unit": "px" + } + ], + [ + "whitespace-token", + " ", + 109, + 109, + null + ] + ] + }, + "valueTwo": { + "type": "mf-value", + "value": { + "type": "token", + "tokens": [ + [ + "dimension-token", + "50rem", + 120, + 124, + { + "value": 50, + "type": "integer", + "unit": "rem" + } + ] + ] + }, + "tokens": [ + [ + "whitespace-token", + " ", + 119, + 119, + null + ], + [ + "dimension-token", + "50rem", + 120, + 124, + { + "value": 50, + "type": "integer", + "unit": "rem" + } + ] + ] + }, + "tokens": [ + [ + "dimension-token", + "30px", + 105, + 108, + { + "value": 30, + "type": "integer", + "unit": "px" + } + ], + [ + "whitespace-token", + " ", + 109, + 109, + null + ], + [ + "delim-token", + "<", + 110, + 110, + { + "value": "<" + } + ], + [ + "whitespace-token", + " ", + 111, + 111, + null + ], + [ + "ident-token", + "width", + 112, + 116, + { + "value": "width" + } + ], + [ + "whitespace-token", + " ", + 117, + 117, + null + ], + [ + "delim-token", + "<", + 118, + 118, + { + "value": "<" + } + ], + [ + "whitespace-token", + " ", + 119, + 119, + null + ], + [ + "dimension-token", + "50rem", + 120, + 124, + { + "value": 50, + "type": "integer", + "unit": "rem" + } + ] + ] + } + }, + "before": [ + [ + "whitespace-token", + " ", + 103, + 103, + null + ], + [ + "(-token", + "(", + 104, + 104, + null + ] + ], + "after": [ + [ + ")-token", + ")", + 125, + 125, + null + ] + ] + } + } + } +] \ No newline at end of file diff --git a/packages/media-query-list-parser/test/cases/various/0002.mjs b/packages/media-query-list-parser/test/cases/various/0002.mjs new file mode 100644 index 000000000..964b4d9b9 --- /dev/null +++ b/packages/media-query-list-parser/test/cases/various/0002.mjs @@ -0,0 +1,13 @@ +import assert from 'assert'; +import { runTest } from '../../util/run-test.mjs'; + +runTest( + 'not screen and (min-width: 300px) and (prefers-color-scheme:/* a comment */dark) and (width < 40vw) and (30px < width < 50rem)', + 'various/0002', + (actual, expected) => { + assert.deepStrictEqual( + actual, + expected, + ); + }, +); diff --git a/packages/media-query-list-parser/test/cases/various/0003.expect.json b/packages/media-query-list-parser/test/cases/various/0003.expect.json new file mode 100644 index 000000000..60a2e8b79 --- /dev/null +++ b/packages/media-query-list-parser/test/cases/various/0003.expect.json @@ -0,0 +1,253 @@ +[ + { + "type": "media-query-without-type", + "string": "(resolution < infinite) and (infinite <= resolution) ", + "media": { + "type": "media-condition", + "media": { + "type": "media-condition-list-and", + "leading": { + "type": "media-in-parens", + "media": { + "type": "media-feature", + "feature": { + "type": "mf-range-name-value", + "name": { + "type": "mf-name", + "name": "resolution", + "tokens": [ + [ + "ident-token", + "resolution", + 1, + 10, + { + "value": "resolution" + } + ], + [ + "whitespace-token", + " ", + 11, + 11, + null + ] + ] + }, + "value": { + "type": "mf-value", + "value": { + "type": "token", + "tokens": [ + [ + "ident-token", + "infinite", + 14, + 21, + { + "value": "infinite" + } + ] + ] + }, + "tokens": [ + [ + "whitespace-token", + " ", + 13, + 13, + null + ], + [ + "ident-token", + "infinite", + 14, + 21, + { + "value": "infinite" + } + ] + ] + }, + "tokens": [ + [ + "ident-token", + "resolution", + 1, + 10, + { + "value": "resolution" + } + ], + [ + "whitespace-token", + " ", + 11, + 11, + null + ], + [ + "delim-token", + "<", + 12, + 12, + { + "value": "<" + } + ], + [ + "whitespace-token", + " ", + 13, + 13, + null + ], + [ + "ident-token", + "infinite", + 14, + 21, + { + "value": "infinite" + } + ] + ] + } + }, + "before": [ + [ + "(-token", + "(", + 0, + 0, + null + ] + ], + "after": [ + [ + ")-token", + ")", + 22, + 22, + null + ] + ] + }, + "list": [ + { + "type": "media-and", + "modifier": [ + [ + "whitespace-token", + " ", + 23, + 23, + null + ], + [ + "ident-token", + "and", + 24, + 26, + { + "value": "and" + } + ], + [ + "whitespace-token", + " ", + 27, + 27, + null + ] + ], + "media": { + "type": "media-in-parens", + "media": { + "type": "general-enclosed", + "tokens": [ + [ + "(-token", + "(", + 28, + 28, + null + ], + [ + "ident-token", + "infinite", + 29, + 36, + { + "value": "infinite" + } + ], + [ + "whitespace-token", + " ", + 37, + 37, + null + ], + [ + "delim-token", + "<", + 38, + 38, + { + "value": "<" + } + ], + [ + "delim-token", + "=", + 39, + 39, + { + "value": "=" + } + ], + [ + "whitespace-token", + " ", + 40, + 40, + null + ], + [ + "ident-token", + "resolution", + 41, + 50, + { + "value": "resolution" + } + ], + [ + ")-token", + ")", + 51, + 51, + null + ] + ] + }, + "before": [], + "after": [] + } + } + ], + "before": [], + "after": [ + [ + "whitespace-token", + " ", + 52, + 52, + null + ] + ] + } + } + } +] \ No newline at end of file diff --git a/packages/media-query-list-parser/test/cases/various/0003.mjs b/packages/media-query-list-parser/test/cases/various/0003.mjs new file mode 100644 index 000000000..2af154b33 --- /dev/null +++ b/packages/media-query-list-parser/test/cases/various/0003.mjs @@ -0,0 +1,13 @@ +import assert from 'assert'; +import { runTest } from '../../util/run-test.mjs'; + +runTest( + '(resolution < infinite) and (infinite <= resolution) ', + 'various/0003', + (actual, expected) => { + assert.deepStrictEqual( + actual, + expected, + ); + }, +); diff --git a/packages/media-query-list-parser/test/cases/various/0004.expect.json b/packages/media-query-list-parser/test/cases/various/0004.expect.json new file mode 100644 index 000000000..0c5514ab5 --- /dev/null +++ b/packages/media-query-list-parser/test/cases/various/0004.expect.json @@ -0,0 +1,293 @@ +[ + { + "type": "media-query-without-type", + "string": "(width < calc(50vw - 3rem))", + "media": { + "type": "media-condition", + "media": { + "type": "media-in-parens", + "media": { + "type": "media-feature", + "feature": { + "type": "mf-range-name-value", + "name": { + "type": "mf-name", + "name": "width", + "tokens": [ + [ + "ident-token", + "width", + 1, + 5, + { + "value": "width" + } + ], + [ + "whitespace-token", + " ", + 6, + 6, + null + ] + ] + }, + "value": { + "type": "mf-value", + "value": { + "type": "function", + "name": "calc", + "tokens": [ + [ + "function-token", + "calc(", + 9, + 13, + { + "value": "calc" + } + ], + [ + "dimension-token", + "50vw", + 14, + 17, + { + "value": 50, + "type": "integer", + "unit": "vw" + } + ], + [ + "whitespace-token", + " ", + 18, + 18, + null + ], + [ + "delim-token", + "-", + 19, + 19, + { + "value": "-" + } + ], + [ + "whitespace-token", + " ", + 20, + 20, + null + ], + [ + "dimension-token", + "3rem", + 21, + 24, + { + "value": 3, + "type": "integer", + "unit": "rem" + } + ], + [ + ")-token", + ")", + 25, + 25, + null + ] + ] + }, + "tokens": [ + [ + "whitespace-token", + " ", + 8, + 8, + null + ], + [ + "function-token", + "calc(", + 9, + 13, + { + "value": "calc" + } + ], + [ + "dimension-token", + "50vw", + 14, + 17, + { + "value": 50, + "type": "integer", + "unit": "vw" + } + ], + [ + "whitespace-token", + " ", + 18, + 18, + null + ], + [ + "delim-token", + "-", + 19, + 19, + { + "value": "-" + } + ], + [ + "whitespace-token", + " ", + 20, + 20, + null + ], + [ + "dimension-token", + "3rem", + 21, + 24, + { + "value": 3, + "type": "integer", + "unit": "rem" + } + ], + [ + ")-token", + ")", + 25, + 25, + null + ] + ] + }, + "tokens": [ + [ + "ident-token", + "width", + 1, + 5, + { + "value": "width" + } + ], + [ + "whitespace-token", + " ", + 6, + 6, + null + ], + [ + "delim-token", + "<", + 7, + 7, + { + "value": "<" + } + ], + [ + "whitespace-token", + " ", + 8, + 8, + null + ], + [ + "function-token", + "calc(", + 9, + 13, + { + "value": "calc" + } + ], + [ + "dimension-token", + "50vw", + 14, + 17, + { + "value": 50, + "type": "integer", + "unit": "vw" + } + ], + [ + "whitespace-token", + " ", + 18, + 18, + null + ], + [ + "delim-token", + "-", + 19, + 19, + { + "value": "-" + } + ], + [ + "whitespace-token", + " ", + 20, + 20, + null + ], + [ + "dimension-token", + "3rem", + 21, + 24, + { + "value": 3, + "type": "integer", + "unit": "rem" + } + ], + [ + ")-token", + ")", + 25, + 25, + null + ] + ] + } + }, + "before": [ + [ + "(-token", + "(", + 0, + 0, + null + ] + ], + "after": [ + [ + ")-token", + ")", + 26, + 26, + null + ] + ] + } + } + } +] \ No newline at end of file diff --git a/packages/media-query-list-parser/test/cases/various/0004.mjs b/packages/media-query-list-parser/test/cases/various/0004.mjs new file mode 100644 index 000000000..62f73bf0a --- /dev/null +++ b/packages/media-query-list-parser/test/cases/various/0004.mjs @@ -0,0 +1,13 @@ +import assert from 'assert'; +import { runTest } from '../../util/run-test.mjs'; + +runTest( + '(width < calc(50vw - 3rem))', + 'various/0004', + (actual, expected) => { + assert.deepStrictEqual( + actual, + expected, + ); + }, +); diff --git a/packages/media-query-list-parser/test/cases/various/0005.expect.json b/packages/media-query-list-parser/test/cases/various/0005.expect.json new file mode 100644 index 000000000..a44f49722 --- /dev/null +++ b/packages/media-query-list-parser/test/cases/various/0005.expect.json @@ -0,0 +1,177 @@ +[ + { + "type": "media-query-with-type", + "string": "screen and not (min-width: 300px)", + "modifier": [ + [ + "ident-token", + "screen", + 0, + 5, + { + "value": "screen" + } + ], + [ + "whitespace-token", + " ", + 6, + 6, + null + ], + [ + "ident-token", + "and", + 7, + 9, + { + "value": "and" + } + ], + [ + "whitespace-token", + " ", + 10, + 10, + null + ], + [ + "ident-token", + "not", + 11, + 13, + { + "value": "not" + } + ] + ], + "mediaType": [], + "media": { + "type": "media-condition", + "media": { + "type": "media-in-parens", + "media": { + "type": "media-feature", + "feature": { + "type": "mf-plain", + "name": { + "type": "mf-name", + "name": "min-width", + "tokens": [ + [ + "ident-token", + "min-width", + 16, + 24, + { + "value": "min-width" + } + ] + ] + }, + "value": { + "type": "mf-value", + "value": { + "type": "token", + "tokens": [ + [ + "dimension-token", + "300px", + 27, + 31, + { + "value": 300, + "type": "integer", + "unit": "px" + } + ] + ] + }, + "tokens": [ + [ + "whitespace-token", + " ", + 26, + 26, + null + ], + [ + "dimension-token", + "300px", + 27, + 31, + { + "value": 300, + "type": "integer", + "unit": "px" + } + ] + ] + }, + "tokens": [ + [ + "ident-token", + "min-width", + 16, + 24, + { + "value": "min-width" + } + ], + [ + "colon-token", + ":", + 25, + 25, + null + ], + [ + "whitespace-token", + " ", + 26, + 26, + null + ], + [ + "dimension-token", + "300px", + 27, + 31, + { + "value": 300, + "type": "integer", + "unit": "px" + } + ] + ] + } + }, + "before": [ + [ + "whitespace-token", + " ", + 14, + 14, + null + ], + [ + "(-token", + "(", + 15, + 15, + null + ] + ], + "after": [ + [ + ")-token", + ")", + 32, + 32, + null + ] + ] + } + } + } +] \ No newline at end of file diff --git a/packages/media-query-list-parser/test/cases/various/0005.mjs b/packages/media-query-list-parser/test/cases/various/0005.mjs new file mode 100644 index 000000000..7c55c608d --- /dev/null +++ b/packages/media-query-list-parser/test/cases/various/0005.mjs @@ -0,0 +1,13 @@ +import assert from 'assert'; +import { runTest } from '../../util/run-test.mjs'; + +runTest( + 'screen and not (min-width: 300px)', + 'various/0005', + (actual, expected) => { + assert.deepStrictEqual( + actual, + expected, + ); + }, +); diff --git a/packages/media-query-list-parser/test/cases/various/0006.expect.json b/packages/media-query-list-parser/test/cases/various/0006.expect.json new file mode 100644 index 000000000..521c0c9cd --- /dev/null +++ b/packages/media-query-list-parser/test/cases/various/0006.expect.json @@ -0,0 +1,42 @@ +[ + { + "type": "media-query-with-type", + "string": "only screen ", + "modifier": [ + [ + "ident-token", + "only", + 0, + 3, + { + "value": "only" + } + ] + ], + "mediaType": [ + [ + "whitespace-token", + " ", + 4, + 4, + null + ], + [ + "ident-token", + "screen", + 5, + 10, + { + "value": "screen" + } + ], + [ + "whitespace-token", + " ", + 11, + 11, + null + ] + ] + } +] \ No newline at end of file diff --git a/packages/media-query-list-parser/test/cases/various/0006.mjs b/packages/media-query-list-parser/test/cases/various/0006.mjs new file mode 100644 index 000000000..2d9faf758 --- /dev/null +++ b/packages/media-query-list-parser/test/cases/various/0006.mjs @@ -0,0 +1,13 @@ +import assert from 'assert'; +import { runTest } from '../../util/run-test.mjs'; + +runTest( + 'only screen ', + 'various/0006', + (actual, expected) => { + assert.deepStrictEqual( + actual, + expected, + ); + }, +); diff --git a/packages/media-query-list-parser/test/test.mjs b/packages/media-query-list-parser/test/test.mjs index c615a485c..a76b0a444 100644 --- a/packages/media-query-list-parser/test/test.mjs +++ b/packages/media-query-list-parser/test/test.mjs @@ -1,68 +1,8 @@ -import { parse } from '@csstools/media-query-list-parser'; - -parse('(/* a comment */foo ) something else'); - -parse('not screen and (min-width: 300px) and (prefers-color-scheme:/* a comment */dark) and (width < 40vw) and (30px < width < 50rem)').forEach((mediaQuery) => { - console.log(JSON.stringify(mediaQuery, null, '\t')); - - if (!('walk' in mediaQuery)) { - return; - } - - // mediaQuery.walk((a) => { - // console.log(a.node.type, a.parent.type); - // console.log(a.node.toString()); - // }); -}); - -parse('(resolution < infinite) and (infinite <= resolution) ').forEach((mediaQuery) => { - console.log(JSON.stringify(mediaQuery, null, '\t')); - - if (!('walk' in mediaQuery)) { - return; - } - - // mediaQuery.walk((a) => { - // console.log(a.node.type, a.parent.type); - // console.log(a.node.toString()); - // }); -}); - -parse('(width < calc(50vw - 3rem))').forEach((mediaQuery) => { - console.log(JSON.stringify(mediaQuery, null, '\t')); - - if (!('walk' in mediaQuery)) { - return; - } - - // mediaQuery.walk((a) => { - // console.log(a.node.type, a.parent.type); - // console.log(a.node.toString()); - // }); -}); - -parse('screen and not (min-width: 300px)').forEach((mediaQuery) => { - console.log(JSON.stringify(mediaQuery, null, '\t')); - - if (!('walk' in mediaQuery)) { - return; - } - - // mediaQuery.walk((a) => { - // console.log(a.node.type, a.parent.type); - // console.log(a.node.toString()); - // }); -}); - -parse('only screen ').forEach((mediaQuery) => { - console.log(JSON.stringify(mediaQuery, null, '\t')); - - if (!('walk' in mediaQuery)) { - return; - } - - // mediaQuery.walk((a) => { - // console.log(a.node.type, a.parent.type); - // console.log(a.node.toString()); - // }); -}); +import './cases/mf-plain/0001.mjs'; +import './cases/mf-plain/0002.mjs'; +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'; diff --git a/packages/media-query-list-parser/test/util/run-test.mjs b/packages/media-query-list-parser/test/util/run-test.mjs new file mode 100644 index 000000000..e1a747c7f --- /dev/null +++ b/packages/media-query-list-parser/test/util/run-test.mjs @@ -0,0 +1,25 @@ +import fs from 'fs'; +import path from 'path'; +import { parse } from '@csstools/media-query-list-parser'; + +export function runTest(source, testPath, assertEqual) { + const resultAST = parse(source); + 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 { + const expectData = JSON.parse(fs.readFileSync(path.join(process.cwd(), `./test/cases/${testPath}.expect.json`)).toString()); + + assertEqual( + resultAST.map((x) => x.toString()).join(','), + expectData.map((x) => x.string).join(','), + ); + + assertEqual( + JSON.parse(resultAST_JSON), + expectData, + ); + } +} From 48c621849f501cda7d85f26c0ad8d7271acd69c4 Mon Sep 17 00:00:00 2001 From: Romain Menke Date: Tue, 25 Oct 2022 10:01:04 +0200 Subject: [PATCH 15/35] more tests --- packages/media-query-list-parser/src/index.ts | 6 + .../src/nodes/media-not.ts | 3 +- .../src/nodes/media-query.ts | 5 +- .../src/nodes/node-type.ts | 5 + .../src/parser/parse-media-query.ts | 3 +- .../src/parser/parse.ts | 3 +- .../test/cases/media-not/0001.expect.json | 69 ++++++++++ .../test/cases/media-not/0001.mjs | 13 ++ .../test/cases/mf-boolean/0001.expect.json | 48 +++++++ .../test/cases/mf-boolean/0001.mjs | 13 ++ .../test/cases/mf-boolean/0002.expect.json | 76 +++++++++++ .../test/cases/mf-boolean/0002.mjs | 13 ++ .../test/cases/mf-boolean/0003.expect.json | 48 +++++++ .../test/cases/mf-boolean/0003.mjs | 13 ++ .../test/cases/mf-boolean/0004.expect.json | 48 +++++++ .../test/cases/mf-boolean/0004.mjs | 13 ++ .../test/cases/mf-boolean/0005.expect.json | 49 +++++++ .../test/cases/mf-boolean/0005.mjs | 13 ++ .../test/cases/mf-plain/0003.expect.json | 120 ++++++++++++++++++ .../test/cases/mf-plain/0003.mjs | 13 ++ .../test/cases/mf-plain/0004.expect.json | 81 ++++++++++++ .../test/cases/mf-plain/0004.mjs | 13 ++ .../test/cases/mf-plain/0005.expect.json | 1 + .../test/cases/mf-plain/0005.mjs | 13 ++ .../cases/query-with-type/0001.expect.json | 18 +++ .../test/cases/query-with-type/0001.mjs | 13 ++ .../cases/query-with-type/0002.expect.json | 35 +++++ .../test/cases/query-with-type/0002.mjs | 13 ++ .../cases/query-with-type/0003.expect.json | 35 +++++ .../test/cases/query-with-type/0003.mjs | 13 ++ .../media-query-list-parser/test/test.mjs | 16 +++ 31 files changed, 820 insertions(+), 5 deletions(-) create mode 100644 packages/media-query-list-parser/src/nodes/node-type.ts create mode 100644 packages/media-query-list-parser/test/cases/media-not/0001.expect.json create mode 100644 packages/media-query-list-parser/test/cases/media-not/0001.mjs create mode 100644 packages/media-query-list-parser/test/cases/mf-boolean/0001.expect.json create mode 100644 packages/media-query-list-parser/test/cases/mf-boolean/0001.mjs create mode 100644 packages/media-query-list-parser/test/cases/mf-boolean/0002.expect.json create mode 100644 packages/media-query-list-parser/test/cases/mf-boolean/0002.mjs create mode 100644 packages/media-query-list-parser/test/cases/mf-boolean/0003.expect.json create mode 100644 packages/media-query-list-parser/test/cases/mf-boolean/0003.mjs create mode 100644 packages/media-query-list-parser/test/cases/mf-boolean/0004.expect.json create mode 100644 packages/media-query-list-parser/test/cases/mf-boolean/0004.mjs create mode 100644 packages/media-query-list-parser/test/cases/mf-boolean/0005.expect.json create mode 100644 packages/media-query-list-parser/test/cases/mf-boolean/0005.mjs create mode 100644 packages/media-query-list-parser/test/cases/mf-plain/0003.expect.json create mode 100644 packages/media-query-list-parser/test/cases/mf-plain/0003.mjs create mode 100644 packages/media-query-list-parser/test/cases/mf-plain/0004.expect.json create mode 100644 packages/media-query-list-parser/test/cases/mf-plain/0004.mjs create mode 100644 packages/media-query-list-parser/test/cases/mf-plain/0005.expect.json create mode 100644 packages/media-query-list-parser/test/cases/mf-plain/0005.mjs create mode 100644 packages/media-query-list-parser/test/cases/query-with-type/0001.expect.json create mode 100644 packages/media-query-list-parser/test/cases/query-with-type/0001.mjs create mode 100644 packages/media-query-list-parser/test/cases/query-with-type/0002.expect.json create mode 100644 packages/media-query-list-parser/test/cases/query-with-type/0002.mjs create mode 100644 packages/media-query-list-parser/test/cases/query-with-type/0003.expect.json create mode 100644 packages/media-query-list-parser/test/cases/query-with-type/0003.mjs diff --git a/packages/media-query-list-parser/src/index.ts b/packages/media-query-list-parser/src/index.ts index 6c0dde8d4..7283214d3 100644 --- a/packages/media-query-list-parser/src/index.ts +++ b/packages/media-query-list-parser/src/index.ts @@ -1 +1,7 @@ export { parse } from './parser/parse'; +export { NodeType } from './nodes/node-type'; + +export { MediaInParens } from './nodes/media-in-parens'; +export { MediaCondition } from './nodes/media-condition'; +export { MediaNot } from './nodes/media-not'; +export { MediaQuery, MediaQueryWithType, MediaQueryWithoutType } from './nodes/media-query'; diff --git a/packages/media-query-list-parser/src/nodes/media-not.ts b/packages/media-query-list-parser/src/nodes/media-not.ts index c81cbd4b8..4605a3dfa 100644 --- a/packages/media-query-list-parser/src/nodes/media-not.ts +++ b/packages/media-query-list-parser/src/nodes/media-not.ts @@ -1,8 +1,9 @@ import { CSSToken, stringify } from '@csstools/css-tokenizer'; import { MediaInParens, MediaInParensWalkerEntry, MediaInParensWalkerParent } from './media-in-parens'; +import { NodeType } from './node-type'; export class MediaNot { - type = 'media-not'; + type = NodeType.MediaNot; modifier: Array; media: MediaInParens; diff --git a/packages/media-query-list-parser/src/nodes/media-query.ts b/packages/media-query-list-parser/src/nodes/media-query.ts index 5b3075306..c80a8f781 100644 --- a/packages/media-query-list-parser/src/nodes/media-query.ts +++ b/packages/media-query-list-parser/src/nodes/media-query.ts @@ -1,10 +1,11 @@ import { CSSToken, stringify } from '@csstools/css-tokenizer'; import { MediaCondition, MediaConditionWalkerEntry, MediaConditionWalkerParent } from './media-condition'; +import { NodeType } from './node-type'; export type MediaQuery = MediaQueryWithType | MediaQueryWithoutType; export class MediaQueryWithType { - type = 'media-query-with-type'; + type = NodeType.MediaQueryWithType; modifier: Array; mediaType: Array; @@ -76,7 +77,7 @@ export type MediaQueryWithTypeWalkerEntry = MediaConditionWalkerEntry | MediaCon export type MediaQueryWithTypeWalkerParent = MediaConditionWalkerParent | MediaQueryWithType; export class MediaQueryWithoutType { - type = 'media-query-without-type'; + type = NodeType.MediaQueryWithoutType; media: MediaCondition; diff --git a/packages/media-query-list-parser/src/nodes/node-type.ts b/packages/media-query-list-parser/src/nodes/node-type.ts new file mode 100644 index 000000000..826046e98 --- /dev/null +++ b/packages/media-query-list-parser/src/nodes/node-type.ts @@ -0,0 +1,5 @@ +export enum NodeType { + MediaQueryWithType = 'media-query-with-type', + MediaQueryWithoutType = 'media-query-without-type', + MediaNot = 'media-not', +} diff --git a/packages/media-query-list-parser/src/parser/parse-media-query.ts b/packages/media-query-list-parser/src/parser/parse-media-query.ts index ba449bcfa..186257dd5 100644 --- a/packages/media-query-list-parser/src/parser/parse-media-query.ts +++ b/packages/media-query-list-parser/src/parser/parse-media-query.ts @@ -94,7 +94,6 @@ export function parseMediaQuery(componentValues: Array) { condition, ); } - } export function parseMediaConditionListWithOr(componentValues: Array) { @@ -385,6 +384,8 @@ export function parseMediaNot(componentValues: Array) { }), media, ); + + continue; } return false; diff --git a/packages/media-query-list-parser/src/parser/parse.ts b/packages/media-query-list-parser/src/parser/parse.ts index b958160ef..f15f9607d 100644 --- a/packages/media-query-list-parser/src/parser/parse.ts +++ b/packages/media-query-list-parser/src/parser/parse.ts @@ -1,5 +1,6 @@ import { parseCommaSeparatedListOfComponentValues } from '@csstools/css-parser-algorithms'; import { tokenizer } from '@csstools/css-tokenizer'; +import { MediaQueryWithoutType, MediaQueryWithType } from '../nodes/media-query'; import { parseMediaQuery } from './parse-media-query'; export function parse(source: string) { @@ -26,5 +27,5 @@ export function parse(source: string) { onParseError: onParseError, }).map((componentValuesList) => { return parseMediaQuery(componentValuesList); - }).filter((x) => !!x); + }).filter((x) => !!x) as Array; } diff --git a/packages/media-query-list-parser/test/cases/media-not/0001.expect.json b/packages/media-query-list-parser/test/cases/media-not/0001.expect.json new file mode 100644 index 000000000..5118ae6f3 --- /dev/null +++ b/packages/media-query-list-parser/test/cases/media-not/0001.expect.json @@ -0,0 +1,69 @@ +[ + { + "type": "media-query-without-type", + "string": "not (color)", + "media": { + "type": "media-condition", + "media": { + "type": "media-not", + "modifier": [ + [ + "ident-token", + "not", + 0, + 2, + { + "value": "not" + } + ], + [ + "whitespace-token", + " ", + 3, + 3, + null + ] + ], + "media": { + "type": "media-in-parens", + "media": { + "type": "media-feature", + "feature": { + "type": "mf-boolean", + "name": "color", + "tokens": [ + [ + "ident-token", + "color", + 5, + 9, + { + "value": "color" + } + ] + ] + } + }, + "before": [ + [ + "(-token", + "(", + 4, + 4, + null + ] + ], + "after": [ + [ + ")-token", + ")", + 10, + 10, + null + ] + ] + } + } + } + } +] \ No newline at end of file diff --git a/packages/media-query-list-parser/test/cases/media-not/0001.mjs b/packages/media-query-list-parser/test/cases/media-not/0001.mjs new file mode 100644 index 000000000..5eb55f343 --- /dev/null +++ b/packages/media-query-list-parser/test/cases/media-not/0001.mjs @@ -0,0 +1,13 @@ +import assert from 'assert'; +import { runTest } from '../../util/run-test.mjs'; + +runTest( + 'not (color)', + 'media-not/0001', + (actual, expected) => { + assert.deepStrictEqual( + actual, + expected, + ); + }, +); diff --git a/packages/media-query-list-parser/test/cases/mf-boolean/0001.expect.json b/packages/media-query-list-parser/test/cases/mf-boolean/0001.expect.json new file mode 100644 index 000000000..0cfc0c59b --- /dev/null +++ b/packages/media-query-list-parser/test/cases/mf-boolean/0001.expect.json @@ -0,0 +1,48 @@ +[ + { + "type": "media-query-without-type", + "string": "(color)", + "media": { + "type": "media-condition", + "media": { + "type": "media-in-parens", + "media": { + "type": "media-feature", + "feature": { + "type": "mf-boolean", + "name": "color", + "tokens": [ + [ + "ident-token", + "color", + 1, + 5, + { + "value": "color" + } + ] + ] + } + }, + "before": [ + [ + "(-token", + "(", + 0, + 0, + null + ] + ], + "after": [ + [ + ")-token", + ")", + 6, + 6, + null + ] + ] + } + } + } +] \ No newline at end of file diff --git a/packages/media-query-list-parser/test/cases/mf-boolean/0001.mjs b/packages/media-query-list-parser/test/cases/mf-boolean/0001.mjs new file mode 100644 index 000000000..2686abd60 --- /dev/null +++ b/packages/media-query-list-parser/test/cases/mf-boolean/0001.mjs @@ -0,0 +1,13 @@ +import assert from 'assert'; +import { runTest } from '../../util/run-test.mjs'; + +runTest( + '(color)', + 'mf-boolean/0001', + (actual, expected) => { + assert.deepStrictEqual( + actual, + expected, + ); + }, +); diff --git a/packages/media-query-list-parser/test/cases/mf-boolean/0002.expect.json b/packages/media-query-list-parser/test/cases/mf-boolean/0002.expect.json new file mode 100644 index 000000000..dfe2f14de --- /dev/null +++ b/packages/media-query-list-parser/test/cases/mf-boolean/0002.expect.json @@ -0,0 +1,76 @@ +[ + { + "type": "media-query-without-type", + "string": "/* comment 1 */(/* comment 2 */color/* comment 3 */)/* comment 4 */", + "media": { + "type": "media-condition", + "media": { + "type": "media-in-parens", + "media": { + "type": "media-feature", + "feature": { + "type": "mf-boolean", + "name": "color", + "tokens": [ + [ + "comment", + "/* comment 2 */", + 16, + 30, + null + ], + [ + "ident-token", + "color", + 31, + 35, + { + "value": "color" + } + ], + [ + "comment", + "/* comment 3 */", + 36, + 50, + null + ] + ] + } + }, + "before": [ + [ + "comment", + "/* comment 1 */", + 0, + 14, + null + ], + [ + "(-token", + "(", + 15, + 15, + null + ] + ], + "after": [ + [ + ")-token", + ")", + 51, + 51, + null + ], + [ + "comment", + "/* comment 4 */", + 52, + 66, + null + ] + ] + } + } + } +] \ No newline at end of file diff --git a/packages/media-query-list-parser/test/cases/mf-boolean/0002.mjs b/packages/media-query-list-parser/test/cases/mf-boolean/0002.mjs new file mode 100644 index 000000000..f8266d441 --- /dev/null +++ b/packages/media-query-list-parser/test/cases/mf-boolean/0002.mjs @@ -0,0 +1,13 @@ +import assert from 'assert'; +import { runTest } from '../../util/run-test.mjs'; + +runTest( + '/* comment 1 */(/* comment 2 */color/* comment 3 */)/* comment 4 */', + 'mf-boolean/0002', + (actual, expected) => { + assert.deepStrictEqual( + actual, + expected, + ); + }, +); diff --git a/packages/media-query-list-parser/test/cases/mf-boolean/0003.expect.json b/packages/media-query-list-parser/test/cases/mf-boolean/0003.expect.json new file mode 100644 index 000000000..6d83ff6e8 --- /dev/null +++ b/packages/media-query-list-parser/test/cases/mf-boolean/0003.expect.json @@ -0,0 +1,48 @@ +[ + { + "type": "media-query-without-type", + "string": "(true)", + "media": { + "type": "media-condition", + "media": { + "type": "media-in-parens", + "media": { + "type": "media-feature", + "feature": { + "type": "mf-boolean", + "name": "true", + "tokens": [ + [ + "ident-token", + "true", + 1, + 4, + { + "value": "true" + } + ] + ] + } + }, + "before": [ + [ + "(-token", + "(", + 0, + 0, + null + ] + ], + "after": [ + [ + ")-token", + ")", + 5, + 5, + null + ] + ] + } + } + } +] \ No newline at end of file diff --git a/packages/media-query-list-parser/test/cases/mf-boolean/0003.mjs b/packages/media-query-list-parser/test/cases/mf-boolean/0003.mjs new file mode 100644 index 000000000..c0c9f5645 --- /dev/null +++ b/packages/media-query-list-parser/test/cases/mf-boolean/0003.mjs @@ -0,0 +1,13 @@ +import assert from 'assert'; +import { runTest } from '../../util/run-test.mjs'; + +runTest( + '(true)', + 'mf-boolean/0003', + (actual, expected) => { + assert.deepStrictEqual( + actual, + expected, + ); + }, +); diff --git a/packages/media-query-list-parser/test/cases/mf-boolean/0004.expect.json b/packages/media-query-list-parser/test/cases/mf-boolean/0004.expect.json new file mode 100644 index 000000000..7970b3331 --- /dev/null +++ b/packages/media-query-list-parser/test/cases/mf-boolean/0004.expect.json @@ -0,0 +1,48 @@ +[ + { + "type": "media-query-without-type", + "string": "(false)", + "media": { + "type": "media-condition", + "media": { + "type": "media-in-parens", + "media": { + "type": "media-feature", + "feature": { + "type": "mf-boolean", + "name": "false", + "tokens": [ + [ + "ident-token", + "false", + 1, + 5, + { + "value": "false" + } + ] + ] + } + }, + "before": [ + [ + "(-token", + "(", + 0, + 0, + null + ] + ], + "after": [ + [ + ")-token", + ")", + 6, + 6, + null + ] + ] + } + } + } +] \ No newline at end of file diff --git a/packages/media-query-list-parser/test/cases/mf-boolean/0004.mjs b/packages/media-query-list-parser/test/cases/mf-boolean/0004.mjs new file mode 100644 index 000000000..0a34a2dc8 --- /dev/null +++ b/packages/media-query-list-parser/test/cases/mf-boolean/0004.mjs @@ -0,0 +1,13 @@ +import assert from 'assert'; +import { runTest } from '../../util/run-test.mjs'; + +runTest( + '(false)', + 'mf-boolean/0004', + (actual, expected) => { + assert.deepStrictEqual( + actual, + expected, + ); + }, +); diff --git a/packages/media-query-list-parser/test/cases/mf-boolean/0005.expect.json b/packages/media-query-list-parser/test/cases/mf-boolean/0005.expect.json new file mode 100644 index 000000000..924b11622 --- /dev/null +++ b/packages/media-query-list-parser/test/cases/mf-boolean/0005.expect.json @@ -0,0 +1,49 @@ +[ + { + "type": "media-query-without-type", + "string": "(color())", + "media": { + "type": "media-condition", + "media": { + "type": "media-in-parens", + "media": { + "type": "general-enclosed", + "tokens": [ + [ + "(-token", + "(", + 0, + 0, + null + ], + [ + "function-token", + "color(", + 1, + 6, + { + "value": "color" + } + ], + [ + ")-token", + ")", + 7, + 7, + null + ], + [ + ")-token", + ")", + 8, + 8, + null + ] + ] + }, + "before": [], + "after": [] + } + } + } +] \ No newline at end of file diff --git a/packages/media-query-list-parser/test/cases/mf-boolean/0005.mjs b/packages/media-query-list-parser/test/cases/mf-boolean/0005.mjs new file mode 100644 index 000000000..c44c8c1f7 --- /dev/null +++ b/packages/media-query-list-parser/test/cases/mf-boolean/0005.mjs @@ -0,0 +1,13 @@ +import assert from 'assert'; +import { runTest } from '../../util/run-test.mjs'; + +runTest( + '(color())', + 'mf-boolean/0005', + (actual, expected) => { + assert.deepStrictEqual( + actual, + expected, + ); + }, +); diff --git a/packages/media-query-list-parser/test/cases/mf-plain/0003.expect.json b/packages/media-query-list-parser/test/cases/mf-plain/0003.expect.json new file mode 100644 index 000000000..e0c959e8d --- /dev/null +++ b/packages/media-query-list-parser/test/cases/mf-plain/0003.expect.json @@ -0,0 +1,120 @@ +[ + { + "type": "media-query-without-type", + "string": "(resolution: infinite)", + "media": { + "type": "media-condition", + "media": { + "type": "media-in-parens", + "media": { + "type": "media-feature", + "feature": { + "type": "mf-plain", + "name": { + "type": "mf-name", + "name": "resolution", + "tokens": [ + [ + "ident-token", + "resolution", + 1, + 10, + { + "value": "resolution" + } + ] + ] + }, + "value": { + "type": "mf-value", + "value": { + "type": "token", + "tokens": [ + [ + "ident-token", + "infinite", + 13, + 20, + { + "value": "infinite" + } + ] + ] + }, + "tokens": [ + [ + "whitespace-token", + " ", + 12, + 12, + null + ], + [ + "ident-token", + "infinite", + 13, + 20, + { + "value": "infinite" + } + ] + ] + }, + "tokens": [ + [ + "ident-token", + "resolution", + 1, + 10, + { + "value": "resolution" + } + ], + [ + "colon-token", + ":", + 11, + 11, + null + ], + [ + "whitespace-token", + " ", + 12, + 12, + null + ], + [ + "ident-token", + "infinite", + 13, + 20, + { + "value": "infinite" + } + ] + ] + } + }, + "before": [ + [ + "(-token", + "(", + 0, + 0, + null + ] + ], + "after": [ + [ + ")-token", + ")", + 21, + 21, + null + ] + ] + } + } + } +] \ No newline at end of file diff --git a/packages/media-query-list-parser/test/cases/mf-plain/0003.mjs b/packages/media-query-list-parser/test/cases/mf-plain/0003.mjs new file mode 100644 index 000000000..ad7c3adf2 --- /dev/null +++ b/packages/media-query-list-parser/test/cases/mf-plain/0003.mjs @@ -0,0 +1,13 @@ +import assert from 'assert'; +import { runTest } from '../../util/run-test.mjs'; + +runTest( + '(resolution: infinite)', + 'mf-plain/0003', + (actual, expected) => { + assert.deepStrictEqual( + actual, + expected, + ); + }, +); diff --git a/packages/media-query-list-parser/test/cases/mf-plain/0004.expect.json b/packages/media-query-list-parser/test/cases/mf-plain/0004.expect.json new file mode 100644 index 000000000..5c69041e4 --- /dev/null +++ b/packages/media-query-list-parser/test/cases/mf-plain/0004.expect.json @@ -0,0 +1,81 @@ +[ + { + "type": "media-query-without-type", + "string": "(resolution(foo): infinite)", + "media": { + "type": "media-condition", + "media": { + "type": "media-in-parens", + "media": { + "type": "general-enclosed", + "tokens": [ + [ + "(-token", + "(", + 0, + 0, + null + ], + [ + "function-token", + "resolution(", + 1, + 11, + { + "value": "resolution" + } + ], + [ + "ident-token", + "foo", + 12, + 14, + { + "value": "foo" + } + ], + [ + ")-token", + ")", + 15, + 15, + null + ], + [ + "colon-token", + ":", + 16, + 16, + null + ], + [ + "whitespace-token", + " ", + 17, + 17, + null + ], + [ + "ident-token", + "infinite", + 18, + 25, + { + "value": "infinite" + } + ], + [ + ")-token", + ")", + 26, + 26, + null + ] + ] + }, + "before": [], + "after": [] + } + } + } +] \ No newline at end of file diff --git a/packages/media-query-list-parser/test/cases/mf-plain/0004.mjs b/packages/media-query-list-parser/test/cases/mf-plain/0004.mjs new file mode 100644 index 000000000..6d9a61a7b --- /dev/null +++ b/packages/media-query-list-parser/test/cases/mf-plain/0004.mjs @@ -0,0 +1,13 @@ +import assert from 'assert'; +import { runTest } from '../../util/run-test.mjs'; + +runTest( + '(resolution(foo): infinite)', + 'mf-plain/0004', + (actual, expected) => { + assert.deepStrictEqual( + actual, + expected, + ); + }, +); diff --git a/packages/media-query-list-parser/test/cases/mf-plain/0005.expect.json b/packages/media-query-list-parser/test/cases/mf-plain/0005.expect.json new file mode 100644 index 000000000..0637a088a --- /dev/null +++ b/packages/media-query-list-parser/test/cases/mf-plain/0005.expect.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/packages/media-query-list-parser/test/cases/mf-plain/0005.mjs b/packages/media-query-list-parser/test/cases/mf-plain/0005.mjs new file mode 100644 index 000000000..946d31898 --- /dev/null +++ b/packages/media-query-list-parser/test/cases/mf-plain/0005.mjs @@ -0,0 +1,13 @@ +import assert from 'assert'; +import { runTest } from '../../util/run-test.mjs'; + +runTest( + '[resolution: infinite]', + 'mf-plain/0005', + (actual, expected) => { + assert.deepStrictEqual( + actual, + expected, + ); + }, +); diff --git a/packages/media-query-list-parser/test/cases/query-with-type/0001.expect.json b/packages/media-query-list-parser/test/cases/query-with-type/0001.expect.json new file mode 100644 index 000000000..b32206ad3 --- /dev/null +++ b/packages/media-query-list-parser/test/cases/query-with-type/0001.expect.json @@ -0,0 +1,18 @@ +[ + { + "type": "media-query-with-type", + "string": "screen", + "modifier": [], + "mediaType": [ + [ + "ident-token", + "screen", + 0, + 5, + { + "value": "screen" + } + ] + ] + } +] \ No newline at end of file diff --git a/packages/media-query-list-parser/test/cases/query-with-type/0001.mjs b/packages/media-query-list-parser/test/cases/query-with-type/0001.mjs new file mode 100644 index 000000000..b8d1c3f6b --- /dev/null +++ b/packages/media-query-list-parser/test/cases/query-with-type/0001.mjs @@ -0,0 +1,13 @@ +import assert from 'assert'; +import { runTest } from '../../util/run-test.mjs'; + +runTest( + 'screen', + 'query-with-type/0001', + (actual, expected) => { + assert.deepStrictEqual( + actual, + expected, + ); + }, +); diff --git a/packages/media-query-list-parser/test/cases/query-with-type/0002.expect.json b/packages/media-query-list-parser/test/cases/query-with-type/0002.expect.json new file mode 100644 index 000000000..59b3a0db1 --- /dev/null +++ b/packages/media-query-list-parser/test/cases/query-with-type/0002.expect.json @@ -0,0 +1,35 @@ +[ + { + "type": "media-query-with-type", + "string": "only screen", + "modifier": [ + [ + "ident-token", + "only", + 0, + 3, + { + "value": "only" + } + ] + ], + "mediaType": [ + [ + "whitespace-token", + " ", + 4, + 4, + null + ], + [ + "ident-token", + "screen", + 5, + 10, + { + "value": "screen" + } + ] + ] + } +] \ No newline at end of file diff --git a/packages/media-query-list-parser/test/cases/query-with-type/0002.mjs b/packages/media-query-list-parser/test/cases/query-with-type/0002.mjs new file mode 100644 index 000000000..d9da6523a --- /dev/null +++ b/packages/media-query-list-parser/test/cases/query-with-type/0002.mjs @@ -0,0 +1,13 @@ +import assert from 'assert'; +import { runTest } from '../../util/run-test.mjs'; + +runTest( + 'only screen', + 'query-with-type/0002', + (actual, expected) => { + assert.deepStrictEqual( + actual, + expected, + ); + }, +); diff --git a/packages/media-query-list-parser/test/cases/query-with-type/0003.expect.json b/packages/media-query-list-parser/test/cases/query-with-type/0003.expect.json new file mode 100644 index 000000000..65fc34d9d --- /dev/null +++ b/packages/media-query-list-parser/test/cases/query-with-type/0003.expect.json @@ -0,0 +1,35 @@ +[ + { + "type": "media-query-with-type", + "string": "not screen", + "modifier": [ + [ + "ident-token", + "not", + 0, + 2, + { + "value": "not" + } + ] + ], + "mediaType": [ + [ + "whitespace-token", + " ", + 3, + 3, + null + ], + [ + "ident-token", + "screen", + 4, + 9, + { + "value": "screen" + } + ] + ] + } +] \ No newline at end of file diff --git a/packages/media-query-list-parser/test/cases/query-with-type/0003.mjs b/packages/media-query-list-parser/test/cases/query-with-type/0003.mjs new file mode 100644 index 000000000..1684cf679 --- /dev/null +++ b/packages/media-query-list-parser/test/cases/query-with-type/0003.mjs @@ -0,0 +1,13 @@ +import assert from 'assert'; +import { runTest } from '../../util/run-test.mjs'; + +runTest( + 'not screen', + 'query-with-type/0003', + (actual, expected) => { + assert.deepStrictEqual( + actual, + expected, + ); + }, +); diff --git a/packages/media-query-list-parser/test/test.mjs b/packages/media-query-list-parser/test/test.mjs index a76b0a444..c219a4ac6 100644 --- a/packages/media-query-list-parser/test/test.mjs +++ b/packages/media-query-list-parser/test/test.mjs @@ -1,5 +1,21 @@ +import './cases/media-not/0001.mjs'; + +import './cases/mf-boolean/0001.mjs'; +import './cases/mf-boolean/0002.mjs'; +import './cases/mf-boolean/0003.mjs'; +import './cases/mf-boolean/0004.mjs'; +import './cases/mf-boolean/0005.mjs'; + import './cases/mf-plain/0001.mjs'; import './cases/mf-plain/0002.mjs'; +import './cases/mf-plain/0003.mjs'; +import './cases/mf-plain/0004.mjs'; +import './cases/mf-plain/0005.mjs'; + +import './cases/query-with-type/0001.mjs'; +import './cases/query-with-type/0002.mjs'; +import './cases/query-with-type/0003.mjs'; + import './cases/various/0001.mjs'; import './cases/various/0002.mjs'; import './cases/various/0003.mjs'; From ac8814f8276d40839b3dc5c9fa53aaccb3a843b2 Mon Sep 17 00:00:00 2001 From: Romain Menke Date: Wed, 26 Oct 2022 10:48:44 +0200 Subject: [PATCH 16/35] wip --- package.json | 1 + packages/css-parser-algorithms/.gitignore | 1 + packages/css-parser-algorithms/package.json | 7 +- .../consume-component-block-function.ts | 2 + ...omma-separated-list-of-component-values.ts | 1 - .../src/parse/parse-component-value.ts | 1 - .../parse/parse-list-of-component-values.ts | 1 - .../media-not/0001.list-comma.expect.json | 81 ++ .../media-not/0001.list-space.expect.json | 79 ++ .../test/cases/media-not/0001.mjs | 13 + .../mf-boolean/0001.list-comma.expect.json | 55 ++ .../mf-boolean/0001.list-space.expect.json | 53 ++ .../test/cases/mf-boolean/0001.mjs | 13 + .../mf-boolean/0002.list-comma.expect.json | 117 +++ .../mf-boolean/0002.list-space.expect.json | 115 +++ .../test/cases/mf-boolean/0002.mjs | 13 + .../mf-boolean/0003.list-comma.expect.json | 55 ++ .../mf-boolean/0003.list-space.expect.json | 53 ++ .../test/cases/mf-boolean/0003.mjs | 13 + .../mf-boolean/0004.list-comma.expect.json | 55 ++ .../mf-boolean/0004.list-space.expect.json | 53 ++ .../test/cases/mf-boolean/0004.mjs | 13 + .../mf-boolean/0005.list-comma.expect.json | 71 ++ .../mf-boolean/0005.list-space.expect.json | 69 ++ .../test/cases/mf-boolean/0005.mjs | 13 + .../mf-plain/0001.list-comma.expect.json | 120 +++ .../mf-plain/0001.list-space.expect.json | 118 +++ .../test/cases/mf-plain/0001.mjs | 13 + .../mf-plain/0002.list-comma.expect.json | 201 +++++ .../mf-plain/0002.list-space.expect.json | 199 +++++ .../test/cases/mf-plain/0002.mjs | 13 + .../mf-plain/0003.list-comma.expect.json | 116 +++ .../mf-plain/0003.list-space.expect.json | 114 +++ .../test/cases/mf-plain/0003.mjs | 13 + .../mf-plain/0004.list-comma.expect.json | 165 ++++ .../mf-plain/0004.list-space.expect.json | 163 ++++ .../test/cases/mf-plain/0004.mjs | 13 + .../mf-plain/0005.list-comma.expect.json | 116 +++ .../mf-plain/0005.list-space.expect.json | 114 +++ .../test/cases/mf-plain/0005.mjs | 13 + .../0001.list-comma.expect.json | 18 + .../0001.list-space.expect.json | 16 + .../test/cases/query-with-type/0001.mjs | 13 + .../0002.list-comma.expect.json | 44 + .../0002.list-space.expect.json | 42 + .../test/cases/query-with-type/0002.mjs | 13 + .../0003.list-comma.expect.json | 44 + .../0003.list-space.expect.json | 42 + .../test/cases/query-with-type/0003.mjs | 13 + .../cases/various/0001.list-comma.expect.json | 145 ++++ .../cases/various/0001.list-space.expect.json | 143 ++++ .../test/cases/various/0001.mjs | 13 + .../cases/various/0002.list-comma.expect.json | 790 ++++++++++++++++++ .../cases/various/0002.list-space.expect.json | 788 +++++++++++++++++ .../test/cases/various/0002.mjs | 13 + .../cases/various/0003.list-comma.expect.json | 347 ++++++++ .../cases/various/0003.list-space.expect.json | 345 ++++++++ .../test/cases/various/0003.mjs | 13 + .../cases/various/0004.list-comma.expect.json | 316 +++++++ .../cases/various/0004.list-space.expect.json | 314 +++++++ .../test/cases/various/0004.mjs | 13 + .../cases/various/0005.list-comma.expect.json | 198 +++++ .../cases/various/0005.list-space.expect.json | 196 +++++ .../test/cases/various/0005.mjs | 13 + .../cases/various/0006.list-comma.expect.json | 56 ++ .../cases/various/0006.list-space.expect.json | 54 ++ .../test/cases/various/0006.mjs | 13 + .../cases/various/0007.list-comma.expect.json | 59 ++ .../cases/various/0007.list-space.expect.json | 57 ++ .../test/cases/various/0007.mjs | 13 + .../cases/various/0008.list-comma.expect.json | 110 +++ .../cases/various/0008.list-space.expect.json | 108 +++ .../test/cases/various/0008.mjs | 13 + .../cases/various/0009.list-comma.expect.json | 129 +++ .../cases/various/0009.list-space.expect.json | 127 +++ .../test/cases/various/0009.mjs | 13 + .../cases/various/0010.list-comma.expect.json | 129 +++ .../cases/various/0010.list-space.expect.json | 127 +++ .../test/cases/various/0010.mjs | 13 + .../cases/various/0011.list-comma.expect.json | 298 +++++++ .../cases/various/0011.list-space.expect.json | 296 +++++++ .../test/cases/various/0011.mjs | 13 + .../cases/various/0012.list-comma.expect.json | 55 ++ .../cases/various/0012.list-space.expect.json | 53 ++ .../test/cases/various/0012.mjs | 13 + .../cases/various/0013.list-comma.expect.json | 74 ++ .../cases/various/0013.list-space.expect.json | 72 ++ .../test/cases/various/0013.mjs | 13 + .../cases/various/0014.list-comma.expect.json | 74 ++ .../cases/various/0014.list-space.expect.json | 72 ++ .../test/cases/various/0014.mjs | 13 + .../cases/various/0015.list-comma.expect.json | 347 ++++++++ .../cases/various/0015.list-space.expect.json | 345 ++++++++ .../test/cases/various/0015.mjs | 13 + .../cases/various/0016.list-comma.expect.json | 162 ++++ .../cases/various/0016.list-space.expect.json | 170 ++++ .../test/cases/various/0016.mjs | 13 + .../cases/various/0017.list-comma.expect.json | 151 ++++ .../cases/various/0017.list-space.expect.json | 159 ++++ .../test/cases/various/0017.mjs | 13 + .../cases/various/0018.list-comma.expect.json | 48 ++ .../cases/various/0018.list-space.expect.json | 46 + .../test/cases/various/0018.mjs | 13 + .../css-parser-algorithms/test/consume.mjs | 121 --- ...omma-separated-list-of-component-value.mjs | 38 - .../test/parse-component-value.mjs | 32 - .../test/parse-list-of-component-value.mjs | 33 - packages/css-parser-algorithms/test/parse.mjs | 3 - packages/css-parser-algorithms/test/test.mjs | 37 +- .../test/util/collect-tokens.mjs | 11 - .../test/util/run-test.mjs | 65 ++ packages/media-query-list-parser/package.json | 6 +- packages/media-query-list-parser/src/index.ts | 16 +- .../src/nodes/general-enclosed.ts | 3 +- .../src/nodes/media-and.ts | 3 +- .../src/nodes/media-condition-list.ts | 5 +- .../src/nodes/media-condition.ts | 3 +- .../src/nodes/media-feature-boolean.ts | 3 +- .../src/nodes/media-feature-name.ts | 3 +- .../src/nodes/media-feature-plain.ts | 3 +- .../src/nodes/media-feature-range.ts | 7 +- .../src/nodes/media-feature-value.ts | 3 +- .../src/nodes/media-feature.ts | 3 +- .../src/nodes/media-in-parens.ts | 3 +- .../src/nodes/media-or.ts | 3 +- .../src/nodes/media-type.ts | 1 - .../src/nodes/node-type.ts | 17 +- .../media-query-list-parser/test/_import.mjs | 3 + .../media-query-list-parser/test/_require.cjs | 3 + .../test/cases/various/0004.expect.json | 72 ++ plugins/postcss-custom-media/package.json | 1 + .../src/custom-media-from-root.ts | 2 +- .../always-true-or-false.ts | 14 + .../at-media-params-tokens.ts | 19 + .../src/transform-at-media-v2/custom-media.ts | 111 +++ .../split-media-query-list.ts | 60 ++ .../top-level-combination-keywords.ts | 90 ++ .../transform-at-media.ts | 227 +++++ .../transform-at-media-v2/true-and-false.ts | 66 ++ .../transform-at-media/transform-at-media.ts | 2 +- .../postcss-custom-media/test/and.expect.css | 2 +- .../test/basic-after-v9.expect.css | 6 +- .../test/basic-after-v9.preserve.expect.css | 6 +- .../test/basic.expect.css | 16 +- .../test/comma-1.expect.css | 2 +- .../test/complex.expect.css | 12 +- .../postcss-custom-media/test/list.expect.css | 8 +- .../postcss-custom-media/test/not.expect.css | 2 +- .../postcss-custom-media/test/or.expect.css | 2 +- .../test/true-false.expect.css | 12 +- rollup/configs/externals.js | 2 + 151 files changed, 10738 insertions(+), 302 deletions(-) create mode 100644 packages/css-parser-algorithms/test/cases/media-not/0001.list-comma.expect.json create mode 100644 packages/css-parser-algorithms/test/cases/media-not/0001.list-space.expect.json create mode 100644 packages/css-parser-algorithms/test/cases/media-not/0001.mjs create mode 100644 packages/css-parser-algorithms/test/cases/mf-boolean/0001.list-comma.expect.json create mode 100644 packages/css-parser-algorithms/test/cases/mf-boolean/0001.list-space.expect.json create mode 100644 packages/css-parser-algorithms/test/cases/mf-boolean/0001.mjs create mode 100644 packages/css-parser-algorithms/test/cases/mf-boolean/0002.list-comma.expect.json create mode 100644 packages/css-parser-algorithms/test/cases/mf-boolean/0002.list-space.expect.json create mode 100644 packages/css-parser-algorithms/test/cases/mf-boolean/0002.mjs create mode 100644 packages/css-parser-algorithms/test/cases/mf-boolean/0003.list-comma.expect.json create mode 100644 packages/css-parser-algorithms/test/cases/mf-boolean/0003.list-space.expect.json create mode 100644 packages/css-parser-algorithms/test/cases/mf-boolean/0003.mjs create mode 100644 packages/css-parser-algorithms/test/cases/mf-boolean/0004.list-comma.expect.json create mode 100644 packages/css-parser-algorithms/test/cases/mf-boolean/0004.list-space.expect.json create mode 100644 packages/css-parser-algorithms/test/cases/mf-boolean/0004.mjs create mode 100644 packages/css-parser-algorithms/test/cases/mf-boolean/0005.list-comma.expect.json create mode 100644 packages/css-parser-algorithms/test/cases/mf-boolean/0005.list-space.expect.json create mode 100644 packages/css-parser-algorithms/test/cases/mf-boolean/0005.mjs create mode 100644 packages/css-parser-algorithms/test/cases/mf-plain/0001.list-comma.expect.json create mode 100644 packages/css-parser-algorithms/test/cases/mf-plain/0001.list-space.expect.json create mode 100644 packages/css-parser-algorithms/test/cases/mf-plain/0001.mjs create mode 100644 packages/css-parser-algorithms/test/cases/mf-plain/0002.list-comma.expect.json create mode 100644 packages/css-parser-algorithms/test/cases/mf-plain/0002.list-space.expect.json create mode 100644 packages/css-parser-algorithms/test/cases/mf-plain/0002.mjs create mode 100644 packages/css-parser-algorithms/test/cases/mf-plain/0003.list-comma.expect.json create mode 100644 packages/css-parser-algorithms/test/cases/mf-plain/0003.list-space.expect.json create mode 100644 packages/css-parser-algorithms/test/cases/mf-plain/0003.mjs create mode 100644 packages/css-parser-algorithms/test/cases/mf-plain/0004.list-comma.expect.json create mode 100644 packages/css-parser-algorithms/test/cases/mf-plain/0004.list-space.expect.json create mode 100644 packages/css-parser-algorithms/test/cases/mf-plain/0004.mjs create mode 100644 packages/css-parser-algorithms/test/cases/mf-plain/0005.list-comma.expect.json create mode 100644 packages/css-parser-algorithms/test/cases/mf-plain/0005.list-space.expect.json create mode 100644 packages/css-parser-algorithms/test/cases/mf-plain/0005.mjs create mode 100644 packages/css-parser-algorithms/test/cases/query-with-type/0001.list-comma.expect.json create mode 100644 packages/css-parser-algorithms/test/cases/query-with-type/0001.list-space.expect.json create mode 100644 packages/css-parser-algorithms/test/cases/query-with-type/0001.mjs create mode 100644 packages/css-parser-algorithms/test/cases/query-with-type/0002.list-comma.expect.json create mode 100644 packages/css-parser-algorithms/test/cases/query-with-type/0002.list-space.expect.json create mode 100644 packages/css-parser-algorithms/test/cases/query-with-type/0002.mjs create mode 100644 packages/css-parser-algorithms/test/cases/query-with-type/0003.list-comma.expect.json create mode 100644 packages/css-parser-algorithms/test/cases/query-with-type/0003.list-space.expect.json create mode 100644 packages/css-parser-algorithms/test/cases/query-with-type/0003.mjs create mode 100644 packages/css-parser-algorithms/test/cases/various/0001.list-comma.expect.json create mode 100644 packages/css-parser-algorithms/test/cases/various/0001.list-space.expect.json create mode 100644 packages/css-parser-algorithms/test/cases/various/0001.mjs create mode 100644 packages/css-parser-algorithms/test/cases/various/0002.list-comma.expect.json create mode 100644 packages/css-parser-algorithms/test/cases/various/0002.list-space.expect.json create mode 100644 packages/css-parser-algorithms/test/cases/various/0002.mjs create mode 100644 packages/css-parser-algorithms/test/cases/various/0003.list-comma.expect.json create mode 100644 packages/css-parser-algorithms/test/cases/various/0003.list-space.expect.json create mode 100644 packages/css-parser-algorithms/test/cases/various/0003.mjs create mode 100644 packages/css-parser-algorithms/test/cases/various/0004.list-comma.expect.json create mode 100644 packages/css-parser-algorithms/test/cases/various/0004.list-space.expect.json create mode 100644 packages/css-parser-algorithms/test/cases/various/0004.mjs create mode 100644 packages/css-parser-algorithms/test/cases/various/0005.list-comma.expect.json create mode 100644 packages/css-parser-algorithms/test/cases/various/0005.list-space.expect.json create mode 100644 packages/css-parser-algorithms/test/cases/various/0005.mjs create mode 100644 packages/css-parser-algorithms/test/cases/various/0006.list-comma.expect.json create mode 100644 packages/css-parser-algorithms/test/cases/various/0006.list-space.expect.json create mode 100644 packages/css-parser-algorithms/test/cases/various/0006.mjs create mode 100644 packages/css-parser-algorithms/test/cases/various/0007.list-comma.expect.json create mode 100644 packages/css-parser-algorithms/test/cases/various/0007.list-space.expect.json create mode 100644 packages/css-parser-algorithms/test/cases/various/0007.mjs create mode 100644 packages/css-parser-algorithms/test/cases/various/0008.list-comma.expect.json create mode 100644 packages/css-parser-algorithms/test/cases/various/0008.list-space.expect.json create mode 100644 packages/css-parser-algorithms/test/cases/various/0008.mjs create mode 100644 packages/css-parser-algorithms/test/cases/various/0009.list-comma.expect.json create mode 100644 packages/css-parser-algorithms/test/cases/various/0009.list-space.expect.json create mode 100644 packages/css-parser-algorithms/test/cases/various/0009.mjs create mode 100644 packages/css-parser-algorithms/test/cases/various/0010.list-comma.expect.json create mode 100644 packages/css-parser-algorithms/test/cases/various/0010.list-space.expect.json create mode 100644 packages/css-parser-algorithms/test/cases/various/0010.mjs create mode 100644 packages/css-parser-algorithms/test/cases/various/0011.list-comma.expect.json create mode 100644 packages/css-parser-algorithms/test/cases/various/0011.list-space.expect.json create mode 100644 packages/css-parser-algorithms/test/cases/various/0011.mjs create mode 100644 packages/css-parser-algorithms/test/cases/various/0012.list-comma.expect.json create mode 100644 packages/css-parser-algorithms/test/cases/various/0012.list-space.expect.json create mode 100644 packages/css-parser-algorithms/test/cases/various/0012.mjs create mode 100644 packages/css-parser-algorithms/test/cases/various/0013.list-comma.expect.json create mode 100644 packages/css-parser-algorithms/test/cases/various/0013.list-space.expect.json create mode 100644 packages/css-parser-algorithms/test/cases/various/0013.mjs create mode 100644 packages/css-parser-algorithms/test/cases/various/0014.list-comma.expect.json create mode 100644 packages/css-parser-algorithms/test/cases/various/0014.list-space.expect.json create mode 100644 packages/css-parser-algorithms/test/cases/various/0014.mjs create mode 100644 packages/css-parser-algorithms/test/cases/various/0015.list-comma.expect.json create mode 100644 packages/css-parser-algorithms/test/cases/various/0015.list-space.expect.json create mode 100644 packages/css-parser-algorithms/test/cases/various/0015.mjs create mode 100644 packages/css-parser-algorithms/test/cases/various/0016.list-comma.expect.json create mode 100644 packages/css-parser-algorithms/test/cases/various/0016.list-space.expect.json create mode 100644 packages/css-parser-algorithms/test/cases/various/0016.mjs create mode 100644 packages/css-parser-algorithms/test/cases/various/0017.list-comma.expect.json create mode 100644 packages/css-parser-algorithms/test/cases/various/0017.list-space.expect.json create mode 100644 packages/css-parser-algorithms/test/cases/various/0017.mjs create mode 100644 packages/css-parser-algorithms/test/cases/various/0018.list-comma.expect.json create mode 100644 packages/css-parser-algorithms/test/cases/various/0018.list-space.expect.json create mode 100644 packages/css-parser-algorithms/test/cases/various/0018.mjs delete mode 100644 packages/css-parser-algorithms/test/consume.mjs delete mode 100644 packages/css-parser-algorithms/test/parse-comma-separated-list-of-component-value.mjs delete mode 100644 packages/css-parser-algorithms/test/parse-component-value.mjs delete mode 100644 packages/css-parser-algorithms/test/parse-list-of-component-value.mjs delete mode 100644 packages/css-parser-algorithms/test/parse.mjs delete mode 100644 packages/css-parser-algorithms/test/util/collect-tokens.mjs create mode 100644 packages/css-parser-algorithms/test/util/run-test.mjs create mode 100644 packages/media-query-list-parser/test/_import.mjs create mode 100644 packages/media-query-list-parser/test/_require.cjs create mode 100644 plugins/postcss-custom-media/src/transform-at-media-v2/always-true-or-false.ts create mode 100644 plugins/postcss-custom-media/src/transform-at-media-v2/at-media-params-tokens.ts create mode 100644 plugins/postcss-custom-media/src/transform-at-media-v2/custom-media.ts create mode 100644 plugins/postcss-custom-media/src/transform-at-media-v2/split-media-query-list.ts create mode 100644 plugins/postcss-custom-media/src/transform-at-media-v2/top-level-combination-keywords.ts create mode 100644 plugins/postcss-custom-media/src/transform-at-media-v2/transform-at-media.ts create mode 100644 plugins/postcss-custom-media/src/transform-at-media-v2/true-and-false.ts diff --git a/package.json b/package.json index 31a9bcc8a..92b4a687e 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "workspaces": [ "packages/css-tokenizer", "packages/css-parser-algorithms", + "packages/media-query-list-parser", "packages/*", "plugins/postcss-progressive-custom-properties", "plugins/*", diff --git a/packages/css-parser-algorithms/.gitignore b/packages/css-parser-algorithms/.gitignore index 7172b04f1..f548255b0 100644 --- a/packages/css-parser-algorithms/.gitignore +++ b/packages/css-parser-algorithms/.gitignore @@ -3,4 +3,5 @@ package-lock.json yarn.lock *.result.css *.result.css.map +*.result.json dist/* diff --git a/packages/css-parser-algorithms/package.json b/packages/css-parser-algorithms/package.json index 2db452189..35048273d 100644 --- a/packages/css-parser-algorithms/package.json +++ b/packages/css-parser-algorithms/package.json @@ -1,6 +1,6 @@ { "name": "@csstools/css-parser-algorithms", - "description": "Parse CSS", + "description": "Algorithms to help you parse CSS from an array of tokens.", "version": "1.0.0", "contributors": [ { @@ -48,8 +48,9 @@ "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": "npm run test:exports && node ./test/test.mjs", - "test:exports": "node ./test/_import.mjs && node ./test/_require.cjs" + "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/css-parser-algorithms#readme", "repository": { diff --git a/packages/css-parser-algorithms/src/consume/consume-component-block-function.ts b/packages/css-parser-algorithms/src/consume/consume-component-block-function.ts index c59a5dd3e..ccad1b119 100644 --- a/packages/css-parser-algorithms/src/consume/consume-component-block-function.ts +++ b/packages/css-parser-algorithms/src/consume/consume-component-block-function.ts @@ -152,6 +152,7 @@ export class FunctionNode { type: this.type, name: this.name[4].value, tokens: this.tokens(), + value: this.value.map((x) => x.toJSON()), }; } } @@ -285,6 +286,7 @@ export class SimpleBlockNode { type: this.type, startToken: this.startToken, tokens: this.tokens(), + value: this.value.map((x) => x.toJSON()), }; } } 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 fd138691d..a4136fcb8 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 @@ -57,4 +57,3 @@ export function parseCommaSeparatedListOfComponentValues(tokens: Array i += result.advance; } } - diff --git a/packages/css-parser-algorithms/src/parse/parse-component-value.ts b/packages/css-parser-algorithms/src/parse/parse-component-value.ts index b837b04cd..1fbb8ca01 100644 --- a/packages/css-parser-algorithms/src/parse/parse-component-value.ts +++ b/packages/css-parser-algorithms/src/parse/parse-component-value.ts @@ -38,4 +38,3 @@ export function parseComponentValue(tokens: Array, options?: { onParse ], }); } - diff --git a/packages/css-parser-algorithms/src/parse/parse-list-of-component-values.ts b/packages/css-parser-algorithms/src/parse/parse-list-of-component-values.ts index 4c167ddc1..833a5d17a 100644 --- a/packages/css-parser-algorithms/src/parse/parse-list-of-component-values.ts +++ b/packages/css-parser-algorithms/src/parse/parse-list-of-component-values.ts @@ -38,4 +38,3 @@ export function parseListOfComponentValues(tokens: Array, options?: { i += result.advance; } } - diff --git a/packages/css-parser-algorithms/test/cases/media-not/0001.list-comma.expect.json b/packages/css-parser-algorithms/test/cases/media-not/0001.list-comma.expect.json new file mode 100644 index 000000000..33eb1719a --- /dev/null +++ b/packages/css-parser-algorithms/test/cases/media-not/0001.list-comma.expect.json @@ -0,0 +1,81 @@ +[ + [ + { + "type": "token", + "tokens": [ + [ + "ident-token", + "not", + 0, + 2, + { + "value": "not" + } + ] + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 3, + 3, + null + ] + ] + }, + { + "type": "simple-block", + "startToken": [ + "(-token", + "(", + 4, + 4, + null + ], + "tokens": [ + [ + "(-token", + "(", + 4, + 4, + null + ], + [ + "ident-token", + "color", + 5, + 9, + { + "value": "color" + } + ], + [ + ")-token", + ")", + 10, + 10, + null + ] + ], + "value": [ + { + "type": "token", + "tokens": [ + [ + "ident-token", + "color", + 5, + 9, + { + "value": "color" + } + ] + ] + } + ] + } + ] +] \ No newline at end of file diff --git a/packages/css-parser-algorithms/test/cases/media-not/0001.list-space.expect.json b/packages/css-parser-algorithms/test/cases/media-not/0001.list-space.expect.json new file mode 100644 index 000000000..88ce51c60 --- /dev/null +++ b/packages/css-parser-algorithms/test/cases/media-not/0001.list-space.expect.json @@ -0,0 +1,79 @@ +[ + { + "type": "token", + "tokens": [ + [ + "ident-token", + "not", + 0, + 2, + { + "value": "not" + } + ] + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 3, + 3, + null + ] + ] + }, + { + "type": "simple-block", + "startToken": [ + "(-token", + "(", + 4, + 4, + null + ], + "tokens": [ + [ + "(-token", + "(", + 4, + 4, + null + ], + [ + "ident-token", + "color", + 5, + 9, + { + "value": "color" + } + ], + [ + ")-token", + ")", + 10, + 10, + null + ] + ], + "value": [ + { + "type": "token", + "tokens": [ + [ + "ident-token", + "color", + 5, + 9, + { + "value": "color" + } + ] + ] + } + ] + } +] \ No newline at end of file diff --git a/packages/css-parser-algorithms/test/cases/media-not/0001.mjs b/packages/css-parser-algorithms/test/cases/media-not/0001.mjs new file mode 100644 index 000000000..5eb55f343 --- /dev/null +++ b/packages/css-parser-algorithms/test/cases/media-not/0001.mjs @@ -0,0 +1,13 @@ +import assert from 'assert'; +import { runTest } from '../../util/run-test.mjs'; + +runTest( + 'not (color)', + 'media-not/0001', + (actual, expected) => { + assert.deepStrictEqual( + actual, + expected, + ); + }, +); diff --git a/packages/css-parser-algorithms/test/cases/mf-boolean/0001.list-comma.expect.json b/packages/css-parser-algorithms/test/cases/mf-boolean/0001.list-comma.expect.json new file mode 100644 index 000000000..7afd42c4d --- /dev/null +++ b/packages/css-parser-algorithms/test/cases/mf-boolean/0001.list-comma.expect.json @@ -0,0 +1,55 @@ +[ + [ + { + "type": "simple-block", + "startToken": [ + "(-token", + "(", + 0, + 0, + null + ], + "tokens": [ + [ + "(-token", + "(", + 0, + 0, + null + ], + [ + "ident-token", + "color", + 1, + 5, + { + "value": "color" + } + ], + [ + ")-token", + ")", + 6, + 6, + null + ] + ], + "value": [ + { + "type": "token", + "tokens": [ + [ + "ident-token", + "color", + 1, + 5, + { + "value": "color" + } + ] + ] + } + ] + } + ] +] \ No newline at end of file diff --git a/packages/css-parser-algorithms/test/cases/mf-boolean/0001.list-space.expect.json b/packages/css-parser-algorithms/test/cases/mf-boolean/0001.list-space.expect.json new file mode 100644 index 000000000..5c51b53b7 --- /dev/null +++ b/packages/css-parser-algorithms/test/cases/mf-boolean/0001.list-space.expect.json @@ -0,0 +1,53 @@ +[ + { + "type": "simple-block", + "startToken": [ + "(-token", + "(", + 0, + 0, + null + ], + "tokens": [ + [ + "(-token", + "(", + 0, + 0, + null + ], + [ + "ident-token", + "color", + 1, + 5, + { + "value": "color" + } + ], + [ + ")-token", + ")", + 6, + 6, + null + ] + ], + "value": [ + { + "type": "token", + "tokens": [ + [ + "ident-token", + "color", + 1, + 5, + { + "value": "color" + } + ] + ] + } + ] + } +] \ No newline at end of file diff --git a/packages/css-parser-algorithms/test/cases/mf-boolean/0001.mjs b/packages/css-parser-algorithms/test/cases/mf-boolean/0001.mjs new file mode 100644 index 000000000..2686abd60 --- /dev/null +++ b/packages/css-parser-algorithms/test/cases/mf-boolean/0001.mjs @@ -0,0 +1,13 @@ +import assert from 'assert'; +import { runTest } from '../../util/run-test.mjs'; + +runTest( + '(color)', + 'mf-boolean/0001', + (actual, expected) => { + assert.deepStrictEqual( + actual, + expected, + ); + }, +); diff --git a/packages/css-parser-algorithms/test/cases/mf-boolean/0002.list-comma.expect.json b/packages/css-parser-algorithms/test/cases/mf-boolean/0002.list-comma.expect.json new file mode 100644 index 000000000..031cdcd31 --- /dev/null +++ b/packages/css-parser-algorithms/test/cases/mf-boolean/0002.list-comma.expect.json @@ -0,0 +1,117 @@ +[ + [ + { + "type": "comment", + "tokens": [ + [ + "comment", + "/* comment 1 */", + 0, + 14, + null + ] + ] + }, + { + "type": "simple-block", + "startToken": [ + "(-token", + "(", + 15, + 15, + null + ], + "tokens": [ + [ + "(-token", + "(", + 15, + 15, + null + ], + [ + "comment", + "/* comment 2 */", + 16, + 30, + null + ], + [ + "ident-token", + "color", + 31, + 35, + { + "value": "color" + } + ], + [ + "comment", + "/* comment 3 */", + 36, + 50, + null + ], + [ + ")-token", + ")", + 51, + 51, + null + ] + ], + "value": [ + { + "type": "comment", + "tokens": [ + [ + "comment", + "/* comment 2 */", + 16, + 30, + null + ] + ] + }, + { + "type": "token", + "tokens": [ + [ + "ident-token", + "color", + 31, + 35, + { + "value": "color" + } + ] + ] + }, + { + "type": "comment", + "tokens": [ + [ + "comment", + "/* comment 3 */", + 36, + 50, + null + ] + ] + } + ] + }, + { + "type": "comment", + "tokens": [ + [ + "comment", + "/* comment 4 */", + 52, + 66, + null + ] + ] + } + ] +] \ No newline at end of file diff --git a/packages/css-parser-algorithms/test/cases/mf-boolean/0002.list-space.expect.json b/packages/css-parser-algorithms/test/cases/mf-boolean/0002.list-space.expect.json new file mode 100644 index 000000000..c3cc37c5c --- /dev/null +++ b/packages/css-parser-algorithms/test/cases/mf-boolean/0002.list-space.expect.json @@ -0,0 +1,115 @@ +[ + { + "type": "comment", + "tokens": [ + [ + "comment", + "/* comment 1 */", + 0, + 14, + null + ] + ] + }, + { + "type": "simple-block", + "startToken": [ + "(-token", + "(", + 15, + 15, + null + ], + "tokens": [ + [ + "(-token", + "(", + 15, + 15, + null + ], + [ + "comment", + "/* comment 2 */", + 16, + 30, + null + ], + [ + "ident-token", + "color", + 31, + 35, + { + "value": "color" + } + ], + [ + "comment", + "/* comment 3 */", + 36, + 50, + null + ], + [ + ")-token", + ")", + 51, + 51, + null + ] + ], + "value": [ + { + "type": "comment", + "tokens": [ + [ + "comment", + "/* comment 2 */", + 16, + 30, + null + ] + ] + }, + { + "type": "token", + "tokens": [ + [ + "ident-token", + "color", + 31, + 35, + { + "value": "color" + } + ] + ] + }, + { + "type": "comment", + "tokens": [ + [ + "comment", + "/* comment 3 */", + 36, + 50, + null + ] + ] + } + ] + }, + { + "type": "comment", + "tokens": [ + [ + "comment", + "/* comment 4 */", + 52, + 66, + null + ] + ] + } +] \ No newline at end of file diff --git a/packages/css-parser-algorithms/test/cases/mf-boolean/0002.mjs b/packages/css-parser-algorithms/test/cases/mf-boolean/0002.mjs new file mode 100644 index 000000000..f8266d441 --- /dev/null +++ b/packages/css-parser-algorithms/test/cases/mf-boolean/0002.mjs @@ -0,0 +1,13 @@ +import assert from 'assert'; +import { runTest } from '../../util/run-test.mjs'; + +runTest( + '/* comment 1 */(/* comment 2 */color/* comment 3 */)/* comment 4 */', + 'mf-boolean/0002', + (actual, expected) => { + assert.deepStrictEqual( + actual, + expected, + ); + }, +); diff --git a/packages/css-parser-algorithms/test/cases/mf-boolean/0003.list-comma.expect.json b/packages/css-parser-algorithms/test/cases/mf-boolean/0003.list-comma.expect.json new file mode 100644 index 000000000..ea4485beb --- /dev/null +++ b/packages/css-parser-algorithms/test/cases/mf-boolean/0003.list-comma.expect.json @@ -0,0 +1,55 @@ +[ + [ + { + "type": "simple-block", + "startToken": [ + "(-token", + "(", + 0, + 0, + null + ], + "tokens": [ + [ + "(-token", + "(", + 0, + 0, + null + ], + [ + "ident-token", + "true", + 1, + 4, + { + "value": "true" + } + ], + [ + ")-token", + ")", + 5, + 5, + null + ] + ], + "value": [ + { + "type": "token", + "tokens": [ + [ + "ident-token", + "true", + 1, + 4, + { + "value": "true" + } + ] + ] + } + ] + } + ] +] \ No newline at end of file diff --git a/packages/css-parser-algorithms/test/cases/mf-boolean/0003.list-space.expect.json b/packages/css-parser-algorithms/test/cases/mf-boolean/0003.list-space.expect.json new file mode 100644 index 000000000..72c7af5f3 --- /dev/null +++ b/packages/css-parser-algorithms/test/cases/mf-boolean/0003.list-space.expect.json @@ -0,0 +1,53 @@ +[ + { + "type": "simple-block", + "startToken": [ + "(-token", + "(", + 0, + 0, + null + ], + "tokens": [ + [ + "(-token", + "(", + 0, + 0, + null + ], + [ + "ident-token", + "true", + 1, + 4, + { + "value": "true" + } + ], + [ + ")-token", + ")", + 5, + 5, + null + ] + ], + "value": [ + { + "type": "token", + "tokens": [ + [ + "ident-token", + "true", + 1, + 4, + { + "value": "true" + } + ] + ] + } + ] + } +] \ No newline at end of file diff --git a/packages/css-parser-algorithms/test/cases/mf-boolean/0003.mjs b/packages/css-parser-algorithms/test/cases/mf-boolean/0003.mjs new file mode 100644 index 000000000..c0c9f5645 --- /dev/null +++ b/packages/css-parser-algorithms/test/cases/mf-boolean/0003.mjs @@ -0,0 +1,13 @@ +import assert from 'assert'; +import { runTest } from '../../util/run-test.mjs'; + +runTest( + '(true)', + 'mf-boolean/0003', + (actual, expected) => { + assert.deepStrictEqual( + actual, + expected, + ); + }, +); diff --git a/packages/css-parser-algorithms/test/cases/mf-boolean/0004.list-comma.expect.json b/packages/css-parser-algorithms/test/cases/mf-boolean/0004.list-comma.expect.json new file mode 100644 index 000000000..2c9b6e948 --- /dev/null +++ b/packages/css-parser-algorithms/test/cases/mf-boolean/0004.list-comma.expect.json @@ -0,0 +1,55 @@ +[ + [ + { + "type": "simple-block", + "startToken": [ + "(-token", + "(", + 0, + 0, + null + ], + "tokens": [ + [ + "(-token", + "(", + 0, + 0, + null + ], + [ + "ident-token", + "false", + 1, + 5, + { + "value": "false" + } + ], + [ + ")-token", + ")", + 6, + 6, + null + ] + ], + "value": [ + { + "type": "token", + "tokens": [ + [ + "ident-token", + "false", + 1, + 5, + { + "value": "false" + } + ] + ] + } + ] + } + ] +] \ No newline at end of file diff --git a/packages/css-parser-algorithms/test/cases/mf-boolean/0004.list-space.expect.json b/packages/css-parser-algorithms/test/cases/mf-boolean/0004.list-space.expect.json new file mode 100644 index 000000000..8d6e7f3be --- /dev/null +++ b/packages/css-parser-algorithms/test/cases/mf-boolean/0004.list-space.expect.json @@ -0,0 +1,53 @@ +[ + { + "type": "simple-block", + "startToken": [ + "(-token", + "(", + 0, + 0, + null + ], + "tokens": [ + [ + "(-token", + "(", + 0, + 0, + null + ], + [ + "ident-token", + "false", + 1, + 5, + { + "value": "false" + } + ], + [ + ")-token", + ")", + 6, + 6, + null + ] + ], + "value": [ + { + "type": "token", + "tokens": [ + [ + "ident-token", + "false", + 1, + 5, + { + "value": "false" + } + ] + ] + } + ] + } +] \ No newline at end of file diff --git a/packages/css-parser-algorithms/test/cases/mf-boolean/0004.mjs b/packages/css-parser-algorithms/test/cases/mf-boolean/0004.mjs new file mode 100644 index 000000000..0a34a2dc8 --- /dev/null +++ b/packages/css-parser-algorithms/test/cases/mf-boolean/0004.mjs @@ -0,0 +1,13 @@ +import assert from 'assert'; +import { runTest } from '../../util/run-test.mjs'; + +runTest( + '(false)', + 'mf-boolean/0004', + (actual, expected) => { + assert.deepStrictEqual( + actual, + expected, + ); + }, +); diff --git a/packages/css-parser-algorithms/test/cases/mf-boolean/0005.list-comma.expect.json b/packages/css-parser-algorithms/test/cases/mf-boolean/0005.list-comma.expect.json new file mode 100644 index 000000000..b6014d3df --- /dev/null +++ b/packages/css-parser-algorithms/test/cases/mf-boolean/0005.list-comma.expect.json @@ -0,0 +1,71 @@ +[ + [ + { + "type": "simple-block", + "startToken": [ + "(-token", + "(", + 0, + 0, + null + ], + "tokens": [ + [ + "(-token", + "(", + 0, + 0, + null + ], + [ + "function-token", + "color(", + 1, + 6, + { + "value": "color" + } + ], + [ + ")-token", + ")", + 7, + 7, + null + ], + [ + ")-token", + ")", + 8, + 8, + null + ] + ], + "value": [ + { + "type": "function", + "name": "color", + "tokens": [ + [ + "function-token", + "color(", + 1, + 6, + { + "value": "color" + } + ], + [ + ")-token", + ")", + 7, + 7, + null + ] + ], + "value": [] + } + ] + } + ] +] \ No newline at end of file diff --git a/packages/css-parser-algorithms/test/cases/mf-boolean/0005.list-space.expect.json b/packages/css-parser-algorithms/test/cases/mf-boolean/0005.list-space.expect.json new file mode 100644 index 000000000..536dbad65 --- /dev/null +++ b/packages/css-parser-algorithms/test/cases/mf-boolean/0005.list-space.expect.json @@ -0,0 +1,69 @@ +[ + { + "type": "simple-block", + "startToken": [ + "(-token", + "(", + 0, + 0, + null + ], + "tokens": [ + [ + "(-token", + "(", + 0, + 0, + null + ], + [ + "function-token", + "color(", + 1, + 6, + { + "value": "color" + } + ], + [ + ")-token", + ")", + 7, + 7, + null + ], + [ + ")-token", + ")", + 8, + 8, + null + ] + ], + "value": [ + { + "type": "function", + "name": "color", + "tokens": [ + [ + "function-token", + "color(", + 1, + 6, + { + "value": "color" + } + ], + [ + ")-token", + ")", + 7, + 7, + null + ] + ], + "value": [] + } + ] + } +] \ No newline at end of file diff --git a/packages/css-parser-algorithms/test/cases/mf-boolean/0005.mjs b/packages/css-parser-algorithms/test/cases/mf-boolean/0005.mjs new file mode 100644 index 000000000..c44c8c1f7 --- /dev/null +++ b/packages/css-parser-algorithms/test/cases/mf-boolean/0005.mjs @@ -0,0 +1,13 @@ +import assert from 'assert'; +import { runTest } from '../../util/run-test.mjs'; + +runTest( + '(color())', + 'mf-boolean/0005', + (actual, expected) => { + assert.deepStrictEqual( + actual, + expected, + ); + }, +); diff --git a/packages/css-parser-algorithms/test/cases/mf-plain/0001.list-comma.expect.json b/packages/css-parser-algorithms/test/cases/mf-plain/0001.list-comma.expect.json new file mode 100644 index 000000000..297d582fc --- /dev/null +++ b/packages/css-parser-algorithms/test/cases/mf-plain/0001.list-comma.expect.json @@ -0,0 +1,120 @@ +[ + [ + { + "type": "simple-block", + "startToken": [ + "(-token", + "(", + 0, + 0, + null + ], + "tokens": [ + [ + "(-token", + "(", + 0, + 0, + null + ], + [ + "ident-token", + "min-width", + 1, + 9, + { + "value": "min-width" + } + ], + [ + "colon-token", + ":", + 10, + 10, + null + ], + [ + "whitespace-token", + " ", + 11, + 11, + null + ], + [ + "dimension-token", + "300px", + 12, + 16, + { + "value": 300, + "type": "integer", + "unit": "px" + } + ], + [ + ")-token", + ")", + 17, + 17, + null + ] + ], + "value": [ + { + "type": "token", + "tokens": [ + [ + "ident-token", + "min-width", + 1, + 9, + { + "value": "min-width" + } + ] + ] + }, + { + "type": "token", + "tokens": [ + [ + "colon-token", + ":", + 10, + 10, + null + ] + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 11, + 11, + null + ] + ] + }, + { + "type": "token", + "tokens": [ + [ + "dimension-token", + "300px", + 12, + 16, + { + "value": 300, + "type": "integer", + "unit": "px" + } + ] + ] + } + ] + } + ] +] \ No newline at end of file diff --git a/packages/css-parser-algorithms/test/cases/mf-plain/0001.list-space.expect.json b/packages/css-parser-algorithms/test/cases/mf-plain/0001.list-space.expect.json new file mode 100644 index 000000000..511342310 --- /dev/null +++ b/packages/css-parser-algorithms/test/cases/mf-plain/0001.list-space.expect.json @@ -0,0 +1,118 @@ +[ + { + "type": "simple-block", + "startToken": [ + "(-token", + "(", + 0, + 0, + null + ], + "tokens": [ + [ + "(-token", + "(", + 0, + 0, + null + ], + [ + "ident-token", + "min-width", + 1, + 9, + { + "value": "min-width" + } + ], + [ + "colon-token", + ":", + 10, + 10, + null + ], + [ + "whitespace-token", + " ", + 11, + 11, + null + ], + [ + "dimension-token", + "300px", + 12, + 16, + { + "value": 300, + "type": "integer", + "unit": "px" + } + ], + [ + ")-token", + ")", + 17, + 17, + null + ] + ], + "value": [ + { + "type": "token", + "tokens": [ + [ + "ident-token", + "min-width", + 1, + 9, + { + "value": "min-width" + } + ] + ] + }, + { + "type": "token", + "tokens": [ + [ + "colon-token", + ":", + 10, + 10, + null + ] + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 11, + 11, + null + ] + ] + }, + { + "type": "token", + "tokens": [ + [ + "dimension-token", + "300px", + 12, + 16, + { + "value": 300, + "type": "integer", + "unit": "px" + } + ] + ] + } + ] + } +] \ No newline at end of file diff --git a/packages/css-parser-algorithms/test/cases/mf-plain/0001.mjs b/packages/css-parser-algorithms/test/cases/mf-plain/0001.mjs new file mode 100644 index 000000000..b674721b7 --- /dev/null +++ b/packages/css-parser-algorithms/test/cases/mf-plain/0001.mjs @@ -0,0 +1,13 @@ +import assert from 'assert'; +import { runTest } from '../../util/run-test.mjs'; + +runTest( + '(min-width: 300px)', + 'mf-plain/0001', + (actual, expected) => { + assert.deepStrictEqual( + actual, + expected, + ); + }, +); diff --git a/packages/css-parser-algorithms/test/cases/mf-plain/0002.list-comma.expect.json b/packages/css-parser-algorithms/test/cases/mf-plain/0002.list-comma.expect.json new file mode 100644 index 000000000..8669e19b6 --- /dev/null +++ b/packages/css-parser-algorithms/test/cases/mf-plain/0002.list-comma.expect.json @@ -0,0 +1,201 @@ +[ + [ + { + "type": "comment", + "tokens": [ + [ + "comment", + "/* comment 1 */", + 0, + 14, + null + ] + ] + }, + { + "type": "simple-block", + "startToken": [ + "(-token", + "(", + 15, + 15, + null + ], + "tokens": [ + [ + "(-token", + "(", + 15, + 15, + null + ], + [ + "comment", + "/* comment 2 */", + 16, + 30, + null + ], + [ + "ident-token", + "min-width", + 31, + 39, + { + "value": "min-width" + } + ], + [ + "comment", + "/* comment 3 */", + 40, + 54, + null + ], + [ + "colon-token", + ":", + 55, + 55, + null + ], + [ + "comment", + "/* comment 4 */", + 56, + 70, + null + ], + [ + "dimension-token", + "300px", + 71, + 75, + { + "value": 300, + "type": "integer", + "unit": "px" + } + ], + [ + "comment", + "/* comment 5 */", + 76, + 90, + null + ], + [ + ")-token", + ")", + 91, + 91, + null + ] + ], + "value": [ + { + "type": "comment", + "tokens": [ + [ + "comment", + "/* comment 2 */", + 16, + 30, + null + ] + ] + }, + { + "type": "token", + "tokens": [ + [ + "ident-token", + "min-width", + 31, + 39, + { + "value": "min-width" + } + ] + ] + }, + { + "type": "comment", + "tokens": [ + [ + "comment", + "/* comment 3 */", + 40, + 54, + null + ] + ] + }, + { + "type": "token", + "tokens": [ + [ + "colon-token", + ":", + 55, + 55, + null + ] + ] + }, + { + "type": "comment", + "tokens": [ + [ + "comment", + "/* comment 4 */", + 56, + 70, + null + ] + ] + }, + { + "type": "token", + "tokens": [ + [ + "dimension-token", + "300px", + 71, + 75, + { + "value": 300, + "type": "integer", + "unit": "px" + } + ] + ] + }, + { + "type": "comment", + "tokens": [ + [ + "comment", + "/* comment 5 */", + 76, + 90, + null + ] + ] + } + ] + }, + { + "type": "comment", + "tokens": [ + [ + "comment", + "/* comment 6 */", + 92, + 106, + null + ] + ] + } + ] +] \ No newline at end of file diff --git a/packages/css-parser-algorithms/test/cases/mf-plain/0002.list-space.expect.json b/packages/css-parser-algorithms/test/cases/mf-plain/0002.list-space.expect.json new file mode 100644 index 000000000..e61e96cf2 --- /dev/null +++ b/packages/css-parser-algorithms/test/cases/mf-plain/0002.list-space.expect.json @@ -0,0 +1,199 @@ +[ + { + "type": "comment", + "tokens": [ + [ + "comment", + "/* comment 1 */", + 0, + 14, + null + ] + ] + }, + { + "type": "simple-block", + "startToken": [ + "(-token", + "(", + 15, + 15, + null + ], + "tokens": [ + [ + "(-token", + "(", + 15, + 15, + null + ], + [ + "comment", + "/* comment 2 */", + 16, + 30, + null + ], + [ + "ident-token", + "min-width", + 31, + 39, + { + "value": "min-width" + } + ], + [ + "comment", + "/* comment 3 */", + 40, + 54, + null + ], + [ + "colon-token", + ":", + 55, + 55, + null + ], + [ + "comment", + "/* comment 4 */", + 56, + 70, + null + ], + [ + "dimension-token", + "300px", + 71, + 75, + { + "value": 300, + "type": "integer", + "unit": "px" + } + ], + [ + "comment", + "/* comment 5 */", + 76, + 90, + null + ], + [ + ")-token", + ")", + 91, + 91, + null + ] + ], + "value": [ + { + "type": "comment", + "tokens": [ + [ + "comment", + "/* comment 2 */", + 16, + 30, + null + ] + ] + }, + { + "type": "token", + "tokens": [ + [ + "ident-token", + "min-width", + 31, + 39, + { + "value": "min-width" + } + ] + ] + }, + { + "type": "comment", + "tokens": [ + [ + "comment", + "/* comment 3 */", + 40, + 54, + null + ] + ] + }, + { + "type": "token", + "tokens": [ + [ + "colon-token", + ":", + 55, + 55, + null + ] + ] + }, + { + "type": "comment", + "tokens": [ + [ + "comment", + "/* comment 4 */", + 56, + 70, + null + ] + ] + }, + { + "type": "token", + "tokens": [ + [ + "dimension-token", + "300px", + 71, + 75, + { + "value": 300, + "type": "integer", + "unit": "px" + } + ] + ] + }, + { + "type": "comment", + "tokens": [ + [ + "comment", + "/* comment 5 */", + 76, + 90, + null + ] + ] + } + ] + }, + { + "type": "comment", + "tokens": [ + [ + "comment", + "/* comment 6 */", + 92, + 106, + null + ] + ] + } +] \ No newline at end of file diff --git a/packages/css-parser-algorithms/test/cases/mf-plain/0002.mjs b/packages/css-parser-algorithms/test/cases/mf-plain/0002.mjs new file mode 100644 index 000000000..90eb3e1f9 --- /dev/null +++ b/packages/css-parser-algorithms/test/cases/mf-plain/0002.mjs @@ -0,0 +1,13 @@ +import assert from 'assert'; +import { runTest } from '../../util/run-test.mjs'; + +runTest( + '/* comment 1 */(/* comment 2 */min-width/* comment 3 */:/* comment 4 */300px/* comment 5 */)/* comment 6 */', + 'mf-plain/0002', + (actual, expected) => { + assert.deepStrictEqual( + actual, + expected, + ); + }, +); diff --git a/packages/css-parser-algorithms/test/cases/mf-plain/0003.list-comma.expect.json b/packages/css-parser-algorithms/test/cases/mf-plain/0003.list-comma.expect.json new file mode 100644 index 000000000..f27dd647b --- /dev/null +++ b/packages/css-parser-algorithms/test/cases/mf-plain/0003.list-comma.expect.json @@ -0,0 +1,116 @@ +[ + [ + { + "type": "simple-block", + "startToken": [ + "(-token", + "(", + 0, + 0, + null + ], + "tokens": [ + [ + "(-token", + "(", + 0, + 0, + null + ], + [ + "ident-token", + "resolution", + 1, + 10, + { + "value": "resolution" + } + ], + [ + "colon-token", + ":", + 11, + 11, + null + ], + [ + "whitespace-token", + " ", + 12, + 12, + null + ], + [ + "ident-token", + "infinite", + 13, + 20, + { + "value": "infinite" + } + ], + [ + ")-token", + ")", + 21, + 21, + null + ] + ], + "value": [ + { + "type": "token", + "tokens": [ + [ + "ident-token", + "resolution", + 1, + 10, + { + "value": "resolution" + } + ] + ] + }, + { + "type": "token", + "tokens": [ + [ + "colon-token", + ":", + 11, + 11, + null + ] + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 12, + 12, + null + ] + ] + }, + { + "type": "token", + "tokens": [ + [ + "ident-token", + "infinite", + 13, + 20, + { + "value": "infinite" + } + ] + ] + } + ] + } + ] +] \ No newline at end of file diff --git a/packages/css-parser-algorithms/test/cases/mf-plain/0003.list-space.expect.json b/packages/css-parser-algorithms/test/cases/mf-plain/0003.list-space.expect.json new file mode 100644 index 000000000..704164d67 --- /dev/null +++ b/packages/css-parser-algorithms/test/cases/mf-plain/0003.list-space.expect.json @@ -0,0 +1,114 @@ +[ + { + "type": "simple-block", + "startToken": [ + "(-token", + "(", + 0, + 0, + null + ], + "tokens": [ + [ + "(-token", + "(", + 0, + 0, + null + ], + [ + "ident-token", + "resolution", + 1, + 10, + { + "value": "resolution" + } + ], + [ + "colon-token", + ":", + 11, + 11, + null + ], + [ + "whitespace-token", + " ", + 12, + 12, + null + ], + [ + "ident-token", + "infinite", + 13, + 20, + { + "value": "infinite" + } + ], + [ + ")-token", + ")", + 21, + 21, + null + ] + ], + "value": [ + { + "type": "token", + "tokens": [ + [ + "ident-token", + "resolution", + 1, + 10, + { + "value": "resolution" + } + ] + ] + }, + { + "type": "token", + "tokens": [ + [ + "colon-token", + ":", + 11, + 11, + null + ] + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 12, + 12, + null + ] + ] + }, + { + "type": "token", + "tokens": [ + [ + "ident-token", + "infinite", + 13, + 20, + { + "value": "infinite" + } + ] + ] + } + ] + } +] \ No newline at end of file diff --git a/packages/css-parser-algorithms/test/cases/mf-plain/0003.mjs b/packages/css-parser-algorithms/test/cases/mf-plain/0003.mjs new file mode 100644 index 000000000..ad7c3adf2 --- /dev/null +++ b/packages/css-parser-algorithms/test/cases/mf-plain/0003.mjs @@ -0,0 +1,13 @@ +import assert from 'assert'; +import { runTest } from '../../util/run-test.mjs'; + +runTest( + '(resolution: infinite)', + 'mf-plain/0003', + (actual, expected) => { + assert.deepStrictEqual( + actual, + expected, + ); + }, +); diff --git a/packages/css-parser-algorithms/test/cases/mf-plain/0004.list-comma.expect.json b/packages/css-parser-algorithms/test/cases/mf-plain/0004.list-comma.expect.json new file mode 100644 index 000000000..cd0b720eb --- /dev/null +++ b/packages/css-parser-algorithms/test/cases/mf-plain/0004.list-comma.expect.json @@ -0,0 +1,165 @@ +[ + [ + { + "type": "simple-block", + "startToken": [ + "(-token", + "(", + 0, + 0, + null + ], + "tokens": [ + [ + "(-token", + "(", + 0, + 0, + null + ], + [ + "function-token", + "resolution(", + 1, + 11, + { + "value": "resolution" + } + ], + [ + "ident-token", + "foo", + 12, + 14, + { + "value": "foo" + } + ], + [ + ")-token", + ")", + 15, + 15, + null + ], + [ + "colon-token", + ":", + 16, + 16, + null + ], + [ + "whitespace-token", + " ", + 17, + 17, + null + ], + [ + "ident-token", + "infinite", + 18, + 25, + { + "value": "infinite" + } + ], + [ + ")-token", + ")", + 26, + 26, + null + ] + ], + "value": [ + { + "type": "function", + "name": "resolution", + "tokens": [ + [ + "function-token", + "resolution(", + 1, + 11, + { + "value": "resolution" + } + ], + [ + "ident-token", + "foo", + 12, + 14, + { + "value": "foo" + } + ], + [ + ")-token", + ")", + 15, + 15, + null + ] + ], + "value": [ + { + "type": "token", + "tokens": [ + [ + "ident-token", + "foo", + 12, + 14, + { + "value": "foo" + } + ] + ] + } + ] + }, + { + "type": "token", + "tokens": [ + [ + "colon-token", + ":", + 16, + 16, + null + ] + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 17, + 17, + null + ] + ] + }, + { + "type": "token", + "tokens": [ + [ + "ident-token", + "infinite", + 18, + 25, + { + "value": "infinite" + } + ] + ] + } + ] + } + ] +] \ No newline at end of file diff --git a/packages/css-parser-algorithms/test/cases/mf-plain/0004.list-space.expect.json b/packages/css-parser-algorithms/test/cases/mf-plain/0004.list-space.expect.json new file mode 100644 index 000000000..1410e6a8b --- /dev/null +++ b/packages/css-parser-algorithms/test/cases/mf-plain/0004.list-space.expect.json @@ -0,0 +1,163 @@ +[ + { + "type": "simple-block", + "startToken": [ + "(-token", + "(", + 0, + 0, + null + ], + "tokens": [ + [ + "(-token", + "(", + 0, + 0, + null + ], + [ + "function-token", + "resolution(", + 1, + 11, + { + "value": "resolution" + } + ], + [ + "ident-token", + "foo", + 12, + 14, + { + "value": "foo" + } + ], + [ + ")-token", + ")", + 15, + 15, + null + ], + [ + "colon-token", + ":", + 16, + 16, + null + ], + [ + "whitespace-token", + " ", + 17, + 17, + null + ], + [ + "ident-token", + "infinite", + 18, + 25, + { + "value": "infinite" + } + ], + [ + ")-token", + ")", + 26, + 26, + null + ] + ], + "value": [ + { + "type": "function", + "name": "resolution", + "tokens": [ + [ + "function-token", + "resolution(", + 1, + 11, + { + "value": "resolution" + } + ], + [ + "ident-token", + "foo", + 12, + 14, + { + "value": "foo" + } + ], + [ + ")-token", + ")", + 15, + 15, + null + ] + ], + "value": [ + { + "type": "token", + "tokens": [ + [ + "ident-token", + "foo", + 12, + 14, + { + "value": "foo" + } + ] + ] + } + ] + }, + { + "type": "token", + "tokens": [ + [ + "colon-token", + ":", + 16, + 16, + null + ] + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 17, + 17, + null + ] + ] + }, + { + "type": "token", + "tokens": [ + [ + "ident-token", + "infinite", + 18, + 25, + { + "value": "infinite" + } + ] + ] + } + ] + } +] \ No newline at end of file diff --git a/packages/css-parser-algorithms/test/cases/mf-plain/0004.mjs b/packages/css-parser-algorithms/test/cases/mf-plain/0004.mjs new file mode 100644 index 000000000..6d9a61a7b --- /dev/null +++ b/packages/css-parser-algorithms/test/cases/mf-plain/0004.mjs @@ -0,0 +1,13 @@ +import assert from 'assert'; +import { runTest } from '../../util/run-test.mjs'; + +runTest( + '(resolution(foo): infinite)', + 'mf-plain/0004', + (actual, expected) => { + assert.deepStrictEqual( + actual, + expected, + ); + }, +); diff --git a/packages/css-parser-algorithms/test/cases/mf-plain/0005.list-comma.expect.json b/packages/css-parser-algorithms/test/cases/mf-plain/0005.list-comma.expect.json new file mode 100644 index 000000000..3afb8dc07 --- /dev/null +++ b/packages/css-parser-algorithms/test/cases/mf-plain/0005.list-comma.expect.json @@ -0,0 +1,116 @@ +[ + [ + { + "type": "simple-block", + "startToken": [ + "[-token", + "[", + 0, + 0, + null + ], + "tokens": [ + [ + "[-token", + "[", + 0, + 0, + null + ], + [ + "ident-token", + "resolution", + 1, + 10, + { + "value": "resolution" + } + ], + [ + "colon-token", + ":", + 11, + 11, + null + ], + [ + "whitespace-token", + " ", + 12, + 12, + null + ], + [ + "ident-token", + "infinite", + 13, + 20, + { + "value": "infinite" + } + ], + [ + "]-token", + "]", + 21, + 21, + null + ] + ], + "value": [ + { + "type": "token", + "tokens": [ + [ + "ident-token", + "resolution", + 1, + 10, + { + "value": "resolution" + } + ] + ] + }, + { + "type": "token", + "tokens": [ + [ + "colon-token", + ":", + 11, + 11, + null + ] + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 12, + 12, + null + ] + ] + }, + { + "type": "token", + "tokens": [ + [ + "ident-token", + "infinite", + 13, + 20, + { + "value": "infinite" + } + ] + ] + } + ] + } + ] +] \ No newline at end of file diff --git a/packages/css-parser-algorithms/test/cases/mf-plain/0005.list-space.expect.json b/packages/css-parser-algorithms/test/cases/mf-plain/0005.list-space.expect.json new file mode 100644 index 000000000..a7c69ba1c --- /dev/null +++ b/packages/css-parser-algorithms/test/cases/mf-plain/0005.list-space.expect.json @@ -0,0 +1,114 @@ +[ + { + "type": "simple-block", + "startToken": [ + "[-token", + "[", + 0, + 0, + null + ], + "tokens": [ + [ + "[-token", + "[", + 0, + 0, + null + ], + [ + "ident-token", + "resolution", + 1, + 10, + { + "value": "resolution" + } + ], + [ + "colon-token", + ":", + 11, + 11, + null + ], + [ + "whitespace-token", + " ", + 12, + 12, + null + ], + [ + "ident-token", + "infinite", + 13, + 20, + { + "value": "infinite" + } + ], + [ + "]-token", + "]", + 21, + 21, + null + ] + ], + "value": [ + { + "type": "token", + "tokens": [ + [ + "ident-token", + "resolution", + 1, + 10, + { + "value": "resolution" + } + ] + ] + }, + { + "type": "token", + "tokens": [ + [ + "colon-token", + ":", + 11, + 11, + null + ] + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 12, + 12, + null + ] + ] + }, + { + "type": "token", + "tokens": [ + [ + "ident-token", + "infinite", + 13, + 20, + { + "value": "infinite" + } + ] + ] + } + ] + } +] \ No newline at end of file diff --git a/packages/css-parser-algorithms/test/cases/mf-plain/0005.mjs b/packages/css-parser-algorithms/test/cases/mf-plain/0005.mjs new file mode 100644 index 000000000..946d31898 --- /dev/null +++ b/packages/css-parser-algorithms/test/cases/mf-plain/0005.mjs @@ -0,0 +1,13 @@ +import assert from 'assert'; +import { runTest } from '../../util/run-test.mjs'; + +runTest( + '[resolution: infinite]', + 'mf-plain/0005', + (actual, expected) => { + assert.deepStrictEqual( + actual, + expected, + ); + }, +); diff --git a/packages/css-parser-algorithms/test/cases/query-with-type/0001.list-comma.expect.json b/packages/css-parser-algorithms/test/cases/query-with-type/0001.list-comma.expect.json new file mode 100644 index 000000000..45a0eace6 --- /dev/null +++ b/packages/css-parser-algorithms/test/cases/query-with-type/0001.list-comma.expect.json @@ -0,0 +1,18 @@ +[ + [ + { + "type": "token", + "tokens": [ + [ + "ident-token", + "screen", + 0, + 5, + { + "value": "screen" + } + ] + ] + } + ] +] \ No newline at end of file diff --git a/packages/css-parser-algorithms/test/cases/query-with-type/0001.list-space.expect.json b/packages/css-parser-algorithms/test/cases/query-with-type/0001.list-space.expect.json new file mode 100644 index 000000000..f8d27aa46 --- /dev/null +++ b/packages/css-parser-algorithms/test/cases/query-with-type/0001.list-space.expect.json @@ -0,0 +1,16 @@ +[ + { + "type": "token", + "tokens": [ + [ + "ident-token", + "screen", + 0, + 5, + { + "value": "screen" + } + ] + ] + } +] \ No newline at end of file diff --git a/packages/css-parser-algorithms/test/cases/query-with-type/0001.mjs b/packages/css-parser-algorithms/test/cases/query-with-type/0001.mjs new file mode 100644 index 000000000..b8d1c3f6b --- /dev/null +++ b/packages/css-parser-algorithms/test/cases/query-with-type/0001.mjs @@ -0,0 +1,13 @@ +import assert from 'assert'; +import { runTest } from '../../util/run-test.mjs'; + +runTest( + 'screen', + 'query-with-type/0001', + (actual, expected) => { + assert.deepStrictEqual( + actual, + expected, + ); + }, +); diff --git a/packages/css-parser-algorithms/test/cases/query-with-type/0002.list-comma.expect.json b/packages/css-parser-algorithms/test/cases/query-with-type/0002.list-comma.expect.json new file mode 100644 index 000000000..d708f4353 --- /dev/null +++ b/packages/css-parser-algorithms/test/cases/query-with-type/0002.list-comma.expect.json @@ -0,0 +1,44 @@ +[ + [ + { + "type": "token", + "tokens": [ + [ + "ident-token", + "only", + 0, + 3, + { + "value": "only" + } + ] + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 4, + 4, + null + ] + ] + }, + { + "type": "token", + "tokens": [ + [ + "ident-token", + "screen", + 5, + 10, + { + "value": "screen" + } + ] + ] + } + ] +] \ No newline at end of file diff --git a/packages/css-parser-algorithms/test/cases/query-with-type/0002.list-space.expect.json b/packages/css-parser-algorithms/test/cases/query-with-type/0002.list-space.expect.json new file mode 100644 index 000000000..e27303e35 --- /dev/null +++ b/packages/css-parser-algorithms/test/cases/query-with-type/0002.list-space.expect.json @@ -0,0 +1,42 @@ +[ + { + "type": "token", + "tokens": [ + [ + "ident-token", + "only", + 0, + 3, + { + "value": "only" + } + ] + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 4, + 4, + null + ] + ] + }, + { + "type": "token", + "tokens": [ + [ + "ident-token", + "screen", + 5, + 10, + { + "value": "screen" + } + ] + ] + } +] \ No newline at end of file diff --git a/packages/css-parser-algorithms/test/cases/query-with-type/0002.mjs b/packages/css-parser-algorithms/test/cases/query-with-type/0002.mjs new file mode 100644 index 000000000..d9da6523a --- /dev/null +++ b/packages/css-parser-algorithms/test/cases/query-with-type/0002.mjs @@ -0,0 +1,13 @@ +import assert from 'assert'; +import { runTest } from '../../util/run-test.mjs'; + +runTest( + 'only screen', + 'query-with-type/0002', + (actual, expected) => { + assert.deepStrictEqual( + actual, + expected, + ); + }, +); diff --git a/packages/css-parser-algorithms/test/cases/query-with-type/0003.list-comma.expect.json b/packages/css-parser-algorithms/test/cases/query-with-type/0003.list-comma.expect.json new file mode 100644 index 000000000..4d70580d2 --- /dev/null +++ b/packages/css-parser-algorithms/test/cases/query-with-type/0003.list-comma.expect.json @@ -0,0 +1,44 @@ +[ + [ + { + "type": "token", + "tokens": [ + [ + "ident-token", + "not", + 0, + 2, + { + "value": "not" + } + ] + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 3, + 3, + null + ] + ] + }, + { + "type": "token", + "tokens": [ + [ + "ident-token", + "screen", + 4, + 9, + { + "value": "screen" + } + ] + ] + } + ] +] \ No newline at end of file diff --git a/packages/css-parser-algorithms/test/cases/query-with-type/0003.list-space.expect.json b/packages/css-parser-algorithms/test/cases/query-with-type/0003.list-space.expect.json new file mode 100644 index 000000000..445e7635b --- /dev/null +++ b/packages/css-parser-algorithms/test/cases/query-with-type/0003.list-space.expect.json @@ -0,0 +1,42 @@ +[ + { + "type": "token", + "tokens": [ + [ + "ident-token", + "not", + 0, + 2, + { + "value": "not" + } + ] + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 3, + 3, + null + ] + ] + }, + { + "type": "token", + "tokens": [ + [ + "ident-token", + "screen", + 4, + 9, + { + "value": "screen" + } + ] + ] + } +] \ No newline at end of file diff --git a/packages/css-parser-algorithms/test/cases/query-with-type/0003.mjs b/packages/css-parser-algorithms/test/cases/query-with-type/0003.mjs new file mode 100644 index 000000000..1684cf679 --- /dev/null +++ b/packages/css-parser-algorithms/test/cases/query-with-type/0003.mjs @@ -0,0 +1,13 @@ +import assert from 'assert'; +import { runTest } from '../../util/run-test.mjs'; + +runTest( + 'not screen', + 'query-with-type/0003', + (actual, expected) => { + assert.deepStrictEqual( + actual, + expected, + ); + }, +); diff --git a/packages/css-parser-algorithms/test/cases/various/0001.list-comma.expect.json b/packages/css-parser-algorithms/test/cases/various/0001.list-comma.expect.json new file mode 100644 index 000000000..e2cb41189 --- /dev/null +++ b/packages/css-parser-algorithms/test/cases/various/0001.list-comma.expect.json @@ -0,0 +1,145 @@ +[ + [ + { + "type": "simple-block", + "startToken": [ + "(-token", + "(", + 0, + 0, + null + ], + "tokens": [ + [ + "(-token", + "(", + 0, + 0, + null + ], + [ + "comment", + "/* a comment */", + 1, + 15, + null + ], + [ + "ident-token", + "foo", + 16, + 18, + { + "value": "foo" + } + ], + [ + "whitespace-token", + " ", + 19, + 20, + null + ], + [ + ")-token", + ")", + 21, + 21, + null + ] + ], + "value": [ + { + "type": "comment", + "tokens": [ + [ + "comment", + "/* a comment */", + 1, + 15, + null + ] + ] + }, + { + "type": "token", + "tokens": [ + [ + "ident-token", + "foo", + 16, + 18, + { + "value": "foo" + } + ] + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 19, + 20, + null + ] + ] + } + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 22, + 22, + null + ] + ] + }, + { + "type": "token", + "tokens": [ + [ + "ident-token", + "something", + 23, + 31, + { + "value": "something" + } + ] + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 32, + 32, + null + ] + ] + }, + { + "type": "token", + "tokens": [ + [ + "ident-token", + "else", + 33, + 36, + { + "value": "else" + } + ] + ] + } + ] +] \ No newline at end of file diff --git a/packages/css-parser-algorithms/test/cases/various/0001.list-space.expect.json b/packages/css-parser-algorithms/test/cases/various/0001.list-space.expect.json new file mode 100644 index 000000000..1fb3acae1 --- /dev/null +++ b/packages/css-parser-algorithms/test/cases/various/0001.list-space.expect.json @@ -0,0 +1,143 @@ +[ + { + "type": "simple-block", + "startToken": [ + "(-token", + "(", + 0, + 0, + null + ], + "tokens": [ + [ + "(-token", + "(", + 0, + 0, + null + ], + [ + "comment", + "/* a comment */", + 1, + 15, + null + ], + [ + "ident-token", + "foo", + 16, + 18, + { + "value": "foo" + } + ], + [ + "whitespace-token", + " ", + 19, + 20, + null + ], + [ + ")-token", + ")", + 21, + 21, + null + ] + ], + "value": [ + { + "type": "comment", + "tokens": [ + [ + "comment", + "/* a comment */", + 1, + 15, + null + ] + ] + }, + { + "type": "token", + "tokens": [ + [ + "ident-token", + "foo", + 16, + 18, + { + "value": "foo" + } + ] + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 19, + 20, + null + ] + ] + } + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 22, + 22, + null + ] + ] + }, + { + "type": "token", + "tokens": [ + [ + "ident-token", + "something", + 23, + 31, + { + "value": "something" + } + ] + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 32, + 32, + null + ] + ] + }, + { + "type": "token", + "tokens": [ + [ + "ident-token", + "else", + 33, + 36, + { + "value": "else" + } + ] + ] + } +] \ No newline at end of file diff --git a/packages/css-parser-algorithms/test/cases/various/0001.mjs b/packages/css-parser-algorithms/test/cases/various/0001.mjs new file mode 100644 index 000000000..c5764560e --- /dev/null +++ b/packages/css-parser-algorithms/test/cases/various/0001.mjs @@ -0,0 +1,13 @@ +import assert from 'assert'; +import { runTest } from '../../util/run-test.mjs'; + +runTest( + '(/* a comment */foo ) something else', + 'various/0001', + (actual, expected) => { + assert.deepStrictEqual( + actual, + expected, + ); + }, +); diff --git a/packages/css-parser-algorithms/test/cases/various/0002.list-comma.expect.json b/packages/css-parser-algorithms/test/cases/various/0002.list-comma.expect.json new file mode 100644 index 000000000..b88eee50e --- /dev/null +++ b/packages/css-parser-algorithms/test/cases/various/0002.list-comma.expect.json @@ -0,0 +1,790 @@ +[ + [ + { + "type": "token", + "tokens": [ + [ + "ident-token", + "not", + 0, + 2, + { + "value": "not" + } + ] + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 3, + 3, + null + ] + ] + }, + { + "type": "token", + "tokens": [ + [ + "ident-token", + "screen", + 4, + 9, + { + "value": "screen" + } + ] + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 10, + 10, + null + ] + ] + }, + { + "type": "token", + "tokens": [ + [ + "ident-token", + "and", + 11, + 13, + { + "value": "and" + } + ] + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 14, + 14, + null + ] + ] + }, + { + "type": "simple-block", + "startToken": [ + "(-token", + "(", + 15, + 15, + null + ], + "tokens": [ + [ + "(-token", + "(", + 15, + 15, + null + ], + [ + "ident-token", + "min-width", + 16, + 24, + { + "value": "min-width" + } + ], + [ + "colon-token", + ":", + 25, + 25, + null + ], + [ + "whitespace-token", + " ", + 26, + 26, + null + ], + [ + "dimension-token", + "300px", + 27, + 31, + { + "value": 300, + "type": "integer", + "unit": "px" + } + ], + [ + ")-token", + ")", + 32, + 32, + null + ] + ], + "value": [ + { + "type": "token", + "tokens": [ + [ + "ident-token", + "min-width", + 16, + 24, + { + "value": "min-width" + } + ] + ] + }, + { + "type": "token", + "tokens": [ + [ + "colon-token", + ":", + 25, + 25, + null + ] + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 26, + 26, + null + ] + ] + }, + { + "type": "token", + "tokens": [ + [ + "dimension-token", + "300px", + 27, + 31, + { + "value": 300, + "type": "integer", + "unit": "px" + } + ] + ] + } + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 33, + 33, + null + ] + ] + }, + { + "type": "token", + "tokens": [ + [ + "ident-token", + "and", + 34, + 36, + { + "value": "and" + } + ] + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 37, + 37, + null + ] + ] + }, + { + "type": "simple-block", + "startToken": [ + "(-token", + "(", + 38, + 38, + null + ], + "tokens": [ + [ + "(-token", + "(", + 38, + 38, + null + ], + [ + "ident-token", + "prefers-color-scheme", + 39, + 58, + { + "value": "prefers-color-scheme" + } + ], + [ + "colon-token", + ":", + 59, + 59, + null + ], + [ + "comment", + "/* a comment */", + 60, + 74, + null + ], + [ + "ident-token", + "dark", + 75, + 78, + { + "value": "dark" + } + ], + [ + ")-token", + ")", + 79, + 79, + null + ] + ], + "value": [ + { + "type": "token", + "tokens": [ + [ + "ident-token", + "prefers-color-scheme", + 39, + 58, + { + "value": "prefers-color-scheme" + } + ] + ] + }, + { + "type": "token", + "tokens": [ + [ + "colon-token", + ":", + 59, + 59, + null + ] + ] + }, + { + "type": "comment", + "tokens": [ + [ + "comment", + "/* a comment */", + 60, + 74, + null + ] + ] + }, + { + "type": "token", + "tokens": [ + [ + "ident-token", + "dark", + 75, + 78, + { + "value": "dark" + } + ] + ] + } + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 80, + 80, + null + ] + ] + }, + { + "type": "token", + "tokens": [ + [ + "ident-token", + "and", + 81, + 83, + { + "value": "and" + } + ] + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 84, + 84, + null + ] + ] + }, + { + "type": "simple-block", + "startToken": [ + "(-token", + "(", + 85, + 85, + null + ], + "tokens": [ + [ + "(-token", + "(", + 85, + 85, + null + ], + [ + "ident-token", + "width", + 86, + 90, + { + "value": "width" + } + ], + [ + "whitespace-token", + " ", + 91, + 91, + null + ], + [ + "delim-token", + "<", + 92, + 92, + { + "value": "<" + } + ], + [ + "whitespace-token", + " ", + 93, + 93, + null + ], + [ + "dimension-token", + "40vw", + 94, + 97, + { + "value": 40, + "type": "integer", + "unit": "vw" + } + ], + [ + ")-token", + ")", + 98, + 98, + null + ] + ], + "value": [ + { + "type": "token", + "tokens": [ + [ + "ident-token", + "width", + 86, + 90, + { + "value": "width" + } + ] + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 91, + 91, + null + ] + ] + }, + { + "type": "token", + "tokens": [ + [ + "delim-token", + "<", + 92, + 92, + { + "value": "<" + } + ] + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 93, + 93, + null + ] + ] + }, + { + "type": "token", + "tokens": [ + [ + "dimension-token", + "40vw", + 94, + 97, + { + "value": 40, + "type": "integer", + "unit": "vw" + } + ] + ] + } + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 99, + 99, + null + ] + ] + }, + { + "type": "token", + "tokens": [ + [ + "ident-token", + "and", + 100, + 102, + { + "value": "and" + } + ] + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 103, + 103, + null + ] + ] + }, + { + "type": "simple-block", + "startToken": [ + "(-token", + "(", + 104, + 104, + null + ], + "tokens": [ + [ + "(-token", + "(", + 104, + 104, + null + ], + [ + "dimension-token", + "30px", + 105, + 108, + { + "value": 30, + "type": "integer", + "unit": "px" + } + ], + [ + "whitespace-token", + " ", + 109, + 109, + null + ], + [ + "delim-token", + "<", + 110, + 110, + { + "value": "<" + } + ], + [ + "whitespace-token", + " ", + 111, + 111, + null + ], + [ + "ident-token", + "width", + 112, + 116, + { + "value": "width" + } + ], + [ + "whitespace-token", + " ", + 117, + 117, + null + ], + [ + "delim-token", + "<", + 118, + 118, + { + "value": "<" + } + ], + [ + "whitespace-token", + " ", + 119, + 119, + null + ], + [ + "dimension-token", + "50rem", + 120, + 124, + { + "value": 50, + "type": "integer", + "unit": "rem" + } + ], + [ + ")-token", + ")", + 125, + 125, + null + ] + ], + "value": [ + { + "type": "token", + "tokens": [ + [ + "dimension-token", + "30px", + 105, + 108, + { + "value": 30, + "type": "integer", + "unit": "px" + } + ] + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 109, + 109, + null + ] + ] + }, + { + "type": "token", + "tokens": [ + [ + "delim-token", + "<", + 110, + 110, + { + "value": "<" + } + ] + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 111, + 111, + null + ] + ] + }, + { + "type": "token", + "tokens": [ + [ + "ident-token", + "width", + 112, + 116, + { + "value": "width" + } + ] + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 117, + 117, + null + ] + ] + }, + { + "type": "token", + "tokens": [ + [ + "delim-token", + "<", + 118, + 118, + { + "value": "<" + } + ] + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 119, + 119, + null + ] + ] + }, + { + "type": "token", + "tokens": [ + [ + "dimension-token", + "50rem", + 120, + 124, + { + "value": 50, + "type": "integer", + "unit": "rem" + } + ] + ] + } + ] + } + ] +] \ No newline at end of file diff --git a/packages/css-parser-algorithms/test/cases/various/0002.list-space.expect.json b/packages/css-parser-algorithms/test/cases/various/0002.list-space.expect.json new file mode 100644 index 000000000..1df900bfc --- /dev/null +++ b/packages/css-parser-algorithms/test/cases/various/0002.list-space.expect.json @@ -0,0 +1,788 @@ +[ + { + "type": "token", + "tokens": [ + [ + "ident-token", + "not", + 0, + 2, + { + "value": "not" + } + ] + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 3, + 3, + null + ] + ] + }, + { + "type": "token", + "tokens": [ + [ + "ident-token", + "screen", + 4, + 9, + { + "value": "screen" + } + ] + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 10, + 10, + null + ] + ] + }, + { + "type": "token", + "tokens": [ + [ + "ident-token", + "and", + 11, + 13, + { + "value": "and" + } + ] + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 14, + 14, + null + ] + ] + }, + { + "type": "simple-block", + "startToken": [ + "(-token", + "(", + 15, + 15, + null + ], + "tokens": [ + [ + "(-token", + "(", + 15, + 15, + null + ], + [ + "ident-token", + "min-width", + 16, + 24, + { + "value": "min-width" + } + ], + [ + "colon-token", + ":", + 25, + 25, + null + ], + [ + "whitespace-token", + " ", + 26, + 26, + null + ], + [ + "dimension-token", + "300px", + 27, + 31, + { + "value": 300, + "type": "integer", + "unit": "px" + } + ], + [ + ")-token", + ")", + 32, + 32, + null + ] + ], + "value": [ + { + "type": "token", + "tokens": [ + [ + "ident-token", + "min-width", + 16, + 24, + { + "value": "min-width" + } + ] + ] + }, + { + "type": "token", + "tokens": [ + [ + "colon-token", + ":", + 25, + 25, + null + ] + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 26, + 26, + null + ] + ] + }, + { + "type": "token", + "tokens": [ + [ + "dimension-token", + "300px", + 27, + 31, + { + "value": 300, + "type": "integer", + "unit": "px" + } + ] + ] + } + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 33, + 33, + null + ] + ] + }, + { + "type": "token", + "tokens": [ + [ + "ident-token", + "and", + 34, + 36, + { + "value": "and" + } + ] + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 37, + 37, + null + ] + ] + }, + { + "type": "simple-block", + "startToken": [ + "(-token", + "(", + 38, + 38, + null + ], + "tokens": [ + [ + "(-token", + "(", + 38, + 38, + null + ], + [ + "ident-token", + "prefers-color-scheme", + 39, + 58, + { + "value": "prefers-color-scheme" + } + ], + [ + "colon-token", + ":", + 59, + 59, + null + ], + [ + "comment", + "/* a comment */", + 60, + 74, + null + ], + [ + "ident-token", + "dark", + 75, + 78, + { + "value": "dark" + } + ], + [ + ")-token", + ")", + 79, + 79, + null + ] + ], + "value": [ + { + "type": "token", + "tokens": [ + [ + "ident-token", + "prefers-color-scheme", + 39, + 58, + { + "value": "prefers-color-scheme" + } + ] + ] + }, + { + "type": "token", + "tokens": [ + [ + "colon-token", + ":", + 59, + 59, + null + ] + ] + }, + { + "type": "comment", + "tokens": [ + [ + "comment", + "/* a comment */", + 60, + 74, + null + ] + ] + }, + { + "type": "token", + "tokens": [ + [ + "ident-token", + "dark", + 75, + 78, + { + "value": "dark" + } + ] + ] + } + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 80, + 80, + null + ] + ] + }, + { + "type": "token", + "tokens": [ + [ + "ident-token", + "and", + 81, + 83, + { + "value": "and" + } + ] + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 84, + 84, + null + ] + ] + }, + { + "type": "simple-block", + "startToken": [ + "(-token", + "(", + 85, + 85, + null + ], + "tokens": [ + [ + "(-token", + "(", + 85, + 85, + null + ], + [ + "ident-token", + "width", + 86, + 90, + { + "value": "width" + } + ], + [ + "whitespace-token", + " ", + 91, + 91, + null + ], + [ + "delim-token", + "<", + 92, + 92, + { + "value": "<" + } + ], + [ + "whitespace-token", + " ", + 93, + 93, + null + ], + [ + "dimension-token", + "40vw", + 94, + 97, + { + "value": 40, + "type": "integer", + "unit": "vw" + } + ], + [ + ")-token", + ")", + 98, + 98, + null + ] + ], + "value": [ + { + "type": "token", + "tokens": [ + [ + "ident-token", + "width", + 86, + 90, + { + "value": "width" + } + ] + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 91, + 91, + null + ] + ] + }, + { + "type": "token", + "tokens": [ + [ + "delim-token", + "<", + 92, + 92, + { + "value": "<" + } + ] + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 93, + 93, + null + ] + ] + }, + { + "type": "token", + "tokens": [ + [ + "dimension-token", + "40vw", + 94, + 97, + { + "value": 40, + "type": "integer", + "unit": "vw" + } + ] + ] + } + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 99, + 99, + null + ] + ] + }, + { + "type": "token", + "tokens": [ + [ + "ident-token", + "and", + 100, + 102, + { + "value": "and" + } + ] + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 103, + 103, + null + ] + ] + }, + { + "type": "simple-block", + "startToken": [ + "(-token", + "(", + 104, + 104, + null + ], + "tokens": [ + [ + "(-token", + "(", + 104, + 104, + null + ], + [ + "dimension-token", + "30px", + 105, + 108, + { + "value": 30, + "type": "integer", + "unit": "px" + } + ], + [ + "whitespace-token", + " ", + 109, + 109, + null + ], + [ + "delim-token", + "<", + 110, + 110, + { + "value": "<" + } + ], + [ + "whitespace-token", + " ", + 111, + 111, + null + ], + [ + "ident-token", + "width", + 112, + 116, + { + "value": "width" + } + ], + [ + "whitespace-token", + " ", + 117, + 117, + null + ], + [ + "delim-token", + "<", + 118, + 118, + { + "value": "<" + } + ], + [ + "whitespace-token", + " ", + 119, + 119, + null + ], + [ + "dimension-token", + "50rem", + 120, + 124, + { + "value": 50, + "type": "integer", + "unit": "rem" + } + ], + [ + ")-token", + ")", + 125, + 125, + null + ] + ], + "value": [ + { + "type": "token", + "tokens": [ + [ + "dimension-token", + "30px", + 105, + 108, + { + "value": 30, + "type": "integer", + "unit": "px" + } + ] + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 109, + 109, + null + ] + ] + }, + { + "type": "token", + "tokens": [ + [ + "delim-token", + "<", + 110, + 110, + { + "value": "<" + } + ] + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 111, + 111, + null + ] + ] + }, + { + "type": "token", + "tokens": [ + [ + "ident-token", + "width", + 112, + 116, + { + "value": "width" + } + ] + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 117, + 117, + null + ] + ] + }, + { + "type": "token", + "tokens": [ + [ + "delim-token", + "<", + 118, + 118, + { + "value": "<" + } + ] + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 119, + 119, + null + ] + ] + }, + { + "type": "token", + "tokens": [ + [ + "dimension-token", + "50rem", + 120, + 124, + { + "value": 50, + "type": "integer", + "unit": "rem" + } + ] + ] + } + ] + } +] \ No newline at end of file diff --git a/packages/css-parser-algorithms/test/cases/various/0002.mjs b/packages/css-parser-algorithms/test/cases/various/0002.mjs new file mode 100644 index 000000000..964b4d9b9 --- /dev/null +++ b/packages/css-parser-algorithms/test/cases/various/0002.mjs @@ -0,0 +1,13 @@ +import assert from 'assert'; +import { runTest } from '../../util/run-test.mjs'; + +runTest( + 'not screen and (min-width: 300px) and (prefers-color-scheme:/* a comment */dark) and (width < 40vw) and (30px < width < 50rem)', + 'various/0002', + (actual, expected) => { + assert.deepStrictEqual( + actual, + expected, + ); + }, +); diff --git a/packages/css-parser-algorithms/test/cases/various/0003.list-comma.expect.json b/packages/css-parser-algorithms/test/cases/various/0003.list-comma.expect.json new file mode 100644 index 000000000..ad2407cda --- /dev/null +++ b/packages/css-parser-algorithms/test/cases/various/0003.list-comma.expect.json @@ -0,0 +1,347 @@ +[ + [ + { + "type": "simple-block", + "startToken": [ + "(-token", + "(", + 0, + 0, + null + ], + "tokens": [ + [ + "(-token", + "(", + 0, + 0, + null + ], + [ + "ident-token", + "resolution", + 1, + 10, + { + "value": "resolution" + } + ], + [ + "whitespace-token", + " ", + 11, + 11, + null + ], + [ + "delim-token", + "<", + 12, + 12, + { + "value": "<" + } + ], + [ + "whitespace-token", + " ", + 13, + 13, + null + ], + [ + "ident-token", + "infinite", + 14, + 21, + { + "value": "infinite" + } + ], + [ + ")-token", + ")", + 22, + 22, + null + ] + ], + "value": [ + { + "type": "token", + "tokens": [ + [ + "ident-token", + "resolution", + 1, + 10, + { + "value": "resolution" + } + ] + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 11, + 11, + null + ] + ] + }, + { + "type": "token", + "tokens": [ + [ + "delim-token", + "<", + 12, + 12, + { + "value": "<" + } + ] + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 13, + 13, + null + ] + ] + }, + { + "type": "token", + "tokens": [ + [ + "ident-token", + "infinite", + 14, + 21, + { + "value": "infinite" + } + ] + ] + } + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 23, + 23, + null + ] + ] + }, + { + "type": "token", + "tokens": [ + [ + "ident-token", + "and", + 24, + 26, + { + "value": "and" + } + ] + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 27, + 27, + null + ] + ] + }, + { + "type": "simple-block", + "startToken": [ + "(-token", + "(", + 28, + 28, + null + ], + "tokens": [ + [ + "(-token", + "(", + 28, + 28, + null + ], + [ + "ident-token", + "infinite", + 29, + 36, + { + "value": "infinite" + } + ], + [ + "whitespace-token", + " ", + 37, + 37, + null + ], + [ + "delim-token", + "<", + 38, + 38, + { + "value": "<" + } + ], + [ + "delim-token", + "=", + 39, + 39, + { + "value": "=" + } + ], + [ + "whitespace-token", + " ", + 40, + 40, + null + ], + [ + "ident-token", + "resolution", + 41, + 50, + { + "value": "resolution" + } + ], + [ + ")-token", + ")", + 51, + 51, + null + ] + ], + "value": [ + { + "type": "token", + "tokens": [ + [ + "ident-token", + "infinite", + 29, + 36, + { + "value": "infinite" + } + ] + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 37, + 37, + null + ] + ] + }, + { + "type": "token", + "tokens": [ + [ + "delim-token", + "<", + 38, + 38, + { + "value": "<" + } + ] + ] + }, + { + "type": "token", + "tokens": [ + [ + "delim-token", + "=", + 39, + 39, + { + "value": "=" + } + ] + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 40, + 40, + null + ] + ] + }, + { + "type": "token", + "tokens": [ + [ + "ident-token", + "resolution", + 41, + 50, + { + "value": "resolution" + } + ] + ] + } + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 52, + 52, + null + ] + ] + } + ] +] \ No newline at end of file diff --git a/packages/css-parser-algorithms/test/cases/various/0003.list-space.expect.json b/packages/css-parser-algorithms/test/cases/various/0003.list-space.expect.json new file mode 100644 index 000000000..3bfcbac73 --- /dev/null +++ b/packages/css-parser-algorithms/test/cases/various/0003.list-space.expect.json @@ -0,0 +1,345 @@ +[ + { + "type": "simple-block", + "startToken": [ + "(-token", + "(", + 0, + 0, + null + ], + "tokens": [ + [ + "(-token", + "(", + 0, + 0, + null + ], + [ + "ident-token", + "resolution", + 1, + 10, + { + "value": "resolution" + } + ], + [ + "whitespace-token", + " ", + 11, + 11, + null + ], + [ + "delim-token", + "<", + 12, + 12, + { + "value": "<" + } + ], + [ + "whitespace-token", + " ", + 13, + 13, + null + ], + [ + "ident-token", + "infinite", + 14, + 21, + { + "value": "infinite" + } + ], + [ + ")-token", + ")", + 22, + 22, + null + ] + ], + "value": [ + { + "type": "token", + "tokens": [ + [ + "ident-token", + "resolution", + 1, + 10, + { + "value": "resolution" + } + ] + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 11, + 11, + null + ] + ] + }, + { + "type": "token", + "tokens": [ + [ + "delim-token", + "<", + 12, + 12, + { + "value": "<" + } + ] + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 13, + 13, + null + ] + ] + }, + { + "type": "token", + "tokens": [ + [ + "ident-token", + "infinite", + 14, + 21, + { + "value": "infinite" + } + ] + ] + } + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 23, + 23, + null + ] + ] + }, + { + "type": "token", + "tokens": [ + [ + "ident-token", + "and", + 24, + 26, + { + "value": "and" + } + ] + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 27, + 27, + null + ] + ] + }, + { + "type": "simple-block", + "startToken": [ + "(-token", + "(", + 28, + 28, + null + ], + "tokens": [ + [ + "(-token", + "(", + 28, + 28, + null + ], + [ + "ident-token", + "infinite", + 29, + 36, + { + "value": "infinite" + } + ], + [ + "whitespace-token", + " ", + 37, + 37, + null + ], + [ + "delim-token", + "<", + 38, + 38, + { + "value": "<" + } + ], + [ + "delim-token", + "=", + 39, + 39, + { + "value": "=" + } + ], + [ + "whitespace-token", + " ", + 40, + 40, + null + ], + [ + "ident-token", + "resolution", + 41, + 50, + { + "value": "resolution" + } + ], + [ + ")-token", + ")", + 51, + 51, + null + ] + ], + "value": [ + { + "type": "token", + "tokens": [ + [ + "ident-token", + "infinite", + 29, + 36, + { + "value": "infinite" + } + ] + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 37, + 37, + null + ] + ] + }, + { + "type": "token", + "tokens": [ + [ + "delim-token", + "<", + 38, + 38, + { + "value": "<" + } + ] + ] + }, + { + "type": "token", + "tokens": [ + [ + "delim-token", + "=", + 39, + 39, + { + "value": "=" + } + ] + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 40, + 40, + null + ] + ] + }, + { + "type": "token", + "tokens": [ + [ + "ident-token", + "resolution", + 41, + 50, + { + "value": "resolution" + } + ] + ] + } + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 52, + 52, + null + ] + ] + } +] \ No newline at end of file diff --git a/packages/css-parser-algorithms/test/cases/various/0003.mjs b/packages/css-parser-algorithms/test/cases/various/0003.mjs new file mode 100644 index 000000000..2af154b33 --- /dev/null +++ b/packages/css-parser-algorithms/test/cases/various/0003.mjs @@ -0,0 +1,13 @@ +import assert from 'assert'; +import { runTest } from '../../util/run-test.mjs'; + +runTest( + '(resolution < infinite) and (infinite <= resolution) ', + 'various/0003', + (actual, expected) => { + assert.deepStrictEqual( + actual, + expected, + ); + }, +); diff --git a/packages/css-parser-algorithms/test/cases/various/0004.list-comma.expect.json b/packages/css-parser-algorithms/test/cases/various/0004.list-comma.expect.json new file mode 100644 index 000000000..72fb943f6 --- /dev/null +++ b/packages/css-parser-algorithms/test/cases/various/0004.list-comma.expect.json @@ -0,0 +1,316 @@ +[ + [ + { + "type": "simple-block", + "startToken": [ + "(-token", + "(", + 0, + 0, + null + ], + "tokens": [ + [ + "(-token", + "(", + 0, + 0, + null + ], + [ + "ident-token", + "width", + 1, + 5, + { + "value": "width" + } + ], + [ + "whitespace-token", + " ", + 6, + 6, + null + ], + [ + "delim-token", + "<", + 7, + 7, + { + "value": "<" + } + ], + [ + "whitespace-token", + " ", + 8, + 8, + null + ], + [ + "function-token", + "calc(", + 9, + 13, + { + "value": "calc" + } + ], + [ + "dimension-token", + "50vw", + 14, + 17, + { + "value": 50, + "type": "integer", + "unit": "vw" + } + ], + [ + "whitespace-token", + " ", + 18, + 18, + null + ], + [ + "delim-token", + "-", + 19, + 19, + { + "value": "-" + } + ], + [ + "whitespace-token", + " ", + 20, + 20, + null + ], + [ + "dimension-token", + "3rem", + 21, + 24, + { + "value": 3, + "type": "integer", + "unit": "rem" + } + ], + [ + ")-token", + ")", + 25, + 25, + null + ], + [ + ")-token", + ")", + 26, + 26, + null + ] + ], + "value": [ + { + "type": "token", + "tokens": [ + [ + "ident-token", + "width", + 1, + 5, + { + "value": "width" + } + ] + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 6, + 6, + null + ] + ] + }, + { + "type": "token", + "tokens": [ + [ + "delim-token", + "<", + 7, + 7, + { + "value": "<" + } + ] + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 8, + 8, + null + ] + ] + }, + { + "type": "function", + "name": "calc", + "tokens": [ + [ + "function-token", + "calc(", + 9, + 13, + { + "value": "calc" + } + ], + [ + "dimension-token", + "50vw", + 14, + 17, + { + "value": 50, + "type": "integer", + "unit": "vw" + } + ], + [ + "whitespace-token", + " ", + 18, + 18, + null + ], + [ + "delim-token", + "-", + 19, + 19, + { + "value": "-" + } + ], + [ + "whitespace-token", + " ", + 20, + 20, + null + ], + [ + "dimension-token", + "3rem", + 21, + 24, + { + "value": 3, + "type": "integer", + "unit": "rem" + } + ], + [ + ")-token", + ")", + 25, + 25, + null + ] + ], + "value": [ + { + "type": "token", + "tokens": [ + [ + "dimension-token", + "50vw", + 14, + 17, + { + "value": 50, + "type": "integer", + "unit": "vw" + } + ] + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 18, + 18, + null + ] + ] + }, + { + "type": "token", + "tokens": [ + [ + "delim-token", + "-", + 19, + 19, + { + "value": "-" + } + ] + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 20, + 20, + null + ] + ] + }, + { + "type": "token", + "tokens": [ + [ + "dimension-token", + "3rem", + 21, + 24, + { + "value": 3, + "type": "integer", + "unit": "rem" + } + ] + ] + } + ] + } + ] + } + ] +] \ No newline at end of file diff --git a/packages/css-parser-algorithms/test/cases/various/0004.list-space.expect.json b/packages/css-parser-algorithms/test/cases/various/0004.list-space.expect.json new file mode 100644 index 000000000..5653f5136 --- /dev/null +++ b/packages/css-parser-algorithms/test/cases/various/0004.list-space.expect.json @@ -0,0 +1,314 @@ +[ + { + "type": "simple-block", + "startToken": [ + "(-token", + "(", + 0, + 0, + null + ], + "tokens": [ + [ + "(-token", + "(", + 0, + 0, + null + ], + [ + "ident-token", + "width", + 1, + 5, + { + "value": "width" + } + ], + [ + "whitespace-token", + " ", + 6, + 6, + null + ], + [ + "delim-token", + "<", + 7, + 7, + { + "value": "<" + } + ], + [ + "whitespace-token", + " ", + 8, + 8, + null + ], + [ + "function-token", + "calc(", + 9, + 13, + { + "value": "calc" + } + ], + [ + "dimension-token", + "50vw", + 14, + 17, + { + "value": 50, + "type": "integer", + "unit": "vw" + } + ], + [ + "whitespace-token", + " ", + 18, + 18, + null + ], + [ + "delim-token", + "-", + 19, + 19, + { + "value": "-" + } + ], + [ + "whitespace-token", + " ", + 20, + 20, + null + ], + [ + "dimension-token", + "3rem", + 21, + 24, + { + "value": 3, + "type": "integer", + "unit": "rem" + } + ], + [ + ")-token", + ")", + 25, + 25, + null + ], + [ + ")-token", + ")", + 26, + 26, + null + ] + ], + "value": [ + { + "type": "token", + "tokens": [ + [ + "ident-token", + "width", + 1, + 5, + { + "value": "width" + } + ] + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 6, + 6, + null + ] + ] + }, + { + "type": "token", + "tokens": [ + [ + "delim-token", + "<", + 7, + 7, + { + "value": "<" + } + ] + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 8, + 8, + null + ] + ] + }, + { + "type": "function", + "name": "calc", + "tokens": [ + [ + "function-token", + "calc(", + 9, + 13, + { + "value": "calc" + } + ], + [ + "dimension-token", + "50vw", + 14, + 17, + { + "value": 50, + "type": "integer", + "unit": "vw" + } + ], + [ + "whitespace-token", + " ", + 18, + 18, + null + ], + [ + "delim-token", + "-", + 19, + 19, + { + "value": "-" + } + ], + [ + "whitespace-token", + " ", + 20, + 20, + null + ], + [ + "dimension-token", + "3rem", + 21, + 24, + { + "value": 3, + "type": "integer", + "unit": "rem" + } + ], + [ + ")-token", + ")", + 25, + 25, + null + ] + ], + "value": [ + { + "type": "token", + "tokens": [ + [ + "dimension-token", + "50vw", + 14, + 17, + { + "value": 50, + "type": "integer", + "unit": "vw" + } + ] + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 18, + 18, + null + ] + ] + }, + { + "type": "token", + "tokens": [ + [ + "delim-token", + "-", + 19, + 19, + { + "value": "-" + } + ] + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 20, + 20, + null + ] + ] + }, + { + "type": "token", + "tokens": [ + [ + "dimension-token", + "3rem", + 21, + 24, + { + "value": 3, + "type": "integer", + "unit": "rem" + } + ] + ] + } + ] + } + ] + } +] \ No newline at end of file diff --git a/packages/css-parser-algorithms/test/cases/various/0004.mjs b/packages/css-parser-algorithms/test/cases/various/0004.mjs new file mode 100644 index 000000000..62f73bf0a --- /dev/null +++ b/packages/css-parser-algorithms/test/cases/various/0004.mjs @@ -0,0 +1,13 @@ +import assert from 'assert'; +import { runTest } from '../../util/run-test.mjs'; + +runTest( + '(width < calc(50vw - 3rem))', + 'various/0004', + (actual, expected) => { + assert.deepStrictEqual( + actual, + expected, + ); + }, +); diff --git a/packages/css-parser-algorithms/test/cases/various/0005.list-comma.expect.json b/packages/css-parser-algorithms/test/cases/various/0005.list-comma.expect.json new file mode 100644 index 000000000..8e0833bda --- /dev/null +++ b/packages/css-parser-algorithms/test/cases/various/0005.list-comma.expect.json @@ -0,0 +1,198 @@ +[ + [ + { + "type": "token", + "tokens": [ + [ + "ident-token", + "screen", + 0, + 5, + { + "value": "screen" + } + ] + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 6, + 6, + null + ] + ] + }, + { + "type": "token", + "tokens": [ + [ + "ident-token", + "and", + 7, + 9, + { + "value": "and" + } + ] + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 10, + 10, + null + ] + ] + }, + { + "type": "token", + "tokens": [ + [ + "ident-token", + "not", + 11, + 13, + { + "value": "not" + } + ] + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 14, + 14, + null + ] + ] + }, + { + "type": "simple-block", + "startToken": [ + "(-token", + "(", + 15, + 15, + null + ], + "tokens": [ + [ + "(-token", + "(", + 15, + 15, + null + ], + [ + "ident-token", + "min-width", + 16, + 24, + { + "value": "min-width" + } + ], + [ + "colon-token", + ":", + 25, + 25, + null + ], + [ + "whitespace-token", + " ", + 26, + 26, + null + ], + [ + "dimension-token", + "300px", + 27, + 31, + { + "value": 300, + "type": "integer", + "unit": "px" + } + ], + [ + ")-token", + ")", + 32, + 32, + null + ] + ], + "value": [ + { + "type": "token", + "tokens": [ + [ + "ident-token", + "min-width", + 16, + 24, + { + "value": "min-width" + } + ] + ] + }, + { + "type": "token", + "tokens": [ + [ + "colon-token", + ":", + 25, + 25, + null + ] + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 26, + 26, + null + ] + ] + }, + { + "type": "token", + "tokens": [ + [ + "dimension-token", + "300px", + 27, + 31, + { + "value": 300, + "type": "integer", + "unit": "px" + } + ] + ] + } + ] + } + ] +] \ No newline at end of file diff --git a/packages/css-parser-algorithms/test/cases/various/0005.list-space.expect.json b/packages/css-parser-algorithms/test/cases/various/0005.list-space.expect.json new file mode 100644 index 000000000..b41645b59 --- /dev/null +++ b/packages/css-parser-algorithms/test/cases/various/0005.list-space.expect.json @@ -0,0 +1,196 @@ +[ + { + "type": "token", + "tokens": [ + [ + "ident-token", + "screen", + 0, + 5, + { + "value": "screen" + } + ] + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 6, + 6, + null + ] + ] + }, + { + "type": "token", + "tokens": [ + [ + "ident-token", + "and", + 7, + 9, + { + "value": "and" + } + ] + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 10, + 10, + null + ] + ] + }, + { + "type": "token", + "tokens": [ + [ + "ident-token", + "not", + 11, + 13, + { + "value": "not" + } + ] + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 14, + 14, + null + ] + ] + }, + { + "type": "simple-block", + "startToken": [ + "(-token", + "(", + 15, + 15, + null + ], + "tokens": [ + [ + "(-token", + "(", + 15, + 15, + null + ], + [ + "ident-token", + "min-width", + 16, + 24, + { + "value": "min-width" + } + ], + [ + "colon-token", + ":", + 25, + 25, + null + ], + [ + "whitespace-token", + " ", + 26, + 26, + null + ], + [ + "dimension-token", + "300px", + 27, + 31, + { + "value": 300, + "type": "integer", + "unit": "px" + } + ], + [ + ")-token", + ")", + 32, + 32, + null + ] + ], + "value": [ + { + "type": "token", + "tokens": [ + [ + "ident-token", + "min-width", + 16, + 24, + { + "value": "min-width" + } + ] + ] + }, + { + "type": "token", + "tokens": [ + [ + "colon-token", + ":", + 25, + 25, + null + ] + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 26, + 26, + null + ] + ] + }, + { + "type": "token", + "tokens": [ + [ + "dimension-token", + "300px", + 27, + 31, + { + "value": 300, + "type": "integer", + "unit": "px" + } + ] + ] + } + ] + } +] \ No newline at end of file diff --git a/packages/css-parser-algorithms/test/cases/various/0005.mjs b/packages/css-parser-algorithms/test/cases/various/0005.mjs new file mode 100644 index 000000000..7c55c608d --- /dev/null +++ b/packages/css-parser-algorithms/test/cases/various/0005.mjs @@ -0,0 +1,13 @@ +import assert from 'assert'; +import { runTest } from '../../util/run-test.mjs'; + +runTest( + 'screen and not (min-width: 300px)', + 'various/0005', + (actual, expected) => { + assert.deepStrictEqual( + actual, + expected, + ); + }, +); diff --git a/packages/css-parser-algorithms/test/cases/various/0006.list-comma.expect.json b/packages/css-parser-algorithms/test/cases/various/0006.list-comma.expect.json new file mode 100644 index 000000000..0e5184edc --- /dev/null +++ b/packages/css-parser-algorithms/test/cases/various/0006.list-comma.expect.json @@ -0,0 +1,56 @@ +[ + [ + { + "type": "token", + "tokens": [ + [ + "ident-token", + "only", + 0, + 3, + { + "value": "only" + } + ] + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 4, + 4, + null + ] + ] + }, + { + "type": "token", + "tokens": [ + [ + "ident-token", + "screen", + 5, + 10, + { + "value": "screen" + } + ] + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 11, + 11, + null + ] + ] + } + ] +] \ No newline at end of file diff --git a/packages/css-parser-algorithms/test/cases/various/0006.list-space.expect.json b/packages/css-parser-algorithms/test/cases/various/0006.list-space.expect.json new file mode 100644 index 000000000..75bf497e4 --- /dev/null +++ b/packages/css-parser-algorithms/test/cases/various/0006.list-space.expect.json @@ -0,0 +1,54 @@ +[ + { + "type": "token", + "tokens": [ + [ + "ident-token", + "only", + 0, + 3, + { + "value": "only" + } + ] + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 4, + 4, + null + ] + ] + }, + { + "type": "token", + "tokens": [ + [ + "ident-token", + "screen", + 5, + 10, + { + "value": "screen" + } + ] + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 11, + 11, + null + ] + ] + } +] \ No newline at end of file diff --git a/packages/css-parser-algorithms/test/cases/various/0006.mjs b/packages/css-parser-algorithms/test/cases/various/0006.mjs new file mode 100644 index 000000000..2d9faf758 --- /dev/null +++ b/packages/css-parser-algorithms/test/cases/various/0006.mjs @@ -0,0 +1,13 @@ +import assert from 'assert'; +import { runTest } from '../../util/run-test.mjs'; + +runTest( + 'only screen ', + 'various/0006', + (actual, expected) => { + assert.deepStrictEqual( + actual, + expected, + ); + }, +); diff --git a/packages/css-parser-algorithms/test/cases/various/0007.list-comma.expect.json b/packages/css-parser-algorithms/test/cases/various/0007.list-comma.expect.json new file mode 100644 index 000000000..e500341e8 --- /dev/null +++ b/packages/css-parser-algorithms/test/cases/various/0007.list-comma.expect.json @@ -0,0 +1,59 @@ +[ + [ + { + "type": "simple-block", + "startToken": [ + "[-token", + "[", + 0, + 0, + null + ], + "tokens": [ + [ + "[-token", + "[", + 0, + 0, + null + ], + [ + "dimension-token", + "10px", + 1, + 4, + { + "value": 10, + "type": "integer", + "unit": "px" + } + ], + [ + "]-token", + "]", + 5, + 5, + null + ] + ], + "value": [ + { + "type": "token", + "tokens": [ + [ + "dimension-token", + "10px", + 1, + 4, + { + "value": 10, + "type": "integer", + "unit": "px" + } + ] + ] + } + ] + } + ] +] \ No newline at end of file diff --git a/packages/css-parser-algorithms/test/cases/various/0007.list-space.expect.json b/packages/css-parser-algorithms/test/cases/various/0007.list-space.expect.json new file mode 100644 index 000000000..c9ead936b --- /dev/null +++ b/packages/css-parser-algorithms/test/cases/various/0007.list-space.expect.json @@ -0,0 +1,57 @@ +[ + { + "type": "simple-block", + "startToken": [ + "[-token", + "[", + 0, + 0, + null + ], + "tokens": [ + [ + "[-token", + "[", + 0, + 0, + null + ], + [ + "dimension-token", + "10px", + 1, + 4, + { + "value": 10, + "type": "integer", + "unit": "px" + } + ], + [ + "]-token", + "]", + 5, + 5, + null + ] + ], + "value": [ + { + "type": "token", + "tokens": [ + [ + "dimension-token", + "10px", + 1, + 4, + { + "value": 10, + "type": "integer", + "unit": "px" + } + ] + ] + } + ] + } +] \ No newline at end of file diff --git a/packages/css-parser-algorithms/test/cases/various/0007.mjs b/packages/css-parser-algorithms/test/cases/various/0007.mjs new file mode 100644 index 000000000..f3e178c40 --- /dev/null +++ b/packages/css-parser-algorithms/test/cases/various/0007.mjs @@ -0,0 +1,13 @@ +import assert from 'assert'; +import { runTest } from '../../util/run-test.mjs'; + +runTest( + '[10px]', + 'various/0007', + (actual, expected) => { + assert.deepStrictEqual( + actual, + expected, + ); + }, +); diff --git a/packages/css-parser-algorithms/test/cases/various/0008.list-comma.expect.json b/packages/css-parser-algorithms/test/cases/various/0008.list-comma.expect.json new file mode 100644 index 000000000..9fa9596f9 --- /dev/null +++ b/packages/css-parser-algorithms/test/cases/various/0008.list-comma.expect.json @@ -0,0 +1,110 @@ +[ + [ + { + "type": "simple-block", + "startToken": [ + "(-token", + "(", + 0, + 0, + null + ], + "tokens": [ + [ + "(-token", + "(", + 0, + 0, + null + ], + [ + "function-token", + "calc(", + 1, + 5, + { + "value": "calc" + } + ], + [ + "dimension-token", + "10px", + 6, + 9, + { + "value": 10, + "type": "integer", + "unit": "px" + } + ], + [ + ")-token", + ")", + 10, + 10, + null + ], + [ + ")-token", + ")", + 11, + 11, + null + ] + ], + "value": [ + { + "type": "function", + "name": "calc", + "tokens": [ + [ + "function-token", + "calc(", + 1, + 5, + { + "value": "calc" + } + ], + [ + "dimension-token", + "10px", + 6, + 9, + { + "value": 10, + "type": "integer", + "unit": "px" + } + ], + [ + ")-token", + ")", + 10, + 10, + null + ] + ], + "value": [ + { + "type": "token", + "tokens": [ + [ + "dimension-token", + "10px", + 6, + 9, + { + "value": 10, + "type": "integer", + "unit": "px" + } + ] + ] + } + ] + } + ] + } + ] +] \ No newline at end of file diff --git a/packages/css-parser-algorithms/test/cases/various/0008.list-space.expect.json b/packages/css-parser-algorithms/test/cases/various/0008.list-space.expect.json new file mode 100644 index 000000000..75447e844 --- /dev/null +++ b/packages/css-parser-algorithms/test/cases/various/0008.list-space.expect.json @@ -0,0 +1,108 @@ +[ + { + "type": "simple-block", + "startToken": [ + "(-token", + "(", + 0, + 0, + null + ], + "tokens": [ + [ + "(-token", + "(", + 0, + 0, + null + ], + [ + "function-token", + "calc(", + 1, + 5, + { + "value": "calc" + } + ], + [ + "dimension-token", + "10px", + 6, + 9, + { + "value": 10, + "type": "integer", + "unit": "px" + } + ], + [ + ")-token", + ")", + 10, + 10, + null + ], + [ + ")-token", + ")", + 11, + 11, + null + ] + ], + "value": [ + { + "type": "function", + "name": "calc", + "tokens": [ + [ + "function-token", + "calc(", + 1, + 5, + { + "value": "calc" + } + ], + [ + "dimension-token", + "10px", + 6, + 9, + { + "value": 10, + "type": "integer", + "unit": "px" + } + ], + [ + ")-token", + ")", + 10, + 10, + null + ] + ], + "value": [ + { + "type": "token", + "tokens": [ + [ + "dimension-token", + "10px", + 6, + 9, + { + "value": 10, + "type": "integer", + "unit": "px" + } + ] + ] + } + ] + } + ] + } +] \ No newline at end of file diff --git a/packages/css-parser-algorithms/test/cases/various/0008.mjs b/packages/css-parser-algorithms/test/cases/various/0008.mjs new file mode 100644 index 000000000..6ea1fad9d --- /dev/null +++ b/packages/css-parser-algorithms/test/cases/various/0008.mjs @@ -0,0 +1,13 @@ +import assert from 'assert'; +import { runTest } from '../../util/run-test.mjs'; + +runTest( + '(calc(10px))', + 'various/0008', + (actual, expected) => { + assert.deepStrictEqual( + actual, + expected, + ); + }, +); diff --git a/packages/css-parser-algorithms/test/cases/various/0009.list-comma.expect.json b/packages/css-parser-algorithms/test/cases/various/0009.list-comma.expect.json new file mode 100644 index 000000000..70fea63dd --- /dev/null +++ b/packages/css-parser-algorithms/test/cases/various/0009.list-comma.expect.json @@ -0,0 +1,129 @@ +[ + [ + { + "type": "simple-block", + "startToken": [ + "(-token", + "(", + 0, + 0, + null + ], + "tokens": [ + [ + "(-token", + "(", + 0, + 0, + null + ], + [ + "function-token", + "calc(", + 1, + 5, + { + "value": "calc" + } + ], + [ + "dimension-token", + "10px", + 6, + 9, + { + "value": 10, + "type": "integer", + "unit": "px" + } + ], + [ + ")-token", + ")", + 10, + 10, + null + ], + [ + "whitespace-token", + " ", + 11, + 11, + null + ], + [ + ")-token", + ")", + 12, + 12, + null + ] + ], + "value": [ + { + "type": "function", + "name": "calc", + "tokens": [ + [ + "function-token", + "calc(", + 1, + 5, + { + "value": "calc" + } + ], + [ + "dimension-token", + "10px", + 6, + 9, + { + "value": 10, + "type": "integer", + "unit": "px" + } + ], + [ + ")-token", + ")", + 10, + 10, + null + ] + ], + "value": [ + { + "type": "token", + "tokens": [ + [ + "dimension-token", + "10px", + 6, + 9, + { + "value": 10, + "type": "integer", + "unit": "px" + } + ] + ] + } + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 11, + 11, + null + ] + ] + } + ] + } + ] +] \ No newline at end of file diff --git a/packages/css-parser-algorithms/test/cases/various/0009.list-space.expect.json b/packages/css-parser-algorithms/test/cases/various/0009.list-space.expect.json new file mode 100644 index 000000000..9efe5dbe6 --- /dev/null +++ b/packages/css-parser-algorithms/test/cases/various/0009.list-space.expect.json @@ -0,0 +1,127 @@ +[ + { + "type": "simple-block", + "startToken": [ + "(-token", + "(", + 0, + 0, + null + ], + "tokens": [ + [ + "(-token", + "(", + 0, + 0, + null + ], + [ + "function-token", + "calc(", + 1, + 5, + { + "value": "calc" + } + ], + [ + "dimension-token", + "10px", + 6, + 9, + { + "value": 10, + "type": "integer", + "unit": "px" + } + ], + [ + ")-token", + ")", + 10, + 10, + null + ], + [ + "whitespace-token", + " ", + 11, + 11, + null + ], + [ + ")-token", + ")", + 12, + 12, + null + ] + ], + "value": [ + { + "type": "function", + "name": "calc", + "tokens": [ + [ + "function-token", + "calc(", + 1, + 5, + { + "value": "calc" + } + ], + [ + "dimension-token", + "10px", + 6, + 9, + { + "value": 10, + "type": "integer", + "unit": "px" + } + ], + [ + ")-token", + ")", + 10, + 10, + null + ] + ], + "value": [ + { + "type": "token", + "tokens": [ + [ + "dimension-token", + "10px", + 6, + 9, + { + "value": 10, + "type": "integer", + "unit": "px" + } + ] + ] + } + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 11, + 11, + null + ] + ] + } + ] + } +] \ No newline at end of file diff --git a/packages/css-parser-algorithms/test/cases/various/0009.mjs b/packages/css-parser-algorithms/test/cases/various/0009.mjs new file mode 100644 index 000000000..bdf46f868 --- /dev/null +++ b/packages/css-parser-algorithms/test/cases/various/0009.mjs @@ -0,0 +1,13 @@ +import assert from 'assert'; +import { runTest } from '../../util/run-test.mjs'; + +runTest( + '(calc(10px) )', + 'various/0009', + (actual, expected) => { + assert.deepStrictEqual( + actual, + expected, + ); + }, +); diff --git a/packages/css-parser-algorithms/test/cases/various/0010.list-comma.expect.json b/packages/css-parser-algorithms/test/cases/various/0010.list-comma.expect.json new file mode 100644 index 000000000..38a3b8c7a --- /dev/null +++ b/packages/css-parser-algorithms/test/cases/various/0010.list-comma.expect.json @@ -0,0 +1,129 @@ +[ + [ + { + "type": "simple-block", + "startToken": [ + "(-token", + "(", + 0, + 0, + null + ], + "tokens": [ + [ + "(-token", + "(", + 0, + 0, + null + ], + [ + "function-token", + "calc(", + 1, + 5, + { + "value": "calc" + } + ], + [ + "dimension-token", + "10px", + 6, + 9, + { + "value": 10, + "type": "integer", + "unit": "px" + } + ], + [ + ")-token", + ")", + 10, + 10, + null + ], + [ + "comment", + "/* a comment */", + 11, + 25, + null + ], + [ + ")-token", + ")", + 26, + 26, + null + ] + ], + "value": [ + { + "type": "function", + "name": "calc", + "tokens": [ + [ + "function-token", + "calc(", + 1, + 5, + { + "value": "calc" + } + ], + [ + "dimension-token", + "10px", + 6, + 9, + { + "value": 10, + "type": "integer", + "unit": "px" + } + ], + [ + ")-token", + ")", + 10, + 10, + null + ] + ], + "value": [ + { + "type": "token", + "tokens": [ + [ + "dimension-token", + "10px", + 6, + 9, + { + "value": 10, + "type": "integer", + "unit": "px" + } + ] + ] + } + ] + }, + { + "type": "comment", + "tokens": [ + [ + "comment", + "/* a comment */", + 11, + 25, + null + ] + ] + } + ] + } + ] +] \ No newline at end of file diff --git a/packages/css-parser-algorithms/test/cases/various/0010.list-space.expect.json b/packages/css-parser-algorithms/test/cases/various/0010.list-space.expect.json new file mode 100644 index 000000000..bc616fb21 --- /dev/null +++ b/packages/css-parser-algorithms/test/cases/various/0010.list-space.expect.json @@ -0,0 +1,127 @@ +[ + { + "type": "simple-block", + "startToken": [ + "(-token", + "(", + 0, + 0, + null + ], + "tokens": [ + [ + "(-token", + "(", + 0, + 0, + null + ], + [ + "function-token", + "calc(", + 1, + 5, + { + "value": "calc" + } + ], + [ + "dimension-token", + "10px", + 6, + 9, + { + "value": 10, + "type": "integer", + "unit": "px" + } + ], + [ + ")-token", + ")", + 10, + 10, + null + ], + [ + "comment", + "/* a comment */", + 11, + 25, + null + ], + [ + ")-token", + ")", + 26, + 26, + null + ] + ], + "value": [ + { + "type": "function", + "name": "calc", + "tokens": [ + [ + "function-token", + "calc(", + 1, + 5, + { + "value": "calc" + } + ], + [ + "dimension-token", + "10px", + 6, + 9, + { + "value": 10, + "type": "integer", + "unit": "px" + } + ], + [ + ")-token", + ")", + 10, + 10, + null + ] + ], + "value": [ + { + "type": "token", + "tokens": [ + [ + "dimension-token", + "10px", + 6, + 9, + { + "value": 10, + "type": "integer", + "unit": "px" + } + ] + ] + } + ] + }, + { + "type": "comment", + "tokens": [ + [ + "comment", + "/* a comment */", + 11, + 25, + null + ] + ] + } + ] + } +] \ No newline at end of file diff --git a/packages/css-parser-algorithms/test/cases/various/0010.mjs b/packages/css-parser-algorithms/test/cases/various/0010.mjs new file mode 100644 index 000000000..9b314124f --- /dev/null +++ b/packages/css-parser-algorithms/test/cases/various/0010.mjs @@ -0,0 +1,13 @@ +import assert from 'assert'; +import { runTest } from '../../util/run-test.mjs'; + +runTest( + '(calc(10px)/* a comment */)', + 'various/0010', + (actual, expected) => { + assert.deepStrictEqual( + actual, + expected, + ); + }, +); diff --git a/packages/css-parser-algorithms/test/cases/various/0011.list-comma.expect.json b/packages/css-parser-algorithms/test/cases/various/0011.list-comma.expect.json new file mode 100644 index 000000000..b7d0d623d --- /dev/null +++ b/packages/css-parser-algorithms/test/cases/various/0011.list-comma.expect.json @@ -0,0 +1,298 @@ +[ + [ + { + "type": "simple-block", + "startToken": [ + "(-token", + "(", + 0, + 0, + null + ], + "tokens": [ + [ + "(-token", + "(", + 0, + 0, + null + ], + [ + "(-token", + "(", + 1, + 1, + null + ], + [ + "function-token", + "calc(", + 2, + 6, + { + "value": "calc" + } + ], + [ + "dimension-token", + "10px", + 7, + 10, + { + "value": 10, + "type": "integer", + "unit": "px" + } + ], + [ + ")-token", + ")", + 11, + 11, + null + ], + [ + "comment", + "/* a comment */", + 12, + 26, + null + ], + [ + ")-token", + ")", + 27, + 27, + null + ], + [ + "whitespace-token", + " ", + 28, + 28, + null + ], + [ + "(-token", + "(", + 29, + 29, + null + ], + [ + "ident-token", + "other", + 30, + 34, + { + "value": "other" + } + ], + [ + ")-token", + ")", + 35, + 35, + null + ], + [ + ")-token", + ")", + 36, + 36, + null + ] + ], + "value": [ + { + "type": "simple-block", + "startToken": [ + "(-token", + "(", + 1, + 1, + null + ], + "tokens": [ + [ + "(-token", + "(", + 1, + 1, + null + ], + [ + "function-token", + "calc(", + 2, + 6, + { + "value": "calc" + } + ], + [ + "dimension-token", + "10px", + 7, + 10, + { + "value": 10, + "type": "integer", + "unit": "px" + } + ], + [ + ")-token", + ")", + 11, + 11, + null + ], + [ + "comment", + "/* a comment */", + 12, + 26, + null + ], + [ + ")-token", + ")", + 27, + 27, + null + ] + ], + "value": [ + { + "type": "function", + "name": "calc", + "tokens": [ + [ + "function-token", + "calc(", + 2, + 6, + { + "value": "calc" + } + ], + [ + "dimension-token", + "10px", + 7, + 10, + { + "value": 10, + "type": "integer", + "unit": "px" + } + ], + [ + ")-token", + ")", + 11, + 11, + null + ] + ], + "value": [ + { + "type": "token", + "tokens": [ + [ + "dimension-token", + "10px", + 7, + 10, + { + "value": 10, + "type": "integer", + "unit": "px" + } + ] + ] + } + ] + }, + { + "type": "comment", + "tokens": [ + [ + "comment", + "/* a comment */", + 12, + 26, + null + ] + ] + } + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 28, + 28, + null + ] + ] + }, + { + "type": "simple-block", + "startToken": [ + "(-token", + "(", + 29, + 29, + null + ], + "tokens": [ + [ + "(-token", + "(", + 29, + 29, + null + ], + [ + "ident-token", + "other", + 30, + 34, + { + "value": "other" + } + ], + [ + ")-token", + ")", + 35, + 35, + null + ] + ], + "value": [ + { + "type": "token", + "tokens": [ + [ + "ident-token", + "other", + 30, + 34, + { + "value": "other" + } + ] + ] + } + ] + } + ] + } + ] +] \ No newline at end of file diff --git a/packages/css-parser-algorithms/test/cases/various/0011.list-space.expect.json b/packages/css-parser-algorithms/test/cases/various/0011.list-space.expect.json new file mode 100644 index 000000000..ce17e46da --- /dev/null +++ b/packages/css-parser-algorithms/test/cases/various/0011.list-space.expect.json @@ -0,0 +1,296 @@ +[ + { + "type": "simple-block", + "startToken": [ + "(-token", + "(", + 0, + 0, + null + ], + "tokens": [ + [ + "(-token", + "(", + 0, + 0, + null + ], + [ + "(-token", + "(", + 1, + 1, + null + ], + [ + "function-token", + "calc(", + 2, + 6, + { + "value": "calc" + } + ], + [ + "dimension-token", + "10px", + 7, + 10, + { + "value": 10, + "type": "integer", + "unit": "px" + } + ], + [ + ")-token", + ")", + 11, + 11, + null + ], + [ + "comment", + "/* a comment */", + 12, + 26, + null + ], + [ + ")-token", + ")", + 27, + 27, + null + ], + [ + "whitespace-token", + " ", + 28, + 28, + null + ], + [ + "(-token", + "(", + 29, + 29, + null + ], + [ + "ident-token", + "other", + 30, + 34, + { + "value": "other" + } + ], + [ + ")-token", + ")", + 35, + 35, + null + ], + [ + ")-token", + ")", + 36, + 36, + null + ] + ], + "value": [ + { + "type": "simple-block", + "startToken": [ + "(-token", + "(", + 1, + 1, + null + ], + "tokens": [ + [ + "(-token", + "(", + 1, + 1, + null + ], + [ + "function-token", + "calc(", + 2, + 6, + { + "value": "calc" + } + ], + [ + "dimension-token", + "10px", + 7, + 10, + { + "value": 10, + "type": "integer", + "unit": "px" + } + ], + [ + ")-token", + ")", + 11, + 11, + null + ], + [ + "comment", + "/* a comment */", + 12, + 26, + null + ], + [ + ")-token", + ")", + 27, + 27, + null + ] + ], + "value": [ + { + "type": "function", + "name": "calc", + "tokens": [ + [ + "function-token", + "calc(", + 2, + 6, + { + "value": "calc" + } + ], + [ + "dimension-token", + "10px", + 7, + 10, + { + "value": 10, + "type": "integer", + "unit": "px" + } + ], + [ + ")-token", + ")", + 11, + 11, + null + ] + ], + "value": [ + { + "type": "token", + "tokens": [ + [ + "dimension-token", + "10px", + 7, + 10, + { + "value": 10, + "type": "integer", + "unit": "px" + } + ] + ] + } + ] + }, + { + "type": "comment", + "tokens": [ + [ + "comment", + "/* a comment */", + 12, + 26, + null + ] + ] + } + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 28, + 28, + null + ] + ] + }, + { + "type": "simple-block", + "startToken": [ + "(-token", + "(", + 29, + 29, + null + ], + "tokens": [ + [ + "(-token", + "(", + 29, + 29, + null + ], + [ + "ident-token", + "other", + 30, + 34, + { + "value": "other" + } + ], + [ + ")-token", + ")", + 35, + 35, + null + ] + ], + "value": [ + { + "type": "token", + "tokens": [ + [ + "ident-token", + "other", + 30, + 34, + { + "value": "other" + } + ] + ] + } + ] + } + ] + } +] \ No newline at end of file diff --git a/packages/css-parser-algorithms/test/cases/various/0011.mjs b/packages/css-parser-algorithms/test/cases/various/0011.mjs new file mode 100644 index 000000000..dc4fa6514 --- /dev/null +++ b/packages/css-parser-algorithms/test/cases/various/0011.mjs @@ -0,0 +1,13 @@ +import assert from 'assert'; +import { runTest } from '../../util/run-test.mjs'; + +runTest( + '((calc(10px)/* a comment */) (other))', + 'various/0011', + (actual, expected) => { + assert.deepStrictEqual( + actual, + expected, + ); + }, +); diff --git a/packages/css-parser-algorithms/test/cases/various/0012.list-comma.expect.json b/packages/css-parser-algorithms/test/cases/various/0012.list-comma.expect.json new file mode 100644 index 000000000..b99cb9831 --- /dev/null +++ b/packages/css-parser-algorithms/test/cases/various/0012.list-comma.expect.json @@ -0,0 +1,55 @@ +[ + [ + { + "type": "function", + "name": "calc", + "tokens": [ + [ + "function-token", + "calc(", + 0, + 4, + { + "value": "calc" + } + ], + [ + "dimension-token", + "10px", + 5, + 8, + { + "value": 10, + "type": "integer", + "unit": "px" + } + ], + [ + ")-token", + ")", + 9, + 9, + null + ] + ], + "value": [ + { + "type": "token", + "tokens": [ + [ + "dimension-token", + "10px", + 5, + 8, + { + "value": 10, + "type": "integer", + "unit": "px" + } + ] + ] + } + ] + } + ] +] \ No newline at end of file diff --git a/packages/css-parser-algorithms/test/cases/various/0012.list-space.expect.json b/packages/css-parser-algorithms/test/cases/various/0012.list-space.expect.json new file mode 100644 index 000000000..ed15ba06a --- /dev/null +++ b/packages/css-parser-algorithms/test/cases/various/0012.list-space.expect.json @@ -0,0 +1,53 @@ +[ + { + "type": "function", + "name": "calc", + "tokens": [ + [ + "function-token", + "calc(", + 0, + 4, + { + "value": "calc" + } + ], + [ + "dimension-token", + "10px", + 5, + 8, + { + "value": 10, + "type": "integer", + "unit": "px" + } + ], + [ + ")-token", + ")", + 9, + 9, + null + ] + ], + "value": [ + { + "type": "token", + "tokens": [ + [ + "dimension-token", + "10px", + 5, + 8, + { + "value": 10, + "type": "integer", + "unit": "px" + } + ] + ] + } + ] + } +] \ No newline at end of file diff --git a/packages/css-parser-algorithms/test/cases/various/0012.mjs b/packages/css-parser-algorithms/test/cases/various/0012.mjs new file mode 100644 index 000000000..6f2b31339 --- /dev/null +++ b/packages/css-parser-algorithms/test/cases/various/0012.mjs @@ -0,0 +1,13 @@ +import assert from 'assert'; +import { runTest } from '../../util/run-test.mjs'; + +runTest( + 'calc(10px)', + 'various/0012', + (actual, expected) => { + assert.deepStrictEqual( + actual, + expected, + ); + }, +); diff --git a/packages/css-parser-algorithms/test/cases/various/0013.list-comma.expect.json b/packages/css-parser-algorithms/test/cases/various/0013.list-comma.expect.json new file mode 100644 index 000000000..8f8bd84d1 --- /dev/null +++ b/packages/css-parser-algorithms/test/cases/various/0013.list-comma.expect.json @@ -0,0 +1,74 @@ +[ + [ + { + "type": "function", + "name": "calc", + "tokens": [ + [ + "function-token", + "calc(", + 0, + 4, + { + "value": "calc" + } + ], + [ + "dimension-token", + "10px", + 5, + 8, + { + "value": 10, + "type": "integer", + "unit": "px" + } + ], + [ + "whitespace-token", + " ", + 9, + 9, + null + ], + [ + ")-token", + ")", + 10, + 10, + null + ] + ], + "value": [ + { + "type": "token", + "tokens": [ + [ + "dimension-token", + "10px", + 5, + 8, + { + "value": 10, + "type": "integer", + "unit": "px" + } + ] + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 9, + 9, + null + ] + ] + } + ] + } + ] +] \ No newline at end of file diff --git a/packages/css-parser-algorithms/test/cases/various/0013.list-space.expect.json b/packages/css-parser-algorithms/test/cases/various/0013.list-space.expect.json new file mode 100644 index 000000000..18a984378 --- /dev/null +++ b/packages/css-parser-algorithms/test/cases/various/0013.list-space.expect.json @@ -0,0 +1,72 @@ +[ + { + "type": "function", + "name": "calc", + "tokens": [ + [ + "function-token", + "calc(", + 0, + 4, + { + "value": "calc" + } + ], + [ + "dimension-token", + "10px", + 5, + 8, + { + "value": 10, + "type": "integer", + "unit": "px" + } + ], + [ + "whitespace-token", + " ", + 9, + 9, + null + ], + [ + ")-token", + ")", + 10, + 10, + null + ] + ], + "value": [ + { + "type": "token", + "tokens": [ + [ + "dimension-token", + "10px", + 5, + 8, + { + "value": 10, + "type": "integer", + "unit": "px" + } + ] + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 9, + 9, + null + ] + ] + } + ] + } +] \ No newline at end of file diff --git a/packages/css-parser-algorithms/test/cases/various/0013.mjs b/packages/css-parser-algorithms/test/cases/various/0013.mjs new file mode 100644 index 000000000..94cc52dee --- /dev/null +++ b/packages/css-parser-algorithms/test/cases/various/0013.mjs @@ -0,0 +1,13 @@ +import assert from 'assert'; +import { runTest } from '../../util/run-test.mjs'; + +runTest( + 'calc(10px )', + 'various/0013', + (actual, expected) => { + assert.deepStrictEqual( + actual, + expected, + ); + }, +); diff --git a/packages/css-parser-algorithms/test/cases/various/0014.list-comma.expect.json b/packages/css-parser-algorithms/test/cases/various/0014.list-comma.expect.json new file mode 100644 index 000000000..c7e67316b --- /dev/null +++ b/packages/css-parser-algorithms/test/cases/various/0014.list-comma.expect.json @@ -0,0 +1,74 @@ +[ + [ + { + "type": "function", + "name": "calc", + "tokens": [ + [ + "function-token", + "calc(", + 0, + 4, + { + "value": "calc" + } + ], + [ + "dimension-token", + "10px", + 5, + 8, + { + "value": 10, + "type": "integer", + "unit": "px" + } + ], + [ + "comment", + "/* a comment */", + 9, + 23, + null + ], + [ + ")-token", + ")", + 24, + 24, + null + ] + ], + "value": [ + { + "type": "token", + "tokens": [ + [ + "dimension-token", + "10px", + 5, + 8, + { + "value": 10, + "type": "integer", + "unit": "px" + } + ] + ] + }, + { + "type": "comment", + "tokens": [ + [ + "comment", + "/* a comment */", + 9, + 23, + null + ] + ] + } + ] + } + ] +] \ No newline at end of file diff --git a/packages/css-parser-algorithms/test/cases/various/0014.list-space.expect.json b/packages/css-parser-algorithms/test/cases/various/0014.list-space.expect.json new file mode 100644 index 000000000..c59243b26 --- /dev/null +++ b/packages/css-parser-algorithms/test/cases/various/0014.list-space.expect.json @@ -0,0 +1,72 @@ +[ + { + "type": "function", + "name": "calc", + "tokens": [ + [ + "function-token", + "calc(", + 0, + 4, + { + "value": "calc" + } + ], + [ + "dimension-token", + "10px", + 5, + 8, + { + "value": 10, + "type": "integer", + "unit": "px" + } + ], + [ + "comment", + "/* a comment */", + 9, + 23, + null + ], + [ + ")-token", + ")", + 24, + 24, + null + ] + ], + "value": [ + { + "type": "token", + "tokens": [ + [ + "dimension-token", + "10px", + 5, + 8, + { + "value": 10, + "type": "integer", + "unit": "px" + } + ] + ] + }, + { + "type": "comment", + "tokens": [ + [ + "comment", + "/* a comment */", + 9, + 23, + null + ] + ] + } + ] + } +] \ No newline at end of file diff --git a/packages/css-parser-algorithms/test/cases/various/0014.mjs b/packages/css-parser-algorithms/test/cases/various/0014.mjs new file mode 100644 index 000000000..26a323ac3 --- /dev/null +++ b/packages/css-parser-algorithms/test/cases/various/0014.mjs @@ -0,0 +1,13 @@ +import assert from 'assert'; +import { runTest } from '../../util/run-test.mjs'; + +runTest( + 'calc(10px/* a comment */)', + 'various/0014', + (actual, expected) => { + assert.deepStrictEqual( + actual, + expected, + ); + }, +); diff --git a/packages/css-parser-algorithms/test/cases/various/0015.list-comma.expect.json b/packages/css-parser-algorithms/test/cases/various/0015.list-comma.expect.json new file mode 100644 index 000000000..d5108b8fd --- /dev/null +++ b/packages/css-parser-algorithms/test/cases/various/0015.list-comma.expect.json @@ -0,0 +1,347 @@ +[ + [ + { + "type": "function", + "name": "calc", + "tokens": [ + [ + "function-token", + "calc(", + 0, + 4, + { + "value": "calc" + } + ], + [ + "dimension-token", + "10px", + 5, + 8, + { + "value": 10, + "type": "integer", + "unit": "px" + } + ], + [ + "comma-token", + ",", + 9, + 9, + null + ], + [ + "whitespace-token", + " ", + 10, + 10, + null + ], + [ + "comment", + "/* a comment */", + 11, + 25, + null + ], + [ + "comma-token", + ",", + 26, + 26, + null + ], + [ + "whitespace-token", + " ", + 27, + 27, + null + ], + [ + "(-token", + "(", + 28, + 28, + null + ], + [ + "ident-token", + "other", + 29, + 33, + { + "value": "other" + } + ], + [ + "whitespace-token", + " ", + 34, + 34, + null + ], + [ + "function-token", + "calc(", + 35, + 39, + { + "value": "calc" + } + ], + [ + "ident-token", + "more", + 40, + 43, + { + "value": "more" + } + ], + [ + ")-token", + ")", + 44, + 44, + null + ], + [ + ")-token", + ")", + 45, + 45, + null + ], + [ + ")-token", + ")", + 46, + 46, + null + ] + ], + "value": [ + { + "type": "token", + "tokens": [ + [ + "dimension-token", + "10px", + 5, + 8, + { + "value": 10, + "type": "integer", + "unit": "px" + } + ] + ] + }, + { + "type": "token", + "tokens": [ + [ + "comma-token", + ",", + 9, + 9, + null + ] + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 10, + 10, + null + ] + ] + }, + { + "type": "comment", + "tokens": [ + [ + "comment", + "/* a comment */", + 11, + 25, + null + ] + ] + }, + { + "type": "token", + "tokens": [ + [ + "comma-token", + ",", + 26, + 26, + null + ] + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 27, + 27, + null + ] + ] + }, + { + "type": "simple-block", + "startToken": [ + "(-token", + "(", + 28, + 28, + null + ], + "tokens": [ + [ + "(-token", + "(", + 28, + 28, + null + ], + [ + "ident-token", + "other", + 29, + 33, + { + "value": "other" + } + ], + [ + "whitespace-token", + " ", + 34, + 34, + null + ], + [ + "function-token", + "calc(", + 35, + 39, + { + "value": "calc" + } + ], + [ + "ident-token", + "more", + 40, + 43, + { + "value": "more" + } + ], + [ + ")-token", + ")", + 44, + 44, + null + ], + [ + ")-token", + ")", + 45, + 45, + null + ] + ], + "value": [ + { + "type": "token", + "tokens": [ + [ + "ident-token", + "other", + 29, + 33, + { + "value": "other" + } + ] + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 34, + 34, + null + ] + ] + }, + { + "type": "function", + "name": "calc", + "tokens": [ + [ + "function-token", + "calc(", + 35, + 39, + { + "value": "calc" + } + ], + [ + "ident-token", + "more", + 40, + 43, + { + "value": "more" + } + ], + [ + ")-token", + ")", + 44, + 44, + null + ] + ], + "value": [ + { + "type": "token", + "tokens": [ + [ + "ident-token", + "more", + 40, + 43, + { + "value": "more" + } + ] + ] + } + ] + } + ] + } + ] + } + ] +] \ No newline at end of file diff --git a/packages/css-parser-algorithms/test/cases/various/0015.list-space.expect.json b/packages/css-parser-algorithms/test/cases/various/0015.list-space.expect.json new file mode 100644 index 000000000..9bf015a26 --- /dev/null +++ b/packages/css-parser-algorithms/test/cases/various/0015.list-space.expect.json @@ -0,0 +1,345 @@ +[ + { + "type": "function", + "name": "calc", + "tokens": [ + [ + "function-token", + "calc(", + 0, + 4, + { + "value": "calc" + } + ], + [ + "dimension-token", + "10px", + 5, + 8, + { + "value": 10, + "type": "integer", + "unit": "px" + } + ], + [ + "comma-token", + ",", + 9, + 9, + null + ], + [ + "whitespace-token", + " ", + 10, + 10, + null + ], + [ + "comment", + "/* a comment */", + 11, + 25, + null + ], + [ + "comma-token", + ",", + 26, + 26, + null + ], + [ + "whitespace-token", + " ", + 27, + 27, + null + ], + [ + "(-token", + "(", + 28, + 28, + null + ], + [ + "ident-token", + "other", + 29, + 33, + { + "value": "other" + } + ], + [ + "whitespace-token", + " ", + 34, + 34, + null + ], + [ + "function-token", + "calc(", + 35, + 39, + { + "value": "calc" + } + ], + [ + "ident-token", + "more", + 40, + 43, + { + "value": "more" + } + ], + [ + ")-token", + ")", + 44, + 44, + null + ], + [ + ")-token", + ")", + 45, + 45, + null + ], + [ + ")-token", + ")", + 46, + 46, + null + ] + ], + "value": [ + { + "type": "token", + "tokens": [ + [ + "dimension-token", + "10px", + 5, + 8, + { + "value": 10, + "type": "integer", + "unit": "px" + } + ] + ] + }, + { + "type": "token", + "tokens": [ + [ + "comma-token", + ",", + 9, + 9, + null + ] + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 10, + 10, + null + ] + ] + }, + { + "type": "comment", + "tokens": [ + [ + "comment", + "/* a comment */", + 11, + 25, + null + ] + ] + }, + { + "type": "token", + "tokens": [ + [ + "comma-token", + ",", + 26, + 26, + null + ] + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 27, + 27, + null + ] + ] + }, + { + "type": "simple-block", + "startToken": [ + "(-token", + "(", + 28, + 28, + null + ], + "tokens": [ + [ + "(-token", + "(", + 28, + 28, + null + ], + [ + "ident-token", + "other", + 29, + 33, + { + "value": "other" + } + ], + [ + "whitespace-token", + " ", + 34, + 34, + null + ], + [ + "function-token", + "calc(", + 35, + 39, + { + "value": "calc" + } + ], + [ + "ident-token", + "more", + 40, + 43, + { + "value": "more" + } + ], + [ + ")-token", + ")", + 44, + 44, + null + ], + [ + ")-token", + ")", + 45, + 45, + null + ] + ], + "value": [ + { + "type": "token", + "tokens": [ + [ + "ident-token", + "other", + 29, + 33, + { + "value": "other" + } + ] + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 34, + 34, + null + ] + ] + }, + { + "type": "function", + "name": "calc", + "tokens": [ + [ + "function-token", + "calc(", + 35, + 39, + { + "value": "calc" + } + ], + [ + "ident-token", + "more", + 40, + 43, + { + "value": "more" + } + ], + [ + ")-token", + ")", + 44, + 44, + null + ] + ], + "value": [ + { + "type": "token", + "tokens": [ + [ + "ident-token", + "more", + 40, + 43, + { + "value": "more" + } + ] + ] + } + ] + } + ] + } + ] + } +] \ No newline at end of file diff --git a/packages/css-parser-algorithms/test/cases/various/0015.mjs b/packages/css-parser-algorithms/test/cases/various/0015.mjs new file mode 100644 index 000000000..656a7b017 --- /dev/null +++ b/packages/css-parser-algorithms/test/cases/various/0015.mjs @@ -0,0 +1,13 @@ +import assert from 'assert'; +import { runTest } from '../../util/run-test.mjs'; + +runTest( + 'calc(10px, /* a comment */, (other calc(more)))', + 'various/0015', + (actual, expected) => { + assert.deepStrictEqual( + actual, + expected, + ); + }, +); diff --git a/packages/css-parser-algorithms/test/cases/various/0016.list-comma.expect.json b/packages/css-parser-algorithms/test/cases/various/0016.list-comma.expect.json new file mode 100644 index 000000000..28f35b5cd --- /dev/null +++ b/packages/css-parser-algorithms/test/cases/various/0016.list-comma.expect.json @@ -0,0 +1,162 @@ +[ + [ + { + "type": "simple-block", + "startToken": [ + "(-token", + "(", + 0, + 0, + null + ], + "tokens": [ + [ + "(-token", + "(", + 0, + 0, + null + ], + [ + "dimension-token", + "10px", + 1, + 4, + { + "value": 10, + "type": "integer", + "unit": "px" + } + ], + [ + "comma-token", + ",", + 5, + 5, + null + ], + [ + "dimension-token", + "12px", + 6, + 9, + { + "value": 12, + "type": "integer", + "unit": "px" + } + ], + [ + ")-token", + ")", + 10, + 10, + null + ] + ], + "value": [ + { + "type": "token", + "tokens": [ + [ + "dimension-token", + "10px", + 1, + 4, + { + "value": 10, + "type": "integer", + "unit": "px" + } + ] + ] + }, + { + "type": "token", + "tokens": [ + [ + "comma-token", + ",", + 5, + 5, + null + ] + ] + }, + { + "type": "token", + "tokens": [ + [ + "dimension-token", + "12px", + 6, + 9, + { + "value": 12, + "type": "integer", + "unit": "px" + } + ] + ] + } + ] + } + ], + [ + { + "type": "simple-block", + "startToken": [ + "(-token", + "(", + 12, + 12, + null + ], + "tokens": [ + [ + "(-token", + "(", + 12, + 12, + null + ], + [ + "dimension-token", + "10px", + 13, + 16, + { + "value": 10, + "type": "integer", + "unit": "px" + } + ], + [ + ")-token", + ")", + 17, + 17, + null + ] + ], + "value": [ + { + "type": "token", + "tokens": [ + [ + "dimension-token", + "10px", + 13, + 16, + { + "value": 10, + "type": "integer", + "unit": "px" + } + ] + ] + } + ] + } + ] +] \ No newline at end of file diff --git a/packages/css-parser-algorithms/test/cases/various/0016.list-space.expect.json b/packages/css-parser-algorithms/test/cases/various/0016.list-space.expect.json new file mode 100644 index 000000000..bc31e9fd7 --- /dev/null +++ b/packages/css-parser-algorithms/test/cases/various/0016.list-space.expect.json @@ -0,0 +1,170 @@ +[ + { + "type": "simple-block", + "startToken": [ + "(-token", + "(", + 0, + 0, + null + ], + "tokens": [ + [ + "(-token", + "(", + 0, + 0, + null + ], + [ + "dimension-token", + "10px", + 1, + 4, + { + "value": 10, + "type": "integer", + "unit": "px" + } + ], + [ + "comma-token", + ",", + 5, + 5, + null + ], + [ + "dimension-token", + "12px", + 6, + 9, + { + "value": 12, + "type": "integer", + "unit": "px" + } + ], + [ + ")-token", + ")", + 10, + 10, + null + ] + ], + "value": [ + { + "type": "token", + "tokens": [ + [ + "dimension-token", + "10px", + 1, + 4, + { + "value": 10, + "type": "integer", + "unit": "px" + } + ] + ] + }, + { + "type": "token", + "tokens": [ + [ + "comma-token", + ",", + 5, + 5, + null + ] + ] + }, + { + "type": "token", + "tokens": [ + [ + "dimension-token", + "12px", + 6, + 9, + { + "value": 12, + "type": "integer", + "unit": "px" + } + ] + ] + } + ] + }, + { + "type": "token", + "tokens": [ + [ + "comma-token", + ",", + 11, + 11, + null + ] + ] + }, + { + "type": "simple-block", + "startToken": [ + "(-token", + "(", + 12, + 12, + null + ], + "tokens": [ + [ + "(-token", + "(", + 12, + 12, + null + ], + [ + "dimension-token", + "10px", + 13, + 16, + { + "value": 10, + "type": "integer", + "unit": "px" + } + ], + [ + ")-token", + ")", + 17, + 17, + null + ] + ], + "value": [ + { + "type": "token", + "tokens": [ + [ + "dimension-token", + "10px", + 13, + 16, + { + "value": 10, + "type": "integer", + "unit": "px" + } + ] + ] + } + ] + } +] \ No newline at end of file diff --git a/packages/css-parser-algorithms/test/cases/various/0016.mjs b/packages/css-parser-algorithms/test/cases/various/0016.mjs new file mode 100644 index 000000000..238192b31 --- /dev/null +++ b/packages/css-parser-algorithms/test/cases/various/0016.mjs @@ -0,0 +1,13 @@ +import assert from 'assert'; +import { runTest } from '../../util/run-test.mjs'; + +runTest( + '(10px,12px),(10px)', + 'various/0016', + (actual, expected) => { + assert.deepStrictEqual( + actual, + expected, + ); + }, +); diff --git a/packages/css-parser-algorithms/test/cases/various/0017.list-comma.expect.json b/packages/css-parser-algorithms/test/cases/various/0017.list-comma.expect.json new file mode 100644 index 000000000..51b1cc6a1 --- /dev/null +++ b/packages/css-parser-algorithms/test/cases/various/0017.list-comma.expect.json @@ -0,0 +1,151 @@ +[ + [ + { + "type": "simple-block", + "startToken": [ + "(-token", + "(", + 0, + 0, + null + ], + "tokens": [ + [ + "(-token", + "(", + 0, + 0, + null + ], + [ + "dimension-token", + "10px", + 1, + 4, + { + "value": 10, + "type": "integer", + "unit": "px" + } + ], + [ + "comma-token", + ",", + 5, + 5, + null + ], + [ + "dimension-token", + "12px", + 6, + 9, + { + "value": 12, + "type": "integer", + "unit": "px" + } + ], + [ + ")-token", + ")", + 10, + 10, + null + ] + ], + "value": [ + { + "type": "token", + "tokens": [ + [ + "dimension-token", + "10px", + 1, + 4, + { + "value": 10, + "type": "integer", + "unit": "px" + } + ] + ] + }, + { + "type": "token", + "tokens": [ + [ + "comma-token", + ",", + 5, + 5, + null + ] + ] + }, + { + "type": "token", + "tokens": [ + [ + "dimension-token", + "12px", + 6, + 9, + { + "value": 12, + "type": "integer", + "unit": "px" + } + ] + ] + } + ] + } + ], + [ + { + "type": "token", + "tokens": [ + [ + "dimension-token", + "10px", + 12, + 15, + { + "value": 10, + "type": "integer", + "unit": "px" + } + ] + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 16, + 16, + null + ] + ] + }, + { + "type": "token", + "tokens": [ + [ + "dimension-token", + "10px", + 17, + 20, + { + "value": 10, + "type": "integer", + "unit": "px" + } + ] + ] + } + ] +] \ No newline at end of file diff --git a/packages/css-parser-algorithms/test/cases/various/0017.list-space.expect.json b/packages/css-parser-algorithms/test/cases/various/0017.list-space.expect.json new file mode 100644 index 000000000..c5cf8279c --- /dev/null +++ b/packages/css-parser-algorithms/test/cases/various/0017.list-space.expect.json @@ -0,0 +1,159 @@ +[ + { + "type": "simple-block", + "startToken": [ + "(-token", + "(", + 0, + 0, + null + ], + "tokens": [ + [ + "(-token", + "(", + 0, + 0, + null + ], + [ + "dimension-token", + "10px", + 1, + 4, + { + "value": 10, + "type": "integer", + "unit": "px" + } + ], + [ + "comma-token", + ",", + 5, + 5, + null + ], + [ + "dimension-token", + "12px", + 6, + 9, + { + "value": 12, + "type": "integer", + "unit": "px" + } + ], + [ + ")-token", + ")", + 10, + 10, + null + ] + ], + "value": [ + { + "type": "token", + "tokens": [ + [ + "dimension-token", + "10px", + 1, + 4, + { + "value": 10, + "type": "integer", + "unit": "px" + } + ] + ] + }, + { + "type": "token", + "tokens": [ + [ + "comma-token", + ",", + 5, + 5, + null + ] + ] + }, + { + "type": "token", + "tokens": [ + [ + "dimension-token", + "12px", + 6, + 9, + { + "value": 12, + "type": "integer", + "unit": "px" + } + ] + ] + } + ] + }, + { + "type": "token", + "tokens": [ + [ + "comma-token", + ",", + 11, + 11, + null + ] + ] + }, + { + "type": "token", + "tokens": [ + [ + "dimension-token", + "10px", + 12, + 15, + { + "value": 10, + "type": "integer", + "unit": "px" + } + ] + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 16, + 16, + null + ] + ] + }, + { + "type": "token", + "tokens": [ + [ + "dimension-token", + "10px", + 17, + 20, + { + "value": 10, + "type": "integer", + "unit": "px" + } + ] + ] + } +] \ No newline at end of file diff --git a/packages/css-parser-algorithms/test/cases/various/0017.mjs b/packages/css-parser-algorithms/test/cases/various/0017.mjs new file mode 100644 index 000000000..3ffaa1e3b --- /dev/null +++ b/packages/css-parser-algorithms/test/cases/various/0017.mjs @@ -0,0 +1,13 @@ +import assert from 'assert'; +import { runTest } from '../../util/run-test.mjs'; + +runTest( + '(10px,12px),10px 10px', + 'various/0017', + (actual, expected) => { + assert.deepStrictEqual( + actual, + expected, + ); + }, +); diff --git a/packages/css-parser-algorithms/test/cases/various/0018.list-comma.expect.json b/packages/css-parser-algorithms/test/cases/various/0018.list-comma.expect.json new file mode 100644 index 000000000..d025163ca --- /dev/null +++ b/packages/css-parser-algorithms/test/cases/various/0018.list-comma.expect.json @@ -0,0 +1,48 @@ +[ + [ + { + "type": "token", + "tokens": [ + [ + "dimension-token", + "10px", + 0, + 3, + { + "value": 10, + "type": "integer", + "unit": "px" + } + ] + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 4, + 4, + null + ] + ] + }, + { + "type": "token", + "tokens": [ + [ + "dimension-token", + "15px", + 5, + 8, + { + "value": 15, + "type": "integer", + "unit": "px" + } + ] + ] + } + ] +] \ No newline at end of file diff --git a/packages/css-parser-algorithms/test/cases/various/0018.list-space.expect.json b/packages/css-parser-algorithms/test/cases/various/0018.list-space.expect.json new file mode 100644 index 000000000..fd958c8ba --- /dev/null +++ b/packages/css-parser-algorithms/test/cases/various/0018.list-space.expect.json @@ -0,0 +1,46 @@ +[ + { + "type": "token", + "tokens": [ + [ + "dimension-token", + "10px", + 0, + 3, + { + "value": 10, + "type": "integer", + "unit": "px" + } + ] + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 4, + 4, + null + ] + ] + }, + { + "type": "token", + "tokens": [ + [ + "dimension-token", + "15px", + 5, + 8, + { + "value": 15, + "type": "integer", + "unit": "px" + } + ] + ] + } +] \ No newline at end of file diff --git a/packages/css-parser-algorithms/test/cases/various/0018.mjs b/packages/css-parser-algorithms/test/cases/various/0018.mjs new file mode 100644 index 000000000..342074128 --- /dev/null +++ b/packages/css-parser-algorithms/test/cases/various/0018.mjs @@ -0,0 +1,13 @@ +import assert from 'assert'; +import { runTest } from '../../util/run-test.mjs'; + +runTest( + '10px 15px', + 'various/0018', + (actual, expected) => { + assert.deepStrictEqual( + actual, + expected, + ); + }, +); diff --git a/packages/css-parser-algorithms/test/consume.mjs b/packages/css-parser-algorithms/test/consume.mjs deleted file mode 100644 index 30aa0a8f6..000000000 --- a/packages/css-parser-algorithms/test/consume.mjs +++ /dev/null @@ -1,121 +0,0 @@ -import { tokenizer, TokenType } from '@csstools/css-tokenizer'; -import assert from 'assert'; -import { collectTokens } from './util/collect-tokens.mjs'; -import { consumeFunction, consumeSimpleBlock } from '@csstools/css-parser-algorithms'; - -{ - const testCases = [ - { - css: '[10px]', - advance: 3, - }, - { - css: '(calc(10px))', - advance: 5, - }, - { - css: '(calc(10px) )', - advance: 6, - }, - { - css: '(calc(10px)/* a comment */)', - advance: 6, - }, - { - css: '((calc(10px)/* a comment */) (other))', - advance: 12, - }, - ]; - - for (const testCase of testCases) { - const t = tokenizer({ - css: testCase.css, - }, { commentsAreTokens: true }); - - const tokens = collectTokens(t); - const ctx = { - onParseError: ((err) => { - throw new Error(JSON.stringify(err)); - }), - }; - - const result = consumeSimpleBlock(ctx, tokens); - - assert.deepEqual( - result.advance, - testCase.advance, - ); - - assert.deepEqual( - tokens[result.advance][0], - TokenType.EOF, - ); - - assert.deepEqual( - result.node.toString(), - testCase.css, - ); - - assert.deepEqual( - result.node.tokens(), - tokens.slice(0, -1), - ); - } -} - -{ - const testCases = [ - { - css: 'calc(10px)', - advance: 3, - }, - { - css: 'calc(10px )', - advance: 4, - }, - { - css: 'calc(10px/* a comment */)', - advance: 4, - }, - { - css: 'calc(10px, /* a comment */, (other calc(more)))', - advance: 15, - }, - ]; - - for (const testCase of testCases) { - const t = tokenizer({ - css: testCase.css, - }, { commentsAreTokens: true }); - - const tokens = collectTokens(t); - - const ctx = { - onParseError: ((err) => { - throw new Error(JSON.stringify(err)); - }), - }; - - const result = consumeFunction(ctx, tokens); - - assert.deepEqual( - result.advance, - testCase.advance, - ); - - assert.deepEqual( - tokens[result.advance][0], - TokenType.EOF, - ); - - assert.deepEqual( - result.node.toString(), - testCase.css, - ); - - assert.deepEqual( - result.node.tokens(), - tokens.slice(0, -1), - ); - } -} diff --git a/packages/css-parser-algorithms/test/parse-comma-separated-list-of-component-value.mjs b/packages/css-parser-algorithms/test/parse-comma-separated-list-of-component-value.mjs deleted file mode 100644 index 3c8417a11..000000000 --- a/packages/css-parser-algorithms/test/parse-comma-separated-list-of-component-value.mjs +++ /dev/null @@ -1,38 +0,0 @@ -import { tokenizer } from '@csstools/css-tokenizer'; -import assert from 'assert'; -import { collectTokens } from './util/collect-tokens.mjs'; -import { parseCommaSeparatedListOfComponentValues } from '@csstools/css-parser-algorithms'; - -{ - const testCases = [ - { - css: '(10px,12px),(10px)', - result: [['(10px,12px)'], ['(10px)']], - }, - - { - css: '(10px,12px),10px 10px', - result: [['(10px,12px)'], ['10px', ' ', '10px']], - }, - ]; - - for (const testCase of testCases) { - const t = tokenizer({ - css: testCase.css, - }, { commentsAreTokens: true }); - - const tokens = collectTokens(t); - const options = { - onParseError: ((err) => { - throw new Error(JSON.stringify(err)); - }), - }; - - const result = parseCommaSeparatedListOfComponentValues(tokens, options); - - assert.deepEqual( - result.map((x) => x.map((y) => y.toString())), - testCase.result, - ); - } -} diff --git a/packages/css-parser-algorithms/test/parse-component-value.mjs b/packages/css-parser-algorithms/test/parse-component-value.mjs deleted file mode 100644 index e9cd6aa2b..000000000 --- a/packages/css-parser-algorithms/test/parse-component-value.mjs +++ /dev/null @@ -1,32 +0,0 @@ -import { tokenizer } from '@csstools/css-tokenizer'; -import assert from 'assert'; -import { collectTokens } from './util/collect-tokens.mjs'; -import { parseComponentValue } from '@csstools/css-parser-algorithms'; - -{ - const testCases = [ - { - css: '(calc(10px */* a comment */5))', - }, - ]; - - for (const testCase of testCases) { - const t = tokenizer({ - css: testCase.css, - }, { commentsAreTokens: true }); - - const tokens = collectTokens(t); - const options = { - onParseError: ((err) => { - throw new Error(JSON.stringify(err)); - }), - }; - - const result = parseComponentValue(tokens, options); - - assert.deepEqual( - result.toString(), - testCase.css, - ); - } -} diff --git a/packages/css-parser-algorithms/test/parse-list-of-component-value.mjs b/packages/css-parser-algorithms/test/parse-list-of-component-value.mjs deleted file mode 100644 index d417d8490..000000000 --- a/packages/css-parser-algorithms/test/parse-list-of-component-value.mjs +++ /dev/null @@ -1,33 +0,0 @@ -import { tokenizer } from '@csstools/css-tokenizer'; -import assert from 'assert'; -import { collectTokens } from './util/collect-tokens.mjs'; -import { parseListOfComponentValues } from '@csstools/css-parser-algorithms'; - -{ - const testCases = [ - { - css: '10px 15px', - result: ['10px', ' ', '15px'], - }, - ]; - - for (const testCase of testCases) { - const t = tokenizer({ - css: testCase.css, - }, { commentsAreTokens: true }); - - const tokens = collectTokens(t); - const options = { - onParseError: ((err) => { - throw new Error(JSON.stringify(err)); - }), - }; - - const result = parseListOfComponentValues(tokens, options); - - assert.deepEqual( - result.map((x) => x.toString()), - testCase.result, - ); - } -} diff --git a/packages/css-parser-algorithms/test/parse.mjs b/packages/css-parser-algorithms/test/parse.mjs deleted file mode 100644 index c776b8120..000000000 --- a/packages/css-parser-algorithms/test/parse.mjs +++ /dev/null @@ -1,3 +0,0 @@ -import './parse-component-value.mjs'; -import './parse-list-of-component-value.mjs'; -import './parse-comma-separated-list-of-component-value.mjs'; diff --git a/packages/css-parser-algorithms/test/test.mjs b/packages/css-parser-algorithms/test/test.mjs index 1f4f59b9c..4a0b01d36 100644 --- a/packages/css-parser-algorithms/test/test.mjs +++ b/packages/css-parser-algorithms/test/test.mjs @@ -1,3 +1,36 @@ -import './consume.mjs'; -import './parse.mjs'; +import './cases/media-not/0001.mjs'; +import './cases/mf-boolean/0001.mjs'; +import './cases/mf-boolean/0002.mjs'; +import './cases/mf-boolean/0003.mjs'; +import './cases/mf-boolean/0004.mjs'; +import './cases/mf-boolean/0005.mjs'; + +import './cases/mf-plain/0001.mjs'; +import './cases/mf-plain/0002.mjs'; +import './cases/mf-plain/0003.mjs'; +import './cases/mf-plain/0004.mjs'; +import './cases/mf-plain/0005.mjs'; + +import './cases/query-with-type/0001.mjs'; +import './cases/query-with-type/0002.mjs'; +import './cases/query-with-type/0003.mjs'; + +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'; diff --git a/packages/css-parser-algorithms/test/util/collect-tokens.mjs b/packages/css-parser-algorithms/test/util/collect-tokens.mjs deleted file mode 100644 index 5ab1c998b..000000000 --- a/packages/css-parser-algorithms/test/util/collect-tokens.mjs +++ /dev/null @@ -1,11 +0,0 @@ -export function collectTokens(t) { - const bag = []; - - while (!t.endOfFile()) { - bag.push(t.nextToken()); - } - - bag.push(t.nextToken()); // EOF-token - - return bag; -} diff --git a/packages/css-parser-algorithms/test/util/run-test.mjs b/packages/css-parser-algorithms/test/util/run-test.mjs new file mode 100644 index 000000000..5d58211c9 --- /dev/null +++ b/packages/css-parser-algorithms/test/util/run-test.mjs @@ -0,0 +1,65 @@ +import fs from 'fs'; +import path from 'path'; +import { parseCommaSeparatedListOfComponentValues, parseListOfComponentValues } from '@csstools/css-parser-algorithms'; +import { tokenizer } from '@csstools/css-tokenizer'; + +export function runTest(source, testPath, assertEqual) { + const onParseError = (err) => { + console.warn(err); + throw new Error(`Unable to parse "${source}"`); + }; + const t = tokenizer({ css: source }, { + commentsAreTokens: true, + onParseError: onParseError, + }); + + const tokens = []; + + { + while (!t.endOfFile()) { + tokens.push(t.nextToken()); + } + + tokens.push(t.nextToken()); // EOF-token + } + + { + // Space separated list of component values + const resultAST = parseListOfComponentValues(tokens, { + onParseError: onParseError, + }); + const resultAST_JSON = JSON.stringify(resultAST, null, '\t'); + + if (process.env['REWRITE_EXPECTS'] === 'true') { + fs.writeFileSync(path.join(process.cwd(), `./test/cases/${testPath}.list-space.expect.json`), resultAST_JSON); + fs.writeFileSync(path.join(process.cwd(), `./test/cases/${testPath}.list-space.result.json`), resultAST_JSON); + } else { + const expectData = JSON.parse(fs.readFileSync(path.join(process.cwd(), `./test/cases/${testPath}.list-space.expect.json`)).toString()); + + assertEqual( + JSON.parse(resultAST_JSON), + expectData, + ); + } + } + + { + // Comma separated list of component values + const resultAST = parseCommaSeparatedListOfComponentValues(tokens, { + onParseError: onParseError, + }); + const resultAST_JSON = JSON.stringify(resultAST, null, '\t'); + + if (process.env['REWRITE_EXPECTS'] === 'true') { + fs.writeFileSync(path.join(process.cwd(), `./test/cases/${testPath}.list-comma.expect.json`), resultAST_JSON); + fs.writeFileSync(path.join(process.cwd(), `./test/cases/${testPath}.list-comma.result.json`), resultAST_JSON); + } else { + const expectData = JSON.parse(fs.readFileSync(path.join(process.cwd(), `./test/cases/${testPath}.list-comma.expect.json`)).toString()); + + assertEqual( + JSON.parse(resultAST_JSON), + expectData, + ); + } + } +} diff --git a/packages/media-query-list-parser/package.json b/packages/media-query-list-parser/package.json index f1dfa6c4c..a24b5bdca 100644 --- a/packages/media-query-list-parser/package.json +++ b/packages/media-query-list-parser/package.json @@ -1,6 +1,6 @@ { "name": "@csstools/media-query-list-parser", - "description": "Tokenize CSS", + "description": "Parse CSS media query lists.", "version": "1.0.0", "contributors": [ { @@ -49,6 +49,7 @@ "lint:package-json": "node ../../.github/bin/format-package-json.mjs", "prepublishOnly": "npm run clean && npm run build && npm run test", "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/media-query-list-parser#readme", @@ -60,7 +61,8 @@ "bugs": "https://github.com/csstools/postcss-plugins/issues", "keywords": [ "css", - "tokenizer" + "media query", + "parser" ], "volta": { "extends": "../../package.json" diff --git a/packages/media-query-list-parser/src/index.ts b/packages/media-query-list-parser/src/index.ts index 7283214d3..bb084e297 100644 --- a/packages/media-query-list-parser/src/index.ts +++ b/packages/media-query-list-parser/src/index.ts @@ -1,7 +1,21 @@ export { parse } from './parser/parse'; export { NodeType } from './nodes/node-type'; -export { MediaInParens } from './nodes/media-in-parens'; +export { GeneralEnclosed } from './nodes/general-enclosed'; +export { MediaAnd } from './nodes/media-and'; export { MediaCondition } from './nodes/media-condition'; +export { MediaConditionList, MediaConditionListWithAnd, MediaConditionListWithOr } from './nodes/media-condition-list'; +export { MediaFeature } from './nodes/media-feature'; +export { MediaFeatureBoolean } from './nodes/media-feature-boolean'; +export { MediaFeatureName } from './nodes/media-feature-name'; +export { MediaFeaturePlain } from './nodes/media-feature-plain'; +export { MediaFeatureRange, MediaFeatureRangeNameValue, MediaFeatureRangeValueName, MediaFeatureRangeValueNameValue } from './nodes/media-feature-range'; +export { MediaFeatureValue } from './nodes/media-feature-value'; +export { MediaInParens } from './nodes/media-in-parens'; export { MediaNot } from './nodes/media-not'; +export { MediaOr } from './nodes/media-or'; export { MediaQuery, MediaQueryWithType, MediaQueryWithoutType } from './nodes/media-query'; + +export { MediaFeatureComparison, MediaFeatureEQ, MediaFeatureGT, MediaFeatureLT, matchesComparison, comparisonFromTokens } from './nodes/media-feature-comparison'; +export { MediaType, typeFromToken } from './nodes/media-type'; +export { MediaQueryModifier, modifierFromToken } from './nodes/media-query-modifier'; diff --git a/packages/media-query-list-parser/src/nodes/general-enclosed.ts b/packages/media-query-list-parser/src/nodes/general-enclosed.ts index d7ea7b4af..230e4b0cd 100644 --- a/packages/media-query-list-parser/src/nodes/general-enclosed.ts +++ b/packages/media-query-list-parser/src/nodes/general-enclosed.ts @@ -1,7 +1,8 @@ import { ComponentValue, ContainerNode } from '@csstools/css-parser-algorithms'; +import { NodeType } from './node-type'; export class GeneralEnclosed { - type = 'general-enclosed'; + type = NodeType.GeneralEnclosed; value: ComponentValue; diff --git a/packages/media-query-list-parser/src/nodes/media-and.ts b/packages/media-query-list-parser/src/nodes/media-and.ts index f14df64d6..1a301366b 100644 --- a/packages/media-query-list-parser/src/nodes/media-and.ts +++ b/packages/media-query-list-parser/src/nodes/media-and.ts @@ -1,8 +1,9 @@ import { CSSToken, stringify } from '@csstools/css-tokenizer'; import { MediaInParens, MediaInParensWalkerEntry, MediaInParensWalkerParent } from './media-in-parens'; +import { NodeType } from './node-type'; export class MediaAnd { - type = 'media-and'; + type = NodeType.MediaAnd; modifier: Array; media: MediaInParens; diff --git a/packages/media-query-list-parser/src/nodes/media-condition-list.ts b/packages/media-query-list-parser/src/nodes/media-condition-list.ts index 641f78cf4..427826486 100644 --- a/packages/media-query-list-parser/src/nodes/media-condition-list.ts +++ b/packages/media-query-list-parser/src/nodes/media-condition-list.ts @@ -2,11 +2,12 @@ import { CSSToken, stringify } from '@csstools/css-tokenizer'; import { MediaAnd, MediaAndWalkerEntry, MediaAndWalkerParent } from './media-and'; import { MediaInParens } from './media-in-parens'; import { MediaOr, MediaOrWalkerEntry, MediaOrWalkerParent } from './media-or'; +import { NodeType } from './node-type'; export type MediaConditionList = MediaConditionListWithAnd | MediaConditionListWithOr; export class MediaConditionListWithAnd { - type = 'media-condition-list-and'; + type = NodeType.MediaConditionListWithAnd; leading: MediaInParens; list: Array; @@ -109,7 +110,7 @@ export type MediaConditionListWithAndWalkerEntry = MediaAndWalkerEntry | MediaAn export type MediaConditionListWithAndWalkerParent = MediaAndWalkerParent | MediaConditionListWithAnd; export class MediaConditionListWithOr { - type = 'media-condition-list-or'; + type = NodeType.MediaConditionListWithOr; leading: MediaInParens; list: Array; diff --git a/packages/media-query-list-parser/src/nodes/media-condition.ts b/packages/media-query-list-parser/src/nodes/media-condition.ts index fff8842e2..94753461f 100644 --- a/packages/media-query-list-parser/src/nodes/media-condition.ts +++ b/packages/media-query-list-parser/src/nodes/media-condition.ts @@ -1,9 +1,10 @@ import { MediaConditionListWithAnd, MediaConditionListWithAndWalkerEntry, MediaConditionListWithAndWalkerParent, MediaConditionListWithOr, MediaConditionListWithOrWalkerEntry, MediaConditionListWithOrWalkerParent } from './media-condition-list'; import { MediaInParens } from './media-in-parens'; import { MediaNot, MediaNotWalkerEntry, MediaNotWalkerParent } from './media-not'; +import { NodeType } from './node-type'; export class MediaCondition { - type = 'media-condition'; + type = NodeType.MediaCondition; media: MediaNot | MediaInParens | MediaConditionListWithAnd | MediaConditionListWithOr; diff --git a/packages/media-query-list-parser/src/nodes/media-feature-boolean.ts b/packages/media-query-list-parser/src/nodes/media-feature-boolean.ts index ad3ab9e65..aadda1a31 100644 --- a/packages/media-query-list-parser/src/nodes/media-feature-boolean.ts +++ b/packages/media-query-list-parser/src/nodes/media-feature-boolean.ts @@ -1,8 +1,9 @@ import { ComponentValue } from '@csstools/css-parser-algorithms'; import { MediaFeatureName, parseMediaFeatureName } from './media-feature-name'; +import { NodeType } from './node-type'; export class MediaFeatureBoolean extends MediaFeatureName { - type = 'mf-boolean'; + type = NodeType.MediaFeatureBoolean; } export function parseMediaFeatureBoolean(componentValues: Array) { diff --git a/packages/media-query-list-parser/src/nodes/media-feature-name.ts b/packages/media-query-list-parser/src/nodes/media-feature-name.ts index 339fa9683..d74ae4b72 100644 --- a/packages/media-query-list-parser/src/nodes/media-feature-name.ts +++ b/packages/media-query-list-parser/src/nodes/media-feature-name.ts @@ -1,9 +1,10 @@ import { ComponentValue, ComponentValueType, TokenNode } from '@csstools/css-parser-algorithms'; import { CSSToken, stringify, TokenIdent } from '@csstools/css-tokenizer'; import { isIdent } from '../util/component-value-is'; +import { NodeType } from './node-type'; export class MediaFeatureName { - type = 'mf-name'; + type = NodeType.MediaFeatureName; name: ComponentValue; before: Array; diff --git a/packages/media-query-list-parser/src/nodes/media-feature-plain.ts b/packages/media-query-list-parser/src/nodes/media-feature-plain.ts index db91d3575..c88b4bd1b 100644 --- a/packages/media-query-list-parser/src/nodes/media-feature-plain.ts +++ b/packages/media-query-list-parser/src/nodes/media-feature-plain.ts @@ -2,9 +2,10 @@ import { ComponentValue, ComponentValueType } from '@csstools/css-parser-algorit import { CSSToken, stringify, TokenColon, TokenType } from '@csstools/css-tokenizer'; import { parseMediaFeatureName, MediaFeatureName } from './media-feature-name'; import { parseMediaFeatureValue, MediaFeatureValue, MediaFeatureValueWalkerEntry, MediaFeatureValueWalkerParent } from './media-feature-value'; +import { NodeType } from './node-type'; export class MediaFeaturePlain { - type = 'mf-plain'; + type = NodeType.MediaFeaturePlain; name: MediaFeatureName; colon: TokenColon; diff --git a/packages/media-query-list-parser/src/nodes/media-feature-range.ts b/packages/media-query-list-parser/src/nodes/media-feature-range.ts index d454a7ecd..88435acb4 100644 --- a/packages/media-query-list-parser/src/nodes/media-feature-range.ts +++ b/packages/media-query-list-parser/src/nodes/media-feature-range.ts @@ -3,13 +3,14 @@ import { CSSToken, stringify, TokenDelim, TokenType } from '@csstools/css-tokeni import { comparisonFromTokens, matchesComparison } from './media-feature-comparison'; import { MediaFeatureName, parseMediaFeatureName } from './media-feature-name'; import { MediaFeatureValue, MediaFeatureValueWalkerEntry, MediaFeatureValueWalkerParent, parseMediaFeatureValue } from './media-feature-value'; +import { NodeType } from './node-type'; export type MediaFeatureRange = MediaFeatureRangeNameValue | MediaFeatureRangeValueName | MediaFeatureRangeValueNameValue; export class MediaFeatureRangeNameValue { - type = 'mf-range-name-value'; + type = NodeType.MediaFeatureRangeNameValue; name: MediaFeatureName; operator: [TokenDelim, TokenDelim] | [TokenDelim]; @@ -80,7 +81,7 @@ export class MediaFeatureRangeNameValue { } export class MediaFeatureRangeValueName { - type = 'mf-range-value-range'; + type = NodeType.MediaFeatureRangeValueName; name: MediaFeatureName; operator: [TokenDelim, TokenDelim] | [TokenDelim]; @@ -151,7 +152,7 @@ export class MediaFeatureRangeValueName { } export class MediaFeatureRangeValueNameValue { - type = 'mf-range-value-name-value'; + type = NodeType.MediaFeatureRangeValueNameValue; name: MediaFeatureName; valueOne: MediaFeatureValue; diff --git a/packages/media-query-list-parser/src/nodes/media-feature-value.ts b/packages/media-query-list-parser/src/nodes/media-feature-value.ts index 8b31019de..fa888f95a 100644 --- a/packages/media-query-list-parser/src/nodes/media-feature-value.ts +++ b/packages/media-query-list-parser/src/nodes/media-feature-value.ts @@ -1,9 +1,10 @@ import { ComponentValue, ComponentValueType, ContainerNode } from '@csstools/css-parser-algorithms'; import { CSSToken, stringify, TokenType } from '@csstools/css-tokenizer'; import { isDimension, isIdent, isNumber } from '../util/component-value-is'; +import { NodeType } from './node-type'; export class MediaFeatureValue { - type = 'mf-value'; + type = NodeType.MediaFeatureValue; value: ComponentValue | Array; before: Array; diff --git a/packages/media-query-list-parser/src/nodes/media-feature.ts b/packages/media-query-list-parser/src/nodes/media-feature.ts index b496587c1..752d895bf 100644 --- a/packages/media-query-list-parser/src/nodes/media-feature.ts +++ b/packages/media-query-list-parser/src/nodes/media-feature.ts @@ -3,9 +3,10 @@ import { TokenType } from '@csstools/css-tokenizer'; import { MediaFeatureBoolean, parseMediaFeatureBoolean } from './media-feature-boolean'; import { MediaFeaturePlain, MediaFeaturePlainWalkerEntry, MediaFeaturePlainWalkerParent, parseMediaFeaturePlain } from './media-feature-plain'; import { MediaFeatureRange, MediaFeatureRangeWalkerEntry, MediaFeatureRangeWalkerParent, parseMediaFeatureRange } from './media-feature-range'; +import { NodeType } from './node-type'; export class MediaFeature { - type = 'media-feature'; + type = NodeType.MediaFeature; feature: MediaFeaturePlain | MediaFeatureBoolean | MediaFeatureRange; diff --git a/packages/media-query-list-parser/src/nodes/media-in-parens.ts b/packages/media-query-list-parser/src/nodes/media-in-parens.ts index f918dcb97..b0755c35e 100644 --- a/packages/media-query-list-parser/src/nodes/media-in-parens.ts +++ b/packages/media-query-list-parser/src/nodes/media-in-parens.ts @@ -10,9 +10,10 @@ import { MediaFeatureName } from './media-feature-name'; import { MediaFeaturePlain } from './media-feature-plain'; import { MediaFeatureRange } from './media-feature-range'; import { MediaFeatureValue } from './media-feature-value'; +import { NodeType } from './node-type'; export class MediaInParens { - type = 'media-in-parens'; + type = NodeType.MediaInParens; media: MediaCondition | MediaFeature | GeneralEnclosed; before: Array; diff --git a/packages/media-query-list-parser/src/nodes/media-or.ts b/packages/media-query-list-parser/src/nodes/media-or.ts index ac17c15bb..36e7dcb24 100644 --- a/packages/media-query-list-parser/src/nodes/media-or.ts +++ b/packages/media-query-list-parser/src/nodes/media-or.ts @@ -1,8 +1,9 @@ import { CSSToken, stringify } from '@csstools/css-tokenizer'; import { MediaInParens, MediaInParensWalkerEntry, MediaInParensWalkerParent } from './media-in-parens'; +import { NodeType } from './node-type'; export class MediaOr { - type = 'media-or'; + type = NodeType.MediaOr; modifier: Array; media: MediaInParens; diff --git a/packages/media-query-list-parser/src/nodes/media-type.ts b/packages/media-query-list-parser/src/nodes/media-type.ts index 2eeebf847..d30336bea 100644 --- a/packages/media-query-list-parser/src/nodes/media-type.ts +++ b/packages/media-query-list-parser/src/nodes/media-type.ts @@ -23,7 +23,6 @@ export enum MediaType { Speech = 'speech', } - export function typeFromToken(token: TokenIdent): MediaType | false { if (token[0] !== TokenType.Ident) { return false; diff --git a/packages/media-query-list-parser/src/nodes/node-type.ts b/packages/media-query-list-parser/src/nodes/node-type.ts index 826046e98..66db2c079 100644 --- a/packages/media-query-list-parser/src/nodes/node-type.ts +++ b/packages/media-query-list-parser/src/nodes/node-type.ts @@ -1,5 +1,20 @@ export enum NodeType { + GeneralEnclosed = 'general-enclosed', + MediaAnd = 'media-and', + MediaCondition = 'media-condition', + MediaConditionListWithAnd = 'media-condition-list-and', + MediaConditionListWithOr = 'media-condition-list-or', + MediaFeature = 'media-feature', + MediaFeatureBoolean = 'mf-boolean', + MediaFeatureName = 'mf-name', + MediaFeaturePlain = 'mf-plain', + MediaFeatureRangeNameValue = 'mf-range-name-value', + MediaFeatureRangeValueName = 'mf-range-value-name', + MediaFeatureRangeValueNameValue = 'mf-range-value-name-value', + MediaFeatureValue = 'mf-value', + MediaInParens = 'media-in-parens', + MediaNot = 'media-not', + MediaOr = 'media-or', MediaQueryWithType = 'media-query-with-type', MediaQueryWithoutType = 'media-query-without-type', - MediaNot = 'media-not', } diff --git a/packages/media-query-list-parser/test/_import.mjs b/packages/media-query-list-parser/test/_import.mjs new file mode 100644 index 000000000..3215dfbbb --- /dev/null +++ b/packages/media-query-list-parser/test/_import.mjs @@ -0,0 +1,3 @@ +import { parse } from '@csstools/media-query-list-parser'; + +parse('(min-width: 300px)'); diff --git a/packages/media-query-list-parser/test/_require.cjs b/packages/media-query-list-parser/test/_require.cjs new file mode 100644 index 000000000..09406cf78 --- /dev/null +++ b/packages/media-query-list-parser/test/_require.cjs @@ -0,0 +1,3 @@ +const { parse } = require('@csstools/media-query-list-parser'); + +parse('(min-width: 300px)'); diff --git a/packages/media-query-list-parser/test/cases/various/0004.expect.json b/packages/media-query-list-parser/test/cases/various/0004.expect.json index 0c5514ab5..0933ba63b 100644 --- a/packages/media-query-list-parser/test/cases/various/0004.expect.json +++ b/packages/media-query-list-parser/test/cases/various/0004.expect.json @@ -99,6 +99,78 @@ 25, null ] + ], + "value": [ + { + "type": "token", + "tokens": [ + [ + "dimension-token", + "50vw", + 14, + 17, + { + "value": 50, + "type": "integer", + "unit": "vw" + } + ] + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 18, + 18, + null + ] + ] + }, + { + "type": "token", + "tokens": [ + [ + "delim-token", + "-", + 19, + 19, + { + "value": "-" + } + ] + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 20, + 20, + null + ] + ] + }, + { + "type": "token", + "tokens": [ + [ + "dimension-token", + "3rem", + 21, + 24, + { + "value": 3, + "type": "integer", + "unit": "rem" + } + ] + ] + } ] }, "tokens": [ diff --git a/plugins/postcss-custom-media/package.json b/plugins/postcss-custom-media/package.json index dea2f6d01..161469525 100644 --- a/plugins/postcss-custom-media/package.json +++ b/plugins/postcss-custom-media/package.json @@ -45,6 +45,7 @@ "dist" ], "dependencies": { + "@csstools/media-query-list-parser": "^1.0.0", "@csstools/css-tokenizer": "^1.0.0" }, "peerDependencies": { diff --git a/plugins/postcss-custom-media/src/custom-media-from-root.ts b/plugins/postcss-custom-media/src/custom-media-from-root.ts index c7364a678..3bc4367b0 100644 --- a/plugins/postcss-custom-media/src/custom-media-from-root.ts +++ b/plugins/postcss-custom-media/src/custom-media-from-root.ts @@ -1,7 +1,7 @@ import type { ChildNode, Container, Document, Root as PostCSSRoot } from 'postcss'; import { isProcessableCustomMediaRule } from './is-processable-custom-media-rule'; import { removeCyclicReferences } from './toposort'; -import { parseCustomMedia } from './transform-at-media/custom-media'; +import { parseCustomMedia } from './transform-at-media-v2/custom-media'; // return custom media from the css root, conditionally removing them export default function getCustomMedia(root: PostCSSRoot, result, opts: { preserve?: boolean }): Map { diff --git a/plugins/postcss-custom-media/src/transform-at-media-v2/always-true-or-false.ts b/plugins/postcss-custom-media/src/transform-at-media-v2/always-true-or-false.ts new file mode 100644 index 000000000..78d0c72b0 --- /dev/null +++ b/plugins/postcss-custom-media/src/transform-at-media-v2/always-true-or-false.ts @@ -0,0 +1,14 @@ +import { NumberType, TokenType } from '@csstools/css-tokenizer'; +import type { CSSToken } from '@csstools/css-tokenizer'; + +export const alwaysTrue: Array = [ + [TokenType.Ident, 'max-color', 0, 0, { value: 'max-color' }], + [TokenType.Colon, ':', 0, 0, undefined], + [TokenType.Number, '2147477350', 0, 0, { value: 9999943, type: NumberType.Integer }], +]; + +export const neverTrue: Array = [ + [TokenType.Ident, 'color', 0, 0, { value: 'color' }], + [TokenType.Colon, ':', 0, 0, undefined], + [TokenType.Number, '2147477350', 0, 0, { value: 9999943, type: NumberType.Integer }], +]; diff --git a/plugins/postcss-custom-media/src/transform-at-media-v2/at-media-params-tokens.ts b/plugins/postcss-custom-media/src/transform-at-media-v2/at-media-params-tokens.ts new file mode 100644 index 000000000..815e1dd30 --- /dev/null +++ b/plugins/postcss-custom-media/src/transform-at-media-v2/at-media-params-tokens.ts @@ -0,0 +1,19 @@ +import { tokenizer } from '@csstools/css-tokenizer'; +import type { CSSToken } from '@csstools/css-tokenizer'; + +export function atMediaParamsTokens(params: string): Array { + const t = tokenizer({ + css: params, + }, { + commentsAreTokens: true, onParseError: () => { + throw new Error(`Unable to parse media query "${params}"`); + }, + }); + + const tokens: Array = []; + while (!t.endOfFile()) { + tokens.push(t.nextToken()); + } + + return tokens; +} diff --git a/plugins/postcss-custom-media/src/transform-at-media-v2/custom-media.ts b/plugins/postcss-custom-media/src/transform-at-media-v2/custom-media.ts new file mode 100644 index 000000000..13a94d2bd --- /dev/null +++ b/plugins/postcss-custom-media/src/transform-at-media-v2/custom-media.ts @@ -0,0 +1,111 @@ +import { stringify, TokenIdent, TokenType } from '@csstools/css-tokenizer'; +import { NodeType, parse, MediaCondition, MediaInParens, MediaQueryWithoutType, MediaQueryWithType, MediaNot } from '@csstools/media-query-list-parser'; +import { atMediaParamsTokens } from '../transform-at-media/at-media-params-tokens'; + +export function parseCustomMedia(params: string): { name: string, truthy: string, falsy: string, dependsOn: Array<[string, string]> } | false { + const tokens = atMediaParamsTokens(params); + + const customMediaReferences: Set = new Set(); + + let name = ''; + let remainder = tokens; + for (let i = 0; i < tokens.length; i++) { + if (tokens[i][0] === TokenType.Comment) { + continue; + } + if (tokens[i][0] === TokenType.Whitespace) { + continue; + } + + if (tokens[i][0] === TokenType.Ident) { + const identToken = tokens[i] as TokenIdent; + if (identToken[4].value.startsWith('--')) { + name = identToken[4].value; + remainder = tokens.slice(i + 1); + break; + } + } + + return false; + } + + for (let i = 0; i < remainder.length; i++) { + if (remainder[i][0] === TokenType.Ident) { + const identToken = remainder[i] as TokenIdent; + if (identToken[4].value.startsWith('--')) { + customMediaReferences.add(identToken[4].value); + } + } + } + + const mediaQueryListTruthy = parse(stringify(...remainder)); + const mediaQueryListFalsy = parse(stringify(...remainder)); + + for (let i = 0; i < mediaQueryListFalsy.length; i++) { + const mediaQuery = mediaQueryListFalsy[i]; + if (mediaQuery.type === NodeType.MediaQueryWithType) { + const query = mediaQuery as MediaQueryWithType; + if (query.modifier.length === 0) { + query.modifier = [ + [TokenType.Ident, 'not', 0, 0, { value: 'not' }], + ]; + } else { + for (let j = 0; j < query.modifier.length; j++) { + const token = query.modifier[j]; + if (token[0] === TokenType.Ident && token[4].value.toLowerCase() === 'not') { + query.modifier = []; + continue; + } + + if (token[0] === TokenType.Ident && token[4].value.toLowerCase() === 'only') { + query.modifier[j][1] = 'not'; + query.modifier[j][4].value = 'not'; + continue; + } + } + } + + mediaQueryListFalsy[i] = query; + continue; + } + + if (mediaQuery.type === NodeType.MediaQueryWithoutType) { + const mediaCondition = (mediaQuery as MediaQueryWithoutType).media; + if (mediaCondition.media.type === NodeType.MediaNot) { + const query = new MediaQueryWithoutType( + new MediaCondition( + ((mediaQuery as MediaQueryWithoutType).media as MediaNot).media, + ), + ); + + mediaQueryListFalsy[i] = query; + continue; + } + + const query = new MediaQueryWithoutType( + new MediaNot( + [ + [TokenType.Ident, 'not', 0, 0, { value: 'not' }], + ], + new MediaInParens( + (mediaQuery as MediaQueryWithoutType).media, + [[TokenType.Whitespace, ' ', 0, 0, null],[TokenType.OpenParen, '(', 0, 0, null]], + [[TokenType.CloseParen, ')', 0, 0, null]], + ), + ), + ); + + mediaQueryListFalsy[i] = query; + continue; + } + } + + return { + name: name, + truthy: mediaQueryListTruthy.map((x) => x.toString().trim()).join(','), + falsy: mediaQueryListFalsy.map((x) => x.toString().trim()).join(','), + dependsOn: Array.from(customMediaReferences).map((x) => { + return [x, name]; + }), + }; +} diff --git a/plugins/postcss-custom-media/src/transform-at-media-v2/split-media-query-list.ts b/plugins/postcss-custom-media/src/transform-at-media-v2/split-media-query-list.ts new file mode 100644 index 000000000..ae43ab3d2 --- /dev/null +++ b/plugins/postcss-custom-media/src/transform-at-media-v2/split-media-query-list.ts @@ -0,0 +1,60 @@ +import { TokenType } from '@csstools/css-tokenizer'; +import type { CSSToken } from '@csstools/css-tokenizer'; + +export function splitMediaQueryList(tokens: Array): Array> { + let parenDepth = 0; + let squareDepth = 0; + let curlyDepth = 0; + let depth = 0; + + const listItems = []; + let lastSliceIndex = 0; + + for (let i = 0; i < tokens.length; i++) { + if (tokens[i][0] === TokenType.OpenParen || tokens[i][0] === TokenType.Function) { + depth++; + parenDepth++; + continue; + } + if (tokens[i][0] === TokenType.CloseParen && parenDepth > 0) { + depth--; + parenDepth--; + continue; + } + + if (tokens[i][0] === TokenType.OpenCurly) { + depth++; + curlyDepth++; + continue; + } + if (tokens[i][0] === TokenType.CloseCurly && curlyDepth > 0) { + depth--; + curlyDepth--; + continue; + } + + if (tokens[i][0] === TokenType.OpenSquare) { + depth++; + squareDepth++; + continue; + } + if (tokens[i][0] === TokenType.CloseSquare && squareDepth > 0) { + depth--; + squareDepth--; + continue; + } + + if (tokens[i][0] === TokenType.Comma && depth === 0) { + listItems.push(tokens.slice(lastSliceIndex, i)); + lastSliceIndex = i + 1; + continue; + } + } + + if (lastSliceIndex === 0) { + return [tokens]; + } + + listItems.push(tokens.slice(lastSliceIndex)); + return listItems; +} diff --git a/plugins/postcss-custom-media/src/transform-at-media-v2/top-level-combination-keywords.ts b/plugins/postcss-custom-media/src/transform-at-media-v2/top-level-combination-keywords.ts new file mode 100644 index 000000000..b893b5606 --- /dev/null +++ b/plugins/postcss-custom-media/src/transform-at-media-v2/top-level-combination-keywords.ts @@ -0,0 +1,90 @@ +import { TokenType, TokenIdent } from '@csstools/css-tokenizer'; +import type { CSSToken } from '@csstools/css-tokenizer'; + +export function topLevelCombinationKeywords(tokens: Array): Set { + const keywords: Set = new Set(); + + for (let i = 0; i < tokens.length; i++) { + switch (tokens[i][0]) { + case TokenType.Function: { + let depth = 1; + while (depth !== 0) { + i++; + if (!tokens[i] || tokens[i][0] === TokenType.EOF) { + throw new Error('unexpected EOF'); + } + + switch (tokens[i][0]) { + case TokenType.OpenParen: + case TokenType.Function: + depth++; + break; + case TokenType.CloseParen: + depth--; + break; + } + } + break; + } + + case TokenType.OpenCurly: { + let depth = 1; + while (depth !== 0) { + i++; + if (!tokens[i] || tokens[i][0] === TokenType.EOF) { + throw new Error('unexpected EOF'); + } + + switch (tokens[i][0]) { + case TokenType.OpenCurly: + depth++; + break; + case TokenType.CloseCurly: + depth--; + break; + } + } + break; + } + + case TokenType.OpenSquare: { + let depth = 1; + while (depth !== 0) { + i++; + if (!tokens[i] || tokens[i][0] === TokenType.EOF) { + throw new Error('unexpected EOF'); + } + + switch (tokens[i][0]) { + case TokenType.OpenSquare: + depth++; + break; + case TokenType.CloseSquare: + depth--; + break; + } + } + break; + } + + case TokenType.Ident: { + const identToken = tokens[i] as TokenIdent; + switch (identToken[4].value.toLowerCase()) { + case 'not': + keywords.add('not'); + break; + case 'and': + keywords.add('and'); + break; + case 'or': + keywords.add('or'); + break; + } + + break; + } + } + } + + return keywords; +} diff --git a/plugins/postcss-custom-media/src/transform-at-media-v2/transform-at-media.ts b/plugins/postcss-custom-media/src/transform-at-media-v2/transform-at-media.ts new file mode 100644 index 000000000..f405b7a83 --- /dev/null +++ b/plugins/postcss-custom-media/src/transform-at-media-v2/transform-at-media.ts @@ -0,0 +1,227 @@ +import { stringify, TokenType, TokenIdent } from '@csstools/css-tokenizer'; +import type { CSSToken } from '@csstools/css-tokenizer'; +import { splitMediaQueryList } from './split-media-query-list'; +import { alwaysTrue, neverTrue } from './always-true-or-false'; +import { atMediaParamsTokens } from './at-media-params-tokens'; + +export function transformAtMediaListTokens(params: string, replacements: Map): Array<{ replaceWith: string, encapsulateWith?: string }> { + const mediaQueries = splitMediaQueryList(atMediaParamsTokens(params)); + + const stringQueries = mediaQueries.map((x) => stringify(...x)); + + for (let i = 0; i < mediaQueries.length; i++) { + const mediaQuery = mediaQueries[i]; + const original = stringQueries[i]; + + const transformedQuery = transformAtMediaTokens(mediaQuery, replacements); + if (!transformedQuery || transformedQuery.length === 0) { + continue; + } + + if (transformedQuery[0].replaceWith === original) { + continue; + } + + return stringQueries.flatMap((query, index) => { + if (index === i) { + return transformedQuery; + } + + return [{ + replaceWith: query, + }]; + }); + } + + return []; +} + +export function transformAtMediaTokens(tokens: Array, replacements: Map): Array<{replaceWith: string, encapsulateWith?: string}> { + const tokenTypes: Set = new Set(); + let identCounter = 0; + for (let i = 0; i < tokens.length; i++) { + tokenTypes.add(tokens[i][0]); + if (tokens[i][0] === TokenType.Ident) { + identCounter++; + } + } + + tokenTypes.delete(TokenType.Comment); + tokenTypes.delete(TokenType.Whitespace); + tokenTypes.delete(TokenType.OpenParen); + tokenTypes.delete(TokenType.CloseParen); + tokenTypes.delete(TokenType.Ident); + + // replacement slot is in a simple @media query : + // - @media (--custom-mq) { ... } + // - @media ((--custom-mq)) { ... } + if (tokenTypes.size == 0 && identCounter === 1) { + let candidate: Array<{ replaceWith: string, encapsulateWith?: string }> | null = null; + + let parenDepth = 0; + for (let i = 0; i < tokens.length; i++) { + if (tokens[i][0] === TokenType.Whitespace || tokens[i][0] === TokenType.Comment) { + continue; + } + + if (tokens[i][0] === TokenType.CloseParen) { + if (candidate) { + return candidate; + } + } + + candidate = null; + + if (tokens[i][0] === TokenType.CloseParen) { + parenDepth--; + continue; + } + if (tokens[i][0] === TokenType.OpenParen) { + parenDepth++; + continue; + } + + if (tokens[i][0] === TokenType.Ident && parenDepth > 0) { + const identToken = tokens[i] as TokenIdent; + + if (replacements.has(identToken[4].value)) { + candidate = [{ + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + replaceWith: replacements.get(identToken[4].value)!.truthy, + }]; + } + } + } + + return []; + } + + // replacement slot is in a complex @media query : + // - @media not (--custom-mq) { ... } + // - @media ((--custom-mq-1) or (--custom-mq-2) or (not (--custom-mq-3))) { ... } + for (let i = 0; i < tokens.length; i++) { + switch (tokens[i][0]) { + case TokenType.Function: { + let depth = 1; + while (depth !== 0) { + i++; + if (!tokens[i] || tokens[i][0] === TokenType.EOF) { + throw new Error('unexpected EOF'); + } + + switch (tokens[i][0]) { + case TokenType.OpenParen: + case TokenType.Function: + depth++; + break; + case TokenType.CloseParen: + depth--; + break; + } + } + break; + } + + case TokenType.OpenCurly: { + let depth = 1; + while (depth !== 0) { + i++; + if (!tokens[i] || tokens[i][0] === TokenType.EOF) { + throw new Error('unexpected EOF'); + } + + switch (tokens[i][0]) { + case TokenType.OpenCurly: + depth++; + break; + case TokenType.CloseCurly: + depth--; + break; + } + } + break; + } + + case TokenType.OpenSquare: { + let depth = 1; + while (depth !== 0) { + i++; + if (!tokens[i] || tokens[i][0] === TokenType.EOF) { + throw new Error('unexpected EOF'); + } + + switch (tokens[i][0]) { + case TokenType.OpenSquare: + depth++; + break; + case TokenType.CloseSquare: + depth--; + break; + } + } + break; + } + + case TokenType.Ident: { + const identToken = tokens[i] as TokenIdent; + + if (!replacements.has(identToken[4].value)) { + break; + } + + let isValid = true; + for (let p = i-1; p>= 0; p--) { + if (tokens[p][0] === TokenType.Comment || tokens[p][0] === TokenType.Whitespace) { + continue; + } + + if (tokens[p][0] === TokenType.OpenParen) { + break; + } + + isValid = false; + break; + } + + for (let n = i + 1; n < tokens.length; n++) { + if (tokens[n][0] === TokenType.Comment || tokens[n][0] === TokenType.Whitespace) { + continue; + } + + if (tokens[n][0] === TokenType.CloseParen) { + break; + } + + isValid = false; + break; + } + + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const replacement = replacements.get(identToken[4].value)!; + + if (isValid) { + const replaceWithTrue = tokens.slice(); + replaceWithTrue.splice(i, 1, ...alwaysTrue); + + const replaceWithFalse = tokens.slice(); + replaceWithFalse.splice(i, 1, ...neverTrue); + + return [ + { + replaceWith: stringify(...replaceWithTrue), + encapsulateWith: replacement.truthy, + }, + { + replaceWith: stringify(...replaceWithFalse), + encapsulateWith: replacement.falsy, + }, + ]; + } + + break; + } + } + } + + return []; +} diff --git a/plugins/postcss-custom-media/src/transform-at-media-v2/true-and-false.ts b/plugins/postcss-custom-media/src/transform-at-media-v2/true-and-false.ts new file mode 100644 index 000000000..46a8ec636 --- /dev/null +++ b/plugins/postcss-custom-media/src/transform-at-media-v2/true-and-false.ts @@ -0,0 +1,66 @@ +import { TokenType, TokenIdent } from '@csstools/css-tokenizer'; +import type { CSSToken } from '@csstools/css-tokenizer'; +import { alwaysTrue, neverTrue } from './always-true-or-false'; + +export function replaceTrueAndFalseTokens(tokens: Array): Array { + let booleanToken; + let remainder; + + for (let i = 0; i < tokens.length; i++) { + if (tokens[i][0] === TokenType.Comment) { + continue; + } + if (tokens[i][0] === TokenType.Whitespace) { + continue; + } + + if (tokens[i][0] === TokenType.Ident) { + const identToken = tokens[i] as TokenIdent; + if (identToken[4].value.toLowerCase() === 'true') { + booleanToken = 'true'; + remainder = tokens.slice(i + 1); + break; + } + + if (identToken[4].value.toLowerCase() === 'false') { + booleanToken = 'false'; + remainder = tokens.slice(i + 1); + break; + } + } + + return tokens; + } + + if (!booleanToken) { + return tokens; + } + + { + // Nothing is allowed after true|false except for comments and whitespace + for (let i = 0; i < remainder.length; i++) { + if (remainder[i][0] === TokenType.Comment) { + continue; + } + if (remainder[i][0] === TokenType.Whitespace) { + continue; + } + + return tokens; + } + } + + if (booleanToken === 'true') { + return [ + [TokenType.OpenParen, '(', 0, 0, undefined], + ...alwaysTrue, + [TokenType.CloseParen, ')', 0, 0, undefined], + ]; + } + + return [ + [TokenType.OpenParen, '(', 0, 0, undefined], + ...neverTrue, + [TokenType.CloseParen, ')', 0, 0, undefined], + ]; +} diff --git a/plugins/postcss-custom-media/src/transform-at-media/transform-at-media.ts b/plugins/postcss-custom-media/src/transform-at-media/transform-at-media.ts index a02074b7a..f405b7a83 100644 --- a/plugins/postcss-custom-media/src/transform-at-media/transform-at-media.ts +++ b/plugins/postcss-custom-media/src/transform-at-media/transform-at-media.ts @@ -96,7 +96,7 @@ export function transformAtMediaTokens(tokens: Array, replacements: Ma return []; } - // replacement slot is in a comples @media query : + // replacement slot is in a complex @media query : // - @media not (--custom-mq) { ... } // - @media ((--custom-mq-1) or (--custom-mq-2) or (not (--custom-mq-3))) { ... } for (let i = 0; i < tokens.length; i++) { diff --git a/plugins/postcss-custom-media/test/and.expect.css b/plugins/postcss-custom-media/test/and.expect.css index 9cbf54fd4..e41f3748f 100644 --- a/plugins/postcss-custom-media/test/and.expect.css +++ b/plugins/postcss-custom-media/test/and.expect.css @@ -9,7 +9,7 @@ } } -@media not all and (min-width: 300px) and (min-height: 300px) { +@media not ( (min-width: 300px) and (min-height: 300px)) { @media not screen and (color:2147477350) { .a { diff --git a/plugins/postcss-custom-media/test/basic-after-v9.expect.css b/plugins/postcss-custom-media/test/basic-after-v9.expect.css index fe92455d5..f0b7ed39d 100644 --- a/plugins/postcss-custom-media/test/basic-after-v9.expect.css +++ b/plugins/postcss-custom-media/test/basic-after-v9.expect.css @@ -13,7 +13,7 @@ } } } -@media not all and (min-width: 300px) { +@media not ( (min-width: 300px)) { @media screen and (color:2147477350) { .a { order: 2; @@ -29,7 +29,7 @@ } } } -@media not all and (min-width: 300px) { +@media not ( (min-width: 300px)) { @media not (color:2147477350) { .a { order: 3; @@ -48,7 +48,7 @@ } } -@media not all and (color),not all and (hover) { +@media not ( (color)),not ( (hover)) { @media (color:2147477350) and (width > 1024px) { .a { diff --git a/plugins/postcss-custom-media/test/basic-after-v9.preserve.expect.css b/plugins/postcss-custom-media/test/basic-after-v9.preserve.expect.css index 1a74c4295..3a1becd3e 100644 --- a/plugins/postcss-custom-media/test/basic-after-v9.preserve.expect.css +++ b/plugins/postcss-custom-media/test/basic-after-v9.preserve.expect.css @@ -20,7 +20,7 @@ } } } -@media not all and (min-width: 300px) { +@media not ( (min-width: 300px)) { @media screen and (color:2147477350) { .a { order: 2; @@ -41,7 +41,7 @@ } } } -@media not all and (min-width: 300px) { +@media not ( (min-width: 300px)) { @media not (color:2147477350) { .a { order: 3; @@ -66,7 +66,7 @@ } } -@media not all and (color),not all and (hover) { +@media not ( (color)),not ( (hover)) { @media (color:2147477350) and (width > 1024px) { .a { diff --git a/plugins/postcss-custom-media/test/basic.expect.css b/plugins/postcss-custom-media/test/basic.expect.css index 97d82e390..41af01505 100644 --- a/plugins/postcss-custom-media/test/basic.expect.css +++ b/plugins/postcss-custom-media/test/basic.expect.css @@ -25,7 +25,7 @@ } } -@media not all and (max-width: 30em),not all and (max-height: 30em) { +@media not ( (max-width: 30em)),not ( (max-height: 30em)) { @media not all and (color:2147477350) { body { @@ -43,7 +43,7 @@ } } -@media not all and (max-width: 30em),not all and (max-height: 30em) { +@media not ( (max-width: 30em)),not ( (max-height: 30em)) { @media not all and (color:2147477350) { body { @@ -64,7 +64,7 @@ } } -@media not all and (max-width: 30em),not all and (max-height: 30em) { +@media not ( (max-width: 30em)),not ( (max-height: 30em)) { @media not all and (color:2147477350) { @@ -88,7 +88,7 @@ } } -@media not all and (max-width: 30em),not all and (max-height: 30em) { +@media not ( (max-width: 30em)),not ( (max-height: 30em)) { @media all and (color:2147477350) { @@ -129,7 +129,7 @@ } } -@media not all and (max-width: 640px) { +@media not ( (max-width: 640px)) { @media (max-color:2147477350) and (color:2147477350) { body { @@ -139,7 +139,7 @@ } } -@media not all and (min-width: 320px) { +@media not ( (min-width: 320px)) { @media (max-width: 640px) { @@ -150,7 +150,7 @@ } } -@media not all and (max-width: 640px) { +@media not ( (max-width: 640px)) { @media (color:2147477350) and (color:2147477350) { body { @@ -175,7 +175,7 @@ } } -@media not all and (min-width: 320px) and (max-width: 640px) { +@media not ( (min-width: 320px) and (max-width: 640px)) { @media (color:2147477350) and (min-aspect-ratio: 16/9) { body { diff --git a/plugins/postcss-custom-media/test/comma-1.expect.css b/plugins/postcss-custom-media/test/comma-1.expect.css index 6a87b6d35..828a148b1 100644 --- a/plugins/postcss-custom-media/test/comma-1.expect.css +++ b/plugins/postcss-custom-media/test/comma-1.expect.css @@ -10,7 +10,7 @@ } } -@media screen,[aa,bb,cc] { +@media screen { .a { order: 3; } diff --git a/plugins/postcss-custom-media/test/complex.expect.css b/plugins/postcss-custom-media/test/complex.expect.css index 792e80338..913d18996 100644 --- a/plugins/postcss-custom-media/test/complex.expect.css +++ b/plugins/postcss-custom-media/test/complex.expect.css @@ -17,7 +17,7 @@ } } -@media not all and (min-width: 3),not all and (min-width: 4) { +@media not ( (min-width: 3)),not ( (min-width: 4)) { @media (color:2147477350) and (width > 1024px) { .a { order: 3; } @@ -67,7 +67,7 @@ } } -@media not all and (width >= 570px) { +@media not ( (width >= 570px)) { @media (max-color:2147477350) and (color:2147477350) { body { @@ -88,7 +88,7 @@ } } -@media not all and (width >= 570px) { +@media not ( (width >= 570px)) { @media (color:2147477350) and (color:2147477350) { body { @@ -109,7 +109,7 @@ } } -@media not all and (width < 1000px) { +@media not ( (width < 1000px)) { @media (max-color:2147477350) and (color:2147477350) { body { @@ -119,7 +119,7 @@ } } -@media not all and (width >= 570px) { +@media not ( (width >= 570px)) { @media (width < 1000px) { @@ -130,7 +130,7 @@ } } -@media not all and (width < 1000px) { +@media not ( (width < 1000px)) { @media (color:2147477350) and (color:2147477350) { body { diff --git a/plugins/postcss-custom-media/test/list.expect.css b/plugins/postcss-custom-media/test/list.expect.css index 3bf1138d3..ff4721297 100644 --- a/plugins/postcss-custom-media/test/list.expect.css +++ b/plugins/postcss-custom-media/test/list.expect.css @@ -9,7 +9,7 @@ } } -@media not all and (min-width: 101px),not all and (min-height: 102px) { +@media not ( (min-width: 101px)),not ( (min-height: 102px)) { @media screen and (color:2147477350) { .a { @@ -27,7 +27,7 @@ } } -@media not all and (min-width: 101px),not all and (min-height: 102px) { +@media not ( (min-width: 101px)),not ( (min-height: 102px)) { @media ((other: feature) and (color:2147477350)) { .a { @@ -53,7 +53,7 @@ } } -@media not all and (min-width: 201px) { +@media not ( (min-width: 201px)) { @media screen and (color:2147477350) { .b { @@ -71,7 +71,7 @@ } } -@media not all and (min-width: 202px) { +@media not ( (min-width: 202px)) { @media not (color:2147477350) { .b { diff --git a/plugins/postcss-custom-media/test/not.expect.css b/plugins/postcss-custom-media/test/not.expect.css index e1d749ba4..14ae8d227 100644 --- a/plugins/postcss-custom-media/test/not.expect.css +++ b/plugins/postcss-custom-media/test/not.expect.css @@ -9,7 +9,7 @@ } } -@media (min-height: 300px) { +@media not (min-height: 300px) { @media not (color:2147477350) { .a { diff --git a/plugins/postcss-custom-media/test/or.expect.css b/plugins/postcss-custom-media/test/or.expect.css index 17d9c8262..ec11d88df 100644 --- a/plugins/postcss-custom-media/test/or.expect.css +++ b/plugins/postcss-custom-media/test/or.expect.css @@ -9,7 +9,7 @@ } } -@media not all and ((min-width: 300px) or (min-height: 300px)) { +@media not ( (min-width: 300px) or (min-height: 300px)) { @media not screen and (color:2147477350) { .a { diff --git a/plugins/postcss-custom-media/test/true-false.expect.css b/plugins/postcss-custom-media/test/true-false.expect.css index fdde69e1b..78631c2a1 100644 --- a/plugins/postcss-custom-media/test/true-false.expect.css +++ b/plugins/postcss-custom-media/test/true-false.expect.css @@ -1,16 +1,16 @@ -@media (max-color:2147477350) {@media screen and (max-color:2147477350) { +@media tRUe {@media screen and (max-color:2147477350) { .true { order: 1; } } -}@media not all and (max-color:2147477350) {@media screen and (color:2147477350) { +}@media not tRUe {@media screen and (color:2147477350) { .true { order: 1; } } } -@media (color:2147477350) { +@media fAlsE { @media screen and (max-color:2147477350) { .false { @@ -19,7 +19,7 @@ } } -@media not all and (color:2147477350) { +@media not fAlsE { @media screen and (color:2147477350) { .false { @@ -28,13 +28,13 @@ } } -@media (max-color:2147477350) { +@media tRUe { .true { order: 3; } } -@media (color:2147477350) { +@media fAlsE { .false { order: 4; } diff --git a/rollup/configs/externals.js b/rollup/configs/externals.js index c3a6e57a3..9c0bdea8d 100644 --- a/rollup/configs/externals.js +++ b/rollup/configs/externals.js @@ -6,6 +6,7 @@ export const externalsForCLI = [ '@csstools/css-parser-algorithms', '@csstools/css-tokenizer', + '@csstools/media-query-list-parser', '@csstools/postcss-cascade-layers', '@csstools/postcss-color-function', '@csstools/postcss-font-format-keywords', @@ -73,6 +74,7 @@ export const externalsForPlugin = [ '@csstools/css-parser-algorithms', '@csstools/css-tokenizer', + '@csstools/media-query-list-parser', '@csstools/postcss-cascade-layers', '@csstools/postcss-color-function', '@csstools/postcss-font-format-keywords', From 55673843a178e1a0cb67570730e56bb97f6ac4f8 Mon Sep 17 00:00:00 2001 From: Romain Menke Date: Wed, 26 Oct 2022 10:57:55 +0200 Subject: [PATCH 17/35] lint --- plugins/postcss-custom-media/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/postcss-custom-media/package.json b/plugins/postcss-custom-media/package.json index 161469525..39dc809f5 100644 --- a/plugins/postcss-custom-media/package.json +++ b/plugins/postcss-custom-media/package.json @@ -45,8 +45,8 @@ "dist" ], "dependencies": { - "@csstools/media-query-list-parser": "^1.0.0", - "@csstools/css-tokenizer": "^1.0.0" + "@csstools/css-tokenizer": "^1.0.0", + "@csstools/media-query-list-parser": "^1.0.0" }, "peerDependencies": { "postcss": "^8.4" From 8ebe98821657e17a06d7ae10d47e774c06c1e5a4 Mon Sep 17 00:00:00 2001 From: Romain Menke Date: Wed, 26 Oct 2022 13:08:58 +0200 Subject: [PATCH 18/35] more tests --- .../cases/various/0019.list-comma.expect.json | 1704 +++++++++++++++++ .../cases/various/0019.list-space.expect.json | 1702 ++++++++++++++++ .../test/cases/various/0019.mjs | 13 + packages/css-parser-algorithms/test/test.mjs | 1 + 4 files changed, 3420 insertions(+) create mode 100644 packages/css-parser-algorithms/test/cases/various/0019.list-comma.expect.json create mode 100644 packages/css-parser-algorithms/test/cases/various/0019.list-space.expect.json create mode 100644 packages/css-parser-algorithms/test/cases/various/0019.mjs diff --git a/packages/css-parser-algorithms/test/cases/various/0019.list-comma.expect.json b/packages/css-parser-algorithms/test/cases/various/0019.list-comma.expect.json new file mode 100644 index 000000000..1bd898203 --- /dev/null +++ b/packages/css-parser-algorithms/test/cases/various/0019.list-comma.expect.json @@ -0,0 +1,1704 @@ +[ + [ + { + "type": "simple-block", + "startToken": [ + "(-token", + "(", + 0, + 0, + null + ], + "tokens": [ + [ + "(-token", + "(", + 0, + 0, + null + ], + [ + "whitespace-token", + " ", + 1, + 1, + null + ], + [ + "(-token", + "(", + 2, + 2, + null + ], + [ + "ident-token", + "aa", + 3, + 4, + { + "value": "aa" + } + ], + [ + ")-token", + ")", + 5, + 5, + null + ], + [ + "whitespace-token", + " ", + 6, + 6, + null + ], + [ + "[-token", + "[", + 7, + 7, + null + ], + [ + "ident-token", + "ab", + 8, + 9, + { + "value": "ab" + } + ], + [ + "]-token", + "]", + 10, + 10, + null + ], + [ + "whitespace-token", + " ", + 11, + 11, + null + ], + [ + "{-token", + "{", + 12, + 12, + null + ], + [ + "ident-token", + "ac", + 13, + 14, + { + "value": "ac" + } + ], + [ + "}-token", + "}", + 15, + 15, + null + ], + [ + "whitespace-token", + " ", + 16, + 16, + null + ], + [ + "function-token", + "z(", + 17, + 18, + { + "value": "z" + } + ], + [ + "ident-token", + "ad", + 19, + 20, + { + "value": "ad" + } + ], + [ + ")-token", + ")", + 21, + 21, + null + ], + [ + "whitespace-token", + " ", + 22, + 22, + null + ], + [ + ")-token", + ")", + 23, + 23, + null + ] + ], + "value": [ + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 1, + 1, + null + ] + ] + }, + { + "type": "simple-block", + "startToken": [ + "(-token", + "(", + 2, + 2, + null + ], + "tokens": [ + [ + "(-token", + "(", + 2, + 2, + null + ], + [ + "ident-token", + "aa", + 3, + 4, + { + "value": "aa" + } + ], + [ + ")-token", + ")", + 5, + 5, + null + ] + ], + "value": [ + { + "type": "token", + "tokens": [ + [ + "ident-token", + "aa", + 3, + 4, + { + "value": "aa" + } + ] + ] + } + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 6, + 6, + null + ] + ] + }, + { + "type": "simple-block", + "startToken": [ + "[-token", + "[", + 7, + 7, + null + ], + "tokens": [ + [ + "[-token", + "[", + 7, + 7, + null + ], + [ + "ident-token", + "ab", + 8, + 9, + { + "value": "ab" + } + ], + [ + "]-token", + "]", + 10, + 10, + null + ] + ], + "value": [ + { + "type": "token", + "tokens": [ + [ + "ident-token", + "ab", + 8, + 9, + { + "value": "ab" + } + ] + ] + } + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 11, + 11, + null + ] + ] + }, + { + "type": "simple-block", + "startToken": [ + "{-token", + "{", + 12, + 12, + null + ], + "tokens": [ + [ + "{-token", + "{", + 12, + 12, + null + ], + [ + "ident-token", + "ac", + 13, + 14, + { + "value": "ac" + } + ], + [ + "}-token", + "}", + 15, + 15, + null + ] + ], + "value": [ + { + "type": "token", + "tokens": [ + [ + "ident-token", + "ac", + 13, + 14, + { + "value": "ac" + } + ] + ] + } + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 16, + 16, + null + ] + ] + }, + { + "type": "function", + "name": "z", + "tokens": [ + [ + "function-token", + "z(", + 17, + 18, + { + "value": "z" + } + ], + [ + "ident-token", + "ad", + 19, + 20, + { + "value": "ad" + } + ], + [ + ")-token", + ")", + 21, + 21, + null + ] + ], + "value": [ + { + "type": "token", + "tokens": [ + [ + "ident-token", + "ad", + 19, + 20, + { + "value": "ad" + } + ] + ] + } + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 22, + 22, + null + ] + ] + } + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 24, + 24, + null + ] + ] + }, + { + "type": "simple-block", + "startToken": [ + "[-token", + "[", + 25, + 25, + null + ], + "tokens": [ + [ + "[-token", + "[", + 25, + 25, + null + ], + [ + "whitespace-token", + " ", + 26, + 26, + null + ], + [ + "(-token", + "(", + 27, + 27, + null + ], + [ + "ident-token", + "ba", + 28, + 29, + { + "value": "ba" + } + ], + [ + ")-token", + ")", + 30, + 30, + null + ], + [ + "whitespace-token", + " ", + 31, + 31, + null + ], + [ + "[-token", + "[", + 32, + 32, + null + ], + [ + "ident-token", + "bb", + 33, + 34, + { + "value": "bb" + } + ], + [ + "]-token", + "]", + 35, + 35, + null + ], + [ + "whitespace-token", + " ", + 36, + 36, + null + ], + [ + "{-token", + "{", + 37, + 37, + null + ], + [ + "ident-token", + "bc", + 38, + 39, + { + "value": "bc" + } + ], + [ + "}-token", + "}", + 40, + 40, + null + ], + [ + "whitespace-token", + " ", + 41, + 41, + null + ], + [ + "function-token", + "z(", + 42, + 43, + { + "value": "z" + } + ], + [ + "ident-token", + "bd", + 44, + 45, + { + "value": "bd" + } + ], + [ + ")-token", + ")", + 46, + 46, + null + ], + [ + "whitespace-token", + " ", + 47, + 47, + null + ], + [ + "]-token", + "]", + 48, + 48, + null + ] + ], + "value": [ + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 26, + 26, + null + ] + ] + }, + { + "type": "simple-block", + "startToken": [ + "(-token", + "(", + 27, + 27, + null + ], + "tokens": [ + [ + "(-token", + "(", + 27, + 27, + null + ], + [ + "ident-token", + "ba", + 28, + 29, + { + "value": "ba" + } + ], + [ + ")-token", + ")", + 30, + 30, + null + ] + ], + "value": [ + { + "type": "token", + "tokens": [ + [ + "ident-token", + "ba", + 28, + 29, + { + "value": "ba" + } + ] + ] + } + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 31, + 31, + null + ] + ] + }, + { + "type": "simple-block", + "startToken": [ + "[-token", + "[", + 32, + 32, + null + ], + "tokens": [ + [ + "[-token", + "[", + 32, + 32, + null + ], + [ + "ident-token", + "bb", + 33, + 34, + { + "value": "bb" + } + ], + [ + "]-token", + "]", + 35, + 35, + null + ] + ], + "value": [ + { + "type": "token", + "tokens": [ + [ + "ident-token", + "bb", + 33, + 34, + { + "value": "bb" + } + ] + ] + } + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 36, + 36, + null + ] + ] + }, + { + "type": "simple-block", + "startToken": [ + "{-token", + "{", + 37, + 37, + null + ], + "tokens": [ + [ + "{-token", + "{", + 37, + 37, + null + ], + [ + "ident-token", + "bc", + 38, + 39, + { + "value": "bc" + } + ], + [ + "}-token", + "}", + 40, + 40, + null + ] + ], + "value": [ + { + "type": "token", + "tokens": [ + [ + "ident-token", + "bc", + 38, + 39, + { + "value": "bc" + } + ] + ] + } + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 41, + 41, + null + ] + ] + }, + { + "type": "function", + "name": "z", + "tokens": [ + [ + "function-token", + "z(", + 42, + 43, + { + "value": "z" + } + ], + [ + "ident-token", + "bd", + 44, + 45, + { + "value": "bd" + } + ], + [ + ")-token", + ")", + 46, + 46, + null + ] + ], + "value": [ + { + "type": "token", + "tokens": [ + [ + "ident-token", + "bd", + 44, + 45, + { + "value": "bd" + } + ] + ] + } + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 47, + 47, + null + ] + ] + } + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 49, + 49, + null + ] + ] + }, + { + "type": "simple-block", + "startToken": [ + "{-token", + "{", + 50, + 50, + null + ], + "tokens": [ + [ + "{-token", + "{", + 50, + 50, + null + ], + [ + "whitespace-token", + " ", + 51, + 51, + null + ], + [ + "(-token", + "(", + 52, + 52, + null + ], + [ + "ident-token", + "ca", + 53, + 54, + { + "value": "ca" + } + ], + [ + ")-token", + ")", + 55, + 55, + null + ], + [ + "whitespace-token", + " ", + 56, + 56, + null + ], + [ + "[-token", + "[", + 57, + 57, + null + ], + [ + "ident-token", + "cb", + 58, + 59, + { + "value": "cb" + } + ], + [ + "]-token", + "]", + 60, + 60, + null + ], + [ + "whitespace-token", + " ", + 61, + 61, + null + ], + [ + "{-token", + "{", + 62, + 62, + null + ], + [ + "ident-token", + "cc", + 63, + 64, + { + "value": "cc" + } + ], + [ + "}-token", + "}", + 65, + 65, + null + ], + [ + "whitespace-token", + " ", + 66, + 66, + null + ], + [ + "function-token", + "z(", + 67, + 68, + { + "value": "z" + } + ], + [ + "ident-token", + "cd", + 69, + 70, + { + "value": "cd" + } + ], + [ + ")-token", + ")", + 71, + 71, + null + ], + [ + "whitespace-token", + " ", + 72, + 72, + null + ], + [ + "}-token", + "}", + 73, + 73, + null + ] + ], + "value": [ + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 51, + 51, + null + ] + ] + }, + { + "type": "simple-block", + "startToken": [ + "(-token", + "(", + 52, + 52, + null + ], + "tokens": [ + [ + "(-token", + "(", + 52, + 52, + null + ], + [ + "ident-token", + "ca", + 53, + 54, + { + "value": "ca" + } + ], + [ + ")-token", + ")", + 55, + 55, + null + ] + ], + "value": [ + { + "type": "token", + "tokens": [ + [ + "ident-token", + "ca", + 53, + 54, + { + "value": "ca" + } + ] + ] + } + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 56, + 56, + null + ] + ] + }, + { + "type": "simple-block", + "startToken": [ + "[-token", + "[", + 57, + 57, + null + ], + "tokens": [ + [ + "[-token", + "[", + 57, + 57, + null + ], + [ + "ident-token", + "cb", + 58, + 59, + { + "value": "cb" + } + ], + [ + "]-token", + "]", + 60, + 60, + null + ] + ], + "value": [ + { + "type": "token", + "tokens": [ + [ + "ident-token", + "cb", + 58, + 59, + { + "value": "cb" + } + ] + ] + } + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 61, + 61, + null + ] + ] + }, + { + "type": "simple-block", + "startToken": [ + "{-token", + "{", + 62, + 62, + null + ], + "tokens": [ + [ + "{-token", + "{", + 62, + 62, + null + ], + [ + "ident-token", + "cc", + 63, + 64, + { + "value": "cc" + } + ], + [ + "}-token", + "}", + 65, + 65, + null + ] + ], + "value": [ + { + "type": "token", + "tokens": [ + [ + "ident-token", + "cc", + 63, + 64, + { + "value": "cc" + } + ] + ] + } + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 66, + 66, + null + ] + ] + }, + { + "type": "function", + "name": "z", + "tokens": [ + [ + "function-token", + "z(", + 67, + 68, + { + "value": "z" + } + ], + [ + "ident-token", + "cd", + 69, + 70, + { + "value": "cd" + } + ], + [ + ")-token", + ")", + 71, + 71, + null + ] + ], + "value": [ + { + "type": "token", + "tokens": [ + [ + "ident-token", + "cd", + 69, + 70, + { + "value": "cd" + } + ] + ] + } + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 72, + 72, + null + ] + ] + } + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 74, + 74, + null + ] + ] + }, + { + "type": "function", + "name": "z", + "tokens": [ + [ + "function-token", + "z(", + 75, + 76, + { + "value": "z" + } + ], + [ + "whitespace-token", + " ", + 77, + 77, + null + ], + [ + "(-token", + "(", + 78, + 78, + null + ], + [ + "ident-token", + "da", + 79, + 80, + { + "value": "da" + } + ], + [ + ")-token", + ")", + 81, + 81, + null + ], + [ + "whitespace-token", + " ", + 82, + 82, + null + ], + [ + "[-token", + "[", + 83, + 83, + null + ], + [ + "ident-token", + "db", + 84, + 85, + { + "value": "db" + } + ], + [ + "]-token", + "]", + 86, + 86, + null + ], + [ + "whitespace-token", + " ", + 87, + 87, + null + ], + [ + "{-token", + "{", + 88, + 88, + null + ], + [ + "ident-token", + "dc", + 89, + 90, + { + "value": "dc" + } + ], + [ + "}-token", + "}", + 91, + 91, + null + ], + [ + "whitespace-token", + " ", + 92, + 92, + null + ], + [ + "function-token", + "z(", + 93, + 94, + { + "value": "z" + } + ], + [ + "ident-token", + "dd", + 95, + 96, + { + "value": "dd" + } + ], + [ + ")-token", + ")", + 97, + 97, + null + ], + [ + "whitespace-token", + " ", + 98, + 98, + null + ], + [ + ")-token", + ")", + 99, + 99, + null + ] + ], + "value": [ + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 77, + 77, + null + ] + ] + }, + { + "type": "simple-block", + "startToken": [ + "(-token", + "(", + 78, + 78, + null + ], + "tokens": [ + [ + "(-token", + "(", + 78, + 78, + null + ], + [ + "ident-token", + "da", + 79, + 80, + { + "value": "da" + } + ], + [ + ")-token", + ")", + 81, + 81, + null + ] + ], + "value": [ + { + "type": "token", + "tokens": [ + [ + "ident-token", + "da", + 79, + 80, + { + "value": "da" + } + ] + ] + } + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 82, + 82, + null + ] + ] + }, + { + "type": "simple-block", + "startToken": [ + "[-token", + "[", + 83, + 83, + null + ], + "tokens": [ + [ + "[-token", + "[", + 83, + 83, + null + ], + [ + "ident-token", + "db", + 84, + 85, + { + "value": "db" + } + ], + [ + "]-token", + "]", + 86, + 86, + null + ] + ], + "value": [ + { + "type": "token", + "tokens": [ + [ + "ident-token", + "db", + 84, + 85, + { + "value": "db" + } + ] + ] + } + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 87, + 87, + null + ] + ] + }, + { + "type": "simple-block", + "startToken": [ + "{-token", + "{", + 88, + 88, + null + ], + "tokens": [ + [ + "{-token", + "{", + 88, + 88, + null + ], + [ + "ident-token", + "dc", + 89, + 90, + { + "value": "dc" + } + ], + [ + "}-token", + "}", + 91, + 91, + null + ] + ], + "value": [ + { + "type": "token", + "tokens": [ + [ + "ident-token", + "dc", + 89, + 90, + { + "value": "dc" + } + ] + ] + } + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 92, + 92, + null + ] + ] + }, + { + "type": "function", + "name": "z", + "tokens": [ + [ + "function-token", + "z(", + 93, + 94, + { + "value": "z" + } + ], + [ + "ident-token", + "dd", + 95, + 96, + { + "value": "dd" + } + ], + [ + ")-token", + ")", + 97, + 97, + null + ] + ], + "value": [ + { + "type": "token", + "tokens": [ + [ + "ident-token", + "dd", + 95, + 96, + { + "value": "dd" + } + ] + ] + } + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 98, + 98, + null + ] + ] + } + ] + } + ] +] \ No newline at end of file diff --git a/packages/css-parser-algorithms/test/cases/various/0019.list-space.expect.json b/packages/css-parser-algorithms/test/cases/various/0019.list-space.expect.json new file mode 100644 index 000000000..72ba46a67 --- /dev/null +++ b/packages/css-parser-algorithms/test/cases/various/0019.list-space.expect.json @@ -0,0 +1,1702 @@ +[ + { + "type": "simple-block", + "startToken": [ + "(-token", + "(", + 0, + 0, + null + ], + "tokens": [ + [ + "(-token", + "(", + 0, + 0, + null + ], + [ + "whitespace-token", + " ", + 1, + 1, + null + ], + [ + "(-token", + "(", + 2, + 2, + null + ], + [ + "ident-token", + "aa", + 3, + 4, + { + "value": "aa" + } + ], + [ + ")-token", + ")", + 5, + 5, + null + ], + [ + "whitespace-token", + " ", + 6, + 6, + null + ], + [ + "[-token", + "[", + 7, + 7, + null + ], + [ + "ident-token", + "ab", + 8, + 9, + { + "value": "ab" + } + ], + [ + "]-token", + "]", + 10, + 10, + null + ], + [ + "whitespace-token", + " ", + 11, + 11, + null + ], + [ + "{-token", + "{", + 12, + 12, + null + ], + [ + "ident-token", + "ac", + 13, + 14, + { + "value": "ac" + } + ], + [ + "}-token", + "}", + 15, + 15, + null + ], + [ + "whitespace-token", + " ", + 16, + 16, + null + ], + [ + "function-token", + "z(", + 17, + 18, + { + "value": "z" + } + ], + [ + "ident-token", + "ad", + 19, + 20, + { + "value": "ad" + } + ], + [ + ")-token", + ")", + 21, + 21, + null + ], + [ + "whitespace-token", + " ", + 22, + 22, + null + ], + [ + ")-token", + ")", + 23, + 23, + null + ] + ], + "value": [ + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 1, + 1, + null + ] + ] + }, + { + "type": "simple-block", + "startToken": [ + "(-token", + "(", + 2, + 2, + null + ], + "tokens": [ + [ + "(-token", + "(", + 2, + 2, + null + ], + [ + "ident-token", + "aa", + 3, + 4, + { + "value": "aa" + } + ], + [ + ")-token", + ")", + 5, + 5, + null + ] + ], + "value": [ + { + "type": "token", + "tokens": [ + [ + "ident-token", + "aa", + 3, + 4, + { + "value": "aa" + } + ] + ] + } + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 6, + 6, + null + ] + ] + }, + { + "type": "simple-block", + "startToken": [ + "[-token", + "[", + 7, + 7, + null + ], + "tokens": [ + [ + "[-token", + "[", + 7, + 7, + null + ], + [ + "ident-token", + "ab", + 8, + 9, + { + "value": "ab" + } + ], + [ + "]-token", + "]", + 10, + 10, + null + ] + ], + "value": [ + { + "type": "token", + "tokens": [ + [ + "ident-token", + "ab", + 8, + 9, + { + "value": "ab" + } + ] + ] + } + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 11, + 11, + null + ] + ] + }, + { + "type": "simple-block", + "startToken": [ + "{-token", + "{", + 12, + 12, + null + ], + "tokens": [ + [ + "{-token", + "{", + 12, + 12, + null + ], + [ + "ident-token", + "ac", + 13, + 14, + { + "value": "ac" + } + ], + [ + "}-token", + "}", + 15, + 15, + null + ] + ], + "value": [ + { + "type": "token", + "tokens": [ + [ + "ident-token", + "ac", + 13, + 14, + { + "value": "ac" + } + ] + ] + } + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 16, + 16, + null + ] + ] + }, + { + "type": "function", + "name": "z", + "tokens": [ + [ + "function-token", + "z(", + 17, + 18, + { + "value": "z" + } + ], + [ + "ident-token", + "ad", + 19, + 20, + { + "value": "ad" + } + ], + [ + ")-token", + ")", + 21, + 21, + null + ] + ], + "value": [ + { + "type": "token", + "tokens": [ + [ + "ident-token", + "ad", + 19, + 20, + { + "value": "ad" + } + ] + ] + } + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 22, + 22, + null + ] + ] + } + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 24, + 24, + null + ] + ] + }, + { + "type": "simple-block", + "startToken": [ + "[-token", + "[", + 25, + 25, + null + ], + "tokens": [ + [ + "[-token", + "[", + 25, + 25, + null + ], + [ + "whitespace-token", + " ", + 26, + 26, + null + ], + [ + "(-token", + "(", + 27, + 27, + null + ], + [ + "ident-token", + "ba", + 28, + 29, + { + "value": "ba" + } + ], + [ + ")-token", + ")", + 30, + 30, + null + ], + [ + "whitespace-token", + " ", + 31, + 31, + null + ], + [ + "[-token", + "[", + 32, + 32, + null + ], + [ + "ident-token", + "bb", + 33, + 34, + { + "value": "bb" + } + ], + [ + "]-token", + "]", + 35, + 35, + null + ], + [ + "whitespace-token", + " ", + 36, + 36, + null + ], + [ + "{-token", + "{", + 37, + 37, + null + ], + [ + "ident-token", + "bc", + 38, + 39, + { + "value": "bc" + } + ], + [ + "}-token", + "}", + 40, + 40, + null + ], + [ + "whitespace-token", + " ", + 41, + 41, + null + ], + [ + "function-token", + "z(", + 42, + 43, + { + "value": "z" + } + ], + [ + "ident-token", + "bd", + 44, + 45, + { + "value": "bd" + } + ], + [ + ")-token", + ")", + 46, + 46, + null + ], + [ + "whitespace-token", + " ", + 47, + 47, + null + ], + [ + "]-token", + "]", + 48, + 48, + null + ] + ], + "value": [ + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 26, + 26, + null + ] + ] + }, + { + "type": "simple-block", + "startToken": [ + "(-token", + "(", + 27, + 27, + null + ], + "tokens": [ + [ + "(-token", + "(", + 27, + 27, + null + ], + [ + "ident-token", + "ba", + 28, + 29, + { + "value": "ba" + } + ], + [ + ")-token", + ")", + 30, + 30, + null + ] + ], + "value": [ + { + "type": "token", + "tokens": [ + [ + "ident-token", + "ba", + 28, + 29, + { + "value": "ba" + } + ] + ] + } + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 31, + 31, + null + ] + ] + }, + { + "type": "simple-block", + "startToken": [ + "[-token", + "[", + 32, + 32, + null + ], + "tokens": [ + [ + "[-token", + "[", + 32, + 32, + null + ], + [ + "ident-token", + "bb", + 33, + 34, + { + "value": "bb" + } + ], + [ + "]-token", + "]", + 35, + 35, + null + ] + ], + "value": [ + { + "type": "token", + "tokens": [ + [ + "ident-token", + "bb", + 33, + 34, + { + "value": "bb" + } + ] + ] + } + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 36, + 36, + null + ] + ] + }, + { + "type": "simple-block", + "startToken": [ + "{-token", + "{", + 37, + 37, + null + ], + "tokens": [ + [ + "{-token", + "{", + 37, + 37, + null + ], + [ + "ident-token", + "bc", + 38, + 39, + { + "value": "bc" + } + ], + [ + "}-token", + "}", + 40, + 40, + null + ] + ], + "value": [ + { + "type": "token", + "tokens": [ + [ + "ident-token", + "bc", + 38, + 39, + { + "value": "bc" + } + ] + ] + } + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 41, + 41, + null + ] + ] + }, + { + "type": "function", + "name": "z", + "tokens": [ + [ + "function-token", + "z(", + 42, + 43, + { + "value": "z" + } + ], + [ + "ident-token", + "bd", + 44, + 45, + { + "value": "bd" + } + ], + [ + ")-token", + ")", + 46, + 46, + null + ] + ], + "value": [ + { + "type": "token", + "tokens": [ + [ + "ident-token", + "bd", + 44, + 45, + { + "value": "bd" + } + ] + ] + } + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 47, + 47, + null + ] + ] + } + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 49, + 49, + null + ] + ] + }, + { + "type": "simple-block", + "startToken": [ + "{-token", + "{", + 50, + 50, + null + ], + "tokens": [ + [ + "{-token", + "{", + 50, + 50, + null + ], + [ + "whitespace-token", + " ", + 51, + 51, + null + ], + [ + "(-token", + "(", + 52, + 52, + null + ], + [ + "ident-token", + "ca", + 53, + 54, + { + "value": "ca" + } + ], + [ + ")-token", + ")", + 55, + 55, + null + ], + [ + "whitespace-token", + " ", + 56, + 56, + null + ], + [ + "[-token", + "[", + 57, + 57, + null + ], + [ + "ident-token", + "cb", + 58, + 59, + { + "value": "cb" + } + ], + [ + "]-token", + "]", + 60, + 60, + null + ], + [ + "whitespace-token", + " ", + 61, + 61, + null + ], + [ + "{-token", + "{", + 62, + 62, + null + ], + [ + "ident-token", + "cc", + 63, + 64, + { + "value": "cc" + } + ], + [ + "}-token", + "}", + 65, + 65, + null + ], + [ + "whitespace-token", + " ", + 66, + 66, + null + ], + [ + "function-token", + "z(", + 67, + 68, + { + "value": "z" + } + ], + [ + "ident-token", + "cd", + 69, + 70, + { + "value": "cd" + } + ], + [ + ")-token", + ")", + 71, + 71, + null + ], + [ + "whitespace-token", + " ", + 72, + 72, + null + ], + [ + "}-token", + "}", + 73, + 73, + null + ] + ], + "value": [ + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 51, + 51, + null + ] + ] + }, + { + "type": "simple-block", + "startToken": [ + "(-token", + "(", + 52, + 52, + null + ], + "tokens": [ + [ + "(-token", + "(", + 52, + 52, + null + ], + [ + "ident-token", + "ca", + 53, + 54, + { + "value": "ca" + } + ], + [ + ")-token", + ")", + 55, + 55, + null + ] + ], + "value": [ + { + "type": "token", + "tokens": [ + [ + "ident-token", + "ca", + 53, + 54, + { + "value": "ca" + } + ] + ] + } + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 56, + 56, + null + ] + ] + }, + { + "type": "simple-block", + "startToken": [ + "[-token", + "[", + 57, + 57, + null + ], + "tokens": [ + [ + "[-token", + "[", + 57, + 57, + null + ], + [ + "ident-token", + "cb", + 58, + 59, + { + "value": "cb" + } + ], + [ + "]-token", + "]", + 60, + 60, + null + ] + ], + "value": [ + { + "type": "token", + "tokens": [ + [ + "ident-token", + "cb", + 58, + 59, + { + "value": "cb" + } + ] + ] + } + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 61, + 61, + null + ] + ] + }, + { + "type": "simple-block", + "startToken": [ + "{-token", + "{", + 62, + 62, + null + ], + "tokens": [ + [ + "{-token", + "{", + 62, + 62, + null + ], + [ + "ident-token", + "cc", + 63, + 64, + { + "value": "cc" + } + ], + [ + "}-token", + "}", + 65, + 65, + null + ] + ], + "value": [ + { + "type": "token", + "tokens": [ + [ + "ident-token", + "cc", + 63, + 64, + { + "value": "cc" + } + ] + ] + } + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 66, + 66, + null + ] + ] + }, + { + "type": "function", + "name": "z", + "tokens": [ + [ + "function-token", + "z(", + 67, + 68, + { + "value": "z" + } + ], + [ + "ident-token", + "cd", + 69, + 70, + { + "value": "cd" + } + ], + [ + ")-token", + ")", + 71, + 71, + null + ] + ], + "value": [ + { + "type": "token", + "tokens": [ + [ + "ident-token", + "cd", + 69, + 70, + { + "value": "cd" + } + ] + ] + } + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 72, + 72, + null + ] + ] + } + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 74, + 74, + null + ] + ] + }, + { + "type": "function", + "name": "z", + "tokens": [ + [ + "function-token", + "z(", + 75, + 76, + { + "value": "z" + } + ], + [ + "whitespace-token", + " ", + 77, + 77, + null + ], + [ + "(-token", + "(", + 78, + 78, + null + ], + [ + "ident-token", + "da", + 79, + 80, + { + "value": "da" + } + ], + [ + ")-token", + ")", + 81, + 81, + null + ], + [ + "whitespace-token", + " ", + 82, + 82, + null + ], + [ + "[-token", + "[", + 83, + 83, + null + ], + [ + "ident-token", + "db", + 84, + 85, + { + "value": "db" + } + ], + [ + "]-token", + "]", + 86, + 86, + null + ], + [ + "whitespace-token", + " ", + 87, + 87, + null + ], + [ + "{-token", + "{", + 88, + 88, + null + ], + [ + "ident-token", + "dc", + 89, + 90, + { + "value": "dc" + } + ], + [ + "}-token", + "}", + 91, + 91, + null + ], + [ + "whitespace-token", + " ", + 92, + 92, + null + ], + [ + "function-token", + "z(", + 93, + 94, + { + "value": "z" + } + ], + [ + "ident-token", + "dd", + 95, + 96, + { + "value": "dd" + } + ], + [ + ")-token", + ")", + 97, + 97, + null + ], + [ + "whitespace-token", + " ", + 98, + 98, + null + ], + [ + ")-token", + ")", + 99, + 99, + null + ] + ], + "value": [ + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 77, + 77, + null + ] + ] + }, + { + "type": "simple-block", + "startToken": [ + "(-token", + "(", + 78, + 78, + null + ], + "tokens": [ + [ + "(-token", + "(", + 78, + 78, + null + ], + [ + "ident-token", + "da", + 79, + 80, + { + "value": "da" + } + ], + [ + ")-token", + ")", + 81, + 81, + null + ] + ], + "value": [ + { + "type": "token", + "tokens": [ + [ + "ident-token", + "da", + 79, + 80, + { + "value": "da" + } + ] + ] + } + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 82, + 82, + null + ] + ] + }, + { + "type": "simple-block", + "startToken": [ + "[-token", + "[", + 83, + 83, + null + ], + "tokens": [ + [ + "[-token", + "[", + 83, + 83, + null + ], + [ + "ident-token", + "db", + 84, + 85, + { + "value": "db" + } + ], + [ + "]-token", + "]", + 86, + 86, + null + ] + ], + "value": [ + { + "type": "token", + "tokens": [ + [ + "ident-token", + "db", + 84, + 85, + { + "value": "db" + } + ] + ] + } + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 87, + 87, + null + ] + ] + }, + { + "type": "simple-block", + "startToken": [ + "{-token", + "{", + 88, + 88, + null + ], + "tokens": [ + [ + "{-token", + "{", + 88, + 88, + null + ], + [ + "ident-token", + "dc", + 89, + 90, + { + "value": "dc" + } + ], + [ + "}-token", + "}", + 91, + 91, + null + ] + ], + "value": [ + { + "type": "token", + "tokens": [ + [ + "ident-token", + "dc", + 89, + 90, + { + "value": "dc" + } + ] + ] + } + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 92, + 92, + null + ] + ] + }, + { + "type": "function", + "name": "z", + "tokens": [ + [ + "function-token", + "z(", + 93, + 94, + { + "value": "z" + } + ], + [ + "ident-token", + "dd", + 95, + 96, + { + "value": "dd" + } + ], + [ + ")-token", + ")", + 97, + 97, + null + ] + ], + "value": [ + { + "type": "token", + "tokens": [ + [ + "ident-token", + "dd", + 95, + 96, + { + "value": "dd" + } + ] + ] + } + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 98, + 98, + null + ] + ] + } + ] + } +] \ No newline at end of file diff --git a/packages/css-parser-algorithms/test/cases/various/0019.mjs b/packages/css-parser-algorithms/test/cases/various/0019.mjs new file mode 100644 index 000000000..32c9385e6 --- /dev/null +++ b/packages/css-parser-algorithms/test/cases/various/0019.mjs @@ -0,0 +1,13 @@ +import assert from 'assert'; +import { runTest } from '../../util/run-test.mjs'; + +runTest( + '( (aa) [ab] {ac} z(ad) ) [ (ba) [bb] {bc} z(bd) ] { (ca) [cb] {cc} z(cd) } z( (da) [db] {dc} z(dd) )', + 'various/0019', + (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 4a0b01d36..fa184c135 100644 --- a/packages/css-parser-algorithms/test/test.mjs +++ b/packages/css-parser-algorithms/test/test.mjs @@ -34,3 +34,4 @@ import './cases/various/0015.mjs'; import './cases/various/0016.mjs'; import './cases/various/0017.mjs'; import './cases/various/0018.mjs'; +import './cases/various/0019.mjs'; From 406d5e3801df88626195cd1c82627baf761f0bd4 Mon Sep 17 00:00:00 2001 From: Romain Menke Date: Wed, 26 Oct 2022 13:27:06 +0200 Subject: [PATCH 19/35] wip --- .../test/cases/mf-range/0001.expect.json | 168 ++++++ .../test/cases/mf-range/0001.mjs | 13 + .../test/cases/mf-range/0002.expect.json | 184 +++++++ .../test/cases/mf-range/0002.mjs | 13 + .../test/cases/mf-range/0003.expect.json | 480 ++++++++++++++++++ .../test/cases/mf-range/0003.mjs | 13 + .../test/cases/mf-range/0004.expect.json | 298 +++++++++++ .../test/cases/mf-range/0004.mjs | 13 + .../test/cases/mf-range/0005.expect.json | 137 +++++ .../test/cases/mf-range/0005.mjs | 13 + .../test/cases/mf-range/0006.expect.json | 137 +++++ .../test/cases/mf-range/0006.mjs | 13 + .../test/cases/mf-range/0007.expect.json | 222 ++++++++ .../test/cases/mf-range/0007.mjs | 13 + .../media-query-list-parser/test/test.mjs | 8 + 15 files changed, 1725 insertions(+) create mode 100644 packages/media-query-list-parser/test/cases/mf-range/0001.expect.json create mode 100644 packages/media-query-list-parser/test/cases/mf-range/0001.mjs create mode 100644 packages/media-query-list-parser/test/cases/mf-range/0002.expect.json create mode 100644 packages/media-query-list-parser/test/cases/mf-range/0002.mjs create mode 100644 packages/media-query-list-parser/test/cases/mf-range/0003.expect.json create mode 100644 packages/media-query-list-parser/test/cases/mf-range/0003.mjs create mode 100644 packages/media-query-list-parser/test/cases/mf-range/0004.expect.json create mode 100644 packages/media-query-list-parser/test/cases/mf-range/0004.mjs create mode 100644 packages/media-query-list-parser/test/cases/mf-range/0005.expect.json create mode 100644 packages/media-query-list-parser/test/cases/mf-range/0005.mjs create mode 100644 packages/media-query-list-parser/test/cases/mf-range/0006.expect.json create mode 100644 packages/media-query-list-parser/test/cases/mf-range/0006.mjs create mode 100644 packages/media-query-list-parser/test/cases/mf-range/0007.expect.json create mode 100644 packages/media-query-list-parser/test/cases/mf-range/0007.mjs diff --git a/packages/media-query-list-parser/test/cases/mf-range/0001.expect.json b/packages/media-query-list-parser/test/cases/mf-range/0001.expect.json new file mode 100644 index 000000000..28cb45f06 --- /dev/null +++ b/packages/media-query-list-parser/test/cases/mf-range/0001.expect.json @@ -0,0 +1,168 @@ +[ + { + "type": "media-query-without-type", + "string": "(20px < width <= calc(20px * 5))", + "media": { + "type": "media-condition", + "media": { + "type": "media-in-parens", + "media": { + "type": "general-enclosed", + "tokens": [ + [ + "(-token", + "(", + 0, + 0, + null + ], + [ + "dimension-token", + "20px", + 1, + 4, + { + "value": 20, + "type": "integer", + "unit": "px" + } + ], + [ + "whitespace-token", + " ", + 5, + 5, + null + ], + [ + "delim-token", + "<", + 6, + 6, + { + "value": "<" + } + ], + [ + "whitespace-token", + " ", + 7, + 7, + null + ], + [ + "ident-token", + "width", + 8, + 12, + { + "value": "width" + } + ], + [ + "whitespace-token", + " ", + 13, + 13, + null + ], + [ + "delim-token", + "<", + 14, + 14, + { + "value": "<" + } + ], + [ + "delim-token", + "=", + 15, + 15, + { + "value": "=" + } + ], + [ + "whitespace-token", + " ", + 16, + 16, + null + ], + [ + "function-token", + "calc(", + 17, + 21, + { + "value": "calc" + } + ], + [ + "dimension-token", + "20px", + 22, + 25, + { + "value": 20, + "type": "integer", + "unit": "px" + } + ], + [ + "whitespace-token", + " ", + 26, + 26, + null + ], + [ + "delim-token", + "*", + 27, + 27, + { + "value": "*" + } + ], + [ + "whitespace-token", + " ", + 28, + 28, + null + ], + [ + "number-token", + "5", + 29, + 29, + { + "value": 5, + "type": "integer" + } + ], + [ + ")-token", + ")", + 30, + 30, + null + ], + [ + ")-token", + ")", + 31, + 31, + null + ] + ] + }, + "before": [], + "after": [] + } + } + } +] \ No newline at end of file diff --git a/packages/media-query-list-parser/test/cases/mf-range/0001.mjs b/packages/media-query-list-parser/test/cases/mf-range/0001.mjs new file mode 100644 index 000000000..37aba33f4 --- /dev/null +++ b/packages/media-query-list-parser/test/cases/mf-range/0001.mjs @@ -0,0 +1,13 @@ +import assert from 'assert'; +import { runTest } from '../../util/run-test.mjs'; + +runTest( + '(20px < width <= calc(20px * 5))', + 'mf-range/0001', + (actual, expected) => { + assert.deepStrictEqual( + actual, + expected, + ); + }, +); diff --git a/packages/media-query-list-parser/test/cases/mf-range/0002.expect.json b/packages/media-query-list-parser/test/cases/mf-range/0002.expect.json new file mode 100644 index 000000000..b201baf5c --- /dev/null +++ b/packages/media-query-list-parser/test/cases/mf-range/0002.expect.json @@ -0,0 +1,184 @@ +[ + { + "type": "media-query-without-type", + "string": "/* comment 1 */(/* comment 2 */30px/* comment 3 */ { + assert.deepStrictEqual( + actual, + expected, + ); + }, +); diff --git a/packages/media-query-list-parser/test/cases/mf-range/0003.expect.json b/packages/media-query-list-parser/test/cases/mf-range/0003.expect.json new file mode 100644 index 000000000..ef7913f76 --- /dev/null +++ b/packages/media-query-list-parser/test/cases/mf-range/0003.expect.json @@ -0,0 +1,480 @@ +[ + { + "type": "media-query-without-type", + "string": "( 1 / 5 < aspect-ratio < 3 / 2 )", + "media": { + "type": "media-condition", + "media": { + "type": "media-in-parens", + "media": { + "type": "media-feature", + "feature": { + "type": "mf-range-value-name-value", + "name": { + "type": "mf-name", + "name": "aspect-ratio", + "tokens": [ + [ + "whitespace-token", + " ", + 9, + 9, + null + ], + [ + "ident-token", + "aspect-ratio", + 10, + 21, + { + "value": "aspect-ratio" + } + ], + [ + "whitespace-token", + " ", + 22, + 22, + null + ] + ] + }, + "valueOne": { + "type": "mf-value", + "value": [ + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 1, + 1, + null + ] + ] + }, + { + "type": "token", + "tokens": [ + [ + "number-token", + "1", + 2, + 2, + { + "value": 1, + "type": "integer" + } + ] + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 3, + 3, + null + ] + ] + }, + { + "type": "token", + "tokens": [ + [ + "delim-token", + "/", + 4, + 4, + { + "value": "/" + } + ] + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 5, + 5, + null + ] + ] + } + ], + "tokens": [ + [ + "whitespace-token", + " ", + 1, + 1, + null + ], + [ + "number-token", + "1", + 2, + 2, + { + "value": 1, + "type": "integer" + } + ], + [ + "whitespace-token", + " ", + 3, + 3, + null + ], + [ + "delim-token", + "/", + 4, + 4, + { + "value": "/" + } + ], + [ + "whitespace-token", + " ", + 5, + 5, + null + ], + [ + "number-token", + "5", + 6, + 6, + { + "value": 5, + "type": "integer" + } + ], + [ + "whitespace-token", + " ", + 7, + 7, + null + ] + ] + }, + "valueTwo": { + "type": "mf-value", + "value": [ + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 24, + 24, + null + ] + ] + }, + { + "type": "token", + "tokens": [ + [ + "number-token", + "3", + 25, + 25, + { + "value": 3, + "type": "integer" + } + ] + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 26, + 26, + null + ] + ] + }, + { + "type": "token", + "tokens": [ + [ + "delim-token", + "/", + 27, + 27, + { + "value": "/" + } + ] + ] + }, + { + "type": "whitespace", + "tokens": [ + [ + "whitespace-token", + " ", + 28, + 28, + null + ] + ] + } + ], + "tokens": [ + [ + "whitespace-token", + " ", + 24, + 24, + null + ], + [ + "number-token", + "3", + 25, + 25, + { + "value": 3, + "type": "integer" + } + ], + [ + "whitespace-token", + " ", + 26, + 26, + null + ], + [ + "delim-token", + "/", + 27, + 27, + { + "value": "/" + } + ], + [ + "whitespace-token", + " ", + 28, + 28, + null + ], + [ + "number-token", + "2", + 29, + 29, + { + "value": 2, + "type": "integer" + } + ], + [ + "whitespace-token", + " ", + 30, + 30, + null + ] + ] + }, + "tokens": [ + [ + "whitespace-token", + " ", + 1, + 1, + null + ], + [ + "number-token", + "1", + 2, + 2, + { + "value": 1, + "type": "integer" + } + ], + [ + "whitespace-token", + " ", + 3, + 3, + null + ], + [ + "delim-token", + "/", + 4, + 4, + { + "value": "/" + } + ], + [ + "whitespace-token", + " ", + 5, + 5, + null + ], + [ + "number-token", + "5", + 6, + 6, + { + "value": 5, + "type": "integer" + } + ], + [ + "whitespace-token", + " ", + 7, + 7, + null + ], + [ + "delim-token", + "<", + 8, + 8, + { + "value": "<" + } + ], + [ + "whitespace-token", + " ", + 9, + 9, + null + ], + [ + "ident-token", + "aspect-ratio", + 10, + 21, + { + "value": "aspect-ratio" + } + ], + [ + "whitespace-token", + " ", + 22, + 22, + null + ], + [ + "delim-token", + "<", + 23, + 23, + { + "value": "<" + } + ], + [ + "whitespace-token", + " ", + 24, + 24, + null + ], + [ + "number-token", + "3", + 25, + 25, + { + "value": 3, + "type": "integer" + } + ], + [ + "whitespace-token", + " ", + 26, + 26, + null + ], + [ + "delim-token", + "/", + 27, + 27, + { + "value": "/" + } + ], + [ + "whitespace-token", + " ", + 28, + 28, + null + ], + [ + "number-token", + "2", + 29, + 29, + { + "value": 2, + "type": "integer" + } + ], + [ + "whitespace-token", + " ", + 30, + 30, + null + ] + ] + } + }, + "before": [ + [ + "(-token", + "(", + 0, + 0, + null + ] + ], + "after": [ + [ + ")-token", + ")", + 31, + 31, + null + ] + ] + } + } + } +] \ No newline at end of file diff --git a/packages/media-query-list-parser/test/cases/mf-range/0003.mjs b/packages/media-query-list-parser/test/cases/mf-range/0003.mjs new file mode 100644 index 000000000..5626fc00a --- /dev/null +++ b/packages/media-query-list-parser/test/cases/mf-range/0003.mjs @@ -0,0 +1,13 @@ +import assert from 'assert'; +import { runTest } from '../../util/run-test.mjs'; + +runTest( + '( 1 / 5 < aspect-ratio < 3 / 2 )', + 'mf-range/0003', + (actual, expected) => { + assert.deepStrictEqual( + actual, + expected, + ); + }, +); diff --git a/packages/media-query-list-parser/test/cases/mf-range/0004.expect.json b/packages/media-query-list-parser/test/cases/mf-range/0004.expect.json new file mode 100644 index 000000000..53882e943 --- /dev/null +++ b/packages/media-query-list-parser/test/cases/mf-range/0004.expect.json @@ -0,0 +1,298 @@ +[ + { + "type": "media-query-without-type", + "string": "(1/5<3/2)", + "media": { + "type": "media-condition", + "media": { + "type": "media-in-parens", + "media": { + "type": "media-feature", + "feature": { + "type": "mf-range-value-name-value", + "name": { + "type": "mf-name", + "name": "aspect-ratio", + "tokens": [ + [ + "ident-token", + "aspect-ratio", + 5, + 16, + { + "value": "aspect-ratio" + } + ] + ] + }, + "valueOne": { + "type": "mf-value", + "value": [ + { + "type": "token", + "tokens": [ + [ + "number-token", + "1", + 1, + 1, + { + "value": 1, + "type": "integer" + } + ] + ] + }, + { + "type": "token", + "tokens": [ + [ + "delim-token", + "/", + 2, + 2, + { + "value": "/" + } + ] + ] + }, + { + "type": "token", + "tokens": [ + [ + "number-token", + "5", + 3, + 3, + { + "value": 5, + "type": "integer" + } + ] + ] + } + ], + "tokens": [ + [ + "number-token", + "1", + 1, + 1, + { + "value": 1, + "type": "integer" + } + ], + [ + "delim-token", + "/", + 2, + 2, + { + "value": "/" + } + ], + [ + "number-token", + "5", + 3, + 3, + { + "value": 5, + "type": "integer" + } + ] + ] + }, + "valueTwo": { + "type": "mf-value", + "value": [ + { + "type": "token", + "tokens": [ + [ + "number-token", + "3", + 18, + 18, + { + "value": 3, + "type": "integer" + } + ] + ] + }, + { + "type": "token", + "tokens": [ + [ + "delim-token", + "/", + 19, + 19, + { + "value": "/" + } + ] + ] + }, + { + "type": "token", + "tokens": [ + [ + "number-token", + "2", + 20, + 20, + { + "value": 2, + "type": "integer" + } + ] + ] + } + ], + "tokens": [ + [ + "number-token", + "3", + 18, + 18, + { + "value": 3, + "type": "integer" + } + ], + [ + "delim-token", + "/", + 19, + 19, + { + "value": "/" + } + ], + [ + "number-token", + "2", + 20, + 20, + { + "value": 2, + "type": "integer" + } + ] + ] + }, + "tokens": [ + [ + "number-token", + "1", + 1, + 1, + { + "value": 1, + "type": "integer" + } + ], + [ + "delim-token", + "/", + 2, + 2, + { + "value": "/" + } + ], + [ + "number-token", + "5", + 3, + 3, + { + "value": 5, + "type": "integer" + } + ], + [ + "delim-token", + "<", + 4, + 4, + { + "value": "<" + } + ], + [ + "ident-token", + "aspect-ratio", + 5, + 16, + { + "value": "aspect-ratio" + } + ], + [ + "delim-token", + "<", + 17, + 17, + { + "value": "<" + } + ], + [ + "number-token", + "3", + 18, + 18, + { + "value": 3, + "type": "integer" + } + ], + [ + "delim-token", + "/", + 19, + 19, + { + "value": "/" + } + ], + [ + "number-token", + "2", + 20, + 20, + { + "value": 2, + "type": "integer" + } + ] + ] + } + }, + "before": [ + [ + "(-token", + "(", + 0, + 0, + null + ] + ], + "after": [ + [ + ")-token", + ")", + 21, + 21, + null + ] + ] + } + } + } +] \ No newline at end of file diff --git a/packages/media-query-list-parser/test/cases/mf-range/0004.mjs b/packages/media-query-list-parser/test/cases/mf-range/0004.mjs new file mode 100644 index 000000000..3863dfccc --- /dev/null +++ b/packages/media-query-list-parser/test/cases/mf-range/0004.mjs @@ -0,0 +1,13 @@ +import assert from 'assert'; +import { runTest } from '../../util/run-test.mjs'; + +runTest( + '(1/5<3/2)', + 'mf-range/0004', + (actual, expected) => { + assert.deepStrictEqual( + actual, + expected, + ); + }, +); diff --git a/packages/media-query-list-parser/test/cases/mf-range/0005.expect.json b/packages/media-query-list-parser/test/cases/mf-range/0005.expect.json new file mode 100644 index 000000000..5cf47e681 --- /dev/null +++ b/packages/media-query-list-parser/test/cases/mf-range/0005.expect.json @@ -0,0 +1,137 @@ +[ + { + "type": "media-query-without-type", + "string": "(widthundefined= 50px)", + "media": { + "type": "media-condition", + "media": { + "type": "media-in-parens", + "media": { + "type": "media-feature", + "feature": { + "type": "mf-range-name-value", + "name": { + "type": "mf-name", + "name": "width", + "tokens": [ + [ + "ident-token", + "width", + 1, + 5, + { + "value": "width" + } + ] + ] + }, + "value": { + "type": "mf-value", + "value": { + "type": "token", + "tokens": [ + [ + "dimension-token", + "50px", + 9, + 12, + { + "value": 50, + "type": "integer", + "unit": "px" + } + ] + ] + }, + "tokens": [ + [ + "whitespace-token", + " ", + 8, + 8, + null + ], + [ + "dimension-token", + "50px", + 9, + 12, + { + "value": 50, + "type": "integer", + "unit": "px" + } + ] + ] + }, + "tokens": [ + [ + "ident-token", + "width", + 1, + 5, + { + "value": "width" + } + ], + [ + [ + "whitespace-token", + " ", + 6, + 6, + null + ] + ], + [ + "delim-token", + "=", + 7, + 7, + { + "value": "=" + } + ], + [ + "whitespace-token", + " ", + 8, + 8, + null + ], + [ + "dimension-token", + "50px", + 9, + 12, + { + "value": 50, + "type": "integer", + "unit": "px" + } + ] + ] + } + }, + "before": [ + [ + "(-token", + "(", + 0, + 0, + null + ] + ], + "after": [ + [ + ")-token", + ")", + 13, + 13, + null + ] + ] + } + } + } +] \ No newline at end of file diff --git a/packages/media-query-list-parser/test/cases/mf-range/0005.mjs b/packages/media-query-list-parser/test/cases/mf-range/0005.mjs new file mode 100644 index 000000000..fbaf31925 --- /dev/null +++ b/packages/media-query-list-parser/test/cases/mf-range/0005.mjs @@ -0,0 +1,13 @@ +import assert from 'assert'; +import { runTest } from '../../util/run-test.mjs'; + +runTest( + '(width = 50px)', + 'mf-range/0005', + (actual, expected) => { + assert.deepStrictEqual( + actual, + expected, + ); + }, +); diff --git a/packages/media-query-list-parser/test/cases/mf-range/0006.expect.json b/packages/media-query-list-parser/test/cases/mf-range/0006.expect.json new file mode 100644 index 000000000..bcb38e6e5 --- /dev/null +++ b/packages/media-query-list-parser/test/cases/mf-range/0006.expect.json @@ -0,0 +1,137 @@ +[ + { + "type": "media-query-without-type", + "string": "(50pxundefined= width)", + "media": { + "type": "media-condition", + "media": { + "type": "media-in-parens", + "media": { + "type": "media-feature", + "feature": { + "type": "mf-range-value-name", + "name": { + "type": "mf-name", + "name": "width", + "tokens": [ + [ + "whitespace-token", + " ", + 7, + 7, + null + ], + [ + "ident-token", + "width", + 8, + 12, + { + "value": "width" + } + ] + ] + }, + "value": { + "type": "mf-value", + "value": { + "type": "token", + "tokens": [ + [ + "dimension-token", + "50px", + 1, + 4, + { + "value": 50, + "type": "integer", + "unit": "px" + } + ] + ] + }, + "tokens": [ + [ + "dimension-token", + "50px", + 1, + 4, + { + "value": 50, + "type": "integer", + "unit": "px" + } + ] + ] + }, + "tokens": [ + [ + "dimension-token", + "50px", + 1, + 4, + { + "value": 50, + "type": "integer", + "unit": "px" + } + ], + [ + [ + "whitespace-token", + " ", + 5, + 5, + null + ] + ], + [ + "delim-token", + "=", + 6, + 6, + { + "value": "=" + } + ], + [ + "whitespace-token", + " ", + 7, + 7, + null + ], + [ + "ident-token", + "width", + 8, + 12, + { + "value": "width" + } + ] + ] + } + }, + "before": [ + [ + "(-token", + "(", + 0, + 0, + null + ] + ], + "after": [ + [ + ")-token", + ")", + 13, + 13, + null + ] + ] + } + } + } +] \ No newline at end of file diff --git a/packages/media-query-list-parser/test/cases/mf-range/0006.mjs b/packages/media-query-list-parser/test/cases/mf-range/0006.mjs new file mode 100644 index 000000000..cc1c77c20 --- /dev/null +++ b/packages/media-query-list-parser/test/cases/mf-range/0006.mjs @@ -0,0 +1,13 @@ +import assert from 'assert'; +import { runTest } from '../../util/run-test.mjs'; + +runTest( + '(50px = width)', + 'mf-range/0006', + (actual, expected) => { + assert.deepStrictEqual( + actual, + expected, + ); + }, +); diff --git a/packages/media-query-list-parser/test/cases/mf-range/0007.expect.json b/packages/media-query-list-parser/test/cases/mf-range/0007.expect.json new file mode 100644 index 000000000..5610f771b --- /dev/null +++ b/packages/media-query-list-parser/test/cases/mf-range/0007.expect.json @@ -0,0 +1,222 @@ +[ + { + "type": "media-query-without-type", + "string": "(1000px > height > 100px)", + "media": { + "type": "media-condition", + "media": { + "type": "media-in-parens", + "media": { + "type": "media-feature", + "feature": { + "type": "mf-range-value-name-value", + "name": { + "type": "mf-name", + "name": "height", + "tokens": [ + [ + "whitespace-token", + " ", + 9, + 9, + null + ], + [ + "ident-token", + "height", + 10, + 15, + { + "value": "height" + } + ], + [ + "whitespace-token", + " ", + 16, + 16, + null + ] + ] + }, + "valueOne": { + "type": "mf-value", + "value": { + "type": "token", + "tokens": [ + [ + "dimension-token", + "1000px", + 1, + 6, + { + "value": 1000, + "type": "integer", + "unit": "px" + } + ] + ] + }, + "tokens": [ + [ + "dimension-token", + "1000px", + 1, + 6, + { + "value": 1000, + "type": "integer", + "unit": "px" + } + ], + [ + "whitespace-token", + " ", + 7, + 7, + null + ] + ] + }, + "valueTwo": { + "type": "mf-value", + "value": { + "type": "token", + "tokens": [ + [ + "dimension-token", + "100px", + 19, + 23, + { + "value": 100, + "type": "integer", + "unit": "px" + } + ] + ] + }, + "tokens": [ + [ + "whitespace-token", + " ", + 18, + 18, + null + ], + [ + "dimension-token", + "100px", + 19, + 23, + { + "value": 100, + "type": "integer", + "unit": "px" + } + ] + ] + }, + "tokens": [ + [ + "dimension-token", + "1000px", + 1, + 6, + { + "value": 1000, + "type": "integer", + "unit": "px" + } + ], + [ + "whitespace-token", + " ", + 7, + 7, + null + ], + [ + "delim-token", + ">", + 8, + 8, + { + "value": ">" + } + ], + [ + "whitespace-token", + " ", + 9, + 9, + null + ], + [ + "ident-token", + "height", + 10, + 15, + { + "value": "height" + } + ], + [ + "whitespace-token", + " ", + 16, + 16, + null + ], + [ + "delim-token", + ">", + 17, + 17, + { + "value": ">" + } + ], + [ + "whitespace-token", + " ", + 18, + 18, + null + ], + [ + "dimension-token", + "100px", + 19, + 23, + { + "value": 100, + "type": "integer", + "unit": "px" + } + ] + ] + } + }, + "before": [ + [ + "(-token", + "(", + 0, + 0, + null + ] + ], + "after": [ + [ + ")-token", + ")", + 24, + 24, + null + ] + ] + } + } + } +] \ No newline at end of file diff --git a/packages/media-query-list-parser/test/cases/mf-range/0007.mjs b/packages/media-query-list-parser/test/cases/mf-range/0007.mjs new file mode 100644 index 000000000..0db8883c7 --- /dev/null +++ b/packages/media-query-list-parser/test/cases/mf-range/0007.mjs @@ -0,0 +1,13 @@ +import assert from 'assert'; +import { runTest } from '../../util/run-test.mjs'; + +runTest( + '(1000px > height > 100px)', + 'mf-range/0007', + (actual, expected) => { + assert.deepStrictEqual( + actual, + expected, + ); + }, +); diff --git a/packages/media-query-list-parser/test/test.mjs b/packages/media-query-list-parser/test/test.mjs index c219a4ac6..b9fdee14c 100644 --- a/packages/media-query-list-parser/test/test.mjs +++ b/packages/media-query-list-parser/test/test.mjs @@ -12,6 +12,14 @@ import './cases/mf-plain/0003.mjs'; import './cases/mf-plain/0004.mjs'; import './cases/mf-plain/0005.mjs'; +import './cases/mf-range/0001.mjs'; +import './cases/mf-range/0002.mjs'; +import './cases/mf-range/0003.mjs'; +import './cases/mf-range/0004.mjs'; +import './cases/mf-range/0005.mjs'; +import './cases/mf-range/0006.mjs'; +import './cases/mf-range/0007.mjs'; + import './cases/query-with-type/0001.mjs'; import './cases/query-with-type/0002.mjs'; import './cases/query-with-type/0003.mjs'; From 92dd445ab7336abcee9a79be64b27a8435c5b7d2 Mon Sep 17 00:00:00 2001 From: Romain Menke Date: Wed, 26 Oct 2022 13:39:54 +0200 Subject: [PATCH 20/35] fix range parsing --- .../src/nodes/media-feature-value.ts | 4 +- .../test/cases/mf-range/0003.expect.json | 54 ++++++++++--------- 2 files changed, 32 insertions(+), 26 deletions(-) diff --git a/packages/media-query-list-parser/src/nodes/media-feature-value.ts b/packages/media-query-list-parser/src/nodes/media-feature-value.ts index fa888f95a..d19848192 100644 --- a/packages/media-query-list-parser/src/nodes/media-feature-value.ts +++ b/packages/media-query-list-parser/src/nodes/media-feature-value.ts @@ -110,8 +110,8 @@ export function parseMediaFeatureValue(componentValues: Array) { if (isNumber(componentValue)) { const maybeRatio = matchesRatioExactly(componentValues.slice(i)); if (maybeRatio !== -1) { - candidateIndexStart = maybeRatio[0]; - candidateIndexEnd = maybeRatio[1]; + candidateIndexStart = maybeRatio[0] + i; + candidateIndexEnd = maybeRatio[1] + i; i += maybeRatio[1] - maybeRatio[0]; continue; } diff --git a/packages/media-query-list-parser/test/cases/mf-range/0003.expect.json b/packages/media-query-list-parser/test/cases/mf-range/0003.expect.json index ef7913f76..9fb0947b6 100644 --- a/packages/media-query-list-parser/test/cases/mf-range/0003.expect.json +++ b/packages/media-query-list-parser/test/cases/mf-range/0003.expect.json @@ -42,18 +42,6 @@ "valueOne": { "type": "mf-value", "value": [ - { - "type": "whitespace", - "tokens": [ - [ - "whitespace-token", - " ", - 1, - 1, - null - ] - ] - }, { "type": "token", "tokens": [ @@ -106,6 +94,21 @@ null ] ] + }, + { + "type": "token", + "tokens": [ + [ + "number-token", + "5", + 6, + 6, + { + "value": 5, + "type": "integer" + } + ] + ] } ], "tokens": [ @@ -171,18 +174,6 @@ "valueTwo": { "type": "mf-value", "value": [ - { - "type": "whitespace", - "tokens": [ - [ - "whitespace-token", - " ", - 24, - 24, - null - ] - ] - }, { "type": "token", "tokens": [ @@ -235,6 +226,21 @@ null ] ] + }, + { + "type": "token", + "tokens": [ + [ + "number-token", + "2", + 29, + 29, + { + "value": 2, + "type": "integer" + } + ] + ] } ], "tokens": [ From 757bd0d4421825857a383cefd6ec00a977b4acb5 Mon Sep 17 00:00:00 2001 From: Romain Menke Date: Wed, 26 Oct 2022 13:59:29 +0200 Subject: [PATCH 21/35] docs --- packages/css-parser-algorithms/README.md | 2 +- .../consume-component-block-function.ts | 4 +- packages/media-query-list-parser/README.md | 61 ++++++++++++++++++- .../src/nodes/general-enclosed.ts | 2 +- .../src/nodes/media-and.ts | 2 +- .../src/nodes/media-condition-list.ts | 4 +- .../src/nodes/media-condition.ts | 2 +- .../src/nodes/media-feature-plain.ts | 2 +- .../src/nodes/media-feature-range.ts | 6 +- .../src/nodes/media-feature-value.ts | 2 +- .../src/nodes/media-feature.ts | 2 +- .../src/nodes/media-in-parens.ts | 2 +- .../src/nodes/media-not.ts | 2 +- .../src/nodes/media-or.ts | 2 +- .../src/nodes/media-query.ts | 4 +- 15 files changed, 79 insertions(+), 20 deletions(-) diff --git a/packages/css-parser-algorithms/README.md b/packages/css-parser-algorithms/README.md index ff2753b4d..1435d2fbf 100644 --- a/packages/css-parser-algorithms/README.md +++ b/packages/css-parser-algorithms/README.md @@ -99,4 +99,4 @@ What it is not: [discord]: https://discord.gg/bUadyRwkJS [npm-url]: https://www.npmjs.com/package/@csstools/css-parser-algorithms -[CSS Parser Algorithms]: https://github.com/csstools/postcss-plugins/tree/main/packages/css-parser +[CSS Parser Algorithms]: https://github.com/csstools/postcss-plugins/tree/main/packages/css-parser-algorithms diff --git a/packages/css-parser-algorithms/src/consume/consume-component-block-function.ts b/packages/css-parser-algorithms/src/consume/consume-component-block-function.ts index ccad1b119..351bb1546 100644 --- a/packages/css-parser-algorithms/src/consume/consume-component-block-function.ts +++ b/packages/css-parser-algorithms/src/consume/consume-component-block-function.ts @@ -121,7 +121,7 @@ export class FunctionNode { } } - walk(cb: (entry: { node: ComponentValue, parent: ContainerNode }, index: number | string) => boolean) { + walk(cb: (entry: { node: ComponentValue, parent: ContainerNode }, index: number | string) => boolean | void) { let aborted = false; this.value.forEach((child, index) => { @@ -255,7 +255,7 @@ export class SimpleBlockNode { } } - walk(cb: (entry: { node: ComponentValue, parent: ContainerNode }, index: number | string) => boolean) { + walk(cb: (entry: { node: ComponentValue, parent: ContainerNode }, index: number | string) => boolean | void) { let aborted = false; this.value.forEach((child, index) => { diff --git a/packages/media-query-list-parser/README.md b/packages/media-query-list-parser/README.md index 361096774..4f17a86b8 100644 --- a/packages/media-query-list-parser/README.md +++ b/packages/media-query-list-parser/README.md @@ -1 +1,60 @@ -# TODO +# Media Query List Parser + +[npm version][npm-url] +[Build Status][cli-url] +[Discord][discord] + +Implemented from : https://www.w3.org/TR/mediaqueries-5/ + +## Usage + +Add [Media Query List Parser] to your project: + +```bash +npm install postcss @csstools/media-query-list-parser --save-dev +``` + +```ts +import { parse } from '@csstools/media-query-list-parser'; + +export function parseCustomMedia() { + const mediaQueryList = parse('screen and (min-width: 300px), (50px < height < 30vw)'); + + mediaQueryList.forEach((mediaQuery) => { + mediaQuery.walk((entry, index) => { + // Index of the current Node in `parent`. + console.log(index); + // Type of `parent`. + console.log(entry.parent.type); + + // Type of `node` + { + // Sometimes nodes can be arrays. + if (Array.isArray(entry.node)) { + entry.node.forEach((item) => { + console.log(item.type); + }); + } + + if ('type' in entry.node) { + console.log(entry.node.type); + } + } + + // stringified version of the current node. + console.log(entry.node.toString()); + + // Return `false` to stop the walker. + return false; + }); + }); +} +``` + +[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/media-query-list-parser + +[Media Query List Parser]: https://github.com/csstools/postcss-plugins/tree/main/packages/media-query-list-parser + + diff --git a/packages/media-query-list-parser/src/nodes/general-enclosed.ts b/packages/media-query-list-parser/src/nodes/general-enclosed.ts index 230e4b0cd..ebec87307 100644 --- a/packages/media-query-list-parser/src/nodes/general-enclosed.ts +++ b/packages/media-query-list-parser/src/nodes/general-enclosed.ts @@ -32,7 +32,7 @@ export class GeneralEnclosed { } } - walk(cb: (entry: { node: GeneralEnclosedWalkerEntry, parent: GeneralEnclosedWalkerParent }, index: number | string) => boolean) { + walk(cb: (entry: { node: GeneralEnclosedWalkerEntry, parent: GeneralEnclosedWalkerParent }, index: number | string) => boolean | void) { if (cb({ node: this.value, parent: this }, 'value') === false) { return false; } diff --git a/packages/media-query-list-parser/src/nodes/media-and.ts b/packages/media-query-list-parser/src/nodes/media-and.ts index 1a301366b..e20197034 100644 --- a/packages/media-query-list-parser/src/nodes/media-and.ts +++ b/packages/media-query-list-parser/src/nodes/media-and.ts @@ -38,7 +38,7 @@ export class MediaAnd { } } - walk(cb: (entry: { node: MediaAndWalkerEntry, parent: MediaAndWalkerParent }, index: number | string) => boolean) { + walk(cb: (entry: { node: MediaAndWalkerEntry, parent: MediaAndWalkerParent }, index: number | string) => boolean | void) { if (cb({ node: this.media, parent: this }, 'media') === false) { return false; } diff --git a/packages/media-query-list-parser/src/nodes/media-condition-list.ts b/packages/media-query-list-parser/src/nodes/media-condition-list.ts index 427826486..733760ff3 100644 --- a/packages/media-query-list-parser/src/nodes/media-condition-list.ts +++ b/packages/media-query-list-parser/src/nodes/media-condition-list.ts @@ -59,7 +59,7 @@ export class MediaConditionListWithAnd { } } - walk(cb: (entry: { node: MediaConditionListWithAndWalkerEntry, parent: MediaConditionListWithAndWalkerParent }, index: number | string) => boolean) { + walk(cb: (entry: { node: MediaConditionListWithAndWalkerEntry, parent: MediaConditionListWithAndWalkerParent }, index: number | string) => boolean | void) { if (cb({ node: this.leading, parent: this }, 'leading') === false) { return false; } @@ -162,7 +162,7 @@ export class MediaConditionListWithOr { } } - walk(cb: (entry: { node: MediaConditionListWithOrWalkerEntry, parent: MediaConditionListWithOrWalkerParent }, index: number | string) => boolean) { + walk(cb: (entry: { node: MediaConditionListWithOrWalkerEntry, parent: MediaConditionListWithOrWalkerParent }, index: number | string) => boolean | void) { if (cb({ node: this.leading, parent: this }, 'leading') === false) { return false; } diff --git a/packages/media-query-list-parser/src/nodes/media-condition.ts b/packages/media-query-list-parser/src/nodes/media-condition.ts index 94753461f..7a2a8109c 100644 --- a/packages/media-query-list-parser/src/nodes/media-condition.ts +++ b/packages/media-query-list-parser/src/nodes/media-condition.ts @@ -34,7 +34,7 @@ export class MediaCondition { } } - walk(cb: (entry: { node: MediaConditionWalkerEntry, parent: MediaConditionWalkerParent }, index: number | string) => boolean) { + walk(cb: (entry: { node: MediaConditionWalkerEntry, parent: MediaConditionWalkerParent }, index: number | string) => boolean | void) { if (cb({ node: this.media, parent: this }, 'media') === false) { return false; } diff --git a/packages/media-query-list-parser/src/nodes/media-feature-plain.ts b/packages/media-query-list-parser/src/nodes/media-feature-plain.ts index c88b4bd1b..ddd1c5f4c 100644 --- a/packages/media-query-list-parser/src/nodes/media-feature-plain.ts +++ b/packages/media-query-list-parser/src/nodes/media-feature-plain.ts @@ -51,7 +51,7 @@ export class MediaFeaturePlain { } } - walk(cb: (entry: { node: MediaFeaturePlainWalkerEntry, parent: MediaFeaturePlainWalkerParent }, index: number | string) => boolean) { + walk(cb: (entry: { node: MediaFeaturePlainWalkerEntry, parent: MediaFeaturePlainWalkerParent }, index: number | string) => boolean | void) { if (cb({ node: this.value, parent: this }, 'value') === false) { return false; } diff --git a/packages/media-query-list-parser/src/nodes/media-feature-range.ts b/packages/media-query-list-parser/src/nodes/media-feature-range.ts index 88435acb4..fefb7c22d 100644 --- a/packages/media-query-list-parser/src/nodes/media-feature-range.ts +++ b/packages/media-query-list-parser/src/nodes/media-feature-range.ts @@ -60,7 +60,7 @@ export class MediaFeatureRangeNameValue { } } - walk(cb: (entry: { node: MediaFeatureRangeWalkerEntry, parent: MediaFeatureRangeWalkerParent }, index: number | string) => boolean) { + walk(cb: (entry: { node: MediaFeatureRangeWalkerEntry, parent: MediaFeatureRangeWalkerParent }, index: number | string) => boolean | void) { if (cb({ node: this.value, parent: this }, 'value') === false) { return false; } @@ -131,7 +131,7 @@ export class MediaFeatureRangeValueName { } } - walk(cb: (entry: { node: MediaFeatureRangeWalkerEntry, parent: MediaFeatureRangeWalkerParent }, index: number | string) => boolean) { + walk(cb: (entry: { node: MediaFeatureRangeWalkerEntry, parent: MediaFeatureRangeWalkerParent }, index: number | string) => boolean | void) { if (cb({ node: this.value, parent: this }, 'value') === false) { return false; } @@ -220,7 +220,7 @@ export class MediaFeatureRangeValueNameValue { } } - walk(cb: (entry: { node: MediaFeatureRangeWalkerEntry, parent: MediaFeatureRangeWalkerParent }, index: number | string) => boolean) { + walk(cb: (entry: { node: MediaFeatureRangeWalkerEntry, parent: MediaFeatureRangeWalkerParent }, index: number | string) => boolean | void) { if (cb({ node: this.valueOne, parent: this }, 'valueOne') === false) { return false; } diff --git a/packages/media-query-list-parser/src/nodes/media-feature-value.ts b/packages/media-query-list-parser/src/nodes/media-feature-value.ts index d19848192..cbd54b3c8 100644 --- a/packages/media-query-list-parser/src/nodes/media-feature-value.ts +++ b/packages/media-query-list-parser/src/nodes/media-feature-value.ts @@ -59,7 +59,7 @@ export class MediaFeatureValue { } } - walk(cb: (entry: { node: MediaFeatureValueWalkerEntry, parent: MediaFeatureValueWalkerParent }, index: number | string) => boolean) { + walk(cb: (entry: { node: MediaFeatureValueWalkerEntry, parent: MediaFeatureValueWalkerParent }, index: number | string) => boolean | void) { if (cb({ node: this.value, parent: this }, 'value') === false) { return false; } diff --git a/packages/media-query-list-parser/src/nodes/media-feature.ts b/packages/media-query-list-parser/src/nodes/media-feature.ts index 752d895bf..ede349b58 100644 --- a/packages/media-query-list-parser/src/nodes/media-feature.ts +++ b/packages/media-query-list-parser/src/nodes/media-feature.ts @@ -36,7 +36,7 @@ export class MediaFeature { } } - walk(cb: (entry: { node: MediaFeatureWalkerEntry, parent: MediaFeatureWalkerParent }, index: number | string) => boolean) { + walk(cb: (entry: { node: MediaFeatureWalkerEntry, parent: MediaFeatureWalkerParent }, index: number | string) => boolean | void) { if (cb({ node: this.feature, parent: this }, 'feature') === false) { return false; } diff --git a/packages/media-query-list-parser/src/nodes/media-in-parens.ts b/packages/media-query-list-parser/src/nodes/media-in-parens.ts index b0755c35e..df76e8b0b 100644 --- a/packages/media-query-list-parser/src/nodes/media-in-parens.ts +++ b/packages/media-query-list-parser/src/nodes/media-in-parens.ts @@ -51,7 +51,7 @@ export class MediaInParens { } } - walk(cb: (entry: { node: MediaInParensWalkerEntry, parent: MediaInParensWalkerParent }, index: number | string) => boolean) { + walk(cb: (entry: { node: MediaInParensWalkerEntry, parent: MediaInParensWalkerParent }, index: number | string) => boolean | void) { if (cb({ node: this.media, parent: this }, 'media') === false) { return false; } diff --git a/packages/media-query-list-parser/src/nodes/media-not.ts b/packages/media-query-list-parser/src/nodes/media-not.ts index 4605a3dfa..e6cff1139 100644 --- a/packages/media-query-list-parser/src/nodes/media-not.ts +++ b/packages/media-query-list-parser/src/nodes/media-not.ts @@ -38,7 +38,7 @@ export class MediaNot { } } - walk(cb: (entry: { node: MediaNotWalkerEntry, parent: MediaNotWalkerParent }, index: number | string) => boolean) { + walk(cb: (entry: { node: MediaNotWalkerEntry, parent: MediaNotWalkerParent }, index: number | string) => boolean | void) { if (cb({ node: this.media, parent: this }, 'media') === false) { return false; } diff --git a/packages/media-query-list-parser/src/nodes/media-or.ts b/packages/media-query-list-parser/src/nodes/media-or.ts index 36e7dcb24..32412816f 100644 --- a/packages/media-query-list-parser/src/nodes/media-or.ts +++ b/packages/media-query-list-parser/src/nodes/media-or.ts @@ -38,7 +38,7 @@ export class MediaOr { } } - walk(cb: (entry: { node: MediaOrWalkerEntry, parent: MediaOrWalkerParent }, index: number | string) => boolean) { + walk(cb: (entry: { node: MediaOrWalkerEntry, parent: MediaOrWalkerParent }, index: number | string) => boolean | void) { if (cb({ node: this.media, parent: this }, 'media') === false) { return false; } diff --git a/packages/media-query-list-parser/src/nodes/media-query.ts b/packages/media-query-list-parser/src/nodes/media-query.ts index c80a8f781..cd396d70b 100644 --- a/packages/media-query-list-parser/src/nodes/media-query.ts +++ b/packages/media-query-list-parser/src/nodes/media-query.ts @@ -54,7 +54,7 @@ export class MediaQueryWithType { } } - walk(cb: (entry: { node: MediaQueryWithTypeWalkerEntry, parent: MediaQueryWithTypeWalkerParent }, index: number | string) => boolean) { + walk(cb: (entry: { node: MediaQueryWithTypeWalkerEntry, parent: MediaQueryWithTypeWalkerParent }, index: number | string) => boolean | void) { if (cb({ node: this.media, parent: this }, 'media') === false) { return false; } @@ -107,7 +107,7 @@ export class MediaQueryWithoutType { } } - walk(cb: (entry: { node: MediaQueryWithoutTypeWalkerEntry, parent: MediaQueryWithoutTypeWalkerParent }, index: number | string) => boolean) { + walk(cb: (entry: { node: MediaQueryWithoutTypeWalkerEntry, parent: MediaQueryWithoutTypeWalkerParent }, index: number | string) => boolean | void) { if (cb({ node: this.media, parent: this }, 'media') === false) { return false; } From 854f850d17b8b13f49c8a93936cb9071c70cb5c7 Mon Sep 17 00:00:00 2001 From: Romain Menke Date: Wed, 26 Oct 2022 17:53:27 +0200 Subject: [PATCH 22/35] add ancestry utility --- packages/css-parser-algorithms/README.md | 18 ++++++++++++++++++ packages/css-parser-algorithms/src/index.ts | 1 + .../src/util/node-ancestry.ts | 19 +++++++++++++++++++ 3 files changed, 38 insertions(+) create mode 100644 packages/css-parser-algorithms/src/util/node-ancestry.ts diff --git a/packages/css-parser-algorithms/README.md b/packages/css-parser-algorithms/README.md index 1435d2fbf..aeda87344 100644 --- a/packages/css-parser-algorithms/README.md +++ b/packages/css-parser-algorithms/README.md @@ -60,6 +60,24 @@ console.log(result); - [`parseListOfComponentValues`](https://www.w3.org/TR/css-syntax-3/#parse-list-of-component-values) - [`parseCommaSeparatedListOfComponentValues`](https://www.w3.org/TR/css-syntax-3/#parse-comma-separated-list-of-component-values) +### Utilities + +#### `gatherNodeAncestry` + +The AST does not expose the entire ancestry of each node. +The walker methods do provide access to the current parent, but also not the entire ancestry. + +To gather the entire ancestry for a a given sub tree of the AST you can use `gatherNodeAncestry`. +The result is a `Map` with the child nodes as keys and the parents as values. +This allows you to lookup any ancestor of any node. + +```css +import { parseComponentValue } from '@csstools/css-parser-algorithms'; + +const result = parseComponentValue(tokens, options); +const ancestry = gatherNodeAncestry(result); +``` + ### Options ```ts diff --git a/packages/css-parser-algorithms/src/index.ts b/packages/css-parser-algorithms/src/index.ts index b88beecea..ed3490189 100644 --- a/packages/css-parser-algorithms/src/index.ts +++ b/packages/css-parser-algorithms/src/index.ts @@ -2,3 +2,4 @@ export * from './consume/consume-component-block-function'; export { parseComponentValue } from './parse/parse-component-value'; export { parseListOfComponentValues } from './parse/parse-list-of-component-values'; export { parseCommaSeparatedListOfComponentValues } from './parse/parse-comma-separated-list-of-component-values'; +export { gatherNodeAncestry } from './util/node-ancestry'; diff --git a/packages/css-parser-algorithms/src/util/node-ancestry.ts b/packages/css-parser-algorithms/src/util/node-ancestry.ts new file mode 100644 index 000000000..b6e3403da --- /dev/null +++ b/packages/css-parser-algorithms/src/util/node-ancestry.ts @@ -0,0 +1,19 @@ +export interface walkable { + walk(cb: (entry: { node: Array | T, parent: U }, index: number | string) => boolean | void) +} + +export function gatherNodeAncestry(node: walkable) { + const ancestry: Map = new Map(); + + node.walk((entry) => { + if (Array.isArray(entry.node)) { + entry.node.forEach((x) => { + ancestry.set(x, entry.parent); + }); + } else { + ancestry.set(entry.node, entry.parent); + } + }); + + return ancestry; +} From a0f81a3c0059f8c8a929bb554d04cca63e175bc5 Mon Sep 17 00:00:00 2001 From: Romain Menke Date: Thu, 27 Oct 2022 11:23:32 +0200 Subject: [PATCH 23/35] fixes --- .../src/nodes/media-query.ts | 17 +- .../src/parser/parse-media-query.ts | 25 +- .../cases/query-with-type/0001.expect.json | 3 +- .../cases/query-with-type/0002.expect.json | 3 +- .../cases/query-with-type/0003.expect.json | 3 +- .../cases/query-with-type/0004.expect.json | 180 ++++ .../test/cases/query-with-type/0004.mjs | 13 + .../test/cases/various/0002.expect.json | 965 +++++++++++------- .../test/cases/various/0005.expect.json | 209 ++-- .../test/cases/various/0006.expect.json | 3 +- .../media-query-list-parser/test/test.mjs | 1 + .../src/transform-at-media-v2/custom-media.ts | 26 +- .../postcss-custom-media/test/and.expect.css | 2 +- .../test/basic-after-v9.expect.css | 6 +- .../test/basic-after-v9.preserve.expect.css | 6 +- .../test/basic.expect.css | 16 +- .../test/complex.expect.css | 12 +- .../postcss-custom-media/test/list.expect.css | 8 +- .../postcss-custom-media/test/or.expect.css | 2 +- rollup/presets/package-typescript.js | 6 +- 20 files changed, 983 insertions(+), 523 deletions(-) create mode 100644 packages/media-query-list-parser/test/cases/query-with-type/0004.expect.json create mode 100644 packages/media-query-list-parser/test/cases/query-with-type/0004.mjs diff --git a/packages/media-query-list-parser/src/nodes/media-query.ts b/packages/media-query-list-parser/src/nodes/media-query.ts index cd396d70b..9840941f1 100644 --- a/packages/media-query-list-parser/src/nodes/media-query.ts +++ b/packages/media-query-list-parser/src/nodes/media-query.ts @@ -9,19 +9,25 @@ export class MediaQueryWithType { modifier: Array; mediaType: Array; + and: Array; media: MediaCondition | null = null; - constructor(modifier: Array, mediaType: Array, media?: MediaCondition | null) { + constructor(modifier: Array, mediaType: Array, and?: Array, media?: MediaCondition | null) { this.modifier = modifier; this.mediaType = mediaType; - this.media = media; + + if (and && media) { + this.and = and; + this.media = media; + } } tokens() { - if (this.media) { + if (this.and && this.media) { return [ ...this.modifier, ...this.mediaType, + ...this.and, ...this.media.tokens(), ]; } @@ -33,8 +39,8 @@ export class MediaQueryWithType { } toString() { - if (this.media) { - return stringify(...this.modifier) + stringify(...this.mediaType) + this.media.toString(); + if (this.and && this.media) { + return stringify(...this.modifier) + stringify(...this.mediaType) + stringify(...this.and) + this.media.toString(); } return stringify(...this.modifier) + stringify(...this.mediaType); @@ -68,6 +74,7 @@ export class MediaQueryWithType { string: this.toString(), modifier: this.modifier, mediaType: this.mediaType, + and: this.and, media: this.media, }; } diff --git a/packages/media-query-list-parser/src/parser/parse-media-query.ts b/packages/media-query-list-parser/src/parser/parse-media-query.ts index 186257dd5..87c3ca9c4 100644 --- a/packages/media-query-list-parser/src/parser/parse-media-query.ts +++ b/packages/media-query-list-parser/src/parser/parse-media-query.ts @@ -23,6 +23,7 @@ export function parseMediaQuery(componentValues: Array) { { let modifierIndex = -1; let typeIndex = -1; + let andIndex = -1; for (let i = 0; i < componentValues.length; i++) { const componentValue = componentValues[i]; @@ -36,23 +37,30 @@ export function parseMediaQuery(componentValues: Array) { if (componentValue.type === ComponentValueType.Token) { const token = (componentValue as TokenNode).value; - if (token[0] === TokenType.Ident && modifierFromToken(token)) { + if (modifierIndex === -1 && token[0] === TokenType.Ident && modifierFromToken(token)) { modifierIndex = i; continue; } - if (token[0] === TokenType.Ident) { + if (typeIndex === -1 && token[0] === TokenType.Ident && !modifierFromToken(token)) { typeIndex = i; continue; } - return false; - } + if (andIndex === -1 && token[0] === TokenType.Ident && token[4].value.toLowerCase() === 'and') { + andIndex = i; + const condition = parseMediaConditionWithoutOr(componentValues.slice(i+1)); + if (condition === false) { + return false; + } + + break; + } - const condition = parseMediaConditionWithoutOr(componentValues.slice(i)); - if (condition === false) { return false; } + + return false; } let modifierTokens: Array = []; @@ -74,7 +82,7 @@ export function parseMediaQuery(componentValues: Array) { }); } - const remainder = componentValues.slice(Math.max(modifierIndex, typeIndex) + 1); + const remainder = componentValues.slice(Math.max(modifierIndex, typeIndex, andIndex) + 1); const condition = parseMediaConditionWithoutOr(remainder); if (condition === false) { return new MediaQueryWithType( @@ -91,6 +99,9 @@ export function parseMediaQuery(componentValues: Array) { return new MediaQueryWithType( modifierTokens, typeTokens, + componentValues.slice(typeIndex + 1, andIndex + 1).flatMap((x) => { + return x.tokens(); + }), condition, ); } diff --git a/packages/media-query-list-parser/test/cases/query-with-type/0001.expect.json b/packages/media-query-list-parser/test/cases/query-with-type/0001.expect.json index b32206ad3..ca5c59492 100644 --- a/packages/media-query-list-parser/test/cases/query-with-type/0001.expect.json +++ b/packages/media-query-list-parser/test/cases/query-with-type/0001.expect.json @@ -13,6 +13,7 @@ "value": "screen" } ] - ] + ], + "media": null } ] \ No newline at end of file diff --git a/packages/media-query-list-parser/test/cases/query-with-type/0002.expect.json b/packages/media-query-list-parser/test/cases/query-with-type/0002.expect.json index 59b3a0db1..f8f8ec84d 100644 --- a/packages/media-query-list-parser/test/cases/query-with-type/0002.expect.json +++ b/packages/media-query-list-parser/test/cases/query-with-type/0002.expect.json @@ -30,6 +30,7 @@ "value": "screen" } ] - ] + ], + "media": null } ] \ No newline at end of file diff --git a/packages/media-query-list-parser/test/cases/query-with-type/0003.expect.json b/packages/media-query-list-parser/test/cases/query-with-type/0003.expect.json index 65fc34d9d..428d65884 100644 --- a/packages/media-query-list-parser/test/cases/query-with-type/0003.expect.json +++ b/packages/media-query-list-parser/test/cases/query-with-type/0003.expect.json @@ -30,6 +30,7 @@ "value": "screen" } ] - ] + ], + "media": null } ] \ No newline at end of file diff --git a/packages/media-query-list-parser/test/cases/query-with-type/0004.expect.json b/packages/media-query-list-parser/test/cases/query-with-type/0004.expect.json new file mode 100644 index 000000000..4e98e6776 --- /dev/null +++ b/packages/media-query-list-parser/test/cases/query-with-type/0004.expect.json @@ -0,0 +1,180 @@ +[ + { + "type": "media-query-with-type", + "string": "only screen and (min-width: 300px)", + "modifier": [ + [ + "ident-token", + "only", + 0, + 3, + { + "value": "only" + } + ] + ], + "mediaType": [ + [ + "whitespace-token", + " ", + 4, + 4, + null + ], + [ + "ident-token", + "screen", + 5, + 10, + { + "value": "screen" + } + ] + ], + "and": [ + [ + "whitespace-token", + " ", + 11, + 11, + null + ], + [ + "ident-token", + "and", + 12, + 14, + { + "value": "and" + } + ] + ], + "media": { + "type": "media-condition", + "media": { + "type": "media-in-parens", + "media": { + "type": "media-feature", + "feature": { + "type": "mf-plain", + "name": { + "type": "mf-name", + "name": "min-width", + "tokens": [ + [ + "ident-token", + "min-width", + 17, + 25, + { + "value": "min-width" + } + ] + ] + }, + "value": { + "type": "mf-value", + "value": { + "type": "token", + "tokens": [ + [ + "dimension-token", + "300px", + 28, + 32, + { + "value": 300, + "type": "integer", + "unit": "px" + } + ] + ] + }, + "tokens": [ + [ + "whitespace-token", + " ", + 27, + 27, + null + ], + [ + "dimension-token", + "300px", + 28, + 32, + { + "value": 300, + "type": "integer", + "unit": "px" + } + ] + ] + }, + "tokens": [ + [ + "ident-token", + "min-width", + 17, + 25, + { + "value": "min-width" + } + ], + [ + "colon-token", + ":", + 26, + 26, + null + ], + [ + "whitespace-token", + " ", + 27, + 27, + null + ], + [ + "dimension-token", + "300px", + 28, + 32, + { + "value": 300, + "type": "integer", + "unit": "px" + } + ] + ] + } + }, + "before": [ + [ + "whitespace-token", + " ", + 15, + 15, + null + ], + [ + "(-token", + "(", + 16, + 16, + null + ] + ], + "after": [ + [ + ")-token", + ")", + 33, + 33, + null + ] + ] + } + } + } +] \ No newline at end of file diff --git a/packages/media-query-list-parser/test/cases/query-with-type/0004.mjs b/packages/media-query-list-parser/test/cases/query-with-type/0004.mjs new file mode 100644 index 000000000..e056d5497 --- /dev/null +++ b/packages/media-query-list-parser/test/cases/query-with-type/0004.mjs @@ -0,0 +1,13 @@ +import assert from 'assert'; +import { runTest } from '../../util/run-test.mjs'; + +runTest( + 'only screen and (min-width: 300px)', + 'query-with-type/0004', + (actual, expected) => { + assert.deepStrictEqual( + actual, + expected, + ); + }, +); diff --git a/packages/media-query-list-parser/test/cases/various/0002.expect.json b/packages/media-query-list-parser/test/cases/various/0002.expect.json index 6ce2bb672..6199b1d03 100644 --- a/packages/media-query-list-parser/test/cases/various/0002.expect.json +++ b/packages/media-query-list-parser/test/cases/various/0002.expect.json @@ -29,7 +29,9 @@ { "value": "screen" } - ], + ] + ], + "and": [ [ "whitespace-token", " ", @@ -45,277 +47,66 @@ { "value": "and" } - ], - [ - "whitespace-token", - " ", - 14, - 14, - null - ], - [ - "(-token", - "(", - 15, - 15, - null - ], - [ - "ident-token", - "min-width", - 16, - 24, - { - "value": "min-width" - } - ], - [ - "colon-token", - ":", - 25, - 25, - null - ], - [ - "whitespace-token", - " ", - 26, - 26, - null - ], - [ - "dimension-token", - "300px", - 27, - 31, - { - "value": 300, - "type": "integer", - "unit": "px" - } - ], - [ - ")-token", - ")", - 32, - 32, - null - ], - [ - "whitespace-token", - " ", - 33, - 33, - null - ], - [ - "ident-token", - "and", - 34, - 36, - { - "value": "and" - } - ], - [ - "whitespace-token", - " ", - 37, - 37, - null - ], - [ - "(-token", - "(", - 38, - 38, - null - ], - [ - "ident-token", - "prefers-color-scheme", - 39, - 58, - { - "value": "prefers-color-scheme" - } - ], - [ - "colon-token", - ":", - 59, - 59, - null - ], - [ - "comment", - "/* a comment */", - 60, - 74, - null - ], - [ - "ident-token", - "dark", - 75, - 78, - { - "value": "dark" - } - ], - [ - ")-token", - ")", - 79, - 79, - null - ], - [ - "whitespace-token", - " ", - 80, - 80, - null - ], - [ - "ident-token", - "and", - 81, - 83, - { - "value": "and" - } - ], - [ - "whitespace-token", - " ", - 84, - 84, - null - ], - [ - "(-token", - "(", - 85, - 85, - null - ], - [ - "ident-token", - "width", - 86, - 90, - { - "value": "width" - } - ], - [ - "whitespace-token", - " ", - 91, - 91, - null - ], - [ - "delim-token", - "<", - 92, - 92, - { - "value": "<" - } - ], - [ - "whitespace-token", - " ", - 93, - 93, - null - ], - [ - "dimension-token", - "40vw", - 94, - 97, - { - "value": 40, - "type": "integer", - "unit": "vw" - } - ], - [ - ")-token", - ")", - 98, - 98, - null - ], - [ - "whitespace-token", - " ", - 99, - 99, - null - ], - [ - "ident-token", - "and", - 100, - 102, - { - "value": "and" - } ] ], "media": { "type": "media-condition", "media": { - "type": "media-in-parens", - "media": { - "type": "media-feature", - "feature": { - "type": "mf-range-value-name-value", - "name": { - "type": "mf-name", - "name": "width", - "tokens": [ - [ - "whitespace-token", - " ", - 111, - 111, - null - ], - [ - "ident-token", - "width", - 112, - 116, - { - "value": "width" - } - ], - [ - "whitespace-token", - " ", - 117, - 117, - null + "type": "media-condition-list-and", + "leading": { + "type": "media-in-parens", + "media": { + "type": "media-feature", + "feature": { + "type": "mf-plain", + "name": { + "type": "mf-name", + "name": "min-width", + "tokens": [ + [ + "ident-token", + "min-width", + 16, + 24, + { + "value": "min-width" + } + ] ] - ] - }, - "valueOne": { - "type": "mf-value", + }, "value": { - "type": "token", + "type": "mf-value", + "value": { + "type": "token", + "tokens": [ + [ + "dimension-token", + "300px", + 27, + 31, + { + "value": 300, + "type": "integer", + "unit": "px" + } + ] + ] + }, "tokens": [ + [ + "whitespace-token", + " ", + 26, + 26, + null + ], [ "dimension-token", - "30px", - 105, - 108, + "300px", + 27, + 31, { - "value": 30, + "value": 300, "type": "integer", "unit": "px" } @@ -324,170 +115,614 @@ }, "tokens": [ [ - "dimension-token", - "30px", - 105, - 108, + "ident-token", + "min-width", + 16, + 24, { - "value": 30, - "type": "integer", - "unit": "px" + "value": "min-width" } ], [ - "whitespace-token", - " ", - 109, - 109, + "colon-token", + ":", + 25, + 25, null - ] - ] - }, - "valueTwo": { - "type": "mf-value", - "value": { - "type": "token", - "tokens": [ - [ - "dimension-token", - "50rem", - 120, - 124, - { - "value": 50, - "type": "integer", - "unit": "rem" - } - ] - ] - }, - "tokens": [ + ], [ "whitespace-token", " ", - 119, - 119, + 26, + 26, null ], [ "dimension-token", - "50rem", - 120, - 124, + "300px", + 27, + 31, { - "value": 50, + "value": 300, "type": "integer", - "unit": "rem" + "unit": "px" } ] ] - }, - "tokens": [ + } + }, + "before": [ + [ + "(-token", + "(", + 15, + 15, + null + ] + ], + "after": [ + [ + ")-token", + ")", + 32, + 32, + null + ] + ] + }, + "list": [ + { + "type": "media-and", + "modifier": [ + [ + "whitespace-token", + " ", + 33, + 33, + null + ], [ - "dimension-token", - "30px", - 105, - 108, + "ident-token", + "and", + 34, + 36, { - "value": 30, - "type": "integer", - "unit": "px" + "value": "and" } ], [ "whitespace-token", " ", - 109, - 109, + 37, + 37, null - ], - [ - "delim-token", - "<", - 110, - 110, - { - "value": "<" + ] + ], + "media": { + "type": "media-in-parens", + "media": { + "type": "media-feature", + "feature": { + "type": "mf-plain", + "name": { + "type": "mf-name", + "name": "prefers-color-scheme", + "tokens": [ + [ + "ident-token", + "prefers-color-scheme", + 39, + 58, + { + "value": "prefers-color-scheme" + } + ] + ] + }, + "value": { + "type": "mf-value", + "value": { + "type": "token", + "tokens": [ + [ + "ident-token", + "dark", + 75, + 78, + { + "value": "dark" + } + ] + ] + }, + "tokens": [ + [ + "comment", + "/* a comment */", + 60, + 74, + null + ], + [ + "ident-token", + "dark", + 75, + 78, + { + "value": "dark" + } + ] + ] + }, + "tokens": [ + [ + "ident-token", + "prefers-color-scheme", + 39, + 58, + { + "value": "prefers-color-scheme" + } + ], + [ + "colon-token", + ":", + 59, + 59, + null + ], + [ + "comment", + "/* a comment */", + 60, + 74, + null + ], + [ + "ident-token", + "dark", + 75, + 78, + { + "value": "dark" + } + ] + ] } + }, + "before": [ + [ + "(-token", + "(", + 38, + 38, + null + ] ], + "after": [ + [ + ")-token", + ")", + 79, + 79, + null + ] + ] + } + }, + { + "type": "media-and", + "modifier": [ [ "whitespace-token", " ", - 111, - 111, + 80, + 80, null ], [ "ident-token", - "width", - 112, - 116, + "and", + 81, + 83, { - "value": "width" + "value": "and" } ], [ "whitespace-token", " ", - 117, - 117, + 84, + 84, null - ], - [ - "delim-token", - "<", - 118, - 118, - { - "value": "<" + ] + ], + "media": { + "type": "media-in-parens", + "media": { + "type": "media-feature", + "feature": { + "type": "mf-range-name-value", + "name": { + "type": "mf-name", + "name": "width", + "tokens": [ + [ + "ident-token", + "width", + 86, + 90, + { + "value": "width" + } + ], + [ + "whitespace-token", + " ", + 91, + 91, + null + ] + ] + }, + "value": { + "type": "mf-value", + "value": { + "type": "token", + "tokens": [ + [ + "dimension-token", + "40vw", + 94, + 97, + { + "value": 40, + "type": "integer", + "unit": "vw" + } + ] + ] + }, + "tokens": [ + [ + "whitespace-token", + " ", + 93, + 93, + null + ], + [ + "dimension-token", + "40vw", + 94, + 97, + { + "value": 40, + "type": "integer", + "unit": "vw" + } + ] + ] + }, + "tokens": [ + [ + "ident-token", + "width", + 86, + 90, + { + "value": "width" + } + ], + [ + "whitespace-token", + " ", + 91, + 91, + null + ], + [ + "delim-token", + "<", + 92, + 92, + { + "value": "<" + } + ], + [ + "whitespace-token", + " ", + 93, + 93, + null + ], + [ + "dimension-token", + "40vw", + 94, + 97, + { + "value": 40, + "type": "integer", + "unit": "vw" + } + ] + ] } + }, + "before": [ + [ + "(-token", + "(", + 85, + 85, + null + ] ], + "after": [ + [ + ")-token", + ")", + 98, + 98, + null + ] + ] + } + }, + { + "type": "media-and", + "modifier": [ [ "whitespace-token", " ", - 119, - 119, + 99, + 99, null ], [ - "dimension-token", - "50rem", - 120, - 124, + "ident-token", + "and", + 100, + 102, { - "value": 50, - "type": "integer", - "unit": "rem" + "value": "and" } + ], + [ + "whitespace-token", + " ", + 103, + 103, + null ] - ] + ], + "media": { + "type": "media-in-parens", + "media": { + "type": "media-feature", + "feature": { + "type": "mf-range-value-name-value", + "name": { + "type": "mf-name", + "name": "width", + "tokens": [ + [ + "whitespace-token", + " ", + 111, + 111, + null + ], + [ + "ident-token", + "width", + 112, + 116, + { + "value": "width" + } + ], + [ + "whitespace-token", + " ", + 117, + 117, + null + ] + ] + }, + "valueOne": { + "type": "mf-value", + "value": { + "type": "token", + "tokens": [ + [ + "dimension-token", + "30px", + 105, + 108, + { + "value": 30, + "type": "integer", + "unit": "px" + } + ] + ] + }, + "tokens": [ + [ + "dimension-token", + "30px", + 105, + 108, + { + "value": 30, + "type": "integer", + "unit": "px" + } + ], + [ + "whitespace-token", + " ", + 109, + 109, + null + ] + ] + }, + "valueTwo": { + "type": "mf-value", + "value": { + "type": "token", + "tokens": [ + [ + "dimension-token", + "50rem", + 120, + 124, + { + "value": 50, + "type": "integer", + "unit": "rem" + } + ] + ] + }, + "tokens": [ + [ + "whitespace-token", + " ", + 119, + 119, + null + ], + [ + "dimension-token", + "50rem", + 120, + 124, + { + "value": 50, + "type": "integer", + "unit": "rem" + } + ] + ] + }, + "tokens": [ + [ + "dimension-token", + "30px", + 105, + 108, + { + "value": 30, + "type": "integer", + "unit": "px" + } + ], + [ + "whitespace-token", + " ", + 109, + 109, + null + ], + [ + "delim-token", + "<", + 110, + 110, + { + "value": "<" + } + ], + [ + "whitespace-token", + " ", + 111, + 111, + null + ], + [ + "ident-token", + "width", + 112, + 116, + { + "value": "width" + } + ], + [ + "whitespace-token", + " ", + 117, + 117, + null + ], + [ + "delim-token", + "<", + 118, + 118, + { + "value": "<" + } + ], + [ + "whitespace-token", + " ", + 119, + 119, + null + ], + [ + "dimension-token", + "50rem", + 120, + 124, + { + "value": 50, + "type": "integer", + "unit": "rem" + } + ] + ] + } + }, + "before": [ + [ + "(-token", + "(", + 104, + 104, + null + ] + ], + "after": [ + [ + ")-token", + ")", + 125, + 125, + null + ] + ] + } } - }, + ], "before": [ [ "whitespace-token", " ", - 103, - 103, - null - ], - [ - "(-token", - "(", - 104, - 104, + 14, + 14, null ] ], - "after": [ - [ - ")-token", - ")", - 125, - 125, - null - ] - ] + "after": [] } } } diff --git a/packages/media-query-list-parser/test/cases/various/0005.expect.json b/packages/media-query-list-parser/test/cases/various/0005.expect.json index a44f49722..c2e4266f2 100644 --- a/packages/media-query-list-parser/test/cases/various/0005.expect.json +++ b/packages/media-query-list-parser/test/cases/various/0005.expect.json @@ -2,7 +2,8 @@ { "type": "media-query-with-type", "string": "screen and not (min-width: 300px)", - "modifier": [ + "modifier": [], + "mediaType": [ [ "ident-token", "screen", @@ -11,7 +12,9 @@ { "value": "screen" } - ], + ] + ], + "and": [ [ "whitespace-token", " ", @@ -27,53 +30,84 @@ { "value": "and" } - ], - [ - "whitespace-token", - " ", - 10, - 10, - null - ], - [ - "ident-token", - "not", - 11, - 13, - { - "value": "not" - } ] ], - "mediaType": [], "media": { "type": "media-condition", "media": { - "type": "media-in-parens", + "type": "media-not", + "modifier": [ + [ + "whitespace-token", + " ", + 10, + 10, + null + ], + [ + "ident-token", + "not", + 11, + 13, + { + "value": "not" + } + ], + [ + "whitespace-token", + " ", + 14, + 14, + null + ] + ], "media": { - "type": "media-feature", - "feature": { - "type": "mf-plain", - "name": { - "type": "mf-name", - "name": "min-width", - "tokens": [ - [ - "ident-token", - "min-width", - 16, - 24, - { - "value": "min-width" - } + "type": "media-in-parens", + "media": { + "type": "media-feature", + "feature": { + "type": "mf-plain", + "name": { + "type": "mf-name", + "name": "min-width", + "tokens": [ + [ + "ident-token", + "min-width", + 16, + 24, + { + "value": "min-width" + } + ] ] - ] - }, - "value": { - "type": "mf-value", + }, "value": { - "type": "token", + "type": "mf-value", + "value": { + "type": "token", + "tokens": [ + [ + "dimension-token", + "300px", + 27, + 31, + { + "value": 300, + "type": "integer", + "unit": "px" + } + ] + ] + }, "tokens": [ + [ + "whitespace-token", + " ", + 26, + 26, + null + ], [ "dimension-token", "300px", @@ -88,6 +122,22 @@ ] }, "tokens": [ + [ + "ident-token", + "min-width", + 16, + 24, + { + "value": "min-width" + } + ], + [ + "colon-token", + ":", + 25, + 25, + null + ], [ "whitespace-token", " ", @@ -107,70 +157,27 @@ } ] ] - }, - "tokens": [ - [ - "ident-token", - "min-width", - 16, - 24, - { - "value": "min-width" - } - ], - [ - "colon-token", - ":", - 25, - 25, - null - ], - [ - "whitespace-token", - " ", - 26, - 26, - null - ], - [ - "dimension-token", - "300px", - 27, - 31, - { - "value": 300, - "type": "integer", - "unit": "px" - } - ] + } + }, + "before": [ + [ + "(-token", + "(", + 15, + 15, + null ] - } - }, - "before": [ - [ - "whitespace-token", - " ", - 14, - 14, - null ], - [ - "(-token", - "(", - 15, - 15, - null - ] - ], - "after": [ - [ - ")-token", - ")", - 32, - 32, - null + "after": [ + [ + ")-token", + ")", + 32, + 32, + null + ] ] - ] + } } } } diff --git a/packages/media-query-list-parser/test/cases/various/0006.expect.json b/packages/media-query-list-parser/test/cases/various/0006.expect.json index 521c0c9cd..4e7482d4b 100644 --- a/packages/media-query-list-parser/test/cases/various/0006.expect.json +++ b/packages/media-query-list-parser/test/cases/various/0006.expect.json @@ -37,6 +37,7 @@ 11, null ] - ] + ], + "media": null } ] \ No newline at end of file diff --git a/packages/media-query-list-parser/test/test.mjs b/packages/media-query-list-parser/test/test.mjs index b9fdee14c..20a419ccb 100644 --- a/packages/media-query-list-parser/test/test.mjs +++ b/packages/media-query-list-parser/test/test.mjs @@ -23,6 +23,7 @@ import './cases/mf-range/0007.mjs'; import './cases/query-with-type/0001.mjs'; import './cases/query-with-type/0002.mjs'; import './cases/query-with-type/0003.mjs'; +import './cases/query-with-type/0004.mjs'; import './cases/various/0001.mjs'; import './cases/various/0002.mjs'; diff --git a/plugins/postcss-custom-media/src/transform-at-media-v2/custom-media.ts b/plugins/postcss-custom-media/src/transform-at-media-v2/custom-media.ts index 13a94d2bd..fa72f20df 100644 --- a/plugins/postcss-custom-media/src/transform-at-media-v2/custom-media.ts +++ b/plugins/postcss-custom-media/src/transform-at-media-v2/custom-media.ts @@ -1,5 +1,5 @@ import { stringify, TokenIdent, TokenType } from '@csstools/css-tokenizer'; -import { NodeType, parse, MediaCondition, MediaInParens, MediaQueryWithoutType, MediaQueryWithType, MediaNot } from '@csstools/media-query-list-parser'; +import { NodeType, parse, MediaCondition, MediaInParens, MediaQueryWithoutType, MediaQueryWithType, MediaNot, MediaConditionListWithAnd } from '@csstools/media-query-list-parser'; import { atMediaParamsTokens } from '../transform-at-media/at-media-params-tokens'; export function parseCustomMedia(params: string): { name: string, truthy: string, falsy: string, dependsOn: Array<[string, string]> } | false { @@ -82,17 +82,19 @@ export function parseCustomMedia(params: string): { name: string, truthy: string continue; } - const query = new MediaQueryWithoutType( - new MediaNot( - [ - [TokenType.Ident, 'not', 0, 0, { value: 'not' }], - ], - new MediaInParens( - (mediaQuery as MediaQueryWithoutType).media, - [[TokenType.Whitespace, ' ', 0, 0, null],[TokenType.OpenParen, '(', 0, 0, null]], - [[TokenType.CloseParen, ')', 0, 0, null]], - ), - ), + const query = new MediaQueryWithType( + [ + [TokenType.Ident, 'not', 0, 0, { value: 'not' }], + [TokenType.Whitespace, ' ', 0, 0, undefined], + ], + [ + [TokenType.Ident, 'all', 0, 0, { value: 'all' }], + [TokenType.Whitespace, ' ', 0, 0, undefined], + ], + [ + [TokenType.Ident, 'and', 0, 0, { value: 'and' }], + ], + mediaCondition, ); mediaQueryListFalsy[i] = query; diff --git a/plugins/postcss-custom-media/test/and.expect.css b/plugins/postcss-custom-media/test/and.expect.css index e41f3748f..9cbf54fd4 100644 --- a/plugins/postcss-custom-media/test/and.expect.css +++ b/plugins/postcss-custom-media/test/and.expect.css @@ -9,7 +9,7 @@ } } -@media not ( (min-width: 300px) and (min-height: 300px)) { +@media not all and (min-width: 300px) and (min-height: 300px) { @media not screen and (color:2147477350) { .a { diff --git a/plugins/postcss-custom-media/test/basic-after-v9.expect.css b/plugins/postcss-custom-media/test/basic-after-v9.expect.css index f0b7ed39d..fe92455d5 100644 --- a/plugins/postcss-custom-media/test/basic-after-v9.expect.css +++ b/plugins/postcss-custom-media/test/basic-after-v9.expect.css @@ -13,7 +13,7 @@ } } } -@media not ( (min-width: 300px)) { +@media not all and (min-width: 300px) { @media screen and (color:2147477350) { .a { order: 2; @@ -29,7 +29,7 @@ } } } -@media not ( (min-width: 300px)) { +@media not all and (min-width: 300px) { @media not (color:2147477350) { .a { order: 3; @@ -48,7 +48,7 @@ } } -@media not ( (color)),not ( (hover)) { +@media not all and (color),not all and (hover) { @media (color:2147477350) and (width > 1024px) { .a { diff --git a/plugins/postcss-custom-media/test/basic-after-v9.preserve.expect.css b/plugins/postcss-custom-media/test/basic-after-v9.preserve.expect.css index 3a1becd3e..1a74c4295 100644 --- a/plugins/postcss-custom-media/test/basic-after-v9.preserve.expect.css +++ b/plugins/postcss-custom-media/test/basic-after-v9.preserve.expect.css @@ -20,7 +20,7 @@ } } } -@media not ( (min-width: 300px)) { +@media not all and (min-width: 300px) { @media screen and (color:2147477350) { .a { order: 2; @@ -41,7 +41,7 @@ } } } -@media not ( (min-width: 300px)) { +@media not all and (min-width: 300px) { @media not (color:2147477350) { .a { order: 3; @@ -66,7 +66,7 @@ } } -@media not ( (color)),not ( (hover)) { +@media not all and (color),not all and (hover) { @media (color:2147477350) and (width > 1024px) { .a { diff --git a/plugins/postcss-custom-media/test/basic.expect.css b/plugins/postcss-custom-media/test/basic.expect.css index 41af01505..97d82e390 100644 --- a/plugins/postcss-custom-media/test/basic.expect.css +++ b/plugins/postcss-custom-media/test/basic.expect.css @@ -25,7 +25,7 @@ } } -@media not ( (max-width: 30em)),not ( (max-height: 30em)) { +@media not all and (max-width: 30em),not all and (max-height: 30em) { @media not all and (color:2147477350) { body { @@ -43,7 +43,7 @@ } } -@media not ( (max-width: 30em)),not ( (max-height: 30em)) { +@media not all and (max-width: 30em),not all and (max-height: 30em) { @media not all and (color:2147477350) { body { @@ -64,7 +64,7 @@ } } -@media not ( (max-width: 30em)),not ( (max-height: 30em)) { +@media not all and (max-width: 30em),not all and (max-height: 30em) { @media not all and (color:2147477350) { @@ -88,7 +88,7 @@ } } -@media not ( (max-width: 30em)),not ( (max-height: 30em)) { +@media not all and (max-width: 30em),not all and (max-height: 30em) { @media all and (color:2147477350) { @@ -129,7 +129,7 @@ } } -@media not ( (max-width: 640px)) { +@media not all and (max-width: 640px) { @media (max-color:2147477350) and (color:2147477350) { body { @@ -139,7 +139,7 @@ } } -@media not ( (min-width: 320px)) { +@media not all and (min-width: 320px) { @media (max-width: 640px) { @@ -150,7 +150,7 @@ } } -@media not ( (max-width: 640px)) { +@media not all and (max-width: 640px) { @media (color:2147477350) and (color:2147477350) { body { @@ -175,7 +175,7 @@ } } -@media not ( (min-width: 320px) and (max-width: 640px)) { +@media not all and (min-width: 320px) and (max-width: 640px) { @media (color:2147477350) and (min-aspect-ratio: 16/9) { body { diff --git a/plugins/postcss-custom-media/test/complex.expect.css b/plugins/postcss-custom-media/test/complex.expect.css index 913d18996..792e80338 100644 --- a/plugins/postcss-custom-media/test/complex.expect.css +++ b/plugins/postcss-custom-media/test/complex.expect.css @@ -17,7 +17,7 @@ } } -@media not ( (min-width: 3)),not ( (min-width: 4)) { +@media not all and (min-width: 3),not all and (min-width: 4) { @media (color:2147477350) and (width > 1024px) { .a { order: 3; } @@ -67,7 +67,7 @@ } } -@media not ( (width >= 570px)) { +@media not all and (width >= 570px) { @media (max-color:2147477350) and (color:2147477350) { body { @@ -88,7 +88,7 @@ } } -@media not ( (width >= 570px)) { +@media not all and (width >= 570px) { @media (color:2147477350) and (color:2147477350) { body { @@ -109,7 +109,7 @@ } } -@media not ( (width < 1000px)) { +@media not all and (width < 1000px) { @media (max-color:2147477350) and (color:2147477350) { body { @@ -119,7 +119,7 @@ } } -@media not ( (width >= 570px)) { +@media not all and (width >= 570px) { @media (width < 1000px) { @@ -130,7 +130,7 @@ } } -@media not ( (width < 1000px)) { +@media not all and (width < 1000px) { @media (color:2147477350) and (color:2147477350) { body { diff --git a/plugins/postcss-custom-media/test/list.expect.css b/plugins/postcss-custom-media/test/list.expect.css index ff4721297..3bf1138d3 100644 --- a/plugins/postcss-custom-media/test/list.expect.css +++ b/plugins/postcss-custom-media/test/list.expect.css @@ -9,7 +9,7 @@ } } -@media not ( (min-width: 101px)),not ( (min-height: 102px)) { +@media not all and (min-width: 101px),not all and (min-height: 102px) { @media screen and (color:2147477350) { .a { @@ -27,7 +27,7 @@ } } -@media not ( (min-width: 101px)),not ( (min-height: 102px)) { +@media not all and (min-width: 101px),not all and (min-height: 102px) { @media ((other: feature) and (color:2147477350)) { .a { @@ -53,7 +53,7 @@ } } -@media not ( (min-width: 201px)) { +@media not all and (min-width: 201px) { @media screen and (color:2147477350) { .b { @@ -71,7 +71,7 @@ } } -@media not ( (min-width: 202px)) { +@media not all and (min-width: 202px) { @media not (color:2147477350) { .b { diff --git a/plugins/postcss-custom-media/test/or.expect.css b/plugins/postcss-custom-media/test/or.expect.css index ec11d88df..e4ffa2d67 100644 --- a/plugins/postcss-custom-media/test/or.expect.css +++ b/plugins/postcss-custom-media/test/or.expect.css @@ -9,7 +9,7 @@ } } -@media not ( (min-width: 300px) or (min-height: 300px)) { +@media not all and (min-width: 300px) or (min-height: 300px) { @media not screen and (color:2147477350) { .a { diff --git a/rollup/presets/package-typescript.js b/rollup/presets/package-typescript.js index 6242f32c5..4684c6bf9 100644 --- a/rollup/presets/package-typescript.js +++ b/rollup/presets/package-typescript.js @@ -21,9 +21,9 @@ export function packageTypescript() { extensions: ['.js', '.ts'], presets: packageBabelPreset, }), - terser({ - keep_classnames: true, - }), + // terser({ + // keep_classnames: true, + // }), ], }, ]; From 41bbb22fcdde5037a394ed58351b60f1f15b2bbc Mon Sep 17 00:00:00 2001 From: Romain Menke Date: Thu, 27 Oct 2022 12:10:02 +0200 Subject: [PATCH 24/35] fixes --- .../src/parser/parse.ts | 19 ++++++++++--- .../always-true-or-false.ts | 4 +-- .../src/transform-at-media-v2/custom-media.ts | 28 +++++++++++++++---- .../transform-at-media-v2/true-and-false.ts | 2 ++ .../test/comma-1.expect.css | 2 +- .../postcss-custom-media/test/not.expect.css | 2 +- .../postcss-custom-media/test/or.expect.css | 2 +- .../test/true-false.expect.css | 12 ++++---- rollup/presets/package-typescript.js | 6 ++-- 9 files changed, 54 insertions(+), 23 deletions(-) diff --git a/packages/media-query-list-parser/src/parser/parse.ts b/packages/media-query-list-parser/src/parser/parse.ts index f15f9607d..eacf975fa 100644 --- a/packages/media-query-list-parser/src/parser/parse.ts +++ b/packages/media-query-list-parser/src/parser/parse.ts @@ -3,7 +3,11 @@ import { tokenizer } from '@csstools/css-tokenizer'; import { MediaQueryWithoutType, MediaQueryWithType } from '../nodes/media-query'; import { parseMediaQuery } from './parse-media-query'; -export function parse(source: string) { +export type Options = { + preserveInvalidMediaQueries?: boolean +} + +export function parse(source: string, options?: Options) { const onParseError = (err) => { console.warn(err); throw new Error(`Unable to parse "${source}"`); @@ -23,9 +27,16 @@ export function parse(source: string) { tokens.push(t.nextToken()); // EOF-token } - return parseCommaSeparatedListOfComponentValues(tokens, { + const componentValuesLists = parseCommaSeparatedListOfComponentValues(tokens, { onParseError: onParseError, - }).map((componentValuesList) => { - return parseMediaQuery(componentValuesList); + }); + + return componentValuesLists.map((componentValuesList, index) => { + const mediaQuery = parseMediaQuery(componentValuesList); + if (mediaQuery == false && options?.preserveInvalidMediaQueries === true) { + return componentValuesLists[index].map((x) => x.toString()).join(''); + } + + return mediaQuery; }).filter((x) => !!x) as Array; } diff --git a/plugins/postcss-custom-media/src/transform-at-media-v2/always-true-or-false.ts b/plugins/postcss-custom-media/src/transform-at-media-v2/always-true-or-false.ts index 78d0c72b0..ec03674d1 100644 --- a/plugins/postcss-custom-media/src/transform-at-media-v2/always-true-or-false.ts +++ b/plugins/postcss-custom-media/src/transform-at-media-v2/always-true-or-false.ts @@ -4,11 +4,11 @@ import type { CSSToken } from '@csstools/css-tokenizer'; export const alwaysTrue: Array = [ [TokenType.Ident, 'max-color', 0, 0, { value: 'max-color' }], [TokenType.Colon, ':', 0, 0, undefined], - [TokenType.Number, '2147477350', 0, 0, { value: 9999943, type: NumberType.Integer }], + [TokenType.Number, '2147477350', 0, 0, { value: 2147477350, type: NumberType.Integer }], ]; export const neverTrue: Array = [ [TokenType.Ident, 'color', 0, 0, { value: 'color' }], [TokenType.Colon, ':', 0, 0, undefined], - [TokenType.Number, '2147477350', 0, 0, { value: 9999943, type: NumberType.Integer }], + [TokenType.Number, '2147477350', 0, 0, { value: 2147477350, type: NumberType.Integer }], ]; diff --git a/plugins/postcss-custom-media/src/transform-at-media-v2/custom-media.ts b/plugins/postcss-custom-media/src/transform-at-media-v2/custom-media.ts index fa72f20df..0f41cc979 100644 --- a/plugins/postcss-custom-media/src/transform-at-media-v2/custom-media.ts +++ b/plugins/postcss-custom-media/src/transform-at-media-v2/custom-media.ts @@ -1,6 +1,7 @@ import { stringify, TokenIdent, TokenType } from '@csstools/css-tokenizer'; -import { NodeType, parse, MediaCondition, MediaInParens, MediaQueryWithoutType, MediaQueryWithType, MediaNot, MediaConditionListWithAnd } from '@csstools/media-query-list-parser'; +import { NodeType, parse, MediaCondition, MediaInParens, MediaQueryWithoutType, MediaQueryWithType, MediaNot } from '@csstools/media-query-list-parser'; import { atMediaParamsTokens } from '../transform-at-media/at-media-params-tokens'; +import { replaceTrueAndFalseTokens } from './true-and-false'; export function parseCustomMedia(params: string): { name: string, truthy: string, falsy: string, dependsOn: Array<[string, string]> } | false { const tokens = atMediaParamsTokens(params); @@ -38,8 +39,10 @@ export function parseCustomMedia(params: string): { name: string, truthy: string } } - const mediaQueryListTruthy = parse(stringify(...remainder)); - const mediaQueryListFalsy = parse(stringify(...remainder)); + remainder = replaceTrueAndFalseTokens(remainder); + + const mediaQueryListTruthy = parse(stringify(...remainder), { preserveInvalidMediaQueries : true }); + const mediaQueryListFalsy = parse(stringify(...remainder), { preserveInvalidMediaQueries: true }); for (let i = 0; i < mediaQueryListFalsy.length; i++) { const mediaQuery = mediaQueryListFalsy[i]; @@ -70,11 +73,11 @@ export function parseCustomMedia(params: string): { name: string, truthy: string } if (mediaQuery.type === NodeType.MediaQueryWithoutType) { - const mediaCondition = (mediaQuery as MediaQueryWithoutType).media; + let mediaCondition = (mediaQuery as MediaQueryWithoutType).media; if (mediaCondition.media.type === NodeType.MediaNot) { const query = new MediaQueryWithoutType( new MediaCondition( - ((mediaQuery as MediaQueryWithoutType).media as MediaNot).media, + (mediaCondition.media as MediaNot).media, ), ); @@ -82,6 +85,21 @@ export function parseCustomMedia(params: string): { name: string, truthy: string continue; } + if (mediaCondition.media.type === NodeType.MediaConditionListWithOr) { + mediaCondition = new MediaCondition( + new MediaInParens( + mediaCondition, + [ + [TokenType.Whitespace, ' ', 0, 0, undefined], + [TokenType.OpenParen, '(', 0, 0, undefined], + ], + [ + [TokenType.CloseParen, ')', 0, 0, undefined], + ], + ), + ); + } + const query = new MediaQueryWithType( [ [TokenType.Ident, 'not', 0, 0, { value: 'not' }], diff --git a/plugins/postcss-custom-media/src/transform-at-media-v2/true-and-false.ts b/plugins/postcss-custom-media/src/transform-at-media-v2/true-and-false.ts index 46a8ec636..ebb56a460 100644 --- a/plugins/postcss-custom-media/src/transform-at-media-v2/true-and-false.ts +++ b/plugins/postcss-custom-media/src/transform-at-media-v2/true-and-false.ts @@ -52,6 +52,7 @@ export function replaceTrueAndFalseTokens(tokens: Array): Array): Array Date: Thu, 27 Oct 2022 12:38:11 +0200 Subject: [PATCH 25/35] allow parsing from tokens --- packages/media-query-list-parser/src/index.ts | 2 +- .../src/parser/parse.ts | 35 ++++++++++++------- .../src/transform-at-media-v2/custom-media.ts | 16 ++++++--- 3 files changed, 35 insertions(+), 18 deletions(-) diff --git a/packages/media-query-list-parser/src/index.ts b/packages/media-query-list-parser/src/index.ts index bb084e297..efc73b830 100644 --- a/packages/media-query-list-parser/src/index.ts +++ b/packages/media-query-list-parser/src/index.ts @@ -1,4 +1,4 @@ -export { parse } from './parser/parse'; +export { parse, parseFromTokens } from './parser/parse'; export { NodeType } from './nodes/node-type'; export { GeneralEnclosed } from './nodes/general-enclosed'; diff --git a/packages/media-query-list-parser/src/parser/parse.ts b/packages/media-query-list-parser/src/parser/parse.ts index eacf975fa..f32a184db 100644 --- a/packages/media-query-list-parser/src/parser/parse.ts +++ b/packages/media-query-list-parser/src/parser/parse.ts @@ -1,5 +1,5 @@ import { parseCommaSeparatedListOfComponentValues } from '@csstools/css-parser-algorithms'; -import { tokenizer } from '@csstools/css-tokenizer'; +import { CSSToken, stringify, tokenizer } from '@csstools/css-tokenizer'; import { MediaQueryWithoutType, MediaQueryWithType } from '../nodes/media-query'; import { parseMediaQuery } from './parse-media-query'; @@ -7,6 +7,26 @@ export type Options = { preserveInvalidMediaQueries?: boolean } +export function parseFromTokens(tokens: Array, options?: Options) { + const onParseError = (err) => { + console.warn(err); + throw new Error(`Unable to parse "${stringify(...tokens)}"`); + }; + + const componentValuesLists = parseCommaSeparatedListOfComponentValues(tokens, { + onParseError: onParseError, + }); + + return componentValuesLists.map((componentValuesList, index) => { + const mediaQuery = parseMediaQuery(componentValuesList); + if (mediaQuery == false && options?.preserveInvalidMediaQueries === true) { + return componentValuesLists[index].map((x) => x.toString()).join(''); + } + + return mediaQuery; + }).filter((x) => !!x) as Array; +} + export function parse(source: string, options?: Options) { const onParseError = (err) => { console.warn(err); @@ -27,16 +47,5 @@ export function parse(source: string, options?: Options) { tokens.push(t.nextToken()); // EOF-token } - const componentValuesLists = parseCommaSeparatedListOfComponentValues(tokens, { - onParseError: onParseError, - }); - - return componentValuesLists.map((componentValuesList, index) => { - const mediaQuery = parseMediaQuery(componentValuesList); - if (mediaQuery == false && options?.preserveInvalidMediaQueries === true) { - return componentValuesLists[index].map((x) => x.toString()).join(''); - } - - return mediaQuery; - }).filter((x) => !!x) as Array; + return parseFromTokens(tokens, options); } diff --git a/plugins/postcss-custom-media/src/transform-at-media-v2/custom-media.ts b/plugins/postcss-custom-media/src/transform-at-media-v2/custom-media.ts index 0f41cc979..3d271f4d5 100644 --- a/plugins/postcss-custom-media/src/transform-at-media-v2/custom-media.ts +++ b/plugins/postcss-custom-media/src/transform-at-media-v2/custom-media.ts @@ -1,5 +1,5 @@ -import { stringify, TokenIdent, TokenType } from '@csstools/css-tokenizer'; -import { NodeType, parse, MediaCondition, MediaInParens, MediaQueryWithoutType, MediaQueryWithType, MediaNot } from '@csstools/media-query-list-parser'; +import { CSSToken, stringify, TokenIdent, TokenType } from '@csstools/css-tokenizer'; +import { parseFromTokens, NodeType, MediaCondition, MediaInParens, MediaQueryWithoutType, MediaQueryWithType, MediaNot } from '@csstools/media-query-list-parser'; import { atMediaParamsTokens } from '../transform-at-media/at-media-params-tokens'; import { replaceTrueAndFalseTokens } from './true-and-false'; @@ -41,8 +41,8 @@ export function parseCustomMedia(params: string): { name: string, truthy: string remainder = replaceTrueAndFalseTokens(remainder); - const mediaQueryListTruthy = parse(stringify(...remainder), { preserveInvalidMediaQueries : true }); - const mediaQueryListFalsy = parse(stringify(...remainder), { preserveInvalidMediaQueries: true }); + const mediaQueryListTruthy = parseFromTokens(cloneTokens(remainder), { preserveInvalidMediaQueries : true }); + const mediaQueryListFalsy = parseFromTokens(cloneTokens(remainder), { preserveInvalidMediaQueries: true }); for (let i = 0; i < mediaQueryListFalsy.length; i++) { const mediaQuery = mediaQueryListFalsy[i]; @@ -129,3 +129,11 @@ export function parseCustomMedia(params: string): { name: string, truthy: string }), }; } + +function cloneTokens(tokens: Array): Array { + if ('structuredClone' in globalThis) { + return structuredClone(tokens); + } + + return JSON.parse(JSON.stringify(tokens)); +} From 0e52392390abbb7f8e3d1ea979d2930bb81dad43 Mon Sep 17 00:00:00 2001 From: Romain Menke Date: Tue, 1 Nov 2022 10:11:55 +0100 Subject: [PATCH 26/35] add some convenience methods --- packages/media-query-list-parser/src/index.ts | 2 +- .../src/nodes/media-feature.ts | 64 ++++++- .../src/parser/parse-media-query.ts | 8 +- .../test/cases/media-not/0001.expect.json | 40 ++--- .../test/cases/mf-boolean/0001.expect.json | 40 ++--- .../test/cases/mf-boolean/0002.expect.json | 66 ++++---- .../test/cases/mf-boolean/0003.expect.json | 40 ++--- .../test/cases/mf-boolean/0004.expect.json | 40 ++--- .../test/cases/mf-plain/0001.expect.json | 40 ++--- .../test/cases/mf-plain/0002.expect.json | 66 ++++---- .../test/cases/mf-plain/0003.expect.json | 40 ++--- .../test/cases/mf-range/0002.expect.json | 66 ++++---- .../test/cases/mf-range/0003.expect.json | 40 ++--- .../test/cases/mf-range/0004.expect.json | 40 ++--- .../test/cases/mf-range/0005.expect.json | 40 ++--- .../test/cases/mf-range/0006.expect.json | 40 ++--- .../test/cases/mf-range/0007.expect.json | 40 ++--- .../cases/query-with-type/0004.expect.json | 52 +++--- .../test/cases/various/0002.expect.json | 160 +++++++++--------- .../test/cases/various/0003.expect.json | 40 ++--- .../test/cases/various/0004.expect.json | 40 ++--- .../test/cases/various/0005.expect.json | 40 ++--- .../test/serialize/0001.mjs | 13 ++ .../media-query-list-parser/test/test.mjs | 2 + 24 files changed, 582 insertions(+), 477 deletions(-) create mode 100644 packages/media-query-list-parser/test/serialize/0001.mjs diff --git a/packages/media-query-list-parser/src/index.ts b/packages/media-query-list-parser/src/index.ts index efc73b830..38723ea11 100644 --- a/packages/media-query-list-parser/src/index.ts +++ b/packages/media-query-list-parser/src/index.ts @@ -5,7 +5,7 @@ export { GeneralEnclosed } from './nodes/general-enclosed'; export { MediaAnd } from './nodes/media-and'; export { MediaCondition } from './nodes/media-condition'; export { MediaConditionList, MediaConditionListWithAnd, MediaConditionListWithOr } from './nodes/media-condition-list'; -export { MediaFeature } from './nodes/media-feature'; +export { MediaFeature, newMediaFeatureBoolean, newMediaFeaturePlain } from './nodes/media-feature'; export { MediaFeatureBoolean } from './nodes/media-feature-boolean'; export { MediaFeatureName } from './nodes/media-feature-name'; export { MediaFeaturePlain } from './nodes/media-feature-plain'; diff --git a/packages/media-query-list-parser/src/nodes/media-feature.ts b/packages/media-query-list-parser/src/nodes/media-feature.ts index ede349b58..e284bb8c1 100644 --- a/packages/media-query-list-parser/src/nodes/media-feature.ts +++ b/packages/media-query-list-parser/src/nodes/media-feature.ts @@ -1,25 +1,35 @@ -import { SimpleBlockNode } from '@csstools/css-parser-algorithms'; -import { TokenType } from '@csstools/css-tokenizer'; +import { SimpleBlockNode, TokenNode } from '@csstools/css-parser-algorithms'; +import { CSSToken, stringify, TokenType } from '@csstools/css-tokenizer'; import { MediaFeatureBoolean, parseMediaFeatureBoolean } from './media-feature-boolean'; +import { MediaFeatureName } from './media-feature-name'; import { MediaFeaturePlain, MediaFeaturePlainWalkerEntry, MediaFeaturePlainWalkerParent, parseMediaFeaturePlain } from './media-feature-plain'; import { MediaFeatureRange, MediaFeatureRangeWalkerEntry, MediaFeatureRangeWalkerParent, parseMediaFeatureRange } from './media-feature-range'; +import { MediaFeatureValue } from './media-feature-value'; import { NodeType } from './node-type'; export class MediaFeature { type = NodeType.MediaFeature; feature: MediaFeaturePlain | MediaFeatureBoolean | MediaFeatureRange; + before: Array; + after: Array; - constructor(feature: MediaFeaturePlain | MediaFeatureBoolean | MediaFeatureRange) { + constructor(feature: MediaFeaturePlain | MediaFeatureBoolean | MediaFeatureRange, before: Array = [], after: Array = []) { this.feature = feature; + this.before = before; + this.after = after; } tokens() { - return this.feature.tokens(); + return [ + ...this.before, + ...this.feature.tokens(), + ...this.after, + ]; } toString() { - return this.feature.toString(); + return stringify(...this.before) + this.feature.toString() + stringify(...this.after); } indexOf(item: MediaFeaturePlain | MediaFeatureBoolean | MediaFeatureRange): number | string { @@ -50,6 +60,8 @@ export class MediaFeature { return { type: this.type, feature: this.feature.toJSON(), + before: this.before, + after: this.after, }; } } @@ -57,25 +69,59 @@ export class MediaFeature { export type MediaFeatureWalkerEntry = MediaFeaturePlainWalkerEntry | MediaFeatureRangeWalkerEntry | MediaFeaturePlain | MediaFeatureBoolean | MediaFeatureRange; export type MediaFeatureWalkerParent = MediaFeaturePlainWalkerParent | MediaFeatureRangeWalkerParent | MediaFeature; -export function parseMediaFeature(simpleBlock: SimpleBlockNode) { +export function parseMediaFeature(simpleBlock: SimpleBlockNode, before: Array = [], after: Array = []) { if (simpleBlock.startToken[0] !== TokenType.OpenParen) { return false; } const boolean = parseMediaFeatureBoolean(simpleBlock.value); if (boolean !== false) { - return new MediaFeature(boolean); + return new MediaFeature(boolean, before, after); } const plain = parseMediaFeaturePlain(simpleBlock.value); if (plain !== false) { - return new MediaFeature(plain); + return new MediaFeature(plain, before, after); } const range = parseMediaFeatureRange(simpleBlock.value); if (range !== false) { - return new MediaFeature(range); + return new MediaFeature(range, before, after); } return false; } + +export function newMediaFeatureBoolean(name: string) { + return new MediaFeature( + new MediaFeatureBoolean( + new TokenNode([TokenType.Ident, name, 0, 0, { value: name }]), + ), + [ + [TokenType.OpenParen, '(', 0, 0, undefined], + ], + [ + [TokenType.CloseParen, ')', 0, 0, undefined], + ], + ); +} + +export function newMediaFeaturePlain(name: string, ...value: Array) { + return new MediaFeature( + new MediaFeaturePlain( + new MediaFeatureName( + new TokenNode([TokenType.Ident, name, 0, 0, { value: name }]), + ), + [TokenType.Colon, ':', 0, 0, undefined], + new MediaFeatureValue( + value.map((x) => new TokenNode(x)), + ), + ), + [ + [TokenType.OpenParen, '(', 0, 0, undefined], + ], + [ + [TokenType.CloseParen, ')', 0, 0, undefined], + ], + ); +} diff --git a/packages/media-query-list-parser/src/parser/parse-media-query.ts b/packages/media-query-list-parser/src/parser/parse-media-query.ts index 87c3ca9c4..67ee772b9 100644 --- a/packages/media-query-list-parser/src/parser/parse-media-query.ts +++ b/packages/media-query-list-parser/src/parser/parse-media-query.ts @@ -316,9 +316,9 @@ export function parseMediaInParens(componentValues: Array) { }), ]; - const feature = parseMediaFeature(simpleBlock); + const feature = parseMediaFeature(simpleBlock, before, after); if (feature !== false) { - return new MediaInParens(feature, before, after); + return new MediaInParens(feature); } const condition = parseMediaCondition(simpleBlock.value); @@ -342,9 +342,9 @@ export function parseMediaInParensFromSimpleBlock(simpleBlock: SimpleBlockNode) return false; } - const feature = parseMediaFeature(simpleBlock); + const feature = parseMediaFeature(simpleBlock, [simpleBlock.startToken], [simpleBlock.endToken]); if (feature !== false) { - return new MediaInParens(feature, [simpleBlock.startToken], [simpleBlock.endToken]); + return new MediaInParens(feature); } const condition = parseMediaCondition(simpleBlock.value); diff --git a/packages/media-query-list-parser/test/cases/media-not/0001.expect.json b/packages/media-query-list-parser/test/cases/media-not/0001.expect.json index 5118ae6f3..5a9906847 100644 --- a/packages/media-query-list-parser/test/cases/media-not/0001.expect.json +++ b/packages/media-query-list-parser/test/cases/media-not/0001.expect.json @@ -42,26 +42,28 @@ } ] ] - } - }, - "before": [ - [ - "(-token", - "(", - 4, - 4, - null - ] - ], - "after": [ - [ - ")-token", - ")", - 10, - 10, - null + }, + "before": [ + [ + "(-token", + "(", + 4, + 4, + null + ] + ], + "after": [ + [ + ")-token", + ")", + 10, + 10, + null + ] ] - ] + }, + "before": [], + "after": [] } } } diff --git a/packages/media-query-list-parser/test/cases/mf-boolean/0001.expect.json b/packages/media-query-list-parser/test/cases/mf-boolean/0001.expect.json index 0cfc0c59b..af1643cf2 100644 --- a/packages/media-query-list-parser/test/cases/mf-boolean/0001.expect.json +++ b/packages/media-query-list-parser/test/cases/mf-boolean/0001.expect.json @@ -22,26 +22,28 @@ } ] ] - } - }, - "before": [ - [ - "(-token", - "(", - 0, - 0, - null - ] - ], - "after": [ - [ - ")-token", - ")", - 6, - 6, - null + }, + "before": [ + [ + "(-token", + "(", + 0, + 0, + null + ] + ], + "after": [ + [ + ")-token", + ")", + 6, + 6, + null + ] ] - ] + }, + "before": [], + "after": [] } } } diff --git a/packages/media-query-list-parser/test/cases/mf-boolean/0002.expect.json b/packages/media-query-list-parser/test/cases/mf-boolean/0002.expect.json index dfe2f14de..91c136e7d 100644 --- a/packages/media-query-list-parser/test/cases/mf-boolean/0002.expect.json +++ b/packages/media-query-list-parser/test/cases/mf-boolean/0002.expect.json @@ -36,40 +36,42 @@ null ] ] - } - }, - "before": [ - [ - "comment", - "/* comment 1 */", - 0, - 14, - null - ], - [ - "(-token", - "(", - 15, - 15, - null - ] - ], - "after": [ - [ - ")-token", - ")", - 51, - 51, - null + }, + "before": [ + [ + "comment", + "/* comment 1 */", + 0, + 14, + null + ], + [ + "(-token", + "(", + 15, + 15, + null + ] ], - [ - "comment", - "/* comment 4 */", - 52, - 66, - null + "after": [ + [ + ")-token", + ")", + 51, + 51, + null + ], + [ + "comment", + "/* comment 4 */", + 52, + 66, + null + ] ] - ] + }, + "before": [], + "after": [] } } } diff --git a/packages/media-query-list-parser/test/cases/mf-boolean/0003.expect.json b/packages/media-query-list-parser/test/cases/mf-boolean/0003.expect.json index 6d83ff6e8..af07020b7 100644 --- a/packages/media-query-list-parser/test/cases/mf-boolean/0003.expect.json +++ b/packages/media-query-list-parser/test/cases/mf-boolean/0003.expect.json @@ -22,26 +22,28 @@ } ] ] - } - }, - "before": [ - [ - "(-token", - "(", - 0, - 0, - null - ] - ], - "after": [ - [ - ")-token", - ")", - 5, - 5, - null + }, + "before": [ + [ + "(-token", + "(", + 0, + 0, + null + ] + ], + "after": [ + [ + ")-token", + ")", + 5, + 5, + null + ] ] - ] + }, + "before": [], + "after": [] } } } diff --git a/packages/media-query-list-parser/test/cases/mf-boolean/0004.expect.json b/packages/media-query-list-parser/test/cases/mf-boolean/0004.expect.json index 7970b3331..ec092ecf0 100644 --- a/packages/media-query-list-parser/test/cases/mf-boolean/0004.expect.json +++ b/packages/media-query-list-parser/test/cases/mf-boolean/0004.expect.json @@ -22,26 +22,28 @@ } ] ] - } - }, - "before": [ - [ - "(-token", - "(", - 0, - 0, - null - ] - ], - "after": [ - [ - ")-token", - ")", - 6, - 6, - null + }, + "before": [ + [ + "(-token", + "(", + 0, + 0, + null + ] + ], + "after": [ + [ + ")-token", + ")", + 6, + 6, + null + ] ] - ] + }, + "before": [], + "after": [] } } } diff --git a/packages/media-query-list-parser/test/cases/mf-plain/0001.expect.json b/packages/media-query-list-parser/test/cases/mf-plain/0001.expect.json index b1b67c7ca..7a0c3891e 100644 --- a/packages/media-query-list-parser/test/cases/mf-plain/0001.expect.json +++ b/packages/media-query-list-parser/test/cases/mf-plain/0001.expect.json @@ -100,26 +100,28 @@ } ] ] - } - }, - "before": [ - [ - "(-token", - "(", - 0, - 0, - null - ] - ], - "after": [ - [ - ")-token", - ")", - 17, - 17, - null + }, + "before": [ + [ + "(-token", + "(", + 0, + 0, + null + ] + ], + "after": [ + [ + ")-token", + ")", + 17, + 17, + null + ] ] - ] + }, + "before": [], + "after": [] } } } diff --git a/packages/media-query-list-parser/test/cases/mf-plain/0002.expect.json b/packages/media-query-list-parser/test/cases/mf-plain/0002.expect.json index 276dc2dec..dbcb81161 100644 --- a/packages/media-query-list-parser/test/cases/mf-plain/0002.expect.json +++ b/packages/media-query-list-parser/test/cases/mf-plain/0002.expect.json @@ -142,40 +142,42 @@ null ] ] - } - }, - "before": [ - [ - "comment", - "/* comment 1 */", - 0, - 14, - null - ], - [ - "(-token", - "(", - 15, - 15, - null - ] - ], - "after": [ - [ - ")-token", - ")", - 91, - 91, - null + }, + "before": [ + [ + "comment", + "/* comment 1 */", + 0, + 14, + null + ], + [ + "(-token", + "(", + 15, + 15, + null + ] ], - [ - "comment", - "/* comment 6 */", - 92, - 106, - null + "after": [ + [ + ")-token", + ")", + 91, + 91, + null + ], + [ + "comment", + "/* comment 6 */", + 92, + 106, + null + ] ] - ] + }, + "before": [], + "after": [] } } } diff --git a/packages/media-query-list-parser/test/cases/mf-plain/0003.expect.json b/packages/media-query-list-parser/test/cases/mf-plain/0003.expect.json index e0c959e8d..1eb0b3ce3 100644 --- a/packages/media-query-list-parser/test/cases/mf-plain/0003.expect.json +++ b/packages/media-query-list-parser/test/cases/mf-plain/0003.expect.json @@ -94,26 +94,28 @@ } ] ] - } - }, - "before": [ - [ - "(-token", - "(", - 0, - 0, - null - ] - ], - "after": [ - [ - ")-token", - ")", - 21, - 21, - null + }, + "before": [ + [ + "(-token", + "(", + 0, + 0, + null + ] + ], + "after": [ + [ + ")-token", + ")", + 21, + 21, + null + ] ] - ] + }, + "before": [], + "after": [] } } } diff --git a/packages/media-query-list-parser/test/cases/mf-range/0002.expect.json b/packages/media-query-list-parser/test/cases/mf-range/0002.expect.json index b201baf5c..00ce71e31 100644 --- a/packages/media-query-list-parser/test/cases/mf-range/0002.expect.json +++ b/packages/media-query-list-parser/test/cases/mf-range/0002.expect.json @@ -144,40 +144,42 @@ null ] ] - } - }, - "before": [ - [ - "comment", - "/* comment 1 */", - 0, - 14, - null - ], - [ - "(-token", - "(", - 15, - 15, - null - ] - ], - "after": [ - [ - ")-token", - ")", - 86, - 86, - null + }, + "before": [ + [ + "comment", + "/* comment 1 */", + 0, + 14, + null + ], + [ + "(-token", + "(", + 15, + 15, + null + ] ], - [ - "comment", - "/* comment 6 */", - 87, - 101, - null + "after": [ + [ + ")-token", + ")", + 86, + 86, + null + ], + [ + "comment", + "/* comment 6 */", + 87, + 101, + null + ] ] - ] + }, + "before": [], + "after": [] } } } diff --git a/packages/media-query-list-parser/test/cases/mf-range/0003.expect.json b/packages/media-query-list-parser/test/cases/mf-range/0003.expect.json index 9fb0947b6..b509b256c 100644 --- a/packages/media-query-list-parser/test/cases/mf-range/0003.expect.json +++ b/packages/media-query-list-parser/test/cases/mf-range/0003.expect.json @@ -460,26 +460,28 @@ null ] ] - } - }, - "before": [ - [ - "(-token", - "(", - 0, - 0, - null - ] - ], - "after": [ - [ - ")-token", - ")", - 31, - 31, - null + }, + "before": [ + [ + "(-token", + "(", + 0, + 0, + null + ] + ], + "after": [ + [ + ")-token", + ")", + 31, + 31, + null + ] ] - ] + }, + "before": [], + "after": [] } } } diff --git a/packages/media-query-list-parser/test/cases/mf-range/0004.expect.json b/packages/media-query-list-parser/test/cases/mf-range/0004.expect.json index 53882e943..8990ac58b 100644 --- a/packages/media-query-list-parser/test/cases/mf-range/0004.expect.json +++ b/packages/media-query-list-parser/test/cases/mf-range/0004.expect.json @@ -272,26 +272,28 @@ } ] ] - } - }, - "before": [ - [ - "(-token", - "(", - 0, - 0, - null - ] - ], - "after": [ - [ - ")-token", - ")", - 21, - 21, - null + }, + "before": [ + [ + "(-token", + "(", + 0, + 0, + null + ] + ], + "after": [ + [ + ")-token", + ")", + 21, + 21, + null + ] ] - ] + }, + "before": [], + "after": [] } } } diff --git a/packages/media-query-list-parser/test/cases/mf-range/0005.expect.json b/packages/media-query-list-parser/test/cases/mf-range/0005.expect.json index 5cf47e681..716ff79b4 100644 --- a/packages/media-query-list-parser/test/cases/mf-range/0005.expect.json +++ b/packages/media-query-list-parser/test/cases/mf-range/0005.expect.json @@ -111,26 +111,28 @@ } ] ] - } - }, - "before": [ - [ - "(-token", - "(", - 0, - 0, - null - ] - ], - "after": [ - [ - ")-token", - ")", - 13, - 13, - null + }, + "before": [ + [ + "(-token", + "(", + 0, + 0, + null + ] + ], + "after": [ + [ + ")-token", + ")", + 13, + 13, + null + ] ] - ] + }, + "before": [], + "after": [] } } } diff --git a/packages/media-query-list-parser/test/cases/mf-range/0006.expect.json b/packages/media-query-list-parser/test/cases/mf-range/0006.expect.json index bcb38e6e5..a91c89edc 100644 --- a/packages/media-query-list-parser/test/cases/mf-range/0006.expect.json +++ b/packages/media-query-list-parser/test/cases/mf-range/0006.expect.json @@ -111,26 +111,28 @@ } ] ] - } - }, - "before": [ - [ - "(-token", - "(", - 0, - 0, - null - ] - ], - "after": [ - [ - ")-token", - ")", - 13, - 13, - null + }, + "before": [ + [ + "(-token", + "(", + 0, + 0, + null + ] + ], + "after": [ + [ + ")-token", + ")", + 13, + 13, + null + ] ] - ] + }, + "before": [], + "after": [] } } } diff --git a/packages/media-query-list-parser/test/cases/mf-range/0007.expect.json b/packages/media-query-list-parser/test/cases/mf-range/0007.expect.json index 5610f771b..2f4ab2f30 100644 --- a/packages/media-query-list-parser/test/cases/mf-range/0007.expect.json +++ b/packages/media-query-list-parser/test/cases/mf-range/0007.expect.json @@ -196,26 +196,28 @@ } ] ] - } - }, - "before": [ - [ - "(-token", - "(", - 0, - 0, - null - ] - ], - "after": [ - [ - ")-token", - ")", - 24, - 24, - null + }, + "before": [ + [ + "(-token", + "(", + 0, + 0, + null + ] + ], + "after": [ + [ + ")-token", + ")", + 24, + 24, + null + ] ] - ] + }, + "before": [], + "after": [] } } } diff --git a/packages/media-query-list-parser/test/cases/query-with-type/0004.expect.json b/packages/media-query-list-parser/test/cases/query-with-type/0004.expect.json index 4e98e6776..c1793f870 100644 --- a/packages/media-query-list-parser/test/cases/query-with-type/0004.expect.json +++ b/packages/media-query-list-parser/test/cases/query-with-type/0004.expect.json @@ -147,33 +147,35 @@ } ] ] - } - }, - "before": [ - [ - "whitespace-token", - " ", - 15, - 15, - null + }, + "before": [ + [ + "whitespace-token", + " ", + 15, + 15, + null + ], + [ + "(-token", + "(", + 16, + 16, + null + ] ], - [ - "(-token", - "(", - 16, - 16, - null - ] - ], - "after": [ - [ - ")-token", - ")", - 33, - 33, - null + "after": [ + [ + ")-token", + ")", + 33, + 33, + null + ] ] - ] + }, + "before": [], + "after": [] } } } diff --git a/packages/media-query-list-parser/test/cases/various/0002.expect.json b/packages/media-query-list-parser/test/cases/various/0002.expect.json index 6199b1d03..7d8dd3926 100644 --- a/packages/media-query-list-parser/test/cases/various/0002.expect.json +++ b/packages/media-query-list-parser/test/cases/various/0002.expect.json @@ -149,26 +149,28 @@ } ] ] - } - }, - "before": [ - [ - "(-token", - "(", - 15, - 15, - null - ] - ], - "after": [ - [ - ")-token", - ")", - 32, - 32, - null + }, + "before": [ + [ + "(-token", + "(", + 15, + 15, + null + ] + ], + "after": [ + [ + ")-token", + ")", + 32, + 32, + null + ] ] - ] + }, + "before": [], + "after": [] }, "list": [ { @@ -288,26 +290,28 @@ } ] ] - } - }, - "before": [ - [ - "(-token", - "(", - 38, - 38, - null - ] - ], - "after": [ - [ - ")-token", - ")", - 79, - 79, - null + }, + "before": [ + [ + "(-token", + "(", + 38, + 38, + null + ] + ], + "after": [ + [ + ")-token", + ")", + 79, + 79, + null + ] ] - ] + }, + "before": [], + "after": [] } }, { @@ -449,26 +453,28 @@ } ] ] - } - }, - "before": [ - [ - "(-token", - "(", - 85, - 85, - null - ] - ], - "after": [ - [ - ")-token", - ")", - 98, - 98, - null + }, + "before": [ + [ + "(-token", + "(", + 85, + 85, + null + ] + ], + "after": [ + [ + ")-token", + ")", + 98, + 98, + null + ] ] - ] + }, + "before": [], + "after": [] } }, { @@ -690,26 +696,28 @@ } ] ] - } - }, - "before": [ - [ - "(-token", - "(", - 104, - 104, - null - ] - ], - "after": [ - [ - ")-token", - ")", - 125, - 125, - null + }, + "before": [ + [ + "(-token", + "(", + 104, + 104, + null + ] + ], + "after": [ + [ + ")-token", + ")", + 125, + 125, + null + ] ] - ] + }, + "before": [], + "after": [] } } ], diff --git a/packages/media-query-list-parser/test/cases/various/0003.expect.json b/packages/media-query-list-parser/test/cases/various/0003.expect.json index 60a2e8b79..227fdaa80 100644 --- a/packages/media-query-list-parser/test/cases/various/0003.expect.json +++ b/packages/media-query-list-parser/test/cases/various/0003.expect.json @@ -112,26 +112,28 @@ } ] ] - } - }, - "before": [ - [ - "(-token", - "(", - 0, - 0, - null - ] - ], - "after": [ - [ - ")-token", - ")", - 22, - 22, - null + }, + "before": [ + [ + "(-token", + "(", + 0, + 0, + null + ] + ], + "after": [ + [ + ")-token", + ")", + 22, + 22, + null + ] ] - ] + }, + "before": [], + "after": [] }, "list": [ { diff --git a/packages/media-query-list-parser/test/cases/various/0004.expect.json b/packages/media-query-list-parser/test/cases/various/0004.expect.json index 0933ba63b..5288c94e4 100644 --- a/packages/media-query-list-parser/test/cases/various/0004.expect.json +++ b/packages/media-query-list-parser/test/cases/various/0004.expect.json @@ -339,26 +339,28 @@ null ] ] - } - }, - "before": [ - [ - "(-token", - "(", - 0, - 0, - null - ] - ], - "after": [ - [ - ")-token", - ")", - 26, - 26, - null + }, + "before": [ + [ + "(-token", + "(", + 0, + 0, + null + ] + ], + "after": [ + [ + ")-token", + ")", + 26, + 26, + null + ] ] - ] + }, + "before": [], + "after": [] } } } diff --git a/packages/media-query-list-parser/test/cases/various/0005.expect.json b/packages/media-query-list-parser/test/cases/various/0005.expect.json index c2e4266f2..a954c0d33 100644 --- a/packages/media-query-list-parser/test/cases/various/0005.expect.json +++ b/packages/media-query-list-parser/test/cases/various/0005.expect.json @@ -157,26 +157,28 @@ } ] ] - } - }, - "before": [ - [ - "(-token", - "(", - 15, - 15, - null - ] - ], - "after": [ - [ - ")-token", - ")", - 32, - 32, - null + }, + "before": [ + [ + "(-token", + "(", + 15, + 15, + null + ] + ], + "after": [ + [ + ")-token", + ")", + 32, + 32, + null + ] ] - ] + }, + "before": [], + "after": [] } } } diff --git a/packages/media-query-list-parser/test/serialize/0001.mjs b/packages/media-query-list-parser/test/serialize/0001.mjs new file mode 100644 index 000000000..d1f41f3fe --- /dev/null +++ b/packages/media-query-list-parser/test/serialize/0001.mjs @@ -0,0 +1,13 @@ +import assert from 'assert'; +import { newMediaFeatureBoolean, newMediaFeaturePlain } from '@csstools/media-query-list-parser'; +import { TokenType } from '@csstools/css-tokenizer'; + +assert.strictEqual( + newMediaFeaturePlain('min-width', [TokenType.Dimension, '300px', 0, 0, { value: 300, unit: 'px' }]).toString(), + '(min-width:300px)', +); + +assert.strictEqual( + newMediaFeatureBoolean('color').toString(), + '(color)', +); diff --git a/packages/media-query-list-parser/test/test.mjs b/packages/media-query-list-parser/test/test.mjs index 20a419ccb..3184a4e50 100644 --- a/packages/media-query-list-parser/test/test.mjs +++ b/packages/media-query-list-parser/test/test.mjs @@ -31,3 +31,5 @@ import './cases/various/0003.mjs'; import './cases/various/0004.mjs'; import './cases/various/0005.mjs'; import './cases/various/0006.mjs'; + +import './serialize/0001.mjs'; From d98c8832615f310e35644120499052c9a8dbb711 Mon Sep 17 00:00:00 2001 From: Romain Menke Date: Tue, 1 Nov 2022 12:16:34 +0100 Subject: [PATCH 27/35] fixes --- .../consume-component-block-function.ts | 28 ++++----- packages/css-parser-algorithms/src/index.ts | 1 + packages/media-query-list-parser/src/index.ts | 2 +- .../src/nodes/general-enclosed.ts | 5 +- .../src/nodes/media-and.ts | 4 +- .../src/nodes/media-condition-list.ts | 12 ++-- .../src/nodes/media-condition.ts | 5 +- .../src/nodes/media-feature-name.ts | 4 +- .../src/nodes/media-feature-plain.ts | 4 +- .../src/nodes/media-feature-range.ts | 12 ++-- .../src/nodes/media-feature-value.ts | 4 +- .../src/nodes/media-feature.ts | 4 +- .../src/nodes/media-in-parens.ts | 4 +- .../src/nodes/media-not.ts | 4 +- .../src/nodes/media-or.ts | 4 +- .../src/nodes/media-query.ts | 62 ++++++++++++++++++- .../src/nodes/node-type.ts | 1 + .../src/parser/parse.ts | 25 +++----- plugins/postcss-custom-media/src/index.ts | 2 +- .../split-media-query-list.ts | 60 ++---------------- 20 files changed, 124 insertions(+), 123 deletions(-) diff --git a/packages/css-parser-algorithms/src/consume/consume-component-block-function.ts b/packages/css-parser-algorithms/src/consume/consume-component-block-function.ts index 351bb1546..fe3b3010e 100644 --- a/packages/css-parser-algorithms/src/consume/consume-component-block-function.ts +++ b/packages/css-parser-algorithms/src/consume/consume-component-block-function.ts @@ -82,7 +82,7 @@ export class FunctionNode { return this.name[4].value; } - tokens() { + tokens(): Array { return [ this.name, ...this.value.flatMap((x) => { @@ -96,7 +96,7 @@ export class FunctionNode { ]; } - toString() { + toString(): string { const valueString = this.value.map((x) => { if (isToken(x)) { return stringify(x); @@ -216,7 +216,7 @@ export class SimpleBlockNode { this.value = value; } - tokens() { + tokens(): Array { return [ this.startToken, ...this.value.flatMap((x) => { @@ -230,7 +230,7 @@ export class SimpleBlockNode { ]; } - toString() { + toString(): string { const valueString = this.value.map((x) => { if (isToken(x)) { return stringify(x); @@ -351,11 +351,11 @@ export class WhitespaceNode { this.value = value; } - tokens() { + tokens(): Array { return this.value; } - toString() { + toString(): string { return stringify(...this.value); } @@ -393,13 +393,13 @@ export class CommentNode { this.value = value; } - tokens() { + tokens(): Array { return [ this.value, ]; } - toString() { + toString(): string { return stringify(this.value); } @@ -454,13 +454,13 @@ export class TokenNode { this.value = value; } - tokens() { + tokens(): Array { return [ this.value, ]; } - toString() { + toString(): string { return stringify(this.value); } @@ -481,11 +481,11 @@ export class UnclosedFunctionNode { this.value = value; } - tokens() { + tokens(): Array { return this.value; } - toString() { + toString(): string { return stringify(...this.value); } @@ -506,11 +506,11 @@ export class UnclosedSimpleBlockNode { this.value = value; } - tokens() { + tokens(): Array { return this.value; } - toString() { + toString(): string { return stringify(...this.value); } diff --git a/packages/css-parser-algorithms/src/index.ts b/packages/css-parser-algorithms/src/index.ts index ed3490189..e45567959 100644 --- a/packages/css-parser-algorithms/src/index.ts +++ b/packages/css-parser-algorithms/src/index.ts @@ -3,3 +3,4 @@ export { parseComponentValue } from './parse/parse-component-value'; export { parseListOfComponentValues } from './parse/parse-list-of-component-values'; export { parseCommaSeparatedListOfComponentValues } from './parse/parse-comma-separated-list-of-component-values'; export { gatherNodeAncestry } from './util/node-ancestry'; +export { ParserError } from './interfaces/error'; diff --git a/packages/media-query-list-parser/src/index.ts b/packages/media-query-list-parser/src/index.ts index 38723ea11..295793df9 100644 --- a/packages/media-query-list-parser/src/index.ts +++ b/packages/media-query-list-parser/src/index.ts @@ -14,7 +14,7 @@ export { MediaFeatureValue } from './nodes/media-feature-value'; export { MediaInParens } from './nodes/media-in-parens'; export { MediaNot } from './nodes/media-not'; export { MediaOr } from './nodes/media-or'; -export { MediaQuery, MediaQueryWithType, MediaQueryWithoutType } from './nodes/media-query'; +export { MediaQuery, MediaQueryWithType, MediaQueryWithoutType, MediaQueryInvalid } from './nodes/media-query'; export { MediaFeatureComparison, MediaFeatureEQ, MediaFeatureGT, MediaFeatureLT, matchesComparison, comparisonFromTokens } from './nodes/media-feature-comparison'; export { MediaType, typeFromToken } from './nodes/media-type'; diff --git a/packages/media-query-list-parser/src/nodes/general-enclosed.ts b/packages/media-query-list-parser/src/nodes/general-enclosed.ts index ebec87307..061e826ac 100644 --- a/packages/media-query-list-parser/src/nodes/general-enclosed.ts +++ b/packages/media-query-list-parser/src/nodes/general-enclosed.ts @@ -1,4 +1,5 @@ import { ComponentValue, ContainerNode } from '@csstools/css-parser-algorithms'; +import { CSSToken } from '@csstools/css-tokenizer'; import { NodeType } from './node-type'; export class GeneralEnclosed { @@ -10,11 +11,11 @@ export class GeneralEnclosed { this.value = value; } - tokens() { + tokens(): Array { return this.value.tokens(); } - toString() { + toString(): string { return this.value.toString(); } diff --git a/packages/media-query-list-parser/src/nodes/media-and.ts b/packages/media-query-list-parser/src/nodes/media-and.ts index e20197034..42433a4bd 100644 --- a/packages/media-query-list-parser/src/nodes/media-and.ts +++ b/packages/media-query-list-parser/src/nodes/media-and.ts @@ -13,14 +13,14 @@ export class MediaAnd { this.media = media; } - tokens() { + tokens(): Array { return [ ...this.modifier, ...this.media.tokens(), ]; } - toString() { + toString(): string { return stringify(...this.modifier) + this.media.toString(); } diff --git a/packages/media-query-list-parser/src/nodes/media-condition-list.ts b/packages/media-query-list-parser/src/nodes/media-condition-list.ts index 733760ff3..d88bbc14c 100644 --- a/packages/media-query-list-parser/src/nodes/media-condition-list.ts +++ b/packages/media-query-list-parser/src/nodes/media-condition-list.ts @@ -21,16 +21,16 @@ export class MediaConditionListWithAnd { this.after = after; } - tokens() { + tokens(): Array { return [ ...this.before, - this.leading.tokens(), + ...this.leading.tokens(), ...this.list.flatMap((item) => item.tokens()), ...this.after, ]; } - toString() { + toString(): string { return stringify(...this.before) + this.leading.toString() + this.list.map((item) => item.toString()).join('') + stringify(...this.after); } @@ -124,16 +124,16 @@ export class MediaConditionListWithOr { this.after = after; } - tokens() { + tokens(): Array { return [ ...this.before, - this.leading.tokens(), + ...this.leading.tokens(), ...this.list.flatMap((item) => item.tokens()), ...this.after, ]; } - toString() { + toString(): string { return stringify(...this.before) + this.leading.toString() + this.list.map((item) => item.toString()).join('') + stringify(...this.after); } diff --git a/packages/media-query-list-parser/src/nodes/media-condition.ts b/packages/media-query-list-parser/src/nodes/media-condition.ts index 7a2a8109c..a05277a0e 100644 --- a/packages/media-query-list-parser/src/nodes/media-condition.ts +++ b/packages/media-query-list-parser/src/nodes/media-condition.ts @@ -1,3 +1,4 @@ +import { CSSToken } from '@csstools/css-tokenizer'; import { MediaConditionListWithAnd, MediaConditionListWithAndWalkerEntry, MediaConditionListWithAndWalkerParent, MediaConditionListWithOr, MediaConditionListWithOrWalkerEntry, MediaConditionListWithOrWalkerParent } from './media-condition-list'; import { MediaInParens } from './media-in-parens'; import { MediaNot, MediaNotWalkerEntry, MediaNotWalkerParent } from './media-not'; @@ -12,11 +13,11 @@ export class MediaCondition { this.media = media; } - tokens() { + tokens(): Array { return this.media.tokens(); } - toString() { + toString(): string { return this.media.toString(); } diff --git a/packages/media-query-list-parser/src/nodes/media-feature-name.ts b/packages/media-query-list-parser/src/nodes/media-feature-name.ts index d74ae4b72..7c52f6ea7 100644 --- a/packages/media-query-list-parser/src/nodes/media-feature-name.ts +++ b/packages/media-query-list-parser/src/nodes/media-feature-name.ts @@ -21,7 +21,7 @@ export class MediaFeatureName { return token[4].value; } - tokens() { + tokens(): Array { return [ ...this.before, ...this.name.tokens(), @@ -29,7 +29,7 @@ export class MediaFeatureName { ]; } - toString() { + toString(): string { return stringify(...this.before) + this.name.toString() + stringify(...this.after); } diff --git a/packages/media-query-list-parser/src/nodes/media-feature-plain.ts b/packages/media-query-list-parser/src/nodes/media-feature-plain.ts index ddd1c5f4c..bfe1a74bf 100644 --- a/packages/media-query-list-parser/src/nodes/media-feature-plain.ts +++ b/packages/media-query-list-parser/src/nodes/media-feature-plain.ts @@ -17,7 +17,7 @@ export class MediaFeaturePlain { this.value = value; } - tokens() { + tokens(): Array { return [ ...this.name.tokens(), this.colon, @@ -25,7 +25,7 @@ export class MediaFeaturePlain { ]; } - toString() { + toString(): string { return this.name.toString() + stringify(this.colon) + this.value.toString(); } diff --git a/packages/media-query-list-parser/src/nodes/media-feature-range.ts b/packages/media-query-list-parser/src/nodes/media-feature-range.ts index fefb7c22d..c0b0307fa 100644 --- a/packages/media-query-list-parser/src/nodes/media-feature-range.ts +++ b/packages/media-query-list-parser/src/nodes/media-feature-range.ts @@ -26,7 +26,7 @@ export class MediaFeatureRangeNameValue { return comparisonFromTokens(this.operator); } - tokens() { + tokens(): Array { return [ ...this.name.tokens(), ...this.operator, @@ -34,7 +34,7 @@ export class MediaFeatureRangeNameValue { ]; } - toString() { + toString(): string { return this.name.toString() + stringify(...this.operator) + this.value.toString(); } @@ -97,7 +97,7 @@ export class MediaFeatureRangeValueName { return comparisonFromTokens(this.operator); } - tokens() { + tokens(): Array { return [ ...this.value.tokens(), ...this.operator, @@ -105,7 +105,7 @@ export class MediaFeatureRangeValueName { ]; } - toString() { + toString(): string { return this.value.toString() + stringify(...this.operator) + this.name.toString(); } @@ -176,7 +176,7 @@ export class MediaFeatureRangeValueNameValue { return comparisonFromTokens(this.valueTwoOperator); } - tokens() { + tokens(): Array { return [ ...this.valueOne.tokens(), ...this.valueOneOperator, @@ -186,7 +186,7 @@ export class MediaFeatureRangeValueNameValue { ]; } - toString() { + toString(): string { return this.valueOne.toString() + stringify(...this.valueOneOperator) + this.name.toString() + stringify(...this.valueTwoOperator) + this.valueTwo.toString(); } diff --git a/packages/media-query-list-parser/src/nodes/media-feature-value.ts b/packages/media-query-list-parser/src/nodes/media-feature-value.ts index cbd54b3c8..66c163864 100644 --- a/packages/media-query-list-parser/src/nodes/media-feature-value.ts +++ b/packages/media-query-list-parser/src/nodes/media-feature-value.ts @@ -21,7 +21,7 @@ export class MediaFeatureValue { this.after = after; } - tokens() { + tokens(): Array { if (Array.isArray(this.value)) { return [ ...this.before, @@ -37,7 +37,7 @@ export class MediaFeatureValue { ]; } - toString() { + toString(): string { if (Array.isArray(this.value)) { return stringify(...this.before) + this.value.map((x) => x.toString()).join('') + stringify(...this.after); } diff --git a/packages/media-query-list-parser/src/nodes/media-feature.ts b/packages/media-query-list-parser/src/nodes/media-feature.ts index e284bb8c1..84a7560c9 100644 --- a/packages/media-query-list-parser/src/nodes/media-feature.ts +++ b/packages/media-query-list-parser/src/nodes/media-feature.ts @@ -20,7 +20,7 @@ export class MediaFeature { this.after = after; } - tokens() { + tokens(): Array { return [ ...this.before, ...this.feature.tokens(), @@ -28,7 +28,7 @@ export class MediaFeature { ]; } - toString() { + toString(): string { return stringify(...this.before) + this.feature.toString() + stringify(...this.after); } diff --git a/packages/media-query-list-parser/src/nodes/media-in-parens.ts b/packages/media-query-list-parser/src/nodes/media-in-parens.ts index df76e8b0b..0fff4a15d 100644 --- a/packages/media-query-list-parser/src/nodes/media-in-parens.ts +++ b/packages/media-query-list-parser/src/nodes/media-in-parens.ts @@ -25,7 +25,7 @@ export class MediaInParens { this.after = after; } - tokens() { + tokens(): Array { return [ ...this.before, ...this.media.tokens(), @@ -33,7 +33,7 @@ export class MediaInParens { ]; } - toString() { + toString(): string { return stringify(...this.before) + this.media.toString() + stringify(...this.after); } diff --git a/packages/media-query-list-parser/src/nodes/media-not.ts b/packages/media-query-list-parser/src/nodes/media-not.ts index e6cff1139..bddc765d1 100644 --- a/packages/media-query-list-parser/src/nodes/media-not.ts +++ b/packages/media-query-list-parser/src/nodes/media-not.ts @@ -13,14 +13,14 @@ export class MediaNot { this.media = media; } - tokens() { + tokens(): Array { return [ ...this.modifier, ...this.media.tokens(), ]; } - toString() { + toString(): string { return stringify(...this.modifier) + this.media.toString(); } diff --git a/packages/media-query-list-parser/src/nodes/media-or.ts b/packages/media-query-list-parser/src/nodes/media-or.ts index 32412816f..633578b62 100644 --- a/packages/media-query-list-parser/src/nodes/media-or.ts +++ b/packages/media-query-list-parser/src/nodes/media-or.ts @@ -13,14 +13,14 @@ export class MediaOr { this.media = media; } - tokens() { + tokens(): Array { return [ ...this.modifier, ...this.media.tokens(), ]; } - toString() { + toString(): string { return stringify(...this.modifier) + this.media.toString(); } diff --git a/packages/media-query-list-parser/src/nodes/media-query.ts b/packages/media-query-list-parser/src/nodes/media-query.ts index 9840941f1..289f5f955 100644 --- a/packages/media-query-list-parser/src/nodes/media-query.ts +++ b/packages/media-query-list-parser/src/nodes/media-query.ts @@ -1,8 +1,9 @@ +import { ComponentValue } from '@csstools/css-parser-algorithms'; import { CSSToken, stringify } from '@csstools/css-tokenizer'; import { MediaCondition, MediaConditionWalkerEntry, MediaConditionWalkerParent } from './media-condition'; import { NodeType } from './node-type'; -export type MediaQuery = MediaQueryWithType | MediaQueryWithoutType; +export type MediaQuery = MediaQueryWithType | MediaQueryWithoutType | MediaQueryInvalid; export class MediaQueryWithType { type = NodeType.MediaQueryWithType; @@ -92,11 +93,11 @@ export class MediaQueryWithoutType { this.media = media; } - tokens() { + tokens(): Array { return this.media.tokens(); } - toString() { + toString(): string { return this.media.toString(); } @@ -133,3 +134,58 @@ export class MediaQueryWithoutType { export type MediaQueryWithoutTypeWalkerEntry = MediaConditionWalkerEntry | MediaCondition; export type MediaQueryWithoutTypeWalkerParent = MediaConditionWalkerParent | MediaQueryWithoutType; + +export class MediaQueryInvalid { + type = NodeType.MediaQueryInvalid; + + media: Array; + + constructor(media: Array) { + this.media = media; + } + + tokens(): Array { + return this.media.flatMap((x) => x.tokens()); + } + + toString(): string { + return this.media.map((x) => x.toString()).join(''); + } + + walk(cb: (entry: { node: MediaQueryInvalidWalkerEntry, parent: MediaQueryInvalidWalkerParent }, index: number | string) => boolean | void) { + let aborted = false; + + this.media.forEach((child, index) => { + if (aborted) { + return; + } + + if (cb({ node: child, parent: this }, index) === false) { + aborted = true; + return; + } + + if ('walk' in child) { + if (child.walk(cb) === false) { + aborted = true; + return; + } + } + }); + + if (aborted) { + return false; + } + } + + toJSON() { + return { + type: this.type, + string: this.toString(), + media: this.media, + }; + } +} + +export type MediaQueryInvalidWalkerEntry = ComponentValue; +export type MediaQueryInvalidWalkerParent = ComponentValue | MediaQueryInvalid; diff --git a/packages/media-query-list-parser/src/nodes/node-type.ts b/packages/media-query-list-parser/src/nodes/node-type.ts index 66db2c079..dd8fd4374 100644 --- a/packages/media-query-list-parser/src/nodes/node-type.ts +++ b/packages/media-query-list-parser/src/nodes/node-type.ts @@ -17,4 +17,5 @@ export enum NodeType { MediaOr = 'media-or', MediaQueryWithType = 'media-query-with-type', MediaQueryWithoutType = 'media-query-without-type', + MediaQueryInvalid = 'media-query-invalid', } diff --git a/packages/media-query-list-parser/src/parser/parse.ts b/packages/media-query-list-parser/src/parser/parse.ts index f32a184db..950ed7153 100644 --- a/packages/media-query-list-parser/src/parser/parse.ts +++ b/packages/media-query-list-parser/src/parser/parse.ts @@ -1,40 +1,33 @@ import { parseCommaSeparatedListOfComponentValues } from '@csstools/css-parser-algorithms'; -import { CSSToken, stringify, tokenizer } from '@csstools/css-tokenizer'; -import { MediaQueryWithoutType, MediaQueryWithType } from '../nodes/media-query'; +import { ParserError } from '@csstools/css-parser-algorithms/dist/interfaces/error'; +import { CSSToken, tokenizer } from '@csstools/css-tokenizer'; +import { MediaQuery, MediaQueryInvalid } from '../nodes/media-query'; import { parseMediaQuery } from './parse-media-query'; export type Options = { - preserveInvalidMediaQueries?: boolean + preserveInvalidMediaQueries?: boolean, + onParseError?: (error: ParserError) => void } export function parseFromTokens(tokens: Array, options?: Options) { - const onParseError = (err) => { - console.warn(err); - throw new Error(`Unable to parse "${stringify(...tokens)}"`); - }; - const componentValuesLists = parseCommaSeparatedListOfComponentValues(tokens, { - onParseError: onParseError, + onParseError: options?.onParseError, }); return componentValuesLists.map((componentValuesList, index) => { const mediaQuery = parseMediaQuery(componentValuesList); if (mediaQuery == false && options?.preserveInvalidMediaQueries === true) { - return componentValuesLists[index].map((x) => x.toString()).join(''); + return new MediaQueryInvalid(componentValuesLists[index]); } return mediaQuery; - }).filter((x) => !!x) as Array; + }).filter((x) => !!x) as Array; } export function parse(source: string, options?: Options) { - const onParseError = (err) => { - console.warn(err); - throw new Error(`Unable to parse "${source}"`); - }; const t = tokenizer({ css: source }, { commentsAreTokens: true, - onParseError: onParseError, + onParseError: options?.onParseError, }); const tokens = []; diff --git a/plugins/postcss-custom-media/src/index.ts b/plugins/postcss-custom-media/src/index.ts index 9e41dedee..b097065e9 100644 --- a/plugins/postcss-custom-media/src/index.ts +++ b/plugins/postcss-custom-media/src/index.ts @@ -1,6 +1,6 @@ import type { PluginCreator } from 'postcss'; import getCustomMedia from './custom-media-from-root'; -import { transformAtMediaListTokens } from './transform-at-media/transform-at-media'; +import { transformAtMediaListTokens } from './transform-at-media-v2/transform-at-media'; export interface PluginOptions { /** Determines whether Custom Media and media queries using custom media should be preserved in their original form. */ diff --git a/plugins/postcss-custom-media/src/transform-at-media-v2/split-media-query-list.ts b/plugins/postcss-custom-media/src/transform-at-media-v2/split-media-query-list.ts index ae43ab3d2..b047f6e9b 100644 --- a/plugins/postcss-custom-media/src/transform-at-media-v2/split-media-query-list.ts +++ b/plugins/postcss-custom-media/src/transform-at-media-v2/split-media-query-list.ts @@ -1,60 +1,8 @@ -import { TokenType } from '@csstools/css-tokenizer'; import type { CSSToken } from '@csstools/css-tokenizer'; +import { parseFromTokens } from '@csstools/media-query-list-parser'; export function splitMediaQueryList(tokens: Array): Array> { - let parenDepth = 0; - let squareDepth = 0; - let curlyDepth = 0; - let depth = 0; - - const listItems = []; - let lastSliceIndex = 0; - - for (let i = 0; i < tokens.length; i++) { - if (tokens[i][0] === TokenType.OpenParen || tokens[i][0] === TokenType.Function) { - depth++; - parenDepth++; - continue; - } - if (tokens[i][0] === TokenType.CloseParen && parenDepth > 0) { - depth--; - parenDepth--; - continue; - } - - if (tokens[i][0] === TokenType.OpenCurly) { - depth++; - curlyDepth++; - continue; - } - if (tokens[i][0] === TokenType.CloseCurly && curlyDepth > 0) { - depth--; - curlyDepth--; - continue; - } - - if (tokens[i][0] === TokenType.OpenSquare) { - depth++; - squareDepth++; - continue; - } - if (tokens[i][0] === TokenType.CloseSquare && squareDepth > 0) { - depth--; - squareDepth--; - continue; - } - - if (tokens[i][0] === TokenType.Comma && depth === 0) { - listItems.push(tokens.slice(lastSliceIndex, i)); - lastSliceIndex = i + 1; - continue; - } - } - - if (lastSliceIndex === 0) { - return [tokens]; - } - - listItems.push(tokens.slice(lastSliceIndex)); - return listItems; + return parseFromTokens(tokens, { preserveInvalidMediaQueries : true }).map((x) => { + return x.tokens(); + }); } From b93b9da5dd5193a34898b917b3979287a91aae73 Mon Sep 17 00:00:00 2001 From: Romain Menke Date: Tue, 1 Nov 2022 19:16:06 +0100 Subject: [PATCH 28/35] more migration --- .../src/custom-media-from-root.ts | 7 ++-- plugins/postcss-custom-media/src/index.ts | 3 +- .../src/transform-at-media-v2/custom-media.ts | 37 ++++++++++++++----- .../transform-at-media.ts | 11 +++--- .../test/comma-1.expect.css | 2 +- 5 files changed, 41 insertions(+), 19 deletions(-) diff --git a/plugins/postcss-custom-media/src/custom-media-from-root.ts b/plugins/postcss-custom-media/src/custom-media-from-root.ts index 3bc4367b0..ec196d691 100644 --- a/plugins/postcss-custom-media/src/custom-media-from-root.ts +++ b/plugins/postcss-custom-media/src/custom-media-from-root.ts @@ -1,12 +1,13 @@ +import { MediaQuery } from '@csstools/media-query-list-parser'; import type { ChildNode, Container, Document, Root as PostCSSRoot } from 'postcss'; import { isProcessableCustomMediaRule } from './is-processable-custom-media-rule'; import { removeCyclicReferences } from './toposort'; import { parseCustomMedia } from './transform-at-media-v2/custom-media'; // return custom media from the css root, conditionally removing them -export default function getCustomMedia(root: PostCSSRoot, result, opts: { preserve?: boolean }): Map { +export default function getCustomMedia(root: PostCSSRoot, result, opts: { preserve?: boolean }): Map, falsy: Array }> { // initialize custom media - const customMedia: Map = new Map(); + const customMedia: Map, falsy: Array }> = new Map(); const customMediaGraph: Array<[string, string]> = []; root.walkAtRules((atRule) => { @@ -19,7 +20,7 @@ export default function getCustomMedia(root: PostCSSRoot, result, opts: { preser return; } - if (!parsed.truthy.trim()) { + if (parsed.truthy.length === 0) { return; } diff --git a/plugins/postcss-custom-media/src/index.ts b/plugins/postcss-custom-media/src/index.ts index b097065e9..f77e6db47 100644 --- a/plugins/postcss-custom-media/src/index.ts +++ b/plugins/postcss-custom-media/src/index.ts @@ -1,3 +1,4 @@ +import { MediaQuery } from '@csstools/media-query-list-parser'; import type { PluginCreator } from 'postcss'; import getCustomMedia from './custom-media-from-root'; import { transformAtMediaListTokens } from './transform-at-media-v2/transform-at-media'; @@ -22,7 +23,7 @@ const creator: PluginCreator = (opts?: PluginOptions) => { return { postcssPlugin: 'postcss-custom-media', prepare() { - let customMedia = new Map(); + let customMedia: Map, falsy: Array }> = new Map(); return { Once: (root, { result }) => { diff --git a/plugins/postcss-custom-media/src/transform-at-media-v2/custom-media.ts b/plugins/postcss-custom-media/src/transform-at-media-v2/custom-media.ts index 3d271f4d5..d8454bf81 100644 --- a/plugins/postcss-custom-media/src/transform-at-media-v2/custom-media.ts +++ b/plugins/postcss-custom-media/src/transform-at-media-v2/custom-media.ts @@ -1,9 +1,9 @@ -import { CSSToken, stringify, TokenIdent, TokenType } from '@csstools/css-tokenizer'; -import { parseFromTokens, NodeType, MediaCondition, MediaInParens, MediaQueryWithoutType, MediaQueryWithType, MediaNot } from '@csstools/media-query-list-parser'; +import { CSSToken, TokenIdent, TokenType } from '@csstools/css-tokenizer'; +import { parseFromTokens, NodeType, MediaCondition, MediaInParens, MediaQueryWithoutType, MediaQueryWithType, MediaNot, MediaQuery } from '@csstools/media-query-list-parser'; import { atMediaParamsTokens } from '../transform-at-media/at-media-params-tokens'; import { replaceTrueAndFalseTokens } from './true-and-false'; -export function parseCustomMedia(params: string): { name: string, truthy: string, falsy: string, dependsOn: Array<[string, string]> } | false { +export function parseCustomMedia(params: string): { name: string, truthy: Array, falsy: Array, dependsOn: Array<[string, string]> } | false { const tokens = atMediaParamsTokens(params); const customMediaReferences: Set = new Set(); @@ -46,7 +46,28 @@ export function parseCustomMedia(params: string): { name: string, truthy: string for (let i = 0; i < mediaQueryListFalsy.length; i++) { const mediaQuery = mediaQueryListFalsy[i]; - if (mediaQuery.type === NodeType.MediaQueryWithType) { + + if (mediaQuery.type === NodeType.MediaQueryInvalid || mediaQuery.toString().trim() === '') { + mediaQueryListTruthy[i] = new MediaQueryWithType( + [ + [TokenType.Ident, 'not', 0, 0, { value: 'not' }], + [TokenType.Whitespace, ' ', 0, 0, undefined], + ], + [ + [TokenType.Ident, 'all', 0, 0, { value: 'all' }], + ], + null, + null, + ); + + mediaQueryListFalsy[i] = new MediaQueryWithType( + [ + [TokenType.Ident, 'all', 0, 0, { value: 'all' }], + ], + null, + null, + ); + } else if (mediaQuery.type === NodeType.MediaQueryWithType) { const query = mediaQuery as MediaQueryWithType; if (query.modifier.length === 0) { query.modifier = [ @@ -70,9 +91,7 @@ export function parseCustomMedia(params: string): { name: string, truthy: string mediaQueryListFalsy[i] = query; continue; - } - - if (mediaQuery.type === NodeType.MediaQueryWithoutType) { + } else if (mediaQuery.type === NodeType.MediaQueryWithoutType) { let mediaCondition = (mediaQuery as MediaQueryWithoutType).media; if (mediaCondition.media.type === NodeType.MediaNot) { const query = new MediaQueryWithoutType( @@ -122,8 +141,8 @@ export function parseCustomMedia(params: string): { name: string, truthy: string return { name: name, - truthy: mediaQueryListTruthy.map((x) => x.toString().trim()).join(','), - falsy: mediaQueryListFalsy.map((x) => x.toString().trim()).join(','), + truthy: mediaQueryListTruthy, + falsy: mediaQueryListFalsy, dependsOn: Array.from(customMediaReferences).map((x) => { return [x, name]; }), diff --git a/plugins/postcss-custom-media/src/transform-at-media-v2/transform-at-media.ts b/plugins/postcss-custom-media/src/transform-at-media-v2/transform-at-media.ts index f405b7a83..1bee3beae 100644 --- a/plugins/postcss-custom-media/src/transform-at-media-v2/transform-at-media.ts +++ b/plugins/postcss-custom-media/src/transform-at-media-v2/transform-at-media.ts @@ -3,8 +3,9 @@ import type { CSSToken } from '@csstools/css-tokenizer'; import { splitMediaQueryList } from './split-media-query-list'; import { alwaysTrue, neverTrue } from './always-true-or-false'; import { atMediaParamsTokens } from './at-media-params-tokens'; +import { MediaQuery } from '@csstools/media-query-list-parser'; -export function transformAtMediaListTokens(params: string, replacements: Map): Array<{ replaceWith: string, encapsulateWith?: string }> { +export function transformAtMediaListTokens(params: string, replacements: Map, falsy: Array }>): Array<{ replaceWith: string, encapsulateWith?: string }> { const mediaQueries = splitMediaQueryList(atMediaParamsTokens(params)); const stringQueries = mediaQueries.map((x) => stringify(...x)); @@ -36,7 +37,7 @@ export function transformAtMediaListTokens(params: string, replacements: Map, replacements: Map): Array<{replaceWith: string, encapsulateWith?: string}> { +export function transformAtMediaTokens(tokens: Array, replacements: Map, falsy: Array }>): Array<{replaceWith: string, encapsulateWith?: string}> { const tokenTypes: Set = new Set(); let identCounter = 0; for (let i = 0; i < tokens.length; i++) { @@ -87,7 +88,7 @@ export function transformAtMediaTokens(tokens: Array, replacements: Ma if (replacements.has(identToken[4].value)) { candidate = [{ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - replaceWith: replacements.get(identToken[4].value)!.truthy, + replaceWith: replacements.get(identToken[4].value)!.truthy.map((x) => x.toString().trim()).join(','), }]; } } @@ -209,11 +210,11 @@ export function transformAtMediaTokens(tokens: Array, replacements: Ma return [ { replaceWith: stringify(...replaceWithTrue), - encapsulateWith: replacement.truthy, + encapsulateWith: replacement.truthy.map((x) => x.toString().trim()).join(','), }, { replaceWith: stringify(...replaceWithFalse), - encapsulateWith: replacement.falsy, + encapsulateWith: replacement.falsy.map((x) => x.toString().trim()).join(','), }, ]; } diff --git a/plugins/postcss-custom-media/test/comma-1.expect.css b/plugins/postcss-custom-media/test/comma-1.expect.css index 6a87b6d35..62777f0a1 100644 --- a/plugins/postcss-custom-media/test/comma-1.expect.css +++ b/plugins/postcss-custom-media/test/comma-1.expect.css @@ -10,7 +10,7 @@ } } -@media screen,[aa,bb,cc] { +@media screen,not all { .a { order: 3; } From 92820147e6d2403ab209ac638ff34730d8148727 Mon Sep 17 00:00:00 2001 From: Romain Menke Date: Wed, 2 Nov 2022 19:54:55 +0100 Subject: [PATCH 29/35] more convenience methods --- .../src/nodes/media-feature.ts | 14 +-- .../src/nodes/media-query.ts | 109 +++++++++++++++++- .../src/transform-at-media-v2/custom-media.ts | 93 +-------------- .../test/comma-1.expect.css | 2 +- 4 files changed, 117 insertions(+), 101 deletions(-) diff --git a/packages/media-query-list-parser/src/nodes/media-feature.ts b/packages/media-query-list-parser/src/nodes/media-feature.ts index 84a7560c9..4e98a63e0 100644 --- a/packages/media-query-list-parser/src/nodes/media-feature.ts +++ b/packages/media-query-list-parser/src/nodes/media-feature.ts @@ -95,13 +95,13 @@ export function parseMediaFeature(simpleBlock: SimpleBlockNode, before: Array) { return new MediaFeature( new MediaFeaturePlain( new MediaFeatureName( - new TokenNode([TokenType.Ident, name, 0, 0, { value: name }]), + new TokenNode([TokenType.Ident, name, -1, -1, { value: name }]), ), - [TokenType.Colon, ':', 0, 0, undefined], + [TokenType.Colon, ':', -1, -1, undefined], new MediaFeatureValue( value.map((x) => new TokenNode(x)), ), ), [ - [TokenType.OpenParen, '(', 0, 0, undefined], + [TokenType.OpenParen, '(', -1, -1, undefined], ], [ - [TokenType.CloseParen, ')', 0, 0, undefined], + [TokenType.CloseParen, ')', -1, -1, undefined], ], ); } diff --git a/packages/media-query-list-parser/src/nodes/media-query.ts b/packages/media-query-list-parser/src/nodes/media-query.ts index 289f5f955..6dc8faebc 100644 --- a/packages/media-query-list-parser/src/nodes/media-query.ts +++ b/packages/media-query-list-parser/src/nodes/media-query.ts @@ -1,6 +1,8 @@ import { ComponentValue } from '@csstools/css-parser-algorithms'; -import { CSSToken, stringify } from '@csstools/css-tokenizer'; +import { CSSToken, stringify, TokenType } from '@csstools/css-tokenizer'; import { MediaCondition, MediaConditionWalkerEntry, MediaConditionWalkerParent } from './media-condition'; +import { MediaInParens } from './media-in-parens'; +import { MediaNot } from './media-not'; import { NodeType } from './node-type'; export type MediaQuery = MediaQueryWithType | MediaQueryWithoutType | MediaQueryInvalid; @@ -23,6 +25,64 @@ export class MediaQueryWithType { } } + getModifier() { + if (!this.modifier.length) { + return ''; + } + + for (let i = 0; i < this.modifier.length; i++) { + const token = this.modifier[i]; + if (token[0] === TokenType.Ident) { + return token[4].value; + } + } + + return ''; + } + + negateQuery(): MediaQuery { + const copy = new MediaQueryWithType([...this.modifier], [...this.mediaType], this.and, this.media); + if (copy.modifier.length === 0) { + copy.modifier = [ + [TokenType.Ident, 'not', -1, -1, { value: 'not' }], + [TokenType.Whitespace, ' ', -1, -1, undefined], + ]; + + return; + } + + for (let i = 0; i < copy.modifier.length; i++) { + const token = copy.modifier[i]; + if (token[0] === TokenType.Ident && token[4].value.toLowerCase() === 'not') { + copy.modifier.splice(i, 1); + break; + } + + if (token[0] === TokenType.Ident && token[4].value.toLowerCase() === 'only') { + copy.modifier[i][1] = 'not'; + copy.modifier[i][4].value = 'not'; + break; + } + } + + return copy; + } + + getMediaType() { + if (!this.mediaType.length) { + return ''; + } + + for (let i = 0; i < this.mediaType.length; i++) { + const token = this.mediaType[i]; + if (token[0] === TokenType.Ident) { + return token[4].value; + } + } + + return ''; + } + tokens() { if (this.and && this.media) { return [ @@ -93,6 +153,49 @@ export class MediaQueryWithoutType { this.media = media; } + negateQuery(): MediaQuery { + let mediaCondition = this.media; + if (mediaCondition.media.type === NodeType.MediaNot) { + return new MediaQueryWithoutType( + new MediaCondition( + (mediaCondition.media as MediaNot).media, + ), + ); + } + + if (mediaCondition.media.type === NodeType.MediaConditionListWithOr) { + mediaCondition = new MediaCondition( + new MediaInParens( + mediaCondition, + [ + [TokenType.Whitespace, ' ', 0, 0, undefined], + [TokenType.OpenParen, '(', 0, 0, undefined], + ], + [ + [TokenType.CloseParen, ')', 0, 0, undefined], + ], + ), + ); + } + + const query = new MediaQueryWithType( + [ + [TokenType.Ident, 'not', 0, 0, { value: 'not' }], + [TokenType.Whitespace, ' ', 0, 0, undefined], + ], + [ + [TokenType.Ident, 'all', 0, 0, { value: 'all' }], + [TokenType.Whitespace, ' ', 0, 0, undefined], + ], + [ + [TokenType.Ident, 'and', 0, 0, { value: 'and' }], + ], + mediaCondition, + ); + + return query; + } + tokens(): Array { return this.media.tokens(); } @@ -144,6 +247,10 @@ export class MediaQueryInvalid { this.media = media; } + negateQuery(): MediaQuery { + return new MediaQueryInvalid(this.media); + } + tokens(): Array { return this.media.flatMap((x) => x.tokens()); } diff --git a/plugins/postcss-custom-media/src/transform-at-media-v2/custom-media.ts b/plugins/postcss-custom-media/src/transform-at-media-v2/custom-media.ts index d8454bf81..c281fb29f 100644 --- a/plugins/postcss-custom-media/src/transform-at-media-v2/custom-media.ts +++ b/plugins/postcss-custom-media/src/transform-at-media-v2/custom-media.ts @@ -45,98 +45,7 @@ export function parseCustomMedia(params: string): { name: string, truthy: Array< const mediaQueryListFalsy = parseFromTokens(cloneTokens(remainder), { preserveInvalidMediaQueries: true }); for (let i = 0; i < mediaQueryListFalsy.length; i++) { - const mediaQuery = mediaQueryListFalsy[i]; - - if (mediaQuery.type === NodeType.MediaQueryInvalid || mediaQuery.toString().trim() === '') { - mediaQueryListTruthy[i] = new MediaQueryWithType( - [ - [TokenType.Ident, 'not', 0, 0, { value: 'not' }], - [TokenType.Whitespace, ' ', 0, 0, undefined], - ], - [ - [TokenType.Ident, 'all', 0, 0, { value: 'all' }], - ], - null, - null, - ); - - mediaQueryListFalsy[i] = new MediaQueryWithType( - [ - [TokenType.Ident, 'all', 0, 0, { value: 'all' }], - ], - null, - null, - ); - } else if (mediaQuery.type === NodeType.MediaQueryWithType) { - const query = mediaQuery as MediaQueryWithType; - if (query.modifier.length === 0) { - query.modifier = [ - [TokenType.Ident, 'not', 0, 0, { value: 'not' }], - ]; - } else { - for (let j = 0; j < query.modifier.length; j++) { - const token = query.modifier[j]; - if (token[0] === TokenType.Ident && token[4].value.toLowerCase() === 'not') { - query.modifier = []; - continue; - } - - if (token[0] === TokenType.Ident && token[4].value.toLowerCase() === 'only') { - query.modifier[j][1] = 'not'; - query.modifier[j][4].value = 'not'; - continue; - } - } - } - - mediaQueryListFalsy[i] = query; - continue; - } else if (mediaQuery.type === NodeType.MediaQueryWithoutType) { - let mediaCondition = (mediaQuery as MediaQueryWithoutType).media; - if (mediaCondition.media.type === NodeType.MediaNot) { - const query = new MediaQueryWithoutType( - new MediaCondition( - (mediaCondition.media as MediaNot).media, - ), - ); - - mediaQueryListFalsy[i] = query; - continue; - } - - if (mediaCondition.media.type === NodeType.MediaConditionListWithOr) { - mediaCondition = new MediaCondition( - new MediaInParens( - mediaCondition, - [ - [TokenType.Whitespace, ' ', 0, 0, undefined], - [TokenType.OpenParen, '(', 0, 0, undefined], - ], - [ - [TokenType.CloseParen, ')', 0, 0, undefined], - ], - ), - ); - } - - const query = new MediaQueryWithType( - [ - [TokenType.Ident, 'not', 0, 0, { value: 'not' }], - [TokenType.Whitespace, ' ', 0, 0, undefined], - ], - [ - [TokenType.Ident, 'all', 0, 0, { value: 'all' }], - [TokenType.Whitespace, ' ', 0, 0, undefined], - ], - [ - [TokenType.Ident, 'and', 0, 0, { value: 'and' }], - ], - mediaCondition, - ); - - mediaQueryListFalsy[i] = query; - continue; - } + mediaQueryListFalsy[i] = mediaQueryListFalsy[i].negateQuery(); } return { diff --git a/plugins/postcss-custom-media/test/comma-1.expect.css b/plugins/postcss-custom-media/test/comma-1.expect.css index 62777f0a1..6a87b6d35 100644 --- a/plugins/postcss-custom-media/test/comma-1.expect.css +++ b/plugins/postcss-custom-media/test/comma-1.expect.css @@ -10,7 +10,7 @@ } } -@media screen,not all { +@media screen,[aa,bb,cc] { .a { order: 3; } From 94969a96c199975206d96387c985849d0aece742 Mon Sep 17 00:00:00 2001 From: Romain Menke Date: Thu, 3 Nov 2022 09:24:25 +0100 Subject: [PATCH 30/35] more type predicates --- .../consume-component-block-function.ts | 123 ++++++++- packages/css-parser-algorithms/src/index.ts | 10 + .../src/util/component-value-type.ts | 9 + .../src/util/type-predicates.ts | 29 ++ packages/css-tokenizer/src/index.ts | 1 + .../css-tokenizer/src/util/clone-tokens.ts | 9 + packages/media-query-list-parser/src/index.ts | 26 +- .../src/nodes/general-enclosed.ts | 2 +- .../src/nodes/media-and.ts | 2 +- .../src/nodes/media-condition-list.ts | 2 +- .../src/nodes/media-condition.ts | 2 +- .../src/nodes/media-feature-boolean.ts | 2 +- .../src/nodes/media-feature-name.ts | 2 +- .../src/nodes/media-feature-plain.ts | 2 +- .../src/nodes/media-feature-range.ts | 2 +- .../src/nodes/media-feature-value.ts | 2 +- .../src/nodes/media-feature.ts | 2 +- .../src/nodes/media-in-parens.ts | 2 +- .../src/nodes/media-not.ts | 2 +- .../src/nodes/media-or.ts | 2 +- .../src/nodes/media-query.ts | 2 +- .../src/parser/parse-media-query.ts | 12 +- .../src/{nodes => util}/node-type.ts | 0 .../src/util/type-predicates.ts | 255 ++++++++++++++++++ .../src/transform-at-media-v2/custom-media.ts | 12 +- 25 files changed, 473 insertions(+), 41 deletions(-) create mode 100644 packages/css-parser-algorithms/src/util/component-value-type.ts create mode 100644 packages/css-parser-algorithms/src/util/type-predicates.ts create mode 100644 packages/css-tokenizer/src/util/clone-tokens.ts rename packages/media-query-list-parser/src/{nodes => util}/node-type.ts (100%) create mode 100644 packages/media-query-list-parser/src/util/type-predicates.ts diff --git a/packages/css-parser-algorithms/src/consume/consume-component-block-function.ts b/packages/css-parser-algorithms/src/consume/consume-component-block-function.ts index fe3b3010e..0bad490c7 100644 --- a/packages/css-parser-algorithms/src/consume/consume-component-block-function.ts +++ b/packages/css-parser-algorithms/src/consume/consume-component-block-function.ts @@ -1,20 +1,11 @@ import { CSSToken, mirrorVariantType, stringify, TokenType, isToken, TokenFunction } from '@csstools/css-tokenizer'; import { Context } from '../interfaces/context'; +import { ComponentValueType } from '../util/component-value-type'; export type ContainerNode = FunctionNode | SimpleBlockNode; export type ComponentValue = FunctionNode | SimpleBlockNode | WhitespaceNode | CommentNode | TokenNode | UnclosedSimpleBlockNode | UnclosedFunctionNode; -export enum ComponentValueType { - Function = 'function', - SimpleBlock = 'simple-block', - Whitespace = 'whitespace', - Comment = 'comment', - Token = 'token', - UnclosedFunction = 'unclosed-function', - UnclosedSimpleBlock = 'unclosed-simple-block' -} - // https://www.w3.org/TR/css-syntax-3/#consume-a-component-value export function consumeComponentValue(ctx: Context, tokens: Array): { advance: number, node: ComponentValue } { const i = 0; @@ -155,6 +146,22 @@ export class FunctionNode { value: this.value.map((x) => x.toJSON()), }; } + + isFunctionNode(): this is FunctionNode { + return FunctionNode.isFunctionNode(this); + } + + static isFunctionNode(x: unknown): x is FunctionNode { + if (!x) { + return false; + } + + if (!(x instanceof FunctionNode)) { + return false; + } + + return x.type === ComponentValueType.Function; + } } // https://www.w3.org/TR/css-syntax-3/#consume-function @@ -289,6 +296,22 @@ export class SimpleBlockNode { value: this.value.map((x) => x.toJSON()), }; } + + isSimpleBlockNode(): this is SimpleBlockNode { + return SimpleBlockNode.isSimpleBlockNode(this); + } + + static isSimpleBlockNode(x: unknown): x is SimpleBlockNode { + if (!x) { + return false; + } + + if (!(x instanceof SimpleBlockNode)) { + return false; + } + + return x.type === ComponentValueType.SimpleBlock; + } } /** https://www.w3.org/TR/css-syntax-3/#consume-simple-block */ @@ -365,6 +388,22 @@ export class WhitespaceNode { tokens: this.tokens(), }; } + + isWhitespaceNode(): this is WhitespaceNode { + return WhitespaceNode.isWhitespaceNode(this); + } + + static isWhitespaceNode(x: unknown): x is WhitespaceNode { + if (!x) { + return false; + } + + if (!(x instanceof WhitespaceNode)) { + return false; + } + + return x.type === ComponentValueType.Whitespace; + } } export function consumeWhitespace(ctx: Context, tokens: Array): { advance: number, node: WhitespaceNode } { @@ -409,6 +448,22 @@ export class CommentNode { tokens: this.tokens(), }; } + + isCommentNode(): this is CommentNode { + return CommentNode.isCommentNode(this); + } + + static isCommentNode(x: unknown): x is CommentNode { + if (!x) { + return false; + } + + if (!(x instanceof CommentNode)) { + return false; + } + + return x.type === ComponentValueType.Comment; + } } export function consumeComment(ctx: Context, tokens: Array): { advance: number, node: CommentNode } { @@ -470,6 +525,22 @@ export class TokenNode { tokens: this.tokens(), }; } + + isTokenNode(): this is TokenNode { + return TokenNode.isTokenNode(this); + } + + static isTokenNode(x: unknown): x is TokenNode { + if (!x) { + return false; + } + + if (!(x instanceof TokenNode)) { + return false; + } + + return x.type === ComponentValueType.Token; + } } export class UnclosedFunctionNode { @@ -495,6 +566,22 @@ export class UnclosedFunctionNode { tokens: this.tokens(), }; } + + isUnclosedFunctionNode(): this is UnclosedFunctionNode { + return UnclosedFunctionNode.isUnclosedFunctionNode(this); + } + + static isUnclosedFunctionNode(x: unknown): x is UnclosedFunctionNode { + if (!x) { + return false; + } + + if (!(x instanceof UnclosedFunctionNode)) { + return false; + } + + return x.type === ComponentValueType.UnclosedFunction; + } } export class UnclosedSimpleBlockNode { @@ -520,4 +607,20 @@ export class UnclosedSimpleBlockNode { tokens: this.tokens(), }; } + + isUnclosedSimpleBlockNode(): this is UnclosedSimpleBlockNode { + return UnclosedSimpleBlockNode.isUnclosedSimpleBlockNode(this); + } + + static isUnclosedSimpleBlockNode(x: unknown): x is UnclosedSimpleBlockNode { + if (!x) { + return false; + } + + if (!(x instanceof UnclosedSimpleBlockNode)) { + return false; + } + + return x.type === ComponentValueType.UnclosedSimpleBlock; + } } diff --git a/packages/css-parser-algorithms/src/index.ts b/packages/css-parser-algorithms/src/index.ts index e45567959..5bb92c097 100644 --- a/packages/css-parser-algorithms/src/index.ts +++ b/packages/css-parser-algorithms/src/index.ts @@ -4,3 +4,13 @@ export { parseListOfComponentValues } from './parse/parse-list-of-component-valu export { parseCommaSeparatedListOfComponentValues } from './parse/parse-comma-separated-list-of-component-values'; export { gatherNodeAncestry } from './util/node-ancestry'; export { ParserError } from './interfaces/error'; +export { ComponentValueType } from './util/component-value-type'; +export { + isCommentNode, + isFunctionNode, + isSimpleBlockNode, + isTokenNode, + isUnclosedFunctionNode, + isUnclosedSimpleBlockNode, + isWhitespaceNode, +} from './util/type-predicates'; diff --git a/packages/css-parser-algorithms/src/util/component-value-type.ts b/packages/css-parser-algorithms/src/util/component-value-type.ts new file mode 100644 index 000000000..228ae5fc7 --- /dev/null +++ b/packages/css-parser-algorithms/src/util/component-value-type.ts @@ -0,0 +1,9 @@ +export enum ComponentValueType { + Function = 'function', + SimpleBlock = 'simple-block', + Whitespace = 'whitespace', + Comment = 'comment', + Token = 'token', + UnclosedFunction = 'unclosed-function', + UnclosedSimpleBlock = 'unclosed-simple-block' +} diff --git a/packages/css-parser-algorithms/src/util/type-predicates.ts b/packages/css-parser-algorithms/src/util/type-predicates.ts new file mode 100644 index 000000000..a5cb96eda --- /dev/null +++ b/packages/css-parser-algorithms/src/util/type-predicates.ts @@ -0,0 +1,29 @@ +import { CommentNode, FunctionNode, SimpleBlockNode, TokenNode, UnclosedFunctionNode, UnclosedSimpleBlockNode, WhitespaceNode } from '../consume/consume-component-block-function'; + +export function isSimpleBlockNode(x: unknown): x is SimpleBlockNode { + return SimpleBlockNode.isSimpleBlockNode(x); +} + +export function isFunctionNode(x: unknown): x is FunctionNode { + return FunctionNode.isFunctionNode(x); +} + +export function isUnclosedSimpleBlockNode(x: unknown): x is UnclosedSimpleBlockNode { + return UnclosedSimpleBlockNode.isUnclosedSimpleBlockNode(x); +} + +export function isUnclosedFunctionNode(x: unknown): x is UnclosedFunctionNode { + return UnclosedFunctionNode.isUnclosedFunctionNode(x); +} + +export function isWhitespaceNode(x: unknown): x is WhitespaceNode { + return WhitespaceNode.isWhitespaceNode(x); +} + +export function isCommentNode(x: unknown): x is CommentNode { + return CommentNode.isCommentNode(x); +} + +export function isTokenNode(x: unknown): x is TokenNode { + return TokenNode.isTokenNode(x); +} diff --git a/packages/css-tokenizer/src/index.ts b/packages/css-tokenizer/src/index.ts index d199143ce..015d99029 100644 --- a/packages/css-tokenizer/src/index.ts +++ b/packages/css-tokenizer/src/index.ts @@ -3,6 +3,7 @@ export { Reader } from './reader'; export { TokenType, NumberType, mirrorVariantType, isToken } from './interfaces/token'; export { stringify } from './stringify'; export { tokenizer } from './tokenizer'; +export { cloneTokens } from './util/clone-tokens'; export type { TokenAtKeyword, diff --git a/packages/css-tokenizer/src/util/clone-tokens.ts b/packages/css-tokenizer/src/util/clone-tokens.ts new file mode 100644 index 000000000..e70353307 --- /dev/null +++ b/packages/css-tokenizer/src/util/clone-tokens.ts @@ -0,0 +1,9 @@ +import { CSSToken } from '../interfaces/token'; + +export function cloneTokens(tokens: Array): Array { + if ((typeof globalThis !== 'undefined') && 'structuredClone' in globalThis) { + return structuredClone(tokens); + } + + return JSON.parse(JSON.stringify(tokens)); +} diff --git a/packages/media-query-list-parser/src/index.ts b/packages/media-query-list-parser/src/index.ts index 295793df9..4f18e76e7 100644 --- a/packages/media-query-list-parser/src/index.ts +++ b/packages/media-query-list-parser/src/index.ts @@ -1,5 +1,29 @@ export { parse, parseFromTokens } from './parser/parse'; -export { NodeType } from './nodes/node-type'; +export { NodeType } from './util/node-type'; +export { + isGeneralEnclosed, + isMediaAnd, + isMediaCondition, + isMediaConditionList, + isMediaConditionListWithAnd, + isMediaConditionListWithOr, + isMediaFeature, + isMediaFeatureBoolean, + isMediaFeatureName, + isMediaFeaturePlain, + isMediaFeatureRange, + isMediaFeatureRangeNameValue, + isMediaFeatureRangeValueName, + isMediaFeatureRangeValueNameValue, + isMediaFeatureValue, + isMediaInParens, + isMediaNot, + isMediaOr, + isMediaQuery, + isMediaQueryInvalid, + isMediaQueryWithType, + isMediaQueryWithoutType, +} from './util/type-predicates'; export { GeneralEnclosed } from './nodes/general-enclosed'; export { MediaAnd } from './nodes/media-and'; diff --git a/packages/media-query-list-parser/src/nodes/general-enclosed.ts b/packages/media-query-list-parser/src/nodes/general-enclosed.ts index 061e826ac..c9ed91212 100644 --- a/packages/media-query-list-parser/src/nodes/general-enclosed.ts +++ b/packages/media-query-list-parser/src/nodes/general-enclosed.ts @@ -1,6 +1,6 @@ import { ComponentValue, ContainerNode } from '@csstools/css-parser-algorithms'; import { CSSToken } from '@csstools/css-tokenizer'; -import { NodeType } from './node-type'; +import { NodeType } from '../util/node-type'; export class GeneralEnclosed { type = NodeType.GeneralEnclosed; diff --git a/packages/media-query-list-parser/src/nodes/media-and.ts b/packages/media-query-list-parser/src/nodes/media-and.ts index 42433a4bd..001f22e93 100644 --- a/packages/media-query-list-parser/src/nodes/media-and.ts +++ b/packages/media-query-list-parser/src/nodes/media-and.ts @@ -1,6 +1,6 @@ import { CSSToken, stringify } from '@csstools/css-tokenizer'; import { MediaInParens, MediaInParensWalkerEntry, MediaInParensWalkerParent } from './media-in-parens'; -import { NodeType } from './node-type'; +import { NodeType } from '../util/node-type'; export class MediaAnd { type = NodeType.MediaAnd; diff --git a/packages/media-query-list-parser/src/nodes/media-condition-list.ts b/packages/media-query-list-parser/src/nodes/media-condition-list.ts index d88bbc14c..d6dddb6b5 100644 --- a/packages/media-query-list-parser/src/nodes/media-condition-list.ts +++ b/packages/media-query-list-parser/src/nodes/media-condition-list.ts @@ -2,7 +2,7 @@ import { CSSToken, stringify } from '@csstools/css-tokenizer'; import { MediaAnd, MediaAndWalkerEntry, MediaAndWalkerParent } from './media-and'; import { MediaInParens } from './media-in-parens'; import { MediaOr, MediaOrWalkerEntry, MediaOrWalkerParent } from './media-or'; -import { NodeType } from './node-type'; +import { NodeType } from '../util/node-type'; export type MediaConditionList = MediaConditionListWithAnd | MediaConditionListWithOr; diff --git a/packages/media-query-list-parser/src/nodes/media-condition.ts b/packages/media-query-list-parser/src/nodes/media-condition.ts index a05277a0e..387143d15 100644 --- a/packages/media-query-list-parser/src/nodes/media-condition.ts +++ b/packages/media-query-list-parser/src/nodes/media-condition.ts @@ -2,7 +2,7 @@ import { CSSToken } from '@csstools/css-tokenizer'; import { MediaConditionListWithAnd, MediaConditionListWithAndWalkerEntry, MediaConditionListWithAndWalkerParent, MediaConditionListWithOr, MediaConditionListWithOrWalkerEntry, MediaConditionListWithOrWalkerParent } from './media-condition-list'; import { MediaInParens } from './media-in-parens'; import { MediaNot, MediaNotWalkerEntry, MediaNotWalkerParent } from './media-not'; -import { NodeType } from './node-type'; +import { NodeType } from '../util/node-type'; export class MediaCondition { type = NodeType.MediaCondition; diff --git a/packages/media-query-list-parser/src/nodes/media-feature-boolean.ts b/packages/media-query-list-parser/src/nodes/media-feature-boolean.ts index aadda1a31..4ef00c7b3 100644 --- a/packages/media-query-list-parser/src/nodes/media-feature-boolean.ts +++ b/packages/media-query-list-parser/src/nodes/media-feature-boolean.ts @@ -1,6 +1,6 @@ import { ComponentValue } from '@csstools/css-parser-algorithms'; import { MediaFeatureName, parseMediaFeatureName } from './media-feature-name'; -import { NodeType } from './node-type'; +import { NodeType } from '../util/node-type'; export class MediaFeatureBoolean extends MediaFeatureName { type = NodeType.MediaFeatureBoolean; diff --git a/packages/media-query-list-parser/src/nodes/media-feature-name.ts b/packages/media-query-list-parser/src/nodes/media-feature-name.ts index 7c52f6ea7..dc4d028df 100644 --- a/packages/media-query-list-parser/src/nodes/media-feature-name.ts +++ b/packages/media-query-list-parser/src/nodes/media-feature-name.ts @@ -1,7 +1,7 @@ import { ComponentValue, ComponentValueType, TokenNode } from '@csstools/css-parser-algorithms'; import { CSSToken, stringify, TokenIdent } from '@csstools/css-tokenizer'; import { isIdent } from '../util/component-value-is'; -import { NodeType } from './node-type'; +import { NodeType } from '../util/node-type'; export class MediaFeatureName { type = NodeType.MediaFeatureName; diff --git a/packages/media-query-list-parser/src/nodes/media-feature-plain.ts b/packages/media-query-list-parser/src/nodes/media-feature-plain.ts index bfe1a74bf..0292c365c 100644 --- a/packages/media-query-list-parser/src/nodes/media-feature-plain.ts +++ b/packages/media-query-list-parser/src/nodes/media-feature-plain.ts @@ -2,7 +2,7 @@ import { ComponentValue, ComponentValueType } from '@csstools/css-parser-algorit import { CSSToken, stringify, TokenColon, TokenType } from '@csstools/css-tokenizer'; import { parseMediaFeatureName, MediaFeatureName } from './media-feature-name'; import { parseMediaFeatureValue, MediaFeatureValue, MediaFeatureValueWalkerEntry, MediaFeatureValueWalkerParent } from './media-feature-value'; -import { NodeType } from './node-type'; +import { NodeType } from '../util/node-type'; export class MediaFeaturePlain { type = NodeType.MediaFeaturePlain; diff --git a/packages/media-query-list-parser/src/nodes/media-feature-range.ts b/packages/media-query-list-parser/src/nodes/media-feature-range.ts index c0b0307fa..7cda85aa4 100644 --- a/packages/media-query-list-parser/src/nodes/media-feature-range.ts +++ b/packages/media-query-list-parser/src/nodes/media-feature-range.ts @@ -3,7 +3,7 @@ import { CSSToken, stringify, TokenDelim, TokenType } from '@csstools/css-tokeni import { comparisonFromTokens, matchesComparison } from './media-feature-comparison'; import { MediaFeatureName, parseMediaFeatureName } from './media-feature-name'; import { MediaFeatureValue, MediaFeatureValueWalkerEntry, MediaFeatureValueWalkerParent, parseMediaFeatureValue } from './media-feature-value'; -import { NodeType } from './node-type'; +import { NodeType } from '../util/node-type'; export type MediaFeatureRange = MediaFeatureRangeNameValue | MediaFeatureRangeValueName | diff --git a/packages/media-query-list-parser/src/nodes/media-feature-value.ts b/packages/media-query-list-parser/src/nodes/media-feature-value.ts index 66c163864..398569867 100644 --- a/packages/media-query-list-parser/src/nodes/media-feature-value.ts +++ b/packages/media-query-list-parser/src/nodes/media-feature-value.ts @@ -1,7 +1,7 @@ import { ComponentValue, ComponentValueType, ContainerNode } from '@csstools/css-parser-algorithms'; import { CSSToken, stringify, TokenType } from '@csstools/css-tokenizer'; import { isDimension, isIdent, isNumber } from '../util/component-value-is'; -import { NodeType } from './node-type'; +import { NodeType } from '../util/node-type'; export class MediaFeatureValue { type = NodeType.MediaFeatureValue; diff --git a/packages/media-query-list-parser/src/nodes/media-feature.ts b/packages/media-query-list-parser/src/nodes/media-feature.ts index 4e98a63e0..928db90bb 100644 --- a/packages/media-query-list-parser/src/nodes/media-feature.ts +++ b/packages/media-query-list-parser/src/nodes/media-feature.ts @@ -5,7 +5,7 @@ import { MediaFeatureName } from './media-feature-name'; import { MediaFeaturePlain, MediaFeaturePlainWalkerEntry, MediaFeaturePlainWalkerParent, parseMediaFeaturePlain } from './media-feature-plain'; import { MediaFeatureRange, MediaFeatureRangeWalkerEntry, MediaFeatureRangeWalkerParent, parseMediaFeatureRange } from './media-feature-range'; import { MediaFeatureValue } from './media-feature-value'; -import { NodeType } from './node-type'; +import { NodeType } from '../util/node-type'; export class MediaFeature { type = NodeType.MediaFeature; diff --git a/packages/media-query-list-parser/src/nodes/media-in-parens.ts b/packages/media-query-list-parser/src/nodes/media-in-parens.ts index 0fff4a15d..89b716431 100644 --- a/packages/media-query-list-parser/src/nodes/media-in-parens.ts +++ b/packages/media-query-list-parser/src/nodes/media-in-parens.ts @@ -10,7 +10,7 @@ import { MediaFeatureName } from './media-feature-name'; import { MediaFeaturePlain } from './media-feature-plain'; import { MediaFeatureRange } from './media-feature-range'; import { MediaFeatureValue } from './media-feature-value'; -import { NodeType } from './node-type'; +import { NodeType } from '../util/node-type'; export class MediaInParens { type = NodeType.MediaInParens; diff --git a/packages/media-query-list-parser/src/nodes/media-not.ts b/packages/media-query-list-parser/src/nodes/media-not.ts index bddc765d1..eca7722c1 100644 --- a/packages/media-query-list-parser/src/nodes/media-not.ts +++ b/packages/media-query-list-parser/src/nodes/media-not.ts @@ -1,6 +1,6 @@ import { CSSToken, stringify } from '@csstools/css-tokenizer'; import { MediaInParens, MediaInParensWalkerEntry, MediaInParensWalkerParent } from './media-in-parens'; -import { NodeType } from './node-type'; +import { NodeType } from '../util/node-type'; export class MediaNot { type = NodeType.MediaNot; diff --git a/packages/media-query-list-parser/src/nodes/media-or.ts b/packages/media-query-list-parser/src/nodes/media-or.ts index 633578b62..a3f76afae 100644 --- a/packages/media-query-list-parser/src/nodes/media-or.ts +++ b/packages/media-query-list-parser/src/nodes/media-or.ts @@ -1,6 +1,6 @@ import { CSSToken, stringify } from '@csstools/css-tokenizer'; import { MediaInParens, MediaInParensWalkerEntry, MediaInParensWalkerParent } from './media-in-parens'; -import { NodeType } from './node-type'; +import { NodeType } from '../util/node-type'; export class MediaOr { type = NodeType.MediaOr; diff --git a/packages/media-query-list-parser/src/nodes/media-query.ts b/packages/media-query-list-parser/src/nodes/media-query.ts index 6dc8faebc..99e55f226 100644 --- a/packages/media-query-list-parser/src/nodes/media-query.ts +++ b/packages/media-query-list-parser/src/nodes/media-query.ts @@ -1,9 +1,9 @@ import { ComponentValue } from '@csstools/css-parser-algorithms'; import { CSSToken, stringify, TokenType } from '@csstools/css-tokenizer'; +import { NodeType } from '../util/node-type'; import { MediaCondition, MediaConditionWalkerEntry, MediaConditionWalkerParent } from './media-condition'; import { MediaInParens } from './media-in-parens'; import { MediaNot } from './media-not'; -import { NodeType } from './node-type'; export type MediaQuery = MediaQueryWithType | MediaQueryWithoutType | MediaQueryInvalid; diff --git a/packages/media-query-list-parser/src/parser/parse-media-query.ts b/packages/media-query-list-parser/src/parser/parse-media-query.ts index 67ee772b9..9e95b638e 100644 --- a/packages/media-query-list-parser/src/parser/parse-media-query.ts +++ b/packages/media-query-list-parser/src/parser/parse-media-query.ts @@ -1,4 +1,4 @@ -import { ComponentValue, ComponentValueType, SimpleBlockNode, TokenNode } from '@csstools/css-parser-algorithms'; +import { ComponentValue, ComponentValueType, isCommentNode, isTokenNode, isWhitespaceNode, SimpleBlockNode } from '@csstools/css-parser-algorithms'; import { CSSToken, TokenIdent, TokenType } from '@csstools/css-tokenizer'; import { GeneralEnclosed } from '../nodes/general-enclosed'; import { MediaAnd } from '../nodes/media-and'; @@ -26,17 +26,17 @@ export function parseMediaQuery(componentValues: Array) { let andIndex = -1; for (let i = 0; i < componentValues.length; i++) { - const componentValue = componentValues[i]; - if (componentValue.type === ComponentValueType.Whitespace) { + const componentValue = componentValues[i] as ComponentValue; + if (isWhitespaceNode(componentValue)) { continue; } - if (componentValue.type === ComponentValueType.Comment) { + if (isCommentNode(componentValue)) { continue; } - if (componentValue.type === ComponentValueType.Token) { - const token = (componentValue as TokenNode).value; + if (isTokenNode(componentValue)) { + const token = componentValue.value; if (modifierIndex === -1 && token[0] === TokenType.Ident && modifierFromToken(token)) { modifierIndex = i; continue; diff --git a/packages/media-query-list-parser/src/nodes/node-type.ts b/packages/media-query-list-parser/src/util/node-type.ts similarity index 100% rename from packages/media-query-list-parser/src/nodes/node-type.ts rename to packages/media-query-list-parser/src/util/node-type.ts diff --git a/packages/media-query-list-parser/src/util/type-predicates.ts b/packages/media-query-list-parser/src/util/type-predicates.ts new file mode 100644 index 000000000..e1b51bcb7 --- /dev/null +++ b/packages/media-query-list-parser/src/util/type-predicates.ts @@ -0,0 +1,255 @@ +import { GeneralEnclosed } from '../nodes/general-enclosed'; +import { MediaAnd } from '../nodes/media-and'; +import { MediaCondition } from '../nodes/media-condition'; +import { MediaConditionList, MediaConditionListWithAnd, MediaConditionListWithOr } from '../nodes/media-condition-list'; +import { MediaFeature } from '../nodes/media-feature'; +import { MediaFeatureBoolean } from '../nodes/media-feature-boolean'; +import { MediaFeatureName } from '../nodes/media-feature-name'; +import { MediaFeaturePlain } from '../nodes/media-feature-plain'; +import { MediaFeatureRange, MediaFeatureRangeNameValue, MediaFeatureRangeValueName, MediaFeatureRangeValueNameValue } from '../nodes/media-feature-range'; +import { MediaFeatureValue } from '../nodes/media-feature-value'; +import { MediaInParens } from '../nodes/media-in-parens'; +import { MediaNot } from '../nodes/media-not'; +import { MediaOr } from '../nodes/media-or'; +import { MediaQuery, MediaQueryInvalid, MediaQueryWithoutType, MediaQueryWithType } from '../nodes/media-query'; +import { NodeType } from './node-type'; + +export function isGeneralEnclosed(x: unknown): x is GeneralEnclosed { + if (!x) { + return false; + } + + if (!(x instanceof GeneralEnclosed)) { + return false; + } + + return x.type === NodeType.GeneralEnclosed; +} + +export function isMediaAnd(x: unknown): x is MediaAnd { + if (!x) { + return false; + } + + if (!(x instanceof MediaAnd)) { + return false; + } + + return x.type === NodeType.MediaAnd; +} + +export function isMediaConditionList(x: unknown): x is MediaConditionList { + return isMediaConditionListWithAnd(x) || isMediaConditionListWithOr(x); +} + +export function isMediaConditionListWithAnd(x: unknown): x is MediaConditionListWithAnd { + if (!x) { + return false; + } + + if (!(x instanceof MediaConditionListWithAnd)) { + return false; + } + + return x.type === NodeType.MediaConditionListWithAnd; +} + +export function isMediaConditionListWithOr(x: unknown): x is MediaConditionListWithOr { + if (!x) { + return false; + } + + if (!(x instanceof MediaConditionListWithOr)) { + return false; + } + + return x.type === NodeType.MediaConditionListWithOr; +} + +export function isMediaCondition(x: unknown): x is MediaCondition { + if (!x) { + return false; + } + + if (!(x instanceof MediaCondition)) { + return false; + } + + return x.type === NodeType.MediaCondition; +} + +export function isMediaFeatureBoolean(x: unknown): x is MediaFeatureBoolean { + if (!x) { + return false; + } + + if (!(x instanceof MediaFeatureBoolean)) { + return false; + } + + return x.type === NodeType.MediaFeatureBoolean; +} + +export function isMediaFeatureName(x: unknown): x is MediaFeatureName { + if (!x) { + return false; + } + + if (!(x instanceof MediaFeatureName)) { + return false; + } + + return x.type === NodeType.MediaFeatureName; +} + +export function isMediaFeatureValue(x: unknown): x is MediaFeatureValue { + if (!x) { + return false; + } + + if (!(x instanceof MediaFeatureValue)) { + return false; + } + + return x.type === NodeType.MediaFeatureValue; +} + +export function isMediaFeaturePlain(x: unknown): x is MediaFeaturePlain { + if (!x) { + return false; + } + + if (!(x instanceof MediaFeaturePlain)) { + return false; + } + + return x.type === NodeType.MediaFeaturePlain; +} + +export function isMediaFeatureRange(x: unknown): x is MediaFeatureRange { + return isMediaFeatureRangeNameValue(x) || isMediaFeatureRangeValueName(x) || isMediaFeatureRangeValueNameValue(x); +} + +export function isMediaFeatureRangeNameValue(x: unknown): x is MediaFeatureRangeNameValue { + if (!x) { + return false; + } + + if (!(x instanceof MediaFeatureRangeNameValue)) { + return false; + } + + return x.type === NodeType.MediaFeatureRangeNameValue; +} + +export function isMediaFeatureRangeValueName(x: unknown): x is MediaFeatureRangeValueName { + if (!x) { + return false; + } + + if (!(x instanceof MediaFeatureRangeValueName)) { + return false; + } + + return x.type === NodeType.MediaFeatureRangeValueName; +} + +export function isMediaFeatureRangeValueNameValue(x: unknown): x is MediaFeatureRangeValueNameValue { + if (!x) { + return false; + } + + if (!(x instanceof MediaFeatureRangeValueNameValue)) { + return false; + } + + return x.type === NodeType.MediaFeatureRangeValueNameValue; +} + +export function isMediaFeature(x: unknown): x is MediaFeature { + if (!x) { + return false; + } + + if (!(x instanceof MediaFeature)) { + return false; + } + + return x.type === NodeType.MediaFeature; +} + +export function isMediaInParens(x: unknown): x is MediaInParens { + if (!x) { + return false; + } + + if (!(x instanceof MediaInParens)) { + return false; + } + + return x.type === NodeType.MediaInParens; +} + +export function isMediaNot(x: unknown): x is MediaNot { + if (!x) { + return false; + } + + if (!(x instanceof MediaNot)) { + return false; + } + + return x.type === NodeType.MediaNot; +} + +export function isMediaOr(x: unknown): x is MediaOr { + if (!x) { + return false; + } + + if (!(x instanceof MediaOr)) { + return false; + } + + return x.type === NodeType.MediaOr; +} + +export function isMediaQuery(x: unknown): x is MediaQuery { + return isMediaQueryWithType(x) || isMediaQueryWithoutType(x) || isMediaQueryInvalid(x); +} + +export function isMediaQueryWithType(x: unknown): x is MediaQueryWithType { + if (!x) { + return false; + } + + if (!(x instanceof MediaQueryWithType)) { + return false; + } + + return x.type === NodeType.MediaQueryWithType; +} + +export function isMediaQueryWithoutType(x: unknown): x is MediaQueryWithoutType { + if (!x) { + return false; + } + + if (!(x instanceof MediaQueryWithoutType)) { + return false; + } + + return x.type === NodeType.MediaQueryWithoutType; +} + +export function isMediaQueryInvalid(x: unknown): x is MediaQueryInvalid { + if (!x) { + return false; + } + + if (!(x instanceof MediaQueryInvalid)) { + return false; + } + + return x.type === NodeType.MediaQueryInvalid; +} diff --git a/plugins/postcss-custom-media/src/transform-at-media-v2/custom-media.ts b/plugins/postcss-custom-media/src/transform-at-media-v2/custom-media.ts index c281fb29f..1a4ee061c 100644 --- a/plugins/postcss-custom-media/src/transform-at-media-v2/custom-media.ts +++ b/plugins/postcss-custom-media/src/transform-at-media-v2/custom-media.ts @@ -1,5 +1,5 @@ -import { CSSToken, TokenIdent, TokenType } from '@csstools/css-tokenizer'; -import { parseFromTokens, NodeType, MediaCondition, MediaInParens, MediaQueryWithoutType, MediaQueryWithType, MediaNot, MediaQuery } from '@csstools/media-query-list-parser'; +import { cloneTokens, TokenIdent, TokenType } from '@csstools/css-tokenizer'; +import { parseFromTokens, MediaQuery } from '@csstools/media-query-list-parser'; import { atMediaParamsTokens } from '../transform-at-media/at-media-params-tokens'; import { replaceTrueAndFalseTokens } from './true-and-false'; @@ -57,11 +57,3 @@ export function parseCustomMedia(params: string): { name: string, truthy: Array< }), }; } - -function cloneTokens(tokens: Array): Array { - if ('structuredClone' in globalThis) { - return structuredClone(tokens); - } - - return JSON.parse(JSON.stringify(tokens)); -} From 1aa990cc295b4d5e36f23f2c05935dd8a5eaf677 Mon Sep 17 00:00:00 2001 From: Romain Menke Date: Sat, 5 Nov 2022 16:55:46 +0100 Subject: [PATCH 31/35] wip --- package-lock.json | 7 +- packages/media-query-list-parser/src/index.ts | 1 + .../src/nodes/general-enclosed.ts | 16 ++ .../src/nodes/media-and.ts | 16 ++ .../src/nodes/media-condition-list.ts | 32 +++ .../src/nodes/media-condition.ts | 16 ++ .../src/nodes/media-feature-boolean.ts | 72 ++++++- .../src/nodes/media-feature-name.ts | 16 ++ .../src/nodes/media-feature-plain.ts | 16 ++ .../src/nodes/media-feature-range.ts | 48 +++++ .../src/nodes/media-feature-value.ts | 16 ++ .../src/nodes/media-feature.ts | 16 ++ .../src/nodes/media-in-parens.ts | 16 ++ .../src/nodes/media-not.ts | 16 ++ .../src/nodes/media-or.ts | 16 ++ .../src/nodes/media-query.ts | 52 +++++ .../src/util/clone-media-query.ts | 27 +++ .../src/util/type-predicates.ts | 191 ++---------------- .../split-media-query-list.ts | 8 - .../top-level-combination-keywords.ts | 90 --------- .../transform-at-media.ts | 124 +++++++----- .../test/basic-after-v9.css | 6 + .../test/basic-after-v9.expect.css | 6 + .../test/basic-after-v9.preserve.expect.css | 12 ++ 24 files changed, 505 insertions(+), 331 deletions(-) create mode 100644 packages/media-query-list-parser/src/util/clone-media-query.ts delete mode 100644 plugins/postcss-custom-media/src/transform-at-media-v2/split-media-query-list.ts delete mode 100644 plugins/postcss-custom-media/src/transform-at-media-v2/top-level-combination-keywords.ts diff --git a/package-lock.json b/package-lock.json index fb6e8d6e8..8ba40711a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "workspaces": [ "packages/css-tokenizer", "packages/css-parser-algorithms", + "packages/media-query-list-parser", "packages/*", "plugins/postcss-progressive-custom-properties", "plugins/*", @@ -7342,7 +7343,8 @@ "version": "8.0.2", "license": "MIT", "dependencies": { - "@csstools/css-tokenizer": "^1.0.0" + "@csstools/css-tokenizer": "^1.0.0", + "@csstools/media-query-list-parser": "^1.0.0" }, "engines": { "node": "^14 || ^16 || >=18" @@ -11818,7 +11820,8 @@ "postcss-custom-media": { "version": "file:plugins/postcss-custom-media", "requires": { - "@csstools/css-tokenizer": "^1.0.0" + "@csstools/css-tokenizer": "^1.0.0", + "@csstools/media-query-list-parser": "^1.0.0" } }, "postcss-custom-properties": { diff --git a/packages/media-query-list-parser/src/index.ts b/packages/media-query-list-parser/src/index.ts index 4f18e76e7..b87eef271 100644 --- a/packages/media-query-list-parser/src/index.ts +++ b/packages/media-query-list-parser/src/index.ts @@ -43,3 +43,4 @@ export { MediaQuery, MediaQueryWithType, MediaQueryWithoutType, MediaQueryInvali export { MediaFeatureComparison, MediaFeatureEQ, MediaFeatureGT, MediaFeatureLT, matchesComparison, comparisonFromTokens } from './nodes/media-feature-comparison'; export { MediaType, typeFromToken } from './nodes/media-type'; export { MediaQueryModifier, modifierFromToken } from './nodes/media-query-modifier'; +export { cloneMediaQuery } from './util/clone-media-query'; diff --git a/packages/media-query-list-parser/src/nodes/general-enclosed.ts b/packages/media-query-list-parser/src/nodes/general-enclosed.ts index c9ed91212..73d443cba 100644 --- a/packages/media-query-list-parser/src/nodes/general-enclosed.ts +++ b/packages/media-query-list-parser/src/nodes/general-enclosed.ts @@ -49,6 +49,22 @@ export class GeneralEnclosed { tokens: this.tokens(), }; } + + isGeneralEnclosed(): this is GeneralEnclosed { + return GeneralEnclosed.isGeneralEnclosed(this); + } + + static isGeneralEnclosed(x: unknown): x is GeneralEnclosed { + if (!x) { + return false; + } + + if (!(x instanceof GeneralEnclosed)) { + return false; + } + + return x.type === NodeType.GeneralEnclosed; + } } export type GeneralEnclosedWalkerEntry = ComponentValue; diff --git a/packages/media-query-list-parser/src/nodes/media-and.ts b/packages/media-query-list-parser/src/nodes/media-and.ts index 001f22e93..376d79f8b 100644 --- a/packages/media-query-list-parser/src/nodes/media-and.ts +++ b/packages/media-query-list-parser/src/nodes/media-and.ts @@ -53,6 +53,22 @@ export class MediaAnd { media: this.media.toJSON(), }; } + + isMediaAnd(): this is MediaAnd { + return MediaAnd.isMediaAnd(this); + } + + static isMediaAnd(x: unknown): x is MediaAnd { + if (!x) { + return false; + } + + if (!(x instanceof MediaAnd)) { + return false; + } + + return x.type === NodeType.MediaAnd; + } } export type MediaAndWalkerEntry = MediaInParensWalkerEntry | MediaInParens; diff --git a/packages/media-query-list-parser/src/nodes/media-condition-list.ts b/packages/media-query-list-parser/src/nodes/media-condition-list.ts index d6dddb6b5..9ba0e4499 100644 --- a/packages/media-query-list-parser/src/nodes/media-condition-list.ts +++ b/packages/media-query-list-parser/src/nodes/media-condition-list.ts @@ -104,6 +104,22 @@ export class MediaConditionListWithAnd { after: this.after, }; } + + isMediaConditionListWithAnd(): this is MediaConditionListWithAnd { + return MediaConditionListWithAnd.isMediaConditionListWithAnd(this); + } + + static isMediaConditionListWithAnd(x: unknown): x is MediaConditionListWithAnd { + if (!x) { + return false; + } + + if (!(x instanceof MediaConditionListWithAnd)) { + return false; + } + + return x.type === NodeType.MediaConditionListWithAnd; + } } export type MediaConditionListWithAndWalkerEntry = MediaAndWalkerEntry | MediaAnd; @@ -207,6 +223,22 @@ export class MediaConditionListWithOr { after: this.after, }; } + + isMediaConditionListWithOr(): this is MediaConditionListWithOr { + return MediaConditionListWithOr.isMediaConditionListWithOr(this); + } + + static isMediaConditionListWithOr(x: unknown): x is MediaConditionListWithOr { + if (!x) { + return false; + } + + if (!(x instanceof MediaConditionListWithOr)) { + return false; + } + + return x.type === NodeType.MediaConditionListWithOr; + } } export type MediaConditionListWithOrWalkerEntry = MediaOrWalkerEntry | MediaOr; diff --git a/packages/media-query-list-parser/src/nodes/media-condition.ts b/packages/media-query-list-parser/src/nodes/media-condition.ts index 387143d15..766c39eaf 100644 --- a/packages/media-query-list-parser/src/nodes/media-condition.ts +++ b/packages/media-query-list-parser/src/nodes/media-condition.ts @@ -49,6 +49,22 @@ export class MediaCondition { media: this.media.toJSON(), }; } + + isMediaCondition(): this is MediaCondition { + return MediaCondition.isMediaCondition(this); + } + + static isMediaCondition(x: unknown): x is MediaCondition { + if (!x) { + return false; + } + + if (!(x instanceof MediaCondition)) { + return false; + } + + return x.type === NodeType.MediaCondition; + } } export type MediaConditionWalkerEntry = MediaNotWalkerEntry | MediaConditionListWithAndWalkerEntry | MediaConditionListWithOrWalkerEntry | MediaNot | MediaConditionListWithAnd | MediaConditionListWithOr; diff --git a/packages/media-query-list-parser/src/nodes/media-feature-boolean.ts b/packages/media-query-list-parser/src/nodes/media-feature-boolean.ts index 4ef00c7b3..f53344500 100644 --- a/packages/media-query-list-parser/src/nodes/media-feature-boolean.ts +++ b/packages/media-query-list-parser/src/nodes/media-feature-boolean.ts @@ -1,9 +1,75 @@ -import { ComponentValue } from '@csstools/css-parser-algorithms'; -import { MediaFeatureName, parseMediaFeatureName } from './media-feature-name'; +import { ComponentValue, TokenNode } from '@csstools/css-parser-algorithms'; +import { parseMediaFeatureName } from './media-feature-name'; import { NodeType } from '../util/node-type'; +import { CSSToken, stringify, TokenIdent } from '@csstools/css-tokenizer'; -export class MediaFeatureBoolean extends MediaFeatureName { +export class MediaFeatureBoolean { type = NodeType.MediaFeatureBoolean; + + name: ComponentValue; + before: Array; + after: Array; + + constructor(name: ComponentValue, before: Array = [], after: Array = []) { + this.name = name; + this.before = before; + this.after = after; + } + + getName() { + const token = (((this.name as TokenNode).value as CSSToken) as TokenIdent); + return token[4].value; + } + + tokens(): Array { + return [ + ...this.before, + ...this.name.tokens(), + ...this.after, + ]; + } + + toString(): string { + return stringify(...this.before) + this.name.toString() + stringify(...this.after); + } + + indexOf(item: ComponentValue): number | string { + if (item === this.name) { + return 'name'; + } + + return -1; + } + + at(index: number | string) { + if (index === 'name') { + return this.name; + } + } + + toJSON() { + return { + type: this.type, + name: this.getName(), + tokens: this.tokens(), + }; + } + + isMediaFeatureBoolean(): this is MediaFeatureBoolean { + return MediaFeatureBoolean.isMediaFeatureBoolean(this); + } + + static isMediaFeatureBoolean(x: unknown): x is MediaFeatureBoolean { + if (!x) { + return false; + } + + if (!(x instanceof MediaFeatureBoolean)) { + return false; + } + + return x.type === NodeType.MediaFeatureBoolean; + } } export function parseMediaFeatureBoolean(componentValues: Array) { diff --git a/packages/media-query-list-parser/src/nodes/media-feature-name.ts b/packages/media-query-list-parser/src/nodes/media-feature-name.ts index dc4d028df..85bd0e592 100644 --- a/packages/media-query-list-parser/src/nodes/media-feature-name.ts +++ b/packages/media-query-list-parser/src/nodes/media-feature-name.ts @@ -54,6 +54,22 @@ export class MediaFeatureName { tokens: this.tokens(), }; } + + isMediaFeatureName(): this is MediaFeatureName { + return MediaFeatureName.isMediaFeatureName(this); + } + + static isMediaFeatureName(x: unknown): x is MediaFeatureName { + if (!x) { + return false; + } + + if (!(x instanceof MediaFeatureName)) { + return false; + } + + return x.type === NodeType.MediaFeatureName; + } } export function parseMediaFeatureName(componentValues: Array) { diff --git a/packages/media-query-list-parser/src/nodes/media-feature-plain.ts b/packages/media-query-list-parser/src/nodes/media-feature-plain.ts index 0292c365c..4d1a32d5f 100644 --- a/packages/media-query-list-parser/src/nodes/media-feature-plain.ts +++ b/packages/media-query-list-parser/src/nodes/media-feature-plain.ts @@ -67,6 +67,22 @@ export class MediaFeaturePlain { tokens: this.tokens(), }; } + + isMediaFeaturePlain(): this is MediaFeaturePlain { + return MediaFeaturePlain.isMediaFeaturePlain(this); + } + + static isMediaFeaturePlain(x: unknown): x is MediaFeaturePlain { + if (!x) { + return false; + } + + if (!(x instanceof MediaFeaturePlain)) { + return false; + } + + return x.type === NodeType.MediaFeaturePlain; + } } export type MediaFeaturePlainWalkerEntry = MediaFeatureValueWalkerEntry | MediaFeatureValue; diff --git a/packages/media-query-list-parser/src/nodes/media-feature-range.ts b/packages/media-query-list-parser/src/nodes/media-feature-range.ts index 7cda85aa4..970a78c32 100644 --- a/packages/media-query-list-parser/src/nodes/media-feature-range.ts +++ b/packages/media-query-list-parser/src/nodes/media-feature-range.ts @@ -78,6 +78,22 @@ export class MediaFeatureRangeNameValue { tokens: this.tokens(), }; } + + isMediaFeatureRangeNameValue(): this is MediaFeatureRangeNameValue { + return MediaFeatureRangeNameValue.isMediaFeatureRangeNameValue(this); + } + + static isMediaFeatureRangeNameValue(x: unknown): x is MediaFeatureRangeNameValue { + if (!x) { + return false; + } + + if (!(x instanceof MediaFeatureRangeNameValue)) { + return false; + } + + return x.type === NodeType.MediaFeatureRangeNameValue; + } } export class MediaFeatureRangeValueName { @@ -149,6 +165,22 @@ export class MediaFeatureRangeValueName { tokens: this.tokens(), }; } + + isMediaFeatureRangeValueName(): this is MediaFeatureRangeValueName { + return MediaFeatureRangeValueName.isMediaFeatureRangeValueName(this); + } + + static isMediaFeatureRangeValueName(x: unknown): x is MediaFeatureRangeValueName { + if (!x) { + return false; + } + + if (!(x instanceof MediaFeatureRangeValueName)) { + return false; + } + + return x.type === NodeType.MediaFeatureRangeValueName; + } } export class MediaFeatureRangeValueNameValue { @@ -251,6 +283,22 @@ export class MediaFeatureRangeValueNameValue { tokens: this.tokens(), }; } + + isMediaFeatureRangeValueNameValue(): this is MediaFeatureRangeValueNameValue { + return MediaFeatureRangeValueNameValue.isMediaFeatureRangeValueNameValue(this); + } + + static isMediaFeatureRangeValueNameValue(x: unknown): x is MediaFeatureRangeValueNameValue { + if (!x) { + return false; + } + + if (!(x instanceof MediaFeatureRangeValueNameValue)) { + return false; + } + + return x.type === NodeType.MediaFeatureRangeValueNameValue; + } } export type MediaFeatureRangeWalkerEntry = MediaFeatureValueWalkerEntry | MediaFeatureValue; diff --git a/packages/media-query-list-parser/src/nodes/media-feature-value.ts b/packages/media-query-list-parser/src/nodes/media-feature-value.ts index 398569867..642a7487d 100644 --- a/packages/media-query-list-parser/src/nodes/media-feature-value.ts +++ b/packages/media-query-list-parser/src/nodes/media-feature-value.ts @@ -84,6 +84,22 @@ export class MediaFeatureValue { tokens: this.tokens(), }; } + + isMediaFeatureValue(): this is MediaFeatureValue { + return MediaFeatureValue.isMediaFeatureValue(this); + } + + static isMediaFeatureValue(x: unknown): x is MediaFeatureValue { + if (!x) { + return false; + } + + if (!(x instanceof MediaFeatureValue)) { + return false; + } + + return x.type === NodeType.MediaFeatureValue; + } } export type MediaFeatureValueWalkerEntry = ComponentValue | Array; diff --git a/packages/media-query-list-parser/src/nodes/media-feature.ts b/packages/media-query-list-parser/src/nodes/media-feature.ts index 928db90bb..d9a9a4137 100644 --- a/packages/media-query-list-parser/src/nodes/media-feature.ts +++ b/packages/media-query-list-parser/src/nodes/media-feature.ts @@ -64,6 +64,22 @@ export class MediaFeature { after: this.after, }; } + + isMediaFeature(): this is MediaFeature { + return MediaFeature.isMediaFeature(this); + } + + static isMediaFeature(x: unknown): x is MediaFeature { + if (!x) { + return false; + } + + if (!(x instanceof MediaFeature)) { + return false; + } + + return x.type === NodeType.MediaFeature; + } } export type MediaFeatureWalkerEntry = MediaFeaturePlainWalkerEntry | MediaFeatureRangeWalkerEntry | MediaFeaturePlain | MediaFeatureBoolean | MediaFeatureRange; diff --git a/packages/media-query-list-parser/src/nodes/media-in-parens.ts b/packages/media-query-list-parser/src/nodes/media-in-parens.ts index 89b716431..02aff59a3 100644 --- a/packages/media-query-list-parser/src/nodes/media-in-parens.ts +++ b/packages/media-query-list-parser/src/nodes/media-in-parens.ts @@ -69,6 +69,22 @@ export class MediaInParens { after: this.after, }; } + + isMediaInParens(): this is MediaInParens { + return MediaInParens.isMediaInParens(this); + } + + static isMediaInParens(x: unknown): x is MediaInParens { + if (!x) { + return false; + } + + if (!(x instanceof MediaInParens)) { + return false; + } + + return x.type === NodeType.MediaInParens; + } } export type MediaInParensWalkerEntry = ComponentValue | Array | GeneralEnclosed | MediaAnd | MediaConditionList | MediaCondition | MediaFeatureBoolean | MediaFeatureName | MediaFeaturePlain | MediaFeatureRange | MediaFeatureValue | MediaFeature | GeneralEnclosed | MediaInParens; diff --git a/packages/media-query-list-parser/src/nodes/media-not.ts b/packages/media-query-list-parser/src/nodes/media-not.ts index eca7722c1..1a81910e6 100644 --- a/packages/media-query-list-parser/src/nodes/media-not.ts +++ b/packages/media-query-list-parser/src/nodes/media-not.ts @@ -53,6 +53,22 @@ export class MediaNot { media: this.media.toJSON(), }; } + + isMediaNot(): this is MediaNot { + return MediaNot.isMediaNot(this); + } + + static isMediaNot(x: unknown): x is MediaNot { + if (!x) { + return false; + } + + if (!(x instanceof MediaNot)) { + return false; + } + + return x.type === NodeType.MediaNot; + } } export type MediaNotWalkerEntry = MediaInParensWalkerEntry | MediaInParens; diff --git a/packages/media-query-list-parser/src/nodes/media-or.ts b/packages/media-query-list-parser/src/nodes/media-or.ts index a3f76afae..9d45e629f 100644 --- a/packages/media-query-list-parser/src/nodes/media-or.ts +++ b/packages/media-query-list-parser/src/nodes/media-or.ts @@ -53,6 +53,22 @@ export class MediaOr { media: this.media.toJSON(), }; } + + isMediaOr(): this is MediaOr { + return MediaOr.isMediaOr(this); + } + + static isMediaOr(x: unknown): x is MediaOr { + if (!x) { + return false; + } + + if (!(x instanceof MediaOr)) { + return false; + } + + return x.type === NodeType.MediaOr; + } } export type MediaOrWalkerEntry = MediaInParensWalkerEntry | MediaInParens; diff --git a/packages/media-query-list-parser/src/nodes/media-query.ts b/packages/media-query-list-parser/src/nodes/media-query.ts index 99e55f226..a8b91c0d2 100644 --- a/packages/media-query-list-parser/src/nodes/media-query.ts +++ b/packages/media-query-list-parser/src/nodes/media-query.ts @@ -126,6 +126,10 @@ export class MediaQueryWithType { return false; } + if (!this.media) { + return; + } + return this.media.walk(cb); } @@ -139,6 +143,22 @@ export class MediaQueryWithType { media: this.media, }; } + + isMediaQueryWithType(): this is MediaQueryWithType { + return MediaQueryWithType.isMediaQueryWithType(this); + } + + static isMediaQueryWithType(x: unknown): x is MediaQueryWithType { + if (!x) { + return false; + } + + if (!(x instanceof MediaQueryWithType)) { + return false; + } + + return x.type === NodeType.MediaQueryWithType; + } } export type MediaQueryWithTypeWalkerEntry = MediaConditionWalkerEntry | MediaCondition; @@ -233,6 +253,22 @@ export class MediaQueryWithoutType { media: this.media, }; } + + isMediaQueryWithoutType(): this is MediaQueryWithoutType { + return MediaQueryWithoutType.isMediaQueryWithoutType(this); + } + + static isMediaQueryWithoutType(x: unknown): x is MediaQueryWithoutType { + if (!x) { + return false; + } + + if (!(x instanceof MediaQueryWithoutType)) { + return false; + } + + return x.type === NodeType.MediaQueryWithoutType; + } } export type MediaQueryWithoutTypeWalkerEntry = MediaConditionWalkerEntry | MediaCondition; @@ -292,6 +328,22 @@ export class MediaQueryInvalid { media: this.media, }; } + + isMediaQueryInvalid(): this is MediaQueryInvalid { + return MediaQueryInvalid.isMediaQueryInvalid(this); + } + + static isMediaQueryInvalid(x: unknown): x is MediaQueryInvalid { + if (!x) { + return false; + } + + if (!(x instanceof MediaQueryInvalid)) { + return false; + } + + return x.type === NodeType.MediaQueryInvalid; + } } export type MediaQueryInvalidWalkerEntry = ComponentValue; diff --git a/packages/media-query-list-parser/src/util/clone-media-query.ts b/packages/media-query-list-parser/src/util/clone-media-query.ts new file mode 100644 index 000000000..ec8b69bde --- /dev/null +++ b/packages/media-query-list-parser/src/util/clone-media-query.ts @@ -0,0 +1,27 @@ +import { cloneTokens, stringify } from '@csstools/css-tokenizer'; +import { MediaQueryInvalid, MediaQueryWithoutType, MediaQueryWithType } from '../nodes/media-query'; +import { parseFromTokens } from '../parser/parse'; +import { isMediaQueryInvalid, isMediaQueryWithoutType, isMediaQueryWithType } from './type-predicates'; + +export function cloneMediaQuery(x: T): T { + const tokens = cloneTokens(x.tokens()); + const parsed = parseFromTokens(tokens, { preserveInvalidMediaQueries: true }); + const firstQuery = parsed[0]; + if (!firstQuery) { + throw new Error(`Failed to clone media query for : "${stringify(...tokens)}"`); + } + + if (isMediaQueryInvalid(x) && isMediaQueryInvalid(firstQuery)) { + return firstQuery as T; + } + + if (isMediaQueryWithType(x) && isMediaQueryWithType(firstQuery)) { + return firstQuery as T; + } + + if (isMediaQueryWithoutType(x) && isMediaQueryWithoutType(firstQuery)) { + return firstQuery as T; + } + + throw new Error(`Failed to clone media query for : "${stringify(...tokens)}"`); +} diff --git a/packages/media-query-list-parser/src/util/type-predicates.ts b/packages/media-query-list-parser/src/util/type-predicates.ts index e1b51bcb7..659f048b4 100644 --- a/packages/media-query-list-parser/src/util/type-predicates.ts +++ b/packages/media-query-list-parser/src/util/type-predicates.ts @@ -12,30 +12,13 @@ import { MediaInParens } from '../nodes/media-in-parens'; import { MediaNot } from '../nodes/media-not'; import { MediaOr } from '../nodes/media-or'; import { MediaQuery, MediaQueryInvalid, MediaQueryWithoutType, MediaQueryWithType } from '../nodes/media-query'; -import { NodeType } from './node-type'; export function isGeneralEnclosed(x: unknown): x is GeneralEnclosed { - if (!x) { - return false; - } - - if (!(x instanceof GeneralEnclosed)) { - return false; - } - - return x.type === NodeType.GeneralEnclosed; + return GeneralEnclosed.isGeneralEnclosed(x); } export function isMediaAnd(x: unknown): x is MediaAnd { - if (!x) { - return false; - } - - if (!(x instanceof MediaAnd)) { - return false; - } - - return x.type === NodeType.MediaAnd; + return MediaAnd.isMediaAnd(x); } export function isMediaConditionList(x: unknown): x is MediaConditionList { @@ -43,87 +26,31 @@ export function isMediaConditionList(x: unknown): x is MediaConditionList { } export function isMediaConditionListWithAnd(x: unknown): x is MediaConditionListWithAnd { - if (!x) { - return false; - } - - if (!(x instanceof MediaConditionListWithAnd)) { - return false; - } - - return x.type === NodeType.MediaConditionListWithAnd; + return MediaConditionListWithAnd.isMediaConditionListWithAnd(x); } export function isMediaConditionListWithOr(x: unknown): x is MediaConditionListWithOr { - if (!x) { - return false; - } - - if (!(x instanceof MediaConditionListWithOr)) { - return false; - } - - return x.type === NodeType.MediaConditionListWithOr; + return MediaConditionListWithOr.isMediaConditionListWithOr(x); } export function isMediaCondition(x: unknown): x is MediaCondition { - if (!x) { - return false; - } - - if (!(x instanceof MediaCondition)) { - return false; - } - - return x.type === NodeType.MediaCondition; + return MediaCondition.isMediaCondition(x); } export function isMediaFeatureBoolean(x: unknown): x is MediaFeatureBoolean { - if (!x) { - return false; - } - - if (!(x instanceof MediaFeatureBoolean)) { - return false; - } - - return x.type === NodeType.MediaFeatureBoolean; + return MediaFeatureBoolean.isMediaFeatureBoolean(x); } export function isMediaFeatureName(x: unknown): x is MediaFeatureName { - if (!x) { - return false; - } - - if (!(x instanceof MediaFeatureName)) { - return false; - } - - return x.type === NodeType.MediaFeatureName; + return MediaFeatureName.isMediaFeatureName(x); } export function isMediaFeatureValue(x: unknown): x is MediaFeatureValue { - if (!x) { - return false; - } - - if (!(x instanceof MediaFeatureValue)) { - return false; - } - - return x.type === NodeType.MediaFeatureValue; + return MediaFeatureValue.isMediaFeatureValue(x); } export function isMediaFeaturePlain(x: unknown): x is MediaFeaturePlain { - if (!x) { - return false; - } - - if (!(x instanceof MediaFeaturePlain)) { - return false; - } - - return x.type === NodeType.MediaFeaturePlain; + return MediaFeaturePlain.isMediaFeaturePlain(x); } export function isMediaFeatureRange(x: unknown): x is MediaFeatureRange { @@ -131,87 +58,31 @@ export function isMediaFeatureRange(x: unknown): x is MediaFeatureRange { } export function isMediaFeatureRangeNameValue(x: unknown): x is MediaFeatureRangeNameValue { - if (!x) { - return false; - } - - if (!(x instanceof MediaFeatureRangeNameValue)) { - return false; - } - - return x.type === NodeType.MediaFeatureRangeNameValue; + return MediaFeatureRangeNameValue.isMediaFeatureRangeNameValue(x); } export function isMediaFeatureRangeValueName(x: unknown): x is MediaFeatureRangeValueName { - if (!x) { - return false; - } - - if (!(x instanceof MediaFeatureRangeValueName)) { - return false; - } - - return x.type === NodeType.MediaFeatureRangeValueName; + return MediaFeatureRangeValueName.isMediaFeatureRangeValueName(x); } export function isMediaFeatureRangeValueNameValue(x: unknown): x is MediaFeatureRangeValueNameValue { - if (!x) { - return false; - } - - if (!(x instanceof MediaFeatureRangeValueNameValue)) { - return false; - } - - return x.type === NodeType.MediaFeatureRangeValueNameValue; + return MediaFeatureRangeValueNameValue.isMediaFeatureRangeValueNameValue(x); } export function isMediaFeature(x: unknown): x is MediaFeature { - if (!x) { - return false; - } - - if (!(x instanceof MediaFeature)) { - return false; - } - - return x.type === NodeType.MediaFeature; + return MediaFeature.isMediaFeature(x); } export function isMediaInParens(x: unknown): x is MediaInParens { - if (!x) { - return false; - } - - if (!(x instanceof MediaInParens)) { - return false; - } - - return x.type === NodeType.MediaInParens; + return MediaInParens.isMediaInParens(x); } export function isMediaNot(x: unknown): x is MediaNot { - if (!x) { - return false; - } - - if (!(x instanceof MediaNot)) { - return false; - } - - return x.type === NodeType.MediaNot; + return MediaNot.isMediaNot(x); } export function isMediaOr(x: unknown): x is MediaOr { - if (!x) { - return false; - } - - if (!(x instanceof MediaOr)) { - return false; - } - - return x.type === NodeType.MediaOr; + return MediaOr.isMediaOr(x); } export function isMediaQuery(x: unknown): x is MediaQuery { @@ -219,37 +90,13 @@ export function isMediaQuery(x: unknown): x is MediaQuery { } export function isMediaQueryWithType(x: unknown): x is MediaQueryWithType { - if (!x) { - return false; - } - - if (!(x instanceof MediaQueryWithType)) { - return false; - } - - return x.type === NodeType.MediaQueryWithType; + return MediaQueryWithType.isMediaQueryWithType(x); } export function isMediaQueryWithoutType(x: unknown): x is MediaQueryWithoutType { - if (!x) { - return false; - } - - if (!(x instanceof MediaQueryWithoutType)) { - return false; - } - - return x.type === NodeType.MediaQueryWithoutType; + return MediaQueryWithoutType.isMediaQueryWithoutType(x); } export function isMediaQueryInvalid(x: unknown): x is MediaQueryInvalid { - if (!x) { - return false; - } - - if (!(x instanceof MediaQueryInvalid)) { - return false; - } - - return x.type === NodeType.MediaQueryInvalid; + return MediaQueryInvalid.isMediaQueryInvalid(x); } diff --git a/plugins/postcss-custom-media/src/transform-at-media-v2/split-media-query-list.ts b/plugins/postcss-custom-media/src/transform-at-media-v2/split-media-query-list.ts deleted file mode 100644 index b047f6e9b..000000000 --- a/plugins/postcss-custom-media/src/transform-at-media-v2/split-media-query-list.ts +++ /dev/null @@ -1,8 +0,0 @@ -import type { CSSToken } from '@csstools/css-tokenizer'; -import { parseFromTokens } from '@csstools/media-query-list-parser'; - -export function splitMediaQueryList(tokens: Array): Array> { - return parseFromTokens(tokens, { preserveInvalidMediaQueries : true }).map((x) => { - return x.tokens(); - }); -} diff --git a/plugins/postcss-custom-media/src/transform-at-media-v2/top-level-combination-keywords.ts b/plugins/postcss-custom-media/src/transform-at-media-v2/top-level-combination-keywords.ts deleted file mode 100644 index b893b5606..000000000 --- a/plugins/postcss-custom-media/src/transform-at-media-v2/top-level-combination-keywords.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { TokenType, TokenIdent } from '@csstools/css-tokenizer'; -import type { CSSToken } from '@csstools/css-tokenizer'; - -export function topLevelCombinationKeywords(tokens: Array): Set { - const keywords: Set = new Set(); - - for (let i = 0; i < tokens.length; i++) { - switch (tokens[i][0]) { - case TokenType.Function: { - let depth = 1; - while (depth !== 0) { - i++; - if (!tokens[i] || tokens[i][0] === TokenType.EOF) { - throw new Error('unexpected EOF'); - } - - switch (tokens[i][0]) { - case TokenType.OpenParen: - case TokenType.Function: - depth++; - break; - case TokenType.CloseParen: - depth--; - break; - } - } - break; - } - - case TokenType.OpenCurly: { - let depth = 1; - while (depth !== 0) { - i++; - if (!tokens[i] || tokens[i][0] === TokenType.EOF) { - throw new Error('unexpected EOF'); - } - - switch (tokens[i][0]) { - case TokenType.OpenCurly: - depth++; - break; - case TokenType.CloseCurly: - depth--; - break; - } - } - break; - } - - case TokenType.OpenSquare: { - let depth = 1; - while (depth !== 0) { - i++; - if (!tokens[i] || tokens[i][0] === TokenType.EOF) { - throw new Error('unexpected EOF'); - } - - switch (tokens[i][0]) { - case TokenType.OpenSquare: - depth++; - break; - case TokenType.CloseSquare: - depth--; - break; - } - } - break; - } - - case TokenType.Ident: { - const identToken = tokens[i] as TokenIdent; - switch (identToken[4].value.toLowerCase()) { - case 'not': - keywords.add('not'); - break; - case 'and': - keywords.add('and'); - break; - case 'or': - keywords.add('or'); - break; - } - - break; - } - } - } - - return keywords; -} diff --git a/plugins/postcss-custom-media/src/transform-at-media-v2/transform-at-media.ts b/plugins/postcss-custom-media/src/transform-at-media-v2/transform-at-media.ts index 1bee3beae..d3875013f 100644 --- a/plugins/postcss-custom-media/src/transform-at-media-v2/transform-at-media.ts +++ b/plugins/postcss-custom-media/src/transform-at-media-v2/transform-at-media.ts @@ -1,19 +1,31 @@ import { stringify, TokenType, TokenIdent } from '@csstools/css-tokenizer'; -import type { CSSToken } from '@csstools/css-tokenizer'; -import { splitMediaQueryList } from './split-media-query-list'; import { alwaysTrue, neverTrue } from './always-true-or-false'; -import { atMediaParamsTokens } from './at-media-params-tokens'; -import { MediaQuery } from '@csstools/media-query-list-parser'; +import { isGeneralEnclosed, isMediaAnd, isMediaConditionList, isMediaFeatureBoolean, isMediaNot, isMediaOr, isMediaQueryInvalid, isMediaQueryWithType, MediaQuery, parse } from '@csstools/media-query-list-parser'; export function transformAtMediaListTokens(params: string, replacements: Map, falsy: Array }>): Array<{ replaceWith: string, encapsulateWith?: string }> { - const mediaQueries = splitMediaQueryList(atMediaParamsTokens(params)); + const mediaQueries = parse(params, { preserveInvalidMediaQueries: true }); - const stringQueries = mediaQueries.map((x) => stringify(...x)); + const stringQueries = mediaQueries.map((x) => x.toString()); for (let i = 0; i < mediaQueries.length; i++) { const mediaQuery = mediaQueries[i]; const original = stringQueries[i]; + { + const transformedQuery = transformSimpleMediaQuery(mediaQuery, replacements); + if (transformedQuery && transformedQuery.replaceWith !== original) { + return stringQueries.map((query, index) => { + if (index === i) { + return transformedQuery; + } + + return { + replaceWith: query, + }; + }); + } + } + const transformedQuery = transformAtMediaTokens(mediaQuery, replacements); if (!transformedQuery || transformedQuery.length === 0) { continue; @@ -37,66 +49,66 @@ export function transformAtMediaListTokens(params: string, replacements: Map, replacements: Map, falsy: Array }>): Array<{replaceWith: string, encapsulateWith?: string}> { - const tokenTypes: Set = new Set(); - let identCounter = 0; - for (let i = 0; i < tokens.length; i++) { - tokenTypes.add(tokens[i][0]); - if (tokens[i][0] === TokenType.Ident) { - identCounter++; - } +export function transformSimpleMediaQuery(mediaQuery: MediaQuery, replacements: Map, falsy: Array }>): { replaceWith: string, encapsulateWith?: string } | null { + if (!mediaQueryIsSimple(mediaQuery)) { + return null; } - tokenTypes.delete(TokenType.Comment); - tokenTypes.delete(TokenType.Whitespace); - tokenTypes.delete(TokenType.OpenParen); - tokenTypes.delete(TokenType.CloseParen); - tokenTypes.delete(TokenType.Ident); - - // replacement slot is in a simple @media query : - // - @media (--custom-mq) { ... } - // - @media ((--custom-mq)) { ... } - if (tokenTypes.size == 0 && identCounter === 1) { - let candidate: Array<{ replaceWith: string, encapsulateWith?: string }> | null = null; - - let parenDepth = 0; - for (let i = 0; i < tokens.length; i++) { - if (tokens[i][0] === TokenType.Whitespace || tokens[i][0] === TokenType.Comment) { - continue; - } - - if (tokens[i][0] === TokenType.CloseParen) { - if (candidate) { - return candidate; - } - } + let candidate: { replaceWith: string, encapsulateWith?: string } | null = null; - candidate = null; + mediaQuery.walk((entry) => { + const node = entry.node; + if (!isMediaFeatureBoolean(node)) { + return; + } - if (tokens[i][0] === TokenType.CloseParen) { - parenDepth--; - continue; - } - if (tokens[i][0] === TokenType.OpenParen) { - parenDepth++; - continue; - } + const name = node.getName(); + if (!name.startsWith('--')) { + return false; + } - if (tokens[i][0] === TokenType.Ident && parenDepth > 0) { - const identToken = tokens[i] as TokenIdent; + const replacement = replacements.get(name); + if (replacement) { + candidate = { + replaceWith: replacement.truthy.map((x) => x.toString().trim()).join(','), + }; - if (replacements.has(identToken[4].value)) { - candidate = [{ - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - replaceWith: replacements.get(identToken[4].value)!.truthy.map((x) => x.toString().trim()).join(','), - }]; - } - } + return false; } + }); - return []; + return candidate; +} + +function mediaQueryIsSimple(mediaQuery: MediaQuery): boolean { + if (isMediaQueryInvalid(mediaQuery)) { + return false; } + if (isMediaQueryWithType(mediaQuery)) { + return false; + } + + let isSimple = true; + mediaQuery.walk((entry) => { + if ( + isMediaAnd(entry.node) || + isMediaOr(entry.node) || + isMediaNot(entry.node) || + isMediaConditionList(entry.node) || + isGeneralEnclosed(entry.node) + ) { + isSimple = false; + return false; + } + }); + + return isSimple; +} + +export function transformAtMediaTokens(mediaQuery: MediaQuery, replacements: Map, falsy: Array }>): Array<{replaceWith: string, encapsulateWith?: string}> { + const tokens = mediaQuery.tokens(); + // replacement slot is in a complex @media query : // - @media not (--custom-mq) { ... } // - @media ((--custom-mq-1) or (--custom-mq-2) or (not (--custom-mq-3))) { ... } diff --git a/plugins/postcss-custom-media/test/basic-after-v9.css b/plugins/postcss-custom-media/test/basic-after-v9.css index ae998d53d..01fb549e9 100644 --- a/plugins/postcss-custom-media/test/basic-after-v9.css +++ b/plugins/postcss-custom-media/test/basic-after-v9.css @@ -7,6 +7,12 @@ } } +@media ((--simple-feature-test)) { + .a { + order: 1.1; + } +} + /* Also a type condition */ @media screen and (--simple-feature-test) { .a { diff --git a/plugins/postcss-custom-media/test/basic-after-v9.expect.css b/plugins/postcss-custom-media/test/basic-after-v9.expect.css index fe92455d5..6d01623fe 100644 --- a/plugins/postcss-custom-media/test/basic-after-v9.expect.css +++ b/plugins/postcss-custom-media/test/basic-after-v9.expect.css @@ -5,6 +5,12 @@ } } +@media (min-width: 300px) { + .a { + order: 1.1; + } +} + /* Also a type condition */ @media (min-width: 300px) { @media screen and (max-color:2147477350) { diff --git a/plugins/postcss-custom-media/test/basic-after-v9.preserve.expect.css b/plugins/postcss-custom-media/test/basic-after-v9.preserve.expect.css index 1a74c4295..5fa31e9fd 100644 --- a/plugins/postcss-custom-media/test/basic-after-v9.preserve.expect.css +++ b/plugins/postcss-custom-media/test/basic-after-v9.preserve.expect.css @@ -12,6 +12,18 @@ } } +@media (min-width: 300px) { + .a { + order: 1.1; + } +} + +@media ((--simple-feature-test)) { + .a { + order: 1.1; + } +} + /* Also a type condition */ @media (min-width: 300px) { @media screen and (max-color:2147477350) { From f70a9ffb033e46c55c310836b9f5bc124f6587a2 Mon Sep 17 00:00:00 2001 From: Romain Menke Date: Sat, 5 Nov 2022 17:33:45 +0100 Subject: [PATCH 32/35] fix --- .../src/nodes/media-query.ts | 2 +- plugins/postcss-custom-media/test/comma-2.css | 6 +++- .../test/comma-2.expect.css | 34 +++++++++++++++++++ rollup/presets/package-typescript.js | 6 ++-- 4 files changed, 43 insertions(+), 5 deletions(-) diff --git a/packages/media-query-list-parser/src/nodes/media-query.ts b/packages/media-query-list-parser/src/nodes/media-query.ts index a8b91c0d2..e6a1a6531 100644 --- a/packages/media-query-list-parser/src/nodes/media-query.ts +++ b/packages/media-query-list-parser/src/nodes/media-query.ts @@ -48,7 +48,7 @@ export class MediaQueryWithType { [TokenType.Whitespace, ' ', -1, -1, undefined], ]; - return; + return copy; } for (let i = 0; i < copy.modifier.length; i++) { diff --git a/plugins/postcss-custom-media/test/comma-2.css b/plugins/postcss-custom-media/test/comma-2.css index 16f4b8764..6e7c20c54 100644 --- a/plugins/postcss-custom-media/test/comma-2.css +++ b/plugins/postcss-custom-media/test/comma-2.css @@ -43,4 +43,8 @@ } } - +@media ((--list-1) or (min-width: 300px)),((--list-2) and (min-height: 300px)) { + .a { + order: 8; + } +} diff --git a/plugins/postcss-custom-media/test/comma-2.expect.css b/plugins/postcss-custom-media/test/comma-2.expect.css index 0bb166200..820d9b2cb 100644 --- a/plugins/postcss-custom-media/test/comma-2.expect.css +++ b/plugins/postcss-custom-media/test/comma-2.expect.css @@ -40,4 +40,38 @@ } } +@media screen { +@media ((max-color:2147477350) or (min-width: 300px)) { + .a { + order: 8; + } +} +} + +@media not screen { + +@media ((color:2147477350) or (min-width: 300px)) { + .a { + order: 8; + } +} +} + +@media print { + +@media ((max-color:2147477350) and (min-height: 300px)) { + .a { + order: 8; + } +} +} + +@media not print { + +@media ((color:2147477350) and (min-height: 300px)) { + .a { + order: 8; + } +} +} diff --git a/rollup/presets/package-typescript.js b/rollup/presets/package-typescript.js index 6242f32c5..4684c6bf9 100644 --- a/rollup/presets/package-typescript.js +++ b/rollup/presets/package-typescript.js @@ -21,9 +21,9 @@ export function packageTypescript() { extensions: ['.js', '.ts'], presets: packageBabelPreset, }), - terser({ - keep_classnames: true, - }), + // terser({ + // keep_classnames: true, + // }), ], }, ]; From 092b246194759a17c448e49d7f1e08143bdb2f10 Mon Sep 17 00:00:00 2001 From: Romain Menke Date: Sat, 5 Nov 2022 17:35:49 +0100 Subject: [PATCH 33/35] fix --- rollup/presets/package-typescript.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/rollup/presets/package-typescript.js b/rollup/presets/package-typescript.js index 4684c6bf9..6242f32c5 100644 --- a/rollup/presets/package-typescript.js +++ b/rollup/presets/package-typescript.js @@ -21,9 +21,9 @@ export function packageTypescript() { extensions: ['.js', '.ts'], presets: packageBabelPreset, }), - // terser({ - // keep_classnames: true, - // }), + terser({ + keep_classnames: true, + }), ], }, ]; From 15556df0feee34b3746fe5d88c033c56c3f2efb6 Mon Sep 17 00:00:00 2001 From: Romain Menke Date: Sat, 5 Nov 2022 18:06:16 +0100 Subject: [PATCH 34/35] finish up --- plugins/postcss-custom-media/.tape.mjs | 6 +- .../src/custom-media-from-root.ts | 2 +- plugins/postcss-custom-media/src/index.ts | 2 +- .../src/is-processable-custom-media-rule.ts | 4 - .../always-true-or-false.ts | 14 - .../at-media-params-tokens.ts | 19 -- .../src/transform-at-media-v2/custom-media.ts | 59 ---- .../transform-at-media.ts | 240 -------------- .../transform-at-media-v2/true-and-false.ts | 68 ---- .../always-true-or-false.ts | 4 +- .../at-media-params-tokens.ts | 3 +- .../src/transform-at-media/custom-media.ts | 108 ++----- .../split-media-query-list.ts | 60 ---- .../top-level-combination-keywords.ts | 90 ------ .../transform-at-media/transform-at-media.ts | 292 +++++++----------- .../src/transform-at-media/true-and-false.ts | 2 + plugins/postcss-custom-media/test/nesting.css | 25 ++ .../test/nesting.expect.css | 20 ++ .../test/not-processable.css | 4 - .../test/not-processable.expect.css | 4 - 20 files changed, 196 insertions(+), 830 deletions(-) delete mode 100644 plugins/postcss-custom-media/src/transform-at-media-v2/always-true-or-false.ts delete mode 100644 plugins/postcss-custom-media/src/transform-at-media-v2/at-media-params-tokens.ts delete mode 100644 plugins/postcss-custom-media/src/transform-at-media-v2/custom-media.ts delete mode 100644 plugins/postcss-custom-media/src/transform-at-media-v2/transform-at-media.ts delete mode 100644 plugins/postcss-custom-media/src/transform-at-media-v2/true-and-false.ts delete mode 100644 plugins/postcss-custom-media/src/transform-at-media/split-media-query-list.ts delete mode 100644 plugins/postcss-custom-media/src/transform-at-media/top-level-combination-keywords.ts create mode 100644 plugins/postcss-custom-media/test/nesting.css create mode 100644 plugins/postcss-custom-media/test/nesting.expect.css diff --git a/plugins/postcss-custom-media/.tape.mjs b/plugins/postcss-custom-media/.tape.mjs index ef9ef8086..7dac6d271 100644 --- a/plugins/postcss-custom-media/.tape.mjs +++ b/plugins/postcss-custom-media/.tape.mjs @@ -24,6 +24,9 @@ postcssTape(plugin)({ preserve: true } }, + 'nesting': { + message: 'works when nested' + }, 'not-processable': { message: 'only handles processable @custom-media rules' }, @@ -31,7 +34,8 @@ postcssTape(plugin)({ message: 'supports more obscure CSS' }, 'eof-1': { - message: 'handles EOF correctly (1)' + message: 'handles EOF correctly (1)', + warnings: 1, }, 'eof-2': { message: 'handles EOF correctly (2)', diff --git a/plugins/postcss-custom-media/src/custom-media-from-root.ts b/plugins/postcss-custom-media/src/custom-media-from-root.ts index ec196d691..964e69bc0 100644 --- a/plugins/postcss-custom-media/src/custom-media-from-root.ts +++ b/plugins/postcss-custom-media/src/custom-media-from-root.ts @@ -2,7 +2,7 @@ import { MediaQuery } from '@csstools/media-query-list-parser'; import type { ChildNode, Container, Document, Root as PostCSSRoot } from 'postcss'; import { isProcessableCustomMediaRule } from './is-processable-custom-media-rule'; import { removeCyclicReferences } from './toposort'; -import { parseCustomMedia } from './transform-at-media-v2/custom-media'; +import { parseCustomMedia } from './transform-at-media/custom-media'; // return custom media from the css root, conditionally removing them export default function getCustomMedia(root: PostCSSRoot, result, opts: { preserve?: boolean }): Map, falsy: Array }> { diff --git a/plugins/postcss-custom-media/src/index.ts b/plugins/postcss-custom-media/src/index.ts index f77e6db47..9ab51864f 100644 --- a/plugins/postcss-custom-media/src/index.ts +++ b/plugins/postcss-custom-media/src/index.ts @@ -1,7 +1,7 @@ import { MediaQuery } from '@csstools/media-query-list-parser'; import type { PluginCreator } from 'postcss'; import getCustomMedia from './custom-media-from-root'; -import { transformAtMediaListTokens } from './transform-at-media-v2/transform-at-media'; +import { transformAtMediaListTokens } from './transform-at-media/transform-at-media'; export interface PluginOptions { /** Determines whether Custom Media and media queries using custom media should be preserved in their original form. */ diff --git a/plugins/postcss-custom-media/src/is-processable-custom-media-rule.ts b/plugins/postcss-custom-media/src/is-processable-custom-media-rule.ts index 02053ed77..ba3947ccd 100644 --- a/plugins/postcss-custom-media/src/is-processable-custom-media-rule.ts +++ b/plugins/postcss-custom-media/src/is-processable-custom-media-rule.ts @@ -17,10 +17,6 @@ export function isProcessableCustomMediaRule(atRule: AtRule): boolean { let parent: Container | Document = atRule.parent; while (parent) { - if (parent.type === 'rule') { - return false; - } - if (parent.type === 'atrule' && !allowedParentAtRules.has((parent as AtRule).name.toLowerCase())) { return false; } diff --git a/plugins/postcss-custom-media/src/transform-at-media-v2/always-true-or-false.ts b/plugins/postcss-custom-media/src/transform-at-media-v2/always-true-or-false.ts deleted file mode 100644 index ec03674d1..000000000 --- a/plugins/postcss-custom-media/src/transform-at-media-v2/always-true-or-false.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { NumberType, TokenType } from '@csstools/css-tokenizer'; -import type { CSSToken } from '@csstools/css-tokenizer'; - -export const alwaysTrue: Array = [ - [TokenType.Ident, 'max-color', 0, 0, { value: 'max-color' }], - [TokenType.Colon, ':', 0, 0, undefined], - [TokenType.Number, '2147477350', 0, 0, { value: 2147477350, type: NumberType.Integer }], -]; - -export const neverTrue: Array = [ - [TokenType.Ident, 'color', 0, 0, { value: 'color' }], - [TokenType.Colon, ':', 0, 0, undefined], - [TokenType.Number, '2147477350', 0, 0, { value: 2147477350, type: NumberType.Integer }], -]; diff --git a/plugins/postcss-custom-media/src/transform-at-media-v2/at-media-params-tokens.ts b/plugins/postcss-custom-media/src/transform-at-media-v2/at-media-params-tokens.ts deleted file mode 100644 index 815e1dd30..000000000 --- a/plugins/postcss-custom-media/src/transform-at-media-v2/at-media-params-tokens.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { tokenizer } from '@csstools/css-tokenizer'; -import type { CSSToken } from '@csstools/css-tokenizer'; - -export function atMediaParamsTokens(params: string): Array { - const t = tokenizer({ - css: params, - }, { - commentsAreTokens: true, onParseError: () => { - throw new Error(`Unable to parse media query "${params}"`); - }, - }); - - const tokens: Array = []; - while (!t.endOfFile()) { - tokens.push(t.nextToken()); - } - - return tokens; -} diff --git a/plugins/postcss-custom-media/src/transform-at-media-v2/custom-media.ts b/plugins/postcss-custom-media/src/transform-at-media-v2/custom-media.ts deleted file mode 100644 index 1a4ee061c..000000000 --- a/plugins/postcss-custom-media/src/transform-at-media-v2/custom-media.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { cloneTokens, TokenIdent, TokenType } from '@csstools/css-tokenizer'; -import { parseFromTokens, MediaQuery } from '@csstools/media-query-list-parser'; -import { atMediaParamsTokens } from '../transform-at-media/at-media-params-tokens'; -import { replaceTrueAndFalseTokens } from './true-and-false'; - -export function parseCustomMedia(params: string): { name: string, truthy: Array, falsy: Array, dependsOn: Array<[string, string]> } | false { - const tokens = atMediaParamsTokens(params); - - const customMediaReferences: Set = new Set(); - - let name = ''; - let remainder = tokens; - for (let i = 0; i < tokens.length; i++) { - if (tokens[i][0] === TokenType.Comment) { - continue; - } - if (tokens[i][0] === TokenType.Whitespace) { - continue; - } - - if (tokens[i][0] === TokenType.Ident) { - const identToken = tokens[i] as TokenIdent; - if (identToken[4].value.startsWith('--')) { - name = identToken[4].value; - remainder = tokens.slice(i + 1); - break; - } - } - - return false; - } - - for (let i = 0; i < remainder.length; i++) { - if (remainder[i][0] === TokenType.Ident) { - const identToken = remainder[i] as TokenIdent; - if (identToken[4].value.startsWith('--')) { - customMediaReferences.add(identToken[4].value); - } - } - } - - remainder = replaceTrueAndFalseTokens(remainder); - - const mediaQueryListTruthy = parseFromTokens(cloneTokens(remainder), { preserveInvalidMediaQueries : true }); - const mediaQueryListFalsy = parseFromTokens(cloneTokens(remainder), { preserveInvalidMediaQueries: true }); - - for (let i = 0; i < mediaQueryListFalsy.length; i++) { - mediaQueryListFalsy[i] = mediaQueryListFalsy[i].negateQuery(); - } - - return { - name: name, - truthy: mediaQueryListTruthy, - falsy: mediaQueryListFalsy, - dependsOn: Array.from(customMediaReferences).map((x) => { - return [x, name]; - }), - }; -} diff --git a/plugins/postcss-custom-media/src/transform-at-media-v2/transform-at-media.ts b/plugins/postcss-custom-media/src/transform-at-media-v2/transform-at-media.ts deleted file mode 100644 index d3875013f..000000000 --- a/plugins/postcss-custom-media/src/transform-at-media-v2/transform-at-media.ts +++ /dev/null @@ -1,240 +0,0 @@ -import { stringify, TokenType, TokenIdent } from '@csstools/css-tokenizer'; -import { alwaysTrue, neverTrue } from './always-true-or-false'; -import { isGeneralEnclosed, isMediaAnd, isMediaConditionList, isMediaFeatureBoolean, isMediaNot, isMediaOr, isMediaQueryInvalid, isMediaQueryWithType, MediaQuery, parse } from '@csstools/media-query-list-parser'; - -export function transformAtMediaListTokens(params: string, replacements: Map, falsy: Array }>): Array<{ replaceWith: string, encapsulateWith?: string }> { - const mediaQueries = parse(params, { preserveInvalidMediaQueries: true }); - - const stringQueries = mediaQueries.map((x) => x.toString()); - - for (let i = 0; i < mediaQueries.length; i++) { - const mediaQuery = mediaQueries[i]; - const original = stringQueries[i]; - - { - const transformedQuery = transformSimpleMediaQuery(mediaQuery, replacements); - if (transformedQuery && transformedQuery.replaceWith !== original) { - return stringQueries.map((query, index) => { - if (index === i) { - return transformedQuery; - } - - return { - replaceWith: query, - }; - }); - } - } - - const transformedQuery = transformAtMediaTokens(mediaQuery, replacements); - if (!transformedQuery || transformedQuery.length === 0) { - continue; - } - - if (transformedQuery[0].replaceWith === original) { - continue; - } - - return stringQueries.flatMap((query, index) => { - if (index === i) { - return transformedQuery; - } - - return [{ - replaceWith: query, - }]; - }); - } - - return []; -} - -export function transformSimpleMediaQuery(mediaQuery: MediaQuery, replacements: Map, falsy: Array }>): { replaceWith: string, encapsulateWith?: string } | null { - if (!mediaQueryIsSimple(mediaQuery)) { - return null; - } - - let candidate: { replaceWith: string, encapsulateWith?: string } | null = null; - - mediaQuery.walk((entry) => { - const node = entry.node; - if (!isMediaFeatureBoolean(node)) { - return; - } - - const name = node.getName(); - if (!name.startsWith('--')) { - return false; - } - - const replacement = replacements.get(name); - if (replacement) { - candidate = { - replaceWith: replacement.truthy.map((x) => x.toString().trim()).join(','), - }; - - return false; - } - }); - - return candidate; -} - -function mediaQueryIsSimple(mediaQuery: MediaQuery): boolean { - if (isMediaQueryInvalid(mediaQuery)) { - return false; - } - - if (isMediaQueryWithType(mediaQuery)) { - return false; - } - - let isSimple = true; - mediaQuery.walk((entry) => { - if ( - isMediaAnd(entry.node) || - isMediaOr(entry.node) || - isMediaNot(entry.node) || - isMediaConditionList(entry.node) || - isGeneralEnclosed(entry.node) - ) { - isSimple = false; - return false; - } - }); - - return isSimple; -} - -export function transformAtMediaTokens(mediaQuery: MediaQuery, replacements: Map, falsy: Array }>): Array<{replaceWith: string, encapsulateWith?: string}> { - const tokens = mediaQuery.tokens(); - - // replacement slot is in a complex @media query : - // - @media not (--custom-mq) { ... } - // - @media ((--custom-mq-1) or (--custom-mq-2) or (not (--custom-mq-3))) { ... } - for (let i = 0; i < tokens.length; i++) { - switch (tokens[i][0]) { - case TokenType.Function: { - let depth = 1; - while (depth !== 0) { - i++; - if (!tokens[i] || tokens[i][0] === TokenType.EOF) { - throw new Error('unexpected EOF'); - } - - switch (tokens[i][0]) { - case TokenType.OpenParen: - case TokenType.Function: - depth++; - break; - case TokenType.CloseParen: - depth--; - break; - } - } - break; - } - - case TokenType.OpenCurly: { - let depth = 1; - while (depth !== 0) { - i++; - if (!tokens[i] || tokens[i][0] === TokenType.EOF) { - throw new Error('unexpected EOF'); - } - - switch (tokens[i][0]) { - case TokenType.OpenCurly: - depth++; - break; - case TokenType.CloseCurly: - depth--; - break; - } - } - break; - } - - case TokenType.OpenSquare: { - let depth = 1; - while (depth !== 0) { - i++; - if (!tokens[i] || tokens[i][0] === TokenType.EOF) { - throw new Error('unexpected EOF'); - } - - switch (tokens[i][0]) { - case TokenType.OpenSquare: - depth++; - break; - case TokenType.CloseSquare: - depth--; - break; - } - } - break; - } - - case TokenType.Ident: { - const identToken = tokens[i] as TokenIdent; - - if (!replacements.has(identToken[4].value)) { - break; - } - - let isValid = true; - for (let p = i-1; p>= 0; p--) { - if (tokens[p][0] === TokenType.Comment || tokens[p][0] === TokenType.Whitespace) { - continue; - } - - if (tokens[p][0] === TokenType.OpenParen) { - break; - } - - isValid = false; - break; - } - - for (let n = i + 1; n < tokens.length; n++) { - if (tokens[n][0] === TokenType.Comment || tokens[n][0] === TokenType.Whitespace) { - continue; - } - - if (tokens[n][0] === TokenType.CloseParen) { - break; - } - - isValid = false; - break; - } - - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - const replacement = replacements.get(identToken[4].value)!; - - if (isValid) { - const replaceWithTrue = tokens.slice(); - replaceWithTrue.splice(i, 1, ...alwaysTrue); - - const replaceWithFalse = tokens.slice(); - replaceWithFalse.splice(i, 1, ...neverTrue); - - return [ - { - replaceWith: stringify(...replaceWithTrue), - encapsulateWith: replacement.truthy.map((x) => x.toString().trim()).join(','), - }, - { - replaceWith: stringify(...replaceWithFalse), - encapsulateWith: replacement.falsy.map((x) => x.toString().trim()).join(','), - }, - ]; - } - - break; - } - } - } - - return []; -} diff --git a/plugins/postcss-custom-media/src/transform-at-media-v2/true-and-false.ts b/plugins/postcss-custom-media/src/transform-at-media-v2/true-and-false.ts deleted file mode 100644 index ebb56a460..000000000 --- a/plugins/postcss-custom-media/src/transform-at-media-v2/true-and-false.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { TokenType, TokenIdent } from '@csstools/css-tokenizer'; -import type { CSSToken } from '@csstools/css-tokenizer'; -import { alwaysTrue, neverTrue } from './always-true-or-false'; - -export function replaceTrueAndFalseTokens(tokens: Array): Array { - let booleanToken; - let remainder; - - for (let i = 0; i < tokens.length; i++) { - if (tokens[i][0] === TokenType.Comment) { - continue; - } - if (tokens[i][0] === TokenType.Whitespace) { - continue; - } - - if (tokens[i][0] === TokenType.Ident) { - const identToken = tokens[i] as TokenIdent; - if (identToken[4].value.toLowerCase() === 'true') { - booleanToken = 'true'; - remainder = tokens.slice(i + 1); - break; - } - - if (identToken[4].value.toLowerCase() === 'false') { - booleanToken = 'false'; - remainder = tokens.slice(i + 1); - break; - } - } - - return tokens; - } - - if (!booleanToken) { - return tokens; - } - - { - // Nothing is allowed after true|false except for comments and whitespace - for (let i = 0; i < remainder.length; i++) { - if (remainder[i][0] === TokenType.Comment) { - continue; - } - if (remainder[i][0] === TokenType.Whitespace) { - continue; - } - - return tokens; - } - } - - if (booleanToken === 'true') { - return [ - [TokenType.Whitespace, ' ', 0, 0, undefined], - [TokenType.OpenParen, '(', 0, 0, undefined], - ...alwaysTrue, - [TokenType.CloseParen, ')', 0, 0, undefined], - ]; - } - - return [ - [TokenType.Whitespace, ' ', 0, 0, undefined], - [TokenType.OpenParen, '(', 0, 0, undefined], - ...neverTrue, - [TokenType.CloseParen, ')', 0, 0, undefined], - ]; -} diff --git a/plugins/postcss-custom-media/src/transform-at-media/always-true-or-false.ts b/plugins/postcss-custom-media/src/transform-at-media/always-true-or-false.ts index 78d0c72b0..ec03674d1 100644 --- a/plugins/postcss-custom-media/src/transform-at-media/always-true-or-false.ts +++ b/plugins/postcss-custom-media/src/transform-at-media/always-true-or-false.ts @@ -4,11 +4,11 @@ import type { CSSToken } from '@csstools/css-tokenizer'; export const alwaysTrue: Array = [ [TokenType.Ident, 'max-color', 0, 0, { value: 'max-color' }], [TokenType.Colon, ':', 0, 0, undefined], - [TokenType.Number, '2147477350', 0, 0, { value: 9999943, type: NumberType.Integer }], + [TokenType.Number, '2147477350', 0, 0, { value: 2147477350, type: NumberType.Integer }], ]; export const neverTrue: Array = [ [TokenType.Ident, 'color', 0, 0, { value: 'color' }], [TokenType.Colon, ':', 0, 0, undefined], - [TokenType.Number, '2147477350', 0, 0, { value: 9999943, type: NumberType.Integer }], + [TokenType.Number, '2147477350', 0, 0, { value: 2147477350, type: NumberType.Integer }], ]; diff --git a/plugins/postcss-custom-media/src/transform-at-media/at-media-params-tokens.ts b/plugins/postcss-custom-media/src/transform-at-media/at-media-params-tokens.ts index 815e1dd30..3b96058ad 100644 --- a/plugins/postcss-custom-media/src/transform-at-media/at-media-params-tokens.ts +++ b/plugins/postcss-custom-media/src/transform-at-media/at-media-params-tokens.ts @@ -5,7 +5,8 @@ export function atMediaParamsTokens(params: string): Array { const t = tokenizer({ css: params, }, { - commentsAreTokens: true, onParseError: () => { + commentsAreTokens: true, + onParseError: () => { throw new Error(`Unable to parse media query "${params}"`); }, }); diff --git a/plugins/postcss-custom-media/src/transform-at-media/custom-media.ts b/plugins/postcss-custom-media/src/transform-at-media/custom-media.ts index c38e23404..bd9de7806 100644 --- a/plugins/postcss-custom-media/src/transform-at-media/custom-media.ts +++ b/plugins/postcss-custom-media/src/transform-at-media/custom-media.ts @@ -1,10 +1,9 @@ -import { stringify, TokenType, TokenIdent } from '@csstools/css-tokenizer'; -import { topLevelCombinationKeywords } from './top-level-combination-keywords'; -import { splitMediaQueryList } from './split-media-query-list'; +import { cloneTokens, stringify, TokenIdent, TokenType } from '@csstools/css-tokenizer'; +import { parseFromTokens, MediaQuery } from '@csstools/media-query-list-parser'; +import { atMediaParamsTokens } from '../transform-at-media/at-media-params-tokens'; import { replaceTrueAndFalseTokens } from './true-and-false'; -import { atMediaParamsTokens } from './at-media-params-tokens'; -export function parseCustomMedia(params: string): { name: string, truthy: string, falsy: string, dependsOn: Array<[string, string]> } | false { +export function parseCustomMedia(params: string): { name: string, truthy: Array, falsy: Array, dependsOn: Array<[string, string]> } | false { const tokens = atMediaParamsTokens(params); const customMediaReferences: Set = new Set(); @@ -40,90 +39,29 @@ export function parseCustomMedia(params: string): { name: string, truthy: string } } - const list = splitMediaQueryList(remainder); - const truthyParts = []; - const falsyParts = []; - - MEDIA_QUERY_LIST_LOOP: - for (let i = 0; i < list.length; i++) { - const mediaQuery = replaceTrueAndFalseTokens(list[i]); - - const truthy = stringify(...mediaQuery); - - for (let j = 0; j < mediaQuery.length; j++) { - if (mediaQuery[j][0] === TokenType.Comment) { - continue; - } - if (mediaQuery[j][0] === TokenType.Whitespace) { - continue; - } - - if (mediaQuery[j][0] === TokenType.Ident) { - const identToken = mediaQuery[j] as TokenIdent; - if (identToken[4].value.toLowerCase() === 'not') { - truthyParts.push(truthy); - - const falsy = mediaQuery.slice(); - falsy.splice(j, 1); - - falsyParts.push(stringify(...falsy)); - continue MEDIA_QUERY_LIST_LOOP; - } - - if (identToken[4].value.toLowerCase() === 'only') { - mediaQuery[j][1] = 'not'; - mediaQuery[j][4].value = 'not'; - - truthyParts.push(truthy); - falsyParts.push(stringify(...mediaQuery)); - continue MEDIA_QUERY_LIST_LOOP; - } - } - - const falsy = mediaQuery.slice(); - - const falsyRemainder = falsy.slice(j); - const falsyRemainderKeywords = topLevelCombinationKeywords(falsyRemainder); - falsyRemainderKeywords.delete('and'); - - if (falsyRemainderKeywords.size > 0) { - falsy.splice(j, 0, - [TokenType.Ident, 'not', 0, 0, { value: 'not' }], - [TokenType.Whitespace, ' ', 0, 0, undefined], - [TokenType.Ident, 'all', 0, 0, { value: 'all' }], - [TokenType.Whitespace, ' ', 0, 0, undefined], - [TokenType.Ident, 'and', 0, 0, { value: 'and' }], - [TokenType.Whitespace, ' ', 0, 0, undefined], - [TokenType.OpenParen, '(', 0, 0, undefined], - ); - falsy.push( - [TokenType.CloseParen, ')', 0, 0, undefined], - ); - } else { - falsy.splice(j, 0, - [TokenType.Ident, 'not', 0, 0, { value: 'not' }], - [TokenType.Whitespace, ' ', 0, 0, undefined], - [TokenType.Ident, 'all', 0, 0, { value: 'all' }], - [TokenType.Whitespace, ' ', 0, 0, undefined], - [TokenType.Ident, 'and', 0, 0, { value: 'and' }], - [TokenType.Whitespace, ' ', 0, 0, undefined], - ); - } - - truthyParts.push(truthy); - falsyParts.push(stringify(...falsy)); - continue MEDIA_QUERY_LIST_LOOP; - } - - truthyParts.push(truthy); - falsyParts.push('not all'); - continue MEDIA_QUERY_LIST_LOOP; + remainder = replaceTrueAndFalseTokens(remainder); + + const mediaQueryListTruthy = parseFromTokens(cloneTokens(remainder), { + preserveInvalidMediaQueries: true, + onParseError: () => { + throw new Error(`Unable to parse media query "${stringify(...remainder)}"`); + }, + }); + const mediaQueryListFalsy = parseFromTokens(cloneTokens(remainder), { + preserveInvalidMediaQueries: true, + onParseError: () => { + throw new Error(`Unable to parse media query "${stringify(...remainder) }"`); + }, + }); + + for (let i = 0; i < mediaQueryListFalsy.length; i++) { + mediaQueryListFalsy[i] = mediaQueryListFalsy[i].negateQuery(); } return { name: name, - truthy: truthyParts.map((x) => x.trim()).join(','), - falsy: falsyParts.map((x) => x.trim()).join(','), + truthy: mediaQueryListTruthy, + falsy: mediaQueryListFalsy, dependsOn: Array.from(customMediaReferences).map((x) => { return [x, name]; }), diff --git a/plugins/postcss-custom-media/src/transform-at-media/split-media-query-list.ts b/plugins/postcss-custom-media/src/transform-at-media/split-media-query-list.ts deleted file mode 100644 index ae43ab3d2..000000000 --- a/plugins/postcss-custom-media/src/transform-at-media/split-media-query-list.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { TokenType } from '@csstools/css-tokenizer'; -import type { CSSToken } from '@csstools/css-tokenizer'; - -export function splitMediaQueryList(tokens: Array): Array> { - let parenDepth = 0; - let squareDepth = 0; - let curlyDepth = 0; - let depth = 0; - - const listItems = []; - let lastSliceIndex = 0; - - for (let i = 0; i < tokens.length; i++) { - if (tokens[i][0] === TokenType.OpenParen || tokens[i][0] === TokenType.Function) { - depth++; - parenDepth++; - continue; - } - if (tokens[i][0] === TokenType.CloseParen && parenDepth > 0) { - depth--; - parenDepth--; - continue; - } - - if (tokens[i][0] === TokenType.OpenCurly) { - depth++; - curlyDepth++; - continue; - } - if (tokens[i][0] === TokenType.CloseCurly && curlyDepth > 0) { - depth--; - curlyDepth--; - continue; - } - - if (tokens[i][0] === TokenType.OpenSquare) { - depth++; - squareDepth++; - continue; - } - if (tokens[i][0] === TokenType.CloseSquare && squareDepth > 0) { - depth--; - squareDepth--; - continue; - } - - if (tokens[i][0] === TokenType.Comma && depth === 0) { - listItems.push(tokens.slice(lastSliceIndex, i)); - lastSliceIndex = i + 1; - continue; - } - } - - if (lastSliceIndex === 0) { - return [tokens]; - } - - listItems.push(tokens.slice(lastSliceIndex)); - return listItems; -} diff --git a/plugins/postcss-custom-media/src/transform-at-media/top-level-combination-keywords.ts b/plugins/postcss-custom-media/src/transform-at-media/top-level-combination-keywords.ts deleted file mode 100644 index b893b5606..000000000 --- a/plugins/postcss-custom-media/src/transform-at-media/top-level-combination-keywords.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { TokenType, TokenIdent } from '@csstools/css-tokenizer'; -import type { CSSToken } from '@csstools/css-tokenizer'; - -export function topLevelCombinationKeywords(tokens: Array): Set { - const keywords: Set = new Set(); - - for (let i = 0; i < tokens.length; i++) { - switch (tokens[i][0]) { - case TokenType.Function: { - let depth = 1; - while (depth !== 0) { - i++; - if (!tokens[i] || tokens[i][0] === TokenType.EOF) { - throw new Error('unexpected EOF'); - } - - switch (tokens[i][0]) { - case TokenType.OpenParen: - case TokenType.Function: - depth++; - break; - case TokenType.CloseParen: - depth--; - break; - } - } - break; - } - - case TokenType.OpenCurly: { - let depth = 1; - while (depth !== 0) { - i++; - if (!tokens[i] || tokens[i][0] === TokenType.EOF) { - throw new Error('unexpected EOF'); - } - - switch (tokens[i][0]) { - case TokenType.OpenCurly: - depth++; - break; - case TokenType.CloseCurly: - depth--; - break; - } - } - break; - } - - case TokenType.OpenSquare: { - let depth = 1; - while (depth !== 0) { - i++; - if (!tokens[i] || tokens[i][0] === TokenType.EOF) { - throw new Error('unexpected EOF'); - } - - switch (tokens[i][0]) { - case TokenType.OpenSquare: - depth++; - break; - case TokenType.CloseSquare: - depth--; - break; - } - } - break; - } - - case TokenType.Ident: { - const identToken = tokens[i] as TokenIdent; - switch (identToken[4].value.toLowerCase()) { - case 'not': - keywords.add('not'); - break; - case 'and': - keywords.add('and'); - break; - case 'or': - keywords.add('or'); - break; - } - - break; - } - } - } - - return keywords; -} diff --git a/plugins/postcss-custom-media/src/transform-at-media/transform-at-media.ts b/plugins/postcss-custom-media/src/transform-at-media/transform-at-media.ts index f405b7a83..65bea992a 100644 --- a/plugins/postcss-custom-media/src/transform-at-media/transform-at-media.ts +++ b/plugins/postcss-custom-media/src/transform-at-media/transform-at-media.ts @@ -1,19 +1,35 @@ -import { stringify, TokenType, TokenIdent } from '@csstools/css-tokenizer'; -import type { CSSToken } from '@csstools/css-tokenizer'; -import { splitMediaQueryList } from './split-media-query-list'; import { alwaysTrue, neverTrue } from './always-true-or-false'; -import { atMediaParamsTokens } from './at-media-params-tokens'; +import { isGeneralEnclosed, isMediaAnd, isMediaConditionList, isMediaFeature, isMediaFeatureBoolean, isMediaNot, isMediaOr, isMediaQueryInvalid, isMediaQueryWithType, MediaQuery, newMediaFeaturePlain, parse } from '@csstools/media-query-list-parser'; -export function transformAtMediaListTokens(params: string, replacements: Map): Array<{ replaceWith: string, encapsulateWith?: string }> { - const mediaQueries = splitMediaQueryList(atMediaParamsTokens(params)); +export function transformAtMediaListTokens(params: string, replacements: Map, falsy: Array }>): Array<{ replaceWith: string, encapsulateWith?: string }> { + const mediaQueries = parse(params, { + preserveInvalidMediaQueries: true, onParseError: () => { + throw new Error(`Unable to parse media query "${params}"`); + }, + }); - const stringQueries = mediaQueries.map((x) => stringify(...x)); + const stringQueries = mediaQueries.map((x) => x.toString()); for (let i = 0; i < mediaQueries.length; i++) { const mediaQuery = mediaQueries[i]; const original = stringQueries[i]; - const transformedQuery = transformAtMediaTokens(mediaQuery, replacements); + { + const transformedQuery = transformSimpleMediaQuery(mediaQuery, replacements); + if (transformedQuery && transformedQuery.replaceWith !== original) { + return stringQueries.map((query, index) => { + if (index === i) { + return transformedQuery; + } + + return { + replaceWith: query, + }; + }); + } + } + + const transformedQuery = transformComplexMediaQuery(mediaQuery, replacements); if (!transformedQuery || transformedQuery.length === 0) { continue; } @@ -36,192 +52,114 @@ export function transformAtMediaListTokens(params: string, replacements: Map, replacements: Map): Array<{replaceWith: string, encapsulateWith?: string}> { - const tokenTypes: Set = new Set(); - let identCounter = 0; - for (let i = 0; i < tokens.length; i++) { - tokenTypes.add(tokens[i][0]); - if (tokens[i][0] === TokenType.Ident) { - identCounter++; - } +export function transformSimpleMediaQuery(mediaQuery: MediaQuery, replacements: Map, falsy: Array }>): { replaceWith: string, encapsulateWith?: string } | null { + if (!mediaQueryIsSimple(mediaQuery)) { + return null; } - tokenTypes.delete(TokenType.Comment); - tokenTypes.delete(TokenType.Whitespace); - tokenTypes.delete(TokenType.OpenParen); - tokenTypes.delete(TokenType.CloseParen); - tokenTypes.delete(TokenType.Ident); - - // replacement slot is in a simple @media query : - // - @media (--custom-mq) { ... } - // - @media ((--custom-mq)) { ... } - if (tokenTypes.size == 0 && identCounter === 1) { - let candidate: Array<{ replaceWith: string, encapsulateWith?: string }> | null = null; - - let parenDepth = 0; - for (let i = 0; i < tokens.length; i++) { - if (tokens[i][0] === TokenType.Whitespace || tokens[i][0] === TokenType.Comment) { - continue; - } - - if (tokens[i][0] === TokenType.CloseParen) { - if (candidate) { - return candidate; - } - } - - candidate = null; - - if (tokens[i][0] === TokenType.CloseParen) { - parenDepth--; - continue; - } - if (tokens[i][0] === TokenType.OpenParen) { - parenDepth++; - continue; - } - - if (tokens[i][0] === TokenType.Ident && parenDepth > 0) { - const identToken = tokens[i] as TokenIdent; + let candidate: { replaceWith: string, encapsulateWith?: string } | null = null; - if (replacements.has(identToken[4].value)) { - candidate = [{ - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - replaceWith: replacements.get(identToken[4].value)!.truthy, - }]; - } - } + mediaQuery.walk((entry) => { + const node = entry.node; + if (!isMediaFeatureBoolean(node)) { + return; } - return []; - } - - // replacement slot is in a complex @media query : - // - @media not (--custom-mq) { ... } - // - @media ((--custom-mq-1) or (--custom-mq-2) or (not (--custom-mq-3))) { ... } - for (let i = 0; i < tokens.length; i++) { - switch (tokens[i][0]) { - case TokenType.Function: { - let depth = 1; - while (depth !== 0) { - i++; - if (!tokens[i] || tokens[i][0] === TokenType.EOF) { - throw new Error('unexpected EOF'); - } - - switch (tokens[i][0]) { - case TokenType.OpenParen: - case TokenType.Function: - depth++; - break; - case TokenType.CloseParen: - depth--; - break; - } - } - break; - } + const name = node.getName(); + if (!name.startsWith('--')) { + return false; + } - case TokenType.OpenCurly: { - let depth = 1; - while (depth !== 0) { - i++; - if (!tokens[i] || tokens[i][0] === TokenType.EOF) { - throw new Error('unexpected EOF'); - } + const replacement = replacements.get(name); + if (replacement) { + candidate = { + replaceWith: replacement.truthy.map((x) => x.toString().trim()).join(','), + }; - switch (tokens[i][0]) { - case TokenType.OpenCurly: - depth++; - break; - case TokenType.CloseCurly: - depth--; - break; - } - } - break; - } + return false; + } + }); - case TokenType.OpenSquare: { - let depth = 1; - while (depth !== 0) { - i++; - if (!tokens[i] || tokens[i][0] === TokenType.EOF) { - throw new Error('unexpected EOF'); - } + return candidate; +} - switch (tokens[i][0]) { - case TokenType.OpenSquare: - depth++; - break; - case TokenType.CloseSquare: - depth--; - break; - } - } - break; - } +export function transformComplexMediaQuery(mediaQuery: MediaQuery, replacements: Map, falsy: Array }>): Array<{ replaceWith: string, encapsulateWith?: string }> { + let candidate: Array<{ replaceWith: string, encapsulateWith?: string }> = []; - case TokenType.Ident: { - const identToken = tokens[i] as TokenIdent; + mediaQuery.walk((entry) => { + const node = entry.node; + if (!isMediaFeatureBoolean(node)) { + return; + } - if (!replacements.has(identToken[4].value)) { - break; - } + const parent = entry.parent; + if (!isMediaFeature(parent)) { + return; + } - let isValid = true; - for (let p = i-1; p>= 0; p--) { - if (tokens[p][0] === TokenType.Comment || tokens[p][0] === TokenType.Whitespace) { - continue; - } + const name = node.getName(); + if (!name.startsWith('--')) { + return false; + } - if (tokens[p][0] === TokenType.OpenParen) { - break; - } + const replacement = replacements.get(name); + if (replacement) { + const replaceWithTrue = newMediaFeaturePlain( + alwaysTrue[0][4].value as string, + alwaysTrue[2], + ); + + parent.feature = replaceWithTrue.feature; + const replaceWithTrueString = mediaQuery.toString(); + + const replaceWithFalse = newMediaFeaturePlain( + neverTrue[0][4].value as string, + neverTrue[2], + ); + + parent.feature = replaceWithFalse.feature; + const replaceWithFalseString = mediaQuery.toString(); + + candidate = [ + { + replaceWith: replaceWithTrueString, + encapsulateWith: replacement.truthy.map((x) => x.toString().trim()).join(','), + }, + { + replaceWith: replaceWithFalseString, + encapsulateWith: replacement.falsy.map((x) => x.toString().trim()).join(','), + }, + ]; + + return false; + } + }); - isValid = false; - break; - } + return candidate; +} - for (let n = i + 1; n < tokens.length; n++) { - if (tokens[n][0] === TokenType.Comment || tokens[n][0] === TokenType.Whitespace) { - continue; - } +function mediaQueryIsSimple(mediaQuery: MediaQuery): boolean { + if (isMediaQueryInvalid(mediaQuery)) { + return false; + } - if (tokens[n][0] === TokenType.CloseParen) { - break; - } + if (isMediaQueryWithType(mediaQuery)) { + return false; + } - isValid = false; - break; - } - - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - const replacement = replacements.get(identToken[4].value)!; - - if (isValid) { - const replaceWithTrue = tokens.slice(); - replaceWithTrue.splice(i, 1, ...alwaysTrue); - - const replaceWithFalse = tokens.slice(); - replaceWithFalse.splice(i, 1, ...neverTrue); - - return [ - { - replaceWith: stringify(...replaceWithTrue), - encapsulateWith: replacement.truthy, - }, - { - replaceWith: stringify(...replaceWithFalse), - encapsulateWith: replacement.falsy, - }, - ]; - } - - break; - } + let isSimple = true; + mediaQuery.walk((entry) => { + if ( + isMediaAnd(entry.node) || + isMediaOr(entry.node) || + isMediaNot(entry.node) || + isMediaConditionList(entry.node) || + isGeneralEnclosed(entry.node) + ) { + isSimple = false; + return false; } - } + }); - return []; + return isSimple; } diff --git a/plugins/postcss-custom-media/src/transform-at-media/true-and-false.ts b/plugins/postcss-custom-media/src/transform-at-media/true-and-false.ts index 46a8ec636..ebb56a460 100644 --- a/plugins/postcss-custom-media/src/transform-at-media/true-and-false.ts +++ b/plugins/postcss-custom-media/src/transform-at-media/true-and-false.ts @@ -52,6 +52,7 @@ export function replaceTrueAndFalseTokens(tokens: Array): Array): Array Date: Sat, 5 Nov 2022 18:09:51 +0100 Subject: [PATCH 35/35] lets get rid of those build errors --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d98bf10d0..2cb818623 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -81,7 +81,7 @@ jobs: # - without requiring postinstall scripts from dependencies to run - name: build run: | - npm run build --if-present $MODIFIED_WORKSPACES + npm run build --if-present - name: lint run: npm run lint --if-present $MODIFIED_WORKSPACES