Skip to content

Commit

Permalink
Add mixins-no-risky-parent-selectors rule.
Browse files Browse the repository at this point in the history
  • Loading branch information
pamelalozano16 committed Apr 2, 2024
1 parent 26c105c commit e30d734
Show file tree
Hide file tree
Showing 4 changed files with 343 additions and 0 deletions.
1 change: 1 addition & 0 deletions src/rules/index.js
Expand Up @@ -56,6 +56,7 @@ const rules = {
"function-unquote-no-unquoted-strings-inside": require("./function-unquote-no-unquoted-strings-inside"),
"map-keys-quotes": require("./map-keys-quotes"),
"media-feature-value-dollar-variable": require("./media-feature-value-dollar-variable"),
"mixins-no-risky-parent-selectors": require("./mixins-no-risky-parent-selectors"),
"no-dollar-variables": require("./no-dollar-variables"),
"no-duplicate-dollar-variables": require("./no-duplicate-dollar-variables"),
"no-duplicate-mixins": require("./no-duplicate-mixins"),
Expand Down
133 changes: 133 additions & 0 deletions src/rules/mixins-no-risky-parent-selectors/README.md
@@ -0,0 +1,133 @@
# mixins-no-risky-parent-selectors


Parent selectors are not reliable in the following contexts:

- Within a @mixin rule.
- Nested within another style rule.
- When they are not positioned at the beginning of a complex selector.
- Within a @at-root rule.

Using parent selectors in these scenarios may lead to unexpected and uncommon behavior.

```scss
@mixin foo {
.bar {
color: blue;
.baz & {
/*
* Disallow this */
color: red;
}
}
}
```

A more deterministic approach would be:
```scss
@mixin foo {
.bar {
color: blue;
}
.baz .bar {
color: red;
}
}
```

## Options

### `true`

The following patterns are considered warnings:

```scss
@mixin foo {
& .baz {
color: red;
}
}
```

```scss
.bar {
.bar {
color: blue;
& .baz {
color: red;
}
}
}
```

```scss
.bar {
color: blue;
.baz & {
color: red;
}
}
```

```scss
.bar {
color: blue;
.baz & {
color: red;
}
}
```

```scss
.foo {
color: blue;

& .bar, .baz & .qux {
color: red;
}
}
```

```scss
@mixin foo {
color: blue;
@at-root .b {
& {
color: red;
}
}
}
```

The following patterns are _not_ considered warnings:
```scss
.foo {
color: blue;

& .bar {
color: red;
}
}
```

```scss
.foo {
color: blue;

& .bar, & .baz .qux {
color: red;
}
}
```

```scss
.foo {
color: blue;

& .bar {
.baz {
color: red;
}
}
}
```
139 changes: 139 additions & 0 deletions src/rules/mixins-no-risky-parent-selectors/__tests__/index.js
@@ -0,0 +1,139 @@
"use strict";

const { ruleName } = require("..");

testRule({
ruleName,
config: [true],
customSyntax: "postcss-scss",

accept: [
{
code: `
.parent {
color: blue;
& .b {
color: red;
}
}
`,
description: "Nested parent selector"
},
{
code: `
.parent {
color: blue;
& .b, &.context {
color: red;
}
}
`,
description: "Nested parent selector in complex selector"
},
{
code: `
.bar {
& .parent {
color: blue;
.context {
color: red;
}
}
}
`,
description: "Parent selector nested in another style rule"
}
],

reject: [
{
code: `
@mixin foo {
&.context {
color: red;
}
}
`,
description: "Parent selector in mixin",
message:
"Unexpected parent selector in irregular context. (scss/mixins-no-risky-parent-selectors)"
},
{
code: `
.bar {
.parent {
color: blue;
& .context {
color: red;
}
}
}
`,
description: "Parent selector nested in more than one style rule",
message:
"Unexpected parent selector in irregular context. (scss/mixins-no-risky-parent-selectors)"
},
{
code: `
.parent {
color: blue;
.context & {
color: red;
}
}
`,
description: "Selector ending in parent selector",
message:
"Unexpected parent selector in irregular context. (scss/mixins-no-risky-parent-selectors)"
},
{
code: `
.parent {
color: blue;
.context & .b {
color: red;
}
}
`,
description: "Parent selector in the middle of complex selector",
message:
"Unexpected parent selector in irregular context. (scss/mixins-no-risky-parent-selectors)"
},
{
code: `
.parent {
color: blue;
& .b, .context & {
color: red;
}
}
`,
description: "Complex selector, one ending in parent selector",
message:
"Unexpected parent selector in irregular context. (scss/mixins-no-risky-parent-selectors)"
},
{
code: `
@mixin foo {
color: blue;
@at-root .b {
& {
color: red;
}
}
}
`,
description: "Selector within @at-root",
message:
"Unexpected parent selector in irregular context. (scss/mixins-no-risky-parent-selectors)"
}
]
});
70 changes: 70 additions & 0 deletions src/rules/mixins-no-risky-parent-selectors/index.js
@@ -0,0 +1,70 @@
"use strict";

const { utils } = require("stylelint");
const namespace = require("../../utils/namespace");
const ruleUrl = require("../../utils/ruleUrl");

const ruleName = namespace("mixins-no-risky-parent-selectors");

const messages = utils.ruleMessages(ruleName, {
rejected: `Unexpected parent selector in irregular context.`
});

const meta = {
url: ruleUrl(ruleName)
};

function isWithinMixinOrAtRoot(node) {
let parent = node.parent;
while (parent) {
if (
parent.type === "atrule" &&
(parent.name === "mixin" || parent.name === "at-root")
) {
return true;
}
parent = parent.parent;
}
return false;
}

function hasNestedParentSelector(selectors) {
return selectors
.split(",")
.some(
selector =>
selector.includes("&") && !selector.replace(" ", "").startsWith("&")
);
}

function rule(actual) {
return (root, result) => {
const validOptions = utils.validateOptions(result, ruleName, { actual });

if (!validOptions) {
return;
}

root.walkRules(node => {
if (
node.selector?.includes("&") &&
(isWithinMixinOrAtRoot(node) ||
hasNestedParentSelector(node.selector) ||
node.parent.parent.selector)
) {
utils.report({
message: messages.rejected,
node,
result,
ruleName
});
}
});
};
}

rule.ruleName = ruleName;
rule.messages = messages;
rule.meta = meta;

module.exports = rule;

0 comments on commit e30d734

Please sign in to comment.