Skip to content

Commit

Permalink
Add keyframe-block-no-duplicate-selectors (#6024)
Browse files Browse the repository at this point in the history
Co-authored-by: Richard Hallows <jeddy3@users.noreply.github.com>
Co-authored-by: Masafumi Koba <473530+ybiquitous@users.noreply.github.com>
  • Loading branch information
3 people committed Apr 21, 2022
1 parent 3cc3d2b commit 08df98c
Show file tree
Hide file tree
Showing 5 changed files with 187 additions and 0 deletions.
4 changes: 4 additions & 0 deletions docs/user-guide/rules/list.md
Expand Up @@ -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 block

- [`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.
Expand Down
1 change: 1 addition & 0 deletions lib/rules/index.js
Expand Up @@ -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'),
Expand Down
45 changes: 45 additions & 0 deletions lib/rules/keyframe-block-no-duplicate-selectors/README.md
@@ -0,0 +1,45 @@
# keyframe-block-no-duplicate-selectors

Disallow duplicate selectors within keyframe blocks.

<!-- prettier-ignore -->
```css
@keyframes foo { 0% {} 0% {} }
/** ↑
* This duplicate selector */
```

This rule is case-insensitive.

## Options

### `true`

The following patterns are considered problems:

<!-- prettier-ignore -->
```css
@keyframes foo { 0% {} 0% {} }
```

<!-- prettier-ignore -->
```css
@keyframes foo { from {} from {} }
```

<!-- prettier-ignore -->
```css
@keyframes foo { from {} FROM {} }
```

The following patterns are _not_ considered problems:

<!-- prettier-ignore -->
```css
@keyframes foo { 0% {} 100% {} }
```

<!-- prettier-ignore -->
```css
@keyframes foo { from {} to {} }
```
73 changes: 73 additions & 0 deletions lib/rules/keyframe-block-no-duplicate-selectors/__tests__/index.js
@@ -0,0 +1,73 @@
'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 {} 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 {} FROM {} }',
message: messages.rejected('FROM'),
},
{
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%'),
},
],
});

testRule({
ruleName,
config: [true],
customSyntax: 'postcss-scss',

accept: [
{
code: '@keyframes foo { #{$bar} {} #{$bar} {} }',
description: 'SCSS interpolation in selector',
},
],
});
64 changes: 64 additions & 0 deletions lib/rules/keyframe-block-no-duplicate-selectors/index.js
@@ -0,0 +1,64 @@
'use strict';

const isStandardSyntaxSelector = require('../../utils/isStandardSyntaxSelector');
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) => {
if (!isStandardSyntaxSelector(selector)) {
return;
}

const normalizedSelector = selector.toLowerCase();

const isDuplicate = selectors.has(normalizedSelector);

if (isDuplicate) {
report({
message: messages.rejected(selector),
node: keyframeRule,
result,
ruleName,
word: selector,
});

return;
}

selectors.add(normalizedSelector);
});
});
});
};
};

rule.ruleName = ruleName;
rule.messages = messages;
rule.meta = meta;
module.exports = rule;

0 comments on commit 08df98c

Please sign in to comment.