Skip to content

Commit cec1b0a

Browse files
committed
Report error summary from the queue.
1 parent 4193846 commit cec1b0a

File tree

5 files changed

+205
-49
lines changed

5 files changed

+205
-49
lines changed

src/compiler/program.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ namespace ts {
175175
return getDirectoryPath(normalizePath(system.getExecutingFilePath()));
176176
}
177177

178-
const newLine = getNewLineCharacter(options);
178+
const newLine = getNewLineCharacter(options, () => system.newLine);
179179
const realpath = system.realpath && ((path: string) => system.realpath!(path));
180180

181181
return {

src/compiler/tsbuild.ts

+124-40
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,11 @@ namespace ts {
3535
* Map from config file name to up-to-date status
3636
*/
3737
projectStatus: FileMap<UpToDateStatus>;
38+
diagnostics?: FileMap<number>; // TODO(shkamat): this should be really be diagnostics but thats for later time
3839

39-
invalidatedProjects: FileMap<true>;
40-
queuedProjects: FileMap<true>;
40+
invalidateProject(project: ResolvedConfigFileName, dependencyGraph: DependencyGraph | undefined): void;
41+
getNextInvalidatedProject(): ResolvedConfigFileName | undefined;
42+
pendingInvalidatedProjects(): boolean;
4143
missingRoots: Map<true>;
4244
}
4345

@@ -194,6 +196,7 @@ namespace ts {
194196
hasKey(fileName: string): boolean;
195197
removeKey(fileName: string): void;
196198
getKeys(): string[];
199+
getSize(): number;
197200
}
198201

199202
/**
@@ -209,7 +212,8 @@ namespace ts {
209212
getValueOrUndefined,
210213
removeKey,
211214
getKeys,
212-
hasKey
215+
hasKey,
216+
getSize
213217
};
214218

215219
function getKeys(): string[] {
@@ -242,6 +246,10 @@ namespace ts {
242246
const f = normalizePath(fileName);
243247
return lookup.get(f);
244248
}
249+
250+
function getSize() {
251+
return lookup.size;
252+
}
245253
}
246254

247255
function createDependencyMapper() {
@@ -375,18 +383,64 @@ namespace ts {
375383
}
376384

377385
export function createBuildContext(options: BuildOptions): BuildContext {
378-
const invalidatedProjects = createFileMap<true>();
379-
const queuedProjects = createFileMap<true>();
386+
const invalidatedProjectQueue = [] as ResolvedConfigFileName[];
387+
let nextIndex = 0;
388+
const projectPendingBuild = createFileMap<true>();
380389
const missingRoots = createMap<true>();
390+
const diagnostics = options.watch ? createFileMap<number>() : undefined;
381391

382392
return {
383393
options,
384394
projectStatus: createFileMap(),
395+
diagnostics,
385396
unchangedOutputs: createFileMap(),
386-
invalidatedProjects,
387-
missingRoots,
388-
queuedProjects
397+
invalidateProject,
398+
getNextInvalidatedProject,
399+
pendingInvalidatedProjects,
400+
missingRoots
389401
};
402+
403+
function invalidateProject(proj: ResolvedConfigFileName, dependancyGraph: DependencyGraph | undefined) {
404+
if (!projectPendingBuild.hasKey(proj)) {
405+
addProjToQueue(proj);
406+
if (dependancyGraph) {
407+
queueBuildForDownstreamReferences(proj, dependancyGraph);
408+
}
409+
}
410+
}
411+
412+
function addProjToQueue(proj: ResolvedConfigFileName) {
413+
projectPendingBuild.setValue(proj, true);
414+
invalidatedProjectQueue.push(proj);
415+
}
416+
417+
function getNextInvalidatedProject() {
418+
if (nextIndex < invalidatedProjectQueue.length) {
419+
const proj = invalidatedProjectQueue[nextIndex];
420+
nextIndex++;
421+
projectPendingBuild.removeKey(proj);
422+
if (!projectPendingBuild.getSize()) {
423+
invalidatedProjectQueue.length = 0;
424+
}
425+
return proj;
426+
}
427+
}
428+
429+
function pendingInvalidatedProjects() {
430+
return !!projectPendingBuild.getSize();
431+
}
432+
433+
// Mark all downstream projects of this one needing to be built "later"
434+
function queueBuildForDownstreamReferences(root: ResolvedConfigFileName, dependancyGraph: DependencyGraph) {
435+
const deps = dependancyGraph.dependencyMap.getReferencesTo(root);
436+
for (const ref of deps) {
437+
// Can skip circular references
438+
if (!projectPendingBuild.hasKey(ref)) {
439+
addProjToQueue(ref);
440+
queueBuildForDownstreamReferences(ref, dependancyGraph);
441+
}
442+
}
443+
}
390444
}
391445

392446
export interface SolutionBuilderHost extends CompilerHost {
@@ -443,6 +497,7 @@ namespace ts {
443497
const hostWithWatch = host as SolutionBuilderWithWatchHost;
444498
const configFileCache = createConfigFileCache(host);
445499
let context = createBuildContext(defaultOptions);
500+
let timerToBuildInvalidatedProject: any;
446501

447502
const existingWatchersForWildcards = createMap<WildcardDirectoryWatcher>();
448503
return {
@@ -454,8 +509,7 @@ namespace ts {
454509
getBuildGraph,
455510

456511
invalidateProject,
457-
buildInvalidatedProjects,
458-
buildDependentInvalidatedProjects,
512+
buildInvalidatedProject,
459513

460514
resolveProjectName,
461515

@@ -466,7 +520,19 @@ namespace ts {
466520
host.reportSolutionBuilderStatus(createCompilerDiagnostic(message, ...args));
467521
}
468522

469-
function reportWatchStatus(message: DiagnosticMessage, ...args: string[]) {
523+
function storeErrors(proj: ResolvedConfigFileName, diagnostics: ReadonlyArray<Diagnostic>) {
524+
if (context.options.watch) {
525+
storeErrorSummary(proj, diagnostics.filter(diagnostic => diagnostic.category === DiagnosticCategory.Error).length);
526+
}
527+
}
528+
529+
function storeErrorSummary(proj: ResolvedConfigFileName, errorCount: number) {
530+
if (context.options.watch) {
531+
context.diagnostics!.setValue(proj, errorCount);
532+
}
533+
}
534+
535+
function reportWatchStatus(message: DiagnosticMessage, ...args: (string | number | undefined)[]) {
470536
if (hostWithWatch.onWatchStatusChange) {
471537
hostWithWatch.onWatchStatusChange(createCompilerDiagnostic(message, ...args), host.getNewLine(), { preserveWatchOutput: context.options.preserveWatchOutput });
472538
}
@@ -506,15 +572,12 @@ namespace ts {
506572
}
507573
}
508574

509-
function invalidateProjectAndScheduleBuilds(resolved: ResolvedConfigFileName) {
510-
reportWatchStatus(Diagnostics.File_change_detected_Starting_incremental_compilation);
511-
invalidateProject(resolved);
512-
if (!hostWithWatch.setTimeout) {
513-
return;
514-
}
515-
hostWithWatch.setTimeout(buildInvalidatedProjects, 100);
516-
hostWithWatch.setTimeout(buildDependentInvalidatedProjects, 3000);
517-
}
575+
}
576+
577+
function invalidateProjectAndScheduleBuilds(resolved: ResolvedConfigFileName) {
578+
reportWatchStatus(Diagnostics.File_change_detected_Starting_incremental_compilation);
579+
invalidateProject(resolved);
580+
scheduleBuildInvalidatedProject();
518581
}
519582

520583
function resetBuildContext(opts = defaultOptions) {
@@ -725,33 +788,44 @@ namespace ts {
725788
}
726789

727790
configFileCache.removeKey(resolved);
728-
context.invalidatedProjects.setValue(resolved, true);
729791
context.projectStatus.removeKey(resolved);
730-
731-
const graph = getGlobalDependencyGraph()!;
732-
if (graph) {
733-
queueBuildForDownstreamReferences(resolved);
792+
if (context.options.watch) {
793+
context.diagnostics!.removeKey(resolved);
734794
}
735795

736-
// Mark all downstream projects of this one needing to be built "later"
737-
function queueBuildForDownstreamReferences(root: ResolvedConfigFileName) {
738-
const deps = graph.dependencyMap.getReferencesTo(root);
739-
for (const ref of deps) {
740-
// Can skip circular references
741-
if (!context.queuedProjects.hasKey(ref)) {
742-
context.queuedProjects.setValue(ref, true);
743-
queueBuildForDownstreamReferences(ref);
744-
}
745-
}
796+
context.invalidateProject(resolved, getGlobalDependencyGraph());
797+
}
798+
799+
function scheduleBuildInvalidatedProject() {
800+
if (!hostWithWatch.setTimeout || !hostWithWatch.clearTimeout) {
801+
return;
746802
}
803+
if (timerToBuildInvalidatedProject) {
804+
hostWithWatch.clearTimeout(timerToBuildInvalidatedProject);
805+
}
806+
timerToBuildInvalidatedProject = hostWithWatch.setTimeout(buildInvalidatedProject, 250);
747807
}
748808

749-
function buildInvalidatedProjects() {
750-
buildSomeProjects(p => context.invalidatedProjects.hasKey(p));
809+
function buildInvalidatedProject() {
810+
timerToBuildInvalidatedProject = undefined;
811+
const buildProject = context.getNextInvalidatedProject();
812+
buildSomeProjects(p => p === buildProject);
813+
if (context.pendingInvalidatedProjects()) {
814+
if (!timerToBuildInvalidatedProject) {
815+
scheduleBuildInvalidatedProject();
816+
}
817+
}
818+
else {
819+
reportErrorSummary();
820+
}
751821
}
752822

753-
function buildDependentInvalidatedProjects() {
754-
buildSomeProjects(p => context.queuedProjects.hasKey(p));
823+
function reportErrorSummary() {
824+
if (context.options.watch) {
825+
let errorCount = 0;
826+
context.diagnostics!.getKeys().forEach(resolved => errorCount += context.diagnostics!.getValue(resolved));
827+
reportWatchStatus(errorCount === 1 ? Diagnostics.Found_1_error_Watching_for_file_changes : Diagnostics.Found_0_errors_Watching_for_file_changes, errorCount);
828+
}
755829
}
756830

757831
function buildSomeProjects(predicate: (projName: ResolvedConfigFileName) => boolean) {
@@ -808,6 +882,7 @@ namespace ts {
808882
if (temporaryMarks[projPath]) {
809883
if (!inCircularContext) {
810884
hadError = true;
885+
// TODO(shkamat): Account for this error
811886
reportStatus(Diagnostics.Project_references_may_not_form_a_circular_graph_Cycle_detected_Colon_0, circularityReportStack.join("\r\n"));
812887
return;
813888
}
@@ -853,6 +928,7 @@ namespace ts {
853928
if (!configFile) {
854929
// Failed to read the config file
855930
resultFlags |= BuildResultFlags.ConfigFileErrors;
931+
storeErrorSummary(proj, 1);
856932
context.projectStatus.setValue(proj, { type: UpToDateStatusType.Unbuildable, reason: "Config file errors" });
857933
return resultFlags;
858934
}
@@ -880,6 +956,7 @@ namespace ts {
880956
for (const diag of syntaxDiagnostics) {
881957
host.reportDiagnostic(diag);
882958
}
959+
storeErrors(proj, syntaxDiagnostics);
883960
context.projectStatus.setValue(proj, { type: UpToDateStatusType.Unbuildable, reason: "Syntactic errors" });
884961
return resultFlags;
885962
}
@@ -892,6 +969,7 @@ namespace ts {
892969
for (const diag of declDiagnostics) {
893970
host.reportDiagnostic(diag);
894971
}
972+
storeErrors(proj, declDiagnostics);
895973
context.projectStatus.setValue(proj, { type: UpToDateStatusType.Unbuildable, reason: "Declaration file errors" });
896974
return resultFlags;
897975
}
@@ -904,6 +982,7 @@ namespace ts {
904982
for (const diag of semanticDiagnostics) {
905983
host.reportDiagnostic(diag);
906984
}
985+
storeErrors(proj, semanticDiagnostics);
907986
context.projectStatus.setValue(proj, { type: UpToDateStatusType.Unbuildable, reason: "Semantic errors" });
908987
return resultFlags;
909988
}
@@ -1029,6 +1108,7 @@ namespace ts {
10291108
if (host.fileExists(fullPathWithTsconfig)) {
10301109
return fullPathWithTsconfig as ResolvedConfigFileName;
10311110
}
1111+
// TODO(shkamat): right now this is accounted as 1 error in config file, but we need to do better
10321112
host.reportDiagnostic(createCompilerDiagnostic(Diagnostics.File_0_not_found, relName(fullPath)));
10331113
return undefined;
10341114
}
@@ -1048,7 +1128,10 @@ namespace ts {
10481128
function buildAllProjects(): ExitStatus {
10491129
if (context.options.watch) { reportWatchStatus(Diagnostics.Starting_compilation_in_watch_mode); }
10501130
const graph = getGlobalDependencyGraph();
1051-
if (graph === undefined) return ExitStatus.DiagnosticsPresent_OutputsSkipped;
1131+
if (graph === undefined) {
1132+
reportErrorSummary();
1133+
return ExitStatus.DiagnosticsPresent_OutputsSkipped;
1134+
}
10521135

10531136
const queue = graph.buildQueue;
10541137
reportBuildQueue(graph);
@@ -1092,6 +1175,7 @@ namespace ts {
10921175
const buildResult = buildSingleProject(next);
10931176
anyFailed = anyFailed || !!(buildResult & BuildResultFlags.AnyErrors);
10941177
}
1178+
reportErrorSummary();
10951179
return anyFailed ? ExitStatus.DiagnosticsPresent_OutputsSkipped : ExitStatus.Success;
10961180
}
10971181

src/testRunner/unittests/tsbuild.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -205,14 +205,14 @@ namespace ts {
205205
// Rebuild this project
206206
tick();
207207
builder.invalidateProject("/src/logic");
208-
builder.buildInvalidatedProjects();
208+
builder.buildInvalidatedProject();
209209
// The file should be updated
210210
assert.equal(fs.statSync("/src/logic/index.js").mtimeMs, time(), "JS file should have been rebuilt");
211211
assert.isBelow(fs.statSync("/src/tests/index.js").mtimeMs, time(), "Downstream JS file should *not* have been rebuilt");
212212

213213
// Build downstream projects should update 'tests', but not 'core'
214214
tick();
215-
builder.buildDependentInvalidatedProjects();
215+
builder.buildInvalidatedProject();
216216
assert.equal(fs.statSync("/src/tests/index.js").mtimeMs, time(), "Downstream JS file should have been rebuilt");
217217
assert.isBelow(fs.statSync("/src/core/index.js").mtimeMs, time(), "Upstream JS file should not have been rebuilt");
218218
});

0 commit comments

Comments
 (0)