From 392c6cd1baccd8d1c2dc955a7a93258f79012c89 Mon Sep 17 00:00:00 2001 From: Richard Hallows Date: Mon, 6 Feb 2023 09:09:22 +0000 Subject: [PATCH] Add declaration-property-value-no-unknown (#6511) Co-authored-by: Masafumi Koba <473530+ybiquitous@users.noreply.github.com> --- docs/migration-guide/to-15.md | 56 +++- docs/user-guide/rules.md | 1 + .../README.md | 139 ++++++++++ .../__tests__/index.js | 249 ++++++++++++++++++ .../index.js | 147 +++++++++++ lib/rules/index.js | 3 + package-lock.json | 45 ++++ package.json | 2 + patches/@types+css-tree+2.0.1.patch | 23 ++ 9 files changed, 664 insertions(+), 1 deletion(-) create mode 100644 lib/rules/declaration-property-value-no-unknown/README.md create mode 100644 lib/rules/declaration-property-value-no-unknown/__tests__/index.js create mode 100644 lib/rules/declaration-property-value-no-unknown/index.js create mode 100644 patches/@types+css-tree+2.0.1.patch diff --git a/docs/migration-guide/to-15.md b/docs/migration-guide/to-15.md index 39de857a40..730fdec059 100644 --- a/docs/migration-guide/to-15.md +++ b/docs/migration-guide/to-15.md @@ -2,7 +2,14 @@ This release contains significant or breaking changes. -## Significant change +## Significant changes + +Two significant changes may affect you: + +- deprecated stylistic rules +- added the `declaration-property-value-no-unknown` rule + +### Deprecated stylistic rules We've deprecated [76 of the rules that enforce stylistic conventions](../user-guide/rules.md#deprecated), e.g. [`indentation`](../../lib/rules/indentation/README.md). @@ -42,6 +49,53 @@ There are lots of other rules we don't turn on in the [standard config](https:// If you want to continue using Stylelint to enforce stylistic consistency, you can [migrate the deprecated rules you need to a plugin](../developer-guide/plugins.md). +### Added `declaration-property-value-no-unknown` rule + +We added the [`declaration-property-value-no-unknown`](../../lib/rules/declaration-property-value-no-unknown/README.md) rule. It will flag property-value pairs that are unknown in the CSS specifications, for example: + +```css +a { + top: red; +} +``` + +The `top` property accepts either a ``, `` or the `auto` keyword. The rule will flag `red` as it's a ``. + +Many of you have requested this rule, and we plan to add more like it to help you [avoid errors](../user-guide/rules.md#avoid-errors) in your CSS. + +To turn it on in your configuration object: + +```diff json +{ + "extends": ["stylelint-config-standard"], + "rules" { ++ "declaration-property-value-no-unknown": true + .. + } +} +``` + +The rule uses [CSSTree](https://github.com/csstree/csstree) and its [syntax dictionary](https://csstree.github.io/docs/syntax/) of 600+ properties, 350+ types and 100+ functions. You can help identify and plug any gaps in its dictionary by either updating [mdn-data](https://github.com/mdn/data/) or [CSSTree's patch file](https://github.com/csstree/csstree/blob/master/data/patch.json). + +If you use values that aren't in the CSS specifications, you can use the rule's secondary options to make the rule more permissive. You can either: + +- ignore properties or values outright +- extend the properties and values syntaxes + +The latter ensures only specific exceptions are allowed. + +If you currently use the [`stylelint-csstree-validator`](https://www.npmjs.com/package/stylelint-csstree-validator) plugin, you can continue to use it alongside the new rule by limiting the plugin to check only at-rule names and preludes. + +```diff json +{ + "rules" { + "csstree/validator": [true, { ++ "ignoreProperties": ["/.+/"] + }] + } +} +``` + ## Breaking changes Three breaking changes may also affect you: diff --git a/docs/user-guide/rules.md b/docs/user-guide/rules.md index d6465e7414..ebd5f02908 100644 --- a/docs/user-guide/rules.md +++ b/docs/user-guide/rules.md @@ -79,6 +79,7 @@ Disallow unknown things with these `no-unknown` rules. - [`annotation-no-unknown`](../../lib/rules/annotation-no-unknown/README.md): Disallow unknown annotations (Ⓡ & Ⓢ). - [`at-rule-no-unknown`](../../lib/rules/at-rule-no-unknown/README.md): Disallow unknown at-rules (Ⓡ & Ⓢ). +- [`declaration-property-value-no-unknown`](../../lib/rules/declaration-property-value-no-unknown/README.md): Disallow unknown values for properties within declarations. - [`function-no-unknown`](../../lib/rules/function-no-unknown/README.md): Disallow unknown functions (Ⓡ & Ⓢ). - [`media-feature-name-no-unknown`](../../lib/rules/media-feature-name-no-unknown/README.md): Disallow unknown media feature names (Ⓡ & Ⓢ). - [`no-unknown-animations`](../../lib/rules/no-unknown-animations/README.md): Disallow unknown animations. diff --git a/lib/rules/declaration-property-value-no-unknown/README.md b/lib/rules/declaration-property-value-no-unknown/README.md new file mode 100644 index 0000000000..7f03804a60 --- /dev/null +++ b/lib/rules/declaration-property-value-no-unknown/README.md @@ -0,0 +1,139 @@ +# declaration-property-value-no-unknown + +Disallow unknown values for properties within declarations. + + +```css +a { top: unknown; } +/** ↑ ↑ + * property and value pairs like these */ +``` + +This is an experimental rule with some false negatives that will be patched in minor releases. + +It sometimes overlaps with: + +- [`color-no-invalid-hex`](../color-no-invalid-hex/README.md) +- [`function-no-unknown`](../function-no-unknown/README.md) +- [`string-no-newline`](../string-no-newline/README.md) +- [`unit-no-unknown`](../unit-no-unknown/README.md) + +If duplicate problems are flagged, you can turn off the corresponding rule. + +This rule considers values for properties defined within the CSS specifications to be known. You can use the `propertiesSyntax` and `typesSyntax` secondary options to extend the syntax. + +## Options + +### `true` + +The following patterns are considered problems: + + +```css +a { top: red; } +``` + + +```css +a { top: unknown; } +``` + +The following patterns are _not_ considered problems: + + +```css +a { top: 0; } +``` + + +```css +a { top: var(--foo); } +``` + +## Optional secondary options + +### `ignoreProperties: { "property": ["/regex/", /regex/, "non-regex"]|"/regex/"|/regex/|"non-regex" }` + +Ignore the specified property and value pairs. Keys in the object indicate property names. If a string in the object is surrounded with `"/"`, it's interpreted as a regular expression. For example, `"/.+/"` matches any strings. + +Given: + +```json +{ + "top": ["unknown"], + "/^margin-/": "/^--foo/", + "padding": "/.+/", + "/.+/": "--unknown-value" +} +``` + +The following patterns are _not_ considered problems: + + +```css +a { top: unknown; } +``` + + +```css +a { margin-top: --foo-bar; } +``` + + +```css +a { padding: invalid; } +``` + + +```css +a { width: --unknown-value; } +``` + +### `propertiesSyntax: { property: syntax }` + +Extend or alter the properties syntax dictionary. [CSS Value Definition Syntax](https://github.com/csstree/csstree/blob/master/docs/definition-syntax.md) is used to define a value's syntax. If a definition starts with `|` it is added to the [existing definition value](https://csstree.github.io/docs/syntax/) if any. + +Given: + +```json +{ "size": "" } +``` + +The following patterns are _not_ considered problems: + + +```css +a { size: 0; } +``` + + +```css +a { size: 10px } +``` + +### `typesSyntax: { type: syntax }` + +Extend or alter the types syntax dictionary. [CSS Value Definition Syntax](https://github.com/csstree/csstree/blob/master/docs/definition-syntax.md) is used to define a value's syntax. If a definition starts with `|` it is added to the [existing definition value](https://csstree.github.io/docs/syntax/) if any. + +Types are something like a preset which allows you to reuse a definition across other definitions. So, you'll likely want to also use the `propertiesSyntax` option when using this option. + +Given: + +```json +{ + "propertiesSyntax": { "top": "| <--foo()>" }, + "typesSyntax": { "--foo()": "--foo( )" } +} +``` + +The following patterns are _not_ considered problems: + + +```css +a { top: --foo(0); } +``` + + +```css +a { top: --foo(10px); } +``` diff --git a/lib/rules/declaration-property-value-no-unknown/__tests__/index.js b/lib/rules/declaration-property-value-no-unknown/__tests__/index.js new file mode 100644 index 0000000000..62505cd012 --- /dev/null +++ b/lib/rules/declaration-property-value-no-unknown/__tests__/index.js @@ -0,0 +1,249 @@ +'use strict'; + +const { messages, ruleName } = require('..'); + +testRule({ + ruleName, + config: true, + + accept: [ + { + code: 'a { top: 0; }', + }, + { + code: 'a { margin: auto 10em; }', + }, + { + code: 'a { margin-inline: calc(100% - 10px); }', + }, + { + code: 'a { top: var(--foo); }', + }, + { + code: 'a { foo: 1px; }', + }, + { + code: 'a { --foo: 1px; }', + }, + { + code: 'a { color: lightgrey; }', + }, + { + code: 'a { color: lightgray; }', + }, + { + code: 'a { display: -moz-inline-stack; }', + }, + { + code: 'a { color: -moz-initial; }', + }, + { + code: 'a { font-size: clamp(1rem, 1vw + 1rem, 3rem); }', + }, + { + code: 'a { font-size: min(1rem, 3rem); }', + }, + { + code: 'a { font-size: max(1rem, 3rem); }', + }, + ], + + reject: [ + { + code: 'a { top: unknown; }', + message: messages.rejected('top', 'unknown'), + line: 1, + column: 10, + endLine: 1, + endColumn: 17, + }, + { + code: 'a { top: red; }', + message: messages.rejected('top', 'red'), + line: 1, + column: 10, + endLine: 1, + endColumn: 13, + }, + { + code: 'a { color: ; }', + message: messages.rejected('color', ''), + line: 1, + column: 11, + endLine: 1, + endColumn: 12, + }, + { + code: 'a { padding: auto; }', + message: messages.rejected('padding', 'auto'), + line: 1, + column: 14, + endLine: 1, + endColumn: 18, + }, + { + code: 'a { display: fixed }', + message: messages.rejected('display', 'fixed'), + line: 1, + column: 14, + endLine: 1, + endColumn: 19, + }, + { + code: 'a { color: rgb(67 67 67 70%); }', + message: messages.rejected('color', '70%'), + line: 1, + column: 25, + endLine: 1, + endColumn: 28, + }, + { + code: 'a { transition: background-color 0.6s ease-in-out 0; }', + message: messages.rejected('transition', '0'), + line: 1, + column: 51, + endLine: 1, + endColumn: 52, + }, + { + code: 'a { margin-top: "10px"; }', + message: messages.rejected('margin-top', '"10px"'), + line: 1, + column: 17, + endLine: 1, + endColumn: 23, + }, + { + code: 'a { color: greem; }', + message: messages.rejected('color', 'greem'), + line: 1, + column: 12, + endLine: 1, + endColumn: 17, + }, + ], +}); + +testRule({ + ruleName, + config: [ + true, + { + ignoreProperties: { + top: '/.+/', + padding: ['auto'], + '/.+/': /--foo/, + }, + }, + ], + + accept: [ + { + code: 'a { top: 0; }', + }, + { + code: 'a { top: red; }', + }, + { + code: 'a { padding: auto; }', + }, + { + code: 'a { margin: 10px --foo; }', + }, + ], + + reject: [ + { + code: 'a { color: 1; }', + message: messages.rejected('color', '1'), + line: 1, + column: 12, + endLine: 1, + endColumn: 13, + }, + { + code: 'a { padding-left: auto; }', + message: messages.rejected('padding-left', 'auto'), + line: 1, + column: 19, + endLine: 1, + endColumn: 23, + }, + { + code: 'a { padding: 10px --bar; }', + message: messages.rejected('padding', '--bar'), + line: 1, + column: 19, + endLine: 1, + endColumn: 24, + }, + ], +}); + +testRule({ + ruleName, + config: [true, { propertiesSyntax: { size: '' } }], + + accept: [ + { + code: 'a { size: 10px; }', + }, + ], + + reject: [ + { + code: 'a { size: red; }', + message: messages.rejected('size', 'red'), + line: 1, + column: 11, + endLine: 1, + endColumn: 14, + }, + ], +}); + +testRule({ + ruleName, + config: [ + true, + { + propertiesSyntax: { top: '| <--foo()>' }, + typesSyntax: { '--foo()': '--foo( )' }, + }, + ], + + accept: [ + { + code: 'a { top: 10px; }', + }, + { + code: 'a { top: --foo(5px); }', + }, + ], + + reject: [ + { + code: 'a { top: red; }', + message: messages.rejected('top', 'red'), + line: 1, + column: 10, + endLine: 1, + endColumn: 13, + }, + ], +}); + +testRule({ + ruleName, + config: true, + customSyntax: 'postcss-scss', + + accept: [ + { + code: 'a { $foo: bar; }', + }, + { + code: 'a { top: #{foo}; }', + }, + ], +}); diff --git a/lib/rules/declaration-property-value-no-unknown/index.js b/lib/rules/declaration-property-value-no-unknown/index.js new file mode 100644 index 0000000000..97f8c9f80b --- /dev/null +++ b/lib/rules/declaration-property-value-no-unknown/index.js @@ -0,0 +1,147 @@ +'use strict'; + +const { isPlainObject } = require('is-plain-object'); +const { fork, parse, find } = require('css-tree'); + +const declarationValueIndex = require('../../utils/declarationValueIndex'); +const matchesStringOrRegExp = require('../../utils/matchesStringOrRegExp'); +const report = require('../../utils/report'); +const ruleMessages = require('../../utils/ruleMessages'); +const validateOptions = require('../../utils/validateOptions'); +const validateObjectWithArrayProps = require('../../utils/validateObjectWithArrayProps'); +const isCustomProperty = require('../../utils/isCustomProperty'); +const isStandardSyntaxValue = require('../../utils/isStandardSyntaxValue'); +const isStandardSyntaxProperty = require('../../utils/isStandardSyntaxProperty'); +const isStandardSyntaxDeclaration = require('../../utils/isStandardSyntaxDeclaration'); +const { isRegExp, isString } = require('../../utils/validateTypes'); + +const ruleName = 'declaration-property-value-no-unknown'; + +const messages = ruleMessages(ruleName, { + rejected: (property, value) => `Unexpected unknown value "${value}" for property "${property}"`, +}); + +const meta = { + url: 'https://stylelint.io/user-guide/rules/declaration-property-value-no-unknown', +}; + +/** @type {import('stylelint').Rule} */ +const rule = (primary, secondaryOptions) => { + return (root, result) => { + const validOptions = validateOptions( + result, + ruleName, + { actual: primary }, + { + actual: secondaryOptions, + possible: { + ignoreProperties: [validateObjectWithArrayProps(isString, isRegExp)], + propertiesSyntax: [isPlainObject], + typesSyntax: [isPlainObject], + }, + optional: true, + }, + ); + + if (!validOptions) { + return; + } + + const ignoreProperties = Array.from( + Object.entries((secondaryOptions && secondaryOptions.ignoreProperties) || {}), + ); + + /** @type {(name: string, propValue: string) => boolean} */ + const isPropIgnored = (name, value) => { + const [, valuePattern] = + ignoreProperties.find(([namePattern]) => matchesStringOrRegExp(name, namePattern)) || []; + + return valuePattern && matchesStringOrRegExp(value, valuePattern); + }; + + const propertiesSyntax = (secondaryOptions && secondaryOptions.propertiesSyntax) || {}; + const typesSyntax = (secondaryOptions && secondaryOptions.typesSyntax) || {}; + + const forkedLexer = fork({ + properties: propertiesSyntax, + types: typesSyntax, + }).lexer; + + root.walkDecls((decl) => { + const { prop, value } = decl; + + // NOTE: CSSTree's `fork()` doesn't support `-moz-initial`, but it may be possible in the future. + // See https://github.com/stylelint/stylelint/pull/6511#issuecomment-1412921062 + if (/^-moz-initial$/i.test(value)) return; + + if (!isStandardSyntaxDeclaration(decl)) return; + + if (!isStandardSyntaxProperty(prop)) return; + + if (!isStandardSyntaxValue(value)) return; + + if (isCustomProperty(prop)) return; + + if (isPropIgnored(prop, value)) return; + + try { + const valueNode = parse(value, { context: 'value' }); + + if (containsUnsupportedMathFunction(valueNode)) return; + } catch (e) { + result.warn(`Cannot parse property value "${value}"`, { + node: decl, + stylelintType: 'parseError', + }); + + return; + } + + const { error } = forkedLexer.matchProperty(prop, value); + + if (!error) return; + + const { mismatchLength, mismatchOffset, name, rawMessage } = error; + + if (name !== 'SyntaxMatchError') return; + + if (rawMessage !== 'Mismatch') return; + + const mismatchValue = value.slice(mismatchOffset, mismatchOffset + mismatchLength); + const index = declarationValueIndex(decl) + mismatchOffset; + const endIndex = index + mismatchLength; + + report({ + message: messages.rejected(prop, mismatchValue), + node: decl, + index, + endIndex, + result, + ruleName, + }); + }); + }; +}; + +/** + * TODO: This function avoids false positives because CSSTree doesn't fully support + * some math functions like `clamp()` via `fork()`. In the future, it may be unnecessary. + * + * @see https://github.com/stylelint/stylelint/pull/6511#issuecomment-1412921062 + * + * @param {import('css-tree').CssNode} cssTreeNode + * @returns {boolean} + */ +function containsUnsupportedMathFunction(cssTreeNode) { + return Boolean( + find( + cssTreeNode, + (node) => node.type === 'Function' && ['clamp', 'min', 'max'].includes(node.name), + ), + ); +} + +rule.ruleName = ruleName; +rule.messages = messages; +rule.meta = meta; +module.exports = rule; diff --git a/lib/rules/index.js b/lib/rules/index.js index f502a8781d..5e6bc9f414 100644 --- a/lib/rules/index.js +++ b/lib/rules/index.js @@ -121,6 +121,9 @@ const rules = { 'declaration-property-value-disallowed-list': importLazy(() => require('./declaration-property-value-disallowed-list'), )(), + 'declaration-property-value-no-unknown': importLazy(() => + require('./declaration-property-value-no-unknown'), + )(), 'font-family-no-missing-generic-family-keyword': importLazy(() => require('./font-family-no-missing-generic-family-keyword'), )(), diff --git a/package-lock.json b/package-lock.json index 7887b2bd89..7991f778d6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,6 +17,7 @@ "colord": "^2.9.3", "cosmiconfig": "^8.0.0", "css-functions-list": "^3.1.0", + "css-tree": "^2.3.1", "debug": "^4.3.4", "fast-glob": "^3.2.12", "fastest-levenshtein": "^1.0.16", @@ -60,6 +61,7 @@ "@stylelint/prettier-config": "^2.0.0", "@stylelint/remark-preset": "^4.0.0", "@types/balanced-match": "^1.0.2", + "@types/css-tree": "^2.0.1", "@types/debug": "^4.1.7", "@types/file-entry-cache": "^5.0.2", "@types/global-modules": "^2.0.0", @@ -2267,6 +2269,12 @@ "@types/node": "*" } }, + "node_modules/@types/css-tree": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@types/css-tree/-/css-tree-2.0.1.tgz", + "integrity": "sha512-eeRN9rsZK/ZD5nmJCeZXxyTwq+gsvN1EljeCPEyXk+vLOAwsgpsrdXio4lPBzxAuhIKu3MK7QvZxWUw9xDX8Bg==", + "dev": true + }, "node_modules/@types/debug": { "version": "4.1.7", "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.7.tgz", @@ -3720,6 +3728,18 @@ "node": ">=12.22" } }, + "node_modules/css-tree": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz", + "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==", + "dependencies": { + "mdn-data": "2.0.30", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, "node_modules/cssesc": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", @@ -8654,6 +8674,11 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/mdn-data": { + "version": "2.0.30", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz", + "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==" + }, "node_modules/memorystream": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", @@ -15887,6 +15912,12 @@ "@types/node": "*" } }, + "@types/css-tree": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@types/css-tree/-/css-tree-2.0.1.tgz", + "integrity": "sha512-eeRN9rsZK/ZD5nmJCeZXxyTwq+gsvN1EljeCPEyXk+vLOAwsgpsrdXio4lPBzxAuhIKu3MK7QvZxWUw9xDX8Bg==", + "dev": true + }, "@types/debug": { "version": "4.1.7", "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.7.tgz", @@ -17017,6 +17048,15 @@ "resolved": "https://registry.npmjs.org/css-functions-list/-/css-functions-list-3.1.0.tgz", "integrity": "sha512-/9lCvYZaUbBGvYUgYGFJ4dcYiyqdhSjG7IPVluoV8A1ILjkF7ilmhp1OGUz8n+nmBcu0RNrQAzgD8B6FJbrt2w==" }, + "css-tree": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz", + "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==", + "requires": { + "mdn-data": "2.0.30", + "source-map-js": "^1.0.1" + } + }, "cssesc": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", @@ -20625,6 +20665,11 @@ "integrity": "sha512-n4Vypz/DZgwo0iMHLQL49dJzlp7YtAJP+N07MZHpjPf/5XJuHUWstviF4Mn2jEiR/GNmtnRRqnwsXExk3igfFA==", "dev": true }, + "mdn-data": { + "version": "2.0.30", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz", + "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==" + }, "memorystream": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", diff --git a/package.json b/package.json index 0b447cc63c..7c68609f5f 100644 --- a/package.json +++ b/package.json @@ -119,6 +119,7 @@ "colord": "^2.9.3", "cosmiconfig": "^8.0.0", "css-functions-list": "^3.1.0", + "css-tree": "^2.3.1", "debug": "^4.3.4", "fast-glob": "^3.2.12", "fastest-levenshtein": "^1.0.16", @@ -159,6 +160,7 @@ "@stylelint/prettier-config": "^2.0.0", "@stylelint/remark-preset": "^4.0.0", "@types/balanced-match": "^1.0.2", + "@types/css-tree": "^2.0.1", "@types/debug": "^4.1.7", "@types/file-entry-cache": "^5.0.2", "@types/global-modules": "^2.0.0", diff --git a/patches/@types+css-tree+2.0.1.patch b/patches/@types+css-tree+2.0.1.patch new file mode 100644 index 0000000000..4c1fbcb5c7 --- /dev/null +++ b/patches/@types+css-tree+2.0.1.patch @@ -0,0 +1,23 @@ +diff --git a/node_modules/@types/css-tree/index.d.ts b/node_modules/@types/css-tree/index.d.ts +index 2e4ed18..7f096c6 100755 +--- a/node_modules/@types/css-tree/index.d.ts ++++ b/node_modules/@types/css-tree/index.d.ts +@@ -830,3 +830,18 @@ export const url: { + decode(input: string): string; + encode(input: string): string; + }; ++ ++type MatchResult = { ++ error?: { ++ mismatchLength: number; ++ mismatchOffset: number; ++ name: string; ++ rawMessage: string; ++ }; ++}; ++ ++declare class Lexer { ++ matchProperty(propertyName: string, value: string): MatchResult; ++} ++ ++export function fork(extension: Record>): { lexer: Lexer };