Skip to content

Commit 9c1f097

Browse files
authored
[compiler] Restore code frames in ESLint compiler error messages (react#36901)
## Summary The Rust port (react#36173) changed `CompileError` `LoggerEvent`s to carry plain serialized detail objects instead of `CompilerError`/`CompilerDiagnostic` class instances. As a result, the ESLint integrations could no longer call `detail.printErrorMessage(source, {eslint: true})` and were given a replacement `printErrorMessage()` helper that only emitted the `reason` and `description`. This regressed error printing: the **source code frame(s) and `file:line:column` location** that used to appear for each error detail were dropped from lint output. This PR restores the previous behaviour: - Export `printCodeFrame` from `CompilerError` and reuse it from both ESLint integrations instead of duplicating it. - Rebuild the full message (reason, description, per-detail code frames, and hints) in `printErrorMessage`. - Handle **both** detail shapes that flow through `LoggerEvent`s: - a `details` array (`CompilerDiagnostic` and the **Rust** compiler), and - a legacy flat `loc` (deprecated `CompilerErrorDetail`). `formatDetailForLogging` emits one or the other, so the previous unconditional iteration over `error.details` would have thrown `TypeError: not iterable` on the flat-`loc` path. Normalizing to a list fixes that and keeps the code working with both the TypeScript and Rust compilers. ## Test plan - `tsc --noEmit` on both ESLint packages is clean (no new errors vs. baseline). - Verified the detail loop handles the Rust compiler's `details` array shape and the legacy flat `loc` shape.
1 parent d4e4454 commit 9c1f097

3 files changed

Lines changed: 85 additions & 11 deletions

File tree

compiler/packages/babel-plugin-react-compiler/src/CompilerError.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -522,7 +522,7 @@ export class CompilerError extends Error {
522522
}
523523
}
524524

