@@ -4,38 +4,67 @@ import { alias } from "../util/alias";
4
4
import { upsert } from "../util/upsert" ;
5
5
import { getStatementDeclName } from "./ast/getStatementDeclName" ;
6
6
import { projectDir } from "./projectDir" ;
7
+ import {
8
+ declareGlobalSymbol ,
9
+ type ReplacementMap ,
10
+ type ReplacementName ,
11
+ type ReplacementTarget ,
12
+ } from "./ReplacementMap" ;
7
13
8
14
const betterLibDir = path . join ( projectDir , "lib" ) ;
15
+ const aliasFilePath = path . join ( betterLibDir , "alias.d.ts" ) ;
9
16
10
- export type ReplacementTarget = (
11
- | {
12
- type : "interface" ;
13
- originalStatement : ts . InterfaceDeclaration ;
14
- members : Map <
15
- string ,
16
- {
17
- member : ts . TypeElement ;
18
- text : string ;
19
- } [ ]
20
- > ;
21
- }
22
- | {
23
- type : "declare-global" ;
24
- originalStatement : ts . ModuleDeclaration ;
25
- statements : ReplacementMap ;
17
+ type AliasFile = {
18
+ replacementMap : ReplacementMap ;
19
+ } ;
20
+
21
+ // Cache for alias file statements
22
+ let aliasFileCache : ts . SourceFile | undefined ;
23
+
24
+ /**
25
+ * Load the alias file applied to the target file.
26
+ */
27
+ export function loadAliasFile (
28
+ printer : ts . Printer ,
29
+ targetFileName : string ,
30
+ ) : AliasFile {
31
+ if ( ! aliasFileCache ) {
32
+ const aliasProgram = ts . createProgram ( [ aliasFilePath ] , { } ) ;
33
+ const aliasFile = aliasProgram . getSourceFile ( aliasFilePath ) ;
34
+
35
+ if ( ! aliasFile ) {
36
+ throw new Error ( "Alias file not found in the program" ) ;
26
37
}
27
- | {
28
- type : "non-interface" ;
29
- statement : ts . Statement ;
38
+ aliasFileCache = aliasFile ;
39
+ }
40
+ const aliasFile = aliasFileCache ;
41
+ const statements = aliasFile . statements . flatMap ( ( statement ) => {
42
+ const name = getStatementDeclName ( statement ) ?? "" ;
43
+ const aliases = alias . get ( name ) ;
44
+ if ( ! aliases ) {
45
+ return [ statement ] ;
30
46
}
31
- ) & {
32
- sourceFile : ts . SourceFile ;
33
- } ;
47
+ return aliases . map ( ( aliasDetails ) => {
48
+ if ( aliasDetails . file !== targetFileName ) {
49
+ return statement ;
50
+ }
51
+ return replaceAliases ( statement , aliasDetails . replacement ) ;
52
+ } ) ;
53
+ } ) ;
34
54
35
- export type ReplacementMap = Map < ReplacementName , ReplacementTarget [ ] > ;
55
+ // Scan the target file
56
+ const replacementMap = scanStatements ( printer , statements , aliasFile ) ;
57
+ // mark everything as optional
58
+ for ( const targets of replacementMap . values ( ) ) {
59
+ for ( const target of targets ) {
60
+ target . optional = true ;
61
+ }
62
+ }
36
63
37
- export const declareGlobalSymbol = Symbol ( "declare global" ) ;
38
- export type ReplacementName = string | typeof declareGlobalSymbol ;
64
+ return {
65
+ replacementMap,
66
+ } ;
67
+ }
39
68
40
69
/**
41
70
* Scan better lib file to determine which statements need to be replaced.
@@ -56,104 +85,117 @@ export function scanBetterFile(
56
85
57
86
function scanStatements (
58
87
printer : ts . Printer ,
59
- statements : ts . NodeArray < ts . Statement > ,
88
+ statements : readonly ts . Statement [ ] ,
60
89
sourceFile : ts . SourceFile ,
61
90
) : ReplacementMap {
62
91
const replacementTargets = new Map < ReplacementName , ReplacementTarget [ ] > ( ) ;
63
92
for ( const statement of statements ) {
64
93
const name = getStatementDeclName ( statement ) ?? "" ;
65
- const aliasesMap =
66
- alias . get ( name ) ?? new Map ( [ [ name , new Map < string , string > ( ) ] ] ) ;
67
- for ( const [ targetName , typeMap ] of aliasesMap ) {
68
- const transformedStatement = replaceAliases ( statement , typeMap ) ;
69
- if ( ts . isInterfaceDeclaration ( transformedStatement ) ) {
70
- const members = new Map <
71
- string ,
72
- {
73
- member : ts . TypeElement ;
74
- text : string ;
75
- } [ ]
76
- > ( ) ;
77
- for ( const member of transformedStatement . members ) {
78
- const memberName = member . name ?. getText ( sourceFile ) ?? "" ;
79
- upsert ( members , memberName , ( members = [ ] ) => {
80
- const leadingSpacesMatch = / ^ \s * / . exec (
81
- member . getFullText ( sourceFile ) ,
82
- ) ;
83
- const leadingSpaces =
84
- leadingSpacesMatch !== null ? leadingSpacesMatch [ 0 ] : "" ;
85
- members . push ( {
86
- member,
87
- text :
88
- leadingSpaces +
89
- printer . printNode ( ts . EmitHint . Unspecified , member , sourceFile ) ,
90
- } ) ;
91
- return members ;
92
- } ) ;
93
- }
94
- upsert ( replacementTargets , targetName , ( targets = [ ] ) => {
95
- targets . push ( {
96
- type : "interface" ,
97
- members,
98
- originalStatement : transformedStatement ,
99
- sourceFile : sourceFile ,
94
+ const transformedStatement = statement ;
95
+ if ( ts . isInterfaceDeclaration ( transformedStatement ) ) {
96
+ const members = new Map <
97
+ string ,
98
+ {
99
+ member : ts . TypeElement ;
100
+ text : string ;
101
+ } [ ]
102
+ > ( ) ;
103
+ for ( const member of transformedStatement . members ) {
104
+ const memberName = member . name ?. getText ( sourceFile ) ?? "" ;
105
+ upsert ( members , memberName , ( members = [ ] ) => {
106
+ const leadingSpacesMatch = / ^ \s * / . exec (
107
+ member . getFullText ( sourceFile ) ,
108
+ ) ;
109
+ const leadingSpaces =
110
+ leadingSpacesMatch !== null ? leadingSpacesMatch [ 0 ] : "" ;
111
+ members . push ( {
112
+ member,
113
+ text :
114
+ leadingSpaces +
115
+ printer . printNode ( ts . EmitHint . Unspecified , member , sourceFile ) ,
100
116
} ) ;
101
- return targets ;
117
+ return members ;
102
118
} ) ;
103
- } else if (
104
- ts . isModuleDeclaration ( transformedStatement ) &&
105
- ts . isIdentifier ( transformedStatement . name ) &&
106
- transformedStatement . name . text === "global"
107
- ) {
108
- // declare global
109
- upsert ( replacementTargets , declareGlobalSymbol , ( targets = [ ] ) => {
110
- targets . push ( {
111
- type : "declare-global" ,
112
- originalStatement : transformedStatement ,
113
- statements :
114
- transformedStatement . body &&
115
- ts . isModuleBlock ( transformedStatement . body )
116
- ? scanStatements (
117
- printer ,
118
- transformedStatement . body . statements ,
119
- sourceFile ,
120
- )
121
- : new Map ( ) ,
122
- sourceFile : sourceFile ,
123
- } ) ;
124
- return targets ;
119
+ }
120
+ upsert ( replacementTargets , name , ( targets = [ ] ) => {
121
+ targets . push ( {
122
+ type : "interface" ,
123
+ members,
124
+ originalStatement : transformedStatement ,
125
+ optional : false ,
126
+ sourceFile : sourceFile ,
125
127
} ) ;
126
- } else {
127
- upsert ( replacementTargets , targetName , ( statements = [ ] ) => {
128
- statements . push ( {
129
- type : "non-interface" ,
130
- statement : transformedStatement ,
131
- sourceFile : sourceFile ,
132
- } ) ;
133
- return statements ;
128
+ return targets ;
129
+ } ) ;
130
+ } else if (
131
+ ts . isModuleDeclaration ( transformedStatement ) &&
132
+ ts . isIdentifier ( transformedStatement . name ) &&
133
+ transformedStatement . name . text === "global"
134
+ ) {
135
+ // declare global
136
+ upsert ( replacementTargets , declareGlobalSymbol , ( targets = [ ] ) => {
137
+ targets . push ( {
138
+ type : "declare-global" ,
139
+ originalStatement : transformedStatement ,
140
+ statements :
141
+ transformedStatement . body &&
142
+ ts . isModuleBlock ( transformedStatement . body )
143
+ ? scanStatements (
144
+ printer ,
145
+ transformedStatement . body . statements ,
146
+ sourceFile ,
147
+ )
148
+ : new Map ( ) ,
149
+ optional : false ,
150
+ sourceFile : sourceFile ,
134
151
} ) ;
135
- }
152
+ return targets ;
153
+ } ) ;
154
+ } else {
155
+ upsert ( replacementTargets , name , ( statements = [ ] ) => {
156
+ statements . push ( {
157
+ type : "non-interface" ,
158
+ statement : transformedStatement ,
159
+ optional : false ,
160
+ sourceFile : sourceFile ,
161
+ } ) ;
162
+ return statements ;
163
+ } ) ;
136
164
}
137
165
}
138
166
return replacementTargets ;
139
167
}
140
168
141
169
function replaceAliases (
142
170
statement : ts . Statement ,
143
- typeMap : Map < string , string > ,
171
+ replacement : Map < string , string > ,
144
172
) : ts . Statement {
145
- if ( typeMap . size === 0 ) return statement ;
146
173
return ts . transform ( statement , [
147
174
( context ) => ( sourceStatement ) => {
148
175
const visitor = ( node : ts . Node ) : ts . Node => {
176
+ if ( ts . isInterfaceDeclaration ( node ) ) {
177
+ const toName = replacement . get ( node . name . text ) ;
178
+ if ( toName === undefined ) {
179
+ return node ;
180
+ }
181
+ const visited = ts . visitEachChild ( node , visitor , context ) ;
182
+ return ts . factory . updateInterfaceDeclaration (
183
+ visited ,
184
+ visited . modifiers ,
185
+ ts . factory . createIdentifier ( toName ) ,
186
+ visited . typeParameters ,
187
+ visited . heritageClauses ,
188
+ visited . members ,
189
+ ) ;
190
+ }
149
191
if ( ts . isTypeReferenceNode ( node ) && ts . isIdentifier ( node . typeName ) ) {
150
- const replacementType = typeMap . get ( node . typeName . text ) ;
151
- if ( replacementType === undefined ) {
192
+ const toName = replacement . get ( node . typeName . text ) ;
193
+ if ( toName === undefined ) {
152
194
return node ;
153
195
}
154
196
return ts . factory . updateTypeReferenceNode (
155
197
node ,
156
- ts . factory . createIdentifier ( replacementType ) ,
198
+ ts . factory . createIdentifier ( toName ) ,
157
199
node . typeArguments ,
158
200
) ;
159
201
}
0 commit comments