• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1import {
2    addToSeen, altDirectorySeparator, arrayFrom, CallLikeExpression, CancellationToken, changeExtension, CharacterCodes,
3    combinePaths, comparePaths, comparePatternKeys, compareStringsCaseSensitive, compareValues, Comparison,
4    CompilerOptions, CompletionEntry, CompletionEntryDetails, CompletionInfo, contains, containsPath, ContextFlags,
5    createSortedArray, createTextSpan, createTextSpanFromStringLiteralLikeContent, Debug, deduplicate,
6    directorySeparator, ElementAccessExpression, emptyArray, endsWith, ensureTrailingDirectorySeparator,
7    equateStringsCaseSensitive, Extension, fileExtensionIsOneOf, filter, find, findAncestor, findPackageJson,
8    findPackageJsons, firstDefined, firstOrUndefined, flatMap, flatten, forEachAncestorDirectory, getBaseFileName,
9    getContextualTypeFromParent, getDirectoryPath, getEffectiveTypeRoots, getEmitModuleResolutionKind,
10    getLeadingCommentRanges, getModeForUsageLocation, getModuleByPMType, getOwnKeys, getPackageJsonTypesVersionsPaths,
11    getPathComponents, getReplacementSpanForContextToken, getSupportedExtensions,
12    getSupportedExtensionsWithJsonIfResolveJsonModule, getTokenAtPosition, hasIndexSignature, hasProperty,
13    hasTrailingDirectorySeparator, hostGetCanonicalFileName, IndexedAccessTypeNode, isApplicableVersionedTypesKey,
14    isArray, isCallExpression, isIdentifier, isIdentifierText, isImportCall, isInReferenceComment, isInString,
15    isJsxAttribute, isJsxOpeningLikeElement, isLiteralTypeNode, isObjectLiteralExpression, isOhpm, isPatternMatch,
16    isPrivateIdentifierClassElementDeclaration, isRootedDiskPath, isString, isStringLiteral, isStringLiteralLike,
17    isTypeReferenceNode, isUrl, Iterator, JsxAttribute, LanguageServiceHost, length, LiteralExpression, LiteralTypeNode,
18    Map, mapDefined, MapLike, ModuleKind, ModuleResolutionKind, moduleSpecifiers, Node, normalizePath, normalizeSlashes,
19    ObjectLiteralExpression, Path, Program, PropertyAssignment, rangeContainsPosition, readJson, removeFileExtension,
20    removePrefix, removeTrailingDirectorySeparator, resolvePath, ScriptElementKind, ScriptElementKindModifier,
21    ScriptTarget, Signature, signatureHasRestParameter, SignatureHelp, singleElementArray, skipConstraint,
22    skipParentheses, SourceFile, startsWith, stringContains, StringLiteralLike, StringLiteralType, stripQuotes, Symbol,
23    SyntaxKind, textPart, TextSpan, tryAndIgnoreErrors, tryDirectoryExists, tryFileExists, tryGetDirectories,
24    tryGetExtensionFromPath, tryParsePattern, tryReadDirectory, tryRemoveDirectoryPrefix, tryRemovePrefix, Type,
25    TypeChecker, TypeFlags, UnionTypeNode, unmangleScopedPackageName, UserPreferences, walkUpParenthesizedExpressions,
26    walkUpParenthesizedTypes,
27} from "./_namespaces/ts";
28import {
29    CompletionKind, createCompletionDetails, createCompletionDetailsForSymbol, getCompletionEntriesFromSymbols,
30    getPropertiesForObjectExpression, Log, SortText,
31} from "./_namespaces/ts.Completions";
32
33interface NameAndKindSet {
34    add(value: NameAndKind): void;
35    has(name: string): boolean;
36    values(): Iterator<NameAndKind>;
37}
38const kindPrecedence = {
39    [ScriptElementKind.directory]: 0,
40    [ScriptElementKind.scriptElement]: 1,
41    [ScriptElementKind.externalModuleName]: 2,
42};
43function createNameAndKindSet(): NameAndKindSet {
44    const map = new Map<string, NameAndKind>();
45    function add(value: NameAndKind) {
46        const existing = map.get(value.name);
47        if (!existing || kindPrecedence[existing.kind] < kindPrecedence[value.kind]) {
48            map.set(value.name, value);
49        }
50    }
51    return {
52        add,
53        has: map.has.bind(map),
54        values: map.values.bind(map),
55    };
56}
57
58/** @internal */
59export function getStringLiteralCompletions(
60    sourceFile: SourceFile,
61    position: number,
62    contextToken: Node | undefined,
63    options: CompilerOptions,
64    host: LanguageServiceHost,
65    program: Program,
66    log: Log,
67    preferences: UserPreferences): CompletionInfo | undefined {
68    if (isInReferenceComment(sourceFile, position)) {
69        const entries = getTripleSlashReferenceCompletion(sourceFile, position, options, host);
70        return entries && convertPathCompletions(entries);
71    }
72    if (isInString(sourceFile, position, contextToken)) {
73        if (!contextToken || !isStringLiteralLike(contextToken)) return undefined;
74        const entries = getStringLiteralCompletionEntries(sourceFile, contextToken, position, program.getTypeChecker(), options, host, preferences);
75        return convertStringLiteralCompletions(entries, contextToken, sourceFile, host, program, log, options, preferences);
76    }
77}
78
79function convertStringLiteralCompletions(
80    completion: StringLiteralCompletion | undefined,
81    contextToken: StringLiteralLike,
82    sourceFile: SourceFile,
83    host: LanguageServiceHost,
84    program: Program,
85    log: Log,
86    options: CompilerOptions,
87    preferences: UserPreferences,
88): CompletionInfo | undefined {
89    if (completion === undefined) {
90        return undefined;
91    }
92
93    const optionalReplacementSpan = createTextSpanFromStringLiteralLikeContent(contextToken);
94    switch (completion.kind) {
95        case StringLiteralCompletionKind.Paths:
96            return convertPathCompletions(completion.paths);
97        case StringLiteralCompletionKind.Properties: {
98            const entries = createSortedArray<CompletionEntry>();
99            getCompletionEntriesFromSymbols(
100                completion.symbols,
101                entries,
102                contextToken,
103                contextToken,
104                sourceFile,
105                sourceFile,
106                host,
107                program,
108                ScriptTarget.ESNext,
109                log,
110                CompletionKind.String,
111                preferences,
112                options,
113                /*formatContext*/ undefined,
114            ); // Target will not be used, so arbitrary
115            return { isGlobalCompletion: false, isMemberCompletion: true, isNewIdentifierLocation: completion.hasIndexSignature, optionalReplacementSpan, entries };
116        }
117        case StringLiteralCompletionKind.Types: {
118            const entries = completion.types.map(type => ({
119                name: type.value,
120                kindModifiers: ScriptElementKindModifier.none,
121                kind: ScriptElementKind.string,
122                sortText: SortText.LocationPriority,
123                replacementSpan: getReplacementSpanForContextToken(contextToken)
124            }));
125            return { isGlobalCompletion: false, isMemberCompletion: false, isNewIdentifierLocation: completion.isNewIdentifier, optionalReplacementSpan, entries };
126        }
127        default:
128            return Debug.assertNever(completion);
129    }
130}
131
132/** @internal */
133export function getStringLiteralCompletionDetails(name: string, sourceFile: SourceFile, position: number, contextToken: Node | undefined, checker: TypeChecker, options: CompilerOptions, host: LanguageServiceHost, cancellationToken: CancellationToken, preferences: UserPreferences) {
134    if (!contextToken || !isStringLiteralLike(contextToken)) return undefined;
135    const completions = getStringLiteralCompletionEntries(sourceFile, contextToken, position, checker, options, host, preferences);
136    return completions && stringLiteralCompletionDetails(name, contextToken, completions, sourceFile, checker, cancellationToken);
137}
138
139function stringLiteralCompletionDetails(name: string, location: Node, completion: StringLiteralCompletion, sourceFile: SourceFile, checker: TypeChecker, cancellationToken: CancellationToken): CompletionEntryDetails | undefined {
140    switch (completion.kind) {
141        case StringLiteralCompletionKind.Paths: {
142            const match = find(completion.paths, p => p.name === name);
143            return match && createCompletionDetails(name, kindModifiersFromExtension(match.extension), match.kind, [textPart(name)]);
144        }
145        case StringLiteralCompletionKind.Properties: {
146            const match = find(completion.symbols, s => s.name === name);
147            return match && createCompletionDetailsForSymbol(match, checker, sourceFile, location, cancellationToken);
148        }
149        case StringLiteralCompletionKind.Types:
150            return find(completion.types, t => t.value === name) ? createCompletionDetails(name, ScriptElementKindModifier.none, ScriptElementKind.typeElement, [textPart(name)]) : undefined;
151        default:
152            return Debug.assertNever(completion);
153    }
154}
155
156function convertPathCompletions(pathCompletions: readonly PathCompletion[]): CompletionInfo {
157    const isGlobalCompletion = false; // We don't want the editor to offer any other completions, such as snippets, inside a comment.
158    const isNewIdentifierLocation = true; // The user may type in a path that doesn't yet exist, creating a "new identifier" with respect to the collection of identifiers the server is aware of.
159    const entries = pathCompletions.map(({ name, kind, span, extension }): CompletionEntry =>
160        ({ name, kind, kindModifiers: kindModifiersFromExtension(extension), sortText: SortText.LocationPriority, replacementSpan: span }));
161    return { isGlobalCompletion, isMemberCompletion: false, isNewIdentifierLocation, entries };
162}
163function kindModifiersFromExtension(extension: Extension | undefined): ScriptElementKindModifier {
164    switch (extension) {
165        case Extension.Dts: return ScriptElementKindModifier.dtsModifier;
166        case Extension.Js: return ScriptElementKindModifier.jsModifier;
167        case Extension.Json: return ScriptElementKindModifier.jsonModifier;
168        case Extension.Jsx: return ScriptElementKindModifier.jsxModifier;
169        case Extension.Ts: return ScriptElementKindModifier.tsModifier;
170        case Extension.Tsx: return ScriptElementKindModifier.tsxModifier;
171        case Extension.Dmts: return ScriptElementKindModifier.dmtsModifier;
172        case Extension.Mjs: return ScriptElementKindModifier.mjsModifier;
173        case Extension.Mts: return ScriptElementKindModifier.mtsModifier;
174        case Extension.Dcts: return ScriptElementKindModifier.dctsModifier;
175        case Extension.Cjs: return ScriptElementKindModifier.cjsModifier;
176        case Extension.Cts: return ScriptElementKindModifier.ctsModifier;
177        case Extension.TsBuildInfo: return Debug.fail(`Extension ${Extension.TsBuildInfo} is unsupported.`);
178        case undefined: return ScriptElementKindModifier.none;
179        case Extension.Dets: return ScriptElementKindModifier.detsModifier;
180        case Extension.Ets: return ScriptElementKindModifier.etsModifier;
181        default:
182            return Debug.assertNever(extension);
183    }
184}
185
186const enum StringLiteralCompletionKind { Paths, Properties, Types }
187interface StringLiteralCompletionsFromProperties {
188    readonly kind: StringLiteralCompletionKind.Properties;
189    readonly symbols: readonly Symbol[];
190    readonly hasIndexSignature: boolean;
191}
192interface StringLiteralCompletionsFromTypes {
193    readonly kind: StringLiteralCompletionKind.Types;
194    readonly types: readonly StringLiteralType[];
195    readonly isNewIdentifier: boolean;
196}
197type StringLiteralCompletion = { readonly kind: StringLiteralCompletionKind.Paths, readonly paths: readonly PathCompletion[] } | StringLiteralCompletionsFromProperties | StringLiteralCompletionsFromTypes;
198function getStringLiteralCompletionEntries(sourceFile: SourceFile, node: StringLiteralLike, position: number, typeChecker: TypeChecker, compilerOptions: CompilerOptions, host: LanguageServiceHost, preferences: UserPreferences): StringLiteralCompletion | undefined {
199    const parent = walkUpParentheses(node.parent);
200    switch (parent.kind) {
201        case SyntaxKind.LiteralType: {
202            const grandParent = walkUpParentheses(parent.parent);
203            switch (grandParent.kind) {
204                case SyntaxKind.ExpressionWithTypeArguments:
205                case SyntaxKind.TypeReference: {
206                    const typeArgument = findAncestor(parent, n => n.parent === grandParent) as LiteralTypeNode;
207                    if (typeArgument) {
208                        return { kind: StringLiteralCompletionKind.Types, types: getStringLiteralTypes(typeChecker.getTypeArgumentConstraint(typeArgument)), isNewIdentifier: false };
209                    }
210                    return undefined;
211                }
212                case SyntaxKind.IndexedAccessType:
213                    // Get all apparent property names
214                    // i.e. interface Foo {
215                    //          foo: string;
216                    //          bar: string;
217                    //      }
218                    //      let x: Foo["/*completion position*/"]
219                    const { indexType, objectType } = grandParent as IndexedAccessTypeNode;
220                    if (!rangeContainsPosition(indexType, position)) {
221                        return undefined;
222                    }
223                    return stringLiteralCompletionsFromProperties(typeChecker.getTypeFromTypeNode(objectType));
224                case SyntaxKind.ImportType:
225                    return { kind: StringLiteralCompletionKind.Paths, paths: getStringLiteralCompletionsFromModuleNames(sourceFile, node, compilerOptions, host, typeChecker, preferences) };
226                case SyntaxKind.UnionType: {
227                    if (!isTypeReferenceNode(grandParent.parent)) {
228                        return undefined;
229                    }
230                    const alreadyUsedTypes = getAlreadyUsedTypesInStringLiteralUnion(grandParent as UnionTypeNode, parent as LiteralTypeNode);
231                    const types = getStringLiteralTypes(typeChecker.getTypeArgumentConstraint(grandParent as UnionTypeNode)).filter(t => !contains(alreadyUsedTypes, t.value));
232                    return { kind: StringLiteralCompletionKind.Types, types, isNewIdentifier: false };
233                }
234                default:
235                    return undefined;
236            }
237        }
238        case SyntaxKind.PropertyAssignment:
239            if (isObjectLiteralExpression(parent.parent) && (parent as PropertyAssignment).name === node) {
240                // Get quoted name of properties of the object literal expression
241                // i.e. interface ConfigFiles {
242                //          'jspm:dev': string
243                //      }
244                //      let files: ConfigFiles = {
245                //          '/*completion position*/'
246                //      }
247                //
248                //      function foo(c: ConfigFiles) {}
249                //      foo({
250                //          '/*completion position*/'
251                //      });
252                return stringLiteralCompletionsForObjectLiteral(typeChecker, parent.parent);
253            }
254            return fromContextualType();
255
256        case SyntaxKind.ElementAccessExpression: {
257            const { expression, argumentExpression } = parent as ElementAccessExpression;
258            if (node === skipParentheses(argumentExpression)) {
259                // Get all names of properties on the expression
260                // i.e. interface A {
261                //      'prop1': string
262                // }
263                // let a: A;
264                // a['/*completion position*/']
265                return stringLiteralCompletionsFromProperties(typeChecker.getTypeAtLocation(expression));
266            }
267            return undefined;
268        }
269
270        case SyntaxKind.CallExpression:
271        case SyntaxKind.NewExpression:
272        case SyntaxKind.JsxAttribute:
273            if (!isRequireCallArgument(node) && !isImportCall(parent)) {
274                const argumentInfo = SignatureHelp.getArgumentInfoForCompletions(parent.kind === SyntaxKind.JsxAttribute ? parent.parent : node, position, sourceFile);
275                // Get string literal completions from specialized signatures of the target
276                // i.e. declare function f(a: 'A');
277                // f("/*completion position*/")
278                return argumentInfo && getStringLiteralCompletionsFromSignature(argumentInfo.invocation, node, argumentInfo, typeChecker) || fromContextualType();
279            }
280            // falls through (is `require("")` or `require(""` or `import("")`)
281
282        case SyntaxKind.ImportDeclaration:
283        case SyntaxKind.ExportDeclaration:
284        case SyntaxKind.ExternalModuleReference:
285            // Get all known external module names or complete a path to a module
286            // i.e. import * as ns from "/*completion position*/";
287            //      var y = import("/*completion position*/");
288            //      import x = require("/*completion position*/");
289            //      var y = require("/*completion position*/");
290            //      export * from "/*completion position*/";
291            return { kind: StringLiteralCompletionKind.Paths, paths: getStringLiteralCompletionsFromModuleNames(sourceFile, node, compilerOptions, host, typeChecker, preferences) };
292
293        default:
294            return fromContextualType();
295    }
296
297    function fromContextualType(): StringLiteralCompletion {
298        // Get completion for string literal from string literal type
299        // i.e. var x: "hi" | "hello" = "/*completion position*/"
300        return { kind: StringLiteralCompletionKind.Types, types: getStringLiteralTypes(getContextualTypeFromParent(node, typeChecker)), isNewIdentifier: false };
301    }
302}
303
304function walkUpParentheses(node: Node) {
305    switch (node.kind) {
306        case SyntaxKind.ParenthesizedType:
307            return walkUpParenthesizedTypes(node);
308        case SyntaxKind.ParenthesizedExpression:
309            return walkUpParenthesizedExpressions(node);
310        default:
311            return node;
312    }
313}
314
315function getAlreadyUsedTypesInStringLiteralUnion(union: UnionTypeNode, current: LiteralTypeNode): readonly string[] {
316    return mapDefined(union.types, type =>
317        type !== current && isLiteralTypeNode(type) && isStringLiteral(type.literal) ? type.literal.text : undefined);
318}
319
320function getStringLiteralCompletionsFromSignature(call: CallLikeExpression, arg: StringLiteralLike, argumentInfo: SignatureHelp.ArgumentInfoForCompletions, checker: TypeChecker): StringLiteralCompletionsFromTypes | undefined {
321    let isNewIdentifier = false;
322    const uniques = new Map<string, true>();
323    const candidates: Signature[] = [];
324    const editingArgument = isJsxOpeningLikeElement(call) ? Debug.checkDefined(findAncestor(arg.parent, isJsxAttribute)) : arg;
325    checker.getResolvedSignatureForStringLiteralCompletions(call, editingArgument, candidates);
326    const types = flatMap(candidates, candidate => {
327        if (!signatureHasRestParameter(candidate) && argumentInfo.argumentCount > candidate.parameters.length) return;
328        let type = candidate.getTypeParameterAtPosition(argumentInfo.argumentIndex);
329        if (isJsxOpeningLikeElement(call)) {
330            const propType = checker.getTypeOfPropertyOfType(type, (editingArgument as JsxAttribute).name.text);
331            if (propType) {
332                type = propType;
333            }
334        }
335        isNewIdentifier = isNewIdentifier || !!(type.flags & TypeFlags.String);
336        return getStringLiteralTypes(type, uniques);
337    });
338    return length(types) ? { kind: StringLiteralCompletionKind.Types, types, isNewIdentifier } : undefined;
339}
340
341function stringLiteralCompletionsFromProperties(type: Type | undefined): StringLiteralCompletionsFromProperties | undefined {
342    return type && {
343        kind: StringLiteralCompletionKind.Properties,
344        symbols: filter(type.getApparentProperties(), prop => !(prop.valueDeclaration && isPrivateIdentifierClassElementDeclaration(prop.valueDeclaration))),
345        hasIndexSignature: hasIndexSignature(type)
346    };
347}
348
349function stringLiteralCompletionsForObjectLiteral(checker: TypeChecker, objectLiteralExpression: ObjectLiteralExpression): StringLiteralCompletionsFromProperties | undefined {
350    const contextualType = checker.getContextualType(objectLiteralExpression);
351    if (!contextualType) return undefined;
352
353    const completionsType = checker.getContextualType(objectLiteralExpression, ContextFlags.Completions);
354    const symbols = getPropertiesForObjectExpression(
355        contextualType,
356        completionsType,
357        objectLiteralExpression,
358        checker
359    );
360
361    return {
362        kind: StringLiteralCompletionKind.Properties,
363        symbols,
364        hasIndexSignature: hasIndexSignature(contextualType)
365    };
366}
367
368function getStringLiteralTypes(type: Type | undefined, uniques = new Map<string, true>()): readonly StringLiteralType[] {
369    if (!type) return emptyArray;
370    type = skipConstraint(type);
371    return type.isUnion() ? flatMap(type.types, t => getStringLiteralTypes(t, uniques)) :
372        type.isStringLiteral() && !(type.flags & TypeFlags.EnumLiteral) && addToSeen(uniques, type.value) ? [type] : emptyArray;
373}
374
375interface NameAndKind {
376    readonly name: string;
377    readonly kind: ScriptElementKind.scriptElement | ScriptElementKind.directory | ScriptElementKind.externalModuleName;
378    readonly extension: Extension | undefined;
379}
380interface PathCompletion extends NameAndKind {
381    readonly span: TextSpan | undefined;
382}
383
384function nameAndKind(name: string, kind: NameAndKind["kind"], extension: Extension | undefined): NameAndKind {
385    return { name, kind, extension };
386}
387function directoryResult(name: string): NameAndKind {
388    return nameAndKind(name, ScriptElementKind.directory, /*extension*/ undefined);
389}
390
391function addReplacementSpans(text: string, textStart: number, names: readonly NameAndKind[]): readonly PathCompletion[] {
392    const span = getDirectoryFragmentTextSpan(text, textStart);
393    const wholeSpan = text.length === 0 ? undefined : createTextSpan(textStart, text.length);
394    return names.map(({ name, kind, extension }): PathCompletion =>
395        Math.max(name.indexOf(directorySeparator), name.indexOf(altDirectorySeparator)) !== -1 ? { name, kind, extension, span: wholeSpan } : { name, kind, extension, span });
396}
397
398function getStringLiteralCompletionsFromModuleNames(sourceFile: SourceFile, node: LiteralExpression, compilerOptions: CompilerOptions, host: LanguageServiceHost, typeChecker: TypeChecker, preferences: UserPreferences): readonly PathCompletion[] {
399    return addReplacementSpans(node.text, node.getStart(sourceFile) + 1, getStringLiteralCompletionsFromModuleNamesWorker(sourceFile, node, compilerOptions, host, typeChecker, preferences));
400}
401
402function getStringLiteralCompletionsFromModuleNamesWorker(sourceFile: SourceFile, node: LiteralExpression, compilerOptions: CompilerOptions, host: LanguageServiceHost, typeChecker: TypeChecker, preferences: UserPreferences): readonly NameAndKind[] {
403    const literalValue = normalizeSlashes(node.text);
404    const mode = isStringLiteralLike(node) ? getModeForUsageLocation(sourceFile, node) : undefined;
405
406    const scriptPath = sourceFile.path;
407    const scriptDirectory = getDirectoryPath(scriptPath);
408
409    return isPathRelativeToScript(literalValue) || !compilerOptions.baseUrl && (isRootedDiskPath(literalValue) || isUrl(literalValue))
410        ? getCompletionEntriesForRelativeModules(literalValue, scriptDirectory, compilerOptions, host, scriptPath, getIncludeExtensionOption())
411        : getCompletionEntriesForNonRelativeModules(literalValue, scriptDirectory, mode, compilerOptions, host, getIncludeExtensionOption(), typeChecker);
412
413    function getIncludeExtensionOption() {
414        const mode = isStringLiteralLike(node) ? getModeForUsageLocation(sourceFile, node) : undefined;
415        return preferences.importModuleSpecifierEnding === "js" || mode === ModuleKind.ESNext ? IncludeExtensionsOption.ModuleSpecifierCompletion : IncludeExtensionsOption.Exclude;
416    }
417}
418
419interface ExtensionOptions {
420    readonly extensions: readonly Extension[];
421    readonly includeExtensionsOption: IncludeExtensionsOption;
422}
423function getExtensionOptions(compilerOptions: CompilerOptions, includeExtensionsOption = IncludeExtensionsOption.Exclude): ExtensionOptions {
424    return { extensions: flatten(getSupportedExtensionsForModuleResolution(compilerOptions)), includeExtensionsOption };
425}
426function getCompletionEntriesForRelativeModules(literalValue: string, scriptDirectory: string, compilerOptions: CompilerOptions, host: LanguageServiceHost, scriptPath: Path, includeExtensions: IncludeExtensionsOption) {
427    const extensionOptions = getExtensionOptions(compilerOptions, includeExtensions);
428    if (compilerOptions.rootDirs) {
429        return getCompletionEntriesForDirectoryFragmentWithRootDirs(
430            compilerOptions.rootDirs, literalValue, scriptDirectory, extensionOptions, compilerOptions, host, scriptPath);
431    }
432    else {
433        return arrayFrom(getCompletionEntriesForDirectoryFragment(literalValue, scriptDirectory, extensionOptions, host, scriptPath).values());
434    }
435}
436
437function isEmitResolutionKindUsingNodeModules(compilerOptions: CompilerOptions): boolean {
438    return getEmitModuleResolutionKind(compilerOptions) === ModuleResolutionKind.NodeJs ||
439        getEmitModuleResolutionKind(compilerOptions) === ModuleResolutionKind.Node16 ||
440        getEmitModuleResolutionKind(compilerOptions) === ModuleResolutionKind.NodeNext;
441}
442
443function isEmitModuleResolutionRespectingExportMaps(compilerOptions: CompilerOptions) {
444    return getEmitModuleResolutionKind(compilerOptions) === ModuleResolutionKind.Node16 ||
445    getEmitModuleResolutionKind(compilerOptions) === ModuleResolutionKind.NodeNext;
446}
447
448function getSupportedExtensionsForModuleResolution(compilerOptions: CompilerOptions): readonly Extension[][] {
449    const extensions = getSupportedExtensions(compilerOptions);
450    return isEmitResolutionKindUsingNodeModules(compilerOptions) ?
451        getSupportedExtensionsWithJsonIfResolveJsonModule(compilerOptions, extensions) :
452        extensions;
453}
454
455/**
456 * Takes a script path and returns paths for all potential folders that could be merged with its
457 * containing folder via the "rootDirs" compiler option
458 */
459function getBaseDirectoriesFromRootDirs(rootDirs: string[], basePath: string, scriptDirectory: string, ignoreCase: boolean): readonly string[] {
460    // Make all paths absolute/normalized if they are not already
461    rootDirs = rootDirs.map(rootDirectory => normalizePath(isRootedDiskPath(rootDirectory) ? rootDirectory : combinePaths(basePath, rootDirectory)));
462
463    // Determine the path to the directory containing the script relative to the root directory it is contained within
464    const relativeDirectory = firstDefined(rootDirs, rootDirectory =>
465        containsPath(rootDirectory, scriptDirectory, basePath, ignoreCase) ? scriptDirectory.substr(rootDirectory.length) : undefined)!; // TODO: GH#18217
466
467    // Now find a path for each potential directory that is to be merged with the one containing the script
468    return deduplicate<string>(
469        [...rootDirs.map(rootDirectory => combinePaths(rootDirectory, relativeDirectory)), scriptDirectory],
470        equateStringsCaseSensitive,
471        compareStringsCaseSensitive);
472}
473
474function getCompletionEntriesForDirectoryFragmentWithRootDirs(rootDirs: string[], fragment: string, scriptDirectory: string, extensionOptions: ExtensionOptions, compilerOptions: CompilerOptions, host: LanguageServiceHost, exclude: string): readonly NameAndKind[] {
475    const basePath = compilerOptions.project || host.getCurrentDirectory();
476    const ignoreCase = !(host.useCaseSensitiveFileNames && host.useCaseSensitiveFileNames());
477    const baseDirectories = getBaseDirectoriesFromRootDirs(rootDirs, basePath, scriptDirectory, ignoreCase);
478    return flatMap(baseDirectories, baseDirectory => arrayFrom(getCompletionEntriesForDirectoryFragment(fragment, baseDirectory, extensionOptions, host, exclude).values()));
479}
480
481const enum IncludeExtensionsOption {
482    Exclude,
483    Include,
484    ModuleSpecifierCompletion,
485}
486/**
487 * Given a path ending at a directory, gets the completions for the path, and filters for those entries containing the basename.
488 */
489function getCompletionEntriesForDirectoryFragment(
490    fragment: string,
491    scriptPath: string,
492    extensionOptions: ExtensionOptions,
493    host: LanguageServiceHost,
494    exclude?: string,
495    result = createNameAndKindSet()
496): NameAndKindSet {
497    if (fragment === undefined) {
498        fragment = "";
499    }
500
501    fragment = normalizeSlashes(fragment);
502
503    /**
504     * Remove the basename from the path. Note that we don't use the basename to filter completions;
505     * the client is responsible for refining completions.
506     */
507    if (!hasTrailingDirectorySeparator(fragment)) {
508        fragment = getDirectoryPath(fragment);
509    }
510
511    if (fragment === "") {
512        fragment = "." + directorySeparator;
513    }
514
515    fragment = ensureTrailingDirectorySeparator(fragment);
516
517    const absolutePath = resolvePath(scriptPath, fragment);
518    const baseDirectory = hasTrailingDirectorySeparator(absolutePath) ? absolutePath : getDirectoryPath(absolutePath);
519
520    // check for a version redirect
521    const packageJsonPath = findPackageJson(baseDirectory, host);
522    if (packageJsonPath) {
523        const packageJson = readJson(packageJsonPath, host as { readFile: (filename: string) => string | undefined });
524        const typesVersions = (packageJson as any).typesVersions;
525        if (typeof typesVersions === "object") {
526            const versionPaths = getPackageJsonTypesVersionsPaths(typesVersions)?.paths;
527            if (versionPaths) {
528                const packageDirectory = getDirectoryPath(packageJsonPath);
529                const pathInPackage = absolutePath.slice(ensureTrailingDirectorySeparator(packageDirectory).length);
530                if (addCompletionEntriesFromPaths(result, pathInPackage, packageDirectory, extensionOptions, host, versionPaths)) {
531                    // A true result means one of the `versionPaths` was matched, which will block relative resolution
532                    // to files and folders from here. All reachable paths given the pattern match are already added.
533                    return result;
534                }
535            }
536        }
537    }
538
539    const ignoreCase = !(host.useCaseSensitiveFileNames && host.useCaseSensitiveFileNames());
540    if (!tryDirectoryExists(host, baseDirectory)) return result;
541
542    // Enumerate the available files if possible
543    const files = tryReadDirectory(host, baseDirectory, extensionOptions.extensions, /*exclude*/ undefined, /*include*/ ["./*"]);
544
545    if (files) {
546        for (let filePath of files) {
547            filePath = normalizePath(filePath);
548            if (exclude && comparePaths(filePath, exclude, scriptPath, ignoreCase) === Comparison.EqualTo) {
549                continue;
550            }
551
552            const { name, extension } = getFilenameWithExtensionOption(getBaseFileName(filePath), host.getCompilationSettings(), extensionOptions.includeExtensionsOption);
553            result.add(nameAndKind(name, ScriptElementKind.scriptElement, extension));
554        }
555    }
556
557    // If possible, get folder completion as well
558    const directories = tryGetDirectories(host, baseDirectory);
559
560    if (directories) {
561        for (const directory of directories) {
562            const directoryName = getBaseFileName(normalizePath(directory));
563            if (directoryName !== "@types") {
564                result.add(directoryResult(directoryName));
565            }
566        }
567    }
568
569    return result;
570}
571
572function getFilenameWithExtensionOption(name: string, compilerOptions: CompilerOptions, includeExtensionsOption: IncludeExtensionsOption): { name: string, extension: Extension | undefined } {
573    const outputExtension = moduleSpecifiers.tryGetJSExtensionForFile(name, compilerOptions);
574    if (includeExtensionsOption === IncludeExtensionsOption.Exclude && !fileExtensionIsOneOf(name, [Extension.Json, Extension.Mts, Extension.Cts, Extension.Dmts, Extension.Dcts, Extension.Mjs, Extension.Cjs])) {
575        return { name: removeFileExtension(name), extension: tryGetExtensionFromPath(name) };
576    }
577    else if ((fileExtensionIsOneOf(name, [Extension.Mts, Extension.Cts, Extension.Dmts, Extension.Dcts, Extension.Mjs, Extension.Cjs]) || includeExtensionsOption === IncludeExtensionsOption.ModuleSpecifierCompletion) && outputExtension) {
578        return { name: changeExtension(name, outputExtension), extension: outputExtension };
579    }
580    else {
581        return { name, extension: tryGetExtensionFromPath(name) };
582    }
583}
584
585/** @returns whether `fragment` was a match for any `paths` (which should indicate whether any other path completions should be offered) */
586function addCompletionEntriesFromPaths(
587    result: NameAndKindSet,
588    fragment: string,
589    baseDirectory: string,
590    extensionOptions: ExtensionOptions,
591    host: LanguageServiceHost,
592    paths: MapLike<string[]>
593) {
594    const getPatternsForKey = (key: string) => paths[key];
595    const comparePaths = (a: string, b: string): Comparison => {
596        const patternA = tryParsePattern(a);
597        const patternB = tryParsePattern(b);
598        const lengthA = typeof patternA === "object" ? patternA.prefix.length : a.length;
599        const lengthB = typeof patternB === "object" ? patternB.prefix.length : b.length;
600        return compareValues(lengthB, lengthA);
601    };
602    return addCompletionEntriesFromPathsOrExports(result, fragment, baseDirectory, extensionOptions, host, getOwnKeys(paths), getPatternsForKey, comparePaths);
603}
604
605/** @returns whether `fragment` was a match for any `paths` (which should indicate whether any other path completions should be offered) */
606function addCompletionEntriesFromPathsOrExports(
607    result: NameAndKindSet,
608    fragment: string,
609    baseDirectory: string,
610    extensionOptions: ExtensionOptions,
611    host: LanguageServiceHost,
612    keys: readonly string[],
613    getPatternsForKey: (key: string) => string[] | undefined,
614    comparePaths: (a: string, b: string) => Comparison,
615) {
616    let pathResults: { results: NameAndKind[], matchedPattern: boolean }[] = [];
617    let matchedPath: string | undefined;
618    for (const key of keys) {
619        if (key === ".") continue;
620        const keyWithoutLeadingDotSlash = key.replace(/^\.\//, ""); // remove leading "./"
621        const patterns = getPatternsForKey(key);
622        if (patterns) {
623            const pathPattern = tryParsePattern(keyWithoutLeadingDotSlash);
624            if (!pathPattern) continue;
625            const isMatch = typeof pathPattern === "object" && isPatternMatch(pathPattern, fragment);
626            const isLongestMatch = isMatch && (matchedPath === undefined || comparePaths(key, matchedPath) === Comparison.LessThan);
627            if (isLongestMatch) {
628                // If this is a higher priority match than anything we've seen so far, previous results from matches are invalid, e.g.
629                // for `import {} from "some-package/|"` with a typesVersions:
630                // {
631                //   "bar/*": ["bar/*"], // <-- 1. We add 'bar', but 'bar/*' doesn't match yet.
632                //   "*": ["dist/*"],    // <-- 2. We match here and add files from dist. 'bar' is still ok because it didn't come from a match.
633                //   "foo/*": ["foo/*"]  // <-- 3. We matched '*' earlier and added results from dist, but if 'foo/*' also matched,
634                // }                               results in dist would not be visible. 'bar' still stands because it didn't come from a match.
635                //                                 This is especially important if `dist/foo` is a folder, because if we fail to clear results
636                //                                 added by the '*' match, after typing `"some-package/foo/|"` we would get file results from both
637                //                                 ./dist/foo and ./foo, when only the latter will actually be resolvable.
638                //                                 See pathCompletionsTypesVersionsWildcard6.ts.
639                matchedPath = key;
640                pathResults = pathResults.filter(r => !r.matchedPattern);
641            }
642            if (typeof pathPattern === "string" || matchedPath === undefined || comparePaths(key, matchedPath) !== Comparison.GreaterThan) {
643                pathResults.push({
644                    matchedPattern: isMatch,
645                    results: getCompletionsForPathMapping(keyWithoutLeadingDotSlash, patterns, fragment, baseDirectory, extensionOptions, host)
646                        .map(({ name, kind, extension }) => nameAndKind(name, kind, extension)),
647                });
648            }
649        }
650    }
651
652    pathResults.forEach(pathResult => pathResult.results.forEach(r => result.add(r)));
653    return matchedPath !== undefined;
654}
655
656/**
657 * Check all of the declared modules and those in node modules. Possible sources of modules:
658 *      Modules that are found by the type checker
659 *      Modules found relative to "baseUrl" compliler options (including patterns from "paths" compiler option)
660 *      Modules from node_modules (i.e. those listed in package.json)
661 *          This includes all files that are found in node_modules/moduleName/ with acceptable file extensions
662 *      Modules from oh_modules (i.e. those listed in oh-package.json5)
663 *          This includes all files that are found in oh_modules/moduleName/ with acceptable file extensions
664 */
665function getCompletionEntriesForNonRelativeModules(
666    fragment: string,
667    scriptPath: string,
668    mode: SourceFile["impliedNodeFormat"],
669    compilerOptions: CompilerOptions,
670    host: LanguageServiceHost,
671    includeExtensionsOption: IncludeExtensionsOption,
672    typeChecker: TypeChecker,
673): readonly NameAndKind[] {
674    const { baseUrl, paths } = compilerOptions;
675
676    const result = createNameAndKindSet();
677    const extensionOptions = getExtensionOptions(compilerOptions, includeExtensionsOption);
678    if (baseUrl) {
679        const projectDir = compilerOptions.project || host.getCurrentDirectory();
680        const absolute = normalizePath(combinePaths(projectDir, baseUrl));
681        getCompletionEntriesForDirectoryFragment(fragment, absolute, extensionOptions, host, /*exclude*/ undefined, result);
682        if (paths) {
683            addCompletionEntriesFromPaths(result, fragment, absolute, extensionOptions, host, paths);
684        }
685    }
686
687    const fragmentDirectory = getFragmentDirectory(fragment);
688    for (const ambientName of getAmbientModuleCompletions(fragment, fragmentDirectory, typeChecker)) {
689        result.add(nameAndKind(ambientName, ScriptElementKind.externalModuleName, /*extension*/ undefined));
690    }
691
692    getCompletionEntriesFromTypings(host, compilerOptions, scriptPath, fragmentDirectory, extensionOptions, result);
693
694    if (isEmitResolutionKindUsingNodeModules(compilerOptions)) {
695        // If looking for a global package name, don't just include everything in `node_modules` or `oh_modules` because that includes dependencies' own dependencies.
696        // (But do if we didn't find anything, e.g. 'package.json' missing.)
697        let foundGlobal = false;
698        if (fragmentDirectory === undefined) {
699            for (const moduleName of enumerateNodeModulesVisibleToScript(host, scriptPath)) {
700                const moduleResult = nameAndKind(moduleName, ScriptElementKind.externalModuleName, /*extension*/ undefined);
701                if (!result.has(moduleResult.name)) {
702                    foundGlobal = true;
703                    result.add(moduleResult);
704                }
705            }
706        }
707        if (!foundGlobal) {
708            let ancestorLookup: (directory: string) => void | undefined = ancestor => {
709                const nodeModules = combinePaths(ancestor, getModuleByPMType(host.getCompilationSettings().packageManagerType));
710                if (tryDirectoryExists(host, nodeModules)) {
711                    getCompletionEntriesForDirectoryFragment(fragment, nodeModules, extensionOptions, host, /*exclude*/ undefined, result);
712                }
713            };
714            if (fragmentDirectory && isEmitModuleResolutionRespectingExportMaps(compilerOptions)) {
715                const nodeModulesDirectoryLookup = ancestorLookup;
716                ancestorLookup = ancestor => {
717                    const components = getPathComponents(fragment);
718                    components.shift(); // shift off empty root
719                    let packagePath = components.shift();
720                    if (!packagePath) {
721                        return nodeModulesDirectoryLookup(ancestor);
722                    }
723                    if (startsWith(packagePath, "@")) {
724                        const subName = components.shift();
725                        if (!subName) {
726                            return nodeModulesDirectoryLookup(ancestor);
727                        }
728                        packagePath = combinePaths(packagePath, subName);
729                    }
730                    const packageDirectory = combinePaths(ancestor, "node_modules", packagePath);
731                    const packageFile = combinePaths(packageDirectory, "package.json");
732                    if (tryFileExists(host, packageFile)) {
733                        const packageJson = readJson(packageFile, host);
734                        const exports = (packageJson as any).exports;
735                        if (exports) {
736                            if (typeof exports !== "object" || exports === null) { // eslint-disable-line no-null/no-null
737                                return; // null exports or entrypoint only, no sub-modules available
738                            }
739                            const keys = getOwnKeys(exports);
740                            const fragmentSubpath = components.join("/") + (components.length && hasTrailingDirectorySeparator(fragment) ? "/" : "");
741                            const conditions = mode === ModuleKind.ESNext ? ["node", "import", "types"] : ["node", "require", "types"];
742                            addCompletionEntriesFromPathsOrExports(
743                                result,
744                                fragmentSubpath,
745                                packageDirectory,
746                                extensionOptions,
747                                host,
748                                keys,
749                                key => singleElementArray(getPatternFromFirstMatchingCondition(exports[key], conditions)),
750                                comparePatternKeys);
751                            return;
752                        }
753                    }
754                    return nodeModulesDirectoryLookup(ancestor);
755                };
756            }
757            forEachAncestorDirectory(scriptPath, ancestorLookup);
758        }
759    }
760
761    return arrayFrom(result.values());
762}
763
764function getPatternFromFirstMatchingCondition(target: unknown, conditions: readonly string[]): string | undefined {
765    if (typeof target === "string") {
766        return target;
767    }
768    if (target && typeof target === "object" && !isArray(target)) {
769        for (const condition in target) {
770            if (condition === "default" || conditions.indexOf(condition) > -1 || isApplicableVersionedTypesKey(conditions, condition)) {
771                const pattern = (target as MapLike<unknown>)[condition];
772                return getPatternFromFirstMatchingCondition(pattern, conditions);
773            }
774        }
775    }
776}
777
778function getFragmentDirectory(fragment: string): string | undefined {
779    return containsSlash(fragment) ? hasTrailingDirectorySeparator(fragment) ? fragment : getDirectoryPath(fragment) : undefined;
780}
781
782function getCompletionsForPathMapping(
783    path: string,
784    patterns: readonly string[],
785    fragment: string,
786    packageDirectory: string,
787    extensionOptions: ExtensionOptions,
788    host: LanguageServiceHost,
789): readonly NameAndKind[] {
790    if (!endsWith(path, "*")) {
791        // For a path mapping "foo": ["/x/y/z.ts"], add "foo" itself as a completion.
792        return !stringContains(path, "*") ? justPathMappingName(path, ScriptElementKind.scriptElement) : emptyArray;
793    }
794
795    const pathPrefix = path.slice(0, path.length - 1);
796    const remainingFragment = tryRemovePrefix(fragment, pathPrefix);
797    if (remainingFragment === undefined) {
798        const starIsFullPathComponent = path[path.length - 2] === "/";
799        return starIsFullPathComponent ? justPathMappingName(pathPrefix, ScriptElementKind.directory) : flatMap(patterns, pattern =>
800            getModulesForPathsPattern("", packageDirectory, pattern, extensionOptions, host)?.map(({ name, ...rest }) => ({ name: pathPrefix + name, ...rest })));
801    }
802    return flatMap(patterns, pattern => getModulesForPathsPattern(remainingFragment, packageDirectory, pattern, extensionOptions, host));
803
804    function justPathMappingName(name: string, kind: ScriptElementKind.directory | ScriptElementKind.scriptElement): readonly NameAndKind[] {
805        return startsWith(name, fragment) ? [{ name: removeTrailingDirectorySeparator(name), kind, extension: undefined }] : emptyArray;
806    }
807}
808
809function getModulesForPathsPattern(
810    fragment: string,
811    packageDirectory: string,
812    pattern: string,
813    extensionOptions: ExtensionOptions,
814    host: LanguageServiceHost,
815): readonly NameAndKind[] | undefined {
816    if (!host.readDirectory) {
817        return undefined;
818    }
819
820    const parsed = tryParsePattern(pattern);
821    if (parsed === undefined || isString(parsed)) {
822        return undefined;
823    }
824
825    // The prefix has two effective parts: the directory path and the base component after the filepath that is not a
826    // full directory component. For example: directory/path/of/prefix/base*
827    const normalizedPrefix = resolvePath(parsed.prefix);
828    const normalizedPrefixDirectory = hasTrailingDirectorySeparator(parsed.prefix) ? normalizedPrefix : getDirectoryPath(normalizedPrefix);
829    const normalizedPrefixBase = hasTrailingDirectorySeparator(parsed.prefix) ? "" : getBaseFileName(normalizedPrefix);
830
831    const fragmentHasPath = containsSlash(fragment);
832    const fragmentDirectory = fragmentHasPath ? hasTrailingDirectorySeparator(fragment) ? fragment : getDirectoryPath(fragment) : undefined;
833
834    // Try and expand the prefix to include any path from the fragment so that we can limit the readDirectory call
835    const expandedPrefixDirectory = fragmentHasPath ? combinePaths(normalizedPrefixDirectory, normalizedPrefixBase + fragmentDirectory) : normalizedPrefixDirectory;
836
837    const normalizedSuffix = normalizePath(parsed.suffix);
838    // Need to normalize after combining: If we combinePaths("a", "../b"), we want "b" and not "a/../b".
839    const baseDirectory = normalizePath(combinePaths(packageDirectory, expandedPrefixDirectory));
840    const completePrefix = fragmentHasPath ? baseDirectory : ensureTrailingDirectorySeparator(baseDirectory) + normalizedPrefixBase;
841
842    // If we have a suffix, then we read the directory all the way down to avoid returning completions for
843    // directories that don't contain files that would match the suffix. A previous comment here was concerned
844    // about the case where `normalizedSuffix` includes a `?` character, which should be interpreted literally,
845    // but will match any single character as part of the `include` pattern in `tryReadDirectory`. This is not
846    // a problem, because (in the extremely unusual circumstance where the suffix has a `?` in it) a `?`
847    // interpreted as "any character" can only return *too many* results as compared to the literal
848    // interpretation, so we can filter those superfluous results out via `trimPrefixAndSuffix` as we've always
849    // done.
850    const includeGlob = normalizedSuffix ? "**/*" + normalizedSuffix : "./*";
851
852    const matches = mapDefined(tryReadDirectory(host, baseDirectory, extensionOptions.extensions, /*exclude*/ undefined, [includeGlob]), match => {
853        const trimmedWithPattern = trimPrefixAndSuffix(match);
854        if (trimmedWithPattern) {
855            if (containsSlash(trimmedWithPattern)) {
856                return directoryResult(getPathComponents(removeLeadingDirectorySeparator(trimmedWithPattern))[1]);
857            }
858            const { name, extension } = getFilenameWithExtensionOption(trimmedWithPattern, host.getCompilationSettings(), extensionOptions.includeExtensionsOption);
859            return nameAndKind(name, ScriptElementKind.scriptElement, extension);
860        }
861    });
862
863    // If we had a suffix, we already recursively searched for all possible files that could match
864    // it and returned the directories leading to those files. Otherwise, assume any directory could
865    // have something valid to import.
866    const directories = normalizedSuffix
867        ? emptyArray
868        : mapDefined(tryGetDirectories(host, baseDirectory), dir => dir === "node_modules" ? undefined : directoryResult(dir));
869    return [...matches, ...directories];
870
871    function trimPrefixAndSuffix(path: string): string | undefined {
872        const inner = withoutStartAndEnd(normalizePath(path), completePrefix, normalizedSuffix);
873        return inner === undefined ? undefined : removeLeadingDirectorySeparator(inner);
874    }
875}
876
877function withoutStartAndEnd(s: string, start: string, end: string): string | undefined {
878    return startsWith(s, start) && endsWith(s, end) ? s.slice(start.length, s.length - end.length) : undefined;
879}
880
881function removeLeadingDirectorySeparator(path: string): string {
882    return path[0] === directorySeparator ? path.slice(1) : path;
883}
884
885function getAmbientModuleCompletions(fragment: string, fragmentDirectory: string | undefined, checker: TypeChecker): readonly string[] {
886    // Get modules that the type checker picked up
887    const ambientModules = checker.getAmbientModules().map(sym => stripQuotes(sym.name));
888    const nonRelativeModuleNames = ambientModules.filter(moduleName => startsWith(moduleName, fragment));
889
890    // Nested modules of the form "module-name/sub" need to be adjusted to only return the string
891    // after the last '/' that appears in the fragment because that's where the replacement span
892    // starts
893    if (fragmentDirectory !== undefined) {
894        const moduleNameWithSeparator = ensureTrailingDirectorySeparator(fragmentDirectory);
895        return nonRelativeModuleNames.map(nonRelativeModuleName => removePrefix(nonRelativeModuleName, moduleNameWithSeparator));
896    }
897    return nonRelativeModuleNames;
898}
899
900function getTripleSlashReferenceCompletion(sourceFile: SourceFile, position: number, compilerOptions: CompilerOptions, host: LanguageServiceHost): readonly PathCompletion[] | undefined {
901    const token = getTokenAtPosition(sourceFile, position);
902    const commentRanges = getLeadingCommentRanges(sourceFile.text, token.pos);
903    const range = commentRanges && find(commentRanges, commentRange => position >= commentRange.pos && position <= commentRange.end);
904    if (!range) {
905        return undefined;
906    }
907    const text = sourceFile.text.slice(range.pos, position);
908    const match = tripleSlashDirectiveFragmentRegex.exec(text);
909    if (!match) {
910        return undefined;
911    }
912
913    const [, prefix, kind, toComplete] = match;
914    const scriptPath = getDirectoryPath(sourceFile.path);
915    const names = kind === "path" ? getCompletionEntriesForDirectoryFragment(toComplete, scriptPath, getExtensionOptions(compilerOptions, IncludeExtensionsOption.Include), host, sourceFile.path)
916        : kind === "types" ? getCompletionEntriesFromTypings(host, compilerOptions, scriptPath, getFragmentDirectory(toComplete), getExtensionOptions(compilerOptions))
917        : Debug.fail();
918    return addReplacementSpans(toComplete, range.pos + prefix.length, arrayFrom(names.values()));
919}
920
921function getCompletionEntriesFromTypings(host: LanguageServiceHost, options: CompilerOptions, scriptPath: string, fragmentDirectory: string | undefined, extensionOptions: ExtensionOptions, result = createNameAndKindSet()): NameAndKindSet {
922    // Check for typings specified in compiler options
923    const seen = new Map<string, true>();
924
925    const typeRoots = tryAndIgnoreErrors(() => getEffectiveTypeRoots(options, host)) || emptyArray;
926
927    for (const root of typeRoots) {
928        getCompletionEntriesFromDirectories(root);
929    }
930
931    // Also get all @types typings installed in visible node_modules or oh_modules directories
932    for (const packageJson of findPackageJsons(scriptPath, host)) {
933        const typesDir = combinePaths(getDirectoryPath(packageJson), isOhpm(options.packageManagerType) ? "oh_modules/@types" : "node_modules/@types");
934        getCompletionEntriesFromDirectories(typesDir);
935    }
936
937    return result;
938
939    function getCompletionEntriesFromDirectories(directory: string): void {
940        if (!tryDirectoryExists(host, directory)) return;
941
942        for (const typeDirectoryName of tryGetDirectories(host, directory)) {
943            const packageName = unmangleScopedPackageName(typeDirectoryName);
944            if (options.types && !contains(options.types, packageName)) continue;
945
946            if (fragmentDirectory === undefined) {
947                if (!seen.has(packageName)) {
948                    result.add(nameAndKind(packageName, ScriptElementKind.externalModuleName, /*extension*/ undefined));
949                    seen.set(packageName, true);
950                }
951            }
952            else {
953                const baseDirectory = combinePaths(directory, typeDirectoryName);
954                const remainingFragment = tryRemoveDirectoryPrefix(fragmentDirectory, packageName, hostGetCanonicalFileName(host));
955                if (remainingFragment !== undefined) {
956                    getCompletionEntriesForDirectoryFragment(remainingFragment, baseDirectory, extensionOptions, host, /*exclude*/ undefined, result);
957                }
958            }
959        }
960    }
961}
962
963function enumerateNodeModulesVisibleToScript(host: LanguageServiceHost, scriptPath: string): readonly string[] {
964    if (!host.readFile || !host.fileExists) return emptyArray;
965
966    const result: string[] = [];
967    for (const packageJson of findPackageJsons(scriptPath, host)) {
968        const contents = readJson(packageJson, host as { readFile: (filename: string) => string | undefined }); // Cast to assert that readFile is defined
969        // Provide completions for all non @types dependencies
970        for (const key of nodeModulesDependencyKeys) {
971            const dependencies: object | undefined = (contents as any)[key];
972            if (!dependencies) continue;
973            for (const dep in dependencies) {
974                if (hasProperty(dependencies, dep) && !startsWith(dep, "@types/")) {
975                    result.push(dep);
976                }
977            }
978        }
979    }
980    return result;
981}
982
983// Replace everything after the last directory separator that appears
984function getDirectoryFragmentTextSpan(text: string, textStart: number): TextSpan | undefined {
985    const index = Math.max(text.lastIndexOf(directorySeparator), text.lastIndexOf(altDirectorySeparator));
986    const offset = index !== -1 ? index + 1 : 0;
987    // If the range is an identifier, span is unnecessary.
988    const length = text.length - offset;
989    return length === 0 || isIdentifierText(text.substr(offset, length), ScriptTarget.ESNext) ? undefined : createTextSpan(textStart + offset, length);
990}
991
992// Returns true if the path is explicitly relative to the script (i.e. relative to . or ..)
993function isPathRelativeToScript(path: string) {
994    if (path && path.length >= 2 && path.charCodeAt(0) === CharacterCodes.dot) {
995        const slashIndex = path.length >= 3 && path.charCodeAt(1) === CharacterCodes.dot ? 2 : 1;
996        const slashCharCode = path.charCodeAt(slashIndex);
997        return slashCharCode === CharacterCodes.slash || slashCharCode === CharacterCodes.backslash;
998    }
999    return false;
1000}
1001
1002/**
1003 * Matches a triple slash reference directive with an incomplete string literal for its path. Used
1004 * to determine if the caret is currently within the string literal and capture the literal fragment
1005 * for completions.
1006 * For example, this matches
1007 *
1008 * /// <reference path="fragment
1009 *
1010 * but not
1011 *
1012 * /// <reference path="fragment"
1013 */
1014const tripleSlashDirectiveFragmentRegex = /^(\/\/\/\s*<reference\s+(path|types)\s*=\s*(?:'|"))([^\3"]*)$/;
1015
1016const nodeModulesDependencyKeys: readonly string[] = ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"];
1017
1018function containsSlash(fragment: string) {
1019    return stringContains(fragment, directorySeparator);
1020}
1021
1022/**
1023 * Matches
1024 *   require(""
1025 *   require("")
1026 */
1027function isRequireCallArgument(node: Node) {
1028    return isCallExpression(node.parent) && firstOrUndefined(node.parent.arguments) === node
1029        && isIdentifier(node.parent.expression) && node.parent.expression.escapedText === "require";
1030}
1031