Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add declaration-block-no-duplicate-custom-properties #5125

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/user-guide/rules/list.md
Expand Up @@ -41,6 +41,7 @@ Grouped first by the following categories and then by the [_thing_](http://apps.

### 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.
- [`declaration-block-no-duplicate-properties`](../../../lib/rules/declaration-block-no-duplicate-properties/README.md): Disallow duplicate properties within declaration blocks.
- [`declaration-block-no-shorthand-property-overrides`](../../../lib/rules/declaration-block-no-shorthand-property-overrides/README.md): Disallow shorthand properties that override related longhand properties.

Expand Down
@@ -0,0 +1,40 @@
# declaration-block-no-duplicate-custom-properties

Disallow duplicate custom properties within declaration blocks.

<!-- prettier-ignore -->
```css
a { --custom-property: pink; --custom-property: orange; }
/** ↑ ↑
* These duplicated custom properties */
```

This rule is case-sensitive.

## Options

### `true`

The following patterns are considered violations:

<!-- prettier-ignore -->
```css
a { --custom-property: pink; --custom-property: orange; }
```

<!-- prettier-ignore -->
```css
a { --custom-property: pink; background: orange; --custom-property: orange }
```

The following patterns are _not_ considered violations:

<!-- prettier-ignore -->
```css
a { --custom-property: pink; }
```

<!-- prettier-ignore -->
```css
a { --custom-property: pink; --cUstOm-prOpErtY: orange; }
```
@@ -0,0 +1,165 @@
'use strict';

const { messages, ruleName } = require('..');

testRule({
ruleName,
config: [true],

accept: [
{
code: 'a { --custom-property: 1 }',
},
{
code: 'a { --custom-property: 1; --cUstOm-prOpErtY: 1 }',
},
{
code: 'a { --custom-property: 1; color: pink; --cUstOm-prOpErtY: 1 }',
},
{
code: 'a { color: var(--custom-property, --custom-property) }',
voskresla marked this conversation as resolved.
Show resolved Hide resolved
},
{
code: 'a { --custom-property: pink; @media { --custom-property: orange; } }',
description: 'nested',
},
{
code:
'a { --custom-property: pink; @media { --custom-property: orange; &::before { --custom-property: black; } } }',
description: 'double nested',
},
{
code:
'a { --custom-property: pink; { &:hover { --custom-property: orange; --cUstOm-prOpErtY: black; } } }',
description: 'spec nested',
},
{
code:
'a { --cUstOm-prOpErtY: pink; { &:hover { --custom-property: orange; --cUstOm-prOpErtY: black; } } }',
description: 'spec nested',
},
],

reject: [
{
code: 'a { --custom-property: 1; --custom-property: 2; }',
message: messages.rejected('--custom-property'),
},
{
code: 'a { --custom-property: 1; color: pink; --custom-property: 1; }',
message: messages.rejected('--custom-property'),
},
{
code: 'a { --custom-property: 1; --cUstOm-prOpErtY: 1; color: pink; --cUstOm-prOpErtY: 1; }',
message: messages.rejected('--cUstOm-prOpErtY'),
},
{
code:
'a { --custom-property: pink; { &:hover { --custom-property: orange; --custom-property: black; } } }',
description: 'spec nested',
message: messages.rejected('--custom-property'),
},
{
code:
'a { --custom-property: pink; @media { --custom-property: orange; --custom-property: black; } }',
description: 'nested',
message: messages.rejected('--custom-property'),
},
{
code:
'@media { --custom-property: orange; .foo { --custom-property: black; --custom-property: white; } }',
description: 'nested',
message: messages.rejected('--custom-property'),
},
{
code:
'a { --custom-property: pink; @media { --custom-property: orange; &::before { --custom-property: black; --custom-property: white; } } }',
description: 'double nested',
message: messages.rejected('--custom-property'),
},
{
code:
'a { --custom-property: pink; @media { --custom-property: orange; .foo { --custom-property: black; --custom-property: white; } } }',
description: 'double nested again',
message: messages.rejected('--custom-property'),
},
],
});

testRule({
ruleName,
config: [true],
skipBasicChecks: true,
syntax: 'html',

accept: [
{
code: '<style>a { --custom-property: pink; }</style>',
},
{
code: '<a style="--custom-property: pink;"></a>',
},
{
code:
'<style>a { --custom-property: pink; }</style><style>a { --custom-property: pink; }</style>',
},
{
code: '<a style="--custom-property: pink;"></a><a style="--custom-property: pink;"></a>',
},
{
code:
'<a style="--custom-property: pink; --cUstOm-prOpErtY: pink;"></a><a style="--custom-property: pink;"></a>',
},
],

reject: [
{
code: '<a style="--custom-property: pink; --custom-property: orange"></a>',
message: messages.rejected('--custom-property'),
},
{
code:
'<style>p { color: pink; --custom-property: orange; --custom-property: white; }</style>',
message: messages.rejected('--custom-property'),
},
{
code: '<a style="--custom-property: orange; color: pink; --custom-property: white;"></a>',
message: messages.rejected('--custom-property'),
},
{
code:
'<a style="--cUstOm-prOpErtY: orange; color: pink; --custom-property: white; --cUstOm-prOpErtY: black;"></a>',
message: messages.rejected('--cUstOm-prOpErtY'),
},
],
});

testRule({
ruleName,
config: [true],
skipBasicChecks: true,
syntax: 'css-in-js',

accept: [
{
code:
"import styled from 'react-emotion'\nexport default styled.div` --custom-property: pink; `;",
},
{
code:
"import styled from 'react-emotion'\nexport default styled.div` --custom-property: pink; --cUstOm-prOpErtY: pink; `;",
},
],
reject: [
{
code:
"import styled from 'styled-components';\nexport default styled.div` --custom-property: pink; --custom-property: orange; `;",
message: messages.rejected('--custom-property'),
},
{
code:
"import styled from 'react-emotion'\nexport default styled.div` --custom-property: pink; --custom-property: orange; `;",
message: messages.rejected('--custom-property'),
},
],
});
@@ -0,0 +1,61 @@
// @ts-nocheck

'use strict';

const eachDeclarationBlock = require('../../utils/eachDeclarationBlock');
const isCustomProperty = require('../../utils/isCustomProperty');
const isStandardSyntaxProperty = require('../../utils/isStandardSyntaxProperty');
const report = require('../../utils/report');
const ruleMessages = require('../../utils/ruleMessages');
const validateOptions = require('../../utils/validateOptions');

const ruleName = 'declaration-block-no-duplicate-custom-properties';

const messages = ruleMessages(ruleName, {
rejected: (property) => `Unexpected duplicate "${property}"`,
});

function rule(on) {
return (root, result) => {
const validOptions = validateOptions(result, ruleName, { actual: on });

if (!validOptions) {
return;
}

eachDeclarationBlock(root, (eachDecl) => {
const decls = new Set();

eachDecl((decl) => {
const prop = decl.prop;

if (!isStandardSyntaxProperty(prop)) {
return;
}

if (!isCustomProperty(prop)) {
return;
}

const isDuplicate = decls.has(prop);

if (isDuplicate) {
report({
message: messages.rejected(prop),
node: decl,
result,
ruleName,
});
voskresla marked this conversation as resolved.
Show resolved Hide resolved

return;
}

decls.add(prop);
});
});
};
}

rule.ruleName = ruleName;
rule.messages = messages;
module.exports = rule;
3 changes: 3 additions & 0 deletions lib/rules/index.js
Expand Up @@ -72,6 +72,9 @@ const rules = {
'custom-property-pattern': importLazy(() => require('./custom-property-pattern'))(),
'declaration-bang-space-after': importLazy(() => require('./declaration-bang-space-after'))(),
'declaration-bang-space-before': importLazy(() => require('./declaration-bang-space-before'))(),
'declaration-block-no-duplicate-custom-properties': importLazy(() =>
require('./declaration-block-no-duplicate-custom-properties'),
)(),
'declaration-block-no-duplicate-properties': importLazy(() =>
require('./declaration-block-no-duplicate-properties'),
)(),
Expand Down