From ba79f1418405fe5fcce46698aedf6cec46a8fe74 Mon Sep 17 00:00:00 2001 From: Matt Wang Date: Mon, 18 Apr 2022 12:32:32 -0700 Subject: [PATCH 1/7] first draft at adding keyframe-block-no-duplicate-selectors --- docs/user-guide/rules/list.md | 4 ++ lib/rules/index.js | 1 + .../README.md | 40 +++++++++++++ .../__tests__/index.js | 40 +++++++++++++ .../index.js | 56 +++++++++++++++++++ 5 files changed, 141 insertions(+) create mode 100644 lib/rules/keyframe-block-no-duplicate-selectors/README.md create mode 100644 lib/rules/keyframe-block-no-duplicate-selectors/__tests__/index.js create mode 100644 lib/rules/keyframe-block-no-duplicate-selectors/index.js diff --git a/docs/user-guide/rules/list.md b/docs/user-guide/rules/list.md index 26971f3cd7..57ba16e9f2 100644 --- a/docs/user-guide/rules/list.md +++ b/docs/user-guide/rules/list.md @@ -50,6 +50,10 @@ Within each cateogory, the rules are grouped by the [_thing_](http://apps.workfl - [`keyframe-declaration-no-important`](../../../lib/rules/keyframe-declaration-no-important/README.md): Disallow `!important` within keyframe declarations. +### Keyframe blocks + +- [`keyframe-block-no-duplicate-selectors`](../../../lib/rules/keyframe-block-no-duplicate-selectors/README.md): Disallow duplicate selectors within keyframe blocks. + ### Declaration block - [`declaration-block-no-duplicate-custom-properties`](../../../lib/rules/declaration-block-no-duplicate-custom-properties/README.md): Disallow duplicate custom properties within declaration blocks. diff --git a/lib/rules/index.js b/lib/rules/index.js index 238b24a36c..fc80ab86ef 100644 --- a/lib/rules/index.js +++ b/lib/rules/index.js @@ -119,6 +119,7 @@ const rules = { 'function-url-scheme-disallowed-list': importLazy('./function-url-scheme-disallowed-list'), 'function-whitespace-after': importLazy('./function-whitespace-after'), 'hue-degree-notation': importLazy('./hue-degree-notation'), + 'keyframe-block-no-duplicate-selectors': importLazy('./keyframe-block-no-duplicate-selectors'), 'keyframe-declaration-no-important': importLazy('./keyframe-declaration-no-important'), 'keyframes-name-pattern': importLazy('./keyframes-name-pattern'), 'length-zero-no-unit': importLazy('./length-zero-no-unit'), diff --git a/lib/rules/keyframe-block-no-duplicate-selectors/README.md b/lib/rules/keyframe-block-no-duplicate-selectors/README.md new file mode 100644 index 0000000000..1139c79692 --- /dev/null +++ b/lib/rules/keyframe-block-no-duplicate-selectors/README.md @@ -0,0 +1,40 @@ +# keyframe-block-no-duplicate-selectors + +Disallow duplicate selectors within keyframe blocks. + + +```css +@keyframes foo { + 0% {} + 0% {} /* This duplicated selector */ + 100% {} +} +``` + +## Options + +### `true` + +The following patterns are considered problems: + + +```css +@keyframes foo { 0% {} 0% {} 100% {} } +``` + + +```css +@keyframes foo { 0% {} 0%, 100% {} } +``` + +The following patterns are _not_ considered problems: + + +```css +@keyframes foo { 0% {} 100% {} } +``` + + +```css +@keyframes foo { from {} to {} } +``` diff --git a/lib/rules/keyframe-block-no-duplicate-selectors/__tests__/index.js b/lib/rules/keyframe-block-no-duplicate-selectors/__tests__/index.js new file mode 100644 index 0000000000..4f0c2b3a48 --- /dev/null +++ b/lib/rules/keyframe-block-no-duplicate-selectors/__tests__/index.js @@ -0,0 +1,40 @@ +'use strict'; + +const { messages, ruleName } = require('..'); + +testRule({ + ruleName, + config: [true], + + accept: [ + { + code: '@keyframes foo { 0% {} 100% {} }', + }, + { + code: '@keyframes foo { from {} to {} }', + }, + ], + + reject: [ + { + code: '@keyframes foo { from {} to {} to {} }', + message: messages.rejected('to'), + }, + { + code: '@keyframes foo { 0% {} 0% {} 100% {} }', + message: messages.rejected('0%'), + }, + { + code: '@-webkit-keyframes foo { 0% {} 0% {} 100% {} }', + message: messages.rejected('0%'), + }, + { + code: '@-moz-keyframes foo { 0% {} 0% {} 100% {} }', + message: messages.rejected('0%'), + }, + { + code: '@keyframes foo { 0% {} 0%, 100% {} }', + message: messages.rejected('0%'), + }, + ], +}); diff --git a/lib/rules/keyframe-block-no-duplicate-selectors/index.js b/lib/rules/keyframe-block-no-duplicate-selectors/index.js new file mode 100644 index 0000000000..f782e11184 --- /dev/null +++ b/lib/rules/keyframe-block-no-duplicate-selectors/index.js @@ -0,0 +1,56 @@ +'use strict'; + +const report = require('../../utils/report'); +const ruleMessages = require('../../utils/ruleMessages'); +const validateOptions = require('../../utils/validateOptions'); + +const ruleName = 'keyframe-block-no-duplicate-selectors'; + +const messages = ruleMessages(ruleName, { + rejected: (selector) => `Unexpected duplicate "${selector}"`, +}); + +const meta = { + url: 'https://stylelint.io/user-guide/rules/list/keyframe-block-no-duplicate-selectors', +}; + +/** @type {import('stylelint').Rule} */ +const rule = (primary) => { + return (root, result) => { + const validOptions = validateOptions(result, ruleName, { actual: primary }); + + if (!validOptions) { + return; + } + + root.walkAtRules(/^(-(moz|webkit)-)?keyframes$/i, (atRuleKeyframes) => { + const selectors = new Set(); + + atRuleKeyframes.walkRules((keyframeRule) => { + const ruleSelectors = keyframeRule.selectors; + + ruleSelectors.forEach((selector) => { + const isDuplicate = selectors.has(selector); + + if (isDuplicate) { + report({ + message: messages.rejected(selector), + node: keyframeRule, + result, + ruleName, + }); + + return; + } + + selectors.add(selector); + }); + }); + }); + }; +}; + +rule.ruleName = ruleName; +rule.messages = messages; +rule.meta = meta; +module.exports = rule; From ed0d02627ccf7fced4ed0b59982554fe3d78d6ff Mon Sep 17 00:00:00 2001 From: Matt Wang Date: Wed, 20 Apr 2022 09:09:56 -0700 Subject: [PATCH 2/7] Apply suggestions from code review Co-authored-by: Richard Hallows --- .../keyframe-block-no-duplicate-selectors/README.md | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/lib/rules/keyframe-block-no-duplicate-selectors/README.md b/lib/rules/keyframe-block-no-duplicate-selectors/README.md index 1139c79692..c8fde6fef6 100644 --- a/lib/rules/keyframe-block-no-duplicate-selectors/README.md +++ b/lib/rules/keyframe-block-no-duplicate-selectors/README.md @@ -4,11 +4,9 @@ Disallow duplicate selectors within keyframe blocks. ```css -@keyframes foo { - 0% {} - 0% {} /* This duplicated selector */ - 100% {} -} +@keyframes foo { 0% {} 0% {} } +/** ↑ + * This duplicate selector */ ``` ## Options @@ -19,12 +17,12 @@ The following patterns are considered problems: ```css -@keyframes foo { 0% {} 0% {} 100% {} } +@keyframes foo { 0% {} 0% {} } ``` ```css -@keyframes foo { 0% {} 0%, 100% {} } +@keyframes foo { from {} from {} } ``` The following patterns are _not_ considered problems: From 6399cf4543c613d4a617bc256955c38760a4d25f Mon Sep 17 00:00:00 2001 From: Matt Wang Date: Wed, 20 Apr 2022 09:18:14 -0700 Subject: [PATCH 3/7] uses isStandardSyntaxSelector, adds test cases to match new docs --- .../__tests__/index.js | 21 +++++++++++++++++++ .../index.js | 5 +++++ 2 files changed, 26 insertions(+) diff --git a/lib/rules/keyframe-block-no-duplicate-selectors/__tests__/index.js b/lib/rules/keyframe-block-no-duplicate-selectors/__tests__/index.js index 4f0c2b3a48..fc025563fd 100644 --- a/lib/rules/keyframe-block-no-duplicate-selectors/__tests__/index.js +++ b/lib/rules/keyframe-block-no-duplicate-selectors/__tests__/index.js @@ -16,6 +16,14 @@ testRule({ ], reject: [ + { + code: '@keyframes foo { from {} from {} }', + message: messages.rejected('from'), + }, + { + code: '@keyframes foo { 0% {} 0% {} }', + message: messages.rejected('0%'), + }, { code: '@keyframes foo { from {} to {} to {} }', message: messages.rejected('to'), @@ -38,3 +46,16 @@ testRule({ }, ], }); + +testRule({ + ruleName, + config: [true], + customSyntax: 'postcss-scss', + + accept: [ + { + code: '@keyframes foo { #{$bar} {} #{$bar} {} }', + description: 'SCSS interpolation in selector', + }, + ], +}); diff --git a/lib/rules/keyframe-block-no-duplicate-selectors/index.js b/lib/rules/keyframe-block-no-duplicate-selectors/index.js index f782e11184..30d5abf4f4 100644 --- a/lib/rules/keyframe-block-no-duplicate-selectors/index.js +++ b/lib/rules/keyframe-block-no-duplicate-selectors/index.js @@ -1,5 +1,6 @@ 'use strict'; +const isStandardSyntaxSelector = require('../../utils/isStandardSyntaxSelector'); const report = require('../../utils/report'); const ruleMessages = require('../../utils/ruleMessages'); const validateOptions = require('../../utils/validateOptions'); @@ -30,6 +31,10 @@ const rule = (primary) => { const ruleSelectors = keyframeRule.selectors; ruleSelectors.forEach((selector) => { + if (!isStandardSyntaxSelector(selector)) { + return; + } + const isDuplicate = selectors.has(selector); if (isDuplicate) { From 932ccc477345b4361375b726665af58837872b27 Mon Sep 17 00:00:00 2001 From: Matt Wang Date: Wed, 20 Apr 2022 09:25:07 -0700 Subject: [PATCH 4/7] adds support for end positions --- .../__tests__/index.js | 8 ++++++++ lib/rules/keyframe-block-no-duplicate-selectors/index.js | 1 + 2 files changed, 9 insertions(+) diff --git a/lib/rules/keyframe-block-no-duplicate-selectors/__tests__/index.js b/lib/rules/keyframe-block-no-duplicate-selectors/__tests__/index.js index fc025563fd..39ee9972de 100644 --- a/lib/rules/keyframe-block-no-duplicate-selectors/__tests__/index.js +++ b/lib/rules/keyframe-block-no-duplicate-selectors/__tests__/index.js @@ -19,10 +19,18 @@ testRule({ { code: '@keyframes foo { from {} from {} }', message: messages.rejected('from'), + line: 1, + column: 26, + endLine: 1, + endColumn: 30, }, { code: '@keyframes foo { 0% {} 0% {} }', message: messages.rejected('0%'), + line: 1, + column: 24, + endLine: 1, + endColumn: 26, }, { code: '@keyframes foo { from {} to {} to {} }', diff --git a/lib/rules/keyframe-block-no-duplicate-selectors/index.js b/lib/rules/keyframe-block-no-duplicate-selectors/index.js index 30d5abf4f4..be289938c0 100644 --- a/lib/rules/keyframe-block-no-duplicate-selectors/index.js +++ b/lib/rules/keyframe-block-no-duplicate-selectors/index.js @@ -43,6 +43,7 @@ const rule = (primary) => { node: keyframeRule, result, ruleName, + word: selector, }); return; From 2f9d5a9faa5a54ce38753ae1ccd983fe0e402e2a Mon Sep 17 00:00:00 2001 From: Matt Wang Date: Wed, 20 Apr 2022 09:30:24 -0700 Subject: [PATCH 5/7] adds logic, test, docs for case insensitivity --- lib/rules/keyframe-block-no-duplicate-selectors/README.md | 7 +++++++ .../__tests__/index.js | 4 ++++ lib/rules/keyframe-block-no-duplicate-selectors/index.js | 4 ++-- 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/lib/rules/keyframe-block-no-duplicate-selectors/README.md b/lib/rules/keyframe-block-no-duplicate-selectors/README.md index c8fde6fef6..07a528fc88 100644 --- a/lib/rules/keyframe-block-no-duplicate-selectors/README.md +++ b/lib/rules/keyframe-block-no-duplicate-selectors/README.md @@ -9,6 +9,8 @@ Disallow duplicate selectors within keyframe blocks. * This duplicate selector */ ``` +This rule is case-insensitive. + ## Options ### `true` @@ -25,6 +27,11 @@ The following patterns are considered problems: @keyframes foo { from {} from {} } ``` + +```css +@keyframes foo { from {} FROM {} } +``` + The following patterns are _not_ considered problems: diff --git a/lib/rules/keyframe-block-no-duplicate-selectors/__tests__/index.js b/lib/rules/keyframe-block-no-duplicate-selectors/__tests__/index.js index 39ee9972de..19c7116c61 100644 --- a/lib/rules/keyframe-block-no-duplicate-selectors/__tests__/index.js +++ b/lib/rules/keyframe-block-no-duplicate-selectors/__tests__/index.js @@ -32,6 +32,10 @@ testRule({ endLine: 1, endColumn: 26, }, + { + code: '@keyframes foo { from {} FROM {} }', + message: messages.rejected('FROM'), + }, { code: '@keyframes foo { from {} to {} to {} }', message: messages.rejected('to'), diff --git a/lib/rules/keyframe-block-no-duplicate-selectors/index.js b/lib/rules/keyframe-block-no-duplicate-selectors/index.js index be289938c0..d6f8797a2e 100644 --- a/lib/rules/keyframe-block-no-duplicate-selectors/index.js +++ b/lib/rules/keyframe-block-no-duplicate-selectors/index.js @@ -35,7 +35,7 @@ const rule = (primary) => { return; } - const isDuplicate = selectors.has(selector); + const isDuplicate = selectors.has(selector.toLowerCase()); if (isDuplicate) { report({ @@ -49,7 +49,7 @@ const rule = (primary) => { return; } - selectors.add(selector); + selectors.add(selector.toLowerCase()); }); }); }); From 3bafadf52c4628f25b58f6a005da3262151b02b0 Mon Sep 17 00:00:00 2001 From: Matt Wang Date: Wed, 20 Apr 2022 09:50:23 -0700 Subject: [PATCH 6/7] refactors to lowercase selector only once --- lib/rules/keyframe-block-no-duplicate-selectors/index.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/rules/keyframe-block-no-duplicate-selectors/index.js b/lib/rules/keyframe-block-no-duplicate-selectors/index.js index d6f8797a2e..a8d9116cb9 100644 --- a/lib/rules/keyframe-block-no-duplicate-selectors/index.js +++ b/lib/rules/keyframe-block-no-duplicate-selectors/index.js @@ -35,7 +35,9 @@ const rule = (primary) => { return; } - const isDuplicate = selectors.has(selector.toLowerCase()); + const normalizedSelector = selector.toLowerCase(); + + const isDuplicate = selectors.has(normalizedSelector); if (isDuplicate) { report({ @@ -49,7 +51,7 @@ const rule = (primary) => { return; } - selectors.add(selector.toLowerCase()); + selectors.add(normalizedSelector); }); }); }); From ef01c23d89420d6a3fcfadc6e6e5a1718f64363b Mon Sep 17 00:00:00 2001 From: Richard Hallows Date: Thu, 21 Apr 2022 11:08:32 +0100 Subject: [PATCH 7/7] Update docs/user-guide/rules/list.md Co-authored-by: Masafumi Koba <473530+ybiquitous@users.noreply.github.com> --- docs/user-guide/rules/list.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/user-guide/rules/list.md b/docs/user-guide/rules/list.md index 57ba16e9f2..63e1946216 100644 --- a/docs/user-guide/rules/list.md +++ b/docs/user-guide/rules/list.md @@ -50,7 +50,7 @@ Within each cateogory, the rules are grouped by the [_thing_](http://apps.workfl - [`keyframe-declaration-no-important`](../../../lib/rules/keyframe-declaration-no-important/README.md): Disallow `!important` within keyframe declarations. -### Keyframe blocks +### Keyframe block - [`keyframe-block-no-duplicate-selectors`](../../../lib/rules/keyframe-block-no-duplicate-selectors/README.md): Disallow duplicate selectors within keyframe blocks.