From c06b02ac3426a06959e49f50636c9f86c2d620ef Mon Sep 17 00:00:00 2001 From: Richard Knoll Date: Fri, 24 Jun 2016 13:56:45 -0700 Subject: [PATCH 01/39] Adding completions for import and reference directives --- src/compiler/checker.ts | 13 + src/compiler/program.ts | 4 +- src/compiler/types.ts | 1 + src/harness/harness.ts | 4 +- src/harness/harnessLanguageService.ts | 56 +++- src/harness/virtualFileSystem.ts | 101 ++++--- src/server/editorServices.ts | 8 + src/services/services.ts | 278 +++++++++++++++++- src/services/shims.ts | 26 ++ ...etionForStringLiteralNonrelativeImport1.ts | 53 ++++ ...etionForStringLiteralNonrelativeImport2.ts | 32 ++ ...etionForStringLiteralNonrelativeImport3.ts | 30 ++ ...etionForStringLiteralNonrelativeImport4.ts | 45 +++ ...etionForStringLiteralNonrelativeImport5.ts | 28 ++ ...etionForStringLiteralNonrelativeImport6.ts | 57 ++++ ...mpletionForStringLiteralRelativeImport1.ts | 77 +++++ ...mpletionForStringLiteralRelativeImport2.ts | 43 +++ ...mpletionForStringLiteralRelativeImport3.ts | 41 +++ .../completionForTripleSlashReference1.ts | 79 +++++ .../completionForTripleSlashReference2.ts | 47 +++ .../completionForTripleSlashReference3.ts | 41 +++ 21 files changed, 1009 insertions(+), 55 deletions(-) create mode 100644 tests/cases/fourslash/completionForStringLiteralNonrelativeImport1.ts create mode 100644 tests/cases/fourslash/completionForStringLiteralNonrelativeImport2.ts create mode 100644 tests/cases/fourslash/completionForStringLiteralNonrelativeImport3.ts create mode 100644 tests/cases/fourslash/completionForStringLiteralNonrelativeImport4.ts create mode 100644 tests/cases/fourslash/completionForStringLiteralNonrelativeImport5.ts create mode 100644 tests/cases/fourslash/completionForStringLiteralNonrelativeImport6.ts create mode 100644 tests/cases/fourslash/completionForStringLiteralRelativeImport1.ts create mode 100644 tests/cases/fourslash/completionForStringLiteralRelativeImport2.ts create mode 100644 tests/cases/fourslash/completionForStringLiteralRelativeImport3.ts create mode 100644 tests/cases/fourslash/completionForTripleSlashReference1.ts create mode 100644 tests/cases/fourslash/completionForTripleSlashReference2.ts create mode 100644 tests/cases/fourslash/completionForTripleSlashReference3.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 0ca0fd907f..14cab98db0 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -2,6 +2,8 @@ /* @internal */ namespace ts { + const ambientModuleSymbolRegex = /^".+"$/; + let nextSymbolId = 1; let nextNodeId = 1; let nextMergeId = 1; @@ -101,6 +103,7 @@ namespace ts { getAliasedSymbol: resolveAlias, getEmitResolver, getExportsOfModule: getExportsOfModuleAsArray, + getAmbientModules, getJsxElementAttributesType, getJsxIntrinsicTagNames, @@ -19104,5 +19107,15 @@ namespace ts { return true; } } + + function getAmbientModules(): Symbol[] { + const result: Symbol[] = []; + for (const sym in globals) { + if (globals.hasOwnProperty(sym) && ambientModuleSymbolRegex.test(sym)) { + result.push(globals[sym]); + } + } + return result; + } } } diff --git a/src/compiler/program.ts b/src/compiler/program.ts index a01f3a4c5b..2e5a595009 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -15,9 +15,9 @@ namespace ts { const defaultTypeRoots = ["node_modules/@types"]; - export function findConfigFile(searchPath: string, fileExists: (fileName: string) => boolean): string { + export function findConfigFile(searchPath: string, fileExists: (fileName: string) => boolean, configName="tsconfig.json"): string { while (true) { - const fileName = combinePaths(searchPath, "tsconfig.json"); + const fileName = combinePaths(searchPath, configName); if (fileExists(fileName)) { return fileName; } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index cac04172a5..c2cf3a8527 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -1854,6 +1854,7 @@ namespace ts { getJsxElementAttributesType(elementNode: JsxOpeningLikeElement): Type; getJsxIntrinsicTagNames(): Symbol[]; isOptionalParameter(node: ParameterDeclaration): boolean; + getAmbientModules(): Symbol[]; // Should not be called directly. Should only be accessed through the Program instance. /* @internal */ getDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): Diagnostic[]; diff --git a/src/harness/harness.ts b/src/harness/harness.ts index 1977d95492..ace211f255 100644 --- a/src/harness/harness.ts +++ b/src/harness/harness.ts @@ -742,14 +742,14 @@ namespace Harness { } export function readDirectory(path: string, extension?: string[], exclude?: string[], include?: string[]) { - const fs = new Utils.VirtualFileSystem(path, useCaseSensitiveFileNames()); + const fs = new Utils.VirtualFileSystem(path, useCaseSensitiveFileNames()); for (const file in listFiles(path)) { fs.addFile(file); } return ts.matchFiles(path, extension, exclude, include, useCaseSensitiveFileNames(), getCurrentDirectory(), path => { const entry = fs.traversePath(path); if (entry && entry.isDirectory()) { - const directory = entry; + const directory = >entry; return { files: ts.map(directory.getFiles(), f => f.name), directories: ts.map(directory.getDirectories(), d => d.name) diff --git a/src/harness/harnessLanguageService.ts b/src/harness/harnessLanguageService.ts index 8567a9109d..682496442e 100644 --- a/src/harness/harnessLanguageService.ts +++ b/src/harness/harnessLanguageService.ts @@ -123,7 +123,7 @@ namespace Harness.LanguageService { } export class LanguageServiceAdapterHost { - protected fileNameToScript: ts.Map = {}; + protected virtualFileSystem: Utils.VirtualFileSystem = new Utils.VirtualFileSystem(/*root*/"c:", /*useCaseSensitiveFilenames*/false); constructor(protected cancellationToken = DefaultHostCancellationToken.Instance, protected settings = ts.getDefaultCompilerOptions()) { @@ -135,7 +135,8 @@ namespace Harness.LanguageService { public getFilenames(): string[] { const fileNames: string[] = []; - ts.forEachValue(this.fileNameToScript, (scriptInfo) => { + this.virtualFileSystem.getAllFileEntries().forEach((virtualEntry) => { + const scriptInfo = virtualEntry.content; if (scriptInfo.isRootFile) { // only include root files here // usually it means that we won't include lib.d.ts in the list of root files so it won't mess the computation of compilation root dir. @@ -146,11 +147,12 @@ namespace Harness.LanguageService { } public getScriptInfo(fileName: string): ScriptInfo { - return ts.lookUp(this.fileNameToScript, fileName); + const fileEntry = this.virtualFileSystem.traversePath(fileName); + return fileEntry && fileEntry.isFile() ? (>fileEntry).content : undefined; } public addScript(fileName: string, content: string, isRootFile: boolean): void { - this.fileNameToScript[fileName] = new ScriptInfo(fileName, content, isRootFile); + this.virtualFileSystem.addFile(fileName, new ScriptInfo(fileName, content, isRootFile)); } public editScript(fileName: string, start: number, end: number, newText: string) { @@ -171,7 +173,7 @@ namespace Harness.LanguageService { * @param col 0 based index */ public positionToLineAndCharacter(fileName: string, position: number): ts.LineAndCharacter { - const script: ScriptInfo = this.fileNameToScript[fileName]; + const script: ScriptInfo = this.getScriptInfo(fileName); assert.isOk(script); return ts.computeLineAndCharacterOfPosition(script.getLineMap(), position); @@ -182,7 +184,13 @@ namespace Harness.LanguageService { class NativeLanguageServiceHost extends LanguageServiceAdapterHost implements ts.LanguageServiceHost { getCompilationSettings() { return this.settings; } getCancellationToken() { return this.cancellationToken; } - getDirectories(path: string): string[] { return []; } + getDirectories(path: string): string[] { + const dir = this.virtualFileSystem.traversePath(path); + if (dir && dir.isDirectory()) { + return ts.map((>dir).getDirectories(), (d) => ts.combinePaths(path, d.name)); + } + return []; + } getCurrentDirectory(): string { return ""; } getDefaultLibFileName(): string { return Harness.Compiler.defaultLibFileName; } getScriptFileNames(): string[] { return this.getFilenames(); } @@ -196,6 +204,39 @@ namespace Harness.LanguageService { return script ? script.version.toString() : undefined; } + fileExists(fileName: string): boolean { + const script = this.getScriptSnapshot(fileName); + return script !== undefined; + } + readDirectory(path: string, extensions?: string[], exclude?: string[], include?: string[]): string[] { + return ts.matchFiles(path, extensions, exclude, include, + /*useCaseSensitiveFileNames*/false, + /*currentDirectory*/"/", + (p) => this.virtualFileSystem.getAccessibleFileSystemEntries(p)); + } + readFile(path: string, encoding?: string): string { + const snapshot = this.getScriptSnapshot(path); + return snapshot.getText(0, snapshot.getLength()); + } + resolvePath(path: string): string { + // Reduce away "." and ".." + const parts = path.split("/"); + const res: string[] = []; + for (let i = 0; i < parts.length; i++) { + if (parts[i] === ".") { + continue; + } + else if (parts[i] === ".." && res.length > 0) { + res.splice(res.length - 1, 1); + } + else { + res.push(parts[i]); + } + } + return res.join("/"); + } + + log(s: string): void { } trace(s: string): void { } error(s: string): void { } @@ -299,6 +340,9 @@ namespace Harness.LanguageService { const snapshot = this.nativeHost.getScriptSnapshot(fileName); return snapshot && snapshot.getText(0, snapshot.getLength()); } + resolvePath(path: string): string { + return this.nativeHost.resolvePath(path); + } log(s: string): void { this.nativeHost.log(s); } trace(s: string): void { this.nativeHost.trace(s); } error(s: string): void { this.nativeHost.error(s); } diff --git a/src/harness/virtualFileSystem.ts b/src/harness/virtualFileSystem.ts index 30192b8b8e..5a89efea7f 100644 --- a/src/harness/virtualFileSystem.ts +++ b/src/harness/virtualFileSystem.ts @@ -1,11 +1,11 @@ /// /// namespace Utils { - export class VirtualFileSystemEntry { - fileSystem: VirtualFileSystem; + export class VirtualFileSystemEntry { + fileSystem: VirtualFileSystem; name: string; - constructor(fileSystem: VirtualFileSystem, name: string) { + constructor(fileSystem: VirtualFileSystem, name: string) { this.fileSystem = fileSystem; this.name = name; } @@ -15,15 +15,15 @@ namespace Utils { isFileSystem() { return false; } } - export class VirtualFile extends VirtualFileSystemEntry { - content: string; + export class VirtualFile extends VirtualFileSystemEntry { + content: T; isFile() { return true; } } - export abstract class VirtualFileSystemContainer extends VirtualFileSystemEntry { - abstract getFileSystemEntries(): VirtualFileSystemEntry[]; + export abstract class VirtualFileSystemContainer extends VirtualFileSystemEntry { + abstract getFileSystemEntries(): VirtualFileSystemEntry[]; - getFileSystemEntry(name: string): VirtualFileSystemEntry { + getFileSystemEntry(name: string): VirtualFileSystemEntry { for (const entry of this.getFileSystemEntries()) { if (this.fileSystem.sameName(entry.name, name)) { return entry; @@ -32,57 +32,57 @@ namespace Utils { return undefined; } - getDirectories(): VirtualDirectory[] { - return ts.filter(this.getFileSystemEntries(), entry => entry.isDirectory()); + getDirectories(): VirtualDirectory[] { + return []>ts.filter(this.getFileSystemEntries(), entry => entry.isDirectory()); } - getFiles(): VirtualFile[] { - return ts.filter(this.getFileSystemEntries(), entry => entry.isFile()); + getFiles(): VirtualFile[] { + return []>ts.filter(this.getFileSystemEntries(), entry => entry.isFile()); } - getDirectory(name: string): VirtualDirectory { + getDirectory(name: string): VirtualDirectory { const entry = this.getFileSystemEntry(name); - return entry.isDirectory() ? entry : undefined; + return entry.isDirectory() ? >entry : undefined; } - getFile(name: string): VirtualFile { + getFile(name: string): VirtualFile { const entry = this.getFileSystemEntry(name); - return entry.isFile() ? entry : undefined; + return entry.isFile() ? >entry : undefined; } } - export class VirtualDirectory extends VirtualFileSystemContainer { - private entries: VirtualFileSystemEntry[] = []; + export class VirtualDirectory extends VirtualFileSystemContainer { + private entries: VirtualFileSystemEntry[] = []; isDirectory() { return true; } getFileSystemEntries() { return this.entries.slice(); } - addDirectory(name: string): VirtualDirectory { + addDirectory(name: string): VirtualDirectory { const entry = this.getFileSystemEntry(name); if (entry === undefined) { - const directory = new VirtualDirectory(this.fileSystem, name); + const directory = new VirtualDirectory(this.fileSystem, name); this.entries.push(directory); return directory; } else if (entry.isDirectory()) { - return entry; + return >entry; } else { return undefined; } } - addFile(name: string, content?: string): VirtualFile { + addFile(name: string, content?: T): VirtualFile { const entry = this.getFileSystemEntry(name); if (entry === undefined) { - const file = new VirtualFile(this.fileSystem, name); + const file = new VirtualFile(this.fileSystem, name); file.content = content; this.entries.push(file); return file; } else if (entry.isFile()) { - const file = entry; + const file = >entry; file.content = content; return file; } @@ -92,8 +92,8 @@ namespace Utils { } } - export class VirtualFileSystem extends VirtualFileSystemContainer { - private root: VirtualDirectory; + export class VirtualFileSystem extends VirtualFileSystemContainer { + private root: VirtualDirectory; currentDirectory: string; useCaseSensitiveFileNames: boolean; @@ -101,7 +101,7 @@ namespace Utils { constructor(currentDirectory: string, useCaseSensitiveFileNames: boolean) { super(undefined, ""); this.fileSystem = this; - this.root = new VirtualDirectory(this, ""); + this.root = new VirtualDirectory(this, ""); this.currentDirectory = currentDirectory; this.useCaseSensitiveFileNames = useCaseSensitiveFileNames; } @@ -112,7 +112,7 @@ namespace Utils { addDirectory(path: string) { const components = ts.getNormalizedPathComponents(path, this.currentDirectory); - let directory: VirtualDirectory = this.root; + let directory: VirtualDirectory = this.root; for (const component of components) { directory = directory.addDirectory(component); if (directory === undefined) { @@ -123,7 +123,7 @@ namespace Utils { return directory; } - addFile(path: string, content?: string) { + addFile(path: string, content?: T) { const absolutePath = ts.getNormalizedAbsolutePath(path, this.currentDirectory); const fileName = ts.getBaseFileName(path); const directoryPath = ts.getDirectoryPath(absolutePath); @@ -141,14 +141,14 @@ namespace Utils { } traversePath(path: string) { - let directory: VirtualDirectory = this.root; + let directory: VirtualDirectory = this.root; for (const component of ts.getNormalizedPathComponents(path, this.currentDirectory)) { const entry = directory.getFileSystemEntry(component); if (entry === undefined) { return undefined; } else if (entry.isDirectory()) { - directory = entry; + directory = >entry; } else { return entry; @@ -157,9 +157,34 @@ namespace Utils { return directory; } + + getAccessibleFileSystemEntries(path: string) { + const entry = this.traversePath(path); + if (entry && entry.isDirectory()) { + const directory = >entry; + return { + files: ts.map(directory.getFiles(), f => f.name), + directories: ts.map(directory.getDirectories(), d => d.name) + }; + } + return { files: [], directories: [] }; + } + + getAllFileEntries() { + const fileEntries: VirtualFile[] = []; + getFilesRecursive(this.root, fileEntries); + return fileEntries; + + function getFilesRecursive(dir: VirtualDirectory, result: VirtualFile[]) { + dir.getFiles().forEach((e) => result.push(e)); + dir.getDirectories().forEach((subDir) => getFilesRecursive(subDir, result)); + } + } + + } - export class MockParseConfigHost extends VirtualFileSystem implements ts.ParseConfigHost { + export class MockParseConfigHost extends VirtualFileSystem implements ts.ParseConfigHost { constructor(currentDirectory: string, ignoreCase: boolean, files: string[]) { super(currentDirectory, ignoreCase); for (const file of files) { @@ -170,17 +195,5 @@ namespace Utils { readDirectory(path: string, extensions: string[], excludes: string[], includes: string[]) { return ts.matchFiles(path, extensions, excludes, includes, this.useCaseSensitiveFileNames, this.currentDirectory, (path: string) => this.getAccessibleFileSystemEntries(path)); } - - getAccessibleFileSystemEntries(path: string) { - const entry = this.traversePath(path); - if (entry && entry.isDirectory()) { - const directory = entry; - return { - files: ts.map(directory.getFiles(), f => f.name), - directories: ts.map(directory.getDirectories(), d => d.name) - }; - } - return { files: [], directories: [] }; - } } } \ No newline at end of file diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 9c0662e553..b94c366e7c 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -321,6 +321,14 @@ namespace ts.server { return this.host.getDirectories(path); } + readDirectory(path: string, extensions?: string[], exclude?: string[], include?: string[]): string[] { + return this.host.readDirectory(path, extensions, exclude, include); + } + + readFile(path: string, encoding?: string): string { + return this.host.readFile(path, encoding); + } + /** * @param line 1 based index */ diff --git a/src/services/services.ts b/src/services/services.ts index 492be6f171..c6c8a7a2e8 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1070,6 +1070,11 @@ namespace ts { error?(s: string): void; useCaseSensitiveFileNames?(): boolean; + readDirectory(path: string, extensions?: string[], exclude?: string[], include?: string[]): string[]; + resolvePath(path: string): string; + readFile(path: string, encoding?: string): string; + fileExists(path: string): boolean; + /* * LS host can optionally implement this method if it wants to be completely in charge of module name resolution. * if implementation is omitted then language service will use built-in module resolution logic and get answers to @@ -1943,6 +1948,9 @@ namespace ts { } + const allSupportedExtensions = supportedTypeScriptExtensions.concat(supportedJavascriptExtensions); + const tripleSlashDirectivePrefixRegex = /^\/\/\/\s*node); + } else { // Otherwise, get the completions from the contextual type if one exists return getStringLiteralCompletionEntriesFromContextualType(node); @@ -4314,6 +4339,258 @@ namespace ts { } } } + + function getStringLiteralCompletionEntriesFromModuleNames(node: StringLiteral) { + const literalValue = node.text; + let result: CompletionEntry[]; + + const isRelativePath = startsWith(literalValue, "."); + const scriptDir = getDirectoryPath(node.getSourceFile().path); + if (isRelativePath || isRootedDiskPath(literalValue)) { + result = getCompletionEntriesForDirectoryFragment(literalValue, scriptDir, getSupportedExtensions(program.getCompilerOptions()), /*includeExtensions*/false); + } + else { + // Check for node modules + result = getCompletionEntriesForNonRelativeModules(literalValue, scriptDir); + } + + return { + isMemberCompletion: false, + isNewIdentifierLocation: false, + entries: result + }; + } + + function getCompletionEntriesForDirectoryFragment(fragment: string, scriptPath: string, extensions: string[], includeExtensions: boolean): CompletionEntry[] { + // Complete the path by looking for source files and directories + const result: CompletionEntry[] = []; + + const toComplete = getBaseFileName(fragment); + const absolutePath = normalizeSlashes(host.resolvePath(isRootedDiskPath(fragment) ? fragment : combinePaths(scriptPath, fragment))); + const baseDir = getDirectoryPath(absolutePath); + + + if (directoryProbablyExists(baseDir, host)) { + // Enumerate the available files + const files = host.readDirectory(baseDir, extensions, /*exclude*/undefined, /*include*/["./*"]); + files.forEach((f) => { + const fName = includeExtensions ? getBaseFileName(f) : removeFileExtension(getBaseFileName(f)); + + if (startsWith(fName, toComplete)) { + result.push({ + name: fName, + kind: ScriptElementKind.unknown, + kindModifiers: ScriptElementKindModifier.none, + sortText: fName + }); + } + }); + + // If possible, get folder completion as well + if (host.getDirectories) { + const directories = host.getDirectories(baseDir); + directories.forEach((d) => { + const dName = getBaseFileName(removeTrailingDirectorySeparator(d)); + + if (startsWith(dName, toComplete)) { + result.push({ + name: ensureTrailingDirectorySeparator(dName), + kind: ScriptElementKind.unknown, + kindModifiers: ScriptElementKindModifier.none, + sortText: dName + }); + } + }); + } + } + + return includeExtensions ? result : deduplicate(result, (a, b) => a.name === b.name); + } + + /** + * Check all of the declared modules and those in node modules. Possible sources of modules: + * Modules declared in the program + * Modules from node_modules (i.e. those listed in package.json) + * This includes all files that are found in node_modules/moduleName/ and node_modules/@types/moduleName/ + * with acceptable file extensions + */ + function getCompletionEntriesForNonRelativeModules(fragment: string, scriptPath: string): CompletionEntry[] { + return ts.map(enumeratePotentialNonRelativeModules(fragment, scriptPath), (moduleName) => { + return { + name: moduleName, + kind: ScriptElementKind.unknown, + kindModifiers: ScriptElementKindModifier.none, + sortText: moduleName + }; + }); + } + + function enumeratePotentialNonRelativeModules(fragment: string, scriptPath: string) { + const ambientSymbolNameRegex = /^"(.+?)"$/; + + // If this is a nested module, get the module name + const firstSeparator = fragment.indexOf(directorySeparator); + const moduleNameFragment = firstSeparator !== -1 ? fragment.substr(0, firstSeparator) : fragment; + const isNestedModule = fragment !== moduleNameFragment; + + // Get modules that the type checker picked up + const ambientModules = ts.map(program.getTypeChecker().getAmbientModules(), (sym) => { + const match = ambientSymbolNameRegex.exec(sym.name); + if (match) { + return match[1]; + } + // This should never happen + return sym.name; + }); + let nonRelativeModules = ts.filter(ambientModules, (moduleName) => startsWith(moduleName, fragment)); + + // Nested modules of the form "module-name/sub" need to be adjusted to only return the string + // after the last '/' that appears in the fragment because editors insert the completion + // only after that character + nonRelativeModules = ts.map(nonRelativeModules, (moduleName) => { + if (moduleName.indexOf(directorySeparator) !== -1) { + if (isNestedModule) { + return moduleName.substr(fragment.lastIndexOf(directorySeparator) + 1); + } + } + return moduleName; + }); + + // Check for node_modules. We only offer completions for modules that are listed in the + // package.json for a project for efficiency and to ensure that the completion list is + // not polluted with sub-dependencies + findPackageJsons(scriptPath).forEach((packageJson) => { + const package = tryReadingPackageJson(packageJson); + if (!package) { + return; + } + + const nodeModulesDir = combinePaths(getDirectoryPath(packageJson), "node_modules"); + const foundModuleNames: string[] = []; + + if (package.dependencies) { + addPotentialPackageNames(package.dependencies, moduleNameFragment, foundModuleNames); + } + if (package.devDependencies) { + addPotentialPackageNames(package.devDependencies, moduleNameFragment, foundModuleNames); + } + + foundModuleNames.forEach((moduleName) => { + if (isNestedModule && moduleName === moduleNameFragment) { + const moduleDir = combinePaths(nodeModulesDir, moduleName); + if (directoryProbablyExists(moduleDir, host)) { + // Get all possible nested module names from files with all extensions + const nestedFiles = host.readDirectory(moduleDir, allSupportedExtensions, /*exclude*/undefined, /*include*/["./*"]); + + // Add those with typings to the completion list + nestedFiles.forEach((f) => { + const nestedModule = removeFileExtension(getBaseFileName(f)); + if (hasTypeScriptFileExtension(f)) { + nonRelativeModules.push(nestedModule); + } + }); + } + } + else if (startsWith(moduleName, fragment)) { + if (moduleCanBeImported(combinePaths(nodeModulesDir, moduleName))) { + nonRelativeModules.push(moduleName); + } + else { + nonRelativeModules.push(ensureTrailingDirectorySeparator(moduleName)); + } + } + }); + }); + + return deduplicate(nonRelativeModules); + } + + function findPackageJsons(currentDir: string): string[] { + const paths: string[] = []; + let currentConfigPath: string; + while (true) { + currentConfigPath = findConfigFile(currentDir, (f) => host.fileExists(f), "package.json"); + if (currentConfigPath) { + paths.push(currentConfigPath); + + currentDir = getDirectoryPath(currentConfigPath); + const parent = getDirectoryPath(currentDir); + if (currentDir === parent) { + break; + } + currentDir = parent; + } + else { + break; + } + } + + return paths; + } + + function tryReadingPackageJson(filePath: string) { + try { + const fileText = host.readFile(filePath); + return JSON.parse(fileText); + } + catch (e) { + return undefined; + } + } + + function addPotentialPackageNames(dependencies: any, prefix: string, result: string[]) { + for (const dep in dependencies) { + if (dependencies.hasOwnProperty(dep) && startsWith(dep, prefix)) { + result.push(dep); + } + } + } + + /* + * A module can be imported by name alone if one of the following is true: + * It defines the "typings" property in its package.json + * The module has a "main" export and an index.d.ts file + * The module has an index.ts + */ + function moduleCanBeImported(modulePath: string): boolean { + const packagePath = combinePaths(modulePath, "package.json"); + + let hasMainExport = false; + if (host.fileExists(packagePath)) { + const package = tryReadingPackageJson(packagePath); + if (package) { + if (package.typings) { + return true; + } + hasMainExport = !!package.main; + } + } + + hasMainExport = hasMainExport || host.fileExists(combinePaths(modulePath, "index.js")); + + return (hasMainExport && host.fileExists(combinePaths(modulePath, "index.d.ts"))) || host.fileExists(combinePaths(modulePath, "index.ts")); + } + + function getTripleSlashReferenceCompletion(sourceFile: SourceFile, position: number) { + const node = getTokenAtPosition(sourceFile, position); + if (!node) { + return undefined; + } + + const text = sourceFile.text.substr(node.pos, position); + const match = tripleSlashDirectiveFragmentRegex.exec(text); + if (match) { + const fragment = match[1]; + const scriptPath = getDirectoryPath(sourceFile.path); + return { + isMemberCompletion: false, + isNewIdentifierLocation: false, + entries: getCompletionEntriesForDirectoryFragment(fragment, scriptPath, getSupportedExtensions(program.getCompilerOptions()), /*includeExtensions*/true) + }; + } + + return undefined; + } } function getCompletionEntryDetails(fileName: string, position: number, entryName: string): CompletionEntryDetails { @@ -6189,7 +6466,6 @@ namespace ts { symbolToIndex: number[]): void { const sourceFile = container.getSourceFile(); - const tripleSlashDirectivePrefixRegex = /^\/\/\/\s* + +// @Filename: tests/test0.ts +//// import * as foo from "f/*0*/ + +// @Filename: tests/test1.ts +//// import * as foo from "fake-module//*1*/ + +// @Filename: tests/test2.ts +//// import * as foo from "fake-module/*2*/ + +// @Filename: package.json +//// { "dependencies": { "fake-module": "latest" }, "devDependencies": { "fake-module-dev": "latest" } } + +// @Filename: node_modules/fake-module/index.js +//// /*fake-module*/ +// @Filename: node_modules/fake-module/index.d.ts +//// /*fakemodule-d-ts*/ +// @Filename: node_modules/fake-module/ts.ts +//// /*ts*/ +// @Filename: node_modules/fake-module/dts.d.ts +//// /*dts*/ +// @Filename: node_modules/fake-module/tsx.tsx +//// /*tsx*/ +// @Filename: node_modules/fake-module/js.js +//// /*js*/ +// @Filename: node_modules/fake-module/jsx.jsx +//// /*jsx*/ + +// @Filename: node_modules/fake-module-dev/index.js +//// /*fakemodule-dev*/ +// @Filename: node_modules/fake-module-dev/index.d.ts +//// /*fakemodule-dev-d-ts*/ + +// @Filename: node_modules/unlisted-module/index.ts +//// /*unlisted-module*/ + +goTo.marker("0"); +verify.completionListContains("fake-module"); +verify.completionListContains("fake-module-dev"); +verify.not.completionListItemsCountIsGreaterThan(2); + +goTo.marker("1"); +verify.completionListContains("index"); +verify.completionListContains("ts"); +verify.completionListContains("dts"); +verify.completionListContains("tsx"); +verify.not.completionListItemsCountIsGreaterThan(4); + +goTo.marker("2"); +verify.completionListContains("fake-module"); +verify.completionListContains("fake-module-dev"); +verify.not.completionListItemsCountIsGreaterThan(2); \ No newline at end of file diff --git a/tests/cases/fourslash/completionForStringLiteralNonrelativeImport2.ts b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport2.ts new file mode 100644 index 0000000000..c3e6f2e906 --- /dev/null +++ b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport2.ts @@ -0,0 +1,32 @@ +/// + +// @Filename: tests/test0.ts +//// import * as foo from "fake-module//*0*/ + +// @Filename: package.json +//// { "dependencies": { "fake-module": "latest" }, "devDependencies": { "fake-module-dev": "latest" } } + +// @Filename: node_modules/fake-module/repeated.ts +//// /*repeatedts*/ +// @Filename: node_modules/fake-module/repeated.tsx +//// /*repeatedtsx*/ +// @Filename: node_modules/fake-module/repeated.d.ts +//// /*repeateddts*/ +// @Filename: node_modules/fake-module/other.js +//// /*other*/ +// @Filename: node_modules/fake-module/other2.js +//// /*other2*/ + +// @Filename: node_modules/unlisted-module/index.js +//// /*unlisted-module*/ + +// @Filename: node_modules/@types/fake-module/other.d.ts +//// declare module "fake-module/other" {} + +// @Filename: node_modules/@types/unlisted-module/index.d.ts +//// /*unlisted-types*/ + +goTo.marker("0"); +verify.completionListContains("repeated"); +verify.completionListContains("other"); +verify.not.completionListItemsCountIsGreaterThan(2); diff --git a/tests/cases/fourslash/completionForStringLiteralNonrelativeImport3.ts b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport3.ts new file mode 100644 index 0000000000..7be26cf76d --- /dev/null +++ b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport3.ts @@ -0,0 +1,30 @@ +/// +// @allowJs: true + +// @Filename: tests/test0.ts +//// import * as foo from "fake-module//*0*/ + +// @Filename: package.json +//// { "dependencies": { "fake-module": "latest" } } + +// @Filename: node_modules/fake-module/ts.ts +//// /*ts*/ +// @Filename: node_modules/fake-module/tsx.tsx +//// /*tsx*/ +// @Filename: node_modules/fake-module/dts.d.ts +//// /*dts*/ +// @Filename: node_modules/fake-module/js.js +//// /*js*/ +// @Filename: node_modules/fake-module/jsx.jsx +//// /*jsx*/ +// @Filename: node_modules/fake-module/repeated.js +//// /*repeatedjs*/ +// @Filename: node_modules/fake-module/repeated.jsx +//// /*repeatedjsx*/ + +goTo.marker("0"); + +verify.completionListContains("ts"); +verify.completionListContains("tsx"); +verify.completionListContains("dts"); +verify.not.completionListItemsCountIsGreaterThan(3); diff --git a/tests/cases/fourslash/completionForStringLiteralNonrelativeImport4.ts b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport4.ts new file mode 100644 index 0000000000..baa1363856 --- /dev/null +++ b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport4.ts @@ -0,0 +1,45 @@ +/// + +// @Filename: dir1/dir2/dir3/dir4/test0.ts +//// import * as foo from "f/*0*/ + +// @Filename: dir1/dir2/dir3/dir4/test1.ts +//// import * as foo from "a/*1*/ + +// @Filename: dir1/dir2/dir3/dir4/test2.ts +//// import * as foo from "fake-module/*2*/ + +// @Filename: package.json +//// { "dependencies": { "fake-module": "latest" } } +// @Filename: node_modules/fake-module/ts.ts +//// /*module1*/ + +// @Filename: dir1/package.json +//// { "dependencies": { "fake-module2": "latest" } } +// @Filename: dir1/node_modules/@types/fake-module2/js.d.ts +//// declare module "ambient-module-test" {} + +// @Filename: dir1/dir2/dir3/package.json +//// { "dependencies": { "fake-module3": "latest" } } +// @Filename: dir1/dir2/dir3/node_modules/fake-module3/ts.ts +//// /*module3*/ + + +goTo.marker("0"); + +verify.completionListContains("fake-module/"); +verify.completionListContains("fake-module2/"); +verify.completionListContains("fake-module3/"); +verify.not.completionListItemsCountIsGreaterThan(3); + +goTo.marker("1"); + +verify.completionListContains("ambient-module-test"); +verify.not.completionListItemsCountIsGreaterThan(1); + +goTo.marker("2"); + +verify.completionListContains("fake-module/"); +verify.completionListContains("fake-module2/"); +verify.completionListContains("fake-module3/"); +verify.not.completionListItemsCountIsGreaterThan(3); diff --git a/tests/cases/fourslash/completionForStringLiteralNonrelativeImport5.ts b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport5.ts new file mode 100644 index 0000000000..a5bd602bbb --- /dev/null +++ b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport5.ts @@ -0,0 +1,28 @@ +/// + +// @Filename: test0.ts +//// import * as foo from "/*0*/ + +// @Filename: test1.ts +//// import * as foo from "a/*1*/ + +// @Filename: ambientModules.d.ts +//// declare module "ambientModule" {} +//// declare module "otherAmbientModule" {} + +// @Filename: ambientModules2.d.ts +//// declare module "otherOtherAmbientModule" {} + + +goTo.marker("0"); + +verify.completionListContains("ambientModule"); +verify.completionListContains("otherAmbientModule"); +verify.completionListContains("otherOtherAmbientModule"); +verify.not.completionListItemsCountIsGreaterThan(3); + +goTo.marker("1"); + +verify.completionListContains("ambientModule"); +verify.not.completionListItemsCountIsGreaterThan(1); + diff --git a/tests/cases/fourslash/completionForStringLiteralNonrelativeImport6.ts b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport6.ts new file mode 100644 index 0000000000..ed588f3580 --- /dev/null +++ b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport6.ts @@ -0,0 +1,57 @@ +/// + +// @Filename: tests/test0.ts +//// import * as foo from "module-/*0*/ + +// @Filename: package.json +//// { "dependencies": { +//// "module-no-main": "latest", +//// "module-no-main-index-d-ts": "latest", +//// "module-index-ts": "latest", +//// "module-index-d-ts-explicit-main": "latest", +//// "module-index-d-ts-default-main": "latest", +//// "module-typings": "latest" +//// } } + +// @Filename: node_modules/module-no-main/package.json +//// { } + +// @Filename: node_modules/module-no-main-index-d-ts/package.json +//// { } +// @Filename: node_modules/module-no-main-index-d-ts/index.d.ts +//// /*module-no-main-index-d-ts*/ + +// @Filename: node_modules/module-index-ts/package.json +//// { } +// @Filename: node_modules/module-index-ts/index.ts +//// /*module-index-ts*/ + +// @Filename: node_modules/module-index-d-ts-explicit-main/package.json +//// { "main":"./notIndex.js" } +// @Filename: node_modules/module-index-d-ts-explicit-main/notIndex.js +//// /*module-index-d-ts-explicit-main*/ +// @Filename: node_modules/module-index-d-ts-explicit-main/index.d.ts +//// /*module-index-d-ts-explicit-main2*/ + +// @Filename: node_modules/module-index-d-ts-default-main/package.json +//// { } +// @Filename: node_modules/module-index-d-ts-default-main/index.js +//// /*module-index-d-ts-default-main*/ +// @Filename: node_modules/module-index-d-ts-default-main/index.d.ts +//// /*module-index-d-ts-default-main2*/ + +// @Filename: node_modules/module-typings/package.json +//// { "typings":"./types.d.ts" } +// @Filename: node_modules/module-typings/types.d.ts +//// /*module-typings*/ + + +goTo.marker("0"); + +verify.completionListContains("module-no-main/"); +verify.completionListContains("module-no-main-index-d-ts/"); +verify.completionListContains("module-index-ts"); +verify.completionListContains("module-index-d-ts-explicit-main"); +verify.completionListContains("module-index-d-ts-default-main"); +verify.completionListContains("module-typings"); +verify.not.completionListItemsCountIsGreaterThan(6); diff --git a/tests/cases/fourslash/completionForStringLiteralRelativeImport1.ts b/tests/cases/fourslash/completionForStringLiteralRelativeImport1.ts new file mode 100644 index 0000000000..26a6645b3f --- /dev/null +++ b/tests/cases/fourslash/completionForStringLiteralRelativeImport1.ts @@ -0,0 +1,77 @@ +/// + +// @Filename: test0.ts +//// import * as foo from "./*0*/ + +// @Filename: test1.ts +//// import * as foo from ".//*1*/ + +// @Filename: test2.ts +//// import * as foo from "./f/*2*/ + +// @Filename: test3.ts +//// import * as foo from "./folder//*3*/ + +// @Filename: test4.ts +//// import * as foo from "./folder/h/*4*/ + +// @Filename: parentTest/sub/test5.ts +//// import * as foo from "../g/*5*/ + +// @Filename: f1.ts +//// /*f1*/ +// @Filename: f1.js +//// /*f1j*/ +// @Filename: f1.d.ts +//// /*f1d*/ +// @Filename: f2.tsx +//// /f2*/ +// @Filename: f3.js +//// /*f3*/ +// @Filename: f4.jsx +//// /*f4*/ +// @Filename: e1.ts +//// /*e1*/ +// @Filename: folder/f1.ts +//// /*subf1*/ +// @Filename: folder/h1.ts +//// /*subh1*/ +// @Filename: parentTest/f1.ts +//// /*parentf1*/ +// @Filename: parentTest/g1.ts +//// /*parentg1*/ + +goTo.marker("0"); +verify.completionListIsEmpty(); + +goTo.marker("1"); +verify.completionListContains("f1"); +verify.completionListContains("f2"); +verify.completionListContains("e1"); +verify.completionListContains("test0"); +verify.completionListContains("test1"); +verify.completionListContains("test2"); +verify.completionListContains("test3"); +verify.completionListContains("test4"); +verify.completionListContains("folder/"); +verify.completionListContains("parentTest/"); +verify.not.completionListItemsCountIsGreaterThan(10); + +goTo.marker("2"); +verify.completionListContains("f1"); +verify.completionListContains("f2"); +verify.completionListContains("folder/"); +verify.not.completionListItemsCountIsGreaterThan(3); + +goTo.marker("3"); +verify.completionListContains("f1"); +verify.completionListContains("h1"); +verify.not.completionListItemsCountIsGreaterThan(2); + +goTo.marker("4"); +verify.completionListContains("h1"); +verify.not.completionListItemsCountIsGreaterThan(1); + +goTo.marker("5"); +verify.completionListContains("g1"); +verify.not.completionListItemsCountIsGreaterThan(1); \ No newline at end of file diff --git a/tests/cases/fourslash/completionForStringLiteralRelativeImport2.ts b/tests/cases/fourslash/completionForStringLiteralRelativeImport2.ts new file mode 100644 index 0000000000..1b4abe7911 --- /dev/null +++ b/tests/cases/fourslash/completionForStringLiteralRelativeImport2.ts @@ -0,0 +1,43 @@ +/// +// @allowJs: true + +// @Filename: test0.ts +//// import * as foo from ".//*0*/ + +// @Filename: test1.ts +//// import * as foo from "./f/*1*/ + +// @Filename: f1.ts +//// /f1*/ +// @Filename: f1.js +//// /*f1j*/ +// @Filename: f1.d.ts +//// /*f1d*/ +// @Filename: f2.tsx +//// /*f2*/ +// @Filename: f3.js +//// /*f3*/ +// @Filename: f4.jsx +//// /*f4*/ +// @Filename: e1.ts +//// /*e1*/ +// @Filename: e2.js +//// /*e2*/ + +goTo.marker("0"); +verify.completionListContains("f1"); +verify.completionListContains("f2"); +verify.completionListContains("f3"); +verify.completionListContains("f4"); +verify.completionListContains("e1"); +verify.completionListContains("e2"); +verify.completionListContains("test0"); +verify.completionListContains("test1"); +verify.not.completionListItemsCountIsGreaterThan(8); + +goTo.marker("1"); +verify.completionListContains("f1"); +verify.completionListContains("f2"); +verify.completionListContains("f3"); +verify.completionListContains("f4"); +verify.not.completionListItemsCountIsGreaterThan(4); \ No newline at end of file diff --git a/tests/cases/fourslash/completionForStringLiteralRelativeImport3.ts b/tests/cases/fourslash/completionForStringLiteralRelativeImport3.ts new file mode 100644 index 0000000000..1b10ffff5b --- /dev/null +++ b/tests/cases/fourslash/completionForStringLiteralRelativeImport3.ts @@ -0,0 +1,41 @@ +/// + +// @Filename: tests/test0.ts +//// import * as foo from "c:/tests/cases/f/*0*/ + +// @Filename: tests/test1.ts +//// import * as foo from "c:/tests/cases/fourslash/*1*/ + +// @Filename: tests/test2.ts +//// import * as foo from "c:/tests/cases/fourslash//*2*/ + +// @Filename: f1.ts +//// /*f1*/ +// @Filename: f2.tsx +//// /*f2*/ +// @Filename: folder/f1.ts +//// /*subf1*/ +// @Filename: f3.js +//// /*f3*/ +// @Filename: f4.jsx +//// /*f4*/ +// @Filename: e1.ts +//// /*e1*/ +// @Filename: e2.js +//// /*e2*/ + +goTo.marker("0"); +verify.completionListContains("fourslash/"); +verify.not.completionListItemsCountIsGreaterThan(1); + +goTo.marker("1"); +verify.completionListContains("fourslash/"); +verify.not.completionListItemsCountIsGreaterThan(1); + +goTo.marker("2"); +verify.completionListContains("f1"); +verify.completionListContains("f2"); +verify.completionListContains("e1"); +verify.completionListContains("folder/"); +verify.completionListContains("tests/"); +verify.not.completionListItemsCountIsGreaterThan(5); \ No newline at end of file diff --git a/tests/cases/fourslash/completionForTripleSlashReference1.ts b/tests/cases/fourslash/completionForTripleSlashReference1.ts new file mode 100644 index 0000000000..96582be173 --- /dev/null +++ b/tests/cases/fourslash/completionForTripleSlashReference1.ts @@ -0,0 +1,79 @@ +/// + +// @Filename: test0.ts +//// /// + +// @Filename: test3.ts +//// /// +// @allowJs: true + +// @Filename: test0.ts +//// /// + +// @Filename: tests/test0.ts +//// /// Date: Fri, 24 Jun 2016 14:51:30 -0700 Subject: [PATCH 02/39] Minor fix --- src/services/services.ts | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/src/services/services.ts b/src/services/services.ts index c6c8a7a2e8..650d0c4ce1 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1948,7 +1948,6 @@ namespace ts { } - const allSupportedExtensions = supportedTypeScriptExtensions.concat(supportedJavascriptExtensions); const tripleSlashDirectivePrefixRegex = /^\/\/\/\s* { @@ -4479,15 +4477,11 @@ namespace ts { if (isNestedModule && moduleName === moduleNameFragment) { const moduleDir = combinePaths(nodeModulesDir, moduleName); if (directoryProbablyExists(moduleDir, host)) { - // Get all possible nested module names from files with all extensions - const nestedFiles = host.readDirectory(moduleDir, allSupportedExtensions, /*exclude*/undefined, /*include*/["./*"]); + const nestedFiles = host.readDirectory(moduleDir, supportedTypeScriptExtensions, /*exclude*/undefined, /*include*/["./*"]); - // Add those with typings to the completion list nestedFiles.forEach((f) => { const nestedModule = removeFileExtension(getBaseFileName(f)); - if (hasTypeScriptFileExtension(f)) { - nonRelativeModules.push(nestedModule); - } + nonRelativeModules.push(nestedModule); }); } } From dbdd9893f54a5a5e3165bc1e21010d9e51740ae8 Mon Sep 17 00:00:00 2001 From: Richard Knoll Date: Mon, 27 Jun 2016 13:06:40 -0700 Subject: [PATCH 03/39] Import completions for require calls --- src/services/services.ts | 26 ++++-- ...etionForStringLiteralNonrelativeImport1.ts | 46 ++++++---- ...etionForStringLiteralNonrelativeImport2.ts | 16 ++-- ...etionForStringLiteralNonrelativeImport3.ts | 17 ++-- ...etionForStringLiteralNonrelativeImport4.ts | 43 +++++---- ...etionForStringLiteralNonrelativeImport5.ts | 29 +++--- ...etionForStringLiteralNonrelativeImport6.ts | 23 +++-- ...mpletionForStringLiteralRelativeImport1.ts | 89 ++++++++++--------- ...mpletionForStringLiteralRelativeImport2.ts | 44 +++++---- ...mpletionForStringLiteralRelativeImport3.ts | 44 +++++---- 10 files changed, 225 insertions(+), 152 deletions(-) diff --git a/src/services/services.ts b/src/services/services.ts index 650d0c4ce1..04c435f133 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -2775,13 +2775,17 @@ namespace ts { function isNameOfExternalModuleImportOrDeclaration(node: Node): boolean { if (node.kind === SyntaxKind.StringLiteral) { - return isNameOfModuleDeclaration(node) || - (isExternalModuleImportEqualsDeclaration(node.parent.parent) && getExternalModuleImportEqualsDeclarationExpression(node.parent.parent) === node); + return isNameOfModuleDeclaration(node) || isExpressionOfExternalModuleImportEqualsDeclaration(node); } return false; } + function isExpressionOfExternalModuleImportEqualsDeclaration(node: Node) { + return isExternalModuleImportEqualsDeclaration(node.parent.parent) && + getExternalModuleImportEqualsDeclarationExpression(node.parent.parent) === node; + } + /** Returns true if the position is within a comment */ function isInsideComment(sourceFile: SourceFile, token: Node, position: number): boolean { // The position has to be: 1. in the leading trivia (before token.getStart()), and 2. within a comment @@ -4256,15 +4260,25 @@ namespace ts { const argumentInfo = SignatureHelp.getContainingArgumentInfo(node, position, sourceFile); if (argumentInfo) { - // Get string literal completions from specialized signatures of the target - return getStringLiteralCompletionEntriesFromCallExpression(argumentInfo); + // Try to get string literal completions from specialized signatures of the target + const callExpressionCompletionEntries = getStringLiteralCompletionEntriesFromCallExpression(argumentInfo); + if (callExpressionCompletionEntries) { + return callExpressionCompletionEntries; + } + else if (isRequireCall(node.parent, false)) { + // If that failed but this call mataches the signature of a require call, treat the literal as an external module name + return getStringLiteralCompletionEntriesFromModuleNames(node); + } + else { + return undefined; + } } else if (isElementAccessExpression(node.parent) && node.parent.argumentExpression === node) { // Get all names of properties on the expression return getStringLiteralCompletionEntriesFromElementAccess(node.parent); } - else if (node.parent.kind === SyntaxKind.ImportDeclaration) { - // Get all known module names + else if (node.parent.kind === SyntaxKind.ImportDeclaration || isExpressionOfExternalModuleImportEqualsDeclaration(node)) { + // Get all known external module names or complete a path to a module return getStringLiteralCompletionEntriesFromModuleNames(node); } else { diff --git a/tests/cases/fourslash/completionForStringLiteralNonrelativeImport1.ts b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport1.ts index 681a6fce03..5c7b2016a7 100644 --- a/tests/cases/fourslash/completionForStringLiteralNonrelativeImport1.ts +++ b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport1.ts @@ -1,13 +1,17 @@ /// // @Filename: tests/test0.ts -//// import * as foo from "f/*0*/ +//// import * as foo1 from "f/*import_as0*/ +//// import * as foo2 from "fake-module//*import_as1*/ +//// import * as foo3 from "fake-module/*import_as2*/ -// @Filename: tests/test1.ts -//// import * as foo from "fake-module//*1*/ +//// import foo4 = require("f/*import_equals0*/ +//// import foo5 = require("fake-module//*import_equals1*/ +//// import foo6 = require("fake-module/*import_equals2*/ -// @Filename: tests/test2.ts -//// import * as foo from "fake-module/*2*/ +//// var foo7 = require("f/*require0*/ +//// var foo8 = require("fake-module//*require1*/ +//// var foo9 = require("fake-module/*require2*/ // @Filename: package.json //// { "dependencies": { "fake-module": "latest" }, "devDependencies": { "fake-module-dev": "latest" } } @@ -35,19 +39,23 @@ // @Filename: node_modules/unlisted-module/index.ts //// /*unlisted-module*/ -goTo.marker("0"); -verify.completionListContains("fake-module"); -verify.completionListContains("fake-module-dev"); -verify.not.completionListItemsCountIsGreaterThan(2); +const kinds = ["import_as", "import_equals", "require"]; -goTo.marker("1"); -verify.completionListContains("index"); -verify.completionListContains("ts"); -verify.completionListContains("dts"); -verify.completionListContains("tsx"); -verify.not.completionListItemsCountIsGreaterThan(4); +for (const kind of kinds) { + goTo.marker(kind + "0"); + verify.completionListContains("fake-module"); + verify.completionListContains("fake-module-dev"); + verify.not.completionListItemsCountIsGreaterThan(2); -goTo.marker("2"); -verify.completionListContains("fake-module"); -verify.completionListContains("fake-module-dev"); -verify.not.completionListItemsCountIsGreaterThan(2); \ No newline at end of file + goTo.marker(kind + "1"); + verify.completionListContains("index"); + verify.completionListContains("ts"); + verify.completionListContains("dts"); + verify.completionListContains("tsx"); + verify.not.completionListItemsCountIsGreaterThan(4); + + goTo.marker(kind + "2"); + verify.completionListContains("fake-module"); + verify.completionListContains("fake-module-dev"); + verify.not.completionListItemsCountIsGreaterThan(2); +} \ No newline at end of file diff --git a/tests/cases/fourslash/completionForStringLiteralNonrelativeImport2.ts b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport2.ts index c3e6f2e906..2b5f57412d 100644 --- a/tests/cases/fourslash/completionForStringLiteralNonrelativeImport2.ts +++ b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport2.ts @@ -1,7 +1,9 @@ /// // @Filename: tests/test0.ts -//// import * as foo from "fake-module//*0*/ +//// import * as foo1 from "fake-module//*import_as0*/ +//// import foo2 = require("fake-module//*import_equals0*/ +//// var foo3 = require("fake-module//*require0*/ // @Filename: package.json //// { "dependencies": { "fake-module": "latest" }, "devDependencies": { "fake-module-dev": "latest" } } @@ -26,7 +28,11 @@ // @Filename: node_modules/@types/unlisted-module/index.d.ts //// /*unlisted-types*/ -goTo.marker("0"); -verify.completionListContains("repeated"); -verify.completionListContains("other"); -verify.not.completionListItemsCountIsGreaterThan(2); +const kinds = ["import_as", "import_equals", "require"]; + +for (const kind of kinds) { + goTo.marker(kind + "0"); + verify.completionListContains("repeated"); + verify.completionListContains("other"); + verify.not.completionListItemsCountIsGreaterThan(2); +} diff --git a/tests/cases/fourslash/completionForStringLiteralNonrelativeImport3.ts b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport3.ts index 7be26cf76d..aec9f8411b 100644 --- a/tests/cases/fourslash/completionForStringLiteralNonrelativeImport3.ts +++ b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport3.ts @@ -2,7 +2,9 @@ // @allowJs: true // @Filename: tests/test0.ts -//// import * as foo from "fake-module//*0*/ +//// import * as foo1 from "fake-module//*import_as0*/ +//// import foo2 = require("fake-module//*import_equals0*/ +//// var foo3 = require("fake-module//*require0*/ // @Filename: package.json //// { "dependencies": { "fake-module": "latest" } } @@ -22,9 +24,12 @@ // @Filename: node_modules/fake-module/repeated.jsx //// /*repeatedjsx*/ -goTo.marker("0"); +const kinds = ["import_as", "import_equals", "require"]; -verify.completionListContains("ts"); -verify.completionListContains("tsx"); -verify.completionListContains("dts"); -verify.not.completionListItemsCountIsGreaterThan(3); +for (const kind of kinds) { + goTo.marker(kind + "0"); + verify.completionListContains("ts"); + verify.completionListContains("tsx"); + verify.completionListContains("dts"); + verify.not.completionListItemsCountIsGreaterThan(3); +} diff --git a/tests/cases/fourslash/completionForStringLiteralNonrelativeImport4.ts b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport4.ts index baa1363856..64e3cbad48 100644 --- a/tests/cases/fourslash/completionForStringLiteralNonrelativeImport4.ts +++ b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport4.ts @@ -1,13 +1,17 @@ /// // @Filename: dir1/dir2/dir3/dir4/test0.ts -//// import * as foo from "f/*0*/ +//// import * as foo1 from "f/*import_as0*/ +//// import * as foo2 from "a/*import_as1*/ +//// import * as foo3 from "fake-module/*import_as2*/ -// @Filename: dir1/dir2/dir3/dir4/test1.ts -//// import * as foo from "a/*1*/ +//// import foo4 = require("f/*import_equals0*/ +//// import foo5 = require("a/*import_equals1*/ +//// import foo6 = require("fake-module/*import_equals2*/ -// @Filename: dir1/dir2/dir3/dir4/test2.ts -//// import * as foo from "fake-module/*2*/ +//// var foo7 = require("f/*require0*/ +//// var foo8 = require("a/*require1*/ +//// var foo9 = require("fake-module/*require2*/ // @Filename: package.json //// { "dependencies": { "fake-module": "latest" } } @@ -24,22 +28,25 @@ // @Filename: dir1/dir2/dir3/node_modules/fake-module3/ts.ts //// /*module3*/ +const kinds = ["import_as", "import_equals", "require"]; -goTo.marker("0"); +for (const kind of kinds) { + goTo.marker(kind + "0"); -verify.completionListContains("fake-module/"); -verify.completionListContains("fake-module2/"); -verify.completionListContains("fake-module3/"); -verify.not.completionListItemsCountIsGreaterThan(3); + verify.completionListContains("fake-module/"); + verify.completionListContains("fake-module2/"); + verify.completionListContains("fake-module3/"); + verify.not.completionListItemsCountIsGreaterThan(3); -goTo.marker("1"); + goTo.marker(kind + "1"); -verify.completionListContains("ambient-module-test"); -verify.not.completionListItemsCountIsGreaterThan(1); + verify.completionListContains("ambient-module-test"); + verify.not.completionListItemsCountIsGreaterThan(1); -goTo.marker("2"); + goTo.marker(kind + "2"); -verify.completionListContains("fake-module/"); -verify.completionListContains("fake-module2/"); -verify.completionListContains("fake-module3/"); -verify.not.completionListItemsCountIsGreaterThan(3); + verify.completionListContains("fake-module/"); + verify.completionListContains("fake-module2/"); + verify.completionListContains("fake-module3/"); + verify.not.completionListItemsCountIsGreaterThan(3); +} \ No newline at end of file diff --git a/tests/cases/fourslash/completionForStringLiteralNonrelativeImport5.ts b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport5.ts index a5bd602bbb..4a8b9e2f61 100644 --- a/tests/cases/fourslash/completionForStringLiteralNonrelativeImport5.ts +++ b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport5.ts @@ -1,10 +1,14 @@ /// // @Filename: test0.ts -//// import * as foo from "/*0*/ +//// import * as foo1 from "/*import_as0*/ +//// import * as foo2 from "a/*import_as1*/ -// @Filename: test1.ts -//// import * as foo from "a/*1*/ +//// import foo3 = require("/*import_equals0*/ +//// import foo4 = require("a/*import_equals1*/ + +//// var foo5 = require("/*require0*/ +//// var foo6 = require("a/*require1*/ // @Filename: ambientModules.d.ts //// declare module "ambientModule" {} @@ -13,16 +17,19 @@ // @Filename: ambientModules2.d.ts //// declare module "otherOtherAmbientModule" {} +const kinds = ["import_as", "import_equals", "require"]; -goTo.marker("0"); +for (const kind of kinds) { + goTo.marker(kind + "0"); -verify.completionListContains("ambientModule"); -verify.completionListContains("otherAmbientModule"); -verify.completionListContains("otherOtherAmbientModule"); -verify.not.completionListItemsCountIsGreaterThan(3); + verify.completionListContains("ambientModule"); + verify.completionListContains("otherAmbientModule"); + verify.completionListContains("otherOtherAmbientModule"); + verify.not.completionListItemsCountIsGreaterThan(3); -goTo.marker("1"); + goTo.marker(kind + "1"); -verify.completionListContains("ambientModule"); -verify.not.completionListItemsCountIsGreaterThan(1); + verify.completionListContains("ambientModule"); + verify.not.completionListItemsCountIsGreaterThan(1); +} diff --git a/tests/cases/fourslash/completionForStringLiteralNonrelativeImport6.ts b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport6.ts index ed588f3580..bfd0539e14 100644 --- a/tests/cases/fourslash/completionForStringLiteralNonrelativeImport6.ts +++ b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport6.ts @@ -1,7 +1,9 @@ /// // @Filename: tests/test0.ts -//// import * as foo from "module-/*0*/ +//// import * as foo1 from "module-/*import_as0*/ +//// import foo2 = require("module-/*import_equals0*/ +//// var foo3 = require("module-/*require0*/ // @Filename: package.json //// { "dependencies": { @@ -45,13 +47,16 @@ // @Filename: node_modules/module-typings/types.d.ts //// /*module-typings*/ +const kinds = ["import_as", "import_equals", "require"]; -goTo.marker("0"); +for (const kind of kinds) { + goTo.marker(kind + "0"); -verify.completionListContains("module-no-main/"); -verify.completionListContains("module-no-main-index-d-ts/"); -verify.completionListContains("module-index-ts"); -verify.completionListContains("module-index-d-ts-explicit-main"); -verify.completionListContains("module-index-d-ts-default-main"); -verify.completionListContains("module-typings"); -verify.not.completionListItemsCountIsGreaterThan(6); + verify.completionListContains("module-no-main/"); + verify.completionListContains("module-no-main-index-d-ts/"); + verify.completionListContains("module-index-ts"); + verify.completionListContains("module-index-d-ts-explicit-main"); + verify.completionListContains("module-index-d-ts-default-main"); + verify.completionListContains("module-typings"); + verify.not.completionListItemsCountIsGreaterThan(6); +} diff --git a/tests/cases/fourslash/completionForStringLiteralRelativeImport1.ts b/tests/cases/fourslash/completionForStringLiteralRelativeImport1.ts index 26a6645b3f..bb9d6a59c7 100644 --- a/tests/cases/fourslash/completionForStringLiteralRelativeImport1.ts +++ b/tests/cases/fourslash/completionForStringLiteralRelativeImport1.ts @@ -1,22 +1,30 @@ /// // @Filename: test0.ts -//// import * as foo from "./*0*/ +//// import * as foo1 from "./*import_as0*/ +//// import * as foo2 from ".//*import_as1*/ +//// import * as foo3 from "./f/*import_as2*/ +//// import * as foo4 from "./folder//*import_as3*/ +//// import * as foo5 from "./folder/h/*import_as4*/ -// @Filename: test1.ts -//// import * as foo from ".//*1*/ +//// import foo6 = require("./*import_equals0*/ +//// import foo7 = require(".//*import_equals1*/ +//// import foo8 = require("./f/*import_equals2*/ +//// import foo9 = require("./folder//*import_equals3*/ +//// import foo10 = require("./folder/h/*import_equals4*/ -// @Filename: test2.ts -//// import * as foo from "./f/*2*/ - -// @Filename: test3.ts -//// import * as foo from "./folder//*3*/ - -// @Filename: test4.ts -//// import * as foo from "./folder/h/*4*/ +//// var foo11 = require("./*require0*/ +//// var foo12 = require(".//*require1*/ +//// var foo13 = require("./f/*require2*/ +//// var foo14 = require("./folder//*require3*/ +//// var foo15 = require("./folder/h/*require4*/ // @Filename: parentTest/sub/test5.ts -//// import * as foo from "../g/*5*/ +//// import * as foo16 from "../g/*import_as5*/ + +//// import foo17 = require("../g/*import_equals5*/ + +//// var foo18 = require("../g/*require5*/ // @Filename: f1.ts //// /*f1*/ @@ -40,38 +48,37 @@ //// /*parentf1*/ // @Filename: parentTest/g1.ts //// /*parentg1*/ +const kinds = ["import_as", "import_equals", "require"]; -goTo.marker("0"); -verify.completionListIsEmpty(); +for (const kind of kinds) { + goTo.marker(kind + "0"); + verify.completionListIsEmpty(); -goTo.marker("1"); -verify.completionListContains("f1"); -verify.completionListContains("f2"); -verify.completionListContains("e1"); -verify.completionListContains("test0"); -verify.completionListContains("test1"); -verify.completionListContains("test2"); -verify.completionListContains("test3"); -verify.completionListContains("test4"); -verify.completionListContains("folder/"); -verify.completionListContains("parentTest/"); -verify.not.completionListItemsCountIsGreaterThan(10); + goTo.marker(kind + "1"); + verify.completionListContains("f1"); + verify.completionListContains("f2"); + verify.completionListContains("e1"); + verify.completionListContains("test0"); + verify.completionListContains("folder/"); + verify.completionListContains("parentTest/"); + verify.not.completionListItemsCountIsGreaterThan(6); -goTo.marker("2"); -verify.completionListContains("f1"); -verify.completionListContains("f2"); -verify.completionListContains("folder/"); -verify.not.completionListItemsCountIsGreaterThan(3); + goTo.marker(kind + "2"); + verify.completionListContains("f1"); + verify.completionListContains("f2"); + verify.completionListContains("folder/"); + verify.not.completionListItemsCountIsGreaterThan(3); -goTo.marker("3"); -verify.completionListContains("f1"); -verify.completionListContains("h1"); -verify.not.completionListItemsCountIsGreaterThan(2); + goTo.marker(kind + "3"); + verify.completionListContains("f1"); + verify.completionListContains("h1"); + verify.not.completionListItemsCountIsGreaterThan(2); -goTo.marker("4"); -verify.completionListContains("h1"); -verify.not.completionListItemsCountIsGreaterThan(1); + goTo.marker(kind + "4"); + verify.completionListContains("h1"); + verify.not.completionListItemsCountIsGreaterThan(1); -goTo.marker("5"); -verify.completionListContains("g1"); -verify.not.completionListItemsCountIsGreaterThan(1); \ No newline at end of file + goTo.marker(kind + "5"); + verify.completionListContains("g1"); + verify.not.completionListItemsCountIsGreaterThan(1); +} \ No newline at end of file diff --git a/tests/cases/fourslash/completionForStringLiteralRelativeImport2.ts b/tests/cases/fourslash/completionForStringLiteralRelativeImport2.ts index 1b4abe7911..4e87914846 100644 --- a/tests/cases/fourslash/completionForStringLiteralRelativeImport2.ts +++ b/tests/cases/fourslash/completionForStringLiteralRelativeImport2.ts @@ -2,10 +2,14 @@ // @allowJs: true // @Filename: test0.ts -//// import * as foo from ".//*0*/ +//// import * as foo1 from ".//*import_as0*/ +//// import * as foo2 from "./f/*import_as1*/ -// @Filename: test1.ts -//// import * as foo from "./f/*1*/ +//// import foo3 = require(".//*import_equals0*/ +//// import foo4 = require("./f/*import_equals1*/ + +//// var foo5 = require(".//*require0*/ +//// var foo6 = require("./f/*require1*/ // @Filename: f1.ts //// /f1*/ @@ -23,21 +27,23 @@ //// /*e1*/ // @Filename: e2.js //// /*e2*/ +const kinds = ["import_as", "import_equals", "require"]; -goTo.marker("0"); -verify.completionListContains("f1"); -verify.completionListContains("f2"); -verify.completionListContains("f3"); -verify.completionListContains("f4"); -verify.completionListContains("e1"); -verify.completionListContains("e2"); -verify.completionListContains("test0"); -verify.completionListContains("test1"); -verify.not.completionListItemsCountIsGreaterThan(8); +for (const kind of kinds) { + goTo.marker(kind + "0"); + verify.completionListContains("f1"); + verify.completionListContains("f2"); + verify.completionListContains("f3"); + verify.completionListContains("f4"); + verify.completionListContains("e1"); + verify.completionListContains("e2"); + verify.completionListContains("test0"); + verify.not.completionListItemsCountIsGreaterThan(7); -goTo.marker("1"); -verify.completionListContains("f1"); -verify.completionListContains("f2"); -verify.completionListContains("f3"); -verify.completionListContains("f4"); -verify.not.completionListItemsCountIsGreaterThan(4); \ No newline at end of file + goTo.marker(kind + "1"); + verify.completionListContains("f1"); + verify.completionListContains("f2"); + verify.completionListContains("f3"); + verify.completionListContains("f4"); + verify.not.completionListItemsCountIsGreaterThan(4); +} \ No newline at end of file diff --git a/tests/cases/fourslash/completionForStringLiteralRelativeImport3.ts b/tests/cases/fourslash/completionForStringLiteralRelativeImport3.ts index 1b10ffff5b..426eb3c231 100644 --- a/tests/cases/fourslash/completionForStringLiteralRelativeImport3.ts +++ b/tests/cases/fourslash/completionForStringLiteralRelativeImport3.ts @@ -1,13 +1,17 @@ /// // @Filename: tests/test0.ts -//// import * as foo from "c:/tests/cases/f/*0*/ +//// import * as foo1 from "c:/tests/cases/f/*import_as0*/ +//// import * as foo2 from "c:/tests/cases/fourslash/*import_as1*/ +//// import * as foo3 from "c:/tests/cases/fourslash//*import_as2*/ -// @Filename: tests/test1.ts -//// import * as foo from "c:/tests/cases/fourslash/*1*/ +//// import foo4 = require("c:/tests/cases/f/*import_equals0*/ +//// import foo5 = require("c:/tests/cases/fourslash/*import_equals1*/ +//// import foo6 = require("c:/tests/cases/fourslash//*import_equals2*/ -// @Filename: tests/test2.ts -//// import * as foo from "c:/tests/cases/fourslash//*2*/ +//// var foo7 = require("c:/tests/cases/f/*require0*/ +//// var foo8 = require("c:/tests/cases/fourslash/*require1*/ +//// var foo9 = require("c:/tests/cases/fourslash//*require2*/ // @Filename: f1.ts //// /*f1*/ @@ -24,18 +28,22 @@ // @Filename: e2.js //// /*e2*/ -goTo.marker("0"); -verify.completionListContains("fourslash/"); -verify.not.completionListItemsCountIsGreaterThan(1); +const kinds = ["import_as", "import_equals", "require"]; -goTo.marker("1"); -verify.completionListContains("fourslash/"); -verify.not.completionListItemsCountIsGreaterThan(1); +for (const kind of kinds) { + goTo.marker(kind + "0"); + verify.completionListContains("fourslash/"); + verify.not.completionListItemsCountIsGreaterThan(1); -goTo.marker("2"); -verify.completionListContains("f1"); -verify.completionListContains("f2"); -verify.completionListContains("e1"); -verify.completionListContains("folder/"); -verify.completionListContains("tests/"); -verify.not.completionListItemsCountIsGreaterThan(5); \ No newline at end of file + goTo.marker(kind + "1"); + verify.completionListContains("fourslash/"); + verify.not.completionListItemsCountIsGreaterThan(1); + + goTo.marker(kind + "2"); + verify.completionListContains("f1"); + verify.completionListContains("f2"); + verify.completionListContains("e1"); + verify.completionListContains("folder/"); + verify.completionListContains("tests/"); + verify.not.completionListItemsCountIsGreaterThan(5); +} \ No newline at end of file From 801b49360202fd8c2c4f46b2aeedb49f5034b6a7 Mon Sep 17 00:00:00 2001 From: Richard Knoll Date: Thu, 30 Jun 2016 10:30:23 -0700 Subject: [PATCH 04/39] PR feedback --- src/harness/harnessLanguageService.ts | 2 +- src/harness/virtualFileSystem.ts | 9 +++++++-- src/services/services.ts | 19 +++++++++++++------ 3 files changed, 21 insertions(+), 9 deletions(-) diff --git a/src/harness/harnessLanguageService.ts b/src/harness/harnessLanguageService.ts index 682496442e..08c74e695b 100644 --- a/src/harness/harnessLanguageService.ts +++ b/src/harness/harnessLanguageService.ts @@ -135,7 +135,7 @@ namespace Harness.LanguageService { public getFilenames(): string[] { const fileNames: string[] = []; - this.virtualFileSystem.getAllFileEntries().forEach((virtualEntry) => { + ts.forEach(this.virtualFileSystem.getAllFileEntries(), (virtualEntry) => { const scriptInfo = virtualEntry.content; if (scriptInfo.isRootFile) { // only include root files here diff --git a/src/harness/virtualFileSystem.ts b/src/harness/virtualFileSystem.ts index 5a89efea7f..2a46bf4c3f 100644 --- a/src/harness/virtualFileSystem.ts +++ b/src/harness/virtualFileSystem.ts @@ -158,6 +158,11 @@ namespace Utils { return directory; } + /** + * Reads the directory at the given path and retrieves a list of file names and a list + * of directory names within it. Suitable for use with ts.matchFiles() + * @param path The path to the directory to be read + */ getAccessibleFileSystemEntries(path: string) { const entry = this.traversePath(path); if (entry && entry.isDirectory()) { @@ -176,8 +181,8 @@ namespace Utils { return fileEntries; function getFilesRecursive(dir: VirtualDirectory, result: VirtualFile[]) { - dir.getFiles().forEach((e) => result.push(e)); - dir.getDirectories().forEach((subDir) => getFilesRecursive(subDir, result)); + ts.forEach(dir.getFiles(), (e) => result.push(e)); + ts.forEach(dir.getDirectories(), (subDir) => getFilesRecursive(subDir, result)); } } diff --git a/src/services/services.ts b/src/services/services.ts index 04c435f133..b7e79473fc 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1947,8 +1947,15 @@ namespace ts { sourceMapText?: string; } - + // Matches the beginning of a triple slash directive const tripleSlashDirectivePrefixRegex = /^\/\/\/\s* { + ts.forEach(files, (f) => { const fName = includeExtensions ? getBaseFileName(f) : removeFileExtension(getBaseFileName(f)); if (startsWith(fName, toComplete)) { @@ -4402,7 +4409,7 @@ namespace ts { // If possible, get folder completion as well if (host.getDirectories) { const directories = host.getDirectories(baseDir); - directories.forEach((d) => { + ts.forEach(directories, (d) => { const dName = getBaseFileName(removeTrailingDirectorySeparator(d)); if (startsWith(dName, toComplete)) { @@ -4471,7 +4478,7 @@ namespace ts { // Check for node_modules. We only offer completions for modules that are listed in the // package.json for a project for efficiency and to ensure that the completion list is // not polluted with sub-dependencies - findPackageJsons(scriptPath).forEach((packageJson) => { + ts.forEach(findPackageJsons(scriptPath), (packageJson) => { const package = tryReadingPackageJson(packageJson); if (!package) { return; @@ -4487,13 +4494,13 @@ namespace ts { addPotentialPackageNames(package.devDependencies, moduleNameFragment, foundModuleNames); } - foundModuleNames.forEach((moduleName) => { + ts.forEach(foundModuleNames, (moduleName) => { if (isNestedModule && moduleName === moduleNameFragment) { const moduleDir = combinePaths(nodeModulesDir, moduleName); if (directoryProbablyExists(moduleDir, host)) { const nestedFiles = host.readDirectory(moduleDir, supportedTypeScriptExtensions, /*exclude*/undefined, /*include*/["./*"]); - nestedFiles.forEach((f) => { + ts.forEach(nestedFiles, (f) => { const nestedModule = removeFileExtension(getBaseFileName(f)); nonRelativeModules.push(nestedModule); }); From 5c24b3528d75220843334aac1ae56e48743d7c39 Mon Sep 17 00:00:00 2001 From: Richard Knoll Date: Tue, 5 Jul 2016 17:01:26 -0700 Subject: [PATCH 05/39] Refactoring node_modules enumeration code --- src/services/services.ts | 112 ++---------------- src/services/utilities.ts | 103 ++++++++++++++++ ...etionForStringLiteralNonrelativeImport5.ts | 6 +- 3 files changed, 116 insertions(+), 105 deletions(-) diff --git a/src/services/services.ts b/src/services/services.ts index 167f8c9edf..704a12fda2 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -4477,117 +4477,23 @@ namespace ts { return moduleName; }); - // Check for node_modules. We only offer completions for modules that are listed in the - // package.json for a project for efficiency and to ensure that the completion list is - // not polluted with sub-dependencies - ts.forEach(findPackageJsons(scriptPath), (packageJson) => { - const package = tryReadingPackageJson(packageJson); - if (!package) { - return; + forEach(enumerateNodeModulesVisibleToScript(host, scriptPath, moduleNameFragment), visibleModule => { + if (!isNestedModule) { + nonRelativeModules.push(visibleModule.canBeImported ? visibleModule.moduleName : ensureTrailingDirectorySeparator(visibleModule.moduleName)); } + else { + const nestedFiles = host.readDirectory(visibleModule.moduleDir, supportedTypeScriptExtensions, /*exclude*/undefined, /*include*/["./*"]); - const nodeModulesDir = combinePaths(getDirectoryPath(packageJson), "node_modules"); - const foundModuleNames: string[] = []; - - if (package.dependencies) { - addPotentialPackageNames(package.dependencies, moduleNameFragment, foundModuleNames); + forEach(nestedFiles, (f) => { + const nestedModule = removeFileExtension(getBaseFileName(f)); + nonRelativeModules.push(nestedModule); + }); } - if (package.devDependencies) { - addPotentialPackageNames(package.devDependencies, moduleNameFragment, foundModuleNames); - } - - ts.forEach(foundModuleNames, (moduleName) => { - if (isNestedModule && moduleName === moduleNameFragment) { - const moduleDir = combinePaths(nodeModulesDir, moduleName); - if (directoryProbablyExists(moduleDir, host)) { - const nestedFiles = host.readDirectory(moduleDir, supportedTypeScriptExtensions, /*exclude*/undefined, /*include*/["./*"]); - - ts.forEach(nestedFiles, (f) => { - const nestedModule = removeFileExtension(getBaseFileName(f)); - nonRelativeModules.push(nestedModule); - }); - } - } - else if (startsWith(moduleName, fragment)) { - if (moduleCanBeImported(combinePaths(nodeModulesDir, moduleName))) { - nonRelativeModules.push(moduleName); - } - else { - nonRelativeModules.push(ensureTrailingDirectorySeparator(moduleName)); - } - } - }); }); return deduplicate(nonRelativeModules); } - function findPackageJsons(currentDir: string): string[] { - const paths: string[] = []; - let currentConfigPath: string; - while (true) { - currentConfigPath = findConfigFile(currentDir, (f) => host.fileExists(f), "package.json"); - if (currentConfigPath) { - paths.push(currentConfigPath); - - currentDir = getDirectoryPath(currentConfigPath); - const parent = getDirectoryPath(currentDir); - if (currentDir === parent) { - break; - } - currentDir = parent; - } - else { - break; - } - } - - return paths; - } - - function tryReadingPackageJson(filePath: string) { - try { - const fileText = host.readFile(filePath); - return JSON.parse(fileText); - } - catch (e) { - return undefined; - } - } - - function addPotentialPackageNames(dependencies: any, prefix: string, result: string[]) { - for (const dep in dependencies) { - if (dependencies.hasOwnProperty(dep) && startsWith(dep, prefix)) { - result.push(dep); - } - } - } - - /* - * A module can be imported by name alone if one of the following is true: - * It defines the "typings" property in its package.json - * The module has a "main" export and an index.d.ts file - * The module has an index.ts - */ - function moduleCanBeImported(modulePath: string): boolean { - const packagePath = combinePaths(modulePath, "package.json"); - - let hasMainExport = false; - if (host.fileExists(packagePath)) { - const package = tryReadingPackageJson(packagePath); - if (package) { - if (package.typings) { - return true; - } - hasMainExport = !!package.main; - } - } - - hasMainExport = hasMainExport || host.fileExists(combinePaths(modulePath, "index.js")); - - return (hasMainExport && host.fileExists(combinePaths(modulePath, "index.d.ts"))) || host.fileExists(combinePaths(modulePath, "index.ts")); - } - function getTripleSlashReferenceCompletion(sourceFile: SourceFile, position: number) { const node = getTokenAtPosition(sourceFile, position); if (!node) { diff --git a/src/services/utilities.ts b/src/services/utilities.ts index 0c4c0fd1de..44423860d4 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -6,6 +6,12 @@ namespace ts { list: Node; } + export interface VisibleModuleInfo { + moduleName: string; + moduleDir: string; + canBeImported: boolean; + } + export function getLineStartPositionForPosition(position: number, sourceFile: SourceFile): number { const lineStarts = sourceFile.getLineStarts(); const line = sourceFile.getLineAndCharacterOfPosition(position).line; @@ -927,4 +933,101 @@ namespace ts { } return ensureScriptKind(fileName, scriptKind); } + + export function enumerateNodeModulesVisibleToScript(host: LanguageServiceHost, scriptPath: string, modulePrefix?: string) { + const result: VisibleModuleInfo[] = []; + findPackageJsons(scriptPath).forEach((packageJson) => { + const package = tryReadingPackageJson(packageJson); + if (!package) { + return; + } + + const nodeModulesDir = combinePaths(getDirectoryPath(packageJson), "node_modules"); + const foundModuleNames: string[] = []; + + if (package.dependencies) { + addPotentialPackageNames(package.dependencies, modulePrefix, foundModuleNames); + } + if (package.devDependencies) { + addPotentialPackageNames(package.devDependencies, modulePrefix, foundModuleNames); + } + + forEach(foundModuleNames, (moduleName) => { + const moduleDir = combinePaths(nodeModulesDir, moduleName); + result.push({ + moduleName, + moduleDir, + canBeImported: moduleCanBeImported(moduleDir) + }); + }); + }); + + return result; + + function findPackageJsons(currentDir: string): string[] { + const paths: string[] = []; + let currentConfigPath: string; + while (true) { + currentConfigPath = findConfigFile(currentDir, (f) => host.fileExists(f), "package.json"); + if (currentConfigPath) { + paths.push(currentConfigPath); + + currentDir = getDirectoryPath(currentConfigPath); + const parent = getDirectoryPath(currentDir); + if (currentDir === parent) { + break; + } + currentDir = parent; + } + else { + break; + } + } + + return paths; + } + + function tryReadingPackageJson(filePath: string) { + try { + const fileText = host.readFile(filePath); + return JSON.parse(fileText); + } + catch (e) { + return undefined; + } + } + + function addPotentialPackageNames(dependencies: any, prefix: string, result: string[]) { + for (const dep in dependencies) { + if (dependencies.hasOwnProperty(dep) && (!prefix || startsWith(dep, prefix))) { + result.push(dep); + } + } + } + + /* + * A module can be imported by name alone if one of the following is true: + * It defines the "typings" property in its package.json + * The module has a "main" export and an index.d.ts file + * The module has an index.ts + */ + function moduleCanBeImported(modulePath: string): boolean { + const packagePath = combinePaths(modulePath, "package.json"); + + let hasMainExport = false; + if (host.fileExists(packagePath)) { + const package = tryReadingPackageJson(packagePath); + if (package) { + if (package.typings) { + return true; + } + hasMainExport = !!package.main; + } + } + + hasMainExport = hasMainExport || host.fileExists(combinePaths(modulePath, "index.js")); + + return (hasMainExport && host.fileExists(combinePaths(modulePath, "index.d.ts"))) || host.fileExists(combinePaths(modulePath, "index.ts")); + } + } } \ No newline at end of file diff --git a/tests/cases/fourslash/completionForStringLiteralNonrelativeImport5.ts b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport5.ts index 4a8b9e2f61..2e172e3578 100644 --- a/tests/cases/fourslash/completionForStringLiteralNonrelativeImport5.ts +++ b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport5.ts @@ -1,6 +1,8 @@ /// // @Filename: test0.ts +//// /// +//// /// //// import * as foo1 from "/*import_as0*/ //// import * as foo2 from "a/*import_as1*/ @@ -12,10 +14,10 @@ // @Filename: ambientModules.d.ts //// declare module "ambientModule" {} -//// declare module "otherAmbientModule" {} +//// declare module "otherAmbientModule" {} /*dummy0*/ // @Filename: ambientModules2.d.ts -//// declare module "otherOtherAmbientModule" {} +//// declare module "otherOtherAmbientModule" {} /*dummy1*/ const kinds = ["import_as", "import_equals", "require"]; From ffc165ee3657c4cabb5ad06e67eb9410c340ad0b Mon Sep 17 00:00:00 2001 From: Richard Knoll Date: Tue, 5 Jul 2016 17:38:52 -0700 Subject: [PATCH 06/39] Fixing behavior of resolvePath --- src/harness/harnessLanguageService.ts | 16 +--------------- src/services/services.ts | 6 +++--- 2 files changed, 4 insertions(+), 18 deletions(-) diff --git a/src/harness/harnessLanguageService.ts b/src/harness/harnessLanguageService.ts index e6d529bcde..3cb83faf61 100644 --- a/src/harness/harnessLanguageService.ts +++ b/src/harness/harnessLanguageService.ts @@ -219,21 +219,7 @@ namespace Harness.LanguageService { return snapshot.getText(0, snapshot.getLength()); } resolvePath(path: string): string { - // Reduce away "." and ".." - const parts = path.split("/"); - const res: string[] = []; - for (let i = 0; i < parts.length; i++) { - if (parts[i] === ".") { - continue; - } - else if (parts[i] === ".." && res.length > 0) { - res.splice(res.length - 1, 1); - } - else { - res.push(parts[i]); - } - } - return res.join("/"); + return ts.normalizePath(ts.isRootedDiskPath(path) ? path : ts.combinePaths(this.getCurrentDirectory(), path)); } diff --git a/src/services/services.ts b/src/services/services.ts index 704a12fda2..d440f194b7 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -4389,13 +4389,13 @@ namespace ts { const toComplete = getBaseFileName(fragment); const absolutePath = normalizeSlashes(host.resolvePath(isRootedDiskPath(fragment) ? fragment : combinePaths(scriptPath, fragment))); - const baseDir = getDirectoryPath(absolutePath); + const baseDir = toComplete ? getDirectoryPath(absolutePath) : absolutePath; if (directoryProbablyExists(baseDir, host)) { // Enumerate the available files const files = host.readDirectory(baseDir, extensions, /*exclude*/undefined, /*include*/["./*"]); - ts.forEach(files, (f) => { + forEach(files, (f) => { const fName = includeExtensions ? getBaseFileName(f) : removeFileExtension(getBaseFileName(f)); if (startsWith(fName, toComplete)) { @@ -4411,7 +4411,7 @@ namespace ts { // If possible, get folder completion as well if (host.getDirectories) { const directories = host.getDirectories(baseDir); - ts.forEach(directories, (d) => { + forEach(directories, (d) => { const dName = getBaseFileName(removeTrailingDirectorySeparator(d)); if (startsWith(dName, toComplete)) { From 5c87c5a4bcd59e4012b22bbde8ab795a8f3c4522 Mon Sep 17 00:00:00 2001 From: Richard Knoll Date: Wed, 6 Jul 2016 13:05:12 -0700 Subject: [PATCH 07/39] Removing forEach reference --- src/harness/virtualFileSystem.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/harness/virtualFileSystem.ts b/src/harness/virtualFileSystem.ts index 2a46bf4c3f..c2502bbc80 100644 --- a/src/harness/virtualFileSystem.ts +++ b/src/harness/virtualFileSystem.ts @@ -181,8 +181,14 @@ namespace Utils { return fileEntries; function getFilesRecursive(dir: VirtualDirectory, result: VirtualFile[]) { - ts.forEach(dir.getFiles(), (e) => result.push(e)); - ts.forEach(dir.getDirectories(), (subDir) => getFilesRecursive(subDir, result)); + const files = dir.getFiles(); + const dirs = dir.getDirectories(); + for (const file of files) { + result.push(file); + } + for (const subDir of dirs) { + getFilesRecursive(subDir, result); + } } } From 84a10e439ea895afcbdaa5494305f3f2533db7fd Mon Sep 17 00:00:00 2001 From: Richard Knoll Date: Mon, 25 Jul 2016 12:57:17 -0700 Subject: [PATCH 08/39] Some PR feedback --- src/services/services.ts | 61 +++++++++++++++++++--------------------- 1 file changed, 29 insertions(+), 32 deletions(-) diff --git a/src/services/services.ts b/src/services/services.ts index d440f194b7..7024dc7211 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1665,6 +1665,10 @@ namespace ts { export const constElement = "const"; export const letElement = "let"; + + export const directory = "directory"; + + export const externalModuleName = "external module name"; } export namespace ScriptElementKindModifier { @@ -4378,7 +4382,7 @@ namespace ts { return { isMemberCompletion: false, - isNewIdentifierLocation: false, + isNewIdentifierLocation: true, entries: result }; } @@ -4389,44 +4393,46 @@ namespace ts { const toComplete = getBaseFileName(fragment); const absolutePath = normalizeSlashes(host.resolvePath(isRootedDiskPath(fragment) ? fragment : combinePaths(scriptPath, fragment))); - const baseDir = toComplete ? getDirectoryPath(absolutePath) : absolutePath; + const baseDirectory = toComplete ? getDirectoryPath(absolutePath) : absolutePath; - if (directoryProbablyExists(baseDir, host)) { + if (directoryProbablyExists(baseDirectory, host)) { // Enumerate the available files - const files = host.readDirectory(baseDir, extensions, /*exclude*/undefined, /*include*/["./*"]); - forEach(files, (f) => { - const fName = includeExtensions ? getBaseFileName(f) : removeFileExtension(getBaseFileName(f)); + const files = host.readDirectory(baseDirectory, extensions, /*exclude*/undefined, /*include*/["./*"]); + forEach(files, f => { + const fileName = includeExtensions ? getBaseFileName(f) : removeFileExtension(getBaseFileName(f)); - if (startsWith(fName, toComplete)) { + const duplicate = includeExtensions ? false : forEach(result, entry => entry.name === fileName); + + if (startsWith(fileName, toComplete) && !duplicate) { result.push({ - name: fName, - kind: ScriptElementKind.unknown, + name: fileName, + kind: ScriptElementKind.directory, kindModifiers: ScriptElementKindModifier.none, - sortText: fName + sortText: fileName }); } }); // If possible, get folder completion as well if (host.getDirectories) { - const directories = host.getDirectories(baseDir); - forEach(directories, (d) => { - const dName = getBaseFileName(removeTrailingDirectorySeparator(d)); + const directories = host.getDirectories(baseDirectory); + forEach(directories, d => { + const directoryName = getBaseFileName(removeTrailingDirectorySeparator(d)); - if (startsWith(dName, toComplete)) { + if (startsWith(directoryName, toComplete)) { result.push({ - name: ensureTrailingDirectorySeparator(dName), - kind: ScriptElementKind.unknown, + name: ensureTrailingDirectorySeparator(directoryName), + kind: ScriptElementKind.directory, kindModifiers: ScriptElementKindModifier.none, - sortText: dName + sortText: directoryName }); } }); } } - return includeExtensions ? result : deduplicate(result, (a, b) => a.name === b.name); + return result; } /** @@ -4439,36 +4445,27 @@ namespace ts { return ts.map(enumeratePotentialNonRelativeModules(fragment, scriptPath), (moduleName) => { return { name: moduleName, - kind: ScriptElementKind.unknown, + kind: ScriptElementKind.externalModuleName, kindModifiers: ScriptElementKindModifier.none, sortText: moduleName }; }); } - function enumeratePotentialNonRelativeModules(fragment: string, scriptPath: string) { - const ambientSymbolNameRegex = /^"(.+?)"$/; - + function enumeratePotentialNonRelativeModules(fragment: string, scriptPath: string): string[] { // If this is a nested module, get the module name const firstSeparator = fragment.indexOf(directorySeparator); const moduleNameFragment = firstSeparator !== -1 ? fragment.substr(0, firstSeparator) : fragment; const isNestedModule = fragment !== moduleNameFragment; // Get modules that the type checker picked up - const ambientModules = ts.map(program.getTypeChecker().getAmbientModules(), (sym) => { - const match = ambientSymbolNameRegex.exec(sym.name); - if (match) { - return match[1]; - } - // This should never happen - return sym.name; - }); - let nonRelativeModules = ts.filter(ambientModules, (moduleName) => startsWith(moduleName, fragment)); + const ambientModules = ts.map(program.getTypeChecker().getAmbientModules(), sym => stripQuotes(sym.name)); + let nonRelativeModules = ts.filter(ambientModules, moduleName => startsWith(moduleName, fragment)); // Nested modules of the form "module-name/sub" need to be adjusted to only return the string // after the last '/' that appears in the fragment because editors insert the completion // only after that character - nonRelativeModules = ts.map(nonRelativeModules, (moduleName) => { + nonRelativeModules = ts.map(nonRelativeModules, moduleName => { if (moduleName.indexOf(directorySeparator) !== -1) { if (isNestedModule) { return moduleName.substr(fragment.lastIndexOf(directorySeparator) + 1); From ed2da32776449eef338fe535b5f0c662b82a7944 Mon Sep 17 00:00:00 2001 From: Richard Knoll Date: Tue, 26 Jul 2016 13:43:29 -0700 Subject: [PATCH 09/39] Handling more compiler options and minor refactor --- src/harness/fourslash.ts | 34 ++- src/services/services.ts | 208 +++++++++++++++++- src/services/utilities.ts | 103 --------- ...etionForStringLiteralNonrelativeImport7.ts | 27 +++ ...etionForStringLiteralNonrelativeImport8.ts | 53 +++++ ...etionForStringLiteralNonrelativeImport9.ts | 34 +++ 6 files changed, 341 insertions(+), 118 deletions(-) create mode 100644 tests/cases/fourslash/completionForStringLiteralNonrelativeImport7.ts create mode 100644 tests/cases/fourslash/completionForStringLiteralNonrelativeImport8.ts create mode 100644 tests/cases/fourslash/completionForStringLiteralNonrelativeImport9.ts diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index a42abbbc60..87b537417e 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -245,14 +245,7 @@ namespace FourSlash { constructor(private basePath: string, private testType: FourSlashTestType, public testData: FourSlashData) { // Create a new Services Adapter this.cancellationToken = new TestCancellationToken(); - const compilationOptions = convertGlobalOptionsToCompilerOptions(this.testData.globalOptions); - if (compilationOptions.typeRoots) { - compilationOptions.typeRoots = compilationOptions.typeRoots.map(p => ts.getNormalizedAbsolutePath(p, this.basePath)); - } - - const languageServiceAdapter = this.getLanguageServiceAdapter(testType, this.cancellationToken, compilationOptions); - this.languageServiceAdapterHost = languageServiceAdapter.getHost(); - this.languageService = languageServiceAdapter.getLanguageService(); + let compilationOptions = convertGlobalOptionsToCompilerOptions(this.testData.globalOptions); // Initialize the language service with all the scripts let startResolveFileRef: FourSlashFile; @@ -260,6 +253,22 @@ namespace FourSlash { ts.forEach(testData.files, file => { // Create map between fileName and its content for easily looking up when resolveReference flag is specified this.inputFiles[file.fileName] = file.content; + + if (ts.getBaseFileName(file.fileName).toLowerCase() === "tsconfig.json") { + const configJson = ts.parseConfigFileTextToJson(file.fileName, file.content); + assert.isTrue(configJson.config !== undefined); + + // Extend our existing compiler options so that we can also support tsconfig only options + if (configJson.config.compilerOptions) { + let baseDir = ts.normalizePath(ts.getDirectoryPath(file.fileName)); + let tsConfig = ts.convertCompilerOptionsFromJson(configJson.config.compilerOptions, baseDir, file.fileName); + + if (!tsConfig.errors || !tsConfig.errors.length) { + compilationOptions = ts.extend(compilationOptions, tsConfig.options); + } + } + } + if (!startResolveFileRef && file.fileOptions[metadataOptionNames.resolveReference] === "true") { startResolveFileRef = file; } @@ -269,6 +278,15 @@ namespace FourSlash { } }); + + if (compilationOptions.typeRoots) { + compilationOptions.typeRoots = compilationOptions.typeRoots.map(p => ts.getNormalizedAbsolutePath(p, this.basePath)); + } + + const languageServiceAdapter = this.getLanguageServiceAdapter(testType, this.cancellationToken, compilationOptions); + this.languageServiceAdapterHost = languageServiceAdapter.getHost(); + this.languageService = languageServiceAdapter.getLanguageService(); + if (startResolveFileRef) { // Add the entry-point file itself into the languageServiceShimHost this.languageServiceAdapterHost.addScript(startResolveFileRef.fileName, startResolveFileRef.content, /*isRootFile*/ true); diff --git a/src/services/services.ts b/src/services/services.ts index 7024dc7211..275c41732c 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1757,6 +1757,12 @@ namespace ts { owners: string[]; } + interface VisibleModuleInfo { + moduleName: string; + moduleDir: string; + canBeImported: boolean; + } + export interface DisplayPartsSymbolWriter extends SymbolWriter { displayParts(): SymbolDisplayPart[]; } @@ -4438,18 +4444,100 @@ namespace ts { /** * Check all of the declared modules and those in node modules. Possible sources of modules: * Modules that are found by the type checker + * Modules found relative to "baseUrl" compliler options (including patterns from "paths" compiler option) * Modules from node_modules (i.e. those listed in package.json) * This includes all files that are found in node_modules/moduleName/ with acceptable file extensions */ function getCompletionEntriesForNonRelativeModules(fragment: string, scriptPath: string): CompletionEntry[] { - return ts.map(enumeratePotentialNonRelativeModules(fragment, scriptPath), (moduleName) => { - return { - name: moduleName, - kind: ScriptElementKind.externalModuleName, - kindModifiers: ScriptElementKindModifier.none, - sortText: moduleName - }; + const options = program.getCompilerOptions(); + const { baseUrl, paths } = options; + + let result: CompletionEntry[]; + + if (baseUrl) { + const fileExtensions = getSupportedExtensions(options); + const absolute = isRootedDiskPath(baseUrl) ? baseUrl : combinePaths(host.getCurrentDirectory(), baseUrl) + result = getCompletionEntriesForDirectoryFragment(fragment, normalizePath(absolute), fileExtensions, /*includeExtensions*/false); + + if (paths) { + for (var path in paths) { + if (paths.hasOwnProperty(path)) { + if (path === "*") { + if (paths[path]) { + forEach(paths[path], pattern => { + forEach(getModulesForPathsPattern(fragment, baseUrl, pattern, fileExtensions), match => { + result.push(createCompletionEntryForModule(match, ScriptElementKind.externalModuleName)); + }); + }); + } + } + else if (startsWith(path, fragment)) { + const entry = paths[path] && paths[path].length === 1 && paths[path][0]; + if (entry) { + result.push(createCompletionEntryForModule(path, ScriptElementKind.externalModuleName)); + } + } + } + } + } + } + else { + result = []; + } + + + + forEach(enumeratePotentialNonRelativeModules(fragment, scriptPath), moduleName => { + result.push(createCompletionEntryForModule(moduleName, ScriptElementKind.externalModuleName)); }); + + return result; + } + + function getModulesForPathsPattern(fragment: string, baseUrl: string, pattern: string, fileExtensions: string[]): string[] { + const parsed = hasZeroOrOneAsteriskCharacter(pattern) ? tryParsePattern(pattern) : undefined; + if (parsed) { + const hasTrailingSlash = parsed.prefix.charAt(parsed.prefix.length - 1) === "/" || parsed.prefix.charAt(parsed.prefix.length - 1) === "\\"; + + // The prefix has two effective parts: the directory path and the base component after the filepath that is not a + // full directory component. For example: directory/path/of/prefix/base* + const normalizedPrefix = hasTrailingSlash ? ensureTrailingDirectorySeparator(normalizePath(parsed.prefix)) : normalizePath(parsed.prefix); + const normalizedPrefixDirectory = getDirectoryPath(normalizedPrefix); + const normalizedPrefixBase = getBaseFileName(normalizedPrefix); + + const fragmentHasPath = fragment.indexOf(directorySeparator) !== -1; + + // Try and expand the prefix to include any path from the fragment so that we can limit the readDirectory call + const expandedPrefixDir = fragmentHasPath ? combinePaths(normalizedPrefixDirectory, normalizedPrefixBase + getDirectoryPath(fragment)) : normalizedPrefixDirectory; + + const normalizedSuffix = normalizePath(parsed.suffix); + const baseDirectory = combinePaths(baseUrl, expandedPrefixDir); + const completePrefix = fragmentHasPath ? baseDirectory : ensureTrailingDirectorySeparator(baseDirectory) + normalizedPrefixBase; + + // If we have a suffix, then we need to read the directory all the way down. We could create a glob + // that encodes the suffix, but we would have to escape the character "?" which readDirectory + // doesn't support. For now, this is safer but slower + const includeGlob = normalizedSuffix ? "**/*" : "./*" + + const matches = host.readDirectory(baseDirectory, fileExtensions, undefined, [includeGlob]); + const result: string[] = []; + + // Trim away prefix and suffix + forEach(matches, match => { + const normalizedMatch = normalizePath(match); + if (!endsWith(normalizedMatch, normalizedSuffix) || !startsWith(normalizedMatch, completePrefix)) { + return; + } + + const start = completePrefix.length; + const length = normalizedMatch.length - start - normalizedSuffix.length; + + result.push(removeFileExtension(normalizedMatch.substr(start, length))); + }); + return result; + } + + return undefined; } function enumeratePotentialNonRelativeModules(fragment: string, scriptPath: string): string[] { @@ -4513,6 +4601,112 @@ namespace ts { } } + function enumerateNodeModulesVisibleToScript(host: LanguageServiceHost, scriptPath: string, modulePrefix?: string) { + const result: VisibleModuleInfo[] = []; + findPackageJsons(scriptPath).forEach((packageJson) => { + const package = tryReadingPackageJson(packageJson); + if (!package) { + return; + } + + const nodeModulesDir = combinePaths(getDirectoryPath(packageJson), "node_modules"); + const foundModuleNames: string[] = []; + + if (package.dependencies) { + addPotentialPackageNames(package.dependencies, modulePrefix, foundModuleNames); + } + if (package.devDependencies) { + addPotentialPackageNames(package.devDependencies, modulePrefix, foundModuleNames); + } + + forEach(foundModuleNames, (moduleName) => { + const moduleDir = combinePaths(nodeModulesDir, moduleName); + result.push({ + moduleName, + moduleDir, + canBeImported: moduleCanBeImported(moduleDir) + }); + }); + }); + + return result; + + function findPackageJsons(currentDir: string): string[] { + const paths: string[] = []; + let currentConfigPath: string; + while (true) { + currentConfigPath = findConfigFile(currentDir, (f) => host.fileExists(f), "package.json"); + if (currentConfigPath) { + paths.push(currentConfigPath); + + currentDir = getDirectoryPath(currentConfigPath); + const parent = getDirectoryPath(currentDir); + if (currentDir === parent) { + break; + } + currentDir = parent; + } + else { + break; + } + } + + return paths; + } + + function tryReadingPackageJson(filePath: string) { + try { + const fileText = host.readFile(filePath); + return JSON.parse(fileText); + } + catch (e) { + return undefined; + } + } + + function addPotentialPackageNames(dependencies: any, prefix: string, result: string[]) { + for (const dep in dependencies) { + if (dependencies.hasOwnProperty(dep) && (!prefix || startsWith(dep, prefix))) { + result.push(dep); + } + } + } + + /* + * A module can be imported by name alone if one of the following is true: + * It defines the "typings" property in its package.json + * The module has a "main" export and an index.d.ts file + * The module has an index.ts + */ + function moduleCanBeImported(modulePath: string): boolean { + const packagePath = combinePaths(modulePath, "package.json"); + + let hasMainExport = false; + if (host.fileExists(packagePath)) { + const package = tryReadingPackageJson(packagePath); + if (package) { + if (package.typings) { + return true; + } + hasMainExport = !!package.main; + } + } + + hasMainExport = hasMainExport || host.fileExists(combinePaths(modulePath, "index.js")); + + return (hasMainExport && host.fileExists(combinePaths(modulePath, "index.d.ts"))) || host.fileExists(combinePaths(modulePath, "index.ts")); + } + } + + function createCompletionEntryForModule(name: string, kind: string): CompletionEntry { + return { + name, + kind, + kindModifiers: ScriptElementKindModifier.none, + sortText: name + } + } + function getCompletionEntryDetails(fileName: string, position: number, entryName: string): CompletionEntryDetails { synchronizeHostData(); diff --git a/src/services/utilities.ts b/src/services/utilities.ts index 44423860d4..0c4c0fd1de 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -6,12 +6,6 @@ namespace ts { list: Node; } - export interface VisibleModuleInfo { - moduleName: string; - moduleDir: string; - canBeImported: boolean; - } - export function getLineStartPositionForPosition(position: number, sourceFile: SourceFile): number { const lineStarts = sourceFile.getLineStarts(); const line = sourceFile.getLineAndCharacterOfPosition(position).line; @@ -933,101 +927,4 @@ namespace ts { } return ensureScriptKind(fileName, scriptKind); } - - export function enumerateNodeModulesVisibleToScript(host: LanguageServiceHost, scriptPath: string, modulePrefix?: string) { - const result: VisibleModuleInfo[] = []; - findPackageJsons(scriptPath).forEach((packageJson) => { - const package = tryReadingPackageJson(packageJson); - if (!package) { - return; - } - - const nodeModulesDir = combinePaths(getDirectoryPath(packageJson), "node_modules"); - const foundModuleNames: string[] = []; - - if (package.dependencies) { - addPotentialPackageNames(package.dependencies, modulePrefix, foundModuleNames); - } - if (package.devDependencies) { - addPotentialPackageNames(package.devDependencies, modulePrefix, foundModuleNames); - } - - forEach(foundModuleNames, (moduleName) => { - const moduleDir = combinePaths(nodeModulesDir, moduleName); - result.push({ - moduleName, - moduleDir, - canBeImported: moduleCanBeImported(moduleDir) - }); - }); - }); - - return result; - - function findPackageJsons(currentDir: string): string[] { - const paths: string[] = []; - let currentConfigPath: string; - while (true) { - currentConfigPath = findConfigFile(currentDir, (f) => host.fileExists(f), "package.json"); - if (currentConfigPath) { - paths.push(currentConfigPath); - - currentDir = getDirectoryPath(currentConfigPath); - const parent = getDirectoryPath(currentDir); - if (currentDir === parent) { - break; - } - currentDir = parent; - } - else { - break; - } - } - - return paths; - } - - function tryReadingPackageJson(filePath: string) { - try { - const fileText = host.readFile(filePath); - return JSON.parse(fileText); - } - catch (e) { - return undefined; - } - } - - function addPotentialPackageNames(dependencies: any, prefix: string, result: string[]) { - for (const dep in dependencies) { - if (dependencies.hasOwnProperty(dep) && (!prefix || startsWith(dep, prefix))) { - result.push(dep); - } - } - } - - /* - * A module can be imported by name alone if one of the following is true: - * It defines the "typings" property in its package.json - * The module has a "main" export and an index.d.ts file - * The module has an index.ts - */ - function moduleCanBeImported(modulePath: string): boolean { - const packagePath = combinePaths(modulePath, "package.json"); - - let hasMainExport = false; - if (host.fileExists(packagePath)) { - const package = tryReadingPackageJson(packagePath); - if (package) { - if (package.typings) { - return true; - } - hasMainExport = !!package.main; - } - } - - hasMainExport = hasMainExport || host.fileExists(combinePaths(modulePath, "index.js")); - - return (hasMainExport && host.fileExists(combinePaths(modulePath, "index.d.ts"))) || host.fileExists(combinePaths(modulePath, "index.ts")); - } - } } \ No newline at end of file diff --git a/tests/cases/fourslash/completionForStringLiteralNonrelativeImport7.ts b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport7.ts new file mode 100644 index 0000000000..35e144c718 --- /dev/null +++ b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport7.ts @@ -0,0 +1,27 @@ +/// +// @baseUrl: tests/cases/fourslash/modules + +// @Filename: tests/test0.ts +//// import * as foo1 from "mod/*import_as0*/ +//// import foo2 = require("mod/*import_equals0*/ +//// var foo3 = require("mod/*require0*/ + +// @Filename: modules/module.ts +//// export var x = 5; + +// @Filename: package.json +//// { "dependencies": { "module-from-node": "latest" } } +// @Filename: node_modules/module-from-node/index.ts +//// /*module1*/ + + + +const kinds = ["import_as", "import_equals", "require"]; + +for (const kind of kinds) { + goTo.marker(kind + "0"); + + verify.completionListContains("module"); + verify.completionListContains("module-from-node"); + verify.not.completionListItemsCountIsGreaterThan(2); +} diff --git a/tests/cases/fourslash/completionForStringLiteralNonrelativeImport8.ts b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport8.ts new file mode 100644 index 0000000000..0e8da4b02b --- /dev/null +++ b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport8.ts @@ -0,0 +1,53 @@ +/// + +// @Filename: tsconfig.json +//// { +//// "compilerOptions": { +//// "baseUrl": "./modules", +//// "paths": { +//// "*": [ +//// "prefix/0*/suffix.ts", +//// "prefix-only/*", +//// "*/suffix-only.ts" +//// ] +//// } +//// } +//// } + + +// @Filename: tests/test0.ts +//// import * as foo1 from "0/*import_as0*/ +//// import foo2 = require("0/*import_equals0*/ +//// var foo3 = require("0/*require0*/ + +//// import * as foo1 from "1/*import_as1*/ +//// import foo2 = require("1/*import_equals1*/ +//// var foo3 = require("1/*require1*/ + +//// import * as foo1 from "2/*import_as2*/ +//// import foo2 = require("2/*import_equals2*/ +//// var foo3 = require("2/*require2*/ + + +// @Filename: modules/prefix/00test/suffix.ts +//// export var x = 5; + +// @Filename: modules/prefix-only/1test.ts +//// export var y = 5; + +// @Filename: modules/2test/suffix-only.ts +//// export var z = 5; + + +const kinds = ["import_as", "import_equals", "require"]; + +for (const kind of kinds) { + goTo.marker(kind + "0"); + verify.completionListContains("0test"); + + goTo.marker(kind + "1"); + verify.completionListContains("1test"); + + goTo.marker(kind + "2"); + verify.completionListContains("2test"); +} diff --git a/tests/cases/fourslash/completionForStringLiteralNonrelativeImport9.ts b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport9.ts new file mode 100644 index 0000000000..3c32ec2670 --- /dev/null +++ b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport9.ts @@ -0,0 +1,34 @@ +/// + +// @Filename: tsconfig.json +//// { +//// "compilerOptions": { +//// "baseUrl": "./modules", +//// "paths": { +//// "module1": ["some/path/whatever.ts"], +//// "module2": ["some/other/path.ts"] +//// } +//// } +//// } + + +// @Filename: tests/test0.ts +//// import * as foo1 from "m/*import_as0*/ +//// import foo2 = require("m/*import_equals0*/ +//// var foo3 = require("m/*require0*/ + +// @Filename: some/path/whatever.ts +//// export var x = 9; + +// @Filename: some/other/path.ts +//// export var y = 10; + + +const kinds = ["import_as", "import_equals", "require"]; + +for (const kind of kinds) { + goTo.marker(kind + "0"); + verify.completionListContains("module1"); + verify.completionListContains("module2"); + verify.not.completionListItemsCountIsGreaterThan(2); +} From 0b16180174ab6ccab7b58ef15b08f470090b1159 Mon Sep 17 00:00:00 2001 From: Richard Knoll Date: Wed, 27 Jul 2016 11:41:45 -0700 Subject: [PATCH 10/39] Import completions with rootdirs compiler option --- src/harness/harnessLanguageService.ts | 17 ++++++- src/services/services.ts | 49 +++++++++++++++++-- ...mpletionForStringLiteralRelativeImport4.ts | 48 ++++++++++++++++++ 3 files changed, 109 insertions(+), 5 deletions(-) create mode 100644 tests/cases/fourslash/completionForStringLiteralRelativeImport4.ts diff --git a/src/harness/harnessLanguageService.ts b/src/harness/harnessLanguageService.ts index 3cb83faf61..a488146ba6 100644 --- a/src/harness/harnessLanguageService.ts +++ b/src/harness/harnessLanguageService.ts @@ -219,7 +219,22 @@ namespace Harness.LanguageService { return snapshot.getText(0, snapshot.getLength()); } resolvePath(path: string): string { - return ts.normalizePath(ts.isRootedDiskPath(path) ? path : ts.combinePaths(this.getCurrentDirectory(), path)); + if (!ts.isRootedDiskPath(path)) { + // An "absolute" path for fourslash is one that is contained within the tests directory + const components = ts.getNormalizedPathComponents(path, this.getCurrentDirectory()); + if (components.length) { + // If this is still a relative path after normalization (i.e. currentDirectory is relative), the root will be the empty string + if (!components[0]) { + components.splice(0, 1); + if (components[0] !== "tests") { + // If not contained within test, assume its relative to the directory containing the test files + return ts.normalizePath(ts.combinePaths("tests/cases/fourslash", components.join(ts.directorySeparator))); + } + } + return ts.normalizePath(components.join(ts.directorySeparator)); + } + } + return ts.normalizePath(path); } diff --git a/src/services/services.ts b/src/services/services.ts index 275c41732c..43f9552730 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -4379,7 +4379,15 @@ namespace ts { const isRelativePath = startsWith(literalValue, "."); const scriptDir = getDirectoryPath(node.getSourceFile().path); if (isRelativePath || isRootedDiskPath(literalValue)) { - result = getCompletionEntriesForDirectoryFragment(literalValue, scriptDir, getSupportedExtensions(program.getCompilerOptions()), /*includeExtensions*/false); + const compilerOptions = program.getCompilerOptions(); + if (compilerOptions.rootDirs) { + result = getCompletionEntriesForDirectoryFragmentWithRootDirs( + compilerOptions.rootDirs, literalValue, scriptDir, getSupportedExtensions(program.getCompilerOptions()), /*includeExtensions*/false); + } + else { + result = getCompletionEntriesForDirectoryFragment( + literalValue, scriptDir, getSupportedExtensions(program.getCompilerOptions()), /*includeExtensions*/false); + } } else { // Check for node modules @@ -4393,10 +4401,42 @@ namespace ts { }; } - function getCompletionEntriesForDirectoryFragment(fragment: string, scriptPath: string, extensions: string[], includeExtensions: boolean): CompletionEntry[] { - // Complete the path by looking for source files and directories + /** + * Takes a script path and returns paths for all potential folders that could be merged with its + * containing folder via the "rootDirs" compiler option + */ + function getBaseDirectoriesFromRootDirs(rootDirs: string[], basePath: string, scriptPath: string, ignoreCase: boolean) { + // Make all paths absolute/normalized if they are not already + rootDirs = map(rootDirs, 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 + let relativeDirectory: string; + forEach(rootDirs, rootDirectory => { + if (containsPath(rootDirectory, scriptPath, basePath, ignoreCase)) { + relativeDirectory = scriptPath.substr(rootDirectory.length); + return true; + } + }); + + // 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))); + } + + function getCompletionEntriesForDirectoryFragmentWithRootDirs(rootDirs: string[], fragment: string, scriptPath: string, extensions: string[], includeExtensions: boolean): CompletionEntry[] { + const basePath = program.getCompilerOptions().project || host.getCurrentDirectory(); + + const baseDirectories = getBaseDirectoriesFromRootDirs( + rootDirs, basePath, scriptPath, host.useCaseSensitiveFileNames && !host.useCaseSensitiveFileNames()); const result: CompletionEntry[] = []; + for (const baseDirectory of baseDirectories) { + getCompletionEntriesForDirectoryFragment(fragment, baseDirectory, extensions, includeExtensions, result); + } + + return result; + } + + function getCompletionEntriesForDirectoryFragment(fragment: string, scriptPath: string, extensions: string[], includeExtensions: boolean, result: CompletionEntry[] = []): CompletionEntry[] { const toComplete = getBaseFileName(fragment); const absolutePath = normalizeSlashes(host.resolvePath(isRootedDiskPath(fragment) ? fragment : combinePaths(scriptPath, fragment))); const baseDirectory = toComplete ? getDirectoryPath(absolutePath) : absolutePath; @@ -4456,7 +4496,8 @@ namespace ts { if (baseUrl) { const fileExtensions = getSupportedExtensions(options); - const absolute = isRootedDiskPath(baseUrl) ? baseUrl : combinePaths(host.getCurrentDirectory(), baseUrl) + const projectDir = options.project || host.getCurrentDirectory(); + const absolute = isRootedDiskPath(baseUrl) ? baseUrl : combinePaths(projectDir, baseUrl); result = getCompletionEntriesForDirectoryFragment(fragment, normalizePath(absolute), fileExtensions, /*includeExtensions*/false); if (paths) { diff --git a/tests/cases/fourslash/completionForStringLiteralRelativeImport4.ts b/tests/cases/fourslash/completionForStringLiteralRelativeImport4.ts new file mode 100644 index 0000000000..1895fff851 --- /dev/null +++ b/tests/cases/fourslash/completionForStringLiteralRelativeImport4.ts @@ -0,0 +1,48 @@ +/// + +// @rootDirs: sub/src1,src2 + +// @Filename: src2/test0.ts +//// import * as foo1 from "./mo/*import_as0*/ +//// import foo2 = require("./mo/*import_equals0*/ +//// var foo3 = require("./mo/*require0*/ + +// @Filename: src2/module0.ts +//// export var w = 0; + +// @Filename: sub/src1/module1.ts +//// export var x = 0; + +// @Filename: sub/src1/module2.ts +//// export var y = 0; + +// @Filename: sub/src1/more/module3.ts +//// export var z = 0; + + +// @Filename: f1.ts +//// /*f1*/ +// @Filename: f2.tsx +//// /*f2*/ +// @Filename: folder/f1.ts +//// /*subf1*/ +// @Filename: f3.js +//// /*f3*/ +// @Filename: f4.jsx +//// /*f4*/ +// @Filename: e1.ts +//// /*e1*/ +// @Filename: e2.js +//// /*e2*/ + +const kinds = ["import_as", "import_equals", "require"]; + +for (const kind of kinds) { + goTo.marker(kind + "0"); + + verify.completionListContains("module0"); + verify.completionListContains("module1"); + verify.completionListContains("module2"); + verify.completionListContains("more/"); + verify.not.completionListItemsCountIsGreaterThan(4); +} \ No newline at end of file From dbf19f18af35afc21640ecf9b80a6c745992f5a7 Mon Sep 17 00:00:00 2001 From: Richard Knoll Date: Thu, 28 Jul 2016 11:52:15 -0700 Subject: [PATCH 11/39] Adding import completions for typings --- src/harness/fourslash.ts | 4 +- src/services/services.ts | 125 +++++++++++++----- ...etionForStringLiteralNonrelativeImport2.ts | 7 +- ...etionForStringLiteralNonrelativeImport4.ts | 6 +- ...rStringLiteralNonrelativeImportTypings1.ts | 32 +++++ ...rStringLiteralNonrelativeImportTypings2.ts | 29 ++++ ...rStringLiteralNonrelativeImportTypings3.ts | 25 ++++ 7 files changed, 183 insertions(+), 45 deletions(-) create mode 100644 tests/cases/fourslash/completionForStringLiteralNonrelativeImportTypings1.ts create mode 100644 tests/cases/fourslash/completionForStringLiteralNonrelativeImportTypings2.ts create mode 100644 tests/cases/fourslash/completionForStringLiteralNonrelativeImportTypings3.ts diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index 87b537417e..8a8cb348a7 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -260,8 +260,8 @@ namespace FourSlash { // Extend our existing compiler options so that we can also support tsconfig only options if (configJson.config.compilerOptions) { - let baseDir = ts.normalizePath(ts.getDirectoryPath(file.fileName)); - let tsConfig = ts.convertCompilerOptionsFromJson(configJson.config.compilerOptions, baseDir, file.fileName); + const baseDirectory = ts.normalizePath(ts.getDirectoryPath(file.fileName)); + const tsConfig = ts.convertCompilerOptionsFromJson(configJson.config.compilerOptions, baseDirectory, file.fileName); if (!tsConfig.errors || !tsConfig.errors.length) { compilationOptions = ts.extend(compilationOptions, tsConfig.options); diff --git a/src/services/services.ts b/src/services/services.ts index 43f9552730..76aed2ffe0 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1968,7 +1968,7 @@ namespace ts { * for completions. * For example, this matches /// { result.push(createCompletionEntryForModule(moduleName, ScriptElementKind.externalModuleName)); @@ -4558,7 +4558,7 @@ namespace ts { // If we have a suffix, then we need to read the directory all the way down. We could create a glob // that encodes the suffix, but we would have to escape the character "?" which readDirectory // doesn't support. For now, this is safer but slower - const includeGlob = normalizedSuffix ? "**/*" : "./*" + const includeGlob = normalizedSuffix ? "**/*" : "./*"; const matches = host.readDirectory(baseDirectory, fileExtensions, undefined, [includeGlob]); const result: string[] = []; @@ -4629,19 +4629,97 @@ namespace ts { const text = sourceFile.text.substr(node.pos, position); const match = tripleSlashDirectiveFragmentRegex.exec(text); if (match) { - const fragment = match[1]; - const scriptPath = getDirectoryPath(sourceFile.path); - return { - isMemberCompletion: false, - isNewIdentifierLocation: false, - entries: getCompletionEntriesForDirectoryFragment(fragment, scriptPath, getSupportedExtensions(program.getCompilerOptions()), /*includeExtensions*/true) - }; + const kind= match[1]; + const fragment = match[2]; + if (kind === "path") { + // Give completions for a relative path + const scriptPath = getDirectoryPath(sourceFile.path); + return { + isMemberCompletion: false, + isNewIdentifierLocation: false, + entries: getCompletionEntriesForDirectoryFragment(fragment, scriptPath, getSupportedExtensions(program.getCompilerOptions()), /*includeExtensions*/true) + }; + } + else { + // Give completions based on what is available in the types directory + } } return undefined; } } + function getCompletionEntriesFromTypings(host: LanguageServiceHost, options: CompilerOptions, scriptPath: string, result: CompletionEntry[]): CompletionEntry[] { + // Check for typings specified in compiler options + if (options.types) { + forEach(options.types, moduleName => { + result.push(createCompletionEntryForModule(moduleName, ScriptElementKind.externalModuleName)); + }); + } + else if (options.typeRoots) { + const absoluteRoots = map(options.typeRoots, rootDirectory => getAbsoluteProjectPath(rootDirectory, host, options.project)); + forEach(absoluteRoots, absoluteRoot => getCompletionEntriesFromDirectory(host, options, absoluteRoot, result)); + } + + // Also get all @types typings installed in visible node_modules directories + forEach(findPackageJsons(scriptPath), package => { + const typesDir = combinePaths(getDirectoryPath(package), "node_modules/@types"); + getCompletionEntriesFromDirectory(host, options, typesDir, result); + }); + + return result; + } + + function getAbsoluteProjectPath(path: string, host: LanguageServiceHost, projectDir?: string) { + if (isRootedDiskPath(path)) { + return normalizePath(path); + } + + if (projectDir) { + return normalizePath(combinePaths(projectDir, path)); + } + + return normalizePath(host.resolvePath(path)); + } + + function getCompletionEntriesFromDirectory(host: LanguageServiceHost, options: CompilerOptions, directory: string, result: CompletionEntry[]) { + if (directoryProbablyExists(directory, host)) { + const typeDirectories = host.readDirectory(directory, getSupportedExtensions(options), /*exclude*/undefined, /*include*/["./*/*"]); + const seen: {[index: string]: boolean} = {}; + forEach(typeDirectories, typeFile => { + const typeDirectory = getDirectoryPath(typeFile); + if (!hasProperty(seen, typeDirectory)) { + seen[typeDirectory] = true; + result.push(createCompletionEntryForModule(getBaseFileName(typeDirectory), ScriptElementKind.externalModuleName)); + } + }); + } + } + + function findPackageJsons(currentDir: string): string[] { + const paths: string[] = []; + let currentConfigPath: string; + while (true) { + currentConfigPath = findConfigFile(currentDir, (f) => host.fileExists(f), "package.json"); + if (currentConfigPath) { + paths.push(currentConfigPath); + + currentDir = getDirectoryPath(currentConfigPath); + const parent = getDirectoryPath(currentDir); + if (currentDir === parent) { + break; + } + currentDir = parent; + } + else { + break; + } + } + + return paths; + } + + function enumerateNodeModulesVisibleToScript(host: LanguageServiceHost, scriptPath: string, modulePrefix?: string) { const result: VisibleModuleInfo[] = []; findPackageJsons(scriptPath).forEach((packageJson) => { @@ -4672,29 +4750,6 @@ namespace ts { return result; - function findPackageJsons(currentDir: string): string[] { - const paths: string[] = []; - let currentConfigPath: string; - while (true) { - currentConfigPath = findConfigFile(currentDir, (f) => host.fileExists(f), "package.json"); - if (currentConfigPath) { - paths.push(currentConfigPath); - - currentDir = getDirectoryPath(currentConfigPath); - const parent = getDirectoryPath(currentDir); - if (currentDir === parent) { - break; - } - currentDir = parent; - } - else { - break; - } - } - - return paths; - } - function tryReadingPackageJson(filePath: string) { try { const fileText = host.readFile(filePath); @@ -4745,7 +4800,7 @@ namespace ts { kind, kindModifiers: ScriptElementKindModifier.none, sortText: name - } + }; } function getCompletionEntryDetails(fileName: string, position: number, entryName: string): CompletionEntryDetails { diff --git a/tests/cases/fourslash/completionForStringLiteralNonrelativeImport2.ts b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport2.ts index 2b5f57412d..11420fe4f3 100644 --- a/tests/cases/fourslash/completionForStringLiteralNonrelativeImport2.ts +++ b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport2.ts @@ -22,11 +22,8 @@ // @Filename: node_modules/unlisted-module/index.js //// /*unlisted-module*/ -// @Filename: node_modules/@types/fake-module/other.d.ts -//// declare module "fake-module/other" {} - -// @Filename: node_modules/@types/unlisted-module/index.d.ts -//// /*unlisted-types*/ +// @Filename: ambient.ts +//// declare module "fake-module/other" const kinds = ["import_as", "import_equals", "require"]; diff --git a/tests/cases/fourslash/completionForStringLiteralNonrelativeImport4.ts b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport4.ts index 64e3cbad48..5a96e4ee63 100644 --- a/tests/cases/fourslash/completionForStringLiteralNonrelativeImport4.ts +++ b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport4.ts @@ -20,7 +20,7 @@ // @Filename: dir1/package.json //// { "dependencies": { "fake-module2": "latest" } } -// @Filename: dir1/node_modules/@types/fake-module2/js.d.ts +// @Filename: dir1/node_modules/fake-module2/index.ts //// declare module "ambient-module-test" {} // @Filename: dir1/dir2/dir3/package.json @@ -34,7 +34,7 @@ for (const kind of kinds) { goTo.marker(kind + "0"); verify.completionListContains("fake-module/"); - verify.completionListContains("fake-module2/"); + verify.completionListContains("fake-module2"); verify.completionListContains("fake-module3/"); verify.not.completionListItemsCountIsGreaterThan(3); @@ -46,7 +46,7 @@ for (const kind of kinds) { goTo.marker(kind + "2"); verify.completionListContains("fake-module/"); - verify.completionListContains("fake-module2/"); + verify.completionListContains("fake-module2"); verify.completionListContains("fake-module3/"); verify.not.completionListItemsCountIsGreaterThan(3); } \ No newline at end of file diff --git a/tests/cases/fourslash/completionForStringLiteralNonrelativeImportTypings1.ts b/tests/cases/fourslash/completionForStringLiteralNonrelativeImportTypings1.ts new file mode 100644 index 0000000000..53dc12033c --- /dev/null +++ b/tests/cases/fourslash/completionForStringLiteralNonrelativeImportTypings1.ts @@ -0,0 +1,32 @@ +/// + +// @typeRoots: my_typings,my_other_typings + + +// @Filename: tests/test0.ts +//// import * as foo1 from "m/*import_as0*/ +//// import foo2 = require("m/*import_equals0*/ +//// var foo3 = require("m/*require0*/ + +// @Filename: my_typings/module-x/index.d.ts +//// export var x = 9; + +// @Filename: my_typings/module-x/whatever.d.ts +//// export var w = 9; + +// @Filename: my_typings/module-y/index.d.ts +//// export var y = 9; + +// @Filename: my_other_typings/module-z/index.d.ts +//// export var z = 9; + + +const kinds = ["import_as", "import_equals", "require"]; + +for (const kind of kinds) { + goTo.marker(kind + "0"); + verify.completionListContains("module-x"); + verify.completionListContains("module-y"); + verify.completionListContains("module-z"); + verify.not.completionListItemsCountIsGreaterThan(3); +} diff --git a/tests/cases/fourslash/completionForStringLiteralNonrelativeImportTypings2.ts b/tests/cases/fourslash/completionForStringLiteralNonrelativeImportTypings2.ts new file mode 100644 index 0000000000..01e6f0ed42 --- /dev/null +++ b/tests/cases/fourslash/completionForStringLiteralNonrelativeImportTypings2.ts @@ -0,0 +1,29 @@ +/// + +// @typeRoots: my_typings,my_other_typings +// @types: module-x,module-z + + +// @Filename: tests/test0.ts +//// import * as foo1 from "m/*import_as0*/ +//// import foo2 = require("m/*import_equals0*/ +//// var foo3 = require("m/*require0*/ + +// @Filename: my_typings/module-x/index.d.ts +//// export var x = 9; + +// @Filename: my_typings/module-y/index.d.ts +//// export var y = 9; + +// @Filename: my_other_typings/module-z/index.d.ts +//// export var z = 9; + + +const kinds = ["import_as", "import_equals", "require"]; + +for (const kind of kinds) { + goTo.marker(kind + "0"); + verify.completionListContains("module-x"); + verify.completionListContains("module-z"); + verify.not.completionListItemsCountIsGreaterThan(2); +} diff --git a/tests/cases/fourslash/completionForStringLiteralNonrelativeImportTypings3.ts b/tests/cases/fourslash/completionForStringLiteralNonrelativeImportTypings3.ts new file mode 100644 index 0000000000..9710ce05cd --- /dev/null +++ b/tests/cases/fourslash/completionForStringLiteralNonrelativeImportTypings3.ts @@ -0,0 +1,25 @@ +/// + +// @Filename: subdirectory/test0.ts +//// import * as foo1 from "m/*import_as0*/ +//// import foo2 = require("m/*import_equals0*/ +//// var foo3 = require("m/*require0*/ + +// @Filename: subdirectory/node_modules/@types/module-x/index.d.ts +//// export var x = 9; +// @Filename: subdirectory/package.json +//// { "dependencies": { "@types/module-x": "latest" } } + +// @Filename: node_modules/@types/module-y/index.d.ts +//// export var y = 9; +// @Filename: package.json +//// { "dependencies": { "@types/module-y": "latest" } } + +const kinds = ["import_as", "import_equals", "require"]; + +for (const kind of kinds) { + goTo.marker(kind + "0"); + verify.completionListContains("module-x"); + verify.completionListContains("module-y"); + verify.not.completionListItemsCountIsGreaterThan(2); +} From fdbc23e9acc1c2bbc04c6b8440a4bfe3577d6f78 Mon Sep 17 00:00:00 2001 From: Richard Knoll Date: Thu, 28 Jul 2016 13:12:53 -0700 Subject: [PATCH 12/39] Add completions for types triple slash directives --- src/services/services.ts | 23 ++++++++++++++----- src/services/utilities.ts | 5 ++++ ...rStringLiteralNonrelativeImportTypings1.ts | 3 ++- ...rStringLiteralNonrelativeImportTypings2.ts | 3 ++- ...rStringLiteralNonrelativeImportTypings3.ts | 3 ++- 5 files changed, 28 insertions(+), 9 deletions(-) diff --git a/src/services/services.ts b/src/services/services.ts index 76aed2ffe0..a4a1a44459 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -4538,11 +4538,10 @@ namespace ts { function getModulesForPathsPattern(fragment: string, baseUrl: string, pattern: string, fileExtensions: string[]): string[] { const parsed = hasZeroOrOneAsteriskCharacter(pattern) ? tryParsePattern(pattern) : undefined; if (parsed) { - const hasTrailingSlash = parsed.prefix.charAt(parsed.prefix.length - 1) === "/" || parsed.prefix.charAt(parsed.prefix.length - 1) === "\\"; - // The prefix has two effective parts: the directory path and the base component after the filepath that is not a // full directory component. For example: directory/path/of/prefix/base* - const normalizedPrefix = hasTrailingSlash ? ensureTrailingDirectorySeparator(normalizePath(parsed.prefix)) : normalizePath(parsed.prefix); + const normalizedPrefix = hasTrailingDirectorySeparator(parsed.prefix) ? + ensureTrailingDirectorySeparator(normalizePath(parsed.prefix)) : normalizePath(parsed.prefix); const normalizedPrefixDirectory = getDirectoryPath(normalizedPrefix); const normalizedPrefixBase = getBaseFileName(normalizedPrefix); @@ -4582,6 +4581,13 @@ namespace ts { } function enumeratePotentialNonRelativeModules(fragment: string, scriptPath: string): string[] { + const trailingSeperator = hasTrailingDirectorySeparator(fragment); + fragment = normalizePath(fragment); + + if (trailingSeperator) { + fragment = ensureTrailingDirectorySeparator(fragment); + } + // If this is a nested module, get the module name const firstSeparator = fragment.indexOf(directorySeparator); const moduleNameFragment = firstSeparator !== -1 ? fragment.substr(0, firstSeparator) : fragment; @@ -4631,9 +4637,9 @@ namespace ts { if (match) { const kind= match[1]; const fragment = match[2]; + const scriptPath = getDirectoryPath(sourceFile.path); if (kind === "path") { // Give completions for a relative path - const scriptPath = getDirectoryPath(sourceFile.path); return { isMemberCompletion: false, isNewIdentifierLocation: false, @@ -4641,7 +4647,12 @@ namespace ts { }; } else { - // Give completions based on what is available in the types directory + // Give completions based on the typings available + return { + isMemberCompletion: false, + isNewIdentifierLocation: false, + entries: getCompletionEntriesFromTypings(host, program.getCompilerOptions(), scriptPath) + }; } } @@ -4649,7 +4660,7 @@ namespace ts { } } - function getCompletionEntriesFromTypings(host: LanguageServiceHost, options: CompilerOptions, scriptPath: string, result: CompletionEntry[]): CompletionEntry[] { + function getCompletionEntriesFromTypings(host: LanguageServiceHost, options: CompilerOptions, scriptPath: string, result: CompletionEntry[] = []): CompletionEntry[] { // Check for typings specified in compiler options if (options.types) { forEach(options.types, moduleName => { diff --git a/src/services/utilities.ts b/src/services/utilities.ts index 0c4c0fd1de..3a9b56584c 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -927,4 +927,9 @@ namespace ts { } return ensureScriptKind(fileName, scriptKind); } + + export function hasTrailingDirectorySeparator(path: string) { + const lastCharacter = path.charAt(path.length - 1); + return lastCharacter === "/" || lastCharacter === "\\"; + } } \ No newline at end of file diff --git a/tests/cases/fourslash/completionForStringLiteralNonrelativeImportTypings1.ts b/tests/cases/fourslash/completionForStringLiteralNonrelativeImportTypings1.ts index 53dc12033c..edce0ce7e9 100644 --- a/tests/cases/fourslash/completionForStringLiteralNonrelativeImportTypings1.ts +++ b/tests/cases/fourslash/completionForStringLiteralNonrelativeImportTypings1.ts @@ -4,6 +4,7 @@ // @Filename: tests/test0.ts +//// /// //// import * as foo1 from "m/*import_as0*/ //// import foo2 = require("m/*import_equals0*/ //// var foo3 = require("m/*require0*/ @@ -21,7 +22,7 @@ //// export var z = 9; -const kinds = ["import_as", "import_equals", "require"]; +const kinds = ["types_ref", "import_as", "import_equals", "require"]; for (const kind of kinds) { goTo.marker(kind + "0"); diff --git a/tests/cases/fourslash/completionForStringLiteralNonrelativeImportTypings2.ts b/tests/cases/fourslash/completionForStringLiteralNonrelativeImportTypings2.ts index 01e6f0ed42..541bb88c07 100644 --- a/tests/cases/fourslash/completionForStringLiteralNonrelativeImportTypings2.ts +++ b/tests/cases/fourslash/completionForStringLiteralNonrelativeImportTypings2.ts @@ -5,6 +5,7 @@ // @Filename: tests/test0.ts +//// /// //// import * as foo1 from "m/*import_as0*/ //// import foo2 = require("m/*import_equals0*/ //// var foo3 = require("m/*require0*/ @@ -19,7 +20,7 @@ //// export var z = 9; -const kinds = ["import_as", "import_equals", "require"]; +const kinds = ["types_ref", "import_as", "import_equals", "require"]; for (const kind of kinds) { goTo.marker(kind + "0"); diff --git a/tests/cases/fourslash/completionForStringLiteralNonrelativeImportTypings3.ts b/tests/cases/fourslash/completionForStringLiteralNonrelativeImportTypings3.ts index 9710ce05cd..7a7d4e00df 100644 --- a/tests/cases/fourslash/completionForStringLiteralNonrelativeImportTypings3.ts +++ b/tests/cases/fourslash/completionForStringLiteralNonrelativeImportTypings3.ts @@ -1,6 +1,7 @@ /// // @Filename: subdirectory/test0.ts +//// /// //// import * as foo1 from "m/*import_as0*/ //// import foo2 = require("m/*import_equals0*/ //// var foo3 = require("m/*require0*/ @@ -15,7 +16,7 @@ // @Filename: package.json //// { "dependencies": { "@types/module-y": "latest" } } -const kinds = ["import_as", "import_equals", "require"]; +const kinds = ["types_ref", "import_as", "import_equals", "require"]; for (const kind of kinds) { goTo.marker(kind + "0"); From 9e797b4c787b3352cdda0a39ad7ac1704c8b596e Mon Sep 17 00:00:00 2001 From: Richard Knoll Date: Thu, 28 Jul 2016 16:44:24 -0700 Subject: [PATCH 13/39] Use getDirectories and condition node modules resolution on moduleResolution flag --- src/services/services.ts | 65 ++++++++++--------- ...tionForStringLiteralNonrelativeImport10.ts | 33 ++++++++++ 2 files changed, 67 insertions(+), 31 deletions(-) create mode 100644 tests/cases/fourslash/completionForStringLiteralNonrelativeImport10.ts diff --git a/src/services/services.ts b/src/services/services.ts index a14090e26c..025afcd154 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1171,6 +1171,11 @@ namespace ts { resolveModuleNames?(moduleNames: string[], containingFile: string): ResolvedModule[]; resolveTypeReferenceDirectives?(typeDirectiveNames: string[], containingFile: string): ResolvedTypeReferenceDirective[]; directoryExists?(directoryName: string): boolean; + + /** + * getDirectories is also required for full import and type reference completions. Without it defined, certain + * completions will not be provided + */ getDirectories?(directoryName: string): string[]; } @@ -4652,7 +4657,7 @@ namespace ts { getCompletionEntriesFromTypings(host, options, scriptPath, result); - forEach(enumeratePotentialNonRelativeModules(fragment, scriptPath), moduleName => { + forEach(enumeratePotentialNonRelativeModules(fragment, scriptPath, options), moduleName => { result.push(createCompletionEntryForModule(moduleName, ScriptElementKind.externalModuleName)); }); @@ -4704,7 +4709,7 @@ namespace ts { return undefined; } - function enumeratePotentialNonRelativeModules(fragment: string, scriptPath: string): string[] { + function enumeratePotentialNonRelativeModules(fragment: string, scriptPath: string, options: CompilerOptions): string[] { const trailingSeperator = hasTrailingDirectorySeparator(fragment); fragment = normalizePath(fragment); @@ -4733,19 +4738,21 @@ namespace ts { return moduleName; }); - forEach(enumerateNodeModulesVisibleToScript(host, scriptPath, moduleNameFragment), visibleModule => { - if (!isNestedModule) { - nonRelativeModules.push(visibleModule.canBeImported ? visibleModule.moduleName : ensureTrailingDirectorySeparator(visibleModule.moduleName)); - } - else { - const nestedFiles = host.readDirectory(visibleModule.moduleDir, supportedTypeScriptExtensions, /*exclude*/undefined, /*include*/["./*"]); + if (!options.moduleResolution || options.moduleResolution === ModuleResolutionKind.NodeJs) { + forEach(enumerateNodeModulesVisibleToScript(host, scriptPath, moduleNameFragment), visibleModule => { + if (!isNestedModule) { + nonRelativeModules.push(visibleModule.canBeImported ? visibleModule.moduleName : ensureTrailingDirectorySeparator(visibleModule.moduleName)); + } + else { + const nestedFiles = host.readDirectory(visibleModule.moduleDir, supportedTypeScriptExtensions, /*exclude*/undefined, /*include*/["./*"]); - forEach(nestedFiles, (f) => { - const nestedModule = removeFileExtension(getBaseFileName(f)); - nonRelativeModules.push(nestedModule); - }); - } - }); + forEach(nestedFiles, (f) => { + const nestedModule = removeFileExtension(getBaseFileName(f)); + nonRelativeModules.push(nestedModule); + }); + } + }); + } return deduplicate(nonRelativeModules); } @@ -4791,16 +4798,18 @@ namespace ts { result.push(createCompletionEntryForModule(moduleName, ScriptElementKind.externalModuleName)); }); } - else if (options.typeRoots) { + else if (host.getDirectories && options.typeRoots) { const absoluteRoots = map(options.typeRoots, rootDirectory => getAbsoluteProjectPath(rootDirectory, host, options.project)); - forEach(absoluteRoots, absoluteRoot => getCompletionEntriesFromDirectory(host, options, absoluteRoot, result)); + forEach(absoluteRoots, absoluteRoot => getCompletionEntriesFromDirectories(host, options, absoluteRoot, result)); } - // Also get all @types typings installed in visible node_modules directories - forEach(findPackageJsons(scriptPath), package => { - const typesDir = combinePaths(getDirectoryPath(package), "node_modules/@types"); - getCompletionEntriesFromDirectory(host, options, typesDir, result); - }); + if (host.getDirectories) { + // Also get all @types typings installed in visible node_modules directories + forEach(findPackageJsons(scriptPath), package => { + const typesDir = combinePaths(getDirectoryPath(package), "node_modules/@types"); + getCompletionEntriesFromDirectories(host, options, typesDir, result); + }); + } return result; } @@ -4817,16 +4826,10 @@ namespace ts { return normalizePath(host.resolvePath(path)); } - function getCompletionEntriesFromDirectory(host: LanguageServiceHost, options: CompilerOptions, directory: string, result: CompletionEntry[]) { - if (directoryProbablyExists(directory, host)) { - const typeDirectories = host.readDirectory(directory, getSupportedExtensions(options), /*exclude*/undefined, /*include*/["./*/*"]); - const seen: {[index: string]: boolean} = {}; - forEach(typeDirectories, typeFile => { - const typeDirectory = getDirectoryPath(typeFile); - if (!hasProperty(seen, typeDirectory)) { - seen[typeDirectory] = true; - result.push(createCompletionEntryForModule(getBaseFileName(typeDirectory), ScriptElementKind.externalModuleName)); - } + function getCompletionEntriesFromDirectories(host: LanguageServiceHost, options: CompilerOptions, directory: string, result: CompletionEntry[]) { + if (host.getDirectories && directoryProbablyExists(directory, host)) { + forEach(host.getDirectories(directory), typeDirectory => { + result.push(createCompletionEntryForModule(getBaseFileName(typeDirectory), ScriptElementKind.externalModuleName)); }); } } diff --git a/tests/cases/fourslash/completionForStringLiteralNonrelativeImport10.ts b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport10.ts new file mode 100644 index 0000000000..cb1f67f634 --- /dev/null +++ b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport10.ts @@ -0,0 +1,33 @@ +/// + +// @moduleResolution: classic + +// @Filename: dir1/dir2/dir3/dir4/test0.ts +//// import * as foo1 from "f/*import_as0*/ +//// import * as foo3 from "fake-module/*import_as1*/ + +//// import foo4 = require("f/*import_equals0*/ +//// import foo6 = require("fake-module/*import_equals1*/ + +//// var foo7 = require("f/*require0*/ +//// var foo9 = require("fake-module/*require1*/ + +// @Filename: package.json +//// { "dependencies": { "fake-module": "latest" } } +// @Filename: node_modules/fake-module/ts.ts +//// /*module1*/ + +// @Filename: dir1/dir2/dir3/package.json +//// { "dependencies": { "fake-module3": "latest" } } +// @Filename: dir1/dir2/dir3/node_modules/fake-module3/ts.ts +//// /*module3*/ + +const kinds = ["import_as", "import_equals", "require"]; + +for (const kind of kinds) { + goTo.marker(kind + "0"); + verify.completionListIsEmpty(); + + goTo.marker(kind + "1"); + verify.completionListIsEmpty(); +} \ No newline at end of file From dbd8dd5c88b206da384eba5aaf4d80771ff0ec17 Mon Sep 17 00:00:00 2001 From: Michael Date: Fri, 29 Jul 2016 15:56:33 +0200 Subject: [PATCH 14/39] add Array.prototype.filter signature with type guard --- src/lib/es5.d.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/lib/es5.d.ts b/src/lib/es5.d.ts index 6f017c8a34..571c42e0eb 100644 --- a/src/lib/es5.d.ts +++ b/src/lib/es5.d.ts @@ -1055,6 +1055,12 @@ interface ReadonlyArray { * @param thisArg An object to which the this keyword can refer in the callbackfn function. If thisArg is omitted, undefined is used as the this value. */ map(callbackfn: (value: T, index: number, array: ReadonlyArray) => U, thisArg?: any): U[]; + /** + * Returns the elements of an array that meet the condition specified in a callback function. + * @param callbackfn A function that accepts up to three arguments. The filter method calls the callbackfn function one time for each element in the array. + * @param thisArg An object to which the this keyword can refer in the callbackfn function. If thisArg is omitted, undefined is used as the this value. + */ + filter(callbackfn: (value: T, index: number, array: ReadonlyArray) => value is S, thisArg?: any): S[]; /** * Returns the elements of an array that meet the condition specified in a callback function. * @param callbackfn A function that accepts up to three arguments. The filter method calls the callbackfn function one time for each element in the array. From 4ec8b2b134c9f6c91251c12ec42974bf0b3b403f Mon Sep 17 00:00:00 2001 From: Richard Knoll Date: Mon, 1 Aug 2016 14:29:10 -0700 Subject: [PATCH 15/39] Refactoring import completions into their own api --- src/harness/fourslash.ts | 75 ++++ src/harness/harnessLanguageService.ts | 3 + src/server/client.ts | 15 + src/server/protocol.d.ts | 14 + src/server/session.ts | 1 + src/services/services.ts | 350 +++++++++--------- src/services/shims.ts | 8 + ...etionForStringLiteralNonrelativeImport1.ts | 22 +- ...tionForStringLiteralNonrelativeImport10.ts | 4 +- ...etionForStringLiteralNonrelativeImport2.ts | 6 +- ...etionForStringLiteralNonrelativeImport3.ts | 8 +- ...etionForStringLiteralNonrelativeImport4.ts | 20 +- ...etionForStringLiteralNonrelativeImport5.ts | 12 +- ...etionForStringLiteralNonrelativeImport6.ts | 14 +- ...etionForStringLiteralNonrelativeImport7.ts | 6 +- ...etionForStringLiteralNonrelativeImport8.ts | 6 +- ...etionForStringLiteralNonrelativeImport9.ts | 6 +- ...rStringLiteralNonrelativeImportTypings1.ts | 8 +- ...rStringLiteralNonrelativeImportTypings2.ts | 6 +- ...rStringLiteralNonrelativeImportTypings3.ts | 6 +- ...mpletionForStringLiteralRelativeImport1.ts | 38 +- ...mpletionForStringLiteralRelativeImport2.ts | 26 +- ...mpletionForStringLiteralRelativeImport3.ts | 20 +- ...mpletionForStringLiteralRelativeImport4.ts | 10 +- .../completionForTripleSlashReference1.ts | 50 +-- .../completionForTripleSlashReference2.ts | 36 +- .../completionForTripleSlashReference3.ts | 20 +- tests/cases/fourslash/fourslash.ts | 3 + 28 files changed, 456 insertions(+), 337 deletions(-) diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index 8a8cb348a7..8b0968664e 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -597,6 +597,38 @@ namespace FourSlash { } } + public verifyImportModuleCompletionListItemsCountIsGreaterThan(count: number, negative: boolean) { + const completions = this.getImportModuleCompletionListAtCaret(); + const itemsCount = completions.length; + + if (negative) { + if (itemsCount > count) { + this.raiseError(`Expected import module completion list items count to not be greater than ${count}, but is actually ${itemsCount}`); + } + } + else { + if (itemsCount <= count) { + this.raiseError(`Expected import module completion list items count to be greater than ${count}, but is actually ${itemsCount}`); + } + } + } + + public verifyImportModuleCompletionListIsEmpty(negative: boolean) { + const completions = this.getImportModuleCompletionListAtCaret(); + if ((!completions || completions.length === 0) && negative) { + this.raiseError("Completion list is empty at caret at position " + this.activeFile.fileName + " " + this.currentCaretPosition); + } + else if (completions && completions.length !== 0 && !negative) { + let errorMsg = "\n" + "Completion List contains: [" + completions[0].name; + for (let i = 1; i < completions.length; i++) { + errorMsg += ", " + completions[i].name; + } + errorMsg += "]\n"; + + this.raiseError("Completion list is not empty at caret at position " + this.activeFile.fileName + " " + this.currentCaretPosition + errorMsg); + } + } + public verifyCompletionListStartsWithItemsInOrder(items: string[]): void { if (items.length === 0) { return; @@ -734,6 +766,28 @@ namespace FourSlash { } } + public verifyImportModuleCompletionListContains(symbol: string) { + const completions = this.getImportModuleCompletionListAtCaret(); + if (completions) { + if (!ts.forEach(completions, completion => completion.name === symbol)) { + const itemsString = completions.map(item => stringify({ name: item.name, span: item.span })).join(",\n"); + this.raiseError(`Expected "${symbol}" to be in list [${itemsString}]`); + } + } + else { + this.raiseError(`No import module completions at position '${this.currentCaretPosition}' when looking for '${symbol}'.`); + } + } + + public verifyImportModuleCompletionListDoesNotContain(symbol: string) { + const completions = this.getImportModuleCompletionListAtCaret(); + if (completions) { + if (ts.forEach(completions, completion => completion.name === symbol)) { + this.raiseError(`Import module completion list did contain ${symbol}`); + } + } + } + public verifyCompletionEntryDetails(entryName: string, expectedText: string, expectedDocumentation?: string, kind?: string) { const details = this.getCompletionEntryDetails(entryName); @@ -820,6 +874,10 @@ namespace FourSlash { return this.languageService.getCompletionsAtPosition(this.activeFile.fileName, this.currentCaretPosition); } + private getImportModuleCompletionListAtCaret() { + return this.languageService.getImportModuleCompletionsAtPosition(this.activeFile.fileName, this.currentCaretPosition); + } + private getCompletionEntryDetails(entryName: string) { return this.languageService.getCompletionEntryDetails(this.activeFile.fileName, this.currentCaretPosition, entryName); } @@ -2885,6 +2943,23 @@ namespace FourSlashInterface { this.state.verifyCompletionListItemsCountIsGreaterThan(count, this.negative); } + public importModuleCompletionListContains(symbol: string): void { + if (this.negative) { + this.state.verifyImportModuleCompletionListDoesNotContain(symbol); + } + else { + this.state.verifyImportModuleCompletionListContains(symbol); + } + } + + public importModuleCompletionListItemsCountIsGreaterThan(count: number): void { + this.state.verifyImportModuleCompletionListItemsCountIsGreaterThan(count, this.negative); + } + + public importModuleCompletionListIsEmpty(): void { + this.state.verifyImportModuleCompletionListIsEmpty(this.negative); + } + public assertHasRanges(ranges: FourSlash.Range[]) { assert(ranges.length !== 0, "Array of ranges is expected to be non-empty"); } diff --git a/src/harness/harnessLanguageService.ts b/src/harness/harnessLanguageService.ts index d5491d55ac..1972ab85b9 100644 --- a/src/harness/harnessLanguageService.ts +++ b/src/harness/harnessLanguageService.ts @@ -426,6 +426,9 @@ namespace Harness.LanguageService { getCompletionsAtPosition(fileName: string, position: number): ts.CompletionInfo { return unwrapJSONCallResult(this.shim.getCompletionsAtPosition(fileName, position)); } + getImportModuleCompletionsAtPosition(fileName: string, position: number): ts.ImportCompletionEntry[] { + return unwrapJSONCallResult(this.shim.getImportModuleCompletionsAtPosition(fileName, position)); + } getCompletionEntryDetails(fileName: string, position: number, entryName: string): ts.CompletionEntryDetails { return unwrapJSONCallResult(this.shim.getCompletionEntryDetails(fileName, position, entryName)); } diff --git a/src/server/client.ts b/src/server/client.ts index f04dbd8dc0..04784a77f1 100644 --- a/src/server/client.ts +++ b/src/server/client.ts @@ -220,6 +220,21 @@ namespace ts.server { }; } + getImportModuleCompletionsAtPosition(fileName: string, position: number): ImportCompletionEntry[] { + const lineOffset = this.positionToOneBasedLineOffset(fileName, position); + const args: protocol.CompletionsRequestArgs = { + file: fileName, + line: lineOffset.line, + offset: lineOffset.offset, + prefix: undefined + }; + + const request = this.processRequest(CommandNames.ImportModuleCompletions, args); + const response = this.processResponse(request); + + return response.body; + } + getCompletionEntryDetails(fileName: string, position: number, entryName: string): CompletionEntryDetails { const lineOffset = this.positionToOneBasedLineOffset(fileName, position); const args: protocol.CompletionDetailsRequestArgs = { diff --git a/src/server/protocol.d.ts b/src/server/protocol.d.ts index 6442848abb..803edce0d7 100644 --- a/src/server/protocol.d.ts +++ b/src/server/protocol.d.ts @@ -717,6 +717,16 @@ declare namespace ts.server.protocol { arguments: CompletionsRequestArgs; } + /** + * Import Module Completions request; value of command field is + * "importModuleCompletions". Given a file location (file, line, + * col) return the possible completions for external module + * specifiers or paths given that position refers to a module + * import declaration, require call, or triple slash reference. + */ + export interface ImportModuleCompletionsRequest extends FileLocationRequest { + } + /** * Arguments for completion details request. */ @@ -806,6 +816,10 @@ declare namespace ts.server.protocol { body?: CompletionEntry[]; } + export interface ImportModuleCompletionsResponse extends Response { + body?: ImportCompletionEntry[]; + } + export interface CompletionDetailsResponse extends Response { body?: CompletionEntryDetails[]; } diff --git a/src/server/session.ts b/src/server/session.ts index 7e1ca81e2a..74f6e6bb23 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -103,6 +103,7 @@ namespace ts.server { export const Change = "change"; export const Close = "close"; export const Completions = "completions"; + export const ImportModuleCompletions = "importModuleCompletions"; export const CompletionDetails = "completionEntryDetails"; export const Configure = "configure"; export const Definition = "definition"; diff --git a/src/services/services.ts b/src/services/services.ts index 025afcd154..11eec28a5f 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1208,6 +1208,7 @@ namespace ts { getEncodedSemanticClassifications(fileName: string, span: TextSpan): Classifications; getCompletionsAtPosition(fileName: string, position: number): CompletionInfo; + getImportModuleCompletionsAtPosition(fileName: string, position: number): ImportCompletionEntry[]; getCompletionEntryDetails(fileName: string, position: number, entryName: string): CompletionEntryDetails; getQuickInfoAtPosition(fileName: string, position: number): QuickInfo; @@ -1480,6 +1481,13 @@ namespace ts { sortText: string; } + export interface ImportCompletionEntry { + name: string; + kind: string; // see ScriptElementKind + span: TextSpan; + sortText: string; + } + export interface CompletionEntryDetails { name: string; kind: string; // see ScriptElementKind @@ -4225,10 +4233,6 @@ namespace ts { return getStringLiteralCompletionEntries(sourceFile, position); } - if (isInReferenceComment(sourceFile, position)) { - return getTripleSlashReferenceCompletion(sourceFile, position); - } - const completionData = getCompletionData(fileName, position); if (!completionData) { return undefined; @@ -4394,27 +4398,13 @@ namespace ts { // a['/*completion position*/'] return getStringLiteralCompletionEntriesFromElementAccess(node.parent); } - else if (node.parent.kind === SyntaxKind.ImportDeclaration || isExpressionOfExternalModuleImportEqualsDeclaration(node)) { - // Get all known external module names or complete a path to a module - return getStringLiteralCompletionEntriesFromModuleNames(node); - } else { const argumentInfo = SignatureHelp.getContainingArgumentInfo(node, position, sourceFile); if (argumentInfo) { // Get string literal completions from specialized signatures of the target // i.e. declare function f(a: 'A'); // f("/*completion position*/") - const callExpressionCompletionEntries = getStringLiteralCompletionEntriesFromCallExpression(argumentInfo, node); - if (callExpressionCompletionEntries) { - return callExpressionCompletionEntries; - } - else if (isRequireCall(node.parent, false)) { - // If that failed but this call mataches the signature of a require call, treat the literal as an external module name - return getStringLiteralCompletionEntriesFromModuleNames(node); - } - else { - return undefined; - } + return getStringLiteralCompletionEntriesFromCallExpression(argumentInfo, node); } // Get completion for string literal from string literal type @@ -4500,10 +4490,35 @@ namespace ts { } } } + } + + function getImportModuleCompletionsAtPosition(fileName: string, position: number): ImportCompletionEntry[] { + synchronizeHostData(); + + const sourceFile = getValidSourceFile(fileName); + if (isInReferenceComment(sourceFile, position)) { + return getTripleSlashReferenceCompletion(sourceFile, position); + } + else if (isInString(sourceFile, position)) { + const node = findPrecedingToken(position, sourceFile); + if (!node || node.kind !== SyntaxKind.StringLiteral) { + return undefined; + } + + if (node.parent.kind === SyntaxKind.ImportDeclaration || isExpressionOfExternalModuleImportEqualsDeclaration(node) || isRequireCall(node.parent, false)) { + // Get all known external module names or complete a path to a module + return getStringLiteralCompletionEntriesFromModuleNames(node); + } + } + + return undefined; function getStringLiteralCompletionEntriesFromModuleNames(node: StringLiteral) { const literalValue = node.text; - let result: CompletionEntry[]; + let result: ImportCompletionEntry[]; + + const nodeStart = node.getStart(); + const span: TextSpan = { start: nodeStart, length: nodeStart - node.getEnd() }; const isRelativePath = startsWith(literalValue, "."); const scriptDir = getDirectoryPath(node.getSourceFile().path); @@ -4511,30 +4526,26 @@ namespace ts { const compilerOptions = program.getCompilerOptions(); if (compilerOptions.rootDirs) { result = getCompletionEntriesForDirectoryFragmentWithRootDirs( - compilerOptions.rootDirs, literalValue, scriptDir, getSupportedExtensions(program.getCompilerOptions()), /*includeExtensions*/false); + compilerOptions.rootDirs, literalValue, scriptDir, getSupportedExtensions(program.getCompilerOptions()), /*includeExtensions*/false, span); } else { result = getCompletionEntriesForDirectoryFragment( - literalValue, scriptDir, getSupportedExtensions(program.getCompilerOptions()), /*includeExtensions*/false); + literalValue, scriptDir, getSupportedExtensions(program.getCompilerOptions()), /*includeExtensions*/false, span); } } else { // Check for node modules - result = getCompletionEntriesForNonRelativeModules(literalValue, scriptDir); + result = getCompletionEntriesForNonRelativeModules(literalValue, scriptDir, span); } - return { - isMemberCompletion: false, - isNewIdentifierLocation: true, - entries: result - }; + return result; } /** * Takes a script path and returns paths for all potential folders that could be merged with its * containing folder via the "rootDirs" compiler option */ - function getBaseDirectoriesFromRootDirs(rootDirs: string[], basePath: string, scriptPath: string, ignoreCase: boolean) { + 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))); @@ -4551,26 +4562,25 @@ namespace ts { return deduplicate(map(rootDirs, rootDirectory => combinePaths(rootDirectory, relativeDirectory))); } - function getCompletionEntriesForDirectoryFragmentWithRootDirs(rootDirs: string[], fragment: string, scriptPath: string, extensions: string[], includeExtensions: boolean): CompletionEntry[] { + function getCompletionEntriesForDirectoryFragmentWithRootDirs(rootDirs: string[], fragment: string, scriptPath: string, extensions: string[], includeExtensions: boolean, span: TextSpan): ImportCompletionEntry[] { const basePath = program.getCompilerOptions().project || host.getCurrentDirectory(); const baseDirectories = getBaseDirectoriesFromRootDirs( rootDirs, basePath, scriptPath, host.useCaseSensitiveFileNames && !host.useCaseSensitiveFileNames()); - const result: CompletionEntry[] = []; + const result: ImportCompletionEntry[] = []; for (const baseDirectory of baseDirectories) { - getCompletionEntriesForDirectoryFragment(fragment, baseDirectory, extensions, includeExtensions, result); + getCompletionEntriesForDirectoryFragment(fragment, baseDirectory, extensions, includeExtensions, span, result); } return result; } - function getCompletionEntriesForDirectoryFragment(fragment: string, scriptPath: string, extensions: string[], includeExtensions: boolean, result: CompletionEntry[] = []): CompletionEntry[] { + function getCompletionEntriesForDirectoryFragment(fragment: string, scriptPath: string, extensions: string[], includeExtensions: boolean, span: TextSpan, result: ImportCompletionEntry[] = []): ImportCompletionEntry[] { const toComplete = getBaseFileName(fragment); const absolutePath = normalizeSlashes(host.resolvePath(isRootedDiskPath(fragment) ? fragment : combinePaths(scriptPath, fragment))); const baseDirectory = toComplete ? getDirectoryPath(absolutePath) : absolutePath; - if (directoryProbablyExists(baseDirectory, host)) { // Enumerate the available files const files = host.readDirectory(baseDirectory, extensions, /*exclude*/undefined, /*include*/["./*"]); @@ -4583,8 +4593,8 @@ namespace ts { result.push({ name: fileName, kind: ScriptElementKind.directory, - kindModifiers: ScriptElementKindModifier.none, - sortText: fileName + sortText: fileName, + span }); } }); @@ -4599,8 +4609,8 @@ namespace ts { result.push({ name: ensureTrailingDirectorySeparator(directoryName), kind: ScriptElementKind.directory, - kindModifiers: ScriptElementKindModifier.none, - sortText: directoryName + sortText: directoryName, + span }); } }); @@ -4617,17 +4627,17 @@ namespace ts { * Modules from node_modules (i.e. those listed in package.json) * This includes all files that are found in node_modules/moduleName/ with acceptable file extensions */ - function getCompletionEntriesForNonRelativeModules(fragment: string, scriptPath: string): CompletionEntry[] { + function getCompletionEntriesForNonRelativeModules(fragment: string, scriptPath: string, span: TextSpan): ImportCompletionEntry[] { const options = program.getCompilerOptions(); const { baseUrl, paths } = options; - let result: CompletionEntry[]; + let result: ImportCompletionEntry[]; if (baseUrl) { const fileExtensions = getSupportedExtensions(options); const projectDir = options.project || host.getCurrentDirectory(); const absolute = isRootedDiskPath(baseUrl) ? baseUrl : combinePaths(projectDir, baseUrl); - result = getCompletionEntriesForDirectoryFragment(fragment, normalizePath(absolute), fileExtensions, /*includeExtensions*/false); + result = getCompletionEntriesForDirectoryFragment(fragment, normalizePath(absolute), fileExtensions, /*includeExtensions*/false, span); if (paths) { for (const path in paths) { @@ -4636,7 +4646,7 @@ namespace ts { if (paths[path]) { forEach(paths[path], pattern => { forEach(getModulesForPathsPattern(fragment, baseUrl, pattern, fileExtensions), match => { - result.push(createCompletionEntryForModule(match, ScriptElementKind.externalModuleName)); + result.push(createCompletionEntryForModule(match, ScriptElementKind.externalModuleName, span)); }); }); } @@ -4644,7 +4654,7 @@ namespace ts { else if (startsWith(path, fragment)) { const entry = paths[path] && paths[path].length === 1 && paths[path][0]; if (entry) { - result.push(createCompletionEntryForModule(path, ScriptElementKind.externalModuleName)); + result.push(createCompletionEntryForModule(path, ScriptElementKind.externalModuleName, span)); } } } @@ -4655,10 +4665,10 @@ namespace ts { result = []; } - getCompletionEntriesFromTypings(host, options, scriptPath, result); + getCompletionEntriesFromTypings(host, options, scriptPath, span, result); forEach(enumeratePotentialNonRelativeModules(fragment, scriptPath, options), moduleName => { - result.push(createCompletionEntryForModule(moduleName, ScriptElementKind.externalModuleName)); + result.push(createCompletionEntryForModule(moduleName, ScriptElementKind.externalModuleName, span)); }); return result; @@ -4757,12 +4767,14 @@ namespace ts { return deduplicate(nonRelativeModules); } - function getTripleSlashReferenceCompletion(sourceFile: SourceFile, position: number) { + function getTripleSlashReferenceCompletion(sourceFile: SourceFile, position: number): ImportCompletionEntry[] { const node = getTokenAtPosition(sourceFile, position); if (!node) { return undefined; } + const span: TextSpan = undefined; + const text = sourceFile.text.substr(node.pos, position); const match = tripleSlashDirectiveFragmentRegex.exec(text); if (match) { @@ -4771,174 +4783,161 @@ namespace ts { const scriptPath = getDirectoryPath(sourceFile.path); if (kind === "path") { // Give completions for a relative path - return { - isMemberCompletion: false, - isNewIdentifierLocation: false, - entries: getCompletionEntriesForDirectoryFragment(fragment, scriptPath, getSupportedExtensions(program.getCompilerOptions()), /*includeExtensions*/true) - }; + return getCompletionEntriesForDirectoryFragment(fragment, scriptPath, getSupportedExtensions(program.getCompilerOptions()), /*includeExtensions*/true, span); } else { // Give completions based on the typings available - return { - isMemberCompletion: false, - isNewIdentifierLocation: false, - entries: getCompletionEntriesFromTypings(host, program.getCompilerOptions(), scriptPath) - }; + return getCompletionEntriesFromTypings(host, program.getCompilerOptions(), scriptPath, span); } } return undefined; } - } - function getCompletionEntriesFromTypings(host: LanguageServiceHost, options: CompilerOptions, scriptPath: string, result: CompletionEntry[] = []): CompletionEntry[] { - // Check for typings specified in compiler options - if (options.types) { - forEach(options.types, moduleName => { - result.push(createCompletionEntryForModule(moduleName, ScriptElementKind.externalModuleName)); - }); - } - else if (host.getDirectories && options.typeRoots) { - const absoluteRoots = map(options.typeRoots, rootDirectory => getAbsoluteProjectPath(rootDirectory, host, options.project)); - forEach(absoluteRoots, absoluteRoot => getCompletionEntriesFromDirectories(host, options, absoluteRoot, result)); + function getCompletionEntriesFromTypings(host: LanguageServiceHost, options: CompilerOptions, scriptPath: string, span: TextSpan, result: ImportCompletionEntry[] = []): ImportCompletionEntry[] { + // Check for typings specified in compiler options + if (options.types) { + forEach(options.types, moduleName => { + result.push(createCompletionEntryForModule(moduleName, ScriptElementKind.externalModuleName, span)); + }); + } + else if (host.getDirectories && options.typeRoots) { + const absoluteRoots = map(options.typeRoots, rootDirectory => getAbsoluteProjectPath(rootDirectory, host, options.project)); + forEach(absoluteRoots, absoluteRoot => getCompletionEntriesFromDirectories(host, options, absoluteRoot, span, result)); + } + + if (host.getDirectories) { + // Also get all @types typings installed in visible node_modules directories + forEach(findPackageJsons(scriptPath), package => { + const typesDir = combinePaths(getDirectoryPath(package), "node_modules/@types"); + getCompletionEntriesFromDirectories(host, options, typesDir, span, result); + }); + } + + return result; } - if (host.getDirectories) { - // Also get all @types typings installed in visible node_modules directories - forEach(findPackageJsons(scriptPath), package => { - const typesDir = combinePaths(getDirectoryPath(package), "node_modules/@types"); - getCompletionEntriesFromDirectories(host, options, typesDir, result); - }); + function getAbsoluteProjectPath(path: string, host: LanguageServiceHost, projectDir?: string) { + if (isRootedDiskPath(path)) { + return normalizePath(path); + } + + if (projectDir) { + return normalizePath(combinePaths(projectDir, path)); + } + + return normalizePath(host.resolvePath(path)); } - return result; - } - - function getAbsoluteProjectPath(path: string, host: LanguageServiceHost, projectDir?: string) { - if (isRootedDiskPath(path)) { - return normalizePath(path); + function getCompletionEntriesFromDirectories(host: LanguageServiceHost, options: CompilerOptions, directory: string, span: TextSpan, result: ImportCompletionEntry[]) { + if (host.getDirectories && directoryProbablyExists(directory, host)) { + forEach(host.getDirectories(directory), typeDirectory => { + result.push(createCompletionEntryForModule(getBaseFileName(typeDirectory), ScriptElementKind.externalModuleName, span)); + }); + } } - if (projectDir) { - return normalizePath(combinePaths(projectDir, path)); - } + function findPackageJsons(currentDir: string): string[] { + const paths: string[] = []; + let currentConfigPath: string; + while (true) { + currentConfigPath = findConfigFile(currentDir, (f) => host.fileExists(f), "package.json"); + if (currentConfigPath) { + paths.push(currentConfigPath); - return normalizePath(host.resolvePath(path)); - } - - function getCompletionEntriesFromDirectories(host: LanguageServiceHost, options: CompilerOptions, directory: string, result: CompletionEntry[]) { - if (host.getDirectories && directoryProbablyExists(directory, host)) { - forEach(host.getDirectories(directory), typeDirectory => { - result.push(createCompletionEntryForModule(getBaseFileName(typeDirectory), ScriptElementKind.externalModuleName)); - }); - } - } - - function findPackageJsons(currentDir: string): string[] { - const paths: string[] = []; - let currentConfigPath: string; - while (true) { - currentConfigPath = findConfigFile(currentDir, (f) => host.fileExists(f), "package.json"); - if (currentConfigPath) { - paths.push(currentConfigPath); - - currentDir = getDirectoryPath(currentConfigPath); - const parent = getDirectoryPath(currentDir); - if (currentDir === parent) { + currentDir = getDirectoryPath(currentConfigPath); + const parent = getDirectoryPath(currentDir); + if (currentDir === parent) { + break; + } + currentDir = parent; + } + else { break; } - currentDir = parent; - } - else { - break; } + + return paths; } - return paths; - } + function enumerateNodeModulesVisibleToScript(host: LanguageServiceHost, scriptPath: string, modulePrefix?: string) { + const result: VisibleModuleInfo[] = []; + findPackageJsons(scriptPath).forEach((packageJson) => { + const package = tryReadingPackageJson(packageJson); + if (!package) { + return; + } - function enumerateNodeModulesVisibleToScript(host: LanguageServiceHost, scriptPath: string, modulePrefix?: string) { - const result: VisibleModuleInfo[] = []; - findPackageJsons(scriptPath).forEach((packageJson) => { - const package = tryReadingPackageJson(packageJson); - if (!package) { - return; - } + const nodeModulesDir = combinePaths(getDirectoryPath(packageJson), "node_modules"); + const foundModuleNames: string[] = []; - const nodeModulesDir = combinePaths(getDirectoryPath(packageJson), "node_modules"); - const foundModuleNames: string[] = []; + if (package.dependencies) { + addPotentialPackageNames(package.dependencies, modulePrefix, foundModuleNames); + } + if (package.devDependencies) { + addPotentialPackageNames(package.devDependencies, modulePrefix, foundModuleNames); + } - if (package.dependencies) { - addPotentialPackageNames(package.dependencies, modulePrefix, foundModuleNames); - } - if (package.devDependencies) { - addPotentialPackageNames(package.devDependencies, modulePrefix, foundModuleNames); - } - - forEach(foundModuleNames, (moduleName) => { - const moduleDir = combinePaths(nodeModulesDir, moduleName); - result.push({ - moduleName, - moduleDir, - canBeImported: moduleCanBeImported(moduleDir) + forEach(foundModuleNames, (moduleName) => { + const moduleDir = combinePaths(nodeModulesDir, moduleName); + result.push({ + moduleName, + moduleDir, + canBeImported: moduleCanBeImported(moduleDir) + }); }); }); - }); - return result; + return result; - function tryReadingPackageJson(filePath: string) { - try { - const fileText = host.readFile(filePath); - return JSON.parse(fileText); - } - catch (e) { - return undefined; - } - } - - function addPotentialPackageNames(dependencies: any, prefix: string, result: string[]) { - for (const dep in dependencies) { - if (dependencies.hasOwnProperty(dep) && (!prefix || startsWith(dep, prefix))) { - result.push(dep); + function tryReadingPackageJson(filePath: string) { + try { + const fileText = host.readFile(filePath); + return JSON.parse(fileText); + } + catch (e) { + return undefined; } } - } - /* - * A module can be imported by name alone if one of the following is true: - * It defines the "typings" property in its package.json - * The module has a "main" export and an index.d.ts file - * The module has an index.ts - */ - function moduleCanBeImported(modulePath: string): boolean { - const packagePath = combinePaths(modulePath, "package.json"); - - let hasMainExport = false; - if (host.fileExists(packagePath)) { - const package = tryReadingPackageJson(packagePath); - if (package) { - if (package.typings) { - return true; + function addPotentialPackageNames(dependencies: any, prefix: string, result: string[]) { + for (const dep in dependencies) { + if (dependencies.hasOwnProperty(dep) && (!prefix || startsWith(dep, prefix))) { + result.push(dep); } - hasMainExport = !!package.main; } } - hasMainExport = hasMainExport || host.fileExists(combinePaths(modulePath, "index.js")); + /* + * A module can be imported by name alone if one of the following is true: + * It defines the "typings" property in its package.json + * The module has a "main" export and an index.d.ts file + * The module has an index.ts + */ + function moduleCanBeImported(modulePath: string): boolean { + const packagePath = combinePaths(modulePath, "package.json"); - return (hasMainExport && host.fileExists(combinePaths(modulePath, "index.d.ts"))) || host.fileExists(combinePaths(modulePath, "index.ts")); + let hasMainExport = false; + if (host.fileExists(packagePath)) { + const package = tryReadingPackageJson(packagePath); + if (package) { + if (package.typings) { + return true; + } + hasMainExport = !!package.main; + } + } + + hasMainExport = hasMainExport || host.fileExists(combinePaths(modulePath, "index.js")); + + return (hasMainExport && host.fileExists(combinePaths(modulePath, "index.d.ts"))) || host.fileExists(combinePaths(modulePath, "index.ts")); + } } - } - function createCompletionEntryForModule(name: string, kind: string): CompletionEntry { - return { - name, - kind, - kindModifiers: ScriptElementKindModifier.none, - sortText: name - }; + function createCompletionEntryForModule(name: string, kind: string, span: TextSpan): ImportCompletionEntry { + return { name, kind, sortText: name, span }; + } } function getCompletionEntryDetails(fileName: string, position: number, entryName: string): CompletionEntryDetails { @@ -8752,6 +8751,7 @@ namespace ts { getEncodedSyntacticClassifications, getEncodedSemanticClassifications, getCompletionsAtPosition, + getImportModuleCompletionsAtPosition, getCompletionEntryDetails, getSignatureHelpItems, getQuickInfoAtPosition, diff --git a/src/services/shims.ts b/src/services/shims.ts index ca12733e0a..eb6c5e55b7 100644 --- a/src/services/shims.ts +++ b/src/services/shims.ts @@ -131,6 +131,7 @@ namespace ts { getEncodedSemanticClassifications(fileName: string, start: number, length: number): string; getCompletionsAtPosition(fileName: string, position: number): string; + getImportModuleCompletionsAtPosition(fileName: string, position: number): string; getCompletionEntryDetails(fileName: string, position: number, entryName: string): string; getQuickInfoAtPosition(fileName: string, position: number): string; @@ -870,6 +871,13 @@ namespace ts { ); } + getImportModuleCompletionsAtPosition(fileName: string, position: number): string { + return this.forwardJSONCall( + `getImportModuleCompletionsAtPosition('${fileName}', ${position})`, + () => this.languageService.getCompletionsAtPosition(fileName, position) + ); + } + /** Get a string based representation of a completion list entry details */ public getCompletionEntryDetails(fileName: string, position: number, entryName: string) { return this.forwardJSONCall( diff --git a/tests/cases/fourslash/completionForStringLiteralNonrelativeImport1.ts b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport1.ts index 5c7b2016a7..f8ea68a271 100644 --- a/tests/cases/fourslash/completionForStringLiteralNonrelativeImport1.ts +++ b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport1.ts @@ -43,19 +43,19 @@ const kinds = ["import_as", "import_equals", "require"]; for (const kind of kinds) { goTo.marker(kind + "0"); - verify.completionListContains("fake-module"); - verify.completionListContains("fake-module-dev"); - verify.not.completionListItemsCountIsGreaterThan(2); + verify.importModuleCompletionListContains("fake-module"); + verify.importModuleCompletionListContains("fake-module-dev"); + verify.not.importModuleCompletionListItemsCountIsGreaterThan(2); goTo.marker(kind + "1"); - verify.completionListContains("index"); - verify.completionListContains("ts"); - verify.completionListContains("dts"); - verify.completionListContains("tsx"); - verify.not.completionListItemsCountIsGreaterThan(4); + verify.importModuleCompletionListContains("index"); + verify.importModuleCompletionListContains("ts"); + verify.importModuleCompletionListContains("dts"); + verify.importModuleCompletionListContains("tsx"); + verify.not.importModuleCompletionListItemsCountIsGreaterThan(4); goTo.marker(kind + "2"); - verify.completionListContains("fake-module"); - verify.completionListContains("fake-module-dev"); - verify.not.completionListItemsCountIsGreaterThan(2); + verify.importModuleCompletionListContains("fake-module"); + verify.importModuleCompletionListContains("fake-module-dev"); + verify.not.importModuleCompletionListItemsCountIsGreaterThan(2); } \ No newline at end of file diff --git a/tests/cases/fourslash/completionForStringLiteralNonrelativeImport10.ts b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport10.ts index cb1f67f634..393ffed0c5 100644 --- a/tests/cases/fourslash/completionForStringLiteralNonrelativeImport10.ts +++ b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport10.ts @@ -26,8 +26,8 @@ const kinds = ["import_as", "import_equals", "require"]; for (const kind of kinds) { goTo.marker(kind + "0"); - verify.completionListIsEmpty(); + verify.importModuleCompletionListIsEmpty(); goTo.marker(kind + "1"); - verify.completionListIsEmpty(); + verify.importModuleCompletionListIsEmpty(); } \ No newline at end of file diff --git a/tests/cases/fourslash/completionForStringLiteralNonrelativeImport2.ts b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport2.ts index 11420fe4f3..08b92b0529 100644 --- a/tests/cases/fourslash/completionForStringLiteralNonrelativeImport2.ts +++ b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport2.ts @@ -29,7 +29,7 @@ const kinds = ["import_as", "import_equals", "require"]; for (const kind of kinds) { goTo.marker(kind + "0"); - verify.completionListContains("repeated"); - verify.completionListContains("other"); - verify.not.completionListItemsCountIsGreaterThan(2); + verify.importModuleCompletionListContains("repeated"); + verify.importModuleCompletionListContains("other"); + verify.not.importModuleCompletionListItemsCountIsGreaterThan(2); } diff --git a/tests/cases/fourslash/completionForStringLiteralNonrelativeImport3.ts b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport3.ts index aec9f8411b..9d9beac16e 100644 --- a/tests/cases/fourslash/completionForStringLiteralNonrelativeImport3.ts +++ b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport3.ts @@ -28,8 +28,8 @@ const kinds = ["import_as", "import_equals", "require"]; for (const kind of kinds) { goTo.marker(kind + "0"); - verify.completionListContains("ts"); - verify.completionListContains("tsx"); - verify.completionListContains("dts"); - verify.not.completionListItemsCountIsGreaterThan(3); + verify.importModuleCompletionListContains("ts"); + verify.importModuleCompletionListContains("tsx"); + verify.importModuleCompletionListContains("dts"); + verify.not.importModuleCompletionListItemsCountIsGreaterThan(3); } diff --git a/tests/cases/fourslash/completionForStringLiteralNonrelativeImport4.ts b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport4.ts index 5a96e4ee63..2817075445 100644 --- a/tests/cases/fourslash/completionForStringLiteralNonrelativeImport4.ts +++ b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport4.ts @@ -33,20 +33,20 @@ const kinds = ["import_as", "import_equals", "require"]; for (const kind of kinds) { goTo.marker(kind + "0"); - verify.completionListContains("fake-module/"); - verify.completionListContains("fake-module2"); - verify.completionListContains("fake-module3/"); - verify.not.completionListItemsCountIsGreaterThan(3); + verify.importModuleCompletionListContains("fake-module/"); + verify.importModuleCompletionListContains("fake-module2"); + verify.importModuleCompletionListContains("fake-module3/"); + verify.not.importModuleCompletionListItemsCountIsGreaterThan(3); goTo.marker(kind + "1"); - verify.completionListContains("ambient-module-test"); - verify.not.completionListItemsCountIsGreaterThan(1); + verify.importModuleCompletionListContains("ambient-module-test"); + verify.not.importModuleCompletionListItemsCountIsGreaterThan(1); goTo.marker(kind + "2"); - verify.completionListContains("fake-module/"); - verify.completionListContains("fake-module2"); - verify.completionListContains("fake-module3/"); - verify.not.completionListItemsCountIsGreaterThan(3); + verify.importModuleCompletionListContains("fake-module/"); + verify.importModuleCompletionListContains("fake-module2"); + verify.importModuleCompletionListContains("fake-module3/"); + verify.not.importModuleCompletionListItemsCountIsGreaterThan(3); } \ No newline at end of file diff --git a/tests/cases/fourslash/completionForStringLiteralNonrelativeImport5.ts b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport5.ts index 2e172e3578..9e0c756a91 100644 --- a/tests/cases/fourslash/completionForStringLiteralNonrelativeImport5.ts +++ b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport5.ts @@ -24,14 +24,14 @@ const kinds = ["import_as", "import_equals", "require"]; for (const kind of kinds) { goTo.marker(kind + "0"); - verify.completionListContains("ambientModule"); - verify.completionListContains("otherAmbientModule"); - verify.completionListContains("otherOtherAmbientModule"); - verify.not.completionListItemsCountIsGreaterThan(3); + verify.importModuleCompletionListContains("ambientModule"); + verify.importModuleCompletionListContains("otherAmbientModule"); + verify.importModuleCompletionListContains("otherOtherAmbientModule"); + verify.not.importModuleCompletionListItemsCountIsGreaterThan(3); goTo.marker(kind + "1"); - verify.completionListContains("ambientModule"); - verify.not.completionListItemsCountIsGreaterThan(1); + verify.importModuleCompletionListContains("ambientModule"); + verify.not.importModuleCompletionListItemsCountIsGreaterThan(1); } diff --git a/tests/cases/fourslash/completionForStringLiteralNonrelativeImport6.ts b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport6.ts index bfd0539e14..263b57d9d2 100644 --- a/tests/cases/fourslash/completionForStringLiteralNonrelativeImport6.ts +++ b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport6.ts @@ -52,11 +52,11 @@ const kinds = ["import_as", "import_equals", "require"]; for (const kind of kinds) { goTo.marker(kind + "0"); - verify.completionListContains("module-no-main/"); - verify.completionListContains("module-no-main-index-d-ts/"); - verify.completionListContains("module-index-ts"); - verify.completionListContains("module-index-d-ts-explicit-main"); - verify.completionListContains("module-index-d-ts-default-main"); - verify.completionListContains("module-typings"); - verify.not.completionListItemsCountIsGreaterThan(6); + verify.importModuleCompletionListContains("module-no-main/"); + verify.importModuleCompletionListContains("module-no-main-index-d-ts/"); + verify.importModuleCompletionListContains("module-index-ts"); + verify.importModuleCompletionListContains("module-index-d-ts-explicit-main"); + verify.importModuleCompletionListContains("module-index-d-ts-default-main"); + verify.importModuleCompletionListContains("module-typings"); + verify.not.importModuleCompletionListItemsCountIsGreaterThan(6); } diff --git a/tests/cases/fourslash/completionForStringLiteralNonrelativeImport7.ts b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport7.ts index 35e144c718..d08ebd17b7 100644 --- a/tests/cases/fourslash/completionForStringLiteralNonrelativeImport7.ts +++ b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport7.ts @@ -21,7 +21,7 @@ const kinds = ["import_as", "import_equals", "require"]; for (const kind of kinds) { goTo.marker(kind + "0"); - verify.completionListContains("module"); - verify.completionListContains("module-from-node"); - verify.not.completionListItemsCountIsGreaterThan(2); + verify.importModuleCompletionListContains("module"); + verify.importModuleCompletionListContains("module-from-node"); + verify.not.importModuleCompletionListItemsCountIsGreaterThan(2); } diff --git a/tests/cases/fourslash/completionForStringLiteralNonrelativeImport8.ts b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport8.ts index 0e8da4b02b..b7e745f814 100644 --- a/tests/cases/fourslash/completionForStringLiteralNonrelativeImport8.ts +++ b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport8.ts @@ -43,11 +43,11 @@ const kinds = ["import_as", "import_equals", "require"]; for (const kind of kinds) { goTo.marker(kind + "0"); - verify.completionListContains("0test"); + verify.importModuleCompletionListContains("0test"); goTo.marker(kind + "1"); - verify.completionListContains("1test"); + verify.importModuleCompletionListContains("1test"); goTo.marker(kind + "2"); - verify.completionListContains("2test"); + verify.importModuleCompletionListContains("2test"); } diff --git a/tests/cases/fourslash/completionForStringLiteralNonrelativeImport9.ts b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport9.ts index 3c32ec2670..31984df908 100644 --- a/tests/cases/fourslash/completionForStringLiteralNonrelativeImport9.ts +++ b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport9.ts @@ -28,7 +28,7 @@ const kinds = ["import_as", "import_equals", "require"]; for (const kind of kinds) { goTo.marker(kind + "0"); - verify.completionListContains("module1"); - verify.completionListContains("module2"); - verify.not.completionListItemsCountIsGreaterThan(2); + verify.importModuleCompletionListContains("module1"); + verify.importModuleCompletionListContains("module2"); + verify.not.importModuleCompletionListItemsCountIsGreaterThan(2); } diff --git a/tests/cases/fourslash/completionForStringLiteralNonrelativeImportTypings1.ts b/tests/cases/fourslash/completionForStringLiteralNonrelativeImportTypings1.ts index edce0ce7e9..1b77c57f5c 100644 --- a/tests/cases/fourslash/completionForStringLiteralNonrelativeImportTypings1.ts +++ b/tests/cases/fourslash/completionForStringLiteralNonrelativeImportTypings1.ts @@ -26,8 +26,8 @@ const kinds = ["types_ref", "import_as", "import_equals", "require"]; for (const kind of kinds) { goTo.marker(kind + "0"); - verify.completionListContains("module-x"); - verify.completionListContains("module-y"); - verify.completionListContains("module-z"); - verify.not.completionListItemsCountIsGreaterThan(3); + verify.importModuleCompletionListContains("module-x"); + verify.importModuleCompletionListContains("module-y"); + verify.importModuleCompletionListContains("module-z"); + verify.not.importModuleCompletionListItemsCountIsGreaterThan(3); } diff --git a/tests/cases/fourslash/completionForStringLiteralNonrelativeImportTypings2.ts b/tests/cases/fourslash/completionForStringLiteralNonrelativeImportTypings2.ts index 541bb88c07..3e991b3d4b 100644 --- a/tests/cases/fourslash/completionForStringLiteralNonrelativeImportTypings2.ts +++ b/tests/cases/fourslash/completionForStringLiteralNonrelativeImportTypings2.ts @@ -24,7 +24,7 @@ const kinds = ["types_ref", "import_as", "import_equals", "require"]; for (const kind of kinds) { goTo.marker(kind + "0"); - verify.completionListContains("module-x"); - verify.completionListContains("module-z"); - verify.not.completionListItemsCountIsGreaterThan(2); + verify.importModuleCompletionListContains("module-x"); + verify.importModuleCompletionListContains("module-z"); + verify.not.importModuleCompletionListItemsCountIsGreaterThan(2); } diff --git a/tests/cases/fourslash/completionForStringLiteralNonrelativeImportTypings3.ts b/tests/cases/fourslash/completionForStringLiteralNonrelativeImportTypings3.ts index 7a7d4e00df..add1238cef 100644 --- a/tests/cases/fourslash/completionForStringLiteralNonrelativeImportTypings3.ts +++ b/tests/cases/fourslash/completionForStringLiteralNonrelativeImportTypings3.ts @@ -20,7 +20,7 @@ const kinds = ["types_ref", "import_as", "import_equals", "require"]; for (const kind of kinds) { goTo.marker(kind + "0"); - verify.completionListContains("module-x"); - verify.completionListContains("module-y"); - verify.not.completionListItemsCountIsGreaterThan(2); + verify.importModuleCompletionListContains("module-x"); + verify.importModuleCompletionListContains("module-y"); + verify.not.importModuleCompletionListItemsCountIsGreaterThan(2); } diff --git a/tests/cases/fourslash/completionForStringLiteralRelativeImport1.ts b/tests/cases/fourslash/completionForStringLiteralRelativeImport1.ts index bb9d6a59c7..f3607d70c2 100644 --- a/tests/cases/fourslash/completionForStringLiteralRelativeImport1.ts +++ b/tests/cases/fourslash/completionForStringLiteralRelativeImport1.ts @@ -52,33 +52,33 @@ const kinds = ["import_as", "import_equals", "require"]; for (const kind of kinds) { goTo.marker(kind + "0"); - verify.completionListIsEmpty(); + verify.importModuleCompletionListIsEmpty(); goTo.marker(kind + "1"); - verify.completionListContains("f1"); - verify.completionListContains("f2"); - verify.completionListContains("e1"); - verify.completionListContains("test0"); - verify.completionListContains("folder/"); - verify.completionListContains("parentTest/"); - verify.not.completionListItemsCountIsGreaterThan(6); + verify.importModuleCompletionListContains("f1"); + verify.importModuleCompletionListContains("f2"); + verify.importModuleCompletionListContains("e1"); + verify.importModuleCompletionListContains("test0"); + verify.importModuleCompletionListContains("folder/"); + verify.importModuleCompletionListContains("parentTest/"); + verify.not.importModuleCompletionListItemsCountIsGreaterThan(6); goTo.marker(kind + "2"); - verify.completionListContains("f1"); - verify.completionListContains("f2"); - verify.completionListContains("folder/"); - verify.not.completionListItemsCountIsGreaterThan(3); + verify.importModuleCompletionListContains("f1"); + verify.importModuleCompletionListContains("f2"); + verify.importModuleCompletionListContains("folder/"); + verify.not.importModuleCompletionListItemsCountIsGreaterThan(3); goTo.marker(kind + "3"); - verify.completionListContains("f1"); - verify.completionListContains("h1"); - verify.not.completionListItemsCountIsGreaterThan(2); + verify.importModuleCompletionListContains("f1"); + verify.importModuleCompletionListContains("h1"); + verify.not.importModuleCompletionListItemsCountIsGreaterThan(2); goTo.marker(kind + "4"); - verify.completionListContains("h1"); - verify.not.completionListItemsCountIsGreaterThan(1); + verify.importModuleCompletionListContains("h1"); + verify.not.importModuleCompletionListItemsCountIsGreaterThan(1); goTo.marker(kind + "5"); - verify.completionListContains("g1"); - verify.not.completionListItemsCountIsGreaterThan(1); + verify.importModuleCompletionListContains("g1"); + verify.not.importModuleCompletionListItemsCountIsGreaterThan(1); } \ No newline at end of file diff --git a/tests/cases/fourslash/completionForStringLiteralRelativeImport2.ts b/tests/cases/fourslash/completionForStringLiteralRelativeImport2.ts index 4e87914846..eeb24d591f 100644 --- a/tests/cases/fourslash/completionForStringLiteralRelativeImport2.ts +++ b/tests/cases/fourslash/completionForStringLiteralRelativeImport2.ts @@ -31,19 +31,19 @@ const kinds = ["import_as", "import_equals", "require"]; for (const kind of kinds) { goTo.marker(kind + "0"); - verify.completionListContains("f1"); - verify.completionListContains("f2"); - verify.completionListContains("f3"); - verify.completionListContains("f4"); - verify.completionListContains("e1"); - verify.completionListContains("e2"); - verify.completionListContains("test0"); - verify.not.completionListItemsCountIsGreaterThan(7); + verify.importModuleCompletionListContains("f1"); + verify.importModuleCompletionListContains("f2"); + verify.importModuleCompletionListContains("f3"); + verify.importModuleCompletionListContains("f4"); + verify.importModuleCompletionListContains("e1"); + verify.importModuleCompletionListContains("e2"); + verify.importModuleCompletionListContains("test0"); + verify.not.importModuleCompletionListItemsCountIsGreaterThan(7); goTo.marker(kind + "1"); - verify.completionListContains("f1"); - verify.completionListContains("f2"); - verify.completionListContains("f3"); - verify.completionListContains("f4"); - verify.not.completionListItemsCountIsGreaterThan(4); + verify.importModuleCompletionListContains("f1"); + verify.importModuleCompletionListContains("f2"); + verify.importModuleCompletionListContains("f3"); + verify.importModuleCompletionListContains("f4"); + verify.not.importModuleCompletionListItemsCountIsGreaterThan(4); } \ No newline at end of file diff --git a/tests/cases/fourslash/completionForStringLiteralRelativeImport3.ts b/tests/cases/fourslash/completionForStringLiteralRelativeImport3.ts index 426eb3c231..4f35612c5c 100644 --- a/tests/cases/fourslash/completionForStringLiteralRelativeImport3.ts +++ b/tests/cases/fourslash/completionForStringLiteralRelativeImport3.ts @@ -32,18 +32,18 @@ const kinds = ["import_as", "import_equals", "require"]; for (const kind of kinds) { goTo.marker(kind + "0"); - verify.completionListContains("fourslash/"); - verify.not.completionListItemsCountIsGreaterThan(1); + verify.importModuleCompletionListContains("fourslash/"); + verify.not.importModuleCompletionListItemsCountIsGreaterThan(1); goTo.marker(kind + "1"); - verify.completionListContains("fourslash/"); - verify.not.completionListItemsCountIsGreaterThan(1); + verify.importModuleCompletionListContains("fourslash/"); + verify.not.importModuleCompletionListItemsCountIsGreaterThan(1); goTo.marker(kind + "2"); - verify.completionListContains("f1"); - verify.completionListContains("f2"); - verify.completionListContains("e1"); - verify.completionListContains("folder/"); - verify.completionListContains("tests/"); - verify.not.completionListItemsCountIsGreaterThan(5); + verify.importModuleCompletionListContains("f1"); + verify.importModuleCompletionListContains("f2"); + verify.importModuleCompletionListContains("e1"); + verify.importModuleCompletionListContains("folder/"); + verify.importModuleCompletionListContains("tests/"); + verify.not.importModuleCompletionListItemsCountIsGreaterThan(5); } \ No newline at end of file diff --git a/tests/cases/fourslash/completionForStringLiteralRelativeImport4.ts b/tests/cases/fourslash/completionForStringLiteralRelativeImport4.ts index 1895fff851..5f4d7bb7fc 100644 --- a/tests/cases/fourslash/completionForStringLiteralRelativeImport4.ts +++ b/tests/cases/fourslash/completionForStringLiteralRelativeImport4.ts @@ -40,9 +40,9 @@ const kinds = ["import_as", "import_equals", "require"]; for (const kind of kinds) { goTo.marker(kind + "0"); - verify.completionListContains("module0"); - verify.completionListContains("module1"); - verify.completionListContains("module2"); - verify.completionListContains("more/"); - verify.not.completionListItemsCountIsGreaterThan(4); + verify.importModuleCompletionListContains("module0"); + verify.importModuleCompletionListContains("module1"); + verify.importModuleCompletionListContains("module2"); + verify.importModuleCompletionListContains("more/"); + verify.not.importModuleCompletionListItemsCountIsGreaterThan(4); } \ No newline at end of file diff --git a/tests/cases/fourslash/completionForTripleSlashReference1.ts b/tests/cases/fourslash/completionForTripleSlashReference1.ts index 96582be173..284d5ff66a 100644 --- a/tests/cases/fourslash/completionForTripleSlashReference1.ts +++ b/tests/cases/fourslash/completionForTripleSlashReference1.ts @@ -42,38 +42,38 @@ //// /*parentg1*/ goTo.marker("0"); -verify.completionListIsEmpty(); +verify.importModuleCompletionListIsEmpty(); goTo.marker("1"); -verify.completionListContains("f1.ts"); -verify.completionListContains("f1.d.ts"); -verify.completionListContains("f2.tsx"); -verify.completionListContains("e1.ts"); -verify.completionListContains("test0.ts"); -verify.completionListContains("test1.ts"); -verify.completionListContains("test2.ts"); -verify.completionListContains("test3.ts"); -verify.completionListContains("test4.ts"); -verify.completionListContains("folder/"); -verify.completionListContains("parentTest/"); -verify.not.completionListItemsCountIsGreaterThan(11); +verify.importModuleCompletionListContains("f1.ts"); +verify.importModuleCompletionListContains("f1.d.ts"); +verify.importModuleCompletionListContains("f2.tsx"); +verify.importModuleCompletionListContains("e1.ts"); +verify.importModuleCompletionListContains("test0.ts"); +verify.importModuleCompletionListContains("test1.ts"); +verify.importModuleCompletionListContains("test2.ts"); +verify.importModuleCompletionListContains("test3.ts"); +verify.importModuleCompletionListContains("test4.ts"); +verify.importModuleCompletionListContains("folder/"); +verify.importModuleCompletionListContains("parentTest/"); +verify.not.importModuleCompletionListItemsCountIsGreaterThan(11); goTo.marker("2"); -verify.completionListContains("f1.ts"); -verify.completionListContains("f1.d.ts"); -verify.completionListContains("f2.tsx"); -verify.completionListContains("folder/"); -verify.not.completionListItemsCountIsGreaterThan(4); +verify.importModuleCompletionListContains("f1.ts"); +verify.importModuleCompletionListContains("f1.d.ts"); +verify.importModuleCompletionListContains("f2.tsx"); +verify.importModuleCompletionListContains("folder/"); +verify.not.importModuleCompletionListItemsCountIsGreaterThan(4); goTo.marker("3"); -verify.completionListContains("f1.ts"); -verify.completionListContains("h1.ts"); -verify.not.completionListItemsCountIsGreaterThan(2); +verify.importModuleCompletionListContains("f1.ts"); +verify.importModuleCompletionListContains("h1.ts"); +verify.not.importModuleCompletionListItemsCountIsGreaterThan(2); goTo.marker("4"); -verify.completionListContains("h1.ts"); -verify.not.completionListItemsCountIsGreaterThan(1); +verify.importModuleCompletionListContains("h1.ts"); +verify.not.importModuleCompletionListItemsCountIsGreaterThan(1); goTo.marker("5"); -verify.completionListContains("g1.ts"); -verify.not.completionListItemsCountIsGreaterThan(1); \ No newline at end of file +verify.importModuleCompletionListContains("g1.ts"); +verify.not.importModuleCompletionListItemsCountIsGreaterThan(1); \ No newline at end of file diff --git a/tests/cases/fourslash/completionForTripleSlashReference2.ts b/tests/cases/fourslash/completionForTripleSlashReference2.ts index 69a9e98810..8296b851ac 100644 --- a/tests/cases/fourslash/completionForTripleSlashReference2.ts +++ b/tests/cases/fourslash/completionForTripleSlashReference2.ts @@ -25,23 +25,23 @@ //// /*e2*/ goTo.marker("0"); -verify.completionListContains("f1.ts"); -verify.completionListContains("f1.js"); -verify.completionListContains("f1.d.ts"); -verify.completionListContains("f2.tsx"); -verify.completionListContains("f3.js"); -verify.completionListContains("f4.jsx"); -verify.completionListContains("e1.ts"); -verify.completionListContains("e2.js"); -verify.completionListContains("test0.ts"); -verify.completionListContains("test1.ts"); -verify.not.completionListItemsCountIsGreaterThan(10); +verify.importModuleCompletionListContains("f1.ts"); +verify.importModuleCompletionListContains("f1.js"); +verify.importModuleCompletionListContains("f1.d.ts"); +verify.importModuleCompletionListContains("f2.tsx"); +verify.importModuleCompletionListContains("f3.js"); +verify.importModuleCompletionListContains("f4.jsx"); +verify.importModuleCompletionListContains("e1.ts"); +verify.importModuleCompletionListContains("e2.js"); +verify.importModuleCompletionListContains("test0.ts"); +verify.importModuleCompletionListContains("test1.ts"); +verify.not.importModuleCompletionListItemsCountIsGreaterThan(10); goTo.marker("1"); -verify.completionListContains("f1.ts"); -verify.completionListContains("f1.js"); -verify.completionListContains("f1.d.ts"); -verify.completionListContains("f2.tsx"); -verify.completionListContains("f3.js"); -verify.completionListContains("f4.jsx"); -verify.not.completionListItemsCountIsGreaterThan(6); \ No newline at end of file +verify.importModuleCompletionListContains("f1.ts"); +verify.importModuleCompletionListContains("f1.js"); +verify.importModuleCompletionListContains("f1.d.ts"); +verify.importModuleCompletionListContains("f2.tsx"); +verify.importModuleCompletionListContains("f3.js"); +verify.importModuleCompletionListContains("f4.jsx"); +verify.not.importModuleCompletionListItemsCountIsGreaterThan(6); \ No newline at end of file diff --git a/tests/cases/fourslash/completionForTripleSlashReference3.ts b/tests/cases/fourslash/completionForTripleSlashReference3.ts index e7aca7399f..dda6ae92b4 100644 --- a/tests/cases/fourslash/completionForTripleSlashReference3.ts +++ b/tests/cases/fourslash/completionForTripleSlashReference3.ts @@ -25,17 +25,17 @@ //// /*e2*/ goTo.marker("0"); -verify.completionListContains("fourslash/"); -verify.not.completionListItemsCountIsGreaterThan(1); +verify.importModuleCompletionListContains("fourslash/"); +verify.not.importModuleCompletionListItemsCountIsGreaterThan(1); goTo.marker("1"); -verify.completionListContains("fourslash/"); -verify.not.completionListItemsCountIsGreaterThan(1); +verify.importModuleCompletionListContains("fourslash/"); +verify.not.importModuleCompletionListItemsCountIsGreaterThan(1); goTo.marker("2"); -verify.completionListContains("f1.ts"); -verify.completionListContains("f2.tsx"); -verify.completionListContains("e1.ts"); -verify.completionListContains("folder/"); -verify.completionListContains("tests/"); -verify.not.completionListItemsCountIsGreaterThan(5); \ No newline at end of file +verify.importModuleCompletionListContains("f1.ts"); +verify.importModuleCompletionListContains("f2.tsx"); +verify.importModuleCompletionListContains("e1.ts"); +verify.importModuleCompletionListContains("folder/"); +verify.importModuleCompletionListContains("tests/"); +verify.not.importModuleCompletionListItemsCountIsGreaterThan(5); \ No newline at end of file diff --git a/tests/cases/fourslash/fourslash.ts b/tests/cases/fourslash/fourslash.ts index 9ce3197a46..e011dde4b6 100644 --- a/tests/cases/fourslash/fourslash.ts +++ b/tests/cases/fourslash/fourslash.ts @@ -125,6 +125,9 @@ declare namespace FourSlashInterface { completionListItemsCountIsGreaterThan(count: number): void; completionListIsEmpty(): void; completionListAllowsNewIdentifier(): void; + importModuleCompletionListContains(symbol: string): void; + importModuleCompletionListItemsCountIsGreaterThan(count: number): void; + importModuleCompletionListIsEmpty(): void; memberListIsEmpty(): void; signatureHelpPresent(): void; errorExistsBetweenMarkers(startMarker: string, endMarker: string): void; From 98a162be2a510a7771e7998f63370252c799741f Mon Sep 17 00:00:00 2001 From: Richard Knoll Date: Mon, 1 Aug 2016 16:58:33 -0700 Subject: [PATCH 16/39] Replacement spans for import completions --- src/harness/fourslash.ts | 24 ++++++++++--- src/services/services.ts | 34 +++++++++++++------ .../completionForStringLiteralImport1.ts | 19 +++++++++++ .../completionForStringLiteralImport2.ts | 28 +++++++++++++++ tests/cases/fourslash/fourslash.ts | 2 +- 5 files changed, 92 insertions(+), 15 deletions(-) create mode 100644 tests/cases/fourslash/completionForStringLiteralImport1.ts create mode 100644 tests/cases/fourslash/completionForStringLiteralImport2.ts diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index 8b0968664e..9ad5f25e00 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -766,13 +766,29 @@ namespace FourSlash { } } - public verifyImportModuleCompletionListContains(symbol: string) { + public verifyImportModuleCompletionListContains(symbol: string, rangeIndex?: number) { const completions = this.getImportModuleCompletionListAtCaret(); if (completions) { - if (!ts.forEach(completions, completion => completion.name === symbol)) { + const completion = ts.forEach(completions, completion => completion.name === symbol ? completion : undefined); + if (!completion) { const itemsString = completions.map(item => stringify({ name: item.name, span: item.span })).join(",\n"); this.raiseError(`Expected "${symbol}" to be in list [${itemsString}]`); } + else if (rangeIndex !== undefined) { + const ranges = this.getRanges(); + if (ranges && ranges.length > rangeIndex) { + const range = ranges[rangeIndex]; + + const start = completion.span.start; + const end = start + completion.span.length; + if (range.start !== start || range.end !== end) { + this.raiseError(`Expected completion span for '${symbol}', ${stringify(completion.span)}, to cover range ${stringify(range)}`); + } + } + else { + this.raiseError(`Expected completion span for '${symbol}' to cover range at index ${rangeIndex}, but no range was found at that index`); + } + } } else { this.raiseError(`No import module completions at position '${this.currentCaretPosition}' when looking for '${symbol}'.`); @@ -2943,12 +2959,12 @@ namespace FourSlashInterface { this.state.verifyCompletionListItemsCountIsGreaterThan(count, this.negative); } - public importModuleCompletionListContains(symbol: string): void { + public importModuleCompletionListContains(symbol: string, rangeIndex?: number): void { if (this.negative) { this.state.verifyImportModuleCompletionListDoesNotContain(symbol); } else { - this.state.verifyImportModuleCompletionListContains(symbol); + this.state.verifyImportModuleCompletionListContains(symbol, rangeIndex); } } diff --git a/src/services/services.ts b/src/services/services.ts index 11eec28a5f..61d624c29d 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -2069,7 +2069,7 @@ namespace ts { * for completions. * For example, this matches /// position >= commentRange.pos && position <= commentRange.end && commentRange); + + if (!range) { + return undefined; + } + + const text = sourceFile.text.substr(range.pos, position - range.pos); - const text = sourceFile.text.substr(node.pos, position); const match = tripleSlashDirectiveFragmentRegex.exec(text); if (match) { - const kind= match[1]; - const fragment = match[2]; + const prefix = match[1]; + const kind = match[2]; + const toComplete = match[3]; + + const span: TextSpan = { start: range.pos + prefix.length, length: match[0].length - prefix.length }; + const scriptPath = getDirectoryPath(sourceFile.path); if (kind === "path") { // Give completions for a relative path - return getCompletionEntriesForDirectoryFragment(fragment, scriptPath, getSupportedExtensions(program.getCompilerOptions()), /*includeExtensions*/true, span); + return getCompletionEntriesForDirectoryFragment(toComplete, scriptPath, getSupportedExtensions(program.getCompilerOptions()), /*includeExtensions*/true, span); } else { // Give completions based on the typings available diff --git a/tests/cases/fourslash/completionForStringLiteralImport1.ts b/tests/cases/fourslash/completionForStringLiteralImport1.ts new file mode 100644 index 0000000000..42c2dbaf3d --- /dev/null +++ b/tests/cases/fourslash/completionForStringLiteralImport1.ts @@ -0,0 +1,19 @@ +/// + +// @Filename: test.ts +//// import * as foo0 from "[|./some|]/*0*/ +//// import foo1 = require( "[|./some|]/*1*/ +//// var foo2 = require( "[|./some|]/*2*/ + +//// import * as foo3 from "[|./some|]/*3*/"; +//// import foo4 = require( "[|./some|]/*4*/"; +//// var foo5 = require( "[|./some|]/*5*/"; + + +// @Filename: someFile.ts +//// /*someFile*/ + +for (let i = 0; i < 6; i++) { + goTo.marker("" + i); + verify.importModuleCompletionListContains("someFile", i); +} \ No newline at end of file diff --git a/tests/cases/fourslash/completionForStringLiteralImport2.ts b/tests/cases/fourslash/completionForStringLiteralImport2.ts new file mode 100644 index 0000000000..a0618c344a --- /dev/null +++ b/tests/cases/fourslash/completionForStringLiteralImport2.ts @@ -0,0 +1,28 @@ +/// + +// @typeRoots: my_typings + +// @Filename: test.ts +//// /// +//// /// + +// @Filename: someFile.ts +//// /*someFile*/ + +// @Filename: my_typings/some-module/index.d.ts +//// export var x = 9; + +goTo.marker("0"); +verify.importModuleCompletionListContains("someFile.ts", 0); + +goTo.marker("1"); +verify.importModuleCompletionListContains("some-module", 1); + +goTo.marker("2"); +verify.importModuleCompletionListContains("someFile.ts", 2); + +goTo.marker("3"); +verify.importModuleCompletionListContains("some-module", 3); \ No newline at end of file diff --git a/tests/cases/fourslash/fourslash.ts b/tests/cases/fourslash/fourslash.ts index e011dde4b6..948477b78d 100644 --- a/tests/cases/fourslash/fourslash.ts +++ b/tests/cases/fourslash/fourslash.ts @@ -125,7 +125,7 @@ declare namespace FourSlashInterface { completionListItemsCountIsGreaterThan(count: number): void; completionListIsEmpty(): void; completionListAllowsNewIdentifier(): void; - importModuleCompletionListContains(symbol: string): void; + importModuleCompletionListContains(symbol: string, rangeIndex?: number): void; importModuleCompletionListItemsCountIsGreaterThan(count: number): void; importModuleCompletionListIsEmpty(): void; memberListIsEmpty(): void; From 35cd480a9c81b1db735587e3dcab3d12d3879f41 Mon Sep 17 00:00:00 2001 From: Richard Knoll Date: Mon, 1 Aug 2016 17:49:13 -0700 Subject: [PATCH 17/39] Fixing import completion spans to only include the end of the directory fragment --- src/services/services.ts | 15 ++++++-- .../completionForStringLiteralImport1.ts | 38 ++++++++++++------- .../completionForStringLiteralImport2.ts | 9 +++-- 3 files changed, 43 insertions(+), 19 deletions(-) diff --git a/src/services/services.ts b/src/services/services.ts index 61d624c29d..66b894bb42 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -4518,7 +4518,7 @@ namespace ts { let result: ImportCompletionEntry[]; // Replace the entire text of the string literal (excluding the quotes) - const span: TextSpan = { start: node.getStart() + 1, length: literalValue.length }; + const span: TextSpan = getDirectoryFragmentTextSpan(literalValue, node.getStart() + 1); const isRelativePath = startsWith(literalValue, "."); const scriptDir = getDirectoryPath(node.getSourceFile().path); @@ -4792,15 +4792,16 @@ namespace ts { const kind = match[2]; const toComplete = match[3]; - const span: TextSpan = { start: range.pos + prefix.length, length: match[0].length - prefix.length }; - const scriptPath = getDirectoryPath(sourceFile.path); if (kind === "path") { // Give completions for a relative path + const span: TextSpan = getDirectoryFragmentTextSpan(toComplete, range.pos + prefix.length); return getCompletionEntriesForDirectoryFragment(toComplete, scriptPath, getSupportedExtensions(program.getCompilerOptions()), /*includeExtensions*/true, span); } else { // Give completions based on the typings available + // Replace the entire string + const span: TextSpan = { start: range.pos + prefix.length, length: match[0].length - prefix.length }; return getCompletionEntriesFromTypings(host, program.getCompilerOptions(), scriptPath, span); } } @@ -4952,6 +4953,14 @@ namespace ts { function createCompletionEntryForModule(name: string, kind: string, span: TextSpan): ImportCompletionEntry { return { name, kind, sortText: name, span }; } + + // Replace everything after the last directory seperator that appears + // FIXME: do we care about the other seperator? + function getDirectoryFragmentTextSpan(text: string, textStart: number): TextSpan { + const index = text.lastIndexOf(directorySeparator); + const offset = index !== -1 ? index + 1 : 0; + return { start: textStart + offset, length: text.length - offset } + } } function getCompletionEntryDetails(fileName: string, position: number, entryName: string): CompletionEntryDetails { diff --git a/tests/cases/fourslash/completionForStringLiteralImport1.ts b/tests/cases/fourslash/completionForStringLiteralImport1.ts index 42c2dbaf3d..239c3be42f 100644 --- a/tests/cases/fourslash/completionForStringLiteralImport1.ts +++ b/tests/cases/fourslash/completionForStringLiteralImport1.ts @@ -1,19 +1,31 @@ /// +// @typeRoots: my_typings + // @Filename: test.ts -//// import * as foo0 from "[|./some|]/*0*/ -//// import foo1 = require( "[|./some|]/*1*/ -//// var foo2 = require( "[|./some|]/*2*/ - -//// import * as foo3 from "[|./some|]/*3*/"; -//// import foo4 = require( "[|./some|]/*4*/"; -//// var foo5 = require( "[|./some|]/*5*/"; +//// import * as foo0 from "./[|some|]/*0*/ +//// import * as foo1 from "./sub/[|some|]/*1*/ +//// import * as foo2 from "[|some-|]/*2*/" +//// import * as foo3 from "../[||]/*3*/"; -// @Filename: someFile.ts -//// /*someFile*/ +// @Filename: someFile1.ts +//// /*someFile1*/ -for (let i = 0; i < 6; i++) { - goTo.marker("" + i); - verify.importModuleCompletionListContains("someFile", i); -} \ No newline at end of file +// @Filename: sub/someFile2.ts +//// /*someFile2*/ + +// @Filename: my_typings/some-module/index.d.ts +//// export var x = 9; + +goTo.marker("0"); +verify.importModuleCompletionListContains("someFile1", 0); + +goTo.marker("1"); +verify.importModuleCompletionListContains("someFile2", 1); + +goTo.marker("2"); +verify.importModuleCompletionListContains("some-module", 2); + +goTo.marker("3"); +verify.importModuleCompletionListContains("fourslash/", 3); \ No newline at end of file diff --git a/tests/cases/fourslash/completionForStringLiteralImport2.ts b/tests/cases/fourslash/completionForStringLiteralImport2.ts index a0618c344a..2910d9e147 100644 --- a/tests/cases/fourslash/completionForStringLiteralImport2.ts +++ b/tests/cases/fourslash/completionForStringLiteralImport2.ts @@ -3,15 +3,18 @@ // @typeRoots: my_typings // @Filename: test.ts -//// /// +//// /// //// /// // @Filename: someFile.ts //// /*someFile*/ +// @Filename: sub/someOtherFile.ts +//// /*someOtherFile*/ + // @Filename: my_typings/some-module/index.d.ts //// export var x = 9; @@ -22,7 +25,7 @@ goTo.marker("1"); verify.importModuleCompletionListContains("some-module", 1); goTo.marker("2"); -verify.importModuleCompletionListContains("someFile.ts", 2); +verify.importModuleCompletionListContains("someOtherFile.ts", 2); goTo.marker("3"); verify.importModuleCompletionListContains("some-module", 3); \ No newline at end of file From a5d73bfc2430091176de535af86a90ce677293a4 Mon Sep 17 00:00:00 2001 From: Richard Knoll Date: Tue, 2 Aug 2016 15:55:30 -0700 Subject: [PATCH 18/39] No more filtering results --- src/services/services.ts | 122 ++++++++++-------- ...etionForStringLiteralNonrelativeImport4.ts | 22 +--- ...mpletionForStringLiteralRelativeImport1.ts | 44 ++----- ...mpletionForStringLiteralRelativeImport2.ts | 7 +- ...mpletionForStringLiteralRelativeImport4.ts | 2 + .../completionForTripleSlashReference1.ts | 78 ++++------- .../completionForTripleSlashReference2.ts | 49 +++---- 7 files changed, 127 insertions(+), 197 deletions(-) diff --git a/src/services/services.ts b/src/services/services.ts index 66b894bb42..e8ff384780 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -2069,7 +2069,7 @@ namespace ts { * for completions. * For example, this matches /// combinePaths(rootDirectory, relativeDirectory))); } - function getCompletionEntriesForDirectoryFragmentWithRootDirs(rootDirs: string[], fragment: string, scriptPath: string, extensions: string[], includeExtensions: boolean, span: TextSpan): ImportCompletionEntry[] { + function getCompletionEntriesForDirectoryFragmentWithRootDirs(rootDirs: string[], fragment: string, scriptPath: string, extensions: string[], includeExtensions: boolean, span: TextSpan, exclude?: string): ImportCompletionEntry[] { const basePath = program.getCompilerOptions().project || host.getCurrentDirectory(); + const ignoreCase = !(host.useCaseSensitiveFileNames && host.useCaseSensitiveFileNames()); + const baseDirectories = getBaseDirectoriesFromRootDirs(rootDirs, basePath, scriptPath, ignoreCase); - const baseDirectories = getBaseDirectoriesFromRootDirs( - rootDirs, basePath, scriptPath, host.useCaseSensitiveFileNames && !host.useCaseSensitiveFileNames()); const result: ImportCompletionEntry[] = []; for (const baseDirectory of baseDirectories) { - getCompletionEntriesForDirectoryFragment(fragment, baseDirectory, extensions, includeExtensions, span, result); + getCompletionEntriesForDirectoryFragment(fragment, baseDirectory, extensions, includeExtensions, span, exclude, result); } return result; } - function getCompletionEntriesForDirectoryFragment(fragment: string, scriptPath: string, extensions: string[], includeExtensions: boolean, span: TextSpan, result: ImportCompletionEntry[] = []): ImportCompletionEntry[] { - const toComplete = getBaseFileName(fragment); - const absolutePath = normalizeSlashes(host.resolvePath(isRootedDiskPath(fragment) ? fragment : combinePaths(scriptPath, fragment))); - const baseDirectory = toComplete ? getDirectoryPath(absolutePath) : absolutePath; + function getCompletionEntriesForDirectoryFragment(fragment: string, scriptPath: string, extensions: string[], includeExtensions: boolean, span: TextSpan, exclude?: string, result: ImportCompletionEntry[] = []): ImportCompletionEntry[] { + fragment = getDirectoryPath(fragment); + if (!fragment) { + fragment = "./" + } + else { + fragment = ensureTrailingDirectorySeparator(fragment); + } + + const absolutePath = normalizeAndPreserveTrailingSlash(isRootedDiskPath(fragment) ? fragment : combinePaths(scriptPath, fragment)); + const baseDirectory = host.resolvePath(getDirectoryPath(absolutePath)); + const ignoreCase = !(host.useCaseSensitiveFileNames && host.useCaseSensitiveFileNames()); if (directoryProbablyExists(baseDirectory, host)) { // Enumerate the available files const files = host.readDirectory(baseDirectory, extensions, /*exclude*/undefined, /*include*/["./*"]); - forEach(files, f => { - const fileName = includeExtensions ? getBaseFileName(f) : removeFileExtension(getBaseFileName(f)); + forEach(files, filePath => { + if (exclude && comparePaths(filePath, exclude, scriptPath, ignoreCase) === Comparison.EqualTo) { + return false; + } - const duplicate = includeExtensions ? false : forEach(result, entry => entry.name === fileName); + const fileName = includeExtensions ? getBaseFileName(filePath) : removeFileExtension(getBaseFileName(filePath)); + const duplicate = !includeExtensions && forEach(result, entry => entry.name === fileName); - if (startsWith(fileName, toComplete) && !duplicate) { + if (!duplicate) { result.push({ name: fileName, kind: ScriptElementKind.directory, @@ -4605,14 +4612,12 @@ namespace ts { forEach(directories, d => { const directoryName = getBaseFileName(removeTrailingDirectorySeparator(d)); - if (startsWith(directoryName, toComplete)) { - result.push({ - name: ensureTrailingDirectorySeparator(directoryName), - kind: ScriptElementKind.directory, - sortText: directoryName, - span - }); - } + result.push({ + name: ensureTrailingDirectorySeparator(directoryName), + kind: ScriptElementKind.directory, + sortText: directoryName, + span + }); }); } } @@ -4679,18 +4684,17 @@ namespace ts { if (parsed) { // The prefix has two effective parts: the directory path and the base component after the filepath that is not a // full directory component. For example: directory/path/of/prefix/base* - const normalizedPrefix = hasTrailingDirectorySeparator(parsed.prefix) ? - ensureTrailingDirectorySeparator(normalizePath(parsed.prefix)) : normalizePath(parsed.prefix); + const normalizedPrefix = normalizeAndPreserveTrailingSlash(parsed.prefix); const normalizedPrefixDirectory = getDirectoryPath(normalizedPrefix); const normalizedPrefixBase = getBaseFileName(normalizedPrefix); const fragmentHasPath = fragment.indexOf(directorySeparator) !== -1; // Try and expand the prefix to include any path from the fragment so that we can limit the readDirectory call - const expandedPrefixDir = fragmentHasPath ? combinePaths(normalizedPrefixDirectory, normalizedPrefixBase + getDirectoryPath(fragment)) : normalizedPrefixDirectory; + const expandedPrefixDirectory = fragmentHasPath ? combinePaths(normalizedPrefixDirectory, normalizedPrefixBase + getDirectoryPath(fragment)) : normalizedPrefixDirectory; const normalizedSuffix = normalizePath(parsed.suffix); - const baseDirectory = combinePaths(baseUrl, expandedPrefixDir); + const baseDirectory = combinePaths(baseUrl, expandedPrefixDirectory); const completePrefix = fragmentHasPath ? baseDirectory : ensureTrailingDirectorySeparator(baseDirectory) + normalizedPrefixBase; // If we have a suffix, then we need to read the directory all the way down. We could create a glob @@ -4720,17 +4724,8 @@ namespace ts { } function enumeratePotentialNonRelativeModules(fragment: string, scriptPath: string, options: CompilerOptions): string[] { - const trailingSeperator = hasTrailingDirectorySeparator(fragment); - fragment = normalizePath(fragment); - - if (trailingSeperator) { - fragment = ensureTrailingDirectorySeparator(fragment); - } - - // If this is a nested module, get the module name - const firstSeparator = fragment.indexOf(directorySeparator); - const moduleNameFragment = firstSeparator !== -1 ? fragment.substr(0, firstSeparator) : fragment; - const isNestedModule = fragment !== moduleNameFragment; + // Check If this is a nested module + const isNestedModule = fragment.indexOf(directorySeparator) !== -1 ; // Get modules that the type checker picked up const ambientModules = ts.map(program.getTypeChecker().getAmbientModules(), sym => stripQuotes(sym.name)); @@ -4749,7 +4744,7 @@ namespace ts { }); if (!options.moduleResolution || options.moduleResolution === ModuleResolutionKind.NodeJs) { - forEach(enumerateNodeModulesVisibleToScript(host, scriptPath, moduleNameFragment), visibleModule => { + forEach(enumerateNodeModulesVisibleToScript(host, scriptPath), visibleModule => { if (!isNestedModule) { nonRelativeModules.push(visibleModule.canBeImported ? visibleModule.moduleName : ensureTrailingDirectorySeparator(visibleModule.moduleName)); } @@ -4796,7 +4791,7 @@ namespace ts { if (kind === "path") { // Give completions for a relative path const span: TextSpan = getDirectoryFragmentTextSpan(toComplete, range.pos + prefix.length); - return getCompletionEntriesForDirectoryFragment(toComplete, scriptPath, getSupportedExtensions(program.getCompilerOptions()), /*includeExtensions*/true, span); + return getCompletionEntriesForDirectoryFragment(toComplete, scriptPath, getSupportedExtensions(program.getCompilerOptions()), /*includeExtensions*/true, span, sourceFile.path); } else { // Give completions based on the typings available @@ -4876,7 +4871,7 @@ namespace ts { } - function enumerateNodeModulesVisibleToScript(host: LanguageServiceHost, scriptPath: string, modulePrefix?: string) { + function enumerateNodeModulesVisibleToScript(host: LanguageServiceHost, scriptPath: string) { const result: VisibleModuleInfo[] = []; findPackageJsons(scriptPath).forEach((packageJson) => { const package = tryReadingPackageJson(packageJson); @@ -4888,10 +4883,10 @@ namespace ts { const foundModuleNames: string[] = []; if (package.dependencies) { - addPotentialPackageNames(package.dependencies, modulePrefix, foundModuleNames); + addPotentialPackageNames(package.dependencies, foundModuleNames); } if (package.devDependencies) { - addPotentialPackageNames(package.devDependencies, modulePrefix, foundModuleNames); + addPotentialPackageNames(package.devDependencies, foundModuleNames); } forEach(foundModuleNames, (moduleName) => { @@ -4916,9 +4911,10 @@ namespace ts { } } - function addPotentialPackageNames(dependencies: any, prefix: string, result: string[]) { + function addPotentialPackageNames(dependencies: any, result: string[]) { for (const dep in dependencies) { - if (dependencies.hasOwnProperty(dep) && (!prefix || startsWith(dep, prefix))) { + if (dependencies.hasOwnProperty(dep) && !startsWith(dep, "@types") && + dep.charCodeAt(6) !== CharacterCodes.slash && dep.charCodeAt(6) !== CharacterCodes.backslash) { result.push(dep); } } @@ -4961,6 +4957,20 @@ namespace ts { const offset = index !== -1 ? index + 1 : 0; return { start: textStart + offset, length: text.length - offset } } + + // Returns true if the path is explicitly relative to the script (i.e. relative to . or ..) + function isPathRelativeToScript(path: string) { + if (path && path.length >= 2 && path.charCodeAt(0) === CharacterCodes.dot) { + const slashIndex = path.length >= 3 && path.charCodeAt(1) === CharacterCodes.dot ? 2 : 1; + const slashCharCode = path.charCodeAt(slashIndex); + return slashCharCode === CharacterCodes.slash || slashCharCode === CharacterCodes.backslash; + } + return false; + } + + function normalizeAndPreserveTrailingSlash(path: string) { + return hasTrailingDirectorySeparator(path) ? ensureTrailingDirectorySeparator(normalizePath(path)) : normalizePath(path); + } } function getCompletionEntryDetails(fileName: string, position: number, entryName: string): CompletionEntryDetails { diff --git a/tests/cases/fourslash/completionForStringLiteralNonrelativeImport4.ts b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport4.ts index 2817075445..f4a9ae2d53 100644 --- a/tests/cases/fourslash/completionForStringLiteralNonrelativeImport4.ts +++ b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport4.ts @@ -2,16 +2,8 @@ // @Filename: dir1/dir2/dir3/dir4/test0.ts //// import * as foo1 from "f/*import_as0*/ -//// import * as foo2 from "a/*import_as1*/ -//// import * as foo3 from "fake-module/*import_as2*/ - //// import foo4 = require("f/*import_equals0*/ -//// import foo5 = require("a/*import_equals1*/ -//// import foo6 = require("fake-module/*import_equals2*/ - //// var foo7 = require("f/*require0*/ -//// var foo8 = require("a/*require1*/ -//// var foo9 = require("fake-module/*require2*/ // @Filename: package.json //// { "dependencies": { "fake-module": "latest" } } @@ -21,7 +13,7 @@ // @Filename: dir1/package.json //// { "dependencies": { "fake-module2": "latest" } } // @Filename: dir1/node_modules/fake-module2/index.ts -//// declare module "ambient-module-test" {} +//// /*module2*/ // @Filename: dir1/dir2/dir3/package.json //// { "dependencies": { "fake-module3": "latest" } } @@ -37,16 +29,4 @@ for (const kind of kinds) { verify.importModuleCompletionListContains("fake-module2"); verify.importModuleCompletionListContains("fake-module3/"); verify.not.importModuleCompletionListItemsCountIsGreaterThan(3); - - goTo.marker(kind + "1"); - - verify.importModuleCompletionListContains("ambient-module-test"); - verify.not.importModuleCompletionListItemsCountIsGreaterThan(1); - - goTo.marker(kind + "2"); - - verify.importModuleCompletionListContains("fake-module/"); - verify.importModuleCompletionListContains("fake-module2"); - verify.importModuleCompletionListContains("fake-module3/"); - verify.not.importModuleCompletionListItemsCountIsGreaterThan(3); } \ No newline at end of file diff --git a/tests/cases/fourslash/completionForStringLiteralRelativeImport1.ts b/tests/cases/fourslash/completionForStringLiteralRelativeImport1.ts index f3607d70c2..52d62f3b4f 100644 --- a/tests/cases/fourslash/completionForStringLiteralRelativeImport1.ts +++ b/tests/cases/fourslash/completionForStringLiteralRelativeImport1.ts @@ -3,28 +3,21 @@ // @Filename: test0.ts //// import * as foo1 from "./*import_as0*/ //// import * as foo2 from ".//*import_as1*/ -//// import * as foo3 from "./f/*import_as2*/ -//// import * as foo4 from "./folder//*import_as3*/ -//// import * as foo5 from "./folder/h/*import_as4*/ +//// import * as foo4 from "./folder//*import_as2*/ //// import foo6 = require("./*import_equals0*/ //// import foo7 = require(".//*import_equals1*/ -//// import foo8 = require("./f/*import_equals2*/ -//// import foo9 = require("./folder//*import_equals3*/ -//// import foo10 = require("./folder/h/*import_equals4*/ +//// import foo9 = require("./folder//*import_equals2*/ //// var foo11 = require("./*require0*/ //// var foo12 = require(".//*require1*/ -//// var foo13 = require("./f/*require2*/ -//// var foo14 = require("./folder//*require3*/ -//// var foo15 = require("./folder/h/*require4*/ +//// var foo14 = require("./folder//*require2*/ // @Filename: parentTest/sub/test5.ts -//// import * as foo16 from "../g/*import_as5*/ +//// import * as foo16 from "../g/*import_as3*/ +//// import foo17 = require("../g/*import_equals3*/ +//// var foo18 = require("../g/*require3*/ -//// import foo17 = require("../g/*import_equals5*/ - -//// var foo18 = require("../g/*require5*/ // @Filename: f1.ts //// /*f1*/ @@ -40,11 +33,11 @@ //// /*f4*/ // @Filename: e1.ts //// /*e1*/ -// @Filename: folder/f1.ts +// @Filename: folder/f3.ts //// /*subf1*/ // @Filename: folder/h1.ts //// /*subh1*/ -// @Filename: parentTest/f1.ts +// @Filename: parentTest/f4.ts //// /*parentf1*/ // @Filename: parentTest/g1.ts //// /*parentg1*/ @@ -58,27 +51,18 @@ for (const kind of kinds) { verify.importModuleCompletionListContains("f1"); verify.importModuleCompletionListContains("f2"); verify.importModuleCompletionListContains("e1"); - verify.importModuleCompletionListContains("test0"); verify.importModuleCompletionListContains("folder/"); verify.importModuleCompletionListContains("parentTest/"); - verify.not.importModuleCompletionListItemsCountIsGreaterThan(6); + verify.not.importModuleCompletionListItemsCountIsGreaterThan(5); goTo.marker(kind + "2"); - verify.importModuleCompletionListContains("f1"); - verify.importModuleCompletionListContains("f2"); - verify.importModuleCompletionListContains("folder/"); - verify.not.importModuleCompletionListItemsCountIsGreaterThan(3); - - goTo.marker(kind + "3"); - verify.importModuleCompletionListContains("f1"); + verify.importModuleCompletionListContains("f3"); verify.importModuleCompletionListContains("h1"); verify.not.importModuleCompletionListItemsCountIsGreaterThan(2); - goTo.marker(kind + "4"); - verify.importModuleCompletionListContains("h1"); - verify.not.importModuleCompletionListItemsCountIsGreaterThan(1); - - goTo.marker(kind + "5"); + goTo.marker(kind + "3"); + verify.importModuleCompletionListContains("f4"); verify.importModuleCompletionListContains("g1"); - verify.not.importModuleCompletionListItemsCountIsGreaterThan(1); + verify.importModuleCompletionListContains("sub/"); + verify.not.importModuleCompletionListItemsCountIsGreaterThan(3); } \ No newline at end of file diff --git a/tests/cases/fourslash/completionForStringLiteralRelativeImport2.ts b/tests/cases/fourslash/completionForStringLiteralRelativeImport2.ts index eeb24d591f..72b28d39b8 100644 --- a/tests/cases/fourslash/completionForStringLiteralRelativeImport2.ts +++ b/tests/cases/fourslash/completionForStringLiteralRelativeImport2.ts @@ -37,13 +37,14 @@ for (const kind of kinds) { verify.importModuleCompletionListContains("f4"); verify.importModuleCompletionListContains("e1"); verify.importModuleCompletionListContains("e2"); - verify.importModuleCompletionListContains("test0"); - verify.not.importModuleCompletionListItemsCountIsGreaterThan(7); + verify.not.importModuleCompletionListItemsCountIsGreaterThan(6); goTo.marker(kind + "1"); verify.importModuleCompletionListContains("f1"); verify.importModuleCompletionListContains("f2"); verify.importModuleCompletionListContains("f3"); verify.importModuleCompletionListContains("f4"); - verify.not.importModuleCompletionListItemsCountIsGreaterThan(4); + verify.importModuleCompletionListContains("e1"); + verify.importModuleCompletionListContains("e2"); + verify.not.importModuleCompletionListItemsCountIsGreaterThan(6); } \ No newline at end of file diff --git a/tests/cases/fourslash/completionForStringLiteralRelativeImport4.ts b/tests/cases/fourslash/completionForStringLiteralRelativeImport4.ts index 5f4d7bb7fc..773c4d90d7 100644 --- a/tests/cases/fourslash/completionForStringLiteralRelativeImport4.ts +++ b/tests/cases/fourslash/completionForStringLiteralRelativeImport4.ts @@ -44,5 +44,7 @@ for (const kind of kinds) { verify.importModuleCompletionListContains("module1"); verify.importModuleCompletionListContains("module2"); verify.importModuleCompletionListContains("more/"); + + // Should not contain itself verify.not.importModuleCompletionListItemsCountIsGreaterThan(4); } \ No newline at end of file diff --git a/tests/cases/fourslash/completionForTripleSlashReference1.ts b/tests/cases/fourslash/completionForTripleSlashReference1.ts index 284d5ff66a..8c6188ac90 100644 --- a/tests/cases/fourslash/completionForTripleSlashReference1.ts +++ b/tests/cases/fourslash/completionForTripleSlashReference1.ts @@ -1,22 +1,15 @@ /// // @Filename: test0.ts -//// /// +//// /// - -// @Filename: test3.ts -//// /// // @Filename: f1.ts -//// /f1*/ +//// /*f1*/ // @Filename: f1.js //// /*f1j*/ // @Filename: f1.d.ts //// /*f1d*/ // @Filename: f2.tsx -//// /*f2*/ -// @Filename: f3.js -//// /*f3*/ +//// /f2*/ // @Filename: f4.jsx //// /*f4*/ -// @Filename: e1.ts -//// /*e1*/ -// @Filename: e2.js -//// /*e2*/ -goTo.marker("0"); -verify.importModuleCompletionListContains("f1.ts"); -verify.importModuleCompletionListContains("f1.js"); -verify.importModuleCompletionListContains("f1.d.ts"); -verify.importModuleCompletionListContains("f2.tsx"); -verify.importModuleCompletionListContains("f3.js"); -verify.importModuleCompletionListContains("f4.jsx"); -verify.importModuleCompletionListContains("e1.ts"); -verify.importModuleCompletionListContains("e2.js"); -verify.importModuleCompletionListContains("test0.ts"); -verify.importModuleCompletionListContains("test1.ts"); -verify.not.importModuleCompletionListItemsCountIsGreaterThan(10); - -goTo.marker("1"); -verify.importModuleCompletionListContains("f1.ts"); -verify.importModuleCompletionListContains("f1.js"); -verify.importModuleCompletionListContains("f1.d.ts"); -verify.importModuleCompletionListContains("f2.tsx"); -verify.importModuleCompletionListContains("f3.js"); -verify.importModuleCompletionListContains("f4.jsx"); -verify.not.importModuleCompletionListItemsCountIsGreaterThan(6); \ No newline at end of file +for (let i = 0; i < 5; i++) { + goTo.marker("" + i); + verify.importModuleCompletionListContains("f1.ts"); + verify.importModuleCompletionListContains("f1.js"); + verify.importModuleCompletionListContains("f1.d.ts"); + verify.importModuleCompletionListContains("f2.tsx"); + verify.importModuleCompletionListContains("f4.jsx"); + verify.not.importModuleCompletionListItemsCountIsGreaterThan(5); +} \ No newline at end of file From 8b5a3d9fd7142a18b2f25c589622a70476306ae9 Mon Sep 17 00:00:00 2001 From: Richard Knoll Date: Tue, 2 Aug 2016 19:03:36 -0700 Subject: [PATCH 19/39] Refactoring API to remove duplicate spans --- src/harness/fourslash.ts | 24 ++++---- src/harness/harnessLanguageService.ts | 2 +- src/server/client.ts | 10 +++- src/server/protocol.d.ts | 3 +- src/services/services.ts | 84 +++++++++++++++------------ src/services/shims.ts | 2 +- 6 files changed, 70 insertions(+), 55 deletions(-) diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index 9ad5f25e00..532dccd4f6 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -599,7 +599,7 @@ namespace FourSlash { public verifyImportModuleCompletionListItemsCountIsGreaterThan(count: number, negative: boolean) { const completions = this.getImportModuleCompletionListAtCaret(); - const itemsCount = completions.length; + const itemsCount = completions.entries.length; if (negative) { if (itemsCount > count) { @@ -615,13 +615,13 @@ namespace FourSlash { public verifyImportModuleCompletionListIsEmpty(negative: boolean) { const completions = this.getImportModuleCompletionListAtCaret(); - if ((!completions || completions.length === 0) && negative) { + if ((!completions || completions.entries.length === 0) && negative) { this.raiseError("Completion list is empty at caret at position " + this.activeFile.fileName + " " + this.currentCaretPosition); } - else if (completions && completions.length !== 0 && !negative) { - let errorMsg = "\n" + "Completion List contains: [" + completions[0].name; - for (let i = 1; i < completions.length; i++) { - errorMsg += ", " + completions[i].name; + else if (completions && completions.entries.length !== 0 && !negative) { + let errorMsg = "\n" + "Completion List contains: [" + completions.entries[0].name; + for (let i = 1; i < completions.entries.length; i++) { + errorMsg += ", " + completions.entries[i].name; } errorMsg += "]\n"; @@ -769,9 +769,9 @@ namespace FourSlash { public verifyImportModuleCompletionListContains(symbol: string, rangeIndex?: number) { const completions = this.getImportModuleCompletionListAtCaret(); if (completions) { - const completion = ts.forEach(completions, completion => completion.name === symbol ? completion : undefined); + const completion = ts.forEach(completions.entries, completion => completion.name === symbol ? completion : undefined); if (!completion) { - const itemsString = completions.map(item => stringify({ name: item.name, span: item.span })).join(",\n"); + const itemsString = completions.entries.map(item => item.name).join(",\n"); this.raiseError(`Expected "${symbol}" to be in list [${itemsString}]`); } else if (rangeIndex !== undefined) { @@ -779,10 +779,10 @@ namespace FourSlash { if (ranges && ranges.length > rangeIndex) { const range = ranges[rangeIndex]; - const start = completion.span.start; - const end = start + completion.span.length; + const start = completions.span.start; + const end = start + completions.span.length; if (range.start !== start || range.end !== end) { - this.raiseError(`Expected completion span for '${symbol}', ${stringify(completion.span)}, to cover range ${stringify(range)}`); + this.raiseError(`Expected completion span for '${symbol}', ${stringify(completions.span)}, to cover range ${stringify(range)}`); } } else { @@ -798,7 +798,7 @@ namespace FourSlash { public verifyImportModuleCompletionListDoesNotContain(symbol: string) { const completions = this.getImportModuleCompletionListAtCaret(); if (completions) { - if (ts.forEach(completions, completion => completion.name === symbol)) { + if (ts.forEach(completions.entries, completion => completion.name === symbol)) { this.raiseError(`Import module completion list did contain ${symbol}`); } } diff --git a/src/harness/harnessLanguageService.ts b/src/harness/harnessLanguageService.ts index 1972ab85b9..35032f3182 100644 --- a/src/harness/harnessLanguageService.ts +++ b/src/harness/harnessLanguageService.ts @@ -426,7 +426,7 @@ namespace Harness.LanguageService { getCompletionsAtPosition(fileName: string, position: number): ts.CompletionInfo { return unwrapJSONCallResult(this.shim.getCompletionsAtPosition(fileName, position)); } - getImportModuleCompletionsAtPosition(fileName: string, position: number): ts.ImportCompletionEntry[] { + getImportModuleCompletionsAtPosition(fileName: string, position: number): ts.ImportCompletionInfo { return unwrapJSONCallResult(this.shim.getImportModuleCompletionsAtPosition(fileName, position)); } getCompletionEntryDetails(fileName: string, position: number, entryName: string): ts.CompletionEntryDetails { diff --git a/src/server/client.ts b/src/server/client.ts index 04784a77f1..03a370411d 100644 --- a/src/server/client.ts +++ b/src/server/client.ts @@ -220,7 +220,7 @@ namespace ts.server { }; } - getImportModuleCompletionsAtPosition(fileName: string, position: number): ImportCompletionEntry[] { + getImportModuleCompletionsAtPosition(fileName: string, position: number): ImportCompletionInfo { const lineOffset = this.positionToOneBasedLineOffset(fileName, position); const args: protocol.CompletionsRequestArgs = { file: fileName, @@ -232,7 +232,13 @@ namespace ts.server { const request = this.processRequest(CommandNames.ImportModuleCompletions, args); const response = this.processResponse(request); - return response.body; + const startPosition = this.lineOffsetToPosition(fileName, response.span.start); + const endPosition = this.lineOffsetToPosition(fileName, response.span.end); + + return { + span: ts.createTextSpanFromBounds(startPosition, endPosition), + entries: response.body + }; } getCompletionEntryDetails(fileName: string, position: number, entryName: string): CompletionEntryDetails { diff --git a/src/server/protocol.d.ts b/src/server/protocol.d.ts index 803edce0d7..20670ae024 100644 --- a/src/server/protocol.d.ts +++ b/src/server/protocol.d.ts @@ -816,7 +816,8 @@ declare namespace ts.server.protocol { body?: CompletionEntry[]; } - export interface ImportModuleCompletionsResponse extends Response { + export interface ImportModuleCompletionsResponse extends Response { + span: TextSpan; body?: ImportCompletionEntry[]; } diff --git a/src/services/services.ts b/src/services/services.ts index e8ff384780..fd66590824 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1208,7 +1208,7 @@ namespace ts { getEncodedSemanticClassifications(fileName: string, span: TextSpan): Classifications; getCompletionsAtPosition(fileName: string, position: number): CompletionInfo; - getImportModuleCompletionsAtPosition(fileName: string, position: number): ImportCompletionEntry[]; + getImportModuleCompletionsAtPosition(fileName: string, position: number): ImportCompletionInfo; getCompletionEntryDetails(fileName: string, position: number, entryName: string): CompletionEntryDetails; getQuickInfoAtPosition(fileName: string, position: number): QuickInfo; @@ -1481,10 +1481,14 @@ namespace ts { sortText: string; } + export interface ImportCompletionInfo { + span: TextSpan; + entries: ImportCompletionEntry[]; + } + export interface ImportCompletionEntry { name: string; kind: string; // see ScriptElementKind - span: TextSpan; sortText: string; } @@ -4492,7 +4496,7 @@ namespace ts { } } - function getImportModuleCompletionsAtPosition(fileName: string, position: number): ImportCompletionEntry[] { + function getImportModuleCompletionsAtPosition(fileName: string, position: number): ImportCompletionInfo { synchronizeHostData(); const sourceFile = getValidSourceFile(fileName); @@ -4507,7 +4511,10 @@ namespace ts { if (node.parent.kind === SyntaxKind.ImportDeclaration || isExpressionOfExternalModuleImportEqualsDeclaration(node) || isRequireCall(node.parent, false)) { // Get all known external module names or complete a path to a module - return getStringLiteralCompletionEntriesFromModuleNames(node); + return { + entries: getStringLiteralCompletionEntriesFromModuleNames(node), + span: getDirectoryFragmentTextSpan((node).text, node.getStart() + 1) + }; } } @@ -4515,8 +4522,6 @@ namespace ts { function getStringLiteralCompletionEntriesFromModuleNames(node: StringLiteral) { const literalValue = normalizeSlashes(node.text); - // Replace the entire text of the string literal (excluding the quotes) - const span: TextSpan = getDirectoryFragmentTextSpan(literalValue, node.getStart() + 1); const scriptPath = node.getSourceFile().path; const scriptDirectory = getDirectoryPath(scriptPath); @@ -4524,16 +4529,16 @@ namespace ts { const compilerOptions = program.getCompilerOptions(); if (compilerOptions.rootDirs) { return getCompletionEntriesForDirectoryFragmentWithRootDirs( - compilerOptions.rootDirs, literalValue, scriptDirectory, getSupportedExtensions(program.getCompilerOptions()), /*includeExtensions*/false, span, scriptPath); + compilerOptions.rootDirs, literalValue, scriptDirectory, getSupportedExtensions(program.getCompilerOptions()), /*includeExtensions*/false, scriptPath); } else { return getCompletionEntriesForDirectoryFragment( - literalValue, scriptDirectory, getSupportedExtensions(program.getCompilerOptions()), /*includeExtensions*/false, span, scriptPath); + literalValue, scriptDirectory, getSupportedExtensions(program.getCompilerOptions()), /*includeExtensions*/false, scriptPath); } } else { // Check for node modules - return getCompletionEntriesForNonRelativeModules(literalValue, scriptDirectory, span); + return getCompletionEntriesForNonRelativeModules(literalValue, scriptDirectory); } } @@ -4558,7 +4563,7 @@ namespace ts { return deduplicate(map(rootDirs, rootDirectory => combinePaths(rootDirectory, relativeDirectory))); } - function getCompletionEntriesForDirectoryFragmentWithRootDirs(rootDirs: string[], fragment: string, scriptPath: string, extensions: string[], includeExtensions: boolean, span: TextSpan, exclude?: string): ImportCompletionEntry[] { + function getCompletionEntriesForDirectoryFragmentWithRootDirs(rootDirs: string[], fragment: string, scriptPath: string, extensions: string[], includeExtensions: boolean, exclude?: string): ImportCompletionEntry[] { const basePath = program.getCompilerOptions().project || host.getCurrentDirectory(); const ignoreCase = !(host.useCaseSensitiveFileNames && host.useCaseSensitiveFileNames()); const baseDirectories = getBaseDirectoriesFromRootDirs(rootDirs, basePath, scriptPath, ignoreCase); @@ -4566,16 +4571,16 @@ namespace ts { const result: ImportCompletionEntry[] = []; for (const baseDirectory of baseDirectories) { - getCompletionEntriesForDirectoryFragment(fragment, baseDirectory, extensions, includeExtensions, span, exclude, result); + getCompletionEntriesForDirectoryFragment(fragment, baseDirectory, extensions, includeExtensions, exclude, result); } return result; } - function getCompletionEntriesForDirectoryFragment(fragment: string, scriptPath: string, extensions: string[], includeExtensions: boolean, span: TextSpan, exclude?: string, result: ImportCompletionEntry[] = []): ImportCompletionEntry[] { + function getCompletionEntriesForDirectoryFragment(fragment: string, scriptPath: string, extensions: string[], includeExtensions: boolean, exclude?: string, result: ImportCompletionEntry[] = []): ImportCompletionEntry[] { fragment = getDirectoryPath(fragment); if (!fragment) { - fragment = "./" + fragment = "./"; } else { fragment = ensureTrailingDirectorySeparator(fragment); @@ -4600,8 +4605,7 @@ namespace ts { result.push({ name: fileName, kind: ScriptElementKind.directory, - sortText: fileName, - span + sortText: fileName }); } }); @@ -4615,8 +4619,7 @@ namespace ts { result.push({ name: ensureTrailingDirectorySeparator(directoryName), kind: ScriptElementKind.directory, - sortText: directoryName, - span + sortText: directoryName }); }); } @@ -4632,7 +4635,7 @@ namespace ts { * Modules from node_modules (i.e. those listed in package.json) * This includes all files that are found in node_modules/moduleName/ with acceptable file extensions */ - function getCompletionEntriesForNonRelativeModules(fragment: string, scriptPath: string, span: TextSpan): ImportCompletionEntry[] { + function getCompletionEntriesForNonRelativeModules(fragment: string, scriptPath: string): ImportCompletionEntry[] { const options = program.getCompilerOptions(); const { baseUrl, paths } = options; @@ -4642,7 +4645,7 @@ namespace ts { const fileExtensions = getSupportedExtensions(options); const projectDir = options.project || host.getCurrentDirectory(); const absolute = isRootedDiskPath(baseUrl) ? baseUrl : combinePaths(projectDir, baseUrl); - result = getCompletionEntriesForDirectoryFragment(fragment, normalizePath(absolute), fileExtensions, /*includeExtensions*/false, span); + result = getCompletionEntriesForDirectoryFragment(fragment, normalizePath(absolute), fileExtensions, /*includeExtensions*/false); if (paths) { for (const path in paths) { @@ -4651,7 +4654,7 @@ namespace ts { if (paths[path]) { forEach(paths[path], pattern => { forEach(getModulesForPathsPattern(fragment, baseUrl, pattern, fileExtensions), match => { - result.push(createCompletionEntryForModule(match, ScriptElementKind.externalModuleName, span)); + result.push(createCompletionEntryForModule(match, ScriptElementKind.externalModuleName)); }); }); } @@ -4659,7 +4662,7 @@ namespace ts { else if (startsWith(path, fragment)) { const entry = paths[path] && paths[path].length === 1 && paths[path][0]; if (entry) { - result.push(createCompletionEntryForModule(path, ScriptElementKind.externalModuleName, span)); + result.push(createCompletionEntryForModule(path, ScriptElementKind.externalModuleName)); } } } @@ -4670,10 +4673,10 @@ namespace ts { result = []; } - getCompletionEntriesFromTypings(host, options, scriptPath, span, result); + getCompletionEntriesFromTypings(host, options, scriptPath, result); forEach(enumeratePotentialNonRelativeModules(fragment, scriptPath, options), moduleName => { - result.push(createCompletionEntryForModule(moduleName, ScriptElementKind.externalModuleName, span)); + result.push(createCompletionEntryForModule(moduleName, ScriptElementKind.externalModuleName)); }); return result; @@ -4762,7 +4765,7 @@ namespace ts { return deduplicate(nonRelativeModules); } - function getTripleSlashReferenceCompletion(sourceFile: SourceFile, position: number): ImportCompletionEntry[] { + function getTripleSlashReferenceCompletion(sourceFile: SourceFile, position: number): ImportCompletionInfo { const token = getTokenAtPosition(sourceFile, position); if (!token) { return undefined; @@ -4791,36 +4794,41 @@ namespace ts { if (kind === "path") { // Give completions for a relative path const span: TextSpan = getDirectoryFragmentTextSpan(toComplete, range.pos + prefix.length); - return getCompletionEntriesForDirectoryFragment(toComplete, scriptPath, getSupportedExtensions(program.getCompilerOptions()), /*includeExtensions*/true, span, sourceFile.path); + return { + entries: getCompletionEntriesForDirectoryFragment(toComplete, scriptPath, getSupportedExtensions(program.getCompilerOptions()), /*includeExtensions*/true, sourceFile.path), + span + }; } else { // Give completions based on the typings available - // Replace the entire string const span: TextSpan = { start: range.pos + prefix.length, length: match[0].length - prefix.length }; - return getCompletionEntriesFromTypings(host, program.getCompilerOptions(), scriptPath, span); + return { + entries: getCompletionEntriesFromTypings(host, program.getCompilerOptions(), scriptPath), + span + }; } } return undefined; } - function getCompletionEntriesFromTypings(host: LanguageServiceHost, options: CompilerOptions, scriptPath: string, span: TextSpan, result: ImportCompletionEntry[] = []): ImportCompletionEntry[] { + function getCompletionEntriesFromTypings(host: LanguageServiceHost, options: CompilerOptions, scriptPath: string, result: ImportCompletionEntry[] = []): ImportCompletionEntry[] { // Check for typings specified in compiler options if (options.types) { forEach(options.types, moduleName => { - result.push(createCompletionEntryForModule(moduleName, ScriptElementKind.externalModuleName, span)); + result.push(createCompletionEntryForModule(moduleName, ScriptElementKind.externalModuleName)); }); } else if (host.getDirectories && options.typeRoots) { const absoluteRoots = map(options.typeRoots, rootDirectory => getAbsoluteProjectPath(rootDirectory, host, options.project)); - forEach(absoluteRoots, absoluteRoot => getCompletionEntriesFromDirectories(host, options, absoluteRoot, span, result)); + forEach(absoluteRoots, absoluteRoot => getCompletionEntriesFromDirectories(host, options, absoluteRoot, result)); } if (host.getDirectories) { // Also get all @types typings installed in visible node_modules directories forEach(findPackageJsons(scriptPath), package => { const typesDir = combinePaths(getDirectoryPath(package), "node_modules/@types"); - getCompletionEntriesFromDirectories(host, options, typesDir, span, result); + getCompletionEntriesFromDirectories(host, options, typesDir, result); }); } @@ -4839,10 +4847,10 @@ namespace ts { return normalizePath(host.resolvePath(path)); } - function getCompletionEntriesFromDirectories(host: LanguageServiceHost, options: CompilerOptions, directory: string, span: TextSpan, result: ImportCompletionEntry[]) { + function getCompletionEntriesFromDirectories(host: LanguageServiceHost, options: CompilerOptions, directory: string, result: ImportCompletionEntry[]) { if (host.getDirectories && directoryProbablyExists(directory, host)) { forEach(host.getDirectories(directory), typeDirectory => { - result.push(createCompletionEntryForModule(getBaseFileName(typeDirectory), ScriptElementKind.externalModuleName, span)); + result.push(createCompletionEntryForModule(getBaseFileName(typeDirectory), ScriptElementKind.externalModuleName)); }); } } @@ -4911,10 +4919,10 @@ namespace ts { } } + // Add all the package names that are not in the @types scope function addPotentialPackageNames(dependencies: any, result: string[]) { for (const dep in dependencies) { - if (dependencies.hasOwnProperty(dep) && !startsWith(dep, "@types") && - dep.charCodeAt(6) !== CharacterCodes.slash && dep.charCodeAt(6) !== CharacterCodes.backslash) { + if (dependencies.hasOwnProperty(dep) && !startsWith(dep, "@types/")) { result.push(dep); } } @@ -4946,8 +4954,8 @@ namespace ts { } } - function createCompletionEntryForModule(name: string, kind: string, span: TextSpan): ImportCompletionEntry { - return { name, kind, sortText: name, span }; + function createCompletionEntryForModule(name: string, kind: string): ImportCompletionEntry { + return { name, kind, sortText: name }; } // Replace everything after the last directory seperator that appears @@ -4955,7 +4963,7 @@ namespace ts { function getDirectoryFragmentTextSpan(text: string, textStart: number): TextSpan { const index = text.lastIndexOf(directorySeparator); const offset = index !== -1 ? index + 1 : 0; - return { start: textStart + offset, length: text.length - offset } + return { start: textStart + offset, length: text.length - offset }; } // Returns true if the path is explicitly relative to the script (i.e. relative to . or ..) diff --git a/src/services/shims.ts b/src/services/shims.ts index eb6c5e55b7..9f4d9d24a4 100644 --- a/src/services/shims.ts +++ b/src/services/shims.ts @@ -874,7 +874,7 @@ namespace ts { getImportModuleCompletionsAtPosition(fileName: string, position: number): string { return this.forwardJSONCall( `getImportModuleCompletionsAtPosition('${fileName}', ${position})`, - () => this.languageService.getCompletionsAtPosition(fileName, position) + () => this.languageService.getImportModuleCompletionsAtPosition(fileName, position) ); } From 293ca60ffd224f8c4a2d4c9c17426b54adee8b1f Mon Sep 17 00:00:00 2001 From: Richard Knoll Date: Wed, 3 Aug 2016 11:07:57 -0700 Subject: [PATCH 20/39] Renamed span to textSpan to better follow other language service APIs --- src/harness/fourslash.ts | 6 +++--- src/server/client.ts | 2 +- src/services/services.ts | 12 ++++++------ 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index 532dccd4f6..be8f1877c8 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -779,10 +779,10 @@ namespace FourSlash { if (ranges && ranges.length > rangeIndex) { const range = ranges[rangeIndex]; - const start = completions.span.start; - const end = start + completions.span.length; + const start = completions.textSpan.start; + const end = start + completions.textSpan.length; if (range.start !== start || range.end !== end) { - this.raiseError(`Expected completion span for '${symbol}', ${stringify(completions.span)}, to cover range ${stringify(range)}`); + this.raiseError(`Expected completion span for '${symbol}', ${stringify(completions.textSpan)}, to cover range ${stringify(range)}`); } } else { diff --git a/src/server/client.ts b/src/server/client.ts index 03a370411d..7af7be16f1 100644 --- a/src/server/client.ts +++ b/src/server/client.ts @@ -236,7 +236,7 @@ namespace ts.server { const endPosition = this.lineOffsetToPosition(fileName, response.span.end); return { - span: ts.createTextSpanFromBounds(startPosition, endPosition), + textSpan: ts.createTextSpanFromBounds(startPosition, endPosition), entries: response.body }; } diff --git a/src/services/services.ts b/src/services/services.ts index fd66590824..1ed9138146 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1482,7 +1482,7 @@ namespace ts { } export interface ImportCompletionInfo { - span: TextSpan; + textSpan: TextSpan; entries: ImportCompletionEntry[]; } @@ -4513,7 +4513,7 @@ namespace ts { // Get all known external module names or complete a path to a module return { entries: getStringLiteralCompletionEntriesFromModuleNames(node), - span: getDirectoryFragmentTextSpan((node).text, node.getStart() + 1) + textSpan: getDirectoryFragmentTextSpan((node).text, node.getStart() + 1) }; } } @@ -4793,18 +4793,18 @@ namespace ts { const scriptPath = getDirectoryPath(sourceFile.path); if (kind === "path") { // Give completions for a relative path - const span: TextSpan = getDirectoryFragmentTextSpan(toComplete, range.pos + prefix.length); + const textSpan: TextSpan = getDirectoryFragmentTextSpan(toComplete, range.pos + prefix.length); return { entries: getCompletionEntriesForDirectoryFragment(toComplete, scriptPath, getSupportedExtensions(program.getCompilerOptions()), /*includeExtensions*/true, sourceFile.path), - span + textSpan }; } else { // Give completions based on the typings available - const span: TextSpan = { start: range.pos + prefix.length, length: match[0].length - prefix.length }; + const textSpan: TextSpan = { start: range.pos + prefix.length, length: match[0].length - prefix.length }; return { entries: getCompletionEntriesFromTypings(host, program.getCompilerOptions(), scriptPath), - span + textSpan }; } } From ca288231f7a531778e511643524fa83b03cfc478 Mon Sep 17 00:00:00 2001 From: Richard Knoll Date: Thu, 4 Aug 2016 11:10:00 -0700 Subject: [PATCH 21/39] Fixing shim and normalizing paths --- src/services/services.ts | 5 ++++- src/services/shims.ts | 15 ++++++++++----- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/services/services.ts b/src/services/services.ts index 1ed9138146..7b523d7e7a 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -4594,6 +4594,7 @@ namespace ts { // Enumerate the available files const files = host.readDirectory(baseDirectory, extensions, /*exclude*/undefined, /*include*/["./*"]); forEach(files, filePath => { + filePath = normalizePath(filePath); if (exclude && comparePaths(filePath, exclude, scriptPath, ignoreCase) === Comparison.EqualTo) { return false; } @@ -4614,7 +4615,7 @@ namespace ts { if (host.getDirectories) { const directories = host.getDirectories(baseDirectory); forEach(directories, d => { - const directoryName = getBaseFileName(removeTrailingDirectorySeparator(d)); + const directoryName = getBaseFileName(normalizePath(d)); result.push({ name: ensureTrailingDirectorySeparator(directoryName), @@ -4755,6 +4756,7 @@ namespace ts { const nestedFiles = host.readDirectory(visibleModule.moduleDir, supportedTypeScriptExtensions, /*exclude*/undefined, /*include*/["./*"]); forEach(nestedFiles, (f) => { + f = normalizePath(f); const nestedModule = removeFileExtension(getBaseFileName(f)); nonRelativeModules.push(nestedModule); }); @@ -4850,6 +4852,7 @@ namespace ts { function getCompletionEntriesFromDirectories(host: LanguageServiceHost, options: CompilerOptions, directory: string, result: ImportCompletionEntry[]) { if (host.getDirectories && directoryProbablyExists(directory, host)) { forEach(host.getDirectories(directory), typeDirectory => { + typeDirectory = normalizePath(typeDirectory); result.push(createCompletionEntryForModule(getBaseFileName(typeDirectory), ScriptElementKind.externalModuleName)); }); } diff --git a/src/services/shims.ts b/src/services/shims.ts index 9f4d9d24a4..70e4975ce4 100644 --- a/src/services/shims.ts +++ b/src/services/shims.ts @@ -67,7 +67,7 @@ namespace ts { getProjectVersion?(): string; useCaseSensitiveFileNames?(): boolean; - readDirectory(path: string, extensions?: string, exclude?: string, include?: string): string; + readDirectory(rootDir: string, extension: string, basePaths?: string, excludeEx?: string, includeFileEx?: string, includeDirEx?: string, depth?: number): string; readFile(path: string, encoding?: string): string; resolvePath(path: string): string; fileExists(path: string): boolean; @@ -417,12 +417,17 @@ namespace ts { return this.shimHost.getDefaultLibFileName(JSON.stringify(options)); } - public readDirectory(path: string, extensions?: string[], exclude?: string[], include?: string[]): string[] { + public readDirectory(path: string, extensions?: string[], exclude?: string[], include?: string[], depth?: number): string[] { + const pattern = getFileMatcherPatterns(path, extensions, exclude, include, + this.shimHost.useCaseSensitiveFileNames(), this.shimHost.getCurrentDirectory()); return JSON.parse(this.shimHost.readDirectory( path, - extensions ? JSON.stringify(extensions) : undefined, - exclude ? JSON.stringify(exclude) : undefined, - include ? JSON.stringify(include) : undefined + JSON.stringify(extensions), + JSON.stringify(pattern.basePaths), + pattern.excludePattern, + pattern.includeFilePattern, + pattern.includeDirectoryPattern, + depth )); } From 0f22079d9e52f3fd4c26b9999ef20a774896649b Mon Sep 17 00:00:00 2001 From: Richard Knoll Date: Thu, 4 Aug 2016 18:17:41 -0700 Subject: [PATCH 22/39] Remove trailing slashes, remove mostly useless IO, fix script element kind for files --- src/services/services.ts | 35 +++---------------- .../completionForStringLiteralImport1.ts | 2 +- ...etionForStringLiteralNonrelativeImport4.ts | 4 +-- ...etionForStringLiteralNonrelativeImport6.ts | 4 +-- ...mpletionForStringLiteralRelativeImport1.ts | 6 ++-- ...mpletionForStringLiteralRelativeImport3.ts | 8 ++--- ...mpletionForStringLiteralRelativeImport4.ts | 2 +- .../completionForTripleSlashReference1.ts | 6 ++-- .../completionForTripleSlashReference3.ts | 8 ++--- 9 files changed, 24 insertions(+), 51 deletions(-) diff --git a/src/services/services.ts b/src/services/services.ts index 7b523d7e7a..6cb9254799 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1865,7 +1865,6 @@ namespace ts { interface VisibleModuleInfo { moduleName: string; moduleDir: string; - canBeImported: boolean; } export interface DisplayPartsSymbolWriter extends SymbolWriter { @@ -4605,7 +4604,7 @@ namespace ts { if (!duplicate) { result.push({ name: fileName, - kind: ScriptElementKind.directory, + kind: ScriptElementKind.scriptElement, sortText: fileName }); } @@ -4618,7 +4617,7 @@ namespace ts { const directoryName = getBaseFileName(normalizePath(d)); result.push({ - name: ensureTrailingDirectorySeparator(directoryName), + name: directoryName, kind: ScriptElementKind.directory, sortText: directoryName }); @@ -4750,7 +4749,7 @@ namespace ts { if (!options.moduleResolution || options.moduleResolution === ModuleResolutionKind.NodeJs) { forEach(enumerateNodeModulesVisibleToScript(host, scriptPath), visibleModule => { if (!isNestedModule) { - nonRelativeModules.push(visibleModule.canBeImported ? visibleModule.moduleName : ensureTrailingDirectorySeparator(visibleModule.moduleName)); + nonRelativeModules.push(visibleModule.moduleName); } else { const nestedFiles = host.readDirectory(visibleModule.moduleDir, supportedTypeScriptExtensions, /*exclude*/undefined, /*include*/["./*"]); @@ -4904,8 +4903,7 @@ namespace ts { const moduleDir = combinePaths(nodeModulesDir, moduleName); result.push({ moduleName, - moduleDir, - canBeImported: moduleCanBeImported(moduleDir) + moduleDir }); }); }); @@ -4930,31 +4928,6 @@ namespace ts { } } } - - /* - * A module can be imported by name alone if one of the following is true: - * It defines the "typings" property in its package.json - * The module has a "main" export and an index.d.ts file - * The module has an index.ts - */ - function moduleCanBeImported(modulePath: string): boolean { - const packagePath = combinePaths(modulePath, "package.json"); - - let hasMainExport = false; - if (host.fileExists(packagePath)) { - const package = tryReadingPackageJson(packagePath); - if (package) { - if (package.typings) { - return true; - } - hasMainExport = !!package.main; - } - } - - hasMainExport = hasMainExport || host.fileExists(combinePaths(modulePath, "index.js")); - - return (hasMainExport && host.fileExists(combinePaths(modulePath, "index.d.ts"))) || host.fileExists(combinePaths(modulePath, "index.ts")); - } } function createCompletionEntryForModule(name: string, kind: string): ImportCompletionEntry { diff --git a/tests/cases/fourslash/completionForStringLiteralImport1.ts b/tests/cases/fourslash/completionForStringLiteralImport1.ts index 239c3be42f..5fcb3e42f1 100644 --- a/tests/cases/fourslash/completionForStringLiteralImport1.ts +++ b/tests/cases/fourslash/completionForStringLiteralImport1.ts @@ -28,4 +28,4 @@ goTo.marker("2"); verify.importModuleCompletionListContains("some-module", 2); goTo.marker("3"); -verify.importModuleCompletionListContains("fourslash/", 3); \ No newline at end of file +verify.importModuleCompletionListContains("fourslash", 3); \ No newline at end of file diff --git a/tests/cases/fourslash/completionForStringLiteralNonrelativeImport4.ts b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport4.ts index f4a9ae2d53..ef926a7c8a 100644 --- a/tests/cases/fourslash/completionForStringLiteralNonrelativeImport4.ts +++ b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport4.ts @@ -25,8 +25,8 @@ const kinds = ["import_as", "import_equals", "require"]; for (const kind of kinds) { goTo.marker(kind + "0"); - verify.importModuleCompletionListContains("fake-module/"); + verify.importModuleCompletionListContains("fake-module"); verify.importModuleCompletionListContains("fake-module2"); - verify.importModuleCompletionListContains("fake-module3/"); + verify.importModuleCompletionListContains("fake-module3"); verify.not.importModuleCompletionListItemsCountIsGreaterThan(3); } \ No newline at end of file diff --git a/tests/cases/fourslash/completionForStringLiteralNonrelativeImport6.ts b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport6.ts index 263b57d9d2..c430310a21 100644 --- a/tests/cases/fourslash/completionForStringLiteralNonrelativeImport6.ts +++ b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport6.ts @@ -52,8 +52,8 @@ const kinds = ["import_as", "import_equals", "require"]; for (const kind of kinds) { goTo.marker(kind + "0"); - verify.importModuleCompletionListContains("module-no-main/"); - verify.importModuleCompletionListContains("module-no-main-index-d-ts/"); + verify.importModuleCompletionListContains("module-no-main"); + verify.importModuleCompletionListContains("module-no-main-index-d-ts"); verify.importModuleCompletionListContains("module-index-ts"); verify.importModuleCompletionListContains("module-index-d-ts-explicit-main"); verify.importModuleCompletionListContains("module-index-d-ts-default-main"); diff --git a/tests/cases/fourslash/completionForStringLiteralRelativeImport1.ts b/tests/cases/fourslash/completionForStringLiteralRelativeImport1.ts index 52d62f3b4f..3257dff40a 100644 --- a/tests/cases/fourslash/completionForStringLiteralRelativeImport1.ts +++ b/tests/cases/fourslash/completionForStringLiteralRelativeImport1.ts @@ -51,8 +51,8 @@ for (const kind of kinds) { verify.importModuleCompletionListContains("f1"); verify.importModuleCompletionListContains("f2"); verify.importModuleCompletionListContains("e1"); - verify.importModuleCompletionListContains("folder/"); - verify.importModuleCompletionListContains("parentTest/"); + verify.importModuleCompletionListContains("folder"); + verify.importModuleCompletionListContains("parentTest"); verify.not.importModuleCompletionListItemsCountIsGreaterThan(5); goTo.marker(kind + "2"); @@ -63,6 +63,6 @@ for (const kind of kinds) { goTo.marker(kind + "3"); verify.importModuleCompletionListContains("f4"); verify.importModuleCompletionListContains("g1"); - verify.importModuleCompletionListContains("sub/"); + verify.importModuleCompletionListContains("sub"); verify.not.importModuleCompletionListItemsCountIsGreaterThan(3); } \ No newline at end of file diff --git a/tests/cases/fourslash/completionForStringLiteralRelativeImport3.ts b/tests/cases/fourslash/completionForStringLiteralRelativeImport3.ts index 4f35612c5c..95e4e51577 100644 --- a/tests/cases/fourslash/completionForStringLiteralRelativeImport3.ts +++ b/tests/cases/fourslash/completionForStringLiteralRelativeImport3.ts @@ -32,18 +32,18 @@ const kinds = ["import_as", "import_equals", "require"]; for (const kind of kinds) { goTo.marker(kind + "0"); - verify.importModuleCompletionListContains("fourslash/"); + verify.importModuleCompletionListContains("fourslash"); verify.not.importModuleCompletionListItemsCountIsGreaterThan(1); goTo.marker(kind + "1"); - verify.importModuleCompletionListContains("fourslash/"); + verify.importModuleCompletionListContains("fourslash"); verify.not.importModuleCompletionListItemsCountIsGreaterThan(1); goTo.marker(kind + "2"); verify.importModuleCompletionListContains("f1"); verify.importModuleCompletionListContains("f2"); verify.importModuleCompletionListContains("e1"); - verify.importModuleCompletionListContains("folder/"); - verify.importModuleCompletionListContains("tests/"); + verify.importModuleCompletionListContains("folder"); + verify.importModuleCompletionListContains("tests"); verify.not.importModuleCompletionListItemsCountIsGreaterThan(5); } \ No newline at end of file diff --git a/tests/cases/fourslash/completionForStringLiteralRelativeImport4.ts b/tests/cases/fourslash/completionForStringLiteralRelativeImport4.ts index 773c4d90d7..f89eaa567a 100644 --- a/tests/cases/fourslash/completionForStringLiteralRelativeImport4.ts +++ b/tests/cases/fourslash/completionForStringLiteralRelativeImport4.ts @@ -43,7 +43,7 @@ for (const kind of kinds) { verify.importModuleCompletionListContains("module0"); verify.importModuleCompletionListContains("module1"); verify.importModuleCompletionListContains("module2"); - verify.importModuleCompletionListContains("more/"); + verify.importModuleCompletionListContains("more"); // Should not contain itself verify.not.importModuleCompletionListItemsCountIsGreaterThan(4); diff --git a/tests/cases/fourslash/completionForTripleSlashReference1.ts b/tests/cases/fourslash/completionForTripleSlashReference1.ts index 8c6188ac90..66dfa5f93c 100644 --- a/tests/cases/fourslash/completionForTripleSlashReference1.ts +++ b/tests/cases/fourslash/completionForTripleSlashReference1.ts @@ -34,16 +34,16 @@ for (let i = 0; i < 5; i++) { verify.importModuleCompletionListContains("f1.d.ts"); verify.importModuleCompletionListContains("f2.tsx"); verify.importModuleCompletionListContains("e1.ts"); - verify.importModuleCompletionListContains("parentTest/"); + verify.importModuleCompletionListContains("parentTest"); verify.not.importModuleCompletionListItemsCountIsGreaterThan(5); } goTo.marker("5"); verify.importModuleCompletionListContains("g1.ts"); -verify.importModuleCompletionListContains("sub/"); +verify.importModuleCompletionListContains("sub"); verify.not.importModuleCompletionListItemsCountIsGreaterThan(2); goTo.marker("6"); verify.importModuleCompletionListContains("g1.ts"); -verify.importModuleCompletionListContains("sub/"); +verify.importModuleCompletionListContains("sub"); verify.not.importModuleCompletionListItemsCountIsGreaterThan(2); \ No newline at end of file diff --git a/tests/cases/fourslash/completionForTripleSlashReference3.ts b/tests/cases/fourslash/completionForTripleSlashReference3.ts index dda6ae92b4..af8d49d9df 100644 --- a/tests/cases/fourslash/completionForTripleSlashReference3.ts +++ b/tests/cases/fourslash/completionForTripleSlashReference3.ts @@ -25,17 +25,17 @@ //// /*e2*/ goTo.marker("0"); -verify.importModuleCompletionListContains("fourslash/"); +verify.importModuleCompletionListContains("fourslash"); verify.not.importModuleCompletionListItemsCountIsGreaterThan(1); goTo.marker("1"); -verify.importModuleCompletionListContains("fourslash/"); +verify.importModuleCompletionListContains("fourslash"); verify.not.importModuleCompletionListItemsCountIsGreaterThan(1); goTo.marker("2"); verify.importModuleCompletionListContains("f1.ts"); verify.importModuleCompletionListContains("f2.tsx"); verify.importModuleCompletionListContains("e1.ts"); -verify.importModuleCompletionListContains("folder/"); -verify.importModuleCompletionListContains("tests/"); +verify.importModuleCompletionListContains("folder"); +verify.importModuleCompletionListContains("tests"); verify.not.importModuleCompletionListItemsCountIsGreaterThan(5); \ No newline at end of file From ecdbdb33af5b360427d15635a4826556ce93f472 Mon Sep 17 00:00:00 2001 From: Richard Knoll Date: Fri, 5 Aug 2016 17:42:52 -0700 Subject: [PATCH 23/39] Fixing the filtering of nested module completions --- src/services/services.ts | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/src/services/services.ts b/src/services/services.ts index 6cb9254799..1a95249136 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -4728,30 +4728,33 @@ namespace ts { function enumeratePotentialNonRelativeModules(fragment: string, scriptPath: string, options: CompilerOptions): string[] { // Check If this is a nested module - const isNestedModule = fragment.indexOf(directorySeparator) !== -1 ; + const isNestedModule = fragment.indexOf(directorySeparator) !== -1; + const moduleNameFragment = isNestedModule ? fragment.substr(0, fragment.lastIndexOf(directorySeparator)) : undefined; // Get modules that the type checker picked up const ambientModules = ts.map(program.getTypeChecker().getAmbientModules(), sym => stripQuotes(sym.name)); let nonRelativeModules = ts.filter(ambientModules, moduleName => startsWith(moduleName, fragment)); // Nested modules of the form "module-name/sub" need to be adjusted to only return the string - // after the last '/' that appears in the fragment because editors insert the completion - // only after that character - nonRelativeModules = ts.map(nonRelativeModules, moduleName => { - if (moduleName.indexOf(directorySeparator) !== -1) { - if (isNestedModule) { - return moduleName.substr(fragment.lastIndexOf(directorySeparator) + 1); + // after the last '/' that appears in the fragment because that's where the replacement span + // starts + if (isNestedModule) { + const moduleNameWithSeperator = ensureTrailingDirectorySeparator(moduleNameFragment); + nonRelativeModules = ts.map(nonRelativeModules, moduleName => { + if (startsWith(fragment, moduleNameWithSeperator)) { + return moduleName.substr(moduleNameWithSeperator.length); } - } - return moduleName; - }); + return moduleName; + }); + } + if (!options.moduleResolution || options.moduleResolution === ModuleResolutionKind.NodeJs) { forEach(enumerateNodeModulesVisibleToScript(host, scriptPath), visibleModule => { if (!isNestedModule) { nonRelativeModules.push(visibleModule.moduleName); } - else { + else if (startsWith(visibleModule.moduleName, moduleNameFragment)) { const nestedFiles = host.readDirectory(visibleModule.moduleDir, supportedTypeScriptExtensions, /*exclude*/undefined, /*include*/["./*"]); forEach(nestedFiles, (f) => { From e11d5e9de632bdd93a26eb2133050d4934aec1c8 Mon Sep 17 00:00:00 2001 From: Richard Knoll Date: Fri, 5 Aug 2016 17:53:04 -0700 Subject: [PATCH 24/39] Cleaning up test cases and adding a few more --- .../completionForStringLiteralImport1.ts | 2 + .../completionForStringLiteralImport2.ts | 2 + ...etionForStringLiteralNonrelativeImport1.ts | 2 + ...tionForStringLiteralNonrelativeImport10.ts | 2 + ...tionForStringLiteralNonrelativeImport11.ts | 38 ++++++++++++ ...etionForStringLiteralNonrelativeImport2.ts | 2 + ...etionForStringLiteralNonrelativeImport3.ts | 3 + ...etionForStringLiteralNonrelativeImport4.ts | 2 + ...etionForStringLiteralNonrelativeImport5.ts | 2 + ...etionForStringLiteralNonrelativeImport6.ts | 62 ------------------- ...etionForStringLiteralNonrelativeImport7.ts | 3 + ...etionForStringLiteralNonrelativeImport8.ts | 2 + ...etionForStringLiteralNonrelativeImport9.ts | 2 + ...rStringLiteralNonrelativeImportTypings1.ts | 2 + ...rStringLiteralNonrelativeImportTypings2.ts | 2 + ...rStringLiteralNonrelativeImportTypings3.ts | 2 + ...mpletionForStringLiteralRelativeImport1.ts | 2 + ...mpletionForStringLiteralRelativeImport2.ts | 3 + ...mpletionForStringLiteralRelativeImport3.ts | 2 + ...mpletionForStringLiteralRelativeImport4.ts | 2 + .../completionForTripleSlashReference1.ts | 2 + .../completionForTripleSlashReference2.ts | 3 + .../completionForTripleSlashReference3.ts | 2 + .../completionForTripleSlashReference4.ts | 43 +++++++++++++ 24 files changed, 127 insertions(+), 62 deletions(-) create mode 100644 tests/cases/fourslash/completionForStringLiteralNonrelativeImport11.ts delete mode 100644 tests/cases/fourslash/completionForStringLiteralNonrelativeImport6.ts create mode 100644 tests/cases/fourslash/completionForTripleSlashReference4.ts diff --git a/tests/cases/fourslash/completionForStringLiteralImport1.ts b/tests/cases/fourslash/completionForStringLiteralImport1.ts index 5fcb3e42f1..482726f630 100644 --- a/tests/cases/fourslash/completionForStringLiteralImport1.ts +++ b/tests/cases/fourslash/completionForStringLiteralImport1.ts @@ -1,5 +1,7 @@ /// +// Should define spans for replacement that appear after the last directory seperator in import statements + // @typeRoots: my_typings // @Filename: test.ts diff --git a/tests/cases/fourslash/completionForStringLiteralImport2.ts b/tests/cases/fourslash/completionForStringLiteralImport2.ts index 2910d9e147..43896f1f39 100644 --- a/tests/cases/fourslash/completionForStringLiteralImport2.ts +++ b/tests/cases/fourslash/completionForStringLiteralImport2.ts @@ -1,5 +1,7 @@ /// +// Should define spans for replacement that appear after the last directory seperator in triple slash references + // @typeRoots: my_typings // @Filename: test.ts diff --git a/tests/cases/fourslash/completionForStringLiteralNonrelativeImport1.ts b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport1.ts index f8ea68a271..d6f38431ce 100644 --- a/tests/cases/fourslash/completionForStringLiteralNonrelativeImport1.ts +++ b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport1.ts @@ -1,5 +1,7 @@ /// +// Should give completions for node modules and files within those modules with ts file extensions + // @Filename: tests/test0.ts //// import * as foo1 from "f/*import_as0*/ //// import * as foo2 from "fake-module//*import_as1*/ diff --git a/tests/cases/fourslash/completionForStringLiteralNonrelativeImport10.ts b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport10.ts index 393ffed0c5..aa9fd97664 100644 --- a/tests/cases/fourslash/completionForStringLiteralNonrelativeImport10.ts +++ b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport10.ts @@ -1,5 +1,7 @@ /// +// Should not give node module completions if classic module resolution is enabled + // @moduleResolution: classic // @Filename: dir1/dir2/dir3/dir4/test0.ts diff --git a/tests/cases/fourslash/completionForStringLiteralNonrelativeImport11.ts b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport11.ts new file mode 100644 index 0000000000..70d39182a8 --- /dev/null +++ b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport11.ts @@ -0,0 +1,38 @@ +/// + +// Should handle nested files in folders discovered via the baseUrl compiler option + +// @baseUrl: tests/cases/fourslash/modules + +// @Filename: tests/test0.ts +//// import * as foo1 from "subfolder//*import_as0*/ +//// import foo2 = require("subfolder//*import_equals0*/ +//// var foo3 = require("subfolder//*require0*/ + +//// import * as foo1 from "module-from-node//*import_as1*/ +//// import foo2 = require("module-from-node//*import_equals1*/ +//// var foo3 = require("module-from-node//*require1*/ + +// @Filename: modules/subfolder/module.ts +//// export var x = 5; + +// @Filename: package.json +//// { "dependencies": { "module-from-node": "latest" } } +// @Filename: node_modules/module-from-node/index.ts +//// /*module1*/ + + + +const kinds = ["import_as", "import_equals", "require"]; + +for (const kind of kinds) { + goTo.marker(kind + "0"); + + verify.importModuleCompletionListContains("module"); + verify.not.importModuleCompletionListItemsCountIsGreaterThan(1); + + goTo.marker(kind + "1"); + + verify.importModuleCompletionListContains("index"); + verify.not.importModuleCompletionListItemsCountIsGreaterThan(1); +} diff --git a/tests/cases/fourslash/completionForStringLiteralNonrelativeImport2.ts b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport2.ts index 08b92b0529..bc2529e86f 100644 --- a/tests/cases/fourslash/completionForStringLiteralNonrelativeImport2.ts +++ b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport2.ts @@ -1,5 +1,7 @@ /// +// Should not give duplicate entries for similarly named files with different extensions + // @Filename: tests/test0.ts //// import * as foo1 from "fake-module//*import_as0*/ //// import foo2 = require("fake-module//*import_equals0*/ diff --git a/tests/cases/fourslash/completionForStringLiteralNonrelativeImport3.ts b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport3.ts index 9d9beac16e..9b1fd45118 100644 --- a/tests/cases/fourslash/completionForStringLiteralNonrelativeImport3.ts +++ b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport3.ts @@ -1,4 +1,7 @@ /// + +// Should give completions for js files in node modules when allowJs is set to true + // @allowJs: true // @Filename: tests/test0.ts diff --git a/tests/cases/fourslash/completionForStringLiteralNonrelativeImport4.ts b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport4.ts index ef926a7c8a..b9be9f9021 100644 --- a/tests/cases/fourslash/completionForStringLiteralNonrelativeImport4.ts +++ b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport4.ts @@ -1,5 +1,7 @@ /// +// Should give completions for all node modules visible to the script + // @Filename: dir1/dir2/dir3/dir4/test0.ts //// import * as foo1 from "f/*import_as0*/ //// import foo4 = require("f/*import_equals0*/ diff --git a/tests/cases/fourslash/completionForStringLiteralNonrelativeImport5.ts b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport5.ts index 9e0c756a91..4afa5854bf 100644 --- a/tests/cases/fourslash/completionForStringLiteralNonrelativeImport5.ts +++ b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport5.ts @@ -1,5 +1,7 @@ /// +// Should give completions for ambiently declared modules + // @Filename: test0.ts //// /// //// /// diff --git a/tests/cases/fourslash/completionForStringLiteralNonrelativeImport6.ts b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport6.ts deleted file mode 100644 index c430310a21..0000000000 --- a/tests/cases/fourslash/completionForStringLiteralNonrelativeImport6.ts +++ /dev/null @@ -1,62 +0,0 @@ -/// - -// @Filename: tests/test0.ts -//// import * as foo1 from "module-/*import_as0*/ -//// import foo2 = require("module-/*import_equals0*/ -//// var foo3 = require("module-/*require0*/ - -// @Filename: package.json -//// { "dependencies": { -//// "module-no-main": "latest", -//// "module-no-main-index-d-ts": "latest", -//// "module-index-ts": "latest", -//// "module-index-d-ts-explicit-main": "latest", -//// "module-index-d-ts-default-main": "latest", -//// "module-typings": "latest" -//// } } - -// @Filename: node_modules/module-no-main/package.json -//// { } - -// @Filename: node_modules/module-no-main-index-d-ts/package.json -//// { } -// @Filename: node_modules/module-no-main-index-d-ts/index.d.ts -//// /*module-no-main-index-d-ts*/ - -// @Filename: node_modules/module-index-ts/package.json -//// { } -// @Filename: node_modules/module-index-ts/index.ts -//// /*module-index-ts*/ - -// @Filename: node_modules/module-index-d-ts-explicit-main/package.json -//// { "main":"./notIndex.js" } -// @Filename: node_modules/module-index-d-ts-explicit-main/notIndex.js -//// /*module-index-d-ts-explicit-main*/ -// @Filename: node_modules/module-index-d-ts-explicit-main/index.d.ts -//// /*module-index-d-ts-explicit-main2*/ - -// @Filename: node_modules/module-index-d-ts-default-main/package.json -//// { } -// @Filename: node_modules/module-index-d-ts-default-main/index.js -//// /*module-index-d-ts-default-main*/ -// @Filename: node_modules/module-index-d-ts-default-main/index.d.ts -//// /*module-index-d-ts-default-main2*/ - -// @Filename: node_modules/module-typings/package.json -//// { "typings":"./types.d.ts" } -// @Filename: node_modules/module-typings/types.d.ts -//// /*module-typings*/ - -const kinds = ["import_as", "import_equals", "require"]; - -for (const kind of kinds) { - goTo.marker(kind + "0"); - - verify.importModuleCompletionListContains("module-no-main"); - verify.importModuleCompletionListContains("module-no-main-index-d-ts"); - verify.importModuleCompletionListContains("module-index-ts"); - verify.importModuleCompletionListContains("module-index-d-ts-explicit-main"); - verify.importModuleCompletionListContains("module-index-d-ts-default-main"); - verify.importModuleCompletionListContains("module-typings"); - verify.not.importModuleCompletionListItemsCountIsGreaterThan(6); -} diff --git a/tests/cases/fourslash/completionForStringLiteralNonrelativeImport7.ts b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport7.ts index d08ebd17b7..eca5a3e541 100644 --- a/tests/cases/fourslash/completionForStringLiteralNonrelativeImport7.ts +++ b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport7.ts @@ -1,4 +1,7 @@ /// + +// Should give completions for files that are discovered via the baseUrl compiler option + // @baseUrl: tests/cases/fourslash/modules // @Filename: tests/test0.ts diff --git a/tests/cases/fourslash/completionForStringLiteralNonrelativeImport8.ts b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport8.ts index b7e745f814..5489399143 100644 --- a/tests/cases/fourslash/completionForStringLiteralNonrelativeImport8.ts +++ b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport8.ts @@ -1,5 +1,7 @@ /// +// Should give completions for modules referenced via baseUrl and paths compiler options with wildcards + // @Filename: tsconfig.json //// { //// "compilerOptions": { diff --git a/tests/cases/fourslash/completionForStringLiteralNonrelativeImport9.ts b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport9.ts index 31984df908..bcdb731d1f 100644 --- a/tests/cases/fourslash/completionForStringLiteralNonrelativeImport9.ts +++ b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport9.ts @@ -1,5 +1,7 @@ /// +// Should give completions for modules referenced via baseUrl and paths compiler options with explicit name mappings + // @Filename: tsconfig.json //// { //// "compilerOptions": { diff --git a/tests/cases/fourslash/completionForStringLiteralNonrelativeImportTypings1.ts b/tests/cases/fourslash/completionForStringLiteralNonrelativeImportTypings1.ts index 1b77c57f5c..9cf2af9c87 100644 --- a/tests/cases/fourslash/completionForStringLiteralNonrelativeImportTypings1.ts +++ b/tests/cases/fourslash/completionForStringLiteralNonrelativeImportTypings1.ts @@ -1,5 +1,7 @@ /// +// Should give completions for typings discovered via the typeRoots compiler option + // @typeRoots: my_typings,my_other_typings diff --git a/tests/cases/fourslash/completionForStringLiteralNonrelativeImportTypings2.ts b/tests/cases/fourslash/completionForStringLiteralNonrelativeImportTypings2.ts index 3e991b3d4b..1452c6c1c6 100644 --- a/tests/cases/fourslash/completionForStringLiteralNonrelativeImportTypings2.ts +++ b/tests/cases/fourslash/completionForStringLiteralNonrelativeImportTypings2.ts @@ -1,5 +1,7 @@ /// +// Should respect the types compiler option when giving completions + // @typeRoots: my_typings,my_other_typings // @types: module-x,module-z diff --git a/tests/cases/fourslash/completionForStringLiteralNonrelativeImportTypings3.ts b/tests/cases/fourslash/completionForStringLiteralNonrelativeImportTypings3.ts index add1238cef..f4a4dbaaa9 100644 --- a/tests/cases/fourslash/completionForStringLiteralNonrelativeImportTypings3.ts +++ b/tests/cases/fourslash/completionForStringLiteralNonrelativeImportTypings3.ts @@ -1,5 +1,7 @@ /// +// Should give completions for typings discovered in all visible @types directories + // @Filename: subdirectory/test0.ts //// /// //// import * as foo1 from "m/*import_as0*/ diff --git a/tests/cases/fourslash/completionForStringLiteralRelativeImport1.ts b/tests/cases/fourslash/completionForStringLiteralRelativeImport1.ts index 3257dff40a..909e2c6b14 100644 --- a/tests/cases/fourslash/completionForStringLiteralRelativeImport1.ts +++ b/tests/cases/fourslash/completionForStringLiteralRelativeImport1.ts @@ -1,5 +1,7 @@ /// +// Should give completions for ts files when allowJs is false + // @Filename: test0.ts //// import * as foo1 from "./*import_as0*/ //// import * as foo2 from ".//*import_as1*/ diff --git a/tests/cases/fourslash/completionForStringLiteralRelativeImport2.ts b/tests/cases/fourslash/completionForStringLiteralRelativeImport2.ts index 72b28d39b8..8c8cb405cd 100644 --- a/tests/cases/fourslash/completionForStringLiteralRelativeImport2.ts +++ b/tests/cases/fourslash/completionForStringLiteralRelativeImport2.ts @@ -1,4 +1,7 @@ /// + +// Should give completions for ts and js files when allowJs is true + // @allowJs: true // @Filename: test0.ts diff --git a/tests/cases/fourslash/completionForStringLiteralRelativeImport3.ts b/tests/cases/fourslash/completionForStringLiteralRelativeImport3.ts index 95e4e51577..a2035ee7fd 100644 --- a/tests/cases/fourslash/completionForStringLiteralRelativeImport3.ts +++ b/tests/cases/fourslash/completionForStringLiteralRelativeImport3.ts @@ -1,5 +1,7 @@ /// +// Should give completions for absolute paths + // @Filename: tests/test0.ts //// import * as foo1 from "c:/tests/cases/f/*import_as0*/ //// import * as foo2 from "c:/tests/cases/fourslash/*import_as1*/ diff --git a/tests/cases/fourslash/completionForStringLiteralRelativeImport4.ts b/tests/cases/fourslash/completionForStringLiteralRelativeImport4.ts index f89eaa567a..df30b925d1 100644 --- a/tests/cases/fourslash/completionForStringLiteralRelativeImport4.ts +++ b/tests/cases/fourslash/completionForStringLiteralRelativeImport4.ts @@ -1,5 +1,7 @@ /// +// Should give completions for directories that are merged via the rootDirs compiler option + // @rootDirs: sub/src1,src2 // @Filename: src2/test0.ts diff --git a/tests/cases/fourslash/completionForTripleSlashReference1.ts b/tests/cases/fourslash/completionForTripleSlashReference1.ts index 66dfa5f93c..f8ea7fc4d4 100644 --- a/tests/cases/fourslash/completionForTripleSlashReference1.ts +++ b/tests/cases/fourslash/completionForTripleSlashReference1.ts @@ -1,5 +1,7 @@ /// +// Should give completions for relative references to ts files when allowJs is false + // @Filename: test0.ts //// /// + +// Should give completions for relative references to js and ts files when allowJs is true + // @allowJs: true // @Filename: test0.ts diff --git a/tests/cases/fourslash/completionForTripleSlashReference3.ts b/tests/cases/fourslash/completionForTripleSlashReference3.ts index af8d49d9df..2f92de19a2 100644 --- a/tests/cases/fourslash/completionForTripleSlashReference3.ts +++ b/tests/cases/fourslash/completionForTripleSlashReference3.ts @@ -1,5 +1,7 @@ /// +// Should give completions for absolute paths + // @Filename: tests/test0.ts //// /// Date: Mon, 8 Aug 2016 11:04:17 -0700 Subject: [PATCH 25/39] Moving some utility functions around --- src/services/services.ts | 21 --------------------- src/services/utilities.ts | 31 ++++++++++++++++++++++++++----- 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/src/services/services.ts b/src/services/services.ts index 1a95249136..ade5cc0b1b 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -2063,9 +2063,6 @@ namespace ts { sourceMapText?: string; } - // Matches the beginning of a triple slash directive - const tripleSlashDirectivePrefixRegex = /^\/\/\/\s* Date: Tue, 16 Aug 2016 14:55:19 -0700 Subject: [PATCH 26/39] Use rooted paths in the fourslash virtual file system --- src/compiler/checker.ts | 2 +- src/harness/fourslash.ts | 10 +++++++--- src/harness/harness.ts | 3 +++ src/harness/harnessLanguageService.ts | 20 ++++--------------- src/harness/virtualFileSystem.ts | 10 +++++++++- src/services/services.ts | 4 +--- ...sWhenEmitBlockingErrorOnOtherFile.baseline | 4 ++-- .../reference/getEmitOutput-pp.baseline | 8 ++++---- .../reference/getEmitOutput.baseline | 8 ++++---- ...etEmitOutputDeclarationMultiFiles.baseline | 8 ++++---- .../reference/getEmitOutputNoErrors.baseline | 2 +- .../getEmitOutputOnlyOneFile.baseline | 2 +- .../reference/getEmitOutputSourceMap.baseline | 4 ++-- .../getEmitOutputSourceRoot.baseline | 4 ++-- ...getEmitOutputSourceRootMultiFiles.baseline | 8 ++++---- .../getEmitOutputTsxFile_Preserve.baseline | 12 +++++------ .../getEmitOutputTsxFile_React.baseline | 12 +++++------ .../getEmitOutputWithDeclarationFile.baseline | 2 +- ...getEmitOutputWithDeclarationFile2.baseline | 4 ++-- ...mitOutputWithEarlySyntacticErrors.baseline | 2 +- .../getEmitOutputWithEmitterErrors.baseline | 2 +- .../getEmitOutputWithEmitterErrors2.baseline | 2 +- .../getEmitOutputWithSemanticErrors.baseline | 2 +- .../getEmitOutputWithSemanticErrors2.baseline | 4 ++-- ...ithSemanticErrorsForMultipleFiles.baseline | 4 ++-- ...thSyntacticErrorsForMultipleFiles.baseline | 2 +- .../getEmitOutputWithSyntaxErrors.baseline | 2 +- ...mpletionForStringLiteralRelativeImport4.ts | 2 +- .../fourslash/getEmitOutputSingleFile2.ts | 2 +- .../fourslash/renameForDefaultExport04.ts | 2 +- .../fourslash/renameForDefaultExport05.ts | 2 +- .../fourslash/renameForDefaultExport06.ts | 2 +- .../fourslash/renameForDefaultExport07.ts | 2 +- .../fourslash/renameForDefaultExport08.ts | 2 +- 34 files changed, 81 insertions(+), 80 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 498e8228e7..d850408107 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -19892,7 +19892,7 @@ namespace ts { function getAmbientModules(): Symbol[] { const result: Symbol[] = []; for (const sym in globals) { - if (globals.hasOwnProperty(sym) && ambientModuleSymbolRegex.test(sym)) { + if (hasProperty(globals, sym) && ambientModuleSymbolRegex.test(sym)) { result.push(globals[sym]); } } diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index 61041d1ab4..3ba8f5e3b1 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -2352,12 +2352,16 @@ namespace FourSlash { } export function runFourSlashTestContent(basePath: string, testType: FourSlashTestType, content: string, fileName: string): void { + // Give file paths an absolute path for the virtual file system + const absoluteBasePath = ts.combinePaths(Harness.virtualFileSystemRoot, basePath); + const absoluteFileName = ts.combinePaths(Harness.virtualFileSystemRoot, fileName); + // Parse out the files and their metadata - const testData = parseTestData(basePath, content, fileName); - const state = new TestState(basePath, testType, testData); + const testData = parseTestData(absoluteBasePath, content, absoluteFileName); + const state = new TestState(absoluteBasePath, testType, testData); const output = ts.transpileModule(content, { reportDiagnostics: true }); if (output.diagnostics.length > 0) { - throw new Error(`Syntax error in ${basePath}: ${output.diagnostics[0].messageText}`); + throw new Error(`Syntax error in ${absoluteBasePath}: ${output.diagnostics[0].messageText}`); } runCode(output.outputText, state); } diff --git a/src/harness/harness.ts b/src/harness/harness.ts index 6efb9400b3..35921f868e 100644 --- a/src/harness/harness.ts +++ b/src/harness/harness.ts @@ -458,6 +458,9 @@ namespace Harness { // harness always uses one kind of new line const harnessNewLine = "\r\n"; + // Roote for file paths that are stored in a virtual file system + export const virtualFileSystemRoot = "/"; + namespace IOImpl { declare class Enumerator { public atEnd(): boolean; diff --git a/src/harness/harnessLanguageService.ts b/src/harness/harnessLanguageService.ts index 50e3d42588..89997aeeab 100644 --- a/src/harness/harnessLanguageService.ts +++ b/src/harness/harnessLanguageService.ts @@ -123,7 +123,7 @@ namespace Harness.LanguageService { } export class LanguageServiceAdapterHost { - protected virtualFileSystem: Utils.VirtualFileSystem = new Utils.VirtualFileSystem(/*root*/"c:", /*useCaseSensitiveFilenames*/false); + protected virtualFileSystem: Utils.VirtualFileSystem = new Utils.VirtualFileSystem(virtualFileSystemRoot, /*useCaseSensitiveFilenames*/false); constructor(protected cancellationToken = DefaultHostCancellationToken.Instance, protected settings = ts.getDefaultCompilerOptions()) { @@ -191,7 +191,7 @@ namespace Harness.LanguageService { } return []; } - getCurrentDirectory(): string { return ""; } + getCurrentDirectory(): string { return virtualFileSystemRoot } getDefaultLibFileName(): string { return Harness.Compiler.defaultLibFileName; } getScriptFileNames(): string[] { return this.getFilenames(); } getScriptSnapshot(fileName: string): ts.IScriptSnapshot { @@ -211,7 +211,7 @@ namespace Harness.LanguageService { readDirectory(path: string, extensions?: string[], exclude?: string[], include?: string[]): string[] { return ts.matchFiles(path, extensions, exclude, include, /*useCaseSensitiveFileNames*/false, - /*currentDirectory*/"/", + this.getCurrentDirectory(), (p) => this.virtualFileSystem.getAccessibleFileSystemEntries(p)); } readFile(path: string, encoding?: string): string { @@ -220,19 +220,7 @@ namespace Harness.LanguageService { } resolvePath(path: string): string { if (!ts.isRootedDiskPath(path)) { - // An "absolute" path for fourslash is one that is contained within the tests directory - const components = ts.getNormalizedPathComponents(path, this.getCurrentDirectory()); - if (components.length) { - // If this is still a relative path after normalization (i.e. currentDirectory is relative), the root will be the empty string - if (!components[0]) { - components.splice(0, 1); - if (components[0] !== "tests") { - // If not contained within test, assume its relative to the directory containing the test files - return ts.normalizePath(ts.combinePaths("tests/cases/fourslash", components.join(ts.directorySeparator))); - } - } - return ts.normalizePath(components.join(ts.directorySeparator)); - } + path = ts.combinePaths(this.getCurrentDirectory(), path); } return ts.normalizePath(path); } diff --git a/src/harness/virtualFileSystem.ts b/src/harness/virtualFileSystem.ts index c2502bbc80..036965f75d 100644 --- a/src/harness/virtualFileSystem.ts +++ b/src/harness/virtualFileSystem.ts @@ -111,6 +111,7 @@ namespace Utils { getFileSystemEntries() { return this.root.getFileSystemEntries(); } addDirectory(path: string) { + path = this.normalizePathRoot(path); const components = ts.getNormalizedPathComponents(path, this.currentDirectory); let directory: VirtualDirectory = this.root; for (const component of components) { @@ -124,7 +125,7 @@ namespace Utils { } addFile(path: string, content?: T) { - const absolutePath = ts.getNormalizedAbsolutePath(path, this.currentDirectory); + const absolutePath = this.normalizePathRoot(ts.getNormalizedAbsolutePath(path, this.currentDirectory)); const fileName = ts.getBaseFileName(path); const directoryPath = ts.getDirectoryPath(absolutePath); const directory = this.addDirectory(directoryPath); @@ -141,6 +142,7 @@ namespace Utils { } traversePath(path: string) { + path = this.normalizePathRoot(path); let directory: VirtualDirectory = this.root; for (const component of ts.getNormalizedPathComponents(path, this.currentDirectory)) { const entry = directory.getFileSystemEntry(component); @@ -192,7 +194,13 @@ namespace Utils { } } + normalizePathRoot(path: string) { + const components = ts.getNormalizedPathComponents(path, this.currentDirectory); + // Toss the root component + components[0] = ""; + return components.join(ts.directorySeparator); + } } export class MockParseConfigHost extends VirtualFileSystem implements ts.ParseConfigHost { diff --git a/src/services/services.ts b/src/services/services.ts index bbcc9f9504..3050018911 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -3154,9 +3154,7 @@ namespace ts { writeFile: (fileName, data, writeByteOrderMark) => { }, getCurrentDirectory: () => currentDirectory, fileExists: (fileName): boolean => { - // stub missing host functionality - Debug.assert(!host.resolveModuleNames || !host.resolveTypeReferenceDirectives); - return hostCache.getOrCreateEntry(fileName) !== undefined; + return host.fileExists(fileName); }, readFile: (fileName): string => { // stub missing host functionality diff --git a/tests/baselines/reference/compileOnSaveWorksWhenEmitBlockingErrorOnOtherFile.baseline b/tests/baselines/reference/compileOnSaveWorksWhenEmitBlockingErrorOnOtherFile.baseline index f9bcca268e..a088006d14 100644 --- a/tests/baselines/reference/compileOnSaveWorksWhenEmitBlockingErrorOnOtherFile.baseline +++ b/tests/baselines/reference/compileOnSaveWorksWhenEmitBlockingErrorOnOtherFile.baseline @@ -1,8 +1,8 @@ EmitSkipped: true Diagnostics: - Cannot write file 'tests/cases/fourslash/b.js' because it would overwrite input file. + Cannot write file '/tests/cases/fourslash/b.js' because it would overwrite input file. EmitSkipped: false -FileName : tests/cases/fourslash/a.js +FileName : /tests/cases/fourslash/a.js function foo2() { return 30; } // no error - should emit a.js diff --git a/tests/baselines/reference/getEmitOutput-pp.baseline b/tests/baselines/reference/getEmitOutput-pp.baseline index d6952e4537..683b454a27 100644 --- a/tests/baselines/reference/getEmitOutput-pp.baseline +++ b/tests/baselines/reference/getEmitOutput-pp.baseline @@ -1,12 +1,12 @@ EmitSkipped: false -FileName : tests/cases/fourslash/shims-pp/inputFile1.js +FileName : /tests/cases/fourslash/shims-pp/inputFile1.js var x = 5; var Bar = (function () { function Bar() { } return Bar; }()); -FileName : tests/cases/fourslash/shims-pp/inputFile1.d.ts +FileName : /tests/cases/fourslash/shims-pp/inputFile1.d.ts declare var x: number; declare class Bar { x: string; @@ -14,14 +14,14 @@ declare class Bar { } EmitSkipped: false -FileName : tests/cases/fourslash/shims-pp/inputFile2.js +FileName : /tests/cases/fourslash/shims-pp/inputFile2.js var x1 = "hello world"; var Foo = (function () { function Foo() { } return Foo; }()); -FileName : tests/cases/fourslash/shims-pp/inputFile2.d.ts +FileName : /tests/cases/fourslash/shims-pp/inputFile2.d.ts declare var x1: string; declare class Foo { x: string; diff --git a/tests/baselines/reference/getEmitOutput.baseline b/tests/baselines/reference/getEmitOutput.baseline index 52c7d8a7aa..8251850e1b 100644 --- a/tests/baselines/reference/getEmitOutput.baseline +++ b/tests/baselines/reference/getEmitOutput.baseline @@ -1,12 +1,12 @@ EmitSkipped: false -FileName : tests/cases/fourslash/shims/inputFile1.js +FileName : /tests/cases/fourslash/shims/inputFile1.js var x = 5; var Bar = (function () { function Bar() { } return Bar; }()); -FileName : tests/cases/fourslash/shims/inputFile1.d.ts +FileName : /tests/cases/fourslash/shims/inputFile1.d.ts declare var x: number; declare class Bar { x: string; @@ -14,14 +14,14 @@ declare class Bar { } EmitSkipped: false -FileName : tests/cases/fourslash/shims/inputFile2.js +FileName : /tests/cases/fourslash/shims/inputFile2.js var x1 = "hello world"; var Foo = (function () { function Foo() { } return Foo; }()); -FileName : tests/cases/fourslash/shims/inputFile2.d.ts +FileName : /tests/cases/fourslash/shims/inputFile2.d.ts declare var x1: string; declare class Foo { x: string; diff --git a/tests/baselines/reference/getEmitOutputDeclarationMultiFiles.baseline b/tests/baselines/reference/getEmitOutputDeclarationMultiFiles.baseline index 5a6fb434b1..56eb932fb5 100644 --- a/tests/baselines/reference/getEmitOutputDeclarationMultiFiles.baseline +++ b/tests/baselines/reference/getEmitOutputDeclarationMultiFiles.baseline @@ -1,12 +1,12 @@ EmitSkipped: false -FileName : tests/cases/fourslash/inputFile1.js +FileName : /tests/cases/fourslash/inputFile1.js var x = 5; var Bar = (function () { function Bar() { } return Bar; }()); -FileName : tests/cases/fourslash/inputFile1.d.ts +FileName : /tests/cases/fourslash/inputFile1.d.ts declare var x: number; declare class Bar { x: string; @@ -14,14 +14,14 @@ declare class Bar { } EmitSkipped: false -FileName : tests/cases/fourslash/inputFile2.js +FileName : /tests/cases/fourslash/inputFile2.js var x1 = "hello world"; var Foo = (function () { function Foo() { } return Foo; }()); -FileName : tests/cases/fourslash/inputFile2.d.ts +FileName : /tests/cases/fourslash/inputFile2.d.ts declare var x1: string; declare class Foo { x: string; diff --git a/tests/baselines/reference/getEmitOutputNoErrors.baseline b/tests/baselines/reference/getEmitOutputNoErrors.baseline index 461cba4ea0..6469ab3d1a 100644 --- a/tests/baselines/reference/getEmitOutputNoErrors.baseline +++ b/tests/baselines/reference/getEmitOutputNoErrors.baseline @@ -1,5 +1,5 @@ EmitSkipped: false -FileName : tests/cases/fourslash/inputFile.js +FileName : /tests/cases/fourslash/inputFile.js var x; var M = (function () { function M() { diff --git a/tests/baselines/reference/getEmitOutputOnlyOneFile.baseline b/tests/baselines/reference/getEmitOutputOnlyOneFile.baseline index 4d1c88e45b..dbcfe28afa 100644 --- a/tests/baselines/reference/getEmitOutputOnlyOneFile.baseline +++ b/tests/baselines/reference/getEmitOutputOnlyOneFile.baseline @@ -1,5 +1,5 @@ EmitSkipped: false -FileName : tests/cases/fourslash/inputFile2.js +FileName : /tests/cases/fourslash/inputFile2.js var x; var Foo = (function () { function Foo() { diff --git a/tests/baselines/reference/getEmitOutputSourceMap.baseline b/tests/baselines/reference/getEmitOutputSourceMap.baseline index 65257d6360..25667b8b70 100644 --- a/tests/baselines/reference/getEmitOutputSourceMap.baseline +++ b/tests/baselines/reference/getEmitOutputSourceMap.baseline @@ -1,6 +1,6 @@ EmitSkipped: false -FileName : tests/cases/fourslash/inputFile.js.map -{"version":3,"file":"inputFile.js","sourceRoot":"","sources":["inputFile.ts"],"names":[],"mappings":"AAAA,IAAI,CAAC,GAAG,GAAG,CAAC;AACZ,IAAI,GAAG,GAAG,aAAa,CAAC;AACxB;IAAA;IAGA,CAAC;IAAD,QAAC;AAAD,CAAC,AAHD,IAGC"}FileName : tests/cases/fourslash/inputFile.js +FileName : /tests/cases/fourslash/inputFile.js.map +{"version":3,"file":"inputFile.js","sourceRoot":"","sources":["inputFile.ts"],"names":[],"mappings":"AAAA,IAAI,CAAC,GAAG,GAAG,CAAC;AACZ,IAAI,GAAG,GAAG,aAAa,CAAC;AACxB;IAAA;IAGA,CAAC;IAAD,QAAC;AAAD,CAAC,AAHD,IAGC"}FileName : /tests/cases/fourslash/inputFile.js var x = 109; var foo = "hello world"; var M = (function () { diff --git a/tests/baselines/reference/getEmitOutputSourceRoot.baseline b/tests/baselines/reference/getEmitOutputSourceRoot.baseline index d38078a08e..5881a61f22 100644 --- a/tests/baselines/reference/getEmitOutputSourceRoot.baseline +++ b/tests/baselines/reference/getEmitOutputSourceRoot.baseline @@ -1,6 +1,6 @@ EmitSkipped: false -FileName : tests/cases/fourslash/inputFile.js.map -{"version":3,"file":"inputFile.js","sourceRoot":"sourceRootDir/","sources":["inputFile.ts"],"names":[],"mappings":"AAAA,IAAI,CAAC,GAAG,GAAG,CAAC;AACZ,IAAI,GAAG,GAAG,aAAa,CAAC;AACxB;IAAA;IAGA,CAAC;IAAD,QAAC;AAAD,CAAC,AAHD,IAGC"}FileName : tests/cases/fourslash/inputFile.js +FileName : /tests/cases/fourslash/inputFile.js.map +{"version":3,"file":"inputFile.js","sourceRoot":"sourceRootDir/","sources":["inputFile.ts"],"names":[],"mappings":"AAAA,IAAI,CAAC,GAAG,GAAG,CAAC;AACZ,IAAI,GAAG,GAAG,aAAa,CAAC;AACxB;IAAA;IAGA,CAAC;IAAD,QAAC;AAAD,CAAC,AAHD,IAGC"}FileName : /tests/cases/fourslash/inputFile.js var x = 109; var foo = "hello world"; var M = (function () { diff --git a/tests/baselines/reference/getEmitOutputSourceRootMultiFiles.baseline b/tests/baselines/reference/getEmitOutputSourceRootMultiFiles.baseline index 50a6ad6860..62e3f2eb7e 100644 --- a/tests/baselines/reference/getEmitOutputSourceRootMultiFiles.baseline +++ b/tests/baselines/reference/getEmitOutputSourceRootMultiFiles.baseline @@ -1,6 +1,6 @@ EmitSkipped: false -FileName : tests/cases/fourslash/inputFile1.js.map -{"version":3,"file":"inputFile1.js","sourceRoot":"sourceRootDir/","sources":["inputFile1.ts"],"names":[],"mappings":"AAAA,IAAI,CAAC,GAAG,GAAG,CAAC;AACZ,IAAI,GAAG,GAAG,aAAa,CAAC;AACxB;IAAA;IAGA,CAAC;IAAD,QAAC;AAAD,CAAC,AAHD,IAGC"}FileName : tests/cases/fourslash/inputFile1.js +FileName : /tests/cases/fourslash/inputFile1.js.map +{"version":3,"file":"inputFile1.js","sourceRoot":"sourceRootDir/","sources":["inputFile1.ts"],"names":[],"mappings":"AAAA,IAAI,CAAC,GAAG,GAAG,CAAC;AACZ,IAAI,GAAG,GAAG,aAAa,CAAC;AACxB;IAAA;IAGA,CAAC;IAAD,QAAC;AAAD,CAAC,AAHD,IAGC"}FileName : /tests/cases/fourslash/inputFile1.js var x = 109; var foo = "hello world"; var M = (function () { @@ -10,8 +10,8 @@ var M = (function () { }()); //# sourceMappingURL=inputFile1.js.map EmitSkipped: false -FileName : tests/cases/fourslash/inputFile2.js.map -{"version":3,"file":"inputFile2.js","sourceRoot":"sourceRootDir/","sources":["inputFile2.ts"],"names":[],"mappings":"AAAA,IAAI,GAAG,GAAG,wBAAwB,CAAC;AACnC;IAAA;IAGA,CAAC;IAAD,QAAC;AAAD,CAAC,AAHD,IAGC"}FileName : tests/cases/fourslash/inputFile2.js +FileName : /tests/cases/fourslash/inputFile2.js.map +{"version":3,"file":"inputFile2.js","sourceRoot":"sourceRootDir/","sources":["inputFile2.ts"],"names":[],"mappings":"AAAA,IAAI,GAAG,GAAG,wBAAwB,CAAC;AACnC;IAAA;IAGA,CAAC;IAAD,QAAC;AAAD,CAAC,AAHD,IAGC"}FileName : /tests/cases/fourslash/inputFile2.js var bar = "hello world Typescript"; var C = (function () { function C() { diff --git a/tests/baselines/reference/getEmitOutputTsxFile_Preserve.baseline b/tests/baselines/reference/getEmitOutputTsxFile_Preserve.baseline index bebd1ff1dd..015ddba07d 100644 --- a/tests/baselines/reference/getEmitOutputTsxFile_Preserve.baseline +++ b/tests/baselines/reference/getEmitOutputTsxFile_Preserve.baseline @@ -1,6 +1,6 @@ EmitSkipped: false -FileName : tests/cases/fourslash/inputFile1.js.map -{"version":3,"file":"inputFile1.js","sourceRoot":"","sources":["inputFile1.ts"],"names":[],"mappings":"AAAA,kBAAkB;AACjB,IAAI,CAAC,GAAW,CAAC,CAAC;AAClB;IAAA;IAGA,CAAC;IAAD,UAAC;AAAD,CAAC,AAHD,IAGC"}FileName : tests/cases/fourslash/inputFile1.js +FileName : /tests/cases/fourslash/inputFile1.js.map +{"version":3,"file":"inputFile1.js","sourceRoot":"","sources":["inputFile1.ts"],"names":[],"mappings":"AAAA,kBAAkB;AACjB,IAAI,CAAC,GAAW,CAAC,CAAC;AAClB;IAAA;IAGA,CAAC;IAAD,UAAC;AAAD,CAAC,AAHD,IAGC"}FileName : /tests/cases/fourslash/inputFile1.js // regular ts file var t = 5; var Bar = (function () { @@ -8,7 +8,7 @@ var Bar = (function () { } return Bar; }()); -//# sourceMappingURL=inputFile1.js.mapFileName : tests/cases/fourslash/inputFile1.d.ts +//# sourceMappingURL=inputFile1.js.mapFileName : /tests/cases/fourslash/inputFile1.d.ts declare var t: number; declare class Bar { x: string; @@ -16,11 +16,11 @@ declare class Bar { } EmitSkipped: false -FileName : tests/cases/fourslash/inputFile2.jsx.map -{"version":3,"file":"inputFile2.jsx","sourceRoot":"","sources":["inputFile2.tsx"],"names":[],"mappings":"AAAA,IAAI,CAAC,GAAG,QAAQ,CAAC;AACjB,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAE,CAAC,CAAC,CAAC,EAAG,CAAA"}FileName : tests/cases/fourslash/inputFile2.jsx +FileName : /tests/cases/fourslash/inputFile2.jsx.map +{"version":3,"file":"inputFile2.jsx","sourceRoot":"","sources":["inputFile2.tsx"],"names":[],"mappings":"AAAA,IAAI,CAAC,GAAG,QAAQ,CAAC;AACjB,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAE,CAAC,CAAC,CAAC,EAAG,CAAA"}FileName : /tests/cases/fourslash/inputFile2.jsx var y = "my div"; var x =
; -//# sourceMappingURL=inputFile2.jsx.mapFileName : tests/cases/fourslash/inputFile2.d.ts +//# sourceMappingURL=inputFile2.jsx.mapFileName : /tests/cases/fourslash/inputFile2.d.ts declare var y: string; declare var x: any; diff --git a/tests/baselines/reference/getEmitOutputTsxFile_React.baseline b/tests/baselines/reference/getEmitOutputTsxFile_React.baseline index 5b668fd65e..b78cbecf6c 100644 --- a/tests/baselines/reference/getEmitOutputTsxFile_React.baseline +++ b/tests/baselines/reference/getEmitOutputTsxFile_React.baseline @@ -1,6 +1,6 @@ EmitSkipped: false -FileName : tests/cases/fourslash/inputFile1.js.map -{"version":3,"file":"inputFile1.js","sourceRoot":"","sources":["inputFile1.ts"],"names":[],"mappings":"AAAA,kBAAkB;AACjB,IAAI,CAAC,GAAW,CAAC,CAAC;AAClB;IAAA;IAGA,CAAC;IAAD,UAAC;AAAD,CAAC,AAHD,IAGC"}FileName : tests/cases/fourslash/inputFile1.js +FileName : /tests/cases/fourslash/inputFile1.js.map +{"version":3,"file":"inputFile1.js","sourceRoot":"","sources":["inputFile1.ts"],"names":[],"mappings":"AAAA,kBAAkB;AACjB,IAAI,CAAC,GAAW,CAAC,CAAC;AAClB;IAAA;IAGA,CAAC;IAAD,UAAC;AAAD,CAAC,AAHD,IAGC"}FileName : /tests/cases/fourslash/inputFile1.js // regular ts file var t = 5; var Bar = (function () { @@ -8,7 +8,7 @@ var Bar = (function () { } return Bar; }()); -//# sourceMappingURL=inputFile1.js.mapFileName : tests/cases/fourslash/inputFile1.d.ts +//# sourceMappingURL=inputFile1.js.mapFileName : /tests/cases/fourslash/inputFile1.d.ts declare var t: number; declare class Bar { x: string; @@ -16,11 +16,11 @@ declare class Bar { } EmitSkipped: false -FileName : tests/cases/fourslash/inputFile2.js.map -{"version":3,"file":"inputFile2.js","sourceRoot":"","sources":["inputFile2.tsx"],"names":[],"mappings":"AACA,IAAI,CAAC,GAAG,QAAQ,CAAC;AACjB,IAAI,CAAC,GAAG,qBAAC,GAAG,IAAC,IAAI,EAAG,CAAE,EAAG,CAAA"}FileName : tests/cases/fourslash/inputFile2.js +FileName : /tests/cases/fourslash/inputFile2.js.map +{"version":3,"file":"inputFile2.js","sourceRoot":"","sources":["inputFile2.tsx"],"names":[],"mappings":"AACA,IAAI,CAAC,GAAG,QAAQ,CAAC;AACjB,IAAI,CAAC,GAAG,qBAAC,GAAG,IAAC,IAAI,EAAG,CAAE,EAAG,CAAA"}FileName : /tests/cases/fourslash/inputFile2.js var y = "my div"; var x = React.createElement("div", {name: y}); -//# sourceMappingURL=inputFile2.js.mapFileName : tests/cases/fourslash/inputFile2.d.ts +//# sourceMappingURL=inputFile2.js.mapFileName : /tests/cases/fourslash/inputFile2.d.ts declare var React: any; declare var y: string; declare var x: any; diff --git a/tests/baselines/reference/getEmitOutputWithDeclarationFile.baseline b/tests/baselines/reference/getEmitOutputWithDeclarationFile.baseline index af4ccd1058..a10ef5967b 100644 --- a/tests/baselines/reference/getEmitOutputWithDeclarationFile.baseline +++ b/tests/baselines/reference/getEmitOutputWithDeclarationFile.baseline @@ -1,7 +1,7 @@ EmitSkipped: false EmitSkipped: false -FileName : tests/cases/fourslash/inputFile2.js +FileName : /tests/cases/fourslash/inputFile2.js var x1 = "hello world"; var Foo = (function () { function Foo() { diff --git a/tests/baselines/reference/getEmitOutputWithDeclarationFile2.baseline b/tests/baselines/reference/getEmitOutputWithDeclarationFile2.baseline index 8e43219e00..5c86532169 100644 --- a/tests/baselines/reference/getEmitOutputWithDeclarationFile2.baseline +++ b/tests/baselines/reference/getEmitOutputWithDeclarationFile2.baseline @@ -1,7 +1,7 @@ EmitSkipped: false EmitSkipped: false -FileName : tests/cases/fourslash/inputFile2.js +FileName : /tests/cases/fourslash/inputFile2.js "use strict"; var Foo = (function () { function Foo() { @@ -11,6 +11,6 @@ var Foo = (function () { exports.Foo = Foo; EmitSkipped: false -FileName : tests/cases/fourslash/inputFile3.js +FileName : /tests/cases/fourslash/inputFile3.js var x = "hello"; diff --git a/tests/baselines/reference/getEmitOutputWithEarlySyntacticErrors.baseline b/tests/baselines/reference/getEmitOutputWithEarlySyntacticErrors.baseline index 9e2e9978f4..6609554a29 100644 --- a/tests/baselines/reference/getEmitOutputWithEarlySyntacticErrors.baseline +++ b/tests/baselines/reference/getEmitOutputWithEarlySyntacticErrors.baseline @@ -1,5 +1,5 @@ EmitSkipped: false -FileName : tests/cases/fourslash/inputFile1.js +FileName : /tests/cases/fourslash/inputFile1.js // File contains early errors. All outputs should be skipped. var uninitialized_const_error; diff --git a/tests/baselines/reference/getEmitOutputWithEmitterErrors.baseline b/tests/baselines/reference/getEmitOutputWithEmitterErrors.baseline index 8ba493e04b..326d547ae1 100644 --- a/tests/baselines/reference/getEmitOutputWithEmitterErrors.baseline +++ b/tests/baselines/reference/getEmitOutputWithEmitterErrors.baseline @@ -1,7 +1,7 @@ EmitSkipped: true Diagnostics: Exported variable 'foo' has or is using private name 'C'. -FileName : tests/cases/fourslash/inputFile.js +FileName : /tests/cases/fourslash/inputFile.js var M; (function (M) { var C = (function () { diff --git a/tests/baselines/reference/getEmitOutputWithEmitterErrors2.baseline b/tests/baselines/reference/getEmitOutputWithEmitterErrors2.baseline index bf12bd3554..a181526eb2 100644 --- a/tests/baselines/reference/getEmitOutputWithEmitterErrors2.baseline +++ b/tests/baselines/reference/getEmitOutputWithEmitterErrors2.baseline @@ -1,7 +1,7 @@ EmitSkipped: true Diagnostics: Exported variable 'foo' has or is using private name 'C'. -FileName : tests/cases/fourslash/inputFile.js +FileName : /tests/cases/fourslash/inputFile.js define(["require", "exports"], function (require, exports) { "use strict"; var C = (function () { diff --git a/tests/baselines/reference/getEmitOutputWithSemanticErrors.baseline b/tests/baselines/reference/getEmitOutputWithSemanticErrors.baseline index b624982213..55a58ea041 100644 --- a/tests/baselines/reference/getEmitOutputWithSemanticErrors.baseline +++ b/tests/baselines/reference/getEmitOutputWithSemanticErrors.baseline @@ -1,4 +1,4 @@ EmitSkipped: false -FileName : tests/cases/fourslash/inputFile.js +FileName : /tests/cases/fourslash/inputFile.js var x = "hello world"; diff --git a/tests/baselines/reference/getEmitOutputWithSemanticErrors2.baseline b/tests/baselines/reference/getEmitOutputWithSemanticErrors2.baseline index 396e220bdb..b5848da345 100644 --- a/tests/baselines/reference/getEmitOutputWithSemanticErrors2.baseline +++ b/tests/baselines/reference/getEmitOutputWithSemanticErrors2.baseline @@ -1,6 +1,6 @@ EmitSkipped: false -FileName : tests/cases/fourslash/inputFile.js +FileName : /tests/cases/fourslash/inputFile.js var x = "hello world"; -FileName : tests/cases/fourslash/inputFile.d.ts +FileName : /tests/cases/fourslash/inputFile.d.ts declare var x: number; diff --git a/tests/baselines/reference/getEmitOutputWithSemanticErrorsForMultipleFiles.baseline b/tests/baselines/reference/getEmitOutputWithSemanticErrorsForMultipleFiles.baseline index 7f012b6a54..4c22a7e3f9 100644 --- a/tests/baselines/reference/getEmitOutputWithSemanticErrorsForMultipleFiles.baseline +++ b/tests/baselines/reference/getEmitOutputWithSemanticErrorsForMultipleFiles.baseline @@ -1,8 +1,8 @@ EmitSkipped: false -FileName : tests/cases/fourslash/inputFile1.js +FileName : /tests/cases/fourslash/inputFile1.js // File to emit, does not contain semantic errors // expected to be emitted correctelly regardless of the semantic errors in the other file var noErrors = true; -FileName : tests/cases/fourslash/inputFile1.d.ts +FileName : /tests/cases/fourslash/inputFile1.d.ts declare var noErrors: boolean; diff --git a/tests/baselines/reference/getEmitOutputWithSyntacticErrorsForMultipleFiles.baseline b/tests/baselines/reference/getEmitOutputWithSyntacticErrorsForMultipleFiles.baseline index 701b275eb3..f51782cc75 100644 --- a/tests/baselines/reference/getEmitOutputWithSyntacticErrorsForMultipleFiles.baseline +++ b/tests/baselines/reference/getEmitOutputWithSyntacticErrorsForMultipleFiles.baseline @@ -1,5 +1,5 @@ EmitSkipped: false -FileName : tests/cases/fourslash/inputFile1.js +FileName : /tests/cases/fourslash/inputFile1.js // File to emit, does not contain syntactic errors // expected to be emitted correctelly regardless of the syntactic errors in the other file var noErrors = true; diff --git a/tests/baselines/reference/getEmitOutputWithSyntaxErrors.baseline b/tests/baselines/reference/getEmitOutputWithSyntaxErrors.baseline index 2c341821fb..8aa81ba60c 100644 --- a/tests/baselines/reference/getEmitOutputWithSyntaxErrors.baseline +++ b/tests/baselines/reference/getEmitOutputWithSyntaxErrors.baseline @@ -1,4 +1,4 @@ EmitSkipped: false -FileName : tests/cases/fourslash/inputFile.js +FileName : /tests/cases/fourslash/inputFile.js var x; diff --git a/tests/cases/fourslash/completionForStringLiteralRelativeImport4.ts b/tests/cases/fourslash/completionForStringLiteralRelativeImport4.ts index df30b925d1..ba9f95a08c 100644 --- a/tests/cases/fourslash/completionForStringLiteralRelativeImport4.ts +++ b/tests/cases/fourslash/completionForStringLiteralRelativeImport4.ts @@ -2,7 +2,7 @@ // Should give completions for directories that are merged via the rootDirs compiler option -// @rootDirs: sub/src1,src2 +// @rootDirs: tests/cases/fourslash/sub/src1,tests/cases/fourslash/src2 // @Filename: src2/test0.ts //// import * as foo1 from "./mo/*import_as0*/ diff --git a/tests/cases/fourslash/getEmitOutputSingleFile2.ts b/tests/cases/fourslash/getEmitOutputSingleFile2.ts index 5dd0276a93..5d616b19e1 100644 --- a/tests/cases/fourslash/getEmitOutputSingleFile2.ts +++ b/tests/cases/fourslash/getEmitOutputSingleFile2.ts @@ -4,7 +4,7 @@ // @module: CommonJS // @declaration: true // @out: declSingleFile.js -// @outDir: tests/cases/fourslash/ +// @outDir: /tests/cases/fourslash/ // @Filename: inputFile1.ts //// var x: number = 5; diff --git a/tests/cases/fourslash/renameForDefaultExport04.ts b/tests/cases/fourslash/renameForDefaultExport04.ts index ba40374e26..b225c47b2b 100644 --- a/tests/cases/fourslash/renameForDefaultExport04.ts +++ b/tests/cases/fourslash/renameForDefaultExport04.ts @@ -12,4 +12,4 @@ ////var y = new DefaultExportedClass; goTo.marker(); -verify.renameInfoSucceeded("DefaultExportedClass", '"tests/cases/fourslash/foo".DefaultExportedClass'); \ No newline at end of file +verify.renameInfoSucceeded("DefaultExportedClass", '"/tests/cases/fourslash/foo".DefaultExportedClass'); \ No newline at end of file diff --git a/tests/cases/fourslash/renameForDefaultExport05.ts b/tests/cases/fourslash/renameForDefaultExport05.ts index 099f878bda..2e5fcd847d 100644 --- a/tests/cases/fourslash/renameForDefaultExport05.ts +++ b/tests/cases/fourslash/renameForDefaultExport05.ts @@ -12,4 +12,4 @@ ////var y = new DefaultExportedClass; goTo.marker(); -verify.renameInfoSucceeded("DefaultExportedClass", '"tests/cases/fourslash/foo".DefaultExportedClass'); \ No newline at end of file +verify.renameInfoSucceeded("DefaultExportedClass", '"/tests/cases/fourslash/foo".DefaultExportedClass'); \ No newline at end of file diff --git a/tests/cases/fourslash/renameForDefaultExport06.ts b/tests/cases/fourslash/renameForDefaultExport06.ts index 3ec067b702..ba7f8cac7d 100644 --- a/tests/cases/fourslash/renameForDefaultExport06.ts +++ b/tests/cases/fourslash/renameForDefaultExport06.ts @@ -12,4 +12,4 @@ ////var y = new /**/[|DefaultExportedClass|]; goTo.marker(); -verify.renameInfoSucceeded("DefaultExportedClass", '"tests/cases/fourslash/foo".DefaultExportedClass'); \ No newline at end of file +verify.renameInfoSucceeded("DefaultExportedClass", '"/tests/cases/fourslash/foo".DefaultExportedClass'); \ No newline at end of file diff --git a/tests/cases/fourslash/renameForDefaultExport07.ts b/tests/cases/fourslash/renameForDefaultExport07.ts index b65f348f2a..9e3b4741b5 100644 --- a/tests/cases/fourslash/renameForDefaultExport07.ts +++ b/tests/cases/fourslash/renameForDefaultExport07.ts @@ -13,4 +13,4 @@ ////var y = DefaultExportedFunction(); goTo.marker(); -verify.renameInfoSucceeded("DefaultExportedFunction", '"tests/cases/fourslash/foo".DefaultExportedFunction'); \ No newline at end of file +verify.renameInfoSucceeded("DefaultExportedFunction", '"/tests/cases/fourslash/foo".DefaultExportedFunction'); \ No newline at end of file diff --git a/tests/cases/fourslash/renameForDefaultExport08.ts b/tests/cases/fourslash/renameForDefaultExport08.ts index 9b3f23db2c..ad8a2b8242 100644 --- a/tests/cases/fourslash/renameForDefaultExport08.ts +++ b/tests/cases/fourslash/renameForDefaultExport08.ts @@ -13,4 +13,4 @@ ////var y = DefaultExportedFunction(); goTo.marker(); -verify.renameInfoSucceeded("DefaultExportedFunction", '"tests/cases/fourslash/foo".DefaultExportedFunction'); \ No newline at end of file +verify.renameInfoSucceeded("DefaultExportedFunction", '"/tests/cases/fourslash/foo".DefaultExportedFunction'); \ No newline at end of file From 310bce44591f2f8208a4381d9b15664b20c403f2 Mon Sep 17 00:00:00 2001 From: Richard Knoll Date: Tue, 16 Aug 2016 15:18:25 -0700 Subject: [PATCH 27/39] Removing resolvePath from language service host --- src/harness/harnessLanguageService.ts | 9 --------- src/server/editorServices.ts | 5 ----- src/services/services.ts | 24 +++++++++--------------- src/services/shims.ts | 5 ----- 4 files changed, 9 insertions(+), 34 deletions(-) diff --git a/src/harness/harnessLanguageService.ts b/src/harness/harnessLanguageService.ts index 89997aeeab..9a6f9ef77c 100644 --- a/src/harness/harnessLanguageService.ts +++ b/src/harness/harnessLanguageService.ts @@ -218,12 +218,6 @@ namespace Harness.LanguageService { const snapshot = this.getScriptSnapshot(path); return snapshot.getText(0, snapshot.getLength()); } - resolvePath(path: string): string { - if (!ts.isRootedDiskPath(path)) { - path = ts.combinePaths(this.getCurrentDirectory(), path); - } - return ts.normalizePath(path); - } log(s: string): void { } @@ -329,9 +323,6 @@ namespace Harness.LanguageService { const snapshot = this.nativeHost.getScriptSnapshot(fileName); return snapshot && snapshot.getText(0, snapshot.getLength()); } - resolvePath(path: string): string { - return this.nativeHost.resolvePath(path); - } log(s: string): void { this.nativeHost.log(s); } trace(s: string): void { this.nativeHost.trace(s); } error(s: string): void { this.nativeHost.error(s); } diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 6b2cdb804c..3a1a4556e0 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -306,11 +306,6 @@ namespace ts.server { throw new Error("No script with name '" + filename + "'"); } - resolvePath(path: string): string { - const result = this.host.resolvePath(path); - return result; - } - fileExists(path: string): boolean { const result = this.host.fileExists(path); return result; diff --git a/src/services/services.ts b/src/services/services.ts index 3050018911..6108e1e976 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1159,7 +1159,6 @@ namespace ts { useCaseSensitiveFileNames?(): boolean; readDirectory(path: string, extensions?: string[], exclude?: string[], include?: string[]): string[]; - resolvePath(path: string): string; readFile(path: string, encoding?: string): string; fileExists(path: string): boolean; @@ -4589,7 +4588,7 @@ namespace ts { } const absolutePath = normalizeAndPreserveTrailingSlash(isRootedDiskPath(fragment) ? fragment : combinePaths(scriptPath, fragment)); - const baseDirectory = host.resolvePath(getDirectoryPath(absolutePath)); + const baseDirectory = getDirectoryPath(absolutePath); const ignoreCase = !(host.useCaseSensitiveFileNames && host.useCaseSensitiveFileNames()); if (directoryProbablyExists(baseDirectory, host)) { @@ -4827,7 +4826,14 @@ namespace ts { }); } else if (host.getDirectories && options.typeRoots) { - const absoluteRoots = map(options.typeRoots, rootDirectory => getAbsoluteProjectPath(rootDirectory, host, options.project)); + const absoluteRoots = map(options.typeRoots, rootDirectory => { + if (isRootedDiskPath(rootDirectory)) { + return normalizePath(rootDirectory); + } + + const basePath = options.project || host.getCurrentDirectory(); + return normalizePath(combinePaths(basePath, rootDirectory)); + }); forEach(absoluteRoots, absoluteRoot => getCompletionEntriesFromDirectories(host, options, absoluteRoot, result)); } @@ -4842,18 +4848,6 @@ namespace ts { return result; } - function getAbsoluteProjectPath(path: string, host: LanguageServiceHost, projectDir?: string) { - if (isRootedDiskPath(path)) { - return normalizePath(path); - } - - if (projectDir) { - return normalizePath(combinePaths(projectDir, path)); - } - - return normalizePath(host.resolvePath(path)); - } - function getCompletionEntriesFromDirectories(host: LanguageServiceHost, options: CompilerOptions, directory: string, result: ImportCompletionEntry[]) { if (host.getDirectories && directoryProbablyExists(directory, host)) { forEach(host.getDirectories(directory), typeDirectory => { diff --git a/src/services/shims.ts b/src/services/shims.ts index 401137a28e..b5ae306c71 100644 --- a/src/services/shims.ts +++ b/src/services/shims.ts @@ -69,7 +69,6 @@ namespace ts { readDirectory(rootDir: string, extension: string, basePaths?: string, excludeEx?: string, includeFileEx?: string, includeDirEx?: string, depth?: number): string; readFile(path: string, encoding?: string): string; - resolvePath(path: string): string; fileExists(path: string): boolean; getModuleResolutionsForFile?(fileName: string): string; @@ -446,10 +445,6 @@ namespace ts { return this.shimHost.readFile(path, encoding); } - public resolvePath(path: string): string { - return this.shimHost.resolvePath(path); - } - public fileExists(path: string): boolean { return this.shimHost.fileExists(path); } From cf7feb3faace557964447214cae150ae92ec7a80 Mon Sep 17 00:00:00 2001 From: Richard Knoll Date: Fri, 19 Aug 2016 16:49:55 -0700 Subject: [PATCH 28/39] Responding to PR feedback --- src/compiler/program.ts | 2 +- src/harness/harness.ts | 6 +- src/harness/harnessLanguageService.ts | 8 +- src/harness/virtualFileSystem.ts | 84 +++++++++---------- src/services/services.ts | 2 +- ...mpletionForStringLiteralRelativeImport3.ts | 18 ++-- .../completionForTripleSlashReference3.ts | 6 +- 7 files changed, 59 insertions(+), 67 deletions(-) diff --git a/src/compiler/program.ts b/src/compiler/program.ts index e8b8e0aa35..834cba1358 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -10,7 +10,7 @@ namespace ts { const defaultTypeRoots = ["node_modules/@types"]; - export function findConfigFile(searchPath: string, fileExists: (fileName: string) => boolean, configName="tsconfig.json"): string { + export function findConfigFile(searchPath: string, fileExists: (fileName: string) => boolean, configName = "tsconfig.json"): string { while (true) { const fileName = combinePaths(searchPath, configName); if (fileExists(fileName)) { diff --git a/src/harness/harness.ts b/src/harness/harness.ts index 35921f868e..41227c9bff 100644 --- a/src/harness/harness.ts +++ b/src/harness/harness.ts @@ -458,7 +458,7 @@ namespace Harness { // harness always uses one kind of new line const harnessNewLine = "\r\n"; - // Roote for file paths that are stored in a virtual file system + // Root for file paths that are stored in a virtual file system export const virtualFileSystemRoot = "/"; namespace IOImpl { @@ -752,14 +752,14 @@ namespace Harness { } export function readDirectory(path: string, extension?: string[], exclude?: string[], include?: string[]) { - const fs = new Utils.VirtualFileSystem(path, useCaseSensitiveFileNames()); + const fs = new Utils.VirtualFileSystem(path, useCaseSensitiveFileNames()); for (const file of listFiles(path)) { fs.addFile(file); } return ts.matchFiles(path, extension, exclude, include, useCaseSensitiveFileNames(), getCurrentDirectory(), path => { const entry = fs.traversePath(path); if (entry && entry.isDirectory()) { - const directory = >entry; + const directory = entry; return { files: ts.map(directory.getFiles(), f => f.name), directories: ts.map(directory.getDirectories(), d => d.name) diff --git a/src/harness/harnessLanguageService.ts b/src/harness/harnessLanguageService.ts index 9a6f9ef77c..a694002d70 100644 --- a/src/harness/harnessLanguageService.ts +++ b/src/harness/harnessLanguageService.ts @@ -123,7 +123,7 @@ namespace Harness.LanguageService { } export class LanguageServiceAdapterHost { - protected virtualFileSystem: Utils.VirtualFileSystem = new Utils.VirtualFileSystem(virtualFileSystemRoot, /*useCaseSensitiveFilenames*/false); + protected virtualFileSystem: Utils.VirtualFileSystem = new Utils.VirtualFileSystem(virtualFileSystemRoot, /*useCaseSensitiveFilenames*/false); constructor(protected cancellationToken = DefaultHostCancellationToken.Instance, protected settings = ts.getDefaultCompilerOptions()) { @@ -148,7 +148,7 @@ namespace Harness.LanguageService { public getScriptInfo(fileName: string): ScriptInfo { const fileEntry = this.virtualFileSystem.traversePath(fileName); - return fileEntry && fileEntry.isFile() ? (>fileEntry).content : undefined; + return fileEntry && fileEntry.isFile() ? (fileEntry).content : undefined; } public addScript(fileName: string, content: string, isRootFile: boolean): void { @@ -187,11 +187,11 @@ namespace Harness.LanguageService { getDirectories(path: string): string[] { const dir = this.virtualFileSystem.traversePath(path); if (dir && dir.isDirectory()) { - return ts.map((>dir).getDirectories(), (d) => ts.combinePaths(path, d.name)); + return ts.map((dir).getDirectories(), (d) => ts.combinePaths(path, d.name)); } return []; } - getCurrentDirectory(): string { return virtualFileSystemRoot } + getCurrentDirectory(): string { return virtualFileSystemRoot; } getDefaultLibFileName(): string { return Harness.Compiler.defaultLibFileName; } getScriptFileNames(): string[] { return this.getFilenames(); } getScriptSnapshot(fileName: string): ts.IScriptSnapshot { diff --git a/src/harness/virtualFileSystem.ts b/src/harness/virtualFileSystem.ts index 036965f75d..b6d234acd5 100644 --- a/src/harness/virtualFileSystem.ts +++ b/src/harness/virtualFileSystem.ts @@ -1,11 +1,11 @@ /// /// namespace Utils { - export class VirtualFileSystemEntry { - fileSystem: VirtualFileSystem; + export class VirtualFileSystemEntry { + fileSystem: VirtualFileSystem; name: string; - constructor(fileSystem: VirtualFileSystem, name: string) { + constructor(fileSystem: VirtualFileSystem, name: string) { this.fileSystem = fileSystem; this.name = name; } @@ -15,15 +15,15 @@ namespace Utils { isFileSystem() { return false; } } - export class VirtualFile extends VirtualFileSystemEntry { - content: T; + export class VirtualFile extends VirtualFileSystemEntry { + content?: Harness.LanguageService.ScriptInfo; isFile() { return true; } } - export abstract class VirtualFileSystemContainer extends VirtualFileSystemEntry { - abstract getFileSystemEntries(): VirtualFileSystemEntry[]; + export abstract class VirtualFileSystemContainer extends VirtualFileSystemEntry { + abstract getFileSystemEntries(): VirtualFileSystemEntry[]; - getFileSystemEntry(name: string): VirtualFileSystemEntry { + getFileSystemEntry(name: string): VirtualFileSystemEntry { for (const entry of this.getFileSystemEntries()) { if (this.fileSystem.sameName(entry.name, name)) { return entry; @@ -32,57 +32,57 @@ namespace Utils { return undefined; } - getDirectories(): VirtualDirectory[] { - return []>ts.filter(this.getFileSystemEntries(), entry => entry.isDirectory()); + getDirectories(): VirtualDirectory[] { + return ts.filter(this.getFileSystemEntries(), entry => entry.isDirectory()); } - getFiles(): VirtualFile[] { - return []>ts.filter(this.getFileSystemEntries(), entry => entry.isFile()); + getFiles(): VirtualFile[] { + return ts.filter(this.getFileSystemEntries(), entry => entry.isFile()); } - getDirectory(name: string): VirtualDirectory { + getDirectory(name: string): VirtualDirectory { const entry = this.getFileSystemEntry(name); - return entry.isDirectory() ? >entry : undefined; + return entry.isDirectory() ? entry : undefined; } - getFile(name: string): VirtualFile { + getFile(name: string): VirtualFile { const entry = this.getFileSystemEntry(name); - return entry.isFile() ? >entry : undefined; + return entry.isFile() ? entry : undefined; } } - export class VirtualDirectory extends VirtualFileSystemContainer { - private entries: VirtualFileSystemEntry[] = []; + export class VirtualDirectory extends VirtualFileSystemContainer { + private entries: VirtualFileSystemEntry[] = []; isDirectory() { return true; } getFileSystemEntries() { return this.entries.slice(); } - addDirectory(name: string): VirtualDirectory { + addDirectory(name: string): VirtualDirectory { const entry = this.getFileSystemEntry(name); if (entry === undefined) { - const directory = new VirtualDirectory(this.fileSystem, name); + const directory = new VirtualDirectory(this.fileSystem, name); this.entries.push(directory); return directory; } else if (entry.isDirectory()) { - return >entry; + return entry; } else { return undefined; } } - addFile(name: string, content?: T): VirtualFile { + addFile(name: string, content?: Harness.LanguageService.ScriptInfo): VirtualFile { const entry = this.getFileSystemEntry(name); if (entry === undefined) { - const file = new VirtualFile(this.fileSystem, name); + const file = new VirtualFile(this.fileSystem, name); file.content = content; this.entries.push(file); return file; } else if (entry.isFile()) { - const file = >entry; + const file = entry; file.content = content; return file; } @@ -92,8 +92,8 @@ namespace Utils { } } - export class VirtualFileSystem extends VirtualFileSystemContainer { - private root: VirtualDirectory; + export class VirtualFileSystem extends VirtualFileSystemContainer { + private root: VirtualDirectory; currentDirectory: string; useCaseSensitiveFileNames: boolean; @@ -101,7 +101,7 @@ namespace Utils { constructor(currentDirectory: string, useCaseSensitiveFileNames: boolean) { super(undefined, ""); this.fileSystem = this; - this.root = new VirtualDirectory(this, ""); + this.root = new VirtualDirectory(this, ""); this.currentDirectory = currentDirectory; this.useCaseSensitiveFileNames = useCaseSensitiveFileNames; } @@ -111,9 +111,9 @@ namespace Utils { getFileSystemEntries() { return this.root.getFileSystemEntries(); } addDirectory(path: string) { - path = this.normalizePathRoot(path); + path = ts.normalizePath(path); const components = ts.getNormalizedPathComponents(path, this.currentDirectory); - let directory: VirtualDirectory = this.root; + let directory: VirtualDirectory = this.root; for (const component of components) { directory = directory.addDirectory(component); if (directory === undefined) { @@ -124,8 +124,8 @@ namespace Utils { return directory; } - addFile(path: string, content?: T) { - const absolutePath = this.normalizePathRoot(ts.getNormalizedAbsolutePath(path, this.currentDirectory)); + addFile(path: string, content?: Harness.LanguageService.ScriptInfo) { + const absolutePath = ts.normalizePath(ts.getNormalizedAbsolutePath(path, this.currentDirectory)); const fileName = ts.getBaseFileName(path); const directoryPath = ts.getDirectoryPath(absolutePath); const directory = this.addDirectory(directoryPath); @@ -142,15 +142,15 @@ namespace Utils { } traversePath(path: string) { - path = this.normalizePathRoot(path); - let directory: VirtualDirectory = this.root; + path = ts.normalizePath(path); + let directory: VirtualDirectory = this.root; for (const component of ts.getNormalizedPathComponents(path, this.currentDirectory)) { const entry = directory.getFileSystemEntry(component); if (entry === undefined) { return undefined; } else if (entry.isDirectory()) { - directory = >entry; + directory = entry; } else { return entry; @@ -168,7 +168,7 @@ namespace Utils { getAccessibleFileSystemEntries(path: string) { const entry = this.traversePath(path); if (entry && entry.isDirectory()) { - const directory = >entry; + const directory = entry; return { files: ts.map(directory.getFiles(), f => f.name), directories: ts.map(directory.getDirectories(), d => d.name) @@ -178,11 +178,11 @@ namespace Utils { } getAllFileEntries() { - const fileEntries: VirtualFile[] = []; + const fileEntries: VirtualFile[] = []; getFilesRecursive(this.root, fileEntries); return fileEntries; - function getFilesRecursive(dir: VirtualDirectory, result: VirtualFile[]) { + function getFilesRecursive(dir: VirtualDirectory, result: VirtualFile[]) { const files = dir.getFiles(); const dirs = dir.getDirectories(); for (const file of files) { @@ -193,17 +193,9 @@ namespace Utils { } } } - - normalizePathRoot(path: string) { - const components = ts.getNormalizedPathComponents(path, this.currentDirectory); - - // Toss the root component - components[0] = ""; - return components.join(ts.directorySeparator); - } } - export class MockParseConfigHost extends VirtualFileSystem implements ts.ParseConfigHost { + export class MockParseConfigHost extends VirtualFileSystem implements ts.ParseConfigHost { constructor(currentDirectory: string, ignoreCase: boolean, files: string[]) { super(currentDirectory, ignoreCase); for (const file of files) { diff --git a/src/services/services.ts b/src/services/services.ts index 6108e1e976..49fc48b6ea 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1171,7 +1171,7 @@ namespace ts { resolveTypeReferenceDirectives?(typeDirectiveNames: string[], containingFile: string): ResolvedTypeReferenceDirective[]; directoryExists?(directoryName: string): boolean; - /** + /* * getDirectories is also required for full import and type reference completions. Without it defined, certain * completions will not be provided */ diff --git a/tests/cases/fourslash/completionForStringLiteralRelativeImport3.ts b/tests/cases/fourslash/completionForStringLiteralRelativeImport3.ts index a2035ee7fd..82c3ea7d1e 100644 --- a/tests/cases/fourslash/completionForStringLiteralRelativeImport3.ts +++ b/tests/cases/fourslash/completionForStringLiteralRelativeImport3.ts @@ -3,17 +3,17 @@ // Should give completions for absolute paths // @Filename: tests/test0.ts -//// import * as foo1 from "c:/tests/cases/f/*import_as0*/ -//// import * as foo2 from "c:/tests/cases/fourslash/*import_as1*/ -//// import * as foo3 from "c:/tests/cases/fourslash//*import_as2*/ +//// import * as foo1 from "/tests/cases/f/*import_as0*/ +//// import * as foo2 from "/tests/cases/fourslash/*import_as1*/ +//// import * as foo3 from "/tests/cases/fourslash//*import_as2*/ -//// import foo4 = require("c:/tests/cases/f/*import_equals0*/ -//// import foo5 = require("c:/tests/cases/fourslash/*import_equals1*/ -//// import foo6 = require("c:/tests/cases/fourslash//*import_equals2*/ +//// import foo4 = require("/tests/cases/f/*import_equals0*/ +//// import foo5 = require("/tests/cases/fourslash/*import_equals1*/ +//// import foo6 = require("/tests/cases/fourslash//*import_equals2*/ -//// var foo7 = require("c:/tests/cases/f/*require0*/ -//// var foo8 = require("c:/tests/cases/fourslash/*require1*/ -//// var foo9 = require("c:/tests/cases/fourslash//*require2*/ +//// var foo7 = require("/tests/cases/f/*require0*/ +//// var foo8 = require("/tests/cases/fourslash/*require1*/ +//// var foo9 = require("/tests/cases/fourslash//*require2*/ // @Filename: f1.ts //// /*f1*/ diff --git a/tests/cases/fourslash/completionForTripleSlashReference3.ts b/tests/cases/fourslash/completionForTripleSlashReference3.ts index 2f92de19a2..d27d0e658c 100644 --- a/tests/cases/fourslash/completionForTripleSlashReference3.ts +++ b/tests/cases/fourslash/completionForTripleSlashReference3.ts @@ -3,13 +3,13 @@ // Should give completions for absolute paths // @Filename: tests/test0.ts -//// /// Date: Fri, 19 Aug 2016 17:06:59 -0700 Subject: [PATCH 29/39] Removing hasProperty check --- src/compiler/checker.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 3cf7528abb..603d9c6dea 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -19958,7 +19958,7 @@ namespace ts { function getAmbientModules(): Symbol[] { const result: Symbol[] = []; for (const sym in globals) { - if (hasProperty(globals, sym) && ambientModuleSymbolRegex.test(sym)) { + if (ambientModuleSymbolRegex.test(sym)) { result.push(globals[sym]); } } From 0ebd19618d4b8b5aa64a0c7b2676898cef09308e Mon Sep 17 00:00:00 2001 From: Richard Knoll Date: Fri, 19 Aug 2016 17:17:49 -0700 Subject: [PATCH 30/39] Fixing regex for triple slash references --- src/services/services.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/services.ts b/src/services/services.ts index a10383ae09..4c199de186 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -2070,7 +2070,7 @@ namespace ts { * for completions. * For example, this matches /// Date: Tue, 23 Aug 2016 16:45:51 -0700 Subject: [PATCH 31/39] Using for..of instead of forEach --- src/harness/harnessLanguageService.ts | 4 +- src/services/services.ts | 74 ++++++++++++++------------- 2 files changed, 40 insertions(+), 38 deletions(-) diff --git a/src/harness/harnessLanguageService.ts b/src/harness/harnessLanguageService.ts index 5ae6a33463..880a103a20 100644 --- a/src/harness/harnessLanguageService.ts +++ b/src/harness/harnessLanguageService.ts @@ -135,14 +135,14 @@ namespace Harness.LanguageService { public getFilenames(): string[] { const fileNames: string[] = []; - ts.forEach(this.virtualFileSystem.getAllFileEntries(), (virtualEntry) => { + for (const virtualEntry of this.virtualFileSystem.getAllFileEntries()){ const scriptInfo = virtualEntry.content; if (scriptInfo.isRootFile) { // only include root files here // usually it means that we won't include lib.d.ts in the list of root files so it won't mess the computation of compilation root dir. fileNames.push(scriptInfo.fileName); } - }); + } return fileNames; } diff --git a/src/services/services.ts b/src/services/services.ts index 4c199de186..48cc186f7e 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -4559,12 +4559,12 @@ namespace ts { // Determine the path to the directory containing the script relative to the root directory it is contained within let relativeDirectory: string; - forEach(rootDirs, rootDirectory => { + for (const rootDirectory of rootDirs) { if (containsPath(rootDirectory, scriptPath, basePath, ignoreCase)) { relativeDirectory = scriptPath.substr(rootDirectory.length); - return true; + break; } - }); + } // 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))); @@ -4600,10 +4600,10 @@ namespace ts { if (directoryProbablyExists(baseDirectory, host)) { // Enumerate the available files const files = host.readDirectory(baseDirectory, extensions, /*exclude*/undefined, /*include*/["./*"]); - forEach(files, filePath => { + for (let filePath of files) { filePath = normalizePath(filePath); if (exclude && comparePaths(filePath, exclude, scriptPath, ignoreCase) === Comparison.EqualTo) { - return false; + continue; } const fileName = includeExtensions ? getBaseFileName(filePath) : removeFileExtension(getBaseFileName(filePath)); @@ -4616,20 +4616,20 @@ namespace ts { sortText: fileName }); } - }); + } // If possible, get folder completion as well if (host.getDirectories) { const directories = host.getDirectories(baseDirectory); - forEach(directories, d => { - const directoryName = getBaseFileName(normalizePath(d)); + for (const directory of directories) { + const directoryName = getBaseFileName(normalizePath(directory)); result.push({ name: directoryName, kind: ScriptElementKind.directory, sortText: directoryName }); - }); + } } } @@ -4660,11 +4660,11 @@ namespace ts { if (paths.hasOwnProperty(path)) { if (path === "*") { if (paths[path]) { - forEach(paths[path], pattern => { - forEach(getModulesForPathsPattern(fragment, baseUrl, pattern, fileExtensions), match => { + for (const pattern of paths[path]) { + for (const match of getModulesForPathsPattern(fragment, baseUrl, pattern, fileExtensions)) { result.push(createCompletionEntryForModule(match, ScriptElementKind.externalModuleName)); - }); - }); + } + } } } else if (startsWith(path, fragment)) { @@ -4683,9 +4683,9 @@ namespace ts { getCompletionEntriesFromTypings(host, options, scriptPath, result); - forEach(enumeratePotentialNonRelativeModules(fragment, scriptPath, options), moduleName => { + for (const moduleName of enumeratePotentialNonRelativeModules(fragment, scriptPath, options)) { result.push(createCompletionEntryForModule(moduleName, ScriptElementKind.externalModuleName)); - }); + } return result; } @@ -4717,17 +4717,17 @@ namespace ts { const result: string[] = []; // Trim away prefix and suffix - forEach(matches, match => { + for (const match of matches) { const normalizedMatch = normalizePath(match); if (!endsWith(normalizedMatch, normalizedSuffix) || !startsWith(normalizedMatch, completePrefix)) { - return; + continue; } const start = completePrefix.length; const length = normalizedMatch.length - start - normalizedSuffix.length; result.push(removeFileExtension(normalizedMatch.substr(start, length))); - }); + } return result; } @@ -4740,15 +4740,15 @@ namespace ts { const moduleNameFragment = isNestedModule ? fragment.substr(0, fragment.lastIndexOf(directorySeparator)) : undefined; // Get modules that the type checker picked up - const ambientModules = ts.map(program.getTypeChecker().getAmbientModules(), sym => stripQuotes(sym.name)); - let nonRelativeModules = ts.filter(ambientModules, moduleName => startsWith(moduleName, fragment)); + const ambientModules = map(program.getTypeChecker().getAmbientModules(), sym => stripQuotes(sym.name)); + let nonRelativeModules = filter(ambientModules, moduleName => startsWith(moduleName, fragment)); // Nested modules of the form "module-name/sub" need to be adjusted to only return the string // after the last '/' that appears in the fragment because that's where the replacement span // starts if (isNestedModule) { const moduleNameWithSeperator = ensureTrailingDirectorySeparator(moduleNameFragment); - nonRelativeModules = ts.map(nonRelativeModules, moduleName => { + nonRelativeModules = map(nonRelativeModules, moduleName => { if (startsWith(fragment, moduleNameWithSeperator)) { return moduleName.substr(moduleNameWithSeperator.length); } @@ -4758,20 +4758,20 @@ namespace ts { if (!options.moduleResolution || options.moduleResolution === ModuleResolutionKind.NodeJs) { - forEach(enumerateNodeModulesVisibleToScript(host, scriptPath), visibleModule => { + for (const visibleModule of enumerateNodeModulesVisibleToScript(host, scriptPath)) { if (!isNestedModule) { nonRelativeModules.push(visibleModule.moduleName); } else if (startsWith(visibleModule.moduleName, moduleNameFragment)) { const nestedFiles = host.readDirectory(visibleModule.moduleDir, supportedTypeScriptExtensions, /*exclude*/undefined, /*include*/["./*"]); - forEach(nestedFiles, (f) => { + for (let f of nestedFiles) { f = normalizePath(f); const nestedModule = removeFileExtension(getBaseFileName(f)); nonRelativeModules.push(nestedModule); - }); + } } - }); + } } return deduplicate(nonRelativeModules); @@ -4827,9 +4827,9 @@ namespace ts { function getCompletionEntriesFromTypings(host: LanguageServiceHost, options: CompilerOptions, scriptPath: string, result: ImportCompletionEntry[] = []): ImportCompletionEntry[] { // Check for typings specified in compiler options if (options.types) { - forEach(options.types, moduleName => { + for (const moduleName of options.types){ result.push(createCompletionEntryForModule(moduleName, ScriptElementKind.externalModuleName)); - }); + } } else if (host.getDirectories && options.typeRoots) { const absoluteRoots = map(options.typeRoots, rootDirectory => { @@ -4840,15 +4840,17 @@ namespace ts { const basePath = options.project || host.getCurrentDirectory(); return normalizePath(combinePaths(basePath, rootDirectory)); }); - forEach(absoluteRoots, absoluteRoot => getCompletionEntriesFromDirectories(host, options, absoluteRoot, result)); + for (const absoluteRoot of absoluteRoots) { + getCompletionEntriesFromDirectories(host, options, absoluteRoot, result); + } } if (host.getDirectories) { // Also get all @types typings installed in visible node_modules directories - forEach(findPackageJsons(scriptPath), package => { + for (const package of findPackageJsons(scriptPath)) { const typesDir = combinePaths(getDirectoryPath(package), "node_modules/@types"); getCompletionEntriesFromDirectories(host, options, typesDir, result); - }); + } } return result; @@ -4856,10 +4858,10 @@ namespace ts { function getCompletionEntriesFromDirectories(host: LanguageServiceHost, options: CompilerOptions, directory: string, result: ImportCompletionEntry[]) { if (host.getDirectories && directoryProbablyExists(directory, host)) { - forEach(host.getDirectories(directory), typeDirectory => { + for (let typeDirectory of host.getDirectories(directory)) { typeDirectory = normalizePath(typeDirectory); result.push(createCompletionEntryForModule(getBaseFileName(typeDirectory), ScriptElementKind.externalModuleName)); - }); + } } } @@ -4889,7 +4891,7 @@ namespace ts { function enumerateNodeModulesVisibleToScript(host: LanguageServiceHost, scriptPath: string) { const result: VisibleModuleInfo[] = []; - findPackageJsons(scriptPath).forEach((packageJson) => { + for (const packageJson of findPackageJsons(scriptPath)) { const package = tryReadingPackageJson(packageJson); if (!package) { return; @@ -4905,14 +4907,14 @@ namespace ts { addPotentialPackageNames(package.devDependencies, foundModuleNames); } - forEach(foundModuleNames, (moduleName) => { + for (const moduleName of foundModuleNames) { const moduleDir = combinePaths(nodeModulesDir, moduleName); result.push({ moduleName, moduleDir }); - }); - }); + } + } return result; From 34847f0ce05de1c8daccbec99c5181b0988b3b04 Mon Sep 17 00:00:00 2001 From: Richard Knoll Date: Thu, 25 Aug 2016 14:08:37 -0700 Subject: [PATCH 32/39] Making language service host changes optional --- src/services/services.ts | 150 +++++++++++++++++++++------------------ 1 file changed, 81 insertions(+), 69 deletions(-) diff --git a/src/services/services.ts b/src/services/services.ts index 48cc186f7e..fde5b300c2 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1158,9 +1158,13 @@ namespace ts { error?(s: string): void; useCaseSensitiveFileNames?(): boolean; - readDirectory(path: string, extensions?: string[], exclude?: string[], include?: string[]): string[]; - readFile(path: string, encoding?: string): string; - fileExists(path: string): boolean; + /* + * LS host can optionally implement these methods to support getImportModuleCompletionsAtPosition. + * Without these methods, only completions for ambient modules will be provided. + */ + readDirectory?(path: string, extensions?: string[], exclude?: string[], include?: string[]): string[]; + readFile?(path: string, encoding?: string): string; + fileExists?(path: string): boolean; /* * LS host can optionally implement this method if it wants to be completely in charge of module name resolution. @@ -3155,7 +3159,8 @@ namespace ts { writeFile: (fileName, data, writeByteOrderMark) => { }, getCurrentDirectory: () => currentDirectory, fileExists: (fileName): boolean => { - return host.fileExists(fileName); + // stub missing host functionality + return hostCache.getOrCreateEntry(fileName) !== undefined; }, readFile: (fileName): string => { // stub missing host functionality @@ -4598,23 +4603,25 @@ namespace ts { const ignoreCase = !(host.useCaseSensitiveFileNames && host.useCaseSensitiveFileNames()); if (directoryProbablyExists(baseDirectory, host)) { - // Enumerate the available files - const files = host.readDirectory(baseDirectory, extensions, /*exclude*/undefined, /*include*/["./*"]); - for (let filePath of files) { - filePath = normalizePath(filePath); - if (exclude && comparePaths(filePath, exclude, scriptPath, ignoreCase) === Comparison.EqualTo) { - continue; - } + if (host.readDirectory) { + // Enumerate the available files if possible + const files = host.readDirectory(baseDirectory, extensions, /*exclude*/undefined, /*include*/["./*"]); + for (let filePath of files) { + filePath = normalizePath(filePath); + if (exclude && comparePaths(filePath, exclude, scriptPath, ignoreCase) === Comparison.EqualTo) { + continue; + } - const fileName = includeExtensions ? getBaseFileName(filePath) : removeFileExtension(getBaseFileName(filePath)); - const duplicate = !includeExtensions && forEach(result, entry => entry.name === fileName); + const fileName = includeExtensions ? getBaseFileName(filePath) : removeFileExtension(getBaseFileName(filePath)); + const duplicate = !includeExtensions && forEach(result, entry => entry.name === fileName); - if (!duplicate) { - result.push({ - name: fileName, - kind: ScriptElementKind.scriptElement, - sortText: fileName - }); + if (!duplicate) { + result.push({ + name: fileName, + kind: ScriptElementKind.scriptElement, + sortText: fileName + }); + } } } @@ -4691,44 +4698,46 @@ namespace ts { } function getModulesForPathsPattern(fragment: string, baseUrl: string, pattern: string, fileExtensions: string[]): string[] { - const parsed = hasZeroOrOneAsteriskCharacter(pattern) ? tryParsePattern(pattern) : undefined; - if (parsed) { - // The prefix has two effective parts: the directory path and the base component after the filepath that is not a - // full directory component. For example: directory/path/of/prefix/base* - const normalizedPrefix = normalizeAndPreserveTrailingSlash(parsed.prefix); - const normalizedPrefixDirectory = getDirectoryPath(normalizedPrefix); - const normalizedPrefixBase = getBaseFileName(normalizedPrefix); + if (host.readDirectory) { + const parsed = hasZeroOrOneAsteriskCharacter(pattern) ? tryParsePattern(pattern) : undefined; + if (parsed) { + // The prefix has two effective parts: the directory path and the base component after the filepath that is not a + // full directory component. For example: directory/path/of/prefix/base* + const normalizedPrefix = normalizeAndPreserveTrailingSlash(parsed.prefix); + const normalizedPrefixDirectory = getDirectoryPath(normalizedPrefix); + const normalizedPrefixBase = getBaseFileName(normalizedPrefix); - const fragmentHasPath = fragment.indexOf(directorySeparator) !== -1; + const fragmentHasPath = fragment.indexOf(directorySeparator) !== -1; - // Try and expand the prefix to include any path from the fragment so that we can limit the readDirectory call - const expandedPrefixDirectory = fragmentHasPath ? combinePaths(normalizedPrefixDirectory, normalizedPrefixBase + getDirectoryPath(fragment)) : normalizedPrefixDirectory; + // Try and expand the prefix to include any path from the fragment so that we can limit the readDirectory call + const expandedPrefixDirectory = fragmentHasPath ? combinePaths(normalizedPrefixDirectory, normalizedPrefixBase + getDirectoryPath(fragment)) : normalizedPrefixDirectory; - const normalizedSuffix = normalizePath(parsed.suffix); - const baseDirectory = combinePaths(baseUrl, expandedPrefixDirectory); - const completePrefix = fragmentHasPath ? baseDirectory : ensureTrailingDirectorySeparator(baseDirectory) + normalizedPrefixBase; + const normalizedSuffix = normalizePath(parsed.suffix); + const baseDirectory = combinePaths(baseUrl, expandedPrefixDirectory); + const completePrefix = fragmentHasPath ? baseDirectory : ensureTrailingDirectorySeparator(baseDirectory) + normalizedPrefixBase; - // If we have a suffix, then we need to read the directory all the way down. We could create a glob - // that encodes the suffix, but we would have to escape the character "?" which readDirectory - // doesn't support. For now, this is safer but slower - const includeGlob = normalizedSuffix ? "**/*" : "./*"; + // If we have a suffix, then we need to read the directory all the way down. We could create a glob + // that encodes the suffix, but we would have to escape the character "?" which readDirectory + // doesn't support. For now, this is safer but slower + const includeGlob = normalizedSuffix ? "**/*" : "./*"; - const matches = host.readDirectory(baseDirectory, fileExtensions, undefined, [includeGlob]); - const result: string[] = []; + const matches = host.readDirectory(baseDirectory, fileExtensions, undefined, [includeGlob]); + const result: string[] = []; - // Trim away prefix and suffix - for (const match of matches) { - const normalizedMatch = normalizePath(match); - if (!endsWith(normalizedMatch, normalizedSuffix) || !startsWith(normalizedMatch, completePrefix)) { - continue; + // Trim away prefix and suffix + for (const match of matches) { + const normalizedMatch = normalizePath(match); + if (!endsWith(normalizedMatch, normalizedSuffix) || !startsWith(normalizedMatch, completePrefix)) { + continue; + } + + const start = completePrefix.length; + const length = normalizedMatch.length - start - normalizedSuffix.length; + + result.push(removeFileExtension(normalizedMatch.substr(start, length))); } - - const start = completePrefix.length; - const length = normalizedMatch.length - start - normalizedSuffix.length; - - result.push(removeFileExtension(normalizedMatch.substr(start, length))); + return result; } - return result; } return undefined; @@ -4762,7 +4771,7 @@ namespace ts { if (!isNestedModule) { nonRelativeModules.push(visibleModule.moduleName); } - else if (startsWith(visibleModule.moduleName, moduleNameFragment)) { + else if (host.readDirectory && startsWith(visibleModule.moduleName, moduleNameFragment)) { const nestedFiles = host.readDirectory(visibleModule.moduleDir, supportedTypeScriptExtensions, /*exclude*/undefined, /*include*/["./*"]); for (let f of nestedFiles) { @@ -4891,28 +4900,31 @@ namespace ts { function enumerateNodeModulesVisibleToScript(host: LanguageServiceHost, scriptPath: string) { const result: VisibleModuleInfo[] = []; - for (const packageJson of findPackageJsons(scriptPath)) { - const package = tryReadingPackageJson(packageJson); - if (!package) { - return; - } - const nodeModulesDir = combinePaths(getDirectoryPath(packageJson), "node_modules"); - const foundModuleNames: string[] = []; + if (host.readFile && host.fileExists) { + for (const packageJson of findPackageJsons(scriptPath)) { + const package = tryReadingPackageJson(packageJson); + if (!package) { + return; + } - if (package.dependencies) { - addPotentialPackageNames(package.dependencies, foundModuleNames); - } - if (package.devDependencies) { - addPotentialPackageNames(package.devDependencies, foundModuleNames); - } + const nodeModulesDir = combinePaths(getDirectoryPath(packageJson), "node_modules"); + const foundModuleNames: string[] = []; - for (const moduleName of foundModuleNames) { - const moduleDir = combinePaths(nodeModulesDir, moduleName); - result.push({ - moduleName, - moduleDir - }); + if (package.dependencies) { + addPotentialPackageNames(package.dependencies, foundModuleNames); + } + if (package.devDependencies) { + addPotentialPackageNames(package.devDependencies, foundModuleNames); + } + + for (const moduleName of foundModuleNames) { + const moduleDir = combinePaths(nodeModulesDir, moduleName); + result.push({ + moduleName, + moduleDir + }); + } } } From 276b56dfb03ef5f9a035aa0b0509ef46a469c476 Mon Sep 17 00:00:00 2001 From: Richard Knoll Date: Thu, 25 Aug 2016 17:39:55 -0700 Subject: [PATCH 33/39] More PR feedback --- src/services/services.ts | 42 ++++++++++--------- ...tionForStringLiteralNonrelativeImport12.ts | 28 +++++++++++++ 2 files changed, 51 insertions(+), 19 deletions(-) create mode 100644 tests/cases/fourslash/completionForStringLiteralNonrelativeImport12.ts diff --git a/src/services/services.ts b/src/services/services.ts index fde5b300c2..8bb378e090 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -2076,6 +2076,8 @@ namespace ts { */ const tripleSlashDirectiveFragmentRegex = /^(\/\/\/\s*(); for (let filePath of files) { filePath = normalizePath(filePath); if (exclude && comparePaths(filePath, exclude, scriptPath, ignoreCase) === Comparison.EqualTo) { continue; } - const fileName = includeExtensions ? getBaseFileName(filePath) : removeFileExtension(getBaseFileName(filePath)); - const duplicate = !includeExtensions && forEach(result, entry => entry.name === fileName); + const foundFileName = includeExtensions ? getBaseFileName(filePath) : removeFileExtension(getBaseFileName(filePath)); - if (!duplicate) { - result.push({ - name: fileName, - kind: ScriptElementKind.scriptElement, - sortText: fileName - }); + if (!foundFiles[foundFileName]) { + foundFiles[foundFileName] = true; } } + + for (const foundFile in foundFiles) { + result.push({ + name: foundFile, + kind: ScriptElementKind.scriptElement, + sortText: foundFile + }); + } } // If possible, get folder completion as well @@ -4836,7 +4842,7 @@ namespace ts { function getCompletionEntriesFromTypings(host: LanguageServiceHost, options: CompilerOptions, scriptPath: string, result: ImportCompletionEntry[] = []): ImportCompletionEntry[] { // Check for typings specified in compiler options if (options.types) { - for (const moduleName of options.types){ + for (const moduleName of options.types) { result.push(createCompletionEntryForModule(moduleName, ScriptElementKind.externalModuleName)); } } @@ -4911,11 +4917,9 @@ namespace ts { const nodeModulesDir = combinePaths(getDirectoryPath(packageJson), "node_modules"); const foundModuleNames: string[] = []; - if (package.dependencies) { - addPotentialPackageNames(package.dependencies, foundModuleNames); - } - if (package.devDependencies) { - addPotentialPackageNames(package.devDependencies, foundModuleNames); + // Provide completions for all non @types dependencies + for (const key of nodeModulesDependencyKeys) { + addPotentialPackageNames(package[key], foundModuleNames); } for (const moduleName of foundModuleNames) { @@ -4940,11 +4944,12 @@ namespace ts { } } - // Add all the package names that are not in the @types scope function addPotentialPackageNames(dependencies: any, result: string[]) { - for (const dep in dependencies) { - if (dependencies.hasOwnProperty(dep) && !startsWith(dep, "@types/")) { - result.push(dep); + if (dependencies) { + for (const dep in dependencies) { + if (dependencies.hasOwnProperty(dep) && !startsWith(dep, "@types/")) { + result.push(dep); + } } } } @@ -4955,7 +4960,6 @@ namespace ts { } // Replace everything after the last directory seperator that appears - // FIXME: do we care about the other seperator? function getDirectoryFragmentTextSpan(text: string, textStart: number): TextSpan { const index = text.lastIndexOf(directorySeparator); const offset = index !== -1 ? index + 1 : 0; diff --git a/tests/cases/fourslash/completionForStringLiteralNonrelativeImport12.ts b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport12.ts new file mode 100644 index 0000000000..92db8d5a50 --- /dev/null +++ b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport12.ts @@ -0,0 +1,28 @@ +/// + +// Should give completions for all dependencies in package.json + +// @Filename: tests/test0.ts +//// import * as foo1 from "m/*import_as0*/ +//// import foo2 = require("m/*import_equals0*/ +//// var foo3 = require("m/*require0*/ + +// @Filename: package.json +//// { +//// "dependencies": { "module": "latest" }, +//// "devDependencies": { "dev-module": "latest" }, +//// "optionalDependencies": { "optional-module": "latest" }, +//// "peerDependencies": { "peer-module": "latest" } +//// } + +const kinds = ["import_as", "import_equals", "require"]; + +for (const kind of kinds) { + goTo.marker(kind + "0"); + + verify.importModuleCompletionListContains("module"); + verify.importModuleCompletionListContains("dev-module"); + verify.importModuleCompletionListContains("optional-module"); + verify.importModuleCompletionListContains("peer-module"); + verify.not.importModuleCompletionListItemsCountIsGreaterThan(4); +} From fb6ff42b93d46c33ff9439ca392b36511e95093d Mon Sep 17 00:00:00 2001 From: Richard Knoll Date: Fri, 26 Aug 2016 18:03:20 -0700 Subject: [PATCH 34/39] Reuse effective type roots code in language service --- src/compiler/program.ts | 11 ++++------- src/services/services.ts | 15 ++++----------- 2 files changed, 8 insertions(+), 18 deletions(-) diff --git a/src/compiler/program.ts b/src/compiler/program.ts index f48f29785d..7048496ada 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -168,22 +168,19 @@ namespace ts { const typeReferenceExtensions = [".d.ts"]; - function getEffectiveTypeRoots(options: CompilerOptions, host: ModuleResolutionHost) { + export function getEffectiveTypeRoots(options: CompilerOptions, currentDirectory: string) { if (options.typeRoots) { return options.typeRoots; } - let currentDirectory: string; if (options.configFilePath) { currentDirectory = getDirectoryPath(options.configFilePath); } - else if (host.getCurrentDirectory) { - currentDirectory = host.getCurrentDirectory(); - } if (!currentDirectory) { return undefined; } + return map(defaultTypeRoots, d => combinePaths(currentDirectory, d)); } @@ -201,7 +198,7 @@ namespace ts { traceEnabled }; - const typeRoots = getEffectiveTypeRoots(options, host); + const typeRoots = getEffectiveTypeRoots(options, host.getCurrentDirectory && host.getCurrentDirectory()); if (traceEnabled) { if (containingFile === undefined) { if (typeRoots === undefined) { @@ -1061,7 +1058,7 @@ namespace ts { // Walk the primary type lookup locations const result: string[] = []; if (host.directoryExists && host.getDirectories) { - const typeRoots = getEffectiveTypeRoots(options, host); + const typeRoots = getEffectiveTypeRoots(options, host.getCurrentDirectory && host.getCurrentDirectory()); if (typeRoots) { for (const root of typeRoots) { if (host.directoryExists(root)) { diff --git a/src/services/services.ts b/src/services/services.ts index 8bb378e090..519a5abe8c 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -4846,17 +4846,10 @@ namespace ts { result.push(createCompletionEntryForModule(moduleName, ScriptElementKind.externalModuleName)); } } - else if (host.getDirectories && options.typeRoots) { - const absoluteRoots = map(options.typeRoots, rootDirectory => { - if (isRootedDiskPath(rootDirectory)) { - return normalizePath(rootDirectory); - } - - const basePath = options.project || host.getCurrentDirectory(); - return normalizePath(combinePaths(basePath, rootDirectory)); - }); - for (const absoluteRoot of absoluteRoots) { - getCompletionEntriesFromDirectories(host, options, absoluteRoot, result); + else if (host.getDirectories) { + const typeRoots = getEffectiveTypeRoots(options, host.getCurrentDirectory()); + for (const root of typeRoots) { + getCompletionEntriesFromDirectories(host, options, root, result); } } From b9b79af1b7f93411786849ee53f7cca0b0ab260a Mon Sep 17 00:00:00 2001 From: Richard Knoll Date: Wed, 31 Aug 2016 18:11:47 -0700 Subject: [PATCH 35/39] Recombining import completions and regular completion APIs --- src/harness/fourslash.ts | 8 +- src/harness/harnessLanguageService.ts | 3 - src/server/client.ts | 28 ++----- src/server/protocol.d.ts | 20 ++--- src/server/session.ts | 13 ++- src/services/services.ts | 109 ++++++++++++-------------- src/services/shims.ts | 8 -- 7 files changed, 77 insertions(+), 112 deletions(-) diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index 86317faa17..2b4373d79f 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -780,10 +780,10 @@ namespace FourSlash { if (ranges && ranges.length > rangeIndex) { const range = ranges[rangeIndex]; - const start = completions.textSpan.start; - const end = start + completions.textSpan.length; + const start = completion.replacementSpan.start; + const end = start + completion.replacementSpan.length; if (range.start !== start || range.end !== end) { - this.raiseError(`Expected completion span for '${symbol}', ${stringify(completions.textSpan)}, to cover range ${stringify(range)}`); + this.raiseError(`Expected completion span for '${symbol}', ${stringify(completion.replacementSpan)}, to cover range ${stringify(range)}`); } } else { @@ -892,7 +892,7 @@ namespace FourSlash { } private getImportModuleCompletionListAtCaret() { - return this.languageService.getImportModuleCompletionsAtPosition(this.activeFile.fileName, this.currentCaretPosition); + return this.languageService.getCompletionsAtPosition(this.activeFile.fileName, this.currentCaretPosition); } private getCompletionEntryDetails(entryName: string) { diff --git a/src/harness/harnessLanguageService.ts b/src/harness/harnessLanguageService.ts index 880a103a20..fba1b87610 100644 --- a/src/harness/harnessLanguageService.ts +++ b/src/harness/harnessLanguageService.ts @@ -405,9 +405,6 @@ namespace Harness.LanguageService { getCompletionsAtPosition(fileName: string, position: number): ts.CompletionInfo { return unwrapJSONCallResult(this.shim.getCompletionsAtPosition(fileName, position)); } - getImportModuleCompletionsAtPosition(fileName: string, position: number): ts.ImportCompletionInfo { - return unwrapJSONCallResult(this.shim.getImportModuleCompletionsAtPosition(fileName, position)); - } getCompletionEntryDetails(fileName: string, position: number, entryName: string): ts.CompletionEntryDetails { return unwrapJSONCallResult(this.shim.getCompletionEntryDetails(fileName, position, entryName)); } diff --git a/src/server/client.ts b/src/server/client.ts index deb7f53801..d5aed77bd1 100644 --- a/src/server/client.ts +++ b/src/server/client.ts @@ -216,28 +216,16 @@ namespace ts.server { return { isMemberCompletion: false, isNewIdentifierLocation: false, - entries: response.body - }; - } + entries: response.body.map(({ name, kind, kindModifiers, sortText, replacementSpan }) => { - getImportModuleCompletionsAtPosition(fileName: string, position: number): ImportCompletionInfo { - const lineOffset = this.positionToOneBasedLineOffset(fileName, position); - const args: protocol.CompletionsRequestArgs = { - file: fileName, - line: lineOffset.line, - offset: lineOffset.offset, - prefix: undefined - }; + let convertedSpan: TextSpan; + if (replacementSpan) { + convertedSpan = createTextSpanFromBounds(this.lineOffsetToPosition(fileName, replacementSpan.start), + this.lineOffsetToPosition(fileName, replacementSpan.end)); + } - const request = this.processRequest(CommandNames.ImportModuleCompletions, args); - const response = this.processResponse(request); - - const startPosition = this.lineOffsetToPosition(fileName, response.span.start); - const endPosition = this.lineOffsetToPosition(fileName, response.span.end); - - return { - textSpan: ts.createTextSpanFromBounds(startPosition, endPosition), - entries: response.body + return { name, kind, kindModifiers, sortText, replacementSpan: convertedSpan }; + }) }; } diff --git a/src/server/protocol.d.ts b/src/server/protocol.d.ts index 20670ae024..2f71c3d5b7 100644 --- a/src/server/protocol.d.ts +++ b/src/server/protocol.d.ts @@ -717,16 +717,6 @@ declare namespace ts.server.protocol { arguments: CompletionsRequestArgs; } - /** - * Import Module Completions request; value of command field is - * "importModuleCompletions". Given a file location (file, line, - * col) return the possible completions for external module - * specifiers or paths given that position refers to a module - * import declaration, require call, or triple slash reference. - */ - export interface ImportModuleCompletionsRequest extends FileLocationRequest { - } - /** * Arguments for completion details request. */ @@ -783,6 +773,11 @@ declare namespace ts.server.protocol { * is often the same as the name but may be different in certain circumstances. */ sortText: string; + /** + * An optional span that indicates the text to be replaced by this completion item. If present, + * this span should be used instead of the default one. + */ + replacementSpan?: TextSpan; } /** @@ -816,11 +811,6 @@ declare namespace ts.server.protocol { body?: CompletionEntry[]; } - export interface ImportModuleCompletionsResponse extends Response { - span: TextSpan; - body?: ImportCompletionEntry[]; - } - export interface CompletionDetailsResponse extends Response { body?: CompletionEntryDetails[]; } diff --git a/src/server/session.ts b/src/server/session.ts index c771d10022..5af4203311 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -103,7 +103,6 @@ namespace ts.server { export const Change = "change"; export const Close = "close"; export const Completions = "completions"; - export const ImportModuleCompletions = "importModuleCompletions"; export const CompletionDetails = "completionEntryDetails"; export const Configure = "configure"; export const Definition = "definition"; @@ -773,7 +772,17 @@ namespace ts.server { return completions.entries.reduce((result: protocol.CompletionEntry[], entry: ts.CompletionEntry) => { if (completions.isMemberCompletion || (entry.name.toLowerCase().indexOf(prefix.toLowerCase()) === 0)) { - result.push(entry); + const { name, kind, kindModifiers, sortText, replacementSpan } = entry; + + let convertedSpan: protocol.TextSpan = undefined; + if (replacementSpan) { + convertedSpan = { + start: compilerService.host.positionToLineOffset(fileName, replacementSpan.start), + end: compilerService.host.positionToLineOffset(fileName, replacementSpan.start + replacementSpan.length) + }; + } + + result.push({ name, kind, kindModifiers, sortText, replacementSpan: convertedSpan }); } return result; }, []).sort((a, b) => a.name.localeCompare(b.name)); diff --git a/src/services/services.ts b/src/services/services.ts index 519a5abe8c..66e450bf83 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1159,7 +1159,7 @@ namespace ts { useCaseSensitiveFileNames?(): boolean; /* - * LS host can optionally implement these methods to support getImportModuleCompletionsAtPosition. + * LS host can optionally implement these methods to support completions for module specifiers. * Without these methods, only completions for ambient modules will be provided. */ readDirectory?(path: string, extensions?: string[], exclude?: string[], include?: string[]): string[]; @@ -1211,7 +1211,6 @@ namespace ts { getEncodedSemanticClassifications(fileName: string, span: TextSpan): Classifications; getCompletionsAtPosition(fileName: string, position: number): CompletionInfo; - getImportModuleCompletionsAtPosition(fileName: string, position: number): ImportCompletionInfo; getCompletionEntryDetails(fileName: string, position: number, entryName: string): CompletionEntryDetails; getQuickInfoAtPosition(fileName: string, position: number): QuickInfo; @@ -1482,17 +1481,7 @@ namespace ts { kind: string; // see ScriptElementKind kindModifiers: string; // see ScriptElementKindModifier, comma separated sortText: string; - } - - export interface ImportCompletionInfo { - textSpan: TextSpan; - entries: ImportCompletionEntry[]; - } - - export interface ImportCompletionEntry { - name: string; - kind: string; // see ScriptElementKind - sortText: string; + replacementSpan?: TextSpan; } export interface CompletionEntryDetails { @@ -4247,6 +4236,11 @@ namespace ts { const sourceFile = getValidSourceFile(fileName); + const importCompletionInfo = getImportModuleCompletionsAtPosition(fileName, position); + if (importCompletionInfo && importCompletionInfo.entries.length !== 0) { + return importCompletionInfo; + } + if (isInString(sourceFile, position)) { return getStringLiteralCompletionEntries(sourceFile, position); } @@ -4510,7 +4504,7 @@ namespace ts { } } - function getImportModuleCompletionsAtPosition(fileName: string, position: number): ImportCompletionInfo { + function getImportModuleCompletionsAtPosition(fileName: string, position: number): CompletionInfo { synchronizeHostData(); const sourceFile = getValidSourceFile(fileName); @@ -4526,8 +4520,9 @@ namespace ts { if (node.parent.kind === SyntaxKind.ImportDeclaration || isExpressionOfExternalModuleImportEqualsDeclaration(node) || isRequireCall(node.parent, false)) { // Get all known external module names or complete a path to a module return { - entries: getStringLiteralCompletionEntriesFromModuleNames(node), - textSpan: getDirectoryFragmentTextSpan((node).text, node.getStart() + 1) + isMemberCompletion: false, + isNewIdentifierLocation: true, + entries: getStringLiteralCompletionEntriesFromModuleNames(node) }; } } @@ -4539,20 +4534,22 @@ namespace ts { const scriptPath = node.getSourceFile().path; const scriptDirectory = getDirectoryPath(scriptPath); + + const span = getDirectoryFragmentTextSpan((node).text, node.getStart() + 1); if (isPathRelativeToScript(literalValue) || isRootedDiskPath(literalValue)) { const compilerOptions = program.getCompilerOptions(); if (compilerOptions.rootDirs) { return getCompletionEntriesForDirectoryFragmentWithRootDirs( - compilerOptions.rootDirs, literalValue, scriptDirectory, getSupportedExtensions(program.getCompilerOptions()), /*includeExtensions*/false, scriptPath); + compilerOptions.rootDirs, literalValue, scriptDirectory, getSupportedExtensions(program.getCompilerOptions()), /*includeExtensions*/false, span, scriptPath); } else { return getCompletionEntriesForDirectoryFragment( - literalValue, scriptDirectory, getSupportedExtensions(program.getCompilerOptions()), /*includeExtensions*/false, scriptPath); + literalValue, scriptDirectory, getSupportedExtensions(program.getCompilerOptions()), /*includeExtensions*/false, span, scriptPath); } } else { // Check for node modules - return getCompletionEntriesForNonRelativeModules(literalValue, scriptDirectory); + return getCompletionEntriesForNonRelativeModules(literalValue, scriptDirectory, span); } } @@ -4577,21 +4574,21 @@ namespace ts { return deduplicate(map(rootDirs, rootDirectory => combinePaths(rootDirectory, relativeDirectory))); } - function getCompletionEntriesForDirectoryFragmentWithRootDirs(rootDirs: string[], fragment: string, scriptPath: string, extensions: string[], includeExtensions: boolean, exclude?: string): ImportCompletionEntry[] { + function getCompletionEntriesForDirectoryFragmentWithRootDirs(rootDirs: string[], fragment: string, scriptPath: string, extensions: string[], includeExtensions: boolean, span: TextSpan, exclude?: string): CompletionEntry[] { const basePath = program.getCompilerOptions().project || host.getCurrentDirectory(); const ignoreCase = !(host.useCaseSensitiveFileNames && host.useCaseSensitiveFileNames()); const baseDirectories = getBaseDirectoriesFromRootDirs(rootDirs, basePath, scriptPath, ignoreCase); - const result: ImportCompletionEntry[] = []; + const result: CompletionEntry[] = []; for (const baseDirectory of baseDirectories) { - getCompletionEntriesForDirectoryFragment(fragment, baseDirectory, extensions, includeExtensions, exclude, result); + getCompletionEntriesForDirectoryFragment(fragment, baseDirectory, extensions, includeExtensions, span, exclude, result); } return result; } - function getCompletionEntriesForDirectoryFragment(fragment: string, scriptPath: string, extensions: string[], includeExtensions: boolean, exclude?: string, result: ImportCompletionEntry[] = []): ImportCompletionEntry[] { + function getCompletionEntriesForDirectoryFragment(fragment: string, scriptPath: string, extensions: string[], includeExtensions: boolean, span: TextSpan, exclude?: string, result: CompletionEntry[] = []): CompletionEntry[] { fragment = getDirectoryPath(fragment); if (!fragment) { fragment = "./"; @@ -4623,11 +4620,7 @@ namespace ts { } for (const foundFile in foundFiles) { - result.push({ - name: foundFile, - kind: ScriptElementKind.scriptElement, - sortText: foundFile - }); + result.push(createCompletionEntryForModule(foundFile, ScriptElementKind.scriptElement, span)); } } @@ -4637,11 +4630,7 @@ namespace ts { for (const directory of directories) { const directoryName = getBaseFileName(normalizePath(directory)); - result.push({ - name: directoryName, - kind: ScriptElementKind.directory, - sortText: directoryName - }); + result.push(createCompletionEntryForModule(directoryName, ScriptElementKind.directory, span)); } } } @@ -4656,17 +4645,17 @@ namespace ts { * Modules from node_modules (i.e. those listed in package.json) * This includes all files that are found in node_modules/moduleName/ with acceptable file extensions */ - function getCompletionEntriesForNonRelativeModules(fragment: string, scriptPath: string): ImportCompletionEntry[] { + function getCompletionEntriesForNonRelativeModules(fragment: string, scriptPath: string, span: TextSpan): CompletionEntry[] { const options = program.getCompilerOptions(); const { baseUrl, paths } = options; - let result: ImportCompletionEntry[]; + let result: CompletionEntry[]; if (baseUrl) { const fileExtensions = getSupportedExtensions(options); const projectDir = options.project || host.getCurrentDirectory(); const absolute = isRootedDiskPath(baseUrl) ? baseUrl : combinePaths(projectDir, baseUrl); - result = getCompletionEntriesForDirectoryFragment(fragment, normalizePath(absolute), fileExtensions, /*includeExtensions*/false); + result = getCompletionEntriesForDirectoryFragment(fragment, normalizePath(absolute), fileExtensions, /*includeExtensions*/false, span); if (paths) { for (const path in paths) { @@ -4675,7 +4664,7 @@ namespace ts { if (paths[path]) { for (const pattern of paths[path]) { for (const match of getModulesForPathsPattern(fragment, baseUrl, pattern, fileExtensions)) { - result.push(createCompletionEntryForModule(match, ScriptElementKind.externalModuleName)); + result.push(createCompletionEntryForModule(match, ScriptElementKind.externalModuleName, span)); } } } @@ -4683,7 +4672,7 @@ namespace ts { else if (startsWith(path, fragment)) { const entry = paths[path] && paths[path].length === 1 && paths[path][0]; if (entry) { - result.push(createCompletionEntryForModule(path, ScriptElementKind.externalModuleName)); + result.push(createCompletionEntryForModule(path, ScriptElementKind.externalModuleName, span)); } } } @@ -4694,10 +4683,10 @@ namespace ts { result = []; } - getCompletionEntriesFromTypings(host, options, scriptPath, result); + getCompletionEntriesFromTypings(host, options, scriptPath, span, result); for (const moduleName of enumeratePotentialNonRelativeModules(fragment, scriptPath, options)) { - result.push(createCompletionEntryForModule(moduleName, ScriptElementKind.externalModuleName)); + result.push(createCompletionEntryForModule(moduleName, ScriptElementKind.externalModuleName, span)); } return result; @@ -4792,7 +4781,7 @@ namespace ts { return deduplicate(nonRelativeModules); } - function getTripleSlashReferenceCompletion(sourceFile: SourceFile, position: number): ImportCompletionInfo { + function getTripleSlashReferenceCompletion(sourceFile: SourceFile, position: number): CompletionInfo { const token = getTokenAtPosition(sourceFile, position); if (!token) { return undefined; @@ -4818,38 +4807,39 @@ namespace ts { const toComplete = match[3]; const scriptPath = getDirectoryPath(sourceFile.path); + let entries: CompletionEntry[]; if (kind === "path") { // Give completions for a relative path - const textSpan: TextSpan = getDirectoryFragmentTextSpan(toComplete, range.pos + prefix.length); - return { - entries: getCompletionEntriesForDirectoryFragment(toComplete, scriptPath, getSupportedExtensions(program.getCompilerOptions()), /*includeExtensions*/true, sourceFile.path), - textSpan - }; + const span: TextSpan = getDirectoryFragmentTextSpan(toComplete, range.pos + prefix.length); + entries = getCompletionEntriesForDirectoryFragment(toComplete, scriptPath, getSupportedExtensions(program.getCompilerOptions()), /*includeExtensions*/true, span, sourceFile.path); } else { // Give completions based on the typings available - const textSpan: TextSpan = { start: range.pos + prefix.length, length: match[0].length - prefix.length }; - return { - entries: getCompletionEntriesFromTypings(host, program.getCompilerOptions(), scriptPath), - textSpan - }; + const span: TextSpan = { start: range.pos + prefix.length, length: match[0].length - prefix.length }; + entries = getCompletionEntriesFromTypings(host, program.getCompilerOptions(), scriptPath, span); } + + return { + isMemberCompletion: false, + isNewIdentifierLocation: true, + entries + }; } return undefined; } - function getCompletionEntriesFromTypings(host: LanguageServiceHost, options: CompilerOptions, scriptPath: string, result: ImportCompletionEntry[] = []): ImportCompletionEntry[] { + function getCompletionEntriesFromTypings(host: LanguageServiceHost, options: CompilerOptions, scriptPath: string, span: TextSpan, result: CompletionEntry[] = []): CompletionEntry[] { // Check for typings specified in compiler options if (options.types) { for (const moduleName of options.types) { - result.push(createCompletionEntryForModule(moduleName, ScriptElementKind.externalModuleName)); + result.push(createCompletionEntryForModule(moduleName, ScriptElementKind.externalModuleName, span)); } } else if (host.getDirectories) { const typeRoots = getEffectiveTypeRoots(options, host.getCurrentDirectory()); for (const root of typeRoots) { - getCompletionEntriesFromDirectories(host, options, root, result); + getCompletionEntriesFromDirectories(host, options, root, span, result); } } @@ -4857,18 +4847,18 @@ namespace ts { // Also get all @types typings installed in visible node_modules directories for (const package of findPackageJsons(scriptPath)) { const typesDir = combinePaths(getDirectoryPath(package), "node_modules/@types"); - getCompletionEntriesFromDirectories(host, options, typesDir, result); + getCompletionEntriesFromDirectories(host, options, typesDir, span, result); } } return result; } - function getCompletionEntriesFromDirectories(host: LanguageServiceHost, options: CompilerOptions, directory: string, result: ImportCompletionEntry[]) { + function getCompletionEntriesFromDirectories(host: LanguageServiceHost, options: CompilerOptions, directory: string, span: TextSpan, result: CompletionEntry[]) { if (host.getDirectories && directoryProbablyExists(directory, host)) { for (let typeDirectory of host.getDirectories(directory)) { typeDirectory = normalizePath(typeDirectory); - result.push(createCompletionEntryForModule(getBaseFileName(typeDirectory), ScriptElementKind.externalModuleName)); + result.push(createCompletionEntryForModule(getBaseFileName(typeDirectory), ScriptElementKind.externalModuleName, span)); } } } @@ -4948,8 +4938,8 @@ namespace ts { } } - function createCompletionEntryForModule(name: string, kind: string): ImportCompletionEntry { - return { name, kind, sortText: name }; + function createCompletionEntryForModule(name: string, kind: string, replacementSpan: TextSpan): CompletionEntry { + return { name, kind, kindModifiers: ScriptElementKindModifier.none, sortText: name, replacementSpan }; } // Replace everything after the last directory seperator that appears @@ -8783,7 +8773,6 @@ namespace ts { getEncodedSyntacticClassifications, getEncodedSemanticClassifications, getCompletionsAtPosition, - getImportModuleCompletionsAtPosition, getCompletionEntryDetails, getSignatureHelpItems, getQuickInfoAtPosition, diff --git a/src/services/shims.ts b/src/services/shims.ts index 90d29dfa65..41b366e085 100644 --- a/src/services/shims.ts +++ b/src/services/shims.ts @@ -140,7 +140,6 @@ namespace ts { getEncodedSemanticClassifications(fileName: string, start: number, length: number): string; getCompletionsAtPosition(fileName: string, position: number): string; - getImportModuleCompletionsAtPosition(fileName: string, position: number): string; getCompletionEntryDetails(fileName: string, position: number, entryName: string): string; getQuickInfoAtPosition(fileName: string, position: number): string; @@ -886,13 +885,6 @@ namespace ts { ); } - getImportModuleCompletionsAtPosition(fileName: string, position: number): string { - return this.forwardJSONCall( - `getImportModuleCompletionsAtPosition('${fileName}', ${position})`, - () => this.languageService.getImportModuleCompletionsAtPosition(fileName, position) - ); - } - /** Get a string based representation of a completion list entry details */ public getCompletionEntryDetails(fileName: string, position: number, entryName: string) { return this.forwardJSONCall( From 7261866c6cb8a312d2598a1abcd3d39f10ccbb81 Mon Sep 17 00:00:00 2001 From: Richard Knoll Date: Wed, 31 Aug 2016 19:20:15 -0700 Subject: [PATCH 36/39] Cleaning up the completion code and tests --- src/harness/fourslash.ts | 154 ++++++------------ src/services/services.ts | 52 +++--- .../completionForStringLiteralImport1.ts | 8 +- .../completionForStringLiteralImport2.ts | 8 +- ...etionForStringLiteralNonrelativeImport1.ts | 22 +-- ...tionForStringLiteralNonrelativeImport10.ts | 4 +- ...tionForStringLiteralNonrelativeImport11.ts | 8 +- ...tionForStringLiteralNonrelativeImport12.ts | 10 +- ...etionForStringLiteralNonrelativeImport2.ts | 6 +- ...etionForStringLiteralNonrelativeImport3.ts | 8 +- ...etionForStringLiteralNonrelativeImport4.ts | 8 +- ...etionForStringLiteralNonrelativeImport5.ts | 12 +- ...etionForStringLiteralNonrelativeImport7.ts | 6 +- ...etionForStringLiteralNonrelativeImport8.ts | 6 +- ...etionForStringLiteralNonrelativeImport9.ts | 6 +- ...rStringLiteralNonrelativeImportTypings1.ts | 8 +- ...rStringLiteralNonrelativeImportTypings2.ts | 6 +- ...rStringLiteralNonrelativeImportTypings3.ts | 6 +- ...mpletionForStringLiteralRelativeImport1.ts | 28 ++-- ...mpletionForStringLiteralRelativeImport2.ts | 28 ++-- ...mpletionForStringLiteralRelativeImport3.ts | 20 +-- ...mpletionForStringLiteralRelativeImport4.ts | 10 +- .../completionForTripleSlashReference1.ts | 24 +-- .../completionForTripleSlashReference2.ts | 12 +- .../completionForTripleSlashReference3.ts | 20 +-- .../completionForTripleSlashReference4.ts | 4 +- tests/cases/fourslash/fourslash.ts | 5 +- 27 files changed, 206 insertions(+), 283 deletions(-) diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index 2b4373d79f..41471ed9a0 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -598,38 +598,6 @@ namespace FourSlash { } } - public verifyImportModuleCompletionListItemsCountIsGreaterThan(count: number, negative: boolean) { - const completions = this.getImportModuleCompletionListAtCaret(); - const itemsCount = completions.entries.length; - - if (negative) { - if (itemsCount > count) { - this.raiseError(`Expected import module completion list items count to not be greater than ${count}, but is actually ${itemsCount}`); - } - } - else { - if (itemsCount <= count) { - this.raiseError(`Expected import module completion list items count to be greater than ${count}, but is actually ${itemsCount}`); - } - } - } - - public verifyImportModuleCompletionListIsEmpty(negative: boolean) { - const completions = this.getImportModuleCompletionListAtCaret(); - if ((!completions || completions.entries.length === 0) && negative) { - this.raiseError("Completion list is empty at caret at position " + this.activeFile.fileName + " " + this.currentCaretPosition); - } - else if (completions && completions.entries.length !== 0 && !negative) { - let errorMsg = "\n" + "Completion List contains: [" + completions.entries[0].name; - for (let i = 1; i < completions.entries.length; i++) { - errorMsg += ", " + completions.entries[i].name; - } - errorMsg += "]\n"; - - this.raiseError("Completion list is not empty at caret at position " + this.activeFile.fileName + " " + this.currentCaretPosition + errorMsg); - } - } - public verifyCompletionListStartsWithItemsInOrder(items: string[]): void { if (items.length === 0) { return; @@ -701,10 +669,10 @@ namespace FourSlash { } } - public verifyCompletionListContains(symbol: string, text?: string, documentation?: string, kind?: string) { + public verifyCompletionListContains(symbol: string, text?: string, documentation?: string, kind?: string, spanIndex?: number) { const completions = this.getCompletionListAtCaret(); if (completions) { - this.assertItemInCompletionList(completions.entries, symbol, text, documentation, kind); + this.assertItemInCompletionList(completions.entries, symbol, text, documentation, kind, spanIndex); } else { this.raiseError(`No completions at position '${this.currentCaretPosition}' when looking for '${symbol}'.`); @@ -720,25 +688,32 @@ namespace FourSlash { * @param expectedText the text associated with the symbol * @param expectedDocumentation the documentation text associated with the symbol * @param expectedKind the kind of symbol (see ScriptElementKind) + * @param spanIndex the index of the range that the completion item's replacement text span should match */ - public verifyCompletionListDoesNotContain(symbol: string, expectedText?: string, expectedDocumentation?: string, expectedKind?: string) { + public verifyCompletionListDoesNotContain(symbol: string, expectedText?: string, expectedDocumentation?: string, expectedKind?: string, spanIndex?: number) { const that = this; + let replacementSpan: ts.TextSpan; + if (spanIndex !== undefined) { + replacementSpan = this.getTextSpanForRangeAtIndex(spanIndex); + } + function filterByTextOrDocumentation(entry: ts.CompletionEntry) { const details = that.getCompletionEntryDetails(entry.name); const documentation = ts.displayPartsToString(details.documentation); const text = ts.displayPartsToString(details.displayParts); - if (expectedText && expectedDocumentation) { - return (documentation === expectedDocumentation && text === expectedText) ? true : false; + + // If any of the expected values are undefined, assume that users don't + // care about them. + if (replacementSpan && !TestState.textSpansEqual(replacementSpan, entry.replacementSpan)) { + return false; } - else if (expectedText && !expectedDocumentation) { - return text === expectedText ? true : false; + else if (expectedText && text !== expectedText) { + return false; } - else if (expectedDocumentation && !expectedText) { - return documentation === expectedDocumentation ? true : false; + else if (expectedDocumentation && documentation !== expectedDocumentation) { + return false; } - // Because expectedText and expectedDocumentation are undefined, we assume that - // users don"t care to compare them so we will treat that entry as if the entry has matching text and documentation - // and keep it in the list of filtered entry. + return true; } @@ -762,49 +737,15 @@ namespace FourSlash { if (expectedKind) { error += "Expected kind: " + expectedKind + " to equal: " + filterCompletions[0].kind + "."; } + if (replacementSpan) { + const spanText = filterCompletions[0].replacementSpan ? stringify(filterCompletions[0].replacementSpan) : undefined; + error += "Expected replacement span: " + stringify(replacementSpan) + " to equal: " + spanText + "."; + } this.raiseError(error); } } } - public verifyImportModuleCompletionListContains(symbol: string, rangeIndex?: number) { - const completions = this.getImportModuleCompletionListAtCaret(); - if (completions) { - const completion = ts.forEach(completions.entries, completion => completion.name === symbol ? completion : undefined); - if (!completion) { - const itemsString = completions.entries.map(item => item.name).join(",\n"); - this.raiseError(`Expected "${symbol}" to be in list [${itemsString}]`); - } - else if (rangeIndex !== undefined) { - const ranges = this.getRanges(); - if (ranges && ranges.length > rangeIndex) { - const range = ranges[rangeIndex]; - - const start = completion.replacementSpan.start; - const end = start + completion.replacementSpan.length; - if (range.start !== start || range.end !== end) { - this.raiseError(`Expected completion span for '${symbol}', ${stringify(completion.replacementSpan)}, to cover range ${stringify(range)}`); - } - } - else { - this.raiseError(`Expected completion span for '${symbol}' to cover range at index ${rangeIndex}, but no range was found at that index`); - } - } - } - else { - this.raiseError(`No import module completions at position '${this.currentCaretPosition}' when looking for '${symbol}'.`); - } - } - - public verifyImportModuleCompletionListDoesNotContain(symbol: string) { - const completions = this.getImportModuleCompletionListAtCaret(); - if (completions) { - if (ts.forEach(completions.entries, completion => completion.name === symbol)) { - this.raiseError(`Import module completion list did contain ${symbol}`); - } - } - } - public verifyCompletionEntryDetails(entryName: string, expectedText: string, expectedDocumentation?: string, kind?: string) { const details = this.getCompletionEntryDetails(entryName); @@ -891,10 +832,6 @@ namespace FourSlash { return this.languageService.getCompletionsAtPosition(this.activeFile.fileName, this.currentCaretPosition); } - private getImportModuleCompletionListAtCaret() { - return this.languageService.getCompletionsAtPosition(this.activeFile.fileName, this.currentCaretPosition); - } - private getCompletionEntryDetails(entryName: string) { return this.languageService.getCompletionEntryDetails(this.activeFile.fileName, this.currentCaretPosition, entryName); } @@ -2241,7 +2178,7 @@ namespace FourSlash { return text.substring(startPos, endPos); } - private assertItemInCompletionList(items: ts.CompletionEntry[], name: string, text?: string, documentation?: string, kind?: string) { + private assertItemInCompletionList(items: ts.CompletionEntry[], name: string, text?: string, documentation?: string, kind?: string, spanIndex?: number) { for (let i = 0; i < items.length; i++) { const item = items[i]; if (item.name === name) { @@ -2260,6 +2197,11 @@ namespace FourSlash { assert.equal(item.kind, kind, this.assertionMessageAtLastKnownMarker("completion item kind for " + name)); } + if (spanIndex !== undefined) { + const span = this.getTextSpanForRangeAtIndex(spanIndex); + assert.isTrue(TestState.textSpansEqual(span, item.replacementSpan), this.assertionMessageAtLastKnownMarker(stringify(span) + " does not equal " + stringify(item.replacementSpan) + " replacement span for " + name)); + } + return; } } @@ -2316,6 +2258,17 @@ namespace FourSlash { return `line ${(pos.line + 1)}, col ${pos.character}`; } + private getTextSpanForRangeAtIndex(index: number): ts.TextSpan { + const ranges = this.getRanges(); + if (ranges && ranges.length > index) { + const range = ranges[index]; + return { start: range.start, length: range.end - range.start }; + } + else { + this.raiseError("Supplied span index: " + index + " does not exist in range list of size: " + (ranges ? 0 : ranges.length)); + } + } + public getMarkerByName(markerName: string) { const markerPos = this.testData.markerPositions[markerName]; if (markerPos === undefined) { @@ -2339,6 +2292,10 @@ namespace FourSlash { public resetCancelled(): void { this.cancellationToken.resetCancelled(); } + + private static textSpansEqual(a: ts.TextSpan, b: ts.TextSpan) { + return a && b && a.start === b.start && a.length === b.length; + } } export function runFourSlashTest(basePath: string, testType: FourSlashTestType, fileName: string) { @@ -2909,12 +2866,12 @@ namespace FourSlashInterface { // Verifies the completion list contains the specified symbol. The // completion list is brought up if necessary - public completionListContains(symbol: string, text?: string, documentation?: string, kind?: string) { + public completionListContains(symbol: string, text?: string, documentation?: string, kind?: string, spanIndex?: number) { if (this.negative) { - this.state.verifyCompletionListDoesNotContain(symbol, text, documentation, kind); + this.state.verifyCompletionListDoesNotContain(symbol, text, documentation, kind, spanIndex); } else { - this.state.verifyCompletionListContains(symbol, text, documentation, kind); + this.state.verifyCompletionListContains(symbol, text, documentation, kind, spanIndex); } } @@ -2924,23 +2881,6 @@ namespace FourSlashInterface { this.state.verifyCompletionListItemsCountIsGreaterThan(count, this.negative); } - public importModuleCompletionListContains(symbol: string, rangeIndex?: number): void { - if (this.negative) { - this.state.verifyImportModuleCompletionListDoesNotContain(symbol); - } - else { - this.state.verifyImportModuleCompletionListContains(symbol, rangeIndex); - } - } - - public importModuleCompletionListItemsCountIsGreaterThan(count: number): void { - this.state.verifyImportModuleCompletionListItemsCountIsGreaterThan(count, this.negative); - } - - public importModuleCompletionListIsEmpty(): void { - this.state.verifyImportModuleCompletionListIsEmpty(this.negative); - } - public assertHasRanges(ranges: FourSlash.Range[]) { assert(ranges.length !== 0, "Array of ranges is expected to be non-empty"); } diff --git a/src/services/services.ts b/src/services/services.ts index 66e450bf83..0bbaa9b5c3 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -4236,9 +4236,8 @@ namespace ts { const sourceFile = getValidSourceFile(fileName); - const importCompletionInfo = getImportModuleCompletionsAtPosition(fileName, position); - if (importCompletionInfo && importCompletionInfo.entries.length !== 0) { - return importCompletionInfo; + if (isInReferenceComment(sourceFile, position)) { + return getTripleSlashReferenceCompletion(sourceFile, position); } if (isInString(sourceFile, position)) { @@ -4410,6 +4409,13 @@ namespace ts { // a['/*completion position*/'] return getStringLiteralCompletionEntriesFromElementAccess(node.parent); } + else if (node.parent.kind === SyntaxKind.ImportDeclaration || isExpressionOfExternalModuleImportEqualsDeclaration(node) || isRequireCall(node.parent, false)) { + // Get all known external module names or complete a path to a module + // i.e. import * as ns from "/*completion position*/"; + // import x = require("/*completion position*/"); + // var y = require("/*completion position*/"); + return getStringLiteralCompletionEntriesFromModuleNames(node); + } else { const argumentInfo = SignatureHelp.getContainingArgumentInfo(node, position, sourceFile); if (argumentInfo) { @@ -4502,55 +4508,35 @@ namespace ts { } } } - } - function getImportModuleCompletionsAtPosition(fileName: string, position: number): CompletionInfo { - synchronizeHostData(); - - const sourceFile = getValidSourceFile(fileName); - if (isInReferenceComment(sourceFile, position)) { - return getTripleSlashReferenceCompletion(sourceFile, position); - } - else if (isInString(sourceFile, position)) { - const node = findPrecedingToken(position, sourceFile); - if (!node || node.kind !== SyntaxKind.StringLiteral) { - return undefined; - } - - if (node.parent.kind === SyntaxKind.ImportDeclaration || isExpressionOfExternalModuleImportEqualsDeclaration(node) || isRequireCall(node.parent, false)) { - // Get all known external module names or complete a path to a module - return { - isMemberCompletion: false, - isNewIdentifierLocation: true, - entries: getStringLiteralCompletionEntriesFromModuleNames(node) - }; - } - } - - return undefined; - - function getStringLiteralCompletionEntriesFromModuleNames(node: StringLiteral) { + function getStringLiteralCompletionEntriesFromModuleNames(node: StringLiteral): CompletionInfo { const literalValue = normalizeSlashes(node.text); const scriptPath = node.getSourceFile().path; const scriptDirectory = getDirectoryPath(scriptPath); const span = getDirectoryFragmentTextSpan((node).text, node.getStart() + 1); + let entries: CompletionEntry[]; if (isPathRelativeToScript(literalValue) || isRootedDiskPath(literalValue)) { const compilerOptions = program.getCompilerOptions(); if (compilerOptions.rootDirs) { - return getCompletionEntriesForDirectoryFragmentWithRootDirs( + entries = getCompletionEntriesForDirectoryFragmentWithRootDirs( compilerOptions.rootDirs, literalValue, scriptDirectory, getSupportedExtensions(program.getCompilerOptions()), /*includeExtensions*/false, span, scriptPath); } else { - return getCompletionEntriesForDirectoryFragment( + entries = getCompletionEntriesForDirectoryFragment( literalValue, scriptDirectory, getSupportedExtensions(program.getCompilerOptions()), /*includeExtensions*/false, span, scriptPath); } } else { // Check for node modules - return getCompletionEntriesForNonRelativeModules(literalValue, scriptDirectory, span); + entries = getCompletionEntriesForNonRelativeModules(literalValue, scriptDirectory, span); } + return { + isMemberCompletion: false, + isNewIdentifierLocation: true, + entries + }; } /** diff --git a/tests/cases/fourslash/completionForStringLiteralImport1.ts b/tests/cases/fourslash/completionForStringLiteralImport1.ts index 482726f630..4cdee88953 100644 --- a/tests/cases/fourslash/completionForStringLiteralImport1.ts +++ b/tests/cases/fourslash/completionForStringLiteralImport1.ts @@ -21,13 +21,13 @@ //// export var x = 9; goTo.marker("0"); -verify.importModuleCompletionListContains("someFile1", 0); +verify.completionListContains("someFile1", undefined, undefined, undefined, 0); goTo.marker("1"); -verify.importModuleCompletionListContains("someFile2", 1); +verify.completionListContains("someFile2", undefined, undefined, undefined, 1); goTo.marker("2"); -verify.importModuleCompletionListContains("some-module", 2); +verify.completionListContains("some-module", undefined, undefined, undefined, 2); goTo.marker("3"); -verify.importModuleCompletionListContains("fourslash", 3); \ No newline at end of file +verify.completionListContains("fourslash", undefined, undefined, undefined, 3); \ No newline at end of file diff --git a/tests/cases/fourslash/completionForStringLiteralImport2.ts b/tests/cases/fourslash/completionForStringLiteralImport2.ts index 43896f1f39..25de3d2240 100644 --- a/tests/cases/fourslash/completionForStringLiteralImport2.ts +++ b/tests/cases/fourslash/completionForStringLiteralImport2.ts @@ -21,13 +21,13 @@ //// export var x = 9; goTo.marker("0"); -verify.importModuleCompletionListContains("someFile.ts", 0); +verify.completionListContains("someFile.ts", undefined, undefined, undefined, 0); goTo.marker("1"); -verify.importModuleCompletionListContains("some-module", 1); +verify.completionListContains("some-module", undefined, undefined, undefined, 1); goTo.marker("2"); -verify.importModuleCompletionListContains("someOtherFile.ts", 2); +verify.completionListContains("someOtherFile.ts", undefined, undefined, undefined, 2); goTo.marker("3"); -verify.importModuleCompletionListContains("some-module", 3); \ No newline at end of file +verify.completionListContains("some-module", undefined, undefined, undefined, 3); \ No newline at end of file diff --git a/tests/cases/fourslash/completionForStringLiteralNonrelativeImport1.ts b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport1.ts index d6f38431ce..c0e5ebbcd9 100644 --- a/tests/cases/fourslash/completionForStringLiteralNonrelativeImport1.ts +++ b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport1.ts @@ -45,19 +45,19 @@ const kinds = ["import_as", "import_equals", "require"]; for (const kind of kinds) { goTo.marker(kind + "0"); - verify.importModuleCompletionListContains("fake-module"); - verify.importModuleCompletionListContains("fake-module-dev"); - verify.not.importModuleCompletionListItemsCountIsGreaterThan(2); + verify.completionListContains("fake-module"); + verify.completionListContains("fake-module-dev"); + verify.not.completionListItemsCountIsGreaterThan(2); goTo.marker(kind + "1"); - verify.importModuleCompletionListContains("index"); - verify.importModuleCompletionListContains("ts"); - verify.importModuleCompletionListContains("dts"); - verify.importModuleCompletionListContains("tsx"); - verify.not.importModuleCompletionListItemsCountIsGreaterThan(4); + verify.completionListContains("index"); + verify.completionListContains("ts"); + verify.completionListContains("dts"); + verify.completionListContains("tsx"); + verify.not.completionListItemsCountIsGreaterThan(4); goTo.marker(kind + "2"); - verify.importModuleCompletionListContains("fake-module"); - verify.importModuleCompletionListContains("fake-module-dev"); - verify.not.importModuleCompletionListItemsCountIsGreaterThan(2); + verify.completionListContains("fake-module"); + verify.completionListContains("fake-module-dev"); + verify.not.completionListItemsCountIsGreaterThan(2); } \ No newline at end of file diff --git a/tests/cases/fourslash/completionForStringLiteralNonrelativeImport10.ts b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport10.ts index aa9fd97664..24a323bc6e 100644 --- a/tests/cases/fourslash/completionForStringLiteralNonrelativeImport10.ts +++ b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport10.ts @@ -28,8 +28,8 @@ const kinds = ["import_as", "import_equals", "require"]; for (const kind of kinds) { goTo.marker(kind + "0"); - verify.importModuleCompletionListIsEmpty(); + verify.completionListIsEmpty(); goTo.marker(kind + "1"); - verify.importModuleCompletionListIsEmpty(); + verify.completionListIsEmpty(); } \ No newline at end of file diff --git a/tests/cases/fourslash/completionForStringLiteralNonrelativeImport11.ts b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport11.ts index 70d39182a8..7b81c4da10 100644 --- a/tests/cases/fourslash/completionForStringLiteralNonrelativeImport11.ts +++ b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport11.ts @@ -28,11 +28,11 @@ const kinds = ["import_as", "import_equals", "require"]; for (const kind of kinds) { goTo.marker(kind + "0"); - verify.importModuleCompletionListContains("module"); - verify.not.importModuleCompletionListItemsCountIsGreaterThan(1); + verify.completionListContains("module"); + verify.not.completionListItemsCountIsGreaterThan(1); goTo.marker(kind + "1"); - verify.importModuleCompletionListContains("index"); - verify.not.importModuleCompletionListItemsCountIsGreaterThan(1); + verify.completionListContains("index"); + verify.not.completionListItemsCountIsGreaterThan(1); } diff --git a/tests/cases/fourslash/completionForStringLiteralNonrelativeImport12.ts b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport12.ts index 92db8d5a50..ed202d68ec 100644 --- a/tests/cases/fourslash/completionForStringLiteralNonrelativeImport12.ts +++ b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport12.ts @@ -20,9 +20,9 @@ const kinds = ["import_as", "import_equals", "require"]; for (const kind of kinds) { goTo.marker(kind + "0"); - verify.importModuleCompletionListContains("module"); - verify.importModuleCompletionListContains("dev-module"); - verify.importModuleCompletionListContains("optional-module"); - verify.importModuleCompletionListContains("peer-module"); - verify.not.importModuleCompletionListItemsCountIsGreaterThan(4); + verify.completionListContains("module"); + verify.completionListContains("dev-module"); + verify.completionListContains("optional-module"); + verify.completionListContains("peer-module"); + verify.not.completionListItemsCountIsGreaterThan(4); } diff --git a/tests/cases/fourslash/completionForStringLiteralNonrelativeImport2.ts b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport2.ts index bc2529e86f..bac5c54ff2 100644 --- a/tests/cases/fourslash/completionForStringLiteralNonrelativeImport2.ts +++ b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport2.ts @@ -31,7 +31,7 @@ const kinds = ["import_as", "import_equals", "require"]; for (const kind of kinds) { goTo.marker(kind + "0"); - verify.importModuleCompletionListContains("repeated"); - verify.importModuleCompletionListContains("other"); - verify.not.importModuleCompletionListItemsCountIsGreaterThan(2); + verify.completionListContains("repeated"); + verify.completionListContains("other"); + verify.not.completionListItemsCountIsGreaterThan(2); } diff --git a/tests/cases/fourslash/completionForStringLiteralNonrelativeImport3.ts b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport3.ts index 9b1fd45118..d2103f6f10 100644 --- a/tests/cases/fourslash/completionForStringLiteralNonrelativeImport3.ts +++ b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport3.ts @@ -31,8 +31,8 @@ const kinds = ["import_as", "import_equals", "require"]; for (const kind of kinds) { goTo.marker(kind + "0"); - verify.importModuleCompletionListContains("ts"); - verify.importModuleCompletionListContains("tsx"); - verify.importModuleCompletionListContains("dts"); - verify.not.importModuleCompletionListItemsCountIsGreaterThan(3); + verify.completionListContains("ts"); + verify.completionListContains("tsx"); + verify.completionListContains("dts"); + verify.not.completionListItemsCountIsGreaterThan(3); } diff --git a/tests/cases/fourslash/completionForStringLiteralNonrelativeImport4.ts b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport4.ts index b9be9f9021..b95fd96f38 100644 --- a/tests/cases/fourslash/completionForStringLiteralNonrelativeImport4.ts +++ b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport4.ts @@ -27,8 +27,8 @@ const kinds = ["import_as", "import_equals", "require"]; for (const kind of kinds) { goTo.marker(kind + "0"); - verify.importModuleCompletionListContains("fake-module"); - verify.importModuleCompletionListContains("fake-module2"); - verify.importModuleCompletionListContains("fake-module3"); - verify.not.importModuleCompletionListItemsCountIsGreaterThan(3); + verify.completionListContains("fake-module"); + verify.completionListContains("fake-module2"); + verify.completionListContains("fake-module3"); + verify.not.completionListItemsCountIsGreaterThan(3); } \ No newline at end of file diff --git a/tests/cases/fourslash/completionForStringLiteralNonrelativeImport5.ts b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport5.ts index 4afa5854bf..c8c9a18ff8 100644 --- a/tests/cases/fourslash/completionForStringLiteralNonrelativeImport5.ts +++ b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport5.ts @@ -26,14 +26,14 @@ const kinds = ["import_as", "import_equals", "require"]; for (const kind of kinds) { goTo.marker(kind + "0"); - verify.importModuleCompletionListContains("ambientModule"); - verify.importModuleCompletionListContains("otherAmbientModule"); - verify.importModuleCompletionListContains("otherOtherAmbientModule"); - verify.not.importModuleCompletionListItemsCountIsGreaterThan(3); + verify.completionListContains("ambientModule"); + verify.completionListContains("otherAmbientModule"); + verify.completionListContains("otherOtherAmbientModule"); + verify.not.completionListItemsCountIsGreaterThan(3); goTo.marker(kind + "1"); - verify.importModuleCompletionListContains("ambientModule"); - verify.not.importModuleCompletionListItemsCountIsGreaterThan(1); + verify.completionListContains("ambientModule"); + verify.not.completionListItemsCountIsGreaterThan(1); } diff --git a/tests/cases/fourslash/completionForStringLiteralNonrelativeImport7.ts b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport7.ts index eca5a3e541..57ba6e440c 100644 --- a/tests/cases/fourslash/completionForStringLiteralNonrelativeImport7.ts +++ b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport7.ts @@ -24,7 +24,7 @@ const kinds = ["import_as", "import_equals", "require"]; for (const kind of kinds) { goTo.marker(kind + "0"); - verify.importModuleCompletionListContains("module"); - verify.importModuleCompletionListContains("module-from-node"); - verify.not.importModuleCompletionListItemsCountIsGreaterThan(2); + verify.completionListContains("module"); + verify.completionListContains("module-from-node"); + verify.not.completionListItemsCountIsGreaterThan(2); } diff --git a/tests/cases/fourslash/completionForStringLiteralNonrelativeImport8.ts b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport8.ts index 5489399143..603e8d1104 100644 --- a/tests/cases/fourslash/completionForStringLiteralNonrelativeImport8.ts +++ b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport8.ts @@ -45,11 +45,11 @@ const kinds = ["import_as", "import_equals", "require"]; for (const kind of kinds) { goTo.marker(kind + "0"); - verify.importModuleCompletionListContains("0test"); + verify.completionListContains("0test"); goTo.marker(kind + "1"); - verify.importModuleCompletionListContains("1test"); + verify.completionListContains("1test"); goTo.marker(kind + "2"); - verify.importModuleCompletionListContains("2test"); + verify.completionListContains("2test"); } diff --git a/tests/cases/fourslash/completionForStringLiteralNonrelativeImport9.ts b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport9.ts index bcdb731d1f..8703c60846 100644 --- a/tests/cases/fourslash/completionForStringLiteralNonrelativeImport9.ts +++ b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport9.ts @@ -30,7 +30,7 @@ const kinds = ["import_as", "import_equals", "require"]; for (const kind of kinds) { goTo.marker(kind + "0"); - verify.importModuleCompletionListContains("module1"); - verify.importModuleCompletionListContains("module2"); - verify.not.importModuleCompletionListItemsCountIsGreaterThan(2); + verify.completionListContains("module1"); + verify.completionListContains("module2"); + verify.not.completionListItemsCountIsGreaterThan(2); } diff --git a/tests/cases/fourslash/completionForStringLiteralNonrelativeImportTypings1.ts b/tests/cases/fourslash/completionForStringLiteralNonrelativeImportTypings1.ts index 9cf2af9c87..687103629e 100644 --- a/tests/cases/fourslash/completionForStringLiteralNonrelativeImportTypings1.ts +++ b/tests/cases/fourslash/completionForStringLiteralNonrelativeImportTypings1.ts @@ -28,8 +28,8 @@ const kinds = ["types_ref", "import_as", "import_equals", "require"]; for (const kind of kinds) { goTo.marker(kind + "0"); - verify.importModuleCompletionListContains("module-x"); - verify.importModuleCompletionListContains("module-y"); - verify.importModuleCompletionListContains("module-z"); - verify.not.importModuleCompletionListItemsCountIsGreaterThan(3); + verify.completionListContains("module-x"); + verify.completionListContains("module-y"); + verify.completionListContains("module-z"); + verify.not.completionListItemsCountIsGreaterThan(3); } diff --git a/tests/cases/fourslash/completionForStringLiteralNonrelativeImportTypings2.ts b/tests/cases/fourslash/completionForStringLiteralNonrelativeImportTypings2.ts index 1452c6c1c6..12a4fcf243 100644 --- a/tests/cases/fourslash/completionForStringLiteralNonrelativeImportTypings2.ts +++ b/tests/cases/fourslash/completionForStringLiteralNonrelativeImportTypings2.ts @@ -26,7 +26,7 @@ const kinds = ["types_ref", "import_as", "import_equals", "require"]; for (const kind of kinds) { goTo.marker(kind + "0"); - verify.importModuleCompletionListContains("module-x"); - verify.importModuleCompletionListContains("module-z"); - verify.not.importModuleCompletionListItemsCountIsGreaterThan(2); + verify.completionListContains("module-x"); + verify.completionListContains("module-z"); + verify.not.completionListItemsCountIsGreaterThan(2); } diff --git a/tests/cases/fourslash/completionForStringLiteralNonrelativeImportTypings3.ts b/tests/cases/fourslash/completionForStringLiteralNonrelativeImportTypings3.ts index f4a4dbaaa9..14cf71ad0d 100644 --- a/tests/cases/fourslash/completionForStringLiteralNonrelativeImportTypings3.ts +++ b/tests/cases/fourslash/completionForStringLiteralNonrelativeImportTypings3.ts @@ -22,7 +22,7 @@ const kinds = ["types_ref", "import_as", "import_equals", "require"]; for (const kind of kinds) { goTo.marker(kind + "0"); - verify.importModuleCompletionListContains("module-x"); - verify.importModuleCompletionListContains("module-y"); - verify.not.importModuleCompletionListItemsCountIsGreaterThan(2); + verify.completionListContains("module-x"); + verify.completionListContains("module-y"); + verify.not.completionListItemsCountIsGreaterThan(2); } diff --git a/tests/cases/fourslash/completionForStringLiteralRelativeImport1.ts b/tests/cases/fourslash/completionForStringLiteralRelativeImport1.ts index 909e2c6b14..3ba82fb0ac 100644 --- a/tests/cases/fourslash/completionForStringLiteralRelativeImport1.ts +++ b/tests/cases/fourslash/completionForStringLiteralRelativeImport1.ts @@ -47,24 +47,24 @@ const kinds = ["import_as", "import_equals", "require"]; for (const kind of kinds) { goTo.marker(kind + "0"); - verify.importModuleCompletionListIsEmpty(); + verify.completionListIsEmpty(); goTo.marker(kind + "1"); - verify.importModuleCompletionListContains("f1"); - verify.importModuleCompletionListContains("f2"); - verify.importModuleCompletionListContains("e1"); - verify.importModuleCompletionListContains("folder"); - verify.importModuleCompletionListContains("parentTest"); - verify.not.importModuleCompletionListItemsCountIsGreaterThan(5); + verify.completionListContains("f1"); + verify.completionListContains("f2"); + verify.completionListContains("e1"); + verify.completionListContains("folder"); + verify.completionListContains("parentTest"); + verify.not.completionListItemsCountIsGreaterThan(5); goTo.marker(kind + "2"); - verify.importModuleCompletionListContains("f3"); - verify.importModuleCompletionListContains("h1"); - verify.not.importModuleCompletionListItemsCountIsGreaterThan(2); + verify.completionListContains("f3"); + verify.completionListContains("h1"); + verify.not.completionListItemsCountIsGreaterThan(2); goTo.marker(kind + "3"); - verify.importModuleCompletionListContains("f4"); - verify.importModuleCompletionListContains("g1"); - verify.importModuleCompletionListContains("sub"); - verify.not.importModuleCompletionListItemsCountIsGreaterThan(3); + verify.completionListContains("f4"); + verify.completionListContains("g1"); + verify.completionListContains("sub"); + verify.not.completionListItemsCountIsGreaterThan(3); } \ No newline at end of file diff --git a/tests/cases/fourslash/completionForStringLiteralRelativeImport2.ts b/tests/cases/fourslash/completionForStringLiteralRelativeImport2.ts index 8c8cb405cd..18a80ab348 100644 --- a/tests/cases/fourslash/completionForStringLiteralRelativeImport2.ts +++ b/tests/cases/fourslash/completionForStringLiteralRelativeImport2.ts @@ -34,20 +34,20 @@ const kinds = ["import_as", "import_equals", "require"]; for (const kind of kinds) { goTo.marker(kind + "0"); - verify.importModuleCompletionListContains("f1"); - verify.importModuleCompletionListContains("f2"); - verify.importModuleCompletionListContains("f3"); - verify.importModuleCompletionListContains("f4"); - verify.importModuleCompletionListContains("e1"); - verify.importModuleCompletionListContains("e2"); - verify.not.importModuleCompletionListItemsCountIsGreaterThan(6); + verify.completionListContains("f1"); + verify.completionListContains("f2"); + verify.completionListContains("f3"); + verify.completionListContains("f4"); + verify.completionListContains("e1"); + verify.completionListContains("e2"); + verify.not.completionListItemsCountIsGreaterThan(6); goTo.marker(kind + "1"); - verify.importModuleCompletionListContains("f1"); - verify.importModuleCompletionListContains("f2"); - verify.importModuleCompletionListContains("f3"); - verify.importModuleCompletionListContains("f4"); - verify.importModuleCompletionListContains("e1"); - verify.importModuleCompletionListContains("e2"); - verify.not.importModuleCompletionListItemsCountIsGreaterThan(6); + verify.completionListContains("f1"); + verify.completionListContains("f2"); + verify.completionListContains("f3"); + verify.completionListContains("f4"); + verify.completionListContains("e1"); + verify.completionListContains("e2"); + verify.not.completionListItemsCountIsGreaterThan(6); } \ No newline at end of file diff --git a/tests/cases/fourslash/completionForStringLiteralRelativeImport3.ts b/tests/cases/fourslash/completionForStringLiteralRelativeImport3.ts index 82c3ea7d1e..915cebdf6e 100644 --- a/tests/cases/fourslash/completionForStringLiteralRelativeImport3.ts +++ b/tests/cases/fourslash/completionForStringLiteralRelativeImport3.ts @@ -34,18 +34,18 @@ const kinds = ["import_as", "import_equals", "require"]; for (const kind of kinds) { goTo.marker(kind + "0"); - verify.importModuleCompletionListContains("fourslash"); - verify.not.importModuleCompletionListItemsCountIsGreaterThan(1); + verify.completionListContains("fourslash"); + verify.not.completionListItemsCountIsGreaterThan(1); goTo.marker(kind + "1"); - verify.importModuleCompletionListContains("fourslash"); - verify.not.importModuleCompletionListItemsCountIsGreaterThan(1); + verify.completionListContains("fourslash"); + verify.not.completionListItemsCountIsGreaterThan(1); goTo.marker(kind + "2"); - verify.importModuleCompletionListContains("f1"); - verify.importModuleCompletionListContains("f2"); - verify.importModuleCompletionListContains("e1"); - verify.importModuleCompletionListContains("folder"); - verify.importModuleCompletionListContains("tests"); - verify.not.importModuleCompletionListItemsCountIsGreaterThan(5); + verify.completionListContains("f1"); + verify.completionListContains("f2"); + verify.completionListContains("e1"); + verify.completionListContains("folder"); + verify.completionListContains("tests"); + verify.not.completionListItemsCountIsGreaterThan(5); } \ No newline at end of file diff --git a/tests/cases/fourslash/completionForStringLiteralRelativeImport4.ts b/tests/cases/fourslash/completionForStringLiteralRelativeImport4.ts index ba9f95a08c..b9750de9c1 100644 --- a/tests/cases/fourslash/completionForStringLiteralRelativeImport4.ts +++ b/tests/cases/fourslash/completionForStringLiteralRelativeImport4.ts @@ -42,11 +42,11 @@ const kinds = ["import_as", "import_equals", "require"]; for (const kind of kinds) { goTo.marker(kind + "0"); - verify.importModuleCompletionListContains("module0"); - verify.importModuleCompletionListContains("module1"); - verify.importModuleCompletionListContains("module2"); - verify.importModuleCompletionListContains("more"); + verify.completionListContains("module0"); + verify.completionListContains("module1"); + verify.completionListContains("module2"); + verify.completionListContains("more"); // Should not contain itself - verify.not.importModuleCompletionListItemsCountIsGreaterThan(4); + verify.not.completionListItemsCountIsGreaterThan(4); } \ No newline at end of file diff --git a/tests/cases/fourslash/completionForTripleSlashReference1.ts b/tests/cases/fourslash/completionForTripleSlashReference1.ts index f8ea7fc4d4..2ce4de2e72 100644 --- a/tests/cases/fourslash/completionForTripleSlashReference1.ts +++ b/tests/cases/fourslash/completionForTripleSlashReference1.ts @@ -32,20 +32,20 @@ for (let i = 0; i < 5; i++) { goTo.marker("" + i); - verify.importModuleCompletionListContains("f1.ts"); - verify.importModuleCompletionListContains("f1.d.ts"); - verify.importModuleCompletionListContains("f2.tsx"); - verify.importModuleCompletionListContains("e1.ts"); - verify.importModuleCompletionListContains("parentTest"); - verify.not.importModuleCompletionListItemsCountIsGreaterThan(5); + verify.completionListContains("f1.ts"); + verify.completionListContains("f1.d.ts"); + verify.completionListContains("f2.tsx"); + verify.completionListContains("e1.ts"); + verify.completionListContains("parentTest"); + verify.not.completionListItemsCountIsGreaterThan(5); } goTo.marker("5"); -verify.importModuleCompletionListContains("g1.ts"); -verify.importModuleCompletionListContains("sub"); -verify.not.importModuleCompletionListItemsCountIsGreaterThan(2); +verify.completionListContains("g1.ts"); +verify.completionListContains("sub"); +verify.not.completionListItemsCountIsGreaterThan(2); goTo.marker("6"); -verify.importModuleCompletionListContains("g1.ts"); -verify.importModuleCompletionListContains("sub"); -verify.not.importModuleCompletionListItemsCountIsGreaterThan(2); \ No newline at end of file +verify.completionListContains("g1.ts"); +verify.completionListContains("sub"); +verify.not.completionListItemsCountIsGreaterThan(2); \ No newline at end of file diff --git a/tests/cases/fourslash/completionForTripleSlashReference2.ts b/tests/cases/fourslash/completionForTripleSlashReference2.ts index 0530e5e409..e6553ce8e7 100644 --- a/tests/cases/fourslash/completionForTripleSlashReference2.ts +++ b/tests/cases/fourslash/completionForTripleSlashReference2.ts @@ -24,10 +24,10 @@ for (let i = 0; i < 5; i++) { goTo.marker("" + i); - verify.importModuleCompletionListContains("f1.ts"); - verify.importModuleCompletionListContains("f1.js"); - verify.importModuleCompletionListContains("f1.d.ts"); - verify.importModuleCompletionListContains("f2.tsx"); - verify.importModuleCompletionListContains("f4.jsx"); - verify.not.importModuleCompletionListItemsCountIsGreaterThan(5); + verify.completionListContains("f1.ts"); + verify.completionListContains("f1.js"); + verify.completionListContains("f1.d.ts"); + verify.completionListContains("f2.tsx"); + verify.completionListContains("f4.jsx"); + verify.not.completionListItemsCountIsGreaterThan(5); } \ No newline at end of file diff --git a/tests/cases/fourslash/completionForTripleSlashReference3.ts b/tests/cases/fourslash/completionForTripleSlashReference3.ts index d27d0e658c..798e556be5 100644 --- a/tests/cases/fourslash/completionForTripleSlashReference3.ts +++ b/tests/cases/fourslash/completionForTripleSlashReference3.ts @@ -27,17 +27,17 @@ //// /*e2*/ goTo.marker("0"); -verify.importModuleCompletionListContains("fourslash"); -verify.not.importModuleCompletionListItemsCountIsGreaterThan(1); +verify.completionListContains("fourslash"); +verify.not.completionListItemsCountIsGreaterThan(1); goTo.marker("1"); -verify.importModuleCompletionListContains("fourslash"); -verify.not.importModuleCompletionListItemsCountIsGreaterThan(1); +verify.completionListContains("fourslash"); +verify.not.completionListItemsCountIsGreaterThan(1); goTo.marker("2"); -verify.importModuleCompletionListContains("f1.ts"); -verify.importModuleCompletionListContains("f2.tsx"); -verify.importModuleCompletionListContains("e1.ts"); -verify.importModuleCompletionListContains("folder"); -verify.importModuleCompletionListContains("tests"); -verify.not.importModuleCompletionListItemsCountIsGreaterThan(5); \ No newline at end of file +verify.completionListContains("f1.ts"); +verify.completionListContains("f2.tsx"); +verify.completionListContains("e1.ts"); +verify.completionListContains("folder"); +verify.completionListContains("tests"); +verify.not.completionListItemsCountIsGreaterThan(5); \ No newline at end of file diff --git a/tests/cases/fourslash/completionForTripleSlashReference4.ts b/tests/cases/fourslash/completionForTripleSlashReference4.ts index 9cedf4932b..2eb5864667 100644 --- a/tests/cases/fourslash/completionForTripleSlashReference4.ts +++ b/tests/cases/fourslash/completionForTripleSlashReference4.ts @@ -38,6 +38,6 @@ goTo.marker("0"); -verify.importModuleCompletionListContains("module0.ts"); +verify.completionListContains("module0.ts"); -verify.not.importModuleCompletionListItemsCountIsGreaterThan(1); \ No newline at end of file +verify.not.completionListItemsCountIsGreaterThan(1); \ No newline at end of file diff --git a/tests/cases/fourslash/fourslash.ts b/tests/cases/fourslash/fourslash.ts index 948477b78d..525fcc608f 100644 --- a/tests/cases/fourslash/fourslash.ts +++ b/tests/cases/fourslash/fourslash.ts @@ -121,13 +121,10 @@ declare namespace FourSlashInterface { constructor(negative?: boolean); memberListContains(symbol: string, text?: string, documenation?: string, kind?: string): void; memberListCount(expectedCount: number): void; - completionListContains(symbol: string, text?: string, documentation?: string, kind?: string): void; + completionListContains(symbol: string, text?: string, documentation?: string, kind?: string, spanIndex?: number): void; completionListItemsCountIsGreaterThan(count: number): void; completionListIsEmpty(): void; completionListAllowsNewIdentifier(): void; - importModuleCompletionListContains(symbol: string, rangeIndex?: number): void; - importModuleCompletionListItemsCountIsGreaterThan(count: number): void; - importModuleCompletionListIsEmpty(): void; memberListIsEmpty(): void; signatureHelpPresent(): void; errorExistsBetweenMarkers(startMarker: string, endMarker: string): void; From 8728b9857d2f5913a2295570861d40fb4b54c8ab Mon Sep 17 00:00:00 2001 From: Richard Knoll Date: Fri, 2 Sep 2016 16:44:25 -0700 Subject: [PATCH 37/39] Adding comment and removing unnecessary object creation --- src/server/client.ts | 12 +++++++----- src/services/services.ts | 5 +++++ 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/server/client.ts b/src/server/client.ts index d5aed77bd1..ca2d517701 100644 --- a/src/server/client.ts +++ b/src/server/client.ts @@ -216,15 +216,17 @@ namespace ts.server { return { isMemberCompletion: false, isNewIdentifierLocation: false, - entries: response.body.map(({ name, kind, kindModifiers, sortText, replacementSpan }) => { + entries: response.body.map(entry => { - let convertedSpan: TextSpan; - if (replacementSpan) { - convertedSpan = createTextSpanFromBounds(this.lineOffsetToPosition(fileName, replacementSpan.start), + if (entry.replacementSpan !== undefined) { + const { name, kind, kindModifiers, sortText, replacementSpan} = entry; + + const convertedSpan = createTextSpanFromBounds(this.lineOffsetToPosition(fileName, replacementSpan.start), this.lineOffsetToPosition(fileName, replacementSpan.end)); + return { name, kind, kindModifiers, sortText, replacementSpan: convertedSpan }; } - return { name, kind, kindModifiers, sortText, replacementSpan: convertedSpan }; + return entry as { name: string, kind: string, kindModifiers: string, sortText: string }; }) }; } diff --git a/src/services/services.ts b/src/services/services.ts index b70e860b30..b581836d9c 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1488,6 +1488,11 @@ namespace ts { kind: string; // see ScriptElementKind kindModifiers: string; // see ScriptElementKindModifier, comma separated sortText: string; + /** + * An optional span that indicates the text to be replaced by this completion item. It will be + * set if the required span differs from the one generated by the default replacement behavior and should + * be used in that case + */ replacementSpan?: TextSpan; } From 8f0c7ef6c76290578cf360f74e4336b0f4874a6c Mon Sep 17 00:00:00 2001 From: Richard Knoll Date: Tue, 6 Sep 2016 12:46:27 -0700 Subject: [PATCH 38/39] Pass the right host to getEffectiveTyperoots --- src/compiler/program.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/compiler/program.ts b/src/compiler/program.ts index fdb4952d21..96356ea34a 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -225,7 +225,7 @@ namespace ts { traceEnabled }; - const typeRoots = getEffectiveTypeRoots(options, host.getCurrentDirectory && host.getCurrentDirectory()); + const typeRoots = getEffectiveTypeRoots(options, host); if (traceEnabled) { if (containingFile === undefined) { if (typeRoots === undefined) { @@ -1086,7 +1086,7 @@ namespace ts { // Walk the primary type lookup locations const result: string[] = []; if (host.directoryExists && host.getDirectories) { - const typeRoots = getEffectiveTypeRoots(options, host.getCurrentDirectory && host.getCurrentDirectory()); + const typeRoots = getEffectiveTypeRoots(options, host); if (typeRoots) { for (const root of typeRoots) { if (host.directoryExists(root)) { From bef6a668ad25d55083f9f0e83ccf02cb52288216 Mon Sep 17 00:00:00 2001 From: Zhengbo Li Date: Tue, 6 Sep 2016 16:15:13 -0700 Subject: [PATCH 39/39] Add a rule to toggle space after opening brace and before closing brace (#10447) * Add a rule to toggle space after opening brace and before closing brace * Make the added format option optional * Fix merge issues --- src/harness/fourslash.ts | 1 + src/server/editorServices.ts | 1 + src/services/formatting/rules.ts | 6 +++++- src/services/formatting/rulesProvider.ts | 13 +++++++++++++ src/services/types.ts | 1 + tests/cases/fourslash/formattingOptionsChange.ts | 2 ++ 6 files changed, 23 insertions(+), 1 deletion(-) diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index 439230ec67..b4e40fbc44 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -360,6 +360,7 @@ namespace FourSlash { InsertSpaceAfterFunctionKeywordForAnonymousFunctions: false, InsertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis: false, InsertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets: false, + InsertSpaceAfterOpeningAndBeforeClosingNonemptyBraces: true, InsertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces: false, InsertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces: false, PlaceOpenBraceOnNewLineForFunctions: false, diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 1668f77490..e0f9e8d5cf 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -1594,6 +1594,7 @@ namespace ts.server { InsertSpaceAfterFunctionKeywordForAnonymousFunctions: false, InsertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis: false, InsertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets: false, + InsertSpaceAfterOpeningAndBeforeClosingNonemptyBraces: true, InsertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces: false, InsertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces: false, PlaceOpenBraceOnNewLineForFunctions: false, diff --git a/src/services/formatting/rules.ts b/src/services/formatting/rules.ts index 1839726b9a..3b1fde053a 100644 --- a/src/services/formatting/rules.ts +++ b/src/services/formatting/rules.ts @@ -50,6 +50,8 @@ namespace ts.formatting { // Insert a space after { and before } in single-line contexts, but remove space from empty object literals {}. public SpaceAfterOpenBrace: Rule; public SpaceBeforeCloseBrace: Rule; + public NoSpaceAfterOpenBrace: Rule; + public NoSpaceBeforeCloseBrace: Rule; public NoSpaceBetweenEmptyBraceBrackets: Rule; // Insert new line after { and before } in multi-line contexts. @@ -287,6 +289,8 @@ namespace ts.formatting { // Insert a space after { and before } in single-line contexts, but remove space from empty object literals {}. this.SpaceAfterOpenBrace = new Rule(RuleDescriptor.create3(SyntaxKind.OpenBraceToken, Shared.TokenRange.Any), RuleOperation.create2(new RuleOperationContext(Rules.IsSingleLineBlockContext), RuleAction.Space)); this.SpaceBeforeCloseBrace = new Rule(RuleDescriptor.create2(Shared.TokenRange.Any, SyntaxKind.CloseBraceToken), RuleOperation.create2(new RuleOperationContext(Rules.IsSingleLineBlockContext), RuleAction.Space)); + this.NoSpaceAfterOpenBrace = new Rule(RuleDescriptor.create3(SyntaxKind.OpenBraceToken, Shared.TokenRange.Any), RuleOperation.create2(new RuleOperationContext(Rules.IsSingleLineBlockContext), RuleAction.Delete)); + this.NoSpaceBeforeCloseBrace = new Rule(RuleDescriptor.create2(Shared.TokenRange.Any, SyntaxKind.CloseBraceToken), RuleOperation.create2(new RuleOperationContext(Rules.IsSingleLineBlockContext), RuleAction.Delete)); this.NoSpaceBetweenEmptyBraceBrackets = new Rule(RuleDescriptor.create1(SyntaxKind.OpenBraceToken, SyntaxKind.CloseBraceToken), RuleOperation.create2(new RuleOperationContext(Rules.IsNonJsxSameLineTokenContext, Rules.IsObjectContext), RuleAction.Delete)); // Insert new line after { and before } in multi-line contexts. @@ -414,7 +418,7 @@ namespace ts.formatting { this.SpaceAfterPostdecrementWhenFollowedBySubtract, this.SpaceAfterSubtractWhenFollowedByUnaryMinus, this.SpaceAfterSubtractWhenFollowedByPredecrement, this.NoSpaceAfterCloseBrace, - this.SpaceAfterOpenBrace, this.SpaceBeforeCloseBrace, this.NewLineBeforeCloseBraceInBlockContext, + this.NewLineBeforeCloseBraceInBlockContext, this.SpaceAfterCloseBrace, this.SpaceBetweenCloseBraceAndElse, this.SpaceBetweenCloseBraceAndWhile, this.NoSpaceBetweenEmptyBraceBrackets, this.NoSpaceBetweenFunctionKeywordAndStar, this.SpaceAfterStarInGeneratorDeclaration, this.SpaceAfterFunctionInFuncDecl, this.NewLineAfterOpenBraceInBlockContext, this.SpaceAfterGetSetInMember, diff --git a/src/services/formatting/rulesProvider.ts b/src/services/formatting/rulesProvider.ts index 1be0f9e912..b885accaf5 100644 --- a/src/services/formatting/rulesProvider.ts +++ b/src/services/formatting/rulesProvider.ts @@ -81,6 +81,19 @@ namespace ts.formatting { rules.push(this.globalRules.NoSpaceBetweenBrackets); } + // The default value of InsertSpaceAfterOpeningAndBeforeClosingNonemptyBraces is true + // so if the option is undefined, we should treat it as true as well + if (options.InsertSpaceAfterOpeningAndBeforeClosingNonemptyBraces !== false) { + rules.push(this.globalRules.SpaceAfterOpenBrace); + rules.push(this.globalRules.SpaceBeforeCloseBrace); + rules.push(this.globalRules.NoSpaceBetweenEmptyBraceBrackets); + } + else { + rules.push(this.globalRules.NoSpaceAfterOpenBrace); + rules.push(this.globalRules.NoSpaceBeforeCloseBrace); + rules.push(this.globalRules.NoSpaceBetweenEmptyBraceBrackets); + } + if (options.InsertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces) { rules.push(this.globalRules.SpaceAfterTemplateHeadAndMiddle); rules.push(this.globalRules.SpaceBeforeTemplateMiddleAndTail); diff --git a/src/services/types.ts b/src/services/types.ts index c4db614f39..b8c9122fb9 100644 --- a/src/services/types.ts +++ b/src/services/types.ts @@ -349,6 +349,7 @@ namespace ts { InsertSpaceAfterFunctionKeywordForAnonymousFunctions: boolean; InsertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis: boolean; InsertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets: boolean; + InsertSpaceAfterOpeningAndBeforeClosingNonemptyBraces?: boolean; InsertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces: boolean; InsertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces?: boolean; PlaceOpenBraceOnNewLineForFunctions: boolean; diff --git a/tests/cases/fourslash/formattingOptionsChange.ts b/tests/cases/fourslash/formattingOptionsChange.ts index f266a8e96c..62272958b8 100644 --- a/tests/cases/fourslash/formattingOptionsChange.ts +++ b/tests/cases/fourslash/formattingOptionsChange.ts @@ -12,6 +12,7 @@ ////} /////*PlaceOpenBraceOnNewLineForControlBlocks*/if (true) { ////} +/////*InsertSpaceAfterOpeningAndBeforeClosingNonemptyBraces*/{ var t = 1}; runTest("InsertSpaceAfterCommaDelimiter", "[1, 2, 3];[72,];", "[1,2,3];[72,];"); runTest("InsertSpaceAfterSemicolonInForStatements", "for (i = 0; i; i++);", "for (i = 0;i;i++);"); @@ -23,6 +24,7 @@ runTest("InsertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets", "[ 1 ];[];[]; runTest("InsertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces", "`${ 1 }`; `${ 1 }`", "`${1}`; `${1}`"); runTest("PlaceOpenBraceOnNewLineForFunctions", "class foo", "class foo {"); runTest("PlaceOpenBraceOnNewLineForControlBlocks", "if ( true )", "if ( true ) {"); +runTest("InsertSpaceAfterOpeningAndBeforeClosingNonemptyBraces", "{ var t = 1 };", "{var t = 1};"); function runTest(propertyName: string, expectedStringWhenTrue: string, expectedStringWhenFalse: string) {