-
Notifications
You must be signed in to change notification settings - Fork 43
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add a command to replace needs with injection
- Loading branch information
paddyobrien
committed
Oct 5, 2015
1 parent
6790486
commit 8df9ae3
Showing
16 changed files
with
290 additions
and
1 deletion.
There are no files selected for viewing
This file contains 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
This file contains 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
This file contains 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
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); | ||
} | ||
}; |
This file contains 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
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)] | ||
) | ||
); | ||
}; |
This file contains 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
This file contains 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
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') | ||
}); |
This file contains 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
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'] | ||
}); |
6 changes: 6 additions & 0 deletions
6
tests/fixtures/replace-needs-with-injection/duplicate-names-result.js
This file contains 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
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') | ||
}); |
8 changes: 8 additions & 0 deletions
8
tests/fixtures/replace-needs-with-injection/duplicate-names.js
This file contains 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
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' | ||
] | ||
}); |
6 changes: 6 additions & 0 deletions
6
tests/fixtures/replace-needs-with-injection/no-ember-import-result.js
This file contains 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
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') | ||
}); |
5 changes: 5 additions & 0 deletions
5
tests/fixtures/replace-needs-with-injection/no-ember-import.js
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
import SomeBaseController from 'somewhere'; | ||
|
||
export default SomeBaseController.extend({ | ||
needs: ['foo'] | ||
}); |
This file contains 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
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') | ||
}); |
This file contains 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
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' | ||
}); |
This file contains 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
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() {}) | ||
}); |
This file contains 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
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() {}) | ||
}); |
This file contains 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
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')); | ||
}); | ||
}); |