main
md 199 lines 6.16 KB
Rendered Raw
1 # validateSourceLocations
2
3 ## File
4 `src/Validation/ValidateSourceLocations.ts`
5
6 ## Purpose
7 **IMPORTANT: This validation is intended for unit tests only, not production use.**
8
9 Validates that important source locations from the original code are preserved in the generated AST. This ensures that code coverage instrumentation tools (like Istanbul) can properly map back to the original source code for accurate coverage reports.
10
11 ## Input Invariants
12 - Operates on the original Babel AST (`NodePath<FunctionDeclaration | ArrowFunctionExpression | FunctionExpression>`)
13 - Operates on the generated CodegenFunction output
14 - Must run after code generation
15
16 ## Validation Rules
17 The pass checks that "important" source locations (as defined by Istanbul's instrumentation requirements) are preserved in the generated output.
18
19 ### Two types of errors:
20
21 1. **Missing location:**
22 ```
23 Important source location missing in generated code. Source location for [NodeType]
24 is missing in the generated output. This can cause coverage instrumentation to fail
25 to track this code properly, resulting in inaccurate coverage reports.
26 ```
27
28 2. **Wrong node type:**
29 ```
30 Important source location has wrong node type in generated code. Source location for
31 [ExpectedType] exists in the generated output but with wrong node type(s): [ActualTypes].
32 This can cause coverage instrumentation to fail to track this code properly.
33 ```
34
35 ### Important Node Types
36 The following node types are considered important for coverage tracking:
37 ```typescript
38 const IMPORTANT_INSTRUMENTED_TYPES = new Set([
39 'ArrowFunctionExpression',
40 'AssignmentPattern',
41 'ObjectMethod',
42 'ExpressionStatement',
43 'BreakStatement',
44 'ContinueStatement',
45 'ReturnStatement',
46 'ThrowStatement',
47 'TryStatement',
48 'VariableDeclarator',
49 'IfStatement',
50 'ForStatement',
51 'ForInStatement',
52 'ForOfStatement',
53 'WhileStatement',
54 'DoWhileStatement',
55 'SwitchStatement',
56 'SwitchCase',
57 'WithStatement',
58 'FunctionDeclaration',
59 'FunctionExpression',
60 'LabeledStatement',
61 'ConditionalExpression',
62 'LogicalExpression',
63 'VariableDeclaration',
64 'Identifier',
65 ]);
66 ```
67
68 ### Strict Node Types
69 For these types, both the location AND node type must match:
70 - `VariableDeclaration`
71 - `VariableDeclarator`
72 - `Identifier`
73
74 ## Algorithm
75
76 ### Step 1: Collect Important Original Locations
77 Traverse the original AST and collect locations from nodes whose types are in `IMPORTANT_INSTRUMENTED_TYPES`:
78 - Skip nodes that are manual memoization calls (`useMemo`/`useCallback`) since the compiler intentionally removes these
79 - Build a map from location key to `{loc, nodeTypes}`
80
81 ### Step 2: Collect Generated Locations
82 Recursively traverse the generated AST (main function body + outlined functions) and collect all locations with their node types.
83
84 ### Step 3: Validate Preservation
85 For each important original location:
86 - If the location is completely missing in generated output, report an error
87 - For strict node types, verify the specific node type is present
88 - Handle cases where a generated location has a different node type
89
90 ### Location Key Format
91 Locations are compared using a string key:
92 ```typescript
93 function locationKey(loc: SourceLocation): string {
94 return `${loc.start.line}:${loc.start.column}-${loc.end.line}:${loc.end.column}`;
95 }
96 ```
97
98 ## Edge Cases
99
100 ### Manual Memoization Removal
101 The compiler intentionally removes `useMemo` and `useCallback` calls (replacing them with compiler-generated memoization). These are detected and exempted from validation:
102 ```typescript
103 function isManualMemoization(node: Node): boolean {
104 // Checks for useMemo/useCallback or React.useMemo/React.useCallback
105 }
106 ```
107
108 ### Outlined Functions
109 The validation also checks locations in outlined functions (functions extracted by the compiler for optimization purposes).
110
111 ### Multiple Node Types at Same Location
112 Multiple node types can share the same location (e.g., a `VariableDeclarator` and its `Identifier` child). The pass tracks all node types for each location.
113
114 ## TODOs
115 From the file documentation:
116 > There's one big gotcha with this validation: it only works if the "important" original nodes are not optimized away by the compiler.
117 >
118 > When that scenario happens, we should just update the fixture to not include a node that has no corresponding node in the generated AST due to being completely removed during compilation.
119
120 ## Example
121
122 ### Fixture: `error.todo-missing-source-locations.js`
123
124 **Input:**
125 ```javascript
126 // @validateSourceLocations
127 import {useEffect, useCallback} from 'react';
128
129 function Component({prop1, prop2}) {
130 const x = prop1 + prop2;
131 const y = x * 2;
132 const arr = [x, y];
133 const obj = {x, y};
134 let destA, destB;
135 if (y > 5) {
136 [destA, destB] = arr;
137 }
138
139 const [a, b] = arr;
140 const {x: c, y: d} = obj;
141 let sound;
142
143 if (y > 10) {
144 sound = 'woof';
145 } else {
146 sound = 'meow';
147 }
148
149 useEffect(() => {
150 if (a > 10) {
151 console.log(a);
152 console.log(sound);
153 console.log(destA, destB);
154 }
155 }, [a, sound, destA, destB]);
156
157 const foo = useCallback(() => {
158 return a + b;
159 }, [a, b]);
160
161 function bar() {
162 return (c + d) * 2;
163 }
164
165 console.log('Hello, world!');
166
167 return [y, foo, bar];
168 }
169 ```
170
171 **Error (partial):**
172 ```
173 Found 25 errors:
174
175 Todo: Important source location missing in generated code
176 Source location for Identifier is missing in the generated output...
177
178 error.todo-missing-source-locations.ts:4:9
179 > 4 | function Component({prop1, prop2}) {
180 | ^^^^^^^^^
181
182 Todo: Important source location missing in generated code
183 Source location for VariableDeclaration is missing in the generated output...
184
185 error.todo-missing-source-locations.ts:9:2
186 > 9 | let destA, destB;
187 | ^^^^^^^^^^^^^^^^^
188
189 Todo: Important source location missing in generated code
190 Source location for ExpressionStatement is missing in the generated output...
191
192 error.todo-missing-source-locations.ts:11:4
193 > 11 | [destA, destB] = arr;
194 | ^^^^^^^^^^^^^^^^^^^^^
195 ```
196
197 **Why it fails:** The compiler transforms the code significantly, and many original source locations are not preserved in the output. This causes coverage tools to lose track of which lines were executed.
198
199 **Note:** This fixture is prefixed with `error.todo-` indicating this is a known limitation that needs to be addressed.