@samitouri / QOS-React / commits / 6ffcac8558

[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