@@ -43,11 +43,18 @@ export class TSServiceManager {
43
43
export class TSService {
44
44
private readonly watch : ts . WatchOfConfigFile < ts . BuilderProgram > ;
45
45
46
+ private readonly patchedHostSet = new WeakSet < ts . CompilerHost > ( ) ;
47
+
46
48
private readonly tsconfigPath : string ;
47
49
48
50
public readonly extraFileExtensions : string [ ] ;
49
51
50
- private currTarget = {
52
+ private currTarget : {
53
+ code : string ;
54
+ filePath : string ;
55
+ sourceFile ?: ts . SourceFile ;
56
+ dirMap : Map < string , { name : string ; path : string } > ;
57
+ } = {
51
58
code : "" ,
52
59
filePath : "" ,
53
60
dirMap : new Map < string , { name : string ; path : string } > ( ) ,
@@ -119,7 +126,90 @@ export class TSService {
119
126
tsconfigPath : string ,
120
127
extraFileExtensions : string [ ]
121
128
) : ts . WatchOfConfigFile < ts . BuilderProgram > {
122
- const normalizedTsconfigPaths = new Set ( [ normalizeFileName ( tsconfigPath ) ] ) ;
129
+ type CreateProgram = ts . CreateProgram < ts . BuilderProgram > ;
130
+
131
+ const createAbstractBuilder = (
132
+ ...args : Parameters < CreateProgram >
133
+ ) : ReturnType < CreateProgram > => {
134
+ const [
135
+ rootNames ,
136
+ options ,
137
+ argHost ,
138
+ oldProgram ,
139
+ configFileParsingDiagnostics ,
140
+ projectReferences ,
141
+ ] = args ;
142
+
143
+ const host : ts . CompilerHost = argHost ! ;
144
+ if ( ! this . patchedHostSet . has ( host ) ) {
145
+ this . patchedHostSet . add ( host ) ;
146
+
147
+ const getTargetSourceFile = (
148
+ fileName : string ,
149
+ languageVersionOrOptions : ts . ScriptTarget | ts . CreateSourceFileOptions
150
+ ) => {
151
+ if (
152
+ this . currTarget . filePath === normalizeFileName ( fileName ) &&
153
+ isExtra ( fileName , extraFileExtensions )
154
+ ) {
155
+ return ( this . currTarget . sourceFile ??= ts . createSourceFile (
156
+ this . currTarget . filePath ,
157
+ this . currTarget . code ,
158
+ languageVersionOrOptions ,
159
+ true ,
160
+ ts . ScriptKind . TSX
161
+ ) ) ;
162
+ }
163
+ return null ;
164
+ } ;
165
+
166
+ /* eslint-disable @typescript-eslint/unbound-method -- ignore */
167
+ const original = {
168
+ getSourceFile : host . getSourceFile ,
169
+ getSourceFileByPath : host . getSourceFileByPath ! ,
170
+ } ;
171
+ /* eslint-enable @typescript-eslint/unbound-method -- ignore */
172
+ host . getSourceFile = ( fileName , languageVersionOrOptions , ...args ) => {
173
+ const originalSourceFile = original . getSourceFile . call (
174
+ host ,
175
+ fileName ,
176
+ languageVersionOrOptions ,
177
+ ...args
178
+ ) ;
179
+ return (
180
+ getTargetSourceFile ( fileName , languageVersionOrOptions ) ??
181
+ originalSourceFile
182
+ ) ;
183
+ } ;
184
+ host . getSourceFileByPath = (
185
+ fileName ,
186
+ path ,
187
+ languageVersionOrOptions ,
188
+ ...args
189
+ ) => {
190
+ const originalSourceFile = original . getSourceFileByPath . call (
191
+ host ,
192
+ fileName ,
193
+ path ,
194
+ languageVersionOrOptions ,
195
+ ...args
196
+ ) ;
197
+ return (
198
+ getTargetSourceFile ( fileName , languageVersionOrOptions ) ??
199
+ originalSourceFile
200
+ ) ;
201
+ } ;
202
+ }
203
+ return ts . createAbstractBuilder (
204
+ rootNames ,
205
+ options ,
206
+ host ,
207
+ oldProgram ,
208
+ configFileParsingDiagnostics ,
209
+ projectReferences
210
+ ) ;
211
+ } ;
212
+
123
213
const watchCompilerHost = ts . createWatchCompilerHost (
124
214
tsconfigPath ,
125
215
{
@@ -132,19 +222,19 @@ export class TSService {
132
222
allowNonTsExtensions : true ,
133
223
} ,
134
224
ts . sys ,
135
- ts . createAbstractBuilder ,
225
+ createAbstractBuilder ,
136
226
( diagnostic ) => {
137
227
throw new Error ( formatDiagnostics ( [ diagnostic ] ) ) ;
138
228
} ,
139
229
( ) => {
140
230
// Not reported in reportWatchStatus.
141
231
} ,
142
- undefined
143
- // extraFileExtensions.map((extension) => ({
144
- // extension,
145
- // isMixedContent: true,
146
- // scriptKind: ts.ScriptKind.Deferred,
147
- // }))
232
+ undefined ,
233
+ extraFileExtensions . map ( ( extension ) => ( {
234
+ extension,
235
+ isMixedContent : true ,
236
+ scriptKind : ts . ScriptKind . Deferred ,
237
+ } ) )
148
238
) ;
149
239
const original = {
150
240
// eslint-disable-next-line @typescript-eslint/unbound-method -- Store original
@@ -159,11 +249,12 @@ export class TSService {
159
249
getDirectories : watchCompilerHost . getDirectories ! ,
160
250
} ;
161
251
watchCompilerHost . getDirectories = ( dirName , ...args ) => {
162
- return distinctArray (
252
+ const result = distinctArray (
163
253
...original . getDirectories . call ( watchCompilerHost , dirName , ...args ) ,
164
254
// Include the path to the target file if the target file does not actually exist.
165
255
this . currTarget . dirMap . get ( normalizeFileName ( dirName ) ) ?. name
166
256
) ;
257
+ return result ;
167
258
} ;
168
259
watchCompilerHost . directoryExists = ( dirName , ...args ) => {
169
260
return (
@@ -173,21 +264,23 @@ export class TSService {
173
264
) ;
174
265
} ;
175
266
watchCompilerHost . readDirectory = ( dirName , ...args ) => {
176
- const results = original . readDirectory . call (
267
+ let results = original . readDirectory . call (
177
268
watchCompilerHost ,
178
269
dirName ,
179
270
...args
180
271
) ;
181
272
182
273
// Include the target file if the target file does not actually exist.
183
274
const file = this . currTarget . dirMap . get ( normalizeFileName ( dirName ) ) ;
184
- if ( file && file . path === this . currTarget . filePath ) {
185
- results . push ( file . path ) ;
275
+ if ( file ) {
276
+ if ( file . path === this . currTarget . filePath ) {
277
+ results . push ( file . path ) ;
278
+ } else {
279
+ results = results . filter ( ( f ) => file . path !== f && file . name !== f ) ;
280
+ }
186
281
}
187
282
188
- return distinctArray ( ...results ) . map ( ( result ) =>
189
- toVirtualTSXFileName ( result , extraFileExtensions )
190
- ) ;
283
+ return distinctArray ( ...results ) ;
191
284
} ;
192
285
watchCompilerHost . readFile = ( fileName , ...args ) => {
193
286
const realFileName = toRealFileName ( fileName , extraFileExtensions ) ;
@@ -225,35 +318,6 @@ export class TSService {
225
318
if ( ! code ) {
226
319
return code ;
227
320
}
228
- // If it's tsconfig, it will take care of rewriting the `include`.
229
- if ( normalizedTsconfigPaths . has ( normalized ) ) {
230
- const configJson = ts . parseConfigFileTextToJson ( realFileName , code ) ;
231
- if ( ! configJson . config ) {
232
- return code ;
233
- }
234
- if ( configJson . config . extends ) {
235
- // If it references another tsconfig, rewrite the `include` for that file as well.
236
- for ( const extendConfigPath of [ configJson . config . extends ] . flat ( ) ) {
237
- normalizedTsconfigPaths . add (
238
- normalizeFileName (
239
- toAbsolutePath ( extendConfigPath , path . dirname ( normalized ) )
240
- )
241
- ) ;
242
- }
243
- }
244
-
245
- if ( ! configJson . config . include ) {
246
- return code ;
247
- }
248
- const include = [ configJson . config . include ]
249
- . flat ( )
250
- . map ( ( s ) => toVirtualTSXFileName ( s , extraFileExtensions ) ) ;
251
-
252
- return JSON . stringify ( {
253
- ...configJson . config ,
254
- include,
255
- } ) ;
256
- }
257
321
return transformExtraFile ( code , {
258
322
filePath : normalized ,
259
323
current : false ,
@@ -377,14 +441,6 @@ function getRefreshTargetFileNames(
377
441
return [ fileName ] ;
378
442
}
379
443
380
- /** If the given filename has extra extensions, returns the real virtual filename. */
381
- function toVirtualTSXFileName ( fileName : string , extraFileExtensions : string [ ] ) {
382
- if ( isExtra ( fileName , extraFileExtensions ) ) {
383
- return `${ fileName } .tsx` ;
384
- }
385
- return fileName ;
386
- }
387
-
388
444
/** If the given filename has extra extensions, returns the d.ts filename. */
389
445
function toExtraDtsFileName ( fileName : string , extraFileExtensions : string [ ] ) {
390
446
if ( isExtra ( fileName , extraFileExtensions ) ) {
0 commit comments