refactor(compiler-cli): create diagnostics using `ts.DiagnosticRelatedInformation` (#37587)

Previously, an anonymous type was used for creating a diagnostic with related
information. The anonymous type would then be translated into the necessary
`ts.DiagnosticRelatedInformation` shape within `makeDiagnostic`. This commit
switches the `makeDiagnostic` signature over to taking `ts.DiagnosticRelatedInformation`
directly and introduces `makeRelatedInformation` to easily create such objects.
This is done to aid in making upcoming work more readable.

PR Close #37587
This commit is contained in:
JoostK 2020-06-12 22:21:34 +02:00 committed by Andrew Kushnir
parent 5103d908c8
commit d2fb552116
6 changed files with 35 additions and 38 deletions

View File

@ -8,7 +8,7 @@
import * as ts from 'typescript'; import * as ts from 'typescript';
import {ErrorCode, makeDiagnostic} from '../../diagnostics'; import {ErrorCode, makeDiagnostic, makeRelatedInformation} from '../../diagnostics';
import {Reference} from '../../imports'; import {Reference} from '../../imports';
import {InjectableClassRegistry, MetadataReader} from '../../metadata'; import {InjectableClassRegistry, MetadataReader} from '../../metadata';
import {PartialEvaluator} from '../../partial_evaluator'; import {PartialEvaluator} from '../../partial_evaluator';
@ -44,7 +44,7 @@ Either add the @Injectable() decorator to '${
provider.node.name provider.node.name
.text}', or configure a different provider (such as a provider with 'useFactory'). .text}', or configure a different provider (such as a provider with 'useFactory').
`, `,
[{node: provider.node, messageText: `'${provider.node.name.text}' is declared here.`}])); [makeRelatedInformation(provider.node, `'${provider.node.name.text}' is declared here.`)]));
} }
return diagnostics; return diagnostics;

View File

@ -9,7 +9,7 @@
import {compileInjector, compileNgModule, CUSTOM_ELEMENTS_SCHEMA, Expression, ExternalExpr, InvokeFunctionExpr, LiteralArrayExpr, LiteralExpr, NO_ERRORS_SCHEMA, R3Identifiers, R3InjectorMetadata, R3NgModuleMetadata, R3Reference, SchemaMetadata, Statement, STRING_TYPE, WrappedNodeExpr} from '@angular/compiler'; import {compileInjector, compileNgModule, CUSTOM_ELEMENTS_SCHEMA, Expression, ExternalExpr, InvokeFunctionExpr, LiteralArrayExpr, LiteralExpr, NO_ERRORS_SCHEMA, R3Identifiers, R3InjectorMetadata, R3NgModuleMetadata, R3Reference, SchemaMetadata, Statement, STRING_TYPE, WrappedNodeExpr} from '@angular/compiler';
import * as ts from 'typescript'; import * as ts from 'typescript';
import {ErrorCode, FatalDiagnosticError, makeDiagnostic} from '../../diagnostics'; import {ErrorCode, FatalDiagnosticError, makeDiagnostic, makeRelatedInformation} from '../../diagnostics';
import {DefaultImportRecorder, Reference, ReferenceEmitter} from '../../imports'; import {DefaultImportRecorder, Reference, ReferenceEmitter} from '../../imports';
import {InjectableClassRegistry, MetadataReader, MetadataRegistry} from '../../metadata'; import {InjectableClassRegistry, MetadataReader, MetadataRegistry} from '../../metadata';
import {PartialEvaluator, ResolvedValue, ResolvedValueArray} from '../../partial_evaluator'; import {PartialEvaluator, ResolvedValue, ResolvedValueArray} from '../../partial_evaluator';
@ -133,10 +133,8 @@ export class NgModuleDecoratorHandler implements
`Cannot declare '${ `Cannot declare '${
ref.node.name ref.node.name
.text}' in an NgModule as it's not a part of the current compilation.`, .text}' in an NgModule as it's not a part of the current compilation.`,
[{ [makeRelatedInformation(
node: ref.node.name, ref.node.name, `'${ref.node.name.text}' is declared here.`)]));
messageText: `'${ref.node.name.text}' is declared here.`,
}]));
} }
} }
} }

