Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Fix *-no-unknown false positives for latest specs by integrating `@…
…csstools/css-syntax-patches-for-csstree`
  • Loading branch information
romainmenke committed Nov 26, 2025
commit 61c1c78b76eea8b1ce726fc4c695ae9dcb0f86ec
19 changes: 13 additions & 6 deletions lib/lintPostcssResult.cjs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

18 changes: 12 additions & 6 deletions lib/lintPostcssResult.mjs
Original file line number Diff line number Diff line change
@@ -1,15 +1,21 @@
import { EOL } from 'node:os';
import { createRequire } from 'node:module';

import { DEFAULT_SEVERITY, RULE_NAME_ALL } from './constants.mjs';
import { DEFAULT_CONFIGURATION_COMMENT } from './utils/configurationComment.mjs';
import assignDisabledRanges from './assignDisabledRanges.mjs';
import { emitDeprecationWarning } from './utils/emitWarning.mjs';
import { fork } from 'css-tree';
import getStylelintRule from './utils/getStylelintRule.mjs';
import mergeSyntaxDefinitions from './utils/mergeSyntaxDefinitions.mjs';
import reportUnknownRuleNames from './reportUnknownRuleNames.mjs';
import rules from './rules/index.mjs';
import timing from './timing.mjs';

/** @todo leverage import attributes once support for Node.js v18.19 is dropped */
const require = createRequire(import.meta.url);
const syntaxPatches = require('@csstools/css-syntax-patches-for-csstree/dist/index.json').next;

/** @import {Config, LinterOptions, PostcssResult} from 'stylelint' */

/**
Expand Down Expand Up @@ -171,12 +177,12 @@ function getCachedLexer(config) {
return lexerCache.get(cacheKey);
}

const newLexer = fork({
atrules: config.languageOptions?.syntax?.atRules || {},
properties: config.languageOptions?.syntax?.properties || {},
types: config.languageOptions?.syntax?.types || {},
cssWideKeywords: config.languageOptions?.syntax?.cssWideKeywords || [],
}).lexer;
const newLexer = fork(
mergeSyntaxDefinitions(syntaxPatches, {
...config.languageOptions?.syntax,
atrules: config.languageOptions?.syntax?.atRules,
}),
).lexer;

lexerCache.set(cacheKey, newLexer);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ testRule({
{
code: 'a { color: lightgray; }',
},
{
code: 'a { color: oklch(from red l c calc(h / 2) / alpha); }',
},
{
code: 'a { display: -moz-inline-stack; }',
},
Expand Down Expand Up @@ -186,6 +189,31 @@ testRule({
red;
}`,
},
{
code: 'a { height: calc-size(auto, size); }',
},
{
code: 'a { transition-timing-function: linear(0 0%, 0.999 44.8%, 0.866 58.4%, 0.998 77.2%, 1 100%) }',
},
{
code: 'a { container-type: scroll-state }',
},
{
code: 'a { inset: anchor(center) }',
},
{
code: stripIndent`a {
background: linear-gradient(in hsl longer hue, red, red);
background: radial-gradient(in hsl longer hue, red, red);
background: conic-gradient(in hsl longer hue, red, red);
background: repeating-linear-gradient(in hsl longer hue, red, red 50px);
background: repeating-radial-gradient(in hsl longer hue, red, red 50px);
background: repeating-conic-gradient(in hsl longer hue, red 0%, yellow 15%, red 33%);
}`,
},
{
code: 'a { appearance: base-select }',
},
],

reject: [
Expand Down Expand Up @@ -459,6 +487,24 @@ testRule({
endColumn: 31,
description: 'too few arguments in rgba()',
},
{
code: 'a { color: rgb(from red, r, g, b); }',
message: messages.rejected('color', 'rgb(from red, r, g, b)'),
line: 1,
column: 12,
endLine: 1,
endColumn: 34,
description: 'legacy syntax does not support relative colors',
},
{
code: 'a { color: rgb(from red l c h); }',
message: messages.rejected('color', 'rgb(from red l c h)'),
line: 1,
column: 12,
endLine: 1,
endColumn: 31,
description: 'component keywords are specific to each color function',
},
],
});

Expand Down
36 changes: 15 additions & 21 deletions lib/rules/declaration-property-value-no-unknown/index.cjs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

35 changes: 14 additions & 21 deletions lib/rules/declaration-property-value-no-unknown/index.mjs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { find, fork, parse, string } from 'css-tree';
import { createRequire } from 'node:module';

import { isRegExp, isString } from '../../utils/validateTypes.mjs';
import { atRuleRegexes } from '../../utils/regexes.mjs';
Expand All @@ -12,12 +13,17 @@ import isStandardSyntaxDeclaration from '../../utils/isStandardSyntaxDeclaration
import isStandardSyntaxProperty from '../../utils/isStandardSyntaxProperty.mjs';
import isStandardSyntaxValue from '../../utils/isStandardSyntaxValue.mjs';
import matchesStringOrRegExp from '../../utils/matchesStringOrRegExp.mjs';
import mergeSyntaxDefinitions from '../../utils/mergeSyntaxDefinitions.mjs';
import report from '../../utils/report.mjs';
import ruleMessages from '../../utils/ruleMessages.mjs';
import validateObjectWithArrayProps from '../../utils/validateObjectWithArrayProps.mjs';
import validateObjectWithProps from '../../utils/validateObjectWithProps.mjs';
import validateOptions from '../../utils/validateOptions.mjs';

/** @todo leverage import attributes once support for Node.js v18.19 is dropped */
const require = createRequire(import.meta.url);
const syntaxPatches = require('@csstools/css-syntax-patches-for-csstree/dist/index.json').next;

