Skip to content

Commit

Permalink
Add a command to replace needs with injection
Browse files Browse the repository at this point in the history
  • Loading branch information
paddyobrien committed Oct 5, 2015
1 parent 6790486 commit 8df9ae3
Show file tree
Hide file tree
Showing 16 changed files with 290 additions and 1 deletion.
8 changes: 8 additions & 0 deletions lib/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,14 @@ program
watson.transformTestToUseDestroyApp(path);
});

program
.command('replace-needs-with-injection [path]')
.description('Replace needs with controller injection.')
.action(function(path) {
path = path || 'app';
watson.replaceNeedsWithInjection(path);
});

module.exports = function init(args) {
program.parse(args);
};
3 changes: 2 additions & 1 deletion lib/commands/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@ module.exports = {
'watson:methodify': require('./methodify'),
'watson:convert-resource-router-mapping': require('./convert-resource-router-mapping'),
'watson:find-overloaded-cps': require('./find-overloaded-cps'),
'watson:use-destroy-app-helper': require('./use-destroy-app-helper')
'watson:use-destroy-app-helper': require('./use-destroy-app-helper'),
'watson:replace-needs-with-injection': require('./replace-needs-with-injection')
};
17 changes: 17 additions & 0 deletions lib/commands/replace-needs-with-injection.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
'use strict';

var Watson = require('../../index');
var watson = new Watson();

module.exports = {
name: 'watson:replace-needs-with-injection',
description: 'Replace needs with controller injection.',
works: 'insideProject',
anonymousOptions: [
'<path>'
],
run: function(commandOptions, rawArgs) {
var path = rawArgs[0] || 'app/controllers';
watson.replaceNeedsWithInjection(path);
}
};
154 changes: 154 additions & 0 deletions lib/formulas/replace-needs-with-injection.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
var parseAst = require('../helpers/parse-ast');
var recast = require('recast');
var b = recast.types.builders;
var isImportFor = require('./helpers/is-import-for');
var addDefaultImport = require('./helpers/add-default-import');

module.exports = function transform(source) {
var ast = parseAst(source);
var needs, emberImport;
var uses = [];
recast.visit(ast, {
visitProperty: function(path) {
if(isNeedsDeclaration(path)) {
needs = path;
return false;
}
this.traverse(path);
},
visitLiteral: function(path) {
if (isNeedsUsage(path)) {
uses.push(path);
}
this.traverse(path);
},
visitImportDeclaration: function(path) {
if (isImportFor('ember', path.node)) {
emberImport = path;
}
this.traverse(path);
},
});

transformNeeds(ast, needs, uses, emberImport);
return recast.print(ast, { tabWidth: 2, quote: 'single' }).code;
};

function transformNeeds(ast, needs, uses, emberImport) {
if (needs) {
// What is Ember imported as?
var emberIdentifier = getEmberImportName(ast, emberImport);
// Get a normal array of needs names
var needsArray = normaliseNeeds(needs);
// Generate a map from controller path to new property names
var newKeysMap = generateNewKeysMap(needsArray);
// Update the paths
replaceNeedsProperty(emberIdentifier, needs, newKeysMap);
if (uses.length > 0) {
transformUses(uses, newKeysMap);
}
}
}

var replaceNeedsProperty = function(emberIdentifier, path, newKeyMap) {
var replacements = [];
if (path.value.value.elements) {
path.value.value.elements.forEach(function(element) {
path.parentPath.unshift(buildInjectionExpression(emberIdentifier, element.value, newKeyMap));
});
path.replace();
} else {
path.replace(buildInjectionExpression(emberIdentifier, path.value.value.value, newKeyMap));
}
};

var transformUses = function(paths, newKeyMap) {
paths.forEach(function(path) {
for(var prop in newKeyMap) {
var regex = new RegExp('controllers\\.' + prop + '(\\.|$)');
var match = path.value.value.match(regex);
if (match) {
var newLiteral = path.value.value.replace(regex, newKeyMap[prop] + match[1]);
path.replace(b.literal(newLiteral));
}
}
});
};

function capitalizeFirstLetter(string) {
return string.charAt(0).toUpperCase() + string.slice(1);
}

function invertMap(map) {
var invertedMap = {};
for(var prop in map) {
if(map.hasOwnProperty(prop)) {
invertedMap[map[prop]] = prop;
}
}
return invertedMap;
}

var generateNewKeysMap = function(needsArray) {
var newKeys = {};
needsArray.forEach(function(needsNode) {
var needsValue = needsNode.value;
var needsParts = needsValue.split('/');
var key = 'Controller';
do {
key = capitalizeFirstLetter(key);
key = needsParts.pop().replace(/-/g, '') + key;
} while(newKeys[key] !== undefined);
newKeys[key] = needsValue;
});
return invertMap(newKeys);
};

function isNeedsUsage(path) {
return path &&
path.value &&
typeof path.value.value === 'string' &&
path.value.value.indexOf('controllers.') === 0;
}

function isNeedsDeclaration(path) {
return path.value.key.name === 'needs';
}

function normaliseNeeds(needs) {
var needsArray = needs.value.value.elements;
if (needsArray === undefined) {
needsArray = [needs.value.value];
}
return needsArray;
}

function getEmberImportName(ast, emberImport) {
if (!emberImport) {
addDefaultImport(ast, 'ember', 'Ember');
emberIdentifier = 'Ember';
} else {
emberIdentifier = emberImport.value.specifiers[0].local.name;
}
return emberIdentifier;
}

