Merge remote-tracking branch 'upstream/master' into async-es2018

This commit is contained in:
Kagami Sascha Rosylight
2019-02-07 09:51:26 +09:00
464 changed files with 83268 additions and 8270 deletions
+2 -1
View File
@@ -1,5 +1,5 @@
{
"extends": "../tsconfig-base",
"extends": "../tsconfig-noncomposite-base",
"compilerOptions": {
"outFile": "../../built/local/run.js",
"composite": false,
@@ -43,6 +43,7 @@
"unittests/asserts.ts",
"unittests/base64.ts",
"unittests/builder.ts",
"unittests/comments.ts",
"unittests/compilerCore.ts",
"unittests/convertToBase64.ts",
"unittests/customTransforms.ts",
+3
View File
@@ -5,5 +5,8 @@ namespace ts {
assert.throws(() => assert.deepEqual(createNodeArray([], /*hasTrailingComma*/ true), createNodeArray([], /*hasTrailingComma*/ false)));
assert.deepEqual(createNodeArray([createIdentifier("A")], /*hasTrailingComma*/ true), createNodeArray([createIdentifier("A")], /*hasTrailingComma*/ true));
});
it("assertNever on string has correct error", () => {
assert.throws(() => Debug.assertNever("hi" as never), "Debug Failure. Illegal value: \"hi\"");
});
});
}
+32
View File
@@ -0,0 +1,32 @@
namespace ts {
describe("comment parsing", () => {
const withShebang = `#! node
/** comment */
// another one
;`;
const noShebang = `/** comment */
// another one
;`;
const withTrailing = `;/* comment */
// another one
`;
it("skips shebang", () => {
const result = getLeadingCommentRanges(withShebang, 0);
assert.isDefined(result);
assert.strictEqual(result!.length, 2);
});
it("treats all comments at start of file as leading comments", () => {
const result = getLeadingCommentRanges(noShebang, 0);
assert.isDefined(result);
assert.strictEqual(result!.length, 2);
});
it("returns leading comments if position is not 0", () => {
const result = getLeadingCommentRanges(withTrailing, 1);
assert.isDefined(result);
assert.strictEqual(result!.length, 1);
assert.strictEqual(result![0].kind, SyntaxKind.SingleLineCommentTrivia);
});
});
}
@@ -85,7 +85,7 @@ namespace ts {
// We shouldn't have any errors about invalid tsconfig files in these tests
assert(config && !error, flattenDiagnosticMessageText(error && error.messageText, "\n"));
const file = parseJsonConfigFileContent(config, parseConfigHostFromCompilerHost(host), getDirectoryPath(entryPointConfigFileName), {}, entryPointConfigFileName);
const file = parseJsonConfigFileContent(config, parseConfigHostFromCompilerHostLike(host), getDirectoryPath(entryPointConfigFileName), {}, entryPointConfigFileName);
file.options.configFilePath = entryPointConfigFileName;
const prog = createProgram({
rootNames: file.fileNames,
@@ -129,5 +129,34 @@ namespace ts {
},
{ sourceMap: true }
);
emitsCorrectly("skipTriviaExternalSourceFiles",
[
{
file: "source.ts",
// The source file contains preceding trivia (e.g. whitespace) to try to confuse the `skipSourceTrivia` function.
text: " original;"
},
],
{
before: [
context => node => visitNode(node, function visitor(node: Node): Node {
if (isIdentifier(node) && node.text === "original") {
const newNode = createIdentifier("changed");
setSourceMapRange(newNode, {
pos: 0,
end: 7,
// Do not provide a custom skipTrivia function for `source`.
source: createSourceMapSource("another.html", "changed;")
});
return newNode;
}
return visitEachChild(node, visitor, context);
})
]
},
{ sourceMap: true }
);
});
}
+40
View File
@@ -400,6 +400,46 @@ namespace Foo {
}
}).outputText;
});
// https://github.com/Microsoft/TypeScript/issues/24709
testBaseline("issue24709", () => {
const fs = vfs.createFromFileSystem(Harness.IO, /*caseSensitive*/ true);
const transformed = transform(createSourceFile("source.ts", "class X { echo(x: string) { return x; } }", ScriptTarget.ES3), [transformSourceFile]);
const transformedSourceFile = transformed.transformed[0];
transformed.dispose();
const host = new fakes.CompilerHost(fs);
host.getSourceFile = () => transformedSourceFile;
const program = createProgram(["source.ts"], {
target: ScriptTarget.ES3,
module: ModuleKind.None,
noLib: true
}, host);
program.emit(transformedSourceFile, (_p, s, b) => host.writeFile("source.js", s, b));
return host.readFile("source.js")!.toString();
function transformSourceFile(context: TransformationContext) {
const visitor: Visitor = (node) => {
if (isMethodDeclaration(node)) {
return updateMethod(
node,
node.decorators,
node.modifiers,
node.asteriskToken,
createIdentifier("foobar"),
node.questionToken,
node.typeParameters,
node.parameters,
node.type,
node.body,
);
}
return visitEachChild(node, visitor, context);
};
return (node: SourceFile) => visitNode(node, visitor);
}
});
});
}
+154 -67
View File
@@ -1,5 +1,10 @@
namespace ts {
let currentTime = 100;
function getExpectedDiagnosticForProjectsInBuild(...projects: string[]): fakes.ExpectedDiagnostic {
return [Diagnostics.Projects_in_this_build_Colon_0, projects.map(p => "\r\n * " + p).join("")];
}
export namespace Sample1 {
tick();
const projFs = loadProjectFromDisk("tests/projects/sample1");
@@ -67,7 +72,11 @@ namespace ts {
const host = new fakes.SolutionBuilderHost(fs);
const builder = createSolutionBuilder(host, ["/src/tests"], { dry: true, force: false, verbose: false });
builder.buildAllProjects();
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);
host.assertDiagnosticMessages(
[Diagnostics.A_non_dry_build_would_build_project_0, "/src/core/tsconfig.json"],
[Diagnostics.A_non_dry_build_would_build_project_0, "/src/logic/tsconfig.json"],
[Diagnostics.A_non_dry_build_would_build_project_0, "/src/tests/tsconfig.json"]
);
// Check for outputs to not be written. Not an exhaustive list
for (const output of allExpectedOutputs) {
@@ -86,7 +95,11 @@ namespace ts {
host.clearDiagnostics();
builder = createSolutionBuilder(host, ["/src/tests"], { dry: true, force: false, verbose: false });
builder.buildAllProjects();
host.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, "/src/core/tsconfig.json"],
[Diagnostics.Project_0_is_up_to_date, "/src/logic/tsconfig.json"],
[Diagnostics.Project_0_is_up_to_date, "/src/tests/tsconfig.json"]
);
});
});
@@ -146,13 +159,15 @@ namespace ts {
host.clearDiagnostics();
builder.resetBuildContext();
builder.buildAllProjects();
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,
Diagnostics.Building_project_0,
Diagnostics.Project_0_is_out_of_date_because_output_file_1_does_not_exist,
Diagnostics.Building_project_0);
host.assertDiagnosticMessages(
getExpectedDiagnosticForProjectsInBuild("src/core/tsconfig.json", "src/logic/tsconfig.json", "src/tests/tsconfig.json"),
[Diagnostics.Project_0_is_out_of_date_because_output_file_1_does_not_exist, "src/core/tsconfig.json", "src/core/anotherModule.js"],
[Diagnostics.Building_project_0, "/src/core/tsconfig.json"],
[Diagnostics.Project_0_is_out_of_date_because_output_file_1_does_not_exist, "src/logic/tsconfig.json", "src/logic/index.js"],
[Diagnostics.Building_project_0, "/src/logic/tsconfig.json"],
[Diagnostics.Project_0_is_out_of_date_because_output_file_1_does_not_exist, "src/tests/tsconfig.json", "src/tests/index.js"],
[Diagnostics.Building_project_0, "/src/tests/tsconfig.json"]
);
tick();
});
@@ -161,10 +176,12 @@ namespace ts {
host.clearDiagnostics();
builder.resetBuildContext();
builder.buildAllProjects();
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);
host.assertDiagnosticMessages(
getExpectedDiagnosticForProjectsInBuild("src/core/tsconfig.json", "src/logic/tsconfig.json", "src/tests/tsconfig.json"),
[Diagnostics.Project_0_is_up_to_date_because_newest_input_1_is_older_than_oldest_output_2, "src/core/tsconfig.json", "src/core/anotherModule.ts", "src/core/anotherModule.js"],
[Diagnostics.Project_0_is_up_to_date_because_newest_input_1_is_older_than_oldest_output_2, "src/logic/tsconfig.json", "src/logic/index.ts", "src/logic/index.js"],
[Diagnostics.Project_0_is_up_to_date_because_newest_input_1_is_older_than_oldest_output_2, "src/tests/tsconfig.json", "src/tests/index.ts", "src/tests/index.js"]
);
tick();
});
@@ -175,11 +192,13 @@ namespace ts {
builder.resetBuildContext();
builder.buildAllProjects();
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,
Diagnostics.Building_project_0);
host.assertDiagnosticMessages(
getExpectedDiagnosticForProjectsInBuild("src/core/tsconfig.json", "src/logic/tsconfig.json", "src/tests/tsconfig.json"),
[Diagnostics.Project_0_is_up_to_date_because_newest_input_1_is_older_than_oldest_output_2, "src/core/tsconfig.json", "src/core/anotherModule.ts", "src/core/anotherModule.js"],
[Diagnostics.Project_0_is_up_to_date_because_newest_input_1_is_older_than_oldest_output_2, "src/logic/tsconfig.json", "src/logic/index.ts", "src/logic/index.js"],
[Diagnostics.Project_0_is_out_of_date_because_oldest_output_1_is_older_than_newest_input_2, "src/tests/tsconfig.json", "src/tests/index.js", "src/tests/index.ts"],
[Diagnostics.Building_project_0, "/src/tests/tsconfig.json"]
);
tick();
});
@@ -190,13 +209,15 @@ namespace ts {
builder.resetBuildContext();
builder.buildAllProjects();
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,
Diagnostics.Updating_output_timestamps_of_project_0,
Diagnostics.Project_0_is_up_to_date_with_d_ts_files_from_its_dependencies,
Diagnostics.Updating_output_timestamps_of_project_0);
host.assertDiagnosticMessages(
getExpectedDiagnosticForProjectsInBuild("src/core/tsconfig.json", "src/logic/tsconfig.json", "src/tests/tsconfig.json"),
[Diagnostics.Project_0_is_out_of_date_because_oldest_output_1_is_older_than_newest_input_2, "src/core/tsconfig.json", "src/core/anotherModule.js", "src/core/index.ts"],
[Diagnostics.Building_project_0, "/src/core/tsconfig.json"],
[Diagnostics.Project_0_is_up_to_date_with_d_ts_files_from_its_dependencies, "src/logic/tsconfig.json"],
[Diagnostics.Updating_output_timestamps_of_project_0, "/src/logic/tsconfig.json"],
[Diagnostics.Project_0_is_up_to_date_with_d_ts_files_from_its_dependencies, "src/tests/tsconfig.json"],
[Diagnostics.Updating_output_timestamps_of_project_0, "/src/tests/tsconfig.json"]
);
});
});
@@ -210,14 +231,14 @@ namespace ts {
replaceText(fs, "/src/logic/index.ts", "c.multiply(10, 15)", `c.muitply()`);
builder.buildAllProjects();
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,
Diagnostics.Building_project_0,
Diagnostics.Property_0_does_not_exist_on_type_1,
Diagnostics.Project_0_can_t_be_built_because_its_dependency_1_has_errors,
Diagnostics.Skipping_build_of_project_0_because_its_dependency_1_has_errors
getExpectedDiagnosticForProjectsInBuild("src/core/tsconfig.json", "src/logic/tsconfig.json", "src/tests/tsconfig.json"),
[Diagnostics.Project_0_is_out_of_date_because_output_file_1_does_not_exist, "src/core/tsconfig.json", "src/core/anotherModule.js"],
[Diagnostics.Building_project_0, "/src/core/tsconfig.json"],
[Diagnostics.Project_0_is_out_of_date_because_output_file_1_does_not_exist, "src/logic/tsconfig.json", "src/logic/index.js"],
[Diagnostics.Building_project_0, "/src/logic/tsconfig.json"],
[Diagnostics.Property_0_does_not_exist_on_type_1, "muitply", `typeof import("/src/core/index")`],
[Diagnostics.Project_0_can_t_be_built_because_its_dependency_1_has_errors, "src/tests/tsconfig.json", "src/logic"],
[Diagnostics.Skipping_build_of_project_0_because_its_dependency_1_has_errors, "/src/tests/tsconfig.json", "/src/logic"]
);
});
});
@@ -234,39 +255,46 @@ namespace ts {
// Update a timestamp in the middle project
tick();
touch(fs, "/src/logic/index.ts");
const originalWriteFile = fs.writeFileSync;
const writtenFiles = createMap<true>();
fs.writeFileSync = (path, data, encoding) => {
writtenFiles.set(path, true);
originalWriteFile.call(fs, path, data, encoding);
};
// Because we haven't reset the build context, the builder should assume there's nothing to do right now
const status = builder.getUpToDateStatusOfFile(builder.resolveProjectName("/src/logic"));
assert.equal(status.type, UpToDateStatusType.UpToDate, "Project should be assumed to be up-to-date");
verifyInvalidation(/*expectedToWriteTests*/ false);
// Rebuild this project
tick();
builder.invalidateProject("/src/logic");
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");
// Does not build tests or core because there is no change in declaration file
tick();
builder.buildInvalidatedProject();
assert.isBelow(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");
// Rebuild this project
tick();
fs.writeFileSync("/src/logic/index.ts", `${fs.readFileSync("/src/logic/index.ts")}
export class cNew {}`);
builder.invalidateProject("/src/logic");
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");
verifyInvalidation(/*expectedToWriteTests*/ true);
// Build downstream projects should update 'tests', but not 'core'
tick();
builder.buildInvalidatedProject();
assert.isBelow(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");
function verifyInvalidation(expectedToWriteTests: boolean) {
// Rebuild this project
tick();
builder.invalidateProject("/src/logic");
builder.buildInvalidatedProject();
// The file should be updated
assert.isTrue(writtenFiles.has("/src/logic/index.js"), "JS file should have been rebuilt");
assert.equal(fs.statSync("/src/logic/index.js").mtimeMs, time(), "JS file should have been rebuilt");
assert.isFalse(writtenFiles.has("/src/tests/index.js"), "Downstream JS file should *not* have been rebuilt");
assert.isBelow(fs.statSync("/src/tests/index.js").mtimeMs, time(), "Downstream JS file should *not* have been rebuilt");
writtenFiles.clear();
// Build downstream projects should update 'tests', but not 'core'
tick();
builder.buildInvalidatedProject();
if (expectedToWriteTests) {
assert.isTrue(writtenFiles.has("/src/tests/index.js"), "Downstream JS file should have been rebuilt");
}
else {
assert.equal(writtenFiles.size, 0, "Should not write any new files");
}
assert.equal(fs.statSync("/src/tests/index.js").mtimeMs, time(), "Downstream JS file should have new timestamp");
assert.isBelow(fs.statSync("/src/core/index.js").mtimeMs, time(), "Upstream JS file should not have been rebuilt");
}
});
});
@@ -274,12 +302,12 @@ export class cNew {}`);
const projFs = loadProjectFromDisk("tests/projects/resolveJsonModuleAndComposite");
const allExpectedOutputs = ["/src/tests/dist/src/index.js", "/src/tests/dist/src/index.d.ts", "/src/tests/dist/src/hello.json"];
function verifyProjectWithResolveJsonModule(configFile: string, ...expectedDiagnosticMessages: DiagnosticMessage[]) {
function verifyProjectWithResolveJsonModule(configFile: string, ...expectedDiagnosticMessages: fakes.ExpectedDiagnostic[]) {
const fs = projFs.shadow();
verifyProjectWithResolveJsonModuleWithFs(fs, configFile, allExpectedOutputs, ...expectedDiagnosticMessages);
}
function verifyProjectWithResolveJsonModuleWithFs(fs: vfs.FileSystem, configFile: string, allExpectedOutputs: ReadonlyArray<string>, ...expectedDiagnosticMessages: DiagnosticMessage[]) {
function verifyProjectWithResolveJsonModuleWithFs(fs: vfs.FileSystem, configFile: string, allExpectedOutputs: ReadonlyArray<string>, ...expectedDiagnosticMessages: fakes.ExpectedDiagnostic[]) {
const host = new fakes.SolutionBuilderHost(fs);
const builder = createSolutionBuilder(host, [configFile], { dry: false, force: false, verbose: false });
builder.buildAllProjects();
@@ -293,7 +321,10 @@ export class cNew {}`);
}
it("with resolveJsonModule and include only", () => {
verifyProjectWithResolveJsonModule("/src/tests/tsconfig_withInclude.json", Diagnostics.File_0_is_not_in_project_file_list_Projects_must_list_all_files_or_use_an_include_pattern);
verifyProjectWithResolveJsonModule("/src/tests/tsconfig_withInclude.json", [
Diagnostics.File_0_is_not_in_project_file_list_Projects_must_list_all_files_or_use_an_include_pattern,
"/src/tests/src/hello.json"
]);
});
it("with resolveJsonModule and include of *.json along with other include", () => {
@@ -408,7 +439,7 @@ export default hello.hello`);
"/src/c.ts"
];
function verifyBuild(modifyDiskLayout: (fs: vfs.FileSystem) => void, allExpectedOutputs: ReadonlyArray<string>, expectedDiagnostics: DiagnosticMessage[], expectedFileTraces: ReadonlyArray<string>) {
function verifyBuild(modifyDiskLayout: (fs: vfs.FileSystem) => void, allExpectedOutputs: ReadonlyArray<string>, expectedFileTraces: ReadonlyArray<string>, ...expectedDiagnostics: fakes.ExpectedDiagnostic[]) {
const fs = projFs.shadow();
const host = new fakes.SolutionBuilderHost(fs);
modifyDiskLayout(fs);
@@ -435,11 +466,11 @@ export const b = new A();`);
}
it("verify that it builds correctly", () => {
verifyBuild(noop, allExpectedOutputs, emptyArray, expectedFileTraces);
verifyBuild(noop, allExpectedOutputs, expectedFileTraces);
});
it("verify that it builds correctly when the referenced project uses different module resolution", () => {
verifyBuild(fs => modifyFsBTsToNonRelativeImport(fs, "classic"), allExpectedOutputs, emptyArray, expectedFileTraces);
verifyBuild(fs => modifyFsBTsToNonRelativeImport(fs, "classic"), allExpectedOutputs, expectedFileTraces);
});
it("verify that it build reports error about module not found with node resolution with external module name", () => {
@@ -451,10 +482,25 @@ export const b = new A();`);
];
verifyBuild(fs => modifyFsBTsToNonRelativeImport(fs, "node"),
allExpectedOutputs,
[Diagnostics.Cannot_find_module_0],
expectedFileTraces);
expectedFileTraces,
[Diagnostics.Cannot_find_module_0, "a"],
);
});
});
it("unittests:: tsbuild - when tsconfig extends the missing file", () => {
const projFs = loadProjectFromDisk("tests/projects/missingExtendedConfig");
const fs = projFs.shadow();
const host = new fakes.SolutionBuilderHost(fs);
const builder = createSolutionBuilder(host, ["/src/tsconfig.json"], {});
builder.buildAllProjects();
host.assertDiagnosticMessages(
[Diagnostics.The_specified_path_does_not_exist_Colon_0, "/src/foobar.json"],
[Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2, "/src/tsconfig.first.json", "[\"**/*\"]", "[]"],
[Diagnostics.The_specified_path_does_not_exist_Colon_0, "/src/foobar.json"],
[Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2, "/src/tsconfig.second.json", "[\"**/*\"]", "[]"]
);
});
}
export namespace OutFile {
@@ -462,11 +508,20 @@ export const b = new A();`);
describe("unittests:: tsbuild - baseline sectioned sourcemaps", () => {
let fs: vfs.FileSystem | undefined;
const actualReadFileMap = createMap<number>();
before(() => {
fs = outFileFs.shadow();
const host = new fakes.SolutionBuilderHost(fs);
const builder = createSolutionBuilder(host, ["/src/third"], { dry: false, force: false, verbose: false });
host.clearDiagnostics();
const originalReadFile = host.readFile;
host.readFile = path => {
// Dont record libs
if (path.startsWith("/src/")) {
actualReadFileMap.set(path, (actualReadFileMap.get(path) || 0) + 1);
}
return originalReadFile.call(host, path);
};
builder.buildAllProjects();
host.assertDiagnosticMessages(/*none*/);
});
@@ -478,6 +533,38 @@ export const b = new A();`);
// tslint:disable-next-line:no-null-keyword
Harness.Baseline.runBaseline("outfile-concat.js", patch ? vfs.formatPatch(patch) : null);
});
it("verify readFile calls", () => {
const expected = [
// Configs
"/src/third/tsconfig.json",
"/src/second/tsconfig.json",
"/src/first/tsconfig.json",
// Source files
"/src/third/third_part1.ts",
"/src/second/second_part1.ts",
"/src/second/second_part2.ts",
"/src/first/first_PART1.ts",
"/src/first/first_part2.ts",
"/src/first/first_part3.ts",
// outputs
"/src/first/bin/first-output.js",
"/src/first/bin/first-output.js.map",
"/src/first/bin/first-output.d.ts",
"/src/first/bin/first-output.d.ts.map",
"/src/2/second-output.js",
"/src/2/second-output.js.map",
"/src/2/second-output.d.ts",
"/src/2/second-output.d.ts.map"
];
assert.equal(actualReadFileMap.size, expected.length, `Expected: ${JSON.stringify(expected)} \nActual: ${JSON.stringify(arrayFrom(actualReadFileMap.entries()))}`);
expected.forEach(expectedValue => {
const actual = actualReadFileMap.get(expectedValue);
assert.equal(actual, 1, `Mismatch in read file call number for: ${expectedValue}\nExpected: ${JSON.stringify(expected)} \nActual: ${JSON.stringify(arrayFrom(actualReadFileMap.entries()))}`);
});
});
});
describe("unittests:: tsbuild - downstream prepend projects always get rebuilt", () => {
@@ -516,7 +603,7 @@ export const b = new A();`);
host.clearDiagnostics();
builder.buildAllProjects();
host.assertDiagnosticMessages(Diagnostics.The_files_list_in_config_file_0_is_empty);
host.assertDiagnosticMessages([Diagnostics.The_files_list_in_config_file_0_is_empty, "/src/no-references/tsconfig.json"]);
// Check for outputs to not be written.
for (const output of allExpectedOutputs) {
+215 -73
View File
@@ -2,18 +2,37 @@ namespace ts.tscWatch {
import projectsLocation = TestFSWithWatch.tsbuildProjectsLocation;
import getFilePathInProject = TestFSWithWatch.getTsBuildProjectFilePath;
import getFileFromProject = TestFSWithWatch.getTsBuildProjectFile;
type TsBuildWatchSystem = WatchedSystem & { writtenFiles: Map<true>; };
function createTsBuildWatchSystem(fileOrFolderList: ReadonlyArray<TestFSWithWatch.FileOrFolderOrSymLink>, params?: TestFSWithWatch.TestServerHostCreationParameters) {
const host = createWatchedSystem(fileOrFolderList, params) as TsBuildWatchSystem;
const originalWriteFile = host.writeFile;
host.writtenFiles = createMap<true>();
host.writeFile = (fileName, content) => {
originalWriteFile.call(host, fileName, content);
const path = host.toFullPath(fileName);
host.writtenFiles.set(path, true);
};
return host;
}
export function createSolutionBuilder(system: WatchedSystem, rootNames: ReadonlyArray<string>, defaultOptions?: BuildOptions) {
const host = createSolutionBuilderWithWatchHost(system);
return ts.createSolutionBuilder(host, rootNames, defaultOptions || { watch: true });
}
function createSolutionBuilderWithWatch(host: WatchedSystem, rootNames: ReadonlyArray<string>, defaultOptions?: BuildOptions) {
function createSolutionBuilderWithWatch(host: TsBuildWatchSystem, rootNames: ReadonlyArray<string>, defaultOptions?: BuildOptions) {
const solutionBuilder = createSolutionBuilder(host, rootNames, defaultOptions);
solutionBuilder.buildAllProjects();
solutionBuilder.startWatching();
return solutionBuilder;
}
type OutputFileStamp = [string, Date | undefined, boolean];
function transformOutputToOutputFileStamp(f: string, host: TsBuildWatchSystem): OutputFileStamp {
return [f, host.getModifiedTime(f), host.writtenFiles.has(host.toFullPath(f))] as OutputFileStamp;
}
describe("unittests:: tsbuild-watch program updates", () => {
const project = "sample1";
const enum SubProject {
@@ -61,12 +80,11 @@ namespace ts.tscWatch {
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 getOutputStamps(host: TsBuildWatchSystem, subProject: SubProject, baseFileNameWithoutExtension: string): OutputFileStamp[] {
return getOutputFileNames(subProject, baseFileNameWithoutExtension).map(f => transformOutputToOutputFileStamp(f, host));
}
function getOutputFileStamps(host: WatchedSystem, additionalFiles?: ReadonlyArray<[SubProject, string]>): OutputFileStamp[] {
function getOutputFileStamps(host: TsBuildWatchSystem, additionalFiles?: ReadonlyArray<[SubProject, string]>): OutputFileStamp[] {
const result = [
...getOutputStamps(host, SubProject.core, "anotherModule"),
...getOutputStamps(host, SubProject.core, "index"),
@@ -76,18 +94,21 @@ namespace ts.tscWatch {
if (additionalFiles) {
additionalFiles.forEach(([subProject, baseFileNameWithoutExtension]) => result.push(...getOutputStamps(host, subProject, baseFileNameWithoutExtension)));
}
host.writtenFiles.clear();
return result;
}
function verifyChangedFiles(actualStamps: OutputFileStamp[], oldTimeStamps: OutputFileStamp[], changedFiles: string[]) {
function verifyChangedFiles(actualStamps: OutputFileStamp[], oldTimeStamps: OutputFileStamp[], changedFiles: ReadonlyArray<string>, modifiedTimeStampFiles: ReadonlyArray<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`);
const expectedIsChanged = contains(changedFiles, actual[0]);
assert.equal(actual[2], contains(changedFiles, actual[0]), `Expected ${actual[0]} to be written.`);
if (expectedIsChanged || contains(modifiedTimeStampFiles, actual[0])) {
assert.isTrue((actual[1] || 0) > (old[1] || 0), `${actual[0]} file expected to have newer modified time because it is expected to ${expectedIsChanged ? "be changed" : "have modified time stamp"}`);
}
else {
assert.equal(actual[1], old[1], `${actual[0]} expected to not change`);
assert.equal(actual[1], old[1], `${actual[0]} expected to not change or have timestamp modified.`);
}
}
}
@@ -101,7 +122,7 @@ namespace ts.tscWatch {
const testProjectExpectedWatchedDirectoriesRecursive = [projectPath(SubProject.core), projectPath(SubProject.logic)];
function createSolutionInWatchMode(allFiles: ReadonlyArray<File>, defaultOptions?: BuildOptions, disableConsoleClears?: boolean) {
const host = createWatchedSystem(allFiles, { currentDirectory: projectsLocation });
const host = createTsBuildWatchSystem(allFiles, { currentDirectory: projectsLocation });
createSolutionBuilderWithWatch(host, [`${project}/${SubProject.tests}`], defaultOptions);
verifyWatches(host);
checkOutputErrorsInitial(host, emptyArray, disableConsoleClears);
@@ -112,7 +133,7 @@ namespace ts.tscWatch {
return host;
}
function verifyWatches(host: WatchedSystem) {
function verifyWatches(host: TsBuildWatchSystem) {
checkWatchedFiles(host, testProjectExpectedWatchedFiles);
checkWatchedDirectories(host, emptyArray, /*recursive*/ false);
checkWatchedDirectories(host, testProjectExpectedWatchedDirectoriesRecursive, /*recursive*/ true);
@@ -134,30 +155,50 @@ namespace ts.tscWatch {
const host = createSolutionInWatchMode(allFiles);
return { host, verifyChangeWithFile, verifyChangeAfterTimeout, verifyWatches };
function verifyChangeWithFile(fileName: string, content: string) {
function verifyChangeWithFile(fileName: string, content: string, local?: boolean) {
const outputFileStamps = getOutputFileStamps(host, additionalFiles);
host.writeFile(fileName, content);
verifyChangeAfterTimeout(outputFileStamps);
verifyChangeAfterTimeout(outputFileStamps, local);
}
function verifyChangeAfterTimeout(outputFileStamps: OutputFileStamp[]) {
function verifyChangeAfterTimeout(outputFileStamps: OutputFileStamp[], local?: boolean) {
host.checkTimeoutQueueLengthAndRun(1); // Builds core
const changedCore = getOutputFileStamps(host, additionalFiles);
verifyChangedFiles(changedCore, outputFileStamps, [
...getOutputFileNames(SubProject.core, "anotherModule"), // This should not be written really
...getOutputFileNames(SubProject.core, "index"),
...(additionalFiles ? getOutputFileNames(SubProject.core, newFileWithoutExtension) : emptyArray)
]);
host.checkTimeoutQueueLengthAndRun(1); // Builds logic
verifyChangedFiles(
changedCore,
outputFileStamps,
additionalFiles ?
getOutputFileNames(SubProject.core, newFileWithoutExtension) :
getOutputFileNames(SubProject.core, "index"), // Written files are new file or core index file thats changed
[
...getOutputFileNames(SubProject.core, "anotherModule"),
...(additionalFiles ? getOutputFileNames(SubProject.core, "index") : emptyArray)
]
);
host.checkTimeoutQueueLengthAndRun(1); // Builds logic or updates timestamps
const changedLogic = getOutputFileStamps(host, additionalFiles);
verifyChangedFiles(changedLogic, changedCore, [
...getOutputFileNames(SubProject.logic, "index") // Again these need not be written
]);
verifyChangedFiles(
changedLogic,
changedCore,
additionalFiles || local ?
emptyArray :
getOutputFileNames(SubProject.logic, "index"),
additionalFiles || local ?
getOutputFileNames(SubProject.logic, "index") :
emptyArray
);
host.checkTimeoutQueueLengthAndRun(1); // Builds tests
const changedTests = getOutputFileStamps(host, additionalFiles);
verifyChangedFiles(changedTests, changedLogic, [
...getOutputFileNames(SubProject.tests, "index") // Again these need not be written
]);
verifyChangedFiles(
changedTests,
changedLogic,
additionalFiles || local ?
emptyArray :
getOutputFileNames(SubProject.tests, "index"),
additionalFiles || local ?
getOutputFileNames(SubProject.tests, "index") :
emptyArray
);
host.checkTimeoutQueueLength(0);
checkOutputErrorsIncremental(host, emptyArray);
verifyWatches();
@@ -193,19 +234,9 @@ export class someClass2 { }`);
});
it("non local change does not start build of referencing projects", () => {
const host = createSolutionInWatchMode(allFiles);
const outputFileStamps = getOutputFileStamps(host);
host.writeFile(core[1].path, `${core[1].content}
function foo() { }`);
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.checkTimeoutQueueLength(0);
checkOutputErrorsIncremental(host, emptyArray);
verifyWatches(host);
const { verifyChangeWithFile } = createSolutionInWatchModeToVerifyChanges();
verifyChangeWithFile(core[1].path, `${core[1].content}
function foo() { }`, /*local*/ true);
});
it("builds when new file is added, and its subsequent updates", () => {
@@ -242,7 +273,7 @@ export class someClass2 { }`);
it("watches config files that are not present", () => {
const allFiles = [libFile, ...core, logic[1], ...tests];
const host = createWatchedSystem(allFiles, { currentDirectory: projectsLocation });
const host = createTsBuildWatchSystem(allFiles, { currentDirectory: projectsLocation });
createSolutionBuilderWithWatch(host, [`${project}/${SubProject.tests}`]);
checkWatchedFiles(host, [core[0], core[1], core[2]!, logic[0], ...tests].map(f => f.path.toLowerCase())); // tslint:disable-line no-unnecessary-type-assertion (TODO: type assertion should be necessary)
checkWatchedDirectories(host, emptyArray, /*recursive*/ false);
@@ -268,14 +299,10 @@ export class someClass2 { }`);
host.writeFile(logic[0].path, logic[0].content);
host.checkTimeoutQueueLengthAndRun(1); // Builds logic
const changedLogic = getOutputFileStamps(host);
verifyChangedFiles(changedLogic, initial, [
...getOutputFileNames(SubProject.logic, "index")
]);
verifyChangedFiles(changedLogic, initial, getOutputFileNames(SubProject.logic, "index"), emptyArray);
host.checkTimeoutQueueLengthAndRun(1); // Builds tests
const changedTests = getOutputFileStamps(host);
verifyChangedFiles(changedTests, changedLogic, [
...getOutputFileNames(SubProject.tests, "index")
]);
verifyChangedFiles(changedTests, changedLogic, getOutputFileNames(SubProject.tests, "index"), emptyArray);
host.checkTimeoutQueueLength(0);
checkOutputErrorsIncremental(host, emptyArray);
verifyWatches(host);
@@ -305,7 +332,7 @@ export class someClass2 { }`);
};
const projectFiles = [coreTsConfig, coreIndex, logicTsConfig, logicIndex];
const host = createWatchedSystem([libFile, ...projectFiles], { currentDirectory: projectsLocation });
const host = createTsBuildWatchSystem([libFile, ...projectFiles], { currentDirectory: projectsLocation });
createSolutionBuilderWithWatch(host, [`${project}/${SubProject.logic}`]);
verifyWatches();
checkOutputErrorsInitial(host, emptyArray);
@@ -318,6 +345,7 @@ export class someClass2 { }`);
verifyChangeInCore(`${coreIndex.content}
function myFunc() { return 10; }`);
// TODO:: local change does not build logic.js because builder doesnt find any changes in input files to generate output
// Make local change to function bar
verifyChangeInCore(`${coreIndex.content}
function myFunc() { return 100; }`);
@@ -328,14 +356,20 @@ function myFunc() { return 100; }`);
host.checkTimeoutQueueLengthAndRun(1); // Builds core
const changedCore = getOutputFileStamps();
verifyChangedFiles(changedCore, outputFileStamps, [
...getOutputFileNames(SubProject.core, "index")
]);
verifyChangedFiles(
changedCore,
outputFileStamps,
getOutputFileNames(SubProject.core, "index"),
emptyArray
);
host.checkTimeoutQueueLengthAndRun(1); // Builds logic
const changedLogic = getOutputFileStamps();
verifyChangedFiles(changedLogic, changedCore, [
...getOutputFileNames(SubProject.logic, "index")
]);
verifyChangedFiles(
changedLogic,
changedCore,
getOutputFileNames(SubProject.logic, "index"),
emptyArray
);
host.checkTimeoutQueueLength(0);
checkOutputErrorsIncremental(host, emptyArray);
verifyWatches();
@@ -346,6 +380,7 @@ function myFunc() { return 100; }`);
...getOutputStamps(host, SubProject.core, "index"),
...getOutputStamps(host, SubProject.logic, "index"),
];
host.writtenFiles.clear();
return result;
}
@@ -389,7 +424,7 @@ createSomeObject().message;`
};
const files = [libFile, libraryTs, libraryTsconfig, appTs, appTsconfig];
const host = createWatchedSystem(files, { currentDirectory: `${projectsLocation}/${project}` });
const host = createTsBuildWatchSystem(files, { currentDirectory: `${projectsLocation}/${project}` });
createSolutionBuilderWithWatch(host, ["App"]);
checkOutputErrorsInitial(host, emptyArray);
@@ -418,7 +453,7 @@ let y: string = 10;`);
host.checkTimeoutQueueLengthAndRun(1); // Builds logic
const changedLogic = getOutputFileStamps(host);
verifyChangedFiles(changedLogic, outputFileStamps, emptyArray);
verifyChangedFiles(changedLogic, outputFileStamps, emptyArray, emptyArray);
host.checkTimeoutQueueLength(0);
checkOutputErrorsIncremental(host, [
`sample1/logic/index.ts(8,5): error TS2322: Type '10' is not assignable to type 'string'.\n`
@@ -429,7 +464,7 @@ let x: string = 10;`);
host.checkTimeoutQueueLengthAndRun(1); // Builds core
const changedCore = getOutputFileStamps(host);
verifyChangedFiles(changedCore, changedLogic, emptyArray);
verifyChangedFiles(changedCore, changedLogic, emptyArray, emptyArray);
host.checkTimeoutQueueLength(0);
checkOutputErrorsIncremental(host, [
`sample1/core/index.ts(5,5): error TS2322: Type '10' is not assignable to type 'string'.\n`,
@@ -444,11 +479,118 @@ let x: string = 10;`);
it("when preserveWatchOutput is passed on command line", () => {
verifyIncrementalErrors({ preserveWatchOutput: true, watch: true }, /*disabledConsoleClear*/ true);
});
describe("when declaration emit errors are present", () => {
const solution = "solution";
const subProject = "app";
const subProjectLocation = `${projectsLocation}/${solution}/${subProject}`;
const fileWithError: File = {
path: `${subProjectLocation}/fileWithError.ts`,
content: `export var myClassWithError = class {
tags() { }
private p = 12
};`
};
const fileWithFixedError: File = {
path: fileWithError.path,
content: fileWithError.content.replace("private p = 12", "")
};
const fileWithoutError: File = {
path: `${subProjectLocation}/fileWithoutError.ts`,
content: `export class myClass { }`
};
const tsconfig: File = {
path: `${subProjectLocation}/tsconfig.json`,
content: JSON.stringify({ compilerOptions: { composite: true } })
};
const expectedDtsEmitErrors = [
`${subProject}/fileWithError.ts(1,12): error TS4094: Property 'p' of exported class expression may not be private or protected.\n`
];
const outputs = [
changeExtension(fileWithError.path, Extension.Js),
changeExtension(fileWithError.path, Extension.Dts),
changeExtension(fileWithoutError.path, Extension.Js),
changeExtension(fileWithoutError.path, Extension.Dts)
];
function verifyDtsErrors(host: TsBuildWatchSystem, isIncremental: boolean, expectedErrors: ReadonlyArray<string>) {
(isIncremental ? checkOutputErrorsIncremental : checkOutputErrorsInitial)(host, expectedErrors);
outputs.forEach(f => assert.equal(host.fileExists(f), !expectedErrors.length, `Expected file ${f} to ${!expectedErrors.length ? "exist" : "not exist"}`));
}
function createSolutionWithWatch(withFixedError?: true) {
const files = [libFile, withFixedError ? fileWithFixedError : fileWithError, fileWithoutError, tsconfig];
const host = createTsBuildWatchSystem(files, { currentDirectory: `${projectsLocation}/${solution}` });
createSolutionBuilderWithWatch(host, [subProject]);
verifyDtsErrors(host, /*isIncremental*/ false, withFixedError ? emptyArray : expectedDtsEmitErrors);
return host;
}
function incrementalBuild(host: TsBuildWatchSystem) {
host.checkTimeoutQueueLengthAndRun(1); // Build the app
host.checkTimeoutQueueLength(0);
}
function fixError(host: TsBuildWatchSystem) {
// Fix error
host.writeFile(fileWithError.path, fileWithFixedError.content);
host.writtenFiles.clear();
incrementalBuild(host);
verifyDtsErrors(host, /*isIncremental*/ true, emptyArray);
}
it("when fixing error files all files are emitted", () => {
const host = createSolutionWithWatch();
fixError(host);
});
it("when file with no error changes, declaration errors are reported", () => {
const host = createSolutionWithWatch();
host.writeFile(fileWithoutError.path, fileWithoutError.content.replace(/myClass/g, "myClass2"));
incrementalBuild(host);
verifyDtsErrors(host, /*isIncremental*/ true, expectedDtsEmitErrors);
});
describe("when reporting errors on introducing error", () => {
function createSolutionWithIncrementalError() {
const host = createSolutionWithWatch(/*withFixedError*/ true);
host.writeFile(fileWithError.path, fileWithError.content);
host.writtenFiles.clear();
incrementalBuild(host);
checkOutputErrorsIncremental(host, expectedDtsEmitErrors);
assert.equal(host.writtenFiles.size, 0, `Expected not to write any files: ${arrayFrom(host.writtenFiles.keys())}`);
return host;
}
function verifyWrittenFile(host: TsBuildWatchSystem, f: string) {
assert.isTrue(host.writtenFiles.has(host.toFullPath(f)), `Expected to write ${f}: ${arrayFrom(host.writtenFiles.keys())}`);
}
it("when fixing errors only changed file is emitted", () => {
const host = createSolutionWithIncrementalError();
fixError(host);
assert.equal(host.writtenFiles.size, 2, `Expected to write only changed files: ${arrayFrom(host.writtenFiles.keys())}`);
verifyWrittenFile(host, outputs[0]);
verifyWrittenFile(host, outputs[1]);
});
it("when file with no error changes, declaration errors are reported", () => {
const host = createSolutionWithIncrementalError();
host.writeFile(fileWithoutError.path, fileWithoutError.content.replace(/myClass/g, "myClass2"));
host.writtenFiles.clear();
incrementalBuild(host);
checkOutputErrorsIncremental(host, expectedDtsEmitErrors);
assert.equal(host.writtenFiles.size, 0, `Expected not to write any files: ${arrayFrom(host.writtenFiles.keys())}`);
});
});
});
});
describe("tsc-watch and tsserver works with project references", () => {
describe("invoking when references are already built", () => {
function verifyWatchesOfProject(host: WatchedSystem, expectedWatchedFiles: ReadonlyArray<string>, expectedWatchedDirectoriesRecursive: ReadonlyArray<string>, expectedWatchedDirectories?: ReadonlyArray<string>) {
function verifyWatchesOfProject(host: TsBuildWatchSystem, expectedWatchedFiles: ReadonlyArray<string>, expectedWatchedDirectoriesRecursive: ReadonlyArray<string>, expectedWatchedDirectories?: ReadonlyArray<string>) {
checkWatchedFilesDetailed(host, expectedWatchedFiles, 1);
checkWatchedDirectoriesDetailed(host, expectedWatchedDirectories || emptyArray, 1, /*recursive*/ false);
checkWatchedDirectoriesDetailed(host, expectedWatchedDirectoriesRecursive, 1, /*recursive*/ true);
@@ -457,9 +599,9 @@ let x: string = 10;`);
function createSolutionOfProject(allFiles: ReadonlyArray<File>,
currentDirectory: string,
solutionBuilderconfig: string,
getOutputFileStamps: (host: WatchedSystem) => ReadonlyArray<OutputFileStamp>) {
getOutputFileStamps: (host: TsBuildWatchSystem) => ReadonlyArray<OutputFileStamp>) {
// Build the composite project
const host = createWatchedSystem(allFiles, { currentDirectory });
const host = createTsBuildWatchSystem(allFiles, { currentDirectory });
const solutionBuilder = createSolutionBuilder(host, [solutionBuilderconfig], {});
solutionBuilder.buildAllProjects();
const outputFileStamps = getOutputFileStamps(host);
@@ -474,7 +616,7 @@ let x: string = 10;`);
currentDirectory: string,
solutionBuilderconfig: string,
watchConfig: string,
getOutputFileStamps: (host: WatchedSystem) => ReadonlyArray<OutputFileStamp>) {
getOutputFileStamps: (host: TsBuildWatchSystem) => ReadonlyArray<OutputFileStamp>) {
// Build the composite project
const { host, solutionBuilder } = createSolutionOfProject(allFiles, currentDirectory, solutionBuilderconfig, getOutputFileStamps);
@@ -489,7 +631,7 @@ let x: string = 10;`);
currentDirectory: string,
solutionBuilderconfig: string,
openFileName: string,
getOutputFileStamps: (host: WatchedSystem) => ReadonlyArray<OutputFileStamp>) {
getOutputFileStamps: (host: TsBuildWatchSystem) => ReadonlyArray<OutputFileStamp>) {
// Build the composite project
const { host, solutionBuilder } = createSolutionOfProject(allFiles, currentDirectory, solutionBuilderconfig, getOutputFileStamps);
@@ -525,12 +667,12 @@ let x: string = 10;`);
return createSolutionAndServiceOfProject(allFiles, projectsLocation, `${project}/${SubProject.tests}`, tests[1].path, getOutputFileStamps);
}
function verifyWatches(host: WatchedSystem, withTsserver?: boolean) {
function verifyWatches(host: TsBuildWatchSystem, withTsserver?: boolean) {
verifyWatchesOfProject(host, withTsserver ? expectedWatchedFiles.filter(f => f !== tests[1].path.toLowerCase()) : expectedWatchedFiles, expectedWatchedDirectoriesRecursive);
}
function verifyScenario(
edit: (host: WatchedSystem, solutionBuilder: SolutionBuilder) => void,
edit: (host: TsBuildWatchSystem, solutionBuilder: SolutionBuilder) => void,
expectedFilesAfterEdit: ReadonlyArray<string>
) {
it("with tsc-watch", () => {
@@ -633,7 +775,7 @@ export function gfoo() {
}
function verifyWatchState(
host: WatchedSystem,
host: TsBuildWatchSystem,
watch: Watch,
expectedProgramFiles: ReadonlyArray<string>,
expectedWatchedFiles: ReadonlyArray<string>,
@@ -720,20 +862,20 @@ export function gfoo() {
return createSolutionAndServiceOfProject(allFiles, getProjectPath(project), configToBuild, cTs.path, getOutputFileStamps);
}
function getOutputFileStamps(host: WatchedSystem) {
return expectedFiles.map(file => [file, host.getModifiedTime(file)] as OutputFileStamp);
function getOutputFileStamps(host: TsBuildWatchSystem) {
return expectedFiles.map(file => transformOutputToOutputFileStamp(file, host));
}
function verifyProgram(host: WatchedSystem, watch: Watch) {
function verifyProgram(host: TsBuildWatchSystem, watch: Watch) {
verifyWatchState(host, watch, expectedProgramFiles, expectedWatchedFiles, expectedWatchedDirectoriesRecursive, defaultDependencies, expectedWatchedDirectories);
}
function verifyProject(host: WatchedSystem, service: projectSystem.TestProjectService, orphanInfos?: ReadonlyArray<string>) {
function verifyProject(host: TsBuildWatchSystem, service: projectSystem.TestProjectService, orphanInfos?: ReadonlyArray<string>) {
verifyServerState(host, service, expectedProgramFiles, expectedWatchedFiles, expectedWatchedDirectoriesRecursive, orphanInfos);
}
function verifyServerState(
host: WatchedSystem,
host: TsBuildWatchSystem,
service: projectSystem.TestProjectService,
expectedProgramFiles: ReadonlyArray<string>,
expectedWatchedFiles: ReadonlyArray<string>,
@@ -753,13 +895,13 @@ export function gfoo() {
}
function verifyScenario(
edit: (host: WatchedSystem, solutionBuilder: SolutionBuilder) => void,
edit: (host: TsBuildWatchSystem, solutionBuilder: SolutionBuilder) => void,
expectedEditErrors: ReadonlyArray<string>,
expectedProgramFiles: ReadonlyArray<string>,
expectedWatchedFiles: ReadonlyArray<string>,
expectedWatchedDirectoriesRecursive: ReadonlyArray<string>,
dependencies: ReadonlyArray<[string, ReadonlyArray<string>]>,
revert?: (host: WatchedSystem) => void,
revert?: (host: TsBuildWatchSystem) => void,
orphanInfosAfterEdit?: ReadonlyArray<string>,
orphanInfosAfterRevert?: ReadonlyArray<string>) {
it("with tsc-watch", () => {
@@ -978,8 +1120,8 @@ export function gfoo() {
[refs.path, [refs.path]],
[cTsFile.path, [cTsFile.path, refs.path, bDts]]
];
function getOutputFileStamps(host: WatchedSystem) {
return expectedFiles.map(file => [file, host.getModifiedTime(file)] as OutputFileStamp);
function getOutputFileStamps(host: TsBuildWatchSystem) {
return expectedFiles.map(file => transformOutputToOutputFileStamp(file, host));
}
const { host, watch } = createSolutionAndWatchModeOfProject(allFiles, getProjectPath(project), "tsconfig.c.json", "tsconfig.c.json", getOutputFileStamps);
verifyWatchState(host, watch, expectedProgramFiles, expectedWatchedFiles, expectedWatchedDirectoriesRecursive, defaultDependencies);
+136 -4
View File
@@ -7,6 +7,7 @@ namespace ts.projectSystem {
const session = createSession(createServerHost([aTs, bTs]));
openFilesForSession([bTs], session);
// rename fails with allowRenameOfImportPath disabled
const response1 = executeSessionRequest<protocol.RenameRequest, protocol.RenameResponse>(session, protocol.CommandTypes.Rename, protocolFileLocationFromSubstring(bTs, 'a";'));
assert.deepEqual<protocol.RenameResponseBody | undefined>(response1, {
info: {
@@ -16,6 +17,7 @@ namespace ts.projectSystem {
locs: [{ file: bTs.path, locs: [protocolRenameSpanFromSubstring(bTs.content, "./a")] }],
});
// rename succeeds with allowRenameOfImportPath enabled in host
session.getProjectService().setHostConfiguration({ preferences: { allowRenameOfImportPath: true } });
const response2 = executeSessionRequest<protocol.RenameRequest, protocol.RenameResponse>(session, protocol.CommandTypes.Rename, protocolFileLocationFromSubstring(bTs, 'a";'));
assert.deepEqual<protocol.RenameResponseBody | undefined>(response2, {
@@ -30,15 +32,83 @@ namespace ts.projectSystem {
},
locs: [{ file: bTs.path, locs: [protocolRenameSpanFromSubstring(bTs.content, "./a")] }],
});
// rename succeeds with allowRenameOfImportPath enabled in file
session.getProjectService().setHostConfiguration({ preferences: { allowRenameOfImportPath: false } });
session.getProjectService().setHostConfiguration({ file: "/b.ts", formatOptions: {}, preferences: { allowRenameOfImportPath: true } });
const response3 = executeSessionRequest<protocol.RenameRequest, protocol.RenameResponse>(session, protocol.CommandTypes.Rename, protocolFileLocationFromSubstring(bTs, 'a";'));
assert.deepEqual<protocol.RenameResponseBody | undefined>(response3, {
info: {
canRename: true,
fileToRename: aTs.path,
displayName: aTs.path,
fullDisplayName: aTs.path,
kind: ScriptElementKind.moduleElement,
kindModifiers: "",
triggerSpan: protocolTextSpanFromSubstring(bTs.content, "a", { index: 1 }),
},
locs: [{ file: bTs.path, locs: [protocolRenameSpanFromSubstring(bTs.content, "./a")] }],
});
});
it("works with prefixText and suffixText", () => {
it("works with prefixText and suffixText when enabled", () => {
const aTs: File = { path: "/a.ts", content: "const x = 0; const o = { x };" };
const session = createSession(createServerHost([aTs]));
const host = createServerHost([aTs]);
const session = createSession(host);
openFilesForSession([aTs], session);
const response = executeSessionRequest<protocol.RenameRequest, protocol.RenameResponse>(session, protocol.CommandTypes.Rename, protocolFileLocationFromSubstring(aTs, "x"));
assert.deepEqual<protocol.RenameResponseBody | undefined>(response, {
// rename with prefixText and suffixText disabled
const response1 = executeSessionRequest<protocol.RenameRequest, protocol.RenameResponse>(session, protocol.CommandTypes.Rename, protocolFileLocationFromSubstring(aTs, "x"));
assert.deepEqual<protocol.RenameResponseBody | undefined>(response1, {
info: {
canRename: true,
fileToRename: undefined,
displayName: "x",
fullDisplayName: "x",
kind: ScriptElementKind.constElement,
kindModifiers: ScriptElementKindModifier.none,
triggerSpan: protocolTextSpanFromSubstring(aTs.content, "x"),
},
locs: [
{
file: aTs.path,
locs: [
protocolRenameSpanFromSubstring(aTs.content, "x"),
protocolRenameSpanFromSubstring(aTs.content, "x", { index: 1 }),
],
},
],
});
// rename with prefixText and suffixText enabled in host
session.getProjectService().setHostConfiguration({ preferences: { providePrefixAndSuffixTextForRename: true } });
const response2 = executeSessionRequest<protocol.RenameRequest, protocol.RenameResponse>(session, protocol.CommandTypes.Rename, protocolFileLocationFromSubstring(aTs, "x"));
assert.deepEqual<protocol.RenameResponseBody | undefined>(response2, {
info: {
canRename: true,
fileToRename: undefined,
displayName: "x",
fullDisplayName: "x",
kind: ScriptElementKind.constElement,
kindModifiers: ScriptElementKindModifier.none,
triggerSpan: protocolTextSpanFromSubstring(aTs.content, "x"),
},
locs: [
{
file: aTs.path,
locs: [
protocolRenameSpanFromSubstring(aTs.content, "x"),
protocolRenameSpanFromSubstring(aTs.content, "x", { index: 1 }, { prefixText: "x: " }),
],
},
],
});
// rename with prefixText and suffixText enabled for file
session.getProjectService().setHostConfiguration({ preferences: { providePrefixAndSuffixTextForRename: false } });
session.getProjectService().setHostConfiguration({ file: "/a.ts", formatOptions: {}, preferences: { providePrefixAndSuffixTextForRename: true } });
const response3 = executeSessionRequest<protocol.RenameRequest, protocol.RenameResponse>(session, protocol.CommandTypes.Rename, protocolFileLocationFromSubstring(aTs, "x"));
assert.deepEqual<protocol.RenameResponseBody | undefined>(response3, {
info: {
canRename: true,
fileToRename: undefined,
@@ -59,5 +129,67 @@ namespace ts.projectSystem {
],
});
});
it("rename behavior is based on file of rename initiation", () => {
const aTs: File = { path: "/a.ts", content: "const x = 1; export { x };" };
const bTs: File = { path: "/b.ts", content: `import { x } from "./a"; const y = x + 1;` };
const host = createServerHost([aTs, bTs]);
const session = createSession(host);
openFilesForSession([aTs, bTs], session);
// rename from file with prefixText and suffixText enabled
session.getProjectService().setHostConfiguration({ file: "/a.ts", formatOptions: {}, preferences: { providePrefixAndSuffixTextForRename: true } });
const response1 = executeSessionRequest<protocol.RenameRequest, protocol.RenameResponse>(session, protocol.CommandTypes.Rename, protocolFileLocationFromSubstring(aTs, "x"));
assert.deepEqual<protocol.RenameResponseBody | undefined>(response1, {
info: {
canRename: true,
fileToRename: undefined,
displayName: "x",
fullDisplayName: "x",
kind: ScriptElementKind.constElement,
kindModifiers: ScriptElementKindModifier.none,
triggerSpan: protocolTextSpanFromSubstring(aTs.content, "x"),
},
locs: [
{
file: aTs.path,
locs: [
protocolRenameSpanFromSubstring(aTs.content, "x"),
protocolRenameSpanFromSubstring(aTs.content, "x", { index: 2 }, { suffixText: " as x" }),
],
},
],
});
// rename from file with prefixText and suffixText disabled
const response2 = executeSessionRequest<protocol.RenameRequest, protocol.RenameResponse>(session, protocol.CommandTypes.Rename, protocolFileLocationFromSubstring(bTs, "x"));
assert.deepEqual<protocol.RenameResponseBody | undefined>(response2, {
info: {
canRename: true,
fileToRename: undefined,
displayName: "x",
fullDisplayName: "x",
kind: ScriptElementKind.alias,
kindModifiers: ScriptElementKindModifier.none,
triggerSpan: protocolTextSpanFromSubstring(bTs.content, "x"),
},
locs: [
{
file: bTs.path,
locs: [
protocolRenameSpanFromSubstring(bTs.content, "x"),
protocolRenameSpanFromSubstring(bTs.content, "x", { index: 1 })
]
},
{
file: aTs.path,
locs: [
protocolRenameSpanFromSubstring(aTs.content, "x"),
protocolRenameSpanFromSubstring(aTs.content, "x", { index: 2 }),
],
},
],
});
});
});
}