@@ -3,17 +3,22 @@ namespace ts.Completions.PathCompletions {
3
3
export interface NameAndKind {
4
4
readonly name : string ;
5
5
readonly kind : ScriptElementKind . scriptElement | ScriptElementKind . directory | ScriptElementKind . externalModuleName ;
6
+ readonly extension : Extension | undefined ;
6
7
}
7
8
export interface PathCompletion extends NameAndKind {
8
9
readonly span : TextSpan | undefined ;
9
10
}
10
11
11
- function nameAndKind ( name : string , kind : NameAndKind [ "kind" ] ) : NameAndKind {
12
- return { name, kind } ;
12
+ function nameAndKind ( name : string , kind : NameAndKind [ "kind" ] , extension : Extension | undefined ) : NameAndKind {
13
+ return { name, kind, extension } ;
13
14
}
15
+ function directoryResult ( name : string ) : NameAndKind {
16
+ return nameAndKind ( name , ScriptElementKind . directory , /*extension*/ undefined ) ;
17
+ }
18
+
14
19
function addReplacementSpans ( text : string , textStart : number , names : ReadonlyArray < NameAndKind > ) : ReadonlyArray < PathCompletion > {
15
20
const span = getDirectoryFragmentTextSpan ( text , textStart ) ;
16
- return names . map ( ( { name, kind } ) : PathCompletion => ( { name, kind, span } ) ) ;
21
+ return names . map ( ( { name, kind, extension } ) : PathCompletion => ( { name, kind, extension , span } ) ) ;
17
22
}
18
23
19
24
export function getStringLiteralCompletionsFromModuleNames ( sourceFile : SourceFile , node : LiteralExpression , compilerOptions : CompilerOptions , host : LanguageServiceHost , typeChecker : TypeChecker ) : ReadonlyArray < PathCompletion > {
@@ -129,22 +134,19 @@ namespace ts.Completions.PathCompletions {
129
134
*
130
135
* both foo.ts and foo.tsx become foo
131
136
*/
132
- const foundFiles = createMap < true > ( ) ;
137
+ const foundFiles = createMap < Extension | undefined > ( ) ; // maps file to its extension
133
138
for ( let filePath of files ) {
134
139
filePath = normalizePath ( filePath ) ;
135
140
if ( exclude && comparePaths ( filePath , exclude , scriptPath , ignoreCase ) === Comparison . EqualTo ) {
136
141
continue ;
137
142
}
138
143
139
144
const foundFileName = includeExtensions || fileExtensionIs ( filePath , Extension . Json ) ? getBaseFileName ( filePath ) : removeFileExtension ( getBaseFileName ( filePath ) ) ;
140
-
141
- if ( ! foundFiles . has ( foundFileName ) ) {
142
- foundFiles . set ( foundFileName , true ) ;
143
- }
145
+ foundFiles . set ( foundFileName , tryGetExtensionFromPath ( filePath ) ) ;
144
146
}
145
147
146
- forEachKey ( foundFiles , foundFile => {
147
- result . push ( nameAndKind ( foundFile , ScriptElementKind . scriptElement ) ) ;
148
+ foundFiles . forEach ( ( ext , foundFile ) => {
149
+ result . push ( nameAndKind ( foundFile , ScriptElementKind . scriptElement , ext ) ) ;
148
150
} ) ;
149
151
}
150
152
@@ -155,7 +157,7 @@ namespace ts.Completions.PathCompletions {
155
157
for ( const directory of directories ) {
156
158
const directoryName = getBaseFileName ( normalizePath ( directory ) ) ;
157
159
if ( directoryName !== "@types" ) {
158
- result . push ( nameAndKind ( directoryName , ScriptElementKind . directory ) ) ;
160
+ result . push ( directoryResult ( directoryName ) ) ;
159
161
}
160
162
}
161
163
}
@@ -183,10 +185,10 @@ namespace ts.Completions.PathCompletions {
183
185
if ( ! hasProperty ( paths , path ) ) continue ;
184
186
const patterns = paths [ path ] ;
185
187
if ( patterns ) {
186
- for ( const { name, kind } of getCompletionsForPathMapping ( path , patterns , fragment , baseDirectory , fileExtensions , host ) ) {
188
+ for ( const { name, kind, extension } of getCompletionsForPathMapping ( path , patterns , fragment , baseDirectory , fileExtensions , host ) ) {
187
189
// Path mappings may provide a duplicate way to get to something we've already added, so don't add again.
188
190
if ( ! result . some ( entry => entry . name === name ) ) {
189
- result . push ( nameAndKind ( name , kind ) ) ;
191
+ result . push ( nameAndKind ( name , kind , extension ) ) ;
190
192
}
191
193
}
192
194
}
@@ -200,7 +202,7 @@ namespace ts.Completions.PathCompletions {
200
202
* Modules from node_modules (i.e. those listed in package.json)
201
203
* This includes all files that are found in node_modules/moduleName/ with acceptable file extensions
202
204
*/
203
- function getCompletionEntriesForNonRelativeModules ( fragment : string , scriptPath : string , compilerOptions : CompilerOptions , host : LanguageServiceHost , typeChecker : TypeChecker ) : NameAndKind [ ] {
205
+ function getCompletionEntriesForNonRelativeModules ( fragment : string , scriptPath : string , compilerOptions : CompilerOptions , host : LanguageServiceHost , typeChecker : TypeChecker ) : ReadonlyArray < NameAndKind > {
204
206
const { baseUrl, paths } = compilerOptions ;
205
207
206
208
const result : NameAndKind [ ] = [ ] ;
@@ -217,7 +219,7 @@ namespace ts.Completions.PathCompletions {
217
219
218
220
const fragmentDirectory = getFragmentDirectory ( fragment ) ;
219
221
for ( const ambientName of getAmbientModuleCompletions ( fragment , fragmentDirectory , typeChecker ) ) {
220
- result . push ( nameAndKind ( ambientName , ScriptElementKind . externalModuleName ) ) ;
222
+ result . push ( nameAndKind ( ambientName , ScriptElementKind . externalModuleName , /*extension*/ undefined ) ) ;
221
223
}
222
224
223
225
getCompletionEntriesFromTypings ( host , compilerOptions , scriptPath , fragmentDirectory , extensionOptions , result ) ;
@@ -230,7 +232,7 @@ namespace ts.Completions.PathCompletions {
230
232
for ( const moduleName of enumerateNodeModulesVisibleToScript ( host , scriptPath ) ) {
231
233
if ( ! result . some ( entry => entry . name === moduleName ) ) {
232
234
foundGlobal = true ;
233
- result . push ( nameAndKind ( moduleName , ScriptElementKind . externalModuleName ) ) ;
235
+ result . push ( nameAndKind ( moduleName , ScriptElementKind . externalModuleName , /*extension*/ undefined ) ) ;
234
236
}
235
237
}
236
238
}
@@ -265,7 +267,7 @@ namespace ts.Completions.PathCompletions {
265
267
getModulesForPathsPattern ( remainingFragment , baseUrl , pattern , fileExtensions , host ) ) ;
266
268
267
269
function justPathMappingName ( name : string ) : ReadonlyArray < NameAndKind > {
268
- return startsWith ( name , fragment ) ? [ { name, kind : ScriptElementKind . directory } ] : emptyArray ;
270
+ return startsWith ( name , fragment ) ? [ directoryResult ( name ) ] : emptyArray ;
269
271
}
270
272
}
271
273
@@ -301,15 +303,21 @@ namespace ts.Completions.PathCompletions {
301
303
// doesn't support. For now, this is safer but slower
302
304
const includeGlob = normalizedSuffix ? "**/*" : "./*" ;
303
305
304
- const matches = tryReadDirectory ( host , baseDirectory , fileExtensions , /*exclude*/ undefined , [ includeGlob ] ) . map < NameAndKind > ( name => ( { name , kind : ScriptElementKind . scriptElement } ) ) ;
305
- const directories = tryGetDirectories ( host , baseDirectory ) . map ( d => combinePaths ( baseDirectory , d ) ) . map < NameAndKind > ( name => ( { name , kind : ScriptElementKind . directory } ) ) ;
306
-
307
- // Trim away prefix and suffix
308
- return mapDefined < NameAndKind , NameAndKind > ( concatenate ( matches , directories ) , ( { name , kind } ) => {
309
- const normalizedMatch = normalizePath ( name ) ;
310
- const inner = withoutStartAndEnd ( normalizedMatch , completePrefix , normalizedSuffix ) ;
311
- return inner !== undefined ? { name : removeLeadingDirectorySeparator ( removeFileExtension ( inner ) ) , kind } : undefined ;
306
+ const matches = mapDefined ( tryReadDirectory ( host , baseDirectory , fileExtensions , /*exclude*/ undefined , [ includeGlob ] ) , match => {
307
+ const extension = tryGetExtensionFromPath ( match ) ;
308
+ const name = trimPrefixAndSuffix ( match ) ;
309
+ return name === undefined ? undefined : nameAndKind ( removeFileExtension ( name ) , ScriptElementKind . scriptElement , extension ) ;
310
+ } ) ;
311
+ const directories = mapDefined ( tryGetDirectories ( host , baseDirectory ) . map ( d => combinePaths ( baseDirectory , d ) ) , dir => {
312
+ const name = trimPrefixAndSuffix ( dir ) ;
313
+ return name === undefined ? undefined : directoryResult ( name ) ;
312
314
} ) ;
315
+ return [ ...matches , ...directories ] ;
316
+
317
+ function trimPrefixAndSuffix ( path : string ) : string | undefined {
318
+ const inner = withoutStartAndEnd ( normalizePath ( path ) , completePrefix , normalizedSuffix ) ;
319
+ return inner === undefined ? undefined : removeLeadingDirectorySeparator ( inner ) ;
320
+ }
313
321
}
314
322
315
323
function withoutStartAndEnd ( s : string , start : string , end : string ) : string | undefined {
@@ -382,7 +390,10 @@ namespace ts.Completions.PathCompletions {
382
390
if ( options . types && ! contains ( options . types , packageName ) ) continue ;
383
391
384
392
if ( fragmentDirectory === undefined ) {
385
- pushResult ( packageName ) ;
393
+ if ( ! seen . has ( packageName ) ) {
394
+ result . push ( nameAndKind ( packageName , ScriptElementKind . externalModuleName , /*extension*/ undefined ) ) ;
395
+ seen . set ( packageName , true ) ;
396
+ }
386
397
}
387
398
else {
388
399
const baseDirectory = combinePaths ( directory , typeDirectoryName ) ;
@@ -393,13 +404,6 @@ namespace ts.Completions.PathCompletions {
393
404
}
394
405
}
395
406
}
396
-
397
- function pushResult ( moduleName : string ) {
398
- if ( ! seen . has ( moduleName ) ) {
399
- result . push ( nameAndKind ( moduleName , ScriptElementKind . externalModuleName ) ) ;
400
- seen . set ( moduleName , true ) ;
401
- }
402
- }
403
407
}
404
408
405
409
function findPackageJsons ( directory : string , host : LanguageServiceHost ) : string [ ] {
0 commit comments