Skip to content

Commit b2cd635

Browse files
author
Robert Jackson
committed
Simplify module state management significantly.
The number of methods that thread things through was very annoying. This creates a `ModuleInfo` class that can be used to parse a NodePath representing a `moduleFor*` invocation and have properties for the common values needed to parse from it.
1 parent d86f86f commit b2cd635

1 file changed

Lines changed: 90 additions & 78 deletions

File tree

ember-qunit-codemod.js

Lines changed: 90 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,65 @@ module.exports = function(file, api) {
22
const j = api.jscodeshift;
33
const root = j(file.source);
44

5+
const POSSIBLE_MODULES = [
6+
{ expression: { callee: { name: 'module' } } },
7+
{ expression: { callee: { name: 'moduleFor' } } },
8+
{ expression: { callee: { name: 'moduleForComponent' } } },
9+
{ expression: { callee: { name: 'moduleForModel' } } },
10+
];
11+
12+
class ModuleInfo {
13+
static isModuleDefinition(nodePath) {
14+
return POSSIBLE_MODULES.some(matcher => j.match(nodePath, matcher));
15+
}
16+
17+
constructor(p) {
18+
let calleeName = p.node.expression.callee.name;
19+
// Find the moduleName and the module's options
20+
let moduleName, subject, options, hasCustomSubject, isNestedModule;
21+
let calleeArguments = p.node.expression.arguments.slice();
22+
let lastArgument = calleeArguments[calleeArguments.length - 1];
23+
if (lastArgument.type === 'ObjectExpression') {
24+
options = calleeArguments.pop();
25+
}
26+
if (calleeArguments[1] && j.match(calleeArguments[1], { type: 'FunctionExpression' })) {
27+
isNestedModule = true;
28+
moduleName = calleeArguments[0];
29+
} else {
30+
moduleName = calleeArguments[1] || calleeArguments[0];
31+
subject = calleeArguments[0];
32+
}
33+
34+
let setupIdentifier = calleeName === 'module' ? null : 'setupTest';
35+
if (options) {
36+
let hasIntegration = options.properties.some(p => p.key.name === 'integration');
37+
38+
if (calleeName === `moduleForComponent`) {
39+
if (hasIntegration) {
40+
setupIdentifier = 'setupRenderingTest';
41+
subject = null;
42+
} else {
43+
subject = j.literal(`component:${calleeArguments[0].value}`);
44+
}
45+
} else if (calleeName === 'moduleForModel') {
46+
subject = j.literal(`model:${calleeArguments[0].value}`);
47+
}
48+
49+
hasCustomSubject = options.properties.some(p => p.key.name === 'subject');
50+
}
51+
52+
this.moduleName = moduleName;
53+
this.moduleOptions = options;
54+
this.setupType = setupIdentifier;
55+
this.subjectContainerKey = subject;
56+
this.hasCustomSubjectImplementation = hasCustomSubject;
57+
this.isNestedModule = isNestedModule;
58+
59+
this.moduleInvocation = null;
60+
this.moduleCallbackBody = null;
61+
}
62+
}
63+
564
function ensureImportWithSpecifiers({ source, specifiers, anchor, positionMethod }) {
665
let importStatement = ensureImport(source, anchor, positionMethod);
766
let combinedSpecifiers = new Set(specifiers);
@@ -106,8 +165,8 @@ module.exports = function(file, api) {
106165
},
107166
})
108167
.forEach(p => {
109-
let [, , setupType] = parseModule(p);
110-
emberQUnitSpecifiers.add(setupType);
168+
let moduleInfo = new ModuleInfo(p);
169+
emberQUnitSpecifiers.add(moduleInfo.setupType);
111170
});
112171
} else {
113172
emberQUnitSpecifiers.add(mappedName);
@@ -141,56 +200,7 @@ module.exports = function(file, api) {
141200
});
142201
}
143202

