Repository: rwjblue/ember-cli-template-lint
Pull request #81 (2016-04-16 18:14:02)

Add ability to get raw source for a node.


Patch:
diff --git a/broccoli-template-linter.js b/broccoli-template-linter.js
index b319828..c9174df 100644
--- a/broccoli-template-linter.js
+++ b/broccoli-template-linter.js
@@ -99,6 +99,7 @@ TemplateLinter.prototype.processString = function(contents, relativePath) {
 
   compile(contents, {
     moduleName: relativePath,
+    rawSource: contents,
     plugins: {
       ast: this.buildASTPlugins()
     }
diff --git a/ext/plugins/base.js b/ext/plugins/base.js
index b1aa082..5c1ccff 100644
--- a/ext/plugins/base.js
+++ b/ext/plugins/base.js
@@ -5,6 +5,9 @@ module.exports = function(addonContext, name) {
     this.options = options;
     this.syntax = null; // set by HTMLBars
 
+    // split into a source array (allow windows and posix line endings)
+    this.source = this.options.rawSource.split(/(?:\r\n?|\n)/g);
+
     this.config = this.parseConfig(
       addonContext.loadConfig()[name]
     );
@@ -91,5 +94,36 @@ module.exports = function(addonContext, name) {
     throw new Error('Must implemented #process');
   };
 
+  // mostly copy/pasta from tildeio/htmlbars with a few tweaks:
+  // https://github.com/tildeio/htmlbars/blob/v0.4.17/packages/htmlbars-syntax/lib/parser.js#L59-L90
+  BasePlugin.prototype.sourceForNode = function(node) {
+    var firstLine = node.loc.start.line - 1;
+    var lastLine = node.loc.end.line - 1;
+    var currentLine = firstLine - 1;
+    var firstColumn = node.loc.start.column;
+    var lastColumn = node.loc.end.column;
+    var string = [];
+    var line;
+
+    while (currentLine < lastLine) {
+      currentLine++;
+      line = this.source[currentLine];
+
+      if (currentLine === firstLine) {
+        if (firstLine === lastLine) {
+          string.push(line.slice(firstColumn, lastColumn));
+        } else {
+          string.push(line.slice(firstColumn));
+        }
+      } else if (currentLine === lastLine) {
+        string.push(line.slice(0, lastColumn));
+      } else {
+        string.push(line);
+      }
+    }
+
+    return string.join('\n');
+  };
+
   return BasePlugin;
 };


Test patch:
diff --git a/node-tests/helpers/rule-test-harness.js b/node-tests/helpers/rule-test-harness.js
index 1e2db75..404a77c 100644
--- a/node-tests/helpers/rule-test-harness.js
+++ b/node-tests/helpers/rule-test-harness.js
@@ -14,6 +14,7 @@ module.exports = function(options) {
 
     function compile(template) {
       _compile(template, {
+        rawSource: template,
         moduleName: 'layout.hbs',
         plugins: {
           ast: [
diff --git a/node-tests/unit/base-plugin-test.js b/node-tests/unit/base-plugin-test.js
new file mode 100644
index 0000000..fcc2594
--- /dev/null
+++ b/node-tests/unit/base-plugin-test.js
@@ -0,0 +1,80 @@
+'use strict';
+
+var assert = require('assert');
+var _compile = require('htmlbars').compile;
+var buildPlugin = require('./../../ext/plugins/base');
+var ast = require('./../../ext/helpers/ast-node-info');
+
+describe('base plugin tests', function() {
+  var addonContext, messages, config;
+
+  function plugin(addonContext) {
+    var FakePlugin = buildPlugin(addonContext, 'fake');
+
+    FakePlugin.prototype.detect = function(node) {
+      return ast.isElementNode(node) || ast.isTextNode(node);
+    };
+
+    FakePlugin.prototype.process = function(node) {
+      this.log(this.sourceForNode(node));
+    };
+
+    return FakePlugin;
+  }
+
+  function compile(template) {
+    _compile(template, {
+      rawSource: template,
+      moduleName: 'layout.hbs',
+      plugins: {
+        ast: [
+          plugin(addonContext)
+        ]
+      }
+    });
+  }
+
+  beforeEach(function() {
+    messages = [];
+    config   = {};
+
+    addonContext = {
+      logLintingError: function(pluginName, moduleName, message) {
+        messages.push(message);
+      },
+      loadConfig: function() {
+        return config;
+      }
+    };
+  });
+
+  function expectSource(config) {
+    var template = config.template;
+    var nodeSources = config.sources;
+
+    it('can get raw source for `' + template + '`', function() {
+      compile(template);
+
+      assert.deepEqual(messages, nodeSources);
+    });
+  }
+
+  expectSource({
+    template: '<div>Foo</div>',
+    sources: [
+      '<div>Foo</div>',
+      'Foo'
+    ]
+  });
+
+  expectSource({
+    template: '<div>\n  <div data-foo="blerp">\n    Wheee!\n  </div>\n</div>',
+    sources: [
+      '<div>\n  <div data-foo="blerp">\n    Wheee!\n  </div>\n</div>',
+      '\n  ',
+      '<div data-foo="blerp">\n    Wheee!\n  </div>',
+      '\n    Wheee!\n  ',
+      '\n'
+    ]
+  });
+});
diff --git a/node-tests/unit/linting-compiler-test.js b/node-tests/unit/linting-compiler-test.js
index f33df51..1e498f0 100644
--- a/node-tests/unit/linting-compiler-test.js
+++ b/node-tests/unit/linting-compiler-test.js
@@ -8,6 +8,7 @@ describe('Ember template compiler', function() {
 
   function compile(template) {
     return _compile(template, {
+      rawSource: template,
       plugins: {
         ast: astPlugins
       }
@@ -36,4 +37,19 @@ describe('Ember template compiler', function() {
 
     assert.equal(instanceCount, 1, 'registered plugins are instantiated');
   });
+
+  it('can access rawSource via options', function() {
+    var options;
+    var NoopPlugin = function(_options){
+      options = _options;
+    };
+    NoopPlugin.prototype.transform = function(ast) {
+      return ast;
+    };
+    astPlugins.push(NoopPlugin);
+    var expectedTemplate = '<div></div>';
+    compile(expectedTemplate);
+
+    assert.equal(expectedTemplate, options.rawSource, 'rawSource can be passed through compile options');
+  });
 });