mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-02-06 20:14:01 -06:00
Migrate tsserverProjectSystem to vfs
This commit is contained in:
parent
fa428356d5
commit
c5e502009f
@ -124,12 +124,16 @@ export class Timers {
|
||||
* - Use `Timers.MAX_DEPTH` to continue processing nested `setImmediate` calls up to the maximum depth.
|
||||
*/
|
||||
public advance(ms: number, maxDepth = 0): number {
|
||||
if (ms <= 0) throw new TypeError("Argument 'ms' out of range.");
|
||||
if (ms < 0) throw new TypeError("Argument 'ms' out of range.");
|
||||
if (maxDepth < 0) throw new TypeError("Argument 'maxDepth' out of range.");
|
||||
let count = 0;
|
||||
const endTime = this._time + (ms | 0);
|
||||
while (true) {
|
||||
count += this.executeImmediates(maxDepth);
|
||||
if (maxDepth >= 0) {
|
||||
count += this.executeImmediates(maxDepth);
|
||||
maxDepth--;
|
||||
}
|
||||
|
||||
const dueTimer = this.dequeueIfBefore(endTime);
|
||||
if (dueTimer) {
|
||||
this._time = dueTimer.due;
|
||||
@ -150,7 +154,7 @@ export class Timers {
|
||||
* - Use `Timers.MAX_DEPTH` to continue processing nested `setImmediate` calls up to the maximum depth.
|
||||
*/
|
||||
public advanceToEnd(maxDepth = 0) {
|
||||
return this.remainingTime > 0 ? this.advance(this.remainingTime, maxDepth) : 0;
|
||||
return this.advance(this.remainingTime, maxDepth);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -30,11 +30,18 @@ namespace fakes {
|
||||
* Indicates whether to include a bare _lib.d.ts_.
|
||||
*/
|
||||
lib?: boolean;
|
||||
/**
|
||||
* Indicates whether to use DOS paths by default.
|
||||
*/
|
||||
dos?: boolean;
|
||||
}
|
||||
|
||||
export class FakeServerHost implements ts.server.ServerHost, ts.FormatDiagnosticsHost {
|
||||
export class FakeServerHost implements ts.server.ServerHost, ts.FormatDiagnosticsHost, ts.ModuleResolutionHost {
|
||||
public static readonly dosExecutingFilePath = "c:/.ts/tsc.js";
|
||||
public static readonly defaultExecutingFilePath = "/.ts/tsc.js";
|
||||
public static readonly dosDefaultCurrentDirectory = "c:/";
|
||||
public static readonly defaultCurrentDirectory = "/";
|
||||
public static readonly dosSafeListPath = "c:/safelist.json";
|
||||
public static readonly safeListPath = "/safelist.json";
|
||||
public static readonly safeListContent =
|
||||
`{\n` +
|
||||
@ -46,6 +53,7 @@ namespace fakes {
|
||||
` "chroma": "chroma-js"\n` +
|
||||
`}`;
|
||||
|
||||
public static readonly dosLibPath = "c:/.ts/lib.d.ts";
|
||||
public static readonly libPath = "/.ts/lib.d.ts";
|
||||
public static readonly libContent =
|
||||
`/// <reference no-default-lib="true"/>\n` +
|
||||
@ -64,22 +72,32 @@ namespace fakes {
|
||||
|
||||
private static readonly processExitSentinel = new Error("System exit");
|
||||
private readonly _output: string[] = [];
|
||||
private readonly _trace: string[] = [];
|
||||
private readonly _executingFilePath: string;
|
||||
private readonly _getCanonicalFileName: (file: string) => string;
|
||||
|
||||
constructor(options: FakeServerHostOptions = {}) {
|
||||
const {
|
||||
dos = false,
|
||||
vfs: _vfs = {},
|
||||
executingFilePath = FakeServerHost.defaultExecutingFilePath,
|
||||
executingFilePath = dos
|
||||
? FakeServerHost.dosExecutingFilePath
|
||||
: FakeServerHost.defaultExecutingFilePath,
|
||||
newLine = "\n",
|
||||
safeList = false,
|
||||
lib = false
|
||||
} = options;
|
||||
|
||||
const { currentDirectory = FakeServerHost.defaultCurrentDirectory, useCaseSensitiveFileNames = false } = _vfs;
|
||||
const {
|
||||
currentDirectory = dos
|
||||
? FakeServerHost.dosDefaultCurrentDirectory
|
||||
: FakeServerHost.defaultCurrentDirectory,
|
||||
useCaseSensitiveFileNames = false
|
||||
} = _vfs;
|
||||
|
||||
this.vfs = _vfs instanceof vfs.VirtualFileSystem ? _vfs :
|
||||
new vfs.VirtualFileSystem(currentDirectory, useCaseSensitiveFileNames);
|
||||
this.vfs = _vfs instanceof vfs.VirtualFileSystem
|
||||
? _vfs
|
||||
: new vfs.VirtualFileSystem(currentDirectory, useCaseSensitiveFileNames);
|
||||
|
||||
this.useCaseSensitiveFileNames = this.vfs.useCaseSensitiveFileNames;
|
||||
this.newLine = newLine;
|
||||
@ -87,11 +105,15 @@ namespace fakes {
|
||||
this._getCanonicalFileName = ts.createGetCanonicalFileName(this.useCaseSensitiveFileNames);
|
||||
|
||||
if (safeList) {
|
||||
this.vfs.addFile(FakeServerHost.safeListPath, FakeServerHost.safeListContent);
|
||||
this.vfs.addFile(
|
||||
dos ? FakeServerHost.dosSafeListPath : FakeServerHost.safeListPath,
|
||||
FakeServerHost.safeListContent);
|
||||
}
|
||||
|
||||
if (lib) {
|
||||
this.vfs.addFile(FakeServerHost.libPath, FakeServerHost.libContent);
|
||||
this.vfs.addFile(
|
||||
dos ? FakeServerHost.dosLibPath : FakeServerHost.libPath,
|
||||
FakeServerHost.libContent);
|
||||
}
|
||||
}
|
||||
|
||||
@ -143,6 +165,12 @@ namespace fakes {
|
||||
}
|
||||
// #endregion DirectoryStructureHost members
|
||||
|
||||
// #region ModuleResolutionHost members
|
||||
public trace(message: string) {
|
||||
this._trace.push(message);
|
||||
}
|
||||
// #endregion
|
||||
|
||||
// #region System members
|
||||
public readonly args: string[] = [];
|
||||
|
||||
@ -226,6 +254,14 @@ namespace fakes {
|
||||
this._output.length = 0;
|
||||
}
|
||||
|
||||
public getTrace(): ReadonlyArray<string> {
|
||||
return this._trace;
|
||||
}
|
||||
|
||||
public clearTrace() {
|
||||
this._trace.length = 0;
|
||||
}
|
||||
|
||||
public checkTimeoutQueueLength(expected: number) {
|
||||
const callbacksCount = this.timers.getPending({ kind: "timeout", ms: this.timers.remainingTime }).length;
|
||||
assert.equal(callbacksCount, expected, `expected ${expected} timeout callbacks queued but found ${callbacksCount}.`);
|
||||
@ -236,6 +272,17 @@ namespace fakes {
|
||||
this.runQueuedTimeoutCallbacks();
|
||||
}
|
||||
|
||||
public runQueuedImmediateCallbacks() {
|
||||
try {
|
||||
this.timers.executeImmediates();
|
||||
}
|
||||
catch (e) {
|
||||
if (e !== FakeServerHost.processExitSentinel) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public runQueuedTimeoutCallbacks() {
|
||||
try {
|
||||
this.timers.advanceToEnd();
|
||||
|
||||
@ -6,6 +6,8 @@
|
||||
|
||||
namespace ts.projectSystem {
|
||||
const nullCancellationToken = server.nullCancellationToken;
|
||||
import libFile = ts.TestFSWithWatch.libFile;
|
||||
import FileOrFolder = ts.TestFSWithWatch.FileOrFolder;
|
||||
|
||||
function createTestTypingsInstaller(host: server.ServerHost) {
|
||||
return new TestTypingsInstaller("/a/data/", /*throttleLimit*/5, host);
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
/// <reference path="..\harness.ts" />
|
||||
/// <reference path="tsserverProjectSystem.ts" />
|
||||
/// <reference path="../fakes.ts" />
|
||||
|
||||
namespace ts {
|
||||
export interface Range {
|
||||
@ -98,6 +99,19 @@ namespace ts {
|
||||
getCurrentDirectory: notImplemented,
|
||||
};
|
||||
|
||||
function createServerHost(files: ts.TestFSWithWatch.FileOrFolder[], options?: Partial<fakes.FakeServerHostOptions>) {
|
||||
const host = new fakes.FakeServerHost(options);
|
||||
for (const file of files) {
|
||||
if (isString(file.content)) {
|
||||
host.vfs.writeFile(file.path, file.content);
|
||||
}
|
||||
else {
|
||||
host.vfs.addDirectory(file.path);
|
||||
}
|
||||
}
|
||||
return host;
|
||||
}
|
||||
|
||||
export function testExtractSymbol(caption: string, text: string, baselineFolder: string, description: DiagnosticMessage, includeLib?: boolean) {
|
||||
const t = extractTest(text);
|
||||
const selectionRange = t.ranges.get("selection");
|
||||
@ -154,7 +168,7 @@ namespace ts {
|
||||
}
|
||||
|
||||
function makeProgram(f: {path: string, content: string }, includeLib?: boolean) {
|
||||
const host = projectSystem.createServerHost(includeLib ? [f, projectSystem.libFile] : [f]); // libFile is expensive to parse repeatedly - only test when required
|
||||
const host = createServerHost(includeLib ? [f, ts.TestFSWithWatch.libFile] : [f]); // libFile is expensive to parse repeatedly - only test when required
|
||||
const projectService = projectSystem.createProjectService(host);
|
||||
projectService.openClientFile(f.path);
|
||||
const program = projectService.inferredProjects[0].getLanguageService().getProgram();
|
||||
@ -178,7 +192,7 @@ namespace ts {
|
||||
path: "/a.ts",
|
||||
content: t.source
|
||||
};
|
||||
const host = projectSystem.createServerHost([f, projectSystem.libFile]);
|
||||
const host = ts.TestFSWithWatch.createServerHost([f, ts.TestFSWithWatch.libFile]);
|
||||
const projectService = projectSystem.createProjectService(host);
|
||||
projectService.openClientFile(f.path);
|
||||
const program = projectService.inferredProjects[0].getLanguageService().getProgram();
|
||||
|
||||
@ -2,6 +2,8 @@
|
||||
/// <reference path="./tsserverProjectSystem.ts" />
|
||||
|
||||
namespace ts.projectSystem {
|
||||
import FileOrFolder = ts.TestFSWithWatch.FileOrFolder;
|
||||
|
||||
describe("project telemetry", () => {
|
||||
it("does nothing for inferred project", () => {
|
||||
const file = makeFile("/a.js");
|
||||
@ -235,7 +237,7 @@ namespace ts.projectSystem {
|
||||
});
|
||||
});
|
||||
|
||||
function makeFile(path: string, content: {} = ""): projectSystem.FileOrFolder {
|
||||
function makeFile(path: string, content: {} = ""): FileOrFolder {
|
||||
return { path, content: isString(content) ? "" : JSON.stringify(content) };
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@ -13,6 +13,7 @@ namespace ts.projectSystem {
|
||||
import TI = server.typingsInstaller;
|
||||
import validatePackageName = JsTyping.validatePackageName;
|
||||
import PackageNameValidationResult = JsTyping.PackageNameValidationResult;
|
||||
import FileOrFolder = ts.TestFSWithWatch.FileOrFolder;
|
||||
|
||||
interface InstallerParams {
|
||||
globalTypingsCacheLocation?: string;
|
||||
@ -628,7 +629,7 @@ namespace ts.projectSystem {
|
||||
checkNumberOfProjects(projectService, { configuredProjects: 1 });
|
||||
const p = configuredProjectAt(projectService, 0);
|
||||
checkProjectActualFiles(p, [app.path, jsconfig.path]);
|
||||
checkWatchedFiles(host, [jsconfig.path, "/bower_components", "/node_modules", "/.ts/lib.d.ts"]);
|
||||
ts.TestFSWithWatch.checkWatchedFiles(host, [jsconfig.path, "/bower_components", "/node_modules", "/.ts/lib.d.ts"]);
|
||||
|
||||
installer.installAll(/*expectedCount*/ 1);
|
||||
|
||||
|
||||
@ -55,4 +55,52 @@ namespace utils {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes leading indentation from a template literal string.
|
||||
*/
|
||||
export function dedent(array: TemplateStringsArray, ...args: any[]) {
|
||||
let text = array[0];
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
text += args[i];
|
||||
text += array[i + 1];
|
||||
}
|
||||
|
||||
const lineTerminatorRegExp = /\r\n?|\n/g;
|
||||
const lines: string[] = [];
|
||||
const lineTerminators: string[] = [];
|
||||
let match: RegExpExecArray | null;
|
||||
let lineStart = 0;
|
||||
while (match = lineTerminatorRegExp.exec(text)) {
|
||||
lines.push(text.slice(lineStart, match.index));
|
||||
lineTerminators.push(match[0]);
|
||||
lineStart = match.index + match[0].length;
|
||||
}
|
||||
|
||||
const indentation = guessIndentation(lines);
|
||||
|
||||
let result = "";
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const lineText = lines[i];
|
||||
const line = indentation ? lineText.slice(indentation) : lineText;
|
||||
result += line;
|
||||
result += lineTerminators[i];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function guessIndentation(lines: string[]) {
|
||||
let indentation: number;
|
||||
for (const line of lines) {
|
||||
for (let i = 0; i < line.length && (indentation === undefined || i < indentation); i++) {
|
||||
if (!ts.isWhiteSpaceLike(line.charCodeAt(i))) {
|
||||
if (indentation === undefined || i < indentation) {
|
||||
indentation = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return indentation;
|
||||
}
|
||||
}
|
||||
@ -426,7 +426,8 @@ namespace vfs {
|
||||
public rename(oldpath: string, newpath: string) {
|
||||
oldpath = vpath.resolve(this.currentDirectory, oldpath);
|
||||
newpath = vpath.resolve(this.currentDirectory, newpath);
|
||||
return this.root.replaceEntry(newpath, this.getEntry(oldpath));
|
||||
const oldEntry = this.getEntry(oldpath);
|
||||
return oldEntry !== undefined && this.root.replaceEntry(newpath, oldEntry);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -274,6 +274,8 @@ interface Array<T> {}`
|
||||
|
||||
// temporary vfs shim
|
||||
public vfs = {
|
||||
watchFiles: true,
|
||||
watchDirectories: true,
|
||||
addFile: (path: string, content: string, options?: { overwrite?: boolean }) => {
|
||||
let file = this.files.find(createFileMatcher(this, path));
|
||||
if (file) {
|
||||
@ -308,14 +310,31 @@ interface Array<T> {}`
|
||||
this.reloadFS(this.files);
|
||||
}
|
||||
},
|
||||
renameFile: (oldpath: string, newpath: string) => {
|
||||
const oldItem = this.vfs.getFile(oldpath);
|
||||
if (oldItem) {
|
||||
const newIndex = this.files.findIndex(createFileMatcher(this, newpath));
|
||||
if (newIndex >= 0) ts.orderedRemoveItemAt(this.files, newIndex);
|
||||
oldItem.path = newpath;
|
||||
this.reloadFS(this.files);
|
||||
rename: (oldpath: string, newpath: string) => {
|
||||
if (this.vfs.getFile(oldpath)) {
|
||||
const oldIndex = this.files.findIndex(createFileMatcher(this, oldpath));
|
||||
if (oldIndex >= 0) {
|
||||
const oldItem = this.files[oldIndex];
|
||||
ts.orderedRemoveItemAt(this.files, oldIndex);
|
||||
const newIndex = this.files.findIndex(createFileMatcher(this, newpath));
|
||||
if (newIndex >= 0) {
|
||||
ts.orderedRemoveItemAt(this.files, newIndex);
|
||||
}
|
||||
this.files.push({ path: newpath, content: oldItem.content });
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (let i = 0; i < this.files.length; i++) {
|
||||
const file = this.files[i];
|
||||
if (vpath.beneath(oldpath, file.path, !this.useCaseSensitiveFileNames)) {
|
||||
this.files[i] = {
|
||||
path: vpath.combine(newpath, vpath.relative(oldpath, file.path, !this.useCaseSensitiveFileNames)),
|
||||
content: file.content
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
this.reloadFS(this.files);
|
||||
},
|
||||
addDirectory: (path: string) => {
|
||||
let file = this.files.find(createFolderMatcher(this, path));
|
||||
@ -326,6 +345,13 @@ interface Array<T> {}`
|
||||
}
|
||||
return file;
|
||||
},
|
||||
removeDirectory: (path: string) => {
|
||||
const index = this.files.findIndex(createFolderMatcher(this, path));
|
||||
if (index >= 0) {
|
||||
ts.orderedRemoveItemAt(this.files, index);
|
||||
this.reloadFS(this.files);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
constructor(public withSafeList: boolean, public useCaseSensitiveFileNames: boolean, executingFilePath: string, currentDirectory: string, fileOrFolderList: ReadonlyArray<FileOrFolder>, public readonly newLine = "\n", public readonly useWindowsStylePath?: boolean) {
|
||||
@ -512,12 +538,14 @@ interface Array<T> {}`
|
||||
}
|
||||
else {
|
||||
Debug.assert(fileOrDirectory.entries.length === 0 || isRenaming);
|
||||
const relativePath = this.getRelativePathToDirectory(fileOrDirectory.fullPath, fileOrDirectory.fullPath);
|
||||
// Invoke directory and recursive directory watcher for the folder
|
||||
// Here we arent invoking recursive directory watchers for the base folders
|
||||
// since that is something we would want to do for both file as well as folder we are deleting
|
||||
invokeWatcherCallbacks(this.watchedDirectories.get(fileOrDirectory.path), cb => this.directoryCallback(cb, relativePath));
|
||||
invokeWatcherCallbacks(this.watchedDirectoriesRecursive.get(fileOrDirectory.path), cb => this.directoryCallback(cb, relativePath));
|
||||
if (this.vfs.watchDirectories) {
|
||||
const relativePath = this.getRelativePathToDirectory(fileOrDirectory.fullPath, fileOrDirectory.fullPath);
|
||||
// Invoke directory and recursive directory watcher for the folder
|
||||
// Here we arent invoking recursive directory watchers for the base folders
|
||||
// since that is something we would want to do for both file as well as folder we are deleting
|
||||
invokeWatcherCallbacks(this.watchedDirectories.get(fileOrDirectory.path), cb => this.directoryCallback(cb, relativePath));
|
||||
invokeWatcherCallbacks(this.watchedDirectoriesRecursive.get(fileOrDirectory.path), cb => this.directoryCallback(cb, relativePath));
|
||||
}
|
||||
}
|
||||
|
||||
if (basePath !== fileOrDirectory.path) {
|
||||
@ -531,6 +559,7 @@ interface Array<T> {}`
|
||||
}
|
||||
|
||||
private invokeFileWatcher(fileFullPath: string, eventKind: FileWatcherEventKind) {
|
||||
if (!this.vfs.watchFiles) return;
|
||||
const callbacks = this.watchedFiles.get(this.toPath(fileFullPath));
|
||||
invokeWatcherCallbacks(callbacks, ({ cb, fileName }) => cb(fileName, eventKind));
|
||||
}
|
||||
@ -543,6 +572,7 @@ interface Array<T> {}`
|
||||
* This will call the directory watcher for the folderFullPath and recursive directory watchers for this and base folders
|
||||
*/
|
||||
private invokeDirectoryWatcher(folderFullPath: string, fileName: string) {
|
||||
if (!this.vfs.watchDirectories) return;
|
||||
const relativePath = this.getRelativePathToDirectory(folderFullPath, fileName);
|
||||
invokeWatcherCallbacks(this.watchedDirectories.get(this.toPath(folderFullPath)), cb => this.directoryCallback(cb, relativePath));
|
||||
this.invokeRecursiveDirectoryWatcher(folderFullPath, fileName);
|
||||
@ -556,6 +586,7 @@ interface Array<T> {}`
|
||||
* This will call the recursive directory watcher for this directory as well as all the base directories
|
||||
*/
|
||||
private invokeRecursiveDirectoryWatcher(fullPath: string, fileName: string) {
|
||||
if (!this.vfs.watchDirectories) return;
|
||||
const relativePath = this.getRelativePathToDirectory(fullPath, fileName);
|
||||
invokeWatcherCallbacks(this.watchedDirectoriesRecursive.get(this.toPath(fullPath)), cb => this.directoryCallback(cb, relativePath));
|
||||
const basePath = getDirectoryPath(fullPath);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user