UNPKG

3.76 kBJavaScriptView Raw
1'use strict';
2
3const _ = require('lodash');
4const atRuleParamIndex = require('../../utils/atRuleParamIndex');
5const isCustomSelector = require('../../utils/isCustomSelector');
6const isStandardSyntaxAtRule = require('../../utils/isStandardSyntaxAtRule');
7const isStandardSyntaxRule = require('../../utils/isStandardSyntaxRule');
8const isStandardSyntaxSelector = require('../../utils/isStandardSyntaxSelector');
9const keywordSets = require('../../reference/keywordSets');
10const optionsMatches = require('../../utils/optionsMatches');
11const parseSelector = require('../../utils/parseSelector');
12const postcss = require('postcss');
13const report = require('../../utils/report');
14const ruleMessages = require('../../utils/ruleMessages');
15const validateOptions = require('../../utils/validateOptions');
16
17const ruleName = 'selector-pseudo-class-no-unknown';
18
19const messages = ruleMessages(ruleName, {
20 rejected: (selector) => `Unexpected unknown pseudo-class selector "${selector}"`,
21});
22
23function rule(actual, options) {
24 return (root, result) => {
25 const validOptions = validateOptions(
26 result,
27 ruleName,
28 { actual },
29 {
30 actual: options,
31 possible: {
32 ignorePseudoClasses: [_.isString],
33 },
34 optional: true,
35 },
36 );
37
38 if (!validOptions) {
39 return;
40 }
41
42 function check(selector, result, node) {
43 parseSelector(selector, result, node, (selectorTree) => {
44 selectorTree.walkPseudos((pseudoNode) => {
45 const value = pseudoNode.value;
46
47 if (!isStandardSyntaxSelector(value)) {
48 return;
49 }
50
51 if (isCustomSelector(value)) {
52 return;
53 }
54
55 // Ignore pseudo-elements
56 if (value.slice(0, 2) === '::') {
57 return;
58 }
59
60 if (optionsMatches(options, 'ignorePseudoClasses', pseudoNode.value.slice(1))) {
61 return;
62 }
63
64 let index = null;
65 const name = value.slice(1).toLowerCase();
66
67 if (node.type === 'atrule' && node.name === 'page') {
68 if (keywordSets.atRulePagePseudoClasses.has(name)) {
69 return;
70 }
71
72 index = atRuleParamIndex(node) + pseudoNode.sourceIndex;
73 } else {
74 if (
75 postcss.vendor.prefix(name) ||
76 keywordSets.pseudoClasses.has(name) ||
77 keywordSets.pseudoElements.has(name)
78 ) {
79 return;
80 }
81
82 let prevPseudoElement = pseudoNode;
83
84 do {
85 prevPseudoElement = prevPseudoElement.prev();
86
87 if (prevPseudoElement && prevPseudoElement.value.slice(0, 2) === '::') {
88 break;
89 }
90 } while (prevPseudoElement);
91
92 if (prevPseudoElement) {
93 const prevPseudoElementValue = postcss.vendor.unprefixed(
94 prevPseudoElement.value.toLowerCase().slice(2),
95 );
96
97 if (
98 keywordSets.webkitProprietaryPseudoElements.has(prevPseudoElementValue) &&
99 keywordSets.webkitProprietaryPseudoClasses.has(name)
100 ) {
101 return;
102 }
103 }
104
105 index = pseudoNode.sourceIndex;
106 }
107
108 report({
109 message: messages.rejected(value),
110 node,
111 index,
112 ruleName,
113 result,
114 });
115 });
116 });
117 }
118
119 root.walk((node) => {
120 let selector = null;
121
122 if (node.type === 'rule') {
123 if (!isStandardSyntaxRule(node)) {
124 return;
125 }
126
127 selector = node.selector;
128 } else if (node.type === 'atrule' && node.name === 'page' && node.params) {
129 if (!isStandardSyntaxAtRule(node)) {
130 return;
131 }
132
133 selector = node.params;
134 }
135
136 // Return if selector empty, it is meaning node type is not a rule or a at-rule
137
138 if (!selector) {
139 return;
140 }
141
142 // Return early before parse if no pseudos for performance
143
144 if (!selector.includes(':')) {
145 return;
146 }
147
148 check(selector, result, node);
149 });
150 };
151}
152
153rule.ruleName = ruleName;
154rule.messages = messages;
155module.exports = rule;