From ae62ad346118defb0958c7ba85c51d8a056e4fdc Mon Sep 17 00:00:00 2001 From: Romain Menke Date: Fri, 30 Jun 2023 15:55:40 +0200 Subject: [PATCH 1/4] Fix `unit-disallowed-list` false negatives with percentages --- .../unit-disallowed-list/__tests__/index.js | 8 +++ lib/rules/unit-disallowed-list/index.js | 70 +++++++++++-------- 2 files changed, 49 insertions(+), 29 deletions(-) diff --git a/lib/rules/unit-disallowed-list/__tests__/index.js b/lib/rules/unit-disallowed-list/__tests__/index.js index 49293e43c4..ad135ca3cb 100644 --- a/lib/rules/unit-disallowed-list/__tests__/index.js +++ b/lib/rules/unit-disallowed-list/__tests__/index.js @@ -641,6 +641,14 @@ testRule({ endLine: 1, endColumn: 80, }, + { + code: 'a { color: rgb(10% 127 127) }', + message: messages.rejected('%'), + line: 1, + column: 18, + endLine: 1, + endColumn: 19, + }, ], }); diff --git a/lib/rules/unit-disallowed-list/index.js b/lib/rules/unit-disallowed-list/index.js index 57b80823ae..1182622a1b 100644 --- a/lib/rules/unit-disallowed-list/index.js +++ b/lib/rules/unit-disallowed-list/index.js @@ -119,7 +119,7 @@ const rule = (primary, secondaryOptions) => { if (!hasDimension(params)) return; - parseFromTokens(tokenizeWithoutPercentageTokens(params)).forEach((mediaQuery) => { + parseFromTokens(tokenizeWithDimensionsInsteadOfPercentages(params)).forEach((mediaQuery) => { /** @type {{ mediaFeatureName: string | undefined }} */ const initialState = { mediaFeatureName: undefined, @@ -154,50 +154,62 @@ const rule = (primary, secondaryOptions) => { if (!hasDimension(value)) return; - parseListOfComponentValues(tokenize({ css: value })).forEach((componentValue) => { - if (isTokenNode(componentValue)) { - check( - decl, - declarationValueIndex, - componentValue, - decl.prop, - secondaryOptions?.ignoreProperties, - ); + parseListOfComponentValues(tokenizeWithDimensionsInsteadOfPercentages(value)).forEach( + (componentValue) => { + if (isTokenNode(componentValue)) { + check( + decl, + declarationValueIndex, + componentValue, + decl.prop, + secondaryOptions?.ignoreProperties, + ); - return; - } + return; + } - if (!isFunctionNode(componentValue) && !isSimpleBlockNode(componentValue)) return; + if (!isFunctionNode(componentValue) && !isSimpleBlockNode(componentValue)) return; - const initialState = { - ignored: componentValueIsIgnored(componentValue), - }; + const initialState = { + ignored: componentValueIsIgnored(componentValue), + }; - componentValue.walk(({ node, state }) => { - if (!state) return; + componentValue.walk(({ node, state }) => { + if (!state) return; - if (state.ignored) return; + if (state.ignored) return; - if (isTokenNode(node)) { - check(decl, declarationValueIndex, node, decl.prop, secondaryOptions?.ignoreProperties); + if (isTokenNode(node)) { + check( + decl, + declarationValueIndex, + node, + decl.prop, + secondaryOptions?.ignoreProperties, + ); - return; - } + return; + } - if (componentValueIsIgnored(node)) { - state.ignored = true; - } - }, initialState); - }); + if (componentValueIsIgnored(node)) { + state.ignored = true; + } + }, initialState); + }, + ); }); }; }; /** + * In the CSS syntax percentages are a different token type than dimensions. + * For CSS authors however this distinction doesn't make sense, so we convert + * percentage tokens to dimension tokens with a unit of "%". + * * @param {string} value * @returns {Array} */ -function tokenizeWithoutPercentageTokens(value) { +function tokenizeWithDimensionsInsteadOfPercentages(value) { return tokenize({ css: value }).map((x) => { if (x[0] !== TokenType.Percentage) return x; From 23e4bb3b25bbf794cd2458a9f67ed8a22b32743f Mon Sep 17 00:00:00 2001 From: Romain Menke Date: Fri, 30 Jun 2023 16:00:44 +0200 Subject: [PATCH 2/4] cleanup --- lib/rules/unit-disallowed-list/index.js | 66 +++++++++++-------------- 1 file changed, 29 insertions(+), 37 deletions(-) diff --git a/lib/rules/unit-disallowed-list/index.js b/lib/rules/unit-disallowed-list/index.js index 1182622a1b..78b65f5973 100644 --- a/lib/rules/unit-disallowed-list/index.js +++ b/lib/rules/unit-disallowed-list/index.js @@ -119,7 +119,7 @@ const rule = (primary, secondaryOptions) => { if (!hasDimension(params)) return; - parseFromTokens(tokenizeWithDimensionsInsteadOfPercentages(params)).forEach((mediaQuery) => { + parseFromTokens(tokenizeWithoutPercentages(params)).forEach((mediaQuery) => { /** @type {{ mediaFeatureName: string | undefined }} */ const initialState = { mediaFeatureName: undefined, @@ -154,49 +154,41 @@ const rule = (primary, secondaryOptions) => { if (!hasDimension(value)) return; - parseListOfComponentValues(tokenizeWithDimensionsInsteadOfPercentages(value)).forEach( - (componentValue) => { - if (isTokenNode(componentValue)) { - check( - decl, - declarationValueIndex, - componentValue, - decl.prop, - secondaryOptions?.ignoreProperties, - ); + parseListOfComponentValues(tokenizeWithoutPercentages(value)).forEach((componentValue) => { + if (isTokenNode(componentValue)) { + check( + decl, + declarationValueIndex, + componentValue, + decl.prop, + secondaryOptions?.ignoreProperties, + ); - return; - } + return; + } - if (!isFunctionNode(componentValue) && !isSimpleBlockNode(componentValue)) return; + if (!isFunctionNode(componentValue) && !isSimpleBlockNode(componentValue)) return; - const initialState = { - ignored: componentValueIsIgnored(componentValue), - }; + const initialState = { + ignored: componentValueIsIgnored(componentValue), + }; - componentValue.walk(({ node, state }) => { - if (!state) return; + componentValue.walk(({ node, state }) => { + if (!state) return; - if (state.ignored) return; + if (state.ignored) return; - if (isTokenNode(node)) { - check( - decl, - declarationValueIndex, - node, - decl.prop, - secondaryOptions?.ignoreProperties, - ); + if (isTokenNode(node)) { + check(decl, declarationValueIndex, node, decl.prop, secondaryOptions?.ignoreProperties); - return; - } + return; + } - if (componentValueIsIgnored(node)) { - state.ignored = true; - } - }, initialState); - }, - ); + if (componentValueIsIgnored(node)) { + state.ignored = true; + } + }, initialState); + }); }); }; }; @@ -209,7 +201,7 @@ const rule = (primary, secondaryOptions) => { * @param {string} value * @returns {Array} */ -function tokenizeWithDimensionsInsteadOfPercentages(value) { +function tokenizeWithoutPercentages(value) { return tokenize({ css: value }).map((x) => { if (x[0] !== TokenType.Percentage) return x; From 5993d791b289bb0c730213d838e9302f5cbcc34d Mon Sep 17 00:00:00 2001 From: Romain Menke <11521496+romainmenke@users.noreply.github.com> Date: Fri, 30 Jun 2023 16:01:37 +0200 Subject: [PATCH 3/4] Create angry-mails-give.md --- .changeset/angry-mails-give.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/angry-mails-give.md diff --git a/.changeset/angry-mails-give.md b/.changeset/angry-mails-give.md new file mode 100644 index 0000000000..a44e6a2151 --- /dev/null +++ b/.changeset/angry-mails-give.md @@ -0,0 +1,5 @@ +--- +"stylelint": patch +--- + +Fixed: `unit-disallowed-list` false negatives with percentages From caf90cee37c416d705bfc6f10bc6a23eb787c39e Mon Sep 17 00:00:00 2001 From: Romain Menke Date: Fri, 30 Jun 2023 17:28:57 +0200 Subject: [PATCH 4/4] cleanup --- lib/rules/unit-disallowed-list/index.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/lib/rules/unit-disallowed-list/index.js b/lib/rules/unit-disallowed-list/index.js index 78b65f5973..602eb7d4c6 100644 --- a/lib/rules/unit-disallowed-list/index.js +++ b/lib/rules/unit-disallowed-list/index.js @@ -198,15 +198,16 @@ const rule = (primary, secondaryOptions) => { * For CSS authors however this distinction doesn't make sense, so we convert * percentage tokens to dimension tokens with a unit of "%". * - * @param {string} value + * Percentage tokens also aren't valid in media queries. + * Converting percentage tokens to dimension tokens simplifies any code checking for units. + * + * @param {string} css * @returns {Array} */ -function tokenizeWithoutPercentages(value) { - return tokenize({ css: value }).map((x) => { +function tokenizeWithoutPercentages(css) { + return tokenize({ css }).map((x) => { if (x[0] !== TokenType.Percentage) return x; - // Percentage values are not valid in media queries, so we can't parse them and get something valid back. - // Dimension tokens with a unit of "%" work just fine. return [ TokenType.Dimension, x[1],