From d66207bb52951ffe0b847979309ff834bc30a7de Mon Sep 17 00:00:00 2001 From: fpetrakov Date: Mon, 22 Aug 2022 17:51:09 +0300 Subject: [PATCH 1/8] init --- .../__tests__/index.js | 64 +++++++++++++++++++ .../index.js | 18 +++++- 2 files changed, 79 insertions(+), 3 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 15f4cec1e7..65f0a9cdf2 100644 --- a/lib/rules/declaration-block-no-duplicate-properties/__tests__/index.js +++ b/lib/rules/declaration-block-no-duplicate-properties/__tests__/index.js @@ -213,9 +213,73 @@ testRule({ ], }); +testRule({ + ruleName, + config: [true, { ignore: ['consecutive-duplicates'] }], + fix: true, + + accept: [ + { + code: 'p { font-size: 16px; font-size: 1rem; }', + }, + { + code: 'p { display: inline-block; font-size: 16px; font-size: 1rem; }', + }, + { + code: 'p { font-size: 16px; font-size: 1rem; color: red; }', + }, + { + code: 'p { display: inline-block; font-size: 16px; font-size: 1rem; color: red; }', + }, + ], + + reject: [ + { + code: 'p { font-size: 16px; font-weight: 400; font-size: 1rem; }', + fixed: 'p { font-size: 16px; font-weight: 400; }', + message: messages.rejected('font-size'), + }, + { + code: 'p { display: inline-block; font-size: 16px; font-weight: 400; font-size: 1rem; }', + fixed: 'p { display: inline-block; font-size: 16px; font-weight: 400; }', + message: messages.rejected('font-size'), + }, + { + code: 'p { font-size: 16px; font-weight: 400; font-size: 1rem; color: red; }', + fixed: 'p { font-size: 16px; font-weight: 400; color: red; }', + message: messages.rejected('font-size'), + }, + { + code: 'p { display: inline-block; font-size: 16px; font-weight: 400; font-size: 1rem; color: red; }', + fixed: 'p { display: inline-block; font-size: 16px; font-weight: 400; color: red; }', + message: messages.rejected('font-size'), + }, + ], +}); + +testRule({ + ruleName, + config: [true, { ignore: ['consecutive-duplicates-with-different-values'] }], + fix: false, + + accept: [{ code: 'p { font-size: 16px; font-size: 1rem; font-weight: 400; }' }], + + reject: [ + { + code: 'p { font-size: 16px; font-weight: 400; font-size: 1rem; }', + message: messages.rejected('font-size'), + }, + { + code: 'p { font-size: 16px; font-size: 16px; font-weight: 400; }', + message: messages.rejected('font-size'), + }, + ], +}); + testRule({ ruleName, config: [true, { ignore: ['consecutive-duplicates-with-different-values'] }], + fix: true, accept: [{ code: 'p { font-size: 16px; font-size: 1rem; font-weight: 400; }' }], diff --git a/lib/rules/declaration-block-no-duplicate-properties/index.js b/lib/rules/declaration-block-no-duplicate-properties/index.js index 346c9dc4f8..5de0cfa147 100644 --- a/lib/rules/declaration-block-no-duplicate-properties/index.js +++ b/lib/rules/declaration-block-no-duplicate-properties/index.js @@ -21,7 +21,7 @@ const meta = { }; /** @type {import('stylelint').Rule} */ -const rule = (primary, secondaryOptions) => { +const rule = (primary, secondaryOptions, context) => { return (root, result) => { const validOptions = validateOptions( result, @@ -121,6 +121,12 @@ const rule = (primary, secondaryOptions) => { // fails if values of consecutive duplicates are equal if (value === duplicateValue) { + if (context.fix) { + decl.remove(); + + return; + } + report({ message: messages.rejected(prop), node: decl, @@ -139,6 +145,12 @@ const rule = (primary, secondaryOptions) => { return; } + if (context.fix) { + decl.remove(); + + return; + } + report({ message: messages.rejected(prop), node: decl, @@ -148,8 +160,8 @@ const rule = (primary, secondaryOptions) => { }); } - decls.push(prop.toLowerCase()); - values.push(value.toLowerCase()); + decls.push(prop); + values.push(value); }); }); }; From 725fce23fafb1f27148e7bca423e187888d313df Mon Sep 17 00:00:00 2001 From: fpetrakov Date: Mon, 22 Aug 2022 20:33:29 +0300 Subject: [PATCH 2/8] add autofix --- .../__tests__/index.js | 226 ++++++++++++++++-- .../index.js | 42 +++- 2 files changed, 238 insertions(+), 30 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 65f0a9cdf2..378054f934 100644 --- a/lib/rules/declaration-block-no-duplicate-properties/__tests__/index.js +++ b/lib/rules/declaration-block-no-duplicate-properties/__tests__/index.js @@ -174,6 +174,95 @@ testRule({ ], }); +testRule({ + ruleName, + config: [true], + fix: true, + + reject: [ + { + code: 'a { color: pink; color: orange }', + fixed: 'a { color: orange }', + message: messages.rejected('color'), + }, + { + code: 'a { cOlOr: pink; color: orange }', + fixed: 'a { color: orange }', + message: messages.rejected('color'), + }, + { + code: 'a { color: pink; cOlOr: orange }', + fixed: 'a { cOlOr: orange }', + message: messages.rejected('cOlOr'), + }, + { + code: 'a { cOlOr: pink; cOlOr: orange }', + fixed: 'a { cOlOr: orange }', + message: messages.rejected('cOlOr'), + }, + { + code: 'a { COLOR: pink; color: orange }', + fixed: 'a { color: orange }', + message: messages.rejected('color'), + }, + { + code: 'a { color: pink; COLOR: orange }', + fixed: 'a { COLOR: orange }', + message: messages.rejected('COLOR'), + }, + { + code: 'a { color: pink; background: orange; color: orange }', + fixed: 'a { background: orange; color: orange }', + message: messages.rejected('color'), + }, + { + code: 'a { color: pink; background: orange; background: pink; }', + fixed: 'a { color: pink; background: pink; }', + message: messages.rejected('background'), + }, + { + code: 'a { color: pink; { &:hover { color: orange; color: black; } } }', + fixed: 'a { color: pink; { &:hover { color: black; } } }', + description: 'spec nested', + message: messages.rejected('color'), + }, + { + code: 'a { color: pink; @media { color: orange; color: black; } }', + fixed: 'a { color: pink; @media { color: black; } }', + description: 'nested', + message: messages.rejected('color'), + }, + { + code: '@media { color: orange; .foo { color: black; color: white; } }', + fixed: '@media { color: orange; .foo { color: white; } }', + description: 'nested', + message: messages.rejected('color'), + }, + { + code: 'a { color: pink; @media { color: orange; &::before { color: black; color: white; } } }', + fixed: 'a { color: pink; @media { color: orange; &::before { color: white; } } }', + description: 'double nested', + message: messages.rejected('color'), + }, + { + code: 'a { color: pink; @media { color: orange; .foo { color: black; color: white; } } }', + fixed: 'a { color: pink; @media { color: orange; .foo { color: white; } } }', + description: 'double nested again', + message: messages.rejected('color'), + }, + { + code: 'a { -webkit-border-radius: 12px; -webkit-border-radius: 10px; }', + fixed: 'a { -webkit-border-radius: 10px; }', + message: messages.rejected('-webkit-border-radius'), + }, + { + code: 'a { -WEBKIT-border-radius: 12px; -webkit-BORDER-radius: 10px; }', + fixed: 'a { -webkit-BORDER-radius: 10px; }', + message: messages.rejected('-webkit-BORDER-radius'), + }, + ], +}); + testRule({ ruleName, config: [true, { ignore: ['consecutive-duplicates'] }], @@ -218,40 +307,25 @@ testRule({ config: [true, { ignore: ['consecutive-duplicates'] }], fix: true, - accept: [ - { - code: 'p { font-size: 16px; font-size: 1rem; }', - }, - { - code: 'p { display: inline-block; font-size: 16px; font-size: 1rem; }', - }, - { - code: 'p { font-size: 16px; font-size: 1rem; color: red; }', - }, - { - code: 'p { display: inline-block; font-size: 16px; font-size: 1rem; color: red; }', - }, - ], - reject: [ { code: 'p { font-size: 16px; font-weight: 400; font-size: 1rem; }', - fixed: 'p { font-size: 16px; font-weight: 400; }', + fixed: 'p { font-weight: 400; font-size: 1rem; }', message: messages.rejected('font-size'), }, { code: 'p { display: inline-block; font-size: 16px; font-weight: 400; font-size: 1rem; }', - fixed: 'p { display: inline-block; font-size: 16px; font-weight: 400; }', + fixed: 'p { display: inline-block; font-weight: 400; font-size: 1rem; }', message: messages.rejected('font-size'), }, { code: 'p { font-size: 16px; font-weight: 400; font-size: 1rem; color: red; }', - fixed: 'p { font-size: 16px; font-weight: 400; color: red; }', + fixed: 'p { font-weight: 400; font-size: 1rem; color: red; }', message: messages.rejected('font-size'), }, { code: 'p { display: inline-block; font-size: 16px; font-weight: 400; font-size: 1rem; color: red; }', - fixed: 'p { display: inline-block; font-size: 16px; font-weight: 400; color: red; }', + fixed: 'p { display: inline-block; font-weight: 400; font-size: 1rem; color: red; }', message: messages.rejected('font-size'), }, ], @@ -260,7 +334,6 @@ testRule({ testRule({ ruleName, config: [true, { ignore: ['consecutive-duplicates-with-different-values'] }], - fix: false, accept: [{ code: 'p { font-size: 16px; font-size: 1rem; font-weight: 400; }' }], @@ -281,15 +354,15 @@ testRule({ config: [true, { ignore: ['consecutive-duplicates-with-different-values'] }], fix: true, - accept: [{ code: 'p { font-size: 16px; font-size: 1rem; font-weight: 400; }' }], - reject: [ { code: 'p { font-size: 16px; font-weight: 400; font-size: 1rem; }', + fixed: 'p { font-weight: 400; font-size: 1rem; }', message: messages.rejected('font-size'), }, { code: 'p { font-size: 16px; font-size: 16px; font-weight: 400; }', + fixed: 'p { font-size: 16px; font-weight: 400; }', message: messages.rejected('font-size'), }, ], @@ -330,6 +403,30 @@ testRule({ ], }); +testRule({ + ruleName, + config: [true, { ignore: ['consecutive-duplicates-with-same-prefixless-values'] }], + fix: true, + + reject: [ + { + code: 'p { width: fit-content; height: 32px; width: -moz-fit-content; }', + fixed: 'p { height: 32px; width: -moz-fit-content; }', + message: messages.rejected('width'), + }, + { + code: 'p { width: 100%; width: -moz-fit-content; height: 32px; }', + fixed: 'p { width: -moz-fit-content; height: 32px; }', + message: messages.rejected('width'), + }, + { + code: 'p { width: -moz-fit-content; width: -moz-fit-content; }', + fixed: 'p { width: -moz-fit-content; }', + message: messages.rejected('width'), + }, + ], +}); + testRule({ ruleName, config: [true, { ignoreProperties: ['color'] }], @@ -358,6 +455,25 @@ testRule({ ], }); +testRule({ + ruleName, + config: [true, { ignoreProperties: ['color'] }], + fix: true, + + reject: [ + { + code: 'p { color: pink; background: orange; background: white; }', + fixed: 'p { color: pink; background: white; }', + message: messages.rejected('background'), + }, + { + code: 'p { background: orange; color: pink; background: white; }', + fixed: 'p { color: pink; background: white; }', + message: messages.rejected('background'), + }, + ], +}); + testRule({ ruleName, config: [true, { ignoreProperties: ['/background-/'] }], @@ -383,6 +499,25 @@ testRule({ ], }); +testRule({ + ruleName, + config: [true, { ignoreProperties: ['/background-/'] }], + fix: true, + + reject: [ + { + code: 'p { color: pink; background: orange; background: white; }', + fixed: 'p { color: pink; background: white; }', + message: messages.rejected('background'), + }, + { + code: 'p { background: orange; color: pink; background: white; }', + fixed: 'p { color: pink; background: white; }', + message: messages.rejected('background'), + }, + ], +}); + testRule({ ruleName, config: [true], @@ -419,6 +554,31 @@ testRule({ ], }); +testRule({ + ruleName, + config: [true], + customSyntax: 'postcss-html', + fix: true, + + reject: [ + { + code: '', + fixed: '', + message: messages.rejected('color'), + }, + { + code: '', + fixed: '', + message: messages.rejected('background'), + }, + { + code: '', + fixed: '', + message: messages.rejected('background'), + }, + ], +}); + testRule({ skip: true, ruleName, @@ -441,3 +601,25 @@ testRule({ }, ], }); + +testRule({ + skip: true, + ruleName, + config: [true], + customSyntax: 'postcss-css-in-js', + fix: true, + + reject: [ + { + code: "import styled from 'styled-components';\nexport default styled.div` color: pink; color: orange; `;", + fixed: + "import styled from 'styled-components';\nexport default styled.div` color: orange; `;", + message: messages.rejected('color'), + }, + { + code: "import styled from 'react-emotion'\nexport default styled.div` color: pink; color: orange; `;", + fixed: "import styled from 'react-emotion'\nexport default styled.div` color: orange; `;", + message: messages.rejected('color'), + }, + ], +}); diff --git a/lib/rules/declaration-block-no-duplicate-properties/index.js b/lib/rules/declaration-block-no-duplicate-properties/index.js index 5de0cfa147..30784f3aac 100644 --- a/lib/rules/declaration-block-no-duplicate-properties/index.js +++ b/lib/rules/declaration-block-no-duplicate-properties/index.js @@ -58,9 +58,11 @@ const rule = (primary, secondaryOptions, context) => { ); eachDeclarationBlock(root, (eachDecl) => { - /** @type {string[]} */ + /** @type {import('postcss').Declaration[]} */ const decls = []; /** @type {string[]} */ + const declsProps = []; + /** @type {string[]} */ const values = []; eachDecl((decl) => { @@ -85,12 +87,18 @@ const rule = (primary, secondaryOptions, context) => { return; } - const indexDuplicate = decls.indexOf(prop.toLowerCase()); + const indexDuplicate = declsProps.indexOf(prop.toLowerCase()); if (indexDuplicate !== -1) { if (ignoreDiffValues || ignorePrefixlessSameValues) { // fails if duplicates are not consecutive - if (indexDuplicate !== decls.length - 1) { + if (indexDuplicate !== declsProps.length - 1) { + if (context.fix) { + removePreviousDuplicate(decls, prop); + + return; + } + report({ message: messages.rejected(prop), node: decl, @@ -107,6 +115,12 @@ const rule = (primary, secondaryOptions, context) => { if (ignorePrefixlessSameValues) { // fails if values of consecutive, unprefixed duplicates are equal if (vendor.unprefixed(value) !== vendor.unprefixed(duplicateValue)) { + if (context.fix) { + removePreviousDuplicate(decls, prop); + + return; + } + report({ message: messages.rejected(prop), node: decl, @@ -122,7 +136,7 @@ const rule = (primary, secondaryOptions, context) => { // fails if values of consecutive duplicates are equal if (value === duplicateValue) { if (context.fix) { - decl.remove(); + removePreviousDuplicate(decls, prop); return; } @@ -141,12 +155,12 @@ const rule = (primary, secondaryOptions, context) => { return; } - if (ignoreDuplicates && indexDuplicate === decls.length - 1) { + if (ignoreDuplicates && indexDuplicate === declsProps.length - 1) { return; } if (context.fix) { - decl.remove(); + removePreviousDuplicate(decls, prop); return; } @@ -160,13 +174,25 @@ const rule = (primary, secondaryOptions, context) => { }); } - decls.push(prop); - values.push(value); + decls.push(decl); + declsProps.push(prop.toLowerCase()); + values.push(value.toLowerCase()); }); }); }; }; +/** + * @param {import('postcss').Declaration[]} declarations + * @param {string} property + * @returns {void} + * */ +function removePreviousDuplicate(declarations, property) { + const declToRemove = declarations.find((d) => d.prop.toLowerCase() === property.toLowerCase()); + + if (declToRemove) declToRemove.remove(); +} + rule.ruleName = ruleName; rule.messages = messages; rule.meta = meta; From 9f3646ddbfe8f838ce5a5518424c429851fdc090 Mon Sep 17 00:00:00 2001 From: fpetrakov Date: Tue, 23 Aug 2022 10:11:16 +0300 Subject: [PATCH 3/8] fixes --- .../README.md | 2 ++ .../index.js | 33 +++++++++---------- 2 files changed, 17 insertions(+), 18 deletions(-) diff --git a/lib/rules/declaration-block-no-duplicate-properties/README.md b/lib/rules/declaration-block-no-duplicate-properties/README.md index a8bd6b7980..258f9d5cdd 100644 --- a/lib/rules/declaration-block-no-duplicate-properties/README.md +++ b/lib/rules/declaration-block-no-duplicate-properties/README.md @@ -11,6 +11,8 @@ a { color: pink; color: orange; } This rule ignores variables (`$sass`, `@less`, `--custom-property`). +The [`fix` option](../../../docs/user-guide/usage/options.md#fix) can automatically fix all of the problems reported by this rule. + ## Options ### `true` diff --git a/lib/rules/declaration-block-no-duplicate-properties/index.js b/lib/rules/declaration-block-no-duplicate-properties/index.js index 30784f3aac..81253bf7bf 100644 --- a/lib/rules/declaration-block-no-duplicate-properties/index.js +++ b/lib/rules/declaration-block-no-duplicate-properties/index.js @@ -18,6 +18,7 @@ const messages = ruleMessages(ruleName, { const meta = { url: 'https://stylelint.io/user-guide/rules/list/declaration-block-no-duplicate-properties', + fixable: true, }; /** @type {import('stylelint').Rule} */ @@ -60,13 +61,10 @@ const rule = (primary, secondaryOptions, context) => { eachDeclarationBlock(root, (eachDecl) => { /** @type {import('postcss').Declaration[]} */ const decls = []; - /** @type {string[]} */ - const declsProps = []; - /** @type {string[]} */ - const values = []; eachDecl((decl) => { const prop = decl.prop; + const lowerProp = decl.prop.toLowerCase(); const value = decl.value; if (!isStandardSyntaxProperty(prop)) { @@ -83,18 +81,18 @@ const rule = (primary, secondaryOptions, context) => { } // Ignore the src property as commonly duplicated in at-fontface - if (prop.toLowerCase() === 'src') { + if (lowerProp === 'src') { return; } - const indexDuplicate = declsProps.indexOf(prop.toLowerCase()); + const indexDuplicate = decls.findIndex((d) => d.prop.toLowerCase() === lowerProp); if (indexDuplicate !== -1) { if (ignoreDiffValues || ignorePrefixlessSameValues) { // fails if duplicates are not consecutive - if (indexDuplicate !== declsProps.length - 1) { + if (indexDuplicate !== decls.length - 1) { if (context.fix) { - removePreviousDuplicate(decls, prop); + removePreviousDuplicate(decls, lowerProp); return; } @@ -110,13 +108,14 @@ const rule = (primary, secondaryOptions, context) => { return; } - const duplicateValue = values[indexDuplicate] || ''; + const duplicateDecl = decls[indexDuplicate]; + const duplicateValue = duplicateDecl ? duplicateDecl.value : ''; if (ignorePrefixlessSameValues) { // fails if values of consecutive, unprefixed duplicates are equal if (vendor.unprefixed(value) !== vendor.unprefixed(duplicateValue)) { if (context.fix) { - removePreviousDuplicate(decls, prop); + removePreviousDuplicate(decls, lowerProp); return; } @@ -136,7 +135,7 @@ const rule = (primary, secondaryOptions, context) => { // fails if values of consecutive duplicates are equal if (value === duplicateValue) { if (context.fix) { - removePreviousDuplicate(decls, prop); + removePreviousDuplicate(decls, lowerProp); return; } @@ -155,12 +154,12 @@ const rule = (primary, secondaryOptions, context) => { return; } - if (ignoreDuplicates && indexDuplicate === declsProps.length - 1) { + if (ignoreDuplicates && indexDuplicate === decls.length - 1) { return; } if (context.fix) { - removePreviousDuplicate(decls, prop); + removePreviousDuplicate(decls, lowerProp); return; } @@ -175,8 +174,6 @@ const rule = (primary, secondaryOptions, context) => { } decls.push(decl); - declsProps.push(prop.toLowerCase()); - values.push(value.toLowerCase()); }); }); }; @@ -184,11 +181,11 @@ const rule = (primary, secondaryOptions, context) => { /** * @param {import('postcss').Declaration[]} declarations - * @param {string} property + * @param {string} lowerProperty * @returns {void} * */ -function removePreviousDuplicate(declarations, property) { - const declToRemove = declarations.find((d) => d.prop.toLowerCase() === property.toLowerCase()); +function removePreviousDuplicate(declarations, lowerProperty) { + const declToRemove = declarations.find((d) => d.prop.toLowerCase() === lowerProperty); if (declToRemove) declToRemove.remove(); } From f4ef93b582c7529297c1874b909d9f5544c172eb Mon Sep 17 00:00:00 2001 From: fpetrakov Date: Tue, 23 Aug 2022 10:23:29 +0300 Subject: [PATCH 4/8] merge tests --- .../__tests__/index.js | 250 ++---------------- 1 file changed, 23 insertions(+), 227 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 378054f934..05e7e0718a 100644 --- a/lib/rules/declaration-block-no-duplicate-properties/__tests__/index.js +++ b/lib/rules/declaration-block-no-duplicate-properties/__tests__/index.js @@ -5,6 +5,7 @@ const { messages, ruleName } = require('..'); testRule({ ruleName, config: [true], + fix: true, accept: [ { @@ -48,6 +49,7 @@ testRule({ reject: [ { code: 'a { color: pink; color: orange }', + fixed: 'a { color: orange }', message: messages.rejected('color'), line: 1, column: 18, @@ -56,6 +58,7 @@ testRule({ }, { code: 'a { cOlOr: pink; color: orange }', + fixed: 'a { color: orange }', message: messages.rejected('color'), line: 1, column: 18, @@ -64,6 +67,7 @@ testRule({ }, { code: 'a { color: pink; cOlOr: orange }', + fixed: 'a { cOlOr: orange }', message: messages.rejected('cOlOr'), line: 1, column: 18, @@ -72,6 +76,7 @@ testRule({ }, { code: 'a { cOlOr: pink; cOlOr: orange }', + fixed: 'a { cOlOr: orange }', message: messages.rejected('cOlOr'), line: 1, column: 18, @@ -80,6 +85,7 @@ testRule({ }, { code: 'a { COLOR: pink; color: orange }', + fixed: 'a { color: orange }', message: messages.rejected('color'), line: 1, column: 18, @@ -88,6 +94,7 @@ testRule({ }, { code: 'a { color: pink; COLOR: orange }', + fixed: 'a { COLOR: orange }', message: messages.rejected('COLOR'), line: 1, column: 18, @@ -96,6 +103,7 @@ testRule({ }, { code: 'a { color: pink; background: orange; color: orange }', + fixed: 'a { background: orange; color: orange }', message: messages.rejected('color'), line: 1, column: 38, @@ -104,6 +112,7 @@ testRule({ }, { code: 'a { color: pink; background: orange; background: pink; }', + fixed: 'a { color: pink; background: pink; }', message: messages.rejected('background'), line: 1, column: 38, @@ -112,6 +121,7 @@ testRule({ }, { code: 'a { color: pink; { &:hover { color: orange; color: black; } } }', + fixed: 'a { color: pink; { &:hover { color: black; } } }', description: 'spec nested', message: messages.rejected('color'), line: 1, @@ -121,6 +131,7 @@ testRule({ }, { code: 'a { color: pink; @media { color: orange; color: black; } }', + fixed: 'a { color: pink; @media { color: black; } }', description: 'nested', message: messages.rejected('color'), line: 1, @@ -130,6 +141,7 @@ testRule({ }, { code: '@media { color: orange; .foo { color: black; color: white; } }', + fixed: '@media { color: orange; .foo { color: white; } }', description: 'nested', message: messages.rejected('color'), line: 1, @@ -139,6 +151,7 @@ testRule({ }, { code: 'a { color: pink; @media { color: orange; &::before { color: black; color: white; } } }', + fixed: 'a { color: pink; @media { color: orange; &::before { color: white; } } }', description: 'double nested', message: messages.rejected('color'), line: 1, @@ -148,6 +161,7 @@ testRule({ }, { code: 'a { color: pink; @media { color: orange; .foo { color: black; color: white; } } }', + fixed: 'a { color: pink; @media { color: orange; .foo { color: white; } } }', description: 'double nested again', message: messages.rejected('color'), line: 1, @@ -157,6 +171,7 @@ testRule({ }, { code: 'a { -webkit-border-radius: 12px; -webkit-border-radius: 10px; }', + fixed: 'a { -webkit-border-radius: 10px; }', message: messages.rejected('-webkit-border-radius'), line: 1, column: 34, @@ -165,6 +180,7 @@ testRule({ }, { code: 'a { -WEBKIT-border-radius: 12px; -webkit-BORDER-radius: 10px; }', + fixed: 'a { -webkit-BORDER-radius: 10px; }', message: messages.rejected('-webkit-BORDER-radius'), line: 1, column: 34, @@ -174,98 +190,10 @@ testRule({ ], }); -testRule({ - ruleName, - config: [true], - fix: true, - - reject: [ - { - code: 'a { color: pink; color: orange }', - fixed: 'a { color: orange }', - message: messages.rejected('color'), - }, - { - code: 'a { cOlOr: pink; color: orange }', - fixed: 'a { color: orange }', - message: messages.rejected('color'), - }, - { - code: 'a { color: pink; cOlOr: orange }', - fixed: 'a { cOlOr: orange }', - message: messages.rejected('cOlOr'), - }, - { - code: 'a { cOlOr: pink; cOlOr: orange }', - fixed: 'a { cOlOr: orange }', - message: messages.rejected('cOlOr'), - }, - { - code: 'a { COLOR: pink; color: orange }', - fixed: 'a { color: orange }', - message: messages.rejected('color'), - }, - { - code: 'a { color: pink; COLOR: orange }', - fixed: 'a { COLOR: orange }', - message: messages.rejected('COLOR'), - }, - { - code: 'a { color: pink; background: orange; color: orange }', - fixed: 'a { background: orange; color: orange }', - message: messages.rejected('color'), - }, - { - code: 'a { color: pink; background: orange; background: pink; }', - fixed: 'a { color: pink; background: pink; }', - message: messages.rejected('background'), - }, - { - code: 'a { color: pink; { &:hover { color: orange; color: black; } } }', - fixed: 'a { color: pink; { &:hover { color: black; } } }', - description: 'spec nested', - message: messages.rejected('color'), - }, - { - code: 'a { color: pink; @media { color: orange; color: black; } }', - fixed: 'a { color: pink; @media { color: black; } }', - description: 'nested', - message: messages.rejected('color'), - }, - { - code: '@media { color: orange; .foo { color: black; color: white; } }', - fixed: '@media { color: orange; .foo { color: white; } }', - description: 'nested', - message: messages.rejected('color'), - }, - { - code: 'a { color: pink; @media { color: orange; &::before { color: black; color: white; } } }', - fixed: 'a { color: pink; @media { color: orange; &::before { color: white; } } }', - description: 'double nested', - message: messages.rejected('color'), - }, - { - code: 'a { color: pink; @media { color: orange; .foo { color: black; color: white; } } }', - fixed: 'a { color: pink; @media { color: orange; .foo { color: white; } } }', - description: 'double nested again', - message: messages.rejected('color'), - }, - { - code: 'a { -webkit-border-radius: 12px; -webkit-border-radius: 10px; }', - fixed: 'a { -webkit-border-radius: 10px; }', - message: messages.rejected('-webkit-border-radius'), - }, - { - code: 'a { -WEBKIT-border-radius: 12px; -webkit-BORDER-radius: 10px; }', - fixed: 'a { -webkit-BORDER-radius: 10px; }', - message: messages.rejected('-webkit-BORDER-radius'), - }, - ], -}); - testRule({ ruleName, config: [true, { ignore: ['consecutive-duplicates'] }], + fix: true, accept: [ { @@ -282,31 +210,6 @@ testRule({ }, ], - reject: [ - { - code: 'p { font-size: 16px; font-weight: 400; font-size: 1rem; }', - message: messages.rejected('font-size'), - }, - { - code: 'p { display: inline-block; font-size: 16px; font-weight: 400; font-size: 1rem; }', - message: messages.rejected('font-size'), - }, - { - code: 'p { font-size: 16px; font-weight: 400; font-size: 1rem; color: red; }', - message: messages.rejected('font-size'), - }, - { - code: 'p { display: inline-block; font-size: 16px; font-weight: 400; font-size: 1rem; color: red; }', - message: messages.rejected('font-size'), - }, - ], -}); - -testRule({ - ruleName, - config: [true, { ignore: ['consecutive-duplicates'] }], - fix: true, - reject: [ { code: 'p { font-size: 16px; font-weight: 400; font-size: 1rem; }', @@ -334,26 +237,10 @@ testRule({ testRule({ ruleName, config: [true, { ignore: ['consecutive-duplicates-with-different-values'] }], + fix: true, accept: [{ code: 'p { font-size: 16px; font-size: 1rem; font-weight: 400; }' }], - reject: [ - { - code: 'p { font-size: 16px; font-weight: 400; font-size: 1rem; }', - message: messages.rejected('font-size'), - }, - { - code: 'p { font-size: 16px; font-size: 16px; font-weight: 400; }', - message: messages.rejected('font-size'), - }, - ], -}); - -testRule({ - ruleName, - config: [true, { ignore: ['consecutive-duplicates-with-different-values'] }], - fix: true, - reject: [ { code: 'p { font-size: 16px; font-weight: 400; font-size: 1rem; }', @@ -371,6 +258,7 @@ testRule({ testRule({ ruleName, config: [true, { ignore: ['consecutive-duplicates-with-same-prefixless-values'] }], + fix: true, accept: [ { @@ -387,27 +275,6 @@ testRule({ }, ], - reject: [ - { - code: 'p { width: fit-content; height: 32px; width: -moz-fit-content; }', - message: messages.rejected('width'), - }, - { - code: 'p { width: 100%; width: -moz-fit-content; height: 32px; }', - message: messages.rejected('width'), - }, - { - code: 'p { width: -moz-fit-content; width: -moz-fit-content; }', - message: messages.rejected('width'), - }, - ], -}); - -testRule({ - ruleName, - config: [true, { ignore: ['consecutive-duplicates-with-same-prefixless-values'] }], - fix: true, - reject: [ { code: 'p { width: fit-content; height: 32px; width: -moz-fit-content; }', @@ -430,6 +297,7 @@ testRule({ testRule({ ruleName, config: [true, { ignoreProperties: ['color'] }], + fix: true, accept: [ { @@ -443,23 +311,6 @@ testRule({ }, ], - reject: [ - { - code: 'p { color: pink; background: orange; background: white; }', - message: messages.rejected('background'), - }, - { - code: 'p { background: orange; color: pink; background: white; }', - message: messages.rejected('background'), - }, - ], -}); - -testRule({ - ruleName, - config: [true, { ignoreProperties: ['color'] }], - fix: true, - reject: [ { code: 'p { color: pink; background: orange; background: white; }', @@ -477,6 +328,7 @@ testRule({ testRule({ ruleName, config: [true, { ignoreProperties: ['/background-/'] }], + fix: true, accept: [ { @@ -487,23 +339,6 @@ testRule({ }, ], - reject: [ - { - code: 'p { color: pink; background: orange; background: white; }', - message: messages.rejected('background'), - }, - { - code: 'p { background: orange; color: pink; background: white; }', - message: messages.rejected('background'), - }, - ], -}); - -testRule({ - ruleName, - config: [true, { ignoreProperties: ['/background-/'] }], - fix: true, - reject: [ { code: 'p { color: pink; background: orange; background: white; }', @@ -522,6 +357,7 @@ testRule({ ruleName, config: [true], customSyntax: 'postcss-html', + fix: true, accept: [ { @@ -538,28 +374,6 @@ testRule({ }, ], - reject: [ - { - code: '', - message: messages.rejected('color'), - }, - { - code: '', - message: messages.rejected('background'), - }, - { - code: '', - message: messages.rejected('background'), - }, - ], -}); - -testRule({ - ruleName, - config: [true], - customSyntax: 'postcss-html', - fix: true, - reject: [ { code: '', @@ -584,31 +398,13 @@ testRule({ ruleName, config: [true], customSyntax: 'postcss-css-in-js', + fix: true, accept: [ { code: "import styled from 'react-emotion'\nexport default styled.div` color: pink; `;", }, ], - reject: [ - { - code: "import styled from 'styled-components';\nexport default styled.div` color: pink; color: orange; `;", - message: messages.rejected('color'), - }, - { - code: "import styled from 'react-emotion'\nexport default styled.div` color: pink; color: orange; `;", - message: messages.rejected('color'), - }, - ], -}); - -testRule({ - skip: true, - ruleName, - config: [true], - customSyntax: 'postcss-css-in-js', - fix: true, - reject: [ { code: "import styled from 'styled-components';\nexport default styled.div` color: pink; color: orange; `;", From 75f1e4aa820294bb3c777cd107629347643a0dbe Mon Sep 17 00:00:00 2001 From: fpetrakov Date: Tue, 23 Aug 2022 10:42:46 +0300 Subject: [PATCH 5/8] add Autofixable to docs and update snapshots --- docs/user-guide/rules/list.md | 2 +- system-tests/001/__snapshots__/fs.test.js.snap | 1 + system-tests/001/__snapshots__/no-fs.test.js.snap | 1 + system-tests/003/__snapshots__/fs.test.js.snap | 1 + system-tests/003/__snapshots__/no-fs.test.js.snap | 1 + 5 files changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/user-guide/rules/list.md b/docs/user-guide/rules/list.md index 6b0b3e5e27..623ce68a89 100644 --- a/docs/user-guide/rules/list.md +++ b/docs/user-guide/rules/list.md @@ -61,7 +61,7 @@ Within each cateogory, the rules are grouped by the [_thing_](http://apps.workfl ### 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-duplicate-properties`](../../../lib/rules/declaration-block-no-duplicate-properties/README.md): Disallow duplicate properties within declaration blocks (Autofixable). - [`declaration-block-no-shorthand-property-overrides`](../../../lib/rules/declaration-block-no-shorthand-property-overrides/README.md): Disallow shorthand properties that override related longhand properties. ### Block diff --git a/system-tests/001/__snapshots__/fs.test.js.snap b/system-tests/001/__snapshots__/fs.test.js.snap index f1df3cf26e..324d5e9c75 100644 --- a/system-tests/001/__snapshots__/fs.test.js.snap +++ b/system-tests/001/__snapshots__/fs.test.js.snap @@ -110,6 +110,7 @@ Object { "url": "https://stylelint.io/user-guide/rules/list/declaration-bang-space-before", }, "declaration-block-no-duplicate-properties": Object { + "fixable": true, "url": "https://stylelint.io/user-guide/rules/list/declaration-block-no-duplicate-properties", }, "declaration-block-no-shorthand-property-overrides": Object { diff --git a/system-tests/001/__snapshots__/no-fs.test.js.snap b/system-tests/001/__snapshots__/no-fs.test.js.snap index f679ecc800..00c3a8368c 100644 --- a/system-tests/001/__snapshots__/no-fs.test.js.snap +++ b/system-tests/001/__snapshots__/no-fs.test.js.snap @@ -110,6 +110,7 @@ Object { "url": "https://stylelint.io/user-guide/rules/list/declaration-bang-space-before", }, "declaration-block-no-duplicate-properties": Object { + "fixable": true, "url": "https://stylelint.io/user-guide/rules/list/declaration-block-no-duplicate-properties", }, "declaration-block-no-shorthand-property-overrides": Object { diff --git a/system-tests/003/__snapshots__/fs.test.js.snap b/system-tests/003/__snapshots__/fs.test.js.snap index 5e45d3bfa7..9c8221fe41 100644 --- a/system-tests/003/__snapshots__/fs.test.js.snap +++ b/system-tests/003/__snapshots__/fs.test.js.snap @@ -130,6 +130,7 @@ Object { "url": "https://stylelint.io/user-guide/rules/list/declaration-bang-space-before", }, "declaration-block-no-duplicate-properties": Object { + "fixable": true, "url": "https://stylelint.io/user-guide/rules/list/declaration-block-no-duplicate-properties", }, "declaration-block-no-shorthand-property-overrides": Object { diff --git a/system-tests/003/__snapshots__/no-fs.test.js.snap b/system-tests/003/__snapshots__/no-fs.test.js.snap index b66e4de33d..89309b1463 100644 --- a/system-tests/003/__snapshots__/no-fs.test.js.snap +++ b/system-tests/003/__snapshots__/no-fs.test.js.snap @@ -322,6 +322,7 @@ footer a:visited { "url": "https://stylelint.io/user-guide/rules/list/declaration-bang-space-before", }, "declaration-block-no-duplicate-properties": Object { + "fixable": true, "url": "https://stylelint.io/user-guide/rules/list/declaration-block-no-duplicate-properties", }, "declaration-block-no-shorthand-property-overrides": Object { From 83886a5b68eb791f4435bcc997b6be32072d3ddf Mon Sep 17 00:00:00 2001 From: fpetrakov Date: Tue, 23 Aug 2022 10:45:42 +0300 Subject: [PATCH 6/8] revert changes for postcss-css-in-js tests --- .../__tests__/index.js | 23 +++++++++++++++++++ 1 file changed, 23 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 05e7e0718a..222a27229e 100644 --- a/lib/rules/declaration-block-no-duplicate-properties/__tests__/index.js +++ b/lib/rules/declaration-block-no-duplicate-properties/__tests__/index.js @@ -393,6 +393,29 @@ testRule({ ], }); +testRule({ + skip: true, + ruleName, + config: [true], + customSyntax: 'postcss-css-in-js', + + accept: [ + { + code: "import styled from 'react-emotion'\nexport default styled.div` color: pink; `;", + }, + ], + reject: [ + { + code: "import styled from 'styled-components';\nexport default styled.div` color: pink; color: orange; `;", + message: messages.rejected('color'), + }, + { + code: "import styled from 'react-emotion'\nexport default styled.div` color: pink; color: orange; `;", + message: messages.rejected('color'), + }, + ], +}); + testRule({ skip: true, ruleName, From b32ddb8c02ff5a2de67852c1a3b1dddeef16d7be Mon Sep 17 00:00:00 2001 From: fpetrakov Date: Tue, 23 Aug 2022 11:05:06 +0300 Subject: [PATCH 7/8] remove skipped tests --- .../__tests__/index.js | 27 ------------------- 1 file changed, 27 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 222a27229e..9049919421 100644 --- a/lib/rules/declaration-block-no-duplicate-properties/__tests__/index.js +++ b/lib/rules/declaration-block-no-duplicate-properties/__tests__/index.js @@ -415,30 +415,3 @@ testRule({ }, ], }); - -testRule({ - skip: true, - ruleName, - config: [true], - customSyntax: 'postcss-css-in-js', - fix: true, - - accept: [ - { - code: "import styled from 'react-emotion'\nexport default styled.div` color: pink; `;", - }, - ], - reject: [ - { - code: "import styled from 'styled-components';\nexport default styled.div` color: pink; color: orange; `;", - fixed: - "import styled from 'styled-components';\nexport default styled.div` color: orange; `;", - message: messages.rejected('color'), - }, - { - code: "import styled from 'react-emotion'\nexport default styled.div` color: pink; color: orange; `;", - fixed: "import styled from 'react-emotion'\nexport default styled.div` color: orange; `;", - message: messages.rejected('color'), - }, - ], -}); From fd25dfb8555a96d903d2f56e295d994e2349c77c Mon Sep 17 00:00:00 2001 From: Masafumi Koba <473530+ybiquitous@users.noreply.github.com> Date: Wed, 24 Aug 2022 13:16:17 +0900 Subject: [PATCH 8/8] Add changeset --- .changeset/nice-lemons-reflect.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/nice-lemons-reflect.md diff --git a/.changeset/nice-lemons-reflect.md b/.changeset/nice-lemons-reflect.md new file mode 100644 index 0000000000..6f75e4c2c8 --- /dev/null +++ b/.changeset/nice-lemons-reflect.md @@ -0,0 +1,5 @@ +--- +"stylelint": minor +--- + +Added: `declaration-block-no-duplicate-properties` autofix