From c3a355ae7b94be88b32c8946005ca9367324004d Mon Sep 17 00:00:00 2001 From: fpetrakov Date: Tue, 16 Aug 2022 19:10:07 +0300 Subject: [PATCH 01/10] tests --- .../selector-max-universal/__tests__/index.js | 116 ++++++++++++++++++ lib/rules/selector-max-universal/index.js | 21 +++- 2 files changed, 132 insertions(+), 5 deletions(-) diff --git a/lib/rules/selector-max-universal/__tests__/index.js b/lib/rules/selector-max-universal/__tests__/index.js index 7796624f7d..34eae0595c 100644 --- a/lib/rules/selector-max-universal/__tests__/index.js +++ b/lib/rules/selector-max-universal/__tests__/index.js @@ -186,6 +186,18 @@ testRule({ code: '* { @media print { * {} } }', description: 'media query: nested', }, + { + code: '* + * {}', + description: 'adjacent sibling combinator compound selector', + }, + { + code: '* > * {}', + description: 'child combinator compound selector', + }, + { + code: '* ~ * {}', + description: 'general sibling combinator compound selector', + }, ], reject: [ @@ -198,6 +210,35 @@ testRule({ endLine: 1, endColumn: 6, }, + { + code: '* + * + * {}', + description: + 'adjacent sibling combinator compound selector: greater than max universal selectors', + message: messages.expected('* + * + *', 2), + line: 1, + column: 1, + endLine: 1, + endColumn: 10, + }, + { + code: '* > * > * {}', + description: 'child combinator compound selector: greater than max universal selectors', + message: messages.expected('* > * > *', 2), + line: 1, + column: 1, + endLine: 1, + endColumn: 10, + }, + { + code: '* ~ * ~ * {}', + description: + 'general sibling combinator compound selector: greater than max universal selectors', + message: messages.expected('* ~ * ~ *', 2), + line: 1, + column: 1, + endLine: 1, + endColumn: 10, + }, { code: '*, \n* * * {}', description: 'multiple selectors: greater than max universal selectors', @@ -228,6 +269,81 @@ testRule({ ], }); +testRule({ + ruleName, + config: [0, { ignoreAfterCombinators: ['+'] }], + + accept: [ + { + code: 'foo {}', + }, + { + code: 'foo + * {}', + }, + ], + + reject: [ + { + code: '* {}', + message: messages.expected('*', 0), + line: 1, + column: 1, + endLine: 1, + endColumn: 2, + }, + ], +}); + +testRule({ + ruleName, + config: [0, { ignoreAfterCombinators: ['>'] }], + + accept: [ + { + code: 'foo {}', + }, + { + code: 'foo > * {}', + }, + ], + + reject: [ + { + code: '* {}', + message: messages.expected('*', 0), + line: 1, + column: 1, + endLine: 1, + endColumn: 2, + }, + ], +}); + +testRule({ + ruleName, + config: [0, { ignoreAfterCombinators: ['~'] }], + + accept: [ + { + code: 'foo {}', + }, + { + code: 'foo ~ * {}', + }, + ], + + reject: [ + { + code: '* {}', + message: messages.expected('*', 0), + line: 1, + column: 1, + endLine: 1, + endColumn: 2, + }, + ], +}); + testRule({ ruleName, config: [0], diff --git a/lib/rules/selector-max-universal/index.js b/lib/rules/selector-max-universal/index.js index eca5823a58..05547c00be 100644 --- a/lib/rules/selector-max-universal/index.js +++ b/lib/rules/selector-max-universal/index.js @@ -23,12 +23,23 @@ const meta = { }; /** @type {import('stylelint').Rule} */ -const rule = (primary) => { +const rule = (primary, secondaryOptions) => { return (root, result) => { - const validOptions = validateOptions(result, ruleName, { - actual: primary, - possible: isNonNegativeInteger, - }); + const validOptions = validateOptions( + result, + ruleName, + { + actual: primary, + possible: isNonNegativeInteger, + }, + { + actual: secondaryOptions, + possible: { + ignoreAfterCombinators: ['+', '>', '~'], + }, + optional: true, + }, + ); if (!validOptions) { return; From 45bce810578477694205a37fb41e254f51ca1941 Mon Sep 17 00:00:00 2001 From: fpetrakov Date: Wed, 17 Aug 2022 10:17:20 +0300 Subject: [PATCH 02/10] add option ignoreAfterCombinators --- lib/rules/selector-max-universal/index.js | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/lib/rules/selector-max-universal/index.js b/lib/rules/selector-max-universal/index.js index 05547c00be..4c9fcc8ac5 100644 --- a/lib/rules/selector-max-universal/index.js +++ b/lib/rules/selector-max-universal/index.js @@ -8,6 +8,7 @@ const resolvedNestedSelector = require('postcss-resolve-nested-selector'); const ruleMessages = require('../../utils/ruleMessages'); const selectorParser = require('postcss-selector-parser'); const validateOptions = require('../../utils/validateOptions'); +const optionsMatches = require('../../utils/optionsMatches'); const ruleName = 'selector-max-universal'; @@ -50,14 +51,22 @@ const rule = (primary, secondaryOptions) => { * @param {import('postcss').Rule} ruleNode */ function checkSelector(selectorNode, ruleNode) { - const count = selectorNode.reduce((total, childNode) => { + const count = selectorNode.reduce((total, childNode, index, nodes) => { // Only traverse inside actual selectors // All logical combinations will be resolved as nested selector in `postcss-resolve-nested-selector` if (childNode.type === 'selector') { checkSelector(childNode, ruleNode); } - if (childNode.type === 'universal') total += 1; + const prevChildNode = nodes[index - 1]; + + const prevChildNodeValue = prevChildNode && prevChildNode.value; + + if (childNode.type === 'universal') { + if (!optionsMatches(secondaryOptions, 'ignoreAfterCombinators', prevChildNodeValue)) { + total += 1; + } + } return total; }, 0); From 8c00178d6c2593f3195e6d2a7186142b50f919b6 Mon Sep 17 00:00:00 2001 From: fpetrakov Date: Wed, 17 Aug 2022 10:45:07 +0300 Subject: [PATCH 03/10] add docs and improve tests --- lib/rules/selector-max-universal/README.md | 32 +++++++++++++++++++ .../selector-max-universal/__tests__/index.js | 24 ++++++++------ 2 files changed, 47 insertions(+), 9 deletions(-) diff --git a/lib/rules/selector-max-universal/README.md b/lib/rules/selector-max-universal/README.md index 5424b55d2d..aa38e4ee0b 100644 --- a/lib/rules/selector-max-universal/README.md +++ b/lib/rules/selector-max-universal/README.md @@ -74,3 +74,35 @@ The following patterns are _not_ considered problems: /* `*` is inside `:not()`, so it is evaluated separately */ * > * .foo:not(*) {} ``` + +## Optional secondary options + +### `ignoreAfterCombinators: [">", "+", "~"]` + +Ignore universal selector if it's preceded by specified after combinators. + +Given: + +```json +[1] // maximum universal selectors allowed +[">", "+"] +``` + +The following patterns are considered problems: + + +```css +*.foo * {} +``` + +While the following patters are _not_ considered problems: + + +```css +*.foo + * {} +``` + + +```css +*.foo > * {} +``` diff --git a/lib/rules/selector-max-universal/__tests__/index.js b/lib/rules/selector-max-universal/__tests__/index.js index 34eae0595c..99439e99f1 100644 --- a/lib/rules/selector-max-universal/__tests__/index.js +++ b/lib/rules/selector-max-universal/__tests__/index.js @@ -296,39 +296,45 @@ testRule({ testRule({ ruleName, - config: [0, { ignoreAfterCombinators: ['>'] }], + config: [1, { ignoreAfterCombinators: ['>'] }], accept: [ { - code: 'foo {}', + code: '*.foo {}', }, { - code: 'foo > * {}', + code: '*.foo > * {}', }, ], reject: [ { - code: '* {}', - message: messages.expected('*', 0), + code: '*.foo * {}', + message: messages.expected('*.foo *', 1), line: 1, column: 1, endLine: 1, - endColumn: 2, + endColumn: 8, }, ], }); testRule({ ruleName, - config: [0, { ignoreAfterCombinators: ['~'] }], + config: [0, { ignoreAfterCombinators: ['~', '+', '>'] }], accept: [ { - code: 'foo {}', + code: '.foo {}', + }, + { + code: '.foo ~ * {}', + }, + { + code: '.foo + * {}', }, { - code: 'foo ~ * {}', + code: '.foo > * {}', }, ], From c157b12f3542530888786abb7755b54c5fec7d79 Mon Sep 17 00:00:00 2001 From: fpetrakov Date: Wed, 17 Aug 2022 13:26:20 +0300 Subject: [PATCH 04/10] add support for user defined combinators --- lib/rules/selector-max-universal/README.md | 7 +++---- lib/rules/selector-max-universal/__tests__/index.js | 8 +++++++- lib/rules/selector-max-universal/index.js | 13 ++++++++++--- 3 files changed, 20 insertions(+), 8 deletions(-) diff --git a/lib/rules/selector-max-universal/README.md b/lib/rules/selector-max-universal/README.md index aa38e4ee0b..cfbaae9722 100644 --- a/lib/rules/selector-max-universal/README.md +++ b/lib/rules/selector-max-universal/README.md @@ -77,15 +77,14 @@ The following patterns are _not_ considered problems: ## Optional secondary options -### `ignoreAfterCombinators: [">", "+", "~"]` +### `ignoreAfterCombinators: ["array "of "combinators"]` Ignore universal selector if it's preceded by specified after combinators. Given: ```json -[1] // maximum universal selectors allowed -[">", "+"] +[1, [">", "+"]] ``` The following patterns are considered problems: @@ -95,7 +94,7 @@ The following patterns are considered problems: *.foo * {} ``` -While the following patters are _not_ considered problems: +The following patters are _not_ considered problems: ```css diff --git a/lib/rules/selector-max-universal/__tests__/index.js b/lib/rules/selector-max-universal/__tests__/index.js index 99439e99f1..78e588a0b6 100644 --- a/lib/rules/selector-max-universal/__tests__/index.js +++ b/lib/rules/selector-max-universal/__tests__/index.js @@ -321,12 +321,18 @@ testRule({ testRule({ ruleName, - config: [0, { ignoreAfterCombinators: ['~', '+', '>'] }], + config: [0, { ignoreAfterCombinators: ['~', '+', '>', 'h1', 'article'] }], accept: [ { code: '.foo {}', }, + { + code: 'article * {}', + }, + { + code: 'h1 * {}', + }, { code: '.foo ~ * {}', }, diff --git a/lib/rules/selector-max-universal/index.js b/lib/rules/selector-max-universal/index.js index 4c9fcc8ac5..2987286a1b 100644 --- a/lib/rules/selector-max-universal/index.js +++ b/lib/rules/selector-max-universal/index.js @@ -9,6 +9,7 @@ const ruleMessages = require('../../utils/ruleMessages'); const selectorParser = require('postcss-selector-parser'); const validateOptions = require('../../utils/validateOptions'); const optionsMatches = require('../../utils/optionsMatches'); +const { isString } = require('../../utils/validateTypes'); const ruleName = 'selector-max-universal'; @@ -36,7 +37,7 @@ const rule = (primary, secondaryOptions) => { { actual: secondaryOptions, possible: { - ignoreAfterCombinators: ['+', '>', '~'], + ignoreAfterCombinators: [isString], }, optional: true, }, @@ -58,9 +59,15 @@ const rule = (primary, secondaryOptions) => { checkSelector(childNode, ruleNode); } - const prevChildNode = nodes[index - 1]; + let prevChildNode = nodes[index - 1]; + let prevChildNodeValue = prevChildNode && prevChildNode.value; - const prevChildNodeValue = prevChildNode && prevChildNode.value; + // checks if previous child node is descendant combinator + if (prevChildNodeValue === ' ') { + prevChildNode = nodes[index - 2]; + + prevChildNodeValue = prevChildNode && prevChildNode.value; + } if (childNode.type === 'universal') { if (!optionsMatches(secondaryOptions, 'ignoreAfterCombinators', prevChildNodeValue)) { From f8982a1a626b5996a924d44c560aef69bfc51707 Mon Sep 17 00:00:00 2001 From: fpetrakov Date: Wed, 17 Aug 2022 13:29:20 +0300 Subject: [PATCH 05/10] doc fix --- lib/rules/selector-max-universal/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/rules/selector-max-universal/README.md b/lib/rules/selector-max-universal/README.md index cfbaae9722..2d1e17d0d3 100644 --- a/lib/rules/selector-max-universal/README.md +++ b/lib/rules/selector-max-universal/README.md @@ -77,7 +77,7 @@ The following patterns are _not_ considered problems: ## Optional secondary options -### `ignoreAfterCombinators: ["array "of "combinators"]` +### `ignoreAfterCombinators: ["array "of" "combinators"]` Ignore universal selector if it's preceded by specified after combinators. From 0610d1f8889b376df382a951794e7ccc83c05efa Mon Sep 17 00:00:00 2001 From: fpetrakov Date: Wed, 17 Aug 2022 13:30:17 +0300 Subject: [PATCH 06/10] one more doc fix --- lib/rules/selector-max-universal/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/rules/selector-max-universal/README.md b/lib/rules/selector-max-universal/README.md index 2d1e17d0d3..19a7b352d7 100644 --- a/lib/rules/selector-max-universal/README.md +++ b/lib/rules/selector-max-universal/README.md @@ -77,7 +77,7 @@ The following patterns are _not_ considered problems: ## Optional secondary options -### `ignoreAfterCombinators: ["array "of" "combinators"]` +### `ignoreAfterCombinators: ["array", "of", "combinators"]` Ignore universal selector if it's preceded by specified after combinators. From f752f30856a72ef5f709c4c3ddad24850dab5419 Mon Sep 17 00:00:00 2001 From: fpetrakov Date: Wed, 17 Aug 2022 17:31:54 +0300 Subject: [PATCH 07/10] made use of prev method on childNode + added check descendant combinator type --- lib/rules/selector-max-universal/__tests__/index.js | 8 +------- lib/rules/selector-max-universal/index.js | 8 ++++---- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/lib/rules/selector-max-universal/__tests__/index.js b/lib/rules/selector-max-universal/__tests__/index.js index 78e588a0b6..99439e99f1 100644 --- a/lib/rules/selector-max-universal/__tests__/index.js +++ b/lib/rules/selector-max-universal/__tests__/index.js @@ -321,18 +321,12 @@ testRule({ testRule({ ruleName, - config: [0, { ignoreAfterCombinators: ['~', '+', '>', 'h1', 'article'] }], + config: [0, { ignoreAfterCombinators: ['~', '+', '>'] }], accept: [ { code: '.foo {}', }, - { - code: 'article * {}', - }, - { - code: 'h1 * {}', - }, { code: '.foo ~ * {}', }, diff --git a/lib/rules/selector-max-universal/index.js b/lib/rules/selector-max-universal/index.js index 2987286a1b..2a8bda7b83 100644 --- a/lib/rules/selector-max-universal/index.js +++ b/lib/rules/selector-max-universal/index.js @@ -52,19 +52,19 @@ const rule = (primary, secondaryOptions) => { * @param {import('postcss').Rule} ruleNode */ function checkSelector(selectorNode, ruleNode) { - const count = selectorNode.reduce((total, childNode, index, nodes) => { + const count = selectorNode.reduce((total, childNode) => { // Only traverse inside actual selectors // All logical combinations will be resolved as nested selector in `postcss-resolve-nested-selector` if (childNode.type === 'selector') { checkSelector(childNode, ruleNode); } - let prevChildNode = nodes[index - 1]; + let prevChildNode = childNode.prev(); let prevChildNodeValue = prevChildNode && prevChildNode.value; // checks if previous child node is descendant combinator - if (prevChildNodeValue === ' ') { - prevChildNode = nodes[index - 2]; + if (prevChildNode && prevChildNode.type === 'combinator' && prevChildNodeValue === ' ') { + prevChildNode = prevChildNode.prev(); prevChildNodeValue = prevChildNode && prevChildNode.value; } From 67e4c180bd34eab4056894cdebbafe30e72f2f15 Mon Sep 17 00:00:00 2001 From: fpetrakov Date: Thu, 18 Aug 2022 11:08:15 +0300 Subject: [PATCH 08/10] tests and docs fixes --- lib/rules/selector-max-universal/README.md | 20 ++++----------- .../selector-max-universal/__tests__/index.js | 25 ------------------- 2 files changed, 5 insertions(+), 40 deletions(-) diff --git a/lib/rules/selector-max-universal/README.md b/lib/rules/selector-max-universal/README.md index 19a7b352d7..5a387b7b44 100644 --- a/lib/rules/selector-max-universal/README.md +++ b/lib/rules/selector-max-universal/README.md @@ -79,29 +79,19 @@ The following patterns are _not_ considered problems: ### `ignoreAfterCombinators: ["array", "of", "combinators"]` -Ignore universal selector if it's preceded by specified after combinators. +Ignore universal selectors that come after one of the specified combinators. Given: ```json -[1, [">", "+"]] +[">", "+"] ``` -The following patterns are considered problems: - - -```css -*.foo * {} -``` - -The following patters are _not_ considered problems: +For example, with `2`. - -```css -*.foo + * {} -``` +The following pattern is _not_ considered a problem: ```css -*.foo > * {} +* * > * {} ``` diff --git a/lib/rules/selector-max-universal/__tests__/index.js b/lib/rules/selector-max-universal/__tests__/index.js index 99439e99f1..957f7ed463 100644 --- a/lib/rules/selector-max-universal/__tests__/index.js +++ b/lib/rules/selector-max-universal/__tests__/index.js @@ -269,31 +269,6 @@ testRule({ ], }); -testRule({ - ruleName, - config: [0, { ignoreAfterCombinators: ['+'] }], - - accept: [ - { - code: 'foo {}', - }, - { - code: 'foo + * {}', - }, - ], - - reject: [ - { - code: '* {}', - message: messages.expected('*', 0), - line: 1, - column: 1, - endLine: 1, - endColumn: 2, - }, - ], -}); - testRule({ ruleName, config: [1, { ignoreAfterCombinators: ['>'] }], From 65ebc48dd4d4d05bc0f888a2e69d2ec83ce660b4 Mon Sep 17 00:00:00 2001 From: fpetrakov Date: Thu, 18 Aug 2022 12:06:53 +0300 Subject: [PATCH 09/10] fixes --- lib/rules/selector-max-universal/README.md | 2 +- lib/rules/selector-max-universal/__tests__/index.js | 5 ++++- lib/rules/selector-max-universal/index.js | 11 ++--------- 3 files changed, 7 insertions(+), 11 deletions(-) diff --git a/lib/rules/selector-max-universal/README.md b/lib/rules/selector-max-universal/README.md index 5a387b7b44..752b6249ac 100644 --- a/lib/rules/selector-max-universal/README.md +++ b/lib/rules/selector-max-universal/README.md @@ -77,7 +77,7 @@ The following patterns are _not_ considered problems: ## Optional secondary options -### `ignoreAfterCombinators: ["array", "of", "combinators"]` +### `ignoreAfterCombinators: [" ", ">", "*", "~"]` Ignore universal selectors that come after one of the specified combinators. diff --git a/lib/rules/selector-max-universal/__tests__/index.js b/lib/rules/selector-max-universal/__tests__/index.js index 957f7ed463..fd35306c1f 100644 --- a/lib/rules/selector-max-universal/__tests__/index.js +++ b/lib/rules/selector-max-universal/__tests__/index.js @@ -296,12 +296,15 @@ testRule({ testRule({ ruleName, - config: [0, { ignoreAfterCombinators: ['~', '+', '>'] }], + config: [0, { ignoreAfterCombinators: ['~', '+', '>', ' '] }], accept: [ { code: '.foo {}', }, + { + code: '.foo * {}', + }, { code: '.foo ~ * {}', }, diff --git a/lib/rules/selector-max-universal/index.js b/lib/rules/selector-max-universal/index.js index 2a8bda7b83..f5b2a2bb5b 100644 --- a/lib/rules/selector-max-universal/index.js +++ b/lib/rules/selector-max-universal/index.js @@ -59,15 +59,8 @@ const rule = (primary, secondaryOptions) => { checkSelector(childNode, ruleNode); } - let prevChildNode = childNode.prev(); - let prevChildNodeValue = prevChildNode && prevChildNode.value; - - // checks if previous child node is descendant combinator - if (prevChildNode && prevChildNode.type === 'combinator' && prevChildNodeValue === ' ') { - prevChildNode = prevChildNode.prev(); - - prevChildNodeValue = prevChildNode && prevChildNode.value; - } + const prevChildNode = childNode.prev(); + const prevChildNodeValue = prevChildNode && prevChildNode.value; if (childNode.type === 'universal') { if (!optionsMatches(secondaryOptions, 'ignoreAfterCombinators', prevChildNodeValue)) { From 50c610dbb0b86d0368f5eb9f5e9cb83ab82a1f99 Mon Sep 17 00:00:00 2001 From: fpetrakov Date: Thu, 18 Aug 2022 12:23:10 +0300 Subject: [PATCH 10/10] revert doc change --- lib/rules/selector-max-universal/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/rules/selector-max-universal/README.md b/lib/rules/selector-max-universal/README.md index 752b6249ac..5a387b7b44 100644 --- a/lib/rules/selector-max-universal/README.md +++ b/lib/rules/selector-max-universal/README.md @@ -77,7 +77,7 @@ The following patterns are _not_ considered problems: ## Optional secondary options -### `ignoreAfterCombinators: [" ", ">", "*", "~"]` +### `ignoreAfterCombinators: ["array", "of", "combinators"]` Ignore universal selectors that come after one of the specified combinators.