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

[FIxed:314] add rule selector-no-union-class-name #338

Merged
merged 5 commits into from Jun 29, 2019
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
1 change: 1 addition & 0 deletions README.md
Expand Up @@ -144,6 +144,7 @@ Please also see the [example configs](./docs/examples/) for special cases.

- [`selector-nest-combinators`](./src/rules/selector-nest-combinators/README.md): Require or disallow nesting of combinators in selectors.
- [`selector-no-redundant-nesting-selector`](./src/rules/selector-no-redundant-nesting-selector/README.md): Disallow redundant nesting selectors (`&`).
- [`selector-no-union-class-name`](./src/rules/selector-no-union-class-name/README.md): Disallow union class names with the parent selector (`&`).

### General / Sheet

Expand Down
4 changes: 3 additions & 1 deletion src/rules/index.js
Expand Up @@ -41,6 +41,7 @@ import partialNoImport from "./partial-no-import";
import percentPlaceholderPattern from "./percent-placeholder-pattern";
import selectorNestCombinators from "./selector-nest-combinators";
import selectorNoRedundantNestingSelector from "./selector-no-redundant-nesting-selector";
import selectorNoUnionClassName from "./selector-no-union-class-name";

export default {
"at-extend-no-missing-placeholder": atExtendNoMissingPlaceholder,
Expand Down Expand Up @@ -85,5 +86,6 @@ export default {
"percent-placeholder-pattern": percentPlaceholderPattern,
"partial-no-import": partialNoImport,
"selector-nest-combinators": selectorNestCombinators,
"selector-no-redundant-nesting-selector": selectorNoRedundantNestingSelector
"selector-no-redundant-nesting-selector": selectorNoRedundantNestingSelector,
"selector-no-union-class-name": selectorNoUnionClassName
};
46 changes: 46 additions & 0 deletions src/rules/selector-no-union-class-name/README.md
@@ -0,0 +1,46 @@
# selector-no-union-class-name

Disallow union class names with the parent selector (`&`).

```scss
.class {
&-union {
//↑
// This type usage of `&`
}
}
```

The following patterns are considered warnings:

```scss
.class {
&-union {}
}
```

```scss
.class {
&_union {}
}
```

```scss
.class {
&union {}
}
```

The following patterns are *not* considered warnings:

```scss
.class {
&.foo {}
}
```

```scss
.class {
& p {}
}
```
68 changes: 68 additions & 0 deletions src/rules/selector-no-union-class-name/__tests__/index.js
@@ -0,0 +1,68 @@
import rule, { ruleName, messages } from "..";

testRule(rule, {
ruleName,
config: [undefined],
syntax: "scss",

accept: [
{
code: `
.class {
&.foo {}
}
`,
description: "when an ampersand is chained with another class name"
},
{
code: `
.class span {
&-union {}
}
`,
description: "when an ampersand parent is not class name"
},
{
code: `
.class {
& span {}
}
`,
description: "when an ampersand is chained with conbinator"
}
],

reject: [
{
code: `
.class {
&-union {}
}
`,
line: 3,
message: messages.rejected,
description: "when an ampersand is chained with union class name (hyphen)"
},
{
code: `
.class {
&_union {}
}
`,
line: 3,
message: messages.rejected,
description:
"when an ampersand is chained with union class name (underscore)"
},
{
code: `
.class {
&union {}
}
`,
line: 3,
message: messages.rejected,
description: "when an ampersand is chained with union class name (direct)"
}
]
});
51 changes: 51 additions & 0 deletions src/rules/selector-no-union-class-name/index.js
@@ -0,0 +1,51 @@
import { utils } from "stylelint";
import { namespace, parseSelector } from "../../utils";
import { isClassName, isCombinator } from "postcss-selector-parser";

export const ruleName = namespace("selector-no-union-class-name");

export const messages = utils.ruleMessages(ruleName, {
rejected: "Unexpected union class name with the parent selector (&)"
});

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

if (!validOptions) {
return;
}

root.walkRules(/&/, rule => {
const parentNodes = [];

parseSelector(rule.parent.selector, result, rule, fullSelector => {
fullSelector.walk(node => parentNodes.push(node));
});

const lastParentNode = parentNodes[parentNodes.length - 1];

if (!isClassName(lastParentNode)) return;

parseSelector(rule.selector, result, rule, fullSelector => {
fullSelector.walkNesting(node => {
const next = node.next();

if (!next) return;

if (isCombinator(next)) return;

if (isClassName(next)) return;

utils.report({
ruleName,
result,
node: rule,
message: messages.rejected,
index: node.sourceIndex
});
});
});
});
};
}