UNPKG

ember-codemods-telemetry-helpers

Version:
233 lines (211 loc) 6.91 kB
module.exports = function analyzeEmberObject(possibleEmberObject, modulePath) { let isTemplate = false; if (typeof possibleEmberObject !== 'object' || possibleEmberObject === null) { return undefined; } let eObjDefault = possibleEmberObject.default; if (eObjDefault) { if (eObjDefault.isHelperFactory) { return { type: 'Helper', }; } else if (typeof eObjDefault.proto !== 'function') { /** * this is for JavaScript-less components that only have a template. Only * components with a backing JavaScript file will be in the `require.entries` * that will have a `proto()` we can instantiate. */ if (modulePath && modulePath.includes('templates/components/')) { isTemplate = true; return { isTemplate, type: 'Component', }; } return undefined; } } let proto = possibleEmberObject.default.proto(); // Ember here is assumed to be global when ran within the context of the browser /* globals Ember */ let meta = Ember.meta(proto); /** * Parses the ember meta with passed key * * @param {Ember.meta} map * @param {String} key * @returns {Object} meta - The listener meta data * @returns {String} meta.type - Type of listener can be observer|event * @returns {String[]} meta.events - name of events/properties the listener is registered on */ let getListenerData = (map, key) => { while (map) { let type = 'event'; const events = parseListeners(map._listeners).reduce((acc, [event, , method]) => { if (method === key) { const [observedProp, observerEvent] = event.split(':'); if (observerEvent) { type = 'observer'; } acc.push(observedProp); } return acc; }, []); if (events.length) { return { type, events, }; } map = map.parent; } return {}; }; /** * Checks if passed key is overriding any value from the parent objects * * @param {Object} map * @param {String} key * @returns boolean */ let isOverridden = (map, key) => { while (map) { const value = map.peekValues ? map.peekValues(key) : undefined; if (value !== undefined || (map.source && key in map.source)) { return true; } map = map.parent; } return false; }; /** * Parse the listeners to a group of array of 4 elements * * @param {Array} listeners * @param {int} size * @returns Array */ let parseListeners = (listeners = [], size = 4) => { var result = []; if (listeners.length) { if (typeof listeners[0] === 'object') { result = listeners.map(({ event, target, method, kind }) => [event, target, method, kind]); } else { const input = listeners.slice(0); while (input.length) { result.push(input.splice(0, size)); } } } return result; }; /** * Checks if passed key is overriding any value from the parent objects' actions * * @param {Object} map * @param {String} key * @returns boolean */ let isActionOverridden = (map, key) => { while (map) { const { source } = map; if (source) { const { actions } = source; const value = actions ? actions[key] : undefined; if (value !== undefined) { return true; } } map = map.parent; } return false; }; // eslint-disable-next-line no-undef if (!meta || !meta.source) { return {}; } const { source } = meta; const type = getType(source); const ownProperties = Object.keys(source).filter(key => !['_super', 'actions'].includes(key)); const ownActions = source.actions ? Object.keys(source.actions) : []; const observedProperties = Object.keys(meta._watching || {}); const overriddenProperties = ownProperties.filter(key => isOverridden(meta.parent, key)); const overriddenActions = ownActions.filter(key => isActionOverridden(meta.parent, key)); const computedProperties = []; meta.forEachDescriptors((name, desc) => { const descProto = Object.getPrototypeOf(desc) || {}; const constructorName = descProto.constructor ? descProto.constructor.name : ''; if (desc.enumerable && ownProperties.includes(name) && constructorName === 'ComputedProperty') { computedProperties.push(name); } }); function getType(object) { const types = [ 'Application', 'Controller', 'Route', 'Component', 'Service', 'Helper', 'Router', 'Engine', ]; // eslint-disable-next-line no-undef return types.find(type => Ember[type] && object instanceof Ember[type]) || 'EmberObject'; } /** * Parses ember meta data object and collects the runtime information * * @param {Object} meta * @returns {Object} data - Parsed metadata for the ember object * @returns {String[]} data.computedProperties - list of computed properties * @returns {String[]} data.getters - list of ES5 getters * @returns {String[]} data.observedProperties - list of observed properties * @returns {Object} data.observerProperties - list of observer properties * @returns {Object} data.offProperties - list of observer properties * @returns {String[]} data.overriddenActions - list of overridden actions * @returns {String[]} data.overriddenProperties - list of overridden properties * @returns {String[]} data.ownProperties - list of object's own properties * @returns {String} data.type - type of ember object * @returns {Object} data.unobservedProperties - list of unobserved properties */ const ownDesc = Object.getOwnPropertyDescriptors(source); const getters = Object.keys(ownDesc).filter( key => ownDesc[key].get && !computedProperties.includes(key) ); const { offProperties, unobservedProperties } = ownProperties.reduce( ({ offProperties, unobservedProperties }, key) => { const { type, events } = getListenerData(meta.parent, key); if (type === 'event') { offProperties[key] = events; } else if (type === 'observer') { unobservedProperties[key] = events; } return { offProperties, unobservedProperties }; }, { offProperties: {}, unobservedProperties: {}, } ); const observerProperties = observedProperties.reduce((acc, oProp) => { const listenerData = meta.matchingListeners(`${oProp}:change`); if (listenerData) { const listener = listenerData[1]; acc[listener] = [].concat(acc[listener] || [], [oProp]); } return acc; }, {}); return { computedProperties, getters, observedProperties, observerProperties, offProperties, overriddenActions, overriddenProperties, ownProperties, type, unobservedProperties, }; };