+++ /dev/null
-"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;