diff --git a/.changeset/chilled-lizards-film.md b/.changeset/chilled-lizards-film.md new file mode 100644 index 0000000000..7078ddd0e3 --- /dev/null +++ b/.changeset/chilled-lizards-film.md @@ -0,0 +1,5 @@ +--- +"stylelint": minor +--- + +Added: `splitList: boolean` to `selector-nested-pattern` diff --git a/lib/rules/selector-nested-pattern/README.md b/lib/rules/selector-nested-pattern/README.md index 23a9e13fb4..9f2a560e66 100644 --- a/lib/rules/selector-nested-pattern/README.md +++ b/lib/rules/selector-nested-pattern/README.md @@ -77,3 +77,37 @@ a { &:focus {} } ``` + +## Optional secondary options + +### `splitList: true | false` (default: `false`) + +Split selector lists into individual selectors. + +For example, with `true`. + +Given the string: + +```json +"^&:(?:hover|focus)$" +``` + +The following patterns are considered problems: + + +```css +a { + .bar:hover, + &:focus {} +} +``` + +The following patterns are _not_ considered problems: + + +```css +a { + &:hover, + &:focus {} +} +``` diff --git a/lib/rules/selector-nested-pattern/__tests__/index.js b/lib/rules/selector-nested-pattern/__tests__/index.js index 4b8f9f7f5a..e9288f5674 100644 --- a/lib/rules/selector-nested-pattern/__tests__/index.js +++ b/lib/rules/selector-nested-pattern/__tests__/index.js @@ -152,12 +152,45 @@ testRule({ endColumn: 18, }, { - code: '.foo { &:hover, &focus {} }', - message: messages.expected('&:hover, &focus', '^&:(?:hover|focus)$'), + code: '.foo { &:hover, &:focus {} }', + message: messages.expected('&:hover, &:focus', '^&:(?:hover|focus)$'), line: 1, column: 8, endLine: 1, - endColumn: 23, + endColumn: 24, + }, + ], +}); + +testRule({ + ruleName, + config: ['^&:(?:hover|focus)$', { splitList: true }], + + accept: [ + { + code: '.foo { &:hover {} }', + }, + { + code: '.foo { &:hover, &:focus {} }', + }, + ], + + reject: [ + { + code: '.foo { .bar:hover {} }', + message: messages.expected('.bar:hover', '^&:(?:hover|focus)$'), + line: 1, + column: 8, + endLine: 1, + endColumn: 18, + }, + { + code: '.foo { .bar:hover, &:focus {} }', + message: messages.expected('.bar:hover', '^&:(?:hover|focus)$'), + line: 1, + column: 8, + endLine: 1, + endColumn: 18, }, ], }); diff --git a/lib/rules/selector-nested-pattern/index.js b/lib/rules/selector-nested-pattern/index.js index d43c826e64..6d10d4fbe0 100644 --- a/lib/rules/selector-nested-pattern/index.js +++ b/lib/rules/selector-nested-pattern/index.js @@ -4,7 +4,7 @@ const isStandardSyntaxRule = require('../../utils/isStandardSyntaxRule'); const report = require('../../utils/report'); const ruleMessages = require('../../utils/ruleMessages'); const validateOptions = require('../../utils/validateOptions'); -const { isRegExp, isString } = require('../../utils/validateTypes'); +const { isBoolean, isRegExp, isString } = require('../../utils/validateTypes'); const ruleName = 'selector-nested-pattern'; @@ -17,18 +17,30 @@ const meta = { }; /** @type {import('stylelint').Rule} */ -const rule = (primary) => { +const rule = (primary, secondaryOptions) => { return (root, result) => { - const validOptions = validateOptions(result, ruleName, { - actual: primary, - possible: [isRegExp, isString], - }); + const validOptions = validateOptions( + result, + ruleName, + { + actual: primary, + possible: [isRegExp, isString], + }, + { + actual: secondaryOptions, + possible: { + splitList: [isBoolean], + }, + optional: true, + }, + ); if (!validOptions) { return; } const normalizedPattern = isString(primary) ? new RegExp(primary) : primary; + const splitList = secondaryOptions && secondaryOptions.splitList; root.walkRules((ruleNode) => { if (ruleNode.parent && ruleNode.parent.type !== 'rule') { @@ -39,20 +51,22 @@ const rule = (primary) => { return; } - const selector = ruleNode.selector; + const selectors = splitList ? ruleNode.selectors : [ruleNode.selector]; - if (normalizedPattern.test(selector)) { - return; - } + for (const selector of selectors) { + if (normalizedPattern.test(selector)) { + continue; + } - report({ - result, - ruleName, - message: messages.expected, - messageArgs: [selector, primary], - node: ruleNode, - word: selector, - }); + report({ + result, + ruleName, + message: messages.expected, + messageArgs: [selector, primary], + node: ruleNode, + word: selector, + }); + } }); }; };