Add more tests that verify semantic diagnostics cache

This commit is contained in:
Sheetal Nandi 2018-12-12 15:33:46 -08:00
parent 7c92d09e23
commit f97a2b3f90

View File

@ -1319,92 +1319,6 @@ export default test;`;
}
});
it("updates errors when deep import file changes", () => {
const currentDirectory = "/user/username/projects/myproject";
const aFile: File = {
path: `${currentDirectory}/a.ts`,
content: `import {B} from './b';
declare var console: any;
let b = new B();
console.log(b.c.d);`
};
const bFile: File = {
path: `${currentDirectory}/b.ts`,
content: `import {C} from './c';
export class B
{
c = new C();
}`
};
const cFile: File = {
path: `${currentDirectory}/c.ts`,
content: `export class C
{
d = 1;
}`
};
const config: File = {
path: `${currentDirectory}/tsconfig.json`,
content: `{}`
};
const files = [aFile, bFile, cFile, config, libFile];
const host = createWatchedSystem(files, { currentDirectory });
const watch = createWatchOfConfigFile("tsconfig.json", host);
checkProgramActualFiles(watch(), [aFile.path, bFile.path, cFile.path, libFile.path]);
checkOutputErrorsInitial(host, emptyArray);
const modifiedTimeOfAJs = host.getModifiedTime(`${currentDirectory}/a.js`);
host.writeFile(cFile.path, cFile.content.replace("d", "d2"));
host.runQueuedTimeoutCallbacks();
checkOutputErrorsIncremental(host, [
getDiagnosticOfFileFromProgram(watch(), aFile.path, aFile.content.lastIndexOf("d"), 1, Diagnostics.Property_0_does_not_exist_on_type_1, "d", "C")
]);
// File a need not be rewritten
assert.equal(host.getModifiedTime(`${currentDirectory}/a.js`), modifiedTimeOfAJs);
});
it("updates errors when deep import through declaration file changes", () => {
const currentDirectory = "/user/username/projects/myproject";
const aFile: File = {
path: `${currentDirectory}/a.ts`,
content: `import {B} from './b';
declare var console: any;
let b = new B();
console.log(b.c.d);`
};
const bFile: File = {
path: `${currentDirectory}/b.d.ts`,
content: `import {C} from './c';
export class B
{
c: C;
}`
};
const cFile: File = {
path: `${currentDirectory}/c.d.ts`,
content: `export class C
{
d: number;
}`
};
const config: File = {
path: `${currentDirectory}/tsconfig.json`,
content: `{}`
};
const files = [aFile, bFile, cFile, config, libFile];
const host = createWatchedSystem(files, { currentDirectory });
const watch = createWatchOfConfigFile("tsconfig.json", host);
checkProgramActualFiles(watch(), [aFile.path, bFile.path, cFile.path, libFile.path]);
checkOutputErrorsInitial(host, emptyArray);
const modifiedTimeOfAJs = host.getModifiedTime(`${currentDirectory}/a.js`);
host.writeFile(cFile.path, cFile.content.replace("d", "d2"));
host.runQueuedTimeoutCallbacks();
checkOutputErrorsIncremental(host, [
getDiagnosticOfFileFromProgram(watch(), aFile.path, aFile.content.lastIndexOf("d"), 1, Diagnostics.Property_0_does_not_exist_on_type_1, "d", "C")
]);
// File a need not be rewritten
assert.equal(host.getModifiedTime(`${currentDirectory}/a.js`), modifiedTimeOfAJs);
});
it("updates errors when strictNullChecks changes", () => {
const currentDirectory = "/user/username/projects/myproject";
const aFile: File = {
@ -1477,98 +1391,6 @@ foo().hello`
checkOutputErrorsIncremental(host, emptyArray);
});
describe("updates errors when file transitively exported file changes", () => {
const projectLocation = "/user/username/projects/myproject";
const config: File = {
path: `${projectLocation}/tsconfig.json`,
content: JSON.stringify({
files: ["app.ts"],
compilerOptions: { baseUrl: "." }
})
};
const app: File = {
path: `${projectLocation}/app.ts`,
content: `import { Data } from "lib2/public";
export class App {
public constructor() {
new Data().test();
}
}`
};
const lib2Public: File = {
path: `${projectLocation}/lib2/public.ts`,
content: `export * from "./data";`
};
const lib2Data: File = {
path: `${projectLocation}/lib2/data.ts`,
content: `import { ITest } from "lib1/public";
export class Data {
public test() {
const result: ITest = {
title: "title"
}
return result;
}
}`
};
const lib1Public: File = {
path: `${projectLocation}/lib1/public.ts`,
content: `export * from "./tools/public";`
};
const lib1ToolsPublic: File = {
path: `${projectLocation}/lib1/tools/public.ts`,
content: `export * from "./tools.interface";`
};
const lib1ToolsInterface: File = {
path: `${projectLocation}/lib1/tools/tools.interface.ts`,
content: `export interface ITest {
title: string;
}`
};
function verifyTransitiveExports(filesWithoutConfig: ReadonlyArray<File>) {
const files = [config, ...filesWithoutConfig];
const host = createWatchedSystem(files, { currentDirectory: projectLocation });
const watch = createWatchOfConfigFile(config.path, host);
checkProgramActualFiles(watch(), filesWithoutConfig.map(f => f.path));
checkOutputErrorsInitial(host, emptyArray);
host.writeFile(lib1ToolsInterface.path, lib1ToolsInterface.content.replace("title", "title2"));
host.checkTimeoutQueueLengthAndRun(1);
checkProgramActualFiles(watch(), filesWithoutConfig.map(f => f.path));
checkOutputErrorsIncremental(host, [
"lib2/data.ts(5,13): error TS2322: Type '{ title: string; }' is not assignable to type 'ITest'.\n Object literal may only specify known properties, but 'title' does not exist in type 'ITest'. Did you mean to write 'title2'?\n"
]);
}
it("when there are no circular import and exports", () => {
verifyTransitiveExports([libFile, app, lib2Public, lib2Data, lib1Public, lib1ToolsPublic, lib1ToolsInterface]);
});
it("when there are circular import and exports", () => {
const lib2Data: File = {
path: `${projectLocation}/lib2/data.ts`,
content: `import { ITest } from "lib1/public"; import { Data2 } from "./data2";
export class Data {
public dat?: Data2; public test() {
const result: ITest = {
title: "title"
}
return result;
}
}`
};
const lib2Data2: File = {
path: `${projectLocation}/lib2/data2.ts`,
content: `import { Data } from "./data";
export class Data2 {
public dat?: Data;
}`
};
verifyTransitiveExports([libFile, app, lib2Public, lib2Data, lib2Data2, lib1Public, lib1ToolsPublic, lib1ToolsInterface]);
});
});
describe("updates errors in lib file", () => {
const currentDirectory = "/user/username/projects/myproject";
const field = "fullscreen";
@ -1711,6 +1533,11 @@ interface Document {
});
describe("Emit times and Error updates in builder after program changes", () => {
const currentDirectory = "/user/username/projects/myproject";
const config: File = {
path: `${currentDirectory}/tsconfig.json`,
content: `{}`
};
function getOutputFileStampAndError(host: WatchedSystem, watch: Watch, file: File) {
const builderProgram = watch.getBuilderProgram();
const state = builderProgram.getState();
@ -1757,8 +1584,108 @@ interface Document {
}
}
interface VerifyEmitAndErrorUpdates {
change: (host: WatchedSystem) => void;
getInitialErrors: (watch: Watch) => ReadonlyArray<Diagnostic> | ReadonlyArray<string>;
getIncrementalErrors: (watch: Watch) => ReadonlyArray<Diagnostic> | ReadonlyArray<string>;
filesWithNewEmit: ReadonlyArray<File>;
filesWithOnlyErrorRefresh: ReadonlyArray<File>;
filesNotTouched: ReadonlyArray<File>;
configFile?: File;
}
function verifyEmitAndErrorUpdates({ filesWithNewEmit, filesWithOnlyErrorRefresh, filesNotTouched, configFile = config, change, getInitialErrors, getIncrementalErrors }: VerifyEmitAndErrorUpdates) {
const nonLibFiles = [...filesWithNewEmit, ...filesWithOnlyErrorRefresh, ...filesNotTouched];
const files = [...nonLibFiles, configFile, libFile];
const host = createWatchedSystem(files, { currentDirectory });
const watch = createWatchOfConfigFile("tsconfig.json", host);
checkProgramActualFiles(watch(), [...nonLibFiles.map(f => f.path), libFile.path]);
checkOutputErrorsInitial(host, getInitialErrors(watch));
const beforeChange = getOutputFileStampsAndErrors(host, watch, nonLibFiles);
change(host);
host.runQueuedTimeoutCallbacks();
checkOutputErrorsIncremental(host, getIncrementalErrors(watch));
const afterChange = getOutputFileStampsAndErrors(host, watch, nonLibFiles);
filesWithNewEmit.forEach(file => verifyOutputFileStampsAndErrors(file, /*emitExpected*/ true, /*errorRefershExpected*/ true, beforeChange, afterChange));
filesWithOnlyErrorRefresh.forEach(file => verifyOutputFileStampsAndErrors(file, /*emitExpected*/ false, /*errorRefershExpected*/ true, beforeChange, afterChange));
filesNotTouched.forEach(file => verifyOutputFileStampsAndErrors(file, /*emitExpected*/ false, /*errorRefershExpected*/ false, beforeChange, afterChange));
}
describe("deep import changes", () => {
const aFile: File = {
path: `${currentDirectory}/a.ts`,
content: `import {B} from './b';
declare var console: any;
let b = new B();
console.log(b.c.d);`
};
function verifyDeepImportChange(bFile: File, cFile: File) {
const filesWithNewEmit: File[] = [];
const filesWithOnlyErrorRefresh = [aFile];
addImportedModule(bFile);
addImportedModule(cFile);
verifyEmitAndErrorUpdates({
filesWithNewEmit,
filesWithOnlyErrorRefresh,
filesNotTouched: emptyArray,
change: host => host.writeFile(cFile.path, cFile.content.replace("d", "d2")),
getInitialErrors: () => emptyArray,
getIncrementalErrors: watch => [
getDiagnosticOfFileFromProgram(watch(), aFile.path, aFile.content.lastIndexOf("d"), 1, Diagnostics.Property_0_does_not_exist_on_type_1, "d", "C")
]
});
function addImportedModule(file: File) {
if (file.path.endsWith(".d.ts")) {
filesWithOnlyErrorRefresh.push(file);
}
else {
filesWithNewEmit.push(file);
}
}
}
it("updates errors when deep import file changes", () => {
const bFile: File = {
path: `${currentDirectory}/b.ts`,
content: `import {C} from './c';
export class B
{
c = new C();
}`
};
const cFile: File = {
path: `${currentDirectory}/c.ts`,
content: `export class C
{
d = 1;
}`
};
verifyDeepImportChange(bFile, cFile);
});
it("updates errors when deep import through declaration file changes", () => {
const bFile: File = {
path: `${currentDirectory}/b.d.ts`,
content: `import {C} from './c';
export class B
{
c: C;
}`
};
const cFile: File = {
path: `${currentDirectory}/c.d.ts`,
content: `export class C
{
d: number;
}`
};
verifyDeepImportChange(bFile, cFile);
});
});
it("updates errors in file not exporting a deep multilevel import that changes", () => {
const currentDirectory = "/user/username/projects/myproject";
const aFile: File = {
path: `${currentDirectory}/a.ts`,
content: `export interface Point {
@ -1798,34 +1725,116 @@ getPoint().c.x;`
path: `${currentDirectory}/e.ts`,
content: `import "./d";`
};
verifyEmitAndErrorUpdates({
filesWithNewEmit: [aFile, bFile],
filesWithOnlyErrorRefresh: [cFile, dFile],
filesNotTouched: [eFile],
change: host => host.writeFile(aFile.path, aFile.content.replace("x2", "x")),
getInitialErrors: watch => [
getDiagnosticOfFileFromProgram(watch(), cFile.path, cFile.content.indexOf("x: 1"), 4, chainDiagnosticMessages(
chainDiagnosticMessages(/*details*/ undefined, Diagnostics.Object_literal_may_only_specify_known_properties_and_0_does_not_exist_in_type_1, "x", "Coords"),
Diagnostics.Type_0_is_not_assignable_to_type_1,
"{ x: number; y: number; }",
"Coords"
)),
getDiagnosticOfFileFromProgram(watch(), dFile.path, dFile.content.lastIndexOf("x"), 1, Diagnostics.Property_0_does_not_exist_on_type_1, "x", "Coords")
],
getIncrementalErrors: () => emptyArray
});
});
describe("updates errors when file transitively exported file changes", () => {
const config: File = {
path: `${currentDirectory}/tsconfig.json`,
content: `{}`
content: JSON.stringify({
files: ["app.ts"],
compilerOptions: { baseUrl: "." }
})
};
const directoryFiles = [aFile, bFile, cFile, dFile, eFile];
const files = [...directoryFiles, config, libFile];
const host = createWatchedSystem(files, { currentDirectory });
const watch = createWatchOfConfigFile("tsconfig.json", host);
checkProgramActualFiles(watch(), [aFile.path, bFile.path, cFile.path, dFile.path, eFile.path, libFile.path]);
checkOutputErrorsInitial(host, [
getDiagnosticOfFileFromProgram(watch(), cFile.path, cFile.content.indexOf("x: 1"), 4, chainDiagnosticMessages(
chainDiagnosticMessages(/*details*/ undefined, Diagnostics.Object_literal_may_only_specify_known_properties_and_0_does_not_exist_in_type_1, "x", "Coords"),
Diagnostics.Type_0_is_not_assignable_to_type_1,
"{ x: number; y: number; }",
"Coords"
)),
getDiagnosticOfFileFromProgram(watch(), dFile.path, dFile.content.lastIndexOf("x"), 1, Diagnostics.Property_0_does_not_exist_on_type_1, "x", "Coords")
]);
const beforeChange = getOutputFileStampsAndErrors(host, watch, directoryFiles);
host.writeFile(aFile.path, aFile.content.replace("x2", "x"));
host.runQueuedTimeoutCallbacks();
checkOutputErrorsIncremental(host, emptyArray);
const afterChange = getOutputFileStampsAndErrors(host, watch, directoryFiles);
verifyOutputFileStampsAndErrors(aFile, /*emitExpected*/ true, /*errorRefershExpected*/ true, beforeChange, afterChange);
verifyOutputFileStampsAndErrors(bFile, /*emitExpected*/ true, /*errorRefershExpected*/ true, beforeChange, afterChange);
verifyOutputFileStampsAndErrors(cFile, /*emitExpected*/ false, /*errorRefershExpected*/ true, beforeChange, afterChange);
verifyOutputFileStampsAndErrors(dFile, /*emitExpected*/ false, /*errorRefershExpected*/ true, beforeChange, afterChange);
verifyOutputFileStampsAndErrors(eFile, /*emitExpected*/ false, /*errorRefershExpected*/ false, beforeChange, afterChange);
const app: File = {
path: `${currentDirectory}/app.ts`,
content: `import { Data } from "lib2/public";
export class App {
public constructor() {
new Data().test();
}
}`
};
const lib2Public: File = {
path: `${currentDirectory}/lib2/public.ts`,
content: `export * from "./data";`
};
const lib2Data: File = {
path: `${currentDirectory}/lib2/data.ts`,
content: `import { ITest } from "lib1/public";
export class Data {
public test() {
const result: ITest = {
title: "title"
}
return result;
}
}`
};
const lib1Public: File = {
path: `${currentDirectory}/lib1/public.ts`,
content: `export * from "./tools/public";`
};
const lib1ToolsPublic: File = {
path: `${currentDirectory}/lib1/tools/public.ts`,
content: `export * from "./tools.interface";`
};
const lib1ToolsInterface: File = {
path: `${currentDirectory}/lib1/tools/tools.interface.ts`,
content: `export interface ITest {
title: string;
}`
};
function verifyTransitiveExports(lib2Data: File, lib2Data2?: File) {
const filesWithNewEmit = [lib1ToolsInterface, lib1ToolsPublic];
const filesWithOnlyErrorRefresh = [app, lib2Public, lib1Public, lib2Data];
if (lib2Data2) {
filesWithOnlyErrorRefresh.push(lib2Data2);
}
verifyEmitAndErrorUpdates({
filesWithNewEmit,
filesWithOnlyErrorRefresh,
filesNotTouched: emptyArray,
configFile: config,
change: host => host.writeFile(lib1ToolsInterface.path, lib1ToolsInterface.content.replace("title", "title2")),
getInitialErrors: () => emptyArray,
getIncrementalErrors: () => [
"lib2/data.ts(5,13): error TS2322: Type '{ title: string; }' is not assignable to type 'ITest'.\n Object literal may only specify known properties, but 'title' does not exist in type 'ITest'. Did you mean to write 'title2'?\n"
]
});
}
it("when there are no circular import and exports", () => {
verifyTransitiveExports(lib2Data);
});
it("when there are circular import and exports", () => {
const lib2Data: File = {
path: `${currentDirectory}/lib2/data.ts`,
content: `import { ITest } from "lib1/public"; import { Data2 } from "./data2";
export class Data {
public dat?: Data2; public test() {
const result: ITest = {
title: "title"
}
return result;
}
}`
};
const lib2Data2: File = {
path: `${currentDirectory}/lib2/data2.ts`,
content: `import { Data } from "./data";
export class Data2 {
public dat?: Data;
}`
};
verifyTransitiveExports(lib2Data, lib2Data2);
});
});
});