"use strict"; const _ = require("lodash"); const findAtRuleContext = require("../../utils/findAtRuleContext"); const isKeyframeRule = require("../../utils/isKeyframeRule"); const nodeContextLookup = require("../../utils/nodeContextLookup"); const normalizeSelector = require("normalize-selector"); const report = require("../../utils/report"); const resolvedNestedSelector = require("postcss-resolve-nested-selector"); const ruleMessages = require("../../utils/ruleMessages"); const validateOptions = require("../../utils/validateOptions"); const ruleName = "no-duplicate-selectors"; const messages = ruleMessages(ruleName, { rejected: (selector, firstDuplicateLine) => `Unexpected duplicate selector "${selector}", first used at line ${ firstDuplicateLine }` }); 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); const selectorLine = rule.source.start.line; // 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; const previousDuplicatePosition = contextSelectorSet.get( sortedSelectorList ); return report({ result, ruleName, node: rule, message: messages.rejected( selectorForMessage, previousDuplicatePosition ) }); } contextSelectorSet.set(sortedSelectorList, selectorLine); // 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, selectorLine) }); } }); }); }; }; rule.ruleName = ruleName; rule.messages = messages; module.exports = rule;