1+ import recast from "recast" ;
2+ import ts from "recast/parsers/typescript.js" ;
3+ import babel from "recast/parsers/babel.js" ;
4+
5+ const { namedTypes : n , builders : b } = recast . types ;
6+
7+ export function optionsWithSetup ( sourceCode , fileName = "" , keys = [ ] ) {
8+ // Choose the appropriate parser.
9+ const parser =
10+ fileName . endsWith ( ".ts" ) || fileName . endsWith ( ".tsx" ) ? ts : babel ;
11+
12+ // Parse the source code.
13+ const ast = recast . parse ( sourceCode , {
14+ parser,
15+ sourceFileName : fileName
16+ } ) ;
17+
18+ // ------------------------------
19+ // Step 1: Remove any existing useFusion imports.
20+ recast . types . visit ( ast , {
21+ visitImportDeclaration ( path ) {
22+ const specifiers = path . node . specifiers ;
23+ if (
24+ specifiers &&
25+ specifiers . some (
26+ spec => n . ImportSpecifier . check ( spec ) && spec . imported . name === "useFusion"
27+ )
28+ ) {
29+ path . prune ( ) ;
30+ return false ;
31+ }
32+ this . traverse ( path ) ;
33+ }
34+ } ) ;
35+
36+ // ------------------------------
37+ // Step 2: Find the default export (an object) and rewrite it as __default__.
38+ let defaultExportObject = null ;
39+ recast . types . visit ( ast , {
40+ visitExportDefaultDeclaration ( path ) {
41+ if ( n . ObjectExpression . check ( path . node . declaration ) ) {
42+ defaultExportObject = path . node . declaration ;
43+ const defaultVarDecl = b . variableDeclaration ( "const" , [
44+ b . variableDeclarator ( b . identifier ( "__default__" ) , defaultExportObject )
45+ ] ) ;
46+ path . replace ( defaultVarDecl ) ;
47+ return false ;
48+ }
49+ this . traverse ( path ) ;
50+ }
51+ } ) ;
52+ if ( ! defaultExportObject ) {
53+ throw new Error ( "Default export is not an object expression." ) ;
54+ }
55+
56+ // ------------------------------
57+ // Step 3: Locate the 'setup' property in __default__.
58+ let setupProperty = null ;
59+ defaultExportObject . properties . forEach ( prop => {
60+ const keyName =
61+ n . Identifier . check ( prop . key )
62+ ? prop . key . name
63+ : n . Literal . check ( prop . key )
64+ ? prop . key . value
65+ : null ;
66+ if ( keyName === "setup" ) {
67+ setupProperty = prop ;
68+ }
69+ } ) ;
70+ if ( ! setupProperty ) {
71+ throw new Error ( "No setup function found in default export." ) ;
72+ }
73+
74+ // If the setup property is an ObjectMethod, convert it into a normal property with a FunctionExpression.
75+ let setupFunctionNode = null ;
76+ if ( n . ObjectMethod && n . ObjectMethod . check ( setupProperty ) ) {
77+ setupFunctionNode = b . functionExpression (
78+ null ,
79+ setupProperty . params ,
80+ setupProperty . body ,
81+ setupProperty . generator ,
82+ setupProperty . async
83+ ) ;
84+ const newProp = b . property ( "init" , setupProperty . key , setupFunctionNode ) ;
85+ newProp . shorthand = false ;
86+ const index = defaultExportObject . properties . indexOf ( setupProperty ) ;
87+ defaultExportObject . properties [ index ] = newProp ;
88+ setupProperty = newProp ;
89+ } else {
90+ setupFunctionNode = setupProperty . value ;
91+ }
92+
93+ // ------------------------------
94+ // Step 4: Traverse the setup() function body.
95+ // Update any useFusion calls and collect the keys used.
96+ const fusionUsedKeys = new Set ( ) ;
97+ let foundUseFusionCall = false ;
98+ recast . types . visit ( setupFunctionNode , {
99+ visitCallExpression ( path ) {
100+ if (
101+ n . Identifier . check ( path . node . callee ) &&
102+ path . node . callee . name === "useFusion"
103+ ) {
104+ foundUseFusionCall = true ;
105+ if ( path . node . arguments . length === 0 ) {
106+ // No arguments passed: inject provided keys.
107+ const newArray = b . arrayExpression ( keys . map ( key => b . literal ( key ) ) ) ;
108+ // Mark all keys as used.
109+ keys . forEach ( key => fusionUsedKeys . add ( key ) ) ;
110+ path . node . arguments . push ( newArray ) ;
111+ } else {
112+ // There is at least one argument.
113+ const origArg = path . node . arguments [ 0 ] ;
114+ if ( n . ArrayExpression . check ( origArg ) ) {
115+ if ( origArg . elements . length > 0 ) {
116+ // Non-empty array: collect the keys.
117+ origArg . elements . forEach ( elem => {
118+ if ( n . Literal . check ( elem ) && typeof elem . value === "string" ) {
119+ fusionUsedKeys . add ( elem . value ) ;
120+ }
121+ } ) ;
122+ }
123+ // Else: explicitly passed empty array—leave it as-is.
124+ }
125+ }
126+ // In all cases, update the call to add a second parameter.
127+ path . node . arguments = [
128+ path . node . arguments [ 0 ] ,
129+ b . logicalExpression (
130+ "||" ,
131+ b . memberExpression ( b . identifier ( "__fusionProvidedProps" ) , b . identifier ( "fusion" ) ) ,
132+ b . objectExpression ( [ ] )
133+ )
134+ ] ;
135+ }
136+ this . traverse ( path ) ;
137+ }
138+ } ) ;
139+ const usedKeysArray = foundUseFusionCall ? Array . from ( fusionUsedKeys ) : [ ] ;
140+ const missingKeys = keys . filter ( key => ! usedKeysArray . includes ( key ) ) ;
141+
142+ // ------------------------------
143+ // Step 5: Insert a top-level declaration for __fusionProvidedProps.
144+ const fusionPropsDecl = b . variableDeclaration ( "let" , [
145+ b . variableDeclarator ( b . identifier ( "__fusionProvidedProps" ) , null )
146+ ] ) ;
147+ ast . program . body . unshift ( fusionPropsDecl ) ;
148+
149+ // ------------------------------
150+ // Step 6: Insert a new import for useFusion.
151+ let lastImportIndex = - 1 ;
152+ ast . program . body . forEach ( ( node , idx ) => {
153+ if ( n . ImportDeclaration . check ( node ) ) {
154+ lastImportIndex = idx ;
155+ }
156+ } ) ;
157+ const fusionImport = b . importDeclaration (
158+ [ b . importSpecifier ( b . identifier ( "useFusion" ) ) ] ,
159+ b . literal ( "__aliasedFusionPath__" )
160+ ) ;
161+ ast . program . body . splice ( lastImportIndex + 1 , 0 , fusionImport ) ;
162+
163+ // ------------------------------
164+ // Step 7: Append a wrapper to override the setup function.
165+ // If a useFusion call exists, then missingKeys (the keys not handled inside setup) are used;
166+ // otherwise, all provided keys are used.
167+ const missingKeysForWrapper = foundUseFusionCall ? missingKeys : keys ;
168+ let fusionDataDecl ;
169+ if ( missingKeysForWrapper . length === 0 ) {
170+ fusionDataDecl = b . variableDeclaration ( "const" , [
171+ b . variableDeclarator ( b . identifier ( "fusionData" ) , b . objectExpression ( [ ] ) )
172+ ] ) ;
173+ } else {
174+ const missingArrayExpr = b . arrayExpression (
175+ missingKeysForWrapper . map ( key => b . literal ( key ) )
176+ ) ;
177+ const fusionDataCall = b . callExpression ( b . identifier ( "useFusion" ) , [
178+ missingArrayExpr ,
179+ b . logicalExpression (
180+ "||" ,
181+ b . memberExpression ( b . identifier ( "props" ) , b . identifier ( "fusion" ) ) ,
182+ b . objectExpression ( [ ] )
183+ )
184+ ] ) ;
185+ fusionDataDecl = b . variableDeclaration ( "const" , [
186+ b . variableDeclarator ( b . identifier ( "fusionData" ) , fusionDataCall )
187+ ] ) ;
188+ }
189+ const userReturnsDecl = b . variableDeclaration ( "let" , [
190+ b . variableDeclarator (
191+ b . identifier ( "userReturns" ) ,
192+ b . conditionalExpression (
193+ b . binaryExpression (
194+ "===" ,
195+ b . unaryExpression ( "typeof" , b . identifier ( "userSetup" ) , true ) ,
196+ b . literal ( "function" )
197+ ) ,
198+ b . callExpression ( b . identifier ( "userSetup" ) , [
199+ b . identifier ( "props" ) ,
200+ b . identifier ( "ctx" )
201+ ] ) ,
202+ b . objectExpression ( [ ] )
203+ )
204+ )
205+ ] ) ;
206+ const newSetupBody = b . blockStatement ( [
207+ b . expressionStatement (
208+ b . assignmentExpression ( "=" , b . identifier ( "__fusionProvidedProps" ) , b . identifier ( "props" ) )
209+ ) ,
210+ fusionDataDecl ,
211+ userReturnsDecl ,
212+ b . returnStatement (
213+ b . objectExpression ( [
214+ b . spreadElement ( b . identifier ( "fusionData" ) ) ,
215+ b . spreadElement ( b . identifier ( "userReturns" ) )
216+ ] )
217+ )
218+ ] ) ;
219+ const newSetupFunc = b . functionExpression ( null , [ b . identifier ( "props" ) , b . identifier ( "ctx" ) ] , newSetupBody ) ;
220+ const userSetupDecl = b . variableDeclaration ( "const" , [
221+ b . variableDeclarator (
222+ b . identifier ( "userSetup" ) ,
223+ b . memberExpression ( b . identifier ( "__default__" ) , b . identifier ( "setup" ) )
224+ )
225+ ] ) ;
226+ const setupOverride = b . expressionStatement (
227+ b . assignmentExpression (
228+ "=" ,
229+ b . memberExpression ( b . identifier ( "__default__" ) , b . identifier ( "setup" ) ) ,
230+ newSetupFunc
231+ )
232+ ) ;
233+ const exportDefaultDecl = b . exportDefaultDeclaration ( b . identifier ( "__default__" ) ) ;
234+ ast . program . body . push ( userSetupDecl , setupOverride , exportDefaultDecl ) ;
235+
236+ // ------------------------------
237+ // Generate output.
238+ const output = recast . print ( ast , {
239+ quote : "double" ,
240+ sourceMapName : fileName || "transformed.js"
241+ } ) ;
242+ return { code : output . code , map : output . map , remaining : missingKeys } ;
243+ }
244+
245+ export default optionsWithSetup ;
0 commit comments