Repository: abuiles/ember-watson (js)

Issue:
Convert calls to this.resource in the router map to this.route { resetNamespace: true }
See https://github.com/emberjs/ember.js/pull/11517 for more info on this change.

Pull request description:
Created the resource-router-mapping transform
Fixes #50

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 420227d..3f43e1f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,9 @@
 # ember-watson Changelog
 
+### unreleased
+
+Created `convert-resource-router-mapping` command fixes [#50](https://github.com/abuiles/ember-watson/issues/50).
+
 ### 0.5.5
 
 Fixes issue [#47](https://github.com/abuiles/ember-watson/issues/47).
diff --git a/README.md b/README.md
index 22c1e32..36b7047 100644
--- a/README.md
+++ b/README.md
@@ -75,6 +75,10 @@ export default DS.Model.extend({
 });
 ```
 
+#### `ember watson:convert-resource-router-mapping <routerPath>`
+
+It takes the router path, by default `app/router.js`, and convert the deprecated `this.resource('user')` to `this.route('user', { resetNamespace: true })`. Also works for nested resources.
+
 ### Specifying a file or path.
 
 You can run any of the commands passing as argument the path, file or
diff --git a/addon/commands/convert-resource-router-mapping.js b/addon/commands/convert-resource-router-mapping.js
new file mode 100644
index 0000000..b3b3ef3
--- /dev/null
+++ b/addon/commands/convert-resource-router-mapping.js
@@ -0,0 +1,17 @@
+'use strict';
+
+var Watson = require('../../index');
+var watson = new Watson();
+
+module.exports = {
+  name: 'watson:convert-resource-router-mapping',
+  description: 'Convert the resource router mapping to use the route mapping with the new resetNamespace option',
+  works: 'insideProject',
+  anonymousOptions: [
+    '<routerPath>'
+  ],
+  run: function(commandOptions, rawArgs) {
+    var path = rawArgs[0] ||  'app/router.js';
+    watson.transformResourceRouteMapping(path);
+  }
+};
diff --git a/addon/commands/index.js b/addon/commands/index.js
index 2f70e6c..8011cad 100644
--- a/addon/commands/index.js
+++ b/addon/commands/index.js
@@ -4,5 +4,6 @@ module.exports = {
   'watson:upgrade-qunit-tests': require('./upgrade-qunit-tests'),
   'watson:convert-prototype-extensions': require('./convert-prototype-extensions'),
   'watson:convert-ember-data-model-lookups': require('./convert-ember-data-model-lookups'),
-  'watson:convert-ember-data-async-false-relationships': require('./convert-ember-data-async-false-relationships')
+  'watson:convert-ember-data-async-false-relationships': require('./convert-ember-data-async-false-relationships'),
+  'watson:convert-resource-router-mapping': require('./convert-resource-router-mapping')
 };
diff --git a/lib/cli.js b/lib/cli.js
index 2a2cc6a..52ea953 100644
--- a/lib/cli.js
+++ b/lib/cli.js
@@ -39,6 +39,14 @@ program
     watson.transformEmberDataAsyncFalseRelationships(appPath);
   });
 
+program
+  .command('convert-resource-router-mapping [routerPath]')
+  .description('convert the resource router mapping to use the route mapping with the new resetNamespace option')
+  .action(function(routerPath) {
+    routerPath = routerPath || 'app/router.js';
+    watson.transformResourceRouterMapping(routerPath);
+  });
+
 module.exports = function init(args) {
   program.parse(args);
 };
diff --git a/lib/formulas/resource-router-mapping.js b/lib/formulas/resource-router-mapping.js
new file mode 100644
index 0000000..d08c066
--- /dev/null
+++ b/lib/formulas/resource-router-mapping.js
@@ -0,0 +1,81 @@
+'use strict';
+
+var recast = require('recast');
+var builders = recast.types.builders;
+var types = recast.types.namedTypes;
+
+module.exports = function transformResourceRouterMapping(source) {
+  var ast = recast.parse(source);
+
+  recast.visit(ast, {
+    visitCallExpression: function (path) {
+      var node = path.node;
+
+      if (isRouter(node)) {
+        var routes = node.arguments[0].body.body;
+        traverseExpressions(routes);
+        return false;
+      } else {
+        this.traverse(path);
+      }
+    }
+  });
+
+  return recast.print(ast, {tabWidth: 2, quote: 'single'}).code;
+};
+
+function traverseExpressions(routes) {
+  routes.forEach(function (route) {
+    if (isRoute(route)) {
+      transformResourceToRoute(route);
+      var lastArgument = route.expression.arguments[route.expression.arguments.length-1];
+      if (types.FunctionExpression.check(lastArgument)) {
+        traverseExpressions(lastArgument.body.body);
+      }
+    }
+  });
+}
+
+function ensureOptionsObject(route) {
+  var args = route.expression.arguments;
+  var secondArg = args[1];
+
+  if (secondArg && types.ObjectExpression.check(secondArg)) {
+    return secondArg;
+  }
+
+  var optionsObject = builders.objectExpression([]);
+  args.splice(1, 0, optionsObject);
+
+  return optionsObject;
+}
+
+function transformResourceToRoute(route) {
+  var expression = route.expression;
+  var callee = expression.callee;
+  var optionsObject = ensureOptionsObject(route);
+  var resetNamespaceOption = builders.property(
+    'init',
+    builders.identifier('resetNamespace'),
+    builders.literal(true)
+  );
+
+  callee.property.name = 'route';
+  optionsObject.properties.push(resetNamespaceOption);
+}
+
+function isRoute(route) {
+  return types.ExpressionStatement.check(route) &&
+    types.CallExpression.check(route.expression) &&
+    types.MemberExpression.check(route.expression.callee) &&
+    types.ThisExpression.check(route.expression.callee.object) &&
+    types.Identifier.check(route.expression.callee.property) &&
+    route.expression.callee.property.name === 'resource';
+}
+
+function isRouter(node) {
+  var callee = node.callee;
+  return types.MemberExpression.check(callee) &&
+    types.Identifier.check(callee.property) &&
+    callee.property.name === 'map';
+}
diff --git a/lib/watson.js b/lib/watson.js
index db5cb86..5025646 100644
--- a/lib/watson.js
+++ b/lib/watson.js
@@ -7,6 +7,7 @@ var qunitTransforms = require('./formulas/qunit-transforms');
 var prototypeExtensionTransforms = require('./formulas/prototype-extension-transforms');
 var transformEmberDataModelLookups = require('./formulas/ember-data-model-lookup-transform');
 var transformEmberDataAsyncFalseRelationships = require('./formulas/ember-data-async-false-relationships-transforms');
+var transformResourceRouterMapping = require('./formulas/resource-router-mapping');
 
 
 module.exports = EmberWatson;
@@ -47,6 +48,12 @@ EmberWatson.prototype.transformPrototypeExtensions = function(rootPath) {
   transform(files, this._transformPrototypeExtensions);
 };
 
+EmberWatson.prototype._transformResourceRouterMapping = transformResourceRouterMapping;
+
+EmberWatson.prototype.transformResourceRouterMapping = function(routerPath) {
+  transform([routerPath], this._transformResourceRouterMapping);
+};
+
 function transform(files, transformFormula) {
   var wontFix = [];
 


Test patch:
diff --git a/tests/cli-test.js b/tests/cli-test.js
index c42602a..7b2233e 100644
--- a/tests/cli-test.js
+++ b/tests/cli-test.js
@@ -19,6 +19,10 @@ var commands = {
   'convert-ember-data-async-false-relationships': {
     method: 'transformEmberDataAsyncFalseRelationships',
     path: 'app'
+  },
+  'convert-resource-router-mapping': {
+    method: 'transformResourceRouterMapping',
+    path: 'app/router.js'
   }
 };
 
diff --git a/tests/fixtures/resource-router-mapping/new-ember-cli-sample.js b/tests/fixtures/resource-router-mapping/new-ember-cli-sample.js
new file mode 100644
index 0000000..74ac8c4
--- /dev/null
+++ b/tests/fixtures/resource-router-mapping/new-ember-cli-sample.js
@@ -0,0 +1,21 @@
+import Ember from 'ember';
+import config from './config/environment';
+
+var Router = Ember.Router.extend({
+  location: config.locationType
+});
+
+Router.map(function() {
+  this.route('users', {
+    resetNamespace: true
+  }, function() {
+    this.route('edit', { path: ':user_id/edit' });
+  });
+  this.route('dogs', {
+    resetNamespace: true
+  }, function() {
+    this.route('edit', { path: ':dog_id/edit' });
+  });
+});
+
+export default Router;
diff --git a/tests/fixtures/resource-router-mapping/new-foos-bar-route.js b/tests/fixtures/resource-router-mapping/new-foos-bar-route.js
new file mode 100644
index 0000000..071285b
--- /dev/null
+++ b/tests/fixtures/resource-router-mapping/new-foos-bar-route.js
@@ -0,0 +1,10 @@
+Router.map(function() {
+  this.route('foos', {
+    resetNamespace: true
+  }, function() {
+    this.route('bars', {
+      resetNamespace: true
+    }, function() {
+    });
+  });
+});
diff --git a/tests/fixtures/resource-router-mapping/new-foos-route.js b/tests/fixtures/resource-router-mapping/new-foos-route.js
new file mode 100644
index 0000000..c22cf7b
--- /dev/null
+++ b/tests/fixtures/resource-router-mapping/new-foos-route.js
@@ -0,0 +1,5 @@
+Router.map(function() {
+  this.route('foos', {
+    resetNamespace: true
+  });
+});
diff --git a/tests/fixtures/resource-router-mapping/old-ember-cli-sample.js b/tests/fixtures/resource-router-mapping/old-ember-cli-sample.js
new file mode 100644
index 0000000..10b66f7
--- /dev/null
+++ b/tests/fixtures/resource-router-mapping/old-ember-cli-sample.js
@@ -0,0 +1,17 @@
+import Ember from 'ember';
+import config from './config/environment';
+
+var Router = Ember.Router.extend({
+  location: config.locationType
+});
+
+Router.map(function() {
+  this.resource('users', function() {
+    this.route('edit', { path: ':user_id/edit' });
+  });
+  this.resource('dogs', function() {
+    this.route('edit', { path: ':dog_id/edit' });
+  });
+});
+
+export default Router;
diff --git a/tests/fixtures/resource-router-mapping/old-foos-bar-route.js b/tests/fixtures/resource-router-mapping/old-foos-bar-route.js
new file mode 100644
index 0000000..d40f389
--- /dev/null
+++ b/tests/fixtures/resource-router-mapping/old-foos-bar-route.js
@@ -0,0 +1,6 @@
+Router.map(function() {
+  this.resource('foos', function() {
+    this.resource('bars', function() {
+    });
+  });
+});
diff --git a/tests/fixtures/resource-router-mapping/old-foos-route.js b/tests/fixtures/resource-router-mapping/old-foos-route.js
new file mode 100644
index 0000000..d6627bc
--- /dev/null
+++ b/tests/fixtures/resource-router-mapping/old-foos-route.js
@@ -0,0 +1,3 @@
+Router.map(function() {
+  this.resource('foos');
+});
diff --git a/tests/resource-route-mapping-test.js b/tests/resource-route-mapping-test.js
new file mode 100644
index 0000000..eecd5ea
--- /dev/null
+++ b/tests/resource-route-mapping-test.js
@@ -0,0 +1,40 @@
+'use strict';
+
+var Watson      = require('../index.js');
+var fs          = require('fs');
+var astEquality = require('./helpers/ast-equality');
+
+describe('Resource router mapping', function() {
+  var baseDir = './tests/fixtures/resource-router-mapping';
+  var watson;
+
+  beforeEach(function() {
+    watson = new Watson();
+  });
+
+  describe('convert resource to route with resetNamespace option', function() {
+    it('transoform simple resource', function() {
+      var source            = fs.readFileSync(baseDir + '/old-foos-route.js');
+      var newSource         = watson._transformResourceRouterMapping(source);
+      var expectedNewSource = fs.readFileSync(baseDir + '/new-foos-route.js');
+
+      astEquality(newSource, expectedNewSource);
+    });
+
+    it('transform nested resources', function() {
+      var source            = fs.readFileSync(baseDir + '/old-foos-bar-route.js');
+      var newSource         = watson._transformResourceRouterMapping(source);
+      var expectedNewSource = fs.readFileSync(baseDir + '/new-foos-bar-route.js');
+
+      astEquality(newSource, expectedNewSource);
+    });
+
+    it('transform ember-cli file format', function() {
+      var source            = fs.readFileSync(baseDir + '/old-ember-cli-sample.js');
+      var newSource         = watson._transformResourceRouterMapping(source);
+      var expectedNewSource = fs.readFileSync(baseDir + '/new-ember-cli-sample.js');
+
+      astEquality(newSource, expectedNewSource);
+    });
+  });
+});