Skip to content

Commit 138da0d

Browse files
committed
Report error summary from the queue.
1 parent 8dc16d9 commit 138da0d

File tree

5 files changed

+205
-49
lines changed

5 files changed

+205
-49
lines changed

src/compiler/program.ts

Lines changed: 1 addition & 1 deletion
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

Lines changed: 124 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,11 @@ namespace ts {
3434
* Map from config file name to up-to-date status
3535
*/
3636
projectStatus: FileMap<UpToDateStatus>;
37+
diagnostics?: FileMap<number>; // TODO(shkamat): this should be really be diagnostics but thats for later time
3738

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

@@ -193,6 +195,7 @@ namespace ts {
193195
hasKey(fileName: string): boolean;
194196
removeKey(fileName: string): void;
195197
getKeys(): string[];
198+
getSize(): number;
196199
}
197200

198201
/**
@@ -208,7 +211,8 @@ namespace ts {
208211
getValueOrUndefined,
209212
removeKey,
210213
getKeys,
211-
hasKey
214+
hasKey,
215+
getSize
212216
};
213217

214218
function getKeys(): string[] {
@@ -241,6 +245,10 @@ namespace ts {
241245
const f = normalizePath(fileName);
242246
return lookup.get(f);
243247
}
248+
249+
function getSize() {
250+
return lookup.size;
251+
}
244252
}
245253

246254
function createDependencyMapper() {
@@ -374,18 +382,64 @@ namespace ts {
374382
}
375383

376384
export function createBuildContext(options: BuildOptions): BuildContext {
377-
const invalidatedProjects = createFileMap<true>();
378-
const queuedProjects = createFileMap<true>();
385+
const invalidatedProjectQueue = [] as ResolvedConfigFileName[];
386+
let nextIndex = 0;
387+
const projectPendingBuild = createFileMap<true>();
379388
const missingRoots = createMap<true>();
389+
const diagnostics = options.watch ? createFileMap<number>() : undefined;
380390

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

391445
export interface SolutionBuilderHost extends CompilerHost {
@@ -442,6 +496,7 @@ namespace ts {
442496
const hostWithWatch = host as SolutionBuilderWithWatchHost;
443497
const configFileCache = createConfigFileCache(host);
444498
let context = createBuildContext(defaultOptions);
499+
let timerToBuildInvalidatedProject: any;
445500

446501
const existingWatchersForWildcards = createMap<WildcardDirectoryWatcher>();
447502
return {
@@ -453,8 +508,7 @@ namespace ts {
453508
getBuildGraph,
454509

455510
invalidateProject,
456-
buildInvalidatedProjects,
457-
buildDependentInvalidatedProjects,
511+
buildInvalidatedProject,
458512

459513
resolveProjectName,
460514

@@ -465,7 +519,19 @@ namespace ts {
465519
host.reportSolutionBuilderStatus(createCompilerDiagnostic(message, ...args));
466520
}
467521

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

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

519582
function resetBuildContext(opts = defaultOptions) {
@@ -724,33 +787,44 @@ namespace ts {
724787
}
725788

726789
configFileCache.removeKey(resolved);
727-
context.invalidatedProjects.setValue(resolved, true);
728790
context.projectStatus.removeKey(resolved);
729-
730-
const graph = getGlobalDependencyGraph()!;
731-
if (graph) {
732-
queueBuildForDownstreamReferences(resolved);
791+
if (context.options.watch) {
792+
context.diagnostics!.removeKey(resolved);
733793
}
734794

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

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

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

756830
function buildSomeProjects(predicate: (projName: ResolvedConfigFileName) => boolean) {
@@ -807,6 +881,7 @@ namespace ts {
807881
if (temporaryMarks[projPath]) {
808882
if (!inCircularContext) {
809883
hadError = true;
884+
// TODO(shkamat): Account for this error
810885
reportStatus(Diagnostics.Project_references_may_not_form_a_circular_graph_Cycle_detected_Colon_0, circularityReportStack.join("\r\n"));
811886
return;
812887
}
@@ -852,6 +927,7 @@ namespace ts {
852927
if (!configFile) {
853928
// Failed to read the config file
854929
resultFlags |= BuildResultFlags.ConfigFileErrors;
930+
storeErrorSummary(proj, 1);
855931
context.projectStatus.setValue(proj, { type: UpToDateStatusType.Unbuildable, reason: "Config file errors" });
856932
return resultFlags;
857933
}
@@ -879,6 +955,7 @@ namespace ts {
879955
for (const diag of syntaxDiagnostics) {
880956
host.reportDiagnostic(diag);
881957
}
958+
storeErrors(proj, syntaxDiagnostics);
882959
context.projectStatus.setValue(proj, { type: UpToDateStatusType.Unbuildable, reason: "Syntactic errors" });
883960
return resultFlags;
884961
}
@@ -891,6 +968,7 @@ namespace ts {
891968
for (const diag of declDiagnostics) {
892969
host.reportDiagnostic(diag);
893970
}
971+
storeErrors(proj, declDiagnostics);
894972
context.projectStatus.setValue(proj, { type: UpToDateStatusType.Unbuildable, reason: "Declaration file errors" });
895973
return resultFlags;
896974
}
@@ -903,6 +981,7 @@ namespace ts {
903981
for (const diag of semanticDiagnostics) {
904982
host.reportDiagnostic(diag);
905983
}
984+
storeErrors(proj, semanticDiagnostics);
906985
context.projectStatus.setValue(proj, { type: UpToDateStatusType.Unbuildable, reason: "Semantic errors" });
907986
return resultFlags;
908987
}
@@ -1028,6 +1107,7 @@ namespace ts {
10281107
if (host.fileExists(fullPathWithTsconfig)) {
10291108
return fullPathWithTsconfig as ResolvedConfigFileName;
10301109
}
1110+
// TODO(shkamat): right now this is accounted as 1 error in config file, but we need to do better
10311111
host.reportDiagnostic(createCompilerDiagnostic(Diagnostics.File_0_not_found, relName(fullPath)));
10321112
return undefined;
10331113
}
@@ -1047,7 +1127,10 @@ namespace ts {
10471127
function buildAllProjects(): ExitStatus {
10481128
if (context.options.watch) { reportWatchStatus(Diagnostics.Starting_compilation_in_watch_mode); }
10491129
const graph = getGlobalDependencyGraph();
1050-
if (graph === undefined) return ExitStatus.DiagnosticsPresent_OutputsSkipped;
1130+
if (graph === undefined) {
1131+
reportErrorSummary();
1132+
return ExitStatus.DiagnosticsPresent_OutputsSkipped;
1133+
}
10511134

10521135
const queue = graph.buildQueue;
10531136
reportBuildQueue(graph);
@@ -1091,6 +1174,7 @@ namespace ts {
10911174
const buildResult = buildSingleProject(next);
10921175
anyFailed = anyFailed || !!(buildResult & BuildResultFlags.AnyErrors);
10931176
}
1177+
reportErrorSummary();
10941178
return anyFailed ? ExitStatus.DiagnosticsPresent_OutputsSkipped : ExitStatus.Success;
10951179
}
10961180

src/testRunner/unittests/tsbuild.ts

Lines changed: 2 additions & 2 deletions
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)