feat(ivy): output diagnostics for many errors in ngtsc (#25647)
This commit takes the first steps towards ngtsc producing real TypeScript diagnostics instead of simply throwing errors when encountering incorrect code. A new class is introduced, FatalDiagnosticError, which can be thrown by handlers whenever a condition in the code is encountered which by necessity prevents the class from being compiled. This error type is convertable to a ts.Diagnostic which represents the type and source of the error. Error codes are introduced for Angular errors, and are prefixed with -99 (so error code 1001 becomes -991001) to distinguish them from other TS errors. A function is provided which will read TS diagnostic output and convert the TS errors to NG errors if they match this negative error code format. PR Close #25647
This commit is contained in:
parent
b424b3187e
commit
38f624d7e3
|
@ -116,7 +116,7 @@ export interface Program {
|
||||||
getTsSemanticDiagnostics(sourceFile?: ts.SourceFile, cancellationToken?: ts.CancellationToken):
|
getTsSemanticDiagnostics(sourceFile?: ts.SourceFile, cancellationToken?: ts.CancellationToken):
|
||||||
ReadonlyArray<ts.Diagnostic>;
|
ReadonlyArray<ts.Diagnostic>;
|
||||||
getNgSemanticDiagnostics(fileName?: string, cancellationToken?: ts.CancellationToken):
|
getNgSemanticDiagnostics(fileName?: string, cancellationToken?: ts.CancellationToken):
|
||||||
ReadonlyArray<Diagnostic>;
|
ReadonlyArray<ts.Diagnostic|Diagnostic>;
|
||||||
loadNgStructureAsync(): Promise<void>;
|
loadNgStructureAsync(): Promise<void>;
|
||||||
listLazyRoutes(entryRoute?: string): LazyRoute[];
|
listLazyRoutes(entryRoute?: string): LazyRoute[];
|
||||||
emit({emitFlags, cancellationToken, customTransformers, emitCallback}: {
|
emit({emitFlags, cancellationToken, customTransformers, emitCallback}: {
|
||||||
|
|
|
@ -11,6 +11,7 @@ ts_library(
|
||||||
module_name = "@angular/compiler-cli/src/ngtsc/annotations",
|
module_name = "@angular/compiler-cli/src/ngtsc/annotations",
|
||||||
deps = [
|
deps = [
|
||||||
"//packages/compiler",
|
"//packages/compiler",
|
||||||
|
"//packages/compiler-cli/src/ngtsc/diagnostics",
|
||||||
"//packages/compiler-cli/src/ngtsc/host",
|
"//packages/compiler-cli/src/ngtsc/host",
|
||||||
"//packages/compiler-cli/src/ngtsc/metadata",
|
"//packages/compiler-cli/src/ngtsc/metadata",
|
||||||
"//packages/compiler-cli/src/ngtsc/transform",
|
"//packages/compiler-cli/src/ngtsc/transform",
|
||||||
|
|
|
@ -10,6 +10,7 @@ import {ConstantPool, Expression, R3ComponentMetadata, R3DirectiveMetadata, Wrap
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
|
|
||||||
|
import {ErrorCode, FatalDiagnosticError} from '../../diagnostics';
|
||||||
import {Decorator, ReflectionHost} from '../../host';
|
import {Decorator, ReflectionHost} from '../../host';
|
||||||
import {filterToMembersWithDecorator, reflectObjectLiteral, staticallyResolve} from '../../metadata';
|
import {filterToMembersWithDecorator, reflectObjectLiteral, staticallyResolve} from '../../metadata';
|
||||||
import {AnalysisOutput, CompileResult, DecoratorHandler} from '../../transform';
|
import {AnalysisOutput, CompileResult, DecoratorHandler} from '../../transform';
|
||||||
|
@ -46,10 +47,11 @@ export class ComponentDecoratorHandler implements DecoratorHandler<R3ComponentMe
|
||||||
const component = reflectObjectLiteral(meta);
|
const component = reflectObjectLiteral(meta);
|
||||||
|
|
||||||
if (this.resourceLoader.preload !== undefined && component.has('templateUrl')) {
|
if (this.resourceLoader.preload !== undefined && component.has('templateUrl')) {
|
||||||
const templateUrl =
|
const templateUrlExpr = component.get('templateUrl') !;
|
||||||
staticallyResolve(component.get('templateUrl') !, this.reflector, this.checker);
|
const templateUrl = staticallyResolve(templateUrlExpr, this.reflector, this.checker);
|
||||||
if (typeof templateUrl !== 'string') {
|
if (typeof templateUrl !== 'string') {
|
||||||
throw new Error(`templateUrl should be a string`);
|
throw new FatalDiagnosticError(
|
||||||
|
ErrorCode.VALUE_HAS_WRONG_TYPE, templateUrlExpr, 'templateUrl must be a string');
|
||||||
}
|
}
|
||||||
const url = path.posix.resolve(path.dirname(node.getSourceFile().fileName), templateUrl);
|
const url = path.posix.resolve(path.dirname(node.getSourceFile().fileName), templateUrl);
|
||||||
return this.resourceLoader.preload(url);
|
return this.resourceLoader.preload(url);
|
||||||
|
@ -77,10 +79,11 @@ export class ComponentDecoratorHandler implements DecoratorHandler<R3ComponentMe
|
||||||
|
|
||||||
let templateStr: string|null = null;
|
let templateStr: string|null = null;
|
||||||
if (component.has('templateUrl')) {
|
if (component.has('templateUrl')) {
|
||||||
const templateUrl =
|
const templateUrlExpr = component.get('templateUrl') !;
|
||||||
staticallyResolve(component.get('templateUrl') !, this.reflector, this.checker);
|
const templateUrl = staticallyResolve(templateUrlExpr, this.reflector, this.checker);
|
||||||
if (typeof templateUrl !== 'string') {
|
if (typeof templateUrl !== 'string') {
|
||||||
throw new Error(`templateUrl should be a string`);
|
throw new FatalDiagnosticError(
|
||||||
|
ErrorCode.VALUE_HAS_WRONG_TYPE, templateUrlExpr, 'templateUrl must be a string');
|
||||||
}
|
}
|
||||||
const url = path.posix.resolve(path.dirname(node.getSourceFile().fileName), templateUrl);
|
const url = path.posix.resolve(path.dirname(node.getSourceFile().fileName), templateUrl);
|
||||||
templateStr = this.resourceLoader.load(url);
|
templateStr = this.resourceLoader.load(url);
|
||||||
|
@ -88,19 +91,22 @@ export class ComponentDecoratorHandler implements DecoratorHandler<R3ComponentMe
|
||||||
const templateExpr = component.get('template') !;
|
const templateExpr = component.get('template') !;
|
||||||
const resolvedTemplate = staticallyResolve(templateExpr, this.reflector, this.checker);
|
const resolvedTemplate = staticallyResolve(templateExpr, this.reflector, this.checker);
|
||||||
if (typeof resolvedTemplate !== 'string') {
|
if (typeof resolvedTemplate !== 'string') {
|
||||||
throw new Error(`Template must statically resolve to a string: ${node.name!.text}`);
|
throw new FatalDiagnosticError(
|
||||||
|
ErrorCode.VALUE_HAS_WRONG_TYPE, templateExpr, 'template must be a string');
|
||||||
}
|
}
|
||||||
templateStr = resolvedTemplate;
|
templateStr = resolvedTemplate;
|
||||||
} else {
|
} else {
|
||||||
throw new Error(`Component has no template or templateUrl`);
|
throw new FatalDiagnosticError(
|
||||||
|
ErrorCode.COMPONENT_MISSING_TEMPLATE, decorator.node, 'component is missing a template');
|
||||||
}
|
}
|
||||||
|
|
||||||
let preserveWhitespaces: boolean = false;
|
let preserveWhitespaces: boolean = false;
|
||||||
if (component.has('preserveWhitespaces')) {
|
if (component.has('preserveWhitespaces')) {
|
||||||
const value =
|
const expr = component.get('preserveWhitespaces') !;
|
||||||
staticallyResolve(component.get('preserveWhitespaces') !, this.reflector, this.checker);
|
const value = staticallyResolve(expr, this.reflector, this.checker);
|
||||||
if (typeof value !== 'boolean') {
|
if (typeof value !== 'boolean') {
|
||||||
throw new Error(`preserveWhitespaces must resolve to a boolean if present`);
|
throw new FatalDiagnosticError(
|
||||||
|
ErrorCode.VALUE_HAS_WRONG_TYPE, expr, 'preserveWhitespaces must be a boolean');
|
||||||
}
|
}
|
||||||
preserveWhitespaces = value;
|
preserveWhitespaces = value;
|
||||||
}
|
}
|
||||||
|
@ -191,12 +197,15 @@ export class ComponentDecoratorHandler implements DecoratorHandler<R3ComponentMe
|
||||||
return this.literalCache.get(decorator) !;
|
return this.literalCache.get(decorator) !;
|
||||||
}
|
}
|
||||||
if (decorator.args === null || decorator.args.length !== 1) {
|
if (decorator.args === null || decorator.args.length !== 1) {
|
||||||
throw new Error(`Incorrect number of arguments to @Component decorator`);
|
throw new FatalDiagnosticError(
|
||||||
|
ErrorCode.DECORATOR_ARITY_WRONG, decorator.node,
|
||||||
|
`Incorrect number of arguments to @Component decorator`);
|
||||||
}
|
}
|
||||||
const meta = unwrapExpression(decorator.args[0]);
|
const meta = unwrapExpression(decorator.args[0]);
|
||||||
|
|
||||||
if (!ts.isObjectLiteralExpression(meta)) {
|
if (!ts.isObjectLiteralExpression(meta)) {
|
||||||
throw new Error(`Decorator argument must be literal.`);
|
throw new FatalDiagnosticError(
|
||||||
|
ErrorCode.DECORATOR_ARG_NOT_LITERAL, meta, `Decorator argument must be literal.`);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.literalCache.set(decorator, meta);
|
this.literalCache.set(decorator, meta);
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
import {ConstantPool, Expression, R3DirectiveMetadata, R3QueryMetadata, WrappedNodeExpr, compileDirectiveFromMetadata, makeBindingParser, parseHostBindings} from '@angular/compiler';
|
import {ConstantPool, Expression, R3DirectiveMetadata, R3QueryMetadata, WrappedNodeExpr, compileDirectiveFromMetadata, makeBindingParser, parseHostBindings} from '@angular/compiler';
|
||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
|
|
||||||
|
import {ErrorCode, FatalDiagnosticError} from '../../diagnostics';
|
||||||
import {ClassMember, ClassMemberKind, Decorator, Import, ReflectionHost} from '../../host';
|
import {ClassMember, ClassMemberKind, Decorator, Import, ReflectionHost} from '../../host';
|
||||||
import {Reference, filterToMembersWithDecorator, reflectObjectLiteral, staticallyResolve} from '../../metadata';
|
import {Reference, filterToMembersWithDecorator, reflectObjectLiteral, staticallyResolve} from '../../metadata';
|
||||||
import {AnalysisOutput, CompileResult, DecoratorHandler} from '../../transform';
|
import {AnalysisOutput, CompileResult, DecoratorHandler} from '../../transform';
|
||||||
|
@ -68,11 +69,14 @@ export function extractDirectiveMetadata(
|
||||||
decoratedElements: ClassMember[],
|
decoratedElements: ClassMember[],
|
||||||
}|undefined {
|
}|undefined {
|
||||||
if (decorator.args === null || decorator.args.length !== 1) {
|
if (decorator.args === null || decorator.args.length !== 1) {
|
||||||
throw new Error(`Incorrect number of arguments to @${decorator.name} decorator`);
|
throw new FatalDiagnosticError(
|
||||||
|
ErrorCode.DECORATOR_ARITY_WRONG, decorator.node,
|
||||||
|
`Incorrect number of arguments to @${decorator.name} decorator`);
|
||||||
}
|
}
|
||||||
const meta = unwrapExpression(decorator.args[0]);
|
const meta = unwrapExpression(decorator.args[0]);
|
||||||
if (!ts.isObjectLiteralExpression(meta)) {
|
if (!ts.isObjectLiteralExpression(meta)) {
|
||||||
throw new Error(`Decorator argument must be literal.`);
|
throw new FatalDiagnosticError(
|
||||||
|
ErrorCode.DECORATOR_ARG_NOT_LITERAL, meta, `@${decorator.name} argument must be literal.`);
|
||||||
}
|
}
|
||||||
const directive = reflectObjectLiteral(meta);
|
const directive = reflectObjectLiteral(meta);
|
||||||
|
|
||||||
|
@ -120,9 +124,11 @@ export function extractDirectiveMetadata(
|
||||||
// Parse the selector.
|
// Parse the selector.
|
||||||
let selector = '';
|
let selector = '';
|
||||||
if (directive.has('selector')) {
|
if (directive.has('selector')) {
|
||||||
const resolved = staticallyResolve(directive.get('selector') !, reflector, checker);
|
const expr = directive.get('selector') !;
|
||||||
|
const resolved = staticallyResolve(expr, reflector, checker);
|
||||||
if (typeof resolved !== 'string') {
|
if (typeof resolved !== 'string') {
|
||||||
throw new Error(`Selector must be a string`);
|
throw new FatalDiagnosticError(
|
||||||
|
ErrorCode.VALUE_HAS_WRONG_TYPE, expr, `selector must be a string`);
|
||||||
}
|
}
|
||||||
selector = resolved;
|
selector = resolved;
|
||||||
}
|
}
|
||||||
|
@ -137,9 +143,11 @@ export function extractDirectiveMetadata(
|
||||||
// Parse exportAs.
|
// Parse exportAs.
|
||||||
let exportAs: string|null = null;
|
let exportAs: string|null = null;
|
||||||
if (directive.has('exportAs')) {
|
if (directive.has('exportAs')) {
|
||||||
const resolved = staticallyResolve(directive.get('exportAs') !, reflector, checker);
|
const expr = directive.get('exportAs') !;
|
||||||
|
const resolved = staticallyResolve(expr, reflector, checker);
|
||||||
if (typeof resolved !== 'string') {
|
if (typeof resolved !== 'string') {
|
||||||
throw new Error(`exportAs must be a string`);
|
throw new FatalDiagnosticError(
|
||||||
|
ErrorCode.VALUE_HAS_WRONG_TYPE, expr, `exportAs must be a string`);
|
||||||
}
|
}
|
||||||
exportAs = resolved;
|
exportAs = resolved;
|
||||||
}
|
}
|
||||||
|
@ -163,10 +171,11 @@ export function extractDirectiveMetadata(
|
||||||
}
|
}
|
||||||
|
|
||||||
export function extractQueryMetadata(
|
export function extractQueryMetadata(
|
||||||
name: string, args: ReadonlyArray<ts.Expression>, propertyName: string,
|
exprNode: ts.Node, name: string, args: ReadonlyArray<ts.Expression>, propertyName: string,
|
||||||
reflector: ReflectionHost, checker: ts.TypeChecker): R3QueryMetadata {
|
reflector: ReflectionHost, checker: ts.TypeChecker): R3QueryMetadata {
|
||||||
if (args.length === 0) {
|
if (args.length === 0) {
|
||||||
throw new Error(`@${name} must have arguments`);
|
throw new FatalDiagnosticError(
|
||||||
|
ErrorCode.DECORATOR_ARITY_WRONG, exprNode, `@${name} must have arguments`);
|
||||||
}
|
}
|
||||||
const first = name === 'ViewChild' || name === 'ContentChild';
|
const first = name === 'ViewChild' || name === 'ContentChild';
|
||||||
const node = unwrapForwardRef(args[0], reflector);
|
const node = unwrapForwardRef(args[0], reflector);
|
||||||
|
@ -181,7 +190,8 @@ export function extractQueryMetadata(
|
||||||
} else if (isStringArrayOrDie(arg, '@' + name)) {
|
} else if (isStringArrayOrDie(arg, '@' + name)) {
|
||||||
predicate = arg as string[];
|
predicate = arg as string[];
|
||||||
} else {
|
} else {
|
||||||
throw new Error(`@${name} predicate cannot be interpreted`);
|
throw new FatalDiagnosticError(
|
||||||
|
ErrorCode.VALUE_HAS_WRONG_TYPE, node, `@${name} predicate cannot be interpreted`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extract the read and descendants options.
|
// Extract the read and descendants options.
|
||||||
|
@ -238,7 +248,7 @@ export function extractQueriesFromDecorator(
|
||||||
}
|
}
|
||||||
|
|
||||||
const query = extractQueryMetadata(
|
const query = extractQueryMetadata(
|
||||||
type.name, queryExpr.arguments || [], propertyName, reflector, checker);
|
queryExpr, type.name, queryExpr.arguments || [], propertyName, reflector, checker);
|
||||||
if (type.name.startsWith('Content')) {
|
if (type.name.startsWith('Content')) {
|
||||||
content.push(query);
|
content.push(query);
|
||||||
} else {
|
} else {
|
||||||
|
@ -343,7 +353,7 @@ export function queriesFromFields(
|
||||||
}
|
}
|
||||||
const decorator = decorators[0];
|
const decorator = decorators[0];
|
||||||
return extractQueryMetadata(
|
return extractQueryMetadata(
|
||||||
decorator.name, decorator.args || [], member.name, reflector, checker);
|
decorator.node, decorator.name, decorator.args || [], member.name, reflector, checker);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -365,9 +375,11 @@ function extractHostBindings(
|
||||||
} {
|
} {
|
||||||
let hostMetadata: StringMap = {};
|
let hostMetadata: StringMap = {};
|
||||||
if (metadata.has('host')) {
|
if (metadata.has('host')) {
|
||||||
const hostMetaMap = staticallyResolve(metadata.get('host') !, reflector, checker);
|
const expr = metadata.get('host') !;
|
||||||
|
const hostMetaMap = staticallyResolve(expr, reflector, checker);
|
||||||
if (!(hostMetaMap instanceof Map)) {
|
if (!(hostMetaMap instanceof Map)) {
|
||||||
throw new Error(`Decorator host metadata must be an object`);
|
throw new FatalDiagnosticError(
|
||||||
|
ErrorCode.DECORATOR_ARG_NOT_LITERAL, expr, `Decorator host metadata must be an object`);
|
||||||
}
|
}
|
||||||
hostMetaMap.forEach((value, key) => {
|
hostMetaMap.forEach((value, key) => {
|
||||||
if (typeof value !== 'string' || typeof key !== 'string') {
|
if (typeof value !== 'string' || typeof key !== 'string') {
|
||||||
|
@ -407,12 +419,16 @@ function extractHostBindings(
|
||||||
let args: string[] = [];
|
let args: string[] = [];
|
||||||
if (decorator.args !== null && decorator.args.length > 0) {
|
if (decorator.args !== null && decorator.args.length > 0) {
|
||||||
if (decorator.args.length > 2) {
|
if (decorator.args.length > 2) {
|
||||||
throw new Error(`@HostListener() can have at most two arguments`);
|
throw new FatalDiagnosticError(
|
||||||
|
ErrorCode.DECORATOR_ARITY_WRONG, decorator.args[2],
|
||||||
|
`@HostListener() can have at most two arguments`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const resolved = staticallyResolve(decorator.args[0], reflector, checker);
|
const resolved = staticallyResolve(decorator.args[0], reflector, checker);
|
||||||
if (typeof resolved !== 'string') {
|
if (typeof resolved !== 'string') {
|
||||||
throw new Error(`@HostListener()'s event name argument must be a string`);
|
throw new FatalDiagnosticError(
|
||||||
|
ErrorCode.VALUE_HAS_WRONG_TYPE, decorator.args[0],
|
||||||
|
`@HostListener()'s event name argument must be a string`);
|
||||||
}
|
}
|
||||||
|
|
||||||
eventName = resolved;
|
eventName = resolved;
|
||||||
|
@ -420,7 +436,9 @@ function extractHostBindings(
|
||||||
if (decorator.args.length === 2) {
|
if (decorator.args.length === 2) {
|
||||||
const resolvedArgs = staticallyResolve(decorator.args[1], reflector, checker);
|
const resolvedArgs = staticallyResolve(decorator.args[1], reflector, checker);
|
||||||
if (!isStringArrayOrDie(resolvedArgs, '@HostListener.args')) {
|
if (!isStringArrayOrDie(resolvedArgs, '@HostListener.args')) {
|
||||||
throw new Error(`@HostListener second argument must be a string array`);
|
throw new FatalDiagnosticError(
|
||||||
|
ErrorCode.VALUE_HAS_WRONG_TYPE, decorator.args[1],
|
||||||
|
`@HostListener second argument must be a string array`);
|
||||||
}
|
}
|
||||||
args = resolvedArgs;
|
args = resolvedArgs;
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
import {Expression, LiteralExpr, R3DependencyMetadata, R3InjectableMetadata, R3ResolvedDependencyType, WrappedNodeExpr, compileInjectable as compileIvyInjectable} from '@angular/compiler';
|
import {Expression, LiteralExpr, R3DependencyMetadata, R3InjectableMetadata, R3ResolvedDependencyType, WrappedNodeExpr, compileInjectable as compileIvyInjectable} from '@angular/compiler';
|
||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
|
|
||||||
|
import {ErrorCode, FatalDiagnosticError} from '../../diagnostics';
|
||||||
import {Decorator, ReflectionHost} from '../../host';
|
import {Decorator, ReflectionHost} from '../../host';
|
||||||
import {reflectObjectLiteral} from '../../metadata';
|
import {reflectObjectLiteral} from '../../metadata';
|
||||||
import {AnalysisOutput, CompileResult, DecoratorHandler} from '../../transform';
|
import {AnalysisOutput, CompileResult, DecoratorHandler} from '../../transform';
|
||||||
|
@ -56,13 +57,15 @@ function extractInjectableMetadata(
|
||||||
clazz: ts.ClassDeclaration, decorator: Decorator, reflector: ReflectionHost,
|
clazz: ts.ClassDeclaration, decorator: Decorator, reflector: ReflectionHost,
|
||||||
isCore: boolean): R3InjectableMetadata {
|
isCore: boolean): R3InjectableMetadata {
|
||||||
if (clazz.name === undefined) {
|
if (clazz.name === undefined) {
|
||||||
throw new Error(`@Injectables must have names`);
|
throw new FatalDiagnosticError(
|
||||||
|
ErrorCode.DECORATOR_ON_ANONYMOUS_CLASS, decorator.node, `@Injectable on anonymous class`);
|
||||||
}
|
}
|
||||||
const name = clazz.name.text;
|
const name = clazz.name.text;
|
||||||
const type = new WrappedNodeExpr(clazz.name);
|
const type = new WrappedNodeExpr(clazz.name);
|
||||||
const ctorDeps = getConstructorDependencies(clazz, reflector, isCore);
|
const ctorDeps = getConstructorDependencies(clazz, reflector, isCore);
|
||||||
if (decorator.args === null) {
|
if (decorator.args === null) {
|
||||||
throw new Error(`@Injectable must be called`);
|
throw new FatalDiagnosticError(
|
||||||
|
ErrorCode.DECORATOR_NOT_CALLED, decorator.node, '@Injectable must be called');
|
||||||
}
|
}
|
||||||
if (decorator.args.length === 0) {
|
if (decorator.args.length === 0) {
|
||||||
return {
|
return {
|
||||||
|
@ -90,7 +93,9 @@ function extractInjectableMetadata(
|
||||||
if ((meta.has('useClass') || meta.has('useFactory')) && meta.has('deps')) {
|
if ((meta.has('useClass') || meta.has('useFactory')) && meta.has('deps')) {
|
||||||
const depsExpr = meta.get('deps') !;
|
const depsExpr = meta.get('deps') !;
|
||||||
if (!ts.isArrayLiteralExpression(depsExpr)) {
|
if (!ts.isArrayLiteralExpression(depsExpr)) {
|
||||||
throw new Error(`In Ivy, deps metadata must be inline.`);
|
throw new FatalDiagnosticError(
|
||||||
|
ErrorCode.VALUE_NOT_LITERAL, depsExpr,
|
||||||
|
`In Ivy, deps metadata must be an inline array.`);
|
||||||
}
|
}
|
||||||
if (depsExpr.elements.length > 0) {
|
if (depsExpr.elements.length > 0) {
|
||||||
throw new Error(`deps not yet supported`);
|
throw new Error(`deps not yet supported`);
|
||||||
|
@ -130,7 +135,8 @@ function extractInjectableMetadata(
|
||||||
return {name, type, providedIn, ctorDeps};
|
return {name, type, providedIn, ctorDeps};
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
throw new Error(`Too many arguments to @Injectable`);
|
throw new FatalDiagnosticError(
|
||||||
|
ErrorCode.DECORATOR_ARITY_WRONG, decorator.args[2], 'Too many arguments to @Injectable');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
import {ConstantPool, Expression, LiteralArrayExpr, R3DirectiveMetadata, R3InjectorMetadata, R3NgModuleMetadata, WrappedNodeExpr, compileInjector, compileNgModule, makeBindingParser, parseTemplate} from '@angular/compiler';
|
import {ConstantPool, Expression, LiteralArrayExpr, R3DirectiveMetadata, R3InjectorMetadata, R3NgModuleMetadata, WrappedNodeExpr, compileInjector, compileNgModule, makeBindingParser, parseTemplate} from '@angular/compiler';
|
||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
|
|
||||||
|
import {ErrorCode, FatalDiagnosticError} from '../../diagnostics';
|
||||||
import {Decorator, ReflectionHost} from '../../host';
|
import {Decorator, ReflectionHost} from '../../host';
|
||||||
import {Reference, ResolvedValue, reflectObjectLiteral, staticallyResolve} from '../../metadata';
|
import {Reference, ResolvedValue, reflectObjectLiteral, staticallyResolve} from '../../metadata';
|
||||||
import {AnalysisOutput, CompileResult, DecoratorHandler} from '../../transform';
|
import {AnalysisOutput, CompileResult, DecoratorHandler} from '../../transform';
|
||||||
|
@ -41,7 +42,9 @@ export class NgModuleDecoratorHandler implements DecoratorHandler<NgModuleAnalys
|
||||||
|
|
||||||
analyze(node: ts.ClassDeclaration, decorator: Decorator): AnalysisOutput<NgModuleAnalysis> {
|
analyze(node: ts.ClassDeclaration, decorator: Decorator): AnalysisOutput<NgModuleAnalysis> {
|
||||||
if (decorator.args === null || decorator.args.length > 1) {
|
if (decorator.args === null || decorator.args.length > 1) {
|
||||||
throw new Error(`Incorrect number of arguments to @NgModule decorator`);
|
throw new FatalDiagnosticError(
|
||||||
|
ErrorCode.DECORATOR_ARITY_WRONG, decorator.node,
|
||||||
|
`Incorrect number of arguments to @NgModule decorator`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// @NgModule can be invoked without arguments. In case it is, pretend as if a blank object
|
// @NgModule can be invoked without arguments. In case it is, pretend as if a blank object
|
||||||
|
@ -50,7 +53,9 @@ export class NgModuleDecoratorHandler implements DecoratorHandler<NgModuleAnalys
|
||||||
ts.createObjectLiteral([]);
|
ts.createObjectLiteral([]);
|
||||||
|
|
||||||
if (!ts.isObjectLiteralExpression(meta)) {
|
if (!ts.isObjectLiteralExpression(meta)) {
|
||||||
throw new Error(`Decorator argument must be literal.`);
|
throw new FatalDiagnosticError(
|
||||||
|
ErrorCode.DECORATOR_ARG_NOT_LITERAL, meta,
|
||||||
|
'@NgModule argument must be an object literal');
|
||||||
}
|
}
|
||||||
const ngModule = reflectObjectLiteral(meta);
|
const ngModule = reflectObjectLiteral(meta);
|
||||||
|
|
||||||
|
@ -62,23 +67,25 @@ export class NgModuleDecoratorHandler implements DecoratorHandler<NgModuleAnalys
|
||||||
// Extract the module declarations, imports, and exports.
|
// Extract the module declarations, imports, and exports.
|
||||||
let declarations: Reference[] = [];
|
let declarations: Reference[] = [];
|
||||||
if (ngModule.has('declarations')) {
|
if (ngModule.has('declarations')) {
|
||||||
const declarationMeta =
|
const expr = ngModule.get('declarations') !;
|
||||||
staticallyResolve(ngModule.get('declarations') !, this.reflector, this.checker);
|
const declarationMeta = staticallyResolve(expr, this.reflector, this.checker);
|
||||||
declarations = this.resolveTypeList(declarationMeta, 'declarations');
|
declarations = this.resolveTypeList(expr, declarationMeta, 'declarations');
|
||||||
}
|
}
|
||||||
let imports: Reference[] = [];
|
let imports: Reference[] = [];
|
||||||
if (ngModule.has('imports')) {
|
if (ngModule.has('imports')) {
|
||||||
|
const expr = ngModule.get('imports') !;
|
||||||
const importsMeta = staticallyResolve(
|
const importsMeta = staticallyResolve(
|
||||||
ngModule.get('imports') !, this.reflector, this.checker,
|
expr, this.reflector, this.checker,
|
||||||
ref => this._extractModuleFromModuleWithProvidersFn(ref.node));
|
ref => this._extractModuleFromModuleWithProvidersFn(ref.node));
|
||||||
imports = this.resolveTypeList(importsMeta, 'imports');
|
imports = this.resolveTypeList(expr, importsMeta, 'imports');
|
||||||
}
|
}
|
||||||
let exports: Reference[] = [];
|
let exports: Reference[] = [];
|
||||||
if (ngModule.has('exports')) {
|
if (ngModule.has('exports')) {
|
||||||
|
const expr = ngModule.get('exports') !;
|
||||||
const exportsMeta = staticallyResolve(
|
const exportsMeta = staticallyResolve(
|
||||||
ngModule.get('exports') !, this.reflector, this.checker,
|
expr, this.reflector, this.checker,
|
||||||
ref => this._extractModuleFromModuleWithProvidersFn(ref.node));
|
ref => this._extractModuleFromModuleWithProvidersFn(ref.node));
|
||||||
exports = this.resolveTypeList(exportsMeta, 'exports');
|
exports = this.resolveTypeList(expr, exportsMeta, 'exports');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Register this module's information with the SelectorScopeRegistry. This ensures that during
|
// Register this module's information with the SelectorScopeRegistry. This ensures that during
|
||||||
|
@ -185,10 +192,11 @@ export class NgModuleDecoratorHandler implements DecoratorHandler<NgModuleAnalys
|
||||||
/**
|
/**
|
||||||
* Compute a list of `Reference`s from a resolved metadata value.
|
* Compute a list of `Reference`s from a resolved metadata value.
|
||||||
*/
|
*/
|
||||||
private resolveTypeList(resolvedList: ResolvedValue, name: string): Reference[] {
|
private resolveTypeList(expr: ts.Node, resolvedList: ResolvedValue, name: string): Reference[] {
|
||||||
const refList: Reference[] = [];
|
const refList: Reference[] = [];
|
||||||
if (!Array.isArray(resolvedList)) {
|
if (!Array.isArray(resolvedList)) {
|
||||||
throw new Error(`Expected array when reading property ${name}`);
|
throw new FatalDiagnosticError(
|
||||||
|
ErrorCode.VALUE_HAS_WRONG_TYPE, expr, `Expected array when reading property ${name}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
resolvedList.forEach((entry, idx) => {
|
resolvedList.forEach((entry, idx) => {
|
||||||
|
@ -200,12 +208,15 @@ export class NgModuleDecoratorHandler implements DecoratorHandler<NgModuleAnalys
|
||||||
|
|
||||||
if (Array.isArray(entry)) {
|
if (Array.isArray(entry)) {
|
||||||
// Recurse into nested arrays.
|
// Recurse into nested arrays.
|
||||||
refList.push(...this.resolveTypeList(entry, name));
|
refList.push(...this.resolveTypeList(expr, entry, name));
|
||||||
} else if (entry instanceof Reference) {
|
} else if (entry instanceof Reference) {
|
||||||
if (!entry.expressable) {
|
if (!entry.expressable) {
|
||||||
throw new Error(`Value at position ${idx} in ${name} array is not expressable`);
|
throw new FatalDiagnosticError(
|
||||||
|
ErrorCode.VALUE_HAS_WRONG_TYPE, expr, `One entry in ${name} is not a type`);
|
||||||
} else if (!this.reflector.isClass(entry.node)) {
|
} else if (!this.reflector.isClass(entry.node)) {
|
||||||
throw new Error(`Value at position ${idx} in ${name} array is not a class declaration`);
|
throw new FatalDiagnosticError(
|
||||||
|
ErrorCode.VALUE_HAS_WRONG_TYPE, entry.node,
|
||||||
|
`Entry is not a type, but is used as such in ${name} array`);
|
||||||
}
|
}
|
||||||
refList.push(entry);
|
refList.push(entry);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
import {LiteralExpr, R3PipeMetadata, WrappedNodeExpr, compilePipeFromMetadata} from '@angular/compiler';
|
import {LiteralExpr, R3PipeMetadata, WrappedNodeExpr, compilePipeFromMetadata} from '@angular/compiler';
|
||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
|
|
||||||
|
import {ErrorCode, FatalDiagnosticError} from '../../diagnostics';
|
||||||
import {Decorator, ReflectionHost} from '../../host';
|
import {Decorator, ReflectionHost} from '../../host';
|
||||||
import {reflectObjectLiteral, staticallyResolve} from '../../metadata';
|
import {reflectObjectLiteral, staticallyResolve} from '../../metadata';
|
||||||
import {AnalysisOutput, CompileResult, DecoratorHandler} from '../../transform';
|
import {AnalysisOutput, CompileResult, DecoratorHandler} from '../../transform';
|
||||||
|
@ -31,33 +32,45 @@ export class PipeDecoratorHandler implements DecoratorHandler<R3PipeMetadata, De
|
||||||
|
|
||||||
analyze(clazz: ts.ClassDeclaration, decorator: Decorator): AnalysisOutput<R3PipeMetadata> {
|
analyze(clazz: ts.ClassDeclaration, decorator: Decorator): AnalysisOutput<R3PipeMetadata> {
|
||||||
if (clazz.name === undefined) {
|
if (clazz.name === undefined) {
|
||||||
throw new Error(`@Pipes must have names`);
|
throw new FatalDiagnosticError(
|
||||||
|
ErrorCode.DECORATOR_ON_ANONYMOUS_CLASS, clazz, `@Pipes must have names`);
|
||||||
}
|
}
|
||||||
const name = clazz.name.text;
|
const name = clazz.name.text;
|
||||||
const type = new WrappedNodeExpr(clazz.name);
|
const type = new WrappedNodeExpr(clazz.name);
|
||||||
if (decorator.args === null) {
|
if (decorator.args === null) {
|
||||||
throw new Error(`@Pipe must be called`);
|
throw new FatalDiagnosticError(
|
||||||
|
ErrorCode.DECORATOR_NOT_CALLED, decorator.node, `@Pipe must be called`);
|
||||||
|
}
|
||||||
|
if (decorator.args.length !== 1) {
|
||||||
|
throw new FatalDiagnosticError(
|
||||||
|
ErrorCode.DECORATOR_ARITY_WRONG, decorator.node, '@Pipe must have exactly one argument');
|
||||||
}
|
}
|
||||||
const meta = unwrapExpression(decorator.args[0]);
|
const meta = unwrapExpression(decorator.args[0]);
|
||||||
if (!ts.isObjectLiteralExpression(meta)) {
|
if (!ts.isObjectLiteralExpression(meta)) {
|
||||||
throw new Error(`Decorator argument must be literal.`);
|
throw new FatalDiagnosticError(
|
||||||
|
ErrorCode.DECORATOR_ARG_NOT_LITERAL, meta, '@Pipe must have a literal argument');
|
||||||
}
|
}
|
||||||
const pipe = reflectObjectLiteral(meta);
|
const pipe = reflectObjectLiteral(meta);
|
||||||
|
|
||||||
if (!pipe.has('name')) {
|
if (!pipe.has('name')) {
|
||||||
throw new Error(`@Pipe decorator is missing name field`);
|
throw new FatalDiagnosticError(
|
||||||
|
ErrorCode.PIPE_MISSING_NAME, meta, `@Pipe decorator is missing name field`);
|
||||||
}
|
}
|
||||||
const pipeName = staticallyResolve(pipe.get('name') !, this.reflector, this.checker);
|
const pipeNameExpr = pipe.get('name') !;
|
||||||
|
const pipeName = staticallyResolve(pipeNameExpr, this.reflector, this.checker);
|
||||||
if (typeof pipeName !== 'string') {
|
if (typeof pipeName !== 'string') {
|
||||||
throw new Error(`@Pipe.name must be a string`);
|
throw new FatalDiagnosticError(
|
||||||
|
ErrorCode.VALUE_HAS_WRONG_TYPE, pipeNameExpr, `@Pipe.name must be a string`);
|
||||||
}
|
}
|
||||||
this.scopeRegistry.registerPipe(clazz, pipeName);
|
this.scopeRegistry.registerPipe(clazz, pipeName);
|
||||||
|
|
||||||
let pure = true;
|
let pure = true;
|
||||||
if (pipe.has('pure')) {
|
if (pipe.has('pure')) {
|
||||||
const pureValue = staticallyResolve(pipe.get('pure') !, this.reflector, this.checker);
|
const expr = pipe.get('pure') !;
|
||||||
|
const pureValue = staticallyResolve(expr, this.reflector, this.checker);
|
||||||
if (typeof pureValue !== 'boolean') {
|
if (typeof pureValue !== 'boolean') {
|
||||||
throw new Error(`@Pipe.pure must be a boolean`);
|
throw new FatalDiagnosticError(
|
||||||
|
ErrorCode.VALUE_HAS_WRONG_TYPE, expr, `@Pipe.pure must be a boolean`);
|
||||||
}
|
}
|
||||||
pure = pureValue;
|
pure = pureValue;
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
import {Expression, R3DependencyMetadata, R3Reference, R3ResolvedDependencyType, WrappedNodeExpr} from '@angular/compiler';
|
import {Expression, R3DependencyMetadata, R3Reference, R3ResolvedDependencyType, WrappedNodeExpr} from '@angular/compiler';
|
||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
|
|
||||||
|
import {ErrorCode, FatalDiagnosticError} from '../../diagnostics';
|
||||||
import {Decorator, ReflectionHost} from '../../host';
|
import {Decorator, ReflectionHost} from '../../host';
|
||||||
import {AbsoluteReference, ImportMode, Reference} from '../../metadata';
|
import {AbsoluteReference, ImportMode, Reference} from '../../metadata';
|
||||||
|
|
||||||
|
@ -31,7 +32,9 @@ export function getConstructorDependencies(
|
||||||
(param.decorators || []).filter(dec => isCore || isAngularCore(dec)).forEach(dec => {
|
(param.decorators || []).filter(dec => isCore || isAngularCore(dec)).forEach(dec => {
|
||||||
if (dec.name === 'Inject') {
|
if (dec.name === 'Inject') {
|
||||||
if (dec.args === null || dec.args.length !== 1) {
|
if (dec.args === null || dec.args.length !== 1) {
|
||||||
throw new Error(`Unexpected number of arguments to @Inject().`);
|
throw new FatalDiagnosticError(
|
||||||
|
ErrorCode.DECORATOR_ARITY_WRONG, dec.node,
|
||||||
|
`Unexpected number of arguments to @Inject().`);
|
||||||
}
|
}
|
||||||
tokenExpr = dec.args[0];
|
tokenExpr = dec.args[0];
|
||||||
} else if (dec.name === 'Optional') {
|
} else if (dec.name === 'Optional') {
|
||||||
|
@ -44,16 +47,21 @@ export function getConstructorDependencies(
|
||||||
host = true;
|
host = true;
|
||||||
} else if (dec.name === 'Attribute') {
|
} else if (dec.name === 'Attribute') {
|
||||||
if (dec.args === null || dec.args.length !== 1) {
|
if (dec.args === null || dec.args.length !== 1) {
|
||||||
throw new Error(`Unexpected number of arguments to @Attribute().`);
|
throw new FatalDiagnosticError(
|
||||||
|
ErrorCode.DECORATOR_ARITY_WRONG, dec.node,
|
||||||
|
`Unexpected number of arguments to @Attribute().`);
|
||||||
}
|
}
|
||||||
tokenExpr = dec.args[0];
|
tokenExpr = dec.args[0];
|
||||||
resolved = R3ResolvedDependencyType.Attribute;
|
resolved = R3ResolvedDependencyType.Attribute;
|
||||||
} else {
|
} else {
|
||||||
throw new Error(`Unexpected decorator ${dec.name} on parameter.`);
|
throw new FatalDiagnosticError(
|
||||||
|
ErrorCode.DECORATOR_UNEXPECTED, dec.node,
|
||||||
|
`Unexpected decorator ${dec.name} on parameter.`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (tokenExpr === null) {
|
if (tokenExpr === null) {
|
||||||
throw new Error(
|
throw new FatalDiagnosticError(
|
||||||
|
ErrorCode.PARAM_MISSING_TOKEN, param.nameNode,
|
||||||
`No suitable token for parameter ${param.name || idx} of class ${clazz.name!.text}`);
|
`No suitable token for parameter ${param.name || idx} of class ${clazz.name!.text}`);
|
||||||
}
|
}
|
||||||
if (ts.isIdentifier(tokenExpr)) {
|
if (ts.isIdentifier(tokenExpr)) {
|
||||||
|
|
|
@ -13,6 +13,7 @@ ts_library(
|
||||||
"//packages:types",
|
"//packages:types",
|
||||||
"//packages/compiler",
|
"//packages/compiler",
|
||||||
"//packages/compiler-cli/src/ngtsc/annotations",
|
"//packages/compiler-cli/src/ngtsc/annotations",
|
||||||
|
"//packages/compiler-cli/src/ngtsc/diagnostics",
|
||||||
"//packages/compiler-cli/src/ngtsc/metadata",
|
"//packages/compiler-cli/src/ngtsc/metadata",
|
||||||
"//packages/compiler-cli/src/ngtsc/testing",
|
"//packages/compiler-cli/src/ngtsc/testing",
|
||||||
],
|
],
|
||||||
|
|
|
@ -0,0 +1,65 @@
|
||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright Google Inc. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
|
||||||
|
import * as ts from 'typescript';
|
||||||
|
|
||||||
|
import {ErrorCode, FatalDiagnosticError} from '../../diagnostics';
|
||||||
|
import {TypeScriptReflectionHost} from '../../metadata';
|
||||||
|
import {getDeclaration, makeProgram} from '../../testing/in_memory_typescript';
|
||||||
|
import {ResourceLoader} from '../src/api';
|
||||||
|
import {ComponentDecoratorHandler} from '../src/component';
|
||||||
|
import {SelectorScopeRegistry} from '../src/selector_scope';
|
||||||
|
|
||||||
|
export class NoopResourceLoader implements ResourceLoader {
|
||||||
|
load(url: string): string { throw new Error('Not implemented'); }
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('ComponentDecoratorHandler', () => {
|
||||||
|
it('should produce a diagnostic when @Component has non-literal argument', () => {
|
||||||
|
const {program} = makeProgram([
|
||||||
|
{
|
||||||
|
name: 'node_modules/@angular/core/index.d.ts',
|
||||||
|
contents: 'export const Component: any;',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'entry.ts',
|
||||||
|
contents: `
|
||||||
|
import {Component} from '@angular/core';
|
||||||
|
|
||||||
|
const TEST = '';
|
||||||
|
@Component(TEST) class TestCmp {}
|
||||||
|
`
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
const checker = program.getTypeChecker();
|
||||||
|
const host = new TypeScriptReflectionHost(checker);
|
||||||
|
const handler = new ComponentDecoratorHandler(
|
||||||
|
checker, host, new SelectorScopeRegistry(checker, host), false, new NoopResourceLoader());
|
||||||
|
const TestCmp = getDeclaration(program, 'entry.ts', 'TestCmp', ts.isClassDeclaration);
|
||||||
|
const detected = handler.detect(TestCmp, host.getDecoratorsOfDeclaration(TestCmp));
|
||||||
|
if (detected === undefined) {
|
||||||
|
return fail('Failed to recognize @Component');
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
handler.analyze(TestCmp, detected);
|
||||||
|
return fail('Analysis should have failed');
|
||||||
|
} catch (err) {
|
||||||
|
if (!(err instanceof FatalDiagnosticError)) {
|
||||||
|
return fail('Error should be a FatalDiagnosticError');
|
||||||
|
}
|
||||||
|
const diag = err.toDiagnostic();
|
||||||
|
expect(diag.code).toEqual(ivyCode(ErrorCode.DECORATOR_ARG_NOT_LITERAL));
|
||||||
|
expect(diag.file.fileName.endsWith('entry.ts')).toBe(true);
|
||||||
|
expect(diag.start).toBe(detected.args ![0].getStart());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function ivyCode(code: ErrorCode): number {
|
||||||
|
return Number('-99' + code.valueOf());
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
package(default_visibility = ["//visibility:public"])
|
||||||
|
|
||||||
|
load("//tools:defaults.bzl", "ts_library")
|
||||||
|
|
||||||
|
ts_library(
|
||||||
|
name = "diagnostics",
|
||||||
|
srcs = glob([
|
||||||
|
"index.ts",
|
||||||
|
"src/**/*.ts",
|
||||||
|
]),
|
||||||
|
module_name = "@angular/compiler-cli/src/ngtsc/diagnostics",
|
||||||
|
deps = [
|
||||||
|
"//packages/compiler",
|
||||||
|
],
|
||||||
|
)
|
|
@ -0,0 +1,11 @@
|
||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright Google Inc. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
|
||||||
|
export {ErrorCode} from './src/code';
|
||||||
|
export {FatalDiagnosticError, isFatalDiagnosticError} from './src/error';
|
||||||
|
export {replaceTsWithNgInErrors} from './src/util';
|
|
@ -0,0 +1,22 @@
|
||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright Google Inc. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
|
||||||
|
export enum ErrorCode {
|
||||||
|
DECORATOR_ARG_NOT_LITERAL = 1001,
|
||||||
|
DECORATOR_ARITY_WRONG = 1002,
|
||||||
|
DECORATOR_NOT_CALLED = 1003,
|
||||||
|
DECORATOR_ON_ANONYMOUS_CLASS = 1004,
|
||||||
|
DECORATOR_UNEXPECTED = 1005,
|
||||||
|
|
||||||
|
VALUE_HAS_WRONG_TYPE = 1010,
|
||||||
|
VALUE_NOT_LITERAL = 1011,
|
||||||
|
|
||||||
|
COMPONENT_MISSING_TEMPLATE = 2001,
|
||||||
|
PIPE_MISSING_NAME = 2002,
|
||||||
|
PARAM_MISSING_TOKEN = 2003,
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright Google Inc. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
|
||||||
|
import * as ts from 'typescript';
|
||||||
|
|
||||||
|
import {ErrorCode} from './code';
|
||||||
|
|
||||||
|
export class FatalDiagnosticError {
|
||||||
|
constructor(readonly code: ErrorCode, readonly node: ts.Node, readonly message: string) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
_isFatalDiagnosticError = true;
|
||||||
|
|
||||||
|
toDiagnostic(): ts.DiagnosticWithLocation {
|
||||||
|
const node = ts.getOriginalNode(this.node);
|
||||||
|
return {
|
||||||
|
category: ts.DiagnosticCategory.Error,
|
||||||
|
code: Number('-99' + this.code.valueOf()),
|
||||||
|
file: ts.getOriginalNode(this.node).getSourceFile(),
|
||||||
|
start: node.getStart(undefined, false),
|
||||||
|
length: node.getWidth(),
|
||||||
|
messageText: this.message,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isFatalDiagnosticError(err: any): err is FatalDiagnosticError {
|
||||||
|
return err._isFatalDiagnosticError === true;
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright Google Inc. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
|
||||||
|
import * as ts from 'typescript';
|
||||||
|
|
||||||
|
const ERROR_CODE_MATCHER = /(\u001b\[\d+m ?)TS-99(\d+: ?\u001b\[\d+m)/g;
|
||||||
|
|
||||||
|
export function replaceTsWithNgInErrors(errors: string): string {
|
||||||
|
return errors.replace(ERROR_CODE_MATCHER, '$1NG$2');
|
||||||
|
}
|
|
@ -88,9 +88,10 @@ export class NgtscProgram implements api.Program {
|
||||||
}
|
}
|
||||||
|
|
||||||
getNgSemanticDiagnostics(
|
getNgSemanticDiagnostics(
|
||||||
fileName?: string|undefined,
|
fileName?: string|undefined, cancellationToken?: ts.CancellationToken|
|
||||||
cancellationToken?: ts.CancellationToken|undefined): ReadonlyArray<api.Diagnostic> {
|
undefined): ReadonlyArray<ts.Diagnostic|api.Diagnostic> {
|
||||||
return [];
|
const compilation = this.ensureAnalyzed();
|
||||||
|
return compilation.diagnostics;
|
||||||
}
|
}
|
||||||
|
|
||||||
async loadNgStructureAsync(): Promise<void> {
|
async loadNgStructureAsync(): Promise<void> {
|
||||||
|
@ -117,6 +118,16 @@ export class NgtscProgram implements api.Program {
|
||||||
throw new Error('Method not implemented.');
|
throw new Error('Method not implemented.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private ensureAnalyzed(): IvyCompilation {
|
||||||
|
if (this.compilation === undefined) {
|
||||||
|
this.compilation = this.makeCompilation();
|
||||||
|
this.tsProgram.getSourceFiles()
|
||||||
|
.filter(file => !file.fileName.endsWith('.d.ts'))
|
||||||
|
.forEach(file => this.compilation !.analyzeSync(file));
|
||||||
|
}
|
||||||
|
return this.compilation;
|
||||||
|
}
|
||||||
|
|
||||||
emit(opts?: {
|
emit(opts?: {
|
||||||
emitFlags?: api.EmitFlags,
|
emitFlags?: api.EmitFlags,
|
||||||
cancellationToken?: ts.CancellationToken,
|
cancellationToken?: ts.CancellationToken,
|
||||||
|
@ -126,12 +137,7 @@ export class NgtscProgram implements api.Program {
|
||||||
}): ts.EmitResult {
|
}): ts.EmitResult {
|
||||||
const emitCallback = opts && opts.emitCallback || defaultEmitCallback;
|
const emitCallback = opts && opts.emitCallback || defaultEmitCallback;
|
||||||
|
|
||||||
if (this.compilation === undefined) {
|
this.ensureAnalyzed();
|
||||||
this.compilation = this.makeCompilation();
|
|
||||||
this.tsProgram.getSourceFiles()
|
|
||||||
.filter(file => !file.fileName.endsWith('.d.ts'))
|
|
||||||
.forEach(file => this.compilation !.analyzeSync(file));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Since there is no .d.ts transformation API, .d.ts files are transformed during write.
|
// Since there is no .d.ts transformation API, .d.ts files are transformed during write.
|
||||||
const writeFile: ts.WriteFileCallback =
|
const writeFile: ts.WriteFileCallback =
|
||||||
|
|
|
@ -11,6 +11,7 @@ ts_library(
|
||||||
module_name = "@angular/compiler-cli/src/ngtsc/transform",
|
module_name = "@angular/compiler-cli/src/ngtsc/transform",
|
||||||
deps = [
|
deps = [
|
||||||
"//packages/compiler",
|
"//packages/compiler",
|
||||||
|
"//packages/compiler-cli/src/ngtsc/diagnostics",
|
||||||
"//packages/compiler-cli/src/ngtsc/host",
|
"//packages/compiler-cli/src/ngtsc/host",
|
||||||
"//packages/compiler-cli/src/ngtsc/metadata",
|
"//packages/compiler-cli/src/ngtsc/metadata",
|
||||||
"//packages/compiler-cli/src/ngtsc/util",
|
"//packages/compiler-cli/src/ngtsc/util",
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
import {ConstantPool} from '@angular/compiler';
|
import {ConstantPool} from '@angular/compiler';
|
||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
|
|
||||||
|
import {FatalDiagnosticError} from '../../diagnostics';
|
||||||
import {Decorator, ReflectionHost} from '../../host';
|
import {Decorator, ReflectionHost} from '../../host';
|
||||||
import {reflectNameOfDeclaration} from '../../metadata/src/reflector';
|
import {reflectNameOfDeclaration} from '../../metadata/src/reflector';
|
||||||
|
|
||||||
|
@ -98,23 +99,30 @@ export class IvyCompilation {
|
||||||
|
|
||||||
// Run analysis on the metadata. This will produce either diagnostics, an
|
// Run analysis on the metadata. This will produce either diagnostics, an
|
||||||
// analysis result, or both.
|
// analysis result, or both.
|
||||||
const analysis = adapter.analyze(node, metadata);
|
try {
|
||||||
|
const analysis = adapter.analyze(node, metadata);
|
||||||
|
if (analysis.analysis !== undefined) {
|
||||||
|
this.analysis.set(node, {
|
||||||
|
adapter,
|
||||||
|
analysis: analysis.analysis,
|
||||||
|
metadata: metadata,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (analysis.analysis !== undefined) {
|
if (analysis.diagnostics !== undefined) {
|
||||||
this.analysis.set(node, {
|
this._diagnostics.push(...analysis.diagnostics);
|
||||||
adapter,
|
}
|
||||||
analysis: analysis.analysis,
|
|
||||||
metadata: metadata,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (analysis.diagnostics !== undefined) {
|
if (analysis.factorySymbolName !== undefined && this.sourceToFactorySymbols !== null &&
|
||||||
this._diagnostics.push(...analysis.diagnostics);
|
this.sourceToFactorySymbols.has(sf.fileName)) {
|
||||||
}
|
this.sourceToFactorySymbols.get(sf.fileName) !.add(analysis.factorySymbolName);
|
||||||
|
}
|
||||||
if (analysis.factorySymbolName !== undefined && this.sourceToFactorySymbols !== null &&
|
} catch (err) {
|
||||||
this.sourceToFactorySymbols.has(sf.fileName)) {
|
if (err instanceof FatalDiagnosticError) {
|
||||||
this.sourceToFactorySymbols.get(sf.fileName) !.add(analysis.factorySymbolName);
|
this._diagnostics.push(err.toDiagnostic());
|
||||||
|
} else {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -340,7 +340,7 @@ export interface Program {
|
||||||
* Angular structural information is required to produce these diagnostics.
|
* Angular structural information is required to produce these diagnostics.
|
||||||
*/
|
*/
|
||||||
getNgSemanticDiagnostics(fileName?: string, cancellationToken?: ts.CancellationToken):
|
getNgSemanticDiagnostics(fileName?: string, cancellationToken?: ts.CancellationToken):
|
||||||
ReadonlyArray<Diagnostic>;
|
ReadonlyArray<ts.Diagnostic|Diagnostic>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load Angular structural information asynchronously. If this method is not called then the
|
* Load Angular structural information asynchronously. If this method is not called then the
|
||||||
|
|
Loading…
Reference in New Issue