@@ -3,17 +3,22 @@ namespace ts.Completions.PathCompletions {
33 export interface NameAndKind {
44 readonly name : string ;
55 readonly kind : ScriptElementKind . scriptElement | ScriptElementKind . directory | ScriptElementKind . externalModuleName ;
6+ readonly extension : Extension | undefined ;
67 }
78 export interface PathCompletion extends NameAndKind {
89 readonly span : TextSpan | undefined ;
910 }
1011
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 } ;
1314 }
15+ function directoryResult ( name : string ) : NameAndKind {
16+ return nameAndKind ( name , ScriptElementKind . directory , /*extension*/ undefined ) ;
17+ }
18+
1419 function addReplacementSpans ( text : string , textStart : number , names : ReadonlyArray < NameAndKind > ) : ReadonlyArray < PathCompletion > {
1520 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 } ) ) ;
1722 }
1823
1924 export function getStringLiteralCompletionsFromModuleNames ( sourceFile : SourceFile , node : LiteralExpression , compilerOptions : CompilerOptions , host : LanguageServiceHost , typeChecker : TypeChecker ) : ReadonlyArray < PathCompletion > {
@@ -129,22 +134,19 @@ namespace ts.Completions.PathCompletions {
129134 *
130135 * both foo.ts and foo.tsx become foo
131136 */
132- const foundFiles = createMap < true > ( ) ;
137+ const foundFiles = createMap < Extension | undefined > ( ) ; // maps file to its extension
133138 for ( let filePath of files ) {
134139 filePath = normalizePath ( filePath ) ;
135140 if ( exclude && comparePaths ( filePath , exclude , scriptPath , ignoreCase ) === Comparison . EqualTo ) {
136141 continue ;
137142 }
138143
139144 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 ) ) ;
144146 }
145147
146- forEachKey ( foundFiles , foundFile => {
147- result . push ( nameAndKind ( foundFile , ScriptElementKind . scriptElement ) ) ;
148+ foundFiles . forEach ( ( ext , foundFile ) => {
149+ result . push ( nameAndKind ( foundFile , ScriptElementKind . scriptElement , ext ) ) ;
148150 } ) ;
149151 }
150152
@@ -155,7 +157,7 @@ namespace ts.Completions.PathCompletions {
155157 for ( const directory of directories ) {
156158 const directoryName = getBaseFileName ( normalizePath ( directory ) ) ;
157159 if ( directoryName !== "@types" ) {
158- result . push ( nameAndKind ( directoryName , ScriptElementKind . directory ) ) ;
160+ result . push ( directoryResult ( directoryName ) ) ;
159161 }
160162 }
161163 }
@@ -183,10 +185,10 @@ namespace ts.Completions.PathCompletions {
183185 if ( ! hasProperty ( paths , path ) ) continue ;
184186 const patterns = paths [ path ] ;
185187 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 ) ) {
187189 // Path mappings may provide a duplicate way to get to something we've already added, so don't add again.
188190 if ( ! result . some ( entry => entry . name === name ) ) {
189- result . push ( nameAndKind ( name , kind ) ) ;
191+ result . push ( nameAndKind ( name , kind , extension ) ) ;
190192 }
191193 }
192194 }
@@ -200,7 +202,7 @@ namespace ts.Completions.PathCompletions {
200202 * Modules from node_modules (i.e. those listed in package.json)
201203 * This includes all files that are found in node_modules/moduleName/ with acceptable file extensions
202204 */
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 > {
204206 const { baseUrl, paths } = compilerOptions ;
205207
206208 const result : NameAndKind [ ] = [ ] ;
@@ -217,7 +219,7 @@ namespace ts.Completions.PathCompletions {
217219
218220 const fragmentDirectory = getFragmentDirectory ( fragment ) ;
219221 for ( const ambientName of getAmbientModuleCompletions ( fragment , fragmentDirectory , typeChecker ) ) {
220- result . push ( nameAndKind ( ambientName , ScriptElementKind . externalModuleName ) ) ;
222+ result . push ( nameAndKind ( ambientName , ScriptElementKind . externalModuleName , /*extension*/ undefined ) ) ;
221223 }
222224
223225 getCompletionEntriesFromTypings ( host , compilerOptions , scriptPath , fragmentDirectory , extensionOptions , result ) ;
@@ -230,7 +232,7 @@ namespace ts.Completions.PathCompletions {
230232 for ( const moduleName of enumerateNodeModulesVisibleToScript ( host , scriptPath ) ) {
231233 if ( ! result . some ( entry => entry . name === moduleName ) ) {
232234 foundGlobal = true ;
233- result . push ( nameAndKind ( moduleName , ScriptElementKind . externalModuleName ) ) ;
235+ result . push ( nameAndKind ( moduleName , ScriptElementKind . externalModuleName , /*extension*/ undefined ) ) ;
234236 }
235237 }
236238 }
@@ -265,7 +267,7 @@ namespace ts.Completions.PathCompletions {
265267 getModulesForPathsPattern ( remainingFragment , baseUrl , pattern , fileExtensions , host ) ) ;
266268
267269 function justPathMappingName ( name : string ) : ReadonlyArray < NameAndKind > {
268- return startsWith ( name , fragment ) ? [ { name, kind : ScriptElementKind . directory } ] : emptyArray ;
270+ return startsWith ( name , fragment ) ? [ directoryResult ( name ) ] : emptyArray ;
269271 }
270272 }
271273
@@ -301,15 +303,21 @@ namespace ts.Completions.PathCompletions {
301303 // doesn't support. For now, this is safer but slower
302304 const includeGlob = normalizedSuffix ? "**/*" : "./*" ;
303305
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 ) ;
312314 } ) ;
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+ }
313321 }
314322
315323 function withoutStartAndEnd ( s : string , start : string , end : string ) : string | undefined {
@@ -382,7 +390,10 @@ namespace ts.Completions.PathCompletions {
382390 if ( options . types && ! contains ( options . types , packageName ) ) continue ;
383391
384392 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+ }
386397 }
387398 else {
388399 const baseDirectory = combinePaths ( directory , typeDirectoryName ) ;
@@ -393,13 +404,6 @@ namespace ts.Completions.PathCompletions {
393404 }
394405 }
395406 }
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- }
403407 }
404408
405409 function findPackageJsons ( directory : string , host : LanguageServiceHost ) : string [ ] {
0 commit comments