Skip to content

Commit 1d7f449

Browse files
author
Andy
authored
Minor cleanups in pathCompletions.ts (microsoft#19685)
* Minor cleanups in pathCompletions.ts * Update name
1 parent 749e151 commit 1d7f449

4 files changed

Lines changed: 53 additions & 91 deletions

File tree

src/compiler/program.ts

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,10 @@ namespace ts {
77
const ignoreDiagnosticCommentRegEx = /(^\s*$)|(^\s*\/\/\/?\s*(@ts-ignore)?)/;
88

99
export function findConfigFile(searchPath: string, fileExists: (fileName: string) => boolean, configName = "tsconfig.json"): string {
10-
while (true) {
11-
const fileName = combinePaths(searchPath, configName);
12-
if (fileExists(fileName)) {
13-
return fileName;
14-
}
15-
const parentPath = getDirectoryPath(searchPath);
16-
if (parentPath === searchPath) {
17-
break;
18-
}
19-
searchPath = parentPath;
20-
}
21-
return undefined;
10+
return forEachAncestorDirectory(searchPath, ancestor => {
11+
const fileName = combinePaths(ancestor, configName);
12+
return fileExists(fileName) ? fileName : undefined;
13+
});
2214
}
2315

2416
export function resolveTripleslashReference(moduleName: string, containingFile: string): string {

src/compiler/utilities.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3592,7 +3592,7 @@ namespace ts {
35923592
}
35933593

35943594
/** Calls `callback` on `directory` and every ancestor directory it has, returning the first defined result. */
3595-
export function forEachAncestorDirectory<T>(directory: string, callback: (directory: string) => T): T {
3595+
export function forEachAncestorDirectory<T>(directory: string, callback: (directory: string) => T | undefined): T | undefined {
35963596
while (true) {
35973597
const result = callback(directory);
35983598
if (result !== undefined) {

src/services/completions.ts

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@ namespace ts.Completions {
3030
allSourceFiles: ReadonlyArray<SourceFile>,
3131
): CompletionInfo | undefined {
3232
if (isInReferenceComment(sourceFile, position)) {
33-
return PathCompletions.getTripleSlashReferenceCompletion(sourceFile, position, compilerOptions, host);
33+
const entries = PathCompletions.getTripleSlashReferenceCompletion(sourceFile, position, compilerOptions, host);
34+
return entries && pathCompletionsInfo(entries);
3435
}
3536

3637
if (isInString(sourceFile, position)) {
@@ -250,7 +251,8 @@ namespace ts.Completions {
250251
// import x = require("/*completion position*/");
251252
// var y = require("/*completion position*/");
252253
// export * from "/*completion position*/";
253-
return PathCompletions.getStringLiteralCompletionEntriesFromModuleNames(<StringLiteral>node, compilerOptions, host, typeChecker);
254+
const entries = PathCompletions.getStringLiteralCompletionsFromModuleNames(<StringLiteral>node, compilerOptions, host, typeChecker);
255+
return pathCompletionsInfo(entries);
254256
}
255257
else if (isEqualityExpression(node.parent)) {
256258
// Get completions from the type of the other operand
@@ -279,6 +281,18 @@ namespace ts.Completions {
279281
}
280282
}
281283

284+
function pathCompletionsInfo(entries: CompletionEntry[]): CompletionInfo {
285+
return {
286+
// We don't want the editor to offer any other completions, such as snippets, inside a comment.
287+
isGlobalCompletion: false,
288+
isMemberCompletion: false,
289+
// The user may type in a path that doesn't yet exist, creating a "new identifier"
290+
// with respect to the collection of identifiers the server is aware of.
291+
isNewIdentifierLocation: true,
292+
entries,
293+
};
294+
}
295+
282296
function getStringLiteralCompletionEntriesFromPropertyAssignment(element: ObjectLiteralElement, typeChecker: TypeChecker, target: ScriptTarget, log: Log): CompletionInfo | undefined {
283297
const type = typeChecker.getContextualType((<ObjectLiteralExpression>element.parent));
284298
const entries: CompletionEntry[] = [];

src/services/pathCompletions.ts

Lines changed: 32 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,27 @@
11
/* @internal */
22
namespace ts.Completions.PathCompletions {
3-
export function getStringLiteralCompletionEntriesFromModuleNames(node: StringLiteral, compilerOptions: CompilerOptions, host: LanguageServiceHost, typeChecker: TypeChecker): CompletionInfo {
3+
export function getStringLiteralCompletionsFromModuleNames(node: StringLiteral, 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

99
const span = getDirectoryFragmentTextSpan((<StringLiteral>node).text, node.getStart() + 1);
10-
let entries: CompletionEntry[];
1110
if (isPathRelativeToScript(literalValue) || isRootedDiskPath(literalValue)) {
1211
const extensions = getSupportedExtensions(compilerOptions);
1312
if (compilerOptions.rootDirs) {
14-
entries = getCompletionEntriesForDirectoryFragmentWithRootDirs(
13+
return getCompletionEntriesForDirectoryFragmentWithRootDirs(
1514
compilerOptions.rootDirs, literalValue, scriptDirectory, extensions, /*includeExtensions*/ false, span, compilerOptions, host, scriptPath);
1615
}
1716
else {
18-
entries = getCompletionEntriesForDirectoryFragment(
17+
return getCompletionEntriesForDirectoryFragment(
1918
literalValue, scriptDirectory, extensions, /*includeExtensions*/ false, span, host, scriptPath);
2019
}
2120
}
2221
else {
2322
// Check for node modules
24-
entries = getCompletionEntriesForNonRelativeModules(literalValue, scriptDirectory, span, compilerOptions, host, typeChecker);
23+
return getCompletionEntriesForNonRelativeModules(literalValue, scriptDirectory, span, compilerOptions, host, typeChecker);
2524
}
26-
return {
27-
isGlobalCompletion: false,
28-
isMemberCompletion: false,
29-
isNewIdentifierLocation: true,
30-
entries
31-
};
3225
}
3326

3427
/**
@@ -37,14 +30,14 @@ namespace ts.Completions.PathCompletions {
3730
*/
3831
function getBaseDirectoriesFromRootDirs(rootDirs: string[], basePath: string, scriptPath: string, ignoreCase: boolean): string[] {
3932
// Make all paths absolute/normalized if they are not already
40-
rootDirs = map(rootDirs, rootDirectory => normalizePath(isRootedDiskPath(rootDirectory) ? rootDirectory : combinePaths(basePath, rootDirectory)));
33+
rootDirs = rootDirs.map(rootDirectory => normalizePath(isRootedDiskPath(rootDirectory) ? rootDirectory : combinePaths(basePath, rootDirectory)));
4134

4235
// Determine the path to the directory containing the script relative to the root directory it is contained within
43-
const relativeDirectory = forEach(rootDirs, rootDirectory =>
36+
const relativeDirectory = firstDefined(rootDirs, rootDirectory =>
4437
containsPath(rootDirectory, scriptPath, basePath, ignoreCase) ? scriptPath.substr(rootDirectory.length) : undefined);
4538

4639
// Now find a path for each potential directory that is to be merged with the one containing the script
47-
return deduplicate(map(rootDirs, rootDirectory => combinePaths(rootDirectory, relativeDirectory)));
40+
return deduplicate(rootDirs.map(rootDirectory => combinePaths(rootDirectory, relativeDirectory)));
4841
}
4942

5043
function getCompletionEntriesForDirectoryFragmentWithRootDirs(rootDirs: string[], fragment: string, scriptPath: string, extensions: ReadonlyArray<string>, includeExtensions: boolean, span: TextSpan, compilerOptions: CompilerOptions, host: LanguageServiceHost, exclude?: string): CompletionEntry[] {
@@ -283,61 +276,35 @@ namespace ts.Completions.PathCompletions {
283276
return deduplicate(nonRelativeModuleNames);
284277
}
285278

286-
export function getTripleSlashReferenceCompletion(sourceFile: SourceFile, position: number, compilerOptions: CompilerOptions, host: LanguageServiceHost): CompletionInfo {
279+
export function getTripleSlashReferenceCompletion(sourceFile: SourceFile, position: number, compilerOptions: CompilerOptions, host: LanguageServiceHost): CompletionEntry[] | undefined {
287280
const token = getTokenAtPosition(sourceFile, position, /*includeJsDocComment*/ false);
288-
if (!token) {
289-
return undefined;
290-
}
291-
const commentRanges: CommentRange[] = getLeadingCommentRanges(sourceFile.text, token.pos);
292-
293-
if (!commentRanges || !commentRanges.length) {
294-
return undefined;
295-
}
296-
297-
const range = forEach(commentRanges, commentRange => position >= commentRange.pos && position <= commentRange.end && commentRange);
298-
281+
const commentRanges = getLeadingCommentRanges(sourceFile.text, token.pos);
282+
const range = commentRanges && find(commentRanges, commentRange => position >= commentRange.pos && position <= commentRange.end);
299283
if (!range) {
300284
return undefined;
301285
}
302-
303-
const completionInfo: CompletionInfo = {
304-
/**
305-
* We don't want the editor to offer any other completions, such as snippets, inside a comment.
306-
*/
307-
isGlobalCompletion: false,
308-
isMemberCompletion: false,
309-
/**
310-
* The user may type in a path that doesn't yet exist, creating a "new identifier"
311-
* with respect to the collection of identifiers the server is aware of.
312-
*/
313-
isNewIdentifierLocation: true,
314-
315-
entries: []
316-
};
317-
318-
const text = sourceFile.text.substr(range.pos, position - range.pos);
319-
286+
const text = sourceFile.text.slice(range.pos, position);
320287
const match = tripleSlashDirectiveFragmentRegex.exec(text);
288+
if (!match) {
289+
return undefined;
290+
}
321291

322-
if (match) {
323-
const prefix = match[1];
324-
const kind = match[2];
325-
const toComplete = match[3];
326-
327-
const scriptPath = getDirectoryPath(sourceFile.path);
328-
if (kind === "path") {
292+
const [, prefix, kind, toComplete] = match;
293+
const scriptPath = getDirectoryPath(sourceFile.path);
294+
switch (kind) {
295+
case "path": {
329296
// Give completions for a relative path
330-
const span: TextSpan = getDirectoryFragmentTextSpan(toComplete, range.pos + prefix.length);
331-
completionInfo.entries = getCompletionEntriesForDirectoryFragment(toComplete, scriptPath, getSupportedExtensions(compilerOptions), /*includeExtensions*/ true, span, host, sourceFile.path);
297+
const span = getDirectoryFragmentTextSpan(toComplete, range.pos + prefix.length);
298+
return getCompletionEntriesForDirectoryFragment(toComplete, scriptPath, getSupportedExtensions(compilerOptions), /*includeExtensions*/ true, span, host, sourceFile.path);
332299
}
333-
else {
300+
case "types": {
334301
// Give completions based on the typings available
335-
const span: TextSpan = { start: range.pos + prefix.length, length: match[0].length - prefix.length };
336-
completionInfo.entries = getCompletionEntriesFromTypings(host, compilerOptions, scriptPath, span);
302+
const span = createTextSpan(range.pos + prefix.length, match[0].length - prefix.length);
303+
return getCompletionEntriesFromTypings(host, compilerOptions, scriptPath, span);
337304
}
305+
default:
306+
return undefined;
338307
}
339-
340-
return completionInfo;
341308
}
342309

343310
function getCompletionEntriesFromTypings(host: LanguageServiceHost, options: CompilerOptions, scriptPath: string, span: TextSpan, result: CompletionEntry[] = []): CompletionEntry[] {
@@ -385,26 +352,15 @@ namespace ts.Completions.PathCompletions {
385352
}
386353
}
387354

388-
function findPackageJsons(currentDir: string, host: LanguageServiceHost): string[] {
355+
function findPackageJsons(directory: string, host: LanguageServiceHost): string[] {
389356
const paths: string[] = [];
390-
let currentConfigPath: string;
391-
while (true) {
392-
currentConfigPath = findConfigFile(currentDir, (f) => tryFileExists(host, f), "package.json");
393-
if (currentConfigPath) {
394-
paths.push(currentConfigPath);
395-
396-
currentDir = getDirectoryPath(currentConfigPath);
397-
const parent = getDirectoryPath(currentDir);
398-
if (currentDir === parent) {
399-
break;
400-
}
401-
currentDir = parent;
402-
}
403-
else {
404-
break;
357+
forEachAncestorDirectory(directory, ancestor => {
358+
const currentConfigPath = findConfigFile(ancestor, (f) => tryFileExists(host, f), "package.json");
359+
if (!currentConfigPath) {
360+
return true; // break out
405361
}
406-
}
407-
362+
paths.push(currentConfigPath);
363+
});
408364
return paths;
409365
}
410366

0 commit comments

Comments
 (0)