Repository: ember-cli/eslint-plugin-ember
Pull request #986 (2020-10-25 14:32:31)

Add new rule `no-string-prototype-extensions`
Resolves https://github.com/ember-cli/eslint-plugin-ember/issues/638


Patch:
diff --git a/README.md b/README.md
index 19e8f05c..ad0183c3 100644
--- a/README.md
+++ b/README.md
@@ -108,6 +108,7 @@ Rules are grouped by category to help you understand their purpose. Each rule ha
 | :white_check_mark: | [no-new-mixins](./docs/rules/no-new-mixins.md) | disallow the creation of new mixins |
 | :white_check_mark: | [no-observers](./docs/rules/no-observers.md) | disallow usage of observers |
 | :white_check_mark::wrench: | [no-old-shims](./docs/rules/no-old-shims.md) | disallow usage of old shims for modules |
+|  | [no-string-prototype-extensions](./docs/rules/no-string-prototype-extensions.md) | disallow usage of `String` prototype extensions |
 
 ### Ember Data
 
diff --git a/docs/rules/no-string-prototype-extensions.md b/docs/rules/no-string-prototype-extensions.md
new file mode 100644
index 00000000..ec06f459
--- /dev/null
+++ b/docs/rules/no-string-prototype-extensions.md
@@ -0,0 +1,65 @@
+# no-string-prototype-extensions
+
+Ember by default extends certain native JavaScript objects with additional
+methods. This can lead to problems in certain situations. One example is relying
+on these methods in addons, but that addon being used in an app that has the
+extensions disabled.
+
+Additionally, the prototype extensions for the `String` object have been
+deprecated in [RFC #236](http://emberjs.github.io/rfcs/0236-deprecation-ember-string.html).
+
+## Rule Details
+
+This rule will look for method calls that match any of the forbidden `String`
+prototype extension methods.
+
+## Examples
+
+Examples of **incorrect** code for this rule:
+
+```js
+'myString'.dasherize();
+```
+
+```js
+someString.capitalize();
+```
+
+```js
+'<b>foo</b>'.htmlSafe();
+```
+
+Examples of **correct** code for this rule:
+
+```js
+dasherize('myString');
+```
+
+```js
+capitalize(someString);
+```
+
+```js
+htmlSafe('<b>foo</b>');
+```
+
+## Migration
+
+Replace e.g.:
+
+```js
+'myString'.dasherize();
+```
+
+with:
+
+```js
+import { dasherize } from '@ember/string';
+
+dasherize('myString');
+```
+
+## References
+
+* [Prototype extensions documentation](https://guides.emberjs.com/release/configuring-ember/disabling-prototype-extensions/)
+* [String prototype extensions deprecation RFC](http://emberjs.github.io/rfcs/0236-deprecation-ember-string.html#string-prototype-extensions)
diff --git a/lib/index.js b/lib/index.js
index 25af9fff..12b78d3a 100644
--- a/lib/index.js
+++ b/lib/index.js
@@ -53,6 +53,7 @@ module.exports = {
     'no-restricted-resolver-tests': require('./rules/no-restricted-resolver-tests'),
     'no-restricted-service-injections': require('./rules/no-restricted-service-injections'),
     'no-side-effects': require('./rules/no-side-effects'),
+    'no-string-prototype-extensions': require('./rules/no-string-prototype-extensions'),
     'no-test-and-then': require('./rules/no-test-and-then'),
     'no-test-import-export': require('./rules/no-test-import-export'),
     'no-test-module-for': require('./rules/no-test-module-for'),
diff --git a/lib/rules/no-string-prototype-extensions.js b/lib/rules/no-string-prototype-extensions.js
new file mode 100644
index 00000000..21e6fdb8
--- /dev/null
+++ b/lib/rules/no-string-prototype-extensions.js
@@ -0,0 +1,54 @@
+'use strict';
+
+const ERROR_MESSAGE = 'Do not using `String` prototype extension methods.';
+
+const EXTENSION_METHODS = new Set([
+  'camelize',
+  'capitalize',
+  'classify',
+  'dasherize',
+  'decamelize',
+  'htmlSafe',
+  'loc',
+  'underscore',
+  'w',
+]);
+
+module.exports = {
+  meta: {
+    type: 'suggestion',
+    docs: {
+      description: 'disallow usage of `String` prototype extensions',
+      category: 'Deprecations',
+      url:
+        'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/no-string-prototype-extensions.md',
+    },
+    fixable: null,
+    schema: [],
+  },
+
+  ERROR_MESSAGE,
+
+  create(context) {
+    return {
+      CallExpression(node) {
+        const { callee } = node;
+        if (callee.type !== 'MemberExpression') {
+          return;
+        }
+
+        const { object, property } = callee;
+        if (object.type === 'ThisExpression') {
+          return;
+        }
+        if (property.type !== 'Identifier') {
+          return;
+        }
+
+        if (EXTENSION_METHODS.has(property.name)) {
+          context.report({ node: property, message: ERROR_MESSAGE });
+        }
+      },
+    };
+  },
+};


Test patch:
diff --git a/tests/lib/rules/no-string-prototype-extensions.js b/tests/lib/rules/no-string-prototype-extensions.js
new file mode 100644
index 00000000..801eae3c
--- /dev/null
+++ b/tests/lib/rules/no-string-prototype-extensions.js
@@ -0,0 +1,46 @@
+'use strict';
+
+const rule = require('../../../lib/rules/no-string-prototype-extensions');
+const RuleTester = require('eslint').RuleTester;
+
+const { ERROR_MESSAGE } = rule;
+
+const ruleTester = new RuleTester({
+  parser: require.resolve('babel-eslint'),
+  parserOptions: { ecmaVersion: 6, sourceType: 'module' },
+});
+
+ruleTester.run('no-string-prototype-extensions', rule, {
+  valid: [
+    "dasherize('myString')",
+    'capitalize(someString)',
+    "htmlSafe('<b>foo</b>')",
+    "someString['foo']()",
+    'something.foo()',
+    'dasherize.foo()',
+    'this.dasherize()',
+  ],
+
+  invalid: [
+    {
+      code: "'myString'.dasherize()",
+      output: null,
+      errors: [{ message: ERROR_MESSAGE, column: 12, endColumn: 21 }],
+    },
+    {
+      code: 'someString.capitalize()',
+      output: null,
+      errors: [{ message: ERROR_MESSAGE, column: 12, endColumn: 22 }],
+    },
+    {
+      code: 'getSomeString().dasherize()',
+      output: null,
+      errors: [{ message: ERROR_MESSAGE, column: 17, endColumn: 26 }],
+    },
+    {
+      code: "'<b>foo</b>'.htmlSafe()",
+      output: null,
+      errors: [{ message: ERROR_MESSAGE, column: 14, endColumn: 22 }],
+    },
+  ],
+});