Skip to content

Commit

Permalink
Add at-root-no-redundant rule
Browse files Browse the repository at this point in the history
  • Loading branch information
pamelalozano16 committed Aug 23, 2023
1 parent 9aa4449 commit 05cf7f7
Show file tree
Hide file tree
Showing 5 changed files with 274 additions and 0 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
@@ -1,5 +1,11 @@
# 5.1.0

- Added: `at-root-no-redundant` rule to ban unnecessary `@at-root` rule (#601).

**Full Changelog**: https://github.com/stylelint-scss/stylelint-scss/compare/v5.1.0...v5.2.0

# 5.1.0

- Added: `function-disallowed-list` rule support to ban specific built-in functions (#422, #844).

**Full Changelog**: https://github.com/stylelint-scss/stylelint-scss/compare/v5.0.1...v5.1.0
Expand Down
67 changes: 67 additions & 0 deletions src/rules/at-root-no-redundant/README.md
@@ -0,0 +1,67 @@
# at-root-no-redundant

Ban unnecessary `@at-root` rule.

The `@at-root` rule is redundant in the following cases:

- If `@at-root` is already at the root of the document.
- If any `@at-root` selector contains the parent selector, [`&`](https://sass-lang.com/documentation/style-rules/parent-selector/), outside interpolation.
- If `@at-root` is nested within a `@keyframes` block.

## Options

### `true`

The following patterns are considered warnings:

<!-- prettier-ignore -->
```scss
@at-root .a { margin: 3px; }
```

<!-- prettier-ignore -->
```scss
.a { @at-root .b & : { margin: 3px; } }
```

<!-- prettier-ignore -->
```scss
@keyframes slidein {
@at-root from {
transform: translateX(0%);
}

to {
transform: translateX(100%);
}
}
```

The following patterns are _not_ considered warnings:

<!-- prettier-ignore -->
```scss
.a { @at-root .b { margin: 3px; } }
```

<!-- prettier-ignore -->
```scss
.a { @at-root .b#{&}: { margin: 3px; } }
```

<!-- prettier-ignore -->
```scss
.a {
@at-root .b {
@keyframes slidein {
from {
transform: translateX(0%);
}

to {
transform: translateX(100%);
}
}
}
}
```
141 changes: 141 additions & 0 deletions src/rules/at-root-no-redundant/__tests__/index.js
@@ -0,0 +1,141 @@
"use strict";

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

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

accept: [
{
code: `
.a { @at-root .b { c: d } }
`,
description: "@at-root rule is nested."
},
{
code: `
.a { @at-root .b#{&} { c: d; } }
`,
description:
"@at-root is followed by a selector with an interpolated `&`."
},
{
code: `
.a { @at-root .b { .c & { d: e; } } }
`,
line: 2,
column: 12,
message: messages.rejected,
description: "A parent selector (&) is used under the @at-root rule."
},
{
code: `
.a {
@at-root .b {
@keyframes slidein {
from {
transform: translateX(0%);
}
to {
transform: translateX(100%);
}
}
}
}`,
description: "@at-root outside a keyframes block, nested."
},
{
code: `
@mixin reset-list {
margin: 0;
padding: 0;
list-style: none;
}
@keyframes slidein {
from {
@include reset-list;
transform: translateX(0%);
}
to {
transform: translateX(100%);
}
}`,
description: "Other at-rules nested inside @keyframes block."
}
],
reject: [
{
code: `
@at-root .a { b: c; }
`,
line: 2,
column: 7,
message: messages.rejected,
description: "@at-root rule is already in the root."
},
{
code: `
.a { @at-root .b & { c: d; } }
`,
line: 2,
column: 12,
message: messages.rejected,
description:
"@at-root is followed by a selector with a `&` outside an interpolation."
},
{
code: `
.a { @at-root .b & #{.c} { d: e; } }
`,
line: 2,
column: 12,
message: messages.rejected,
description:
"@at-root is followed by a selector with a `&` outside an interpolation."
},
{
code: `
@keyframes slidein {
@at-root from {
transform: translateX(0%);
}
to {
transform: translateX(100%);
}
}`,
line: 3,
column: 9,
message: messages.rejected,
description: "@at-root inside a keyframes block, top level."
},
{
code: `
@keyframes slidein {
from {
transform: translateX(0%);
@at-root from {
transform: translateX(100%);
}
to {
transform: translateX(0%);
}
}
to {
transform: translateX(100%);
}
}
`,
line: 5,
column: 14,
message: messages.rejected,
description: "@at-root inside a @keyframes block, nested."
}
]
});
59 changes: 59 additions & 0 deletions src/rules/at-root-no-redundant/index.js
@@ -0,0 +1,59 @@
"use strict";

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

const ruleName = namespace("at-root-no-redundant");

function isWithinKeyframes(node) {
let parent = node.parent;
while (parent.type !== "root") {
if (parent.type === "atrule" && parent.name === "keyframes") {
return true;
}
parent = parent.parent;
}
return false;
}

const messages = utils.ruleMessages(ruleName, {
rejected: "Unnecessary @at-root rule"
});

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

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

if (!validOptions) {
return;
}

root.walkAtRules("at-root", node => {
if (
node.parent.type === "root" ||
node.params.replace(/#{.*}/g, "").includes("&") ||
isWithinKeyframes(node)
) {
utils.report({
message: messages.rejected,
node,
result,
ruleName
});
}
});
};
}

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

module.exports = rule;
1 change: 1 addition & 0 deletions src/rules/index.js
Expand Up @@ -22,6 +22,7 @@ const rules = {
"at-mixin-parentheses-space-before": require("./at-mixin-parentheses-space-before"),
"at-mixin-pattern": require("./at-mixin-pattern"),
"at-rule-conditional-no-parentheses": require("./at-rule-conditional-no-parentheses"),
"at-root-no-redundant": require("./at-root-no-redundant"),
"at-rule-no-unknown": require("./at-rule-no-unknown"),
"at-use-no-unnamespaced": require("./at-use-no-unnamespaced"),
"comment-no-empty": require("./comment-no-empty"),
Expand Down

0 comments on commit 05cf7f7

Please sign in to comment.