1
1
import path from "path" ;
2
2
import ts from "typescript" ;
3
- import { alias } from "../util/alias " ;
3
+ import { mergeArrayMap } from "../util/mergeArrayMap " ;
4
4
import { upsert } from "../util/upsert" ;
5
- import { projectDir } from "./projectDir" ;
6
-
7
- const betterLibDir = path . join ( projectDir , "lib" ) ;
5
+ import { getStatementDeclName } from "./ast/getStatementDeclName" ;
6
+ import {
7
+ declareGlobalSymbol ,
8
+ ReplacementMap ,
9
+ ReplacementName ,
10
+ ReplacementTarget ,
11
+ scanBetterFile ,
12
+ } from "./scanBetterFile" ;
8
13
9
14
type GenerateOptions = {
10
15
emitOriginalAsComment ?: boolean ;
@@ -41,9 +46,53 @@ export function generate(
41
46
return result + originalFile . text ;
42
47
}
43
48
44
- const consumedReplacements = new Set < string > ( ) ;
49
+ return (
50
+ result +
51
+ generateStatements (
52
+ printer ,
53
+ originalFile ,
54
+ originalFile . statements ,
55
+ replacementTargets ,
56
+ emitOriginalAsComment ,
57
+ )
58
+ ) ;
59
+ }
60
+
61
+ function generateStatements (
62
+ printer : ts . Printer ,
63
+ originalFile : ts . SourceFile ,
64
+ statements : readonly ts . Statement [ ] ,
65
+ replacementTargets : ReplacementMap ,
66
+ emitOriginalAsComment : boolean ,
67
+ ) : string {
68
+ let result = "" ;
69
+ const consumedReplacements = new Set < ReplacementName > ( ) ;
70
+ for ( const statement of statements ) {
71
+ if (
72
+ ts . isModuleDeclaration ( statement ) &&
73
+ ts . isIdentifier ( statement . name ) &&
74
+ statement . name . text === "global"
75
+ ) {
76
+ // declare global { ... }
77
+ consumedReplacements . add ( declareGlobalSymbol ) ;
78
+
79
+ const declareGlobalReplacement =
80
+ replacementTargets . get ( declareGlobalSymbol ) ;
81
+ if ( declareGlobalReplacement === undefined ) {
82
+ result += statement . getFullText ( originalFile ) ;
83
+ continue ;
84
+ }
85
+
86
+ result += generateDeclareGlobalReplacement (
87
+ printer ,
88
+ originalFile ,
89
+ statement ,
90
+ declareGlobalReplacement ,
91
+ emitOriginalAsComment ,
92
+ ) ;
93
+ continue ;
94
+ }
45
95
46
- for ( const statement of originalFile . statements ) {
47
96
const name = getStatementDeclName ( statement ) ;
48
97
if ( name === undefined ) {
49
98
result += statement . getFullText ( originalFile ) ;
@@ -57,8 +106,9 @@ export function generate(
57
106
58
107
consumedReplacements . add ( name ) ;
59
108
60
- if ( ! ts . isInterfaceDeclaration ( statement ) ) {
61
- result += generateFullReplacement (
109
+ if ( ts . isInterfaceDeclaration ( statement ) ) {
110
+ result += generateInterface (
111
+ printer ,
62
112
originalFile ,
63
113
statement ,
64
114
replacementTarget ,
@@ -67,8 +117,7 @@ export function generate(
67
117
continue ;
68
118
}
69
119
70
- result += generateInterface (
71
- printer ,
120
+ result += generateFullReplacement (
72
121
originalFile ,
73
122
statement ,
74
123
replacementTarget ,
@@ -122,6 +171,42 @@ function generateFullReplacement(
122
171
return result ;
123
172
}
124
173
174
+ function generateDeclareGlobalReplacement (
175
+ printer : ts . Printer ,
176
+ originalFile : ts . SourceFile ,
177
+ statement : ts . ModuleDeclaration ,
178
+ replacementTarget : readonly ReplacementTarget [ ] ,
179
+ emitOriginalAsComment : boolean ,
180
+ ) {
181
+ if ( ! replacementTarget . every ( ( target ) => target . type === "declare-global" ) ) {
182
+ throw new Error ( "Invalid replacement target" ) ;
183
+ }
184
+ if ( ! statement . body || ! ts . isModuleBlock ( statement . body ) ) {
185
+ return statement . getFullText ( originalFile ) ;
186
+ }
187
+
188
+ const nestedStatements = statement . body . statements ;
189
+
190
+ let result = "" ;
191
+
192
+ result += "declare global {\n" ;
193
+
194
+ const nestedReplacementTarget = mergeArrayMap (
195
+ replacementTarget . map ( ( t ) => t . statements ) ,
196
+ ) ;
197
+
198
+ result += generateStatements (
199
+ printer ,
200
+ originalFile ,
201
+ nestedStatements ,
202
+ nestedReplacementTarget ,
203
+ emitOriginalAsComment ,
204
+ ) ;
205
+
206
+ result += "}\n" ;
207
+ return result ;
208
+ }
209
+
125
210
function generateInterface (
126
211
printer : ts . Printer ,
127
212
originalFile : ts . SourceFile ,
@@ -208,101 +293,6 @@ function generateInterface(
208
293
return result ;
209
294
}
210
295
211
- type ReplacementTarget = (
212
- | {
213
- type : "interface" ;
214
- originalStatement : ts . InterfaceDeclaration ;
215
- members : Map <
216
- string ,
217
- {
218
- member : ts . TypeElement ;
219
- text : string ;
220
- } [ ]
221
- > ;
222
- }
223
- | {
224
- type : "non-interface" ;
225
- statement : ts . Statement ;
226
- }
227
- ) & {
228
- sourceFile : ts . SourceFile ;
229
- } ;
230
-
231
- /**
232
- * Scan better lib file to determine which statements need to be replaced.
233
- */
234
- function scanBetterFile (
235
- printer : ts . Printer ,
236
- targetFile : string ,
237
- ) : Map < string , ReplacementTarget [ ] > {
238
- const replacementTargets = new Map < string , ReplacementTarget [ ] > ( ) ;
239
- {
240
- const betterLibFile = path . join ( betterLibDir , targetFile ) ;
241
- const betterProgram = ts . createProgram ( [ betterLibFile ] , { } ) ;
242
- const betterFile = betterProgram . getSourceFile ( betterLibFile ) ;
243
- if ( betterFile ) {
244
- // Scan better file to determine which statements need to be replaced.
245
- for ( const statement of betterFile . statements ) {
246
- const name = getStatementDeclName ( statement ) ?? "" ;
247
- const aliasesMap =
248
- alias . get ( name ) ?? new Map ( [ [ name , new Map < string , string > ( ) ] ] ) ;
249
- for ( const [ targetName , typeMap ] of aliasesMap ) {
250
- const transformedStatement = replaceAliases ( statement , typeMap ) ;
251
- if ( ts . isInterfaceDeclaration ( transformedStatement ) ) {
252
- const members = new Map <
253
- string ,
254
- {
255
- member : ts . TypeElement ;
256
- text : string ;
257
- } [ ]
258
- > ( ) ;
259
- for ( const member of transformedStatement . members ) {
260
- const memberName = member . name ?. getText ( betterFile ) ?? "" ;
261
- upsert ( members , memberName , ( members = [ ] ) => {
262
- const leadingSpacesMatch = / ^ \s * / . exec (
263
- member . getFullText ( betterFile ) ,
264
- ) ;
265
- const leadingSpaces =
266
- leadingSpacesMatch !== null ? leadingSpacesMatch [ 0 ] : "" ;
267
- members . push ( {
268
- member,
269
- text :
270
- leadingSpaces +
271
- printer . printNode (
272
- ts . EmitHint . Unspecified ,
273
- member ,
274
- betterFile ,
275
- ) ,
276
- } ) ;
277
- return members ;
278
- } ) ;
279
- }
280
- upsert ( replacementTargets , targetName , ( targets = [ ] ) => {
281
- targets . push ( {
282
- type : "interface" ,
283
- members,
284
- originalStatement : transformedStatement ,
285
- sourceFile : betterFile ,
286
- } ) ;
287
- return targets ;
288
- } ) ;
289
- } else {
290
- upsert ( replacementTargets , targetName , ( statements = [ ] ) => {
291
- statements . push ( {
292
- type : "non-interface" ,
293
- statement : transformedStatement ,
294
- sourceFile : betterFile ,
295
- } ) ;
296
- return statements ;
297
- } ) ;
298
- }
299
- }
300
- }
301
- }
302
- }
303
- return replacementTargets ;
304
- }
305
-
306
296
/**
307
297
* Determines whether interface can be partially replaced.
308
298
*/
@@ -410,54 +400,8 @@ function printInterface(
410
400
return result ;
411
401
}
412
402
413
- function getStatementDeclName ( statement : ts . Statement ) : string | undefined {
414
- if ( ts . isVariableStatement ( statement ) ) {
415
- for ( const dec of statement . declarationList . declarations ) {
416
- if ( ts . isIdentifier ( dec . name ) ) {
417
- return dec . name . text ;
418
- }
419
- }
420
- } else if (
421
- ts . isFunctionDeclaration ( statement ) ||
422
- ts . isInterfaceDeclaration ( statement ) ||
423
- ts . isTypeAliasDeclaration ( statement ) ||
424
- ts . isModuleDeclaration ( statement )
425
- ) {
426
- return statement . name ?. text ;
427
- } else if ( ts . isInterfaceDeclaration ( statement ) ) {
428
- return statement . name . text ;
429
- }
430
- return undefined ;
431
- }
432
-
433
403
function commentOut ( code : string ) : string {
434
404
const lines = code . split ( "\n" ) . filter ( ( line ) => line . trim ( ) . length > 0 ) ;
435
405
const result = lines . map ( ( line ) => `// ${ line } ` ) ;
436
406
return result . join ( "\n" ) + "\n" ;
437
407
}
438
-
439
- function replaceAliases (
440
- statement : ts . Statement ,
441
- typeMap : Map < string , string > ,
442
- ) : ts . Statement {
443
- if ( typeMap . size === 0 ) return statement ;
444
- return ts . transform ( statement , [
445
- ( context ) => ( sourceStatement ) => {
446
- const visitor = ( node : ts . Node ) : ts . Node => {
447
- if ( ts . isTypeReferenceNode ( node ) && ts . isIdentifier ( node . typeName ) ) {
448
- const replacementType = typeMap . get ( node . typeName . text ) ;
449
- if ( replacementType === undefined ) {
450
- return node ;
451
- }
452
- return ts . factory . updateTypeReferenceNode (
453
- node ,
454
- ts . factory . createIdentifier ( replacementType ) ,
455
- node . typeArguments ,
456
- ) ;
457
- }
458
- return ts . visitEachChild ( node , visitor , context ) ;
459
- } ;
460
- return ts . visitNode ( sourceStatement , visitor , ts . isStatement ) ;
461
- } ,
462
- ] ) . transformed [ 0 ] ;
463
- }
0 commit comments