Repository: ember-cli/eslint-plugin-ember
Pull request #1080 (2021-01-30 18:52:35)

Update `no-html-safe` rule to flag call instead of import
Follow-up to #1079.


Patch:
diff --git a/README.md b/README.md
index ff4b642d..af95b48c 100644
--- a/README.md
+++ b/README.md
@@ -152,7 +152,7 @@ Rules are grouped by category to help you understand their purpose. Each rule ha
 |    | Rule ID | Description |
 |:---|:--------|:------------|
 |  | [named-functions-in-promises](./docs/rules/named-functions-in-promises.md) | enforce usage of named functions in promises |
-|  | [no-html-safe](./docs/rules/no-html-safe.md) | disallow the use of htmlSafe |
+|  | [no-html-safe](./docs/rules/no-html-safe.md) | disallow the use of `htmlSafe` |
 | :white_check_mark: | [no-incorrect-calls-with-inline-anonymous-functions](./docs/rules/no-incorrect-calls-with-inline-anonymous-functions.md) | disallow inline anonymous functions as arguments to `debounce`, `once`, and `scheduleOnce` |
 | :white_check_mark: | [no-invalid-debug-function-arguments](./docs/rules/no-invalid-debug-function-arguments.md) | disallow usages of Ember's `assert()` / `warn()` / `deprecate()` functions that have the arguments passed in the wrong order. |
 |  | [no-restricted-service-injections](./docs/rules/no-restricted-service-injections.md) | disallow injecting certain services under certain paths |
diff --git a/lib/rules/no-html-safe.js b/lib/rules/no-html-safe.js
index 1ce0b095..a068e7b0 100644
--- a/lib/rules/no-html-safe.js
+++ b/lib/rules/no-html-safe.js
@@ -1,6 +1,7 @@
 'use strict';
 
 const ERROR_MESSAGE = 'Do not use `htmlSafe`.';
+const { getImportIdentifier } = require('../utils/import');
 
 //------------------------------------------------------------------------------
 // Rule Definition
@@ -10,7 +11,7 @@ module.exports = {
   meta: {
     type: 'suggestion',
     docs: {
-      description: 'disallow the use of htmlSafe',
+      description: 'disallow the use of `htmlSafe`',
       category: 'Miscellaneous',
       recommended: false,
       url:
@@ -23,15 +24,27 @@ module.exports = {
   ERROR_MESSAGE,
 
   create(context) {
+    let importedHtmlSafeName;
+
     return {
       ImportDeclaration(node) {
-        if (node.source.value === '@ember/string' || node.source.value === '@ember/template') {
-          const htmlSafeImport = node.specifiers.find(
-            (s) => (s.imported || s.local).name === 'htmlSafe'
-          );
-          if (htmlSafeImport) {
-            context.report(htmlSafeImport, ERROR_MESSAGE);
-          }
+        const importSource = node.source.value;
+
+        if (importSource === '@ember/string') {
+          importedHtmlSafeName =
+            importedHtmlSafeName || getImportIdentifier(node, '@ember/string', 'htmlSafe');
+        } else if (importSource === '@ember/template') {
+          importedHtmlSafeName =
+            importedHtmlSafeName || getImportIdentifier(node, '@ember/template', 'htmlSafe');
+        }
+      },
+
+      CallExpression(node) {
+        if (node.callee.type === 'Identifier' && node.callee.name === importedHtmlSafeName) {
+          context.report({
+            node,
+            message: ERROR_MESSAGE,
+          });
         }
       },
     };


Test patch:
diff --git a/tests/lib/rules/no-html-safe.js b/tests/lib/rules/no-html-safe.js
index 2fee50e0..d940fc70 100644
--- a/tests/lib/rules/no-html-safe.js
+++ b/tests/lib/rules/no-html-safe.js
@@ -20,26 +20,37 @@ const ruleTester = new RuleTester({
 
 ruleTester.run('no-html-safe', rule, {
   valid: [
-    "import { ANYTHING } from '@ember/string';",
-    "import { ANYTHING } from '@ember/template';",
-    "import { htmlSafe } from 'something/else';",
-    "import EmberString from '@ember/string';",
+    // Wrong function:
+    "import { ANYTHING } from '@ember/string'; ANYTHING('foo');",
+    "import { ANYTHING } from '@ember/template'; ANYTHING('foo');",
+
+    // Wrong import path:
+    "import { htmlSafe } from 'something/else'; htmlSafe('foo');",
+
+    // No import:
+    "htmlSafe('foo');",
+    "import EmberString from '@ember/string'; htmlSafe('foo');",
+
+    // Wrong object:
+    "import { htmlSafe } from '@ember/string'; foo.htmlSafe('foo');",
+    "import { htmlSafe } from '@ember/string'; htmlSafe.foo('foo');",
   ],
   invalid: [
     {
-      code: "import { htmlSafe } from '@ember/string';",
+      code: "import { htmlSafe } from '@ember/string'; htmlSafe('foo');",
       output: null,
-      errors: [{ message: ERROR_MESSAGE, type: 'ImportSpecifier' }],
+      errors: [{ message: ERROR_MESSAGE, type: 'CallExpression' }],
     },
     {
-      code: "import { htmlSafe } from '@ember/template';",
+      code: "import { htmlSafe } from '@ember/template'; htmlSafe('foo');",
       output: null,
-      errors: [{ message: ERROR_MESSAGE, type: 'ImportSpecifier' }],
+      errors: [{ message: ERROR_MESSAGE, type: 'CallExpression' }],
     },
     {
-      code: "import { htmlSafe as myCustomNameForHtmlSafe } from '@ember/template';",
+      code:
+        "import { htmlSafe as myCustomNameForHtmlSafe } from '@ember/template'; myCustomNameForHtmlSafe();",
       output: null,
-      errors: [{ message: ERROR_MESSAGE, type: 'ImportSpecifier' }],
+      errors: [{ message: ERROR_MESSAGE, type: 'CallExpression' }],
     },
   ],
 });