diff --git a/src/compiler/program.ts b/src/compiler/program.ts index a7cb013c8ee..ca4453bd749 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -552,6 +552,12 @@ namespace ts { allDiagnostics?: Diagnostic[]; } + interface RefFile extends TextRange { + kind: RefFileKind; + index: number; + file: SourceFile; + } + /** * Determines if program structure is upto date or needs to be recreated */ @@ -717,6 +723,8 @@ namespace ts { let noDiagnosticsTypeChecker: TypeChecker; let classifiableNames: UnderscoreEscapedMap; const ambientModuleNameToUnmodifiedFileName = createMap(); + // Todo:: Use this to report why file was included in --extendedDiagnostics + let refFileMap: MultiMap | undefined; const cachedSemanticDiagnosticsForFile: DiagnosticCache = {}; const cachedDeclarationDiagnosticsForFile: DiagnosticCache = {}; @@ -913,6 +921,7 @@ namespace ts { getSourceFileByPath, getSourceFiles: () => files, getMissingFilePaths: () => missingFilePaths!, // TODO: GH#18217 + getRefFileMap: () => refFileMap, getCompilerOptions: () => options, getSyntacticDiagnostics, getOptionsDiagnostics, @@ -1391,6 +1400,7 @@ namespace ts { } missingFilePaths = oldProgram.getMissingFilePaths(); + refFileMap = oldProgram.getRefFileMap(); // update fileName -> file mapping for (const newSourceFile of newSourceFiles) { @@ -2179,25 +2189,24 @@ namespace ts { } /** This has side effects through `findSourceFile`. */ - function processSourceFile(fileName: string, isDefaultLib: boolean, ignoreNoDefaultLib: boolean, packageId: PackageId | undefined, refFile?: SourceFile, refPos?: number, refEnd?: number): void { - getSourceFileFromReferenceWorker(fileName, - fileName => findSourceFile(fileName, toPath(fileName), isDefaultLib, ignoreNoDefaultLib, refFile!, refPos!, refEnd!, packageId), // TODO: GH#18217 - (diagnostic, ...args) => { - fileProcessingDiagnostics.add(refFile !== undefined && refEnd !== undefined && refPos !== undefined - ? createFileDiagnostic(refFile, refPos, refEnd - refPos, diagnostic, ...args) - : createCompilerDiagnostic(diagnostic, ...args)); - }, - refFile); + function processSourceFile(fileName: string, isDefaultLib: boolean, ignoreNoDefaultLib: boolean, packageId: PackageId | undefined, refFile?: RefFile): void { + getSourceFileFromReferenceWorker( + fileName, + fileName => findSourceFile(fileName, toPath(fileName), isDefaultLib, ignoreNoDefaultLib, refFile, packageId), // TODO: GH#18217 + (diagnostic, ...args) => fileProcessingDiagnostics.add( + createRefFileDiagnostic(refFile, diagnostic, ...args) + ), + refFile && refFile.file + ); } - function reportFileNamesDifferOnlyInCasingError(fileName: string, existingFileName: string, refFile: SourceFile, refPos: number, refEnd: number): void { - if (refFile !== undefined && refPos !== undefined && refEnd !== undefined) { - fileProcessingDiagnostics.add(createFileDiagnostic(refFile, refPos, refEnd - refPos, - Diagnostics.File_name_0_differs_from_already_included_file_name_1_only_in_casing, fileName, existingFileName)); - } - else { - fileProcessingDiagnostics.add(createCompilerDiagnostic(Diagnostics.File_name_0_differs_from_already_included_file_name_1_only_in_casing, fileName, existingFileName)); - } + function reportFileNamesDifferOnlyInCasingError(fileName: string, existingFileName: string, refFile: RefFile | undefined): void { + fileProcessingDiagnostics.add(createRefFileDiagnostic( + refFile, + Diagnostics.File_name_0_differs_from_already_included_file_name_1_only_in_casing, + fileName, + existingFileName + )); } function createRedirectSourceFile(redirectTarget: SourceFile, unredirected: SourceFile, fileName: string, path: Path, resolvedPath: Path, originalFileName: string): SourceFile { @@ -2222,7 +2231,7 @@ namespace ts { } // Get source file from normalized fileName - function findSourceFile(fileName: string, path: Path, isDefaultLib: boolean, ignoreNoDefaultLib: boolean, refFile: SourceFile, refPos: number, refEnd: number, packageId: PackageId | undefined): SourceFile | undefined { + function findSourceFile(fileName: string, path: Path, isDefaultLib: boolean, ignoreNoDefaultLib: boolean, refFile: RefFile | undefined, packageId: PackageId | undefined): SourceFile | undefined { const originalFileName = fileName; if (filesByName.has(path)) { const file = filesByName.get(path); @@ -2239,7 +2248,7 @@ namespace ts { const checkedAbsolutePath = getNormalizedAbsolutePathWithoutRoot(checkedName, currentDirectory); const inputAbsolutePath = getNormalizedAbsolutePathWithoutRoot(inputName, currentDirectory); if (checkedAbsolutePath !== inputAbsolutePath) { - reportFileNamesDifferOnlyInCasingError(inputName, checkedName, refFile, refPos, refEnd); + reportFileNamesDifferOnlyInCasingError(inputName, checkedName, refFile); } } @@ -2266,6 +2275,7 @@ namespace ts { } } + addFileToRefFileMap(file || undefined, refFile); return file || undefined; } @@ -2289,15 +2299,17 @@ namespace ts { } // We haven't looked for this file, do so now and cache result - const file = host.getSourceFile(fileName, options.target!, hostErrorMessage => { // TODO: GH#18217 - if (refFile !== undefined && refPos !== undefined && refEnd !== undefined) { - fileProcessingDiagnostics.add(createFileDiagnostic(refFile, refPos, refEnd - refPos, - Diagnostics.Cannot_read_file_0_Colon_1, fileName, hostErrorMessage)); - } - else { - fileProcessingDiagnostics.add(createCompilerDiagnostic(Diagnostics.Cannot_read_file_0_Colon_1, fileName, hostErrorMessage)); - } - }, shouldCreateNewSourceFile); + const file = host.getSourceFile( + fileName, + options.target!, + hostErrorMessage => fileProcessingDiagnostics.add(createRefFileDiagnostic( + refFile, + Diagnostics.Cannot_read_file_0_Colon_1, + fileName, + hostErrorMessage + )), + shouldCreateNewSourceFile + ); if (packageId) { const packageIdKey = packageIdToString(packageId); @@ -2331,7 +2343,7 @@ namespace ts { // for case-sensitive file systems check if we've already seen some file with similar filename ignoring case const existingFile = filesByNameIgnoreCase!.get(pathLowerCase); if (existingFile) { - reportFileNamesDifferOnlyInCasingError(fileName, existingFile.fileName, refFile, refPos, refEnd); + reportFileNamesDifferOnlyInCasingError(fileName, existingFile.fileName, refFile); } else { filesByNameIgnoreCase!.set(pathLowerCase, file); @@ -2359,10 +2371,20 @@ namespace ts { processingOtherFiles!.push(file); } } - + addFileToRefFileMap(file, refFile); return file; } + function addFileToRefFileMap(file: SourceFile | undefined, refFile: RefFile | undefined) { + if (refFile && file) { + (refFileMap || (refFileMap = createMultiMap())).add(file.path, { + kind: refFile.kind, + index: refFile.index, + file: refFile.file.path + }); + } + } + function addFileToFilesByName(file: SourceFile | undefined, path: Path, redirectedPath: Path | undefined) { if (redirectedPath) { filesByName.set(redirectedPath, file); @@ -2479,9 +2501,21 @@ namespace ts { } function processReferencedFiles(file: SourceFile, isDefaultLib: boolean) { - forEach(file.referencedFiles, ref => { + forEach(file.referencedFiles, (ref, index) => { const referencedFileName = resolveTripleslashReference(ref.fileName, file.originalFileName); - processSourceFile(referencedFileName, isDefaultLib, /*ignoreNoDefaultLib*/ false, /*packageId*/ undefined, file, ref.pos, ref.end); + processSourceFile( + referencedFileName, + isDefaultLib, + /*ignoreNoDefaultLib*/ false, + /*packageId*/ undefined, + { + kind: RefFileKind.ReferenceFile, + index, + file, + pos: ref.pos, + end: ref.end + } + ); }); } @@ -2500,12 +2534,25 @@ namespace ts { // store resolved type directive on the file const fileName = ref.fileName.toLocaleLowerCase(); setResolvedTypeReferenceDirective(file, fileName, resolvedTypeReferenceDirective); - processTypeReferenceDirective(fileName, resolvedTypeReferenceDirective, file, ref.pos, ref.end); + processTypeReferenceDirective( + fileName, + resolvedTypeReferenceDirective, + { + kind: RefFileKind.TypeReferenceDirective, + index: i, + file, + pos: ref.pos, + end: ref.end + } + ); } } - function processTypeReferenceDirective(typeReferenceDirective: string, resolvedTypeReferenceDirective?: ResolvedTypeReferenceDirective, - refFile?: SourceFile, refPos?: number, refEnd?: number): void { + function processTypeReferenceDirective( + typeReferenceDirective: string, + resolvedTypeReferenceDirective?: ResolvedTypeReferenceDirective, + refFile?: RefFile + ): void { // If we already found this library as a primary reference - nothing to do const previousResolution = resolvedTypeReferenceDirectives.get(typeReferenceDirective); @@ -2518,7 +2565,7 @@ namespace ts { if (resolvedTypeReferenceDirective.primary) { // resolved from the primary path - processSourceFile(resolvedTypeReferenceDirective.resolvedFileName!, /*isDefaultLib*/ false, /*ignoreNoDefaultLib*/ false, resolvedTypeReferenceDirective.packageId, refFile, refPos, refEnd); // TODO: GH#18217 + processSourceFile(resolvedTypeReferenceDirective.resolvedFileName!, /*isDefaultLib*/ false, /*ignoreNoDefaultLib*/ false, resolvedTypeReferenceDirective.packageId, refFile); // TODO: GH#18217 } else { // If we already resolved to this file, it must have been a secondary reference. Check file contents @@ -2528,12 +2575,15 @@ namespace ts { if (resolvedTypeReferenceDirective.resolvedFileName !== previousResolution.resolvedFileName) { const otherFileText = host.readFile(resolvedTypeReferenceDirective.resolvedFileName!); if (otherFileText !== getSourceFile(previousResolution.resolvedFileName!)!.text) { - fileProcessingDiagnostics.add(createDiagnostic(refFile!, refPos!, refEnd!, // TODO: GH#18217 - Diagnostics.Conflicting_definitions_for_0_found_at_1_and_2_Consider_installing_a_specific_version_of_this_library_to_resolve_the_conflict, - typeReferenceDirective, - resolvedTypeReferenceDirective.resolvedFileName, - previousResolution.resolvedFileName - )); + fileProcessingDiagnostics.add( + createRefFileDiagnostic( + refFile, + Diagnostics.Conflicting_definitions_for_0_found_at_1_and_2_Consider_installing_a_specific_version_of_this_library_to_resolve_the_conflict, + typeReferenceDirective, + resolvedTypeReferenceDirective.resolvedFileName, + previousResolution.resolvedFileName + ) + ); } } // don't overwrite previous resolution result @@ -2541,14 +2591,18 @@ namespace ts { } else { // First resolution of this library - processSourceFile(resolvedTypeReferenceDirective.resolvedFileName!, /*isDefaultLib*/ false, /*ignoreNoDefaultLib*/ false, resolvedTypeReferenceDirective.packageId, refFile, refPos, refEnd); + processSourceFile(resolvedTypeReferenceDirective.resolvedFileName!, /*isDefaultLib*/ false, /*ignoreNoDefaultLib*/ false, resolvedTypeReferenceDirective.packageId, refFile); } } if (resolvedTypeReferenceDirective.isExternalLibraryImport) currentNodeModulesDepth--; } else { - fileProcessingDiagnostics.add(createDiagnostic(refFile!, refPos!, refEnd!, Diagnostics.Cannot_find_type_definition_file_for_0, typeReferenceDirective)); // TODO: GH#18217 + fileProcessingDiagnostics.add(createRefFileDiagnostic( + refFile, + Diagnostics.Cannot_find_type_definition_file_for_0, + typeReferenceDirective + )); } if (saveResolution) { @@ -2568,17 +2622,24 @@ namespace ts { const unqualifiedLibName = removeSuffix(removePrefix(libName, "lib."), ".d.ts"); const suggestion = getSpellingSuggestion(unqualifiedLibName, libs, identity); const message = suggestion ? Diagnostics.Cannot_find_lib_definition_for_0_Did_you_mean_1 : Diagnostics.Cannot_find_lib_definition_for_0; - fileProcessingDiagnostics.add(createDiagnostic(file, libReference.pos, libReference.end, message, libName, suggestion)); + fileProcessingDiagnostics.add(createFileDiagnostic( + file, + libReference.pos, + libReference.end - libReference.pos, + message, + libName, + suggestion + )); } }); } - function createDiagnostic(refFile: SourceFile, refPos: number, refEnd: number, message: DiagnosticMessage, ...args: any[]): Diagnostic { - if (refFile === undefined || refPos === undefined || refEnd === undefined) { + function createRefFileDiagnostic(refFile: RefFile | undefined, message: DiagnosticMessage, ...args: any[]): Diagnostic { + if (!refFile) { return createCompilerDiagnostic(message, ...args); } else { - return createFileDiagnostic(refFile, refPos, refEnd - refPos, message, ...args); + return createFileDiagnostic(refFile.file, refFile.pos, refFile.end - refFile.pos, message, ...args); } } @@ -2632,7 +2693,20 @@ namespace ts { else if (shouldAddFile) { const path = toPath(resolvedFileName); const pos = skipTrivia(file.text, file.imports[i].pos); - findSourceFile(resolvedFileName, path, /*isDefaultLib*/ false, /*ignoreNoDefaultLib*/ false, file, pos, file.imports[i].end, resolution.packageId); + findSourceFile( + resolvedFileName, + path, + /*isDefaultLib*/ false, + /*ignoreNoDefaultLib*/ false, + { + kind: RefFileKind.Import, + index: i, + file, + pos, + end: file.imports[i].end + }, + resolution.packageId + ); } if (isFromNodeModulesSearch) { @@ -2654,12 +2728,20 @@ namespace ts { function checkSourceFilesBelongToPath(sourceFiles: ReadonlyArray, rootDirectory: string): boolean { let allFilesBelongToPath = true; const absoluteRootDirectoryPath = host.getCanonicalFileName(getNormalizedAbsolutePath(rootDirectory, currentDirectory)); + let rootPaths: Map | undefined; for (const sourceFile of sourceFiles) { if (!sourceFile.isDeclarationFile) { const absoluteSourceFilePath = host.getCanonicalFileName(getNormalizedAbsolutePath(sourceFile.fileName, currentDirectory)); if (absoluteSourceFilePath.indexOf(absoluteRootDirectoryPath) !== 0) { - programDiagnostics.add(createCompilerDiagnostic(Diagnostics.File_0_is_not_under_rootDir_1_rootDir_is_expected_to_contain_all_source_files, sourceFile.fileName, rootDirectory)); + if (!rootPaths) rootPaths = arrayToSet(rootNames, toPath); + addProgramDiagnosticAtRefPath( + sourceFile, + rootPaths, + Diagnostics.File_0_is_not_under_rootDir_1_rootDir_is_expected_to_contain_all_source_files, + sourceFile.fileName, + rootDirectory + ); allFilesBelongToPath = false; } } @@ -2771,12 +2853,18 @@ namespace ts { // List of collected files is complete; validate exhautiveness if this is a project with a file list if (options.composite) { - const rootPaths = rootNames.map(toPath); + const rootPaths = arrayToSet(rootNames, toPath); for (const file of files) { // Ignore file that is not emitted if (!sourceFileMayBeEmitted(file, options, isSourceFileFromExternalLibrary, getResolvedProjectReferenceToRedirect)) continue; - if (rootPaths.indexOf(file.path) === -1) { - programDiagnostics.add(createCompilerDiagnostic(Diagnostics.File_0_is_not_listed_within_the_file_list_of_project_1_Projects_must_list_all_files_or_use_an_include_pattern, file.fileName, options.configFilePath || "")); + if (!rootPaths.has(file.path)) { + addProgramDiagnosticAtRefPath( + file, + rootPaths, + Diagnostics.File_0_is_not_listed_within_the_file_list_of_project_1_Projects_must_list_all_files_or_use_an_include_pattern, + file.fileName, + options.configFilePath || "" + ); } } } @@ -2984,6 +3072,35 @@ namespace ts { } } + function addProgramDiagnosticAtRefPath(file: SourceFile, rootPaths: Map, message: DiagnosticMessage, ...args: (string | number | undefined)[]) { + const refPaths = refFileMap && refFileMap.get(file.path); + const refPathToReportErrorOn = forEach(refPaths, refPath => rootPaths.has(refPath.file) ? refPath : undefined) || + elementAt(refPaths, 0); + if (refPathToReportErrorOn) { + const refFile = Debug.assertDefined(getSourceFileByPath(refPathToReportErrorOn.file)); + const { kind, index } = refPathToReportErrorOn; + let pos: number, end: number; + switch (kind) { + case RefFileKind.Import: + pos = skipTrivia(refFile.text, refFile.imports[index].pos); + end = refFile.imports[index].end; + break; + case RefFileKind.ReferenceFile: + ({ pos, end } = refFile.referencedFiles[index]); + break; + case RefFileKind.TypeReferenceDirective: + ({ pos, end } = refFile.typeReferenceDirectives[index]); + break; + default: + return Debug.assertNever(kind); + } + programDiagnostics.add(createFileDiagnostic(refFile, pos, end - pos, message, ...args)); + } + else { + programDiagnostics.add(createCompilerDiagnostic(message, ...args)); + } + } + function verifyProjectReferences() { const buildInfoPath = !options.noEmit && !options.suppressOutputPathCheck ? getOutputPathForBuildInfo(options) : undefined; forEachProjectReference(projectReferences, resolvedProjectReferences, (resolvedRef, index, parent) => { diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 2d835c5a84f..37df60a4787 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2914,6 +2914,20 @@ namespace ts { throwIfCancellationRequested(): void; } + /*@internal*/ + export enum RefFileKind { + Import, + ReferenceFile, + TypeReferenceDirective + } + + /*@internal*/ + export interface RefFile { + kind: RefFileKind; + index: number; + file: Path; + } + // TODO: This should implement TypeCheckerHost but that's an internal type. export interface Program extends ScriptReferenceHost { @@ -2933,6 +2947,8 @@ namespace ts { */ /* @internal */ getMissingFilePaths(): ReadonlyArray; + /* @internal */ + getRefFileMap(): MultiMap | undefined; /** * Emits the JavaScript and declaration files. If targetSourceFile is not specified, then diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 4ca13e663ab..f34fe8ff504 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -192,7 +192,7 @@ namespace ts { export function arrayToSet(array: ReadonlyArray, makeKey: (value: T) => string | undefined): Map; export function arrayToSet(array: ReadonlyArray, makeKey: (value: T) => __String | undefined): UnderscoreEscapedMap; export function arrayToSet(array: ReadonlyArray, makeKey?: (value: any) => string | __String | undefined): Map | UnderscoreEscapedMap { - return arrayToMap(array, makeKey || (s => s), () => true); + return arrayToMap(array, makeKey || (s => s), returnTrue); } export function cloneMap(map: SymbolTable): SymbolTable; diff --git a/src/testRunner/unittests/config/projectReferences.ts b/src/testRunner/unittests/config/projectReferences.ts index 305cc726c97..24a85f5285f 100644 --- a/src/testRunner/unittests/config/projectReferences.ts +++ b/src/testRunner/unittests/config/projectReferences.ts @@ -193,7 +193,7 @@ namespace ts { }; testProjectReferences(spec, "/primary/tsconfig.json", program => { - const errs = program.getOptionsDiagnostics(); + const errs = program.getSemanticDiagnostics(program.getSourceFile("/primary/a.ts")); assertHasError("Reports an error about b.ts not being in the list", errs, Diagnostics.File_0_is_not_listed_within_the_file_list_of_project_1_Projects_must_list_all_files_or_use_an_include_pattern); }); }); @@ -343,8 +343,9 @@ namespace ts { } }; testProjectReferences(spec, "/alpha/tsconfig.json", (program) => { - assertHasError("Issues an error about the rootDir", program.getOptionsDiagnostics(), Diagnostics.File_0_is_not_under_rootDir_1_rootDir_is_expected_to_contain_all_source_files); - assertHasError("Issues an error about the fileList", program.getOptionsDiagnostics(), Diagnostics.File_0_is_not_listed_within_the_file_list_of_project_1_Projects_must_list_all_files_or_use_an_include_pattern); + const semanticDiagnostics = program.getSemanticDiagnostics(program.getSourceFile("/alpha/src/a.ts")); + assertHasError("Issues an error about the rootDir", semanticDiagnostics, Diagnostics.File_0_is_not_under_rootDir_1_rootDir_is_expected_to_contain_all_source_files); + assertHasError("Issues an error about the fileList", semanticDiagnostics, Diagnostics.File_0_is_not_listed_within_the_file_list_of_project_1_Projects_must_list_all_files_or_use_an_include_pattern); }); }); }); diff --git a/src/testRunner/unittests/tsbuild/demo.ts b/src/testRunner/unittests/tsbuild/demo.ts index 559e49718b9..c345ad1e372 100644 --- a/src/testRunner/unittests/tsbuild/demo.ts +++ b/src/testRunner/unittests/tsbuild/demo.ts @@ -42,7 +42,7 @@ namespace ts { interface VerifyBuild { modifyDiskLayout: (fs: vfs.FileSystem) => void; expectedExitStatus: ExitStatus; - expectedDiagnostics: fakes.ExpectedDiagnostic[]; + expectedDiagnostics: (fs: vfs.FileSystem) => fakes.ExpectedDiagnostic[]; expectedOutputs: readonly string[]; notExpectedOutputs: readonly string[]; } @@ -54,7 +54,7 @@ namespace ts { const builder = createSolutionBuilder(host, ["/src/tsconfig.json"], { verbose: true }); const exitStatus = builder.build(); assert.equal(exitStatus, expectedExitStatus); - host.assertDiagnosticMessages(...expectedDiagnostics); + host.assertDiagnosticMessages(...expectedDiagnostics(fs)); verifyOutputsPresent(fs, expectedOutputs); verifyOutputsAbsent(fs, notExpectedOutputs); } @@ -63,7 +63,7 @@ namespace ts { verifyBuild({ modifyDiskLayout: noop, expectedExitStatus: ExitStatus.Success, - expectedDiagnostics: [ + expectedDiagnostics: () => [ getExpectedDiagnosticForProjectsInBuild("src/core/tsconfig.json", "src/animals/tsconfig.json", "src/zoo/tsconfig.json", "src/tsconfig.json"), [Diagnostics.Project_0_is_out_of_date_because_output_file_1_does_not_exist, "src/core/tsconfig.json", "src/lib/core/utilities.js"], [Diagnostics.Building_project_0, "/src/core/tsconfig.json"], @@ -91,7 +91,7 @@ namespace ts { ]` ), expectedExitStatus: ExitStatus.ProjectReferenceCycle_OutputsSkupped, - expectedDiagnostics: [ + expectedDiagnostics: () => [ getExpectedDiagnosticForProjectsInBuild("src/animals/tsconfig.json", "src/zoo/tsconfig.json", "src/core/tsconfig.json", "src/tsconfig.json"), errorDiagnostic([ Diagnostics.Project_references_may_not_form_a_circular_graph_Cycle_detected_Colon_0, @@ -117,16 +117,38 @@ namespace ts { ` ), expectedExitStatus: ExitStatus.DiagnosticsPresent_OutputsSkipped, - expectedDiagnostics: [ + expectedDiagnostics: fs => [ getExpectedDiagnosticForProjectsInBuild("src/core/tsconfig.json", "src/animals/tsconfig.json", "src/zoo/tsconfig.json", "src/tsconfig.json"), [Diagnostics.Project_0_is_out_of_date_because_output_file_1_does_not_exist, "src/core/tsconfig.json", "src/lib/core/utilities.js"], [Diagnostics.Building_project_0, "/src/core/tsconfig.json"], - errorDiagnostic([Diagnostics.File_0_is_not_under_rootDir_1_rootDir_is_expected_to_contain_all_source_files, "/src/animals/animal.ts", "/src/core"]), - errorDiagnostic([Diagnostics.File_0_is_not_under_rootDir_1_rootDir_is_expected_to_contain_all_source_files, "/src/animals/dog.ts", "/src/core"]), - errorDiagnostic([Diagnostics.File_0_is_not_under_rootDir_1_rootDir_is_expected_to_contain_all_source_files, "/src/animals/index.ts", "/src/core"]), - errorDiagnostic([Diagnostics.File_0_is_not_listed_within_the_file_list_of_project_1_Projects_must_list_all_files_or_use_an_include_pattern, "/src/animals/animal.ts", "/src/core/tsconfig.json"]), - errorDiagnostic([Diagnostics.File_0_is_not_listed_within_the_file_list_of_project_1_Projects_must_list_all_files_or_use_an_include_pattern, "/src/animals/dog.ts", "/src/core/tsconfig.json"]), - errorDiagnostic([Diagnostics.File_0_is_not_listed_within_the_file_list_of_project_1_Projects_must_list_all_files_or_use_an_include_pattern, "/src/animals/index.ts", "/src/core/tsconfig.json"]), + { + message: [Diagnostics.File_0_is_not_under_rootDir_1_rootDir_is_expected_to_contain_all_source_files, "/src/animals/animal.ts", "/src/core"], + location: expectedLocationIndexOf(fs, "/src/animals/index.ts", `'./animal'`), + }, + { + message: [Diagnostics.File_0_is_not_listed_within_the_file_list_of_project_1_Projects_must_list_all_files_or_use_an_include_pattern, "/src/animals/animal.ts", "/src/core/tsconfig.json"], + location: expectedLocationIndexOf(fs, "/src/animals/index.ts", `'./animal'`), + }, + { + message: [Diagnostics.File_0_is_not_under_rootDir_1_rootDir_is_expected_to_contain_all_source_files, "/src/animals/dog.ts", "/src/core"], + location: expectedLocationIndexOf(fs, "/src/animals/index.ts", `'./dog'`), + }, + { + message: [Diagnostics.File_0_is_not_listed_within_the_file_list_of_project_1_Projects_must_list_all_files_or_use_an_include_pattern, "/src/animals/dog.ts", "/src/core/tsconfig.json"], + location: expectedLocationIndexOf(fs, "/src/animals/index.ts", `'./dog'`), + }, + { + message: [Diagnostics._0_is_declared_but_its_value_is_never_read, "A"], + location: expectedLocationIndexOf(fs, "/src/core/utilities.ts", `import * as A from '../animals';`), + }, + { + message: [Diagnostics.File_0_is_not_under_rootDir_1_rootDir_is_expected_to_contain_all_source_files, "/src/animals/index.ts", "/src/core"], + location: expectedLocationIndexOf(fs, "/src/core/utilities.ts", `'../animals'`), + }, + { + message: [Diagnostics.File_0_is_not_listed_within_the_file_list_of_project_1_Projects_must_list_all_files_or_use_an_include_pattern, "/src/animals/index.ts", "/src/core/tsconfig.json"], + location: expectedLocationIndexOf(fs, "/src/core/utilities.ts", `'../animals'`), + }, [Diagnostics.Project_0_can_t_be_built_because_its_dependency_1_has_errors, "src/animals/tsconfig.json", "src/core"], [Diagnostics.Skipping_build_of_project_0_because_its_dependency_1_has_errors, "/src/animals/tsconfig.json", "/src/core"], [Diagnostics.Project_0_can_t_be_built_because_its_dependency_1_was_not_built, "src/zoo/tsconfig.json", "src/animals"], diff --git a/src/testRunner/unittests/tsbuild/emptyFiles.ts b/src/testRunner/unittests/tsbuild/emptyFiles.ts index 0b52c4f8485..a2e7aedf290 100644 --- a/src/testRunner/unittests/tsbuild/emptyFiles.ts +++ b/src/testRunner/unittests/tsbuild/emptyFiles.ts @@ -17,11 +17,7 @@ namespace ts { builder.build(); host.assertDiagnosticMessages({ message: [Diagnostics.The_files_list_in_config_file_0_is_empty, "/src/no-references/tsconfig.json"], - location: { - file: "/src/no-references/tsconfig.json", - start: lastIndexOf(fs, "/src/no-references/tsconfig.json", "[]"), - length: 2 - } + location: expectedLocationLastIndexOf(fs, "/src/no-references/tsconfig.json", "[]"), }); // Check for outputs to not be written. diff --git a/src/testRunner/unittests/tsbuild/helpers.ts b/src/testRunner/unittests/tsbuild/helpers.ts index 7b33e3018d1..165aab84916 100644 --- a/src/testRunner/unittests/tsbuild/helpers.ts +++ b/src/testRunner/unittests/tsbuild/helpers.ts @@ -62,6 +62,22 @@ namespace ts { return content.lastIndexOf(searchStr); } + export function expectedLocationIndexOf(fs: vfs.FileSystem, file: string, searchStr: string): fakes.ExpectedDiagnosticLocation { + return { + file, + start: indexOf(fs, file, searchStr), + length: searchStr.length + }; + } + + export function expectedLocationLastIndexOf(fs: vfs.FileSystem, file: string, searchStr: string): fakes.ExpectedDiagnosticLocation { + return { + file, + start: lastIndexOf(fs, file, searchStr), + length: searchStr.length + }; + } + export function getTime() { let currentTime = 100; return { tick, time, touch }; diff --git a/src/testRunner/unittests/tsbuild/referencesWithRootDirInParent.ts b/src/testRunner/unittests/tsbuild/referencesWithRootDirInParent.ts index 9e067367c41..79cc34ce016 100644 --- a/src/testRunner/unittests/tsbuild/referencesWithRootDirInParent.ts +++ b/src/testRunner/unittests/tsbuild/referencesWithRootDirInParent.ts @@ -45,11 +45,7 @@ namespace ts { [Diagnostics.Building_project_0, "/src/src/main/tsconfig.json"], { message: [Diagnostics.Cannot_write_file_0_because_it_will_overwrite_tsbuildinfo_file_generated_by_referenced_project_1, "/src/dist/tsconfig.tsbuildinfo", "/src/src/other"], - location: { - file: "/src/src/main/tsconfig.json", - start: indexOf(fs, "/src/src/main/tsconfig.json", `{ "path": "../other" }`), - length: `{ "path": "../other" }`.length - } + location: expectedLocationIndexOf(fs, "/src/src/main/tsconfig.json", `{ "path": "../other" }`), } ); verifyOutputsPresent(fs, allExpectedOutputs); @@ -84,11 +80,7 @@ namespace ts { [Diagnostics.Building_project_0, "/src/src/main/tsconfig.json"], { message: [Diagnostics.Cannot_write_file_0_because_it_will_overwrite_tsbuildinfo_file_generated_by_referenced_project_1, "/src/dist/tsconfig.tsbuildinfo", "/src/src/other"], - location: { - file: "/src/src/main/tsconfig.json", - start: indexOf(fs, "/src/src/main/tsconfig.json", `{"path":"../other"}`), - length: `{"path":"../other"}`.length - } + location: expectedLocationIndexOf(fs, "/src/src/main/tsconfig.json", `{"path":"../other"}`), } ); verifyOutputsPresent(fs, allExpectedOutputs); diff --git a/src/testRunner/unittests/tsbuild/resolveJsonModule.ts b/src/testRunner/unittests/tsbuild/resolveJsonModule.ts index 719e4beaed7..7531fac65af 100644 --- a/src/testRunner/unittests/tsbuild/resolveJsonModule.ts +++ b/src/testRunner/unittests/tsbuild/resolveJsonModule.ts @@ -30,11 +30,14 @@ namespace ts { it("with resolveJsonModule and include only", () => { verifyProjectWithResolveJsonModule( "/src/tsconfig_withInclude.json", - errorDiagnostic([ - Diagnostics.File_0_is_not_listed_within_the_file_list_of_project_1_Projects_must_list_all_files_or_use_an_include_pattern, - "/src/src/hello.json", - "/src/tsconfig_withInclude.json" - ]) + { + message: [ + Diagnostics.File_0_is_not_listed_within_the_file_list_of_project_1_Projects_must_list_all_files_or_use_an_include_pattern, + "/src/src/hello.json", + "/src/tsconfig_withInclude.json" + ], + location: expectedLocationIndexOf(projFs, "/src/src/index.ts", `"./hello.json"`) + } ); }); diff --git a/src/testRunner/unittests/tsbuild/sample.ts b/src/testRunner/unittests/tsbuild/sample.ts index 260bbff1436..8dcc69d06d9 100644 --- a/src/testRunner/unittests/tsbuild/sample.ts +++ b/src/testRunner/unittests/tsbuild/sample.ts @@ -452,11 +452,7 @@ namespace ts { [Diagnostics.Building_project_0, "/src/logic/tsconfig.json"], { message: [Diagnostics.Property_0_does_not_exist_on_type_1, "muitply", `typeof import("/src/core/index")`], - location: { - file: "/src/logic/index.ts", - start: indexOf(fs, "/src/logic/index.ts", "muitply"), - length: "muitply".length - } + location: expectedLocationIndexOf(fs, "/src/logic/index.ts", "muitply"), }, [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"] diff --git a/src/testRunner/unittests/tsbuild/watchMode.ts b/src/testRunner/unittests/tsbuild/watchMode.ts index 5c4fd5c5259..3d819337e8c 100644 --- a/src/testRunner/unittests/tsbuild/watchMode.ts +++ b/src/testRunner/unittests/tsbuild/watchMode.ts @@ -1295,6 +1295,62 @@ export function someFn() { }`); ]); }); + it("updates with bad reference", () => { + const host = createTsBuildWatchSystem([ + ...allFilesExceptBase, + baseConfig, + { path: libFile.path, content: libContent } + ], { currentDirectory: projectLocation }); + host.writeFile(coreFiles[1].path, `import * as A from '../animals'; +${coreFiles[1].content}`); + createSolutionBuilderWithWatch(host, ["tsconfig.json"], { verbose: true, watch: true }); + const errors = [ + `animals/index.ts(1,20): error TS6059: File '/user/username/projects/demo/animals/animal.ts' is not under 'rootDir' '/user/username/projects/demo/core'. 'rootDir' is expected to contain all source files.\n`, + `animals/index.ts(1,20): error TS6307: File '/user/username/projects/demo/animals/animal.ts' is not listed within the file list of project '/user/username/projects/demo/core/tsconfig.json'. Projects must list all files or use an 'include' pattern.\n`, + `animals/index.ts(4,32): error TS6059: File '/user/username/projects/demo/animals/dog.ts' is not under 'rootDir' '/user/username/projects/demo/core'. 'rootDir' is expected to contain all source files.\n`, + `animals/index.ts(4,32): error TS6307: File '/user/username/projects/demo/animals/dog.ts' is not listed within the file list of project '/user/username/projects/demo/core/tsconfig.json'. Projects must list all files or use an 'include' pattern.\n`, + `core/utilities.ts(1,1): error TS6133: 'A' is declared but its value is never read.\n`, + `core/utilities.ts(1,20): error TS6059: File '/user/username/projects/demo/animals/index.ts' is not under 'rootDir' '/user/username/projects/demo/core'. 'rootDir' is expected to contain all source files.\n`, + `core/utilities.ts(1,20): error TS6307: File '/user/username/projects/demo/animals/index.ts' is not listed within the file list of project '/user/username/projects/demo/core/tsconfig.json'. Projects must list all files or use an 'include' pattern.\n` + ].map(hostOutputDiagnostic); + checkOutputErrors(host, [ + startingCompilationInWatchMode(), + hostOutputLog(`Projects in this build: \r\n * core/tsconfig.json\r\n * animals/tsconfig.json\r\n * zoo/tsconfig.json\r\n * tsconfig.json\n\n`), + hostOutputLog(`Project 'core/tsconfig.json' is out of date because output file 'lib/core/utilities.js' does not exist\n\n`), + hostOutputLog(`Building project '/user/username/projects/demo/core/tsconfig.json'...\n\n`), + ...errors, + hostOutputLog(`Project 'animals/tsconfig.json' can't be built because its dependency 'core' has errors\n\n`), + hostOutputLog(`Skipping build of project '/user/username/projects/demo/animals/tsconfig.json' because its dependency '/user/username/projects/demo/core' has errors\n\n`), + hostOutputLog(`Project 'zoo/tsconfig.json' can't be built because its dependency 'animals' was not built\n\n`), + hostOutputLog(`Skipping build of project '/user/username/projects/demo/zoo/tsconfig.json' because its dependency '/user/username/projects/demo/animals' was not built\n\n`), + foundErrorsWatching(errors) + ]); + verifyWatches(host); + + // Make changes + host.writeFile(coreFiles[1].path, ` +import * as A from '../animals'; +${coreFiles[1].content}`); + const newErrors = [ + `animals/index.ts(1,20): error TS6059: File '/user/username/projects/demo/animals/animal.ts' is not under 'rootDir' '/user/username/projects/demo/core'. 'rootDir' is expected to contain all source files.\n`, + `animals/index.ts(1,20): error TS6307: File '/user/username/projects/demo/animals/animal.ts' is not listed within the file list of project '/user/username/projects/demo/core/tsconfig.json'. Projects must list all files or use an 'include' pattern.\n`, + `animals/index.ts(4,32): error TS6059: File '/user/username/projects/demo/animals/dog.ts' is not under 'rootDir' '/user/username/projects/demo/core'. 'rootDir' is expected to contain all source files.\n`, + `animals/index.ts(4,32): error TS6307: File '/user/username/projects/demo/animals/dog.ts' is not listed within the file list of project '/user/username/projects/demo/core/tsconfig.json'. Projects must list all files or use an 'include' pattern.\n`, + `core/utilities.ts(2,1): error TS6133: 'A' is declared but its value is never read.\n`, + `core/utilities.ts(2,20): error TS6059: File '/user/username/projects/demo/animals/index.ts' is not under 'rootDir' '/user/username/projects/demo/core'. 'rootDir' is expected to contain all source files.\n`, + `core/utilities.ts(2,20): error TS6307: File '/user/username/projects/demo/animals/index.ts' is not listed within the file list of project '/user/username/projects/demo/core/tsconfig.json'. Projects must list all files or use an 'include' pattern.\n` + ].map(hostOutputDiagnostic); + host.checkTimeoutQueueLengthAndRun(1); // build core + host.checkTimeoutQueueLength(0); + checkOutputErrors(host, [ + fileChangeDetected(), + hostOutputLog(`Project 'core/tsconfig.json' is out of date because output file 'lib/core/utilities.js' does not exist\n\n`), + hostOutputLog(`Building project '/user/username/projects/demo/core/tsconfig.json'...\n\n`), + ...newErrors, + foundErrorsWatching(newErrors) + ]); + }); + function subProjectFiles(subProject: string, fileNames: readonly string[]): File[] { return fileNames.map(file => projectFile(`${subProject}/${file}`)); } diff --git a/src/testRunner/unittests/tscWatch/helpers.ts b/src/testRunner/unittests/tscWatch/helpers.ts index ce5fd9d4725..3a9cb9a4fd8 100644 --- a/src/testRunner/unittests/tscWatch/helpers.ts +++ b/src/testRunner/unittests/tscWatch/helpers.ts @@ -54,30 +54,51 @@ namespace ts.tscWatch { const elapsedRegex = /^Elapsed:: [0-9]+ms/; const buildVerboseLogRegEx = /^.+ \- /; - function checkOutputErrors( + export enum HostOutputKind { + Log, + Diagnostic, + WatchDiagnostic + } + + export interface HostOutputLog { + kind: HostOutputKind.Log; + expected: string; + caption?: string; + } + + export interface HostOutputDiagnostic { + kind: HostOutputKind.Diagnostic; + diagnostic: Diagnostic | string; + } + + export interface HostOutputWatchDiagnostic { + kind: HostOutputKind.WatchDiagnostic; + diagnostic: Diagnostic | string; + } + + export type HostOutput = HostOutputLog | HostOutputDiagnostic | HostOutputWatchDiagnostic; + + export function checkOutputErrors( host: WatchedSystem, - logsBeforeWatchDiagnostic: string[] | undefined, - preErrorsWatchDiagnostic: Diagnostic | undefined, - logsBeforeErrors: string[] | undefined, - errors: ReadonlyArray | ReadonlyArray, - disableConsoleClears?: boolean | undefined, - ...postErrorsWatchDiagnostics: Diagnostic[] | string[] + expected: readonly HostOutput[], + disableConsoleClears?: boolean | undefined ) { let screenClears = 0; const outputs = host.getOutput(); - const expectedOutputCount = (preErrorsWatchDiagnostic ? 1 : 0) + - errors.length + - postErrorsWatchDiagnostics.length + - (logsBeforeWatchDiagnostic ? logsBeforeWatchDiagnostic.length : 0) + - (logsBeforeErrors ? logsBeforeErrors.length : 0); - assert.equal(outputs.length, expectedOutputCount, JSON.stringify(outputs)); + assert.equal(outputs.length, expected.length, JSON.stringify(outputs)); let index = 0; - forEach(logsBeforeWatchDiagnostic, log => assertLog("logsBeforeWatchDiagnostic", log)); - if (preErrorsWatchDiagnostic) assertWatchDiagnostic(preErrorsWatchDiagnostic); - forEach(logsBeforeErrors, log => assertLog("logBeforeError", log)); - // Verify errors - forEach(errors, assertDiagnostic); - forEach(postErrorsWatchDiagnostics, assertWatchDiagnostic); + forEach(expected, expected => { + switch (expected.kind) { + case HostOutputKind.Log: + return assertLog(expected); + case HostOutputKind.Diagnostic: + return assertDiagnostic(expected); + case HostOutputKind.WatchDiagnostic: + return assertWatchDiagnostic(expected); + default: + return Debug.assertNever(expected); + } + }); assert.equal(host.screenClears.length, screenClears, "Expected number of screen clears"); host.clearOutput(); @@ -85,7 +106,7 @@ namespace ts.tscWatch { return !!(diagnostic as Diagnostic).messageText; } - function assertDiagnostic(diagnostic: Diagnostic | string) { + function assertDiagnostic({ diagnostic }: HostOutputDiagnostic) { const expected = isDiagnostic(diagnostic) ? formatDiagnostic(diagnostic, host) : diagnostic; assert.equal(outputs[index], expected, getOutputAtFailedMessage("Diagnostic", expected)); index++; @@ -95,13 +116,13 @@ namespace ts.tscWatch { return log.replace(elapsedRegex, "").replace(buildVerboseLogRegEx, ""); } - function assertLog(caption: string, expected: string) { + function assertLog({ caption, expected }: HostOutputLog) { const actual = outputs[index]; - assert.equal(getCleanLogString(actual), getCleanLogString(expected), getOutputAtFailedMessage(caption, expected)); + assert.equal(getCleanLogString(actual), getCleanLogString(expected), getOutputAtFailedMessage(caption || "Log", expected)); index++; } - function assertWatchDiagnostic(diagnostic: Diagnostic | string) { + function assertWatchDiagnostic({ diagnostic }: HostOutputWatchDiagnostic) { if (isString(diagnostic)) { assert.equal(outputs[index], diagnostic, getOutputAtFailedMessage("Diagnostic", diagnostic)); } @@ -128,54 +149,82 @@ namespace ts.tscWatch { } } - function createErrorsFoundCompilerDiagnostic(errors: ReadonlyArray | ReadonlyArray) { - return errors.length === 1 - ? createCompilerDiagnostic(Diagnostics.Found_1_error_Watching_for_file_changes) - : createCompilerDiagnostic(Diagnostics.Found_0_errors_Watching_for_file_changes, errors.length); + export function hostOutputLog(expected: string, caption?: string): HostOutputLog { + return { kind: HostOutputKind.Log, expected, caption }; + } + export function hostOutputDiagnostic(diagnostic: Diagnostic | string): HostOutputDiagnostic { + return { kind: HostOutputKind.Diagnostic, diagnostic }; + } + export function hostOutputWatchDiagnostic(diagnostic: Diagnostic | string): HostOutputWatchDiagnostic { + return { kind: HostOutputKind.WatchDiagnostic, diagnostic }; + } + + export function startingCompilationInWatchMode() { + return hostOutputWatchDiagnostic(createCompilerDiagnostic(Diagnostics.Starting_compilation_in_watch_mode)); + } + export function foundErrorsWatching(errors: readonly any[]) { + return hostOutputWatchDiagnostic(errors.length === 1 ? + createCompilerDiagnostic(Diagnostics.Found_1_error_Watching_for_file_changes) : + createCompilerDiagnostic(Diagnostics.Found_0_errors_Watching_for_file_changes, errors.length) + ); + } + export function fileChangeDetected() { + return hostOutputWatchDiagnostic(createCompilerDiagnostic(Diagnostics.File_change_detected_Starting_incremental_compilation)); } export function checkOutputErrorsInitial(host: WatchedSystem, errors: ReadonlyArray | ReadonlyArray, disableConsoleClears?: boolean, logsBeforeErrors?: string[]) { checkOutputErrors( host, - /*logsBeforeWatchDiagnostic*/ undefined, - createCompilerDiagnostic(Diagnostics.Starting_compilation_in_watch_mode), - logsBeforeErrors, - errors, - disableConsoleClears, - createErrorsFoundCompilerDiagnostic(errors)); + [ + startingCompilationInWatchMode(), + ...map(logsBeforeErrors || emptyArray, expected => hostOutputLog(expected, "logBeforeError")), + ...map(errors, hostOutputDiagnostic), + foundErrorsWatching(errors) + ], + disableConsoleClears + ); } export function checkOutputErrorsIncremental(host: WatchedSystem, errors: ReadonlyArray | ReadonlyArray, disableConsoleClears?: boolean, logsBeforeWatchDiagnostic?: string[], logsBeforeErrors?: string[]) { checkOutputErrors( host, - logsBeforeWatchDiagnostic, - createCompilerDiagnostic(Diagnostics.File_change_detected_Starting_incremental_compilation), - logsBeforeErrors, - errors, - disableConsoleClears, - createErrorsFoundCompilerDiagnostic(errors)); + [ + ...map(logsBeforeWatchDiagnostic || emptyArray, expected => hostOutputLog(expected, "logsBeforeWatchDiagnostic")), + fileChangeDetected(), + ...map(logsBeforeErrors || emptyArray, expected => hostOutputLog(expected, "logBeforeError")), + ...map(errors, hostOutputDiagnostic), + foundErrorsWatching(errors) + ], + disableConsoleClears + ); } export function checkOutputErrorsIncrementalWithExit(host: WatchedSystem, errors: ReadonlyArray | ReadonlyArray, expectedExitCode: ExitStatus, disableConsoleClears?: boolean, logsBeforeWatchDiagnostic?: string[], logsBeforeErrors?: string[]) { checkOutputErrors( host, - logsBeforeWatchDiagnostic, - createCompilerDiagnostic(Diagnostics.File_change_detected_Starting_incremental_compilation), - logsBeforeErrors, - errors, - disableConsoleClears); + [ + ...map(logsBeforeWatchDiagnostic || emptyArray, expected => hostOutputLog(expected, "logsBeforeWatchDiagnostic")), + fileChangeDetected(), + ...map(logsBeforeErrors || emptyArray, expected => hostOutputLog(expected, "logBeforeError")), + ...map(errors, hostOutputDiagnostic), + ], + disableConsoleClears + ); assert.equal(host.exitCode, expectedExitCode); } export function checkNormalBuildErrors(host: WatchedSystem, errors: ReadonlyArray | ReadonlyArray, reportErrorSummary?: boolean) { checkOutputErrors( host, - /*logsBeforeWatchDiagnostic*/ undefined, - /*preErrorsWatchDiagnostic*/ undefined, - /*logsBeforeErrors*/ undefined, - errors, - /*disableConsoleClears*/ undefined, - ...(reportErrorSummary ? [getErrorSummaryText(errors.length, host.newLine)] : emptyArray) + [ + ...map(errors, hostOutputDiagnostic), + ...map( + reportErrorSummary ? + [getErrorSummaryText(errors.length, host.newLine)] : + emptyArray, + hostOutputWatchDiagnostic + ) + ] ); } diff --git a/src/testRunner/unittests/tscWatch/programUpdates.ts b/src/testRunner/unittests/tscWatch/programUpdates.ts index 3fd54264260..6fead1a1afe 100644 --- a/src/testRunner/unittests/tscWatch/programUpdates.ts +++ b/src/testRunner/unittests/tscWatch/programUpdates.ts @@ -1335,5 +1335,42 @@ exports.a = 1; checkProgramActualFiles(watch(), [aFile.path, bFile.path, libFile.path]); } }); + + it("reports errors correctly with file not in rootDir", () => { + const currentDirectory = "/user/username/projects/myproject"; + const aFile: File = { + path: `${currentDirectory}/a.ts`, + content: `import { x } from "../b";` + }; + const bFile: File = { + path: `/user/username/projects/b.ts`, + content: `export const x = 10;` + }; + const configFile: File = { + path: `${currentDirectory}/tsconfig.json`, + content: JSON.stringify({ + compilerOptions: { + rootDir: ".", + outDir: "lib" + } + }) + }; + + const files = [aFile, bFile, libFile, configFile]; + + const host = createWatchedSystem(files, { currentDirectory }); + const watch = createWatchOfConfigFile("tsconfig.json", host); + checkOutputErrorsInitial(host, [ + getDiagnosticOfFileFromProgram(watch(), aFile.path, aFile.content.indexOf(`"../b"`), `"../b"`.length, Diagnostics.File_0_is_not_under_rootDir_1_rootDir_is_expected_to_contain_all_source_files, bFile.path, currentDirectory) + ]); + const aContent = ` + +${aFile.content}`; + host.writeFile(aFile.path, aContent); + host.runQueuedTimeoutCallbacks(); + checkOutputErrorsIncremental(host, [ + getDiagnosticOfFileFromProgram(watch(), aFile.path, aContent.indexOf(`"../b"`), `"../b"`.length, Diagnostics.File_0_is_not_under_rootDir_1_rootDir_is_expected_to_contain_all_source_files, bFile.path, currentDirectory) + ]); + }); }); }