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 ignore: ["inside-block"] and splitList to selector-disallowed-list #6334

5 changes: 5 additions & 0 deletions .changeset/unlucky-dolphins-fetch.md
@@ -0,0 +1,5 @@
---
"stylelint": minor
---

Added: `ignore: ["inside-block"]` and `splitList` to `selector-disallowed-list`
47 changes: 47 additions & 0 deletions lib/rules/selector-disallowed-list/README.md
Expand Up @@ -66,3 +66,50 @@ a
```css
a[href] {}
```

## Optional secondary options

### `splitList: true | false` (default: `false`)

Split selector lists into individual selectors.

For example, with `true`.

Given:

```json
[".foo"]
```

The following pattern is considered a problem:

<!-- prettier-ignore -->
```css
.bar, .foo {}
```

The following pattern is _not_ considered a problem:

<!-- prettier-ignore -->
```css
.bar .foo {}
mattmanuel90 marked this conversation as resolved.
Show resolved Hide resolved
```

### `ignore: ["inside-block"]`

Ignore selectors that are inside a block.

Given:

```json
[".foo"]
```

The following pattern is _not_ considered a problem:

<!-- prettier-ignore -->
```css
.bar {
.foo {}
}
```
57 changes: 57 additions & 0 deletions lib/rules/selector-disallowed-list/__tests__/index.js
Expand Up @@ -66,6 +66,63 @@ testRule({
],
});

testRule({
ruleName,
config: [/^\.(foo)[,\s]?(?!\w).*$/, { ignore: ['inside-block'], splitList: true }],
accept: [
{
code: 'a.foo {}',
},
{
code: 'a\n>\n.foo {}',
},
{
code: '.bar > a > .foo {}',
},
{
code: '.bar { .foo {} }',
},
{
code: '.fooo {}',
},
],

reject: [
{
code: '.foo {}',
message: messages.rejected('.foo'),
line: 1,
column: 1,
endLine: 1,
endColumn: 5,
},
{
code: '.foo > .bar {}',
message: messages.rejected('.foo > .bar'),
line: 1,
column: 1,
endLine: 1,
endColumn: 12,
},
{
code: '.foo, .bar {}',
message: messages.rejected('.foo'),
line: 1,
column: 1,
endLine: 1,
endColumn: 5,
},
{
code: '.bar, .foo {}',
message: messages.rejected('.foo'),
line: 1,
column: 7,
endColumn: 11,
endLine: 1,
},
],
});

testRule({
ruleName,
config: /\.foo[^>]*>.*\.bar/,
Expand Down
73 changes: 55 additions & 18 deletions lib/rules/selector-disallowed-list/index.js
Expand Up @@ -3,9 +3,10 @@
const isStandardSyntaxRule = require('../../utils/isStandardSyntaxRule');
const matchesStringOrRegExp = require('../../utils/matchesStringOrRegExp');
const report = require('../../utils/report');
const optionsMatches = require('../../utils/optionsMatches');
const ruleMessages = require('../../utils/ruleMessages');
const validateOptions = require('../../utils/validateOptions');
const { isRegExp, isString } = require('../../utils/validateTypes');
const { isRegExp, isString, isBoolean } = require('../../utils/validateTypes');

const ruleName = 'selector-disallowed-list';

Expand All @@ -17,38 +18,74 @@ const meta = {
url: 'https://stylelint.io/user-guide/rules/list/selector-disallowed-list',
};

/** @type {import('stylelint').Rule<string | RegExp | Array<string | RegExp>>} */
const rule = (primary) => {
/** @type {import('stylelint').Rule<string | RegExp | Array<string | RegExp>, { splitList: boolean, ignore: string[] }>} */
const rule = (primary, secondaryOptions) => {
return (root, result) => {
const validOptions = validateOptions(result, ruleName, {
actual: primary,
possible: [isString, isRegExp],
});
const validOptions = validateOptions(
result,
ruleName,
{
actual: primary,
possible: [isString, isRegExp],
},
{
actual: secondaryOptions,
possible: {
ignore: ['inside-block'],
splitList: [isBoolean],
},
optional: true,
},
);

if (!validOptions) {
return;
}

const ignoreInsideBlock = optionsMatches(secondaryOptions, 'ignore', 'inside-block');
const splitList = secondaryOptions && secondaryOptions.splitList;

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

const { selector, raws } = ruleNode;
if (ignoreInsideBlock) {
const { parent } = ruleNode;
const isInsideBlock = parent && parent.type !== 'root';

if (!matchesStringOrRegExp(selector, primary)) {
return;
if (isInsideBlock) {
return;
}
}

const word = (raws.selector && raws.selector.raw) || selector;
if (splitList) {
ruleNode.selectors.forEach((selector) => {
if (matchesStringOrRegExp(selector, primary)) {
report({
result,
ruleName,
message: messages.rejected(selector),
node: ruleNode,
word: selector,
});
}
});
} else {
const { selector, raws } = ruleNode;

report({
result,
ruleName,
message: messages.rejected(selector),
node: ruleNode,
word,
});
if (matchesStringOrRegExp(selector, primary)) {
const word = (raws.selector && raws.selector.raw) || selector;

report({
result,
ruleName,
message: messages.rejected(selector),
node: ruleNode,
word,
});
}
}
});
};
};
Expand Down