Skip to content

Fix 188: Autocomplete for imports and triple slash reference paths #9353

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 44 commits into from
Sep 6, 2016
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
c06b02a
Adding completions for import and reference directives
riknoll Jun 24, 2016
ccf27d1
Minor fix
riknoll Jun 24, 2016
dbdd989
Import completions for require calls
riknoll Jun 27, 2016
801b493
PR feedback
riknoll Jun 30, 2016
f644da7
Merge branch 'master' into import_completions_pr
riknoll Jul 5, 2016
5c24b35
Refactoring node_modules enumeration code
riknoll Jul 6, 2016
ffc165e
Fixing behavior of resolvePath
riknoll Jul 6, 2016
5c87c5a
Removing forEach reference
riknoll Jul 6, 2016
84a10e4
Some PR feedback
riknoll Jul 25, 2016
ed2da32
Handling more compiler options and minor refactor
riknoll Jul 26, 2016
0b16180
Import completions with rootdirs compiler option
riknoll Jul 27, 2016
dbf19f1
Adding import completions for typings
riknoll Jul 28, 2016
fdbc23e
Add completions for types triple slash directives
riknoll Jul 28, 2016
4ca7e95
Merge remote-tracking branch 'origin/master' into import_completions_…
riknoll Jul 28, 2016
9e797b4
Use getDirectories and condition node modules resolution on moduleRes…
riknoll Jul 28, 2016
4ec8b2b
Refactoring import completions into their own api
riknoll Aug 1, 2016
98a162b
Replacement spans for import completions
riknoll Aug 1, 2016
35cd480
Fixing import completion spans to only include the end of the directo…
riknoll Aug 2, 2016
a5d73bf
No more filtering results
riknoll Aug 2, 2016
8b5a3d9
Refactoring API to remove duplicate spans
riknoll Aug 3, 2016
293ca60
Renamed span to textSpan to better follow other language service APIs
riknoll Aug 3, 2016
ca28823
Fixing shim and normalizing paths
riknoll Aug 4, 2016
0f22079
Remove trailing slashes, remove mostly useless IO, fix script element…
riknoll Aug 5, 2016
ecdbdb3
Fixing the filtering of nested module completions
riknoll Aug 6, 2016
e11d5e9
Cleaning up test cases and adding a few more
riknoll Aug 6, 2016
8a976f1
Moving some utility functions around
riknoll Aug 8, 2016
cc35bd5
Merge remote-tracking branch 'origin/master' into import_completions_pr
riknoll Aug 15, 2016
2f4a855
Use rooted paths in the fourslash virtual file system
riknoll Aug 16, 2016
310bce4
Removing resolvePath from language service host
riknoll Aug 16, 2016
cf7feb3
Responding to PR feedback
riknoll Aug 19, 2016
473be82
Merge remote-tracking branch 'origin/master' into import_completions_pr
riknoll Aug 19, 2016
00facc2
Removing hasProperty check
riknoll Aug 20, 2016
0ebd196
Fixing regex for triple slash references
riknoll Aug 20, 2016
c71c5a8
Using for..of instead of forEach
riknoll Aug 23, 2016
34847f0
Making language service host changes optional
riknoll Aug 25, 2016
276b56d
More PR feedback
riknoll Aug 26, 2016
fb6ff42
Reuse effective type roots code in language service
riknoll Aug 27, 2016
b9b79af
Recombining import completions and regular completion APIs
riknoll Sep 1, 2016
7261866
Cleaning up the completion code and tests
riknoll Sep 1, 2016
c742d16
Merge remote-tracking branch 'origin/master' into import_completions_pr
riknoll Sep 1, 2016
8728b98
Adding comment and removing unnecessary object creation
riknoll Sep 2, 2016
a26d310
Merge remote-tracking branch 'origin/master' into import_completions_pr
riknoll Sep 6, 2016
8f0c7ef
Pass the right host to getEffectiveTyperoots
riknoll Sep 6, 2016
548e143
Merge remote-tracking branch 'origin/master' into import_completions_pr
riknoll Sep 6, 2016
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

/* @internal */
namespace ts {
const ambientModuleSymbolRegex = /^".+"$/;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was copying the behavior where we access the modules elsewhere in the checker (see resolveExternalModuleNameWorker())


let nextSymbolId = 1;
let nextNodeId = 1;
let nextMergeId = 1;
Expand Down Expand Up @@ -101,6 +103,7 @@ namespace ts {
getAliasedSymbol: resolveAlias,
getEmitResolver,
getExportsOfModule: getExportsOfModuleAsArray,
getAmbientModules,

getJsxElementAttributesType,
getJsxIntrinsicTagNames,
Expand Down Expand Up @@ -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;
}
}
}
4 changes: 2 additions & 2 deletions src/compiler/program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Copy link
Contributor

@rbuckton rbuckton Aug 18, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: spacing around = token.

while (true) {
const fileName = combinePaths(searchPath, "tsconfig.json");
const fileName = combinePaths(searchPath, configName);
if (fileExists(fileName)) {
return fileName;
}
Expand Down
1 change: 1 addition & 0 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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[];
Expand Down
4 changes: 2 additions & 2 deletions src/harness/harness.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
56 changes: 50 additions & 6 deletions src/harness/harnessLanguageService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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()) {
Expand All @@ -135,7 +135,8 @@ namespace Harness.LanguageService {

public getFilenames(): string[] {
const fileNames: string[] = [];
ts.forEachValue(this.fileNameToScript, (scriptInfo) => {
this.virtualFileSystem.getAllFileEntries().forEach((virtualEntry) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use ts.forEach.. since this function won't work downlevel

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.
Expand All @@ -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) {
Expand All @@ -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);
Expand All @@ -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(); }
Expand All @@ -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 { }
Expand Down Expand Up @@ -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); }
Expand Down
101 changes: 57 additions & 44 deletions src/harness/virtualFileSystem.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
/// <reference path="harness.ts" />
/// <reference path="..\compiler\commandLineParser.ts"/>
namespace Utils {
export class VirtualFileSystemEntry {
fileSystem: VirtualFileSystem;
export class VirtualFileSystemEntry<T> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Arbitrary types for a "file system" seems a bit odd to me.

fileSystem: VirtualFileSystem<T>;
name: string;

constructor(fileSystem: VirtualFileSystem, name: string) {
constructor(fileSystem: VirtualFileSystem<T>, name: string) {
this.fileSystem = fileSystem;
this.name = name;
}
Expand All @@ -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;
Expand All @@ -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;
}
Expand All @@ -92,16 +92,16 @@ 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;

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;
}
Expand All @@ -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) {
Expand All @@ -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);
Expand All @@ -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;
Expand All @@ -157,9 +157,34 @@ namespace Utils {

return directory;
}

getAccessibleFileSystemEntries(path: string) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a comment here, not sure what this method does

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

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));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See before about the foreach function, use ts.foreach

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! I removed it everywhere in the PR

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) {
Expand All @@ -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: [] };
}
}
}
Loading