@@ -2,6 +2,7 @@ import * as ts from 'typescript';
22import path from 'path' ;
33import { promises as fs } from 'fs' ;
44import { Ast , TemplateNode } from 'svelte/types/compiler/interfaces' ;
5+ import tmp from 'tmp' ;
56import { Prop , Event , SlotProp } from '../types' ;
67import ITransformer from './transformer' ;
78
@@ -16,6 +17,9 @@ class SvelteTransformer implements ITransformer {
1617 private subdir : string ;
1718 private moduleName : string ;
1819 private isDefault : boolean ;
20+ private typesForSearch : ts . TypeReferenceNode [ ] ;
21+ private declarationNode : string [ ] ;
22+ private declarationImport : string [ ] ;
1923
2024 constructor ( content : string , fileName : string , ast : Ast , dir : string , moduleName : string , isDefault : boolean ) {
2125 this . sourceFile = ts . createSourceFile ( fileName , content , ts . ScriptTarget . Latest ) ;
@@ -28,6 +32,9 @@ class SvelteTransformer implements ITransformer {
2832 this . subdir = path . dirname ( this . fileName ) . replace ( this . dir , '' ) ;
2933 this . moduleName = moduleName ;
3034 this . isDefault = isDefault ;
35+ this . typesForSearch = [ ] ;
36+ this . declarationNode = [ ] ;
37+ this . declarationImport = [ ] ;
3138 }
3239
3340 private containExportModifier = ( node : ts . VariableStatement ) : boolean => {
@@ -58,6 +65,10 @@ class SvelteTransformer implements ITransformer {
5865 if ( declaration . type ) {
5966 type = declaration . type . getText ( this . sourceFile ) ;
6067
68+ if ( ts . isTypeReferenceNode ( declaration . type ) ) {
69+ this . typesForSearch . push ( declaration . type ) ;
70+ }
71+
6172 if ( ts . isUnionTypeNode ( declaration . type ) ) {
6273 const nameValidTypes = declaration . type . types . reduce ( ( acc , type ) => {
6374 if ( type . kind === ts . SyntaxKind . NullKeyword || type . kind === ts . SyntaxKind . UndefinedKeyword ) {
@@ -109,6 +120,21 @@ class SvelteTransformer implements ITransformer {
109120 }
110121 }
111122
123+ private verifyImportDeclaration ( node : ts . ImportDeclaration , name : string ) : void {
124+ if ( node . importClause && node . importClause . namedBindings && ts . isNamedImports ( node . importClause . namedBindings ) ) {
125+ const elements = node . importClause . namedBindings . elements ;
126+ const newElements = elements . filter ( ( element ) => element . name . getText ( this . sourceFile ) === name ) ;
127+
128+ if ( newElements . length > 0 ) {
129+ const importString = newElements . map ( ( item ) => item . name . getText ( this . sourceFile ) ) . join ( ', ' ) ;
130+
131+ this . declarationImport . push (
132+ `import { ${ importString } } from ${ node . moduleSpecifier . getText ( this . sourceFile ) } ;`
133+ ) ;
134+ }
135+ }
136+ }
137+
112138 exec ( ) : void {
113139 ts . forEachChild ( this . sourceFile , ( node : ts . Node ) => {
114140 if ( ts . isVariableStatement ( node ) ) {
@@ -120,10 +146,47 @@ class SvelteTransformer implements ITransformer {
120146 }
121147 } ) ;
122148
149+ this . typesForSearch . forEach ( ( item ) => {
150+ const name = item . typeName . getText ( this . sourceFile ) ;
151+ ts . forEachChild ( this . sourceFile , ( node : ts . Node ) => {
152+ if ( ts . isInterfaceDeclaration ( node ) || ts . isClassDeclaration ( node ) || ts . isTypeAliasDeclaration ( node ) ) {
153+ if ( node . name ?. getText ( this . sourceFile ) === name ) {
154+ this . declarationNode . push ( node . getText ( this . sourceFile ) ) ;
155+ }
156+ } else if ( ts . isImportDeclaration ( node ) ) {
157+ this . verifyImportDeclaration ( node , name ) ;
158+ }
159+ } ) ;
160+ } ) ;
161+
123162 this . execSlotProperty ( this . ast . html ) ;
124163 }
125164
126- toString ( ) : string {
165+ private async toStringDeclarations ( ) : Promise < string > {
166+ const tempFile = tmp . fileSync ( { postfix : '.ts' } ) ;
167+ const content = this . declarationNode . reduce ( ( acc , item ) => `${ acc } ${ item } \n\n` , '' ) ;
168+ let declaration = '' ;
169+
170+ await fs . writeFile ( tempFile . name , content ) ;
171+
172+ const options = { declaration : true , emitDeclarationOnly : true } ;
173+ const host = ts . createCompilerHost ( options ) ;
174+ host . writeFile = ( _ , contents : string ) => ( declaration = contents ) ;
175+ const program = ts . createProgram ( [ tempFile . name ] , options , host ) ;
176+ program . emit ( ) ;
177+
178+ tempFile . removeCallback ( ) ;
179+
180+ declaration = declaration
181+ . replace ( / d e c l a r e / g, '' )
182+ . split ( '\n' )
183+ . map ( ( item ) => `\t${ item } ` )
184+ . join ( '\n' ) ;
185+
186+ return `${ declaration } \n` ;
187+ }
188+
189+ async toString ( ) : Promise < string > {
127190 const pathParse = path . parse ( this . fileName ) ;
128191 const propsString = this . props . reduce (
129192 ( acc , prop ) => `${ acc } \n\t\t${ prop . name } ${ prop . isOptional ? '?' : '' } : ${ prop . type } ;` ,
@@ -138,6 +201,15 @@ class SvelteTransformer implements ITransformer {
138201 string = `declare module '${ this . moduleName } ' {\n` ;
139202 }
140203
204+ if ( this . declarationImport . length > 0 ) {
205+ string += this . declarationImport . reduce ( ( acc , item ) => `${ acc } \t${ item } \n` , '' ) ;
206+ string += '\n' ;
207+ }
208+
209+ if ( this . declarationNode . length > 0 ) {
210+ string += await this . toStringDeclarations ( ) ;
211+ }
212+
141213 string += `\tinterface ${ pathParse . name } Props {${ propsString } \n\t}\n\n` ;
142214 string += `\tclass ${ pathParse . name } extends SvelteComponentTyped<\n` ;
143215 string += `\t\t${ pathParse . name } Props,\n\t\t{ ${ eventsString } },\n\t\t{ ${ slotPropsString } }\n\t> {}` ;
@@ -148,7 +220,7 @@ class SvelteTransformer implements ITransformer {
148220
149221 async appendFile ( path : string ) : Promise < void > {
150222 this . exec ( ) ;
151- await fs . appendFile ( path , this . toString ( ) ) ;
223+ await fs . appendFile ( path , await this . toString ( ) ) ;
152224 }
153225}
154226
0 commit comments