3 const _ = require("lodash");
4 const findAtRuleContext = require("../../utils/findAtRuleContext");
5 const isKeyframeRule = require("../../utils/isKeyframeRule");
6 const nodeContextLookup = require("../../utils/nodeContextLookup");
7 const normalizeSelector = require("normalize-selector");
8 const report = require("../../utils/report");
9 const resolvedNestedSelector = require("postcss-resolve-nested-selector");
10 const ruleMessages = require("../../utils/ruleMessages");
11 const validateOptions = require("../../utils/validateOptions");
13 const ruleName = "no-duplicate-selectors";
15 const messages = ruleMessages(ruleName, {
16 rejected: (selector, firstDuplicateLine) =>
17 `Unexpected duplicate selector "${selector}", first used at line ${
22 const rule = function(actual) {
23 return (root, result) => {
24 const validOptions = validateOptions(result, ruleName, { actual });
29 // The top level of this map will be rule sources.
30 // Each source maps to another map, which maps rule parents to a set of selectors.
31 // This ensures that selectors are only checked against selectors
32 // from other rules that share the same parent and the same source.
33 const selectorContextLookup = nodeContextLookup();
35 root.walkRules(rule => {
36 if (isKeyframeRule(rule)) {
40 const contextSelectorSet = selectorContextLookup.getContext(
42 findAtRuleContext(rule)
44 const resolvedSelectors = rule.selectors.reduce((result, selector) => {
45 return _.union(result, resolvedNestedSelector(selector, rule));
47 const normalizedSelectorList = resolvedSelectors.map(normalizeSelector);
48 const selectorLine = rule.source.start.line;
50 // Complain if the same selector list occurs twice
52 // Sort the selectors list so that the order of the constituents
54 const sortedSelectorList = normalizedSelectorList
58 if (contextSelectorSet.has(sortedSelectorList)) {
59 // If the selector isn't nested we can use its raw value; otherwise,
60 // we have to approximate something for the message -- which is close enough
61 const isNestedSelector =
62 resolvedSelectors.join(",") !== rule.selectors.join(",");
63 const selectorForMessage = isNestedSelector
64 ? resolvedSelectors.join(", ")
66 const previousDuplicatePosition = contextSelectorSet.get(
74 message: messages.rejected(
76 previousDuplicatePosition
81 contextSelectorSet.set(sortedSelectorList, selectorLine);
83 // Or complain if one selector list contains the same selector more than one
84 rule.selectors.forEach((selector, i) => {
87 normalizedSelectorList.slice(0, i),
88 normalizeSelector(selector)
95 message: messages.rejected(selector, selectorLine)
103 rule.ruleName = ruleName;
104 rule.messages = messages;
105 module.exports = rule;