fix(ngcc): report diagnostics from migrations (#34014)
When ngcc is analyzing synthetically inserted decorators from a migration, it is typically not expected that any diagnostics are produced. In the situation where a diagnostic is produced, however, the diagnostic would not be reported at all. This commit ensures that diagnostics in migrations are reported. PR Close #34014
This commit is contained in:
parent
9fa2c398e7
commit
0f0fd25038
@ -160,7 +160,7 @@ export class DecorationAnalyzer {
|
|||||||
protected applyMigrations(analyzedFiles: AnalyzedFile[]): void {
|
protected applyMigrations(analyzedFiles: AnalyzedFile[]): void {
|
||||||
const migrationHost = new DefaultMigrationHost(
|
const migrationHost = new DefaultMigrationHost(
|
||||||
this.reflectionHost, this.fullMetaReader, this.evaluator, this.handlers,
|
this.reflectionHost, this.fullMetaReader, this.evaluator, this.handlers,
|
||||||
this.bundle.entryPoint.path, analyzedFiles);
|
this.bundle.entryPoint.path, analyzedFiles, this.diagnosticHandler);
|
||||||
|
|
||||||
this.migrations.forEach(migration => {
|
this.migrations.forEach(migration => {
|
||||||
analyzedFiles.forEach(analyzedFile => {
|
analyzedFiles.forEach(analyzedFile => {
|
||||||
|
@ -27,7 +27,8 @@ export class DefaultMigrationHost implements MigrationHost {
|
|||||||
constructor(
|
constructor(
|
||||||
readonly reflectionHost: NgccReflectionHost, readonly metadata: MetadataReader,
|
readonly reflectionHost: NgccReflectionHost, readonly metadata: MetadataReader,
|
||||||
readonly evaluator: PartialEvaluator, private handlers: DecoratorHandler<any, any>[],
|
readonly evaluator: PartialEvaluator, private handlers: DecoratorHandler<any, any>[],
|
||||||
private entryPointPath: AbsoluteFsPath, private analyzedFiles: AnalyzedFile[]) {}
|
private entryPointPath: AbsoluteFsPath, private analyzedFiles: AnalyzedFile[],
|
||||||
|
private diagnosticHandler: (error: ts.Diagnostic) => void) {}
|
||||||
|
|
||||||
injectSyntheticDecorator(clazz: ClassDeclaration, decorator: Decorator, flags?: HandlerFlags):
|
injectSyntheticDecorator(clazz: ClassDeclaration, decorator: Decorator, flags?: HandlerFlags):
|
||||||
void {
|
void {
|
||||||
@ -37,6 +38,12 @@ export class DefaultMigrationHost implements MigrationHost {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (newAnalyzedClass.diagnostics !== undefined) {
|
||||||
|
for (const diagnostic of newAnalyzedClass.diagnostics) {
|
||||||
|
this.diagnosticHandler(createMigrationDiagnostic(diagnostic, clazz, decorator));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const analyzedFile = getOrCreateAnalyzedFile(this.analyzedFiles, clazz.getSourceFile());
|
const analyzedFile = getOrCreateAnalyzedFile(this.analyzedFiles, clazz.getSourceFile());
|
||||||
const oldAnalyzedClass = analyzedFile.analyzedClasses.find(c => c.declaration === clazz);
|
const oldAnalyzedClass = analyzedFile.analyzedClasses.find(c => c.declaration === clazz);
|
||||||
if (oldAnalyzedClass === undefined) {
|
if (oldAnalyzedClass === undefined) {
|
||||||
@ -102,3 +109,43 @@ function mergeAnalyzedClasses(oldClass: AnalyzedClass, newClass: AnalyzedClass)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a diagnostic from another one, containing additional information about the synthetic
|
||||||
|
* decorator.
|
||||||
|
*/
|
||||||
|
function createMigrationDiagnostic(
|
||||||
|
diagnostic: ts.Diagnostic, source: ts.Node, decorator: Decorator): ts.Diagnostic {
|
||||||
|
const clone = {...diagnostic};
|
||||||
|
|
||||||
|
const chain: ts.DiagnosticMessageChain[] = [{
|
||||||
|
messageText: `Occurs for @${decorator.name} decorator inserted by an automatic migration`,
|
||||||
|
category: ts.DiagnosticCategory.Message,
|
||||||
|
code: 0,
|
||||||
|
}];
|
||||||
|
|
||||||
|
if (decorator.args !== null) {
|
||||||
|
const args = decorator.args.map(arg => arg.getText()).join(', ');
|
||||||
|
chain.push({
|
||||||
|
messageText: `@${decorator.name}(${args})`,
|
||||||
|
category: ts.DiagnosticCategory.Message,
|
||||||
|
code: 0,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof clone.messageText === 'string') {
|
||||||
|
clone.messageText = {
|
||||||
|
messageText: clone.messageText,
|
||||||
|
category: diagnostic.category,
|
||||||
|
code: diagnostic.code,
|
||||||
|
next: chain,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
if (clone.messageText.next === undefined) {
|
||||||
|
clone.messageText.next = chain;
|
||||||
|
} else {
|
||||||
|
clone.messageText.next.push(...chain);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return clone;
|
||||||
|
}
|
||||||
|
@ -5,7 +5,9 @@
|
|||||||
* Use of this source code is governed by an MIT-style license that can be
|
* Use of this source code is governed by an MIT-style license that can be
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
import {ErrorCode} from '../../../src/ngtsc/diagnostics';
|
import * as ts from 'typescript';
|
||||||
|
|
||||||
|
import {ErrorCode, makeDiagnostic} from '../../../src/ngtsc/diagnostics';
|
||||||
import {AbsoluteFsPath, absoluteFrom} from '../../../src/ngtsc/file_system';
|
import {AbsoluteFsPath, absoluteFrom} from '../../../src/ngtsc/file_system';
|
||||||
import {runInEachFileSystem} from '../../../src/ngtsc/file_system/testing';
|
import {runInEachFileSystem} from '../../../src/ngtsc/file_system/testing';
|
||||||
import {ClassDeclaration, Decorator} from '../../../src/ngtsc/reflection';
|
import {ClassDeclaration, Decorator} from '../../../src/ngtsc/reflection';
|
||||||
@ -13,6 +15,7 @@ import {AnalysisOutput, CompileResult, DecoratorHandler, DetectResult, HandlerPr
|
|||||||
import {DefaultMigrationHost} from '../../src/analysis/migration_host';
|
import {DefaultMigrationHost} from '../../src/analysis/migration_host';
|
||||||
import {AnalyzedClass, AnalyzedFile} from '../../src/analysis/types';
|
import {AnalyzedClass, AnalyzedFile} from '../../src/analysis/types';
|
||||||
import {NgccClassSymbol} from '../../src/host/ngcc_host';
|
import {NgccClassSymbol} from '../../src/host/ngcc_host';
|
||||||
|
import {createComponentDecorator} from '../../src/migrations/utils';
|
||||||
|
|
||||||
runInEachFileSystem(() => {
|
runInEachFileSystem(() => {
|
||||||
describe('DefaultMigrationHost', () => {
|
describe('DefaultMigrationHost', () => {
|
||||||
@ -23,6 +26,7 @@ runInEachFileSystem(() => {
|
|||||||
let mockEvaluator: any = {};
|
let mockEvaluator: any = {};
|
||||||
let mockClazz: any;
|
let mockClazz: any;
|
||||||
let mockDecorator: any = {name: 'MockDecorator'};
|
let mockDecorator: any = {name: 'MockDecorator'};
|
||||||
|
let diagnosticHandler = () => {};
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
_ = absoluteFrom;
|
_ = absoluteFrom;
|
||||||
entryPointPath = _('/node_modules/some-package/entry-point');
|
entryPointPath = _('/node_modules/some-package/entry-point');
|
||||||
@ -51,7 +55,8 @@ runInEachFileSystem(() => {
|
|||||||
const handler1 = new TestHandler('handler1', log);
|
const handler1 = new TestHandler('handler1', log);
|
||||||
const handler2 = new TestHandler('handler2', log);
|
const handler2 = new TestHandler('handler2', log);
|
||||||
const host = new DefaultMigrationHost(
|
const host = new DefaultMigrationHost(
|
||||||
mockHost, mockMetadata, mockEvaluator, [handler1, handler2], entryPointPath, []);
|
mockHost, mockMetadata, mockEvaluator, [handler1, handler2], entryPointPath, [],
|
||||||
|
diagnosticHandler);
|
||||||
host.injectSyntheticDecorator(mockClazz, mockDecorator);
|
host.injectSyntheticDecorator(mockClazz, mockDecorator);
|
||||||
expect(log).toEqual([
|
expect(log).toEqual([
|
||||||
`handler1:detect:MockClazz:MockDecorator`,
|
`handler1:detect:MockClazz:MockDecorator`,
|
||||||
@ -67,7 +72,7 @@ runInEachFileSystem(() => {
|
|||||||
const handler3 = new TestHandler('handler3', log);
|
const handler3 = new TestHandler('handler3', log);
|
||||||
const host = new DefaultMigrationHost(
|
const host = new DefaultMigrationHost(
|
||||||
mockHost, mockMetadata, mockEvaluator, [handler1, handler2, handler3],
|
mockHost, mockMetadata, mockEvaluator, [handler1, handler2, handler3],
|
||||||
entryPointPath, []);
|
entryPointPath, [], diagnosticHandler);
|
||||||
host.injectSyntheticDecorator(mockClazz, mockDecorator);
|
host.injectSyntheticDecorator(mockClazz, mockDecorator);
|
||||||
expect(log).toEqual([
|
expect(log).toEqual([
|
||||||
`handler1:detect:MockClazz:MockDecorator`,
|
`handler1:detect:MockClazz:MockDecorator`,
|
||||||
@ -82,7 +87,8 @@ runInEachFileSystem(() => {
|
|||||||
const handler = new AlwaysDetectHandler('handler', log);
|
const handler = new AlwaysDetectHandler('handler', log);
|
||||||
const analyzedFiles: AnalyzedFile[] = [];
|
const analyzedFiles: AnalyzedFile[] = [];
|
||||||
const host = new DefaultMigrationHost(
|
const host = new DefaultMigrationHost(
|
||||||
mockHost, mockMetadata, mockEvaluator, [handler], entryPointPath, analyzedFiles);
|
mockHost, mockMetadata, mockEvaluator, [handler], entryPointPath, analyzedFiles,
|
||||||
|
diagnosticHandler);
|
||||||
host.injectSyntheticDecorator(mockClazz, mockDecorator);
|
host.injectSyntheticDecorator(mockClazz, mockDecorator);
|
||||||
expect(analyzedFiles.length).toEqual(1);
|
expect(analyzedFiles.length).toEqual(1);
|
||||||
expect(analyzedFiles[0].analyzedClasses.length).toEqual(1);
|
expect(analyzedFiles[0].analyzedClasses.length).toEqual(1);
|
||||||
@ -99,7 +105,8 @@ runInEachFileSystem(() => {
|
|||||||
analyzedClasses: [DUMMY_CLASS_1, DUMMY_CLASS_2],
|
analyzedClasses: [DUMMY_CLASS_1, DUMMY_CLASS_2],
|
||||||
}];
|
}];
|
||||||
const host = new DefaultMigrationHost(
|
const host = new DefaultMigrationHost(
|
||||||
mockHost, mockMetadata, mockEvaluator, [handler], entryPointPath, analyzedFiles);
|
mockHost, mockMetadata, mockEvaluator, [handler], entryPointPath, analyzedFiles,
|
||||||
|
diagnosticHandler);
|
||||||
host.injectSyntheticDecorator(mockClazz, mockDecorator);
|
host.injectSyntheticDecorator(mockClazz, mockDecorator);
|
||||||
expect(analyzedFiles.length).toEqual(1);
|
expect(analyzedFiles.length).toEqual(1);
|
||||||
expect(analyzedFiles[0].analyzedClasses.length).toEqual(3);
|
expect(analyzedFiles[0].analyzedClasses.length).toEqual(3);
|
||||||
@ -120,7 +127,8 @@ runInEachFileSystem(() => {
|
|||||||
analyzedClasses: [analyzedClass],
|
analyzedClasses: [analyzedClass],
|
||||||
}];
|
}];
|
||||||
const host = new DefaultMigrationHost(
|
const host = new DefaultMigrationHost(
|
||||||
mockHost, mockMetadata, mockEvaluator, [handler], entryPointPath, analyzedFiles);
|
mockHost, mockMetadata, mockEvaluator, [handler], entryPointPath, analyzedFiles,
|
||||||
|
diagnosticHandler);
|
||||||
host.injectSyntheticDecorator(mockClazz, mockDecorator);
|
host.injectSyntheticDecorator(mockClazz, mockDecorator);
|
||||||
expect(analyzedFiles.length).toEqual(1);
|
expect(analyzedFiles.length).toEqual(1);
|
||||||
expect(analyzedFiles[0].analyzedClasses.length).toEqual(1);
|
expect(analyzedFiles[0].analyzedClasses.length).toEqual(1);
|
||||||
@ -144,7 +152,8 @@ runInEachFileSystem(() => {
|
|||||||
analyzedClasses: [analyzedClass],
|
analyzedClasses: [analyzedClass],
|
||||||
}];
|
}];
|
||||||
const host = new DefaultMigrationHost(
|
const host = new DefaultMigrationHost(
|
||||||
mockHost, mockMetadata, mockEvaluator, [handler], entryPointPath, analyzedFiles);
|
mockHost, mockMetadata, mockEvaluator, [handler], entryPointPath, analyzedFiles,
|
||||||
|
diagnosticHandler);
|
||||||
host.injectSyntheticDecorator(mockClazz, mockDecorator);
|
host.injectSyntheticDecorator(mockClazz, mockDecorator);
|
||||||
expect(analyzedFiles.length).toEqual(1);
|
expect(analyzedFiles.length).toEqual(1);
|
||||||
expect(analyzedFiles[0].analyzedClasses.length).toEqual(1);
|
expect(analyzedFiles[0].analyzedClasses.length).toEqual(1);
|
||||||
@ -167,11 +176,34 @@ runInEachFileSystem(() => {
|
|||||||
analyzedClasses: [analyzedClass],
|
analyzedClasses: [analyzedClass],
|
||||||
}];
|
}];
|
||||||
const host = new DefaultMigrationHost(
|
const host = new DefaultMigrationHost(
|
||||||
mockHost, mockMetadata, mockEvaluator, [handler], entryPointPath, analyzedFiles);
|
mockHost, mockMetadata, mockEvaluator, [handler], entryPointPath, analyzedFiles,
|
||||||
|
diagnosticHandler);
|
||||||
expect(() => host.injectSyntheticDecorator(mockClazz, mockDecorator))
|
expect(() => host.injectSyntheticDecorator(mockClazz, mockDecorator))
|
||||||
.toThrow(jasmine.objectContaining(
|
.toThrow(jasmine.objectContaining(
|
||||||
{code: ErrorCode.NGCC_MIGRATION_DECORATOR_INJECTION_ERROR}));
|
{code: ErrorCode.NGCC_MIGRATION_DECORATOR_INJECTION_ERROR}));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should report diagnostics from handlers', () => {
|
||||||
|
const log: string[] = [];
|
||||||
|
const handler = new DiagnosticProducingHandler('handler', log);
|
||||||
|
const analyzedFiles: AnalyzedFile[] = [];
|
||||||
|
const diagnostics: ts.Diagnostic[] = [];
|
||||||
|
const host = new DefaultMigrationHost(
|
||||||
|
mockHost, mockMetadata, mockEvaluator, [handler], entryPointPath, analyzedFiles,
|
||||||
|
diagnostic => diagnostics.push(diagnostic));
|
||||||
|
mockClazz.getStart = () => 0;
|
||||||
|
mockClazz.getWidth = () => 0;
|
||||||
|
|
||||||
|
const decorator = createComponentDecorator(mockClazz, {selector: 'comp', exportAs: null});
|
||||||
|
host.injectSyntheticDecorator(mockClazz, decorator);
|
||||||
|
|
||||||
|
expect(diagnostics.length).toBe(1);
|
||||||
|
expect(ts.flattenDiagnosticMessageText(diagnostics[0].messageText, '\n'))
|
||||||
|
.toEqual(
|
||||||
|
`test diagnostic\n` +
|
||||||
|
` Occurs for @Component decorator inserted by an automatic migration\n` +
|
||||||
|
` @Component({ template: "", selector: "comp" })`);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('getAllDecorators', () => {
|
describe('getAllDecorators', () => {
|
||||||
@ -180,7 +212,8 @@ runInEachFileSystem(() => {
|
|||||||
const handler = new AlwaysDetectHandler('handler', log);
|
const handler = new AlwaysDetectHandler('handler', log);
|
||||||
const analyzedFiles: AnalyzedFile[] = [];
|
const analyzedFiles: AnalyzedFile[] = [];
|
||||||
const host = new DefaultMigrationHost(
|
const host = new DefaultMigrationHost(
|
||||||
mockHost, mockMetadata, mockEvaluator, [handler], entryPointPath, analyzedFiles);
|
mockHost, mockMetadata, mockEvaluator, [handler], entryPointPath, analyzedFiles,
|
||||||
|
diagnosticHandler);
|
||||||
|
|
||||||
const decorators = host.getAllDecorators(mockClazz);
|
const decorators = host.getAllDecorators(mockClazz);
|
||||||
expect(decorators).toBeNull();
|
expect(decorators).toBeNull();
|
||||||
@ -191,7 +224,8 @@ runInEachFileSystem(() => {
|
|||||||
const handler = new AlwaysDetectHandler('handler', log);
|
const handler = new AlwaysDetectHandler('handler', log);
|
||||||
const analyzedFiles: AnalyzedFile[] = [];
|
const analyzedFiles: AnalyzedFile[] = [];
|
||||||
const host = new DefaultMigrationHost(
|
const host = new DefaultMigrationHost(
|
||||||
mockHost, mockMetadata, mockEvaluator, [handler], entryPointPath, analyzedFiles);
|
mockHost, mockMetadata, mockEvaluator, [handler], entryPointPath, analyzedFiles,
|
||||||
|
diagnosticHandler);
|
||||||
|
|
||||||
const sourceFile: any = {};
|
const sourceFile: any = {};
|
||||||
const unrelatedClass: any = {
|
const unrelatedClass: any = {
|
||||||
@ -218,7 +252,8 @@ runInEachFileSystem(() => {
|
|||||||
analyzedClasses: [analyzedClass],
|
analyzedClasses: [analyzedClass],
|
||||||
}];
|
}];
|
||||||
const host = new DefaultMigrationHost(
|
const host = new DefaultMigrationHost(
|
||||||
mockHost, mockMetadata, mockEvaluator, [handler], entryPointPath, analyzedFiles);
|
mockHost, mockMetadata, mockEvaluator, [handler], entryPointPath, analyzedFiles,
|
||||||
|
diagnosticHandler);
|
||||||
host.injectSyntheticDecorator(mockClazz, mockDecorator);
|
host.injectSyntheticDecorator(mockClazz, mockDecorator);
|
||||||
|
|
||||||
const decorators = host.getAllDecorators(mockClazz) !;
|
const decorators = host.getAllDecorators(mockClazz) !;
|
||||||
@ -232,7 +267,8 @@ runInEachFileSystem(() => {
|
|||||||
it('should be true for nodes within the entry-point', () => {
|
it('should be true for nodes within the entry-point', () => {
|
||||||
const analyzedFiles: AnalyzedFile[] = [];
|
const analyzedFiles: AnalyzedFile[] = [];
|
||||||
const host = new DefaultMigrationHost(
|
const host = new DefaultMigrationHost(
|
||||||
mockHost, mockMetadata, mockEvaluator, [], entryPointPath, analyzedFiles);
|
mockHost, mockMetadata, mockEvaluator, [], entryPointPath, analyzedFiles,
|
||||||
|
diagnosticHandler);
|
||||||
|
|
||||||
const sourceFile: any = {
|
const sourceFile: any = {
|
||||||
fileName: _('/node_modules/some-package/entry-point/relative.js'),
|
fileName: _('/node_modules/some-package/entry-point/relative.js'),
|
||||||
@ -246,7 +282,8 @@ runInEachFileSystem(() => {
|
|||||||
it('should be false for nodes outside the entry-point', () => {
|
it('should be false for nodes outside the entry-point', () => {
|
||||||
const analyzedFiles: AnalyzedFile[] = [];
|
const analyzedFiles: AnalyzedFile[] = [];
|
||||||
const host = new DefaultMigrationHost(
|
const host = new DefaultMigrationHost(
|
||||||
mockHost, mockMetadata, mockEvaluator, [], entryPointPath, analyzedFiles);
|
mockHost, mockMetadata, mockEvaluator, [], entryPointPath, analyzedFiles,
|
||||||
|
diagnosticHandler);
|
||||||
|
|
||||||
const sourceFile: any = {
|
const sourceFile: any = {
|
||||||
fileName: _('/node_modules/some-package/other-entry/index.js'),
|
fileName: _('/node_modules/some-package/other-entry/index.js'),
|
||||||
@ -284,3 +321,10 @@ class AlwaysDetectHandler extends TestHandler {
|
|||||||
return {trigger: node, metadata: {}};
|
return {trigger: node, metadata: {}};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class DiagnosticProducingHandler extends AlwaysDetectHandler {
|
||||||
|
analyze(node: ClassDeclaration): AnalysisOutput<any> {
|
||||||
|
super.analyze(node);
|
||||||
|
return {diagnostics: [makeDiagnostic(9999, node, 'test diagnostic')]};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user