Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add rule-selector-property-disallowed-list (#5679)
Co-authored-by: Richard Hallows <jeddy3@users.noreply.github.com>
- Loading branch information
Showing
5 changed files
with
208 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
61 changes: 61 additions & 0 deletions
61
lib/rules/rule-selector-property-disallowed-list/README.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; } | ||
``` |
74 changes: 74 additions & 0 deletions
74
lib/rules/rule-selector-property-disallowed-list/__tests__/index.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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, | ||
}, | ||
], | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; |