11/* @internal */
22namespace ts . Completions . PathCompletions {
3- export function getStringLiteralCompletionsFromModuleNames ( node : LiteralExpression , compilerOptions : CompilerOptions , host : LanguageServiceHost , typeChecker : TypeChecker ) : CompletionEntry [ ] {
3+ export function getStringLiteralCompletionsFromModuleNames ( sourceFile : SourceFile , node : LiteralExpression , compilerOptions : CompilerOptions , host : LanguageServiceHost , typeChecker : TypeChecker ) : CompletionEntry [ ] {
44 const literalValue = normalizeSlashes ( node . text ) ;
55
66 const scriptPath = node . getSourceFile ( ) . path ;
77 const scriptDirectory = getDirectoryPath ( scriptPath ) ;
88
9- const span = getDirectoryFragmentTextSpan ( ( < StringLiteral > node ) . text , node . getStart ( ) + 1 ) ;
9+ const span = getDirectoryFragmentTextSpan ( ( < StringLiteral > node ) . text , node . getStart ( sourceFile ) + 1 ) ;
1010 if ( isPathRelativeToScript ( literalValue ) || isRootedDiskPath ( literalValue ) ) {
1111 const extensions = getSupportedExtensions ( compilerOptions ) ;
1212 if ( compilerOptions . rootDirs ) {
@@ -147,25 +147,15 @@ namespace ts.Completions.PathCompletions {
147147 getCompletionEntriesForDirectoryFragment ( fragment , normalizePath ( absolute ) , fileExtensions , /*includeExtensions*/ false , span , host , /*exclude*/ undefined , result ) ;
148148
149149 for ( const path in paths ) {
150- if ( ! paths . hasOwnProperty ( path ) ) continue ;
151150 const patterns = paths [ path ] ;
152- if ( ! patterns ) continue ;
153-
154- if ( path === "*" ) {
155- for ( const pattern of patterns ) {
156- for ( const match of getModulesForPathsPattern ( fragment , baseUrl , pattern , fileExtensions , host ) ) {
157- // Path mappings may provide a duplicate way to get to something we've already added, so don't add again.
158- if ( result . some ( entry => entry . name === match ) ) continue ;
159- result . push ( createCompletionEntryForModule ( match , ScriptElementKind . externalModuleName , span ) ) ;
151+ if ( paths . hasOwnProperty ( path ) && patterns ) {
152+ for ( const pathCompletion of getCompletionsForPathMapping ( path , patterns , fragment , baseUrl , fileExtensions , host ) ) {
153+ // Path mappings may provide a duplicate way to get to something we've already added, so don't add again.
154+ if ( ! result . some ( entry => entry . name === pathCompletion ) ) {
155+ result . push ( createCompletionEntryForModule ( pathCompletion , ScriptElementKind . externalModuleName , span ) ) ;
160156 }
161157 }
162158 }
163- else if ( startsWith ( path , fragment ) ) {
164- if ( patterns . length === 1 ) {
165- if ( result . some ( entry => entry . name === path ) ) continue ;
166- result . push ( createCompletionEntryForModule ( path , ScriptElementKind . externalModuleName , span ) ) ;
167- }
168- }
169159 }
170160 }
171161
@@ -187,52 +177,65 @@ namespace ts.Completions.PathCompletions {
187177 return result ;
188178 }
189179
190- function getModulesForPathsPattern ( fragment : string , baseUrl : string , pattern : string , fileExtensions : ReadonlyArray < string > , host : LanguageServiceHost ) : string [ ] {
191- if ( host . readDirectory ) {
192- const parsed = hasZeroOrOneAsteriskCharacter ( pattern ) ? tryParsePattern ( pattern ) : undefined ;
193- if ( parsed ) {
194- // The prefix has two effective parts: the directory path and the base component after the filepath that is not a
195- // full directory component. For example: directory/path/of/prefix/base*
196- const normalizedPrefix = normalizeAndPreserveTrailingSlash ( parsed . prefix ) ;
197- const normalizedPrefixDirectory = getDirectoryPath ( normalizedPrefix ) ;
198- const normalizedPrefixBase = getBaseFileName ( normalizedPrefix ) ;
199-
200- const fragmentHasPath = stringContains ( fragment , directorySeparator ) ;
201-
202- // Try and expand the prefix to include any path from the fragment so that we can limit the readDirectory call
203- const expandedPrefixDirectory = fragmentHasPath ? combinePaths ( normalizedPrefixDirectory , normalizedPrefixBase + getDirectoryPath ( fragment ) ) : normalizedPrefixDirectory ;
204-
205- const normalizedSuffix = normalizePath ( parsed . suffix ) ;
206- const baseDirectory = combinePaths ( baseUrl , expandedPrefixDirectory ) ;
207- const completePrefix = fragmentHasPath ? baseDirectory : ensureTrailingDirectorySeparator ( baseDirectory ) + normalizedPrefixBase ;
208-
209- // If we have a suffix, then we need to read the directory all the way down. We could create a glob
210- // that encodes the suffix, but we would have to escape the character "?" which readDirectory
211- // doesn't support. For now, this is safer but slower
212- const includeGlob = normalizedSuffix ? "**/*" : "./*" ;
213-
214- const matches = tryReadDirectory ( host , baseDirectory , fileExtensions , /*exclude*/ undefined , [ includeGlob ] ) ;
215- if ( matches ) {
216- const result : string [ ] = [ ] ;
217-
218- // Trim away prefix and suffix
219- for ( const match of matches ) {
220- const normalizedMatch = normalizePath ( match ) ;
221- if ( ! endsWith ( normalizedMatch , normalizedSuffix ) || ! startsWith ( normalizedMatch , completePrefix ) ) {
222- continue ;
223- }
180+ function getCompletionsForPathMapping (
181+ path : string , patterns : ReadonlyArray < string > , fragment : string , baseUrl : string , fileExtensions : ReadonlyArray < string > , host : LanguageServiceHost ,
182+ ) : string [ ] {
183+ if ( ! endsWith ( path , "*" ) ) {
184+ // For a path mapping "foo": ["/x/y/z.ts"], add "foo" itself as a completion.
185+ return ! stringContains ( path , "*" ) && startsWith ( path , fragment ) ? [ path ] : emptyArray ;
186+ }
224187
225- const start = completePrefix . length ;
226- const length = normalizedMatch . length - start - normalizedSuffix . length ;
188+ const pathPrefix = path . slice ( 0 , path . length - 1 ) ;
189+ if ( ! startsWith ( fragment , pathPrefix ) ) {
190+ return emptyArray ;
191+ }
227192
228- result . push ( removeFileExtension ( normalizedMatch . substr ( start , length ) ) ) ;
229- }
230- return result ;
231- }
232- }
193+ const remainingFragment = fragment . slice ( pathPrefix . length ) ;
194+ return flatMap ( patterns , pattern => getModulesForPathsPattern ( remainingFragment , baseUrl , pattern , fileExtensions , host ) ) ;
195+ }
196+
197+ function getModulesForPathsPattern ( fragment : string , baseUrl : string , pattern : string , fileExtensions : ReadonlyArray < string > , host : LanguageServiceHost ) : string [ ] | undefined {
198+ if ( ! host . readDirectory ) {
199+ return undefined ;
233200 }
234201
235- return undefined ;
202+ const parsed = hasZeroOrOneAsteriskCharacter ( pattern ) ? tryParsePattern ( pattern ) : undefined ;
203+ if ( ! parsed ) {
204+ return undefined ;
205+ }
206+
207+ // The prefix has two effective parts: the directory path and the base component after the filepath that is not a
208+ // full directory component. For example: directory/path/of/prefix/base*
209+ const normalizedPrefix = normalizeAndPreserveTrailingSlash ( parsed . prefix ) ;
210+ const normalizedPrefixDirectory = getDirectoryPath ( normalizedPrefix ) ;
211+ const normalizedPrefixBase = getBaseFileName ( normalizedPrefix ) ;
212+
213+ const fragmentHasPath = stringContains ( fragment , directorySeparator ) ;
214+
215+ // Try and expand the prefix to include any path from the fragment so that we can limit the readDirectory call
216+ const expandedPrefixDirectory = fragmentHasPath ? combinePaths ( normalizedPrefixDirectory , normalizedPrefixBase + getDirectoryPath ( fragment ) ) : normalizedPrefixDirectory ;
217+
218+ const normalizedSuffix = normalizePath ( parsed . suffix ) ;
219+ const baseDirectory = combinePaths ( baseUrl , expandedPrefixDirectory ) ;
220+ const completePrefix = fragmentHasPath ? baseDirectory : ensureTrailingDirectorySeparator ( baseDirectory ) + normalizedPrefixBase ;
221+
222+ // If we have a suffix, then we need to read the directory all the way down. We could create a glob
223+ // that encodes the suffix, but we would have to escape the character "?" which readDirectory
224+ // doesn't support. For now, this is safer but slower
225+ const includeGlob = normalizedSuffix ? "**/*" : "./*" ;
226+
227+ const matches = tryReadDirectory ( host , baseDirectory , fileExtensions , /*exclude*/ undefined , [ includeGlob ] ) ;
228+ // Trim away prefix and suffix
229+ return mapDefined ( matches , match => {
230+ const normalizedMatch = normalizePath ( match ) ;
231+ if ( ! endsWith ( normalizedMatch , normalizedSuffix ) || ! startsWith ( normalizedMatch , completePrefix ) ) {
232+ return ;
233+ }
234+
235+ const start = completePrefix . length ;
236+ const length = normalizedMatch . length - start - normalizedSuffix . length ;
237+ return removeFileExtension ( normalizedMatch . substr ( start , length ) ) ;
238+ } ) ;
236239 }
237240
238241 function enumeratePotentialNonRelativeModules ( fragment : string , scriptPath : string , options : CompilerOptions , typeChecker : TypeChecker , host : LanguageServiceHost ) : string [ ] {
0 commit comments