Minor cleanups in pathCompletions.ts (#19685)

* Minor cleanups in pathCompletions.ts

* Update name
This commit is contained in:
Andy 2017-11-03 15:06:22 -07:00 committed by GitHub
parent 749e151c23
commit 1d7f449a87
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 57 additions and 95 deletions

View File

@ -7,18 +7,10 @@ namespace ts {
const ignoreDiagnosticCommentRegEx = /(^\s*$)|(^\s*\/\/\/?\s*(@ts-ignore)?)/;
export function findConfigFile(searchPath: string, fileExists: (fileName: string) => boolean, configName = "tsconfig.json"): string {
while (true) {
const fileName = combinePaths(searchPath, configName);
if (fileExists(fileName)) {
return fileName;
}
const parentPath = getDirectoryPath(searchPath);
if (parentPath === searchPath) {
break;
}
searchPath = parentPath;
}
return undefined;
return forEachAncestorDirectory(searchPath, ancestor => {
const fileName = combinePaths(ancestor, configName);
return fileExists(fileName) ? fileName : undefined;
});
}
export function resolveTripleslashReference(moduleName: string, containingFile: string): string {

View File

@ -3592,7 +3592,7 @@ namespace ts {
}
/** Calls `callback` on `directory` and every ancestor directory it has, returning the first defined result. */
export function forEachAncestorDirectory<T>(directory: string, callback: (directory: string) => T): T {
export function forEachAncestorDirectory<T>(directory: string, callback: (directory: string) => T | undefined): T | undefined {
while (true) {
const result = callback(directory);
if (result !== undefined) {

View File

@ -30,7 +30,8 @@ namespace ts.Completions {
allSourceFiles: ReadonlyArray<SourceFile>,
): CompletionInfo | undefined {
if (isInReferenceComment(sourceFile, position)) {
return PathCompletions.getTripleSlashReferenceCompletion(sourceFile, position, compilerOptions, host);
const entries = PathCompletions.getTripleSlashReferenceCompletion(sourceFile, position, compilerOptions, host);
return entries && pathCompletionsInfo(entries);
}
if (isInString(sourceFile, position)) {
@ -250,7 +251,8 @@ namespace ts.Completions {
// import x = require("/*completion position*/");
// var y = require("/*completion position*/");
// export * from "/*completion position*/";
return PathCompletions.getStringLiteralCompletionEntriesFromModuleNames(<StringLiteral>node, compilerOptions, host, typeChecker);
const entries = PathCompletions.getStringLiteralCompletionsFromModuleNames(<StringLiteral>node, compilerOptions, host, typeChecker);
return pathCompletionsInfo(entries);
}
else if (isEqualityExpression(node.parent)) {
// Get completions from the type of the other operand
@ -279,6 +281,18 @@ namespace ts.Completions {
}
}
function pathCompletionsInfo(entries: CompletionEntry[]): CompletionInfo {
return {
// We don't want the editor to offer any other completions, such as snippets, inside a comment.
isGlobalCompletion: false,
isMemberCompletion: false,
// 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.
isNewIdentifierLocation: true,
entries,
};
}
function getStringLiteralCompletionEntriesFromPropertyAssignment(element: ObjectLiteralElement, typeChecker: TypeChecker, target: ScriptTarget, log: Log): CompletionInfo | undefined {
const type = typeChecker.getContextualType((<ObjectLiteralExpression>element.parent));
const entries: CompletionEntry[] = [];

View File

@ -1,34 +1,27 @@
/* @internal */
namespace ts.Completions.PathCompletions {
export function getStringLiteralCompletionEntriesFromModuleNames(node: StringLiteral, compilerOptions: CompilerOptions, host: LanguageServiceHost, typeChecker: TypeChecker): CompletionInfo {
export function getStringLiteralCompletionsFromModuleNames(node: StringLiteral, compilerOptions: CompilerOptions, host: LanguageServiceHost, typeChecker: TypeChecker): CompletionEntry[] {
const literalValue = normalizeSlashes(node.text);
const scriptPath = node.getSourceFile().path;
const scriptDirectory = getDirectoryPath(scriptPath);
const span = getDirectoryFragmentTextSpan((<StringLiteral>node).text, node.getStart() + 1);
let entries: CompletionEntry[];
if (isPathRelativeToScript(literalValue) || isRootedDiskPath(literalValue)) {
const extensions = getSupportedExtensions(compilerOptions);
if (compilerOptions.rootDirs) {
entries = getCompletionEntriesForDirectoryFragmentWithRootDirs(
return getCompletionEntriesForDirectoryFragmentWithRootDirs(
compilerOptions.rootDirs, literalValue, scriptDirectory, extensions, /*includeExtensions*/ false, span, compilerOptions, host, scriptPath);
}
else {
entries = getCompletionEntriesForDirectoryFragment(
return getCompletionEntriesForDirectoryFragment(
literalValue, scriptDirectory, extensions, /*includeExtensions*/ false, span, host, scriptPath);
}
}
else {
// Check for node modules
entries = getCompletionEntriesForNonRelativeModules(literalValue, scriptDirectory, span, compilerOptions, host, typeChecker);
return getCompletionEntriesForNonRelativeModules(literalValue, scriptDirectory, span, compilerOptions, host, typeChecker);
}
return {
isGlobalCompletion: false,
isMemberCompletion: false,
isNewIdentifierLocation: true,
entries
};
}
/**
@ -37,14 +30,14 @@ namespace ts.Completions.PathCompletions {
*/
function getBaseDirectoriesFromRootDirs(rootDirs: string[], basePath: string, scriptPath: string, ignoreCase: boolean): string[] {
// Make all paths absolute/normalized if they are not already
rootDirs = map(rootDirs, rootDirectory => normalizePath(isRootedDiskPath(rootDirectory) ? rootDirectory : combinePaths(basePath, rootDirectory)));
rootDirs = rootDirs.map(rootDirectory => normalizePath(isRootedDiskPath(rootDirectory) ? rootDirectory : combinePaths(basePath, rootDirectory)));
// Determine the path to the directory containing the script relative to the root directory it is contained within
const relativeDirectory = forEach(rootDirs, rootDirectory =>
const relativeDirectory = firstDefined(rootDirs, rootDirectory =>
containsPath(rootDirectory, scriptPath, basePath, ignoreCase) ? scriptPath.substr(rootDirectory.length) : undefined);
// Now find a path for each potential directory that is to be merged with the one containing the script
return deduplicate(map(rootDirs, rootDirectory => combinePaths(rootDirectory, relativeDirectory)));
return deduplicate(rootDirs.map(rootDirectory => combinePaths(rootDirectory, relativeDirectory)));
}
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 {
return deduplicate(nonRelativeModuleNames);
}
export function getTripleSlashReferenceCompletion(sourceFile: SourceFile, position: number, compilerOptions: CompilerOptions, host: LanguageServiceHost): CompletionInfo {
export function getTripleSlashReferenceCompletion(sourceFile: SourceFile, position: number, compilerOptions: CompilerOptions, host: LanguageServiceHost): CompletionEntry[] | undefined {
const token = getTokenAtPosition(sourceFile, position, /*includeJsDocComment*/ false);
if (!token) {
return undefined;
}
const commentRanges: CommentRange[] = getLeadingCommentRanges(sourceFile.text, token.pos);
if (!commentRanges || !commentRanges.length) {
return undefined;
}
const range = forEach(commentRanges, commentRange => position >= commentRange.pos && position <= commentRange.end && commentRange);
const commentRanges = getLeadingCommentRanges(sourceFile.text, token.pos);
const range = commentRanges && find(commentRanges, commentRange => position >= commentRange.pos && position <= commentRange.end);
if (!range) {
return undefined;
}
const completionInfo: CompletionInfo = {
/**
* We don't want the editor to offer any other completions, such as snippets, inside a comment.
*/
isGlobalCompletion: false,
isMemberCompletion: false,
/**
* 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.
*/
isNewIdentifierLocation: true,
entries: []
};
const text = sourceFile.text.substr(range.pos, position - range.pos);
const text = sourceFile.text.slice(range.pos, position);
const match = tripleSlashDirectiveFragmentRegex.exec(text);
if (match) {
const prefix = match[1];
const kind = match[2];
const toComplete = match[3];
const scriptPath = getDirectoryPath(sourceFile.path);
if (kind === "path") {
// Give completions for a relative path
const span: TextSpan = getDirectoryFragmentTextSpan(toComplete, range.pos + prefix.length);
completionInfo.entries = getCompletionEntriesForDirectoryFragment(toComplete, scriptPath, getSupportedExtensions(compilerOptions), /*includeExtensions*/ true, span, host, sourceFile.path);
}
else {
// Give completions based on the typings available
const span: TextSpan = { start: range.pos + prefix.length, length: match[0].length - prefix.length };
completionInfo.entries = getCompletionEntriesFromTypings(host, compilerOptions, scriptPath, span);
}
if (!match) {
return undefined;
}
return completionInfo;
const [, prefix, kind, toComplete] = match;
const scriptPath = getDirectoryPath(sourceFile.path);
switch (kind) {
case "path": {
// Give completions for a relative path
const span = getDirectoryFragmentTextSpan(toComplete, range.pos + prefix.length);
return getCompletionEntriesForDirectoryFragment(toComplete, scriptPath, getSupportedExtensions(compilerOptions), /*includeExtensions*/ true, span, host, sourceFile.path);
}
case "types": {
// Give completions based on the typings available
const span = createTextSpan(range.pos + prefix.length, match[0].length - prefix.length);
return getCompletionEntriesFromTypings(host, compilerOptions, scriptPath, span);
}
default:
return undefined;
}
}
function getCompletionEntriesFromTypings(host: LanguageServiceHost, options: CompilerOptions, scriptPath: string, span: TextSpan, result: CompletionEntry[] = []): CompletionEntry[] {
@ -385,26 +352,15 @@ namespace ts.Completions.PathCompletions {
}
}
function findPackageJsons(currentDir: string, host: LanguageServiceHost): string[] {
function findPackageJsons(directory: string, host: LanguageServiceHost): string[] {
const paths: string[] = [];
let currentConfigPath: string;
while (true) {
currentConfigPath = findConfigFile(currentDir, (f) => tryFileExists(host, f), "package.json");
if (currentConfigPath) {
paths.push(currentConfigPath);
currentDir = getDirectoryPath(currentConfigPath);
const parent = getDirectoryPath(currentDir);
if (currentDir === parent) {
break;
}
currentDir = parent;
forEachAncestorDirectory(directory, ancestor => {
const currentConfigPath = findConfigFile(ancestor, (f) => tryFileExists(host, f), "package.json");
if (!currentConfigPath) {
return true; // break out
}
else {
break;
}
}
paths.push(currentConfigPath);
});
return paths;
}