8000 refactor(core): Add injector debug information to `LViewDebug` (#38707) · angular/angular@b613639 · GitHub
[go: up one dir, main page]

Skip to content

Commit b613639

Browse files
committed
refactor(core): Add injector debug information to LViewDebug (#38707)
Extended the `LViewDebug` to display node-injector information for each node. PR Close #38707
1 parent bc6ff77 commit b613639

File tree

7 files changed

+316
-37
lines changed

7 files changed

+316
-37
lines changed

packages/core/src/render3/assert.ts

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

9-
import {assertDefined, assertEqual, throwError} from '../util/assert';
10-
9+
import {assertDefined, assertEqual, assertIndexInRange, assertNumber, throwError} from '../util/assert';
1110
import {getComponentDef, getNgModuleDef} from './definition';
1211
import {LContainer} from './interfaces/container';
1312
import {DirectiveDef} from './interfaces/definition';
13+
import { PARENT_INJECTOR } from './interfaces/injector';
1414
import {TNode} from './interfaces/node';
1515
import {isLContainer, isLView} from './interfaces/type_checks';
16-
import {LView, TVIEW, TView} from './interfaces/view';
16+
import {HEADER_OFFSET, LView, TVIEW, TView} from './interfaces/view';
1717

1818
// [Assert functions do not constraint type when they are guarded by a truthy
1919
// expression.](https://github.com/microsoft/TypeScript/issues/37295)
@@ -96,3 +96,55 @@ export function assertDirectiveDef<T>(obj: any): asserts obj is DirectiveDef<T>
9696
`Expected a DirectiveDef/ComponentDef and this object does not seem to have the expected shape.`);
9797
}
9898
}
99+
100+
export function assertIndexInDeclRange(lView: LView, index: number) {
101+
const tView = lView[1];
102+
assertBetween(HEADER_OFFSET, tView.bindingStartIndex, index);
103+
}
104+
105+
export function assertIndexInVarsRange(lView: LView, index: number) {
106+
const tView = lView[1];
107+
assertBetween(
108+
tView.bindingStartIndex, (tView as any as {i18nStartIndex: number}).i18nStartIndex, index);
109+
}
110+
111+
export function assertIndexInI18nRange(lView: LView, index: number) {
112+
const tView = lView[1];
113+
assertBetween(
114+
(tView as any as {i18nStartIndex: number}).i18nStartIndex, tView.expandoStartIndex, index);
115+
}
116+
117+
export function assertIndexInExpandoRange(lView: LView, index: number) {
118+
const tView = lView[1];
119+
assertBetween(tView.expandoStartIndex, lView.length, index);
120+
}
121+
122+
export function assertBetween(lower: number, upper: number, index: number) {
123+
if (!(lower <= index && index < upper)) {
124+
throwError(`Index out of range (expecting ${lower} <= ${index} < ${upper})`);
125+
}
126+
}
127+
128+
129+
/**
130+
* This is a basic sanity check that the `injectorIndex` seems to point to what looks like a
131+
* NodeInjector data structure.
132+
*
133+
* @param lView `LView` which should be checked.
134+
* @param injectorIndex index into the `LView` where the `NodeInjector` is expected.
135+
*/
136+
export function assertNodeInjector(lView: LView, injectorIndex: number) {
137+
assertIndexInExpandoRange(lView, injectorIndex);
138+
assertIndexInExpandoRange(lView, injectorIndex + PARENT_INJECTOR);
139+
assertNumber(lView[injectorIndex + 0], 'injectorIndex should point to a bloom filter');
140+
assertNumber(lView[injectorIndex + 1], 'injectorIndex should point to a bloom filter');
141+
assertNumber(lView[injectorIndex + 2], 'injectorIndex should point to a bloom filter');
142+
assertNumber(lView[injectorIndex + 3], 'injectorIndex should point to a bloom filter');
143+
assertNumber(lView[injectorIndex + 4], 'injectorIndex should point to a bloom filter');
144+
assertNumber(lView[injectorIndex + 5], 'injectorIndex should point to a bloom filter');
145+
assertNumber(lView[injectorIndex + 6], 'injectorIndex should point to a bloom filter');
146+
assertNumber(lView[injectorIndex + 7], 'injectorIndex should point to a bloom filter');
147+
assertNumber(
148+
lView[injectorIndex + 8 /*PARENT_INJECTOR*/],
149+
'injectorIndex should point to parent injector');
150+
}

