Repository: ember-template-lint/ember-template-lint
Pull request #913 (2019-11-07 22:08:03)

Update `no-unnecessary-component-helper` rule to allow the `component` helper as an angle bracket component argument
This adds a failing test case for `no-unnecessary-component-helper` for the usage of the curly `{{component}}` helper as an argument to an angle bracket component invocation.

```hbs
<Foo @bar={{component "my-component"}} />
```

It fixes the test by recording all valid usages of the `{{component}}` helper as attributes to an `ElementNode` in a Set and then exempting these nodes in `checkNode`.

I'm not sure whether this is the best / most elegant solution, but I did not find a different way to get the parent of the node passed to `checkNode`. I'm happy to implement this a different way, if you can point me towards a better solution.


Patch:
diff --git a/docs/rule/no-unnecessary-component-helper.md b/docs/rule/no-unnecessary-component-helper.md
index b1a2ff0f..93759709 100644
--- a/docs/rule/no-unnecessary-component-helper.md
+++ b/docs/rule/no-unnecessary-component-helper.md
@@ -27,6 +27,7 @@ This rule **allows** the following:
 
 ```hbs
 {{my-component close=(component "link-to" "index")}}
+<MyComponent @close={{component "link-to" "index"}} />
 ```
 
 ### References
diff --git a/docs/rule/require-valid-alt-text.md b/docs/rule/require-valid-alt-text.md
index 1fff4179..b0b43ea6 100644
--- a/docs/rule/require-valid-alt-text.md
+++ b/docs/rule/require-valid-alt-text.md
@@ -14,7 +14,7 @@ The content of an `alt` attribute is used to calculate the machine-readable labe
 
 If it's not a meaningful image, it should have an empty alt attribute value and have the role of presentation or none.
 
-`img` alt attribute does not contain the word image, picture, or photo. Screenreaders already announce `img` elements as an image. There is no need to use words such as *image*, *photo*, and/or *picture*.
+`img` alt attribute does not contain the word image, picture, or photo. Screenreaders already announce `img` elements as an image. There is no need to use words such as *image*, *photo*, *logo*, *spacer*, and/or *picture*.
 
 Numbers are not considered valid alt text, and this rule disallows using only numbers in alt text.
 
@@ -24,6 +24,7 @@ This rule **forbids** the following:
 ```hbs
 <img src="rwjblue.png">
 <img src="foo" alt="Photo of foo being weird." />
+<img src="foo" alt="YourCompany logo" />
 <img src="bar" alt="Image of me at a bar!" />
 <img src="baz" alt="Picture of baz fixing a bug." />
 <img src="b52.jpg" alt="52" />
@@ -33,6 +34,7 @@ This rule **allows** the following:
 
 ```hbs
 <img src="rwjblue.png" alt="A man standing in front of a room of people, giving a presentation about Ember.">
+<img src="foo" alt="YourCompany Home Page" />
 <img src="bar" aria-hidden alt="Picture of me taking a photo of an image" /> // Will pass because it is hidden.
 <img src="baz" alt="Baz taking a {{photo}}" /> // This is valid since photo is a variable name.
 <img src="b52.jpg" alt="b52 bomber jet" />
diff --git a/lib/rules/lint-no-unnecessary-component-helper.js b/lib/rules/lint-no-unnecessary-component-helper.js
index 667267af..6f4ba74f 100644
--- a/lib/rules/lint-no-unnecessary-component-helper.js
+++ b/lib/rules/lint-no-unnecessary-component-helper.js
@@ -7,13 +7,30 @@ const ERROR_MESSAGE = 'Invoke component directly instead of using `component` he
 
 module.exports = class NoUnnecessaryComponentHelper extends Rule {
   visitor() {
-    function checkNode(node) {
-      if (
+    let inSafeNamespace = false;
+    const markAsSafeNamespace = {
+      enter() {
+        inSafeNamespace = true;
+      },
+      exit() {
+        inSafeNamespace = false;
+      },
+    };
+
+    function isComponentHelper(node) {
+      return (
         AstNodeInfo.isPathExpression(node.path) &&
         node.path.original === 'component' &&
-        node.params.length > 0 &&
+        node.params.length > 0
+      );
+    }
+
+    function checkNode(node) {
+      if (
+        isComponentHelper(node) &&
         AstNodeInfo.isStringLiteral(node.params[0]) &&
-        !node.params[0].value.includes('@')
+        !node.params[0].value.includes('@') &&
+        !inSafeNamespace
       ) {
         this.log({
           message: ERROR_MESSAGE,
@@ -25,6 +42,7 @@ module.exports = class NoUnnecessaryComponentHelper extends Rule {
     }
 
     return {
+      AttrNode: markAsSafeNamespace,
       BlockStatement: checkNode,
       MustacheStatement: checkNode,
     };


Test patch:
diff --git a/test/unit/rules/lint-no-unnecessary-component-helper-test.js b/test/unit/rules/lint-no-unnecessary-component-helper-test.js
index 36a9cbf3..c63459b0 100644
--- a/test/unit/rules/lint-no-unnecessary-component-helper-test.js
+++ b/test/unit/rules/lint-no-unnecessary-component-helper-test.js
@@ -31,6 +31,23 @@ generateRuleTests({
     '(component SOME_COMPONENT_NAME)',
     '(component "my-component")',
 
+    // Curly inline usage in an angle bracket component:
+    '<Foo @bar={{component SOME_COMPONENT_NAME}} />',
+    '<Foo @bar={{component "my-component"}} />',
+    '<Foo @bar={{component SOME_COMPONENT_NAME}}></Foo>',
+    '<Foo @bar={{component "my-component"}}></Foo>',
+
+    // Static arguments in angle bracket components don't crash the rule:
+    '<Foo @arg="foo" />',
+    '<Foo class="foo" />',
+    '<Foo data-test-bar="foo" />',
+
+    // `if` expressions without `(component)` are allowed:
+    '<Foo @arg={{if this.user.isAdmin "admin"}} />',
+
+    // `if` expression with `(component)` are allowed:
+    '<Foo @arg={{if this.user.isAdmin (component "my-component")}} />',
+
     // Component names of the form `addon-name@component-name` are exempt:
     "{{component 'addon-name@component-name'}}",
     "{{#component 'addon-name@component-name'}}{{/component}}",
@@ -61,5 +78,17 @@ generateRuleTests({
         column: 0,
       },
     },
+    {
+      template:
+        '<Foo @arg={{component "allowed-component"}}>{{component "forbidden-component"}}</Foo>',
+
+      result: {
+        message: ERROR_MESSAGE,
+        moduleId: 'layout.hbs',
+        source: '{{component "forbidden-component"}}',
+        line: 1,
+        column: 44,
+      },
+    },
   ],
 });