const ruleName = 'declaration-property-value-no-unknown';

const messages = ruleMessages(ruleName, {
Expand Down Expand Up @@ -67,21 +73,7 @@ const rule = (primary, secondaryOptions) => {
return Boolean(valuePattern && matchesStringOrRegExp(value, valuePattern));
};

/** @type {SecondaryOptions['propertiesSyntax']} */
const propertiesSyntax = {
'text-box-edge':
'auto | [ text | cap | ex | ideographic | ideographic-ink ] [ text | alphabetic | ideographic | ideographic-ink ]?',
'text-box-trim': 'none | trim-start | trim-end | trim-both',
'view-timeline':
"[ <'view-timeline-name'> [ <'view-timeline-axis'> || <'view-timeline-inset'> ]? ]#",
'font-size': '| math',
...secondaryOptions?.propertiesSyntax,
};

/**
* @todo add support for oklab(), oklch(), color(), color-mix(), light-dark(), etc.
* @see https://drafts.csswg.org/css-color-5/
*/
const propertiesSyntax = { ...secondaryOptions?.propertiesSyntax };
const typesSyntax = { ...secondaryOptions?.typesSyntax };

/** @type {Map<string, string>} */
Expand Down Expand Up @@ -121,12 +113,13 @@ const rule = (primary, secondaryOptions) => {
});

const languageOptions = result.stylelint.config?.languageOptions;
const forkedLexer = fork({
atrules: languageOptions?.syntax?.atRules || {},
properties: { ...(languageOptions?.syntax?.properties || {}), ...propertiesSyntax },
types: { ...(languageOptions?.syntax?.types || {}), ...typesSyntax },
cssWideKeywords: languageOptions?.syntax?.cssWideKeywords || [],
}).lexer;
const forkedLexer = fork(
mergeSyntaxDefinitions(
syntaxPatches,
{ ...languageOptions?.syntax, atrules: languageOptions?.syntax?.atRules },
{ properties: propertiesSyntax, types: typesSyntax },
),
).lexer;

root.walkDecls((decl) => {
const { prop } = decl;
Expand Down
Loading
Loading