[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.
This commit is contained in:
Joseph Savona 2025-08-15 15:09:27 -07:00 committed by GitHub
parent 724b324b96
commit 6ffcac8558
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 24 additions and 18 deletions

View File

@ -58,11 +58,15 @@ export type CompilerDiagnosticDetail =
/** /**
* A/the source of the error * A/the source of the error
*/ */
{ | {
kind: 'error'; kind: 'error';
loc: SourceLocation | null; loc: SourceLocation | null;
message: string; message: string;
}; }
| {
kind: 'hint';
message: string;
};
export enum CompilerSuggestionOperation { export enum CompilerSuggestionOperation {
InsertBefore, InsertBefore,
@ -134,7 +138,12 @@ export class CompilerDiagnostic {
} }
primaryLocation(): SourceLocation | null { primaryLocation(): SourceLocation | null {
return this.options.details.filter(d => d.kind === 'error')[0]?.loc ?? null; const firstErrorDetail = this.options.details.filter(
d => d.kind === 'error',
)[0];
return firstErrorDetail != null && firstErrorDetail.kind === 'error'
? firstErrorDetail.loc
: null;
} }
printErrorMessage(source: string, options: PrintErrorMessageOptions): string { printErrorMessage(source: string, options: PrintErrorMessageOptions): string {
@ -167,9 +176,14 @@ export class CompilerDiagnostic {
buffer.push(codeFrame); buffer.push(codeFrame);
break; break;
} }
case 'hint': {
buffer.push('\n\n');
buffer.push(detail.message);
break;
}
default: { default: {
assertExhaustive( assertExhaustive(
detail.kind, detail,
`Unexpected detail kind ${(detail as any).kind}`, `Unexpected detail kind ${(detail as any).kind}`,
); );
} }

View File

@ -471,8 +471,7 @@ function applySignature(
effect.reason?.kind === 'AssignCurrentProperty' effect.reason?.kind === 'AssignCurrentProperty'
) { ) {
diagnostic.withDetail({ diagnostic.withDetail({
kind: 'error', kind: 'hint',
loc: effect.value.loc,
message: `Hint: If this value is a Ref (value returned by \`useRef()\`), rename the variable to end in "Ref".`, message: `Hint: If this value is a Ref (value returned by \`useRef()\`), rename the variable to end in "Ref".`,
}); });
} }
@ -1096,8 +1095,7 @@ function applyEffect(
effect.reason?.kind === 'AssignCurrentProperty' effect.reason?.kind === 'AssignCurrentProperty'
) { ) {
diagnostic.withDetail({ diagnostic.withDetail({
kind: 'error', kind: 'hint',
loc: effect.value.loc,
message: `Hint: If this value is a Ref (value returned by \`useRef()\`), rename the variable to end in "Ref".`, message: `Hint: If this value is a Ref (value returned by \`useRef()\`), rename the variable to end in "Ref".`,
}); });
} }

View File

@ -30,13 +30,7 @@ Modifying a value returned from a hook is not allowed. Consider moving the modif
7 | } 7 | }
8 | 8 |
3 | component Foo() { Hint: If this value is a Ref (value returned by `useRef()`), rename the variable to end in "Ref".
4 | const foo = useFoo();
> 5 | foo.current = true;
| ^^^ Hint: If this value is a Ref (value returned by `useRef()`), rename the variable to end in "Ref".
6 | return <div />;
7 | }
8 |
``` ```