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
This commit is contained in:
twerske 2021-01-15 11:05:03 -08:00 committed by Andrew Kushnir
parent 0568c7387f
commit bfdca0b87f
5 changed files with 54 additions and 12 deletions

View File

@ -63,9 +63,11 @@ describe('Ivy NgModule', () => {
} }
const errorCode = ivyEnabled ? 'NG0200: ' : ''; const errorCode = ivyEnabled ? 'NG0200: ' : '';
const errorLink = ivyEnabled ? '. Find more at https://angular.io/errors/NG0200' : '';
expect(() => createInjector(AModule)) expect(() => createInjector(AModule))
.toThrowError(`${ .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', () => { it('merges imports and exports', () => {

View File

@ -6,6 +6,11 @@
* found in the LICENSE file at https://angular.io/license * 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 { export const enum RuntimeErrorCode {
// Internal Errors // 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 */ /** Called to format a runtime error */
export function formatRuntimeError(code: RuntimeErrorCode, message: string): string { export function formatRuntimeError(code: RuntimeErrorCode, message: string): string {
const fullCode = code ? `NG0${code}: ` : ''; 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;
} }

View File

@ -617,7 +617,8 @@ describe('di', () => {
TestBed.configureTestingModule({declarations: [DirectiveA, DirectiveB, MyComp]}); TestBed.configureTestingModule({declarations: [DirectiveA, DirectiveB, MyComp]});
expect(() => TestBed.createComponent(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') onlyInIvy('Ivy has different error message for circular dependency')
@ -633,7 +634,8 @@ describe('di', () => {
TestBed.configureTestingModule({declarations: [DirectiveA, DirectiveB, MyComp]}); TestBed.configureTestingModule({declarations: [DirectiveA, DirectiveB, MyComp]});
expect(() => TestBed.createComponent(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', () => { describe('flags', () => {
@ -1779,7 +1781,8 @@ describe('di', () => {
TestBed.configureTestingModule({declarations: [DirectiveString, MyComp, MyApp]}); TestBed.configureTestingModule({declarations: [DirectiveString, MyComp, MyApp]});
expect(() => TestBed.createComponent(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') onlyInIvy('Ivy has different error message when dependency is not found')
@ -1859,7 +1862,8 @@ describe('di', () => {
TestBed.configureTestingModule({declarations: [DirectiveComp, MyComp, MyApp]}); TestBed.configureTestingModule({declarations: [DirectiveComp, MyComp, MyApp]});
expect(() => TestBed.createComponent(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', () => { describe('regression', () => {

View File

@ -628,7 +628,8 @@ describe('View injector', () => {
.it('should not instantiate a directive with cyclic dependencies', () => { .it('should not instantiate a directive with cyclic dependencies', () => {
TestBed.configureTestingModule({declarations: [CycleDirective]}); TestBed.configureTestingModule({declarations: [CycleDirective]});
expect(() => createComponent('<div cycleDirective></div>')) expect(() => createComponent('<div cycleDirective></div>'))
.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') obsoleteInIvy('This error is no longer generated by the compiler')
@ -661,7 +662,8 @@ describe('View injector', () => {
SimpleComponent, {set: {template: '<div needsServiceFromHost><div>'}}); SimpleComponent, {set: {template: '<div needsServiceFromHost><div>'}});
expect(() => createComponent('<div simpleComponent></div>')) expect(() => createComponent('<div simpleComponent></div>'))
.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') obsoleteInIvy('This error is no longer generated by the compiler')
@ -694,7 +696,8 @@ describe('View injector', () => {
SimpleComponent, {set: {template: '<div needsServiceFromHost><div>'}}); SimpleComponent, {set: {template: '<div needsServiceFromHost><div>'}});
expect(() => createComponent('<div simpleComponent someOtherDirective></div>')) expect(() => createComponent('<div simpleComponent someOtherDirective></div>'))
.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') obsoleteInIvy('This error is no longer generated by the compiler')
@ -717,7 +720,8 @@ describe('View injector', () => {
expect( expect(
() => createComponent( () => createComponent(
'<div simpleDirective><div needsDirectiveFromSelf></div></div>')) '<div simpleDirective><div needsDirectiveFromSelf></div></div>'))
.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(() => { it('should instantiate directives that depend on other directives', fakeAsync(() => {
@ -779,7 +783,8 @@ describe('View injector', () => {
TestBed.overrideComponent( TestBed.overrideComponent(
SimpleComponent, {set: {template: '<div needsDirectiveFromHost></div>'}}); SimpleComponent, {set: {template: '<div needsDirectiveFromHost></div>'}});
expect(() => createComponent('<div simpleComponent simpleDirective></div>')) expect(() => createComponent('<div simpleComponent simpleDirective></div>'))
.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', it('should allow to use the NgModule injector from a root ViewContainerRef.parentInjector',

View File

@ -107,7 +107,9 @@ describe('di', () => {
(DirA as any)['__NG_ELEMENT_ID__'] = 1; (DirA as any)['__NG_ELEMENT_ID__'] = 1;
(DirC as any)['__NG_ELEMENT_ID__'] = 257; (DirC as any)['__NG_ELEMENT_ID__'] = 257;
new ComponentFixture(App); 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');
}); });
}); });
}); });