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 ignoreRules to max-nesting-depth #7215

Merged
merged 3 commits into from
Oct 7, 2023
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
5 changes: 5 additions & 0 deletions .changeset/shy-ghosts-report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"stylelint": minor
---

Added: `ignoreRules` to `max-nesting-depth`
58 changes: 58 additions & 0 deletions lib/rules/max-nesting-depth/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -413,3 +413,61 @@ a {
}
}
```

### `ignoreRules: ["/regex/", /regex/, "string"]`

Ignore rules matching with the specified selectors.

For example, with `1` and given:

```json
[".my-selector", "/^.ignored-sel/"]
```

The following patterns are _not_ considered problems:

<!-- prettier-ignore -->
```css
a {
.my-selector { /* ignored */
b { /* 1 */
top: 0;
}
}
}
```

<!-- prettier-ignore -->
```css
a {
.my-selector, .ignored-selector { /* ignored */
b { /* 1 */
top: 0;
}
}
}
```

The following patterns are considered problems:

<!-- prettier-ignore -->
```css
a {
.not-ignored-selector { /* 1 */
b { /* 2 */
top: 0;
}
}
}
```

<!-- prettier-ignore -->
```css
a {
.my-selector, .not-ignored-selector { /* 1 */
b { /* 2 */
top: 0;
}
}
}
```
107 changes: 107 additions & 0 deletions lib/rules/max-nesting-depth/__tests__/index.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,113 @@ testRule({
],
});

testRule({
ruleName,
config: [1, { ignoreRules: [/^.some-sel/, '.my-selector'] }],

accept: [
{
code: 'a { b { top: 0; }}',
description: 'No ignored selector',
},
{
code: 'a { b { .my-selector { top: 0; }}}',
description: 'One ignored selector, ignored selector deepest',
},
{
code: 'a { b { .my-selector { .some-selector { top: 0; }}}}',
description: 'Many ignored selectors',
},
{
code: 'a { .some-selector { b { top: 0; }}}',
description: 'One ignored selector, ignored selector in the middle of tree',
},
{
code: 'a { b { .some-selector { .some-sel { .my-selector { top: 0; }}}}}',
description: 'Many ignored selectors, ignored selectors in the middle of tree',
},
{
code: 'a { .some-sel { .my-selector { top: 0; b { bottom: 0; }}}}',
description:
'Many ignored selectors, ignored selectors in the middle of tree, one block has property and block',
},
{
code: 'a { b { .my-selector, .some-sel { top: 0; }}}',
description: 'One selector has only ignored rules',
},
],

reject: [
{
code: 'a { b { .my-selector c { top: 0; }}}',
message: messages.expected(1),
description: 'One selector has an ignored rule alongside not ignored rule',
},
{
code: 'a { b { c { top: 0; }}}',
message: messages.expected(1),
description: 'No ignored selectors',
},
{
code: 'a { .my-selector { b { c { top: 0; }}}}',
message: messages.expected(1),
description: 'One ignored selector',
},
{
code: 'a { b { .some-sel { .my-selector { .some-selector { c { top: 0; }}}}}}',
message: messages.expected(1),
description: 'Many ignored selectors, but even with ignoring depth is too much',
},
{
code: 'a { b { .not-ignore-selector { color: #64FFDA; }}}',
message: messages.expected(1),
description: 'Not ignored selector',
},
{
code: 'a { b { .my-selector, c { top: 0; }}}',
message: messages.expected(1),
description:
'One selector has an ignored rule alongside not ignored rule, shorthand and same property',
},
{
code: stripIndent`
.foo {
.baz {
.my-selector {
opacity: 0.4;
}
.bar {
color: red;
}
}
}`,
message: messages.expected(1),
description:
'One selector has an ignored rule alongside not ignored rule, different properties',
line: 6,
column: 3,
},
],
});

testRule({
ruleName,
config: [
1,
{
ignoreRules: [/^.some-sel/, '.my-selector'],
ignorePseudoClasses: ['hover', '/^--custom-.*$/'],
},
],

accept: [
{
code: 'a { &:--custom-pseudo, .my-selector { b { top: 0; } } }',
description: 'ignored pseudo-class alongside ignored selector',
},
],
});

testRule({
ruleName,
config: [1],
Expand Down
32 changes: 29 additions & 3 deletions lib/rules/max-nesting-depth/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,13 @@ const rule = (primary, secondaryOptions) => {
const isIgnoreAtRule = (node) =>
isAtRule(node) && optionsMatches(secondaryOptions, 'ignoreAtRules', node.name);

/**
* @param {import('postcss').Node} node
*/
const isIgnoreRule = (node) => {
return isRule(node) && optionsMatches(secondaryOptions, 'ignoreRules', node.selector);
};

return (root, result) => {
const validOptions = validateOptions(
result,
Expand All @@ -42,6 +49,7 @@ const rule = (primary, secondaryOptions) => {
possible: {
ignore: ['blockless-at-rules', 'pseudo-classes'],
ignoreAtRules: [isString, isRegExp],
ignoreRules: [isString, isRegExp],
ignorePseudoClasses: [isString, isRegExp],
},
},
Expand All @@ -60,6 +68,10 @@ const rule = (primary, secondaryOptions) => {
return;
}

if (isIgnoreRule(statement)) {
return;
}

if (!hasBlock(statement)) {
return;
}
Expand Down Expand Up @@ -120,10 +132,24 @@ const rule = (primary, secondaryOptions) => {
* @param {string[]} selectors
* @returns {boolean}
*/
function containsIgnoredPseudoClassesOnly(selectors) {
if (!(secondaryOptions && secondaryOptions.ignorePseudoClasses)) return false;
function containsIgnoredPseudoClassesOrRulesOnly(selectors) {
Mouvedia marked this conversation as resolved.
Show resolved Hide resolved
if (
!(
secondaryOptions &&
(secondaryOptions.ignorePseudoClasses || secondaryOptions.ignoreRules)
)
)
return false;

return selectors.every((selector) => {
if (
secondaryOptions.ignoreRules &&
optionsMatches(secondaryOptions, 'ignoreRules', selector)
)
return true;

if (!secondaryOptions.ignorePseudoClasses) return false;

const pseudoRule = extractPseudoRule(selector);

if (!pseudoRule) return false;
Expand All @@ -139,7 +165,7 @@ const rule = (primary, secondaryOptions) => {
(optionsMatches(secondaryOptions, 'ignore', 'pseudo-classes') &&
isRule(node) &&
containsPseudoClassesOnly(node.selector)) ||
(isRule(node) && containsIgnoredPseudoClassesOnly(node.selectors))
(isRule(node) && containsIgnoredPseudoClassesOrRulesOnly(node.selectors))
) {
return nestingDepth(parent, level);
}
Expand Down