@@ -2,6 +2,8 @@ import '@qraft/test-utils/vitestFsMock';
22import fs from 'node:fs/promises' ;
33import os from 'node:os' ;
44import path from 'node:path' ;
5+ import { TraceMap , originalPositionFor } from '@jridgewell/trace-mapping' ;
6+ import type { SourceMapInput } from '@jridgewell/trace-mapping' ;
57import { describe , expect , it } from 'vitest' ;
68import { transformQraftTreeShaking as transformQraftTreeShakingImpl } from './core.js' ;
79import { createTransformPlan } from './lib/transform/plan.js' ;
@@ -59,11 +61,29 @@ export const createAPIClientOptions = () => ({
5961` ;
6062
6163type TransformOptions = Parameters < typeof transformQraftTreeShakingImpl > [ 2 ] ;
64+ type TransformWithInputSourceMap = (
65+ code : string ,
66+ id : string ,
67+ options : TransformOptions ,
68+ resolver : Parameters < typeof transformQraftTreeShakingImpl > [ 3 ] ,
69+ inputSourceMap ?: SourceMapInput
70+ ) => ReturnType < typeof transformQraftTreeShakingImpl > ;
71+
72+ const transformQraftTreeShakingImplWithInputSourceMap = transformQraftTreeShakingImpl satisfies (
73+ code : string ,
74+ id : string ,
75+ options : TransformOptions ,
76+ resolver : Parameters < typeof transformQraftTreeShakingImpl > [ 3 ]
77+ ) => ReturnType < typeof transformQraftTreeShakingImpl > ;
78+
79+ const transformQraftTreeShakingWithInputSourceMap =
80+ transformQraftTreeShakingImplWithInputSourceMap as unknown as TransformWithInputSourceMap ;
6281
6382async function transformQraftTreeShaking (
6483 code : string ,
6584 id : string ,
66- options : TransformOptions
85+ options : TransformOptions ,
86+ inputSourceMap ?: SourceMapInput
6787) {
6888 const fixtureRoot = path . dirname ( path . dirname ( id ) ) ;
6989 const fixtureResolver = createFixtureResolver ( fixtureRoot ) ;
@@ -80,7 +100,13 @@ async function transformQraftTreeShaking(
80100 return fixtureResolver ( specifier , importer ) ;
81101 } ;
82102
83- return transformQraftTreeShakingImpl ( code , id , options , resolver ) ;
103+ return transformQraftTreeShakingWithInputSourceMap (
104+ code ,
105+ id ,
106+ options ,
107+ resolver ,
108+ inputSourceMap
109+ ) ;
84110}
85111
86112describe ( 'transformQraftTreeShaking' , ( ) => {
@@ -141,6 +167,62 @@ export function App() {
141167 ` ) ;
142168 } ) ;
143169
170+ it ( 'keeps a rewritten user call site traceable through an incoming source map' , async ( ) => {
171+ const fixture = await createFixture ( ) ;
172+ const generatedSourceFile = path . join ( fixture , 'src/App.generated.tsx' ) ;
173+ const originalSourceFile = path . join ( fixture , 'src/App.tsx' ) ;
174+ const code = [
175+ "import { createAPIClient } from './api';" ,
176+ '' ,
177+ 'const api = createAPIClient();' ,
178+ '' ,
179+ 'export function App() {' ,
180+ ' return api.pets.getPets.useQuery();' ,
181+ '}' ,
182+ ] . join ( '\n' ) ;
183+ const inputSourceMap = createIdentitySourceMap (
184+ generatedSourceFile ,
185+ originalSourceFile ,
186+ code
187+ ) ;
188+
189+ const result = await transformQraftTreeShaking (
190+ code ,
191+ generatedSourceFile ,
192+ { createAPIClientFn : [ { name : 'createAPIClient' , module : './api' } ] } ,
193+ inputSourceMap
194+ ) ;
195+
196+ if ( ! result ) {
197+ throw new Error ( 'Expected transform result' ) ;
198+ }
199+
200+ const generatedLineIndex = result . code
201+ . split ( '\n' )
202+ . findIndex ( ( line ) => line . includes ( 'api_pets_getPets.useQuery()' ) ) ;
203+
204+ if ( generatedLineIndex === - 1 ) {
205+ throw new Error ( 'Expected rewritten user call site in generated output' ) ;
206+ }
207+
208+ const generatedLine = generatedLineIndex + 1 ;
209+ const generatedColumn = result . code
210+ . split ( '\n' )
211+ [ generatedLineIndex ] . indexOf ( 'api_pets_getPets' ) ;
212+
213+ const traceMapInput = result . map ! as SourceMapInput ;
214+
215+ const position = originalPositionFor ( new TraceMap ( traceMapInput ) , {
216+ line : generatedLine ,
217+ column : generatedColumn ,
218+ } ) ;
219+
220+ expect ( position ) . toMatchObject ( {
221+ source : originalSourceFile ,
222+ line : 6 ,
223+ } ) ;
224+ } ) ;
225+
144226 it ( 'aliases an imported operation when a local binding uses the same name' , async ( ) => {
145227 const fixture = await createFixture ( ) ;
146228 const sourceFile = path . join ( fixture , 'src/App.tsx' ) ;
@@ -1582,6 +1664,26 @@ function createFixtureResolver(fixtureRoot: string) {
15821664 } ;
15831665}
15841666
1667+ function createIdentitySourceMap (
1668+ generatedSourceFile : string ,
1669+ originalSourceFile : string ,
1670+ source : string
1671+ ) : SourceMapInput {
1672+ const lineCount = source . split ( '\n' ) . length ;
1673+ const mappings = Array . from ( { length : lineCount } , ( _ , index ) =>
1674+ index === 0 ? 'AAAA' : 'AACA'
1675+ ) . join ( ';' ) ;
1676+
1677+ return {
1678+ version : 3 ,
1679+ file : generatedSourceFile ,
1680+ names : [ ] ,
1681+ sources : [ originalSourceFile ] ,
1682+ sourcesContent : [ source ] ,
1683+ mappings,
1684+ } ;
1685+ }
1686+
15851687async function resolveFixtureModule ( baseDir : string , importPath : string ) {
15861688 const base = path . resolve ( baseDir , importPath ) ;
15871689 const candidateBases = new Set ( [ base ] ) ;
0 commit comments