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 ignorePseudoClasses: [] to max-nesting-depth #5620

Merged
merged 13 commits into from Oct 27, 2021
96 changes: 77 additions & 19 deletions lib/rules/max-nesting-depth/README.md
Expand Up @@ -170,8 +170,8 @@ The following patterns are considered problems:

<!-- prettier-ignore -->
```css
.a {
.b { /* 1 */
a {
b { /* 1 */
.c { /* 2 */
top: 0;
}
Expand All @@ -181,9 +181,9 @@ The following patterns are considered problems:

<!-- prettier-ignore -->
```css
.a {
a {
&:hover { /* ignored */
.b { /* 1 */
b { /* 1 */
.c { /* 2 */
top: 0;
}
Expand All @@ -194,8 +194,8 @@ The following patterns are considered problems:

<!-- prettier-ignore -->
```css
.a {
.b { /* 1 */
a {
b { /* 1 */
&::selection { /* 2 */
color: #64FFDA;
}
Expand All @@ -205,8 +205,8 @@ The following patterns are considered problems:

<!-- prettier-ignore -->
```css
.a {
.b { /* 1 */
a {
b { /* 1 */
&:hover, .c { /* 2 */
top: 0;
}
Expand All @@ -216,12 +216,12 @@ The following patterns are considered problems:

The following patterns are _not_ considered problems:

As all of the following pseudoclasses rules would have a nesting depth of just 1.
As all of the following pseudo-classes rules would have a nesting depth of just 1.

<!-- prettier-ignore -->
```css
.a {
.b { /* 1 */
a {
b { /* 1 */
&:hover { /* ignored */
top: 0;
}
Expand All @@ -231,8 +231,8 @@ As all of the following pseudoclasses rules would have a nesting depth of just 1

<!-- prettier-ignore -->
```css
.a {
.b { /* 1 */
a {
b { /* 1 */
&:nest {
&:nest-lvl2 { /* ignored */
top: 0;
Expand All @@ -244,9 +244,9 @@ As all of the following pseudoclasses rules would have a nesting depth of just 1

<!-- prettier-ignore -->
```css
.a {
a {
&:hover { /* ignored */
.b { /* 1 */
b { /* 1 */
top: 0;
}
}
Expand All @@ -255,11 +255,11 @@ As all of the following pseudoclasses rules would have a nesting depth of just 1

<!-- prettier-ignore -->
```css
.a {
a {
&:nest { /* ignored */
&:nest-lvl2 { /* ignored */
top: 0;
.b { /* 1 */
b { /* 1 */
bottom: 0;
}
}
Expand All @@ -269,8 +269,8 @@ As all of the following pseudoclasses rules would have a nesting depth of just 1

<!-- prettier-ignore -->
```css
.a {
.b { /* 1 */
a {
b { /* 1 */
&:hover, &:focus { /* ignored */
top: 0;
}
Expand Down Expand Up @@ -353,3 +353,61 @@ a {
}
}
```

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

Ignore the specified pseudo-classes.

For example, with `1` and given:

```json
["hover", "^focus-"]
```

The following patterns are _not_ considered problems:

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

<!-- prettier-ignore -->
```css
a {
&:hover, &:active { /* ignored */
b { /* 1 */
top: 0;
}
}
}
```

The following patterns are considered problems:

<!-- prettier-ignore -->
```css
a {
&:visited { /* 1 */
b { /* 2 */
top: 0;
}
}
}
```

<!-- prettier-ignore -->
```css
a {
&:hover, &:visited { /* 1 */
b { /* 2 */
top: 0;
}
}
}
```
58 changes: 45 additions & 13 deletions lib/rules/max-nesting-depth/__tests__/index.js
Expand Up @@ -117,51 +117,83 @@ testRule({

accept: [
{
code: '.a { .b { top: 0; }}',
code: 'a { b { top: 0; }}',
},
{
code: '.a { .b { &:hover { top: 0; }}}',
code: 'a { b { &:hover { top: 0; }}}',
},
{
code: '.a { .b { &:nest { &:nest-lvl2 { top: 0; }}}}',
code: 'a { b { &:nest { &:nest-lvl2 { top: 0; }}}}',
},
{
code: '.a { &:hover { .b { top: 0; }}}',
code: 'a { &:hover { b { top: 0; }}}',
},
{
code: '.a { .b { &:hover { &:focus { &:otherone { top: 0; }}}}}',
code: 'a { b { &:hover { &:focus { &:otherone { top: 0; }}}}}',
},
{
code: '.a { &:nest { &:nest-lvl2 { top: 0; .b { bottom: 0; }}}}',
code: 'a { &:nest { &:nest-lvl2 { top: 0; b { bottom: 0; }}}}',
},
{
code: '.a { .b { &:hover .c { top: 0; }}}',
code: 'a { b { &:hover c { top: 0; }}}',
},
{
code: '.a { .b { &:hover, &:focus { top: 0; }}}',
code: 'a { b { &:hover, &:focus { top: 0; }}}',
},
],

reject: [
{
code: '.a { .b { .c { top: 0; }}}',
code: 'a { b { c { top: 0; }}}',
message: messages.expected(1),
},
{
code: '.a { &:hover { .b { .c { top: 0; }}}}',
code: 'a { &:hover { b { c { top: 0; }}}}',
message: messages.expected(1),
},
{
code: '.a { .b { &:hover { &:focus { &:otherone { .c { top: 0; }}}}}}',
code: 'a { b { &:hover { &:focus { &:otherone { c { top: 0; }}}}}}',
message: messages.expected(1),
},
{
code: 'a { b { &::selection { color: #64FFDA; }}}',
message: messages.expected(1),
},
{
code: 'a { b { &:hover, c { top: 0; }}}',
message: messages.expected(1),
},
],
});

testRule({
ruleName,
config: [1, { ignorePseudoClasses: ['hover', '/^custom-.*$/'] }],
lachieh marked this conversation as resolved.
Show resolved Hide resolved

accept: [
{
code: 'a { &:hover { b { top: 0; } } }',
},
{
code: 'a { &:hover, &:custom-pseudo { b { top: 0; } } }',
lachieh marked this conversation as resolved.
Show resolved Hide resolved
},
],

reject: [
{
code: 'a { &:visited { b { top: 0; } } }',
message: messages.expected(1),
description: 'pseudo-class not ignored',
},
{
code: '.a { .b { &::selection { color: #64FFDA; }}}',
code: 'a { &:custom-pseudo, &:visited { b { top: 0; } } }',
lachieh marked this conversation as resolved.
Show resolved Hide resolved
message: messages.expected(1),
description: 'ignored pseudo-class alongside pseudo-class',
},
{
code: '.a { .b { &:hover, .c { top: 0; }}}',
code: 'a { &:hover, b { c { top: 0; } } }',
message: messages.expected(1),
description: 'pseudo-class alongside class',
},
],
});
Expand Down
34 changes: 31 additions & 3 deletions lib/rules/max-nesting-depth/index.js
Expand Up @@ -25,7 +25,7 @@ const rule = (primary, secondaryOptions) => {
isAtRule(node) && optionsMatches(secondaryOptions, 'ignoreAtRules', node.name);

return (root, result) => {
validateOptions(
const validOptions = validateOptions(
result,
ruleName,
{
Expand All @@ -38,10 +38,13 @@ const rule = (primary, secondaryOptions) => {
possible: {
ignore: ['blockless-at-rules', 'pseudo-classes'],
ignoreAtRules: [isString, isRegExp],
ignorePseudoClasses: [isString, isRegExp],
},
},
);

if (!validOptions) return;

root.walkRules(checkStatement);
root.walkAtRules(checkStatement);

Expand Down Expand Up @@ -105,7 +108,23 @@ const rule = (primary, secondaryOptions) => {
const normalized = parser().processSync(selector, { lossless: false });
const selectors = normalized.split(',');

return selectors.every((sel) => sel.startsWith('&:') && sel[2] !== ':');
return selectors.every((sel) => extractPseudoRule(sel));
}

/**
* @param {string[]} selectors
* @returns {boolean}
*/
function containsIgnoredPseudoClassesOnly(selectors) {
if (!(secondaryOptions && secondaryOptions.ignorePseudoClasses)) return false;

return selectors.every((selector) => {
const pseudoRule = extractPseudoRule(selector);

if (!pseudoRule) return false;

return optionsMatches(secondaryOptions, 'ignorePseudoClasses', pseudoRule);
});
}

if (
Expand All @@ -114,7 +133,8 @@ const rule = (primary, secondaryOptions) => {
node.every((child) => !isDeclaration(child))) ||
(optionsMatches(secondaryOptions, 'ignore', 'pseudo-classes') &&
isRule(node) &&
containsPseudoClassesOnly(node.selector))
containsPseudoClassesOnly(node.selector)) ||
(isRule(node) && containsIgnoredPseudoClassesOnly(node.selectors))
) {
return nestingDepth(parent, level);
}
Expand All @@ -127,6 +147,14 @@ const rule = (primary, secondaryOptions) => {
}
};

/**
* @param {string} selector
* @returns {string | undefined}
*/
function extractPseudoRule(selector) {
return selector.startsWith('&:') && selector[2] !== ':' ? selector.substr(2) : undefined;
}

rule.ruleName = ruleName;
rule.messages = messages;
module.exports = rule;