Repository: rwjblue/ember-template-lint (js)

Issue:
Unused block params don't consider partials
Consider the following templates:

`app/templates/components/family-tree.hbs`
```hbs
<ul>
  {{#each _childViews as |childView|}}
    {{partial "partials/family-tree/tier"}}
  {{/each}}
</ul>
```

`app/templates/partials/family-tree/-tier.hbs`
```hbs
{{#if childView.isVisible}}
  <li>
    <h3>{{childView.name}}</h3>
    {{#if childView.childViews}}
      <ul>
        {{#each childView.childViews as |childView|}}
          {{partial "partials/family-tree/tier"}}
        {{/each}}
      </ul>
    {{/if}}
  </li>
{{/if}}
```

Here we're using a partial that relies on a variable existing in its parent scope, because using a component would have performance problems given the large size of the potential tree to be rendered.

The template linter will complain: `unused-block-params: 'childView' is defined but never used`. However, failing to define `childView` will cause the application not to work.

I'd love to discuss how the linter could be improved to infer that block params defined as an ancestor of a `partial` might indeed be used by that partial. Or possibly the linter could even read that partial and check if it's used in that file?

Pull request:
unused-block-params: Disable rule when partial is used in scope
Resolves #240

/cc @rwjblue @Kerrick

Patch:
diff --git a/lib/rules/lint-unused-block-params.js b/lib/rules/lint-unused-block-params.js
index 65a854e..061cb2d 100644
--- a/lib/rules/lint-unused-block-params.js
+++ b/lib/rules/lint-unused-block-params.js
@@ -67,7 +67,13 @@ module.exports = class UnusedBlockParams extends Rule {
 
       PathExpression(node) {
         scope.usePath(node);
-      }
+      },
+
+      MustacheStatement(node) {
+        if (node.path.original === 'partial') {
+          scope.usePartial();
+        }
+      },
     };
   }
 };
@@ -75,6 +81,7 @@ module.exports = class UnusedBlockParams extends Rule {
 class Frame {
   constructor(locals) {
     this.locals = locals;
+    this.hasPartial = false;
     this.usedLocals = {};
 
     for (let i = 0; i < locals.length; i++) {
@@ -91,8 +98,12 @@ class Frame {
     }
   }
 
+  usePartial() {
+    this.hasPartial = true;
+  }
+
   unusedLocals() {
-    if (this.locals.length > 0) {
+    if (!this.hasPartial && this.locals.length > 0) {
       if (!this.usedLocals[this.locals[this.locals.length - 1]]) {
         return this.locals[this.locals.length - 1];
       }
@@ -128,4 +139,10 @@ class Scope {
       }
     }
   }
+
+  usePartial() {
+    for (let i = this.frames.length - 1; i >= 0; i--) {
+      this.frames[i].usePartial();
+    }
+  }
 }


Test patch:
diff --git a/test/unit/rules/lint-unused-block-params-test.js b/test/unit/rules/lint-unused-block-params-test.js
index 2fb02fc..4e6393e 100644
--- a/test/unit/rules/lint-unused-block-params-test.js
+++ b/test/unit/rules/lint-unused-block-params-test.js
@@ -10,6 +10,7 @@ generateRuleTests({
   good: [
     '{{cat}}',
     '{{#each cats as |cat|}}{{cat}}{{/each}}',
+    '{{#each cats as |cat|}}{{partial "cat"}}{{/each}}',
     '{{#each cats as |cat|}}{{cat.name}}{{/each}}',
     '{{#each cats as |cat|}}{{meow cat}}{{/each}}',
     '{{#each cats as |cat index|}}{{index}}{{/each}}',
@@ -56,6 +57,19 @@ generateRuleTests({
         line: 1,
         column: 0
       }
+    },    {
+      template:
+      '{{#each cats as |cat index|}}' +
+        '{{partial "cat"}}' +
+        '{{#each cat.lives as |life|}}Life{{/each}}' +
+      '{{/each}}',
+      result: {
+        message: '\'life\' is defined but never used',
+        moduleId: 'layout.hbs',
+        source: '{{#each cat.lives as |life|}}Life{{/each}}',
+        line: 1,
+        column: 46,
+      }
     }
   ]
 });