Skip to content

Commit d666dc5

Browse files
committed
validate source locations
1 parent fe81314 commit d666dc5

File tree

7 files changed

+409
-14
lines changed

7 files changed

+409
-14
lines changed

compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ import {
8888
validateNoRefAccessInRender,
8989
validateNoSetStateInRender,
9090
validatePreservedManualMemoization,
91+
validateSourceLocations,
9192
validateUseMemo,
9293
} from '../Validation';
9394
import {validateLocalsNotReassignedAfterRender} from '../Validation/ValidateLocalsNotReassignedAfterRender';
@@ -574,6 +575,10 @@ function runWithEnvironment(
574575
log({kind: 'ast', name: 'Codegen (outlined)', value: outlined.fn});
575576
}
576577

578+
if (env.config.validateSourceLocations) {
579+
validateSourceLocations(ast).unwrap();
580+
}
581+
577582
/**
578583
* This flag should be only set for unit / fixture tests to check
579584
* that Forget correctly handles unexpected errors (e.g. exceptions

compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -369,6 +369,13 @@ export const EnvironmentConfigSchema = z.object({
369369
*/
370370
validateNoFreezingKnownMutableFunctions: z.boolean().default(false),
371371

372+
/**
373+
* Validates that all AST nodes generated during codegen have proper source locations.
374+
* This is useful for debugging issues with source maps and Istanbul coverage.
375+
* When enabled, the compiler will error if any AST node is missing a location.
376+
*/
377+
validateSourceLocations: z.boolean().default(false),
378+
372379
/*
373380
* When enabled, the compiler assumes that hooks follow the Rules of React:
374381
* - Hooks may memoize computation based on any of their parameters, thus
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
/**
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
import * as t from '@babel/types';
9+
import {CompilerError, ErrorSeverity} from '..';
10+
import {CodegenFunction} from '../ReactiveScopes';
11+
import {Result} from '../Utils/Result';
12+
13+
/**
14+
* Validates that all AST nodes have proper source locations.
15+
* This is useful for debugging issues with source maps and Istanbul coverage.
16+
*/
17+
export function validateSourceLocations(
18+
ast: CodegenFunction,
19+
): Result<void, CompilerError> {
20+
const errors = new CompilerError();
21+
const missingLocations: Array<{node: t.Node; path: string}> = [];
22+
23+
function checkNode(node: t.Node, path: string): void {
24+
if (node.loc == null) {
25+
missingLocations.push({node, path});
26+
}
27+
28+
// Recursively check all child nodes
29+
for (const [key, value] of Object.entries(node)) {
30+
if (value && typeof value === 'object') {
31+
if (Array.isArray(value)) {
32+
// Handle arrays of nodes
33+
for (let i = 0; i < value.length; i++) {
34+
const item = value[i];
35+
if (item && typeof item === 'object' && 'type' in item) {
36+
checkNode(item as t.Node, `${path}.${key}[${i}]`);
37+
}
38+
}
39+
} else if ('type' in value) {
40+
// Handle single node
41+
checkNode(value as t.Node, `${path}.${key}`);
42+
}
43+
}
44+
}
45+
}
46+
47+
// Check the main function's AST components
48+
if (ast.id) {
49+
checkNode(ast.id, 'ast.id');
50+
}
51+
52+
// Check parameters
53+
for (let i = 0; i < ast.params.length; i++) {
54+
checkNode(ast.params[i], `ast.params[${i}]`);
55+
}
56+
57+
// Check body
58+
checkNode(ast.body, 'ast.body');
59+
60+
// Check outlined functions
61+
for (let i = 0; i < ast.outlined.length; i++) {
62+
const outlined = ast.outlined[i];
63+
if (outlined.fn.id) {
64+
checkNode(outlined.fn.id, `ast.outlined[${i}].fn.id`);
65+
}
66+
for (let j = 0; j < outlined.fn.params.length; j++) {
67+
checkNode(outlined.fn.params[j], `ast.outlined[${i}].fn.params[${j}]`);
68+
}
69+
checkNode(outlined.fn.body, `ast.outlined[${i}].fn.body`);
70+
}
71+
72+
// Report errors for missing locations
73+
for (const {node, path} of missingLocations) {
74+
errors.push({
75+
severity: ErrorSeverity.Todo,
76+
reason: 'AST node is missing source location',
77+
description: `Node at path "${path}" (type: ${node.type}) is missing a location. This can cause issues with source maps and coverage tools.`,
78+
loc: null,
79+
suggestions: null,
80+
});
81+
}
82+
83+
return errors.asResult();
84+
}

compiler/packages/babel-plugin-react-compiler/src/Validation/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,5 @@ export {validateNoCapitalizedCalls} from './ValidateNoCapitalizedCalls';
1212
export {validateNoRefAccessInRender} from './ValidateNoRefAccessInRender';
1313
export {validateNoSetStateInRender} from './ValidateNoSetStateInRender';
1414
export {validatePreservedManualMemoization} from './ValidatePreservedManualMemoization';
15+
export {validateSourceLocations} from './ValidateSourceLocations';
1516
export {validateUseMemo} from './ValidateUseMemo';

0 commit comments

Comments
 (0)