Repository: import-js/eslint-plugin-import
Pull request #2298 (2021-11-10 05:48:19)

Release 2.25.3
Closes https://github.com/airbnb/javascript/issues/2496


Patch:
diff --git a/.eslintrc b/.eslintrc
index 4064c571..12fb8b7a 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -18,7 +18,13 @@
         "ecmaVersion": 2020,
     },
     "rules": {
-        "comma-dangle": [2, "always-multiline"],
+        "comma-dangle": ["error", {
+            "arrays": "always-multiline",
+            "objects": "always-multiline",
+            "imports": "always-multiline",
+            "exports": "always-multiline",
+            "functions": "always-multiline",
+        }],
         "comma-style": [2, "last"],
         "curly": [2, "multi-line"],
         "eol-last": [2, "always"],
@@ -26,18 +32,17 @@
         "func-call-spacing": 2,
         "indent": [2, 2],
         "keyword-spacing": ["error", {
-          before: true,
-          after: true,
-          overrides: {
-            return: { after: true },
-            throw: { after: true },
-            case: { after: true }
+          "before": true,
+          "after": true,
+          "overrides": {
+            "return": { "after": true },
+            "throw": { "after": true },
+            "case": { "after": true }
           }
         }],
-        "max-len": [1, 99, 2],
+        "max-len": 0,
         "no-cond-assign": [2, "always"],
         "no-return-assign": [2, "always"],
-        "no-shadow": 1,
         "no-var": 2,
         "object-curly-spacing": [2, "always"],
         "object-shorthand": ["error", "always", {
@@ -91,6 +96,12 @@
                 "no-console": "off",
             },
         },
+        {
+            "files": "resolvers/**",
+            "env": {
+                "es6": false,
+            },
+        },
         {
             "files": [
                 "resolvers/*/test/**/*",
@@ -99,6 +110,22 @@
                 "mocha": true,
                 "es6": false
             },
-        }
+        },
+        {
+            "files": "utils/**",
+            "parserOptions": {
+                "ecmaVersion": 6,
+            },
+            "rules": {
+                "comma-dangle": ["error", {
+                    "arrays": "always-multiline",
+                    "objects": "always-multiline",
+                    "imports": "always-multiline",
+                    "exports": "always-multiline",
+                    "functions": "never"
+                }],
+                "no-console": 1,
+            },
+        },
     ],
 }
diff --git a/.gitignore b/.gitignore
index e1114fe9..8e2f6da1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -13,10 +13,10 @@ lib-cov
 # Coverage directory used by tools like istanbul
 coverage
 
-# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
+# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
 .grunt
 
-# Compiled binary addons (http://nodejs.org/api/addons.html)
+# Compiled binary addons (https://nodejs.org/api/addons.html)
 build/Release
 
 # Copied from ./LICENSE for the npm module releases
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 46a250c1..0d4d920a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,11 +1,24 @@
 # Change Log
 
 All notable changes to this project will be documented in this file.
