mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-02-12 04:17:34 -06:00
Merge pull request #28992 from Microsoft/tscWatchExportUpdate
When removing the errors for the exports from the file, apart from re…moving transitive exports, remove the diagnostics of file that import these exports
This commit is contained in:
commit
8dff98dda0
@ -281,10 +281,19 @@ namespace ts {
|
||||
}
|
||||
|
||||
// If exported from path is not from cache and exported modules has path, all files referencing file exported from are affected
|
||||
return !!forEachEntry(state.exportedModulesMap!, (exportedModules, exportedFromPath) =>
|
||||
if (forEachEntry(state.exportedModulesMap!, (exportedModules, exportedFromPath) =>
|
||||
!state.currentAffectedFilesExportedModulesMap!.has(exportedFromPath) && // If we already iterated this through cache, ignore it
|
||||
exportedModules.has(filePath) &&
|
||||
removeSemanticDiagnosticsOfFileAndExportsOfFile(state, exportedFromPath as Path, seenFileAndExportsOfFile)
|
||||
)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Remove diagnostics of files that import this file (without going to exports of referencing files)
|
||||
return !!forEachEntry(state.referencedMap!, (referencesInFile, referencingFilePath) =>
|
||||
referencesInFile.has(filePath) &&
|
||||
!seenFileAndExportsOfFile.has(referencingFilePath) && // Not already removed diagnostic file
|
||||
removeSemanticDiagnosticsOf(state, referencingFilePath as Path) // Dont add to seen since this is not yet done with the export removal
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -89,6 +89,7 @@
|
||||
"unittests/services/transpile.ts",
|
||||
"unittests/tscWatch/consoleClearing.ts",
|
||||
"unittests/tscWatch/emit.ts",
|
||||
"unittests/tscWatch/emitAndErrorUpdates.ts",
|
||||
"unittests/tscWatch/programUpdates.ts",
|
||||
"unittests/tscWatch/resolutionCache.ts",
|
||||
"unittests/tscWatch/watchEnvironment.ts",
|
||||
|
||||
@ -479,7 +479,7 @@ let x: string = 10;`);
|
||||
const { host, solutionBuilder } = createSolutionOfProject(allFiles, currentDirectory, solutionBuilderconfig, getOutputFileStamps);
|
||||
|
||||
// Build in watch mode
|
||||
const watch = createWatchOfConfigFileReturningBuilder(watchConfig, host);
|
||||
const watch = createWatchOfConfigFile(watchConfig, host);
|
||||
checkOutputErrorsInitial(host, emptyArray);
|
||||
|
||||
return { host, solutionBuilder, watch };
|
||||
@ -505,10 +505,8 @@ let x: string = 10;`);
|
||||
projectSystem.checkProjectActualFiles(service.configuredProjects.get(configFile.toLowerCase())!, expectedFiles);
|
||||
}
|
||||
|
||||
type Watch = () => BuilderProgram;
|
||||
|
||||
function verifyDependencies(watch: Watch, filePath: string, expected: ReadonlyArray<string>) {
|
||||
checkArray(`${filePath} dependencies`, watch().getAllDependencies(watch().getSourceFile(filePath)!), expected);
|
||||
checkArray(`${filePath} dependencies`, watch.getBuilderProgram().getAllDependencies(watch().getSourceFile(filePath)!), expected);
|
||||
}
|
||||
|
||||
describe("on sample project", () => {
|
||||
@ -542,7 +540,7 @@ let x: string = 10;`);
|
||||
|
||||
host.checkTimeoutQueueLengthAndRun(1);
|
||||
checkOutputErrorsIncremental(host, emptyArray);
|
||||
checkProgramActualFiles(watch().getProgram(), expectedFilesAfterEdit);
|
||||
checkProgramActualFiles(watch(), expectedFilesAfterEdit);
|
||||
|
||||
});
|
||||
|
||||
@ -642,7 +640,7 @@ export function gfoo() {
|
||||
expectedWatchedDirectoriesRecursive: ReadonlyArray<string>,
|
||||
dependencies: ReadonlyArray<[string, ReadonlyArray<string>]>,
|
||||
expectedWatchedDirectories?: ReadonlyArray<string>) {
|
||||
checkProgramActualFiles(watch().getProgram(), expectedProgramFiles);
|
||||
checkProgramActualFiles(watch(), expectedProgramFiles);
|
||||
verifyWatchesOfProject(host, expectedWatchedFiles, expectedWatchedDirectoriesRecursive, expectedWatchedDirectories);
|
||||
for (const [file, deps] of dependencies) {
|
||||
verifyDependencies(watch, file, deps);
|
||||
|
||||
307
src/testRunner/unittests/tscWatch/emitAndErrorUpdates.ts
Normal file
307
src/testRunner/unittests/tscWatch/emitAndErrorUpdates.ts
Normal file
@ -0,0 +1,307 @@
|
||||
namespace ts.tscWatch {
|
||||
describe("unittests:: tsc-watch:: 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();
|
||||
return {
|
||||
file,
|
||||
fileStamp: host.getModifiedTime(file.path.replace(".ts", ".js")),
|
||||
errors: builderProgram.getSemanticDiagnostics(watch().getSourceFileByPath(file.path as Path)),
|
||||
errorsFromOldState: !!state.semanticDiagnosticsFromOldState && state.semanticDiagnosticsFromOldState.has(file.path)
|
||||
};
|
||||
}
|
||||
|
||||
function getOutputFileStampsAndErrors(host: WatchedSystem, watch: Watch, directoryFiles: ReadonlyArray<File>) {
|
||||
return directoryFiles.map(d => getOutputFileStampAndError(host, watch, d));
|
||||
}
|
||||
|
||||
function findStampAndErrors(stampsAndErrors: ReadonlyArray<ReturnType<typeof getOutputFileStampAndError>>, file: File) {
|
||||
return find(stampsAndErrors, info => info.file === file)!;
|
||||
}
|
||||
|
||||
function verifyOutputFileStampsAndErrors(
|
||||
file: File,
|
||||
emitExpected: boolean,
|
||||
errorRefershExpected: boolean,
|
||||
beforeChangeFileStampsAndErrors: ReadonlyArray<ReturnType<typeof getOutputFileStampAndError>>,
|
||||
afterChangeFileStampsAndErrors: ReadonlyArray<ReturnType<typeof getOutputFileStampAndError>>
|
||||
) {
|
||||
const beforeChange = findStampAndErrors(beforeChangeFileStampsAndErrors, file);
|
||||
const afterChange = findStampAndErrors(afterChangeFileStampsAndErrors, file);
|
||||
if (emitExpected) {
|
||||
assert.notStrictEqual(afterChange.fileStamp, beforeChange.fileStamp, `Expected emit for file ${file.path}`);
|
||||
}
|
||||
else {
|
||||
assert.strictEqual(afterChange.fileStamp, beforeChange.fileStamp, `Did not expect new emit for file ${file.path}`);
|
||||
}
|
||||
if (errorRefershExpected) {
|
||||
if (afterChange.errors !== emptyArray || beforeChange.errors !== emptyArray) {
|
||||
assert.notStrictEqual(afterChange.errors, beforeChange.errors, `Expected new errors for file ${file.path}`);
|
||||
}
|
||||
assert.isFalse(afterChange.errorsFromOldState, `Expected errors to be not copied from old state for file ${file.path}`);
|
||||
}
|
||||
else {
|
||||
assert.strictEqual(afterChange.errors, beforeChange.errors, `Expected errors to not change for file ${file.path}`);
|
||||
assert.isTrue(afterChange.errorsFromOldState, `Expected errors to be copied from old state for file ${file.path}`);
|
||||
}
|
||||
}
|
||||
|
||||
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 aFile: File = {
|
||||
path: `${currentDirectory}/a.ts`,
|
||||
content: `export interface Point {
|
||||
name: string;
|
||||
c: Coords;
|
||||
}
|
||||
export interface Coords {
|
||||
x2: number;
|
||||
y: number;
|
||||
}`
|
||||
};
|
||||
const bFile: File = {
|
||||
path: `${currentDirectory}/b.ts`,
|
||||
content: `import { Point } from "./a";
|
||||
export interface PointWrapper extends Point {
|
||||
}`
|
||||
};
|
||||
const cFile: File = {
|
||||
path: `${currentDirectory}/c.ts`,
|
||||
content: `import { PointWrapper } from "./b";
|
||||
export function getPoint(): PointWrapper {
|
||||
return {
|
||||
name: "test",
|
||||
c: {
|
||||
x: 1,
|
||||
y: 2
|
||||
}
|
||||
}
|
||||
};`
|
||||
};
|
||||
const dFile: File = {
|
||||
path: `${currentDirectory}/d.ts`,
|
||||
content: `import { getPoint } from "./c";
|
||||
getPoint().c.x;`
|
||||
};
|
||||
const eFile: File = {
|
||||
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: JSON.stringify({
|
||||
files: ["app.ts"],
|
||||
compilerOptions: { baseUrl: "." }
|
||||
})
|
||||
};
|
||||
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);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
@ -29,18 +29,18 @@ namespace ts.tscWatch {
|
||||
checkArray(`Program rootFileNames`, program.getRootFileNames(), expectedFiles);
|
||||
}
|
||||
|
||||
export function createWatchOfConfigFileReturningBuilder(configFileName: string, host: WatchedSystem, maxNumberOfFilesToIterateForInvalidation?: number) {
|
||||
const compilerHost = createWatchCompilerHostOfConfigFile(configFileName, {}, host);
|
||||
compilerHost.maxNumberOfFilesToIterateForInvalidation = maxNumberOfFilesToIterateForInvalidation;
|
||||
const watch = createWatchProgram(compilerHost);
|
||||
return () => watch.getCurrentProgram();
|
||||
export interface Watch {
|
||||
(): Program;
|
||||
getBuilderProgram(): EmitAndSemanticDiagnosticsBuilderProgram;
|
||||
}
|
||||
|
||||
export function createWatchOfConfigFile(configFileName: string, host: WatchedSystem, maxNumberOfFilesToIterateForInvalidation?: number) {
|
||||
const compilerHost = createWatchCompilerHostOfConfigFile(configFileName, {}, host);
|
||||
compilerHost.maxNumberOfFilesToIterateForInvalidation = maxNumberOfFilesToIterateForInvalidation;
|
||||
const watch = createWatchProgram(compilerHost);
|
||||
return () => watch.getCurrentProgram().getProgram();
|
||||
const result = (() => watch.getCurrentProgram().getProgram()) as Watch;
|
||||
result.getBuilderProgram = () => watch.getCurrentProgram();
|
||||
return result;
|
||||
}
|
||||
|
||||
export function createWatchOfFilesAndCompilerOptions(rootFiles: string[], host: WatchedSystem, options: CompilerOptions = {}, maxNumberOfFilesToIterateForInvalidation?: number) {
|
||||
@ -152,7 +152,21 @@ namespace ts.tscWatch {
|
||||
assert.equal(host.exitCode, expectedExitCode);
|
||||
}
|
||||
|
||||
export function getDiagnosticOfFileFrom(file: SourceFile | undefined, text: string, start: number | undefined, length: number | undefined, message: DiagnosticMessage): Diagnostic {
|
||||
function isDiagnosticMessageChain(message: DiagnosticMessage | DiagnosticMessageChain): message is DiagnosticMessageChain {
|
||||
return !!(message as DiagnosticMessageChain).messageText;
|
||||
}
|
||||
|
||||
export function getDiagnosticOfFileFrom(file: SourceFile | undefined, start: number | undefined, length: number | undefined, message: DiagnosticMessage | DiagnosticMessageChain, ..._args: (string | number)[]): Diagnostic {
|
||||
let text: DiagnosticMessageChain | string;
|
||||
if (isDiagnosticMessageChain(message)) {
|
||||
text = message;
|
||||
}
|
||||
else {
|
||||
text = getLocaleSpecificMessage(message);
|
||||
if (arguments.length > 4) {
|
||||
text = formatStringFromArgs(text, arguments, 4);
|
||||
}
|
||||
}
|
||||
return {
|
||||
file,
|
||||
start,
|
||||
@ -164,35 +178,17 @@ namespace ts.tscWatch {
|
||||
};
|
||||
}
|
||||
|
||||
export function getDiagnosticWithoutFile(message: DiagnosticMessage, ..._args: (string | number)[]): Diagnostic {
|
||||
let text = getLocaleSpecificMessage(message);
|
||||
|
||||
if (arguments.length > 1) {
|
||||
text = formatStringFromArgs(text, arguments, 1);
|
||||
}
|
||||
|
||||
return getDiagnosticOfFileFrom(/*file*/ undefined, text, /*start*/ undefined, /*length*/ undefined, message);
|
||||
export function getDiagnosticWithoutFile(message: DiagnosticMessage | DiagnosticMessageChain, ...args: (string | number)[]): Diagnostic {
|
||||
return getDiagnosticOfFileFrom(/*file*/ undefined, /*start*/ undefined, /*length*/ undefined, message, ...args);
|
||||
}
|
||||
|
||||
export function getDiagnosticOfFile(file: SourceFile, start: number, length: number, message: DiagnosticMessage, ..._args: (string | number)[]): Diagnostic {
|
||||
let text = getLocaleSpecificMessage(message);
|
||||
|
||||
if (arguments.length > 4) {
|
||||
text = formatStringFromArgs(text, arguments, 4);
|
||||
}
|
||||
|
||||
return getDiagnosticOfFileFrom(file, text, start, length, message);
|
||||
export function getDiagnosticOfFile(file: SourceFile, start: number, length: number, message: DiagnosticMessage | DiagnosticMessageChain, ...args: (string | number)[]): Diagnostic {
|
||||
return getDiagnosticOfFileFrom(file, start, length, message, ...args);
|
||||
}
|
||||
|
||||
export function getDiagnosticOfFileFromProgram(program: Program, filePath: string, start: number, length: number, message: DiagnosticMessage, ..._args: (string | number)[]): Diagnostic {
|
||||
let text = getLocaleSpecificMessage(message);
|
||||
|
||||
if (arguments.length > 5) {
|
||||
text = formatStringFromArgs(text, arguments, 5);
|
||||
}
|
||||
|
||||
export function getDiagnosticOfFileFromProgram(program: Program, filePath: string, start: number, length: number, message: DiagnosticMessage | DiagnosticMessageChain, ...args: (string | number)[]): Diagnostic {
|
||||
return getDiagnosticOfFileFrom(program.getSourceFileByPath(toPath(filePath, program.getCurrentDirectory(), s => s.toLowerCase()))!,
|
||||
text, start, length, message);
|
||||
start, length, message, ...args);
|
||||
}
|
||||
|
||||
export function getUnknownCompilerOption(program: Program, configFile: File, option: string) {
|
||||
|
||||
@ -1070,92 +1070,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 = {
|
||||
@ -1228,98 +1142,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";
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user