From bfdca0b87f3071900e6d099d9e15c1fee7d0bf68 Mon Sep 17 00:00:00 2001 From: twerske Date: Fri, 15 Jan 2021 11:05:03 -0800 Subject: [PATCH] refactor(core): add links to top runtime errors (#40326) add links to 5 runtime error messages navigate user to AIO new /errors pages for debugging PR Close #40326 --- .../ivy_build/app/test/module_spec.ts | 4 ++- packages/core/src/render3/error_code.ts | 31 ++++++++++++++++++- packages/core/test/acceptance/di_spec.ts | 12 ++++--- .../linker/view_injector_integration_spec.ts | 15 ++++++--- packages/core/test/render3/di_spec.ts | 4 ++- 5 files changed, 54 insertions(+), 12 deletions(-) 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 24ff406205..8ac13126d0 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 @@ -63,9 +63,11 @@ describe('Ivy NgModule', () => { } const errorCode = ivyEnabled ? 'NG0200: ' : ''; + const errorLink = ivyEnabled ? '. Find more at https://angular.io/errors/NG0200' : ''; expect(() => createInjector(AModule)) .toThrowError(`${ - errorCode}Circular dependency in DI detected for AModule. Dependency path: AModule > BModule > AModule`); + errorCode}Circular dependency in DI detected for AModule. Dependency path: AModule > BModule > AModule${ + errorLink}`); }); it('merges imports and exports', () => { diff --git a/packages/core/src/render3/error_code.ts b/packages/core/src/render3/error_code.ts index b68d111b07..bedac94a60 100644 --- a/packages/core/src/render3/error_code.ts +++ b/packages/core/src/render3/error_code.ts @@ -6,6 +6,11 @@ * found in the LICENSE file at https://angular.io/license */ +// Base URL for the error details page. +// Keep this value in sync with a similar const in +// `packages/compiler-cli/src/ngtsc/diagnostics/src/error_code.ts`. +const ERROR_DETAILS_PAGE_BASE_URL = 'https://angular.io/errors'; + export const enum RuntimeErrorCode { // Internal Errors @@ -38,8 +43,32 @@ export class RuntimeError extends Error { } } +// Contains a set of error messages that have details guides at angular.io. +// Full list of available error guides can be found at https://angular.io/errors +/* tslint:disable:no-toplevel-property-access */ +export const RUNTIME_ERRORS_WITH_GUIDES = new Set([ + RuntimeErrorCode.EXPRESSION_CHANGED_AFTER_CHECKED, + RuntimeErrorCode.CYCLIC_DI_DEPENDENCY, + RuntimeErrorCode.PROVIDER_NOT_FOUND, + RuntimeErrorCode.MULTIPLE_COMPONENTS_MATCH, + RuntimeErrorCode.EXPORT_NOT_FOUND, +]); +/* tslint:enable:no-toplevel-property-access */ + /** Called to format a runtime error */ export function formatRuntimeError(code: RuntimeErrorCode, message: string): string { const fullCode = code ? `NG0${code}: ` : ''; - return `${fullCode}${message}`; + + let errorMessage = `${fullCode}${message}`; + + // Some runtime errors are still thrown without `ngDevMode` (for example + // `throwProviderNotFoundError`), so we add `ngDevMode` check here to avoid pulling + // `RUNTIME_ERRORS_WITH_GUIDES` symbol into prod bundles. + // TODO: revisit all instances where `RuntimeError` is thrown and see if `ngDevMode` can be added + // there instead to tree-shake more devmode-only code (and eventually remove `ngDevMode` check + // from this code). + if (ngDevMode && RUNTIME_ERRORS_WITH_GUIDES.has(code)) { + errorMessage = `${errorMessage}. Find more at ${ERROR_DETAILS_PAGE_BASE_URL}/NG0${code}`; + } + return errorMessage; } diff --git a/packages/core/test/acceptance/di_spec.ts b/packages/core/test/acceptance/di_spec.ts index 768a5621c1..fe1fe122c8 100644 --- a/packages/core/test/acceptance/di_spec.ts +++ b/packages/core/test/acceptance/di_spec.ts @@ -617,7 +617,8 @@ describe('di', () => { TestBed.configureTestingModule({declarations: [DirectiveA, DirectiveB, MyComp]}); expect(() => TestBed.createComponent(MyComp)) - .toThrowError('NG0200: Circular dependency in DI detected for DirectiveA'); + .toThrowError( + 'NG0200: Circular dependency in DI detected for DirectiveA. Find more at https://angular.io/errors/NG0200'); }); onlyInIvy('Ivy has different error message for circular dependency') @@ -633,7 +634,8 @@ describe('di', () => { TestBed.configureTestingModule({declarations: [DirectiveA, DirectiveB, MyComp]}); expect(() => TestBed.createComponent(MyComp)) - .toThrowError('NG0200: Circular dependency in DI detected for DirectiveA'); + .toThrowError( + 'NG0200: Circular dependency in DI detected for DirectiveA. Find more at https://angular.io/errors/NG0200'); }); describe('flags', () => { @@ -1779,7 +1781,8 @@ describe('di', () => { TestBed.configureTestingModule({declarations: [DirectiveString, MyComp, MyApp]}); expect(() => TestBed.createComponent(MyApp)) - .toThrowError('NG0201: No provider for String found in NodeInjector'); + .toThrowError( + 'NG0201: No provider for String found in NodeInjector. Find more at https://angular.io/errors/NG0201'); }); onlyInIvy('Ivy has different error message when dependency is not found') @@ -1859,7 +1862,8 @@ describe('di', () => { TestBed.configureTestingModule({declarations: [DirectiveComp, MyComp, MyApp]}); expect(() => TestBed.createComponent(MyApp)) - .toThrowError('NG0201: No provider for MyApp found in NodeInjector'); + .toThrowError( + 'NG0201: No provider for MyApp found in NodeInjector. Find more at https://angular.io/errors/NG0201'); }); describe('regression', () => { diff --git a/packages/core/test/linker/view_injector_integration_spec.ts b/packages/core/test/linker/view_injector_integration_spec.ts index ecb9b2ab5b..9e2d88d400 100644 --- a/packages/core/test/linker/view_injector_integration_spec.ts +++ b/packages/core/test/linker/view_injector_integration_spec.ts @@ -628,7 +628,8 @@ describe('View injector', () => { .it('should not instantiate a directive with cyclic dependencies', () => { TestBed.configureTestingModule({declarations: [CycleDirective]}); expect(() => createComponent('
')) - .toThrowError('NG0200: Circular dependency in DI detected for CycleDirective'); + .toThrowError( + 'NG0200: Circular dependency in DI detected for CycleDirective. Find more at https://angular.io/errors/NG0200'); }); obsoleteInIvy('This error is no longer generated by the compiler') @@ -661,7 +662,8 @@ describe('View injector', () => { SimpleComponent, {set: {template: '
'}}); expect(() => createComponent('
')) - .toThrowError('NG0201: No provider for service found in NodeInjector'); + .toThrowError( + 'NG0201: No provider for service found in NodeInjector. Find more at https://angular.io/errors/NG0201'); }); obsoleteInIvy('This error is no longer generated by the compiler') @@ -694,7 +696,8 @@ describe('View injector', () => { SimpleComponent, {set: {template: '
'}}); expect(() => createComponent('
')) - .toThrowError('NG0201: No provider for service found in NodeInjector'); + .toThrowError( + 'NG0201: No provider for service found in NodeInjector. Find more at https://angular.io/errors/NG0201'); }); obsoleteInIvy('This error is no longer generated by the compiler') @@ -717,7 +720,8 @@ describe('View injector', () => { expect( () => createComponent( '
')) - .toThrowError('NG0201: No provider for SimpleDirective found in NodeInjector'); + .toThrowError( + 'NG0201: No provider for SimpleDirective found in NodeInjector. Find more at https://angular.io/errors/NG0201'); }); it('should instantiate directives that depend on other directives', fakeAsync(() => { @@ -779,7 +783,8 @@ describe('View injector', () => { TestBed.overrideComponent( SimpleComponent, {set: {template: '
'}}); expect(() => createComponent('
')) - .toThrowError('NG0201: No provider for SimpleDirective found in NodeInjector'); + .toThrowError( + 'NG0201: No provider for SimpleDirective found in NodeInjector. Find more at https://angular.io/errors/NG0201'); }); 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 32aab952a7..fe97b4d767 100644 --- a/packages/core/test/render3/di_spec.ts +++ b/packages/core/test/render3/di_spec.ts @@ -107,7 +107,9 @@ describe('di', () => { (DirA as any)['__NG_ELEMENT_ID__'] = 1; (DirC as any)['__NG_ELEMENT_ID__'] = 257; new ComponentFixture(App); - }).toThrowError('NG0201: No provider for DirB found in NodeInjector'); + }) + .toThrowError( + 'NG0201: No provider for DirB found in NodeInjector. Find more at https://angular.io/errors/NG0201'); }); }); });