Skip to content

Commit 94ec9f0

Browse files
author
Robert Jackson
authored
Specify telemetry gathering implementation. (#14)
Specify telemetry gathering implementation.
2 parents 13265ca + 882625d commit 94ec9f0

9 files changed

Lines changed: 1040 additions & 710 deletions

File tree

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,3 +59,5 @@ typings/
5959

6060
# next.js build output
6161
.next
62+
63+
test/fixtures/classic-app/dist/

.npmignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
*.test.js
2+
**/test/**

lib/gather/analyze-ember-object.js

Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
module.exports = function analyzeEmberObject(possibleEmberObject) {
2+
if (typeof possibleEmberObject !== 'object' || possibleEmberObject === null) {
3+
return undefined;
4+
}
5+
if (possibleEmberObject.default && typeof possibleEmberObject.default.proto !== 'function') {
6+
return undefined;
7+
}
8+
let proto = possibleEmberObject.default.proto();
9+
10+
// Ember here is assumed to be global when ran within the context of the browser
11+
/* globals Ember */
12+
let meta = Ember.meta(proto);
13+
14+
// eslint-disable-next-line no-undef
15+
if (!meta || !meta.source) {
16+
return {};
17+
}
18+
const { source } = meta;
19+
const type = getType(source);
20+
21+
const ownProperties = Object.keys(source).filter(key => !['_super', 'actions'].includes(key));
22+
23+
const ownActions = source.actions ? Object.keys(source.actions) : [];
24+
25+
const observedProperties = Object.keys(meta._watching || {});
26+
27+
const overriddenProperties = ownProperties.filter(key => isOverridden(meta.parent, key));
28+
29+
const overriddenActions = ownActions.filter(key => isActionOverridden(meta.parent, key));
30+
31+
const computedProperties = [];
32+
meta.forEachDescriptors((name, desc) => {
33+
const descProto = Object.getPrototypeOf(desc) || {};
34+
const constructorName = descProto.constructor ? descProto.constructor.name : '';
35+
if (desc.enumerable && ownProperties.includes(name) && constructorName === 'ComputedProperty') {
36+
computedProperties.push(name);
37+
}
38+
});
39+
40+
function getType(object) {
41+
const types = [
42+
'Application',
43+
'Controller',
44+
'Route',
45+
'Component',
46+
'Service',
47+
'Helper',
48+
'Router',
49+
'Engine',
50+
];
51+
// eslint-disable-next-line no-undef
52+
return types.find(type => Ember[type] && object instanceof Ember[type]) || 'EmberObject';
53+
}
54+
55+
/**
56+
* Parses ember meta data object and collects the runtime information
57+
*
58+
* @param {Object} meta
59+
* @returns {Object} data - Parsed metadata for the ember object
60+
* @returns {String[]} data.computedProperties - list of computed properties
61+
* @returns {String[]} data.getters - list of ES5 getters
62+
* @returns {String[]} data.observedProperties - list of observed properties
63+
* @returns {Object} data.observerProperties - list of observer properties
64+
* @returns {Object} data.offProperties - list of observer properties
65+
* @returns {String[]} data.overriddenActions - list of overridden actions
66+
* @returns {String[]} data.overriddenProperties - list of overridden properties
67+
* @returns {String[]} data.ownProperties - list of object's own properties
68+
* @returns {String} data.type - type of ember object
69+
* @returns {Object} data.unobservedProperties - list of unobserved properties
70+
*/
71+
72+
const ownDesc = Object.getOwnPropertyDescriptors(source);
73+
const getters = Object.keys(ownDesc).filter(
74+
key => ownDesc[key].get && !computedProperties.includes(key)
75+
);
76+
77+
const { offProperties, unobservedProperties } = ownProperties.reduce(
78+
({ offProperties, unobservedProperties }, key) => {
79+
const { type, events } = getListenerData(meta.parent, key);
80+
if (type === 'event') {
81+
offProperties[key] = events;
82+
} else if (type === 'observer') {
83+
unobservedProperties[key] = events;
84+
}
85+
return { offProperties, unobservedProperties };
86+
},
87+
{
88+
offProperties: {},
89+
unobservedProperties: {},
90+
}
91+
);
92+
93+
const observerProperties = observedProperties.reduce((acc, oProp) => {
94+
const listenerData = meta.matchingListeners(`${oProp}:change`);
95+
if (listenerData) {
96+
const listener = listenerData[1];
97+
acc[listener] = [].concat(acc[listener] || [], [oProp]);
98+
}
99+
return acc;
100+
}, {});
101+
102+
return {
103+
computedProperties,
104+
getters,
105+
observedProperties,
106+
observerProperties,
107+
offProperties,
108+
overriddenActions,
109+
overriddenProperties,
110+
ownProperties,
111+
type,
112+
unobservedProperties,
113+
};
114+
};
115+
116+
/**
117+
* Parses the ember meta with passed key
118+
*
119+
* @param {Ember.meta} map
120+
* @param {String} key
121+
* @returns {Object} meta - The listener meta data
122+
* @returns {String} meta.type - Type of listener can be observer|event
123+
* @returns {String[]} meta.events - name of events/properties the listener is registered on
124+
*/
125+
function getListenerData(map, key) {
126+
while (map) {
127+
let type = 'event';
128+
const events = parseListeners(map._listeners).reduce((acc, [event, , method]) => {
129+
if (method === key) {
130+
const [observedProp, observerEvent] = event.split(':');
131+
if (observerEvent) {
132+
type = 'observer';
133+
}
134+
acc.push(observedProp);
135+
}
136+
return acc;
137+
}, []);
138+
if (events.length) {
139+
return {
140+
type,
141+
events,
142+
};
143+
}
144+
map = map.parent;
145+
}
146+
return {};
147+
}
148+
149+
/**
150+
* Parse the listeners to a group of array of 4 elements
151+
*
152+
* @param {Array} listeners
153+
* @param {int} size
154+
* @returns Array
155+
*/
156+
function parseListeners(listeners = [], size = 4) {
157+
var result = [];
158+
if (listeners.length) {
159+
if (typeof listeners[0] === 'object') {
160+
result = listeners.map(({ event, target, method, kind }) => [event, target, method, kind]);
161+
} else {
162+
const input = listeners.slice(0);
163+
while (input.length) {
164+
result.push(input.splice(0, size));
165+
}
166+
}
167+
}
168+
return result;
169+
}
170+
171+
/**
172+
* Checks if passed key is overriding any value from the parent objects
173+
*
174+
* @param {Object} map
175+
* @param {String} key
176+
* @returns boolean
177+
*/
178+
function isOverridden(map, key) {
179+
while (map) {
180+
const value = map.peekValues ? map.peekValues(key) : undefined;
181+
if (value !== undefined || (map.source && key in map.source)) {
182+
return true;
183+
}
184+
map = map.parent;
185+
}
186+
return false;
187+
}
188+
189+
/**
190+
* Checks if passed key is overriding any value from the parent objects' actions
191+
*
192+
* @param {Object} map
193+
* @param {String} key
194+
* @returns boolean
195+
*/
196+
function isActionOverridden(map, key) {
197+
while (map) {
198+
const { source } = map;
199+
if (source) {
200+
const { actions } = source;
201+
const value = actions ? actions[key] : undefined;
202+
if (value !== undefined) {
203+
return true;
204+
}
205+
}
206+
map = map.parent;
207+
}
208+
return false;
209+
}

0 commit comments

Comments
 (0)