-
-
Notifications
You must be signed in to change notification settings - Fork 934
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
Added: selector-max-universal
rule.
#2653
Merged
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
# selector-max-universal | ||
|
||
Limit the number of universal selectors in a selector. | ||
|
||
```css | ||
* {} | ||
/** ↑ | ||
* This universal selector */ | ||
``` | ||
|
||
This rule resolves nested selectors before counting the number of universal selectors. Each selector in a [selector list](https://www.w3.org/TR/selectors4/#selector-list) is evaluated separately. | ||
|
||
The `:not()` pseudo-class is also evaluated separately. The rule processes the argument as if it were an independent selector, and the result does not count toward the total for the entire selector. | ||
|
||
## Options | ||
|
||
`int`: Maximum universal selectors allowed. | ||
|
||
For example, with `2`: | ||
|
||
The following patterns are considered violations: | ||
|
||
```css | ||
* * * {} | ||
``` | ||
|
||
```css | ||
* * { | ||
& * {} | ||
} | ||
``` | ||
|
||
```css | ||
* * { | ||
& > * {} | ||
} | ||
``` | ||
|
||
The following patterns are *not* considered violations: | ||
|
||
```css | ||
* {} | ||
``` | ||
|
||
```css | ||
* * {} | ||
``` | ||
|
||
```css | ||
.foo * {} | ||
``` | ||
|
||
```css | ||
*.foo * {} | ||
``` | ||
|
||
```css | ||
/* each selector in a selector list is evaluated separately */ | ||
*.foo, | ||
*.bar * {} | ||
``` | ||
|
||
```css | ||
/* `*` is inside `:not()`, so it is evaluated separately */ | ||
* > * .foo:not(*) {} | ||
``` |
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,161 @@ | ||
"use strict" | ||
|
||
const messages = require("..").messages | ||
const ruleName = require("..").ruleName | ||
const rules = require("../../../rules") | ||
|
||
const rule = rules[ruleName] | ||
|
||
// Sanity checks | ||
testRule(rule, { | ||
ruleName, | ||
config: [0], | ||
|
||
accept: [ { | ||
code: "foo {}", | ||
}, { | ||
code: ".bar {}", | ||
}, { | ||
code: "foo .bar {}", | ||
}, { | ||
code: "#foo {}", | ||
}, { | ||
code: "[foo] {}", | ||
}, { | ||
code: ":root { --foo: 1px; }", | ||
description: "custom property in root", | ||
}, { | ||
code: "html { --foo: 1px; }", | ||
description: "custom property in selector", | ||
}, { | ||
code: ":root { --custom-property-set: {} }", | ||
description: "custom property set in root", | ||
}, { | ||
code: "html { --custom-property-set: {} }", | ||
description: "custom property set in selector", | ||
} ], | ||
|
||
reject: [ { | ||
code: "* {}", | ||
message: messages.expected("*", 0), | ||
line: 1, | ||
column: 1, | ||
}, { | ||
code: ".bar * {}", | ||
message: messages.expected(".bar *", 0), | ||
line: 1, | ||
column: 1, | ||
}, { | ||
code: "*.bar {}", | ||
message: messages.expected("*.bar", 0), | ||
line: 1, | ||
column: 1, | ||
}, { | ||
code: "* [lang^=en] {}", | ||
message: messages.expected("* [lang^=en]", 0), | ||
line: 1, | ||
column: 1, | ||
}, { | ||
code: "*[lang^=en] {}", | ||
message: messages.expected("*[lang^=en]", 0), | ||
line: 1, | ||
column: 1, | ||
}, { | ||
code: ".foo, .bar, *.baz {}", | ||
message: messages.expected("*.baz", 0), | ||
line: 1, | ||
column: 13, | ||
}, { | ||
code: "* #id {}", | ||
message: messages.expected("* #id", 0), | ||
line: 1, | ||
column: 1, | ||
}, { | ||
code: "*#id {}", | ||
message: messages.expected("*#id", 0), | ||
line: 1, | ||
column: 1, | ||
}, { | ||
code: ".foo* {}", | ||
message: messages.expected(".foo*", 0), | ||
line: 1, | ||
column: 1, | ||
}, { | ||
code: "*:hover {}", | ||
message: messages.expected("*:hover", 0), | ||
line: 1, | ||
column: 1, | ||
}, { | ||
code: ":not(*) {}", | ||
message: messages.expected("*", 0), | ||
line: 1, | ||
column: 6, | ||
} ], | ||
}) | ||
|
||
// Standard tests | ||
testRule(rule, { | ||
ruleName, | ||
config: [2], | ||
|
||
accept: [ { | ||
code: "* {}", | ||
description: "fewer than max universal selectors", | ||
}, { | ||
code: "*:hover {}", | ||
description: "pseudo selectors", | ||
}, { | ||
code: "* * {}", | ||
description: "compound selector", | ||
}, { | ||
code: "*, \n* {}", | ||
description: "multiple selectors: fewer than max universal selectors", | ||
}, { | ||
code: "* *, \n* * {}", | ||
description: "multiple selectors: exactly max universal selectors", | ||
}, { | ||
code: "* *:not(*) {}", | ||
description: ":not(): outside and inside", | ||
}, { | ||
code: "* { * {} }", | ||
description: "nested selectors", | ||
}, { | ||
code: "* { * > & {} }", | ||
description: "nested selectors: parent selector", | ||
}, { | ||
code: "*, * { & > * {} }", | ||
description: "nested selectors: superfluous parent selector", | ||
}, { | ||
code: "@media print { * * {} }", | ||
description: "media query: parent", | ||
}, { | ||
code: "* { @media print { * {} } }", | ||
description: "media query: nested", | ||
} ], | ||
|
||
reject: [ { | ||
code: "* * * {}", | ||
description: "compound selector: greater than max universal selectors", | ||
message: messages.expected("* * *", 2), | ||
line: 1, | ||
column: 1, | ||
}, { | ||
code: "*, \n* * * {}", | ||
description: "multiple selectors: greater than max universal selectors", | ||
message: messages.expected("* * *", 2), | ||
line: 2, | ||
column: 1, | ||
}, { | ||
code: "* * *:not(*) {}", | ||
description: ":not(): greater than max universal selectors, outside", | ||
message: messages.expected("* * *:not(*)", 2), | ||
line: 1, | ||
column: 1, | ||
}, { | ||
code: "* { &:hover > * * {} }", | ||
description: "nested selectors: greater than max universal selectors", | ||
message: messages.expected("*:hover > * *", 2), | ||
line: 1, | ||
column: 5, | ||
} ], | ||
}) |
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,75 @@ | ||
"use strict" | ||
|
||
const isStandardSyntaxRule = require("../../utils/isStandardSyntaxRule") | ||
const isStandardSyntaxSelector = require("../../utils/isStandardSyntaxSelector") | ||
const parseSelector = require("../../utils/parseSelector") | ||
const report = require("../../utils/report") | ||
const ruleMessages = require("../../utils/ruleMessages") | ||
const validateOptions = require("../../utils/validateOptions") | ||
const resolvedNestedSelector = require("postcss-resolve-nested-selector") | ||
|
||
const ruleName = "selector-max-universal" | ||
|
||
const messages = ruleMessages(ruleName, { | ||
expected: (selector, max) => `Expected "${selector}" to have no more than ${max} universal ${max === 1 ? "selector" : "selectors"}`, | ||
}) | ||
|
||
function rule(max) { | ||
return (root, result) => { | ||
const validOptions = validateOptions(result, ruleName, { | ||
actual: max, | ||
possible: [ | ||
function (max) { | ||
return typeof max === "number" && max >= 0 | ||
}, | ||
], | ||
}) | ||
if (!validOptions) { | ||
return | ||
} | ||
|
||
function checkSelector(selectorNode, ruleNode) { | ||
const count = selectorNode.reduce((total, childNode) => { | ||
// Only traverse inside actual selectors and :not() | ||
if (childNode.type === "selector" || childNode.value === ":not") { | ||
checkSelector(childNode, ruleNode) | ||
} | ||
|
||
return total += (childNode.type === "universal" ? 1 : 0) | ||
}, 0) | ||
|
||
if (selectorNode.type !== "root" && selectorNode.type !== "pseudo" && count > max) { | ||
report({ | ||
ruleName, | ||
result, | ||
node: ruleNode, | ||
message: messages.expected(selectorNode, max), | ||
word: selectorNode, | ||
}) | ||
} | ||
} | ||
|
||
root.walkRules(ruleNode => { | ||
if (!isStandardSyntaxRule(ruleNode)) { | ||
return | ||
} | ||
if (!isStandardSyntaxSelector(ruleNode.selector)) { | ||
return | ||
} | ||
if (ruleNode.nodes.some(node => [ "rule", "atrule" ].indexOf(node.type) !== -1)) { | ||
// Skip unresolved nested selectors | ||
return | ||
} | ||
|
||
ruleNode.selectors.forEach(selector => { | ||
resolvedNestedSelector(selector, ruleNode).forEach(resolvedSelector => { | ||
parseSelector(resolvedSelector, result, ruleNode, container => checkSelector(container, ruleNode)) | ||
}) | ||
}) | ||
}) | ||
} | ||
} | ||
|
||
rule.ruleName = ruleName | ||
rule.messages = messages | ||
module.exports = rule |
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
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please change to: