Adding completions for import and reference directives

This commit is contained in:
Richard Knoll 2016-06-24 13:56:45 -07:00
parent 2aa1d718ab
commit c06b02ac34
21 changed files with 1009 additions and 55 deletions

View File

@ -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;
}
}
}

View File

@ -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;
}

View File

@ -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[];

View File

@ -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<string>(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 = <Utils.VirtualDirectory>entry;
const directory = <Utils.VirtualDirectory<string>>entry;
return {
files: ts.map(directory.getFiles(), f => f.name),
directories: ts.map(directory.getDirectories(), d => d.name)

View File

@ -123,7 +123,7 @@ namespace Harness.LanguageService {
}
export class LanguageServiceAdapterHost {
protected fileNameToScript: ts.Map<ScriptInfo> = {};
protected virtualFileSystem: Utils.VirtualFileSystem<ScriptInfo> = new Utils.VirtualFileSystem<ScriptInfo>(/*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() ? (<Utils.VirtualFile<ScriptInfo>>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((<Utils.VirtualDirectory<ScriptInfo>>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); }

View File

@ -1,11 +1,11 @@
/// <reference path="harness.ts" />
/// <reference path="..\compiler\commandLineParser.ts"/>
namespace Utils {
export class VirtualFileSystemEntry {
fileSystem: VirtualFileSystem;
export class VirtualFileSystemEntry<T> {
fileSystem: VirtualFileSystem<T>;
name: string;
constructor(fileSystem: VirtualFileSystem, name: string) {
constructor(fileSystem: VirtualFileSystem<T>, 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<T> extends VirtualFileSystemEntry<T> {
content: T;
isFile() { return true; }
}
export abstract class VirtualFileSystemContainer extends VirtualFileSystemEntry {
abstract getFileSystemEntries(): VirtualFileSystemEntry[];
export abstract class VirtualFileSystemContainer<T> extends VirtualFileSystemEntry<T> {
abstract getFileSystemEntries(): VirtualFileSystemEntry<T>[];
getFileSystemEntry(name: string): VirtualFileSystemEntry {
getFileSystemEntry(name: string): VirtualFileSystemEntry<T> {
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 <VirtualDirectory[]>ts.filter(this.getFileSystemEntries(), entry => entry.isDirectory());
getDirectories(): VirtualDirectory<T>[] {
return <VirtualDirectory<T>[]>ts.filter(this.getFileSystemEntries(), entry => entry.isDirectory());
}
getFiles(): VirtualFile[] {
return <VirtualFile[]>ts.filter(this.getFileSystemEntries(), entry => entry.isFile());
getFiles(): VirtualFile<T>[] {
return <VirtualFile<T>[]>ts.filter(this.getFileSystemEntries(), entry => entry.isFile());
}
getDirectory(name: string): VirtualDirectory {
getDirectory(name: string): VirtualDirectory<T> {
const entry = this.getFileSystemEntry(name);
return entry.isDirectory() ? <VirtualDirectory>entry : undefined;
return entry.isDirectory() ? <VirtualDirectory<T>>entry : undefined;
}
getFile(name: string): VirtualFile {
getFile(name: string): VirtualFile<T> {
const entry = this.getFileSystemEntry(name);
return entry.isFile() ? <VirtualFile>entry : undefined;
return entry.isFile() ? <VirtualFile<T>>entry : undefined;
}
}
export class VirtualDirectory extends VirtualFileSystemContainer {
private entries: VirtualFileSystemEntry[] = [];
export class VirtualDirectory<T> extends VirtualFileSystemContainer<T> {
private entries: VirtualFileSystemEntry<T>[] = [];
isDirectory() { return true; }
getFileSystemEntries() { return this.entries.slice(); }
addDirectory(name: string): VirtualDirectory {
addDirectory(name: string): VirtualDirectory<T> {
const entry = this.getFileSystemEntry(name);
if (entry === undefined) {
const directory = new VirtualDirectory(this.fileSystem, name);
const directory = new VirtualDirectory<T>(this.fileSystem, name);
this.entries.push(directory);
return directory;
}
else if (entry.isDirectory()) {
return <VirtualDirectory>entry;
return <VirtualDirectory<T>>entry;
}
else {
return undefined;
}
}
addFile(name: string, content?: string): VirtualFile {
addFile(name: string, content?: T): VirtualFile<T> {
const entry = this.getFileSystemEntry(name);
if (entry === undefined) {
const file = new VirtualFile(this.fileSystem, name);
const file = new VirtualFile<T>(this.fileSystem, name);
file.content = content;
this.entries.push(file);
return file;
}
else if (entry.isFile()) {
const file = <VirtualFile>entry;
const file = <VirtualFile<T>>entry;
file.content = content;
return file;
}
@ -92,8 +92,8 @@ namespace Utils {
}
}
export class VirtualFileSystem extends VirtualFileSystemContainer {
private root: VirtualDirectory;
export class VirtualFileSystem<T> extends VirtualFileSystemContainer<T> {
private root: VirtualDirectory<T>;
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<T>(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<T> = 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<T> = 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 = <VirtualDirectory>entry;
directory = <VirtualDirectory<T>>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 = <VirtualDirectory<T>>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<T>[] = [];
getFilesRecursive(this.root, fileEntries);
return fileEntries;
function getFilesRecursive(dir: VirtualDirectory<T>, result: VirtualFile<T>[]) {
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<string> 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 = <VirtualDirectory>entry;
return {
files: ts.map(directory.getFiles(), f => f.name),
directories: ts.map(directory.getDirectories(), d => d.name)
};
}
return { files: [], directories: [] };
}
}
}

View File

@ -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
*/

View File

@ -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*</;
const tripleSlashDirectiveFragmentRegex = /^\/\/\/\s*<reference\s+path\s*=\s*(?:'|")([^'"]+)$/;
let commandLineOptionsStringToEnum: CommandLineOptionOfCustomType[];
@ -2806,6 +2814,15 @@ namespace ts {
}
}
function isInReferenceComment(sourceFile: SourceFile, position: number): boolean {
return isInCommentHelper(sourceFile, position, isReferenceComment);
function isReferenceComment(c: CommentRange): boolean {
const commentText = sourceFile.text.substring(c.pos, c.end);
return tripleSlashDirectivePrefixRegex.test(commentText);
}
}
const enum SemanticMeaning {
None = 0x0,
Value = 0x1,
@ -4094,6 +4111,10 @@ namespace ts {
return getStringLiteralCompletionEntries(sourceFile, position);
}
if (isInReferenceComment(sourceFile, position)) {
return getTripleSlashReferenceCompletion(sourceFile, position);
}
const completionData = getCompletionData(fileName, position);
if (!completionData) {
return undefined;
@ -4243,6 +4264,10 @@ namespace ts {
// Get all names of properties on the expression
return getStringLiteralCompletionEntriesFromElementAccess(node.parent);
}
else if (node.parent.kind === SyntaxKind.ImportDeclaration) {
// Get all known module names
return getStringLiteralCompletionEntriesFromModuleNames(<StringLiteral>node);
}
else {
// Otherwise, get the completions from the contextual type if one exists
return getStringLiteralCompletionEntriesFromContextualType(<StringLiteral>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*</;
const start = findInComments ? container.getFullStart() : container.getStart();
const possiblePositions = getPossibleSymbolReferencePositions(sourceFile, searchText, start, container.getEnd());

View File

@ -67,6 +67,11 @@ namespace ts {
getProjectVersion?(): string;
useCaseSensitiveFileNames?(): boolean;
readDirectory(path: string, extensions?: string, exclude?: string, include?: string): string;
readFile(path: string, encoding?: string): string;
resolvePath(path: string): string;
fileExists(path: string): boolean;
getModuleResolutionsForFile?(fileName: string): string;
getTypeReferenceDirectiveResolutionsForFile?(fileName: string): string;
directoryExists(directoryName: string): boolean;
@ -409,6 +414,27 @@ namespace ts {
public getDefaultLibFileName(options: CompilerOptions): string {
return this.shimHost.getDefaultLibFileName(JSON.stringify(options));
}
public readDirectory(path: string, extensions?: string[], exclude?: string[], include?: string[]): string[] {
return JSON.parse(this.shimHost.readDirectory(
path,
extensions ? JSON.stringify(extensions) : undefined,
exclude ? JSON.stringify(exclude) : undefined,
include ? JSON.stringify(include) : undefined
));
}
public readFile(path: string, encoding?: string): string {
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);
}
}
/** A cancellation that throttles calls to the host */

View File

@ -0,0 +1,53 @@
/// <reference path='fourslash.ts' />
// @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);

View File

@ -0,0 +1,32 @@
/// <reference path='fourslash.ts' />
// @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);

View File

@ -0,0 +1,30 @@
/// <reference path='fourslash.ts' />
// @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);

View File

@ -0,0 +1,45 @@
/// <reference path='fourslash.ts' />
// @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);

View File

@ -0,0 +1,28 @@
/// <reference path='fourslash.ts' />
// @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);

View File

@ -0,0 +1,57 @@
/// <reference path='fourslash.ts' />
// @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);

View File

@ -0,0 +1,77 @@
/// <reference path='fourslash.ts' />
// @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);

View File

@ -0,0 +1,43 @@
/// <reference path='fourslash.ts' />
// @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);

View File

@ -0,0 +1,41 @@
/// <reference path='fourslash.ts' />
// @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);

View File

@ -0,0 +1,79 @@
/// <reference path='fourslash.ts' />
// @Filename: test0.ts
//// /// <reference path="./*0*/
// @Filename: test1.ts
//// /// <reference path=".//*1*/
// @Filename: test2.ts
//// /// <reference path="./f/*2*/" />
// @Filename: test3.ts
//// /// <reference path="./folder//*3*/
// @Filename: test4.ts
//// /// <reference path="./folder/h/*4*/
// @Filename: parentTest/sub/test5.ts
//// /// <reference path="../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.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);
goTo.marker("2");
verify.completionListContains("f1.ts");
verify.completionListContains("f1.d.ts");
verify.completionListContains("f2.tsx");
verify.completionListContains("folder/");
verify.not.completionListItemsCountIsGreaterThan(4);
goTo.marker("3");
verify.completionListContains("f1.ts");
verify.completionListContains("h1.ts");
verify.not.completionListItemsCountIsGreaterThan(2);
goTo.marker("4");
verify.completionListContains("h1.ts");
verify.not.completionListItemsCountIsGreaterThan(1);
goTo.marker("5");
verify.completionListContains("g1.ts");
verify.not.completionListItemsCountIsGreaterThan(1);

View File

@ -0,0 +1,47 @@
/// <reference path='fourslash.ts' />
// @allowJs: true
// @Filename: test0.ts
//// /// <reference path=".//*0*/
// @Filename: test1.ts
//// /// <reference path="./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.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);
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);

View File

@ -0,0 +1,41 @@
/// <reference path='fourslash.ts' />
// @Filename: tests/test0.ts
//// /// <reference path="c:/tests/cases/f/*0*/
// @Filename: tests/test1.ts
//// /// <reference path="c:/tests/cases/fourslash/*1*/
// @Filename: tests/test2.ts
//// /// <reference path="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.ts");
verify.completionListContains("f2.tsx");
verify.completionListContains("e1.ts");
verify.completionListContains("folder/");
verify.completionListContains("tests/");
verify.not.completionListItemsCountIsGreaterThan(5);