Created
August 4, 2025 13:05
-
-
Save karlbecker/50b98a1f511cbd02cd78bff430087c85 to your computer and use it in GitHub Desktop.
Convert ember-cli-page-object legacy collection approach involving itemScope to newer selector-first API
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // jscodeshift codemod to migrate ember-cli-page-object legacy collection({ ... }) | |
| // to the newer selector-first API: collection('selector', { ... }) | |
| // | |
| // Usage: | |
| // npx jscodeshift -t scripts/collection-api-codemod.js tests/pages | |
| // | |
| // The transform handles the common pattern that was deprecated: | |
| // | |
| // items: collection({ | |
| // itemScope: '.item', | |
| // item: { | |
| // name: text('.name') | |
| // }, | |
| // at: 1, | |
| // // any other options / helpers | |
| // }); | |
| // | |
| // Becomes: | |
| // items: collection('.item', { | |
| // name: text('.name'), | |
| // at: 1, | |
| // // … | |
| // }); | |
| // | |
| // • "itemScope" is moved into the first positional parameter. | |
| // • The object passed to "item" is hoisted into the definition object. | |
| // • All other keys except "itemScope" and "item" are preserved inside | |
| // the definition object. | |
| module.exports = function transformer(fileInfo, api) { | |
| const j = api.jscodeshift; | |
| const root = j(fileInfo.source); | |
| // helper to detect collection(...) call | |
| function isCollectionCall(node) { | |
| return ( | |
| node && | |
| node.type === 'CallExpression' && | |
| node.callee.type === 'Identifier' && | |
| node.callee.name === 'collection' | |
| ); | |
| } | |
| root | |
| .find(j.CallExpression, { | |
| callee: { type: 'Identifier', name: 'collection' }, | |
| arguments: { length: 1 } | |
| }) | |
| .forEach(path => { | |
| const arg = path.node.arguments[0]; | |
| if (arg.type !== 'ObjectExpression') return; // only legacy style | |
| // extract itemScope | |
| const itemScopeProp = arg.properties.find( | |
| p => p.key && ((p.key.name === 'itemScope') || (p.key.value === 'itemScope')) | |
| ); | |
| if (!itemScopeProp) return; // nothing to do | |
| const selector = itemScopeProp.value; // AST node representing string literal or expression | |
| // extract item definition | |
| const itemProp = arg.properties.find( | |
| p => p.key && ((p.key.name === 'item') || (p.key.value === 'item')) | |
| ); | |
| // build new definition object | |
| const newProps = []; | |
| // include properties from itemProp first (if exists) | |
| if (itemProp && itemProp.value.type === 'ObjectExpression') { | |
| newProps.push(...itemProp.value.properties); | |
| } | |
| // include remaining original top-level props except itemScope and item | |
| arg.properties.forEach(p => { | |
| const keyName = p.key && (p.key.name || p.key.value); | |
| if (keyName === 'itemScope' || keyName === 'item') return; | |
| newProps.push(p); | |
| }); | |
| const newDefinitionObj = j.objectExpression(newProps); | |
| // replace arguments: collection(selector, newDefinitionObj) | |
| path.node.arguments = [selector, newDefinitionObj]; | |
| }); | |
| return root.toSource({ quote: 'single' }); | |
| }; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment