| // NOTICE: This file is generated by Rollup. To modify it, |
| // please instead edit the ESM counterpart and rebuild with Rollup (npm run build). |
| 'use strict'; |
| |
| const selectorParser = require('postcss-selector-parser'); |
| const validateTypes = require('../../utils/validateTypes.cjs'); |
| const getRuleSelector = require('../../utils/getRuleSelector.cjs'); |
| const isStandardSyntaxRule = require('../../utils/isStandardSyntaxRule.cjs'); |
| const parseSelector = require('../../utils/parseSelector.cjs'); |
| const report = require('../../utils/report.cjs'); |
| const ruleMessages = require('../../utils/ruleMessages.cjs'); |
| const validateOptions = require('../../utils/validateOptions.cjs'); |
| |
| const { isAttribute, isClassName, isIdentifier, isPseudoClass, isTag, isUniversal } = |
| selectorParser; |
| |
| const ruleName = 'selector-not-notation'; |
| const messages = ruleMessages(ruleName, { |
| expected: (type) => `Expected ${type} :not() pseudo-class notation`, |
| }); |
| const meta = { |
| url: 'https://stylelint.io/user-guide/rules/selector-not-notation', |
| fixable: true, |
| }; |
| |
| /** @typedef {import('postcss-selector-parser').Node} Node */ |
| /** @typedef {import('postcss-selector-parser').Selector} Selector */ |
| /** @typedef {import('postcss-selector-parser').Pseudo} Pseudo */ |
| /** @typedef {import('postcss-selector-parser').Root} Root */ |
| /** @typedef {import('postcss').Rule} Rule */ |
| /** @typedef {import('stylelint').Range} Range */ |
| |
| /** |
| * @param {Node} node |
| * @returns {boolean} |
| */ |
| const isSimpleSelector = (node) => |
| isPseudoClass(node) || |
| isAttribute(node) || |
| isClassName(node) || |
| isUniversal(node) || |
| isIdentifier(node) || |
| isTag(node); |
| |
| /** |
| * @param {Node | undefined} node |
| * @returns {node is Pseudo} |
| */ |
| const isNot = (node) => |
| isPseudoClass(node) && node.value !== undefined && node.value.toLowerCase() === ':not'; |
| |
| /** |
| * @param {Selector[]} list |
| * @returns {boolean} |
| */ |
| const isSimple = (list) => { |
| if (list.length > 1) return false; |
| |
| validateTypes.assert(list[0], 'list is never empty'); |
| const [first, second] = list[0].nodes; |
| |
| if (!first) return true; |
| |
| if (second) return false; |
| |
| return isSimpleSelector(first) && !isNot(first); |
| }; |
| |
| /** @type {import('stylelint').CoreRules[ruleName]} */ |
| const rule = (primary) => { |
| return (root, result) => { |
| const validOptions = validateOptions(result, ruleName, { |
| actual: primary, |
| possible: ['simple', 'complex'], |
| }); |
| |
| if (!validOptions) return; |
| |
| root.walkRules((ruleNode) => { |
| if (!isStandardSyntaxRule(ruleNode)) return; |
| |
| const selector = ruleNode.selector; |
| |
| if (!selector.includes(':not(')) return; |
| |
| const selectorRoot = parseSelector(getRuleSelector(ruleNode), result, ruleNode); |
| |
| if (!selectorRoot) return; |
| |
| selectorRoot.walkPseudos((pseudo) => { |
| if (!isNot(pseudo)) return; |
| |
| const index = pseudo.sourceIndex; |
| const endIndex = index + pseudo.toString().trim().length; |
| |
| let fix; |
| |
| if (primary === 'complex') { |
| const prev = pseudo.prev(); |
| const hasConsecutiveNot = prev && isNot(prev); |
| |
| if (!hasConsecutiveNot) return; |
| |
| fix = () => fixComplex(prev, ruleNode, selectorRoot); |
| } else { |
| const selectors = pseudo.nodes; |
| |
| if (isSimple(selectors)) return; |
| |
| const second = selectors.length > 1 && selectors[1]; |
| const isFixable = |
| second && (!second.nodes.length || selectors.every(({ nodes }) => nodes.length === 1)); |
| |
| if (isFixable) fix = () => fixSimple(pseudo, ruleNode, selectorRoot); |
| } |
| |
| report({ |
| message: messages.expected, |
| messageArgs: [primary], |
| node: ruleNode, |
| result, |
| ruleName, |
| index, |
| endIndex, |
| fix: { |
| apply: fix, |
| node: ruleNode, |
| }, |
| }); |
| }); |
| }); |
| }; |
| }; |
| |
| /** |
| * @param {Pseudo} not |
| * @returns {Node} |
| */ |
| const getLastConsecutiveNot = ({ parent, sourceIndex }) => { |
| validateTypes.assert(parent); |
| |
| const nodes = parent.nodes; |
| const index = nodes.findIndex((node) => node.sourceIndex >= sourceIndex && !isNot(node)); |
| const node = index === -1 ? nodes[nodes.length - 1] : nodes[index - 1]; |
| |
| validateTypes.assert(node); |
| |
| return node; |
| }; |
| |
| /** |
| * @param {Pseudo} not |
| * @param {Rule} ruleNode |
| * @param {Root} selectorRoot |
| */ |
| function fixSimple(not, ruleNode, selectorRoot) { |
| const simpleSelectors = not.nodes |
| .filter(({ nodes }) => nodes[0] && isSimpleSelector(nodes[0])) |
| .map((s) => { |
| validateTypes.assert(s.nodes[0]); |
| s.nodes[0].rawSpaceBefore = ''; |
| s.nodes[0].rawSpaceAfter = ''; |
| |
| return s; |
| }); |
| const firstSelector = simpleSelectors.shift(); |
| |
| validateTypes.assert(firstSelector); |
| validateTypes.assert(not.parent); |
| |
| not.empty(); |
| not.nodes.push(firstSelector); |
| |
| for (const s of simpleSelectors) { |
| const last = getLastConsecutiveNot(not); |
| const clone = last.clone({ nodes: [s] }); |
| |
| clone.rawSpaceBefore = ''; |
| not.parent.insertAfter(last, clone); |
| } |
| |
| ruleNode.selector = selectorRoot.toString(); |
| } |
| |
| /** |
| * @param {Pseudo} previousNot |
| * @param {Rule} ruleNode |
| * @param {Root} selectorRoot |
| */ |
| function fixComplex(previousNot, ruleNode, selectorRoot) { |
| const indentAndTrimRight = (/** @type {Selector[]} */ selectors) => { |
| for (const s of selectors) { |
| validateTypes.assert(s.nodes[0]); |
| s.nodes[0].rawSpaceBefore = ' '; |
| s.nodes[0].rawSpaceAfter = ''; |
| } |
| }; |
| const [head, ...tail] = previousNot.nodes; |
| let node = previousNot.next(); |
| |
| if (head == null || head.nodes.length === 0) return; |
| |
| validateTypes.assert(head.nodes[0]); |
| head.nodes[0].rawSpaceBefore = ''; |
| head.nodes[0].rawSpaceAfter = ''; |
| indentAndTrimRight(tail); |
| |
| while (isNot(node)) { |
| const selectors = node.nodes; |
| const prev = node; |
| |
| indentAndTrimRight(selectors); |
| previousNot.nodes = previousNot.nodes.concat(selectors); |
| node = node.next(); |
| prev.remove(); |
| } |
| |
| ruleNode.selector = selectorRoot.toString(); |
| } |
| |
| rule.ruleName = ruleName; |
| rule.messages = messages; |
| rule.meta = meta; |
| |
| module.exports = rule; |