mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
This commit updates the code around the `RuntimeError` class to make it more reusable between packages (currently it's only usable inside the `core` package). Specifically: - the error formatting logic was updated to handle cases when there is no error message provided - there is no special Set that contains a set of error codes for which we have guides on angular.io. Instead, this is now encoded into the error code itself (making such codes negative integers). Having a separate Set makes it non-tree-shakable, which we want to avoid. This change should allow to employ the `RuntimeError` class in other packages to further standardize this subsystem and make the errors thrown by the framework consistent. As a part of the refactoring, the `common` package code was also updated to follow the same logic as `core`, since the `RuntimeError` class was used there as well. PR Close #44398 PR Close #44652
This commit is contained in:
committed by
Andrew Scott
parent
57d5f9529d
commit
c4fbd85acf
@@ -6,7 +6,9 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Directive, DoCheck, Host, Input, Optional, TemplateRef, ViewContainerRef, ɵRuntimeError as RuntimeError, ɵRuntimeErrorCode as RuntimeErrorCode} from '@angular/core';
|
||||
import {Directive, DoCheck, Host, Input, Optional, TemplateRef, ViewContainerRef, ɵRuntimeError as RuntimeError} from '@angular/core';
|
||||
|
||||
import {RuntimeErrorCode} from '../errors';
|
||||
|
||||
export class SwitchView {
|
||||
private _created = false;
|
||||
@@ -243,7 +245,7 @@ export class NgSwitchDefault {
|
||||
|
||||
function throwNgSwitchProviderNotFoundError(attrName: string, directiveName: string): never {
|
||||
throw new RuntimeError(
|
||||
RuntimeErrorCode.TEMPLATE_STRUCTURE_ERROR,
|
||||
RuntimeErrorCode.PARENT_NG_SWITCH_NOT_FOUND,
|
||||
`An element with the "${attrName}" attribute ` +
|
||||
`(matching the "${
|
||||
directiveName}" directive) must be located inside an element with the "ngSwitch" attribute ` +
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
/**
|
||||
* @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
|
||||
*/
|
||||
|
||||
/**
|
||||
* The list of error codes used in runtime code of the `common` package.
|
||||
* Reserved error code range: 2000-2999.
|
||||
*/
|
||||
export const enum RuntimeErrorCode {
|
||||
// NgSwitch errors
|
||||
PARENT_NG_SWITCH_NOT_FOUND = 2000,
|
||||
}
|
||||
@@ -182,7 +182,7 @@ import {expect} from '@angular/platform-browser/testing/src/matchers';
|
||||
|
||||
expect(() => createTestComponent(template))
|
||||
.toThrowError(
|
||||
'NG0305: An element with the "ngSwitchCase" attribute (matching the "NgSwitchCase" directive) must be located inside an element with the "ngSwitch" attribute (matching "NgSwitch" directive)');
|
||||
'NG02000: An element with the "ngSwitchCase" attribute (matching the "NgSwitchCase" directive) must be located inside an element with the "ngSwitch" attribute (matching "NgSwitch" directive)');
|
||||
}));
|
||||
|
||||
it('should throw error when ngSwitchDefault is used outside of ngSwitch', waitForAsync(() => {
|
||||
@@ -191,7 +191,7 @@ import {expect} from '@angular/platform-browser/testing/src/matchers';
|
||||
|
||||
expect(() => createTestComponent(template))
|
||||
.toThrowError(
|
||||
'NG0305: An element with the "ngSwitchDefault" attribute (matching the "NgSwitchDefault" directive) must be located inside an element with the "ngSwitch" attribute (matching "NgSwitch" directive)');
|
||||
'NG02000: An element with the "ngSwitchDefault" attribute (matching the "NgSwitchDefault" directive) must be located inside an element with the "ngSwitch" attribute (matching "NgSwitch" directive)');
|
||||
}));
|
||||
|
||||
it('should support nested NgSwitch on ng-container with ngTemplateOutlet', () => {
|
||||
|
||||
@@ -22,7 +22,7 @@ export {ComponentFactory as ɵComponentFactory} from './linker/component_factory
|
||||
export {clearResolutionOfComponentResourcesQueue as ɵclearResolutionOfComponentResourcesQueue, resolveComponentResources as ɵresolveComponentResources} from './metadata/resource_loading';
|
||||
export {ReflectionCapabilities as ɵReflectionCapabilities} from './reflection/reflection_capabilities';
|
||||
export {GetterFn as ɵGetterFn, MethodFn as ɵMethodFn, SetterFn as ɵSetterFn} from './reflection/types';
|
||||
export {RuntimeError as ɵRuntimeError, RuntimeErrorCode as ɵRuntimeErrorCode} from './render3/error_code';
|
||||
export {RuntimeError as ɵRuntimeError} from './render3/error_code';
|
||||
export {allowSanitizationBypassAndThrow as ɵallowSanitizationBypassAndThrow, BypassType as ɵBypassType, getSanitizationBypassType as ɵgetSanitizationBypassType, SafeHtml as ɵSafeHtml, SafeResourceUrl as ɵSafeResourceUrl, SafeScript as ɵSafeScript, SafeStyle as ɵSafeStyle, SafeUrl as ɵSafeUrl, SafeValue as ɵSafeValue, unwrapSafeValue as ɵunwrapSafeValue} from './sanitization/bypass';
|
||||
export {_sanitizeHtml as ɵ_sanitizeHtml} from './sanitization/html_sanitizer';
|
||||
export {_sanitizeUrl as ɵ_sanitizeUrl} from './sanitization/url_sanitizer';
|
||||
|
||||
@@ -8,32 +8,40 @@
|
||||
|
||||
import {ERROR_DETAILS_PAGE_BASE_URL} from './error_details_base_url';
|
||||
|
||||
/**
|
||||
* The list of error codes used in runtime code of the `core` package.
|
||||
* Reserved error code range: 100-999.
|
||||
*
|
||||
* Note: the minus sign denotes the fact that a particular code has a detailed guide on
|
||||
* angular.io. This extra annotation is needed to avoid introducing a separate set to store
|
||||
* error codes which have guides, which might leak into runtime code.
|
||||
*
|
||||
* Full list of available error guides can be found at https://angular.io/errors.
|
||||
*/
|
||||
export const enum RuntimeErrorCode {
|
||||
// Internal Errors
|
||||
|
||||
// Change Detection Errors
|
||||
EXPRESSION_CHANGED_AFTER_CHECKED = '100',
|
||||
RECURSIVE_APPLICATION_REF_TICK = '101',
|
||||
EXPRESSION_CHANGED_AFTER_CHECKED = -100,
|
||||
RECURSIVE_APPLICATION_REF_TICK = 101,
|
||||
|
||||
// Dependency Injection Errors
|
||||
CYCLIC_DI_DEPENDENCY = '200',
|
||||
PROVIDER_NOT_FOUND = '201',
|
||||
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',
|
||||
TEMPLATE_STRUCTURE_ERROR = '305',
|
||||
MULTIPLE_COMPONENTS_MATCH = -300,
|
||||
EXPORT_NOT_FOUND = -301,
|
||||
PIPE_NOT_FOUND = -302,
|
||||
UNKNOWN_BINDING = 303,
|
||||
UNKNOWN_ELEMENT = 304,
|
||||
TEMPLATE_STRUCTURE_ERROR = 305,
|
||||
|
||||
// Bootstrap Errors
|
||||
MULTIPLE_PLATFORMS = '400',
|
||||
PLATFORM_NOT_FOUND = '401',
|
||||
ERROR_HANDLER_NOT_FOUND = '402',
|
||||
BOOTSTRAP_COMPONENTS_NOT_FOUND = '403',
|
||||
ALREADY_DESTROYED_PLATFORM = '404',
|
||||
ASYNC_INITIALIZERS_STILL_RUNNING = '405',
|
||||
MULTIPLE_PLATFORMS = 400,
|
||||
PLATFORM_NOT_FOUND = 401,
|
||||
ERROR_HANDLER_NOT_FOUND = 402,
|
||||
BOOTSTRAP_COMPONENTS_NOT_FOUND = 403,
|
||||
ALREADY_DESTROYED_PLATFORM = 404,
|
||||
ASYNC_INITIALIZERS_STILL_RUNNING = 405,
|
||||
|
||||
// Styling Errors
|
||||
|
||||
@@ -41,42 +49,26 @@ export const enum RuntimeErrorCode {
|
||||
|
||||
// i18n Errors
|
||||
|
||||
// Compilation Errors
|
||||
// JIT Compilation Errors
|
||||
}
|
||||
|
||||
export class RuntimeError extends Error {
|
||||
constructor(public code: RuntimeErrorCode, message: string) {
|
||||
super(formatRuntimeError(code, message));
|
||||
export class RuntimeError<T = RuntimeErrorCode> extends Error {
|
||||
constructor(public code: T, message: string) {
|
||||
super(formatRuntimeError<T>(code, message));
|
||||
}
|
||||
}
|
||||
|
||||
// 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,
|
||||
RuntimeErrorCode.PIPE_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}: ` : '';
|
||||
export function formatRuntimeError<T = RuntimeErrorCode>(code: T, message: string): string {
|
||||
const codeAsNumber = code as unknown as number;
|
||||
// Error code might be a negative number, which is a special marker that instructs the logic to
|
||||
// generate a link to the error details page on angular.io.
|
||||
const fullCode = `NG0${Math.abs(codeAsNumber)}`;
|
||||
|
||||
let errorMessage = `${fullCode}${message}`;
|
||||
let errorMessage = `${fullCode}${message ? ': ' + 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}`;
|
||||
if (ngDevMode && codeAsNumber < 0) {
|
||||
errorMessage = `${errorMessage}. Find more at ${ERROR_DETAILS_PAGE_BASE_URL}/${fullCode}`;
|
||||
}
|
||||
return errorMessage;
|
||||
}
|
||||
|
||||
@@ -32,8 +32,6 @@ export function throwErrorIfNoChangesMode(
|
||||
` It seems like the view has been created after its parent and its children have been dirty checked.` +
|
||||
` Has it been created in a change detection hook?`;
|
||||
}
|
||||
// TODO: include debug context, see `viewDebugError` function in
|
||||
// `packages/core/src/view/errors.ts` for reference.
|
||||
throw new RuntimeError(RuntimeErrorCode.EXPRESSION_CHANGED_AFTER_CHECKED, msg);
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
*/
|
||||
import {InjectorType} from '../di/interface/defs';
|
||||
import {stringify} from '../util/stringify';
|
||||
|
||||
import {RuntimeError, RuntimeErrorCode} from './error_code';
|
||||
import {stringifyForError} from './util/stringify_utils';
|
||||
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* @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
|
||||
*/
|
||||
|
||||
import {RuntimeError, RuntimeErrorCode} from '../src/render3/error_code';
|
||||
|
||||
describe('RuntimeError utils', () => {
|
||||
it('should format the error message correctly', () => {
|
||||
// Error with a guide, but without an error message.
|
||||
let errorInstance = new RuntimeError(RuntimeErrorCode.EXPORT_NOT_FOUND, '');
|
||||
expect(errorInstance.toString())
|
||||
.toBe('Error: NG0301. Find more at https://angular.io/errors/NG0301');
|
||||
|
||||
// Error without a guide and an error message.
|
||||
errorInstance = new RuntimeError(RuntimeErrorCode.TEMPLATE_STRUCTURE_ERROR, '');
|
||||
expect(errorInstance.toString()).toBe('Error: NG0305');
|
||||
|
||||
// Error without a guide, but with an error message.
|
||||
errorInstance =
|
||||
new RuntimeError(RuntimeErrorCode.TEMPLATE_STRUCTURE_ERROR, 'Some error message');
|
||||
expect(errorInstance.toString()).toBe('Error: NG0305: Some error message');
|
||||
|
||||
// Error with both a guide and an error message.
|
||||
errorInstance = new RuntimeError(RuntimeErrorCode.EXPORT_NOT_FOUND, 'Some error message');
|
||||
expect(errorInstance.toString())
|
||||
.toBe('Error: NG0301: Some error message. Find more at https://angular.io/errors/NG0301');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user