Skip to content

Commit

Permalink
Add selector-pseudo-element-*list rules (#3087)
Browse files Browse the repository at this point in the history
  • Loading branch information
jeddy3 committed Jan 6, 2018
1 parent 1a6f484 commit 4c90af5
Show file tree
Hide file tree
Showing 12 changed files with 541 additions and 10 deletions.
2 changes: 2 additions & 0 deletions docs/user-guide/example-config.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,9 +150,11 @@ You might want to learn a little about [how rules are named and how they work to
"selector-pseudo-class-no-unknown": true,
"selector-pseudo-class-parentheses-space-inside": "always"|"never",
"selector-pseudo-class-whitelist": string|[],
"selector-pseudo-element-blacklist": string|[],
"selector-pseudo-element-case": "lower"|"upper",
"selector-pseudo-element-colon-notation": "single"|"double",
"selector-pseudo-element-no-unknown": true,
"selector-pseudo-element-whitelist": string|[],
"selector-type-case": "lower"|"upper",
"selector-type-no-unknown": true,
"shorthand-property-no-redundant-values": true,
Expand Down
2 changes: 2 additions & 0 deletions docs/user-guide/rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,8 @@ Here are all the rules within stylelint, grouped first [by category](../../VISIO
- [`selector-no-vendor-prefix`](../../lib/rules/selector-no-vendor-prefix/README.md): Disallow vendor prefixes for selectors.
- [`selector-pseudo-class-blacklist`](../../lib/rules/selector-pseudo-class-blacklist/README.md): Specify a blacklist of disallowed pseudo-class selectors.
- [`selector-pseudo-class-whitelist`](../../lib/rules/selector-pseudo-class-whitelist/README.md): Specify a whitelist of allowed pseudo-class selectors.
- [`selector-pseudo-element-blacklist`](../../lib/rules/selector-pseudo-element-blacklist/README.md): Specify a blacklist of disallowed pseudo-element selectors.
- [`selector-pseudo-element-whitelist`](../../lib/rules/selector-pseudo-element-whitelist/README.md): Specify a whitelist of allowed pseudo-element selectors.

#### Media feature

Expand Down
8 changes: 6 additions & 2 deletions jest-setup.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ global.testRule = (rule, schema) => {
describe("accept", () => {
passingTestCases.forEach(testCase => {
const spec = testCase.only ? it.only : it;
describe(JSON.stringify(schema.config), () => {
describe(JSON.stringify(schema.config, replacer), () => {
describe(JSON.stringify(testCase.code), () => {
spec(testCase.description || "no description", () => {
const options = {
Expand Down Expand Up @@ -72,7 +72,7 @@ global.testRule = (rule, schema) => {
describe("reject", () => {
schema.reject.forEach(testCase => {
const spec = testCase.only ? it.only : it;
describe(JSON.stringify(schema.config), () => {
describe(JSON.stringify(schema.config, replacer), () => {
describe(JSON.stringify(testCase.code), () => {
spec(testCase.description || "no description", () => {
const options = {
Expand Down Expand Up @@ -131,3 +131,7 @@ function getOutputCss(output) {
}
return css;
}

function replacer(key, value) {
return value instanceof RegExp ? `[RegExp] ${value.toString()}` : value;
}
4 changes: 4 additions & 0 deletions lib/rules/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -143,9 +143,11 @@ const selectorPseudoClassCase = require("./selector-pseudo-class-case");
const selectorPseudoClassNoUnknown = require("./selector-pseudo-class-no-unknown");
const selectorPseudoClassParenthesesSpaceInside = require("./selector-pseudo-class-parentheses-space-inside");
const selectorPseudoClassWhitelist = require("./selector-pseudo-class-whitelist");
const selectorPseudoElementBlacklist = require("./selector-pseudo-element-blacklist");
const selectorPseudoElementCase = require("./selector-pseudo-element-case");
const selectorPseudoElementColonNotation = require("./selector-pseudo-element-colon-notation");
const selectorPseudoElementNoUnknown = require("./selector-pseudo-element-no-unknown");
const selectorPseudoElementWhitelist = require("./selector-pseudo-element-whitelist");
const selectorTypeCase = require("./selector-type-case");
const selectorTypeNoUnknown = require("./selector-type-no-unknown");
const shorthandPropertyNoRedundantValues = require("./shorthand-property-no-redundant-values");
Expand Down Expand Up @@ -307,9 +309,11 @@ module.exports = {
"selector-pseudo-class-no-unknown": selectorPseudoClassNoUnknown,
"selector-pseudo-class-parentheses-space-inside": selectorPseudoClassParenthesesSpaceInside,
"selector-pseudo-class-whitelist": selectorPseudoClassWhitelist,
"selector-pseudo-element-blacklist": selectorPseudoElementBlacklist,
"selector-pseudo-element-case": selectorPseudoElementCase,
"selector-pseudo-element-colon-notation": selectorPseudoElementColonNotation,
"selector-pseudo-element-no-unknown": selectorPseudoElementNoUnknown,
"selector-pseudo-element-whitelist": selectorPseudoElementWhitelist,
"selector-type-case": selectorTypeCase,
"selector-type-no-unknown": selectorTypeNoUnknown,
"shorthand-property-no-redundant-values": shorthandPropertyNoRedundantValues,
Expand Down
48 changes: 48 additions & 0 deletions lib/rules/selector-pseudo-element-blacklist/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# selector-pseudo-element-blacklist

Specify a blacklist of disallowed pseudo-element selectors.

```css
a::before {}
/** ↑
* These pseudo-element selectors */
```

This rule ignores CSS2 pseudo-elements i.e. those prefixed with a single colon.

This rule ignores selectors that use variable interpolation e.g. `::#{$variable} {}`.

## Options

`array|string|regex`: `["array", "of", "unprefixed", "pseudo-elements" or "regex"]|"pseudo-element"|/regex/`

Given:

```js
["before", "/^my-/i"]
```

The following patterns are considered violations:

```css
a::before {}
```

```css
a::my-pseudo-element {}
```

```css
a::MY-OTHER-pseudo-element {}
```


The following patterns are *not* considered violations:

```css
a::after {}
```

```css
a::not-my-pseudo-element {}
```
107 changes: 107 additions & 0 deletions lib/rules/selector-pseudo-element-blacklist/__tests__/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
"use strict";

const messages = require("..").messages;
const ruleName = require("..").ruleName;
const rules = require("../../../rules");

const rule = rules[ruleName];

testRule(rule, {
ruleName,
config: ["before", "selection", /^my/i],
skipBasicChecks: true,

accept: [
{
code: "a {}"
},
{
code: "a:hover {}"
},
{
code: "a::BEFORE {}"
},
{
code: "a::after {}"
},
{
code: "::first-line {}"
},
{
code: "::-webkit-first-line {}"
},
{
code: "a:not(::first-line) {}"
},
{
code: "a::their-pseudo-element {}"
},
{
code: "a::THEIR-other-pseudo-element {}"
}
],

reject: [
{
code: "a::before {}",
message: messages.rejected("before"),
line: 1,
column: 2
},
{
code: "a,\nb::before {}",
message: messages.rejected("before"),
line: 2,
column: 2
},
{
code: "::selection {}",
message: messages.rejected("selection"),
line: 1,
column: 1
},
{
code: "::-webkit-selection {}",
message: messages.rejected("-webkit-selection"),
line: 1,
column: 1
},
{
code: "a:not(::selection) {}",
message: messages.rejected("selection"),
line: 1,
column: 7
},
{
code: "a::my-pseudo-element {}",
message: messages.rejected("my-pseudo-element"),
line: 1,
column: 2
},
{
code: "a::MY-OTHER-pseudo-element {}",
message: messages.rejected("MY-OTHER-pseudo-element"),
line: 1,
column: 2
}
]
});

testRule(rule, {
ruleName,
config: ["before"],
skipBasicChecks: true,
syntax: "scss",

accept: [
{
code: "::#{$variable} {}"
},
{
code: "::#{$VARIABLE} {}"
},
{
code: "a::#{$variable} {}"
}
]
});
78 changes: 78 additions & 0 deletions lib/rules/selector-pseudo-element-blacklist/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
"use strict";

const _ = require("lodash");
const isStandardSyntaxRule = require("../../utils/isStandardSyntaxRule");
const isStandardSyntaxSelector = require("../../utils/isStandardSyntaxSelector");
const matchesStringOrRegExp = require("../../utils/matchesStringOrRegExp");
const parseSelector = require("../../utils/parseSelector");
const postcss = require("postcss");
const report = require("../../utils/report");
const ruleMessages = require("../../utils/ruleMessages");
const validateOptions = require("../../utils/validateOptions");

const ruleName = "selector-pseudo-element-blacklist";

const messages = ruleMessages(ruleName, {
rejected: selector => `Unexpected pseudo-element "${selector}"`
});

const rule = function(blacklist) {
return (root, result) => {
const validOptions = validateOptions(result, ruleName, {
actual: blacklist,
possible: [_.isString, _.isRegExp]
});
if (!validOptions) {
return;
}

root.walkRules(rule => {
if (!isStandardSyntaxRule(rule)) {
return;
}

const selector = rule.selector;

if (!isStandardSyntaxSelector(selector)) {
return;
}

if (selector.indexOf("::") === -1) {
return;
}

parseSelector(selector, result, rule, selectorTree => {
selectorTree.walkPseudos(pseudoNode => {
const value = pseudoNode.value;

// Ignore pseudo-classes
if (value[1] !== ":") {
return;
}

const name = value.slice(2);

if (
!matchesStringOrRegExp(postcss.vendor.unprefixed(name), blacklist)
) {
return;
}

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

rule.primaryOptionArray = true;

rule.ruleName = ruleName;
rule.messages = messages;
module.exports = rule;
47 changes: 47 additions & 0 deletions lib/rules/selector-pseudo-element-whitelist/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# selector-pseudo-element-whitelist

Specify a whitelist of allowed pseudo-element selectors.

```css
a::before {}
/** ↑
* These pseudo-element selectors */
```

This rule ignores CSS2 pseudo-elements i.e. those prefixed with a single colon.

This rule ignores selectors that use variable interpolation e.g. `::#{$variable} {}`.

## Options

`array|string|regex`: `["array", "of", "unprefixed", "pseudo-elements" or "regex"]|"pseudo-element"|/regex/`

Given:

```js
["before", "/^my-/i"]
```

The following patterns are considered violations:

```css
a::after {}
```

```css
a::not-my-pseudo-element {}
```

The following patterns are *not* considered violations:

```css
a::before {}
```

```css
a::my-pseudo-element {}
```

```css
a::MY-OTHER-pseudo-element {}
```
Loading

0 comments on commit 4c90af5

Please sign in to comment.