var buildInjectionExpression = function(emberIdentifier, controllerPath, newKeyMap) {
var name = newKeyMap[controllerPath];
return b.property(
'init',
b.identifier(name),
b.callExpression(
b.memberExpression(
b.identifier(emberIdentifier),
b.memberExpression(
b.identifier("inject"),
b.identifier("controller"),
false
),
false
),
[b.literal(controllerPath)]
)
);
};
8 changes: 8 additions & 0 deletions lib/watson.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ var transformResourceRouterMapping = require('./formulas/resource-router-mapping
var transformMethodify = require('./formulas/methodify');
var FindOverloadedCPs = require('./formulas/find-overloaded-cps');
var transformDestroyApp = require('./formulas/destroy-app-transform');
var replaceNeedsWithInjection = require('./formulas/replace-needs-with-injection');

module.exports = EmberWatson;

Expand Down Expand Up @@ -67,6 +68,13 @@ EmberWatson.prototype.transformResourceRouterMapping = function(routerPath) {

EmberWatson.prototype._transformDestroyApp = transformDestroyApp;

EmberWatson.prototype.replaceNeedsWithInjection = function(path) {
var files = findFiles(path, '.js');
transform(files, this._replaceNeedsWithInjection);
};

EmberWatson.prototype._replaceNeedsWithInjection = replaceNeedsWithInjection;

EmberWatson.prototype.transformTestToUseDestroyApp = function(rootPath) {
if (!existsSync('tests/helpers/destroy-app.js')) {
console.log(
Expand Down
6 changes: 6 additions & 0 deletions tests/fixtures/replace-needs-with-injection/array-result.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import Em from 'ember';

export default Ember.Controller.extend({
barController: Em.inject.controller('bar'),
fooController: Em.inject.controller('foo')
});
5 changes: 5 additions & 0 deletions tests/fixtures/replace-needs-with-injection/array.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import Em from 'ember';

export default Ember.Controller.extend({
needs: ['foo', 'bar']
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import Em from 'ember';

export default Ember.Controller.extend({
fooBarController: Em.inject.controller('foo/bar'),
bazBarController: Em.inject.controller('baz/bar')
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import Em from 'ember';

export default Ember.Controller.extend({
needs: [
'foo/bar',
'baz/bar'
]
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import Ember from 'ember';
import SomeBaseController from 'somewhere';

export default SomeBaseController.extend({
fooController: Ember.inject.controller('foo')
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import SomeBaseController from 'somewhere';

export default SomeBaseController.extend({
needs: ['foo']
});
5 changes: 5 additions & 0 deletions tests/fixtures/replace-needs-with-injection/single-result.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import Ember from 'ember';

export default Ember.Controller.extend({
fooController: Ember.inject.controller('foo')
});
5 changes: 5 additions & 0 deletions tests/fixtures/replace-needs-with-injection/single.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import Ember from 'ember';

export default Ember.Controller.extend({
needs: 'foo'
});
7 changes: 7 additions & 0 deletions tests/fixtures/replace-needs-with-injection/uses-result.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import Em from 'ember';

export default Ember.Controller.extend({
fooController: Em.inject.controller('foo'),
fooModel: Em.computed.alias('fooController.model'),
computedFoo: Em.computed('fooController', function() {})
});
7 changes: 7 additions & 0 deletions tests/fixtures/replace-needs-with-injection/uses.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import Em from 'ember';

export default Ember.Controller.extend({
needs: ['foo'],
fooModel: Em.computed.alias('controllers.foo.model'),
computedFoo: Em.computed('controllers.foo', function() {})
});
41 changes: 41 additions & 0 deletions tests/replace-needs-with-injection-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
var Watson = require('../index.js');
var fs = require('fs');
var astEquality = require('./helpers/ast-equality');

describe('replacing deprecated needs with controller injection', function() {

it('replaces single needs', function() {
var source = fs.readFileSync('./tests/fixtures/replace-needs-with-injection/single.js');
var watson = new Watson();
var newSource = watson._replaceNeedsWithInjection(source);
astEquality(newSource, fs.readFileSync('./tests/fixtures/replace-needs-with-injection/single-result.js'));
});

it('replaces needs arrays', function() {
var source = fs.readFileSync('./tests/fixtures/replace-needs-with-injection/array.js');
var watson = new Watson();
var newSource = watson._replaceNeedsWithInjection(source);
astEquality(newSource, fs.readFileSync('./tests/fixtures/replace-needs-with-injection/array-result.js'));
});

it('dedupes controller names', function() {
var source = fs.readFileSync('./tests/fixtures/replace-needs-with-injection/array.js');
var watson = new Watson();
var newSource = watson._replaceNeedsWithInjection(source);
astEquality(newSource, fs.readFileSync('./tests/fixtures/replace-needs-with-injection/array-result.js'));
});

it('replaces uses', function() {
var source = fs.readFileSync('./tests/fixtures/replace-needs-with-injection/uses.js');
var watson = new Watson();
var newSource = watson._replaceNeedsWithInjection(source);
astEquality(newSource, fs.readFileSync('./tests/fixtures/replace-needs-with-injection/uses-result.js'));
});

it('adds an import if needed', function() {
var source = fs.readFileSync('./tests/fixtures/replace-needs-with-injection/no-ember-import.js');
var watson = new Watson();
var newSource = watson._replaceNeedsWithInjection(source);
astEquality(newSource, fs.readFileSync('./tests/fixtures/replace-needs-with-injection/no-ember-import-result.js'));
});
});

0 comments on commit 8df9ae3

Please sign in to comment.