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 at-mixin-no-risky-nesting-selector rule. #985

Merged
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
144 changes: 144 additions & 0 deletions src/rules/at-mixin-no-risky-nesting-selector/README.md
@@ -0,0 +1,144 @@
# at-mixin-no-risky-nesting-selector

Disallow risky nesting selectors within a mixin.

If a mixin contains a parent selector within another style rule, and is used in a nested context,
the output selector may include the outermost parent selector in an unexpected way.

In this example:
```scss
@mixin foo {
.a {
color: blue;
.b & {
color: red;
}
}
}

.c {
@include foo;
}
```

The user may expect `.c` to go outside all selectors in `foo`:
`.c .b .a {...}`

But this outputs:
```scss
.c .a {
color: blue;
}
.b .c .a {
color: red;
}
```

However, if we pull the parent selector into the child and make the child style rule a sibling:
```scss
@mixin foo {
.a {
color: blue;
}
.b .a {
color: red;
}
}

.c {
@include foo;
}
```

It outputs:
```scss
.c .a {
color: blue;
}
.c .b .a {
color: red;
}
```

This only occurs when a parent selector meets all of the following conditions:
- Is within a `@mixin` rule.
- Is nested within another style rule.
- Is not positioned at the beginning of a complex selector.

## Options

### `true`

The following patterns are considered warnings:

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

```scss
@mixin foo {
.bar {
color: blue;
.qux, .baz & .quux{
color: red;
}
}
}
```

The following patterns are _not_ considered warnings:

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

```scss
@mixin foo {
.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 {
.baz & {
color: red;
}
}
```
206 changes: 206 additions & 0 deletions src/rules/at-mixin-no-risky-nesting-selector/__tests__/index.js
@@ -0,0 +1,206 @@
"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"
},
{
code: `
@mixin foo {
&.context {
color: red;
}
}
`,
description: "Parent selector in mixin"
},
{
code: `
.bar {
.parent {
color: blue;
& .context {
color: red;
}
}
}
`,
description: "Parent selector nested in more than one style rule"
},
{
code: `
.parent {
color: blue;
.context & {
color: red;
}
}
`,
description: "Selector ending in parent selector"
},
{
code: `
.parent {
color: blue;
.context & .b {
color: red;
}
}
`,
description: "Parent selector in the middle of complex selector"
},
{
code: `
.parent {
color: blue;
& .b, .context & {
color: red;
}
}
`,
description: "Complex selector, one ending in parent selector"
},
{
code: `
@mixin foo {
.parent {
color: blue;
& .context {
color: red;
}
}
}
`,
description: "Parent selector in mixin, nested, at the beginning"
},
{
code: `
@mixin foo {
.bar & {
color: red;
}
}
`,
description: "Parent selector in mixin, at the end"
},
{
code: `
@mixin foo {
.parent {
.bar, & {
color: red;
}
}
}
`,
description: "Complex selector in mixin, nested, no following class."
}
],

reject: [
{
code: `
@mixin foo {
.bar {
color: blue;
.baz & {
color: red;
}
}
}
`,
description: "Parent selector nested in selector within a mixin",
message:
"Unexpected nested parent selector in @mixin rule. (scss/at-mixin-no-risky-nesting-selector)",
column: 11,
endColumn: 12,
endLine: 7,
line: 5
},
{
code: `
@mixin foo {
.bar {
color: blue;
& .qux, .baz & .quux{
color: red;
}
}
}
`,
description: "Parent selector nested in complex selector within mixin",
message:
"Unexpected nested parent selector in @mixin rule. (scss/at-mixin-no-risky-nesting-selector)",
column: 11,
endColumn: 12,
endLine: 7,
line: 5
},
{
code: `
@mixin foo {
.bar {
color: blue;
& .qux & {
color: red;
}
}
}
`,
description: "Parent selector nested in complex selector within mixin",
message:
"Unexpected nested parent selector in @mixin rule. (scss/at-mixin-no-risky-nesting-selector)",
column: 11,
endColumn: 12,
endLine: 7,
line: 5
}
]
});