-This project adheres to [Semantic Versioning](http://semver.org/).
-This change log adheres to standards from [Keep a CHANGELOG](http://keepachangelog.com).
+This project adheres to [Semantic Versioning](https://semver.org/).
+This change log adheres to standards from [Keep a CHANGELOG](https://keepachangelog.com).
 
 ## [Unreleased]
 
+## [2.25.3] - 2021-11-09
+
+### Fixed
+- [`extensions`]: ignore unresolveable type-only imports ([#2270], [#2271], [@jablko])
+- `importType`: fix `isExternalModule` calculation ([#2282], [@mx-bernhard])
+- [`no-import-module-exports`]: avoid false positives with a shadowed `module` or `exports` ([#2297], [@ljharb])
+
+### Changed
+- [Docs] [`order`]: add type to the default groups ([#2272], [@charpeni])
+- [readme] Add note to TypeScript docs to install appropriate resolver ([#2279], [@johnthagen])
+- [Refactor] `importType`: combine redundant `isScoped` and `isScopedModule` ([@ljharb])
+- [Docs] HTTP => HTTPS ([#2287], [@Schweinepriester])
+
 ## [2.25.2] - 2021-10-12
 
 ### Fixed
@@ -929,6 +942,13 @@ for info on changes for earlier releases.
 
 [`memo-parser`]: ./memo-parser/README.md
 
+[#2297]: https://github.com/import-js/eslint-plugin-import/pull/2297
+[#2287]: https://github.com/import-js/eslint-plugin-import/pull/2287
+[#2282]: https://github.com/import-js/eslint-plugin-import/pull/2282
+[#2279]: https://github.com/import-js/eslint-plugin-import/pull/2279
+[#2272]: https://github.com/import-js/eslint-plugin-import/pull/2272
+[#2271]: https://github.com/import-js/eslint-plugin-import/pull/2271
+[#2270]: https://github.com/import-js/eslint-plugin-import/pull/2270
 [#2240]: https://github.com/import-js/eslint-plugin-import/pull/2240
 [#2233]: https://github.com/import-js/eslint-plugin-import/pull/2233
 [#2226]: https://github.com/import-js/eslint-plugin-import/pull/2226
@@ -1308,7 +1328,8 @@ for info on changes for earlier releases.
 [#119]: https://github.com/import-js/eslint-plugin-import/issues/119
 [#89]: https://github.com/import-js/eslint-plugin-import/issues/89
 
-[Unreleased]: https://github.com/import-js/eslint-plugin-import/compare/v2.25.2...HEAD
+[Unreleased]: https://github.com/import-js/eslint-plugin-import/compare/v2.25.3...HEAD
+[2.25.3]: https://github.com/import-js/eslint-plugin-import/compare/v2.25.2...v2.25.3
 [2.25.2]: https://github.com/import-js/eslint-plugin-import/compare/v2.25.1...v2.25.2
 [2.25.1]: https://github.com/import-js/eslint-plugin-import/compare/v2.25.0...v2.25.1
 [2.25.0]: https://github.com/import-js/eslint-plugin-import/compare/v2.24.2...v2.25.0
@@ -1425,6 +1446,7 @@ for info on changes for earlier releases.
 [@brendo]: https://github.com/brendo
 [@brettz9]: https://github.com/brettz9
 [@charlessuh]: https://github.com/charlessuh
+[@charpeni]: https://github.com/charpeni
 [@cherryblossom000]: https://github.com/cherryblossom000
 [@chrislloyd]: https://github.com/chrislloyd
 [@christianvuerings]: https://github.com/christianvuerings
@@ -1482,6 +1504,7 @@ for info on changes for earlier releases.
 [@jkimbo]: https://github.com/jkimbo
 [@joaovieira]: https://github.com/joaovieira
 [@johndevedu]: https://github.com/johndevedu
+[@johnthagen]: https://github.com/johnthagen
 [@jonboiser]: https://github.com/jonboiser
 [@josh]: https://github.com/josh
 [@JounQin]: https://github.com/JounQin
@@ -1530,6 +1553,7 @@ for info on changes for earlier releases.
 [@MikeyBeLike]: https://github.com/MikeyBeLike
 [@mplewis]: https://github.com/mplewis
 [@mrmckeb]: https://github.com/mrmckeb
+[@mx-bernhard]: https://github.com/mx-bernhard
 [@nickofthyme]: https://github.com/nickofthyme
 [@nicolashenry]: https://github.com/nicolashenry
 [@noelebrun]: https://github.com/noelebrun
@@ -1559,6 +1583,7 @@ for info on changes for earlier releases.
 [@saschanaz]: https://github.com/saschanaz
 [@schmidsi]: https://github.com/schmidsi
 [@schmod]: https://github.com/schmod
+[@Schweinepriester]: https://github.com/Schweinepriester
 [@scottnonnenberg]: https://github.com/scottnonnenberg
 [@sergei-startsev]: https://github.com/sergei-startsev
 [@sharmilajesupaul]: https://github.com/sharmilajesupaul
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 35a6b14b..c992d67f 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -110,7 +110,7 @@ If we can all get together behind the common goal of embracing empathy, everythi
 
 #### Attribution
 
-Thanks for help from http://mozillascience.github.io/working-open-workshop/contributing/
+Thanks for help from https://mozillascience.github.io/working-open-workshop/contributing/
 for inspiration before I wrote this. --ben
 
 [README.md]: ./README.md
diff --git a/README.md b/README.md
index d800cbfe..1d7b55ff 100644
--- a/README.md
+++ b/README.md
@@ -5,7 +5,7 @@
 [![coverage][codecov-image]][codecov-url]
 [![win32 build status](https://ci.appveyor.com/api/projects/status/3mw2fifalmjlqf56/branch/main?svg=true)](https://ci.appveyor.com/project/import-js/eslint-plugin-import/branch/main)
 [![npm](https://img.shields.io/npm/v/eslint-plugin-import.svg)](https://www.npmjs.com/package/eslint-plugin-import)
-[![npm downloads](https://img.shields.io/npm/dt/eslint-plugin-import.svg?maxAge=2592000)](http://www.npmtrends.com/eslint-plugin-import)
+[![npm downloads](https://img.shields.io/npm/dt/eslint-plugin-import.svg?maxAge=2592000)](https://www.npmtrends.com/eslint-plugin-import)
 
 This plugin intends to support linting of ES2015+ (ES6+) import/export syntax, and prevent issues with misspelling of file paths and import names. All the goodness that the ES2015+ static module syntax intends to provide, marked up in your editor.
 
@@ -174,6 +174,9 @@ extends:
 
 [`@typescript-eslint/parser`]: https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser
 
+You will also need to install and configure the TypeScript resolver:
+[`eslint-import-resolver-typescript`](https://github.com/alexgorbatchev/eslint-import-resolver-typescript).
+
 # Resolvers
 
 With the advent of module bundlers and the current state of modules and module
@@ -253,7 +256,7 @@ the process's current working directory if no `package.json` is found.
 If you are interesting in writing a resolver, see the [spec](./resolvers/README.md) for more details.
 
 [`resolve`]: https://www.npmjs.com/package/resolve
-[`externals`]: http://webpack.github.io/docs/library-and-externals.html
+[`externals`]: https://webpack.github.io/docs/library-and-externals.html
 
 [Node]: https://www.npmjs.com/package/eslint-import-resolver-node
 [webpack]: https://www.npmjs.com/package/eslint-import-resolver-webpack
@@ -480,7 +483,7 @@ The purpose of the `chdir` setting, in this case, is to set the working director
 from which ESLint is executed to be the same as the directory on which SublimeLinter-eslint
 bases the relative path it provides.
 
-See the SublimeLinter docs on [`chdir`](http://www.sublimelinter.com/en/latest/linter_settings.html#chdir)
+See the SublimeLinter docs on [`chdir`](https://www.sublimelinter.com/en/latest/linter_settings.html#chdir)
 for more information, in case this does not work with your project.
 
 If you are not using `.eslintignore`, or don't have a Sublime project file, you can also
diff --git a/docs/rules/first.md b/docs/rules/first.md
index cc19003b..c71ab7d8 100644
--- a/docs/rules/first.md
+++ b/docs/rules/first.md
@@ -45,7 +45,7 @@ A directive in this case is assumed to be a single statement that contains only
 a literal string-valued expression.
 
 `'use strict'` would be a good example, except that [modules are always in strict
-mode](http://www.ecma-international.org/ecma-262/6.0/#sec-strict-mode-code) so it would be surprising to see a `'use strict'` sharing a file with `import`s and
+mode](https://262.ecma-international.org/6.0/#sec-strict-mode-code) so it would be surprising to see a `'use strict'` sharing a file with `import`s and
 `export`s.
 
 Given that, see [#255] for the reasoning.
diff --git a/docs/rules/no-duplicates.md b/docs/rules/no-duplicates.md
index c3751032..5252db1b 100644
--- a/docs/rules/no-duplicates.md
+++ b/docs/rules/no-duplicates.md
@@ -3,7 +3,7 @@
 Reports if a resolved path is imported more than once.
 +(fixable) The `--fix` option on the [command line] automatically fixes some problems reported by this rule.
 
-ESLint core has a similar rule ([`no-duplicate-imports`](http://eslint.org/docs/rules/no-duplicate-imports)), but this version
+ESLint core has a similar rule ([`no-duplicate-imports`](https://eslint.org/docs/rules/no-duplicate-imports)), but this version
 is different in two key ways:
 
 1. the paths in the source code don't have to exactly match, they just have to point to the same module on the filesystem. (i.e. `./foo` and `./foo.js`)
diff --git a/docs/rules/no-mutable-exports.md b/docs/rules/no-mutable-exports.md
index e161e87b..f4cc7843 100644
--- a/docs/rules/no-mutable-exports.md
+++ b/docs/rules/no-mutable-exports.md
@@ -44,8 +44,8 @@ want to enable the following core ESLint rules:
 - [no-func-assign]
 - [no-class-assign]
 
-[no-func-assign]: http://eslint.org/docs/rules/no-func-assign
-[no-class-assign]: http://eslint.org/docs/rules/no-class-assign
+[no-func-assign]: https://eslint.org/docs/rules/no-func-assign
+[no-class-assign]: https://eslint.org/docs/rules/no-class-assign
 
 ## When Not To Use It
 
diff --git a/docs/rules/order.md b/docs/rules/order.md
index e320ee0c..a1bae0b1 100644
--- a/docs/rules/order.md
+++ b/docs/rules/order.md
@@ -3,7 +3,7 @@
 Enforce a convention in the order of `require()` / `import` statements.
 +(fixable) The `--fix` option on the [command line] automatically fixes problems reported by this rule.
 
-With the [`groups`](#groups-array) option set to `["builtin", "external", "internal", "parent", "sibling", "index", "object"]` the order is as shown in the following example:
+With the [`groups`](#groups-array) option set to `["builtin", "external", "internal", "parent", "sibling", "index", "object", "type"]` the order is as shown in the following example:
 
 ```js
 // 1. node "builtin" modules
diff --git a/docs/rules/unambiguous.md b/docs/rules/unambiguous.md
index 92839b0c..5be7d3df 100644
--- a/docs/rules/unambiguous.md
+++ b/docs/rules/unambiguous.md
@@ -50,5 +50,5 @@ a `module`.
 - [`parserOptions.sourceType`]
 - [node-eps#13](https://github.com/nodejs/node-eps/issues/13)
 
-[`parserOptions.sourceType`]: http://eslint.org/docs/user-guide/configuring#specifying-parser-options
+[`parserOptions.sourceType`]: https://eslint.org/docs/user-guide/configuring#specifying-parser-options
 [Unambiguous JavaScript Grammar]: https://github.com/nodejs/node-eps/blob/HEAD/002-es-modules.md#32-determining-if-source-is-an-es-module
diff --git a/package.json b/package.json
index aebbc308..551bcb8d 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "eslint-plugin-import",
-  "version": "2.25.2",
+  "version": "2.25.3",
   "description": "Import with sanity.",
   "engines": {
     "node": ">=4"
@@ -103,9 +103,9 @@
     "debug": "^2.6.9",
     "doctrine": "^2.1.0",
     "eslint-import-resolver-node": "^0.3.6",
-    "eslint-module-utils": "^2.7.0",
+    "eslint-module-utils": "^2.7.1",
     "has": "^1.0.3",
-    "is-core-module": "^2.7.0",
+    "is-core-module": "^2.8.0",
     "is-glob": "^4.0.3",
     "minimatch": "^3.0.4",
     "object.values": "^1.1.5",
diff --git a/resolvers/.eslintrc.yml b/resolvers/.eslintrc.yml
deleted file mode 100644
index 9db33eda..00000000
--- a/resolvers/.eslintrc.yml
+++ /dev/null
@@ -1,3 +0,0 @@
----
-env:
-  es6: false
diff --git a/resolvers/node/CHANGELOG.md b/resolvers/node/CHANGELOG.md
index c237855a..8812f127 100644
--- a/resolvers/node/CHANGELOG.md
+++ b/resolvers/node/CHANGELOG.md
@@ -1,7 +1,7 @@
 # Change Log
 All notable changes to this resolver will be documented in this file.
-This project adheres to [Semantic Versioning](http://semver.org/).
-This change log adheres to standards from [Keep a CHANGELOG](http://keepachangelog.com).
+This project adheres to [Semantic Versioning](https://semver.org/).
+This change log adheres to standards from [Keep a CHANGELOG](https://keepachangelog.com).
 
 ## Unreleased
 
diff --git a/resolvers/webpack/CHANGELOG.md b/resolvers/webpack/CHANGELOG.md
index 212f77b0..1626bb27 100644
--- a/resolvers/webpack/CHANGELOG.md
+++ b/resolvers/webpack/CHANGELOG.md
@@ -1,7 +1,7 @@
 # Change Log
 All notable changes to this resolver will be documented in this file.
-This project adheres to [Semantic Versioning](http://semver.org/).
-This change log adheres to standards from [Keep a CHANGELOG](http://keepachangelog.com).
+This project adheres to [Semantic Versioning](https://semver.org/).
+This change log adheres to standards from [Keep a CHANGELOG](https://keepachangelog.com).
 
 ## Unreleased
 
diff --git a/resolvers/webpack/index.js b/resolvers/webpack/index.js
index 690c3b52..b569d532 100644
--- a/resolvers/webpack/index.js
+++ b/resolvers/webpack/index.js
@@ -236,7 +236,7 @@ function createWebpack2ResolveSync(webpackRequire, resolveConfig) {
 }
 
 /**
- * webpack 1 defaults: http://webpack.github.io/docs/configuration.html#resolve-packagemains
+ * webpack 1 defaults: https://webpack.github.io/docs/configuration.html#resolve-packagemains
  * @type {Array}
  */
 const webpack1DefaultMains = [
diff --git a/src/.eslintrc.yml b/src/.eslintrc.yml
deleted file mode 100644
index 340d66bf..00000000
--- a/src/.eslintrc.yml
+++ /dev/null
@@ -1,3 +0,0 @@
----
-rules:
-  comma-dangle: [1, "always-multiline"]
diff --git a/src/core/importType.js b/src/core/importType.js
index 6e4ac9a4..085ce658 100644
--- a/src/core/importType.js
+++ b/src/core/importType.js
@@ -29,7 +29,7 @@ export function isExternalModule(name, settings, path, context) {
   if (arguments.length < 4) {
     throw new TypeError('isExternalModule: name, settings, path, and context are all required');
   }
-  return isModule(name) && isExternalPath(name, settings, path, getContextPackagePath(context));
+  return (isModule(name) || isScoped(name)) && isExternalPath(name, settings, path, getContextPackagePath(context));
 }
 
 export function isExternalModuleMain(name, settings, path, context) {
@@ -101,10 +101,6 @@ function typeTest(name, context, path) {
   return 'unknown';
 }
 
-export function isScopedModule(name) {
-  return name.indexOf('@') === 0 && !name.startsWith('@/');
-}
-
 export default function resolveImportType(name, context) {
   return typeTest(name, context, resolve(name, context));
 }
diff --git a/src/rules/default.js b/src/rules/default.js
index b8277b45..6b917cca 100644
--- a/src/rules/default.js
+++ b/src/rules/default.js
@@ -15,7 +15,7 @@ module.exports = {
     function checkDefault(specifierType, node) {
 
       const defaultSpecifier = node.specifiers.find(
-        specifier => specifier.type === specifierType
+        specifier => specifier.type === specifierType,
       );
 
       if (!defaultSpecifier) return;
diff --git a/src/rules/export.js b/src/rules/export.js
index be75fa07..e0b2c578 100644
--- a/src/rules/export.js
+++ b/src/rules/export.js
@@ -90,7 +90,7 @@ module.exports = {
       'ExportSpecifier': (node) => addNamed(
         node.exported.name,
         node.exported,
-        getParent(node.parent)
+        getParent(node.parent),
       ),
 
       'ExportNamedDeclaration': function (node) {
@@ -146,7 +146,7 @@ module.exports = {
         if (!any) {
           context.report(
             node.source,
-            `No named exports found in module '${node.source.value}'.`
+            `No named exports found in module '${node.source.value}'.`,
           );
         }
       },
@@ -164,7 +164,7 @@ module.exports = {
               } else {
                 context.report(
                   node,
-                  `Multiple exports of name '${name.replace(tsTypePrefix, '')}'.`
+                  `Multiple exports of name '${name.replace(tsTypePrefix, '')}'.`,
                 );
               }
             }
diff --git a/src/rules/extensions.js b/src/rules/extensions.js
index e116d2f8..677a0a09 100644
--- a/src/rules/extensions.js
+++ b/src/rules/extensions.js
@@ -1,7 +1,7 @@
 import path from 'path';
 
 import resolve from 'eslint-module-utils/resolve';
-import { isBuiltIn, isExternalModule, isScoped, isScopedModule } from '../core/importType';
+import { isBuiltIn, isExternalModule, isScoped } from '../core/importType';
 import moduleVisitor from 'eslint-module-utils/moduleVisitor';
 import docsUrl from '../docsUrl';
 
@@ -131,11 +131,11 @@ module.exports = {
       const slashCount = file.split('/').length - 1;
 
       if (slashCount === 0)  return true;
-      if (isScopedModule(file) && slashCount <= 1) return true;
+      if (isScoped(file) && slashCount <= 1) return true;
       return false;
     }
 
-    function checkFileExtension(source) {
+    function checkFileExtension(source, node) {
       // bail if the declaration doesn't have a source, e.g. "export { foo };", or if it's only partially typed like in an editor
       if (!source || !source.value) return;
       
@@ -161,10 +161,12 @@ module.exports = {
         importPath,
         context.settings,
         resolve(importPath, context),
-        context
+        context,
       ) || isScoped(importPath);
 
       if (!extension || !importPath.endsWith(`.${extension}`)) {
+        // ignore type-only imports
+        if (node.importKind === 'type') return;
         const extensionRequired = isUseOfExtensionRequired(extension, isPackage);
         const extensionForbidden = isUseOfExtensionForbidden(extension);
         if (extensionRequired && !extensionForbidden) {
diff --git a/src/rules/first.js b/src/rules/first.js
index eb613d33..087b840c 100644
--- a/src/rules/first.js
+++ b/src/rules/first.js
@@ -105,7 +105,7 @@ module.exports = {
               const range = [0, removeFixers[removeFixers.length - 1].range[1]];
               let insertSourceCode = sortNodes.map(function (_errorInfo) {
                 const nodeSourceCode = String.prototype.slice.apply(
-                  originSourceCode, _errorInfo.range
+                  originSourceCode, _errorInfo.range,
                 );
                 if (/\S/.test(nodeSourceCode[0])) {
                   return '\n' + nodeSourceCode;
@@ -124,7 +124,7 @@ module.exports = {
               const fixers = [insertFixer].concat(removeFixers);
               fixers.forEach(function (computedFixer, i) {
                 replaceSourceCode += (originSourceCode.slice(
-                  fixers[i - 1] ? fixers[i - 1].range[1] : 0, computedFixer.range[0]
+                  fixers[i - 1] ? fixers[i - 1].range[1] : 0, computedFixer.range[0],
                 ) + computedFixer.text);
               });
               return fixer.replaceTextRange(range, replaceSourceCode);
diff --git a/src/rules/namespace.js b/src/rules/namespace.js
index 74ee9dae..6325b88b 100644
--- a/src/rules/namespace.js
+++ b/src/rules/namespace.js
@@ -68,7 +68,7 @@ module.exports = {
             case 'ImportDefaultSpecifier':
             case 'ImportSpecifier': {
               const meta = imports.get(
-                // default to 'default' for default http://i.imgur.com/nj6qAWy.jpg
+                // default to 'default' for default https://i.imgur.com/nj6qAWy.jpg
                 specifier.imported ? specifier.imported.name : 'default',
               );
               if (!meta || !meta.namespace) { break; }
diff --git a/src/rules/newline-after-import.js b/src/rules/newline-after-import.js
index 1b946b35..a3e0a11e 100644
--- a/src/rules/newline-after-import.js
+++ b/src/rules/newline-after-import.js
@@ -107,7 +107,7 @@ module.exports = {
 after ${type} statement not followed by another ${type}.`,
           fix: fixer => fixer.insertTextAfter(
             node,
-            '\n'.repeat(EXPECTED_LINE_DIFFERENCE - lineDifference)
+            '\n'.repeat(EXPECTED_LINE_DIFFERENCE - lineDifference),
           ),
         });
       }
diff --git a/src/rules/no-cycle.js b/src/rules/no-cycle.js
index dda5a679..cefa41f9 100644
--- a/src/rules/no-cycle.js
+++ b/src/rules/no-cycle.js
@@ -46,7 +46,7 @@ module.exports = {
       name,
       context.settings,
       resolve(name, context),
-      context
+      context,
     );
 
     function checkSourceValue(sourceNode, importer) {
@@ -88,7 +88,7 @@ module.exports = {
           const toTraverse = [...declarations].filter(({ source, isOnlyImportingTypes }) =>
             !ignoreModule(source.value) &&
             // Ignore only type imports
-            !isOnlyImportingTypes
+            !isOnlyImportingTypes,
           );
           /*
           Only report as a cycle if there are any import declarations that are considered by
diff --git a/src/rules/no-duplicates.js b/src/rules/no-duplicates.js
index c887b39e..43c2c5b2 100644
--- a/src/rules/no-duplicates.js
+++ b/src/rules/no-duplicates.js
@@ -45,7 +45,7 @@ function getFix(first, rest, sourceCode) {
   }
 
   const defaultImportNames = new Set(
-    [first, ...rest].map(getDefaultImportName).filter(Boolean)
+    [first, ...rest].map(getDefaultImportName).filter(Boolean),
   );
 
   // Bail if there are multiple different default import names – it's up to the
@@ -83,7 +83,7 @@ function getFix(first, rest, sourceCode) {
   const unnecessaryImports = restWithoutComments.filter(node =>
     !hasSpecifiers(node) &&
     !hasNamespace(node) &&
-    !specifiers.some(specifier => specifier.importNode === node)
+    !specifiers.some(specifier => specifier.importNode === node),
   );
 
   const shouldAddDefault = getDefaultImportName(first) == null && defaultImportNames.size === 1;
@@ -115,7 +115,7 @@ function getFix(first, rest, sourceCode) {
           specifier.isEmpty ? needsComma : true,
         ];
       },
-      ['', !firstHasTrailingComma && !firstIsEmpty]
+      ['', !firstHasTrailingComma && !firstIsEmpty],
     );
 
     const fixes = [];
@@ -123,7 +123,7 @@ function getFix(first, rest, sourceCode) {
     if (shouldAddDefault && openBrace == null && shouldAddSpecifiers) {
       // `import './foo'` → `import def, {...} from './foo'`
       fixes.push(
-        fixer.insertTextAfter(firstToken, ` ${defaultImportName}, {${specifiersText}} from`)
+        fixer.insertTextAfter(firstToken, ` ${defaultImportName}, {${specifiersText}} from`),
       );
     } else if (shouldAddDefault && openBrace == null && !shouldAddSpecifiers) {
       // `import './foo'` → `import def from './foo'`
diff --git a/src/rules/no-extraneous-dependencies.js b/src/rules/no-extraneous-dependencies.js
index 25a91aef..b54ee28b 100644
--- a/src/rules/no-extraneous-dependencies.js
+++ b/src/rules/no-extraneous-dependencies.js
@@ -55,13 +55,13 @@ function getDependencies(context, packageDir) {
         const packageJsonPath = path.join(dir, 'package.json');
         if (!depFieldCache.has(packageJsonPath)) {
           const depFields = extractDepFields(
-            JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'))
+            JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')),
           );
           depFieldCache.set(packageJsonPath, depFields);
         }
         const _packageContent = depFieldCache.get(packageJsonPath);
         Object.keys(packageContent).forEach(depsKey =>
-          Object.assign(packageContent[depsKey], _packageContent[depsKey])
+          Object.assign(packageContent[depsKey], _packageContent[depsKey]),
         );
       });
     } else {
@@ -69,8 +69,8 @@ function getDependencies(context, packageDir) {
       Object.assign(
         packageContent,
         extractDepFields(
-          readPkgUp({ cwd: context.getPhysicalFilename ? context.getPhysicalFilename() : context.getFilename(), normalize: false }).pkg
-        )
+          readPkgUp({ cwd: context.getPhysicalFilename ? context.getPhysicalFilename() : context.getFilename(), normalize: false }).pkg,
+        ),
       );
     }
 
diff --git a/src/rules/no-import-module-exports.js b/src/rules/no-import-module-exports.js
index 45710929..4af1e7b4 100644
--- a/src/rules/no-import-module-exports.js
+++ b/src/rules/no-import-module-exports.js
@@ -13,6 +13,12 @@ function getEntryPoint(context) {
   }
 }
 
+function findScope(context, identifier) {
+  const { scopeManager } = context.getSourceCode();
+
+  return scopeManager && scopeManager.scopes.slice().reverse().find((scope) => scope.variables.some(variable => variable.identifiers.some((node) => node.name === identifier)));
+}
+
 module.exports = {
   meta: {
     type: 'problem',
@@ -43,10 +49,11 @@ module.exports = {
       const isEntryPoint = entryPoint === fileName;
       const isIdentifier = node.object.type === 'Identifier';
       const hasKeywords = (/^(module|exports)$/).test(node.object.name);
-      const isException = options.exceptions &&
-        options.exceptions.some(glob => minimatch(fileName, glob));
+      const objectScope = hasKeywords && findScope(context, node.object.name);
+      const hasCJSExportReference = hasKeywords && (!objectScope || objectScope.type === 'module');
+      const isException = !!options.exceptions && options.exceptions.some(glob => minimatch(fileName, glob));
 
-      if (isIdentifier && hasKeywords && !isEntryPoint && !isException) {
+      if (isIdentifier && hasCJSExportReference && !isEntryPoint && !isException) {
         importDeclarations.forEach(importDeclaration => {
           context.report({
             node: importDeclaration,
diff --git a/src/rules/no-namespace.js b/src/rules/no-namespace.js
index fb3d7605..c615dfff 100644
--- a/src/rules/no-namespace.js
+++ b/src/rules/no-namespace.js
@@ -76,7 +76,7 @@ module.exports = {
             const importLocalNames = generateLocalNames(
               importNames,
               importNameConflicts,
-              namespaceVariable.name
+              namespaceVariable.name,
             );
 
             // Replace the ImportNamespaceSpecifier with a list of ImportSpecifiers
diff --git a/src/rules/no-relative-packages.js b/src/rules/no-relative-packages.js
index 714eb3f5..17406e80 100644
--- a/src/rules/no-relative-packages.js
+++ b/src/rules/no-relative-packages.js
@@ -37,7 +37,7 @@ function checkImportForRelativePackage(context, importPath, node) {
     const properImport = path.join(
       importPkg.pkg.name,
       path.dirname(properPath),
-      importBaseName === path.basename(importRoot) ? '' : importBaseName
+      importBaseName === path.basename(importRoot) ? '' : importBaseName,
     );
     context.report({
       node,
diff --git a/src/rules/no-unresolved.js b/src/rules/no-unresolved.js
index 54910189..b9dae97c 100644
--- a/src/rules/no-unresolved.js
+++ b/src/rules/no-unresolved.js
@@ -40,14 +40,14 @@ module.exports = {
       if (resolvedPath === undefined) {
         context.report(
           source,
-          `Unable to resolve path to module '${source.value}'.`
+          `Unable to resolve path to module '${source.value}'.`,
         );
       } else if (caseSensitive || caseSensitiveStrict) {
         const cacheSettings = ModuleCache.getSettings(context.settings);
         if (!fileExistsWithCaseSync(resolvedPath, cacheSettings, caseSensitiveStrict)) {
           context.report(
             source,
-            `Casing of ${source.value} does not match the underlying filesystem.`
+            `Casing of ${source.value} does not match the underlying filesystem.`,
           );
         }
       }
diff --git a/src/rules/no-unused-modules.js b/src/rules/no-unused-modules.js
index 068eb911..9891d409 100644
--- a/src/rules/no-unused-modules.js
+++ b/src/rules/no-unused-modules.js
@@ -248,7 +248,7 @@ const prepareImportsAndExports = (srcFiles, context) => {
         }
         const localImport = imports.get(key) || new Set();
         value.declarations.forEach(({ importedSpecifiers }) =>
-          importedSpecifiers.forEach(specifier => localImport.add(specifier))
+          importedSpecifiers.forEach(specifier => localImport.add(specifier)),
         );
         imports.set(key, localImport);
       });
@@ -564,13 +564,13 @@ module.exports = {
         if (exportStatement.whereUsed.size < 1) {
           context.report(
             node,
-            `exported declaration '${value}' not used within other modules`
+            `exported declaration '${value}' not used within other modules`,
           );
         }
       } else {
         context.report(
           node,
-          `exported declaration '${value}' not used within other modules`
+          `exported declaration '${value}' not used within other modules`,
         );
       }
     };
diff --git a/src/rules/no-useless-path-segments.js b/src/rules/no-useless-path-segments.js
index a0586080..c0156d09 100644
--- a/src/rules/no-useless-path-segments.js
+++ b/src/rules/no-useless-path-segments.js
@@ -88,7 +88,7 @@ module.exports = {
 
       const fileExtensions = getFileExtensions(context.settings);
       const regexUnnecessaryIndex = new RegExp(
-        `.*\\/index(\\${Array.from(fileExtensions).join('|\\')})?$`
+        `.*\\/index(\\${Array.from(fileExtensions).join('|\\')})?$`,
       );
 
       // Check if path contains unnecessary index (including a configured extension)
@@ -135,8 +135,8 @@ module.exports = {
           importPathSplit
             .slice(0, countExpectedRelativeParents)
             .concat(importPathSplit.slice(countImportPathRelativeParents + diff))
-            .join('/')
-        )
+            .join('/'),
+        ),
       );
     }
 
diff --git a/src/rules/no-webpack-loader-syntax.js b/src/rules/no-webpack-loader-syntax.js
index 2f49ad6c..e517207b 100644
--- a/src/rules/no-webpack-loader-syntax.js
+++ b/src/rules/no-webpack-loader-syntax.js
@@ -4,7 +4,7 @@ import docsUrl from '../docsUrl';
 function reportIfNonStandard(context, node, name) {
   if (name && name.indexOf('!') !== -1) {
     context.report(node, `Unexpected '!' in '${name}'. ` +
-      'Do not use import syntax to configure webpack loaders.'
+      'Do not use import syntax to configure webpack loaders.',
     );
   }
 }
diff --git a/src/rules/order.js b/src/rules/order.js
index 194a3fd5..fce979f0 100644
--- a/src/rules/order.js
+++ b/src/rules/order.js
@@ -200,7 +200,7 @@ function fixOutOfOrder(context, firstNode, secondNode, order) {
       fix: canFix && (fixer =>
         fixer.replaceTextRange(
           [firstRootStart, secondRootEnd],
-          newCode + sourceCode.text.substring(firstRootStart, secondRootStart)
+          newCode + sourceCode.text.substring(firstRootStart, secondRootStart),
         )),
     });
   } else if (order === 'after') {
@@ -210,7 +210,7 @@ function fixOutOfOrder(context, firstNode, secondNode, order) {
       fix: canFix && (fixer =>
         fixer.replaceTextRange(
           [secondRootStart, firstRootEnd],
-          sourceCode.text.substring(secondRootEnd, firstRootEnd) + newCode
+          sourceCode.text.substring(secondRootEnd, firstRootEnd) + newCode,
         )),
     });
   }
@@ -463,7 +463,7 @@ function makeNewlinesBetweenReport(context, imported, newlinesBetweenImports) {
   const getNumberOfEmptyLinesBetween = (currentImport, previousImport) => {
     const linesBetweenImports = context.getSourceCode().lines.slice(
       previousImport.node.loc.end.line,
-      currentImport.node.loc.start.line - 1
+      currentImport.node.loc.start.line - 1,
     );
 
     return linesBetweenImports.filter((line) => !line.trim().length).length;
@@ -631,7 +631,7 @@ module.exports = {
             },
             ranks,
             getBlockImports(node.parent),
-            pathGroupsExcludedImportTypes
+            pathGroupsExcludedImportTypes,
           );
         }
       },
@@ -662,7 +662,7 @@ module.exports = {
           },
           ranks,
           getBlockImports(node.parent),
-          pathGroupsExcludedImportTypes
+          pathGroupsExcludedImportTypes,
         );
       },
       CallExpression: function handleRequires(node) {
@@ -684,7 +684,7 @@ module.exports = {
           },
           ranks,
           getBlockImports(block),
-          pathGroupsExcludedImportTypes
+          pathGroupsExcludedImportTypes,
         );
       },
       'Program:exit': function reportAndReset() {
diff --git a/utils/.eslintrc b/utils/.eslintrc
deleted file mode 100644
index fa000aa7..00000000
--- a/utils/.eslintrc
+++ /dev/null
@@ -1,15 +0,0 @@
-{
-  "parserOptions": {
-    "ecmaVersion": 6,
-  },
-  "rules": {
-    "comma-dangle": ["error", {
-      "arrays": "always-multiline",
-      "objects": "always-multiline",
-      "imports": "always-multiline",
-      "exports": "always-multiline",
-      "functions": "never"
-    }],
-    "no-console": 1,
-  }
-}
diff --git a/utils/CHANGELOG.md b/utils/CHANGELOG.md
index bfa7e803..65d67a35 100644
--- a/utils/CHANGELOG.md
+++ b/utils/CHANGELOG.md
@@ -1,7 +1,7 @@
 # Change Log
 All notable changes to this module will be documented in this file.
-This project adheres to [Semantic Versioning](http://semver.org/).
-This change log adheres to standards from [Keep a CHANGELOG](http://keepachangelog.com).
+This project adheres to [Semantic Versioning](https://semver.org/).
+This change log adheres to standards from [Keep a CHANGELOG](https://keepachangelog.com).
 
 ## Unreleased
 
diff --git a/utils/moduleVisitor.js b/utils/moduleVisitor.js
index 8fb2b260..ade475e2 100644
--- a/utils/moduleVisitor.js
+++ b/utils/moduleVisitor.js
@@ -54,7 +54,7 @@ exports.default = function visitModules(visitor, options) {
   }
 
   // for CommonJS `require` calls
-  // adapted from @mctep: http://git.io/v4rAu
+  // adapted from @mctep: https://git.io/v4rAu
   function checkCommon(call) {
     if (call.callee.type !== 'Identifier') return;
     if (call.callee.name !== 'require') return;
@@ -81,7 +81,7 @@ exports.default = function visitModules(visitor, options) {
       if (typeof element.value !== 'string') continue;
 
       if (element.value === 'require' ||
-          element.value === 'exports') continue; // magic modules: http://git.io/vByan
+          element.value === 'exports') continue; // magic modules: https://git.io/vByan
 
       checkSourceValue(element, element);
     }
diff --git a/utils/resolve.js b/utils/resolve.js
index 27d5dcc1..31fb6590 100644
--- a/utils/resolve.js
+++ b/utils/resolve.js
@@ -51,7 +51,7 @@ function tryRequire(target, sourceFile) {
   return require(resolved);
 }
 
-// http://stackoverflow.com/a/27382838
+// https://stackoverflow.com/a/27382838
 exports.fileExistsWithCaseSync = function fileExistsWithCaseSync(filepath, cacheSettings, strict) {
   // don't care if the FS is case-sensitive
   if (CASE_SENSITIVE_FS) return true;


Test patch:
diff --git a/resolvers/webpack/test/alias.js b/resolvers/webpack/test/alias.js
index 06aad446..946365b3 100644
--- a/resolvers/webpack/test/alias.js
+++ b/resolvers/webpack/test/alias.js
@@ -22,7 +22,7 @@ describe('resolve.alias', function () {
 
 // todo: reimplement with resolver function / config
 // describe.skip('webpack alias spec', function () {
-//   // from table: http://webpack.github.io/docs/configuration.html#resolve-alias
+//   // from table: https://webpack.github.io/docs/configuration.html#resolve-alias
 //   function tableLine(alias, xyz, xyzFile) {
 //     describe(JSON.stringify(alias), function () {
 //       it('xyz: ' + xyz, function () {
@@ -82,7 +82,7 @@ describe('resolve.alias', function () {
 // });
 
 // describe.skip('nested module names', function () {
-//   // from table: http://webpack.github.io/docs/configuration.html#resolve-alias
+//   // from table: https://webpack.github.io/docs/configuration.html#resolve-alias
 //   function nestedName(alias, xyz, xyzFile) {
 //     describe(JSON.stringify(alias), function () {
 //       it('top/xyz: ' + xyz, function () {
diff --git a/tests/files/flowtypes.js b/tests/files/flowtypes.js
index 2df24714..4282cf80 100644
--- a/tests/files/flowtypes.js
+++ b/tests/files/flowtypes.js
@@ -1,6 +1,6 @@
 // @flow
 // requires babel-eslint parser or flow plugin
-// http://flowtype.org/blog/2015/02/18/Import-Types.html
+// https://flowtype.org/blog/2015/02/18/Import-Types.html
 export type MyType = {
   id: number,
   firstName: string,
diff --git a/tests/src/core/importType.js b/tests/src/core/importType.js
index 0dcd5266..528377e6 100644
--- a/tests/src/core/importType.js
+++ b/tests/src/core/importType.js
@@ -1,7 +1,7 @@
 import { expect } from 'chai';
 import * as path from 'path';
 
-import importType, { isExternalModule, isScopedModule, isScoped } from 'core/importType';
+import importType, { isExternalModule, isScoped } from 'core/importType';
 
 import { testContext, testFilePath } from '../utils';
 
@@ -234,16 +234,19 @@ describe('importType(name)', function () {
   it('`isExternalModule` works with windows directory separator', function () {
     const context = testContext();
     expect(isExternalModule('foo', {}, 'E:\\path\\to\\node_modules\\foo', context)).to.equal(true);
+    expect(isExternalModule('@foo/bar', {}, 'E:\\path\\to\\node_modules\\@foo\\bar', context)).to.equal(true);
     expect(isExternalModule('foo', {
       'import/external-module-folders': ['E:\\path\\to\\node_modules'],
     }, 'E:\\path\\to\\node_modules\\foo', context)).to.equal(true);
   });
 
-  it('correctly identifies scoped modules with `isScopedModule`', () => {
-    expect(isScopedModule('@/abc')).to.equal(false);
-    expect(isScopedModule('@/abc/def')).to.equal(false);
-    expect(isScopedModule('@a/abc')).to.equal(true);
-    expect(isScopedModule('@a/abc/def')).to.equal(true);
+  it('`isExternalModule` works with unix directory separator', function () {
+    const context = testContext();
+    expect(isExternalModule('foo', {}, '/path/to/node_modules/foo', context)).to.equal(true);
+    expect(isExternalModule('@foo/bar', {}, '/path/to/node_modules/@foo/bar', context)).to.equal(true);
+    expect(isExternalModule('foo', {
+      'import/external-module-folders': ['/path/to/node_modules'],
+    }, '/path/to/node_modules/foo', context)).to.equal(true);
   });
 
   it('correctly identifies scoped modules with `isScoped`', () => {
diff --git a/tests/src/rules/extensions.js b/tests/src/rules/extensions.js
index 01343345..93ebc28f 100644
--- a/tests/src/rules/extensions.js
+++ b/tests/src/rules/extensions.js
@@ -1,6 +1,6 @@
 import { RuleTester } from 'eslint';
 import rule from 'rules/extensions';
-import { test, testFilePath } from '../utils';
+import { getTSParsers, test, testFilePath } from '../utils';
 
 const ruleTester = new RuleTester();
 
@@ -597,3 +597,34 @@ ruleTester.run('extensions', rule, {
     }),
   ],
 });
+
+describe('TypeScript', () => {
+  getTSParsers()
+    // Type-only imports were added in TypeScript ESTree 2.23.0
+    .filter((parser) => parser !== require.resolve('typescript-eslint-parser'))
+    .forEach((parser) => {
+      ruleTester.run(`${parser}: extensions ignore type-only`, rule, {
+        valid: [
+          test({
+            code: 'import type T from "./typescript-declare";',
+            options: [
+              'always',
+              { ts: 'never', tsx: 'never', js: 'never', jsx: 'never' },
+            ],
+            parser,
+          }),
+        ],
+        invalid: [
+          test({
+            code: 'import T from "./typescript-declare";',
+            errors: ['Missing file extension for "./typescript-declare"'],
+            options: [
+              'always',
+              { ts: 'never', tsx: 'never', js: 'never', jsx: 'never' },
+            ],
+            parser,
+          }),
+        ],
+      });
+    });
+});
diff --git a/tests/src/rules/no-import-module-exports.js b/tests/src/rules/no-import-module-exports.js
index 9ffbe6b5..a40eb7e2 100644
--- a/tests/src/rules/no-import-module-exports.js
+++ b/tests/src/rules/no-import-module-exports.js
@@ -1,7 +1,7 @@
 import path from 'path';
 import { RuleTester } from 'eslint';
 
-import { test } from '../utils';
+import { test, testVersion } from '../utils';
 
 const ruleTester = new RuleTester({
   parserOptions: { ecmaVersion: 6, sourceType: 'module' },
@@ -15,7 +15,7 @@ const error = {
 };
 
 ruleTester.run('no-import-module-exports', rule, {
-  valid: [
+  valid: [].concat(
     test({
       code: `
         const thing = require('thing')
@@ -64,7 +64,57 @@ ruleTester.run('no-import-module-exports', rule, {
       `,
       filename: path.join(process.cwd(), 'tests/files/missing-entrypoint/cli.js'),
     }),
-  ],
+    testVersion('>= 6', () => ({
+      code: `
+        import fs from 'fs/promises';
+
+        const subscriptions = new Map();
+        
+        export default async (client) => {
+            /**
+             * loads all modules and their subscriptions
+             */
+            const modules = await fs.readdir('./src/modules');
+        
+            await Promise.all(
+                modules.map(async (moduleName) => {
+                    // Loads the module
+                    const module = await import(\`./modules/\${moduleName}/module.js\`);
+                    // skips the module, in case it is disabled.
+                    if (module.enabled) {
+                        // Loads each of it's subscriptions into their according list.
+                        module.subscriptions.forEach((fun, event) => {
+                            if (!subscriptions.has(event)) {
+                                subscriptions.set(event, []);
+                            }
+                            subscriptions.get(event).push(fun);
+                        });
+                    }
+                })
+            );
+        
+            /**
+             * Setting up all events.
+             * binds all events inside the subscriptions map to call all functions provided
+             */
+            subscriptions.forEach((funs, event) => {
+                client.on(event, (...args) => {
+                    funs.forEach(async (fun) => {
+                        try {
+                            await fun(client, ...args);
+                        } catch (e) {
+                            client.emit('error', e);
+                        }
+                    });
+                });
+            });
+        };
+      `,
+      parserOptions: {
+        ecmaVersion: 2020,
+      },
+    })) || [],
+  ),
   invalid: [
     test({
       code: `
diff --git a/tests/src/rules/no-unresolved.js b/tests/src/rules/no-unresolved.js
index a5ff6da5..71efc128 100644
--- a/tests/src/rules/no-unresolved.js
+++ b/tests/src/rules/no-unresolved.js
@@ -67,7 +67,7 @@ function runResolverTests(resolver) {
         options: [{ amd: true }] }),
       rest({ code: 'require(["./does-not-exist"], function (bar) {})',
         options: [{ amd: false }] }),
-      // magic modules: http://git.io/vByan
+      // magic modules: https://git.io/vByan
       rest({ code: 'define(["require", "exports", "module"], function (r, e, m) { })',
         options: [{ amd: true }] }),