123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144 |
- // @ts-nocheck
- '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 parseSelector = require('../../utils/parseSelector');
- 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}`,
- });
- function rule(actual, options) {
- return (root, result) => {
- const validOptions = validateOptions(
- result,
- ruleName,
- { actual },
- {
- actual: options,
- possible: {
- disallowInList: _.isBoolean,
- },
- optional: true,
- },
- );
- if (!validOptions) {
- return;
- }
- const shouldDisallowDuplicateInList = _.get(options, 'disallowInList');
- // 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);
- // Sort the selectors list so that the order of the constituents
- // doesn't matter
- const sortedSelectorList = normalizedSelectorList.slice().sort().join(',');
- const selectorLine = rule.source.start.line;
- // Complain if the same selector list occurs twice
- let previousDuplicatePosition;
- // When `disallowInList` is true, we must parse `sortedSelectorList` into
- // list items.
- let selectorListParsed = [];
- if (shouldDisallowDuplicateInList) {
- parseSelector(sortedSelectorList, result, rule, (selectors) => {
- selectors.each((s) => {
- const selector = String(s);
- selectorListParsed.push(selector);
- if (contextSelectorSet.get(selector)) {
- previousDuplicatePosition = contextSelectorSet.get(selector);
- }
- });
- });
- } else {
- previousDuplicatePosition = contextSelectorSet.get(sortedSelectorList);
- }
- if (previousDuplicatePosition) {
- // 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, previousDuplicatePosition),
- });
- }
- const presentedSelectors = new Set();
- const reportedSelectors = new Set();
- // Or complain if one selector list contains the same selector more than once
- rule.selectors.forEach((selector) => {
- const normalized = normalizeSelector(selector);
- if (presentedSelectors.has(normalized)) {
- if (reportedSelectors.has(normalized)) {
- return;
- }
- report({
- result,
- ruleName,
- node: rule,
- message: messages.rejected(selector, selectorLine),
- });
- reportedSelectors.add(normalized);
- } else {
- presentedSelectors.add(normalized);
- }
- });
- if (shouldDisallowDuplicateInList) {
- for (let selector of selectorListParsed) {
- // [selectorLine] will not really be accurate for multi-line
- // selectors, such as "bar" in "foo,\nbar {}".
- contextSelectorSet.set(selector, selectorLine);
- }
- } else {
- contextSelectorSet.set(sortedSelectorList, selectorLine);
- }
- });
- };
- }
- rule.ruleName = ruleName;
- rule.messages = messages;
- module.exports = rule;
|