diff --git a/scripts/typemock/src/timers.ts b/scripts/typemock/src/timers.ts
index 7811290b6d3..2dd22781387 100644
--- a/scripts/typemock/src/timers.ts
+++ b/scripts/typemock/src/timers.ts
@@ -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);
}
/**
diff --git a/src/harness/fakes.ts b/src/harness/fakes.ts
index 6fae3658104..125f0ec8804 100644
--- a/src/harness/fakes.ts
+++ b/src/harness/fakes.ts
@@ -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 =
`/// \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 {
+ 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();
diff --git a/src/harness/unittests/compileOnSave.ts b/src/harness/unittests/compileOnSave.ts
index d462d55b4be..84c488770db 100644
--- a/src/harness/unittests/compileOnSave.ts
+++ b/src/harness/unittests/compileOnSave.ts
@@ -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);
diff --git a/src/harness/unittests/extractTestHelpers.ts b/src/harness/unittests/extractTestHelpers.ts
index a04f443c3c9..93ea2642675 100644
--- a/src/harness/unittests/extractTestHelpers.ts
+++ b/src/harness/unittests/extractTestHelpers.ts
@@ -1,5 +1,6 @@
///
///
+///
namespace ts {
export interface Range {
@@ -98,6 +99,19 @@ namespace ts {
getCurrentDirectory: notImplemented,
};
+ function createServerHost(files: ts.TestFSWithWatch.FileOrFolder[], options?: Partial) {
+ 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();
diff --git a/src/harness/unittests/telemetry.ts b/src/harness/unittests/telemetry.ts
index 9bb2db73801..b33274da9ad 100644
--- a/src/harness/unittests/telemetry.ts
+++ b/src/harness/unittests/telemetry.ts
@@ -2,6 +2,8 @@
///
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) };
}
}
diff --git a/src/harness/unittests/tsserverProjectSystem.ts b/src/harness/unittests/tsserverProjectSystem.ts
index 35358e14ad4..af6c99d7d89 100644
--- a/src/harness/unittests/tsserverProjectSystem.ts
+++ b/src/harness/unittests/tsserverProjectSystem.ts
@@ -1,18 +1,21 @@
///
///
///
+///
+///
+///
namespace ts.projectSystem {
+ import spy = typemock.spy;
+ import dedent = utils.dedent;
import TI = server.typingsInstaller;
import protocol = server.protocol;
import CommandNames = server.CommandNames;
- export import TestServerHost = ts.TestFSWithWatch.TestServerHost;
- export type FileOrFolder = ts.TestFSWithWatch.FileOrFolder;
- export import createServerHost = ts.TestFSWithWatch.createServerHost;
- export import checkFileNames = ts.TestFSWithWatch.checkFileNames;
- export import libFile = ts.TestFSWithWatch.libFile;
- export import checkWatchedFiles = ts.TestFSWithWatch.checkWatchedFiles;
+ import FileOrFolder = ts.TestFSWithWatch.FileOrFolder;
+ import checkFileNames = ts.TestFSWithWatch.checkFileNames;
+ import libFile = ts.TestFSWithWatch.libFile;
+ import checkWatchedFiles = ts.TestFSWithWatch.checkWatchedFiles;
import checkWatchedDirectories = ts.TestFSWithWatch.checkWatchedDirectories;
import safeList = ts.TestFSWithWatch.safeList;
@@ -142,8 +145,8 @@ namespace ts.projectSystem {
private events: server.ProjectServiceEvent[] = [];
readonly session: TestSession;
readonly service: server.ProjectService;
- readonly host: projectSystem.TestServerHost;
- constructor(files: projectSystem.FileOrFolder[]) {
+ readonly host: fakes.FakeServerHost;
+ constructor(files: FileOrFolder[]) {
this.host = createServerHost(files);
this.session = createSession(this.host, {
canUseEvents: true,
@@ -283,6 +286,7 @@ namespace ts.projectSystem {
checkNumberOfProjects(this, count);
}
}
+
export function createProjectService(host: server.ServerHost, parameters: CreateProjectServiceParameters = {}, options?: Partial) {
const cancellationToken = parameters.cancellationToken || server.nullCancellationToken;
const logger = parameters.logger || nullLogger;
@@ -456,11 +460,11 @@ namespace ts.projectSystem {
verifyDiagnostics(actual, []);
}
- function assertEvent(actualOutput: string, expectedEvent: protocol.Event, host: TestServerHost) {
+ function assertEvent(actualOutput: string, expectedEvent: protocol.Event, host: ts.System) {
assert.equal(actualOutput, server.formatMessage(expectedEvent, nullLogger, Utils.byteLength, host.newLine));
}
- function checkErrorMessage(host: TestServerHost, eventName: "syntaxDiag" | "semanticDiag", diagnostics: protocol.DiagnosticEventBody) {
+ function checkErrorMessage(host: fakes.FakeServerHost, eventName: "syntaxDiag" | "semanticDiag", diagnostics: protocol.DiagnosticEventBody) {
const outputs = host.getOutput();
assert.isTrue(outputs.length >= 1, outputs.toString());
const event: protocol.Event = {
@@ -472,7 +476,7 @@ namespace ts.projectSystem {
assertEvent(outputs[0], event, host);
}
- function checkCompleteEvent(host: TestServerHost, numberOfCurrentEvents: number, expectedSequenceId: number) {
+ function checkCompleteEvent(host: fakes.FakeServerHost, numberOfCurrentEvents: number, expectedSequenceId: number) {
const outputs = host.getOutput();
assert.equal(outputs.length, numberOfCurrentEvents, outputs.toString());
const event: protocol.RequestCompletedEvent = {
@@ -486,7 +490,7 @@ namespace ts.projectSystem {
assertEvent(outputs[numberOfCurrentEvents - 1], event, host);
}
- function checkProjectUpdatedInBackgroundEvent(host: TestServerHost, openFiles: string[]) {
+ function checkProjectUpdatedInBackgroundEvent(host: fakes.FakeServerHost, openFiles: string[]) {
const outputs = host.getOutput();
assert.equal(outputs.length, 1, outputs.toString());
const event: protocol.ProjectsUpdatedInBackgroundEvent = {
@@ -500,5572 +504,5493 @@ namespace ts.projectSystem {
assertEvent(outputs[0], event, host);
}
+ function createServerHost(files: FileOrFolder[], options?: Partial) {
+ 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;
+ }
+
describe("tsserverProjectSystem", () => {
- const commonFile1: FileOrFolder = {
- path: "/a/b/commonFile1.ts",
- content: "let x = 1"
- };
- const commonFile2: FileOrFolder = {
- path: "/a/b/commonFile2.ts",
- content: "let y = 1"
- };
-
- it("create inferred project", () => {
- const appFile: FileOrFolder = {
- path: "/a/b/c/app.ts",
- content: `
- import {f} from "./module"
- console.log(f)
- `
- };
-
- const moduleFile: FileOrFolder = {
- path: "/a/b/c/module.d.ts",
- content: `export let x: number`
- };
- const host = createServerHost([appFile, moduleFile, libFile]);
- const projectService = createProjectService(host);
- const { configFileName } = projectService.openClientFile(appFile.path);
-
- assert(!configFileName, `should not find config, got: '${configFileName}`);
- checkNumberOfConfiguredProjects(projectService, 0);
- checkNumberOfInferredProjects(projectService, 1);
-
- const project = projectService.inferredProjects[0];
-
- checkFileNames("inferred project", project.getFileNames(), [appFile.path, libFile.path, moduleFile.path]);
- const configFileLocations = ["/a/b/c/", "/a/b/", "/a/", "/"];
- const configFiles = flatMap(configFileLocations, location => [location + "tsconfig.json", location + "jsconfig.json"]);
- checkWatchedFiles(host, configFiles.concat(libFile.path, moduleFile.path));
- checkWatchedDirectories(host, [], /*recursive*/ false);
- checkWatchedDirectories(host, ["/a/b/c", combinePaths(getDirectoryPath(appFile.path), nodeModulesAtTypes)], /*recursive*/ true);
- });
-
- it("can handle tsconfig file name with difference casing", () => {
- const f1 = {
- path: "/a/b/app.ts",
- content: "let x = 1"
- };
- const config = {
- path: "/a/b/tsconfig.json",
- content: JSON.stringify({
- include: []
- })
- };
-
- const host = createServerHost([f1, config], { useCaseSensitiveFileNames: false });
- const service = createProjectService(host);
- const upperCaseConfigFilePath = combinePaths(getDirectoryPath(config.path).toUpperCase(), getBaseFileName(config.path));
- service.openExternalProject({
- projectFileName: "/a/b/project.csproj",
- rootFiles: toExternalFiles([f1.path, upperCaseConfigFilePath]),
- options: {}
- });
- service.checkNumberOfProjects({ configuredProjects: 1 });
- checkProjectActualFiles(configuredProjectAt(service, 0), [upperCaseConfigFilePath]);
-
- service.openClientFile(f1.path);
- service.checkNumberOfProjects({ configuredProjects: 1, inferredProjects: 1 });
-
- checkProjectActualFiles(configuredProjectAt(service, 0), [upperCaseConfigFilePath]);
- checkProjectActualFiles(service.inferredProjects[0], [f1.path]);
- });
-
- it("create configured project without file list", () => {
- const configFile: FileOrFolder = {
- path: "/a/b/tsconfig.json",
- content: `
- {
- "compilerOptions": {},
- "exclude": [
- "e"
- ]
- }`
- };
- const file1: FileOrFolder = {
- path: "/a/b/c/f1.ts",
- content: "let x = 1"
- };
- const file2: FileOrFolder = {
- path: "/a/b/d/f2.ts",
- content: "let y = 1"
- };
- const file3: FileOrFolder = {
- path: "/a/b/e/f3.ts",
- content: "let z = 1"
- };
-
- const host = createServerHost([configFile, libFile, file1, file2, file3]);
- const projectService = createProjectService(host);
- const { configFileName, configFileErrors } = projectService.openClientFile(file1.path);
-
- assert(configFileName, "should find config file");
- assert.isTrue(!configFileErrors || configFileErrors.length === 0, `expect no errors in config file, got ${JSON.stringify(configFileErrors)}`);
- checkNumberOfInferredProjects(projectService, 0);
- checkNumberOfConfiguredProjects(projectService, 1);
-
- const project = configuredProjectAt(projectService, 0);
- checkProjectActualFiles(project, [file1.path, libFile.path, file2.path, configFile.path]);
- checkProjectRootFiles(project, [file1.path, file2.path]);
- // watching all files except one that was open
- checkWatchedFiles(host, [configFile.path, file2.path, libFile.path]);
- const configFileDirectory = getDirectoryPath(configFile.path);
- checkWatchedDirectories(host, [configFileDirectory, combinePaths(configFileDirectory, nodeModulesAtTypes)], /*recursive*/ true);
- });
-
- it("create configured project with the file list", () => {
- const configFile: FileOrFolder = {
- path: "/a/b/tsconfig.json",
- content: `
- {
- "compilerOptions": {},
- "include": ["*.ts"]
- }`
- };
- const file1: FileOrFolder = {
- path: "/a/b/f1.ts",
- content: "let x = 1"
- };
- const file2: FileOrFolder = {
- path: "/a/b/f2.ts",
- content: "let y = 1"
- };
- const file3: FileOrFolder = {
- path: "/a/b/c/f3.ts",
- content: "let z = 1"
- };
-
- const host = createServerHost([configFile, libFile, file1, file2, file3]);
- const projectService = createProjectService(host);
- const { configFileName, configFileErrors } = projectService.openClientFile(file1.path);
-
- assert(configFileName, "should find config file");
- assert.isTrue(!configFileErrors || configFileErrors.length === 0, `expect no errors in config file, got ${JSON.stringify(configFileErrors)}`);
- checkNumberOfInferredProjects(projectService, 0);
- checkNumberOfConfiguredProjects(projectService, 1);
-
- const project = configuredProjectAt(projectService, 0);
- checkProjectActualFiles(project, [file1.path, libFile.path, file2.path, configFile.path]);
- checkProjectRootFiles(project, [file1.path, file2.path]);
- // watching all files except one that was open
- checkWatchedFiles(host, [configFile.path, file2.path, libFile.path]);
- checkWatchedDirectories(host, [getDirectoryPath(configFile.path)], /*recursive*/ false);
- });
-
- it("add and then remove a config file in a folder with loose files", () => {
- const configFile: FileOrFolder = {
- path: "/a/b/tsconfig.json",
- content: `{
- "files": ["commonFile1.ts"]
- }`
- };
- const filesWithoutConfig = [libFile, commonFile1, commonFile2];
- const host = createServerHost(filesWithoutConfig);
-
- const filesWithConfig = [libFile, commonFile1, commonFile2, configFile];
- const projectService = createProjectService(host);
- projectService.openClientFile(commonFile1.path);
- projectService.openClientFile(commonFile2.path);
-
- checkNumberOfInferredProjects(projectService, 2);
- const configFileLocations = ["/", "/a/", "/a/b/"];
- const watchedFiles = flatMap(configFileLocations, location => [location + "tsconfig.json", location + "jsconfig.json"]).concat(libFile.path);
- checkWatchedFiles(host, watchedFiles);
-
- // Add a tsconfig file
- host.reloadFS(filesWithConfig);
- host.checkTimeoutQueueLengthAndRun(1);
- checkNumberOfInferredProjects(projectService, 1);
- checkNumberOfConfiguredProjects(projectService, 1);
- checkWatchedFiles(host, watchedFiles);
-
- // remove the tsconfig file
- host.reloadFS(filesWithoutConfig);
-
- checkNumberOfInferredProjects(projectService, 1);
- host.checkTimeoutQueueLengthAndRun(1); // Refresh inferred projects
-
- checkNumberOfInferredProjects(projectService, 2);
- checkNumberOfConfiguredProjects(projectService, 0);
- checkWatchedFiles(host, watchedFiles);
- });
-
- it("add new files to a configured project without file list", () => {
- const configFile: FileOrFolder = {
- path: "/a/b/tsconfig.json",
- content: `{}`
- };
- const host = createServerHost([commonFile1, libFile, configFile]);
- const projectService = createProjectService(host);
- projectService.openClientFile(commonFile1.path);
- const configFileDir = getDirectoryPath(configFile.path);
- checkWatchedDirectories(host, [configFileDir, combinePaths(configFileDir, nodeModulesAtTypes)], /*recursive*/ true);
- checkNumberOfConfiguredProjects(projectService, 1);
-
- const project = configuredProjectAt(projectService, 0);
- checkProjectRootFiles(project, [commonFile1.path]);
-
- // add a new ts file
- host.reloadFS([commonFile1, commonFile2, libFile, configFile]);
- host.checkTimeoutQueueLengthAndRun(2);
- // project service waits for 250ms to update the project structure, therefore the assertion needs to wait longer.
- checkProjectRootFiles(project, [commonFile1.path, commonFile2.path]);
- });
-
- it("should ignore non-existing files specified in the config file", () => {
- const configFile: FileOrFolder = {
- path: "/a/b/tsconfig.json",
- content: `{
- "compilerOptions": {},
- "files": [
- "commonFile1.ts",
- "commonFile3.ts"
- ]
- }`
- };
- const host = createServerHost([commonFile1, commonFile2, configFile]);
- const projectService = createProjectService(host);
- projectService.openClientFile(commonFile1.path);
- projectService.openClientFile(commonFile2.path);
-
- checkNumberOfConfiguredProjects(projectService, 1);
- const project = configuredProjectAt(projectService, 0);
- checkProjectRootFiles(project, [commonFile1.path]);
- checkNumberOfInferredProjects(projectService, 1);
- });
-
- it("remove not-listed external projects", () => {
- const f1 = {
- path: "/a/app.ts",
- content: "let x = 1"
- };
- const f2 = {
- path: "/b/app.ts",
- content: "let x = 1"
- };
- const f3 = {
- path: "/c/app.ts",
- content: "let x = 1"
- };
- const makeProject = (f: FileOrFolder) => ({ projectFileName: f.path + ".csproj", rootFiles: [toExternalFile(f.path)], options: {} });
- const p1 = makeProject(f1);
- const p2 = makeProject(f2);
- const p3 = makeProject(f3);
-
- const host = createServerHost([f1, f2, f3]);
- const session = createSession(host);
-
- session.executeCommand({
- seq: 1,
- type: "request",
- command: "openExternalProjects",
- arguments: { projects: [p1, p2] }
- });
-
- const projectService = session.getProjectService();
- checkNumberOfProjects(projectService, { externalProjects: 2 });
- assert.equal(projectService.externalProjects[0].getProjectName(), p1.projectFileName);
- assert.equal(projectService.externalProjects[1].getProjectName(), p2.projectFileName);
-
- session.executeCommand({
- seq: 2,
- type: "request",
- command: "openExternalProjects",
- arguments: { projects: [p1, p3] }
- });
- checkNumberOfProjects(projectService, { externalProjects: 2 });
- assert.equal(projectService.externalProjects[0].getProjectName(), p1.projectFileName);
- assert.equal(projectService.externalProjects[1].getProjectName(), p3.projectFileName);
-
- session.executeCommand({
- seq: 3,
- type: "request",
- command: "openExternalProjects",
- arguments: { projects: [] }
- });
- checkNumberOfProjects(projectService, { externalProjects: 0 });
-
- session.executeCommand({
- seq: 3,
- type: "request",
- command: "openExternalProjects",
- arguments: { projects: [p2] }
- });
- assert.equal(projectService.externalProjects[0].getProjectName(), p2.projectFileName);
- });
-
- it("handle recreated files correctly", () => {
- const configFile: FileOrFolder = {
- path: "/a/b/tsconfig.json",
- content: `{}`
- };
- const host = createServerHost([commonFile1, commonFile2, configFile]);
- const projectService = createProjectService(host);
- projectService.openClientFile(commonFile1.path);
-
- checkNumberOfConfiguredProjects(projectService, 1);
- const project = configuredProjectAt(projectService, 0);
- checkProjectRootFiles(project, [commonFile1.path, commonFile2.path]);
-
- // delete commonFile2
- host.reloadFS([commonFile1, configFile]);
- host.checkTimeoutQueueLengthAndRun(2);
- checkProjectRootFiles(project, [commonFile1.path]);
-
- // re-add commonFile2
- host.reloadFS([commonFile1, commonFile2, configFile]);
- host.checkTimeoutQueueLengthAndRun(2);
- checkProjectRootFiles(project, [commonFile1.path, commonFile2.path]);
- });
-
- it("handles the missing files - that were added to program because they were added with ///[ {
- const file1: FileOrFolder = {
+ describe("general", () => {
+ const commonFile1: FileOrFolder = {
path: "/a/b/commonFile1.ts",
- content: `///
- let x = y`
+ content: "let x = 1"
+ };
+ const commonFile2: FileOrFolder = {
+ path: "/a/b/commonFile2.ts",
+ content: "let y = 1"
};
- const host = createServerHost([file1, libFile]);
- const session = createSession(host);
- openFilesForSession([file1], session);
- const projectService = session.getProjectService();
- checkNumberOfInferredProjects(projectService, 1);
- const project = projectService.inferredProjects[0];
- checkProjectRootFiles(project, [file1.path]);
- checkProjectActualFiles(project, [file1.path, libFile.path]);
- const getErrRequest = makeSessionRequest(
- server.CommandNames.SemanticDiagnosticsSync,
- { file: file1.path }
- );
+ it("create inferred project", () => {
+ const host = new fakes.FakeServerHost({ lib: true });
+ host.vfs.addFile("/a/b/c/app.ts", `import {f} from "./module"\nconsole.log(f)`);
+ host.vfs.addFile("/a/b/c/module.d.ts", `export let x: number`);
- // Two errors: CommonFile2 not found and cannot find name y
- let diags = session.executeCommand(getErrRequest).response as server.protocol.Diagnostic[];
- verifyDiagnostics(diags, [
- { diagnosticMessage: Diagnostics.Cannot_find_name_0, errorTextArguments: ["y"] },
- { diagnosticMessage: Diagnostics.File_0_not_found, errorTextArguments: [commonFile2.path] }
- ]);
+ const projectService = createProjectService(host);
+ const { configFileName } = projectService.openClientFile("/a/b/c/app.ts");
- host.reloadFS([file1, commonFile2, libFile]);
- host.runQueuedTimeoutCallbacks();
- checkNumberOfInferredProjects(projectService, 1);
- assert.strictEqual(projectService.inferredProjects[0], project, "Inferred project should be same");
- checkProjectRootFiles(project, [file1.path]);
- checkProjectActualFiles(project, [file1.path, libFile.path, commonFile2.path]);
- diags = session.executeCommand(getErrRequest).response as server.protocol.Diagnostic[];
- verifyNoDiagnostics(diags);
- });
+ assert(!configFileName, `should not find config, got: '${configFileName}`);
+ checkNumberOfConfiguredProjects(projectService, 0);
+ checkNumberOfInferredProjects(projectService, 1);
- it("should create new inferred projects for files excluded from a configured project", () => {
- const configFile: FileOrFolder = {
- path: "/a/b/tsconfig.json",
- content: `{
+ const project = projectService.inferredProjects[0];
+
+ checkFileNames("inferred project", project.getFileNames(), ["/a/b/c/app.ts", fakes.FakeServerHost.libPath, "/a/b/c/module.d.ts"]);
+ const configFileLocations = ["/a/b/c/", "/a/b/", "/a/", "/"];
+ const configFiles = flatMap(configFileLocations, location => [location + "tsconfig.json", location + "jsconfig.json"]);
+ checkWatchedFiles(host, configFiles.concat(fakes.FakeServerHost.libPath, "/a/b/c/module.d.ts"));
+ checkWatchedDirectories(host, [], /*recursive*/ false);
+ checkWatchedDirectories(host, ["/a/b/c", combinePaths("/a/b/c/", nodeModulesAtTypes)], /*recursive*/ true);
+ });
+
+ it("can handle tsconfig file name with difference casing", () => {
+ const host = new fakes.FakeServerHost({ vfs: { useCaseSensitiveFileNames: false } });
+ host.vfs.addFile("/a/b/app.ts", `let x = 1`);
+ host.vfs.addFile("/a/b/tsconfig.json", `{ "include": [] }`);
+
+ const service = createProjectService(host);
+ service.openExternalProject({
+ projectFileName: "/a/b/project.csproj",
+ rootFiles: toExternalFiles(["/a/b/app.ts", "/A/B/tsconfig.json"]),
+ options: {}
+ });
+ service.checkNumberOfProjects({ configuredProjects: 1 });
+ checkProjectActualFiles(configuredProjectAt(service, 0), ["/A/B/tsconfig.json"]);
+
+ service.openClientFile("/a/b/app.ts");
+ service.checkNumberOfProjects({ configuredProjects: 1, inferredProjects: 1 });
+
+ checkProjectActualFiles(configuredProjectAt(service, 0), ["/A/B/tsconfig.json"]);
+ checkProjectActualFiles(service.inferredProjects[0], ["/a/b/app.ts"]);
+ });
+
+ it("create configured project without file list", () => {
+ const configFile: FileOrFolder = {
+ path: "/a/b/tsconfig.json",
+ content: `
+ {
+ "compilerOptions": {},
+ "exclude": [
+ "e"
+ ]
+ }`
+ };
+ const file1: FileOrFolder = {
+ path: "/a/b/c/f1.ts",
+ content: "let x = 1"
+ };
+ const file2: FileOrFolder = {
+ path: "/a/b/d/f2.ts",
+ content: "let y = 1"
+ };
+ const file3: FileOrFolder = {
+ path: "/a/b/e/f3.ts",
+ content: "let z = 1"
+ };
+
+ const host = createServerHost([configFile, file1, file2, file3], { lib: true });
+ const projectService = createProjectService(host);
+ const { configFileName, configFileErrors } = projectService.openClientFile(file1.path);
+
+ assert(configFileName, "should find config file");
+ assert.isTrue(!configFileErrors || configFileErrors.length === 0, `expect no errors in config file, got ${JSON.stringify(configFileErrors)}`);
+ checkNumberOfInferredProjects(projectService, 0);
+ checkNumberOfConfiguredProjects(projectService, 1);
+
+ const project = configuredProjectAt(projectService, 0);
+ checkProjectActualFiles(project, [file1.path, fakes.FakeServerHost.libPath, file2.path, configFile.path]);
+ checkProjectRootFiles(project, [file1.path, file2.path]);
+ // watching all files except one that was open
+ checkWatchedFiles(host, [configFile.path, file2.path, fakes.FakeServerHost.libPath]);
+ const configFileDirectory = getDirectoryPath(configFile.path);
+ checkWatchedDirectories(host, [configFileDirectory, combinePaths(configFileDirectory, nodeModulesAtTypes)], /*recursive*/ true);
+ });
+
+ it("create configured project with the file list", () => {
+ const configFile: FileOrFolder = {
+ path: "/a/b/tsconfig.json",
+ content: `
+ {
+ "compilerOptions": {},
+ "include": ["*.ts"]
+ }`
+ };
+ const file1: FileOrFolder = {
+ path: "/a/b/f1.ts",
+ content: "let x = 1"
+ };
+ const file2: FileOrFolder = {
+ path: "/a/b/f2.ts",
+ content: "let y = 1"
+ };
+ const file3: FileOrFolder = {
+ path: "/a/b/c/f3.ts",
+ content: "let z = 1"
+ };
+
+ const host = createServerHost([configFile, libFile, file1, file2, file3]);
+ const projectService = createProjectService(host);
+ const { configFileName, configFileErrors } = projectService.openClientFile(file1.path);
+
+ assert(configFileName, "should find config file");
+ assert.isTrue(!configFileErrors || configFileErrors.length === 0, `expect no errors in config file, got ${JSON.stringify(configFileErrors)}`);
+ checkNumberOfInferredProjects(projectService, 0);
+ checkNumberOfConfiguredProjects(projectService, 1);
+
+ const project = configuredProjectAt(projectService, 0);
+ checkProjectActualFiles(project, [file1.path, fakes.FakeServerHost.libPath, file2.path, configFile.path]);
+ checkProjectRootFiles(project, [file1.path, file2.path]);
+ // watching all files except one that was open
+ checkWatchedFiles(host, [configFile.path, file2.path, fakes.FakeServerHost.libPath]);
+ checkWatchedDirectories(host, [getDirectoryPath(configFile.path)], /*recursive*/ false);
+ });
+
+ it("add and then remove a config file in a folder with loose files", () => {
+ const configFile: FileOrFolder = {
+ path: "/a/b/tsconfig.json",
+ content: `{
+ "files": ["commonFile1.ts"]
+ }`
+ };
+ const filesWithoutConfig = [libFile, commonFile1, commonFile2];
+ const host = createServerHost(filesWithoutConfig);
+
+ // const filesWithConfig = [libFile, commonFile1, commonFile2, configFile];
+ const projectService = createProjectService(host);
+ projectService.openClientFile(commonFile1.path);
+ projectService.openClientFile(commonFile2.path);
+
+ checkNumberOfInferredProjects(projectService, 2);
+ const configFileLocations = ["/", "/a/", "/a/b/"];
+ const watchedFiles = flatMap(configFileLocations, location => [location + "tsconfig.json", location + "jsconfig.json"]).concat(fakes.FakeServerHost.libPath);
+ checkWatchedFiles(host, watchedFiles);
+
+ // Add a tsconfig file
+ host.vfs.addFile(configFile.path, configFile.content);
+ host.checkTimeoutQueueLengthAndRun(1);
+ checkNumberOfInferredProjects(projectService, 1);
+ checkNumberOfConfiguredProjects(projectService, 1);
+ checkWatchedFiles(host, watchedFiles);
+
+ // remove the tsconfig file
+ host.vfs.removeFile(configFile.path);
+
+ checkNumberOfInferredProjects(projectService, 1);
+ host.checkTimeoutQueueLengthAndRun(1); // Refresh inferred projects
+
+ checkNumberOfInferredProjects(projectService, 2);
+ checkNumberOfConfiguredProjects(projectService, 0);
+ checkWatchedFiles(host, watchedFiles);
+ });
+
+ it("add new files to a configured project without file list", () => {
+ const configFile: FileOrFolder = {
+ path: "/a/b/tsconfig.json",
+ content: `{}`
+ };
+ const host = createServerHost([commonFile1, libFile, configFile]);
+ const projectService = createProjectService(host);
+ projectService.openClientFile(commonFile1.path);
+ const configFileDir = getDirectoryPath(configFile.path);
+ checkWatchedDirectories(host, [configFileDir, combinePaths(configFileDir, nodeModulesAtTypes)], /*recursive*/ true);
+ checkNumberOfConfiguredProjects(projectService, 1);
+
+ const project = configuredProjectAt(projectService, 0);
+ checkProjectRootFiles(project, [commonFile1.path]);
+
+ // add a new ts file
+ host.vfs.addFile(commonFile2.path, commonFile2.content);
+ host.checkTimeoutQueueLengthAndRun(2);
+ // project service waits for 250ms to update the project structure, therefore the assertion needs to wait longer.
+ checkProjectRootFiles(project, [commonFile1.path, commonFile2.path]);
+ });
+
+ it("should ignore non-existing files specified in the config file", () => {
+ const configFile: FileOrFolder = {
+ path: "/a/b/tsconfig.json",
+ content: `{
+ "compilerOptions": {},
+ "files": [
+ "commonFile1.ts",
+ "commonFile3.ts"
+ ]
+ }`
+ };
+ const host = createServerHost([commonFile1, commonFile2, configFile]);
+ const projectService = createProjectService(host);
+ projectService.openClientFile(commonFile1.path);
+ projectService.openClientFile(commonFile2.path);
+
+ checkNumberOfConfiguredProjects(projectService, 1);
+ const project = configuredProjectAt(projectService, 0);
+ checkProjectRootFiles(project, [commonFile1.path]);
+ checkNumberOfInferredProjects(projectService, 1);
+ });
+
+ it("remove not-listed external projects", () => {
+ const f1 = {
+ path: "/a/app.ts",
+ content: "let x = 1"
+ };
+ const f2 = {
+ path: "/b/app.ts",
+ content: "let x = 1"
+ };
+ const f3 = {
+ path: "/c/app.ts",
+ content: "let x = 1"
+ };
+ const makeProject = (f: FileOrFolder) => ({ projectFileName: f.path + ".csproj", rootFiles: [toExternalFile(f.path)], options: {} });
+ const p1 = makeProject(f1);
+ const p2 = makeProject(f2);
+ const p3 = makeProject(f3);
+
+ const host = createServerHost([f1, f2, f3]);
+ const session = createSession(host);
+
+ session.executeCommand({
+ seq: 1,
+ type: "request",
+ command: "openExternalProjects",
+ arguments: { projects: [p1, p2] }
+ });
+
+ const projectService = session.getProjectService();
+ checkNumberOfProjects(projectService, { externalProjects: 2 });
+ assert.equal(projectService.externalProjects[0].getProjectName(), p1.projectFileName);
+ assert.equal(projectService.externalProjects[1].getProjectName(), p2.projectFileName);
+
+ session.executeCommand({
+ seq: 2,
+ type: "request",
+ command: "openExternalProjects",
+ arguments: { projects: [p1, p3] }
+ });
+ checkNumberOfProjects(projectService, { externalProjects: 2 });
+ assert.equal(projectService.externalProjects[0].getProjectName(), p1.projectFileName);
+ assert.equal(projectService.externalProjects[1].getProjectName(), p3.projectFileName);
+
+ session.executeCommand({
+ seq: 3,
+ type: "request",
+ command: "openExternalProjects",
+ arguments: { projects: [] }
+ });
+ checkNumberOfProjects(projectService, { externalProjects: 0 });
+
+ session.executeCommand({
+ seq: 3,
+ type: "request",
+ command: "openExternalProjects",
+ arguments: { projects: [p2] }
+ });
+ assert.equal(projectService.externalProjects[0].getProjectName(), p2.projectFileName);
+ });
+
+ it("handle recreated files correctly", () => {
+ const configFile: FileOrFolder = {
+ path: "/a/b/tsconfig.json",
+ content: `{}`
+ };
+ const host = createServerHost([commonFile1, commonFile2, configFile]);
+ const projectService = createProjectService(host);
+ projectService.openClientFile(commonFile1.path);
+
+ checkNumberOfConfiguredProjects(projectService, 1);
+ const project = configuredProjectAt(projectService, 0);
+ checkProjectRootFiles(project, [commonFile1.path, commonFile2.path]);
+
+ // delete commonFile2
+ host.vfs.removeFile(commonFile2.path);
+ host.checkTimeoutQueueLengthAndRun(2);
+ checkProjectRootFiles(project, [commonFile1.path]);
+
+ // re-add commonFile2
+ host.vfs.addFile(commonFile2.path, commonFile2.content);
+ host.checkTimeoutQueueLengthAndRun(2);
+ checkProjectRootFiles(project, [commonFile1.path, commonFile2.path]);
+ });
+
+ it("handles the missing files - that were added to program because they were added with ///][ {
+ const file1: FileOrFolder = {
+ path: "/a/b/commonFile1.ts",
+ content: `///
+ let x = y`
+ };
+ const host = createServerHost([file1, libFile]);
+ const session = createSession(host);
+ openFilesForSession([file1], session);
+ const projectService = session.getProjectService();
+
+ checkNumberOfInferredProjects(projectService, 1);
+ const project = projectService.inferredProjects[0];
+ checkProjectRootFiles(project, [file1.path]);
+ checkProjectActualFiles(project, [file1.path, fakes.FakeServerHost.libPath]);
+ const getErrRequest = makeSessionRequest(
+ server.CommandNames.SemanticDiagnosticsSync,
+ { file: file1.path }
+ );
+
+ // Two errors: CommonFile2 not found and cannot find name y
+ let diags = session.executeCommand(getErrRequest).response as server.protocol.Diagnostic[];
+ verifyDiagnostics(diags, [
+ { diagnosticMessage: Diagnostics.Cannot_find_name_0, errorTextArguments: ["y"] },
+ { diagnosticMessage: Diagnostics.File_0_not_found, errorTextArguments: [commonFile2.path] }
+ ]);
+
+ host.vfs.addFile(commonFile2.path, commonFile2.content);
+ host.runQueuedTimeoutCallbacks();
+ checkNumberOfInferredProjects(projectService, 1);
+ assert.strictEqual(projectService.inferredProjects[0], project, "Inferred project should be same");
+ checkProjectRootFiles(project, [file1.path]);
+ checkProjectActualFiles(project, [file1.path, fakes.FakeServerHost.libPath, commonFile2.path]);
+ diags = session.executeCommand(getErrRequest).response as server.protocol.Diagnostic[];
+ verifyNoDiagnostics(diags);
+ });
+
+ it("should create new inferred projects for files excluded from a configured project", () => {
+ const configFile: FileOrFolder = {
+ path: "/a/b/tsconfig.json",
+ content: `{
+ "compilerOptions": {},
+ "files": ["${commonFile1.path}", "${commonFile2.path}"]
+ }`
+ };
+ const files = [commonFile1, commonFile2, configFile];
+ const host = createServerHost(files);
+ const projectService = createProjectService(host);
+ projectService.openClientFile(commonFile1.path);
+
+ const project = configuredProjectAt(projectService, 0);
+ checkProjectRootFiles(project, [commonFile1.path, commonFile2.path]);
+ configFile.content = `{
"compilerOptions": {},
- "files": ["${commonFile1.path}", "${commonFile2.path}"]
- }`
- };
- const files = [commonFile1, commonFile2, configFile];
- const host = createServerHost(files);
- const projectService = createProjectService(host);
- projectService.openClientFile(commonFile1.path);
+ "files": ["${commonFile1.path}"]
+ }`;
+ host.vfs.writeFile(configFile.path, configFile.content);
- const project = configuredProjectAt(projectService, 0);
- checkProjectRootFiles(project, [commonFile1.path, commonFile2.path]);
- configFile.content = `{
- "compilerOptions": {},
- "files": ["${commonFile1.path}"]
- }`;
- host.reloadFS(files);
+ checkNumberOfConfiguredProjects(projectService, 1);
+ checkProjectRootFiles(project, [commonFile1.path, commonFile2.path]);
+ host.checkTimeoutQueueLengthAndRun(2); // Update the configured project + refresh inferred projects
+ checkNumberOfConfiguredProjects(projectService, 1);
+ checkProjectRootFiles(project, [commonFile1.path]);
- checkNumberOfConfiguredProjects(projectService, 1);
- checkProjectRootFiles(project, [commonFile1.path, commonFile2.path]);
- host.checkTimeoutQueueLengthAndRun(2); // Update the configured project + refresh inferred projects
- checkNumberOfConfiguredProjects(projectService, 1);
- checkProjectRootFiles(project, [commonFile1.path]);
+ projectService.openClientFile(commonFile2.path);
+ checkNumberOfInferredProjects(projectService, 1);
+ });
- projectService.openClientFile(commonFile2.path);
- checkNumberOfInferredProjects(projectService, 1);
- });
+ it("files explicitly excluded in config file", () => {
+ const configFile: FileOrFolder = {
+ path: "/a/b/tsconfig.json",
+ content: `{
+ "compilerOptions": {},
+ "exclude": ["/a/c"]
+ }`
+ };
+ const excludedFile1: FileOrFolder = {
+ path: "/a/c/excluedFile1.ts",
+ content: `let t = 1;`
+ };
- it("files explicitly excluded in config file", () => {
- const configFile: FileOrFolder = {
- path: "/a/b/tsconfig.json",
- content: `{
- "compilerOptions": {},
- "exclude": ["/a/c"]
- }`
- };
- const excludedFile1: FileOrFolder = {
- path: "/a/c/excluedFile1.ts",
- content: `let t = 1;`
- };
+ const host = createServerHost([commonFile1, commonFile2, excludedFile1, configFile]);
+ const projectService = createProjectService(host);
- const host = createServerHost([commonFile1, commonFile2, excludedFile1, configFile]);
- const projectService = createProjectService(host);
+ projectService.openClientFile(commonFile1.path);
+ checkNumberOfConfiguredProjects(projectService, 1);
+ const project = configuredProjectAt(projectService, 0);
+ checkProjectRootFiles(project, [commonFile1.path, commonFile2.path]);
+ projectService.openClientFile(excludedFile1.path);
+ checkNumberOfInferredProjects(projectService, 1);
+ });
- projectService.openClientFile(commonFile1.path);
- checkNumberOfConfiguredProjects(projectService, 1);
- const project = configuredProjectAt(projectService, 0);
- checkProjectRootFiles(project, [commonFile1.path, commonFile2.path]);
- projectService.openClientFile(excludedFile1.path);
- checkNumberOfInferredProjects(projectService, 1);
- });
+ it("should properly handle module resolution changes in config file", () => {
+ const file1: FileOrFolder = {
+ path: "/a/b/file1.ts",
+ content: `import { T } from "module1";`
+ };
+ const nodeModuleFile: FileOrFolder = {
+ path: "/a/b/node_modules/module1.ts",
+ content: `export interface T {}`
+ };
+ const classicModuleFile: FileOrFolder = {
+ path: "/a/module1.ts",
+ content: `export interface T {}`
+ };
+ const configFile: FileOrFolder = {
+ path: "/a/b/tsconfig.json",
+ content: `{
+ "compilerOptions": {
+ "moduleResolution": "node"
+ },
+ "files": ["${file1.path}"]
+ }`
+ };
+ const files = [file1, nodeModuleFile, classicModuleFile, configFile];
+ const host = createServerHost(files);
+ const projectService = createProjectService(host);
+ projectService.openClientFile(file1.path);
+ projectService.openClientFile(nodeModuleFile.path);
+ projectService.openClientFile(classicModuleFile.path);
- it("should properly handle module resolution changes in config file", () => {
- const file1: FileOrFolder = {
- path: "/a/b/file1.ts",
- content: `import { T } from "module1";`
- };
- const nodeModuleFile: FileOrFolder = {
- path: "/a/b/node_modules/module1.ts",
- content: `export interface T {}`
- };
- const classicModuleFile: FileOrFolder = {
- path: "/a/module1.ts",
- content: `export interface T {}`
- };
- const configFile: FileOrFolder = {
- path: "/a/b/tsconfig.json",
- content: `{
+ checkNumberOfConfiguredProjects(projectService, 1);
+ const project = configuredProjectAt(projectService, 0);
+ checkProjectActualFiles(project, [file1.path, nodeModuleFile.path, configFile.path]);
+ checkNumberOfInferredProjects(projectService, 1);
+
+ configFile.content = `{
"compilerOptions": {
- "moduleResolution": "node"
+ "moduleResolution": "classic"
},
"files": ["${file1.path}"]
- }`
- };
- const files = [file1, nodeModuleFile, classicModuleFile, configFile];
- const host = createServerHost(files);
- const projectService = createProjectService(host);
- projectService.openClientFile(file1.path);
- projectService.openClientFile(nodeModuleFile.path);
- projectService.openClientFile(classicModuleFile.path);
-
- checkNumberOfConfiguredProjects(projectService, 1);
- const project = configuredProjectAt(projectService, 0);
- checkProjectActualFiles(project, [file1.path, nodeModuleFile.path, configFile.path]);
- checkNumberOfInferredProjects(projectService, 1);
-
- configFile.content = `{
- "compilerOptions": {
- "moduleResolution": "classic"
- },
- "files": ["${file1.path}"]
- }`;
- host.reloadFS(files);
- host.checkTimeoutQueueLengthAndRun(2);
- checkProjectActualFiles(project, [file1.path, classicModuleFile.path, configFile.path]);
- checkNumberOfInferredProjects(projectService, 1);
- });
-
- it("should keep the configured project when the opened file is referenced by the project but not its root", () => {
- const file1: FileOrFolder = {
- path: "/a/b/main.ts",
- content: "import { objA } from './obj-a';"
- };
- const file2: FileOrFolder = {
- path: "/a/b/obj-a.ts",
- content: `export const objA = Object.assign({foo: "bar"}, {bar: "baz"});`
- };
- const configFile: FileOrFolder = {
- path: "/a/b/tsconfig.json",
- content: `{
- "compilerOptions": {
- "target": "es6"
- },
- "files": [ "main.ts" ]
- }`
- };
- const host = createServerHost([file1, file2, configFile]);
- const projectService = createProjectService(host);
- projectService.openClientFile(file1.path);
- projectService.closeClientFile(file1.path);
- projectService.openClientFile(file2.path);
- checkNumberOfConfiguredProjects(projectService, 1);
- checkNumberOfInferredProjects(projectService, 0);
- });
-
- it("should keep the configured project when the opened file is referenced by the project but not its root", () => {
- const file1: FileOrFolder = {
- path: "/a/b/main.ts",
- content: "import { objA } from './obj-a';"
- };
- const file2: FileOrFolder = {
- path: "/a/b/obj-a.ts",
- content: `export const objA = Object.assign({foo: "bar"}, {bar: "baz"});`
- };
- const configFile: FileOrFolder = {
- path: "/a/b/tsconfig.json",
- content: `{
- "compilerOptions": {
- "target": "es6"
- },
- "files": [ "main.ts" ]
- }`
- };
- const host = createServerHost([file1, file2, configFile]);
- const projectService = createProjectService(host);
- projectService.openClientFile(file1.path);
- projectService.closeClientFile(file1.path);
- projectService.openClientFile(file2.path);
- checkNumberOfConfiguredProjects(projectService, 1);
- checkNumberOfInferredProjects(projectService, 0);
- });
- it("should tolerate config file errors and still try to build a project", () => {
- const configFile: FileOrFolder = {
- path: "/a/b/tsconfig.json",
- content: `{
- "compilerOptions": {
- "target": "es6",
- "allowAnything": true
- },
- "someOtherProperty": {}
- }`
- };
- const host = createServerHost([commonFile1, commonFile2, libFile, configFile]);
- const projectService = createProjectService(host);
- projectService.openClientFile(commonFile1.path);
- checkNumberOfConfiguredProjects(projectService, 1);
- checkProjectRootFiles(configuredProjectAt(projectService, 0), [commonFile1.path, commonFile2.path]);
- });
-
- it("should disable features when the files are too large", () => {
- const file1 = {
- path: "/a/b/f1.js",
- content: "let x =1;",
- fileSize: 10 * 1024 * 1024
- };
- const file2 = {
- path: "/a/b/f2.js",
- content: "let y =1;",
- fileSize: 6 * 1024 * 1024
- };
- const file3 = {
- path: "/a/b/f3.js",
- content: "let y =1;",
- fileSize: 6 * 1024 * 1024
- };
-
- const proj1name = "proj1", proj2name = "proj2", proj3name = "proj3";
-
- const host = createServerHost([file1, file2, file3]);
- const projectService = createProjectService(host);
-
- projectService.openExternalProject({ rootFiles: toExternalFiles([file1.path]), options: {}, projectFileName: proj1name });
- const proj1 = projectService.findProject(proj1name);
- assert.isTrue(proj1.languageServiceEnabled);
-
- projectService.openExternalProject({ rootFiles: toExternalFiles([file2.path]), options: {}, projectFileName: proj2name });
- const proj2 = projectService.findProject(proj2name);
- assert.isTrue(proj2.languageServiceEnabled);
-
- projectService.openExternalProject({ rootFiles: toExternalFiles([file3.path]), options: {}, projectFileName: proj3name });
- const proj3 = projectService.findProject(proj3name);
- assert.isFalse(proj3.languageServiceEnabled);
- });
-
- it("should use only one inferred project if 'useOneInferredProject' is set", () => {
- const file1 = {
- path: "/a/b/main.ts",
- content: "let x =1;"
- };
- const configFile: FileOrFolder = {
- path: "/a/b/tsconfig.json",
- content: `{
- "compilerOptions": {
- "target": "es6"
- },
- "files": [ "main.ts" ]
- }`
- };
- const file2 = {
- path: "/a/c/main.ts",
- content: "let x =1;"
- };
-
- const file3 = {
- path: "/a/d/main.ts",
- content: "let x =1;"
- };
-
- const host = createServerHost([file1, file2, file3, libFile]);
- const projectService = createProjectService(host, { useSingleInferredProject: true });
- projectService.openClientFile(file1.path);
- projectService.openClientFile(file2.path);
- projectService.openClientFile(file3.path);
-
- checkNumberOfConfiguredProjects(projectService, 0);
- checkNumberOfInferredProjects(projectService, 1);
- checkProjectActualFiles(projectService.inferredProjects[0], [file1.path, file2.path, file3.path, libFile.path]);
-
-
- host.reloadFS([file1, configFile, file2, file3, libFile]);
- host.checkTimeoutQueueLengthAndRun(1);
- checkNumberOfConfiguredProjects(projectService, 1);
- checkNumberOfInferredProjects(projectService, 1);
- checkProjectActualFiles(projectService.inferredProjects[0], [file2.path, file3.path, libFile.path]);
- });
-
- it("should reuse same project if file is opened from the configured project that has no open files", () => {
- const file1 = {
- path: "/a/b/main.ts",
- content: "let x =1;"
- };
- const file2 = {
- path: "/a/b/main2.ts",
- content: "let y =1;"
- };
- const configFile: FileOrFolder = {
- path: "/a/b/tsconfig.json",
- content: `{
- "compilerOptions": {
- "target": "es6"
- },
- "files": [ "main.ts", "main2.ts" ]
- }`
- };
- const host = createServerHost([file1, file2, configFile, libFile]);
- const projectService = createProjectService(host, { useSingleInferredProject: true });
- projectService.openClientFile(file1.path);
- checkNumberOfConfiguredProjects(projectService, 1);
- const project = projectService.configuredProjects.get(configFile.path);
- assert.isTrue(project.hasOpenRef()); // file1
-
- projectService.closeClientFile(file1.path);
- checkNumberOfConfiguredProjects(projectService, 1);
- assert.strictEqual(projectService.configuredProjects.get(configFile.path), project);
- assert.isFalse(project.hasOpenRef()); // No open files
- assert.isFalse(project.isClosed());
-
- projectService.openClientFile(file2.path);
- checkNumberOfConfiguredProjects(projectService, 1);
- assert.strictEqual(projectService.configuredProjects.get(configFile.path), project);
- assert.isTrue(project.hasOpenRef()); // file2
- assert.isFalse(project.isClosed());
- });
-
- it("should not close configured project after closing last open file, but should be closed on next file open if its not the file from same project", () => {
- const file1 = {
- path: "/a/b/main.ts",
- content: "let x =1;"
- };
- const configFile: FileOrFolder = {
- path: "/a/b/tsconfig.json",
- content: `{
- "compilerOptions": {
- "target": "es6"
- },
- "files": [ "main.ts" ]
- }`
- };
- const host = createServerHost([file1, configFile, libFile]);
- const projectService = createProjectService(host, { useSingleInferredProject: true });
- projectService.openClientFile(file1.path);
- checkNumberOfConfiguredProjects(projectService, 1);
- const project = projectService.configuredProjects.get(configFile.path);
- assert.isTrue(project.hasOpenRef()); // file1
-
- projectService.closeClientFile(file1.path);
- checkNumberOfConfiguredProjects(projectService, 1);
- assert.strictEqual(projectService.configuredProjects.get(configFile.path), project);
- assert.isFalse(project.hasOpenRef()); // No files
- assert.isFalse(project.isClosed());
-
- projectService.openClientFile(libFile.path);
- checkNumberOfConfiguredProjects(projectService, 0);
- assert.isFalse(project.hasOpenRef()); // No files + project closed
- assert.isTrue(project.isClosed());
- });
-
- it("should not close external project with no open files", () => {
- const file1 = {
- path: "/a/b/f1.ts",
- content: "let x =1;"
- };
- const file2 = {
- path: "/a/b/f2.ts",
- content: "let y =1;"
- };
- const externalProjectName = "externalproject";
- const host = createServerHost([file1, file2]);
- const projectService = createProjectService(host);
- projectService.openExternalProject({
- rootFiles: toExternalFiles([file1.path, file2.path]),
- options: {},
- projectFileName: externalProjectName
+ }`;
+ host.vfs.writeFile(configFile.path, configFile.content);
+ host.checkTimeoutQueueLengthAndRun(2);
+ checkProjectActualFiles(project, [file1.path, classicModuleFile.path, configFile.path]);
+ checkNumberOfInferredProjects(projectService, 1);
});
- checkNumberOfExternalProjects(projectService, 1);
- checkNumberOfInferredProjects(projectService, 0);
-
- // open client file - should not lead to creation of inferred project
- projectService.openClientFile(file1.path, file1.content);
- checkNumberOfExternalProjects(projectService, 1);
- checkNumberOfInferredProjects(projectService, 0);
-
- // close client file - external project should still exists
- projectService.closeClientFile(file1.path);
- checkNumberOfExternalProjects(projectService, 1);
- checkNumberOfInferredProjects(projectService, 0);
-
- projectService.closeExternalProject(externalProjectName);
- checkNumberOfExternalProjects(projectService, 0);
- checkNumberOfInferredProjects(projectService, 0);
- });
-
- it("external project that included config files", () => {
- const file1 = {
- path: "/a/b/f1.ts",
- content: "let x =1;"
- };
- const config1 = {
- path: "/a/b/tsconfig.json",
- content: JSON.stringify(
- {
- compilerOptions: {},
- files: ["f1.ts"]
- }
- )
- };
- const file2 = {
- path: "/a/c/f2.ts",
- content: "let y =1;"
- };
- const config2 = {
- path: "/a/c/tsconfig.json",
- content: JSON.stringify(
- {
- compilerOptions: {},
- files: ["f2.ts"]
- }
- )
- };
- const file3 = {
- path: "/a/d/f3.ts",
- content: "let z =1;"
- };
- const externalProjectName = "externalproject";
- const host = createServerHost([file1, file2, file3, config1, config2]);
- const projectService = createProjectService(host);
- projectService.openExternalProject({
- rootFiles: toExternalFiles([config1.path, config2.path, file3.path]),
- options: {},
- projectFileName: externalProjectName
- });
-
- checkNumberOfProjects(projectService, { configuredProjects: 2 });
- const proj1 = projectService.configuredProjects.get(config1.path);
- const proj2 = projectService.configuredProjects.get(config2.path);
- assert.isDefined(proj1);
- assert.isDefined(proj2);
-
- // open client file - should not lead to creation of inferred project
- projectService.openClientFile(file1.path, file1.content);
- checkNumberOfProjects(projectService, { configuredProjects: 2 });
- assert.strictEqual(projectService.configuredProjects.get(config1.path), proj1);
- assert.strictEqual(projectService.configuredProjects.get(config2.path), proj2);
-
- projectService.openClientFile(file3.path, file3.content);
- checkNumberOfProjects(projectService, { configuredProjects: 2, inferredProjects: 1 });
- assert.strictEqual(projectService.configuredProjects.get(config1.path), proj1);
- assert.strictEqual(projectService.configuredProjects.get(config2.path), proj2);
-
- projectService.closeExternalProject(externalProjectName);
- // open file 'file1' from configured project keeps project alive
- checkNumberOfProjects(projectService, { configuredProjects: 1, inferredProjects: 1 });
- assert.strictEqual(projectService.configuredProjects.get(config1.path), proj1);
- assert.isUndefined(projectService.configuredProjects.get(config2.path));
-
- projectService.closeClientFile(file3.path);
- checkNumberOfProjects(projectService, { configuredProjects: 1 });
- assert.strictEqual(projectService.configuredProjects.get(config1.path), proj1);
- assert.isUndefined(projectService.configuredProjects.get(config2.path));
-
- projectService.closeClientFile(file1.path);
- checkNumberOfProjects(projectService, { configuredProjects: 1 });
- assert.strictEqual(projectService.configuredProjects.get(config1.path), proj1);
- assert.isUndefined(projectService.configuredProjects.get(config2.path));
-
- projectService.openClientFile(file2.path, file2.content);
- checkNumberOfProjects(projectService, { configuredProjects: 1 });
- assert.isUndefined(projectService.configuredProjects.get(config1.path));
- assert.isDefined(projectService.configuredProjects.get(config2.path));
-
- });
-
- it("reload regular file after closing", () => {
- const f1 = {
- path: "/a/b/app.ts",
- content: "x."
- };
- const f2 = {
- path: "/a/b/lib.ts",
- content: "let x: number;"
- };
-
- const host = createServerHost([f1, f2, libFile]);
- const service = createProjectService(host);
- service.openExternalProject({ projectFileName: "/a/b/project", rootFiles: toExternalFiles([f1.path, f2.path]), options: {} });
-
- service.openClientFile(f1.path);
- service.openClientFile(f2.path, "let x: string");
-
- service.checkNumberOfProjects({ externalProjects: 1 });
- checkProjectActualFiles(service.externalProjects[0], [f1.path, f2.path, libFile.path]);
-
- const completions1 = service.externalProjects[0].getLanguageService().getCompletionsAtPosition(f1.path, 2, { includeExternalModuleExports: false });
- // should contain completions for string
- assert.isTrue(completions1.entries.some(e => e.name === "charAt"), "should contain 'charAt'");
- assert.isFalse(completions1.entries.some(e => e.name === "toExponential"), "should not contain 'toExponential'");
-
- service.closeClientFile(f2.path);
- const completions2 = service.externalProjects[0].getLanguageService().getCompletionsAtPosition(f1.path, 2, { includeExternalModuleExports: false });
- // should contain completions for string
- assert.isFalse(completions2.entries.some(e => e.name === "charAt"), "should not contain 'charAt'");
- assert.isTrue(completions2.entries.some(e => e.name === "toExponential"), "should contain 'toExponential'");
- });
-
- it("clear mixed content file after closing", () => {
- const f1 = {
- path: "/a/b/app.ts",
- content: " "
- };
- const f2 = {
- path: "/a/b/lib.html",
- content: ""
- };
-
- const host = createServerHost([f1, f2, libFile]);
- const service = createProjectService(host);
- service.openExternalProject({ projectFileName: "/a/b/project", rootFiles: [{ fileName: f1.path }, { fileName: f2.path, hasMixedContent: true }], options: {} });
-
- service.openClientFile(f1.path);
- service.openClientFile(f2.path, "let somelongname: string");
-
- service.checkNumberOfProjects({ externalProjects: 1 });
- checkProjectActualFiles(service.externalProjects[0], [f1.path, f2.path, libFile.path]);
-
- const completions1 = service.externalProjects[0].getLanguageService().getCompletionsAtPosition(f1.path, 0, { includeExternalModuleExports: false });
- assert.isTrue(completions1.entries.some(e => e.name === "somelongname"), "should contain 'somelongname'");
-
- service.closeClientFile(f2.path);
- const completions2 = service.externalProjects[0].getLanguageService().getCompletionsAtPosition(f1.path, 0, { includeExternalModuleExports: false });
- assert.isFalse(completions2.entries.some(e => e.name === "somelongname"), "should not contain 'somelongname'");
- const sf2 = service.externalProjects[0].getLanguageService().getProgram().getSourceFile(f2.path);
- assert.equal(sf2.text, "");
- });
-
-
- it("external project with included config file opened after configured project", () => {
- const file1 = {
- path: "/a/b/f1.ts",
- content: "let x = 1"
- };
- const configFile = {
- path: "/a/b/tsconfig.json",
- content: JSON.stringify({ compilerOptions: {} })
- };
- const externalProjectName = "externalproject";
- const host = createServerHost([file1, configFile]);
- const projectService = createProjectService(host);
-
- projectService.openClientFile(file1.path);
- checkNumberOfProjects(projectService, { configuredProjects: 1 });
-
- projectService.openExternalProject({
- rootFiles: toExternalFiles([configFile.path]),
- options: {},
- projectFileName: externalProjectName
- });
-
- checkNumberOfProjects(projectService, { configuredProjects: 1 });
-
- projectService.closeClientFile(file1.path);
- // configured project is alive since it is opened as part of external project
- checkNumberOfProjects(projectService, { configuredProjects: 1 });
-
- projectService.closeExternalProject(externalProjectName);
- checkNumberOfProjects(projectService, { configuredProjects: 0 });
- });
-
- it("external project with included config file opened after configured project and then closed", () => {
- const file1 = {
- path: "/a/b/f1.ts",
- content: "let x = 1"
- };
- const file2 = {
- path: "/a/f2.ts",
- content: "let x = 1"
- };
- const configFile = {
- path: "/a/b/tsconfig.json",
- content: JSON.stringify({ compilerOptions: {} })
- };
- const externalProjectName = "externalproject";
- const host = createServerHost([file1, file2, libFile, configFile]);
- const projectService = createProjectService(host);
-
- projectService.openClientFile(file1.path);
- checkNumberOfProjects(projectService, { configuredProjects: 1 });
- const project = projectService.configuredProjects.get(configFile.path);
-
- projectService.openExternalProject({
- rootFiles: toExternalFiles([configFile.path]),
- options: {},
- projectFileName: externalProjectName
- });
-
- checkNumberOfProjects(projectService, { configuredProjects: 1 });
- assert.strictEqual(projectService.configuredProjects.get(configFile.path), project);
-
- projectService.closeExternalProject(externalProjectName);
- // configured project is alive since file is still open
- checkNumberOfProjects(projectService, { configuredProjects: 1 });
- assert.strictEqual(projectService.configuredProjects.get(configFile.path), project);
-
- projectService.closeClientFile(file1.path);
- checkNumberOfProjects(projectService, { configuredProjects: 1 });
- assert.strictEqual(projectService.configuredProjects.get(configFile.path), project);
-
- projectService.openClientFile(file2.path);
- checkNumberOfProjects(projectService, { inferredProjects: 1 });
- assert.isUndefined(projectService.configuredProjects.get(configFile.path));
- });
-
- it("changes in closed files are reflected in project structure", () => {
- const file1 = {
- path: "/a/b/f1.ts",
- content: `export * from "./f2"`
- };
- const file2 = {
- path: "/a/b/f2.ts",
- content: `export let x = 1`
- };
- const file3 = {
- path: "/a/c/f3.ts",
- content: `export let y = 1;`
- };
- const host = createServerHost([file1, file2, file3]);
- const projectService = createProjectService(host);
-
- projectService.openClientFile(file1.path);
-
- checkNumberOfInferredProjects(projectService, 1);
- checkProjectActualFiles(projectService.inferredProjects[0], [file1.path, file2.path]);
-
- projectService.openClientFile(file3.path);
- checkNumberOfInferredProjects(projectService, 2);
- checkProjectActualFiles(projectService.inferredProjects[1], [file3.path]);
-
- const modifiedFile2 = {
- path: file2.path,
- content: `export * from "../c/f3"` // now inferred project should inclule file3
- };
-
- host.reloadFS([file1, modifiedFile2, file3]);
- host.checkTimeoutQueueLengthAndRun(2);
- checkNumberOfInferredProjects(projectService, 1);
- checkProjectActualFiles(projectService.inferredProjects[0], [file1.path, modifiedFile2.path, file3.path]);
- });
-
- it("deleted files affect project structure", () => {
- const file1 = {
- path: "/a/b/f1.ts",
- content: `export * from "./f2"`
- };
- const file2 = {
- path: "/a/b/f2.ts",
- content: `export * from "../c/f3"`
- };
- const file3 = {
- path: "/a/c/f3.ts",
- content: `export let y = 1;`
- };
- const host = createServerHost([file1, file2, file3]);
- const projectService = createProjectService(host);
-
- projectService.openClientFile(file1.path);
-
- checkNumberOfProjects(projectService, { inferredProjects: 1 });
-
- checkProjectActualFiles(projectService.inferredProjects[0], [file1.path, file2.path, file3.path]);
-
- projectService.openClientFile(file3.path);
- checkNumberOfProjects(projectService, { inferredProjects: 1 });
-
- host.reloadFS([file1, file3]);
- host.checkTimeoutQueueLengthAndRun(2);
-
- checkNumberOfProjects(projectService, { inferredProjects: 2 });
-
- checkProjectActualFiles(projectService.inferredProjects[0], [file1.path]);
- checkProjectActualFiles(projectService.inferredProjects[1], [file3.path]);
- });
-
- it("ignores files excluded by a custom safe type list", () => {
- const file1 = {
- path: "/a/b/f1.ts",
- content: "export let x = 5"
- };
- const office = {
- path: "/lib/duckquack-3.min.js",
- content: "whoa do @@ not parse me ok thanks!!!"
- };
- const host = createServerHost([file1, office, customTypesMap]);
- const projectService = createProjectService(host);
- try {
- projectService.openExternalProject({ projectFileName: "project", options: {}, rootFiles: toExternalFiles([file1.path, office.path]) });
- const proj = projectService.externalProjects[0];
- assert.deepEqual(proj.getFileNames(/*excludeFilesFromExternalLibraries*/ true), [file1.path]);
- assert.deepEqual(proj.getTypeAcquisition().include, ["duck-types"]);
- } finally {
- projectService.resetSafeList();
- }
- });
-
- it("ignores files excluded by the default type list", () => {
- const file1 = {
- path: "/a/b/f1.ts",
- content: "export let x = 5"
- };
- const minFile = {
- path: "/c/moment.min.js",
- content: "unspecified"
- };
- const kendoFile1 = {
- path: "/q/lib/kendo/kendo.all.min.js",
- content: "unspecified"
- };
- const kendoFile2 = {
- path: "/q/lib/kendo/kendo.ui.min.js",
- content: "unspecified"
- };
- const officeFile1 = {
- path: "/scripts/Office/1/excel-15.debug.js",
- content: "unspecified"
- };
- const officeFile2 = {
- path: "/scripts/Office/1/powerpoint.js",
- content: "unspecified"
- };
- const files = [file1, minFile, kendoFile1, kendoFile2, officeFile1, officeFile2];
- const host = createServerHost(files);
- const projectService = createProjectService(host);
- try {
- projectService.openExternalProject({ projectFileName: "project", options: {}, rootFiles: toExternalFiles(files.map(f => f.path)) });
- const proj = projectService.externalProjects[0];
- assert.deepEqual(proj.getFileNames(/*excludeFilesFromExternalLibraries*/ true), [file1.path]);
- assert.deepEqual(proj.getTypeAcquisition().include, ["kendo-ui", "office"]);
- } finally {
- projectService.resetSafeList();
- }
- });
-
- it("removes version numbers correctly", () => {
- const testData: [string, string][] = [
- ["jquery-max", "jquery-max"],
- ["jquery.min", "jquery"],
- ["jquery-min.4.2.3", "jquery"],
- ["jquery.min.4.2.1", "jquery"],
- ["minimum", "minimum"],
- ["min", "min"],
- ["min.3.2", "min"],
- ["jquery", "jquery"]
- ];
- for (const t of testData) {
- assert.equal(removeMinAndVersionNumbers(t[0]), t[1], t[0]);
- }
- });
-
- it("ignores files excluded by a legacy safe type list", () => {
- const file1 = {
- path: "/a/b/bliss.js",
- content: "let x = 5"
- };
- const file2 = {
- path: "/a/b/foo.js",
- content: ""
- };
- const host = createServerHost([file1, file2, customTypesMap]);
- const projectService = createProjectService(host);
- try {
- projectService.openExternalProject({ projectFileName: "project", options: {}, rootFiles: toExternalFiles([file1.path, file2.path]), typeAcquisition: { enable: true } });
- const proj = projectService.externalProjects[0];
- assert.deepEqual(proj.getFileNames(), [file2.path]);
- } finally {
- projectService.resetSafeList();
- }
- });
-
- it("open file become a part of configured project if it is referenced from root file", () => {
- const file1 = {
- path: "/a/b/f1.ts",
- content: "export let x = 5"
- };
- const file2 = {
- path: "/a/c/f2.ts",
- content: `import {x} from "../b/f1"`
- };
- const file3 = {
- path: "/a/c/f3.ts",
- content: "export let y = 1"
- };
- const configFile = {
- path: "/a/c/tsconfig.json",
- content: JSON.stringify({ compilerOptions: {}, files: ["f2.ts", "f3.ts"] })
- };
-
- const host = createServerHost([file1, file2, file3]);
- const projectService = createProjectService(host);
-
- projectService.openClientFile(file1.path);
- checkNumberOfProjects(projectService, { inferredProjects: 1 });
- checkProjectActualFiles(projectService.inferredProjects[0], [file1.path]);
-
- projectService.openClientFile(file3.path);
- checkNumberOfProjects(projectService, { inferredProjects: 2 });
- checkProjectActualFiles(projectService.inferredProjects[0], [file1.path]);
- checkProjectActualFiles(projectService.inferredProjects[1], [file3.path]);
-
- host.reloadFS([file1, file2, file3, configFile]);
- host.checkTimeoutQueueLengthAndRun(1);
- checkNumberOfProjects(projectService, { configuredProjects: 1 });
- checkProjectActualFiles(configuredProjectAt(projectService, 0), [file1.path, file2.path, file3.path, configFile.path]);
- });
-
- it("correctly migrate files between projects", () => {
- const file1 = {
- path: "/a/b/f1.ts",
- content: `
- export * from "../c/f2";
- export * from "../d/f3";`
- };
- const file2 = {
- path: "/a/c/f2.ts",
- content: "export let x = 1;"
- };
- const file3 = {
- path: "/a/d/f3.ts",
- content: "export let y = 1;"
- };
- const host = createServerHost([file1, file2, file3]);
- const projectService = createProjectService(host);
-
- projectService.openClientFile(file2.path);
- checkNumberOfProjects(projectService, { inferredProjects: 1 });
- checkProjectActualFiles(projectService.inferredProjects[0], [file2.path]);
-
- projectService.openClientFile(file3.path);
- checkNumberOfProjects(projectService, { inferredProjects: 2 });
- checkProjectActualFiles(projectService.inferredProjects[0], [file2.path]);
- checkProjectActualFiles(projectService.inferredProjects[1], [file3.path]);
-
- projectService.openClientFile(file1.path);
- checkNumberOfProjects(projectService, { inferredProjects: 1 });
- checkProjectRootFiles(projectService.inferredProjects[0], [file1.path]);
- checkProjectActualFiles(projectService.inferredProjects[0], [file1.path, file2.path, file3.path]);
-
- projectService.closeClientFile(file1.path);
- checkNumberOfProjects(projectService, { inferredProjects: 2 });
- });
-
- it("can correctly update configured project when set of root files has changed (new file on disk)", () => {
- const file1 = {
- path: "/a/b/f1.ts",
- content: "let x = 1"
- };
- const file2 = {
- path: "/a/b/f2.ts",
- content: "let y = 1"
- };
- const configFile = {
- path: "/a/b/tsconfig.json",
- content: JSON.stringify({ compilerOptions: {} })
- };
-
- const host = createServerHost([file1, configFile]);
- const projectService = createProjectService(host);
-
- projectService.openClientFile(file1.path);
- checkNumberOfProjects(projectService, { configuredProjects: 1 });
- checkProjectActualFiles(configuredProjectAt(projectService, 0), [file1.path, configFile.path]);
-
- host.reloadFS([file1, file2, configFile]);
-
- host.checkTimeoutQueueLengthAndRun(2);
-
- checkNumberOfProjects(projectService, { configuredProjects: 1 });
- checkProjectRootFiles(configuredProjectAt(projectService, 0), [file1.path, file2.path]);
- });
-
- it("can correctly update configured project when set of root files has changed (new file in list of files)", () => {
- const file1 = {
- path: "/a/b/f1.ts",
- content: "let x = 1"
- };
- const file2 = {
- path: "/a/b/f2.ts",
- content: "let y = 1"
- };
- const configFile = {
- path: "/a/b/tsconfig.json",
- content: JSON.stringify({ compilerOptions: {}, files: ["f1.ts"] })
- };
-
- const host = createServerHost([file1, file2, configFile]);
- const projectService = createProjectService(host);
-
- projectService.openClientFile(file1.path);
- checkNumberOfProjects(projectService, { configuredProjects: 1 });
- checkProjectActualFiles(configuredProjectAt(projectService, 0), [file1.path, configFile.path]);
-
- const modifiedConfigFile = {
- path: configFile.path,
- content: JSON.stringify({ compilerOptions: {}, files: ["f1.ts", "f2.ts"] })
- };
-
- host.reloadFS([file1, file2, modifiedConfigFile]);
-
- checkNumberOfProjects(projectService, { configuredProjects: 1 });
- host.checkTimeoutQueueLengthAndRun(2);
- checkProjectRootFiles(configuredProjectAt(projectService, 0), [file1.path, file2.path]);
- });
-
- it("can update configured project when set of root files was not changed", () => {
- const file1 = {
- path: "/a/b/f1.ts",
- content: "let x = 1"
- };
- const file2 = {
- path: "/a/b/f2.ts",
- content: "let y = 1"
- };
- const configFile = {
- path: "/a/b/tsconfig.json",
- content: JSON.stringify({ compilerOptions: {}, files: ["f1.ts", "f2.ts"] })
- };
-
- const host = createServerHost([file1, file2, configFile]);
- const projectService = createProjectService(host);
-
- projectService.openClientFile(file1.path);
- checkNumberOfProjects(projectService, { configuredProjects: 1 });
- checkProjectActualFiles(configuredProjectAt(projectService, 0), [file1.path, file2.path, configFile.path]);
-
- const modifiedConfigFile = {
- path: configFile.path,
- content: JSON.stringify({ compilerOptions: { outFile: "out.js" }, files: ["f1.ts", "f2.ts"] })
- };
-
- host.reloadFS([file1, file2, modifiedConfigFile]);
-
- checkNumberOfProjects(projectService, { configuredProjects: 1 });
- checkProjectRootFiles(configuredProjectAt(projectService, 0), [file1.path, file2.path]);
- });
-
- it("can correctly update external project when set of root files has changed", () => {
- const file1 = {
- path: "/a/b/f1.ts",
- content: "let x = 1"
- };
- const file2 = {
- path: "/a/b/f2.ts",
- content: "let y = 1"
- };
- const host = createServerHost([file1, file2]);
- const projectService = createProjectService(host);
-
- projectService.openExternalProject({ projectFileName: "project", options: {}, rootFiles: toExternalFiles([file1.path]) });
- checkNumberOfProjects(projectService, { externalProjects: 1 });
- checkProjectActualFiles(projectService.externalProjects[0], [file1.path]);
-
- projectService.openExternalProject({ projectFileName: "project", options: {}, rootFiles: toExternalFiles([file1.path, file2.path]) });
- checkNumberOfProjects(projectService, { externalProjects: 1 });
- checkProjectRootFiles(projectService.externalProjects[0], [file1.path, file2.path]);
- });
-
- it("can update external project when set of root files was not changed", () => {
- const file1 = {
- path: "/a/b/f1.ts",
- content: `export * from "m"`
- };
- const file2 = {
- path: "/a/b/f2.ts",
- content: "export let y = 1"
- };
- const file3 = {
- path: "/a/m.ts",
- content: "export let y = 1"
- };
-
- const host = createServerHost([file1, file2, file3]);
- const projectService = createProjectService(host);
-
- projectService.openExternalProject({ projectFileName: "project", options: { moduleResolution: ModuleResolutionKind.NodeJs }, rootFiles: toExternalFiles([file1.path, file2.path]) });
- checkNumberOfProjects(projectService, { externalProjects: 1 });
- checkProjectRootFiles(projectService.externalProjects[0], [file1.path, file2.path]);
- checkProjectActualFiles(projectService.externalProjects[0], [file1.path, file2.path]);
-
- projectService.openExternalProject({ projectFileName: "project", options: { moduleResolution: ModuleResolutionKind.Classic }, rootFiles: toExternalFiles([file1.path, file2.path]) });
- checkNumberOfProjects(projectService, { externalProjects: 1 });
- checkProjectRootFiles(projectService.externalProjects[0], [file1.path, file2.path]);
- checkProjectActualFiles(projectService.externalProjects[0], [file1.path, file2.path, file3.path]);
- });
-
- it("regression test for crash in acquireOrUpdateDocument", () => {
- const tsFile = {
- fileName: "/a/b/file1.ts",
- path: "/a/b/file1.ts",
- content: ""
- };
- const jsFile = {
- path: "/a/b/file1.js",
- content: "var x = 10;",
- fileName: "/a/b/file1.js",
- scriptKind: "JS" as "JS"
- };
-
- const host = createServerHost([]);
- const projectService = createProjectService(host);
- projectService.applyChangesInOpenFiles([tsFile], [], []);
- const projs = projectService.synchronizeProjectList([]);
- projectService.findProject(projs[0].info.projectName).getLanguageService().getNavigationBarItems(tsFile.fileName);
- projectService.synchronizeProjectList([projs[0].info]);
- projectService.applyChangesInOpenFiles([jsFile], [], []);
- });
-
- it("config file is deleted", () => {
- const file1 = {
- path: "/a/b/f1.ts",
- content: "let x = 1;"
- };
- const file2 = {
- path: "/a/b/f2.ts",
- content: "let y = 2;"
- };
- const config = {
- path: "/a/b/tsconfig.json",
- content: JSON.stringify({ compilerOptions: {} })
- };
- const host = createServerHost([file1, file2, config]);
- const projectService = createProjectService(host);
-
- projectService.openClientFile(file1.path);
- checkNumberOfProjects(projectService, { configuredProjects: 1 });
- checkProjectActualFiles(configuredProjectAt(projectService, 0), [file1.path, file2.path, config.path]);
-
- projectService.openClientFile(file2.path);
- checkNumberOfProjects(projectService, { configuredProjects: 1 });
- checkProjectActualFiles(configuredProjectAt(projectService, 0), [file1.path, file2.path, config.path]);
-
- host.reloadFS([file1, file2]);
- host.checkTimeoutQueueLengthAndRun(1);
- checkNumberOfProjects(projectService, { inferredProjects: 2 });
- checkProjectActualFiles(projectService.inferredProjects[0], [file1.path]);
- checkProjectActualFiles(projectService.inferredProjects[1], [file2.path]);
- });
-
- it("loading files with correct priority", () => {
- const f1 = {
- path: "/a/main.ts",
- content: "let x = 1"
- };
- const f2 = {
- path: "/a/main.js",
- content: "var y = 1"
- };
- const config = {
- path: "/a/tsconfig.json",
- content: JSON.stringify({
- compilerOptions: { allowJs: true }
- })
- };
- const host = createServerHost([f1, f2, config]);
- const projectService = createProjectService(host);
- projectService.setHostConfiguration({
- extraFileExtensions: [
- { extension: ".js", isMixedContent: false },
- { extension: ".html", isMixedContent: true }
- ]
- });
- projectService.openClientFile(f1.path);
- projectService.checkNumberOfProjects({ configuredProjects: 1 });
- checkProjectActualFiles(configuredProjectAt(projectService, 0), [f1.path, config.path]);
-
- // Should close configured project with next file open
- projectService.closeClientFile(f1.path);
-
- projectService.openClientFile(f2.path);
- projectService.checkNumberOfProjects({ inferredProjects: 1 });
- assert.isUndefined(projectService.configuredProjects.get(config.path));
- checkProjectActualFiles(projectService.inferredProjects[0], [f2.path]);
- });
-
- it("tsconfig script block support", () => {
- const file1 = {
- path: "/a/b/f1.ts",
- content: ` `
- };
- const file2 = {
- path: "/a/b/f2.html",
- content: `var hello = "hello";`
- };
- const config = {
- path: "/a/b/tsconfig.json",
- content: JSON.stringify({ compilerOptions: { allowJs: true } })
- };
- const host = createServerHost([file1, file2, config]);
- const session = createSession(host);
- openFilesForSession([file1], session);
- const projectService = session.getProjectService();
-
- // HTML file will not be included in any projects yet
- checkNumberOfProjects(projectService, { configuredProjects: 1 });
- const configuredProj = configuredProjectAt(projectService, 0);
- checkProjectActualFiles(configuredProj, [file1.path, config.path]);
-
- // Specify .html extension as mixed content
- const extraFileExtensions = [{ extension: ".html", scriptKind: ScriptKind.JS, isMixedContent: true }];
- const configureHostRequest = makeSessionRequest(CommandNames.Configure, { extraFileExtensions });
- session.executeCommand(configureHostRequest);
-
- // The configured project should now be updated to include html file
- checkNumberOfProjects(projectService, { configuredProjects: 1 });
- assert.strictEqual(configuredProjectAt(projectService, 0), configuredProj, "Same configured project should be updated");
- checkProjectActualFiles(configuredProjectAt(projectService, 0), [file1.path, file2.path, config.path]);
-
- // Open HTML file
- projectService.applyChangesInOpenFiles(
- /*openFiles*/[{ fileName: file2.path, hasMixedContent: true, scriptKind: ScriptKind.JS, content: `var hello = "hello";` }],
- /*changedFiles*/ undefined,
- /*closedFiles*/ undefined);
-
- // Now HTML file is included in the project
- checkNumberOfProjects(projectService, { configuredProjects: 1 });
- checkProjectActualFiles(configuredProjectAt(projectService, 0), [file1.path, file2.path, config.path]);
-
- // Check identifiers defined in HTML content are available in .ts file
- const project = configuredProjectAt(projectService, 0);
- let completions = project.getLanguageService().getCompletionsAtPosition(file1.path, 1, { includeExternalModuleExports: false });
- assert(completions && completions.entries[0].name === "hello", `expected entry hello to be in completion list`);
-
- // Close HTML file
- projectService.applyChangesInOpenFiles(
- /*openFiles*/ undefined,
- /*changedFiles*/ undefined,
- /*closedFiles*/[file2.path]);
-
- // HTML file is still included in project
- checkNumberOfProjects(projectService, { configuredProjects: 1 });
- checkProjectActualFiles(configuredProjectAt(projectService, 0), [file1.path, file2.path, config.path]);
-
- // Check identifiers defined in HTML content are not available in .ts file
- completions = project.getLanguageService().getCompletionsAtPosition(file1.path, 5, { includeExternalModuleExports: false });
- assert(completions && completions.entries[0].name !== "hello", `unexpected hello entry in completion list`);
- });
-
- it("no tsconfig script block diagnostic errors", () => {
-
- // #1. Ensure no diagnostic errors when allowJs is true
- const file1 = {
- path: "/a/b/f1.ts",
- content: ` `
- };
- const file2 = {
- path: "/a/b/f2.html",
- content: `var hello = "hello";`
- };
- const config1 = {
- path: "/a/b/tsconfig.json",
- content: JSON.stringify({ compilerOptions: { allowJs: true } })
- };
-
- let host = createServerHost([file1, file2, config1, libFile], { executingFilePath: combinePaths(getDirectoryPath(libFile.path), "tsc.js") });
- let session = createSession(host);
-
- // Specify .html extension as mixed content in a configure host request
- const extraFileExtensions = [{ extension: ".html", scriptKind: ScriptKind.JS, isMixedContent: true }];
- const configureHostRequest = makeSessionRequest(CommandNames.Configure, { extraFileExtensions });
- session.executeCommand(configureHostRequest);
-
- openFilesForSession([file1], session);
- let projectService = session.getProjectService();
-
- checkNumberOfProjects(projectService, { configuredProjects: 1 });
-
- let diagnostics = configuredProjectAt(projectService, 0).getLanguageService().getCompilerOptionsDiagnostics();
- assert.deepEqual(diagnostics, []);
-
- // #2. Ensure no errors when allowJs is false
- const config2 = {
- path: "/a/b/tsconfig.json",
- content: JSON.stringify({ compilerOptions: { allowJs: false } })
- };
-
- host = createServerHost([file1, file2, config2, libFile], { executingFilePath: combinePaths(getDirectoryPath(libFile.path), "tsc.js") });
- session = createSession(host);
-
- session.executeCommand(configureHostRequest);
-
- openFilesForSession([file1], session);
- projectService = session.getProjectService();
-
- checkNumberOfProjects(projectService, { configuredProjects: 1 });
-
- diagnostics = configuredProjectAt(projectService, 0).getLanguageService().getCompilerOptionsDiagnostics();
- assert.deepEqual(diagnostics, []);
-
- // #3. Ensure no errors when compiler options aren't specified
- const config3 = {
- path: "/a/b/tsconfig.json",
- content: JSON.stringify({})
- };
-
- host = createServerHost([file1, file2, config3, libFile], { executingFilePath: combinePaths(getDirectoryPath(libFile.path), "tsc.js") });
- session = createSession(host);
-
- session.executeCommand(configureHostRequest);
-
- openFilesForSession([file1], session);
- projectService = session.getProjectService();
-
- checkNumberOfProjects(projectService, { configuredProjects: 1 });
-
- diagnostics = configuredProjectAt(projectService, 0).getLanguageService().getCompilerOptionsDiagnostics();
- assert.deepEqual(diagnostics, []);
-
- // #4. Ensure no errors when files are explicitly specified in tsconfig
- const config4 = {
- path: "/a/b/tsconfig.json",
- content: JSON.stringify({ compilerOptions: { allowJs: true }, files: [file1.path, file2.path] })
- };
-
- host = createServerHost([file1, file2, config4, libFile], { executingFilePath: combinePaths(getDirectoryPath(libFile.path), "tsc.js") });
- session = createSession(host);
-
- session.executeCommand(configureHostRequest);
-
- openFilesForSession([file1], session);
- projectService = session.getProjectService();
-
- checkNumberOfProjects(projectService, { configuredProjects: 1 });
-
- diagnostics = configuredProjectAt(projectService, 0).getLanguageService().getCompilerOptionsDiagnostics();
- assert.deepEqual(diagnostics, []);
-
- // #4. Ensure no errors when files are explicitly excluded in tsconfig
- const config5 = {
- path: "/a/b/tsconfig.json",
- content: JSON.stringify({ compilerOptions: { allowJs: true }, exclude: [file2.path] })
- };
-
- host = createServerHost([file1, file2, config5, libFile], { executingFilePath: combinePaths(getDirectoryPath(libFile.path), "tsc.js") });
- session = createSession(host);
-
- session.executeCommand(configureHostRequest);
-
- openFilesForSession([file1], session);
- projectService = session.getProjectService();
-
- checkNumberOfProjects(projectService, { configuredProjects: 1 });
-
- diagnostics = configuredProjectAt(projectService, 0).getLanguageService().getCompilerOptionsDiagnostics();
- assert.deepEqual(diagnostics, []);
- });
-
- it("project structure update is deferred if files are not added\removed", () => {
- const file1 = {
- path: "/a/b/f1.ts",
- content: `import {x} from "./f2"`
- };
- const file2 = {
- path: "/a/b/f2.ts",
- content: "export let x = 1"
- };
- const host = createServerHost([file1, file2]);
- const projectService = createProjectService(host);
-
- projectService.openClientFile(file1.path);
- projectService.openClientFile(file2.path);
-
- checkNumberOfProjects(projectService, { inferredProjects: 1 });
- projectService.applyChangesInOpenFiles(
- /*openFiles*/ undefined,
- /*changedFiles*/[{ fileName: file1.path, changes: [{ span: createTextSpan(0, file1.path.length), newText: "let y = 1" }] }],
- /*closedFiles*/ undefined);
-
- checkNumberOfProjects(projectService, { inferredProjects: 1 });
- const changedFiles = projectService.getChangedFiles_TestOnly();
- assert(changedFiles && changedFiles.length === 1, `expected 1 changed file, got ${JSON.stringify(changedFiles && changedFiles.length || 0)}`);
-
- projectService.ensureInferredProjectsUpToDate_TestOnly();
- checkNumberOfProjects(projectService, { inferredProjects: 2 });
- });
-
- it("files with mixed content are handled correctly", () => {
- const file1 = {
- path: "/a/b/f1.html",
- content: `]