Skip to content

Commit 31a2612

Browse files
authored
Merge pull request microsoft#32788 from microsoft/tsbuildFixes
Fixes for tsbuild scenarios
2 parents 8b45b3d + 1a9198a commit 31a2612

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

51 files changed

+1107
-237
lines changed

src/compiler/diagnosticMessages.json

+8
Original file line numberDiff line numberDiff line change
@@ -4172,6 +4172,14 @@
41724172
"category": "Message",
41734173
"code": 6381
41744174
},
4175+
"Skipping build of project '{0}' because its dependency '{1}' was not built": {
4176+
"category": "Message",
4177+
"code": 6382
4178+
},
4179+
"Project '{0}' can't be built because its dependency '{1}' was not built": {
4180+
"category": "Message",
4181+
"code": 6383
4182+
},
41754183

41764184
"The expected type comes from property '{0}' which is declared here on type '{1}'": {
41774185
"category": "Message",

src/compiler/program.ts

+170-53
Large diffs are not rendered by default.

src/compiler/tsbuild.ts

+94-35
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ namespace ts {
116116
export interface UpstreamBlocked {
117117
type: UpToDateStatusType.UpstreamBlocked;
118118
upstreamProjectName: string;
119+
upstreamProjectBlocked: boolean;
119120
}
120121

121122
/**
@@ -273,6 +274,26 @@ namespace ts {
273274
export interface SolutionBuilderWithWatchHost<T extends BuilderProgram> extends SolutionBuilderHostBase<T>, WatchHost {
274275
}
275276

277+
/*@internal*/
278+
export type BuildOrder = readonly ResolvedConfigFileName[];
279+
/*@internal*/
280+
export interface CircularBuildOrder {
281+
buildOrder: BuildOrder;
282+
circularDiagnostics: readonly Diagnostic[];
283+
}
284+
/*@internal*/
285+
export type AnyBuildOrder = BuildOrder | CircularBuildOrder;
286+
287+
/*@internal*/
288+
export function isCircularBuildOrder(buildOrder: AnyBuildOrder): buildOrder is CircularBuildOrder {
289+
return !!buildOrder && !!(buildOrder as CircularBuildOrder).buildOrder;
290+
}
291+
292+
/*@internal*/
293+
export function getBuildOrderFromAnyBuildOrder(anyBuildOrder: AnyBuildOrder): BuildOrder {
294+
return isCircularBuildOrder(anyBuildOrder) ? anyBuildOrder.buildOrder : anyBuildOrder;
295+
}
296+
276297
export interface SolutionBuilder<T extends BuilderProgram> {
277298
build(project?: string, cancellationToken?: CancellationToken): ExitStatus;
278299
clean(project?: string): ExitStatus;
@@ -281,7 +302,7 @@ namespace ts {
281302
getNextInvalidatedProject(cancellationToken?: CancellationToken): InvalidatedProject<T> | undefined;
282303

283304
// Currently used for testing but can be made public if needed:
284-
/*@internal*/ getBuildOrder(): ReadonlyArray<ResolvedConfigFileName>;
305+
/*@internal*/ getBuildOrder(): AnyBuildOrder;
285306

286307
// Testing only
287308
/*@internal*/ getUpToDateStatusOfProject(project: string): UpToDateStatus;
@@ -379,7 +400,7 @@ namespace ts {
379400
readonly moduleResolutionCache: ModuleResolutionCache | undefined;
380401

381402
// Mutable state
382-
buildOrder: readonly ResolvedConfigFileName[] | undefined;
403+
buildOrder: AnyBuildOrder | undefined;
383404
readFileWithCache: (f: string) => string | undefined;
384405
projectCompilerOptions: CompilerOptions;
385406
cache: SolutionBuilderStateCache | undefined;
@@ -523,16 +544,19 @@ namespace ts {
523544
return resolveConfigFileProjectName(resolvePath(state.currentDirectory, name));
524545
}
525546

526-
function createBuildOrder(state: SolutionBuilderState, roots: readonly ResolvedConfigFileName[]): readonly ResolvedConfigFileName[] {
547+
function createBuildOrder(state: SolutionBuilderState, roots: readonly ResolvedConfigFileName[]): AnyBuildOrder {
527548
const temporaryMarks = createMap() as ConfigFileMap<true>;
528549
const permanentMarks = createMap() as ConfigFileMap<true>;
529550
const circularityReportStack: string[] = [];
530551
let buildOrder: ResolvedConfigFileName[] | undefined;
552+
let circularDiagnostics: Diagnostic[] | undefined;
531553
for (const root of roots) {
532554
visit(root);
533555
}
534556

535-
return buildOrder || emptyArray;
557+
return circularDiagnostics ?
558+
{ buildOrder: buildOrder || emptyArray, circularDiagnostics } :
559+
buildOrder || emptyArray;
536560

537561
function visit(configFileName: ResolvedConfigFileName, inCircularContext?: boolean) {
538562
const projPath = toResolvedConfigFilePath(state, configFileName);
@@ -541,8 +565,12 @@ namespace ts {
541565
// Circular
542566
if (temporaryMarks.has(projPath)) {
543567
if (!inCircularContext) {
544-
// TODO:: Do we report this as error?
545-
reportStatus(state, Diagnostics.Project_references_may_not_form_a_circular_graph_Cycle_detected_Colon_0, circularityReportStack.join("\r\n"));
568+
(circularDiagnostics || (circularDiagnostics = [])).push(
569+
createCompilerDiagnostic(
570+
Diagnostics.Project_references_may_not_form_a_circular_graph_Cycle_detected_Colon_0,
571+
circularityReportStack.join("\r\n")
572+
)
573+
);
546574
}
547575
return;
548576
}
@@ -569,12 +597,11 @@ namespace ts {
569597

570598
function createStateBuildOrder(state: SolutionBuilderState) {
571599
const buildOrder = createBuildOrder(state, state.rootNames.map(f => resolveProjectName(state, f)));
572-
if (arrayIsEqualTo(state.buildOrder, buildOrder)) return state.buildOrder!;
573600

574601
// Clear all to ResolvedConfigFilePaths cache to start fresh
575602
state.resolvedConfigFilePaths.clear();
576603
const currentProjects = arrayToSet(
577-
buildOrder,
604+
getBuildOrderFromAnyBuildOrder(buildOrder),
578605
resolved => toResolvedConfigFilePath(state, resolved)
579606
) as ConfigFileMap<true>;
580607

@@ -611,9 +638,10 @@ namespace ts {
611638
return state.buildOrder = buildOrder;
612639
}
613640

614-
function getBuildOrderFor(state: SolutionBuilderState, project: string | undefined, onlyReferences: boolean | undefined) {
641+
function getBuildOrderFor(state: SolutionBuilderState, project: string | undefined, onlyReferences: boolean | undefined): AnyBuildOrder | undefined {
615642
const resolvedProject = project && resolveProjectName(state, project);
616643
const buildOrderFromState = getBuildOrder(state);
644+
if (isCircularBuildOrder(buildOrderFromState)) return buildOrderFromState;
617645
if (resolvedProject) {
618646
const projectPath = toResolvedConfigFilePath(state, resolvedProject);
619647
const projectIndex = findIndex(
@@ -622,7 +650,8 @@ namespace ts {
622650
);
623651
if (projectIndex === -1) return undefined;
624652
}
625-
const buildOrder = resolvedProject ? createBuildOrder(state, [resolvedProject]) : buildOrderFromState;
653+
const buildOrder = resolvedProject ? createBuildOrder(state, [resolvedProject]) as BuildOrder : buildOrderFromState;
654+
Debug.assert(!isCircularBuildOrder(buildOrder));
626655
Debug.assert(!onlyReferences || resolvedProject !== undefined);
627656
Debug.assert(!onlyReferences || buildOrder[buildOrder.length - 1] === resolvedProject);
628657
return onlyReferences ? buildOrder.slice(0, buildOrder.length - 1) : buildOrder;
@@ -702,7 +731,7 @@ namespace ts {
702731
state.allProjectBuildPending = false;
703732
if (state.options.watch) { reportWatchStatus(state, Diagnostics.Starting_compilation_in_watch_mode); }
704733
enableCache(state);
705-
const buildOrder = getBuildOrder(state);
734+
const buildOrder = getBuildOrderFromAnyBuildOrder(getBuildOrder(state));
706735
buildOrder.forEach(configFileName =>
707736
state.projectPendingBuild.set(
708737
toResolvedConfigFilePath(state, configFileName),
@@ -1237,10 +1266,11 @@ namespace ts {
12371266

12381267
function getNextInvalidatedProject<T extends BuilderProgram>(
12391268
state: SolutionBuilderState<T>,
1240-
buildOrder: readonly ResolvedConfigFileName[],
1269+
buildOrder: AnyBuildOrder,
12411270
reportQueue: boolean
12421271
): InvalidatedProject<T> | undefined {
12431272
if (!state.projectPendingBuild.size) return undefined;
1273+
if (isCircularBuildOrder(buildOrder)) return undefined;
12441274
if (state.currentInvalidatedProject) {
12451275
// Only if same buildOrder the currentInvalidated project can be sent again
12461276
return arrayIsEqualTo(state.currentInvalidatedProject.buildOrder, buildOrder) ?
@@ -1309,7 +1339,16 @@ namespace ts {
13091339
if (status.type === UpToDateStatusType.UpstreamBlocked) {
13101340
reportAndStoreErrors(state, projectPath, config.errors);
13111341
projectPendingBuild.delete(projectPath);
1312-
if (options.verbose) reportStatus(state, Diagnostics.Skipping_build_of_project_0_because_its_dependency_1_has_errors, project, status.upstreamProjectName);
1342+
if (options.verbose) {
1343+
reportStatus(
1344+
state,
1345+
status.upstreamProjectBlocked ?
1346+
Diagnostics.Skipping_build_of_project_0_because_its_dependency_1_was_not_built :
1347+
Diagnostics.Skipping_build_of_project_0_because_its_dependency_1_has_errors,
1348+
project,
1349+
status.upstreamProjectName
1350+
);
1351+
}
13131352
continue;
13141353
}
13151354

@@ -1511,10 +1550,12 @@ namespace ts {
15111550
}
15121551

15131552
// An upstream project is blocked
1514-
if (refStatus.type === UpToDateStatusType.Unbuildable) {
1553+
if (refStatus.type === UpToDateStatusType.Unbuildable ||
1554+
refStatus.type === UpToDateStatusType.UpstreamBlocked) {
15151555
return {
15161556
type: UpToDateStatusType.UpstreamBlocked,
1517-
upstreamProjectName: ref.path
1557+
upstreamProjectName: ref.path,
1558+
upstreamProjectBlocked: refStatus.type === UpToDateStatusType.UpstreamBlocked
15181559
};
15191560
}
15201561

@@ -1763,17 +1804,24 @@ namespace ts {
17631804
reportErrorSummary(state, buildOrder);
17641805
startWatching(state, buildOrder);
17651806

1766-
return errorProjects ?
1767-
successfulProjects ?
1768-
ExitStatus.DiagnosticsPresent_OutputsGenerated :
1769-
ExitStatus.DiagnosticsPresent_OutputsSkipped :
1770-
ExitStatus.Success;
1807+
return isCircularBuildOrder(buildOrder) ?
1808+
ExitStatus.ProjectReferenceCycle_OutputsSkupped :
1809+
errorProjects ?
1810+
successfulProjects ?
1811+
ExitStatus.DiagnosticsPresent_OutputsGenerated :
1812+
ExitStatus.DiagnosticsPresent_OutputsSkipped :
1813+
ExitStatus.Success;
17711814
}
17721815

17731816
function clean(state: SolutionBuilderState, project?: string, onlyReferences?: boolean) {
17741817
const buildOrder = getBuildOrderFor(state, project, onlyReferences);
17751818
if (!buildOrder) return ExitStatus.InvalidProject_OutputsSkipped;
17761819

1820+
if (isCircularBuildOrder(buildOrder)) {
1821+
reportErrors(state, buildOrder.circularDiagnostics);
1822+
return ExitStatus.ProjectReferenceCycle_OutputsSkupped;
1823+
}
1824+
17771825
const { options, host } = state;
17781826
const filesToDelete = options.dry ? [] as string[] : undefined;
17791827
for (const proj of buildOrder) {
@@ -1955,10 +2003,10 @@ namespace ts {
19552003
);
19562004
}
19572005

1958-
function startWatching(state: SolutionBuilderState, buildOrder: readonly ResolvedConfigFileName[]) {
2006+
function startWatching(state: SolutionBuilderState, buildOrder: AnyBuildOrder) {
19592007
if (!state.watchAllProjectsPending) return;
19602008
state.watchAllProjectsPending = false;
1961-
for (const resolved of buildOrder) {
2009+
for (const resolved of getBuildOrderFromAnyBuildOrder(buildOrder)) {
19622010
const resolvedPath = toResolvedConfigFilePath(state, resolved);
19632011
// Watch this file
19642012
watchConfigFile(state, resolved, resolvedPath);
@@ -2032,24 +2080,33 @@ namespace ts {
20322080
reportAndStoreErrors(state, proj, [state.configFileCache.get(proj) as Diagnostic]);
20332081
}
20342082

2035-
function reportErrorSummary(state: SolutionBuilderState, buildOrder: readonly ResolvedConfigFileName[]) {
2036-
if (!state.needsSummary || (!state.watch && !state.host.reportErrorSummary)) return;
2083+
function reportErrorSummary(state: SolutionBuilderState, buildOrder: AnyBuildOrder) {
2084+
if (!state.needsSummary) return;
20372085
state.needsSummary = false;
2086+
const canReportSummary = state.watch || !!state.host.reportErrorSummary;
20382087
const { diagnostics } = state;
2039-
// Report errors from the other projects
2040-
buildOrder.forEach(project => {
2041-
const projectPath = toResolvedConfigFilePath(state, project);
2042-
if (!state.projectErrorsReported.has(projectPath)) {
2043-
reportErrors(state, diagnostics.get(projectPath) || emptyArray);
2044-
}
2045-
});
20462088
let totalErrors = 0;
2047-
diagnostics.forEach(singleProjectErrors => totalErrors += getErrorCountForSummary(singleProjectErrors));
2089+
if (isCircularBuildOrder(buildOrder)) {
2090+
reportBuildQueue(state, buildOrder.buildOrder);
2091+
reportErrors(state, buildOrder.circularDiagnostics);
2092+
if (canReportSummary) totalErrors += getErrorCountForSummary(buildOrder.circularDiagnostics);
2093+
}
2094+
else {
2095+
// Report errors from the other projects
2096+
buildOrder.forEach(project => {
2097+
const projectPath = toResolvedConfigFilePath(state, project);
2098+
if (!state.projectErrorsReported.has(projectPath)) {
2099+
reportErrors(state, diagnostics.get(projectPath) || emptyArray);
2100+
}
2101+
});
2102+
if (canReportSummary) diagnostics.forEach(singleProjectErrors => totalErrors += getErrorCountForSummary(singleProjectErrors));
2103+
}
2104+
20482105
if (state.watch) {
20492106
reportWatchStatus(state, getWatchErrorSummaryDiagnosticMessage(totalErrors), totalErrors);
20502107
}
2051-
else {
2052-
state.host.reportErrorSummary!(totalErrors);
2108+
else if (state.host.reportErrorSummary) {
2109+
state.host.reportErrorSummary(totalErrors);
20532110
}
20542111
}
20552112

@@ -2122,7 +2179,9 @@ namespace ts {
21222179
case UpToDateStatusType.UpstreamBlocked:
21232180
return reportStatus(
21242181
state,
2125-
Diagnostics.Project_0_can_t_be_built_because_its_dependency_1_has_errors,
2182+
status.upstreamProjectBlocked ?
2183+
Diagnostics.Project_0_can_t_be_built_because_its_dependency_1_was_not_built :
2184+
Diagnostics.Project_0_can_t_be_built_because_its_dependency_1_has_errors,
21262185
relName(state, configFileName),
21272186
relName(state, status.upstreamProjectName)
21282187
);

src/compiler/types.ts

+19
Original file line numberDiff line numberDiff line change
@@ -2933,6 +2933,20 @@ namespace ts {
29332933
throwIfCancellationRequested(): void;
29342934
}
29352935

2936+
/*@internal*/
2937+
export enum RefFileKind {
2938+
Import,
2939+
ReferenceFile,
2940+
TypeReferenceDirective
2941+
}
2942+
2943+
/*@internal*/
2944+
export interface RefFile {
2945+
kind: RefFileKind;
2946+
index: number;
2947+
file: Path;
2948+
}
2949+
29362950
// TODO: This should implement TypeCheckerHost but that's an internal type.
29372951
export interface Program extends ScriptReferenceHost {
29382952

@@ -2952,6 +2966,8 @@ namespace ts {
29522966
*/
29532967
/* @internal */
29542968
getMissingFilePaths(): ReadonlyArray<Path>;
2969+
/* @internal */
2970+
getRefFileMap(): MultiMap<RefFile> | undefined;
29552971

29562972
/**
29572973
* Emits the JavaScript and declaration files. If targetSourceFile is not specified, then
@@ -3098,6 +3114,9 @@ namespace ts {
30983114

30993115
// When build skipped because passed in project is invalid
31003116
InvalidProject_OutputsSkipped = 3,
3117+
3118+
// When build is skipped because project references form cycle
3119+
ProjectReferenceCycle_OutputsSkupped = 4,
31013120
}
31023121

31033122
export interface EmitResult {

src/compiler/utilities.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,7 @@ namespace ts {
192192
export function arrayToSet<T>(array: ReadonlyArray<T>, makeKey: (value: T) => string | undefined): Map<true>;
193193
export function arrayToSet<T>(array: ReadonlyArray<T>, makeKey: (value: T) => __String | undefined): UnderscoreEscapedMap<true>;
194194
export function arrayToSet(array: ReadonlyArray<any>, makeKey?: (value: any) => string | __String | undefined): Map<true> | UnderscoreEscapedMap<true> {
195-
return arrayToMap<any, true>(array, makeKey || (s => s), () => true);
195+
return arrayToMap<any, true>(array, makeKey || (s => s), returnTrue);
196196
}
197197

198198
export function cloneMap(map: SymbolTable): SymbolTable;

0 commit comments

Comments
 (0)