33
comments: (t.CommentBlock | t.CommentLine)[];
34
};
35
36
-function hasUseForgetDirective(directive: t.Directive): boolean {
37
- return directive.value.value === "use forget";
38
-}
39
-
36
function hasAnyUseForgetDirectives(directives: t.Directive[]): boolean {
37
for (const directive of directives) {
42
- if (hasUseForgetDirective(directive)) {
38
+ if (directive.value.value === "use forget") {
39
return true;
40
}
41
}
50
}
51
return false;
52
}
53
+function handleError(pass: CompilerPass, err: unknown): void {
54
+ if (pass.opts.logger && err) {
55
+ pass.opts.logger.logEvent("err", err);
56
+ }
57
+ /** Always throw if the flag is enabled, otherwise we only throw if the error is critical
58
+ * (eg an invariant is broken, meaning the compiler may be buggy). See
59
+ * {@link CompilerError.isCritical} for mappings.
60
+ * */
61
+ if (
62
+ pass.opts.panicOnBailout ||
63
+ !(err instanceof CompilerError) ||
64
+ (err instanceof CompilerError && err.isCritical())
65
+ ) {
66
+ throw err;
67
+ } else {
68
+ if (pass.opts.isDev) {
69
+ log(err, pass.filename ?? null);
70
+ }
71
+ }
72
+}
73
74
/**
75
* Runs the Compiler pipeline and mutates the source AST to include the newly compiled function.
89
try {
90
compiledFn = compileFn(fnPath, pass.opts.environment);
91
} catch (err) {
76
- if (pass.opts.logger && err) {
77
- pass.opts.logger.logEvent("err", err);
78
- }
79
- /** Always throw if the flag is enabled, otherwise we only throw if the error is critical
80
- * (eg an invariant is broken, meaning the compiler may be buggy). See
81
- * {@link CompilerError.isCritical} for mappings.
82
- * */
83
- if (
84
- pass.opts.panicOnBailout ||
85
- !(err instanceof CompilerError) ||
86
- (err instanceof CompilerError && err.isCritical())
87
- ) {
88
- throw err;
89
- } else {
90
- if (pass.opts.isDev) {
91
- log(err, pass.filename ?? null);
92
- }
93
- }
92
+ handleError(pass, err);
93
return false;
94
}
95
185
}
186
}
187
189
-// This is a hack to work around what seems to be a Babel bug. Babel doesn't
190
-// consistently respect the `skip()` function to avoid revisiting a node within
191
-// a pass, so we use this set to track nodes that we have compiled.
192
-const ALREADY_COMPILED: WeakSet<object> | Set<object> = new (WeakSet ?? Set)();
193
-
194
-export function compileProgram(
195
- program: NodePath<t.Program>,
196
- pass: CompilerPass
197
-): void {
198
- const options = parsePluginOptions(pass.opts);
188
+function findEslintSuppressions(
189
+ fileComments: Array<t.CommentBlock | t.CommentLine>
190
+): CompilerError | null {
191
const violations = [];
200
- const fileComments = pass.comments;
201
- let hasForgetMutatedOriginalSource: boolean = false;
202
- let fileHasUseForgetDirective = false;
192
193
if (Array.isArray(fileComments)) {
194
for (const comment of fileComments) {
203
}
204
205
if (violations.length > 0) {
217
- program.traverse({
218
- Directive(directive) {
219
- if (hasUseForgetDirective(directive.node)) {
220
- fileHasUseForgetDirective = true;
221
- }
222
- },
223
- });
224
-
206
const reason =
207
"React Forget has bailed out of optimizing this component as one or more React eslint rules were disabled. React Forget only works when your components follow all the rules of React, disabling them may result in undefined behavior";
208
const error = new CompilerError();
209
for (const violation of violations) {
229
- if (options.logger != null) {
230
- options.logger.logEvent("err", {
231
- reason,
232
- filename: pass.filename,
233
- violation,
234
- });
235
- }
236
-
210
error.pushErrorDetail(
211
new CompilerErrorDetail({
212
reason,
223
})
224
);
225
}
226
+ return error;
227
+ } else {
228
+ return null;
229
+ }
230
+}
231
254
- if (fileHasUseForgetDirective) {
255
- if (options.panicOnBailout || error.isCritical()) {
256
- throw error;
257
- } else {
258
- if (options.isDev) {
259
- log(error, pass.filename ?? null);
260
- }
261
- }
262
- }
232
+// This is a hack to work around what seems to be a Babel bug. Babel doesn't
233
+// consistently respect the `skip()` function to avoid revisiting a node within
234
+// a pass, so we use this set to track nodes that we have compiled.
235
+const ALREADY_COMPILED: WeakSet<object> | Set<object> = new (WeakSet ?? Set)();
236
264
- return;
265
- }
237
+export function compileProgram(
238
+ program: NodePath<t.Program>,
239
+ pass: CompilerPass
240
+): void {
241
+ const options = parsePluginOptions(pass.opts);
242
+ const lintError = findEslintSuppressions(pass.comments);
243
+ let hasForgetMutatedOriginalSource: boolean = false;
244
245
// Main traversal to compile with Forget
246
program.traverse(
265
): void {
266
if (!shouldVisitNode(fn, pass)) {
267
return;
290
- }
291
-
292
- if (compileAndInsertNewFunctionDeclaration(fn, pass) === true) {
293
- hasForgetMutatedOriginalSource = true;
268
+ } else if (lintError != null) {
269
+ handleError(pass, lintError);
270
+ } else {
271
+ const hasMutated = compileAndInsertNewFunctionDeclaration(fn, pass);
272
+ hasForgetMutatedOriginalSource ||= hasMutated;
273
}
274
},
275
279
): void {
280
if (!shouldVisitNode(fn, pass)) {
281
return;
303
- }
304
-
305
- if (compileAndInsertNewFunctionDeclaration(fn, pass) === true) {
306
- hasForgetMutatedOriginalSource = true;
282
+ } else if (lintError != null) {
283
+ handleError(pass, lintError);
284
+ } else {
285
+ const hasMutated = compileAndInsertNewFunctionDeclaration(fn, pass);
286
+ hasForgetMutatedOriginalSource ||= hasMutated;
287
}
288
},
289
293
): void {
294
if (!shouldVisitNode(fn, pass)) {
295
return;
316
- }
317
-
318
- if (compileAndInsertNewFunctionDeclaration(fn, pass) === true) {
319
- hasForgetMutatedOriginalSource = true;
296
+ } else if (lintError != null) {
297
+ handleError(pass, lintError);
298
+ } else {
299
+ const hasMutated = compileAndInsertNewFunctionDeclaration(fn, pass);
300
+ hasForgetMutatedOriginalSource ||= hasMutated;
301
}
302
},
303
},