View File

@ -9,7 +9,7 @@
import {Expression, ExternalExpr, LiteralExpr, ParseLocation, ParseSourceFile, ParseSourceSpan, R3DependencyMetadata, R3Reference, R3ResolvedDependencyType, ReadPropExpr, WrappedNodeExpr} from '@angular/compiler'; import {Expression, ExternalExpr, LiteralExpr, ParseLocation, ParseSourceFile, ParseSourceSpan, R3DependencyMetadata, R3Reference, R3ResolvedDependencyType, ReadPropExpr, WrappedNodeExpr} from '@angular/compiler';
import * as ts from 'typescript'; import * as ts from 'typescript';
import {ErrorCode, FatalDiagnosticError, makeDiagnostic} from '../../diagnostics'; import {ErrorCode, FatalDiagnosticError, makeDiagnostic, makeRelatedInformation} from '../../diagnostics';
import {DefaultImportRecorder, ImportFlags, Reference, ReferenceEmitter} from '../../imports'; import {DefaultImportRecorder, ImportFlags, Reference, ReferenceEmitter} from '../../imports';
import {ForeignFunctionResolver, PartialEvaluator} from '../../partial_evaluator'; import {ForeignFunctionResolver, PartialEvaluator} from '../../partial_evaluator';
import {ClassDeclaration, CtorParameter, Decorator, Import, isNamedClassDeclaration, ReflectionHost, TypeValueReference} from '../../reflection'; import {ClassDeclaration, CtorParameter, Decorator, Import, isNamedClassDeclaration, ReflectionHost, TypeValueReference} from '../../reflection';
@ -408,7 +408,7 @@ export function wrapFunctionExpressionsInParens(expression: ts.Expression): ts.E
*/ */
export function makeDuplicateDeclarationError( export function makeDuplicateDeclarationError(
node: ClassDeclaration, data: DeclarationData[], kind: string): ts.Diagnostic { node: ClassDeclaration, data: DeclarationData[], kind: string): ts.Diagnostic {
const context: {node: ts.Node; messageText: string;}[] = []; const context: ts.DiagnosticRelatedInformation[] = [];
for (const decl of data) { for (const decl of data) {
if (decl.rawDeclarations === null) { if (decl.rawDeclarations === null) {
continue; continue;
@ -416,11 +416,10 @@ export function makeDuplicateDeclarationError(
// Try to find the reference to the declaration within the declarations array, to hang the // Try to find the reference to the declaration within the declarations array, to hang the
// error there. If it can't be found, fall back on using the NgModule's name. // error there. If it can't be found, fall back on using the NgModule's name.
const contextNode = decl.ref.getOriginForDiagnostics(decl.rawDeclarations, decl.ngModule.name); const contextNode = decl.ref.getOriginForDiagnostics(decl.rawDeclarations, decl.ngModule.name);
context.push({ context.push(makeRelatedInformation(
node: contextNode, contextNode,
messageText: `'${node.name.text}' is listed in the declarations of the NgModule '${ `'${node.name.text}' is listed in the declarations of the NgModule '${
decl.ngModule.name.text}'.`, decl.ngModule.name.text}'.`));
});
} }
// Finally, produce the diagnostic. // Finally, produce the diagnostic.

View File

@ -6,6 +6,6 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
export {FatalDiagnosticError, isFatalDiagnosticError, makeDiagnostic} from './src/error'; export {FatalDiagnosticError, isFatalDiagnosticError, makeDiagnostic, makeRelatedInformation} from './src/error';
export {ErrorCode, ngErrorCode} from './src/error_code'; export {ErrorCode, ngErrorCode} from './src/error_code';
export {replaceTsWithNgInErrors} from './src/util'; export {replaceTsWithNgInErrors} from './src/util';

View File

@ -23,33 +23,32 @@ export class FatalDiagnosticError {
} }
} }
export function makeDiagnostic(code: ErrorCode, node: ts.Node, messageText: string, relatedInfo?: { export function makeDiagnostic(
node: ts.Node, code: ErrorCode, node: ts.Node, messageText: string,
messageText: string, relatedInformation?: ts.DiagnosticRelatedInformation[]): ts.DiagnosticWithLocation {
}[]): ts.DiagnosticWithLocation {
node = ts.getOriginalNode(node); node = ts.getOriginalNode(node);
const diag: ts.DiagnosticWithLocation = { return {
category: ts.DiagnosticCategory.Error, category: ts.DiagnosticCategory.Error,
code: Number('-99' + code.valueOf()), code: Number('-99' + code.valueOf()),
file: ts.getOriginalNode(node).getSourceFile(), file: ts.getOriginalNode(node).getSourceFile(),
start: node.getStart(undefined, false), start: node.getStart(undefined, false),
length: node.getWidth(), length: node.getWidth(),
messageText, messageText,
relatedInformation,
};
}
export function makeRelatedInformation(
node: ts.Node, messageText: string): ts.DiagnosticRelatedInformation {
node = ts.getOriginalNode(node);
return {
category: ts.DiagnosticCategory.Message,
code: 0,
file: node.getSourceFile(),
start: node.getStart(),
length: node.getWidth(),
messageText,
}; };
if (relatedInfo !== undefined) {
diag.relatedInformation = relatedInfo.map(info => {
const infoNode = ts.getOriginalNode(info.node);
return {
category: ts.DiagnosticCategory.Message,
code: 0,
file: infoNode.getSourceFile(),
start: infoNode.getStart(),
length: infoNode.getWidth(),
messageText: info.messageText,
};
});
}
return diag;
} }
export function isFatalDiagnosticError(err: any): err is FatalDiagnosticError { export function isFatalDiagnosticError(err: any): err is FatalDiagnosticError {

View File

@ -9,7 +9,7 @@
import {ExternalExpr, SchemaMetadata} from '@angular/compiler'; import {ExternalExpr, SchemaMetadata} from '@angular/compiler';
import * as ts from 'typescript'; import * as ts from 'typescript';
import {ErrorCode, makeDiagnostic} from '../../diagnostics'; import {ErrorCode, makeDiagnostic, makeRelatedInformation} from '../../diagnostics';
import {AliasingHost, Reexport, Reference, ReferenceEmitter} from '../../imports'; import {AliasingHost, Reexport, Reference, ReferenceEmitter} from '../../imports';
import {DirectiveMeta, MetadataReader, MetadataRegistry, NgModuleMeta, PipeMeta} from '../../metadata'; import {DirectiveMeta, MetadataReader, MetadataRegistry, NgModuleMeta, PipeMeta} from '../../metadata';
import {ClassDeclaration} from '../../reflection'; import {ClassDeclaration} from '../../reflection';
@ -358,7 +358,8 @@ export class LocalModuleScopeRegistry implements MetadataRegistry, ComponentScop
ngModule.ref.node.name ngModule.ref.node.name
.text}', but is not a directive, a component, or a pipe. ` + .text}', but is not a directive, a component, or a pipe. ` +
`Either remove it from the NgModule's declarations, or add an appropriate Angular decorator.`, `Either remove it from the NgModule's declarations, or add an appropriate Angular decorator.`,
[{node: decl.node.name, messageText: `'${decl.node.name.text}' is declared here.`}])); [makeRelatedInformation(
decl.node.name, `'${decl.node.name.text}' is declared here.`)]));
continue; continue;
} }
@ -643,8 +644,8 @@ function reexportCollision(
To fix this problem please re-export one or both classes directly from this file. To fix this problem please re-export one or both classes directly from this file.
`.trim(), `.trim(),
[ [
{node: refA.node.name, messageText: childMessageText}, makeRelatedInformation(refA.node.name, childMessageText),
{node: refB.node.name, messageText: childMessageText}, makeRelatedInformation(refB.node.name, childMessageText),
]); ]);
} }