packages/core/src/render3/features/providers_feature.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ import {DirectiveDef} from '../interfaces/definition';
3232
* ɵɵtextInterpolate(ctx.greeter.greet());
3333
* }
3434
* },
35-
* features: [ProvidersFeature([GreeterDE])]
35+
* features: [ɵɵProvidersFeature([GreeterDE])]
3636
* });
3737
* }
3838
* ```

packages/core/src/render3/instructions/lview_debug.ts

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

9-
import {Injector, SchemaMetadata} from '../../core';
9+
import {Injector, SchemaMetadata, Type} from '../../core';
1010
import {Sanitizer} from '../../sanitization/sanitizer';
1111
import {KeyValueArray} from '../../util/array_utils';
1212
import {assertDefined} from '../../util/assert';
1313
import {createNamedArrayType} from '../../util/named_array_type';
1414
import {initNgDevMode} from '../../util/ng_dev_mode';
15+
import {assertNodeInjector} from '../assert';
16+
import {getInjectorIndex} from '../di';
1517
import {CONTAINER_HEADER_OFFSET, HAS_TRANSPLANTED_VIEWS, LContainer, MOVED_VIEWS, NATIVE} from '../interfaces/container';
16-
import {ComponentTemplate, DirectiveDefList, PipeDefList, ViewQueriesFunction} from '../interfaces/definition';
18+
import {ComponentTemplate, DirectiveDef, DirectiveDefList, PipeDefList, ViewQueriesFunction} from '../interfaces/definition';
19+
import {NO_PARENT_INJECTOR, PARENT_INJECTOR, TNODE} from '../interfaces/injector';
1720
import {AttributeMarker, PropertyAliases, TConstants, TContainerNode, TElementNode, TNode as ITNode, TNodeFlags, TNodeProviderIndexes, TNodeType, TNodeTypeAsString, TViewNode} from '../interfaces/node';
1821
import {SelectorFlags} from '../interfaces/projection';
1922
import {LQueries, TQueries} from '../interfaces/query';
2023
import {RComment, RElement, Renderer3, RendererFactory3, RNode} from '../interfaces/renderer';
2124
import {getTStylingRangeNext, getTStylingRangeNextDuplicate, getTStylingRangePrev, getTStylingRangePrevDuplicate, TStylingKey, TStylingRange} from '../interfaces/styling';
22-
import {CHILD_HEAD, CHILD_TAIL, CLEANUP, CONTEXT, DebugNode, DECLARATION_VIEW, DestroyHookData, ExpandoInstructions, FLAGS, HEADER_OFFSET, HookData, HOST, INJECTOR, LContainerDebug as ILContainerDebug, LView, LViewDebug as ILViewDebug, LViewDebugRange, LViewDebugRangeContent, LViewFlags, NEXT, PARENT, QUERIES, RENDERER, RENDERER_FACTORY, SANITIZER, T_HOST, TData, TView as ITView, TVIEW, TView, TViewType} from '../interfaces/view';
25+
import {CHILD_HEAD, CHILD_TAIL, CLEANUP, CONTEXT, DebugNode, DECLARATION_VIEW, DestroyHookData, ExpandoInstructions, FLAGS, HEADER_OFFSET, HookData, HOST, INJECTOR, LContainerDebug as ILContainerDebug, LView, LViewDebug as ILViewDebug, LViewDebugRange, LViewDebugRangeContent, LViewFlags, NEXT, PARENT, QUERIES, RENDERER, RENDERER_FACTORY, SANITIZER, T_HOST, TData, TView as ITView, TVIEW, TView, TViewType, TViewTypeAsString} from '../interfaces/view';
2326
import {attachDebugObject} from '../util/debug_utils';
27+
import {getParentInjectorIndex, getParentInjectorView} from '../util/injector_utils';
2428
import {unwrapRNode} from '../util/view_utils';
2529

2630
const NG_DEV_MODE = ((typeof ngDevMode === 'undefined' || !!ngDevMode) && initNgDevMode());
@@ -152,6 +156,14 @@ export const TViewConstructor = class TView implements ITView {
152156
processTNodeChildren(this.firstChild, buf);
153157
return buf.join('');
154158
}
159+
160+
get type_(): string {
161+
return TViewTypeAsString[this.type] || `TViewType.?${this.type}?`;
162+
}
163+
164+
get i18nStartIndex(): number {
165+
return HEADER_OFFSET + this._decls + this._vars;
166+
}
155167
};
156168

157169
class TNode implements ITNode {
@@ -189,23 +201,39 @@ class TNode implements ITNode {
189201
public styleBindings: TStylingRange, //
190202
) {}
191203

192-
get type_(): string {
193-
switch (this.type) {
194-
case TNodeType.Container:
195-
return 'TNodeType.Container';
196-
case TNodeType.Element:
197-
return 'TNodeType.Element';
198-
case TNodeType.ElementContainer:
199-
return 'TNodeType.ElementContainer';
200-
case TNodeType.IcuContainer:
201-
return 'TNodeType.IcuContainer';
202-
case TNodeType.Projection:
203-
return 'TNodeType.Projection';
204-
case TNodeType.View:
205-
return 'TNodeType.View';
206-
default:
207-
return 'TNodeType.???';
204+
/**
205+
* Return a human debug version of the set of `NodeInjector`s which will be consulted when
206+
* resolving tokens from this `TNode`.
207+
*
208+
* When debugging applications, it is often difficult to determine which `NodeInjector`s will be
209+
* consulted. This method shows a list of `DebugNode`s representing the `TNode`s which will be
210+
* consulted in order when resolving a token starting at this `TNode`.
211+
*
212+
* The original data is stored in `LView` and `TView` with a lot of offset indexes, and so it is
213+
* difficult to reason about.
214+
*
215+
* @param lView The `LView` instance for this `TNode`.
216+
*/
217+
debugNodeInjectorPath(lView: LView): DebugNode[] {
218+
const path: DebugNode[] = [];
219+
let injectorIndex = getInjectorIndex(this, lView);
220+
ngDevMode && assertNodeInjector(lView, injectorIndex);
221+
while (injectorIndex !== -1) {
222+
const tNode = lView[TVIEW].data[injectorIndex + TNODE] as TNode;
223+
path.push(buildDebugNode(tNode, lView));
224+
const parentLocation = lView[injectorIndex + PARENT_INJECTOR];
225+
if (parentLocation === NO_PARENT_INJECTOR) {
226+
injectorIndex = -1;
227+
} else {
228+
injectorIndex = getParentInjectorIndex(parentLocation);
229+
lView = getParentInjectorView(parentLocation, lView);
230+
}
208231
}
232+
return path;
233+
}
234+
235+
get type_(): string {
236+
return TNodeTypeAsString[this.type] || `TNodeType.?${this.type}?`;
209237
}
210238

211239
get flags_(): string {
@@ -246,6 +274,14 @@ class TNode implements ITNode {
246274
get classBindings_(): DebugStyleBindings {
247275
return toDebugStyleBinding(this, true);
248276
}
277+
278+
get providerIndexStart_(): number {
279+
return this.providerIndexes & TNodeProviderIndexes.ProvidersStartIndexMask;
280+
}
281+
get providerIndexEnd_(): number {
282+
return this.providerIndexStart_ +
283+
(this.providerIndexes >>> TNodeProviderIndexes.CptViewProvidersCountShift);
284+
}
249285
}
250286
export const TNodeDebug = TNode;
251287
export type TNodeDebug = TNode;
@@ -462,21 +498,21 @@ export class LViewDebug implements ILViewDebug {
462498
}
463499

464500
get decls(): LViewDebugRange {
465-
const tView = this.tView as any as {_decls: number, _vars: number};
466-
const start = HEADER_OFFSET;
467-
return toLViewRange(this.tView, this._raw_lView, start, start + tView._decls);
501+
return toLViewRange(this.tView, this._raw_lView, HEADER_OFFSET, this.tView.bindingStartIndex);
468502
}
469503

470504
get vars(): LViewDebugRange {
471-
const tView = this.tView as any as {_decls: number, _vars: number};
472-
const start = HEADER_OFFSET + tView._decls;
473-
return toLViewRange(this.tView, this._raw_lView, start, start + tView._vars);
505+
const tView = this.tView;
506+
return toLViewRange(
507+
tView, this._raw_lView, tView.bindingStartIndex,
508+
(tView as any as {i18nStartIndex: number}).i18nStartIndex);
474509
}
475510

476511
get i18n(): LViewDebugRange {
477-
const tView = this.tView as any as {_decls: number, _vars: number};
478-
const start = HEADER_OFFSET + tView._decls + tView._vars;
479-
return toLViewRange(this.tView, this._raw_lView, start, this.tView.expandoStartIndex);
512+
const tView = this.tView;
513+
return toLViewRange(
514+
tView, this._raw_lView, (tView as any as {i18nStartIndex: number}).i18nStartIndex,
515+
tView.expandoStartIndex);
480516
}
481517

482518
get expando(): LViewDebugRange {
@@ -518,7 +554,7 @@ export function toDebugNodes(tNode: ITNode|null, lView: LView): DebugNode[] {
518554
const debugNodes: DebugNode[] = [];
519555
let tNodeCursor: ITNode|null = tNode;
520556
while (tNodeCursor) {
521-
debugNodes.push(buildDebugNode(tNodeCursor, lView, tNodeCursor.index));
557+
debugNodes.push(buildDebugNode(tNodeCursor, lView));
522558
tNodeCursor = tNodeCursor.next;
523559
}
524560
return debugNodes;
@@ -527,17 +563,75 @@ export function toDebugNodes(tNode: ITNode|null, lView: LView): DebugNode[] {
527563
}
528564
}
529565

530-
export function buildDebugNode(tNode: ITNode, lView: LView, nodeIndex: number): DebugNode {
531-
const rawValue = lView[nodeIndex];
566+
export function buildDebugNode(tNode: ITNode, lView: LView): DebugNode {
567+
const rawValue = lView[tNode.index];
532568
const native = unwrapRNode(rawValue);
569+
const factories: Type<any>[] = [];
570+
const instances: any[] = [];
571+
const tView = lView[TVIEW];
572+
for (let i = tNode.directiveStart; i < tNode.directiveEnd; i++) {
573+
const def = tView.data[i] as DirectiveDef<any>;
574+
factories.push(def.type);
575+
instances.push(lView[i]);
576+
}
533577
return {
534578
html: toHtml(native),
535579
type: TNodeTypeAsString[tNode.type],
536580
native: native as any,
537581
children: toDebugNodes(tNode.child, lView),
582+
factories,
583+
instances,
584+
injector: buildNodeInjectorDebug(tNode, tView, lView)
538585
};
539586
}
540587

588+
function buildNodeInjectorDebug(tNode: ITNode, tView: ITView, lView: LView) {
589+
const viewProviders: Type<any>[] = [];
590+
for (let i = (tNode as TNode).providerIndexStart_; i < (tNode as TNode).providerIndexEnd_; i++) {
591+
viewProviders.push(tView.data[i] as Type<any>);
592+
}
593+
const providers: Type<any>[] = [];
594+
for (let i = (tNode as TNode).providerIndexEnd_; i < (tNode as TNode).directiveEnd; i++) {
595+
providers.push(tView.data[i] as Type<any>);
596+
}
597+
const nodeInjectorDebug = {
598+
bloom: toBloom(lView, tNode.injectorIndex),
599+
cumulativeBloom: toBloom(tView.data, tNode.injectorIndex),
600+
providers,
601+
viewProviders,
602+
parentInjectorIndex: lView[(tNode as TNode).providerIndexStart_ - 1],
603+
};
604+
return nodeInjectorDebug;
605+
}
606+
607+
/**
608+
* Convert a number at `idx` location in `array` into binary representation.
609+
*
610+
* @param array
611+
* @param idx
612+
*/
613+
function binary(array: any[], idx: number): string {
614+
const value = array[idx];
615+
// If not a number we print 8 `?` to retain alignment but let user know that it was called on
616+
// wrong type.
617+
if (typeof value !== 'number') return '????????';
618+
// We prefix 0s so that we have constant length number
619+
const text = '00000000' + value.toString(2);
620+
return text.substring(text.length - 8);
621+
}
622+
623+
/**
624+
* Convert a bloom filter at location `idx` in `array` into binary representation.
625+
*
626+
* @param array
627+
* @param idx
628+
*/
629+
function toBloom(array: any[], idx: number): string {
630+
return `${binary(array, idx + 7)}_${binary(array, idx + 6)}_${binary(array, idx + 5)}_${
631+
binary(array, idx + 4)}_${binary(array, idx + 3)}_${binary(array, idx + 2)}_${
632+
binary(array, idx + 1)}_${binary(array, idx + 0)}`;
633+
}
634+
541635
export class LContainerDebug implements ILContainerDebug {
542636
constructor(private readonly _raw_lContainer: LContainer) {}
543637

packages/core/src/render3/interfaces/injector.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import {InjectionToken} from '../../di/injection_token';
1010
import {InjectFlags} from '../../di/interface/injector';
1111
import {Type} from '../../interface/type';
12+
import {assertDefined, assertEqual} from '../../util/assert';
1213

1314
import {TDirectiveHostNode} from './node';
1415
import {LView, TData} from './view';
@@ -239,6 +240,8 @@ export class NodeInjectorFactory {
239240
isViewProvider: boolean,
240241
injectImplementation: null|
241242
(<T>(token: Type<T>|InjectionToken<T>, flags?: InjectFlags) => T)) {
243+
ngDevMode && assertDefined(factory, 'Factory not specified');
244+
ngDevMode && assertEqual(typeof factory, 'function', 'Expected factory function.');
242245
this.canSeeViewProviders = isViewProvider;
243246
this.injectImpl = injectImplementation;
244247
}

packages/core/src/render3/interfaces/view.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1021,4 +1021,48 @@ export interface DebugNode {
10211021
* Child nodes
10221022
*/
10231023
children: DebugNode[];
1024+
1025+
/**
1026+
* A list of Component/Directive types which need to be instantiated an this location.
1027+
*/
1028+
factories: Type<unknown>[];
1029+
1030+
/**
1031+
* A list of Component/Directive instances which were instantiated an this location.
1032+
*/
1033+
instances: unknown[];
1034+
1035+
/**
1036+
* NodeInjector information.
1037+
*/
1038+
injector: NodeInjectorDebug;
1039+
}
1040+
1041+
interface NodeInjectorDebug {
1042+
/**
1043+
* Instance bloom. Does the current injector have a provider with a given bloom mask.
1044+
*/
1045+
bloom: string;
1046+
1047+
1048+
/**
1049+
* Cumulative bloom. Do any of the above injectors have a provider with a given bloom mask.
1050+
*/
1051+
cumulativeBloom: string;
1052+
1053+
/**
1054+
* A list of providers associated with this injector.
1055+
*/
1056+
providers: (Type<unknown>|DirectiveDef<unknown>|ComponentDef<unknown>)[];
1057+
1058+
/**
1059+
* A list of providers associated with this injector visible to the view of the component only.
1060+
*/
1061+
viewProviders: Type<unknown>[];
1062+
1063+
1064+
/**
1065+
* Location of the parent `TNode`.
1066+
*/
1067+
parentInjectorIndex: number;
10241068
}

packages/core/src/render3/util/discovery_utils.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -389,7 +389,7 @@ export function getDebugNode(element: Element): DebugNode|null {
389389
// data. In this situation the TNode is not accessed at the same spot.
390390
const tNode = isLView(valueInLView) ? (valueInLView[T_HOST] as TNode) :
391391
getTNode(lView[TVIEW], nodeIndex - HEADER_OFFSET);
392-
debugNode = buildDebugNode(tNode, lView, nodeIndex);
392+
debugNode = buildDebugNode(tNode, lView);
393393
}
394394

395395
return debugNode;

0 commit comments

Comments
 (0)
0