Repository: rwjblue/ember-template-lint
Pull request #122 (2016-10-28 18:18:12)

Add deprecation rule about inline View helper
Good: `{{foo}}`
Bad: `{{view 'foo'}}`, `{{view.some.path}}`

For https://github.com/rwjblue/ember-cli-template-lint/issues/28



Patch:
diff --git a/README.md b/README.md
index bef2124..547dc84 100644
--- a/README.md
+++ b/README.md
@@ -388,6 +388,38 @@ Instead, you should write the template as:
 
 More information is available at the [Deprecation Guide](http://emberjs.com/deprecations/v1.x/#toc_code-in-code-syntax-for-code-each-code).
 
+#### deprecated-inline-view-helper
+
+In Ember 1.12, support for invoking the inline View helper was deprecated.
+
+For example, this rule forbids the following:
+
+```hbs
+{{view 'this-is-bad'}}
+
+{{view.also-bad}}
+
+{{qux-qaz please=view.stop}}
+
+{{#not-this please=view.stop}}{{/not-this}}
+
+<div foo={{view.bar}}></div>
+```
+
+Instead, you should use:
+
+```hbs
+{{this-is-better}}
+
+{{qux-qaz this=good}}
+
+{{#ok-this yay=nice}}{{/ok-this}}
+
+<div foo={{bar}}></div>
+```
+
+More information is available at the [Deprecation Guide](http://emberjs.com/deprecations/v1.x/#toc_ember-view).
+
 ## Contributing
 
 A few ideas for where to take this in the future:
diff --git a/lib/config/recommended.js b/lib/config/recommended.js
index efd33a4..10d493f 100644
--- a/lib/config/recommended.js
+++ b/lib/config/recommended.js
@@ -7,7 +7,8 @@ module.exports = {
     'nested-interactive': true,
     'self-closing-void-elements': true,
     'triple-curlies': true,
-    'deprecated-each-syntax': true
+    'deprecated-each-syntax': true,
+    'deprecated-inline-view-helper': true
   },
 
   pending: []
diff --git a/lib/rules/deprecations/lint-deprecated-inline-view-helper.js b/lib/rules/deprecations/lint-deprecated-inline-view-helper.js
new file mode 100644
index 0000000..f1968ec
--- /dev/null
+++ b/lib/rules/deprecations/lint-deprecated-inline-view-helper.js
@@ -0,0 +1,150 @@
+'use strict';
+
+var buildPlugin = require('../base');
+
+var DEPRECATION_URL = 'http://emberjs.com/deprecations/v1.x/#toc_ember-view';
+
+var message = 'The inline form of `view` is deprecated. Please use the `Ember.Component` instead. ' +
+ 'See the deprecation guide at ' + DEPRECATION_URL;
+
+function asElementAsAttributeString(tag, attributeName, value) {
+  return '<' + tag + ' ' + attributeName + '={{' + value + '}}>' + '</' + tag + '>';
+}
+
+function asPassedPropertyString(componentName, keyName, value) {
+  return '{{' + componentName + ' ' + keyName + '=' + value + '}}';
+}
+
+function inBlockStatementString(componentName, keyName, value) {
+  return '{{#' + componentName + ' ' + keyName + '=' + value + '}}{{/' + componentName + '}}';
+}
+
+function logMessage(context, loc, actual, expected) {
+  return context.log({
+    message: message,
+    line: loc && loc.start.line,
+    column: loc && loc.start.column,
+    source: actual,
+    fix: {
+      text: expected
+    }
+  });
+}
+
+function manageMustacheViewInvocation(context, node, isBlockStatement) {
+  var pairs = node.hash.pairs;
+
+  if (pairs.length > 0) {
+    for (var i = 0; i < pairs.length; i++) {
+      var currentPair = pairs[i];
+      var originalValue = currentPair.value.original;
+
+      if (originalValue.split('.')[0] == 'view') {
+        if (isBlockStatement) {
+          context.processInBlockStatement(node, currentPair);
+        } else {
+          context.processAsPassedProperty(node, currentPair);
+        }
+      }
+    }
+  }
+}
+
+module.exports = function(addonContext) {
+  var DeprecatedInlineViewHelper = buildPlugin(addonContext, 'deprecated-inline-view-helper');
+
+  DeprecatedInlineViewHelper.prototype.visitors = function() {
+    return {
+      ElementNode: function(node) {
+        var attributes = node.attributes;
+
+        for (var i = 0; i < attributes.length; i++) {
+          var currentAttribute = attributes[i];
+          var value = currentAttribute.value;
+
+          if (value.type === 'MustacheStatement') {
+            var originalValue = value.path.original;
+
+            if (originalValue.split('.')[0] == 'view') {
+              this.processAsElementAttribute(node, currentAttribute);
+            }
+          }
+        }
+      },
+
+      MustacheStatement: function(node) {
+        if (node.path.parts[0] === 'view') {
+          if (node.params.length === 1) {
+            this.processWithArgument(node);
+          } else if (node.loc.start.line !== null) {
+            this.processWithProperty(node);
+          }
+        } else {
+          manageMustacheViewInvocation(this, node, false);
+        }
+      },
+
+      BlockStatement: function(node) {
+        manageMustacheViewInvocation(this, node, true);
+      }
+    };
+  };
+
+  DeprecatedInlineViewHelper.prototype.processAsElementAttribute = function(node, attribute) {
+    var loc = node.loc;
+    var tag = node.tag;
+    var originalValue = attribute.value.path.original;
+    var strippedValue = originalValue.replace('view.', '');
+    var actual = asElementAsAttributeString(tag, attribute.name, originalValue);
+    var expected = asElementAsAttributeString(tag, attribute.name, strippedValue);
+
+    logMessage(this, loc, actual, expected);
+  };
+
+  DeprecatedInlineViewHelper.prototype.processWithProperty = function(node) {
+    var loc = node.loc;
+    var originalValue = node.path.original;
+    var strippedValue = originalValue.replace('view.', '');
+    var actual = '{{' + originalValue + '}}';
+    var expected = '{{' + strippedValue + '}}';
+
+    logMessage(this, loc, actual, expected);
+  };
+
+  DeprecatedInlineViewHelper.prototype.processWithArgument = function(node) {
+    var loc = node.loc;
+    var originalValue = node.params[0].original;
+    var actual = '{{view \'' + originalValue + '\'}}';
+    var expected = '{{' + originalValue + '}}';
+
+    logMessage(this, loc, actual, expected);
+  };
+
+  DeprecatedInlineViewHelper.prototype.processAsPassedProperty = function(node, pair) {
+    var loc = pair.loc;
+    var componentName = node.path.original;
+    var keyName = pair.key;
+    var originalValue = pair.value.original;
+    var strippedValue = originalValue.replace('view.', '');
+    var actual = asPassedPropertyString(componentName, keyName, originalValue);
+    var expected = asPassedPropertyString(componentName, keyName, strippedValue);
+
+    logMessage(this, loc, actual, expected);
+  };
+
+  DeprecatedInlineViewHelper.prototype.processInBlockStatement = function(node, pair) {
+    var loc = pair.loc;
+    var componentName = node.path.original;
+    var keyName = pair.key;
+    var originalValue = pair.value.original;
+    var strippedValue = originalValue.replace('view.', '');
+    var actual = inBlockStatementString(componentName, keyName, originalValue);
+    var expected = inBlockStatementString(componentName, keyName, strippedValue);
+
+    logMessage(this, loc, actual, expected);
+  };
+
+  return DeprecatedInlineViewHelper;
+};
+
+module.exports.DEPRECATION_URL = DEPRECATION_URL;
diff --git a/lib/rules/index.js b/lib/rules/index.js
index aa5c414..3b14e22 100644
--- a/lib/rules/index.js
+++ b/lib/rules/index.js
@@ -11,6 +11,7 @@ module.exports = {
   'nested-interactive': require('./lint-nested-interactive'),
   'inline-link-to': require('./lint-inline-link-to'),
   'deprecated-each-syntax': require('./deprecations/lint-deprecated-each-syntax'),
+  'deprecated-inline-view-helper': require('./deprecations/lint-deprecated-inline-view-helper'),
   'invalid-interactive': require('./lint-invalid-interactive'),
   'style-concatenation': require('./lint-style-concatenation')
 };


Test patch:
diff --git a/test/unit/rules/deprecations/lint-deprecated-inline-view-helper-test.js b/test/unit/rules/deprecations/lint-deprecated-inline-view-helper-test.js
new file mode 100644
index 0000000..c1e6ad7
--- /dev/null
+++ b/test/unit/rules/deprecations/lint-deprecated-inline-view-helper-test.js
@@ -0,0 +1,112 @@
+'use strict';
+
+var generateRuleTests = require('../../../helpers/rule-test-harness');
+var DEPRECATION_URL = require('../../../../lib/rules/deprecations/lint-deprecated-inline-view-helper').DEPRECATION_URL;
+
+var message = 'The inline form of `view` is deprecated. Please use the `Ember.Component` instead. ' +
+  'See the deprecation guide at ' + DEPRECATION_URL;
+
+generateRuleTests({
+  name: 'deprecated-inline-view-helper',
+
+  config: true,
+
+  good: [
+    {
+      template: '{{great-fishsticks}}'
+    }
+  ],
+
+  bad: [
+    {
+      template: '{{view \'awful-fishsticks\'}}',
+
+      result: {
+        rule: 'deprecated-inline-view-helper',
+        message: message,
+        moduleId: 'layout.hbs',
+        source: '{{view \'awful-fishsticks\'}}',
+        line: 1,
+        column: 0,
+        fix: {
+          text: '{{awful-fishsticks}}'
+        }
+      }
+    },
+    {
+      template: '{{view.bad-fishsticks}}',
+
+      result: {
+        rule: 'deprecated-inline-view-helper',
+        message: message,
+        moduleId: 'layout.hbs',
+        source: '{{view.bad-fishsticks}}',
+        line: 1,
+        column: 0,
+        fix: {
+          text: '{{bad-fishsticks}}'
+        }
+      }
+    },
+    {
+      template: '{{view.terrible.fishsticks}}',
+
+      result: {
+        rule: 'deprecated-inline-view-helper',
+        message: message,
+        moduleId: 'layout.hbs',
+        source: '{{view.terrible.fishsticks}}',
+        line: 1,
+        column: 0,
+        fix: {
+          text: '{{terrible.fishsticks}}'
+        }
+      }
+    },
+    {
+      template: '{{foo-bar bab=good baz=view.qux.qaz boo=okay}}',
+
+      result: {
+        rule: 'deprecated-inline-view-helper',
+        message: message,
+        moduleId: 'layout.hbs',
+        source: '{{foo-bar baz=view.qux.qaz}}',
+        line: 1,
+        column: 19,
+        fix: {
+          text: '{{foo-bar baz=qux.qaz}}'
+        }
+      }
+    },
+    {
+      template: '<div class="whatever-class" data-foo={{view.hallo}} sure=thing></div>',
+
+      result: {
+        rule: 'deprecated-inline-view-helper',
+        message: message,
+        moduleId: 'layout.hbs',
+        source: '<div data-foo={{view.hallo}}></div>',
+        line: 1,
+        column: 0,
+        fix: {
+          text: '<div data-foo={{hallo}}></div>'
+        }
+      }
+    },
+    {
+      template: '{{#foo-bar derp=view.whoops thing=whatever}}{{/foo-bar}}',
+
+      result: {
+        rule: 'deprecated-inline-view-helper',
+        message: message,
+        moduleId: 'layout.hbs',
+        source: '{{#foo-bar derp=view.whoops}}{{/foo-bar}}',
+        line: 1,
+        column: 11,
+        fix: {
+          text: '{{#foo-bar derp=whoops}}{{/foo-bar}}'
+        }
+      }
+    }
+  ]
+});