Repository: ember-template-lint/ember-template-lint
Pull request #896 (2019-11-03 16:54:30)

Pass `moduleName` and `rawSource` to `Rule` upon creation.
Deprecate `templateEnvironmentData` API.


Patch:
diff --git a/lib/index.js b/lib/index.js
index f5ff6ff4..a16a9100 100644
--- a/lib/index.js
+++ b/lib/index.js
@@ -24,10 +24,7 @@ function buildErrorMessage(moduleId, error) {
   return message;
 }
 
-function buildPlugin(rule, metadata) {
-  // TODO: refactor this to avoid needing to set it lazily
-  rule.templateEnvironmentData = metadata;
-
+function buildPlugin(rule) {
   let visitor = rule.getVisitor();
 
   return visitor;
@@ -90,6 +87,8 @@ class Linter {
           log: addToResults,
           defaultSeverity: this._defaultSeverityForRule(ruleName, config.pending),
           ruleNames: Object.keys(loadedRules),
+          moduleName: config.moduleName,
+          rawSource: config.rawSource,
         });
 
         rules.push(rule);
@@ -156,16 +155,13 @@ class Linter {
       results: messages,
       pending: pendingStatus,
       moduleId: options.moduleId,
-    });
-
-    let pluginConfig = {
       moduleName: options.moduleId,
       rawSource: source,
-    };
+    });
 
     for (let rule of rules) {
       try {
-        traverse(templateAST, buildPlugin(rule, pluginConfig));
+        traverse(templateAST, buildPlugin(rule));
       } catch (error) {
         let message = buildErrorMessage(options.moduleId, error);
         messages.push(message);
diff --git a/lib/rules/base.js b/lib/rules/base.js
index 2a6bf488..878ea0be 100644
--- a/lib/rules/base.js
+++ b/lib/rules/base.js
@@ -54,19 +54,24 @@ module.exports = class BaseRule {
     this.config = this.parseConfig(options.config);
     this._configStack = [this.config];
 
-    this._templateEnvironmentData = null;
-    this.source = null;
-  }
+    this._moduleName = options.moduleName;
+    this._rawSource = options.rawSource;
 
-  get templateEnvironmentData() {
-    return this._templateEnvironmentData;
+    // split into a source array (allow windows and posix line endings)
+    this.source = options.rawSource.match(reLines);
   }
 
-  set templateEnvironmentData(env) {
-    this._templateEnvironmentData = env;
+  get templateEnvironmentData() {
+    this._console.warn(
+      chalk.yellow(
+        `[DEPRECATED] Using \`this.templateEnvironmentData\` is deprecated. Please update ${this.ruleName} to avoid it`
+      )
+    );
 
-    // split into a source array (allow windows and posix line endings)
-    this.source = env.rawSource.match(reLines);
+    return {
+      moduleName: this._moduleName,
+      rawSource: this._rawSource,
+    };
   }
 
   // The `name !== this.ruleName` check isn't strictly necessary, but allows
@@ -196,13 +201,10 @@ module.exports = class BaseRule {
       }
 
       if (loggedNodes.indexOf(node) === -1) {
-        let locationInfo = calculateLocationDisplay(
-          pluginContext.templateEnvironmentData.moduleName,
-          {
-            line: node.loc.start.line,
-            column: node.loc.start.column,
-          }
-        );
+        let locationInfo = calculateLocationDisplay(pluginContext._moduleName, {
+          line: node.loc.start.line,
+          column: node.loc.start.column,
+        });
 
         pluginContext._console.log(
           chalk.yellow(
@@ -548,8 +550,8 @@ module.exports = class BaseRule {
       rule: this.ruleName,
       severity: this.severity,
     };
-    if (this.templateEnvironmentData) {
-      defaults.moduleId = this.templateEnvironmentData.moduleName;
+    if (this._moduleName) {
+      defaults.moduleId = this._moduleName;
     }
     let reportedResult = Object.assign({}, defaults, result);
 
diff --git a/lib/rules/lint-no-attrs-in-components.js b/lib/rules/lint-no-attrs-in-components.js
index f4d493b5..4ea3390a 100644
--- a/lib/rules/lint-no-attrs-in-components.js
+++ b/lib/rules/lint-no-attrs-in-components.js
@@ -8,7 +8,7 @@ const componentTemplateRegex = new RegExp(
 
 module.exports = class LinkNoAttrComponent extends Rule {
   isComponentTemplate() {
-    return componentTemplateRegex.test(this.templateEnvironmentData.moduleName);
+    return componentTemplateRegex.test(this._moduleName);
   }
   visitor() {
     return {
diff --git a/lib/rules/lint-no-outlet-outside-routes.js b/lib/rules/lint-no-outlet-outside-routes.js
index 27f7f372..37053a1c 100644
--- a/lib/rules/lint-no-outlet-outside-routes.js
+++ b/lib/rules/lint-no-outlet-outside-routes.js
@@ -5,10 +5,6 @@ const isRouteTemplate = require('../helpers/is-route-template');
 
 const message = 'Unexpected {{outlet}} usage. Only use {{outlet}} within a route template.';
 
-function getModuleName(visitor) {
-  return visitor._templateEnvironmentData.moduleName;
-}
-
 module.exports = class NoOutletOutsideRoutes extends Rule {
   _checkForOutlet(node) {
     if (this.__isRouteTemplate === true) {
@@ -26,7 +22,7 @@ module.exports = class NoOutletOutsideRoutes extends Rule {
   }
 
   visitor() {
-    this.__isRouteTemplate = isRouteTemplate(getModuleName(this));
+    this.__isRouteTemplate = isRouteTemplate(this._moduleName);
 
     return {
       MustacheStatement: this._checkForOutlet,


Test patch:
diff --git a/test/unit/base-plugin-test.js b/test/unit/base-plugin-test.js
index 85954e3b..95258a63 100644
--- a/test/unit/base-plugin-test.js
+++ b/test/unit/base-plugin-test.js
@@ -1,21 +1,27 @@
 'use strict';
 
 const expect = require('chai').expect;
-const _preprocess = require('@glimmer/syntax').preprocess;
+const { traverse, preprocess } = require('@glimmer/syntax');
 const Rule = require('./../../lib/rules/base');
 const { readdirSync, existsSync, readFileSync } = require('fs');
 const { join } = require('path');
 const ruleNames = Object.keys(require('../../lib/rules'));
 
 describe('base plugin', function() {
-  function precompileTemplate(template, ast) {
-    _preprocess(template, {
-      rawSource: template,
-      moduleName: 'layout.hbs',
-      plugins: {
-        ast,
-      },
-    });
+  function runRules(template, rules) {
+    let ast = preprocess(template);
+
+    for (let ruleConfig of rules) {
+      let { Rule } = ruleConfig;
+      let options = Object.assign({}, ruleConfig, {
+        moduleName: 'layout.hbs',
+        rawSource: template,
+      });
+
+      let rule = new Rule(options);
+
+      traverse(ast, rule.getVisitor());
+    }
   }
 
   let messages, config;
@@ -48,15 +54,7 @@ describe('base plugin', function() {
   }
 
   function plugin(Rule, name, config) {
-    let plugin = new Rule({ name, config, ruleNames });
-
-    return env => {
-      plugin.templateEnvironmentData = env;
-
-      let visitor = plugin.getVisitor();
-
-      return { name, visitor };
-    };
+    return { Rule, name, config, ruleNames };
   }
 
   describe('rules setup is correct', function() {
@@ -169,16 +167,12 @@ describe('base plugin', function() {
       },
     };
 
-    function precompile(template) {
-      precompileTemplate(template, [plugin(buildPlugin(visitor), 'fake', config)]);
-    }
-
     function expectSource(config) {
       let template = config.template;
       let nodeSources = config.sources;
 
       it(`can get raw source for \`${template}\``, function() {
-        precompile(template);
+        runRules(template, [plugin(buildPlugin(visitor), 'fake', config)]);
 
         expect(messages).to.deep.equal(nodeSources);
       });
@@ -211,36 +205,34 @@ describe('base plugin', function() {
       },
     };
 
-    function precompile(template) {
-      precompileTemplate(template, [plugin(buildPlugin(visitor), 'fake', config)]);
-    }
-
     it('calls the "Program" node type', function() {
-      precompile('<div>Foo</div>');
+      runRules('<div>Foo</div>', [plugin(buildPlugin(visitor), 'fake', config)]);
 
       expect(wasCalled).to.be.true;
     });
   });
 
   describe('parses instructions', function() {
-    function precompile(template) {
+    function processTemplate(template) {
       let Rule = buildPlugin({
         MustacheCommentStatement(node) {
           this.process(node);
         },
       });
+
       Rule.prototype.log = function(result) {
         messages.push(result.message);
       };
       Rule.prototype.process = function(node) {
         config = this._processInstructionNode(node);
       };
-      precompileTemplate(template, [plugin(Rule, 'fake', 'foo')]);
+
+      runRules(template, [plugin(Rule, 'fake', 'foo')]);
     }
 
     function expectConfig(instruction, expectedConfig) {
       it(`can parse \`${instruction}\``, function() {
-        precompile(`{{! ${instruction} }}`);
+        processTemplate(`{{! ${instruction} }}`);
         expect(config).to.deep.equal(expectedConfig);
         expect(messages).to.deep.equal([]);
       });
@@ -297,7 +289,7 @@ describe('base plugin', function() {
 
     // Errors
     it('logs an error when it encounters an unknown rule name', function() {
-      precompile(
+      processTemplate(
         [
           '{{! template-lint-enable notarule }}',
           '{{! template-lint-disable fake norme meneither }}',
@@ -313,14 +305,14 @@ describe('base plugin', function() {
     });
 
     it("logs an error when it can't parse a configure instruction's JSON", function() {
-      precompile('{{! template-lint-configure fake { not: "json" ] }}');
+      processTemplate('{{! template-lint-configure fake { not: "json" ] }}');
       expect(messages).to.deep.equal([
         'malformed template-lint-configure instruction: `{ not: "json" ]` is not valid JSON',
       ]);
     });
 
     it('logs an error when it encounters an unrecognized instruction starting with `template-lint`', function() {
-      precompile(
+      processTemplate(
         [
           '{{! template-lint-bloober fake }}',
           '{{! template-lint- fake }}',
@@ -335,7 +327,7 @@ describe('base plugin', function() {
     });
 
     it('only logs syntax errors once across all rules', function() {
-      precompileTemplate(
+      runRules(
         '{{! template-lint-enable notarule }}{{! template-lint-disable meneither }}{{! template-lint-configure norme true }}',
         [
           plugin(buildPlugin({}), 'fake1'),
@@ -410,12 +402,12 @@ describe('base plugin', function() {
       return FakeRule;
     }
 
-    function precompile(template, config) {
+    function processTemplate(template, config) {
       if (config === undefined) {
         config = true;
       }
 
-      precompileTemplate(template, [plugin(buildPlugin(), 'fake', config)]);
+      runRules(template, [plugin(buildPlugin(), 'fake', config)]);
     }
 
     beforeEach(function() {
@@ -430,7 +422,7 @@ describe('base plugin', function() {
       let config = data.config;
 
       it(description, function() {
-        precompile(template, config);
+        processTemplate(template, config);
         expect(events).to.deep.equal(expectedEvents);
         expect(messages).to.deep.equal([]);
       });