@@ -4,38 +4,67 @@ import { alias } from "../util/alias";
44import { upsert } from "../util/upsert" ;
55import { getStatementDeclName } from "./ast/getStatementDeclName" ;
66import { projectDir } from "./projectDir" ;
7+ import {
8+ declareGlobalSymbol ,
9+ type ReplacementMap ,
10+ type ReplacementName ,
11+ type ReplacementTarget ,
12+ } from "./ReplacementMap" ;
713
814const betterLibDir = path . join ( projectDir , "lib" ) ;
15+ const aliasFilePath = path . join ( betterLibDir , "alias.d.ts" ) ;
916
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" ) ;
2637 }
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 ] ;
3046 }
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+ } ) ;
3454
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+ }
3663
37- export const declareGlobalSymbol = Symbol ( "declare global" ) ;
38- export type ReplacementName = string | typeof declareGlobalSymbol ;
64+ return {
65+ replacementMap,
66+ } ;
67+ }
3968
4069/**
4170 * Scan better lib file to determine which statements need to be replaced.
@@ -56,104 +85,117 @@ export function scanBetterFile(
5685
5786function scanStatements (
5887 printer : ts . Printer ,
59- statements : ts . NodeArray < ts . Statement > ,
88+ statements : readonly ts . Statement [ ] ,
6089 sourceFile : ts . SourceFile ,
6190) : ReplacementMap {
6291 const replacementTargets = new Map < ReplacementName , ReplacementTarget [ ] > ( ) ;
6392 for ( const statement of statements ) {
6493 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 ) ,
100116 } ) ;
101- return targets ;
117+ return members ;
102118 } ) ;
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 ,
125127 } ) ;
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 ,
134151 } ) ;
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+ } ) ;
136164 }
137165 }
138166 return replacementTargets ;
139167}
140168
141169function replaceAliases (
142170 statement : ts . Statement ,
143- typeMap : Map < string , string > ,
171+ replacement : Map < string , string > ,
144172) : ts . Statement {
145- if ( typeMap . size === 0 ) return statement ;
146173 return ts . transform ( statement , [
147174 ( context ) => ( sourceStatement ) => {
148175 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+ }
149191 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 ) {
152194 return node ;
153195 }
154196 return ts . factory . updateTypeReferenceNode (
155197 node ,
156- ts . factory . createIdentifier ( replacementType ) ,
198+ ts . factory . createIdentifier ( toName ) ,
157199 node . typeArguments ,
158200 ) ;
159201 }
0 commit comments