[compiler] Add support for diagnostic hints (#34126)
Hints are meant as additional information to present to the developer about an error. The first use-case here is for the suggestion to name refs with "-Ref" if we encounter a mutation that looks like it might be a ref. The original error printing used a second error detail which printed the source code twice, a hint with just extra text is less noisy.
Joseph Savona committed
Aug 15, 2025 at 15:09 UTC
6ffcac8558efbd204c7df5d52787a90e507dd8d7
3 files changed
+24
-18
compiler/packages/babel-plugin-react-compiler/src/CompilerError.ts
+21
-7
@@ -58,11 +58,15 @@ export type CompilerDiagnosticDetail =
58
/**
59
* A/the source of the error
60
*/
61
- {
62
- kind: 'error';
63
- loc: SourceLocation | null;
64
- message: string;
65
- };
61
+ | {
62
+ kind: 'error';
63
+ loc: SourceLocation | null;
64
+ message: string;
65
+ }
66
+ | {
67
+ kind: 'hint';
68
+ message: string;
69
+ };
70
71
export enum CompilerSuggestionOperation {
72
InsertBefore,
@@ -134,7 +138,12 @@ export class CompilerDiagnostic {
138
}
139
140
primaryLocation(): SourceLocation | null {
137
- return this.options.details.filter(d => d.kind === 'error')[0]?.loc ?? null;
141
+ const firstErrorDetail = this.options.details.filter(
142
+ d => d.kind === 'error',
143
+ )[0];
144
+ return firstErrorDetail != null && firstErrorDetail.kind === 'error'
145
+ ? firstErrorDetail.loc
146
+ : null;
147
}
148
149
printErrorMessage(source: string, options: PrintErrorMessageOptions): string {
@@ -167,9 +176,14 @@ export class CompilerDiagnostic {
176
buffer.push(codeFrame);
177
break;
178
}
179
+ case 'hint': {
180
+ buffer.push('\n\n');
181
+ buffer.push(detail.message);
182
+ break;
183
+ }
184
default: {
185
assertExhaustive(
172
- detail.kind,
186
+ detail,
187
`Unexpected detail kind ${(detail as any).kind}`,
188
);
189
}
compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutationAliasingEffects.ts
+2
-4
@@ -471,8 +471,7 @@ function applySignature(
471
effect.reason?.kind === 'AssignCurrentProperty'
472
) {
473
diagnostic.withDetail({
474
- kind: 'error',
475
- loc: effect.value.loc,
474
+ kind: 'hint',
475
message: `Hint: If this value is a Ref (value returned by \`useRef()\`), rename the variable to end in "Ref".`,
476
});
477
}
@@ -1096,8 +1095,7 @@ function applyEffect(
1095
effect.reason?.kind === 'AssignCurrentProperty'
1096
) {
1097
diagnostic.withDetail({
1099
- kind: 'error',
1100
- loc: effect.value.loc,
1098
+ kind: 'hint',
1099
message: `Hint: If this value is a Ref (value returned by \`useRef()\`), rename the variable to end in "Ref".`,
1100
});
1101
}
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-assing-to-ref-current-in-render.expect.md
+1
-7
@@ -30,13 +30,7 @@ Modifying a value returned from a hook is not allowed. Consider moving the modif
30
7 | }
31
8 |
32
33
- 3 | component Foo() {
34
- 4 | const foo = useFoo();
35
-> 5 | foo.current = true;
36
- | ^^^ Hint: If this value is a Ref (value returned by `useRef()`), rename the variable to end in "Ref".
37
- 6 | return <div />;
38
- 7 | }
39
- 8 |
33
+Hint: If this value is a Ref (value returned by `useRef()`), rename the variable to end in "Ref".
34
```
35
36
\ No newline at end of file