-
Notifications
You must be signed in to change notification settings - Fork 12.8k
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
Changes from 2 commits
c06b02a
ccf27d1
dbdd989
801b493
f644da7
5c24b35
ffc165e
5c87c5a
84a10e4
ed2da32
0b16180
dbf19f1
fdbc23e
4ca7e95
9e797b4
4ec8b2b
98a162b
35cd480
a5d73bf
8b5a3d9
293ca60
ca28823
0f22079
ecdbdb3
e11d5e9
8a976f1
cc35bd5
2f4a855
310bce4
cf7feb3
473be82
00facc2
0ebd196
c71c5a8
34847f0
276b56d
fb6ff42
b9b79af
7261866
c742d16
8728b98
a26d310
8f0c7ef
548e143
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: spacing around |
||
while (true) { | ||
const fileName = combinePaths(searchPath, "tsconfig.json"); | ||
const fileName = combinePaths(searchPath, configName); | ||
if (fileExists(fileName)) { | ||
return fileName; | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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) => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. | ||
|
@@ -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); } | ||
|
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> { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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; | ||
} | ||
|
@@ -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,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; | ||
} | ||
|
@@ -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) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you add a comment here, not sure what this method does There was a problem hiding this comment. Choose a reason for hiding this commentThe 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)); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. See before about the foreach function, use ts.foreach There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) { | ||
|
@@ -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: [] }; | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
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()
)