From dcdc3b9bbfd5131d963eee5cdb1b5dd48d0f9133 Mon Sep 17 00:00:00 2001 From: kimulaco Date: Mon, 10 Apr 2023 21:48:42 +0900 Subject: [PATCH 01/12] Add option `consecutive-duplicates-with-different-syntax` to `declaration-block-no-duplicate-properties` --- .../__tests__/index.js | 44 ++++++++++++- .../index.js | 66 ++++++++++++++++++- 2 files changed, 108 insertions(+), 2 deletions(-) diff --git a/lib/rules/declaration-block-no-duplicate-properties/__tests__/index.js b/lib/rules/declaration-block-no-duplicate-properties/__tests__/index.js index 45a89130b9..2cee7cfbbc 100644 --- a/lib/rules/declaration-block-no-duplicate-properties/__tests__/index.js +++ b/lib/rules/declaration-block-no-duplicate-properties/__tests__/index.js @@ -262,7 +262,10 @@ testRule({ config: [true, { ignore: ['consecutive-duplicates-with-different-values'] }], fix: true, - accept: [{ code: 'p { font-size: 16px; font-size: 1rem; font-weight: 400; }' }], + accept: [ + { code: 'p { font-size: 16px; font-size: 1rem; font-weight: 400; }' }, + { code: 'p { font-size: 16px; font-size: 18px; font-weight: 400; }' }, + ], reject: [ { @@ -288,6 +291,45 @@ testRule({ ], }); +testRule({ + ruleName, + config: [true, { ignore: ['consecutive-duplicates-with-different-syntax'] }], + fix: true, + + accept: [ + { code: 'p { width: 100vw; height: 100vh; }' }, + { code: 'p { width: 100vw; width: 100dvw; height: 100vh; }' }, + ], + + reject: [ + { + code: 'p { width: 100vw; height: 100vh; width: 100dvw; }', + fixed: 'p { height: 100vh; width: 100dvw; }', + message: messages.rejected('width'), + }, + { + code: 'p { width: 100vw; width: 100vw; height: 100vh; }', + fixed: 'p { width: 100vw; height: 100vh; }', + message: messages.rejected('width'), + }, + { + code: 'p { width: 100vw; width: 50vw; height: 100vh; }', + fixed: 'p { width: 50vw; height: 100vh; }', + message: messages.rejected('width'), + }, + { + code: 'p { width: 100vw !important; height: 100vh; width: 100dvw; }', + fixed: 'p { width: 100vw !important; height: 100vh; }', + message: messages.rejected('width'), + }, + { + code: 'p { width: 100vw; height: 100vh; width: 100dvw !important; }', + fixed: 'p { height: 100vh; width: 100dvw !important; }', + message: messages.rejected('width'), + }, + ], +}); + testRule({ ruleName, config: [true, { ignore: ['consecutive-duplicates-with-same-prefixless-values'] }], diff --git a/lib/rules/declaration-block-no-duplicate-properties/index.js b/lib/rules/declaration-block-no-duplicate-properties/index.js index 6f58c2169c..269a5c7910 100644 --- a/lib/rules/declaration-block-no-duplicate-properties/index.js +++ b/lib/rules/declaration-block-no-duplicate-properties/index.js @@ -1,5 +1,6 @@ 'use strict'; +const { parse } = require('css-tree'); const eachDeclarationBlock = require('../../utils/eachDeclarationBlock'); const isCustomProperty = require('../../utils/isCustomProperty'); const isStandardSyntaxProperty = require('../../utils/isStandardSyntaxProperty'); @@ -21,6 +22,41 @@ const meta = { fixable: true, }; +/** @type {(value1: import('css-tree').CssNode, value2: import('css-tree').CssNode) => boolean} */ +const isEqualValueSyntax = (value1, value2) => { + if (value1.type !== value2.type) { + return false; + } + + if ('unit' in value1) { + if ('unit' in value2) { + return value1.unit === value2.unit; + } + + return false; + } + + return true; +}; + +/** @type {(value: string) => import('css-tree').CssNode | null} */ +const getValueSyntax = (value) => { + /** @type {import('css-tree').CssNode | undefined} */ + let cssTreeValueNode; + + try { + cssTreeValueNode = parse(value, { context: 'value' }); + } catch (e) { + return null; + } + + if (!cssTreeValueNode || !('children' in cssTreeValueNode) || !cssTreeValueNode.children?.first) { + return null; + } + + return cssTreeValueNode.children.first; +}; + /** @type {import('stylelint').Rule} */ const rule = (primary, secondaryOptions, context) => { return (root, result) => { @@ -34,6 +70,7 @@ const rule = (primary, secondaryOptions, context) => { ignore: [ 'consecutive-duplicates', 'consecutive-duplicates-with-different-values', + 'consecutive-duplicates-with-different-syntax', 'consecutive-duplicates-with-same-prefixless-values', ], ignoreProperties: [isString, isRegExp], @@ -52,6 +89,11 @@ const rule = (primary, secondaryOptions, context) => { 'ignore', 'consecutive-duplicates-with-different-values', ); + const ignoreDiffSyntax = optionsMatches( + secondaryOptions, + 'ignore', + 'consecutive-duplicates-with-different-syntax', + ); const ignorePrefixlessSameValues = optionsMatches( secondaryOptions, 'ignore', @@ -124,12 +166,34 @@ const rule = (primary, secondaryOptions, context) => { return duplicateDecl.remove(); }; - if (ignoreDiffValues || ignorePrefixlessSameValues) { + if (ignoreDiffValues || ignoreDiffSyntax || ignorePrefixlessSameValues) { if ( !duplicatesAreConsecutive || (ignorePrefixlessSameValues && !unprefixedDuplicatesAreEqual) ) { fixOrReport(); + + return; + } + + if (ignoreDiffSyntax) { + const valueSyntax = getValueSyntax(value); + const duplicateValueSyntax = getValueSyntax(duplicateValue); + + if (!valueSyntax || !duplicateValueSyntax) { + return; + } + + const duplicateValueSyntaxAreEqual = isEqualValueSyntax( + valueSyntax, + duplicateValueSyntax, + ); + + if (duplicateValueSyntaxAreEqual) { + fixOrReport(); + + return; + } } if (value !== duplicateValue) { From d144886b997d1fed65f78211780c818a22531694 Mon Sep 17 00:00:00 2001 From: kimulaco Date: Mon, 10 Apr 2023 21:51:05 +0900 Subject: [PATCH 02/12] Add check for multiple values --- .../__tests__/index.js | 6 ++ .../index.js | 72 +++++++++++-------- 2 files changed, 49 insertions(+), 29 deletions(-) diff --git a/lib/rules/declaration-block-no-duplicate-properties/__tests__/index.js b/lib/rules/declaration-block-no-duplicate-properties/__tests__/index.js index 2cee7cfbbc..45d92d5e20 100644 --- a/lib/rules/declaration-block-no-duplicate-properties/__tests__/index.js +++ b/lib/rules/declaration-block-no-duplicate-properties/__tests__/index.js @@ -299,6 +299,7 @@ testRule({ accept: [ { code: 'p { width: 100vw; height: 100vh; }' }, { code: 'p { width: 100vw; width: 100dvw; height: 100vh; }' }, + { code: 'p { margin: 10dvw 10dvw; margin: 10vw 10vw; padding: 0; }' }, ], reject: [ @@ -312,6 +313,11 @@ testRule({ fixed: 'p { width: 100vw; height: 100vh; }', message: messages.rejected('width'), }, + { + code: 'p { margin: 10vw 10vw; margin: 10vw 10vw; padding: 0; }', + fixed: 'p { margin: 10vw 10vw; padding: 0; }', + message: messages.rejected('margin'), + }, { code: 'p { width: 100vw; width: 50vw; height: 100vh; }', fixed: 'p { width: 50vw; height: 100vh; }', diff --git a/lib/rules/declaration-block-no-duplicate-properties/index.js b/lib/rules/declaration-block-no-duplicate-properties/index.js index 269a5c7910..5e208d2751 100644 --- a/lib/rules/declaration-block-no-duplicate-properties/index.js +++ b/lib/rules/declaration-block-no-duplicate-properties/index.js @@ -22,15 +22,20 @@ const meta = { fixable: true, }; -/** @type {(value1: import('css-tree').CssNode, value2: import('css-tree').CssNode) => boolean} */ -const isEqualValueSyntax = (value1, value2) => { - if (value1.type !== value2.type) { +/** @type {(node: import('css-tree').CssNode) => node is import('css-tree').Value} */ +const isValueNode = (node) => { + return node.type === 'Value'; +}; + +/** @type {(node1: import('css-tree').CssNode, node2: import('css-tree').CssNode) => boolean} */ +const isEqualValueChildNode = (node1, node2) => { + if (node1.type !== node2.type) { return false; } - if ('unit' in value1) { - if ('unit' in value2) { - return value1.unit === value2.unit; + if ('unit' in node1) { + if ('unit' in node2) { + return node1.unit === node2.unit; } return false; @@ -39,22 +44,41 @@ const isEqualValueSyntax = (value1, value2) => { return true; }; -/** @type {(value: string) => import('css-tree').CssNode | null} */ -const getValueSyntax = (value) => { - /** @type {import('css-tree').CssNode | undefined} */ - let cssTreeValueNode; +/** @type {(value1: string, value2: string) => boolean} */ +const isEqualValueSyntaxes = (value1, value2) => { + if (value1 === value2) { + return true; + } + + /** @type {import('css-tree').CssNode} */ + const value1Node = parse(value1, { context: 'value' }); + const value2Node = parse(value2, { context: 'value' }); - try { - cssTreeValueNode = parse(value, { context: 'value' }); - } catch (e) { - return null; + if (!isValueNode(value1Node) || !isValueNode(value2Node)) { + return false; + } + + if (value1Node.children.size !== value2Node.children.size) { + return false; } - if (!cssTreeValueNode || !('children' in cssTreeValueNode) || !cssTreeValueNode.children?.first) { - return null; + const value1Nodes = value1Node.children.toArray(); + const value2Nodes = value2Node.children.toArray(); + + for (let i = 0; i < value1Nodes.length; i++) { + const node1 = value1Nodes[i]; + const node2 = value2Nodes[i]; + + if ( + typeof node1 === 'undefined' || + typeof node2 === 'undefined' || + !isEqualValueChildNode(node1, node2) + ) { + return false; + } } - return cssTreeValueNode.children.first; + return true; }; /** @type {import('stylelint').Rule} */ @@ -177,19 +201,9 @@ const rule = (primary, secondaryOptions, context) => { } if (ignoreDiffSyntax) { - const valueSyntax = getValueSyntax(value); - const duplicateValueSyntax = getValueSyntax(duplicateValue); - - if (!valueSyntax || !duplicateValueSyntax) { - return; - } - - const duplicateValueSyntaxAreEqual = isEqualValueSyntax( - valueSyntax, - duplicateValueSyntax, - ); + const duplicateValueSyntaxesAreEqual = isEqualValueSyntaxes(value, duplicateValue); - if (duplicateValueSyntaxAreEqual) { + if (duplicateValueSyntaxesAreEqual) { fixOrReport(); return; From cb23aeb622db9374880cc3a70240da785fcf6f67 Mon Sep 17 00:00:00 2001 From: kimulaco Date: Mon, 10 Apr 2023 21:52:30 +0900 Subject: [PATCH 03/12] Rename option name --- .../__tests__/index.js | 2 +- .../declaration-block-no-duplicate-properties/index.js | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/rules/declaration-block-no-duplicate-properties/__tests__/index.js b/lib/rules/declaration-block-no-duplicate-properties/__tests__/index.js index 45d92d5e20..bb3bb9896d 100644 --- a/lib/rules/declaration-block-no-duplicate-properties/__tests__/index.js +++ b/lib/rules/declaration-block-no-duplicate-properties/__tests__/index.js @@ -293,7 +293,7 @@ testRule({ testRule({ ruleName, - config: [true, { ignore: ['consecutive-duplicates-with-different-syntax'] }], + config: [true, { ignore: ['consecutive-duplicates-with-different-syntaxes'] }], fix: true, accept: [ diff --git a/lib/rules/declaration-block-no-duplicate-properties/index.js b/lib/rules/declaration-block-no-duplicate-properties/index.js index 5e208d2751..e04ca92a45 100644 --- a/lib/rules/declaration-block-no-duplicate-properties/index.js +++ b/lib/rules/declaration-block-no-duplicate-properties/index.js @@ -94,7 +94,7 @@ const rule = (primary, secondaryOptions, context) => { ignore: [ 'consecutive-duplicates', 'consecutive-duplicates-with-different-values', - 'consecutive-duplicates-with-different-syntax', + 'consecutive-duplicates-with-different-syntaxes', 'consecutive-duplicates-with-same-prefixless-values', ], ignoreProperties: [isString, isRegExp], @@ -113,10 +113,10 @@ const rule = (primary, secondaryOptions, context) => { 'ignore', 'consecutive-duplicates-with-different-values', ); - const ignoreDiffSyntax = optionsMatches( + const ignoreDiffSyntaxes = optionsMatches( secondaryOptions, 'ignore', - 'consecutive-duplicates-with-different-syntax', + 'consecutive-duplicates-with-different-syntaxes', ); const ignorePrefixlessSameValues = optionsMatches( secondaryOptions, @@ -190,7 +190,7 @@ const rule = (primary, secondaryOptions, context) => { return duplicateDecl.remove(); }; - if (ignoreDiffValues || ignoreDiffSyntax || ignorePrefixlessSameValues) { + if (ignoreDiffValues || ignoreDiffSyntaxes || ignorePrefixlessSameValues) { if ( !duplicatesAreConsecutive || (ignorePrefixlessSameValues && !unprefixedDuplicatesAreEqual) @@ -200,7 +200,7 @@ const rule = (primary, secondaryOptions, context) => { return; } - if (ignoreDiffSyntax) { + if (ignoreDiffSyntaxes) { const duplicateValueSyntaxesAreEqual = isEqualValueSyntaxes(value, duplicateValue); if (duplicateValueSyntaxesAreEqual) { From 9ca1deacd5f27539298a2839641c91096bfa2f51 Mon Sep 17 00:00:00 2001 From: kimulaco Date: Mon, 10 Apr 2023 22:36:41 +0900 Subject: [PATCH 04/12] Add option description to document --- .../README.md | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/lib/rules/declaration-block-no-duplicate-properties/README.md b/lib/rules/declaration-block-no-duplicate-properties/README.md index ef7f883414..adf32bf285 100644 --- a/lib/rules/declaration-block-no-duplicate-properties/README.md +++ b/lib/rules/declaration-block-no-duplicate-properties/README.md @@ -112,6 +112,33 @@ p { } ``` +### `ignore: ["consecutive-duplicates-with-different-syntaxes"]` + +Ignore consecutive duplicated properties with different value syntaxes (type and unit of value). + +The following patterns are considered problems: + + +```css +/* properties with the same value syntax */ +p { + font-size: 16px; + font-size: 14px; + font-weight: 400; +} +``` + +The following patterns are _not_ considered problems: + + +```css +p { + font-size: 16px; + font-size: 16rem; + font-weight: 400; +} +``` + ### `ignore: ["consecutive-duplicates-with-same-prefixless-values"]` Ignore consecutive duplicated properties with identical values, when ignoring their prefix. From c3a04be29efd37bc75c9ce5d8e0573577e13e90a Mon Sep 17 00:00:00 2001 From: kimulaco Date: Mon, 10 Apr 2023 22:40:15 +0900 Subject: [PATCH 05/12] Add changeset --- .changeset/kind-cobras-peel.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/kind-cobras-peel.md diff --git a/.changeset/kind-cobras-peel.md b/.changeset/kind-cobras-peel.md new file mode 100644 index 0000000000..a49cbfac38 --- /dev/null +++ b/.changeset/kind-cobras-peel.md @@ -0,0 +1,5 @@ +--- +"stylelint": minor +--- + +Added: `ignore: ["consecutive-duplicates-with-different-syntaxes"]` to `declaration-block-no-duplicate-properties` From ec023b5e1fa82baf466d3268d534018a864dad75 Mon Sep 17 00:00:00 2001 From: kimulaco Date: Mon, 10 Apr 2023 23:58:07 +0900 Subject: [PATCH 06/12] Add unit test --- .../__tests__/index.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/rules/declaration-block-no-duplicate-properties/__tests__/index.js b/lib/rules/declaration-block-no-duplicate-properties/__tests__/index.js index bb3bb9896d..250b8d18cf 100644 --- a/lib/rules/declaration-block-no-duplicate-properties/__tests__/index.js +++ b/lib/rules/declaration-block-no-duplicate-properties/__tests__/index.js @@ -300,6 +300,8 @@ testRule({ { code: 'p { width: 100vw; height: 100vh; }' }, { code: 'p { width: 100vw; width: 100dvw; height: 100vh; }' }, { code: 'p { margin: 10dvw 10dvw; margin: 10vw 10vw; padding: 0; }' }, + { code: 'p { margin: 10dvh 10dvw 10dvh; margin: 10vw 10vw; padding: 0; }' }, + { code: 'p { width: 100%; width: fit-content; }' }, ], reject: [ @@ -333,6 +335,11 @@ testRule({ fixed: 'p { height: 100vh; width: 100dvw !important; }', message: messages.rejected('width'), }, + { + code: 'p { width: min-content; width: max-content; height: 100%; }', + fixed: 'p { width: max-content; height: 100%; }', + message: messages.rejected('width'), + }, ], }); From 5853221d5849aec69f713dfd3fc5e98d075fde57 Mon Sep 17 00:00:00 2001 From: kimulaco Date: Tue, 11 Apr 2023 00:19:38 +0900 Subject: [PATCH 07/12] Refactor to check unit --- .../index.js | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/lib/rules/declaration-block-no-duplicate-properties/index.js b/lib/rules/declaration-block-no-duplicate-properties/index.js index e04ca92a45..57cd86bdb5 100644 --- a/lib/rules/declaration-block-no-duplicate-properties/index.js +++ b/lib/rules/declaration-block-no-duplicate-properties/index.js @@ -33,15 +33,10 @@ const isEqualValueChildNode = (node1, node2) => { return false; } - if ('unit' in node1) { - if ('unit' in node2) { - return node1.unit === node2.unit; - } + const node1Unit = 'unit' in node1 ? node1.unit : ''; + const node2Unit = 'unit' in node2 ? node2.unit : ''; - return false; - } - - return true; + return node1Unit === node2Unit; }; /** @type {(value1: string, value2: string) => boolean} */ From 214fad6e8d18f87bf021eedf7de66de08413128c Mon Sep 17 00:00:00 2001 From: kimulaco Date: Tue, 11 Apr 2023 00:30:32 +0900 Subject: [PATCH 08/12] Refactor to type guard for CSSNode --- .../declaration-block-no-duplicate-properties/index.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/rules/declaration-block-no-duplicate-properties/index.js b/lib/rules/declaration-block-no-duplicate-properties/index.js index 57cd86bdb5..fdd4c13011 100644 --- a/lib/rules/declaration-block-no-duplicate-properties/index.js +++ b/lib/rules/declaration-block-no-duplicate-properties/index.js @@ -49,11 +49,11 @@ const isEqualValueSyntaxes = (value1, value2) => { const value1Node = parse(value1, { context: 'value' }); const value2Node = parse(value2, { context: 'value' }); - if (!isValueNode(value1Node) || !isValueNode(value2Node)) { - return false; - } - - if (value1Node.children.size !== value2Node.children.size) { + if ( + !isValueNode(value1Node) || + !isValueNode(value2Node) || + value1Node.children.size !== value2Node.children.size + ) { return false; } From 047218e51c6baa146e9aa22e14c3efdca75f341c Mon Sep 17 00:00:00 2001 From: kimulaco Date: Wed, 12 Apr 2023 10:27:08 +0900 Subject: [PATCH 09/12] Update to support function node --- .../__tests__/index.js | 7 +++ .../index.js | 57 +++++++++++-------- 2 files changed, 40 insertions(+), 24 deletions(-) diff --git a/lib/rules/declaration-block-no-duplicate-properties/__tests__/index.js b/lib/rules/declaration-block-no-duplicate-properties/__tests__/index.js index 250b8d18cf..ddb77fbcc2 100644 --- a/lib/rules/declaration-block-no-duplicate-properties/__tests__/index.js +++ b/lib/rules/declaration-block-no-duplicate-properties/__tests__/index.js @@ -302,6 +302,8 @@ testRule({ { code: 'p { margin: 10dvw 10dvw; margin: 10vw 10vw; padding: 0; }' }, { code: 'p { margin: 10dvh 10dvw 10dvh; margin: 10vw 10vw; padding: 0; }' }, { code: 'p { width: 100%; width: fit-content; }' }, + { code: 'p { width: calc(10px + 2px); width: calc(10px + 2rem); }' }, + { code: 'p { width: calc(10px + 2px); width: calc(10rem + 2rem); }' }, ], reject: [ @@ -340,6 +342,11 @@ testRule({ fixed: 'p { width: max-content; height: 100%; }', message: messages.rejected('width'), }, + { + code: 'p { width: calc(10px + 4rem); width: calc(10px + 2rem); }', + fixed: 'p { width: calc(10px + 2rem); }', + message: messages.rejected('width'), + }, ], }); diff --git a/lib/rules/declaration-block-no-duplicate-properties/index.js b/lib/rules/declaration-block-no-duplicate-properties/index.js index fdd4c13011..dbc8fe7e53 100644 --- a/lib/rules/declaration-block-no-duplicate-properties/index.js +++ b/lib/rules/declaration-block-no-duplicate-properties/index.js @@ -27,16 +27,42 @@ const isValueNode = (node) => { return node.type === 'Value'; }; -/** @type {(node1: import('css-tree').CssNode, node2: import('css-tree').CssNode) => boolean} */ -const isEqualValueChildNode = (node1, node2) => { - if (node1.type !== node2.type) { +/** @type {(node: import('css-tree').CssNode) => node is import('css-tree').FunctionNode} */ +const isFunctionNode = (node) => { + return node.type === 'Function'; +}; + +/** @type {(node1: import('css-tree').CssNode[], node2: import('css-tree').CssNode[]) => boolean} */ +const isEqualValueNodes = (nodes1, nodes2) => { + if (nodes1.length !== nodes2.length) { return false; } - const node1Unit = 'unit' in node1 ? node1.unit : ''; - const node2Unit = 'unit' in node2 ? node2.unit : ''; + for (let i = 0; i < nodes1.length; i++) { + const node1 = nodes1[i]; + const node2 = nodes2[i]; + + if (typeof node1 === 'undefined' || typeof node2 === 'undefined' || node1.type !== node2.type) { + return false; + } + + if (isFunctionNode(node1) && isFunctionNode(node2)) { + if (isEqualValueNodes(node1.children.toArray(), node2.children.toArray())) { + continue; + } else { + return false; + } + } + + const node1Unit = 'unit' in node1 ? node1.unit : ''; + const node2Unit = 'unit' in node2 ? node2.unit : ''; + + if (node1Unit !== node2Unit) { + return false; + } + } - return node1Unit === node2Unit; + return true; }; /** @type {(value1: string, value2: string) => boolean} */ @@ -45,7 +71,6 @@ const isEqualValueSyntaxes = (value1, value2) => { return true; } - /** @type {import('css-tree').CssNode} */ const value1Node = parse(value1, { context: 'value' }); const value2Node = parse(value2, { context: 'value' }); @@ -57,23 +82,7 @@ const isEqualValueSyntaxes = (value1, value2) => { return false; } - const value1Nodes = value1Node.children.toArray(); - const value2Nodes = value2Node.children.toArray(); - - for (let i = 0; i < value1Nodes.length; i++) { - const node1 = value1Nodes[i]; - const node2 = value2Nodes[i]; - - if ( - typeof node1 === 'undefined' || - typeof node2 === 'undefined' || - !isEqualValueChildNode(node1, node2) - ) { - return false; - } - } - - return true; + return isEqualValueNodes(value1Node.children.toArray(), value2Node.children.toArray()); }; /** @type {import('stylelint').Rule} */ From f0b45b64d6ecf5809c714193c75d1b8babb85185 Mon Sep 17 00:00:00 2001 From: kimulaco Date: Wed, 12 Apr 2023 21:23:22 +0900 Subject: [PATCH 10/12] Fix to check function name --- .../__tests__/index.js | 6 ++++++ .../declaration-block-no-duplicate-properties/index.js | 5 ++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/lib/rules/declaration-block-no-duplicate-properties/__tests__/index.js b/lib/rules/declaration-block-no-duplicate-properties/__tests__/index.js index ddb77fbcc2..5653e8aec6 100644 --- a/lib/rules/declaration-block-no-duplicate-properties/__tests__/index.js +++ b/lib/rules/declaration-block-no-duplicate-properties/__tests__/index.js @@ -304,6 +304,7 @@ testRule({ { code: 'p { width: 100%; width: fit-content; }' }, { code: 'p { width: calc(10px + 2px); width: calc(10px + 2rem); }' }, { code: 'p { width: calc(10px + 2px); width: calc(10rem + 2rem); }' }, + { code: 'p { width: min(10px, 11px); width: max(10px, 11px); }' }, ], reject: [ @@ -347,6 +348,11 @@ testRule({ fixed: 'p { width: calc(10px + 2rem); }', message: messages.rejected('width'), }, + { + code: 'p { width: CaLC(10px + 4rem); width: calc(10px + 2rem); }', + fixed: 'p { width: calc(10px + 2rem); }', + message: messages.rejected('width'), + }, ], }); diff --git a/lib/rules/declaration-block-no-duplicate-properties/index.js b/lib/rules/declaration-block-no-duplicate-properties/index.js index dbc8fe7e53..ecd75f5b0a 100644 --- a/lib/rules/declaration-block-no-duplicate-properties/index.js +++ b/lib/rules/declaration-block-no-duplicate-properties/index.js @@ -47,7 +47,10 @@ const isEqualValueNodes = (nodes1, nodes2) => { } if (isFunctionNode(node1) && isFunctionNode(node2)) { - if (isEqualValueNodes(node1.children.toArray(), node2.children.toArray())) { + if ( + node1.name.toLowerCase() === node2.name.toLowerCase() && + isEqualValueNodes(node1.children.toArray(), node2.children.toArray()) + ) { continue; } else { return false; From cb5f82b358e38d43a633d6a9721f4421aa8fa9b5 Mon Sep 17 00:00:00 2001 From: kimulaco Date: Wed, 12 Apr 2023 22:26:33 +0900 Subject: [PATCH 11/12] Fix to support parentheses node --- .../__tests__/index.js | 2 + .../index.js | 42 ++++++++----------- 2 files changed, 20 insertions(+), 24 deletions(-) diff --git a/lib/rules/declaration-block-no-duplicate-properties/__tests__/index.js b/lib/rules/declaration-block-no-duplicate-properties/__tests__/index.js index 5653e8aec6..186fc7cdc7 100644 --- a/lib/rules/declaration-block-no-duplicate-properties/__tests__/index.js +++ b/lib/rules/declaration-block-no-duplicate-properties/__tests__/index.js @@ -305,6 +305,8 @@ testRule({ { code: 'p { width: calc(10px + 2px); width: calc(10px + 2rem); }' }, { code: 'p { width: calc(10px + 2px); width: calc(10rem + 2rem); }' }, { code: 'p { width: min(10px, 11px); width: max(10px, 11px); }' }, + { code: 'p { width: calc((10px + 2px)); width: calc((10rem + 2rem)); }' }, + { code: 'p { width: calc((10px + 2px) + 10px); width: calc((10rem + 2rem) + 10px); }' }, ], reject: [ diff --git a/lib/rules/declaration-block-no-duplicate-properties/index.js b/lib/rules/declaration-block-no-duplicate-properties/index.js index ecd75f5b0a..95d1e2224b 100644 --- a/lib/rules/declaration-block-no-duplicate-properties/index.js +++ b/lib/rules/declaration-block-no-duplicate-properties/index.js @@ -1,6 +1,6 @@ 'use strict'; -const { parse } = require('css-tree'); +const { parse, List } = require('css-tree'); const eachDeclarationBlock = require('../../utils/eachDeclarationBlock'); const isCustomProperty = require('../../utils/isCustomProperty'); const isStandardSyntaxProperty = require('../../utils/isStandardSyntaxProperty'); @@ -22,15 +22,8 @@ const meta = { fixable: true, }; -/** @type {(node: import('css-tree').CssNode) => node is import('css-tree').Value} */ -const isValueNode = (node) => { - return node.type === 'Value'; -}; - -/** @type {(node: import('css-tree').CssNode) => node is import('css-tree').FunctionNode} */ -const isFunctionNode = (node) => { - return node.type === 'Function'; -}; +/** @type {(node: import('css-tree').CssNode) => node is import('css-tree').CssNode & { children: List }} */ +const hasChildren = (node) => 'children' in node && node.children instanceof List; /** @type {(node1: import('css-tree').CssNode[], node2: import('css-tree').CssNode[]) => boolean} */ const isEqualValueNodes = (nodes1, nodes2) => { @@ -46,11 +39,18 @@ const isEqualValueNodes = (nodes1, nodes2) => { return false; } - if (isFunctionNode(node1) && isFunctionNode(node2)) { - if ( - node1.name.toLowerCase() === node2.name.toLowerCase() && - isEqualValueNodes(node1.children.toArray(), node2.children.toArray()) - ) { + const node1Children = hasChildren(node1) ? node1.children.toArray() : null; + const node2Children = hasChildren(node2) ? node2.children.toArray() : null; + + if (Array.isArray(node1Children) && Array.isArray(node2Children)) { + const node1Name = 'name' in node1 ? String(node1.name) : ''; + const node2Name = 'name' in node2 ? String(node2.name) : ''; + + if (node1Name.toLowerCase() !== node2Name.toLowerCase()) { + return false; + } + + if (isEqualValueNodes(node1Children, node2Children)) { continue; } else { return false; @@ -76,16 +76,10 @@ const isEqualValueSyntaxes = (value1, value2) => { const value1Node = parse(value1, { context: 'value' }); const value2Node = parse(value2, { context: 'value' }); + const node1Children = hasChildren(value1Node) ? value1Node.children.toArray() : []; + const node2Children = hasChildren(value2Node) ? value2Node.children.toArray() : []; - if ( - !isValueNode(value1Node) || - !isValueNode(value2Node) || - value1Node.children.size !== value2Node.children.size - ) { - return false; - } - - return isEqualValueNodes(value1Node.children.toArray(), value2Node.children.toArray()); + return isEqualValueNodes(node1Children, node2Children); }; /** @type {import('stylelint').Rule} */ From b81cc2335b4e932c73a11ffde9f040b553e403e6 Mon Sep 17 00:00:00 2001 From: kimulaco Date: Wed, 12 Apr 2023 23:31:01 +0900 Subject: [PATCH 12/12] Fix to use `@typedef` --- .../declaration-block-no-duplicate-properties/index.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/rules/declaration-block-no-duplicate-properties/index.js b/lib/rules/declaration-block-no-duplicate-properties/index.js index 95d1e2224b..63d96ad0c6 100644 --- a/lib/rules/declaration-block-no-duplicate-properties/index.js +++ b/lib/rules/declaration-block-no-duplicate-properties/index.js @@ -22,10 +22,12 @@ const meta = { fixable: true, }; -/** @type {(node: import('css-tree').CssNode) => node is import('css-tree').CssNode & { children: List }} */ +/** @typedef {import('css-tree').CssNode} CssNode */ + +/** @type {(node: CssNode) => node is CssNode & { children: List }} */ const hasChildren = (node) => 'children' in node && node.children instanceof List; -/** @type {(node1: import('css-tree').CssNode[], node2: import('css-tree').CssNode[]) => boolean} */ +/** @type {(node1: CssNode[], node2: CssNode[]) => boolean} */ const isEqualValueNodes = (nodes1, nodes2) => { if (nodes1.length !== nodes2.length) { return false;