Skip to content

Commit

Permalink
Add rule-selector-property-disallowed-list (#5679)
Browse files Browse the repository at this point in the history
Co-authored-by: Richard Hallows <jeddy3@users.noreply.github.com>
  • Loading branch information
doing-art and jeddy3 committed Nov 2, 2021
1 parent 8d785f4 commit 1bc40a1
Show file tree
Hide file tree
Showing 5 changed files with 208 additions and 0 deletions.
4 changes: 4 additions & 0 deletions docs/user-guide/rules/list.md
Expand Up @@ -194,6 +194,10 @@ Grouped first by the following categories and then by the [_thing_](http://apps.
- [`selector-pseudo-element-colon-notation`](../../../lib/rules/selector-pseudo-element-colon-notation/README.md): Specify single or double colon notation for applicable pseudo-elements (Autofixable).
- [`selector-pseudo-element-disallowed-list`](../../../lib/rules/selector-pseudo-element-disallowed-list/README.md): Specify a list of disallowed pseudo-element selectors.

### Rules

- [`rule-selector-property-disallowed-list`](../../../lib/rules/rule-selector-property-disallowed-list/README.md): Specify a list of disallowed properties for selectors within rules.

### Media feature

- [`media-feature-name-allowed-list`](../../../lib/rules/media-feature-name-allowed-list/README.md): Specify a list of allowed media feature names.
Expand Down
3 changes: 3 additions & 0 deletions lib/rules/index.js
Expand Up @@ -235,6 +235,9 @@ const rules = {
'property-no-unknown': importLazy(() => require('./property-no-unknown'))(),
'property-no-vendor-prefix': importLazy(() => require('./property-no-vendor-prefix'))(),
'rule-empty-line-before': importLazy(() => require('./rule-empty-line-before'))(),
'rule-selector-property-disallowed-list': importLazy(() =>
require('./rule-selector-property-disallowed-list'),
)(),
'selector-attribute-brackets-space-inside': importLazy(() =>
require('./selector-attribute-brackets-space-inside'),
)(),
Expand Down
61 changes: 61 additions & 0 deletions lib/rules/rule-selector-property-disallowed-list/README.md
@@ -0,0 +1,61 @@
# rule-selector-property-disallowed-list

Specify a list of disallowed properties for selectors within rules.

<!-- prettier-ignore -->
```css
a { color: red; }
/** ↑ ↑
* Selector and property name */
```

## Options

`object`: `{ "selector": ["array", "of", "properties"]`

If a selector name is surrounded with `"/"` (e.g. `"/anchor/"`), it is interpreted as a regular expression. This allows, for example, easy targeting of all the potential anchors: `/anchor/` will match `.anchor`, `[data-anchor]`, etc.

The same goes for properties. Keep in mind that a regular expression value is matched against the entire property name, not specific parts of it. For example, a value like `"animation-duration"` will _not_ match `"/^duration/"` (notice beginning of the line boundary) but _will_ match `"/duration/"`.

Given:

```json
{
"a": ["color", "/margin/"],
"/foo/": ["/size/"]
}
```

The following patterns are considered problems:

<!-- prettier-ignore -->
```css
a { color: red; }
```

<!-- prettier-ignore -->
```css
a { margin-top: 0px; }
```

<!-- prettier-ignore -->
```css
html[data-foo] { font-size: 1px; }
```

The following patterns are _not_ considered problems:

<!-- prettier-ignore -->
```css
a { background: red; }
```

<!-- prettier-ignore -->
```css
a { padding-top: 0px; }
```

<!-- prettier-ignore -->
```css
html[data-foo] { color: red; }
```
@@ -0,0 +1,74 @@
'use strict';

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

testRule({
ruleName,
config: {
a: ['color', '/margin/'],
'/foo/': ['/size/'],
},

accept: [
{
code: 'a { background: red; }',
},
{
code: 'a { padding-top: 0px; }',
},
{
code: 'a { background-color: red; }',
},
{
code: 'a[href="#"] { color: red; }',
},
{
code: 'html.foo { color: red; }',
},
{
code: 'html[data-foo] { color: red; }',
},
],

reject: [
{
code: 'a { color: red; }',
message: messages.rejected('color', 'a'),
line: 1,
column: 5,
},
{
code: 'a { background: red; color: red; }',
message: messages.rejected('color', 'a'),
line: 1,
column: 22,
},
{
code: 'a { margin-top: 0px; }',
message: messages.rejected('margin-top', 'a'),
line: 1,
column: 5,
},
{
code: 'a { color: red; margin-top: 0px; }',
warnings: [
{ message: messages.rejected('color', 'a'), line: 1, column: 5 },
{ message: messages.rejected('margin-top', 'a'), line: 1, column: 17 },
],
line: 1,
column: 5,
},
{
code: '[data-foo] { font-size: 1rem; }',
message: messages.rejected('font-size', '[data-foo]'),
line: 1,
column: 14,
},
{
code: 'html[data-foo] { font-size: 1px; }',
message: messages.rejected('font-size', 'html[data-foo]'),
line: 1,
column: 18,
},
],
});
66 changes: 66 additions & 0 deletions lib/rules/rule-selector-property-disallowed-list/index.js
@@ -0,0 +1,66 @@
'use strict';

const isStandardSyntaxRule = require('../../utils/isStandardSyntaxRule');
const matchesStringOrRegExp = require('../../utils/matchesStringOrRegExp');
const report = require('../../utils/report');
const ruleMessages = require('../../utils/ruleMessages');
const validateOptions = require('../../utils/validateOptions');
const { isPlainObject } = require('is-plain-object');

const ruleName = 'rule-selector-property-disallowed-list';

const messages = ruleMessages(ruleName, {
rejected: (property, selector) => `Unexpected property "${property}" for selector "${selector}"`,
});

/** @type {import('stylelint').Rule} */
const rule = (primary) => {
return (root, result) => {
const validOptions = validateOptions(result, ruleName, {
actual: primary,
possible: [isPlainObject],
});

if (!validOptions) {
return;
}

const selectors = Object.keys(primary);

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

const selectorKey = selectors.find((selector) =>
matchesStringOrRegExp(ruleNode.selector, selector),
);

if (!selectorKey) {
return;
}

const disallowedProperties = primary[selectorKey];

for (const node of ruleNode.nodes) {
const isDisallowedProperty =
node.type === 'decl' && matchesStringOrRegExp(node.prop, disallowedProperties);

if (isDisallowedProperty) {
report({
message: messages.rejected(node.prop, ruleNode.selector),
node,
result,
ruleName,
});
}
}
});
};
};

rule.primaryOptionArray = true;

rule.ruleName = ruleName;
rule.messages = messages;
module.exports = rule;

0 comments on commit 1bc40a1

Please sign in to comment.