144-
function parseModule(p) {
145-
let calleeName = p.node.expression.callee.name;
146-
// Find the moduleName and the module's options
147-
let moduleName, subject, options, hasCustomSubject, isNestedModule;
148-
let calleeArguments = p.node.expression.arguments.slice();
149-
let lastArgument = calleeArguments[calleeArguments.length - 1];
150-
if (lastArgument.type === 'ObjectExpression') {
151-
options = calleeArguments.pop();
152-
}
153-
if (calleeArguments[1] && j.match(calleeArguments[1], { type: 'FunctionExpression' })) {
154-
isNestedModule = true;
155-
moduleName = calleeArguments[0];
156-
} else {
157-
moduleName = calleeArguments[1] || calleeArguments[0];
158-
subject = calleeArguments[0];
159-
}
160-
161-
let setupIdentifier = calleeName === 'module' ? null : 'setupTest';
162-
if (options) {
163-
let hasIntegration = options.properties.some(p => p.key.name === 'integration');
164-
165-
if (calleeName === `moduleForComponent`) {
166-
if (hasIntegration) {
167-
setupIdentifier = 'setupRenderingTest';
168-
subject = null;
169-
} else {
170-
subject = j.literal(`component:${calleeArguments[0].value}`);
171-
}
172-
} else if (calleeName === 'moduleForModel') {
173-
subject = j.literal(`model:${calleeArguments[0].value}`);
174-
}
175-
176-
hasCustomSubject = options.properties.some(p => p.key.name === 'subject');
177-
}
178-
179-
return [moduleName, options, setupIdentifier, subject, hasCustomSubject, isNestedModule];
180-
}
181-
182203
function updateModuleForToNestedModule() {
183-
const POSSIBLE_MODULES = [
184-
{ expression: { callee: { name: 'module' } } },
185-
{ expression: { callee: { name: 'moduleFor' } } },
186-
{ expression: { callee: { name: 'moduleForComponent' } } },
187-
{ expression: { callee: { name: 'moduleForModel' } } },
188-
];
189-
190-
function isModuleDefinition(nodePath) {
191-
return POSSIBLE_MODULES.some(matcher => j.match(nodePath, matcher));
192-
}
193-
194204
const LIFE_CYCLE_METHODS = [
195205
{ key: { name: 'before' }, value: { type: 'FunctionExpression' } },
196206
{ key: { name: 'beforeEach' }, value: { type: 'FunctionExpression' } },
@@ -203,20 +213,18 @@ module.exports = function(file, api) {
203213
}
204214

205215
function createModule(p) {
206-
let [moduleName, options, setupType, subject, hasCustomSubject, isNestedModule] = parseModule(
207-
p
208-
);
216+
let moduleInfo = new ModuleInfo(p);
209217

210-
if (isNestedModule) {
218+
if (moduleInfo.isNestedModule) {
211219
return null;
212220
}
213221

214222
let needsHooks = false;
215223
let moduleSetupExpression;
216-
if (setupType) {
224+
if (moduleInfo.setupType) {
217225
needsHooks = true;
218226
moduleSetupExpression = j.expressionStatement(
219-
j.callExpression(j.identifier(setupType), [j.identifier('hooks')])
227+
j.callExpression(j.identifier(moduleInfo.setupType), [j.identifier('hooks')])
220228
);
221229
}
222230

@@ -254,13 +262,13 @@ module.exports = function(file, api) {
254262
return node;
255263
}
256264

257-
let moduleInvocation = j.expressionStatement(buildModule(moduleName, callback));
265+
let moduleInvocation = j.expressionStatement(buildModule(moduleInfo.moduleName, callback));
258266

259-
if (options) {
267+
if (moduleInfo.moduleOptions) {
260268
let customMethodBeforeEachBody, customMethodBeforeEachExpression;
261269

262-
options.properties.forEach(property => {
263-
if (setupType) {
270+
moduleInfo.moduleOptions.properties.forEach(property => {
271+
if (moduleInfo.setupType) {
264272
let expressionCollection = j(property.value);
265273

266274
updateGetOwnerThisUsage(expressionCollection);
@@ -323,10 +331,10 @@ module.exports = function(file, api) {
323331
}
324332
});
325333

326-
if (setupType === 'setupRenderingTest') {
334+
if (moduleInfo.setupType === 'setupRenderingTest') {
327335
processExpressionForRenderingTest(callback);
328336
} else {
329-
processSubject(callback, subject);
337+
processSubject(callback, moduleInfo.subjectContainerKey);
330338
}
331339
}
332340

@@ -335,7 +343,11 @@ module.exports = function(file, api) {
335343
callback.params = [];
336344
}
337345

338-
return [moduleInvocation, callback.body.body, setupType, subject, hasCustomSubject];
346+
// [moduleInvocation, callback.body.body, setupType, subject, hasCustomSubject];
347+
moduleInfo.moduleInvocation = moduleInvocation;
348+
moduleInfo.moduleCallbackBody = callback.body.body;
349+
350+
return moduleInfo;
339351
}
340352

341353
function processExpressionForRenderingTest(testExpression) {
@@ -464,23 +476,20 @@ module.exports = function(file, api) {
464476
let programPath = root.get('program');
465477
let bodyPath = programPath.get('body');
466478

467-
let currentModuleCallbackBody, currentTestType, currentSubject, currentHasCustomSubject;
479+
let currentModuleInfo;
468480
bodyPath.each(expressionPath => {
469481
let expression = expressionPath.node;
470-
if (isModuleDefinition(expressionPath)) {
471-
let result = createModule(expressionPath);
472-
if (result === null) {
482+
if (ModuleInfo.isModuleDefinition(expressionPath)) {
483+
let moduleInfo = createModule(expressionPath);
484+
if (moduleInfo === null) {
473485
return;
474486
}
475-
expressionPath.replace(result[0]);
476-
currentModuleCallbackBody = result[1];
477-
currentTestType = result[2];
478-
currentSubject = result[3];
479-
currentHasCustomSubject = result[4];
480-
} else if (currentModuleCallbackBody) {
487+
currentModuleInfo = moduleInfo;
488+
expressionPath.replace(moduleInfo.moduleInvocation);
489+
} else if (currentModuleInfo) {
481490
// calling `path.replace()` essentially just removes
482491
expressionPath.replace();
483-
currentModuleCallbackBody.push(expression);
492+
currentModuleInfo.moduleCallbackBody.push(expression);
484493

485494
let isTest = j.match(expression, { expression: { callee: { name: 'test' } } });
486495
if (isTest) {
@@ -492,10 +501,13 @@ module.exports = function(file, api) {
492501
// transformed if the call site's scope is the same as the expression passed
493502
updateGetOwnerThisUsage(j(expression.expression.arguments[1]));
494503

495-
if (currentTestType === 'setupRenderingTest') {
504+
if (currentModuleInfo.setupType === 'setupRenderingTest') {
496505
processExpressionForRenderingTest(expression);
497-
} else if (currentTestType === 'setupTest' && !currentHasCustomSubject) {
498-
processSubject(expression, currentSubject);
506+
} else if (
507+
currentModuleInfo.setupType === 'setupTest' &&
508+
!currentModuleInfo.hasCustomSubjectImplementation
509+
) {
510+
processSubject(expression, currentModuleInfo.subjectContainerKey);
499511
}
500512
}
501513
}

0 commit comments

Comments
 (0)