-
Notifications
You must be signed in to change notification settings - Fork 38
/
Copy pathrenameLabels.ts
176 lines (153 loc) · 5.5 KB
/
renameLabels.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
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
import * as t from "@babel/types";
import { NodePath } from "@babel/traverse";
import { PluginArg, PluginObject } from "./plugin";
import { Order } from "../order";
import { NameGen } from "../utils/NameGen";
import { ok } from "assert";
import { computeProbabilityMap } from "../probability";
const LABEL = Symbol("label");
interface LabelInterface {
label?: string;
renamed?: string;
removed: boolean;
required: boolean;
paths: NodePath<t.BreakStatement | t.ContinueStatement>[];
}
interface NodeLabel {
[LABEL]?: LabelInterface;
}
export default function ({ Plugin }: PluginArg): PluginObject {
const me = Plugin(Order.RenameLabels, {
changeData: {
labelsRenamed: 0,
labelsRemoved: 0,
},
});
return {
visitor: {
Program(path) {
const allLabelInterfaces: LabelInterface[] = [];
// First pass: Collect all label usages
path.traverse({
LabeledStatement(labelPath) {
const labelInterface = {
label: labelPath.node.label.name,
removed: false,
required: false,
paths: [],
};
allLabelInterfaces.push(labelInterface);
(labelPath.node as NodeLabel)[LABEL] = labelInterface;
},
"BreakStatement|ContinueStatement"(_path) {
const path = _path as NodePath<
t.BreakStatement | t.ContinueStatement
>;
if (path.node.label) {
const labelName = path.node.label.name;
let targets: NodePath<
t.For | t.While | t.BlockStatement | t.SwitchStatement
>[] = [];
let onlySearchLoops = path.isContinueStatement();
let currentPath: NodePath = path;
while (currentPath) {
if (
currentPath.isFor() ||
currentPath.isWhile() ||
currentPath.isSwitchStatement()
) {
targets.push(currentPath);
}
if (
currentPath.isBlockStatement() &&
currentPath.parentPath.isLabeledStatement()
) {
targets.push(currentPath);
}
currentPath = currentPath.parentPath;
}
const target = targets.find(
(label) =>
label.parentPath &&
label.parentPath.isLabeledStatement() &&
label.parentPath.node.label.name === labelName
);
if (onlySearchLoops) {
// Remove BlockStatements and SwitchStatements from the list of targets
// a continue statement only target loops
// This helps remove unnecessary labels when a continue is nested with a block statement
// ex: for-loop with if-statement continue
targets = targets.filter(
(target) =>
!target.isBlockStatement() && !target.isSwitchStatement()
);
}
ok(target);
const isRequired =
target.isBlockStatement() || targets[0] !== target;
const labelInterface = (target.parentPath.node as NodeLabel)[
LABEL
];
if (isRequired) {
labelInterface.required = true;
} else {
// Label is not required here, remove it for this particular break/continue statement
path.node.label = null;
}
if (!labelInterface.paths) {
labelInterface.paths = [];
}
labelInterface.paths.push(path);
}
},
});
const nameGen = new NameGen(me.options.identifierGenerator);
for (var labelInterface of allLabelInterfaces) {
const isRequired = labelInterface.required;
if (isRequired) {
var newName = labelInterface.label;
if (
computeProbabilityMap(
me.options.renameLabels,
labelInterface.label
)
) {
newName = nameGen.generate();
}
labelInterface.renamed = newName;
me.changeData.labelsRenamed++;
} else {
labelInterface.removed = true;
me.changeData.labelsRemoved++;
}
}
// Second pass: Rename labels and remove unused ones
path.traverse({
LabeledStatement(labelPath) {
const labelInterface = (labelPath.node as NodeLabel)[LABEL];
if (labelInterface) {
// Remove label but replace it with its body
if (labelInterface.removed) {
labelPath.replaceWith(labelPath.node.body);
}
// Else keep the label but rename it
if (typeof labelInterface.renamed === "string") {
labelPath.node.label.name = labelInterface.renamed;
}
// Update all break/continue statements
for (var breakPath of labelInterface.paths) {
// Remove label from break/continue statement
if (labelInterface.removed) {
breakPath.node.label = null;
} else {
// Update label name
breakPath.node.label = t.identifier(labelInterface.renamed);
}
}
}
},
});
},
},
};
}