@@ -27,7 +27,11 @@ import {Result} from '../Utils/Result';
2727
2828/**
2929 * Some common node types that are important for coverage tracking.
30- * Based on istanbul-lib-instrument
30+ * Based on istanbul-lib-instrument + some other common nodes we expect to be present in the generated AST.
31+ *
32+ * Note: For VariableDeclaration, VariableDeclarator, and Identifier, we enforce stricter validation
33+ * that requires both the source location AND node type to match in the generated AST. This ensures
34+ * that variable declarations maintain their structural integrity through compilation.
3135 */
3236const IMPORTANT_INSTRUMENTED_TYPES = new Set ( [
3337 'ArrowFunctionExpression' ,
@@ -54,6 +58,14 @@ const IMPORTANT_INSTRUMENTED_TYPES = new Set([
5458 'LabeledStatement' ,
5559 'ConditionalExpression' ,
5660 'LogicalExpression' ,
61+
62+ /**
63+ * Note: these aren't important for coverage tracking,
64+ * but we still want to track them to ensure we aren't regressing them when
65+ * we fix the source location tracking for other nodes.
66+ */
67+ 'VariableDeclaration' ,
68+ 'Identifier' ,
5769] ) ;
5870
5971/**
@@ -115,9 +127,10 @@ export function validateSourceLocations(
115127 const errors = new CompilerError ( ) ;
116128
117129 // Step 1: Collect important locations from the original source
130+ // Note: Multiple node types can share the same location (e.g. VariableDeclarator and Identifier)
118131 const importantOriginalLocations = new Map <
119132 string ,
120- { loc : t . SourceLocation ; nodeType : string }
133+ { loc : t . SourceLocation ; nodeTypes : Set < string > }
121134 > ( ) ;
122135
123136 func . traverse ( {
@@ -137,20 +150,31 @@ export function validateSourceLocations(
137150 // Collect the location if it exists
138151 if ( node . loc ) {
139152 const key = locationKey ( node . loc ) ;
140- importantOriginalLocations . set ( key , {
141- loc : node . loc ,
142- nodeType : node . type ,
143- } ) ;
153+ const existing = importantOriginalLocations . get ( key ) ;
154+ if ( existing ) {
155+ existing . nodeTypes . add ( node . type ) ;
156+ } else {
157+ importantOriginalLocations . set ( key , {
158+ loc : node . loc ,
159+ nodeTypes : new Set ( [ node . type ] ) ,
160+ } ) ;
161+ }
144162 }
145163 } ,
146164 } ) ;
147165
148- // Step 2: Collect all locations from the generated AST
149- const generatedLocations = new Set < string > ( ) ;
166+ // Step 2: Collect all locations from the generated AST with their node types
167+ const generatedLocations = new Map < string , Set < string > > ( ) ;
150168
151169 function collectGeneratedLocations ( node : t . Node ) : void {
152170 if ( node . loc ) {
153- generatedLocations . add ( locationKey ( node . loc ) ) ;
171+ const key = locationKey ( node . loc ) ;
172+ const nodeTypes = generatedLocations . get ( key ) ;
173+ if ( nodeTypes ) {
174+ nodeTypes . add ( node . type ) ;
175+ } else {
176+ generatedLocations . set ( key , new Set ( [ node . type ] ) ) ;
177+ }
154178 }
155179
156180 // Use Babel's VISITOR_KEYS to traverse only actual node properties
@@ -184,21 +208,74 @@ export function validateSourceLocations(
184208 }
185209
186210 // Step 3: Validate that all important locations are preserved
187- for ( const [ key , { loc, nodeType} ] of importantOriginalLocations ) {
188- if ( ! generatedLocations . has ( key ) ) {
189- errors . pushDiagnostic (
190- CompilerDiagnostic . create ( {
191- category : ErrorCategory . Todo ,
192- reason : 'Important source location missing in generated code' ,
193- description :
194- `Source location for ${ nodeType } is missing in the generated output. This can cause coverage instrumentation ` +
195- `to fail to track this code properly, resulting in inaccurate coverage reports.` ,
196- } ) . withDetails ( {
197- kind : 'error' ,
198- loc,
199- message : null ,
200- } ) ,
201- ) ;
211+ // For certain node types, also validate that the node type matches
212+ const strictNodeTypes = new Set ( [
213+ 'VariableDeclaration' ,
214+ 'VariableDeclarator' ,
215+ 'Identifier' ,
216+ ] ) ;
217+
218+ const reportMissingLocation = ( loc : t . SourceLocation , nodeType : string ) => {
219+ errors . pushDiagnostic (
220+ CompilerDiagnostic . create ( {
221+ category : ErrorCategory . Todo ,
222+ reason : 'Important source location missing in generated code' ,
223+ description :
224+ `Source location for ${ nodeType } is missing in the generated output. This can cause coverage instrumentation ` +
225+ `to fail to track this code properly, resulting in inaccurate coverage reports.` ,
226+ } ) . withDetails ( {
227+ kind : 'error' ,
228+ loc,
229+ message : null ,
230+ } ) ,
231+ ) ;
232+ } ;
233+
234+ const reportWrongNodeType = (
235+ loc : t . SourceLocation ,
236+ expectedType : string ,
237+ actualTypes : Set < string > ,
238+ ) => {
239+ errors . pushDiagnostic (
240+ CompilerDiagnostic . create ( {
241+ category : ErrorCategory . Todo ,
242+ reason : 'Important source location has wrong node type in generated code' ,
243+ description :
244+ `Source location for ${ expectedType } exists in the generated output but with wrong node type(s): ${ Array . from ( actualTypes ) . join ( ', ' ) } . ` +
245+ `This can cause coverage instrumentation to fail to track this code properly, resulting in inaccurate coverage reports.` ,
246+ } ) . withDetails ( {
247+ kind : 'error' ,
248+ loc,
249+ message : null ,
250+ } ) ,
251+ ) ;
252+ } ;
253+
254+ for ( const [ key , { loc, nodeTypes} ] of importantOriginalLocations ) {
255+ const generatedNodeTypes = generatedLocations . get ( key ) ;
256+
257+ if ( ! generatedNodeTypes ) {
258+ // Location is completely missing
259+ reportMissingLocation ( loc , Array . from ( nodeTypes ) . join ( ', ' ) ) ;
260+ } else {
261+ // Location exists, check each node type
262+ for ( const nodeType of nodeTypes ) {
263+ if ( strictNodeTypes . has ( nodeType ) && ! generatedNodeTypes . has ( nodeType ) ) {
264+ // For strict node types, the specific node type must be present
265+ // Check if any generated node type is also an important original node type
266+ const hasValidNodeType = Array . from ( generatedNodeTypes ) . some ( genType =>
267+ nodeTypes . has ( genType )
268+ ) ;
269+
270+ if ( hasValidNodeType ) {
271+ // At least one generated node type is valid (also in original), so this is just missing
272+ reportMissingLocation ( loc , nodeType ) ;
273+ } else {
274+ // None of the generated node types are in original - this is wrong node type
275+ reportWrongNodeType ( loc , nodeType , generatedNodeTypes ) ;
276+ }
277+ }
278+ }
202279 }
203280 }
204281
0 commit comments