diff --git a/aio/content/guide/dependency-injection-navtree.md b/aio/content/guide/dependency-injection-navtree.md index 3eff1da2c2..7bac02dd8c 100644 --- a/aio/content/guide/dependency-injection-navtree.md +++ b/aio/content/guide/dependency-injection-navtree.md @@ -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. diff --git a/goldens/size-tracking/aio-payloads.json b/goldens/size-tracking/aio-payloads.json index 0e7403b502..3cefa0c90f 100755 --- a/goldens/size-tracking/aio-payloads.json +++ b/goldens/size-tracking/aio-payloads.json @@ -12,7 +12,7 @@ "master": { "uncompressed": { "runtime-es2015": 3037, - "main-es2015": 448085, + "main-es2015": 448615, "polyfills-es2015": 52415 } } diff --git a/packages/compiler-cli/integrationtest/bazel/injector_def/ivy_build/app/test/module_spec.ts b/packages/compiler-cli/integrationtest/bazel/injector_def/ivy_build/app/test/module_spec.ts index c9dfbe4a92..24ff406205 100644 --- a/packages/compiler-cli/integrationtest/bazel/injector_def/ivy_build/app/test/module_spec.ts +++ b/packages/compiler-cli/integrationtest/bazel/injector_def/ivy_build/app/test/module_spec.ts @@ -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', () => { diff --git a/packages/core/src/render3/error_code.ts b/packages/core/src/render3/error_code.ts new file mode 100644 index 0000000000..b68d111b07 --- /dev/null +++ b/packages/core/src/render3/error_code.ts @@ -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}`; +} diff --git a/packages/core/src/render3/errors.ts b/packages/core/src/render3/errors.ts index 2ffd70fd35..01c66935ca 100644 --- a/packages/core/src/render3/errors.ts +++ b/packages/core/src/render3/errors.ts @@ -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}`); } diff --git a/packages/core/src/render3/instructions/element.ts b/packages/core/src/render3/instructions/element.ts index b03b4fdbf8..8c2fe67703 100644 --- a/packages/core/src/render3/instructions/element.ts +++ b/packages/core/src/render3/instructions/element.ts @@ -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)); } } } diff --git a/packages/core/src/render3/instructions/shared.ts b/packages/core/src/render3/instructions/shared.ts index 024dda0334..294dc3e977 100644 --- a/packages/core/src/render3/instructions/shared.ts +++ b/packages/core/src/render3/instructions/shared.ts @@ -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); } } diff --git a/packages/core/src/render3/pipe.ts b/packages/core/src/render3/pipe.ts index 0d33ad41f1..e9fb78a1ae 100644 --- a/packages/core/src/render3/pipe.ts +++ b/packages/core/src/render3/pipe.ts @@ -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 { } } } - throw new Error(`The pipe '${name}' could not be found!`); + throw new RuntimeError(RuntimeErrorCode.PIPE_NOT_FOUND, `The pipe '${name}' could not be found!`); } /** diff --git a/packages/core/test/acceptance/di_spec.ts b/packages/core/test/acceptance/di_spec.ts index 8bc0206a56..768fbf5827 100644 --- a/packages/core/test/acceptance/di_spec.ts +++ b/packages/core/test/acceptance/di_spec.ts @@ -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', () => { diff --git a/packages/core/test/bundling/cyclic_import/bundle.golden_symbols.json b/packages/core/test/bundling/cyclic_import/bundle.golden_symbols.json index 0d1b098faa..cc440fb4aa 100644 --- a/packages/core/test/bundling/cyclic_import/bundle.golden_symbols.json +++ b/packages/core/test/bundling/cyclic_import/bundle.golden_symbols.json @@ -38,6 +38,9 @@ { "name": "NodeInjectorFactory" }, + { + "name": "RuntimeError" + }, { "name": "SimpleChange" }, diff --git a/packages/core/test/bundling/forms/bundle.golden_symbols.json b/packages/core/test/bundling/forms/bundle.golden_symbols.json index 4333990670..829ac9a090 100644 --- a/packages/core/test/bundling/forms/bundle.golden_symbols.json +++ b/packages/core/test/bundling/forms/bundle.golden_symbols.json @@ -521,6 +521,9 @@ { "name": "RootViewRef" }, + { + "name": "RuntimeError" + }, { "name": "SCHEDULER" }, diff --git a/packages/core/test/bundling/hello_world/bundle.golden_symbols.json b/packages/core/test/bundling/hello_world/bundle.golden_symbols.json index 93d584fe53..dd2c07f81c 100644 --- a/packages/core/test/bundling/hello_world/bundle.golden_symbols.json +++ b/packages/core/test/bundling/hello_world/bundle.golden_symbols.json @@ -32,6 +32,9 @@ { "name": "NodeInjectorFactory" }, + { + "name": "RuntimeError" + }, { "name": "SimpleChange" }, diff --git a/packages/core/test/bundling/router/bundle.golden_symbols.json b/packages/core/test/bundling/router/bundle.golden_symbols.json index ab1de47fc9..3757fd55ed 100644 --- a/packages/core/test/bundling/router/bundle.golden_symbols.json +++ b/packages/core/test/bundling/router/bundle.golden_symbols.json @@ -671,6 +671,9 @@ { "name": "RoutesRecognized" }, + { + "name": "RuntimeError" + }, { "name": "SAFE_URL_PATTERN" }, diff --git a/packages/core/test/bundling/todo/bundle.golden_symbols.json b/packages/core/test/bundling/todo/bundle.golden_symbols.json index 55f744fd90..197a374cf8 100644 --- a/packages/core/test/bundling/todo/bundle.golden_symbols.json +++ b/packages/core/test/bundling/todo/bundle.golden_symbols.json @@ -89,6 +89,9 @@ { "name": "RecordViewTuple" }, + { + "name": "RuntimeError" + }, { "name": "SWITCH_ELEMENT_REF_FACTORY" }, diff --git a/packages/core/test/linker/ng_module_integration_spec.ts b/packages/core/test/linker/ng_module_integration_spec.ts index d57678cd26..d3dc195b99 100644 --- a/packages/core/test/linker/ng_module_integration_spec.ts +++ b/packages/core/test/linker/ng_module_integration_spec.ts @@ -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); diff --git a/packages/core/test/linker/view_injector_integration_spec.ts b/packages/core/test/linker/view_injector_integration_spec.ts index cee9d4ec50..ecb9b2ab5b 100644 --- a/packages/core/test/linker/view_injector_integration_spec.ts +++ b/packages/core/test/linker/view_injector_integration_spec.ts @@ -628,7 +628,7 @@ describe('View injector', () => { .it('should not instantiate a directive with cyclic dependencies', () => { TestBed.configureTestingModule({declarations: [CycleDirective]}); expect(() => createComponent('
')) - .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: '
'}}); expect(() => createComponent('
')) - .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: '
'}}); expect(() => createComponent('
')) - .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( '
')) - .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: '
'}}); expect(() => createComponent('
')) - .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', diff --git a/packages/core/test/render3/di_spec.ts b/packages/core/test/render3/di_spec.ts index 768917188c..32aab952a7 100644 --- a/packages/core/test/render3/di_spec.ts +++ b/packages/core/test/render3/di_spec.ts @@ -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'); }); }); });