11#!/usr/bin/env node
2- import { readdir , readFile , stat , writeFile } from "node:fs/promises" ;
2+ import { readFile , stat , writeFile } from "node:fs/promises" ;
33import { join , relative } from "node:path" ;
44import type { ImportDeclaration , Node } from "@oxc-project/types" ;
55import MagicString from "magic-string" ;
66import { parseSync } from "oxc-parser" ;
77import { walk } from "oxc-walker" ;
8- import { isCallExpressionWithName } from "./ast/core.ts" ;
9- import { findImportBySource } from "./ast/imports.ts" ;
10- import { isJSXElementWithName } from "./ast/jsx-helpers.ts" ;
11-
12- async function * walkFiles ( dir : string , extension : string ) : AsyncGenerator < string > {
13- const entries = await readdir ( dir , { withFileTypes : true } ) ;
14-
15- for ( const entry of entries ) {
16- const path = join ( dir , entry . name ) ;
17- if ( entry . isDirectory ( ) ) {
18- yield * walkFiles ( path , extension ) ;
19- continue ;
20- }
21- if ( entry . isFile ( ) && entry . name . endsWith ( extension ) ) {
22- yield path ;
23- }
24- }
25- }
26-
27- function isRenderElement ( node : Node , code : string ) : boolean {
28- return isJSXElementWithName ( node , code , "Render" ) ;
29- }
30-
31- function returnsRenderComponent ( callback : Node , code : string ) : boolean {
32- let hasRenderReturn = false ;
33-
34- walk ( callback , {
35- enter ( node : Node ) {
36- if ( node . type === "ReturnStatement" && "argument" in node && node . argument ) {
37- if ( isRenderElement ( node . argument , code ) ) {
38- hasRenderReturn = true ;
39- return ;
40- }
41- }
42-
43- if ( isRenderElement ( node , code ) ) {
44- hasRenderReturn = true ;
45- }
46- }
47- } ) ;
48-
49- return hasRenderReturn ;
50- }
51-
52- function detectsRenderComponentUsage ( sourceCode : string ) : boolean {
53- try {
54- const ast = parseSync ( "temp.tsx" , sourceCode ) ;
55- let hasRenderComponent = false ;
56-
57- walk ( ast . program , {
58- enter ( node : Node ) {
59- if ( ! isCallExpressionWithName ( node , sourceCode , "component$" ) ) return ;
60- if ( ! ( "arguments" in node ) ) return ;
61-
62- const callback = node . arguments [ 0 ] ;
63- if ( ! callback ) return ;
64- if (
65- callback . type !== "ArrowFunctionExpression" &&
66- callback . type !== "FunctionExpression"
67- ) {
68- return ;
69- }
70-
71- if ( returnsRenderComponent ( callback , sourceCode ) ) {
72- hasRenderComponent = true ;
73- }
74- }
75- } ) ;
76-
77- return hasRenderComponent ;
78- } catch {
79- return false ;
80- }
81- }
8+ import {
9+ findImportBySource ,
10+ hasImportSpecifier ,
11+ injectTypeImport
12+ } from "./ast/imports.ts" ;
13+ import { detectsRenderComponentUsage } from "./ast/qwik.ts" ;
14+ import { walkFiles } from "./fs.ts" ;
8215
8316function injectAsChildTypesIntoComponent (
8417 node : Node ,
@@ -127,42 +60,42 @@ function injectAsChildTypesIntoComponent(
12760 return hasChanges ;
12861}
12962
130- function findToolsImport (
131- ast : ReturnType < typeof parseSync > ,
132- content : string
133- ) : Node | null {
134- return findImportBySource ( ast , content , "@qds.dev/tools" ) ;
135- }
136-
137- function injectAsChildTypesImport (
138- ast : ReturnType < typeof parseSync > ,
139- content : string ,
140- s : MagicString ,
141- toolsImportNode : Node | null
142- ) : void {
143- if ( toolsImportNode ) {
144- const importDecl = toolsImportNode as ImportDeclaration ;
145- if ( importDecl . specifiers && importDecl . specifiers . length > 0 ) {
146- const lastSpecifier = importDecl . specifiers [ importDecl . specifiers . length - 1 ] ;
147- s . appendLeft ( lastSpecifier . end , ", type AsChildTypes" ) ;
148- }
149- return ;
150- }
151-
152- const firstImport = ast . program . body . find (
153- ( node : Node ) => node . type === "ImportDeclaration"
154- ) ;
155- if ( firstImport ) {
156- s . appendLeft (
157- firstImport . start ,
158- 'import type { AsChildTypes } from "@qds.dev/tools";\n'
159- ) ;
160- }
63+ /**
64+ * Injects AsChildTypes import into the declaration file
65+ */
66+ function injectAsChildTypesImport ( options : {
67+ ast : ReturnType < typeof parseSync > ;
68+ magicString : MagicString ;
69+ toolsImportNode : Node | null ;
70+ } ) : void {
71+ injectTypeImport ( {
72+ ast : options . ast ,
73+ magicString : options . magicString ,
74+ importSource : "@qds.dev/tools" ,
75+ specifierName : "AsChildTypes" ,
76+ existingImportNode : options . toolsImportNode
77+ } ) ;
16178}
16279
16380async function transformTypeFile ( dtsPath : string , sourcePath : string ) : Promise < boolean > {
16481 const content = await readFile ( dtsPath , "utf-8" ) ;
165- if ( content . includes ( "AsChildTypes" ) ) return false ;
82+ const hasAsChildTypesUsage = content . includes ( "AsChildTypes" ) ;
83+
84+ // Quick check if AsChildTypes is already used in the file
85+ if ( hasAsChildTypesUsage ) {
86+ try {
87+ const ast = parseSync ( dtsPath , content ) ;
88+ const toolsImportNode = findImportBySource ( ast , content , "@qds.dev/tools" ) ;
89+
90+ if ( toolsImportNode ) {
91+ const importDecl = toolsImportNode as ImportDeclaration ;
92+ // If both import and usage exist, nothing to do
93+ if ( hasImportSpecifier ( importDecl , "AsChildTypes" ) ) return false ;
94+ }
95+ } catch {
96+ return false ;
97+ }
98+ }
16699
167100 try {
168101 const sourceCode = await readFile ( sourcePath , "utf-8" ) ;
@@ -184,10 +117,14 @@ async function transformTypeFile(dtsPath: string, sourcePath: string): Promise<b
184117 }
185118 } ) ;
186119
187- if ( ! hasChanges ) return false ;
120+ if ( ! hasChanges && ! hasAsChildTypesUsage ) return false ;
188121
189- const toolsImportNode = findToolsImport ( ast , content ) ;
190- injectAsChildTypesImport ( ast , content , s , toolsImportNode ) ;
122+ const toolsImportNode = findImportBySource ( ast , content , "@qds.dev/tools" ) ;
123+ injectAsChildTypesImport ( {
124+ ast,
125+ magicString : s ,
126+ toolsImportNode
127+ } ) ;
191128
192129 await writeFile ( dtsPath , s . toString ( ) , "utf-8" ) ;
193130 return true ;
@@ -236,13 +173,4 @@ if (import.meta.url === `file://${process.argv[1]}`) {
236173 } ) ;
237174}
238175
239- export {
240- detectsRenderComponentUsage ,
241- findToolsImport ,
242- injectAsChildTypesImport ,
243- injectAsChildTypesIntoComponent ,
244- isRenderElement ,
245- returnsRenderComponent ,
246- transformTypeFile ,
247- walkFiles
248- } ;
176+ export { injectAsChildTypesImport , injectAsChildTypesIntoComponent , transformTypeFile } ;
0 commit comments