1 | "use strict";
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 | function getBodyIndex(expressions) {
|
11 | let idx = 0;
|
12 |
|
13 |
|
14 |
|
15 |
|
16 |
|
17 | for (let i = 0; i < expressions.length; i++) {
|
18 | switch (expressions[i].type) {
|
19 | case "tag":
|
20 | if (expressions[i].name === "body") {
|
21 | return idx;
|
22 | }
|
23 | break;
|
24 |
|
25 | case "child":
|
26 | case "descendant":
|
27 | idx++;
|
28 | }
|
29 | }
|
30 |
|
31 | return -1;
|
32 | }
|
33 |
|
34 |
|
35 |
|
36 |
|
37 |
|
38 | function firstSelectorHasClass(expressions) {
|
39 |
|
40 | return expressions[0].type === "tag"
|
41 | ?
|
42 | expressions[1].type === "attribute" && expressions[1].name === "class"
|
43 | :
|
44 | expressions[0].type === "attribute" && expressions[0].name === "class";
|
45 | }
|
46 |
|
47 |
|
48 |
|
49 |
|
50 |
|
51 | function getDescendantCombinatorIndex(expressions) {
|
52 |
|
53 |
|
54 | return expressions
|
55 | .filter((item) => {
|
56 | return !["tag", "attribute", "pseudo"].includes(item.type);
|
57 | })
|
58 | .map((item) => {
|
59 | return item.type;
|
60 | })
|
61 | .indexOf("child");
|
62 | }
|
63 |
|
64 |
|
65 |
|
66 |
|
67 | function rule(analyzer) {
|
68 | const debug = require("debug")("analyze-css:bodySelectors");
|
69 |
|
70 | analyzer.setMetric("redundantBodySelectors");
|
71 |
|
72 | analyzer.on("selector", function (_, selector, expressions) {
|
73 | const noExpressions = expressions.length;
|
74 |
|
75 |
|
76 | if (noExpressions < 2) {
|
77 | return;
|
78 | }
|
79 |
|
80 | const firstTag = expressions[0].type === "tag" && expressions[0].name;
|
81 |
|
82 | const firstHasClass = firstSelectorHasClass(expressions);
|
83 |
|
84 | const isDescendantCombinator =
|
85 | getDescendantCombinatorIndex(expressions) === 0;
|
86 |
|
87 |
|
88 |
|
89 | const isShortExpression =
|
90 | expressions.filter((item) => {
|
91 | return ["child", "descendant"].includes(item.type);
|
92 | }).length === 1;
|
93 |
|
94 | let isRedundant = true;
|
95 |
|
96 |
|
97 | const bodyIndex = getBodyIndex(expressions);
|
98 |
|
99 | debug("selector: %s %j", selector, {
|
100 | firstTag,
|
101 | firstHasClass,
|
102 | isDescendantCombinator,
|
103 | isShortExpression,
|
104 | bodyIndex,
|
105 | });
|
106 |
|
107 |
|
108 | if (bodyIndex < 0) {
|
109 | return;
|
110 | }
|
111 |
|
112 |
|
113 |
|
114 |
|
115 |
|
116 |
|
117 |
|
118 |
|
119 |
|
120 |
|
121 |
|
122 | if (
|
123 | firstTag === "html" &&
|
124 | bodyIndex === 1 &&
|
125 | (isDescendantCombinator || isShortExpression)
|
126 | ) {
|
127 | isRedundant = false;
|
128 | }
|
129 |
|
130 | else if (bodyIndex === 0 && isDescendantCombinator) {
|
131 | isRedundant = false;
|
132 | }
|
133 |
|
134 | else if (bodyIndex === 0 && firstHasClass) {
|
135 | isRedundant = false;
|
136 | }
|
137 |
|
138 | else if (firstHasClass && bodyIndex === 1 && isDescendantCombinator) {
|
139 | isRedundant = false;
|
140 | }
|
141 |
|
142 |
|
143 | if (isRedundant) {
|
144 | debug("selector %s - is redundant", selector);
|
145 |
|
146 | analyzer.incrMetric("redundantBodySelectors");
|
147 | analyzer.addOffender("redundantBodySelectors", selector);
|
148 | }
|
149 | });
|
150 | }
|
151 |
|
152 | rule.description = "Reports redundant body selectors";
|
153 | module.exports = rule;
|