Merge branch 'master' into typesVersions

This commit is contained in:
Ron Buckton
2018-08-28 17:37:23 -07:00
225 changed files with 43601 additions and 32361 deletions
+1
View File
@@ -81,6 +81,7 @@
"unittests/transform.ts",
"unittests/transpile.ts",
"unittests/tsbuild.ts",
"unittests/tsbuildWatchMode.ts",
"unittests/tsconfigParsing.ts",
"unittests/tscWatchMode.ts",
"unittests/versionCache.ts",
+48 -90
View File
@@ -1,15 +1,5 @@
namespace ts {
let currentTime = 100;
let lastDiagnostics: Diagnostic[] = [];
const reportDiagnostic: DiagnosticReporter = diagnostic => lastDiagnostics.push(diagnostic);
const report = (message: DiagnosticMessage, ...args: string[]) => reportDiagnostic(createCompilerDiagnostic(message, ...args));
const buildHost: BuildHost = {
error: report,
verbose: report,
message: report,
errorDiagnostic: d => reportDiagnostic(d)
};
export namespace Sample1 {
tick();
const projFs = loadProjectFromDisk("tests/projects/sample1");
@@ -21,12 +11,12 @@ namespace ts {
describe("tsbuild - sanity check of clean build of 'sample1' project", () => {
it("can build the sample project 'sample1' without error", () => {
const fs = projFs.shadow();
const host = new fakes.CompilerHost(fs);
const builder = createSolutionBuilder(host, buildHost, ["/src/tests"], { dry: false, force: false, verbose: false });
const host = new fakes.SolutionBuilderHost(fs);
const builder = createSolutionBuilder(host, ["/src/tests"], { dry: false, force: false, verbose: false });
clearDiagnostics();
host.clearDiagnostics();
builder.buildAllProjects();
assertDiagnosticMessages(/*empty*/);
host.assertDiagnosticMessages(/*empty*/);
// Check for outputs. Not an exhaustive list
for (const output of allExpectedOutputs) {
@@ -37,12 +27,11 @@ namespace ts {
describe("tsbuild - dry builds", () => {
it("doesn't write any files in a dry build", () => {
clearDiagnostics();
const fs = projFs.shadow();
const host = new fakes.CompilerHost(fs);
const builder = createSolutionBuilder(host, buildHost, ["/src/tests"], { dry: true, force: false, verbose: false });
const host = new fakes.SolutionBuilderHost(fs);
const builder = createSolutionBuilder(host, ["/src/tests"], { dry: true, force: false, verbose: false });
builder.buildAllProjects();
assertDiagnosticMessages(Diagnostics.A_non_dry_build_would_build_project_0, Diagnostics.A_non_dry_build_would_build_project_0, Diagnostics.A_non_dry_build_would_build_project_0);
host.assertDiagnosticMessages(Diagnostics.A_non_dry_build_would_build_project_0, Diagnostics.A_non_dry_build_would_build_project_0, Diagnostics.A_non_dry_build_would_build_project_0);
// Check for outputs to not be written. Not an exhaustive list
for (const output of allExpectedOutputs) {
@@ -51,28 +40,26 @@ namespace ts {
});
it("indicates that it would skip builds during a dry build", () => {
clearDiagnostics();
const fs = projFs.shadow();
const host = new fakes.CompilerHost(fs);
const host = new fakes.SolutionBuilderHost(fs);
let builder = createSolutionBuilder(host, buildHost, ["/src/tests"], { dry: false, force: false, verbose: false });
let builder = createSolutionBuilder(host, ["/src/tests"], { dry: false, force: false, verbose: false });
builder.buildAllProjects();
tick();
clearDiagnostics();
builder = createSolutionBuilder(host, buildHost, ["/src/tests"], { dry: true, force: false, verbose: false });
host.clearDiagnostics();
builder = createSolutionBuilder(host, ["/src/tests"], { dry: true, force: false, verbose: false });
builder.buildAllProjects();
assertDiagnosticMessages(Diagnostics.Project_0_is_up_to_date, Diagnostics.Project_0_is_up_to_date, Diagnostics.Project_0_is_up_to_date);
host.assertDiagnosticMessages(Diagnostics.Project_0_is_up_to_date, Diagnostics.Project_0_is_up_to_date, Diagnostics.Project_0_is_up_to_date);
});
});
describe("tsbuild - clean builds", () => {
it("removes all files it built", () => {
clearDiagnostics();
const fs = projFs.shadow();
const host = new fakes.CompilerHost(fs);
const host = new fakes.SolutionBuilderHost(fs);
const builder = createSolutionBuilder(host, buildHost, ["/src/tests"], { dry: false, force: false, verbose: false });
const builder = createSolutionBuilder(host, ["/src/tests"], { dry: false, force: false, verbose: false });
builder.buildAllProjects();
// Verify they exist
for (const output of allExpectedOutputs) {
@@ -91,9 +78,9 @@ namespace ts {
describe("tsbuild - force builds", () => {
it("always builds under --force", () => {
const fs = projFs.shadow();
const host = new fakes.CompilerHost(fs);
const host = new fakes.SolutionBuilderHost(fs);
const builder = createSolutionBuilder(host, buildHost, ["/src/tests"], { dry: false, force: true, verbose: false });
const builder = createSolutionBuilder(host, ["/src/tests"], { dry: false, force: true, verbose: false });
builder.buildAllProjects();
let currentTime = time();
checkOutputTimestamps(currentTime);
@@ -116,14 +103,14 @@ namespace ts {
describe("tsbuild - can detect when and what to rebuild", () => {
const fs = projFs.shadow();
const host = new fakes.CompilerHost(fs);
const builder = createSolutionBuilder(host, buildHost, ["/src/tests"], { dry: false, force: false, verbose: true });
const host = new fakes.SolutionBuilderHost(fs);
const builder = createSolutionBuilder(host, ["/src/tests"], { dry: false, force: false, verbose: true });
it("Builds the project", () => {
clearDiagnostics();
host.clearDiagnostics();
builder.resetBuildContext();
builder.buildAllProjects();
assertDiagnosticMessages(Diagnostics.Projects_in_this_build_Colon_0,
host.assertDiagnosticMessages(Diagnostics.Projects_in_this_build_Colon_0,
Diagnostics.Project_0_is_out_of_date_because_output_file_1_does_not_exist,
Diagnostics.Building_project_0,
Diagnostics.Project_0_is_out_of_date_because_output_file_1_does_not_exist,
@@ -135,10 +122,10 @@ namespace ts {
// All three projects are up to date
it("Detects that all projects are up to date", () => {
clearDiagnostics();
host.clearDiagnostics();
builder.resetBuildContext();
builder.buildAllProjects();
assertDiagnosticMessages(Diagnostics.Projects_in_this_build_Colon_0,
host.assertDiagnosticMessages(Diagnostics.Projects_in_this_build_Colon_0,
Diagnostics.Project_0_is_up_to_date_because_newest_input_1_is_older_than_oldest_output_2,
Diagnostics.Project_0_is_up_to_date_because_newest_input_1_is_older_than_oldest_output_2,
Diagnostics.Project_0_is_up_to_date_because_newest_input_1_is_older_than_oldest_output_2);
@@ -147,12 +134,12 @@ namespace ts {
// Update a file in the leaf node (tests), only it should rebuild the last one
it("Only builds the leaf node project", () => {
clearDiagnostics();
host.clearDiagnostics();
fs.writeFileSync("/src/tests/index.ts", "const m = 10;");
builder.resetBuildContext();
builder.buildAllProjects();
assertDiagnosticMessages(Diagnostics.Projects_in_this_build_Colon_0,
host.assertDiagnosticMessages(Diagnostics.Projects_in_this_build_Colon_0,
Diagnostics.Project_0_is_up_to_date_because_newest_input_1_is_older_than_oldest_output_2,
Diagnostics.Project_0_is_up_to_date_because_newest_input_1_is_older_than_oldest_output_2,
Diagnostics.Project_0_is_out_of_date_because_oldest_output_1_is_older_than_newest_input_2,
@@ -162,12 +149,12 @@ namespace ts {
// Update a file in the parent (without affecting types), should get fast downstream builds
it("Detects type-only changes in upstream projects", () => {
clearDiagnostics();
host.clearDiagnostics();
replaceText(fs, "/src/core/index.ts", "HELLO WORLD", "WELCOME PLANET");
builder.resetBuildContext();
builder.buildAllProjects();
assertDiagnosticMessages(Diagnostics.Projects_in_this_build_Colon_0,
host.assertDiagnosticMessages(Diagnostics.Projects_in_this_build_Colon_0,
Diagnostics.Project_0_is_out_of_date_because_oldest_output_1_is_older_than_newest_input_2,
Diagnostics.Building_project_0,
Diagnostics.Project_0_is_up_to_date_with_d_ts_files_from_its_dependencies,
@@ -180,15 +167,13 @@ namespace ts {
describe("tsbuild - downstream-blocked compilations", () => {
it("won't build downstream projects if upstream projects have errors", () => {
const fs = projFs.shadow();
const host = new fakes.CompilerHost(fs);
const builder = createSolutionBuilder(host, buildHost, ["/src/tests"], { dry: false, force: false, verbose: true });
clearDiagnostics();
const host = new fakes.SolutionBuilderHost(fs);
const builder = createSolutionBuilder(host, ["/src/tests"], { dry: false, force: false, verbose: true });
// Induce an error in the middle project
replaceText(fs, "/src/logic/index.ts", "c.multiply(10, 15)", `c.muitply()`);
builder.buildAllProjects();
assertDiagnosticMessages(
host.assertDiagnosticMessages(
Diagnostics.Projects_in_this_build_Colon_0,
Diagnostics.Project_0_is_out_of_date_because_output_file_1_does_not_exist,
Diagnostics.Building_project_0,
@@ -204,12 +189,11 @@ namespace ts {
describe("tsbuild - project invalidation", () => {
it("invalidates projects correctly", () => {
const fs = projFs.shadow();
const host = new fakes.CompilerHost(fs);
const builder = createSolutionBuilder(host, buildHost, ["/src/tests"], { dry: false, force: false, verbose: false });
const host = new fakes.SolutionBuilderHost(fs);
const builder = createSolutionBuilder(host, ["/src/tests"], { dry: false, force: false, verbose: false });
clearDiagnostics();
builder.buildAllProjects();
assertDiagnosticMessages(/*empty*/);
host.assertDiagnosticMessages(/*empty*/);
// Update a timestamp in the middle project
tick();
@@ -221,14 +205,14 @@ namespace ts {
// Rebuild this project
tick();
builder.invalidateProject("/src/logic");
builder.buildInvalidatedProjects();
builder.buildInvalidatedProject();
// The file should be updated
assert.equal(fs.statSync("/src/logic/index.js").mtimeMs, time(), "JS file should have been rebuilt");
assert.isBelow(fs.statSync("/src/tests/index.js").mtimeMs, time(), "Downstream JS file should *not* have been rebuilt");
// Build downstream projects should update 'tests', but not 'core'
tick();
builder.buildDependentInvalidatedProjects();
builder.buildInvalidatedProject();
assert.equal(fs.statSync("/src/tests/index.js").mtimeMs, time(), "Downstream JS file should have been rebuilt");
assert.isBelow(fs.statSync("/src/core/index.js").mtimeMs, time(), "Upstream JS file should not have been rebuilt");
});
@@ -240,11 +224,10 @@ namespace ts {
function verifyProjectWithResolveJsonModule(configFile: string, ...expectedDiagnosticMessages: DiagnosticMessage[]) {
const fs = projFs.shadow();
const host = new fakes.CompilerHost(fs);
const builder = createSolutionBuilder(host, buildHost, [configFile], { dry: false, force: false, verbose: false });
clearDiagnostics();
const host = new fakes.SolutionBuilderHost(fs);
const builder = createSolutionBuilder(host, [configFile], { dry: false, force: false, verbose: false });
builder.buildAllProjects();
assertDiagnosticMessages(...expectedDiagnosticMessages);
host.assertDiagnosticMessages(...expectedDiagnosticMessages);
if (!expectedDiagnosticMessages.length) {
// Check for outputs. Not an exhaustive list
for (const output of allExpectedOutputs) {
@@ -274,11 +257,11 @@ namespace ts {
let fs: vfs.FileSystem | undefined;
before(() => {
fs = outFileFs.shadow();
const host = new fakes.CompilerHost(fs);
const builder = createSolutionBuilder(host, buildHost, ["/src/third"], { dry: false, force: false, verbose: false });
clearDiagnostics();
const host = new fakes.SolutionBuilderHost(fs);
const builder = createSolutionBuilder(host, ["/src/third"], { dry: false, force: false, verbose: false });
host.clearDiagnostics();
builder.buildAllProjects();
assertDiagnosticMessages(/*none*/);
host.assertDiagnosticMessages(/*none*/);
});
after(() => {
fs = undefined;
@@ -293,25 +276,24 @@ namespace ts {
describe("tsbuild - downstream prepend projects always get rebuilt", () => {
it("", () => {
const fs = outFileFs.shadow();
const host = new fakes.CompilerHost(fs);
const builder = createSolutionBuilder(host, buildHost, ["/src/third"], { dry: false, force: false, verbose: false });
clearDiagnostics();
const host = new fakes.SolutionBuilderHost(fs);
const builder = createSolutionBuilder(host, ["/src/third"], { dry: false, force: false, verbose: false });
builder.buildAllProjects();
assertDiagnosticMessages(/*none*/);
host.assertDiagnosticMessages(/*none*/);
assert.equal(fs.statSync("src/third/thirdjs/output/third-output.js").mtimeMs, time(), "First build timestamp is correct");
tick();
replaceText(fs, "src/first/first_PART1.ts", "Hello", "Hola");
tick();
builder.resetBuildContext();
builder.buildAllProjects();
assertDiagnosticMessages(/*none*/);
host.assertDiagnosticMessages(/*none*/);
assert.equal(fs.statSync("src/third/thirdjs/output/third-output.js").mtimeMs, time(), "Second build timestamp is correct");
});
});
}
describe("tsbuild - graph-ordering", () => {
let host: fakes.CompilerHost | undefined;
let host: fakes.SolutionBuilderHost | undefined;
const deps: [string, string][] = [
["A", "B"],
["B", "C"],
@@ -324,7 +306,7 @@ namespace ts {
before(() => {
const fs = new vfs.FileSystem(false);
host = new fakes.CompilerHost(fs);
host = new fakes.SolutionBuilderHost(fs);
writeProjects(fs, ["A", "B", "C", "D", "E", "F", "G"], deps);
});
@@ -349,7 +331,7 @@ namespace ts {
});
function checkGraphOrdering(rootNames: string[], expectedBuildSet: string[]) {
const builder = createSolutionBuilder(host!, buildHost, rootNames, { dry: true, force: false, verbose: false });
const builder = createSolutionBuilder(host!, rootNames, { dry: true, force: false, verbose: false });
const projFileNames = rootNames.map(getProjectFileName);
const graph = builder.getBuildGraph(projFileNames);
@@ -404,30 +386,6 @@ namespace ts {
fs.writeFileSync(path, newContent, "utf-8");
}
function assertDiagnosticMessages(...expected: DiagnosticMessage[]) {
const actual = lastDiagnostics.slice();
if (actual.length !== expected.length) {
assert.fail<any>(actual, expected, `Diagnostic arrays did not match - got\r\n${actual.map(a => " " + a.messageText).join("\r\n")}\r\nexpected\r\n${expected.map(e => " " + e.message).join("\r\n")}`);
}
for (let i = 0; i < actual.length; i++) {
if (actual[i].code !== expected[i].code) {
assert.fail(actual[i].messageText, expected[i].message, `Mismatched error code - expected diagnostic ${i} "${actual[i].messageText}" to match ${expected[i].message}`);
}
}
}
function clearDiagnostics() {
lastDiagnostics = [];
}
export function printDiagnostics(header = "== Diagnostics ==") {
const out = createDiagnosticReporter(sys);
sys.write(header + "\r\n");
for (const d of lastDiagnostics) {
out(d);
}
}
function tick() {
currentTime += 60_000;
}
@@ -0,0 +1,133 @@
namespace ts.tscWatch {
export import libFile = TestFSWithWatch.libFile;
function createSolutionBuilder(system: WatchedSystem, rootNames: ReadonlyArray<string>, defaultOptions?: BuildOptions) {
const host = createSolutionBuilderWithWatchHost(system);
return ts.createSolutionBuilder(host, rootNames, defaultOptions || { dry: false, force: false, verbose: false, watch: true });
}
function createSolutionBuilderWithWatch(host: WatchedSystem, rootNames: ReadonlyArray<string>, defaultOptions?: BuildOptions) {
const solutionBuilder = createSolutionBuilder(host, rootNames, defaultOptions);
solutionBuilder.buildAllProjects();
solutionBuilder.startWatching();
return solutionBuilder;
}
describe("tsbuild-watch program updates", () => {
const projectsLocation = "/user/username/projects";
const project = "sample1";
const enum SubProject {
core = "core",
logic = "logic",
tests = "tests",
ui = "ui"
}
type ReadonlyFile = Readonly<File>;
/** [tsconfig, index] | [tsconfig, index, anotherModule, someDecl] */
type SubProjectFiles = [ReadonlyFile, ReadonlyFile] | [ReadonlyFile, ReadonlyFile, ReadonlyFile, ReadonlyFile];
const root = Harness.IO.getWorkspaceRoot();
function projectFilePath(subProject: SubProject, baseFileName: string) {
return `${projectsLocation}/${project}/${subProject}/${baseFileName.toLowerCase()}`;
}
function projectFile(subProject: SubProject, baseFileName: string): File {
return {
path: projectFilePath(subProject, baseFileName),
content: Harness.IO.readFile(`${root}/tests/projects/${project}/${subProject}/${baseFileName}`)!
};
}
function subProjectFiles(subProject: SubProject, anotherModuleAndSomeDecl?: true): SubProjectFiles {
const tsconfig = projectFile(subProject, "tsconfig.json");
const index = projectFile(subProject, "index.ts");
if (!anotherModuleAndSomeDecl) {
return [tsconfig, index];
}
const anotherModule = projectFile(SubProject.core, "anotherModule.ts");
const someDecl = projectFile(SubProject.core, "some_decl.ts");
return [tsconfig, index, anotherModule, someDecl];
}
function getOutputFileNames(subProject: SubProject, baseFileNameWithoutExtension: string) {
const file = projectFilePath(subProject, baseFileNameWithoutExtension);
return [`${file}.js`, `${file}.d.ts`];
}
type OutputFileStamp = [string, Date | undefined];
function getOutputStamps(host: WatchedSystem, subProject: SubProject, baseFileNameWithoutExtension: string): OutputFileStamp[] {
return getOutputFileNames(subProject, baseFileNameWithoutExtension).map(f => [f, host.getModifiedTime(f)] as OutputFileStamp);
}
function getOutputFileStamps(host: WatchedSystem): OutputFileStamp[] {
return [
...getOutputStamps(host, SubProject.core, "anotherModule"),
...getOutputStamps(host, SubProject.core, "index"),
...getOutputStamps(host, SubProject.logic, "index"),
...getOutputStamps(host, SubProject.tests, "index"),
];
}
function verifyChangedFiles(actualStamps: OutputFileStamp[], oldTimeStamps: OutputFileStamp[], changedFiles: string[]) {
for (let i = 0; i < oldTimeStamps.length; i++) {
const actual = actualStamps[i];
const old = oldTimeStamps[i];
if (contains(changedFiles, actual[0])) {
assert.isTrue((actual[1] || 0) > (old[1] || 0), `${actual[0]} expected to written`);
}
else {
assert.equal(actual[1], old[1], `${actual[0]} expected to not change`);
}
}
}
const core = subProjectFiles(SubProject.core, /*anotherModuleAndSomeDecl*/ true);
const logic = subProjectFiles(SubProject.logic);
const tests = subProjectFiles(SubProject.tests);
const ui = subProjectFiles(SubProject.ui);
const allFiles: ReadonlyArray<File> = [libFile, ...core, ...logic, ...tests, ...ui];
const testProjectExpectedWatchedFiles = [core[0], core[1], core[2], ...logic, ...tests].map(f => f.path);
function createSolutionInWatchMode() {
const host = createWatchedSystem(allFiles, { currentDirectory: projectsLocation });
createSolutionBuilderWithWatch(host, [`${project}/${SubProject.tests}`]);
checkWatchedFiles(host, testProjectExpectedWatchedFiles);
checkWatchedDirectories(host, emptyArray, /*recursive*/ false);
checkWatchedDirectories(host, emptyArray, /*recursive*/ true); // TODO: #26524
checkOutputErrorsInitial(host, emptyArray);
const outputFileStamps = getOutputFileStamps(host);
for (const stamp of outputFileStamps) {
assert.isDefined(stamp[1], `${stamp[0]} expected to be present`);
}
return { host, outputFileStamps };
}
it("creates solution in watch mode", () => {
createSolutionInWatchMode();
});
it("change builds changes and reports found errors message", () => {
const { host, outputFileStamps } = createSolutionInWatchMode();
host.writeFile(core[1].path, `${core[1].content}
export class someClass { }`);
host.checkTimeoutQueueLengthAndRun(1); // Builds core
const changedCore = getOutputFileStamps(host);
verifyChangedFiles(changedCore, outputFileStamps, [
...getOutputFileNames(SubProject.core, "anotherModule"), // This should not be written really
...getOutputFileNames(SubProject.core, "index")
]);
host.checkTimeoutQueueLengthAndRun(1); // Builds tests
const changedTests = getOutputFileStamps(host);
verifyChangedFiles(changedTests, changedCore, [
...getOutputFileNames(SubProject.tests, "index") // Again these need not be written
]);
host.checkTimeoutQueueLengthAndRun(1); // Builds logic
const changedLogic = getOutputFileStamps(host);
verifyChangedFiles(changedLogic, changedTests, [
...getOutputFileNames(SubProject.logic, "index") // Again these need not be written
]);
host.checkTimeoutQueueLength(0);
checkOutputErrorsIncremental(host, emptyArray);
});
// TODO: write tests reporting errors but that will have more involved work since file
});
}
+15 -16
View File
@@ -1,17 +1,16 @@
namespace ts.tscWatch {
import WatchedSystem = TestFSWithWatch.TestServerHost;
type File = TestFSWithWatch.File;
type SymLink = TestFSWithWatch.SymLink;
import createWatchedSystem = TestFSWithWatch.createWatchedSystem;
import checkArray = TestFSWithWatch.checkArray;
import libFile = TestFSWithWatch.libFile;
import checkWatchedFiles = TestFSWithWatch.checkWatchedFiles;
import checkWatchedFilesDetailed = TestFSWithWatch.checkWatchedFilesDetailed;
import checkWatchedDirectories = TestFSWithWatch.checkWatchedDirectories;
import checkWatchedDirectoriesDetailed = TestFSWithWatch.checkWatchedDirectoriesDetailed;
import checkOutputContains = TestFSWithWatch.checkOutputContains;
import checkOutputDoesNotContain = TestFSWithWatch.checkOutputDoesNotContain;
import Tsc_WatchDirectory = TestFSWithWatch.Tsc_WatchDirectory;
export import WatchedSystem = TestFSWithWatch.TestServerHost;
export type File = TestFSWithWatch.File;
export type SymLink = TestFSWithWatch.SymLink;
export import createWatchedSystem = TestFSWithWatch.createWatchedSystem;
export import checkArray = TestFSWithWatch.checkArray;
export import checkWatchedFiles = TestFSWithWatch.checkWatchedFiles;
export import checkWatchedFilesDetailed = TestFSWithWatch.checkWatchedFilesDetailed;
export import checkWatchedDirectories = TestFSWithWatch.checkWatchedDirectories;
export import checkWatchedDirectoriesDetailed = TestFSWithWatch.checkWatchedDirectoriesDetailed;
export import checkOutputContains = TestFSWithWatch.checkOutputContains;
export import checkOutputDoesNotContain = TestFSWithWatch.checkOutputDoesNotContain;
export import Tsc_WatchDirectory = TestFSWithWatch.Tsc_WatchDirectory;
export function checkProgramActualFiles(program: Program, expectedFiles: string[]) {
checkArray(`Program actual files`, program.getSourceFiles().map(file => file.fileName), expectedFiles);
@@ -111,7 +110,7 @@ namespace ts.tscWatch {
function assertWatchDiagnostic(diagnostic: Diagnostic) {
const expected = getWatchDiagnosticWithoutDate(diagnostic);
if (!disableConsoleClears && !contains(nonClearingMessageCodes, diagnostic.code)) {
if (!disableConsoleClears && contains(screenStartingMessageCodes, diagnostic.code)) {
assert.equal(host.screenClears[screenClears], index, `Expected screen clear at this diagnostic: ${expected}`);
screenClears++;
}
@@ -137,7 +136,7 @@ namespace ts.tscWatch {
: createCompilerDiagnostic(Diagnostics.Found_0_errors_Watching_for_file_changes, errors.length);
}
function checkOutputErrorsInitial(host: WatchedSystem, errors: ReadonlyArray<Diagnostic>, disableConsoleClears?: boolean, logsBeforeErrors?: string[]) {
export function checkOutputErrorsInitial(host: WatchedSystem, errors: ReadonlyArray<Diagnostic>, disableConsoleClears?: boolean, logsBeforeErrors?: string[]) {
checkOutputErrors(
host,
/*logsBeforeWatchDiagnostic*/ undefined,
@@ -148,7 +147,7 @@ namespace ts.tscWatch {
createErrorsFoundCompilerDiagnostic(errors));
}
function checkOutputErrorsIncremental(host: WatchedSystem, errors: ReadonlyArray<Diagnostic>, disableConsoleClears?: boolean, logsBeforeWatchDiagnostic?: string[], logsBeforeErrors?: string[]) {
export function checkOutputErrorsIncremental(host: WatchedSystem, errors: ReadonlyArray<Diagnostic>, disableConsoleClears?: boolean, logsBeforeWatchDiagnostic?: string[], logsBeforeErrors?: string[]) {
checkOutputErrors(
host,
logsBeforeWatchDiagnostic,
+459 -229
View File
@@ -642,37 +642,54 @@ namespace ts.projectSystem {
checkWatchedDirectories(host, [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: []
})
};
describe("can handle tsconfig file name with difference casing", () => {
function verifyConfigFileCasing(lazyConfiguredProjectsFromExternalProject: boolean) {
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(<protocol.ExternalProject>{
projectFileName: "/a/b/project.csproj",
rootFiles: toExternalFiles([f1.path, upperCaseConfigFilePath]),
options: {}
const host = createServerHost([f1, config], { useCaseSensitiveFileNames: false });
const service = createProjectService(host);
service.setHostConfiguration({ preferences: { lazyConfiguredProjectsFromExternalProject } });
const upperCaseConfigFilePath = combinePaths(getDirectoryPath(config.path).toUpperCase(), getBaseFileName(config.path));
service.openExternalProject(<protocol.ExternalProject>{
projectFileName: "/a/b/project.csproj",
rootFiles: toExternalFiles([f1.path, upperCaseConfigFilePath]),
options: {}
});
service.checkNumberOfProjects({ configuredProjects: 1 });
const project = service.configuredProjects.get(config.path)!;
if (lazyConfiguredProjectsFromExternalProject) {
assert.equal(project.pendingReload, ConfigFileProgramReloadLevel.Full); // External project referenced configured project pending to be reloaded
checkProjectActualFiles(project, emptyArray);
}
else {
assert.equal(project.pendingReload, ConfigFileProgramReloadLevel.None); // External project referenced configured project loaded
checkProjectActualFiles(project, [upperCaseConfigFilePath]);
}
service.openClientFile(f1.path);
service.checkNumberOfProjects({ configuredProjects: 1, inferredProjects: 1 });
assert.equal(project.pendingReload, ConfigFileProgramReloadLevel.None); // External project referenced configured project is updated
checkProjectActualFiles(project, [upperCaseConfigFilePath]);
checkProjectActualFiles(service.inferredProjects[0], [f1.path]);
}
it("when lazyConfiguredProjectsFromExternalProject not set", () => {
verifyConfigFileCasing(/*lazyConfiguredProjectsFromExternalProject*/ false);
});
service.checkNumberOfProjects({ configuredProjects: 1 });
const project = service.configuredProjects.get(config.path)!;
assert.equal(project.pendingReload, ConfigFileProgramReloadLevel.Full); // External project referenced configured project pending to be reloaded
checkProjectActualFiles(project, emptyArray);
service.openClientFile(f1.path);
service.checkNumberOfProjects({ configuredProjects: 1, inferredProjects: 1 });
assert.equal(project.pendingReload, ConfigFileProgramReloadLevel.None); // External project referenced configured project is updated
checkProjectActualFiles(project, [upperCaseConfigFilePath]);
checkProjectActualFiles(service.inferredProjects[0], [f1.path]);
it("when lazyConfiguredProjectsFromExternalProject is set", () => {
verifyConfigFileCasing(/*lazyConfiguredProjectsFromExternalProject*/ true);
});
});
it("create configured project without file list", () => {
@@ -2950,45 +2967,56 @@ namespace ts.projectSystem {
assert.equal(navbar[0].spans[0].length, f1.content.length);
});
it("deleting config file opened from the external project works", () => {
const site = {
path: "/user/someuser/project/js/site.js",
content: ""
};
const configFile = {
path: "/user/someuser/project/tsconfig.json",
content: "{}"
};
const projectFileName = "/user/someuser/project/WebApplication6.csproj";
const host = createServerHost([libFile, site, configFile]);
const projectService = createProjectService(host);
describe("deleting config file opened from the external project works", () => {
function verifyDeletingConfigFile(lazyConfiguredProjectsFromExternalProject: boolean) {
const site = {
path: "/user/someuser/project/js/site.js",
content: ""
};
const configFile = {
path: "/user/someuser/project/tsconfig.json",
content: "{}"
};
const projectFileName = "/user/someuser/project/WebApplication6.csproj";
const host = createServerHost([libFile, site, configFile]);
const projectService = createProjectService(host);
projectService.setHostConfiguration({ preferences: { lazyConfiguredProjectsFromExternalProject } });
const externalProject: protocol.ExternalProject = {
projectFileName,
rootFiles: [toExternalFile(site.path), toExternalFile(configFile.path)],
options: { allowJs: false },
typeAcquisition: { include: [] }
};
const externalProject: protocol.ExternalProject = {
projectFileName,
rootFiles: [toExternalFile(site.path), toExternalFile(configFile.path)],
options: { allowJs: false },
typeAcquisition: { include: [] }
};
projectService.openExternalProjects([externalProject]);
projectService.openExternalProjects([externalProject]);
let knownProjects = projectService.synchronizeProjectList([]);
checkNumberOfProjects(projectService, { configuredProjects: 1, externalProjects: 0, inferredProjects: 0 });
let knownProjects = projectService.synchronizeProjectList([]);
checkNumberOfProjects(projectService, { configuredProjects: 1, externalProjects: 0, inferredProjects: 0 });
const configProject = configuredProjectAt(projectService, 0);
checkProjectActualFiles(configProject, []); // Since no files opened from this project, its not loaded
const configProject = configuredProjectAt(projectService, 0);
checkProjectActualFiles(configProject, lazyConfiguredProjectsFromExternalProject ?
emptyArray : // Since no files opened from this project, its not loaded
[libFile.path, configFile.path]);
host.reloadFS([libFile, site]);
host.checkTimeoutQueueLengthAndRun(1);
host.reloadFS([libFile, site]);
host.checkTimeoutQueueLengthAndRun(1);
knownProjects = projectService.synchronizeProjectList(map(knownProjects, proj => proj.info!)); // TODO: GH#18217 GH#20039
checkNumberOfProjects(projectService, { configuredProjects: 0, externalProjects: 0, inferredProjects: 0 });
knownProjects = projectService.synchronizeProjectList(map(knownProjects, proj => proj.info!)); // TODO: GH#18217 GH#20039
checkNumberOfProjects(projectService, { configuredProjects: 0, externalProjects: 0, inferredProjects: 0 });
externalProject.rootFiles.length = 1;
projectService.openExternalProjects([externalProject]);
externalProject.rootFiles.length = 1;
projectService.openExternalProjects([externalProject]);
checkNumberOfProjects(projectService, { configuredProjects: 0, externalProjects: 1, inferredProjects: 0 });
checkProjectActualFiles(projectService.externalProjects[0], [site.path, libFile.path]);
checkNumberOfProjects(projectService, { configuredProjects: 0, externalProjects: 1, inferredProjects: 0 });
checkProjectActualFiles(projectService.externalProjects[0], [site.path, libFile.path]);
}
it("when lazyConfiguredProjectsFromExternalProject not set", () => {
verifyDeletingConfigFile(/*lazyConfiguredProjectsFromExternalProject*/ false);
});
it("when lazyConfiguredProjectsFromExternalProject is set", () => {
verifyDeletingConfigFile(/*lazyConfiguredProjectsFromExternalProject*/ true);
});
});
it("Getting errors from closed script info does not throw exception (because of getting project from orphan script info)", () => {
@@ -3298,49 +3326,64 @@ namespace ts.projectSystem {
});
it("includes deferred files in the project context", () => {
const file1 = {
path: "/a.deferred",
content: "const a = 1;"
};
// Deferred extensions should not affect JS files.
const file2 = {
path: "/b.js",
content: "const b = 1;"
};
const tsconfig = {
path: "/tsconfig.json",
content: ""
};
describe("includes deferred files in the project context", () => {
function verifyDeferredContext(lazyConfiguredProjectsFromExternalProject: boolean) {
const file1 = {
path: "/a.deferred",
content: "const a = 1;"
};
// Deferred extensions should not affect JS files.
const file2 = {
path: "/b.js",
content: "const b = 1;"
};
const tsconfig = {
path: "/tsconfig.json",
content: ""
};
const host = createServerHost([file1, file2, tsconfig]);
const session = createSession(host);
const projectService = session.getProjectService();
const host = createServerHost([file1, file2, tsconfig]);
const session = createSession(host);
const projectService = session.getProjectService();
session.executeCommandSeq<protocol.ConfigureRequest>({
command: protocol.CommandTypes.Configure,
arguments: { preferences: { lazyConfiguredProjectsFromExternalProject } }
});
// Configure the deferred extension.
const extraFileExtensions = [{ extension: ".deferred", scriptKind: ScriptKind.Deferred, isMixedContent: true }];
const configureHostRequest = makeSessionRequest<protocol.ConfigureRequestArguments>(CommandNames.Configure, { extraFileExtensions });
session.executeCommand(configureHostRequest);
// Configure the deferred extension.
const extraFileExtensions = [{ extension: ".deferred", scriptKind: ScriptKind.Deferred, isMixedContent: true }];
const configureHostRequest = makeSessionRequest<protocol.ConfigureRequestArguments>(CommandNames.Configure, { extraFileExtensions });
session.executeCommand(configureHostRequest);
// Open external project
const projectName = "/proj1";
projectService.openExternalProject({
projectFileName: projectName,
rootFiles: toExternalFiles([file1.path, file2.path, tsconfig.path]),
options: {}
// Open external project
const projectName = "/proj1";
projectService.openExternalProject({
projectFileName: projectName,
rootFiles: toExternalFiles([file1.path, file2.path, tsconfig.path]),
options: {}
});
// Assert
checkNumberOfProjects(projectService, { configuredProjects: 1 });
const configuredProject = configuredProjectAt(projectService, 0);
if (lazyConfiguredProjectsFromExternalProject) {
// configured project is just created and not yet loaded
checkProjectActualFiles(configuredProject, emptyArray);
projectService.ensureInferredProjectsUpToDate_TestOnly();
}
checkProjectActualFiles(configuredProject, [file1.path, tsconfig.path]);
// Allow allowNonTsExtensions will be set to true for deferred extensions.
assert.isTrue(configuredProject.getCompilerOptions().allowNonTsExtensions);
}
it("when lazyConfiguredProjectsFromExternalProject not set", () => {
verifyDeferredContext(/*lazyConfiguredProjectsFromExternalProject*/ false);
});
it("when lazyConfiguredProjectsFromExternalProject is set", () => {
verifyDeferredContext(/*lazyConfiguredProjectsFromExternalProject*/ true);
});
// Assert
checkNumberOfProjects(projectService, { configuredProjects: 1 });
const configuredProject = configuredProjectAt(projectService, 0);
// configured project is just created and not yet loaded
checkProjectActualFiles(configuredProject, emptyArray);
projectService.ensureInferredProjectsUpToDate_TestOnly();
checkProjectActualFiles(configuredProject, [file1.path, tsconfig.path]);
// Allow allowNonTsExtensions will be set to true for deferred extensions.
assert.isTrue(configuredProject.getCompilerOptions().allowNonTsExtensions);
});
it("Orphan source files are handled correctly on watch trigger", () => {
@@ -3943,143 +3986,167 @@ namespace ts.projectSystem {
});
describe("tsserverProjectSystem external projects", () => {
it("correctly handling add/remove tsconfig - 1", () => {
const f1 = {
path: "/a/b/app.ts",
content: "let x = 1;"
};
const f2 = {
path: "/a/b/lib.ts",
content: ""
};
const tsconfig = {
path: "/a/b/tsconfig.json",
content: ""
};
const host = createServerHost([f1, f2]);
const projectService = createProjectService(host);
describe("correctly handling add/remove tsconfig - 1", () => {
function verifyAddRemoveConfig(lazyConfiguredProjectsFromExternalProject: boolean) {
const f1 = {
path: "/a/b/app.ts",
content: "let x = 1;"
};
const f2 = {
path: "/a/b/lib.ts",
content: ""
};
const tsconfig = {
path: "/a/b/tsconfig.json",
content: ""
};
const host = createServerHost([f1, f2]);
const projectService = createProjectService(host);
projectService.setHostConfiguration({ preferences: { lazyConfiguredProjectsFromExternalProject } });
// open external project
const projectName = "/a/b/proj1";
projectService.openExternalProject({
projectFileName: projectName,
rootFiles: toExternalFiles([f1.path, f2.path]),
options: {}
// open external project
const projectName = "/a/b/proj1";
projectService.openExternalProject({
projectFileName: projectName,
rootFiles: toExternalFiles([f1.path, f2.path]),
options: {}
});
projectService.openClientFile(f1.path);
projectService.checkNumberOfProjects({ externalProjects: 1 });
checkProjectActualFiles(projectService.externalProjects[0], [f1.path, f2.path]);
// rename lib.ts to tsconfig.json
host.reloadFS([f1, tsconfig]);
projectService.openExternalProject({
projectFileName: projectName,
rootFiles: toExternalFiles([f1.path, tsconfig.path]),
options: {}
});
projectService.checkNumberOfProjects({ configuredProjects: 1 });
if (lazyConfiguredProjectsFromExternalProject) {
checkProjectActualFiles(configuredProjectAt(projectService, 0), emptyArray); // Configured project created but not loaded till actually needed
projectService.ensureInferredProjectsUpToDate_TestOnly();
}
checkProjectActualFiles(configuredProjectAt(projectService, 0), [f1.path, tsconfig.path]);
// rename tsconfig.json back to lib.ts
host.reloadFS([f1, f2]);
projectService.openExternalProject({
projectFileName: projectName,
rootFiles: toExternalFiles([f1.path, f2.path]),
options: {}
});
projectService.checkNumberOfProjects({ externalProjects: 1 });
checkProjectActualFiles(projectService.externalProjects[0], [f1.path, f2.path]);
}
it("when lazyConfiguredProjectsFromExternalProject not set", () => {
verifyAddRemoveConfig(/*lazyConfiguredProjectsFromExternalProject*/ false);
});
projectService.openClientFile(f1.path);
projectService.checkNumberOfProjects({ externalProjects: 1 });
checkProjectActualFiles(projectService.externalProjects[0], [f1.path, f2.path]);
// rename lib.ts to tsconfig.json
host.reloadFS([f1, tsconfig]);
projectService.openExternalProject({
projectFileName: projectName,
rootFiles: toExternalFiles([f1.path, tsconfig.path]),
options: {}
it("when lazyConfiguredProjectsFromExternalProject is set", () => {
verifyAddRemoveConfig(/*lazyConfiguredProjectsFromExternalProject*/ true);
});
projectService.checkNumberOfProjects({ configuredProjects: 1 });
checkProjectActualFiles(configuredProjectAt(projectService, 0), emptyArray); // Configured project created but not loaded till actually needed
projectService.ensureInferredProjectsUpToDate_TestOnly();
checkProjectActualFiles(configuredProjectAt(projectService, 0), [f1.path, tsconfig.path]);
// rename tsconfig.json back to lib.ts
host.reloadFS([f1, f2]);
projectService.openExternalProject({
projectFileName: projectName,
rootFiles: toExternalFiles([f1.path, f2.path]),
options: {}
});
projectService.checkNumberOfProjects({ externalProjects: 1 });
checkProjectActualFiles(projectService.externalProjects[0], [f1.path, f2.path]);
});
describe("correctly handling add/remove tsconfig - 2", () => {
function verifyAddRemoveConfig(lazyConfiguredProjectsFromExternalProject: boolean) {
const f1 = {
path: "/a/b/app.ts",
content: "let x = 1;"
};
const cLib = {
path: "/a/b/c/lib.ts",
content: ""
};
const cTsconfig = {
path: "/a/b/c/tsconfig.json",
content: "{}"
};
const dLib = {
path: "/a/b/d/lib.ts",
content: ""
};
const dTsconfig = {
path: "/a/b/d/tsconfig.json",
content: "{}"
};
const host = createServerHost([f1, cLib, cTsconfig, dLib, dTsconfig]);
const projectService = createProjectService(host);
projectService.setHostConfiguration({ preferences: { lazyConfiguredProjectsFromExternalProject } });
it("correctly handling add/remove tsconfig - 2", () => {
const f1 = {
path: "/a/b/app.ts",
content: "let x = 1;"
};
const cLib = {
path: "/a/b/c/lib.ts",
content: ""
};
const cTsconfig = {
path: "/a/b/c/tsconfig.json",
content: "{}"
};
const dLib = {
path: "/a/b/d/lib.ts",
content: ""
};
const dTsconfig = {
path: "/a/b/d/tsconfig.json",
content: "{}"
};
const host = createServerHost([f1, cLib, cTsconfig, dLib, dTsconfig]);
const projectService = createProjectService(host);
// open external project
const projectName = "/a/b/proj1";
projectService.openExternalProject({
projectFileName: projectName,
rootFiles: toExternalFiles([f1.path]),
options: {}
});
// open external project
const projectName = "/a/b/proj1";
projectService.openExternalProject({
projectFileName: projectName,
rootFiles: toExternalFiles([f1.path]),
options: {}
projectService.checkNumberOfProjects({ externalProjects: 1 });
checkProjectActualFiles(projectService.externalProjects[0], [f1.path]);
// add two config file as root files
projectService.openExternalProject({
projectFileName: projectName,
rootFiles: toExternalFiles([f1.path, cTsconfig.path, dTsconfig.path]),
options: {}
});
projectService.checkNumberOfProjects({ configuredProjects: 2 });
if (lazyConfiguredProjectsFromExternalProject) {
checkProjectActualFiles(configuredProjectAt(projectService, 0), emptyArray); // Configured project created but not loaded till actually needed
checkProjectActualFiles(configuredProjectAt(projectService, 1), emptyArray); // Configured project created but not loaded till actually needed
projectService.ensureInferredProjectsUpToDate_TestOnly();
}
checkProjectActualFiles(configuredProjectAt(projectService, 0), [cLib.path, cTsconfig.path]);
checkProjectActualFiles(configuredProjectAt(projectService, 1), [dLib.path, dTsconfig.path]);
// remove one config file
projectService.openExternalProject({
projectFileName: projectName,
rootFiles: toExternalFiles([f1.path, dTsconfig.path]),
options: {}
});
projectService.checkNumberOfProjects({ configuredProjects: 1 });
checkProjectActualFiles(configuredProjectAt(projectService, 0), [dLib.path, dTsconfig.path]);
// remove second config file
projectService.openExternalProject({
projectFileName: projectName,
rootFiles: toExternalFiles([f1.path]),
options: {}
});
projectService.checkNumberOfProjects({ externalProjects: 1 });
checkProjectActualFiles(projectService.externalProjects[0], [f1.path]);
// open two config files
// add two config file as root files
projectService.openExternalProject({
projectFileName: projectName,
rootFiles: toExternalFiles([f1.path, cTsconfig.path, dTsconfig.path]),
options: {}
});
projectService.checkNumberOfProjects({ configuredProjects: 2 });
if (lazyConfiguredProjectsFromExternalProject) {
checkProjectActualFiles(configuredProjectAt(projectService, 0), emptyArray); // Configured project created but not loaded till actually needed
checkProjectActualFiles(configuredProjectAt(projectService, 1), emptyArray); // Configured project created but not loaded till actually needed
projectService.ensureInferredProjectsUpToDate_TestOnly();
}
checkProjectActualFiles(configuredProjectAt(projectService, 0), [cLib.path, cTsconfig.path]);
checkProjectActualFiles(configuredProjectAt(projectService, 1), [dLib.path, dTsconfig.path]);
// close all projects - no projects should be opened
projectService.closeExternalProject(projectName);
projectService.checkNumberOfProjects({});
}
it("when lazyConfiguredProjectsFromExternalProject not set", () => {
verifyAddRemoveConfig(/*lazyConfiguredProjectsFromExternalProject*/ false);
});
projectService.checkNumberOfProjects({ externalProjects: 1 });
checkProjectActualFiles(projectService.externalProjects[0], [f1.path]);
// add two config file as root files
projectService.openExternalProject({
projectFileName: projectName,
rootFiles: toExternalFiles([f1.path, cTsconfig.path, dTsconfig.path]),
options: {}
it("when lazyConfiguredProjectsFromExternalProject is set", () => {
verifyAddRemoveConfig(/*lazyConfiguredProjectsFromExternalProject*/ true);
});
projectService.checkNumberOfProjects({ configuredProjects: 2 });
checkProjectActualFiles(configuredProjectAt(projectService, 0), emptyArray); // Configured project created but not loaded till actually needed
checkProjectActualFiles(configuredProjectAt(projectService, 1), emptyArray); // Configured project created but not loaded till actually needed
projectService.ensureInferredProjectsUpToDate_TestOnly();
checkProjectActualFiles(configuredProjectAt(projectService, 0), [cLib.path, cTsconfig.path]);
checkProjectActualFiles(configuredProjectAt(projectService, 1), [dLib.path, dTsconfig.path]);
// remove one config file
projectService.openExternalProject({
projectFileName: projectName,
rootFiles: toExternalFiles([f1.path, dTsconfig.path]),
options: {}
});
projectService.checkNumberOfProjects({ configuredProjects: 1 });
checkProjectActualFiles(configuredProjectAt(projectService, 0), [dLib.path, dTsconfig.path]);
// remove second config file
projectService.openExternalProject({
projectFileName: projectName,
rootFiles: toExternalFiles([f1.path]),
options: {}
});
projectService.checkNumberOfProjects({ externalProjects: 1 });
checkProjectActualFiles(projectService.externalProjects[0], [f1.path]);
// open two config files
// add two config file as root files
projectService.openExternalProject({
projectFileName: projectName,
rootFiles: toExternalFiles([f1.path, cTsconfig.path, dTsconfig.path]),
options: {}
});
projectService.checkNumberOfProjects({ configuredProjects: 2 });
checkProjectActualFiles(configuredProjectAt(projectService, 0), emptyArray); // Configured project created but not loaded till actually needed
checkProjectActualFiles(configuredProjectAt(projectService, 1), emptyArray); // Configured project created but not loaded till actually needed
projectService.ensureInferredProjectsUpToDate_TestOnly();
checkProjectActualFiles(configuredProjectAt(projectService, 0), [cLib.path, cTsconfig.path]);
checkProjectActualFiles(configuredProjectAt(projectService, 1), [dLib.path, dTsconfig.path]);
// close all projects - no projects should be opened
projectService.closeExternalProject(projectName);
projectService.checkNumberOfProjects({});
});
it("correctly handles changes in lib section of config file", () => {
@@ -4174,6 +4241,47 @@ namespace ts.projectSystem {
assert.isTrue(project.hasOpenRef()); // f
assert.isFalse(project.isClosed());
});
it("handles loads existing configured projects of external projects when lazyConfiguredProjectsFromExternalProject is disabled", () => {
const f1 = {
path: "/a/b/app.ts",
content: "let x = 1"
};
const config = {
path: "/a/b/tsconfig.json",
content: JSON.stringify({})
};
const projectFileName = "/a/b/project.csproj";
const host = createServerHost([f1, config]);
const service = createProjectService(host);
service.setHostConfiguration({ preferences: { lazyConfiguredProjectsFromExternalProject: true } });
service.openExternalProject(<protocol.ExternalProject>{
projectFileName,
rootFiles: toExternalFiles([f1.path, config.path]),
options: {}
});
service.checkNumberOfProjects({ configuredProjects: 1 });
const project = service.configuredProjects.get(config.path)!;
assert.equal(project.pendingReload, ConfigFileProgramReloadLevel.Full); // External project referenced configured project pending to be reloaded
checkProjectActualFiles(project, emptyArray);
service.setHostConfiguration({ preferences: { lazyConfiguredProjectsFromExternalProject: false } });
assert.equal(project.pendingReload, ConfigFileProgramReloadLevel.None); // External project referenced configured project loaded
checkProjectActualFiles(project, [config.path, f1.path]);
service.closeExternalProject(projectFileName);
service.checkNumberOfProjects({});
service.openExternalProject(<protocol.ExternalProject>{
projectFileName,
rootFiles: toExternalFiles([f1.path, config.path]),
options: {}
});
service.checkNumberOfProjects({ configuredProjects: 1 });
const project2 = service.configuredProjects.get(config.path)!;
assert.equal(project2.pendingReload, ConfigFileProgramReloadLevel.None); // External project referenced configured project loaded
checkProjectActualFiles(project2, [config.path, f1.path]);
});
});
describe("tsserverProjectSystem prefer typings to js", () => {
@@ -8257,7 +8365,7 @@ new C();`
verifyProjectWithResolvedModule(session);
host.removeFolder(recognizersTextDist, /*recursive*/ true);
host.deleteFolder(recognizersTextDist, /*recursive*/ true);
host.runQueuedTimeoutCallbacks();
verifyProjectWithUnresolvedModule(session);
@@ -9173,7 +9281,7 @@ describe("Test Suite 1", () => {
checkProjectActualFiles(project, expectedFilesWithUnitTest1);
const navBarResultUnitTest1 = navBarFull(session, unitTest1);
host.removeFile(unitTest1.path);
host.deleteFile(unitTest1.path);
host.checkTimeoutQueueLengthAndRun(2);
checkProjectActualFiles(project, expectedFilesWithoutUnitTest1);
@@ -9210,6 +9318,128 @@ export function Test2() {
});
});
describe("tsserverProjectSystem completions", () => {
it("works", () => {
const aTs: File = {
path: "/a.ts",
content: "export const foo = 0;",
};
const bTs: File = {
path: "/b.ts",
content: "foo",
};
const tsconfig: File = {
path: "/tsconfig.json",
content: "{}",
};
const host = createServerHost([aTs, bTs, tsconfig]);
const session = createSession(host);
openFilesForSession([aTs, bTs], session);
const requestLocation: protocol.FileLocationRequestArgs = {
file: bTs.path,
line: 1,
offset: 3,
};
const response = executeSessionRequest<protocol.CompletionsRequest, protocol.CompletionInfoResponse>(session, protocol.CommandTypes.CompletionInfo, {
...requestLocation,
includeExternalModuleExports: true,
prefix: "foo",
});
const entry: protocol.CompletionEntry = {
hasAction: true,
insertText: undefined,
isRecommended: undefined,
kind: ScriptElementKind.constElement,
kindModifiers: ScriptElementKindModifier.exportedModifier,
name: "foo",
replacementSpan: undefined,
sortText: "0",
source: "/a",
};
assert.deepEqual<protocol.CompletionInfo | undefined>(response, {
isGlobalCompletion: true,
isMemberCompletion: false,
isNewIdentifierLocation: false,
entries: [entry],
});
const detailsRequestArgs: protocol.CompletionDetailsRequestArgs = {
...requestLocation,
entryNames: [{ name: "foo", source: "/a" }],
};
const detailsResponse = executeSessionRequest<protocol.CompletionDetailsRequest, protocol.CompletionDetailsResponse>(session, protocol.CommandTypes.CompletionDetails, detailsRequestArgs);
const detailsCommon: protocol.CompletionEntryDetails & CompletionEntryDetails = {
displayParts: [
keywordPart(SyntaxKind.ConstKeyword),
spacePart(),
displayPart("foo", SymbolDisplayPartKind.localName),
punctuationPart(SyntaxKind.ColonToken),
spacePart(),
displayPart("0", SymbolDisplayPartKind.stringLiteral),
],
documentation: emptyArray,
kind: ScriptElementKind.constElement,
kindModifiers: ScriptElementKindModifier.exportedModifier,
name: "foo",
source: [{ text: "./a", kind: "text" }],
tags: emptyArray,
};
assert.deepEqual<ReadonlyArray<protocol.CompletionEntryDetails> | undefined>(detailsResponse, [
{
codeActions: [
{
description: `Import 'foo' from module "./a"`,
changes: [
{
fileName: "/b.ts",
textChanges: [
{
start: { line: 1, offset: 1 },
end: { line: 1, offset: 1 },
newText: 'import { foo } from "./a";\n\n',
},
],
},
],
commands: undefined,
},
],
...detailsCommon,
},
]);
interface CompletionDetailsFullRequest extends protocol.FileLocationRequest {
readonly command: protocol.CommandTypes.CompletionDetailsFull;
readonly arguments: protocol.CompletionDetailsRequestArgs;
}
interface CompletionDetailsFullResponse extends protocol.Response {
readonly body?: ReadonlyArray<CompletionEntryDetails>;
}
const detailsFullResponse = executeSessionRequest<CompletionDetailsFullRequest, CompletionDetailsFullResponse>(session, protocol.CommandTypes.CompletionDetailsFull, detailsRequestArgs);
assert.deepEqual<ReadonlyArray<CompletionEntryDetails> | undefined>(detailsFullResponse, [
{
codeActions: [
{
description: `Import 'foo' from module "./a"`,
changes: [
{
fileName: "/b.ts",
textChanges: [createTextChange(createTextSpan(0, 0), 'import { foo } from "./a";\n\n')],
},
],
commands: undefined,
}
],
...detailsCommon,
}
]);
});
});
describe("tsserverProjectSystem project references", () => {
const aTs: File = {
path: "/a/a.ts",
@@ -9297,7 +9527,7 @@ export function Test2() {
checkDeclarationFiles(bTs, session, [bDtsMap, bDts]);
// Testing what happens if we delete the original sources.
host.removeFile(bTs.path);
host.deleteFile(bTs.path);
openFilesForSession([userTs], session);
const service = session.getProjectService();