Skip to content

Commit 1f45530

Browse files
committed
add tag info for ember-inspector
1 parent 1cb493b commit 1f45530

File tree

7 files changed

+217
-4
lines changed

7 files changed

+217
-4
lines changed

packages/@glimmer-workspace/integration-tests/lib/setup-harness.ts

+13
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,19 @@ export async function setupQunit() {
6363
qunit.moduleDone(pause);
6464
}
6565

66+
// @ts-expect-error missing in types, does exist: https://api.qunitjs.com/callbacks/QUnit.on/#the-testend-event
67+
QUnit.on('testEnd', (testEnd) => {
68+
if (testEnd.status === 'failed') {
69+
testEnd.errors.forEach((assertion: any) => {
70+
console.error(assertion.stack);
71+
// message: speedometer
72+
// actual: 75
73+
// expected: 88
74+
// stack: at dmc.test.js:12
75+
});
76+
}
77+
});
78+
6679
qunit.done(({ failed }) => {
6780
if (failed > 0) {
6881
console.log('[HARNESS] fail');

packages/@glimmer/validator/index.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,9 @@ if (globalObj[GLIMMER_VALIDATOR_REGISTRATION] === true) {
1313
globalObj[GLIMMER_VALIDATOR_REGISTRATION] = true;
1414

1515
export { debug } from './lib/debug';
16-
export { dirtyTagFor, tagFor, type TagMeta, tagMetaFor } from './lib/meta';
16+
export { dirtyTagFor, infoForTag, tagFor, type TagMeta, tagMetaFor } from './lib/meta';
1717
export { trackedData } from './lib/tracked-data';
18+
export * from './lib/tracked-utils';
1819
export {
1920
beginTrackFrame,
2021
beginUntrackFrame,

packages/@glimmer/validator/lib/meta.ts

+11-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { ConstantTag, UpdatableTag } from '@glimmer/interfaces';
1+
import type { ConstantTag, Tag, UpdatableTag } from '@glimmer/interfaces';
22

33
import type { Indexable } from './utils';
44

@@ -15,6 +15,7 @@ function isObjectLike<T>(u: T): u is Indexable & T {
1515
export type TagMeta = Map<PropertyKey, UpdatableTag>;
1616

1717
const TRACKED_TAGS = new WeakMap<object, TagMeta>();
18+
const TAG_META = Symbol('TAG_META');
1819

1920
export function dirtyTagFor<T extends object>(
2021
obj: T,
@@ -65,7 +66,16 @@ export function tagFor<T extends object>(
6566
if (tag === undefined) {
6667
tag = createUpdatableTag();
6768
tags.set(key, tag);
69+
70+
(tag as any)[TAG_META] = {
71+
propertyKey: key,
72+
object: obj,
73+
};
6874
}
6975

7076
return tag;
7177
}
78+
79+
export function infoForTag(tag: Tag) {
80+
return (tag as any)[TAG_META];
81+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import type { Tag } from '@glimmer/interfaces';
2+
3+
import type { MonomorphicTagImpl } from './validators';
4+
5+
import { infoForTag, tagFor } from './meta';
6+
import { track } from './tracking';
7+
import { validateTag, valueForTag } from './validators';
8+
9+
type Info = {
10+
tag: MonomorphicTagImpl;
11+
prevValue: number;
12+
dependencies: {
13+
object: object;
14+
propertyKey: string;
15+
changed: boolean;
16+
}[];
17+
};
18+
19+
export function getTrackedDependencies(obj: Record<string, any>, property: string, info?: Info) {
20+
info = info || ({} as Info);
21+
const tag = info?.tag || track(() => obj[property]);
22+
const dependencies = [];
23+
// do not include tracked properties from dependencies
24+
25+
const subtags = (Array.isArray(tag.subtag) ? [tag, ...tag.subtag] : [tag, tag.subtag]).filter(
26+
(t) => !!t
27+
) as Tag[];
28+
for (const subtag of subtags) {
29+
if (subtag === tag) continue;
30+
dependencies.push({ ...infoForTag(subtag), tag: subtag });
31+
if (subtag.subtag && !Array.isArray(subtag.subtag)) {
32+
dependencies.push({ ...infoForTag(subtag.subtag) });
33+
}
34+
}
35+
36+
let maxRevision = info?.prevValue || valueForTag(tag);
37+
dependencies.forEach((t) => {
38+
const value = valueForTag(t.tag);
39+
maxRevision = Math.max(maxRevision, value);
40+
});
41+
42+
const hasChange = (info.prevValue && maxRevision !== info.prevValue) || false;
43+
let latestValue = valueForTag(tag);
44+
45+
info.prevValue = latestValue;
46+
info.dependencies = dependencies.map((t) => {
47+
if (t.tag.lastValue > latestValue) {
48+
latestValue = t.tag.lastValue;
49+
}
50+
const changed = hasChange && t.tag.lastValue >= maxRevision;
51+
return { object: t.object, propertyKey: t.propertyKey, changed };
52+
});
53+
54+
return info;
55+
}
56+
57+
type TrackedInfo = {
58+
changed: string[];
59+
propertyInfo: Record<string, any>;
60+
};
61+
62+
export function getChangedProperties(obj: object, trackedInfo?: TrackedInfo) {
63+
trackedInfo = trackedInfo || ({} as TrackedInfo);
64+
trackedInfo['changed'] = [];
65+
trackedInfo.propertyInfo = trackedInfo.propertyInfo || {};
66+
for (const name in obj) {
67+
let tagInfo = trackedInfo.propertyInfo?.[name] || {
68+
tag: tagFor(obj, name),
69+
revision: (tagFor(obj, name) as MonomorphicTagImpl)['revision'],
70+
};
71+
if (!tagInfo.tag) return;
72+
trackedInfo.propertyInfo[name] = tagInfo;
73+
74+
const changed = !validateTag(tagInfo.tag, tagInfo.revision);
75+
tagInfo.revision = (tagInfo.tag as MonomorphicTagImpl)['revision'];
76+
if (changed) {
77+
trackedInfo['changed'].push(name);
78+
}
79+
}
80+
return trackedInfo;
81+
}

packages/@glimmer/validator/lib/validators.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ function allowsCycles(tag: Tag): boolean {
9090
}
9191
}
9292

93-
class MonomorphicTagImpl<T extends MonomorphicTagId = MonomorphicTagId> {
93+
export class MonomorphicTagImpl<T extends MonomorphicTagId = MonomorphicTagId> {
9494
static combine(this: void, tags: Tag[]): Tag {
9595
switch (tags.length) {
9696
case 0:

packages/@glimmer/validator/test/meta-test.ts

+10-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { dirtyTagFor, tagFor, validateTag, valueForTag } from '@glimmer/validator';
1+
import { dirtyTagFor, infoForTag, tagFor, validateTag, valueForTag } from '@glimmer/validator';
22

33
import { module, test } from './-utils';
44

@@ -18,4 +18,13 @@ module('@glimmer/validator: meta', () => {
1818

1919
assert.notOk(validateTag(tag, snapshot));
2020
});
21+
22+
test('it can provide the object and property for the tag given object', (assert) => {
23+
let obj = {};
24+
let tag = tagFor(obj, 'foo');
25+
26+
let info = infoForTag(tag)!;
27+
assert.strictEqual(info.object, obj);
28+
assert.strictEqual(info.propertyKey, 'foo');
29+
});
2130
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import { getChangedProperties, getTrackedDependencies, trackedData } from '@glimmer/validator';
2+
3+
import { module, test } from './-utils';
4+
5+
module('@glimmer/validator: tracked-utils', () => {
6+
class TestObject {
7+
declare item1: string;
8+
declare item2: string;
9+
item3 = '';
10+
constructor() {}
11+
12+
get getterWithTracked() {
13+
return this.item1 + ' world' + this.item2;
14+
}
15+
}
16+
17+
{
18+
const { getter, setter } = trackedData<TestObject, 'item1'>('item1', () => '');
19+
Object.defineProperty(TestObject.prototype, 'item1', {
20+
enumerable: true,
21+
get(this) {
22+
return getter(this);
23+
},
24+
set(this, v) {
25+
return setter(this, v);
26+
},
27+
});
28+
}
29+
{
30+
const { getter, setter } = trackedData<TestObject, 'item2'>('item2', () => '');
31+
Object.defineProperty(TestObject.prototype, 'item2', {
32+
enumerable: true,
33+
get(this) {
34+
return getter(this);
35+
},
36+
set(this, v) {
37+
return setter(this, v);
38+
},
39+
});
40+
}
41+
42+
test('it can detect changed properties', (assert) => {
43+
const obj = new TestObject();
44+
let trackedInfo = getChangedProperties(obj);
45+
assert.deepEqual(trackedInfo?.changed, []);
46+
47+
obj.item1 = 'hello';
48+
49+
assert.deepEqual(getChangedProperties(obj, trackedInfo)?.changed, ['item1']);
50+
assert.deepEqual(getChangedProperties(obj, trackedInfo)?.changed, []);
51+
52+
obj.item1 = 'hi';
53+
obj.item2 = 'hi';
54+
assert.deepEqual(getChangedProperties(obj, trackedInfo)?.changed, ['item1', 'item2']);
55+
});
56+
57+
test('it can detect tracked dependencies', (assert) => {
58+
const obj = new TestObject();
59+
let info = getTrackedDependencies(obj, 'getterWithTracked');
60+
assert.deepEqual(info.dependencies, [
61+
{
62+
changed: false,
63+
object: obj,
64+
propertyKey: 'item1',
65+
},
66+
{
67+
changed: false,
68+
object: obj,
69+
propertyKey: 'item2',
70+
},
71+
]);
72+
73+
obj.item1 = 'hi';
74+
assert.deepEqual(getTrackedDependencies(obj, 'getterWithTracked', info).dependencies, [
75+
{
76+
changed: true,
77+
object: obj,
78+
propertyKey: 'item1',
79+
},
80+
{
81+
changed: false,
82+
object: obj,
83+
propertyKey: 'item2',
84+
},
85+
]);
86+
assert.deepEqual(getTrackedDependencies(obj, 'getterWithTracked', info).dependencies, [
87+
{
88+
changed: false,
89+
object: obj,
90+
propertyKey: 'item1',
91+
},
92+
{
93+
changed: false,
94+
object: obj,
95+
propertyKey: 'item2',
96+
},
97+
]);
98+
});
99+
});

0 commit comments

Comments
 (0)