525-
function printCodeFrame(
525+
export function printCodeFrame(
526526
source: string,
527527
loc: t.SourceLocation,
528528
message: string,

compiler/packages/eslint-plugin-react-compiler/src/rules/ReactCompilerRule.ts

Lines changed: 42 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
ErrorSeverity,
1717
LintRulePreset,
1818
LintRules,
19+
printCodeFrame,
1920
type LintRule,
2021
} from 'babel-plugin-react-compiler/src/CompilerError';
2122

@@ -43,10 +44,46 @@ function primaryLocation(
4344
* Format an error message from a CompileErrorDetail, matching the old
4445
* CompilerErrorDetail.printErrorMessage() / CompilerDiagnostic.printErrorMessage() behavior.
4546
*/
46-
function printErrorMessage(detail: CompileErrorDetail): string {
47-
const buffer = [`[ReactCompilerError] ${detail.reason}`];
48-
if (detail.description != null) {
49-
buffer.push(`\n\n${detail.description}.`);
47+
function printErrorMessage(source: string, error: CompileErrorDetail): string {
48+
const buffer = [`[ReactCompilerError] ${error.reason}`];
49+
if (error.description != null) {
50+
buffer.push(`\n\n${error.description}.`);
51+
}
52+
/*
53+
* A CompileError's source location(s) may be provided either as a `details`
54+
* array (CompilerDiagnostic and the Rust compiler) or as a single flat `loc`
55+
* (legacy CompilerErrorDetail). Normalize to a list so both shapes render
56+
* code frames identically.
57+
*/
58+
const details =
59+
error.details ??
60+
(error.loc != null
61+
? [{kind: 'error' as const, loc: error.loc, message: error.reason}]
62+
: []);
63+
for (const detail of details) {
64+
if (detail.kind === 'error') {
65+
const loc = detail.loc;
66+
if (loc == null || typeof loc === 'symbol') {
67+
continue;
68+
}
69+
let codeFrame: string;
70+
try {
71+
codeFrame = printCodeFrame(source, loc, detail.message ?? '');
72+
} catch {
73+
codeFrame = detail.message ?? '';
74+
}
75+
buffer.push('\n\n');
76+
if (loc.filename != null) {
77+
// ESLint uses 1-indexed columns
78+
buffer.push(
79+
`${loc.filename}:${loc.start.line}:${loc.start.column + 1}\n`,
80+
);
81+
}
82+
buffer.push(codeFrame);
83+
} else if (detail.kind === 'hint') {
84+
buffer.push('\n\n');
85+
buffer.push(detail.message ?? '');
86+
}
5087
}
5188
return buffer.join('');
5289
}
@@ -161,7 +198,7 @@ function makeRule(rule: LintRule): Rule.RuleModule {
161198
* we should deduplicate them with a "reported" set
162199
*/
163200
context.report({
164-
message: printErrorMessage(detail),
201+
message: printErrorMessage(result.sourceCode, detail),
165202
loc,
166203
suggest: makeSuggestions(detail),
167204
});

packages/eslint-plugin-react-hooks/src/shared/ReactCompiler.ts

Lines changed: 42 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
LintRulePreset,
1717
} from 'babel-plugin-react-compiler';
1818
import type {CompileErrorDetail} from 'babel-plugin-react-compiler/src/Entrypoint';
19+
import {printCodeFrame} from 'babel-plugin-react-compiler/src/CompilerError';
1920
import {type Linter, type Rule} from 'eslint';
2021
import runReactCompiler, {RunCacheEntry} from './RunReactCompiler';
2122

@@ -42,10 +43,46 @@ function primaryLocation(
4243
/**
4344
* Format an error message from a CompileErrorDetail.
4445
*/
45-
function printErrorMessage(detail: CompileErrorDetail): string {
46-
const buffer = [`[ReactCompilerError] ${detail.reason}`];
47-
if (detail.description != null) {
48-
buffer.push(`\n\n${detail.description}.`);
46+
function printErrorMessage(source: string, error: CompileErrorDetail): string {
47+
const buffer = [`[ReactCompilerError] ${error.reason}`];
48+
if (error.description != null) {
49+
buffer.push(`\n\n${error.description}.`);
50+
}
51+
/*
52+
* A CompileError's source location(s) may be provided either as a `details`
53+
* array (CompilerDiagnostic and the Rust compiler) or as a single flat `loc`
54+
* (legacy CompilerErrorDetail). Normalize to a list so both shapes render
55+
* code frames identically.
56+
*/
57+
const details =
58+
error.details ??
59+
(error.loc != null
60+
? [{kind: 'error' as const, loc: error.loc, message: error.reason}]
61+
: []);
62+
for (const detail of details) {
63+
if (detail.kind === 'error') {
64+
const loc = detail.loc;
65+
if (loc == null || typeof loc === 'symbol') {
66+
continue;
67+
}
68+
let codeFrame: string;
69+
try {
70+
codeFrame = printCodeFrame(source, loc, detail.message ?? '');
71+
} catch {
72+
codeFrame = detail.message ?? '';
73+
}
74+
buffer.push('\n\n');
75+
if (loc.filename != null) {
76+
// ESLint uses 1-indexed columns
77+
buffer.push(
78+
`${loc.filename}:${loc.start.line}:${loc.start.column + 1}\n`,
79+
);
80+
}
81+
buffer.push(codeFrame);
82+
} else if (detail.kind === 'hint') {
83+
buffer.push('\n\n');
84+
buffer.push(detail.message ?? '');
85+
}
4986
}
5087
return buffer.join('');
5188
}
@@ -160,7 +197,7 @@ function makeRule(rule: LintRule): Rule.RuleModule {
160197
* we should deduplicate them with a "reported" set
161198
*/
162199
context.report({
163-
message: printErrorMessage(detail),
200+
message: printErrorMessage(result.sourceCode, detail),
164201
loc,
165202
suggest: makeSuggestions(detail),
166203
});

0 commit comments

Comments
 (0)