Initial commit

This commit is contained in:
Patrick Marsceill
2017-03-09 13:16:08 -05:00
commit b7b0d0d7bf
4147 changed files with 401224 additions and 0 deletions
+102
View File
@@ -0,0 +1,102 @@
# no-duplicate-selectors
Disallow duplicate selectors within a stylesheet.
```css
.foo {} .bar {} .foo {}
/** ↑ ↑
* These duplicates */
```
This rule checks for two types of duplication:
- Duplication of a single selector with a rule's selector list, e.g. `a, b, a {}`.
- Duplication of a selector list within a stylesheet, e.g. `a, b {} a, b {}`. Duplicates are found even if the selectors come in different orders or have different spacing, e.g. `a d, b > c {} b>c, a d {}`.
The same selector *is* allowed to repeat in the following circumstances:
- It is used in different selector lists, e.g. `a {} a, b {}`.
- The duplicates are determined to originate in different stylesheets, e.g. you have concatenated or compiled files in a way that produces sourcemaps for PostCSS to read, e.g. postcss-import).
- The duplicates are in rules with different parent nodes, e.g. inside and outside of a media query.
This rule resolves nested selectors. So `a b {} a { & b {} }` counts as a warning, because the resolved selectors end up with a duplicate.
## Options
### `true`
The following patterns are considered warnings:
```css
.foo,
.bar,
.foo {}
```
```css
.foo {}
.bar {}
.foo {}
```
```css
.foo .bar {}
.bar {}
.foo .bar {}
```
```css
@media (min-width: 10px) {
.foo {}
.foo {}
}
```
```css
.foo, .bar {}
.bar, .foo {}
```
```css
a .foo, b + .bar {}
b+.bar,
a
.foo {}
```
```css
a b {}
a {
& b {}
}
```
The following patterns are *not* considered warnings:
```css
.foo {}
@media (min-width: 10px) {
.foo {}
}
```
```css
.foo {
.foo {}
}
```
```css
.foo {}
.bar {}
.foo .bar {}
.bar .foo {}
```
```css
a b {}
a {
& b,
& c {}
}
```
+81
View File
@@ -0,0 +1,81 @@
"use strict"
const findAtRuleContext = require("../../utils/findAtRuleContext")
const isKeyframeRule = require("../../utils/isKeyframeRule")
const nodeContextLookup = require("../../utils/nodeContextLookup")
const report = require("../../utils/report")
const ruleMessages = require("../../utils/ruleMessages")
const validateOptions = require("../../utils/validateOptions")
const _ = require("lodash")
const normalizeSelector = require("normalize-selector")
const resolvedNestedSelector = require("postcss-resolve-nested-selector")
const ruleName = "no-duplicate-selectors"
const messages = ruleMessages(ruleName, {
rejected: selector => `Unexpected duplicate selector "${selector}"`,
})
const rule = function (actual) {
return (root, result) => {
const validOptions = validateOptions(result, ruleName, { actual })
if (!validOptions) {
return
}
// The top level of this map will be rule sources.
// Each source maps to another map, which maps rule parents to a set of selectors.
// This ensures that selectors are only checked against selectors
// from other rules that share the same parent and the same source.
const selectorContextLookup = nodeContextLookup()
root.walkRules(rule => {
if (isKeyframeRule(rule)) {
return
}
const contextSelectorSet = selectorContextLookup.getContext(rule, findAtRuleContext(rule))
const resolvedSelectors = rule.selectors.reduce((result, selector) => {
return _.union(result, resolvedNestedSelector(selector, rule))
}, [])
const normalizedSelectorList = resolvedSelectors.map(normalizeSelector)
// Complain if the same selector list occurs twice
// Sort the selectors list so that the order of the constituents
// doesn't matter
const sortedSelectorList = normalizedSelectorList.slice().sort().join(",")
if (contextSelectorSet.has(sortedSelectorList)) {
// If the selector isn't nested we can use its raw value; otherwise,
// we have to approximate something for the message -- which is close enough
const isNestedSelector = resolvedSelectors.join(",") !== rule.selectors.join(",")
const selectorForMessage = isNestedSelector ? resolvedSelectors.join(", ") : rule.selector
return report({
result,
ruleName,
node: rule,
message: messages.rejected(selectorForMessage),
})
}
// We're treating the Map created by nodeContextLookup as a Set
contextSelectorSet.set(sortedSelectorList, null)
// Or complain if one selector list contains the same selector more than one
rule.selectors.forEach((selector, i) => {
if (_.includes(normalizedSelectorList.slice(0, i), normalizeSelector(selector))) {
report({
result,
ruleName,
node: rule,
message: messages.rejected(selector),
})
}
})
})
}
}
rule.ruleName = ruleName
rule.messages = messages
module.exports = rule