/
VariablesStructure.ts
102 lines (90 loc) · 2.89 KB
/
VariablesStructure.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
import * as postcss from "postcss";
import { matchAll } from "./index";
import { StringRegExpArray } from "./types";
class VariableNode {
public nodes: VariableNode[] = [];
public value: postcss.Declaration;
public isUsed = false;
constructor(declaration: postcss.Declaration) {
this.value = declaration;
}
}
class VariablesStructure {
public nodes: Map<string, VariableNode> = new Map();
public usedVariables: Set<string> = new Set();
public safelist: StringRegExpArray = [];
addVariable(declaration: postcss.Declaration): void {
const { prop } = declaration;
if (!this.nodes.has(prop)) {
const node = new VariableNode(declaration);
this.nodes.set(prop, node);
}
}
addVariableUsage(
declaration: postcss.Declaration,
matchedVariables: RegExpMatchArray[]
): void {
const { prop } = declaration;
const node = this.nodes.get(prop);
for (const variableMatch of matchedVariables) {
// capturing group containing the variable is in index 1
const variableName = variableMatch[1];
if (this.nodes.has(variableName)) {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const usedVariableNode = this.nodes.get(variableName)!;
node?.nodes.push(usedVariableNode);
}
}
}
addVariableUsageInProperties(matchedVariables: RegExpMatchArray[]): void {
for (const variableMatch of matchedVariables) {
// capturing group containing the variable is in index 1
const variableName = variableMatch[1];
this.usedVariables.add(variableName);
}
}
setAsUsed(variableName: string): void {
const node = this.nodes.get(variableName);
const queue = [node];
while (queue.length !== 0) {
const currentNode = queue.pop();
if (currentNode && !currentNode.isUsed) {
currentNode.isUsed = true;
queue.push(...currentNode.nodes);
}
}
}
removeUnused(): void {
// check unordered usage
for (const used of this.usedVariables) {
const usedNode = this.nodes.get(used);
if (usedNode) {
const usedVariablesMatchesInDeclaration = matchAll(
usedNode.value.value,
/var\((.+?)[,)]/g
);
usedVariablesMatchesInDeclaration.forEach((usage) => {
if (!this.usedVariables.has(usage[1])) {
this.usedVariables.add(usage[1]);
}
});
}
}
for (const used of this.usedVariables) {
this.setAsUsed(used);
}
for (const [name, declaration] of this.nodes) {
if (!declaration.isUsed && !this.isVariablesSafelisted(name)) {
declaration.value.remove();
}
}
}
isVariablesSafelisted(variable: string): boolean {
return this.safelist.some((safelistItem) => {
return typeof safelistItem === "string"
? safelistItem === variable
: safelistItem.test(variable);
});
}
}
export default VariablesStructure;