Skip to content

Commit f7471ee

Browse files
JoostKmhevery
authored andcommitted
fix(ngcc): handle compilation diagnostics (angular#31996)
Previously, any diagnostics reported during the compilation of an entry-point would not be shown to the user, but either be ignored or cause a hard crash in case of a `FatalDiagnosticError`. This is unfortunate, as such error instances contain information on which code was responsible for producing the error, whereas only its error message would not. Therefore, it was quite hard to determine where the error originates from. This commit introduces behavior to deal with error diagnostics in a more graceful way. Such diagnostics will still cause the compilation to fail, however the error message now contains formatted diagnostics. Closes angular#31977 Resolves FW-1374 PR Close angular#31996
1 parent 4161d19 commit f7471ee

File tree

5 files changed

+97
-14
lines changed

5 files changed

+97
-14
lines changed

packages/compiler-cli/ngcc/src/analysis/decoration_analyzer.ts

+7-1
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,13 @@ export class DecorationAnalyzer {
130130

131131
protected analyzeClass(symbol: ClassSymbol): AnalyzedClass|null {
132132
const decorators = this.reflectionHost.getDecoratorsOfSymbol(symbol);
133-
return analyzeDecorators(symbol, decorators, this.handlers);
133+
const analyzedClass = analyzeDecorators(symbol, decorators, this.handlers);
134+
if (analyzedClass !== null && analyzedClass.diagnostics !== undefined) {
135+
for (const diagnostic of analyzedClass.diagnostics) {
136+
this.diagnosticHandler(diagnostic);
137+
}
138+
}
139+
return analyzedClass;
134140
}
135141

136142
protected migrateFile(migrationHost: MigrationHost, analyzedFile: AnalyzedFile): void {

packages/compiler-cli/ngcc/src/analysis/util.ts

+15-4
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,12 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88
import * as ts from 'typescript';
9+
10+
import {isFatalDiagnosticError} from '../../../src/ngtsc/diagnostics';
911
import {AbsoluteFsPath, absoluteFromSourceFile, relative} from '../../../src/ngtsc/file_system';
1012
import {ClassSymbol, Decorator} from '../../../src/ngtsc/reflection';
1113
import {DecoratorHandler, DetectResult, HandlerPrecedence} from '../../../src/ngtsc/transform';
14+
1215
import {AnalyzedClass, MatchingHandler} from './types';
1316

1417
export function isWithinPackage(packagePath: AbsoluteFsPath, sourceFile: ts.SourceFile): boolean {
@@ -59,11 +62,19 @@ export function analyzeDecorators(
5962
const matches: {handler: DecoratorHandler<any, any>, analysis: any}[] = [];
6063
const allDiagnostics: ts.Diagnostic[] = [];
6164
for (const {handler, detected} of detections) {
62-
const {analysis, diagnostics} = handler.analyze(declaration, detected.metadata);
63-
if (diagnostics !== undefined) {
64-
allDiagnostics.push(...diagnostics);
65+
try {
66+
const {analysis, diagnostics} = handler.analyze(declaration, detected.metadata);
67+
if (diagnostics !== undefined) {
68+
allDiagnostics.push(...diagnostics);
69+
}
70+
matches.push({handler, analysis});
71+
} catch (e) {
72+
if (isFatalDiagnosticError(e)) {
73+
allDiagnostics.push(e.toDiagnostic());
74+
} else {
75+
throw e;
76+
}
6577
}
66-
matches.push({handler, analysis});
6778
}
6879
return {
6980
name: symbol.name,

packages/compiler-cli/ngcc/src/main.ts

+13-3
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
* Use of this source code is governed by an MIT-style license that can be
66
* found in the LICENSE file at https://angular.io/license
77
*/
8+
import * as ts from 'typescript';
9+
810
import {AbsoluteFsPath, FileSystem, absoluteFrom, dirname, getFileSystem, resolve} from '../../src/ngtsc/file_system';
911

1012
import {CommonJsDependencyHost} from './dependencies/commonjs_dependency_host';
@@ -27,7 +29,6 @@ import {FileWriter} from './writing/file_writer';
2729
import {InPlaceFileWriter} from './writing/in_place_file_writer';
2830
import {NewEntryPointFileWriter} from './writing/new_entry_point_file_writer';
2931

30-
3132
/**
3233
* The options to configure the ngcc compiler.
3334
*/
@@ -171,8 +172,17 @@ export function mainNgcc(
171172

172173
logger.info(`Compiling ${entryPoint.name} : ${formatProperty} as ${format}`);
173174

174-
const transformedFiles = transformer.transform(bundle);
175-
fileWriter.writeBundle(bundle, transformedFiles, formatPropertiesToMarkAsProcessed);
175+
const result = transformer.transform(bundle);
176+
if (result.success) {
177+
if (result.diagnostics.length > 0) {
178+
logger.warn(ts.formatDiagnostics(result.diagnostics, bundle.src.host));
179+
}
180+
fileWriter.writeBundle(bundle, result.transformedFiles, formatPropertiesToMarkAsProcessed);
181+
} else {
182+
const errors = ts.formatDiagnostics(result.diagnostics, bundle.src.host);
183+
throw new Error(
184+
`Failed to compile entry-point ${entryPoint.name} due to compilation errors:\n${errors}`);
185+
}
176186

177187
onTaskCompleted(task, TaskProcessingOutcome.Processed);
178188
};

packages/compiler-cli/ngcc/src/packages/transformer.ts

+27-6
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88
import * as ts from 'typescript';
9+
910
import {FileSystem} from '../../../src/ngtsc/file_system';
1011
import {DecorationAnalyzer} from '../analysis/decoration_analyzer';
1112
import {ModuleWithProvidersAnalyses, ModuleWithProvidersAnalyzer} from '../analysis/module_with_providers_analyzer';
@@ -27,8 +28,17 @@ import {Renderer} from '../rendering/renderer';
2728
import {RenderingFormatter} from '../rendering/rendering_formatter';
2829
import {UmdRenderingFormatter} from '../rendering/umd_rendering_formatter';
2930
import {FileToWrite} from '../rendering/utils';
31+
3032
import {EntryPointBundle} from './entry_point_bundle';
3133

34+
export type TransformResult = {
35+
success: true; diagnostics: ts.Diagnostic[]; transformedFiles: FileToWrite[];
36+
} |
37+
{
38+
success: false;
39+
diagnostics: ts.Diagnostic[];
40+
};
41+
3242
/**
3343
* A Package is stored in a directory on disk and that directory can contain one or more package
3444
* formats - e.g. fesm2015, UMD, etc. Additionally, each package provides typings (`.d.ts` files).
@@ -58,12 +68,17 @@ export class Transformer {
5868
* @param bundle the bundle to transform.
5969
* @returns information about the files that were transformed.
6070
*/
61-
transform(bundle: EntryPointBundle): FileToWrite[] {
71+
transform(bundle: EntryPointBundle): TransformResult {
6272
const reflectionHost = this.getHost(bundle);
6373

6474
// Parse and analyze the files.
6575
const {decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses,
66-
moduleWithProvidersAnalyses} = this.analyzeProgram(reflectionHost, bundle);
76+
moduleWithProvidersAnalyses, diagnostics} = this.analyzeProgram(reflectionHost, bundle);
77+
78+
// Bail if the analysis produced any errors.
79+
if (hasErrors(diagnostics)) {
80+
return {success: false, diagnostics};
81+
}
6782

6883
// Transform the source files and source maps.
6984
const srcFormatter = this.getRenderingFormatter(reflectionHost, bundle);
@@ -81,7 +96,7 @@ export class Transformer {
8196
renderedFiles = renderedFiles.concat(renderedDtsFiles);
8297
}
8398

84-
return renderedFiles;
99+
return {success: true, diagnostics, transformedFiles: renderedFiles};
85100
}
86101

87102
getHost(bundle: EntryPointBundle): NgccReflectionHost {
@@ -127,8 +142,10 @@ export class Transformer {
127142
new SwitchMarkerAnalyzer(reflectionHost, bundle.entryPoint.package);
128143
const switchMarkerAnalyses = switchMarkerAnalyzer.analyzeProgram(bundle.src.program);
129144

130-
const decorationAnalyzer =
131-
new DecorationAnalyzer(this.fs, bundle, reflectionHost, referencesRegistry);
145+
const diagnostics: ts.Diagnostic[] = [];
146+
const decorationAnalyzer = new DecorationAnalyzer(
147+
this.fs, bundle, reflectionHost, referencesRegistry,
148+
diagnostic => diagnostics.push(diagnostic));
132149
const decorationAnalyses = decorationAnalyzer.analyzeProgram();
133150

134151
const moduleWithProvidersAnalyzer =
@@ -142,14 +159,18 @@ export class Transformer {
142159
privateDeclarationsAnalyzer.analyzeProgram(bundle.src.program);
143160

144161
return {decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses,
145-
moduleWithProvidersAnalyses};
162+
moduleWithProvidersAnalyses, diagnostics};
146163
}
147164
}
148165

166+
export function hasErrors(diagnostics: ts.Diagnostic[]) {
167+
return diagnostics.some(d => d.category === ts.DiagnosticCategory.Error);
168+
}
149169

150170
interface ProgramAnalyses {
151171
decorationAnalyses: Map<ts.SourceFile, CompiledFile>;
152172
switchMarkerAnalyses: SwitchMarkerAnalyses;
153173
privateDeclarationsAnalyses: ExportInfo[];
154174
moduleWithProvidersAnalyses: ModuleWithProvidersAnalyses|null;
175+
diagnostics: ts.Diagnostic[];
155176
}

packages/compiler-cli/ngcc/test/integration/ngcc_spec.ts

+35
Original file line numberDiff line numberDiff line change
@@ -406,6 +406,41 @@ runInEachFileSystem(() => {
406406
});
407407
});
408408

409+
describe('diagnostics', () => {
410+
it('should fail with formatted diagnostics when an error diagnostic is produced', () => {
411+
loadTestFiles([
412+
{
413+
name: _('/node_modules/fatal-error/package.json'),
414+
contents: '{"name": "fatal-error", "es2015": "./index.js", "typings": "./index.d.ts"}',
415+
},
416+
{name: _('/node_modules/fatal-error/index.metadata.json'), contents: 'DUMMY DATA'},
417+
{
418+
name: _('/node_modules/fatal-error/index.js'),
419+
contents: `
420+
import {Component} from '@angular/core';
421+
export class FatalError {}
422+
FatalError.decorators = [
423+
{type: Component, args: [{selector: 'fatal-error'}]}
424+
];
425+
`,
426+
},
427+
{
428+
name: _('/node_modules/fatal-error/index.d.ts'),
429+
contents: `
430+
export declare class FatalError {}
431+
`,
432+
},
433+
]);
434+
expect(() => mainNgcc({
435+
basePath: '/node_modules',
436+
targetEntryPointPath: 'fatal-error',
437+
propertiesToConsider: ['es2015']
438+
}))
439+
.toThrowError(
440+
/^Failed to compile entry-point fatal-error due to compilation errors:\nnode_modules\/fatal-error\/index\.js\(5,17\): error TS-992001: component is missing a template\r?\n$/);
441+
});
442+
});
443+
409444
describe('logger', () => {
410445
it('should log info message to the console by default', () => {
411446
const consoleInfoSpy = spyOn(console, 'info');

0 commit comments

Comments
 (0)