refactor(core): add top 10 runtime error codes (#39188)
adds RuntimeError and code enum to improve debugging experience refactor ExpressionChangedAfterItHasBeenCheckedError to code NG0100 refactor CyclicDependency to code NG0200 refactor No Provider to code NG0201 refactor MultipleComponentsMatch to code NG0300 refactor ExportNotFound to code NG0301 refactor PipeNotFound to code NG0302 refactor BindingNotKnown to code NG0303 refactor NotKnownElement to code NG0304 PR Close #39188
This commit is contained in:
parent
0723331b2a
commit
e6ca3d3841
|
@ -196,7 +196,7 @@ which *is* what parent means.
|
|||
|
||||
2. Angular throws a cyclic dependency error if you omit the `@SkipSelf` decorator.
|
||||
|
||||
`Circular dependency in DI detected for BethComponent. Dependency path: BethComponent -> Parent -> BethComponent`
|
||||
`NG0200: Circular dependency in DI detected for BethComponent. Dependency path: BethComponent -> Parent -> BethComponent`
|
||||
|
||||
Here's *Alice*, *Barry*, and family in action.
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
"master": {
|
||||
"uncompressed": {
|
||||
"runtime-es2015": 3037,
|
||||
"main-es2015": 448085,
|
||||
"main-es2015": 448615,
|
||||
"polyfills-es2015": 52415
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
*/
|
||||
|
||||
import {forwardRef, Injectable, InjectionToken, Injector, NgModule, ɵcreateInjector as createInjector} from '@angular/core';
|
||||
import {ivyEnabled} from '@angular/private/testing';
|
||||
import {AOT_TOKEN, AotModule, AotService} from 'app_built/src/module';
|
||||
|
||||
describe('Ivy NgModule', () => {
|
||||
|
@ -61,9 +62,10 @@ describe('Ivy NgModule', () => {
|
|||
class BModule {
|
||||
}
|
||||
|
||||
const errorCode = ivyEnabled ? 'NG0200: ' : '';
|
||||
expect(() => createInjector(AModule))
|
||||
.toThrowError(
|
||||
'Circular dependency in DI detected for AModule. Dependency path: AModule > BModule > AModule');
|
||||
.toThrowError(`${
|
||||
errorCode}Circular dependency in DI detected for AModule. Dependency path: AModule > BModule > AModule`);
|
||||
});
|
||||
|
||||
it('merges imports and exports', () => {
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright Google LLC 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 const enum RuntimeErrorCode {
|
||||
// Internal Errors
|
||||
|
||||
// Change Detection Errors
|
||||
EXPRESSION_CHANGED_AFTER_CHECKED = '100',
|
||||
|
||||
// Dependency Injection Errors
|
||||
CYCLIC_DI_DEPENDENCY = '200',
|
||||
PROVIDER_NOT_FOUND = '201',
|
||||
|
||||
// Template Errors
|
||||
MULTIPLE_COMPONENTS_MATCH = '300',
|
||||
EXPORT_NOT_FOUND = '301',
|
||||
PIPE_NOT_FOUND = '302',
|
||||
UNKNOWN_BINDING = '303',
|
||||
UNKNOWN_ELEMENT = '304',
|
||||
|
||||
// Styling Errors
|
||||
|
||||
// Declarations Errors
|
||||
|
||||
// i18n Errors
|
||||
|
||||
// Compilation Errors
|
||||
}
|
||||
|
||||
export class RuntimeError extends Error {
|
||||
constructor(public code: RuntimeErrorCode, message: string) {
|
||||
super(formatRuntimeError(code, message));
|
||||
}
|
||||
}
|
||||
|
||||
/** Called to format a runtime error */
|
||||
export function formatRuntimeError(code: RuntimeErrorCode, message: string): string {
|
||||
const fullCode = code ? `NG0${code}: ` : '';
|
||||
return `${fullCode}${message}`;
|
||||
}
|
|
@ -8,6 +8,7 @@
|
|||
*/
|
||||
import {InjectorType} from '../di/interface/defs';
|
||||
import {stringify} from '../util/stringify';
|
||||
import {RuntimeError, RuntimeErrorCode} from './error_code';
|
||||
|
||||
import {TNode} from './interfaces/node';
|
||||
import {LView, TVIEW} from './interfaces/view';
|
||||
|
@ -18,12 +19,16 @@ import {INTERPOLATION_DELIMITER, stringifyForError} from './util/misc_utils';
|
|||
/** Called when directives inject each other (creating a circular dependency) */
|
||||
export function throwCyclicDependencyError(token: string, path?: string[]): never {
|
||||
const depPath = path ? `. Dependency path: ${path.join(' > ')} > ${token}` : '';
|
||||
throw new Error(`Circular dependency in DI detected for ${token}${depPath}`);
|
||||
throw new RuntimeError(
|
||||
RuntimeErrorCode.CYCLIC_DI_DEPENDENCY,
|
||||
`Circular dependency in DI detected for ${token}${depPath}`);
|
||||
}
|
||||
|
||||
/** Called when there are multiple component selectors that match a given node */
|
||||
export function throwMultipleComponentError(tNode: TNode): never {
|
||||
throw new Error(`Multiple components match node with tagname ${tNode.value}`);
|
||||
throw new RuntimeError(
|
||||
RuntimeErrorCode.MULTIPLE_COMPONENTS_MATCH,
|
||||
`Multiple components match node with tagname ${tNode.value}`);
|
||||
}
|
||||
|
||||
export function throwMixedMultiProviderError() {
|
||||
|
@ -57,7 +62,7 @@ export function throwErrorIfNoChangesMode(
|
|||
}
|
||||
// TODO: include debug context, see `viewDebugError` function in
|
||||
// `packages/core/src/view/errors.ts` for reference.
|
||||
throw new Error(msg);
|
||||
throw new RuntimeError(RuntimeErrorCode.EXPRESSION_CHANGED_AFTER_CHECKED, msg);
|
||||
}
|
||||
|
||||
function constructDetailsForInterpolation(
|
||||
|
@ -121,5 +126,7 @@ export function getExpressionChangedErrorDetails(
|
|||
/** Throws an error when a token is not found in DI. */
|
||||
export function throwProviderNotFoundError(token: any, injectorName?: string): never {
|
||||
const injectorDetails = injectorName ? ` in ${injectorName}` : '';
|
||||
throw new Error(`No provider for ${stringifyForError(token)} found${injectorDetails}`);
|
||||
throw new RuntimeError(
|
||||
RuntimeErrorCode.PROVIDER_NOT_FOUND,
|
||||
`No provider for ${stringifyForError(token)} found${injectorDetails}`);
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
import {assertDefined, assertEqual, assertIndexInRange} from '../../util/assert';
|
||||
import {assertFirstCreatePass, assertHasParent} from '../assert';
|
||||
import {attachPatchData} from '../context_discovery';
|
||||
import {formatRuntimeError, RuntimeErrorCode} from '../error_code';
|
||||
import {registerPostOrderHooks} from '../hooks';
|
||||
import {hasClassInput, hasStyleInput, TAttributes, TElementNode, TNode, TNodeFlags, TNodeType} from '../interfaces/node';
|
||||
import {RElement} from '../interfaces/renderer';
|
||||
|
@ -216,7 +217,7 @@ function logUnknownElementError(
|
|||
message +=
|
||||
`2. To allow any element add 'NO_ERRORS_SCHEMA' to the '@NgModule.schemas' of this component.`;
|
||||
}
|
||||
console.error(message);
|
||||
console.error(formatRuntimeError(RuntimeErrorCode.UNKNOWN_ELEMENT, message));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ import {assertFirstCreatePass, assertFirstUpdatePass, assertLContainer, assertLV
|
|||
import {attachPatchData} from '../context_discovery';
|
||||
import {getFactoryDef} from '../definition';
|
||||
import {diPublicInInjector, getNodeInjectable, getOrCreateNodeInjectorForNode} from '../di';
|
||||
import {formatRuntimeError, RuntimeError, RuntimeErrorCode} from '../error_code';
|
||||
import {throwMultipleComponentError} from '../errors';
|
||||
import {executeCheckHooks, executeInitAndCheckHooks, incrementInitPhaseFlags} from '../hooks';
|
||||
import {CONTAINER_HEADER_OFFSET, HAS_TRANSPLANTED_VIEWS, LContainer, MOVED_VIEWS} from '../interfaces/container';
|
||||
|
@ -1096,7 +1097,8 @@ export function matchingSchemas(tView: TView, tagName: string|null): boolean {
|
|||
* @param tNode Node on which we encountered the property.
|
||||
*/
|
||||
function logUnknownPropertyError(propName: string, tNode: TNode): void {
|
||||
console.error(`Can't bind to '${propName}' since it isn't a known property of '${tNode.value}'.`);
|
||||
let message = `Can't bind to '${propName}' since it isn't a known property of '${tNode.value}'.`;
|
||||
console.error(formatRuntimeError(RuntimeErrorCode.UNKNOWN_BINDING, message));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1388,7 +1390,9 @@ function cacheMatchingLocalNames(
|
|||
// in the template (for template queries).
|
||||
for (let i = 0; i < localRefs.length; i += 2) {
|
||||
const index = exportsMap[localRefs[i + 1]];
|
||||
if (index == null) throw new Error(`Export of name '${localRefs[i + 1]}' not found!`);
|
||||
if (index == null)
|
||||
throw new RuntimeError(
|
||||
RuntimeErrorCode.EXPORT_NOT_FOUND, `Export of name '${localRefs[i + 1]}' not found!`);
|
||||
localNames.push(localRefs[i], index);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ import {setInjectImplementation} from '../di/injector_compatibility';
|
|||
|
||||
import {getFactoryDef} from './definition';
|
||||
import {setIncludeViewProviders} from './di';
|
||||
import {RuntimeError, RuntimeErrorCode} from './error_code';
|
||||
import {store, ɵɵdirectiveInject} from './instructions/all';
|
||||
import {PipeDef, PipeDefList} from './interfaces/definition';
|
||||
import {HEADER_OFFSET, LView, TVIEW} from './interfaces/view';
|
||||
|
@ -80,7 +81,7 @@ function getPipeDef(name: string, registry: PipeDefList|null): PipeDef<any> {
|
|||
}
|
||||
}
|
||||
}
|
||||
throw new Error(`The pipe '${name}' could not be found!`);
|
||||
throw new RuntimeError(RuntimeErrorCode.PIPE_NOT_FOUND, `The pipe '${name}' could not be found!`);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -616,7 +616,7 @@ describe('di', () => {
|
|||
|
||||
TestBed.configureTestingModule({declarations: [DirectiveA, DirectiveB, MyComp]});
|
||||
expect(() => TestBed.createComponent(MyComp))
|
||||
.toThrowError('Circular dependency in DI detected for DirectiveA');
|
||||
.toThrowError('NG0200: Circular dependency in DI detected for DirectiveA');
|
||||
});
|
||||
|
||||
onlyInIvy('Ivy has different error message for circular dependency')
|
||||
|
@ -632,7 +632,7 @@ describe('di', () => {
|
|||
|
||||
TestBed.configureTestingModule({declarations: [DirectiveA, DirectiveB, MyComp]});
|
||||
expect(() => TestBed.createComponent(MyComp))
|
||||
.toThrowError('Circular dependency in DI detected for DirectiveA');
|
||||
.toThrowError('NG0200: Circular dependency in DI detected for DirectiveA');
|
||||
});
|
||||
|
||||
describe('flags', () => {
|
||||
|
@ -736,7 +736,7 @@ describe('di', () => {
|
|||
}
|
||||
TestBed.configureTestingModule({declarations: [DirectiveA, DirectiveB, MyComp]});
|
||||
expect(() => TestBed.createComponent(MyComp))
|
||||
.toThrowError(/No provider for DirectiveB found in NodeInjector/);
|
||||
.toThrowError(/NG0201: No provider for DirectiveB found in NodeInjector/);
|
||||
});
|
||||
|
||||
describe('@Host', () => {
|
||||
|
@ -814,7 +814,7 @@ describe('di', () => {
|
|||
|
||||
TestBed.configureTestingModule({declarations: [DirectiveString, MyComp, MyApp]});
|
||||
expect(() => TestBed.createComponent(MyApp))
|
||||
.toThrowError('No provider for String found in NodeInjector');
|
||||
.toThrowError('NG0201: No provider for String found in NodeInjector');
|
||||
});
|
||||
|
||||
onlyInIvy('Ivy has different error message when dependency is not found')
|
||||
|
@ -830,7 +830,7 @@ describe('di', () => {
|
|||
TestBed.configureTestingModule(
|
||||
{declarations: [DirectiveA, DirectiveB, MyComp, MyApp]});
|
||||
expect(() => TestBed.createComponent(MyApp))
|
||||
.toThrowError(/No provider for DirectiveB found in NodeInjector/);
|
||||
.toThrowError(/NG0201: No provider for DirectiveB found in NodeInjector/);
|
||||
});
|
||||
|
||||
onlyInIvy('Ivy has different error message when dependency is not found')
|
||||
|
@ -855,7 +855,7 @@ describe('di', () => {
|
|||
expect(() => {
|
||||
fixture.componentInstance.myComp.showing = true;
|
||||
fixture.detectChanges();
|
||||
}).toThrowError(/No provider for DirectiveB found in NodeInjector/);
|
||||
}).toThrowError(/NG0201: No provider for DirectiveB found in NodeInjector/);
|
||||
});
|
||||
|
||||
it('should find providers across embedded views if not passing component boundary', () => {
|
||||
|
@ -894,7 +894,7 @@ describe('di', () => {
|
|||
|
||||
TestBed.configureTestingModule({declarations: [DirectiveComp, MyComp, MyApp]});
|
||||
expect(() => TestBed.createComponent(MyApp))
|
||||
.toThrowError('No provider for MyApp found in NodeInjector');
|
||||
.toThrowError('NG0201: No provider for MyApp found in NodeInjector');
|
||||
});
|
||||
|
||||
describe('regression', () => {
|
||||
|
|
|
@ -38,6 +38,9 @@
|
|||
{
|
||||
"name": "NodeInjectorFactory"
|
||||
},
|
||||
{
|
||||
"name": "RuntimeError"
|
||||
},
|
||||
{
|
||||
"name": "SimpleChange"
|
||||
},
|
||||
|
|
|
@ -521,6 +521,9 @@
|
|||
{
|
||||
"name": "RootViewRef"
|
||||
},
|
||||
{
|
||||
"name": "RuntimeError"
|
||||
},
|
||||
{
|
||||
"name": "SCHEDULER"
|
||||
},
|
||||
|
|
|
@ -32,6 +32,9 @@
|
|||
{
|
||||
"name": "NodeInjectorFactory"
|
||||
},
|
||||
{
|
||||
"name": "RuntimeError"
|
||||
},
|
||||
{
|
||||
"name": "SimpleChange"
|
||||
},
|
||||
|
|
|
@ -671,6 +671,9 @@
|
|||
{
|
||||
"name": "RoutesRecognized"
|
||||
},
|
||||
{
|
||||
"name": "RuntimeError"
|
||||
},
|
||||
{
|
||||
"name": "SAFE_URL_PATTERN"
|
||||
},
|
||||
|
|
|
@ -89,6 +89,9 @@
|
|||
{
|
||||
"name": "RecordViewTuple"
|
||||
},
|
||||
{
|
||||
"name": "RuntimeError"
|
||||
},
|
||||
{
|
||||
"name": "SWITCH_ELEMENT_REF_FACTORY"
|
||||
},
|
||||
|
|
|
@ -1024,7 +1024,7 @@ function declareTests(config?: {useJit: boolean}) {
|
|||
});
|
||||
|
||||
it('should throw when trying to instantiate a cyclic dependency', () => {
|
||||
let errorMessage = ivyEnabled ? /Circular dependency in DI detected for Car/g :
|
||||
let errorMessage = ivyEnabled ? /NG0200: Circular dependency in DI detected for Car/g :
|
||||
/Cannot instantiate cyclic dependency! Car/g;
|
||||
expect(() => createInjector([Car, {provide: Engine, useClass: CyclicEngine}]).get(Car))
|
||||
.toThrowError(errorMessage);
|
||||
|
|
|
@ -628,7 +628,7 @@ describe('View injector', () => {
|
|||
.it('should not instantiate a directive with cyclic dependencies', () => {
|
||||
TestBed.configureTestingModule({declarations: [CycleDirective]});
|
||||
expect(() => createComponent('<div cycleDirective></div>'))
|
||||
.toThrowError('Circular dependency in DI detected for CycleDirective');
|
||||
.toThrowError('NG0200: Circular dependency in DI detected for CycleDirective');
|
||||
});
|
||||
|
||||
obsoleteInIvy('This error is no longer generated by the compiler')
|
||||
|
@ -661,7 +661,7 @@ describe('View injector', () => {
|
|||
SimpleComponent, {set: {template: '<div needsServiceFromHost><div>'}});
|
||||
|
||||
expect(() => createComponent('<div simpleComponent></div>'))
|
||||
.toThrowError('No provider for service found in NodeInjector');
|
||||
.toThrowError('NG0201: No provider for service found in NodeInjector');
|
||||
});
|
||||
|
||||
obsoleteInIvy('This error is no longer generated by the compiler')
|
||||
|
@ -694,7 +694,7 @@ describe('View injector', () => {
|
|||
SimpleComponent, {set: {template: '<div needsServiceFromHost><div>'}});
|
||||
|
||||
expect(() => createComponent('<div simpleComponent someOtherDirective></div>'))
|
||||
.toThrowError('No provider for service found in NodeInjector');
|
||||
.toThrowError('NG0201: No provider for service found in NodeInjector');
|
||||
});
|
||||
|
||||
obsoleteInIvy('This error is no longer generated by the compiler')
|
||||
|
@ -717,7 +717,7 @@ describe('View injector', () => {
|
|||
expect(
|
||||
() => createComponent(
|
||||
'<div simpleDirective><div needsDirectiveFromSelf></div></div>'))
|
||||
.toThrowError('No provider for SimpleDirective found in NodeInjector');
|
||||
.toThrowError('NG0201: No provider for SimpleDirective found in NodeInjector');
|
||||
});
|
||||
|
||||
it('should instantiate directives that depend on other directives', fakeAsync(() => {
|
||||
|
@ -779,7 +779,7 @@ describe('View injector', () => {
|
|||
TestBed.overrideComponent(
|
||||
SimpleComponent, {set: {template: '<div needsDirectiveFromHost></div>'}});
|
||||
expect(() => createComponent('<div simpleComponent simpleDirective></div>'))
|
||||
.toThrowError('No provider for SimpleDirective found in NodeInjector');
|
||||
.toThrowError('NG0201: No provider for SimpleDirective found in NodeInjector');
|
||||
});
|
||||
|
||||
it('should allow to use the NgModule injector from a root ViewContainerRef.parentInjector',
|
||||
|
|
|
@ -107,7 +107,7 @@ describe('di', () => {
|
|||
(DirA as any)['__NG_ELEMENT_ID__'] = 1;
|
||||
(DirC as any)['__NG_ELEMENT_ID__'] = 257;
|
||||
new ComponentFixture(App);
|
||||
}).toThrowError('No provider for DirB found in NodeInjector');
|
||||
}).toThrowError('NG0201: No provider for DirB found in NodeInjector');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue