Skip to content

Commit 33ebcfb

Browse files
authored
Merge pull request #3825 from tanhauhau/tanhauhau/unused-css-string-concat
feat unused css selector that understands string concatenation
2 parents f7833ac + 798a47b commit 33ebcfb

File tree

13 files changed

+364
-9
lines changed

13 files changed

+364
-9
lines changed

src/compiler/compile/css/Selector.ts

+78-9
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import Stylesheet from './Stylesheet';
33
import { gather_possible_values, UNKNOWN } from './gather_possible_values';
44
import { CssNode } from './interfaces';
55
import Component from '../Component';
6+
import Element from '../nodes/Element';
67

78
enum BlockAppliesToNode {
89
NotPossible,
@@ -34,8 +35,8 @@ export default class Selector {
3435
this.used = this.blocks[0].global;
3536
}
3637

37-
apply(node: CssNode, stack: CssNode[]) {
38-
const to_encapsulate: CssNode[] = [];
38+
apply(node: Element, stack: Element[]) {
39+
const to_encapsulate: any[] = [];
3940

4041
apply_selector(this.local_blocks.slice(), node, stack.slice(), to_encapsulate);
4142

@@ -132,7 +133,7 @@ export default class Selector {
132133
}
133134
}
134135

135-
function apply_selector(blocks: Block[], node: CssNode, stack: CssNode[], to_encapsulate: any[]): boolean {
136+
function apply_selector(blocks: Block[], node: Element, stack: Element[], to_encapsulate: any[]): boolean {
136137
const block = blocks.pop();
137138
if (!block) return false;
138139

@@ -259,16 +260,84 @@ function attribute_matches(node: CssNode, name: string, expected_value: string,
259260
const attr = node.attributes.find((attr: CssNode) => attr.name === name);
260261
if (!attr) return false;
261262
if (attr.is_true) return operator === null;
262-
if (attr.chunks.length > 1) return true;
263263
if (!expected_value) return true;
264264

265-
const value = attr.chunks[0];
266-
267-
if (!value) return false;
268-
if (value.type === 'Text') return test_attribute(operator, expected_value, case_insensitive, value.data);
265+
if (attr.chunks.length === 1) {
266+
const value = attr.chunks[0];
267+
if (!value) return false;
268+
if (value.type === 'Text') return test_attribute(operator, expected_value, case_insensitive, value.data);
269+
}
269270

270271
const possible_values = new Set();
271-
gather_possible_values(value.node, possible_values);
272+
273+
let prev_values = [];
274+
for (const chunk of attr.chunks) {
275+
const current_possible_values = new Set();
276+
if (chunk.type === 'Text') {
277+
current_possible_values.add(chunk.data);
278+
} else {
279+
gather_possible_values(chunk.node, current_possible_values);
280+
}
281+
282+
// impossible to find out all combinations
283+
if (current_possible_values.has(UNKNOWN)) return true;
284+
285+
if (prev_values.length > 0) {
286+
const start_with_space = [];
287+
const remaining = [];
288+
current_possible_values.forEach((current_possible_value: string) => {
289+
if (/^\s/.test(current_possible_value)) {
290+
start_with_space.push(current_possible_value);
291+
} else {
292+
remaining.push(current_possible_value);
293+
}
294+
});
295+
296+
if (remaining.length > 0) {
297+
if (start_with_space.length > 0) {
298+
prev_values.forEach(prev_value => possible_values.add(prev_value));
299+
}
300+
301+
const combined = [];
302+
prev_values.forEach((prev_value: string) => {
303+
remaining.forEach((value: string) => {
304+
combined.push(prev_value + value);
305+
});
306+
});
307+
prev_values = combined;
308+
309+
start_with_space.forEach((value: string) => {
310+
if (/\s$/.test(value)) {
311+
possible_values.add(value);
312+
} else {
313+
prev_values.push(value);
314+
}
315+
});
316+
continue;
317+
} else {
318+
prev_values.forEach(prev_value => possible_values.add(prev_value));
319+
prev_values = [];
320+
}
321+
}
322+
323+
current_possible_values.forEach((current_possible_value: string) => {
324+
if (/\s$/.test(current_possible_value)) {
325+
possible_values.add(current_possible_value);
326+
} else {
327+
prev_values.push(current_possible_value);
328+
}
329+
});
330+
if (prev_values.length < current_possible_values.size) {
331+
prev_values.push(' ');
332+
}
333+
334+
if (prev_values.length > 20) {
335+
// might grow exponentially, bail out
336+
return true;
337+
}
338+
}
339+
prev_values.forEach(prev_value => possible_values.add(prev_value));
340+
272341
if (possible_values.has(UNKNOWN)) return true;
273342

274343
for (const value of possible_values) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
export default {
2+
warnings: [
3+
{
4+
code: 'css-unused-selector',
5+
message: 'Unused CSS selector',
6+
frame:
7+
` 9: <style>
8+
10: .foo {color: red;}
9+
11: .fooaa {color: red;}
10+
^
11+
12: .foobb {color: red;}
12+
13: .foocc {color: red;}`,
13+
start: { line: 11, column: 2, character: 206 },
14+
end: { line: 11, column: 8, character: 212 },
15+
pos: 206,
16+
},
17+
{
18+
code: 'css-unused-selector',
19+
message: 'Unused CSS selector',
20+
frame:
21+
`10: .foo {color: red;}
22+
11: .fooaa {color: red;}
23+
12: .foobb {color: red;}
24+
^
25+
13: .foocc {color: red;}
26+
14: .foodd {color: red;}`,
27+
start: { line: 12, column: 2, character: 229 },
28+
end: { line: 12, column: 8, character: 235 },
29+
pos: 229,
30+
},
31+
{
32+
code: 'css-unused-selector',
33+
message: 'Unused CSS selector',
34+
frame:
35+
`12: .foobb {color: red;}
36+
13: .foocc {color: red;}
37+
14: .foodd {color: red;}
38+
^
39+
15: .aa {color: red;}
40+
16: .bb {color: red;}`,
41+
start: { line: 14, column: 2, character: 275 },
42+
end: { line: 14, column: 8, character: 281 },
43+
pos: 275,
44+
},
45+
{
46+
code: 'css-unused-selector',
47+
message: 'Unused CSS selector',
48+
frame:
49+
`18: .dd {color: red;}
50+
19: .aabar {color: red;}
51+
20: .bbbar {color: red;}
52+
^
53+
21: .ccbar {color: red;}
54+
22: .ddbar {color: red;}`,
55+
start: { line: 20, column: 2, character: 401 },
56+
end: { line: 20, column: 8, character: 407 },
57+
pos: 401,
58+
},
59+
{
60+
code: 'css-unused-selector',
61+
message: 'Unused CSS selector',
62+
frame:
63+
`19: .aabar {color: red;}
64+
20: .bbbar {color: red;}
65+
21: .ccbar {color: red;}
66+
^
67+
22: .ddbar {color: red;}
68+
23: .fooaabar {color: red;}`,
69+
start: { line: 21, column: 2, character: 424 },
70+
end: { line: 21, column: 8, character: 430 },
71+
pos: 424,
72+
},
73+
{
74+
code: 'css-unused-selector',
75+
message: 'Unused CSS selector',
76+
frame:
77+
`20: .bbbar {color: red;}
78+
21: .ccbar {color: red;}
79+
22: .ddbar {color: red;}
80+
^
81+
23: .fooaabar {color: red;}
82+
24: .foobbbar {color: red;}`,
83+
start: { line: 22, column: 2, character: 447 },
84+
end: { line: 22, column: 8, character: 453 },
85+
pos: 447,
86+
},
87+
{
88+
code: 'css-unused-selector',
89+
message: 'Unused CSS selector',
90+
frame:
91+
`21: .ccbar {color: red;}
92+
22: .ddbar {color: red;}
93+
23: .fooaabar {color: red;}
94+
^
95+
24: .foobbbar {color: red;}
96+
25: .fooccbar {color: red;}`,
97+
start: { line: 23, column: 2, character: 470 },
98+
end: { line: 23, column: 11, character: 479 },
99+
pos: 470,
100+
},
101+
{
102+
code: 'css-unused-selector',
103+
message: 'Unused CSS selector',
104+
frame:
105+
`22: .ddbar {color: red;}
106+
23: .fooaabar {color: red;}
107+
24: .foobbbar {color: red;}
108+
^
109+
25: .fooccbar {color: red;}
110+
26: .fooddbar {color: red;}`,
111+
start: { line: 24, column: 2, character: 496 },
112+
end: { line: 24, column: 11, character: 505 },
113+
pos: 496,
114+
},
115+
{
116+
code: 'css-unused-selector',
117+
message: 'Unused CSS selector',
118+
frame:
119+
`23: .fooaabar {color: red;}
120+
24: .foobbbar {color: red;}
121+
25: .fooccbar {color: red;}
122+
^
123+
26: .fooddbar {color: red;}
124+
27: .baz {color: red;}`,
125+
start: { line: 25, column: 2, character: 522 },
126+
end: { line: 25, column: 11, character: 531 },
127+
pos: 522,
128+
},
129+
{
130+
code: 'css-unused-selector',
131+
message: 'Unused CSS selector',
132+
frame:
133+
`26: .fooddbar {color: red;}
134+
27: .baz {color: red;}
135+
28: .unused {color: red;}
136+
^
137+
29: </style>`,
138+
start: { line: 28, column: 2, character: 595 },
139+
end: { line: 28, column: 9, character: 602 },
140+
pos: 595,
141+
},
142+
],
143+
};

test/css/samples/unused-selector-string-concat/expected.css

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<script>
2+
export let a, b, c;
3+
</script>
4+
5+
<div class="foo{a ? ' aa' : b ? ' bb ' : c ? 'cc ' : 'dd'}bar baz {a ? ' aa' : b ? ' bb ' : c ? 'cc ' : 'dd'}">
6+
some stuff
7+
</div>
8+
9+
<style>
10+
.foo {color: red;}
11+
.fooaa {color: red;}
12+
.foobb {color: red;}
13+
.foocc {color: red;}
14+
.foodd {color: red;}
15+
.aa {color: red;}
16+
.bb {color: red;}
17+
.cc {color: red;}
18+
.dd {color: red;}
19+
.aabar {color: red;}
20+
.bbbar {color: red;}
21+
.ccbar {color: red;}
22+
.ddbar {color: red;}
23+
.fooaabar {color: red;}
24+
.foobbbar {color: red;}
25+
.fooccbar {color: red;}
26+
.fooddbar {color: red;}
27+
.baz {color: red;}
28+
.unused {color: red;}
29+
</style>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export default {
2+
warnings: [],
3+
};

test/css/samples/unused-selector-ternary-bailed/expected.css

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<script>
2+
export let active;
3+
export let hover;
4+
</script>
5+
6+
<div class="thing {active ? 'active' : hover}">
7+
some stuff
8+
</div>
9+
10+
<style>
11+
.thing {color: blue;}
12+
.active {color: blue;}
13+
.thing.active {color: blue;}
14+
.hover { color: blue; }
15+
.hover.unused { color: blue; }
16+
17+
.unused {color: blue;}
18+
</style>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
export default {
2+
warnings: [
3+
{
4+
code: 'css-unused-selector',
5+
end: {
6+
character: 205,
7+
column: 9,
8+
line: 14,
9+
},
10+
frame: `
11+
12: .thing.active {color: blue;}
12+
13:
13+
14: .unused {color: blue;}
14+
^
15+
15: </style>`,
16+
message: 'Unused CSS selector',
17+
pos: 198,
18+
start: {
19+
character: 198,
20+
column: 2,
21+
line: 14,
22+
},
23+
},
24+
],
25+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
.thing.svelte-xyz{color:blue}.active.svelte-xyz{color:blue}.thing.active.svelte-xyz{color:blue}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<script>
2+
export let active;
3+
</script>
4+
5+
<div class="thing {active ? 'active' : ''}">
6+
some stuff
7+
</div>
8+
9+
<style>
10+
.thing {color: blue;}
11+
.active {color: blue;}
12+
.thing.active {color: blue;}
13+
14+
.unused {color: blue;}
15+
</style>

0 commit comments

Comments
 (0)