Skip to content

Commit 0b68312

Browse files
vicbmatsko
authored andcommitted
refactor(ivy): assertion (angular#22189)
Encourage the use of message to explain the assertion PR Close angular#22189
1 parent 363498b commit 0b68312

File tree

6 files changed

+77
-83
lines changed

6 files changed

+77
-83
lines changed

packages/core/src/render3/assert.ts

Lines changed: 31 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -10,58 +10,48 @@
1010
// about state in an instruction are correct before implementing any logic.
1111
// They are meant only to be called in dev mode as sanity checks.
1212

13-
/**
14-
* Stringifies values such that strings are wrapped in explicit quotation marks and
15-
* other types are stringified normally. Used in error messages (e.g. assertThrow)
16-
* to make it clear that certain values are of the string type when comparing.
17-
*
18-
* e.g. `expected "3" to be 3` is easier to understand than `expected 3 to be 3`.
19-
*
20-
* @param value The value to be stringified
21-
* @returns The stringified value
22-
*/
23-
function stringifyValueForError(value: any): string {
24-
if (value && value.native && value.native.outerHTML) {
25-
return value.native.outerHTML;
13+
export function assertNumber(actual: any, msg: string) {
14+
if (typeof actual != 'number') {
15+
throwError(msg);
2616
}
27-
return typeof value === 'string' ? `"${value}"` : value;
2817
}
2918

30-
export function assertNumber(actual: any, name: string) {
31-
(typeof actual != 'number') && assertThrow(actual, 'number', name, 'typeof ==');
19+
export function assertEqual<T>(actual: T, expected: T, msg: string) {
20+
if (actual != expected) {
21+
throwError(msg);
22+
}
3223
}
3324

34-
export function assertEqual<T>(
35-
actual: T, expected: T, name: string, serializer?: ((v: T) => string)) {
36-
(actual != expected) && assertThrow(actual, expected, name, '==', serializer);
25+
export function assertNotEqual<T>(actual: T, expected: T, msg: string) {
26+
if (actual == expected) {
27+
throwError(msg);
28+
}
3729
}
3830

39-
export function assertLessThan<T>(actual: T, expected: T, name: string) {
40-
(actual >= expected) && assertThrow(actual, expected, name, '<');
31+
export function assertSame<T>(actual: T, expected: T, msg: string) {
32+
if (actual !== expected) {
33+
throwError(msg);
34+
}
4135
}
4236

43-
export function assertNotNull<T>(actual: T, name: string) {
44-
assertNotEqual(actual, null, name);
37+
export function assertLessThan<T>(actual: T, expected: T, msg: string) {
38+
if (actual >= expected) {
39+
throwError(msg);
40+
}
4541
}
4642

47-
export function assertNotEqual<T>(actual: T, expected: T, name: string) {
48-
(actual == expected) && assertThrow(actual, expected, name, '!=');
43+
export function assertNull<T>(actual: T, msg: string) {
44+
if (actual != null) {
45+
throwError(msg);
46+
}
4947
}
5048

51-
/**
52-
* Throws an error with a message constructed from the arguments.
53-
*
54-
* @param actual The actual value (e.g. 3)
55-
* @param expected The expected value (e.g. 5)
56-
* @param name The name of the value being checked (e.g. attrs.length)
57-
* @param operator The comparison operator (e.g. <, >, ==)
58-
* @param serializer Function that maps a value to its display value
59-
*/
60-
export function assertThrow<T>(
61-
actual: T, expected: T, name: string, operator: string,
62-
serializer: ((v: T) => string) = stringifyValueForError): never {
63-
const error =
64-
`ASSERT: expected ${name} ${operator} ${serializer(expected)} but was ${serializer(actual)}!`;
65-
debugger; // leave `debugger` here to aid in debugging.
66-
throw new Error(error);
49+
export function assertNotNull<T>(actual: T, msg: string) {
50+
if (actual == null) {
51+
throwError(msg);
52+
}
6753
}
54+
55+
function throwError(msg: string): never {
56+
throw new Error(`ASSERTION ERROR: ${msg}`);
57+
}

packages/core/src/render3/component.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -189,20 +189,20 @@ export function renderComponent<T>(
189189
}
190190

191191
export function detectChanges<T>(component: T) {
192-
ngDevMode && assertNotNull(component, 'component');
192+
ngDevMode && assertNotNull(component, 'detectChanges should be called with a component');
193193
const hostNode = (component as any)[NG_HOST_SYMBOL] as LElementNode;
194194
if (ngDevMode && !hostNode) {
195195
createError('Not a directive instance', component);
196196
}
197-
ngDevMode && assertNotNull(hostNode.data, 'hostNode.data');
197+
ngDevMode && assertNotNull(hostNode.data, 'Component host node should be attached to an LView');
198198
renderComponentOrTemplate(hostNode, hostNode.view, component);
199199
isDirty = false;
200200
}
201201

202202
let isDirty = false;
203203
export function markDirty<T>(
204204
component: T, scheduler: (fn: () => void) => void = requestAnimationFrame) {
205-
ngDevMode && assertNotNull(component, 'component');
205+
ngDevMode && assertNotNull(component, 'markDirty should be called with a component');
206206
if (!isDirty) {
207207
isDirty = true;
208208
scheduler(() => detectChanges(component));

packages/core/src/render3/instructions.ts

Lines changed: 32 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
import './ng_dev_mode';
1010

11-
import {assertEqual, assertLessThan, assertNotEqual, assertNotNull} from './assert';
11+
import {assertEqual, assertLessThan, assertNotEqual, assertNotNull, assertNull, assertSame} from './assert';
1212
import {LContainer, TContainer} from './interfaces/container';
1313
import {CssSelector, LProjection} from './interfaces/projection';
1414
import {LQueries} from './interfaces/query';
@@ -233,7 +233,7 @@ export function createLNode(
233233
if ((type & LNodeFlags.ViewOrElement) === LNodeFlags.ViewOrElement && isState) {
234234
// Bit of a hack to bust through the readonly because there is a circular dep between
235235
// LView and LNode.
236-
ngDevMode && assertEqual((state as LView).node, null, 'lView.node');
236+
ngDevMode && assertNull((state as LView).node, 'LView.node should not have been initialized');
237237
(state as LView as{node: LNode}).node = node;
238238
}
239239
if (index != null) {
@@ -254,13 +254,17 @@ export function createLNode(
254254
if (previousOrParentNode.view === currentView ||
255255
(previousOrParentNode.flags & LNodeFlags.TYPE_MASK) === LNodeFlags.View) {
256256
// We are in the same view, which means we are adding content node to the parent View.
257-
ngDevMode && assertEqual(previousOrParentNode.child, null, 'previousNode.child');
257+
ngDevMode && assertNull(
258+
previousOrParentNode.child,
259+
`previousOrParentNode's child should not have been set.`);
258260
previousOrParentNode.child = node;
259261
} else {
260262
// We are adding component view, so we don't link parent node child to this node.
261263
}
262264
} else if (previousOrParentNode) {
263-
ngDevMode && assertEqual(previousOrParentNode.next, null, 'previousNode.next');
265+
ngDevMode && assertNull(
266+
previousOrParentNode.next,
267+
`previousOrParentNode's next property should not have been set.`);
264268
previousOrParentNode.next = node;
265269
}
266270
}
@@ -300,7 +304,7 @@ export function renderTemplate<T>(
300304
-1, providedRendererFactory.createRenderer(null, null), getOrCreateTView(template)));
301305
}
302306
const hostView = host.data !;
303-
ngDevMode && assertNotEqual(hostView, null, 'hostView');
307+
ngDevMode && assertNotNull(hostView, 'Host node should have an LView defined in host.data.');
304308
renderComponentOrTemplate(host, hostView, context, template);
305309
return host;
306310
}
@@ -381,7 +385,8 @@ export function elementStart(
381385
const node = data[index] !;
382386
native = node && (node as LElementNode).native;
383387
} else {
384-
ngDevMode && assertEqual(currentView.bindingStartIndex, null, 'bindingStartIndex');
388+
ngDevMode &&
389+
assertNull(currentView.bindingStartIndex, 'elements should be created before any bindings');
385390
const isHostElement = typeof nameOrComponentType !== 'string';
386391
// MEGAMORPHIC: `ngComponentDef` is a megamorphic property access here.
387392
// This is OK, since we will refactor this code and store the result in `TView.data`
@@ -504,7 +509,7 @@ export function createTView(): TView {
504509
}
505510

506511
function setUpAttributes(native: RElement, attrs: string[]): void {
507-
ngDevMode && assertEqual(attrs.length % 2, 0, 'attrs.length % 2');
512+
ngDevMode && assertEqual(attrs.length % 2, 0, 'each attribute should have a key and a value');
508513

509514
const isProc = isProceduralRenderer(renderer);
510515
for (let i = 0; i < attrs.length; i += 2) {
@@ -809,7 +814,8 @@ export function elementStyle<T>(
809814
* If value is not provided than the actual creation of the text node is delayed.
810815
*/
811816
export function text(index: number, value?: any): void {
812-
ngDevMode && assertEqual(currentView.bindingStartIndex, null, 'bindingStartIndex');
817+
ngDevMode &&
818+
assertNull(currentView.bindingStartIndex, 'text nodes should be created before bindings');
813819
const textNode = value != null ?
814820
(isProceduralRenderer(renderer) ? renderer.createText(stringify(value)) :
815821
renderer.createTextNode(stringify(value))) :
@@ -865,7 +871,8 @@ export function textBinding<T>(index: number, value: T | NO_CHANGE): void {
865871
export function directiveCreate<T>(
866872
index: number, directive: T, directiveDef: DirectiveDef<T>, queryName?: string | null): T {
867873
let instance;
868-
ngDevMode && assertEqual(currentView.bindingStartIndex, null, 'bindingStartIndex');
874+
ngDevMode &&
875+
assertNull(currentView.bindingStartIndex, 'directives should be created before any bindings');
869876
ngDevMode && assertPreviousIsParent();
870877
let flags = previousOrParentNode !.flags;
871878
let size = flags & LNodeFlags.SIZE_MASK;
@@ -984,10 +991,12 @@ function generateInitialInputs(
984991
export function container(
985992
index: number, directiveTypes?: DirectiveType<any>[], template?: ComponentTemplate<any>,
986993
tagName?: string, attrs?: string[], localRefs?: string[] | null): void {
987-
ngDevMode && assertEqual(currentView.bindingStartIndex, null, 'bindingStartIndex');
994+
ngDevMode &&
995+
assertNull(
996+
currentView.bindingStartIndex, 'container nodes should be created before any bindings');
988997

989998
const currentParent = isParent ? previousOrParentNode : previousOrParentNode.parent !;
990-
ngDevMode && assertNotEqual(currentParent, null, 'currentParent');
999+
ngDevMode && assertNotNull(currentParent, 'containers should have a parent');
9911000

9921001
const lContainer = <LContainer>{
9931002
views: [],
@@ -1037,9 +1046,9 @@ export function containerRefreshStart(index: number): void {
10371046
ngDevMode && assertNodeType(previousOrParentNode, LNodeFlags.Container);
10381047
isParent = true;
10391048
(previousOrParentNode as LContainerNode).data.nextIndex = 0;
1040-
ngDevMode && assertEqual(
1041-
(previousOrParentNode as LContainerNode).native === undefined, true,
1042-
'previousOrParentNode.native === undefined');
1049+
ngDevMode && assertSame(
1050+
(previousOrParentNode as LContainerNode).native, undefined,
1051+
`the container's native element should not have been set yet.`);
10431052

10441053
// We need to execute init hooks here so ngOnInit hooks are called in top level views
10451054
// before they are called in embedded views (for backwards compatibility).
@@ -1181,11 +1190,11 @@ export function componentRefresh<T>(directiveIndex: number, elementIndex: number
11811190
ngDevMode && assertDataInRange(elementIndex);
11821191
const element = data ![elementIndex] as LElementNode;
11831192
ngDevMode && assertNodeType(element, LNodeFlags.Element);
1184-
ngDevMode && assertNotEqual(element.data, null, 'isComponent');
1193+
ngDevMode &&
1194+
assertNotNull(element.data, `Component's host node should have an LView attached.`);
11851195
ngDevMode && assertDataInRange(directiveIndex);
11861196
const directive = getDirectiveInstance<T>(data[directiveIndex]);
11871197
const hostView = element.data !;
1188-
ngDevMode && assertNotEqual(hostView, null, 'hostView');
11891198
const oldView = enterView(hostView, element);
11901199
try {
11911200
template(directive, creationMode);
@@ -1243,9 +1252,9 @@ function appendToProjectionNode(
12431252
projectionNode: LProjectionNode,
12441253
appendedFirst: LElementNode | LTextNode | LContainerNode | null,
12451254
appendedLast: LElementNode | LTextNode | LContainerNode | null) {
1246-
// appendedFirst can be null if and only if appendedLast is also null
1247-
ngDevMode &&
1248-
assertEqual(!appendedFirst === !appendedLast, true, '!appendedFirst === !appendedLast');
1255+
ngDevMode && assertEqual(
1256+
!!appendedFirst, !!appendedLast,
1257+
'appendedFirst can be null if and only if appendedLast is also null');
12491258
if (!appendedLast) {
12501259
// nothing to append
12511260
return;
@@ -1760,18 +1769,18 @@ export function getDirectiveInstance<T>(instanceOrArray: T | [T]): T {
17601769
}
17611770

17621771
export function assertPreviousIsParent() {
1763-
assertEqual(isParent, true, 'isParent');
1772+
assertEqual(isParent, true, 'previousOrParentNode should be a parent');
17641773
}
17651774

17661775
function assertHasParent() {
1767-
assertNotEqual(previousOrParentNode.parent, null, 'isParent');
1776+
assertNotNull(previousOrParentNode.parent, 'previousOrParentNode should have a parent');
17681777
}
17691778

17701779
function assertDataInRange(index: number, arr?: any[]) {
17711780
if (arr == null) arr = data;
1772-
assertLessThan(index, arr ? arr.length : 0, 'data.length');
1781+
assertLessThan(index, arr ? arr.length : 0, 'index expected to be a valid data index');
17731782
}
17741783

17751784
function assertDataNext(index: number) {
1776-
assertEqual(data.length, index, 'data.length not in sequence');
1785+
assertEqual(data.length, index, 'index expected to be at the end of data');
17771786
}

packages/core/src/render3/node_assert.ts

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,30 +6,25 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9-
import {assertEqual, assertNotEqual} from './assert';
9+
import {assertEqual, assertNotNull} from './assert';
1010
import {LNode, LNodeFlags} from './interfaces/node';
1111

1212
export function assertNodeType(node: LNode, type: LNodeFlags) {
13-
assertNotEqual(node, null, 'node');
14-
assertEqual(node.flags & LNodeFlags.TYPE_MASK, type, 'Node.type', typeSerializer);
13+
assertNotNull(node, 'should be called with a node');
14+
assertEqual(node.flags & LNodeFlags.TYPE_MASK, type, `should be a ${typeName(type)}`);
1515
}
1616

1717
export function assertNodeOfPossibleTypes(node: LNode, ...types: LNodeFlags[]) {
18-
assertNotEqual(node, null, 'node');
19-
const nodeType = (node.flags & LNodeFlags.TYPE_MASK);
20-
for (let i = 0; i < types.length; i++) {
21-
if (nodeType === types[i]) {
22-
return;
23-
}
24-
}
25-
throw new Error(
26-
`Expected node of possible types: ${types.map(typeSerializer).join(', ')} but got ${typeSerializer(nodeType)}`);
18+
assertNotNull(node, 'should be called with a node');
19+
const nodeType = node.flags & LNodeFlags.TYPE_MASK;
20+
const found = types.some(type => nodeType === type);
21+
assertEqual(found, true, `Should be one of ${types.map(typeName).join(', ')}`);
2722
}
2823

29-
function typeSerializer(type: LNodeFlags): string {
24+
function typeName(type: LNodeFlags): string {
3025
if (type == LNodeFlags.Projection) return 'Projection';
3126
if (type == LNodeFlags.Container) return 'Container';
3227
if (type == LNodeFlags.View) return 'View';
3328
if (type == LNodeFlags.Element) return 'Element';
34-
return '??? ' + type + ' ???';
29+
return '<unknown>';
3530
}

packages/core/src/render3/node_selector_matcher.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ function isCssClassMatching(nodeClassAttrVal: string, cssClassToMatch: string):
3737
*/
3838
export function isNodeMatchingSimpleSelector(tNode: TNode, selector: SimpleCssSelector): boolean {
3939
const noOfSelectorParts = selector.length;
40-
ngDevMode && assertNotNull(selector[0], 'selector[0]');
40+
ngDevMode && assertNotNull(selector[0], 'the selector should have a tag name');
4141
const tagNameInSelector = selector[0];
4242

4343
// check tag tame

packages/core/src/render3/query.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,7 @@ function add(query: LQuery<any>| null, node: LNode) {
245245
if (directiveIdx !== null) {
246246
// a node is matching a predicate - determine what to read
247247
// note that queries using name selector must specify read strategy
248-
ngDevMode && assertNotNull(predicate.read, 'predicate.read');
248+
ngDevMode && assertNotNull(predicate.read, 'the node should have a predicate');
249249
const result = readFromNodeInjector(nodeInjector, node, predicate.read !, directiveIdx);
250250
if (result !== null) {
251251
addMatch(query, result);

0 commit comments

Comments
 (0)