diff --git a/.babelrc b/.babelrc
index c2f08fdc2..883c03b79 100644
--- a/.babelrc
+++ b/.babelrc
@@ -1,9 +1,18 @@
 {
-  "presets": [ "es2015-argon" ],
+  "presets": ["airbnb"],
   "sourceMaps": "inline",
+  "retainLines": true,
   "env": {
     "test": {
-      "plugins": [ "istanbul" ]
+      "plugins": [
+        "istanbul",
+        ["module-resolver", { "root": ["./src/"] }],
+      ]
+    },
+    "testCompiled": {
+      "plugins": [
+        ["module-resolver", { "root": ["./lib/"] }],
+      ]
     }
   }
 }
diff --git a/.coveralls.yml b/.coveralls.yml
index 77bcfb374..b8ebe05a1 100644
--- a/.coveralls.yml
+++ b/.coveralls.yml
@@ -1,2 +1,2 @@
 ---
-repo_token: fW3moW39Z8pKOgqTnUMT68DnNCd2SM8Ly
\ No newline at end of file
+repo_token: fW3moW39Z8pKOgqTnUMT68DnNCd2SM8Ly
diff --git a/.editorconfig b/.editorconfig
index e2bfac523..b7b8d0999 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -7,3 +7,4 @@ insert_final_newline = true
 indent_style = space
 indent_size = 2
 end_of_line = lf
+quote_type = single
diff --git a/.eslintignore b/.eslintignore
new file mode 100644
index 000000000..3516f09b9
--- /dev/null
+++ b/.eslintignore
@@ -0,0 +1,13 @@
+lib
+coverage
+.nyc_output
+node_modules
+tests/files/malformed.js
+tests/files/with-syntax-error
+tests/files/just-json-files/invalid.json
+tests/files/typescript-d-ts/
+resolvers/webpack/test/files
+examples
+# we want to ignore "tests/files" here, but unfortunately doing so would
+# interfere with unit test and fail it for some reason.
+# tests/files
diff --git a/.eslintrc b/.eslintrc
new file mode 100644
index 000000000..80e1014c6
--- /dev/null
+++ b/.eslintrc
@@ -0,0 +1,267 @@
+{
+    "root": true,
+    "plugins": [
+        "eslint-plugin",
+        "import",
+    ],
+    "extends": [
+        "eslint:recommended",
+        "plugin:eslint-plugin/recommended",
+        "plugin:import/recommended",
+    ],
+    "env": {
+        "node": true,
+        "es6": true,
+        "es2017": true,
+    },
+    "parserOptions": {
+        "sourceType": "module",
+        "ecmaVersion": 2020,
+    },
+    "rules": {
+        "array-bracket-spacing": [2, "never"],
+        "arrow-body-style": [2, "as-needed"],
+        "arrow-parens": [2, "always"],
+        "arrow-spacing": [2, { "before": true, "after": true }],
+        "block-spacing": [2, "always"],
+        "brace-style": [2, "1tbs", { "allowSingleLine": true }],
+        "comma-dangle": ["error", {
+            "arrays": "always-multiline",
+            "objects": "always-multiline",
+            "imports": "always-multiline",
+            "exports": "always-multiline",
+            "functions": "always-multiline",
+        }],
+        "comma-spacing": [2, { "before": false, "after": true }],
+        "comma-style": [2, "last"],
+        "computed-property-spacing": [2, "never"],
+        "curly": [2, "all"],
+        "default-case": [2, { "commentPattern": "(?:)" }],
+        "default-case-last": [2],
+        "default-param-last": [2],
+        "dot-location": [2, "property"],
+        "dot-notation": [2, { "allowKeywords": true, "allowPattern": "throws" }],
+        "eol-last": [2, "always"],
+        "eqeqeq": [2, "allow-null"],
+        "for-direction": [2],
+        "function-call-argument-newline": [2, "consistent"],
+        "func-call-spacing": [2, "never"],
+        "implicit-arrow-linebreak": [2, "beside"],
+        "indent": [2, 2, {
+            "SwitchCase": 1,
+            "VariableDeclarator": 1,
+            "outerIIFEBody": 1,
+            "FunctionDeclaration": {
+                "parameters": 1,
+                "body": 1
+            },
+            "FunctionExpression": {
+                "parameters": 1,
+                "body": 1
+            },
+            "CallExpression": {
+                "arguments": 1
+            },
+            "ArrayExpression": 1,
+            "ObjectExpression": 1,
+            "ImportDeclaration": 1,
+            "flatTernaryExpressions": false,
+        }],
+        "jsx-quotes": [2, "prefer-double"],
+        "key-spacing": [2, {
+            "beforeColon": false,
+            "afterColon": true,
+            "mode": "strict",
+        }],
+        "keyword-spacing": ["error", {
+          "before": true,
+          "after": true,
+          "overrides": {
+            "return": { "after": true },
+            "throw": { "after": true },
+            "case": { "after": true }
+          }
+        }],
+        "linebreak-style": [2, "unix"],
+        "lines-around-directive": [2, {
+            "before": "always",
+            "after": "always",
+        }],
+        "max-len": 0,
+        "new-parens": 2,
+        "no-array-constructor": 2,
+        "no-compare-neg-zero": 2,
+        "no-cond-assign": [2, "always"],
+        "no-extra-parens": 2,
+        "no-multiple-empty-lines": [2, { "max": 1, "maxEOF": 1, "maxBOF": 0 }],
+        "no-return-assign": [2, "always"],
+        "no-trailing-spaces": 2,
+        "no-use-before-define": [2, { "functions": true, "classes": true, "variables": true }],
+        "no-var": 2,
+        "object-curly-spacing": [2, "always"],
+        "object-shorthand": ["error", "always", {
+            "ignoreConstructors": false,
+            "avoidQuotes": false,
+            "avoidExplicitReturnArrows": true,
+        }],
+        "one-var": [2, "never"],
+        "operator-linebreak": [2, "none", {
+            "overrides": {
+              "?": "before",
+              ":": "before",
+              "&&": "before",
+              "||": "before",
+            },
+        }],
+        "prefer-const": 2,
+        "prefer-object-spread": 2,
+        "prefer-rest-params": 2,
+        "prefer-template": 2,
+        "quote-props": [2, "as-needed", { "keywords": false }],
+        "quotes": [2, "single", {
+            "allowTemplateLiterals": true,
+            "avoidEscape": true,
+        }],
+        "rest-spread-spacing": [2, "never"],
+        "semi": [2, "always"],
+        "semi-spacing": [2, { "before": false, "after": true }],
+        "semi-style": [2, "last"],
+        "space-before-blocks": [2, { "functions": "always", "keywords": "always", "classes": "always" }],
+        "space-before-function-paren": ["error", {
+            "anonymous": "always",
+            "named": "never",
+            "asyncArrow": "always",
+        }],
+        "space-in-parens": [2, "never"],
+        "space-infix-ops": [2],
+        "space-unary-ops": [2, { "words": true, "nonwords": false }],
+        "switch-colon-spacing": [2, { "after": true, "before": false }],
+        "template-curly-spacing": [2, "never"],
+        "template-tag-spacing": [2, "never"],
+        "unicode-bom": [2, "never"],
+        "use-isnan": [2, { "enforceForSwitchCase": true }],
+        "valid-typeof": [2],
+        "wrap-iife": [2, "outside", { "functionPrototypeMethods": true }],
+        "wrap-regex": [2],
+        "yield-star-spacing": [2, { "before": false, "after": true }],
+        "yoda": [2, "never", { "exceptRange": true, "onlyEquality": false }],
+
+        "eslint-plugin/consistent-output": [
+            "error",
+            "always",
+        ],
+        "eslint-plugin/meta-property-ordering": "error",
+        "eslint-plugin/no-deprecated-context-methods": "error",
+        "eslint-plugin/no-deprecated-report-api": "off",
+        "eslint-plugin/prefer-replace-text": "error",
+        "eslint-plugin/report-message-format": "error",
+        "eslint-plugin/require-meta-docs-description": ["error", { "pattern": "^(Enforce|Ensure|Prefer|Forbid).+\\.$" }],
+        "eslint-plugin/require-meta-schema": "error",
+        "eslint-plugin/require-meta-type": "error",
+
+        // dog fooding
+        "import/no-extraneous-dependencies": ["error", {
+            "devDependencies": [
+                "tests/**",
+                "resolvers/*/test/**",
+                "scripts/**"
+            ],
+            "optionalDependencies": false,
+            "peerDependencies": true,
+            "bundledDependencies": false,
+        }],
+        "import/unambiguous": "off",
+    },
+
+    "settings": {
+        "import/resolver": {
+            "node": {
+                "paths": [
+                    "src",
+                ],
+            },
+        },
+    },
+
+    "overrides": [
+        {
+            "files": "scripts/**",
+            "rules": {
+                "no-console": "off",
+            },
+        },
+        {
+            "files": [
+                "resolvers/**",
+                "utils/**",
+            ],
+            "env": {
+                "es6": false,
+            },
+            "parserOptions": {
+                "sourceType": "module",
+                "ecmaVersion": 2016,
+            },
+            "rules": {
+                "comma-dangle": ["error", {
+                    "arrays": "always-multiline",
+                    "objects": "always-multiline",
+                    "imports": "always-multiline",
+                    "exports": "always-multiline",
+                    "functions": "never"
+                }],
+                "prefer-destructuring": "off",
+                "prefer-object-spread": "off",
+                "prefer-rest-params": "off",
+                "prefer-spread": "off",
+                "prefer-template": "off",
+            }
+        },
+        {
+            "files": [
+                "resolvers/webpack/**",
+                "utils/**",
+            ],
+            "rules": {
+                "no-console": 1,
+            },
+        },
+        {
+            "files": [
+                "utils/**", // TODO
+            ],
+            "rules": {
+                "no-use-before-define": "off",
+            },
+        },
+        {
+            "files": [
+                "resolvers/webpack/index.js",
+                "resolvers/webpack/test/example.js",
+                "utils/parse.js",
+            ],
+            "rules": {
+                "no-console": "off",
+            },
+        },
+        {
+            "files": [
+                "resolvers/*/test/**/*",
+            ],
+            "env": {
+                "mocha": true,
+                "es6": false
+            },
+        },
+        {
+            "files": "tests/**",
+            "env": {
+                "mocha": true,
+            },
+            "rules": {
+                "max-len": 0,
+                "import/default": 0,
+            },
+        },
+    ],
+}
diff --git a/.eslintrc.yml b/.eslintrc.yml
deleted file mode 100644
index b54ed522f..000000000
--- a/.eslintrc.yml
+++ /dev/null
@@ -1,35 +0,0 @@
----
-plugins:
-  - import
-extends:
-  - eslint:recommended
-  - plugin:import/recommended
-
-env:
-  node: true
-  es6: true
-
-parserOptions:
-  sourceType: module
-  ecmaVersion: 6
-
-rules:
-  max-len: [1, 99, 2]
-  semi: [2, "never"]
-  curly: [2, "multi-line"]
-  comma-dangle: [2, always-multiline]
-  eqeqeq: [2, "allow-null"]
-  no-shadow: 1
-  quotes:
-    - 2
-    - single
-    - allowTemplateLiterals: true
-
-  # dog fooding
-  import/no-extraneous-dependencies: "error"
-  import/unambiguous: "off"
-
-settings:
-  import/resolver:
-    node:
-      paths: [ src ]
diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
new file mode 100644
index 000000000..0ef28872f
--- /dev/null
+++ b/.github/FUNDING.yml
@@ -0,0 +1,12 @@
+# These are supported funding model platforms
+
+github: [ljharb]
+patreon: # Replace with a single Patreon username
+open_collective: eslint-plugin-import # Replace with a single Open Collective username
+ko_fi: # Replace with a single Ko-fi username
+tidelift: npm/eslint-plugin-import
+community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
+liberapay: # Replace with a single Liberapay username
+issuehunt: # Replace with a single IssueHunt username
+otechie: # Replace with a single Otechie username
+custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
diff --git a/.github/issue_template.md b/.github/issue_template.md
new file mode 100644
index 000000000..673d65237
--- /dev/null
+++ b/.github/issue_template.md
@@ -0,0 +1,10 @@
+<!-- Reporting a bug? -->
+<!--
+  Please provide the output of running ESLint with this environment variable: `DEBUG=eslint-plugin-import:*`
+  Windows:      `set DEBUG=eslint-plugin-import:* & eslint .`
+  Linux/Mac: `export DEBUG=eslint-plugin-import:* & eslint .`
+
+  It will also be helpful if you can provide a minimal reproducible example
+  Preferably a GitHub repository containing all the code & ESLint config required
+  https://stackoverflow.com/help/minimal-reproducible-example
+-->
diff --git a/.github/workflows/native-wsl.yml b/.github/workflows/native-wsl.yml
new file mode 100644
index 000000000..5e8318899
--- /dev/null
+++ b/.github/workflows/native-wsl.yml
@@ -0,0 +1,155 @@
+name: Native and WSL
+
+on: [push, pull_request]
+
+concurrency:
+  group: ${{ github.workflow }}-${{ github.ref }}
+  cancel-in-progress: ${{ github.event_name == 'pull_request' }}
+
+jobs:
+  build:
+    runs-on: ${{ matrix.os }}
+    defaults:
+        run:
+          shell: ${{ matrix.configuration == 'wsl' && 'wsl-bash {0}' || 'pwsh' }}
+    strategy:
+      fail-fast: false
+      matrix:
+        os: [windows-2019]
+        node-version: [18, 16, 14, 12, 10, 8, 6, 4]
+        configuration: [wsl, native]
+
+    steps:
+    - uses: actions/checkout@v4
+    - uses: Vampire/setup-wsl@v3
+      if: matrix.configuration == 'wsl'
+      with:
+        distribution: Ubuntu-22.04
+    - run: curl --version
+    - name: 'WSL: do all npm install steps'
+      if: matrix.configuration == 'wsl'
+      env:
+        ESLINT_VERSION: 7
+        TRAVIS_NODE_VERSION: ${{ matrix.node-version }}
+      run: |
+        curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/master/install.sh | bash
+        export NVM_DIR="$HOME/.nvm"
+        [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"  # This loads nvm
+        nvm install --latest-npm ${{ matrix.node-version }}
+
+        if [ ${{ matrix.node-version }} -ge 4 ] && [ ${{ matrix.node-version }} -lt 6 ]; then
+          npm install eslint@4 --no-save --ignore-scripts
+          npm install
+          npm install eslint-import-resolver-typescript@1.0.2 --no-save
+          npm uninstall @angular-eslint/template-parser @typescript-eslint/parser --no-save
+        fi
+        if [ ${{ matrix.node-version }} -ge 6 ] && [ ${{ matrix.node-version }} -lt 7 ]; then
+          npm install eslint@5 --no-save --ignore-scripts
+          npm install
+          npm uninstall @angular-eslint/template-parser --no-save
+          npm install eslint-import-resolver-typescript@1.0.2 @typescript-eslint/parser@3 --no-save
+        fi
+        if [ ${{ matrix.node-version }} -ge 7 ] && [ ${{ matrix.node-version }} -lt 8 ]; then
+          npm install eslint@6 --no-save --ignore-scripts
+          npm install
+          npm install eslint-import-resolver-typescript@1.0.2 typescript-eslint-parser@20 --no-save
+          npm uninstall @angular-eslint/template-parser --no-save
+        fi
+        if [ ${{ matrix.node-version }} -eq 8 ]; then
+          npm install eslint@6 --no-save --ignore-scripts
+          npm install
+          npm uninstall @angular-eslint/template-parser --no-save
+          npm install @typescript-eslint/parser@3 --no-save
+        fi
+        if [ ${{ matrix.node-version }} -gt 8 ] && [ ${{ matrix.node-version }} -lt 10 ]; then
+          npm install eslint@7 --no-save --ignore-scripts
+          npm install
+          npm install @typescript-eslint/parser@3 --no-save
+        fi
+        if [ ${{ matrix.node-version }} -ge 10 ] && [ ${{ matrix.node-version }} -lt 12 ]; then
+          npm install
+          npm install @typescript-eslint/parser@4 --no-save
+        fi
+        if [ ${{ matrix.node-version }} -ge 12 ]; then
+          npm install
+        fi
+        npm run copy-metafiles
+        npm run pretest
+        npm run tests-only
+
+    - name: install dependencies for node <= 10
+      if: matrix.node-version <= '10' && matrix.configuration == 'native'
+      run: |
+        npm install --legacy-peer-deps
+        npm install eslint@7 --no-save
+
+    - name: Install dependencies for node > 10
+      if: matrix.node-version > '10'  && matrix.configuration == 'native'
+      run: npm install
+
+    - name: install the latest version of nyc
+      if: matrix.configuration == 'native'
+      run: npm install nyc@latest --no-save
+
+    - name: copy metafiles for node <= 8
+      if: matrix.node-version <= 8 && matrix.configuration == 'native'
+      env:
+        ESLINT_VERSION: 6
+        TRAVIS_NODE_VERSION: ${{ matrix.node-version }}
+      run: |
+        npm run copy-metafiles
+        bash ./tests/dep-time-travel.sh 2>&1
+    - name: copy metafiles for Node > 8
+      if: matrix.node-version > 8 && matrix.configuration == 'native'
+      env:
+        ESLINT_VERSION: 7
+        TRAVIS_NODE_VERSION: ${{ matrix.node-version }}
+      run: |
+        npm run copy-metafiles
+        bash ./tests/dep-time-travel.sh 2>&1
+
+    - name: install ./resolver dependencies in Native
+      if: matrix.configuration == 'native'
+      shell: pwsh
+      run: |
+        npm config set package-lock false
+        $resolverDir = "./resolvers"
+        Get-ChildItem -Directory $resolverDir |
+          ForEach {
+            Write-output $(Resolve-Path $(Join-Path $resolverDir  $_.Name))
+            Push-Location $(Resolve-Path $(Join-Path $resolverDir  $_.Name))
+              npm install
+              npm ls nyc > $null;
+              if ($?) {
+                npm install nyc@latest --no-save
+              }
+              Pop-Location
+            }
+
+    - name: run tests in Native
+      if: matrix.configuration == 'native'
+      shell: pwsh
+      run: |
+        npm run pretest
+        npm run tests-only
+        $resolverDir = "./resolvers";
+        $resolvers = @();
+        Get-ChildItem -Directory $resolverDir |
+          ForEach {
+            $resolvers += "$(Resolve-Path $(Join-Path $resolverDir $_.Name))";
+          }
+        $env:RESOLVERS = [string]::Join(";", $resolvers);
+        foreach ($resolver in $resolvers) {
+            Set-Location -Path $resolver.Trim('"')
+            npm run tests-only
+            Set-Location -Path $PSScriptRoot
+        }
+
+    - name: codecov
+      uses: codecov/codecov-action@v3.1.5
+
+  windows:
+    runs-on: ubuntu-latest
+    needs: [build]
+    steps:
+      - run: true
diff --git a/.github/workflows/node-4+.yml b/.github/workflows/node-4+.yml
new file mode 100644
index 000000000..323c2ad54
--- /dev/null
+++ b/.github/workflows/node-4+.yml
@@ -0,0 +1,160 @@
+name: 'Tests: node.js'
+
+on: [pull_request, push]
+
+concurrency:
+  group: ${{ github.workflow }}-${{ github.ref }}
+  cancel-in-progress: ${{ github.event_name == 'pull_request' }}
+
+permissions:
+  contents: read
+
+jobs:
+  matrix:
+    runs-on: ubuntu-latest
+    outputs:
+      latest: ${{ steps.set-matrix.outputs.requireds }}
+      minors: ${{ steps.set-matrix.outputs.optionals }}
+    steps:
+      - uses: ljharb/actions/node/matrix@main
+        id: set-matrix
+        with:
+          versionsAsRoot: true
+          type: majors
+          preset: '>= 6' # preset: '>=4' # see https://github.com/import-js/eslint-plugin-import/issues/2053
+
+  latest:
+    needs: [matrix]
+    name: 'majors'
+    runs-on: ${{ matrix.os }}
+
+    strategy:
+      fail-fast: false
+      matrix:
+        os:
+          - ubuntu-latest
+          - macos-latest
+        node-version: ${{ fromJson(needs.matrix.outputs.latest) }}
+        eslint:
+          - 9
+          - 8
+          - 7
+          - 6
+          - 5
+          - 4
+          - 3
+          - 2
+        include:
+          - node-version: 'lts/*'
+            os: ubuntu-latest
+            eslint: 7
+            ts-parser: 4
+            env:
+              TS_PARSER: 4
+          - node-version: 'lts/*'
+            os: ubuntu-latest
+            eslint: 7
+            ts-parser: 3
+            env:
+              TS_PARSER: 3
+          - node-version: 'lts/*'
+            os: ubuntu-latest
+            eslint: 7
+            ts-parser: 2
+            env:
+              TS_PARSER: 2
+        exclude:
+          - node-version: 16
+            eslint: 9
+          - node-version: 15
+            eslint: 9
+          - node-version: 15
+            eslint: 8
+          - node-version: 14
+            eslint: 9
+          - node-version: 13
+            eslint: 9
+          - node-version: 13
+            eslint: 8
+          - node-version: 12
+            eslint: 9
+          - node-version: 11
+            eslint: 9
+          - node-version: 11
+            eslint: 8
+          - node-version: 10
+            eslint: 9
+          - node-version: 10
+            eslint: 8
+          - node-version: 9
+            eslint: 9
+          - node-version: 9
+            eslint: 8
+          - node-version: 9
+            eslint: 7
+          - node-version: 8
+            eslint: 9
+          - node-version: 8
+            eslint: 8
+          - node-version: 8
+            eslint: 7
+          - node-version: 7
+            eslint: 9
+          - node-version: 7
+            eslint: 8
+          - node-version: 7
+            eslint: 7
+          - node-version: 7
+            eslint: 6
+          - node-version: 6
+            eslint: 9
+          - node-version: 6
+            eslint: 8
+          - node-version: 6
+            eslint: 7
+          - node-version: 6
+            eslint: 6
+          - node-version: 5
+            eslint: 9
+          - node-version: 5
+            eslint: 8
+          - node-version: 5
+            eslint: 7
+          - node-version: 5
+            eslint: 6
+          - node-version: 5
+            eslint: 5
+          - node-version: 4
+            eslint: 9
+          - node-version: 4
+            eslint: 8
+          - node-version: 4
+            eslint: 7
+          - node-version: 4
+            eslint: 6
+          - node-version: 4
+            eslint: 5
+
+    steps:
+      - uses: actions/checkout@v4
+      - uses: ljharb/actions/node/install@main
+        continue-on-error: ${{ matrix.eslint == 4 && matrix.node-version == 4 }}
+        name: 'nvm install ${{ matrix.node-version }} && npm install, with eslint ${{ matrix.eslint }}'
+        env:
+          NPM_CONFIG_LEGACY_PEER_DEPS: ${{ matrix.node-version == 11 && false || true }}
+          ESLINT_VERSION: ${{ matrix.eslint }}
+          TRAVIS_NODE_VERSION: ${{ matrix.node-version }}
+        with:
+          node-version: ${{ matrix.node-version }}
+          after_install: npm run copy-metafiles && ./tests/dep-time-travel.sh
+          skip-ls-check: true
+      - run: npm run pretest
+      - run: npm run tests-only
+      - uses: codecov/codecov-action@v3.1.5
+
+  node:
+    name: 'node 4+'
+    needs: [latest]
+    runs-on: ubuntu-latest
+    steps:
+      - run: 'echo tests completed'
diff --git a/.github/workflows/node-pretest.yml b/.github/workflows/node-pretest.yml
new file mode 100644
index 000000000..e25b00e49
--- /dev/null
+++ b/.github/workflows/node-pretest.yml
@@ -0,0 +1,44 @@
+name: 'Tests: pretest/posttest'
+
+on: [pull_request, push]
+
+permissions:
+  contents: read
+
+jobs:
+  # pretest:
+  #   runs-on: ubuntu-latest
+
+  #   steps:
+  #     - uses: actions/checkout@v4
+  #     - uses: ljharb/actions/node/install@main
+  #       name: 'nvm install lts/* && npm install'
+  #       with:
+  #         node-version: 'lts/*'
+  #         skip-ls-check: true
+  #     - run: npm run pretest
+
+  types:
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v4
+      - uses: ljharb/actions/node/install@main
+        name: 'npm install'
+        with:
+          skip-ls-check: true
+      # for some reason we've got to force typescript to install here
+      # even though the npm script has `typescript@latest`
+      - run: npm i --force typescript@latest
+      - run: npm run test-types
+
+  posttest:
+    runs-on: ubuntu-latest
+
+    steps:
+      - uses: actions/checkout@v4
+      - uses: ljharb/actions/node/install@main
+        name: 'nvm install lts/* && npm install'
+        with:
+          node-version: 'lts/*'
+          skip-ls-check: true
+      - run: npm run posttest
diff --git a/.github/workflows/packages.yml b/.github/workflows/packages.yml
new file mode 100644
index 000000000..f73f8e18f
--- /dev/null
+++ b/.github/workflows/packages.yml
@@ -0,0 +1,64 @@
+name: 'Tests: packages'
+
+on: [pull_request, push]
+
+concurrency:
+  group: ${{ github.workflow }}-${{ github.ref }}
+  cancel-in-progress: ${{ github.event_name == 'pull_request' }}
+
+permissions:
+  contents: read
+
+jobs:
+  matrix:
+    runs-on: ubuntu-latest
+    outputs:
+      latest: ${{ steps.set-matrix.outputs.requireds }}
+      minors: ${{ steps.set-matrix.outputs.optionals }}
+    steps:
+      - uses: ljharb/actions/node/matrix@main
+        id: set-matrix
+        with:
+          type: 'majors'
+          preset: '>= 6' # preset: '>=4' # see https://github.com/import-js/eslint-plugin-import/issues/2053
+          versionsAsRoot: true
+
+  tests:
+    needs: [matrix]
+    name: 'packages'
+    runs-on: ubuntu-latest
+
+    strategy:
+      fail-fast: false
+      matrix:
+        node-version: ${{ fromJson(needs.matrix.outputs.latest) }}
+        eslint:
+          - 8
+          - 7
+        package:
+          - resolvers/node
+          - resolvers/webpack
+          # - memo-parser
+          # - utils
+
+    steps:
+      - uses: actions/checkout@v4
+      - uses: ljharb/actions/node/install@main
+        name: 'nvm install ${{ matrix.node-version }} && npm install'
+        env:
+          NPM_CONFIG_LEGACY_PEER_DEPS: ${{ matrix.node-version == 11 && false || true }}
+          ESLINT_VERSION: ${{ matrix.eslint }}
+          TRAVIS_NODE_VERSION: ${{ matrix.node-version }}
+        with:
+          node-version: ${{ matrix.node-version }}
+          after_install: npm run copy-metafiles && ./tests/dep-time-travel.sh && cd ${{ matrix.package }} && npm install
+          skip-ls-check: true
+      - run: cd ${{ matrix.package }} && npm run tests-only
+      - uses: codecov/codecov-action@v3.1.5
+
+  packages:
+    name: 'packages: all tests'
+    needs: [tests]
+    runs-on: ubuntu-latest
+    steps:
+      - run: 'echo tests completed'
diff --git a/.github/workflows/rebase.yml b/.github/workflows/rebase.yml
new file mode 100644
index 000000000..b9e1712fc
--- /dev/null
+++ b/.github/workflows/rebase.yml
@@ -0,0 +1,9 @@
+name: Automatic Rebase
+
+on: [pull_request_target]
+
+jobs:
+  _:
+    uses: ljharb/actions/.github/workflows/rebase.yml@main
+    secrets:
+      token: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.github/workflows/require-allow-edits.yml b/.github/workflows/require-allow-edits.yml
new file mode 100644
index 000000000..eb3631b9e
--- /dev/null
+++ b/.github/workflows/require-allow-edits.yml
@@ -0,0 +1,17 @@
+name: Require “Allow Edits”
+
+on: [pull_request_target]
+
+permissions:
+  contents: read
+
+jobs:
+  _:
+    permissions:
+      pull-requests: read  # for ljharb/require-allow-edits to check 'allow edits' on PR
+    name: "Require “Allow Edits”"
+
+    runs-on: ubuntu-latest
+
+    steps:
+    - uses: ljharb/require-allow-edits@main
diff --git a/.gitignore b/.gitignore
index 0bf60608a..587dbd928 100644
--- a/.gitignore
+++ b/.gitignore
@@ -13,12 +13,22 @@ 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
+memo-parser/LICENSE
+resolvers/node/LICENSE
+resolvers/webpack/LICENSE
+utils/LICENSE
+memo-parser/.npmrc
+resolvers/node/.npmrc
+resolvers/webpack/.npmrc
+utils/.npmrc
+
 # Dependency directory
 # Commenting this out is preferred by some people, see
 # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git-
@@ -39,3 +49,6 @@ lib/
 yarn.lock
 package-lock.json
 npm-shrinkwrap.json
+
+# macOS
+.DS_Store
diff --git a/.markdownlint.json b/.markdownlint.json
new file mode 100644
index 000000000..f434832d2
--- /dev/null
+++ b/.markdownlint.json
@@ -0,0 +1,14 @@
+{
+  "line-length": false,
+  "ignore_case": true,
+  "no-duplicate-heading": {
+    "siblings_only": true
+  },
+  "ul-indent": {
+    "start_indent": 1,
+    "start_indented": true
+  },
+  "ul-style": {
+    "style": "dash"
+  }
+}
diff --git a/.markdownlintignore b/.markdownlintignore
new file mode 100644
index 000000000..6ed5b5b6e
--- /dev/null
+++ b/.markdownlintignore
@@ -0,0 +1,2 @@
+CHANGELOG.md
+node_modules
diff --git a/.npmrc b/.npmrc
index 43c97e719..6c93bcba7 100644
--- a/.npmrc
+++ b/.npmrc
@@ -1 +1,2 @@
 package-lock=false
+install-links=false
diff --git a/.nycrc b/.nycrc
new file mode 100644
index 000000000..c5396cb18
--- /dev/null
+++ b/.nycrc
@@ -0,0 +1,20 @@
+{
+	"all": true,
+	"check-coverage": false,
+	"reporter": ["text-summary", "lcov", "text", "html", "json"],
+	"require": [
+		"babel-register"
+	],
+	"sourceMap": true,
+	"instrument": false,
+	"exclude": [
+		"coverage",
+		"test",
+		"tests",
+		"resolvers/*/test",
+		"scripts",
+		"memo-parser",
+		"lib",
+		"examples"
+	]
+}
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index 606c35536..000000000
--- a/.travis.yml
+++ /dev/null
@@ -1,83 +0,0 @@
-language: node_js
-node_js:
-  - '12'
-  - '10'
-  - '8'
-  - '6'
-  - '4'
-
-os: linux
-
-env:
-  - ESLINT_VERSION=6
-  - ESLINT_VERSION=5
-  - ESLINT_VERSION=4
-  - ESLINT_VERSION=3
-  - ESLINT_VERSION=2
-
-# osx backlog is often deep, so to be polite we can just hit these highlights
-matrix:
-  include:
-  - env: PACKAGE=resolvers/node
-    node_js: 12
-  - env: PACKAGE=resolvers/node
-    node_js: 10
-  - env: PACKAGE=resolvers/node
-    node_js: 8
-  - env: PACKAGE=resolvers/node
-    node_js: 6
-  - env: PACKAGE=resolvers/node
-    node_js: 4
-  - env: PACKAGE=resolvers/webpack
-    node_js: 12
-  - env: PACKAGE=resolvers/webpack
-    node_js: 10
-  - env: PACKAGE=resolvers/webpack
-    node_js: 8
-  - env: PACKAGE=resolvers/webpack
-    node_js: 6
-  - env: PACKAGE=resolvers/webpack
-    node_js: 4
-
-  - os: osx
-    env: ESLINT_VERSION=5
-    node_js: 12
-  - os: osx
-    env: ESLINT_VERSION=5
-    node_js: 10
-  - os: osx
-    env: ESLINT_VERSION=4
-    node_js: 8
-  - os: osx
-    env: ESLINT_VERSION=3
-    node_js: 6
-  - os: osx
-    env: ESLINT_VERSION=2
-    node_js: 4
-
-  exclude:
-  - node_js: '4'
-    env: ESLINT_VERSION=5
-  - node_js: '4'
-    env: ESLINT_VERSION=6
-  - node_js: '6'
-    env: ESLINT_VERSION=6
-
-  fast_finish: true
-  allow_failures:
-  # issues with typescript deps in this version intersection
-  - node_js: '4'
-    env: ESLINT_VERSION=4
-
-before_install:
-  - 'nvm install-latest-npm'
-  - 'if [ -n "${PACKAGE-}" ]; then cd "${PACKAGE}"; fi'
-install:
-  - npm install
-  - 'if [ -n "${ESLINT_VERSION}" ]; then ./tests/dep-time-travel.sh; fi'
-
-script:
-  - 'npm test'
-
-after_success:
-  - npm run coveralls
diff --git a/CHANGELOG.md b/CHANGELOG.md
index b2f76b79f..74cd1c103 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,23 +1,558 @@
 # 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]
 
+### Added
+- add [`enforce-node-protocol-usage`] rule and `import/node-version` setting ([#3024], thanks [@GoldStrikeArch] and [@sevenc-nanashi])
+- add TypeScript types ([#3097], thanks [@G-Rath])
+- [`extensions`]: add `pathGroupOverrides to allow enforcement decision overrides based on specifier ([#3105], thanks [@Xunnamius])
+- [`order`]: add `sortTypesGroup` option to allow intragroup sorting of type-only imports ([#3104], thanks [@Xunnamius])
+- [`order`]: add `newlines-between-types` option to control intragroup sorting of type-only imports ([#3127], thanks [@Xunnamius])
+- [`order`]: add `consolidateIslands` option to collapse excess spacing for aesthetically pleasing imports ([#3129], thanks [@Xunnamius])
+
+### Fixed
+- [`no-unused-modules`]: provide more meaningful error message when no .eslintrc is present ([#3116], thanks [@michaelfaith])
+- configs: added missing name attribute for eslint config inspector ([#3151], thanks [@NishargShah])
+- [`order`]: ensure arcane imports do not cause undefined behavior ([#3128], thanks [@Xunnamius])
+- [`order`]: resolve undefined property access issue when using `named` ordering ([#3166], thanks [@Xunnamius])
+
+### Changed
+- [Docs] [`extensions`], [`order`]: improve documentation ([#3106], thanks [@Xunnamius])
+- [Docs] add flat config guide for using `tseslint.config()` ([#3125], thanks [@lnuvy])
+- [Docs] add missing comma ([#3122], thanks [@RyanGst])
+- [readme] Update flatConfig example to include typescript config ([#3138], thanks [@intellix])
+- [Refactor] [`order`]: remove unnecessary negative check ([#3167], thanks [@JounQin])
+
+## [2.31.0] - 2024-10-03
+
+### Added
+- support eslint v9 ([#2996], thanks [@G-Rath] [@michaelfaith])
+- [`order`]: allow validating named imports ([#3043], thanks [@manuth])
+- [`extensions`]: add the `checkTypeImports` option ([#2817], thanks [@phryneas])
+
+### Fixed
+- `ExportMap` / flat config: include `languageOptions` in context ([#3052], thanks [@michaelfaith])
+- [`no-named-as-default`]: Allow using an identifier if the export is both a named and a default export ([#3032], thanks [@akwodkiewicz])
+- [`export`]: False positive for exported overloaded functions in TS ([#3065], thanks [@liuxingbaoyu])
+- `exportMap`: export map cache is tainted by unreliable parse results ([#3062], thanks [@michaelfaith])
+- `exportMap`: improve cacheKey when using flat config ([#3072], thanks [@michaelfaith])
+- adjust "is source type module" checks for flat config ([#2996], thanks [@G-Rath])
+
+### Changed
+- [Docs] [`no-relative-packages`]: fix typo ([#3066], thanks [@joshuaobrien])
+- [Performance] [`no-cycle`]: dont scc for each linted file ([#3068], thanks [@soryy708])
+- [Docs] [`no-cycle`]: add `disableScc` to docs ([#3070], thanks [@soryy708])
+- [Tests] use re-exported `RuleTester` ([#3071], thanks [@G-Rath])
+- [Docs] [`no-restricted-paths`]: fix grammar ([#3073], thanks [@unbeauvoyage])
+- [Tests] [`no-default-export`], [`no-named-export`]:  add test case (thanks [@G-Rath])
+
+## [2.30.0] - 2024-09-02
+
+### Added
+- [`dynamic-import-chunkname`]: add `allowEmpty` option to allow empty leading comments ([#2942], thanks [@JiangWeixian])
+- [`dynamic-import-chunkname`]: Allow empty chunk name when webpackMode: 'eager' is set; add suggestions to remove name in eager mode ([#3004], thanks [@amsardesai])
+- [`no-unused-modules`]: Add `ignoreUnusedTypeExports` option ([#3011], thanks [@silverwind])
+- add support for Flat Config ([#3018], thanks [@michaelfaith])
+
+### Fixed
+- [`no-extraneous-dependencies`]: allow wrong path ([#3012], thanks [@chabb])
+- [`no-cycle`]: use scc algorithm to optimize ([#2998], thanks [@soryy708])
+- [`no-duplicates`]: Removing duplicates breaks in TypeScript ([#3033], thanks [@yesl-kim])
+- [`newline-after-import`]: fix considerComments option when require ([#2952], thanks [@developer-bandi])
+- [`order`]: do not compare first path segment for relative paths ([#2682]) ([#2885], thanks [@mihkeleidast])
+
+### Changed
+- [Docs] [`no-extraneous-dependencies`]: Make glob pattern description more explicit ([#2944], thanks [@mulztob])
+- [`no-unused-modules`]: add console message to help debug [#2866]
+- [Refactor] `ExportMap`: make procedures static instead of monkeypatching exportmap ([#2982], thanks [@soryy708])
+- [Refactor] `ExportMap`: separate ExportMap instance from its builder logic ([#2985], thanks [@soryy708])
+- [Docs] [`order`]: Add a quick note on how unbound imports and --fix ([#2640], thanks [@minervabot])
+- [Tests] appveyor -> GHA (run tests on Windows in both pwsh and WSL + Ubuntu) ([#2987], thanks [@joeyguerra])
+- [actions] migrate OSX tests to GHA ([ljharb#37], thanks [@aks-])
+- [Refactor] `exportMapBuilder`: avoid hoisting ([#2989], thanks [@soryy708])
+- [Refactor] `ExportMap`: extract "builder" logic to separate files ([#2991], thanks [@soryy708])
+- [Docs] [`order`]: update the description of the `pathGroupsExcludedImportTypes` option ([#3036], thanks [@liby])
+- [readme] Clarify how to install the plugin ([#2993], thanks [@jwbth])
+
+## [2.29.1] - 2023-12-14
+
+### Fixed
+- [`no-extraneous-dependencies`]: ignore `export type { ... } from '...'` when `includeTypes` is `false` ([#2919], thanks [@Pandemic1617])
+- [`no-unused-modules`]: support export patterns with array destructuring ([#2930], thanks [@ljharb])
+- [Deps] update `tsconfig-paths` ([#2447], thanks [@domdomegg])
+
+## [2.29.0] - 2023-10-22
+
+### Added
+- TypeScript config: add .cts and .mts extensions ([#2851], thanks [@Zamiell])
+- [`newline-after-import`]: new option `exactCount` and docs update ([#1933], thanks [@anikethsaha] and [@reosarevok])
+- [`newline-after-import`]: fix `exactCount` with `considerComments` false positive, when there is a leading comment ([#2884], thanks [@kinland])
+
+## [2.28.1] - 2023-08-18
+
+### Fixed
+- [`order`]: revert breaking change to single nested group ([#2854], thanks [@yndajas])
+
+### Changed
+- [Docs] remove duplicate fixable notices in docs ([#2850], thanks [@bmish])
+
+## [2.28.0] - 2023-07-27
+
+### Fixed
+- [`no-duplicates`]: remove duplicate identifiers in duplicate imports ([#2577], thanks [@joe-matsec])
+- [`consistent-type-specifier-style`]: fix accidental removal of comma in certain cases ([#2754], thanks [@bradzacher])
+- [Perf] `ExportMap`: Improve `ExportMap.for` performance on larger codebases ([#2756], thanks [@leipert])
+- [`no-extraneous-dependencies`]/TypeScript: do not error when importing inline type from dev dependencies ([#2735], thanks [@andyogo])
+- [`newline-after-import`]/TypeScript: do not error when re-exporting a namespaced import ([#2832], thanks [@laurens-dg])
+- [`order`]: partial fix for [#2687] (thanks [@ljharb])
+- [`no-duplicates`]: Detect across type and regular imports ([#2835], thanks [@benkrejci])
+- [`extensions`]: handle `.` and `..` properly ([#2778], thanks [@benasher44])
+- [`no-unused-modules`]: improve schema (thanks [@ljharb])
+- [`no-unused-modules`]: report error on binding instead of parent export ([#2842], thanks [@Chamion])
+
+### Changed
+- [Docs] [`no-duplicates`]: fix example schema ([#2684], thanks [@simmo])
+- [Docs] [`group-exports`]: fix syntax highlighting ([#2699], thanks [@devinrhode2])
+- [Docs] [`extensions`]: reference node ESM behavior ([#2748], thanks [@xM8WVqaG])
+- [Refactor] [`exports-last`]: use `array.prototype.findlastindex` (thanks [@ljharb])
+- [Refactor] [`no-anonymous-default-export`]: use `object.fromentries` (thanks [@ljharb])
+- [Refactor] [`no-unused-modules`]: use `array.prototype.flatmap` (thanks [@ljharb])
+
+## [2.27.5] - 2023-01-16
+
+### Fixed
+- [`order]`: Fix group ranks order when alphabetizing ([#2674], thanks [@Pearce-Ropion])
+
+## [2.27.4] - 2023-01-11
+
+### Fixed
+- `semver` should be a prod dep ([#2668])
+
+## [2.27.3] - 2023-01-11
+
+### Fixed
+- [`no-empty-named-blocks`]: rewrite rule to only check import declarations ([#2666])
+
+## [2.27.2] - 2023-01-11
+
+### Fixed
+- [`no-duplicates`]: do not unconditionally require `typescript` ([#2665])
+
+## [2.27.1] - 2023-01-11
+
+### Fixed
+- `array.prototype.flatmap` should be a prod dep ([#2664], thanks [@cristobal])
+
+## [2.27.0] - 2023-01-11
+
+### Added
+- [`newline-after-import`]: add `considerComments` option ([#2399], thanks [@pri1311])
+- [`no-cycle`]: add `allowUnsafeDynamicCyclicDependency` option ([#2387], thanks [@GerkinDev])
+- [`no-restricted-paths`]: support arrays for `from` and `target` options ([#2466], thanks [@AdriAt360])
+- [`no-anonymous-default-export`]: add `allowNew` option ([#2505], thanks [@DamienCassou])
+- [`order`]: Add `distinctGroup` option ([#2395], thanks [@hyperupcall])
+- [`no-extraneous-dependencies`]: Add `includeInternal` option ([#2541], thanks [@bdwain])
+- [`no-extraneous-dependencies`]: Add `includeTypes` option ([#2543], thanks [@bdwain])
+- [`order`]: new `alphabetize.orderImportKind` option to sort imports with same path based on their kind (`type`, `typeof`) ([#2544], thanks [@stropho])
+- [`consistent-type-specifier-style`]: add rule ([#2473], thanks [@bradzacher])
+- Add [`no-empty-named-blocks`] rule ([#2568], thanks [@guilhermelimak])
+- [`prefer-default-export`]: add "target" option ([#2602], thanks [@azyzz228])
+- [`no-absolute-path`]: add fixer ([#2613], thanks [@adipascu])
+- [`no-duplicates`]: support inline type import with `inlineTypeImport` option ([#2475], thanks [@snewcomer])
+
+### Fixed
+- [`order`]: move nested imports closer to main import entry ([#2396], thanks [@pri1311])
+- [`no-restricted-paths`]: fix an error message ([#2466], thanks [@AdriAt360])
+- [`no-restricted-paths`]: use `Minimatch.match` instead of `minimatch` to comply with Windows Native paths ([#2466], thanks [@AdriAt360])
+- [`order`]: require with member expression could not be fixed if alphabetize.order was used ([#2490], thanks [@msvab])
+- [`order`]: leave more space in rankings for consecutive path groups ([#2506], thanks [@Pearce-Ropion])
+- [`no-cycle`]: add ExportNamedDeclaration statements to dependencies ([#2511], thanks [@BenoitZugmeyer])
+- [`dynamic-import-chunkname`]: prevent false report on a valid webpack magic comment ([#2330], thanks [@mhmadhamster])
+- [`export`]: do not error on TS export overloads ([#1590], thanks [@ljharb])
+- [`no-unresolved`], [`extensions`]: ignore type only exports ([#2436], thanks [@Lukas-Kullmann])
+- `ExportMap`: add missing param to function ([#2589], thanks [@Fdawgs])
+- [`no-unused-modules`]: `checkPkgFieldObject` filters boolean fields from checks ([#2598], thanks [@mpint])
+- [`no-cycle`]: accept Flow `typeof` imports, just like `type` ([#2608], thanks [@gnprice])
+- [`no-import-module-exports`]: avoid a false positive for import variables ([#2315], thanks [@BarryThePenguin])
+
+### Changed
+- [Tests] [`named`]: Run all TypeScript test ([#2427], thanks [@ProdigySim])
+- [readme] note use of typescript in readme `import/extensions` section ([#2440], thanks [@OutdatedVersion])
+- [Docs] [`order`]: use correct default value ([#2392], thanks [@hyperupcall])
+- [meta] replace git.io link in comments with the original URL ([#2444], thanks [@liby])
+- [Docs] remove global install in readme ([#2412], thanks [@aladdin-add])
+- [readme] clarify `eslint-import-resolver-typescript` usage ([#2503], thanks [@JounQin])
+- [Refactor] [`no-cycle`]: Add per-run caching of traversed paths ([#2419], thanks [@nokel81])
+- [Performance] `ExportMap`: add caching after parsing for an ambiguous module ([#2531], thanks [@stenin-nikita])
+- [Docs] [`no-useless-path-segments`]: fix paths ([#2424], thanks [@s-h-a-d-o-w])
+- [Tests] [`no-cycle`]: add passing test cases ([#2438], thanks [@georeith])
+- [Refactor] [`no-extraneous-dependencies`] improve performance using cache ([#2374], thanks [@meowtec])
+- [meta] `CONTRIBUTING.md`: mention inactive PRs ([#2546], thanks [@stropho])
+- [readme] make json for setting groups multiline ([#2570], thanks [@bertyhell])
+- [Tests] [`no-restricted-paths`]: Tests for `import type` statements ([#2459], thanks [@golergka])
+- [Tests] [`no-restricted-paths`]: fix one failing `import type` test case, submitted by [@golergka], thanks [@azyzz228]
+- [Docs] automate docs with eslint-doc-generator ([#2582], thanks [@bmish])
+- [readme] Increase clarity around typescript configuration ([#2588], thanks [@Nfinished])
+- [Docs] update `eslint-doc-generator` to v1.0.0 ([#2605], thanks [@bmish])
+- [Perf] [`no-cycle`], [`no-internal-modules`], [`no-restricted-paths`]: use `anyOf` instead of `oneOf` (thanks [@ljharb], [@remcohaszing])
+
+## [2.26.0] - 2022-04-05
+
+### Added
+- [`no-named-default`], [`no-default-export`], [`prefer-default-export`], [`no-named-export`], [`export`], [`named`], [`namespace`], [`no-unused-modules`]: support arbitrary module namespace names ([#2358], thanks [@sosukesuzuki])
+- [`no-dynamic-require`]: support dynamic import with espree ([#2371], thanks [@sosukesuzuki])
+- [`no-relative-packages`]: add fixer ([#2381], thanks [@forivall])
+
+### Fixed
+- [`default`]: `typescript-eslint-parser`: avoid a crash on exporting as namespace (thanks [@ljharb])
+- [`export`]/TypeScript: false positive for typescript namespace merging ([#1964], thanks [@magarcia])
+- [`no-duplicates`]: ignore duplicate modules in different TypeScript module declarations ([#2378], thanks [@remcohaszing])
+- [`no-unused-modules`]: avoid a crash when processing re-exports ([#2388], thanks [@ljharb])
+
+### Changed
+- [Tests] [`no-nodejs-modules`]: add tests for node protocol URL ([#2367], thanks [@sosukesuzuki])
+- [Tests] [`default`], [`no-anonymous-default-export`], [`no-mutable-exports`], [`no-named-as-default-member`], [`no-named-as-default`]: add tests for arbitrary module namespace names ([#2358], thanks [@sosukesuzuki])
+- [Docs] [`no-unresolved`]: Fix RegExp escaping in readme ([#2332], thanks [@stephtr])
+- [Refactor] [`namespace`]: try to improve performance ([#2340], thanks [@ljharb])
+- [Docs] make rule doc titles consistent ([#2393], thanks [@TheJaredWilcurt])
+- [Docs] [`order`]: TS code examples should use TS code blocks ([#2411], thanks [@MM25Zamanian])
+- [Docs] [`no-unresolved`]: fix link ([#2417], thanks [@kylemh])
+
+## [2.25.4] - 2022-01-02
+
+### Fixed
+- `importType`: avoid crashing on a non-string' ([#2305], thanks [@ljharb])
+- [`first`]: prevent crash when parsing angular templates ([#2210], thanks [@ljharb])
+- `importType`: properly resolve `@/*`-aliased imports as internal ([#2334], thanks [@ombene])
+- [`named`]/`ExportMap`: handle named imports from CJS modules that use dynamic import ([#2341], thanks [@ludofischer])
+
+### Changed
+- [`no-default-import`]: report on the token "default" instead of the entire node ([#2299], thanks [@pmcelhaney])
+- [Docs] [`order`]: Remove duplicate mention of default ([#2280], thanks [@johnthagen])
+- [Deps] update `eslint-module-utils`
+
+## [2.25.3] - 2021-11-09
+
+### Fixed
+- [`extensions`]: ignore unresolveable type-only imports ([#2270], [#2271], thanks [@jablko])
+- `importType`: fix `isExternalModule` calculation ([#2282], thanks [@mx-bernhard])
+- [`no-import-module-exports`]: avoid false positives with a shadowed `module` or `exports` ([#2297], thanks [@ljharb])
+
+### Changed
+- [Docs] [`order`]: add type to the default groups ([#2272], thanks [@charpeni])
+- [readme] Add note to TypeScript docs to install appropriate resolver ([#2279], thanks [@johnthagen])
+- [Refactor] `importType`: combine redundant `isScoped` and `isScopedModule` (thanks [@ljharb])
+- [Docs] HTTP => HTTPS ([#2287], thanks [@Schweinepriester])
+
+## [2.25.2] - 2021-10-12
+
+### Fixed
+- [Deps] update `eslint-module-utils` for real this time ([#2255], thanks [@ljharb])
+
+## [2.25.1] - 2021-10-11
+
+### Fixed
+- [Deps] update `eslint-module-utils`
+
+## [2.25.0] - 2021-10-11
+
+### Added
+- Support `eslint` v8 ([#2191], thanks [@ota-meshi])
+- [`no-unresolved`]: add `caseSensitiveStrict` option ([#1262], thanks [@sergei-startsev])
+- [`no-unused-modules`]: add eslint v8 support ([#2194], thanks [@coderaiser])
+- [`no-restricted-paths`]: add/restore glob pattern support ([#2219], thanks [@stropho])
+- [`no-unused-modules`]: support dynamic imports ([#1660], [#2212], thanks [@maxkomarychev], [@aladdin-add], [@Hypnosphi])
+
+### Fixed
+- [`no-unresolved`]: ignore type-only imports ([#2220], thanks [@jablko])
+- [`order`]: fix sorting imports inside TypeScript module declarations ([#2226], thanks [@remcohaszing])
+- [`default`], `ExportMap`: Resolve extended TypeScript configuration files ([#2240], thanks [@mrmckeb])
+
+### Changed
+- [Refactor] switch to an internal replacement for `pkg-up` and `read-pkg-up` ([#2047], thanks [@mgwalker])
+- [patch] TypeScript config: remove `.d.ts` from [`import/parsers` setting] and [`import/extensions` setting] ([#2220], thanks [@jablko])
+- [Refactor] [`no-unresolved`], [`no-extraneous-dependencies`]: moduleVisitor usage ([#2233], thanks [@jablko])
+
+## [2.24.2] - 2021-08-24
+
+### Fixed
+- [`named`], [`namespace`]: properly handle ExportAllDeclarations ([#2199], thanks [@ljharb])
+
+## [2.24.1] - 2021-08-19
+
+### Fixed
+- `ExportMap`: Add default export when esModuleInterop is true and anything is exported ([#2184], thanks [@Maxim-Mazurok])
+- [`named`], [`namespace`]: properly set reexports on `export * as … from` ([#1998], [#2161], thanks [@ljharb])
+- [`no-duplicates`]: correctly handle case of mixed default/named type imports ([#2149], thanks [@GoodForOneFare], [@nwalters512])
+- [`no-duplicates`]: avoid crash with empty `import type {}` ([#2201], thanks [@ljharb])
+
+### Changed
+- [Docs] `max-dependencies`: 📖 Document `ignoreTypeImports` option ([#2196], thanks [@himynameisdave])
+
+## [2.24.0] - 2021-08-08
+
+### Added
+- [`no-dynamic-require`]: add option `esmodule` ([#1223], thanks [@vikr01])
+- [`named`]: add `commonjs` option ([#1222], thanks [@vikr01])
+- [`no-namespace`]: Add `ignore` option ([#2112], thanks [@aberezkin])
+- [`max-dependencies`]: add option `ignoreTypeImports` ([#1847], thanks [@rfermann])
+
+### Fixed
+- [`no-duplicates`]: ensure autofix avoids excessive newlines ([#2028], thanks [@ertrzyiks])
+- [`extensions`]: avoid crashing on partially typed import/export statements ([#2118], thanks [@ljharb])
+- [`no-extraneous-dependencies`]: add ESM intermediate package.json support ([#2121], thanks [@paztis])
+- Use `context.getPhysicalFilename()` when available (ESLint 7.28+) ([#2160], thanks [@pmcelhaney])
+- [`extensions`]/`importType`: fix isScoped treating @/abc as scoped module ([#2146], thanks [@rperello])
+
+### Changed
+- [Docs] [`extensions`]: improved cases for using `@/...` ([#2140], thanks [@wenfangdu])
+- [Docs] [`extensions`]: removed incorrect cases ([#2138], thanks [@wenfangdu])
+- [Tests] [`order`]: add tests for `pathGroupsExcludedImportTypes: ['type']` ([#2158], thanks [@atav32])
+- [Docs] [`order`]:  improve the documentation for the `pathGroupsExcludedImportTypes` option ([#2156], thanks [@liby])
+- [Tests] [`no-cycle`]: Restructure test files ([#1517], thanks [@soryy708])
+- [Docs] add description how to use plugin with yarn berry ([#2179], thanks [@KostyaZgara])
+
+## [2.23.4] - 2021-05-29
+
+### Fixed
+- [`no-import-module-exports`]: Don't crash if packages have no entrypoint ([#2099], thanks [@eps1lon])
+- [`no-extraneous-dependencies`]: fix package name algorithm ([#2097], thanks [@paztis])
+
+## [2.23.3] - 2021-05-21
+
+### Fixed
+- [`no-restricted-paths`]: fix false positive matches ([#2090], thanks [@malykhinvi])
+- [`no-cycle`]: ignore imports where imported file only imports types of importing file ([#2083], thanks [@cherryblossom000])
+- [`no-cycle`]: fix false negative when file imports a type after importing a value in Flow ([#2083], thanks [@cherryblossom000])
+- [`order`]: restore default behavior unless `type` is in groups ([#2087], thanks [@grit96])
+
+### Changed
+- [Docs] Add [`no-relative-packages`] to list of to the list of rules ([#2075], thanks [@arvigeus])
+
+## [2.23.2] - 2021-05-15
+
+### Changed
+- [meta] add `safe-publish-latest`; use `prepublishOnly` script for npm 7+
+
+## [2.23.1] - 2021-05-14
+
+### Fixed
+- [`newline-after-import`]: fix crash with `export {}` syntax ([#2063], [#2056], thanks [@ljharb])
+- `ExportMap`: do not crash when tsconfig lacks `.compilerOptions` ([#2067], thanks [@ljharb])
+- [`order`]: fix alphabetical sorting ([#2071], thanks [@grit96])
+
+## [2.23.0] - 2021-05-13
+
+### Added
+- [`no-commonjs`]: Also detect require calls with expressionless template literals: ``` require(`x`) ``` ([#1958], thanks [@FloEdelmann])
+- [`no-internal-modules`]: Add `forbid` option ([#1846], thanks [@guillaumewuip])
+- add [`no-relative-packages`] ([#1860], [#966], thanks [@tapayne88] [@panrafal])
+- add [`no-import-module-exports`] rule: report import declarations with CommonJS exports ([#804], thanks [@kentcdodds] and [@ttmarek])
+- [`no-unused-modules`]: Support destructuring assignment for `export`. ([#1997], thanks [@s-h-a-d-o-w])
+- [`order`]: support type imports ([#2021], thanks [@grit96])
+- [`order`]: Add `warnOnUnassignedImports` option to enable warnings for out of order unassigned imports ([#1990], thanks [@hayes])
+
+### Fixed
+- [`export`]/TypeScript: properly detect export specifiers as children of a TS module block ([#1889], thanks [@andreubotella])
+- [`order`]: ignore non-module-level requires ([#1940], thanks [@golopot])
+- [`no-webpack-loader-syntax`]/TypeScript: avoid crash on missing name ([#1947], thanks [@leonardodino])
+- [`no-extraneous-dependencies`]: Add package.json cache ([#1948], thanks [@fa93hws])
+- [`prefer-default-export`]: handle empty array destructuring ([#1965], thanks [@ljharb])
+- [`no-unused-modules`]: make type imports mark a module as used (fixes [#1924]) ([#1974], thanks [@cherryblossom000])
+- [`no-cycle`]: fix perf regression ([#1944], thanks [@Blasz])
+- [`first`]: fix handling of `import = require` ([#1963], thanks [@MatthiasKunnen])
+- [`no-cycle`]/[`extensions`]: fix isExternalModule usage ([#1696], thanks [@paztis])
+- [`extensions`]/[`no-cycle`]/[`no-extraneous-dependencies`]: Correct module real path resolution ([#1696], thanks [@paztis])
+- [`no-named-default`]: ignore Flow import type and typeof ([#1983], thanks [@christianvuerings])
+- [`no-extraneous-dependencies`]: Exclude flow `typeof` imports ([#1534], thanks [@devongovett])
+- [`newline-after-import`]: respect decorator annotations ([#1985], thanks [@lilling])
+- [`no-restricted-paths`]: enhance performance for zones with `except` paths ([#2022], thanks [@malykhinvi])
+- [`no-unresolved`]: check import() ([#2026], thanks [@aladdin-add])
+
+### Changed
+- [Generic Import Callback] Make callback for all imports once in rules ([#1237], thanks [@ljqx])
+- [Docs] [`no-named-as-default`]: add semicolon ([#1897], thanks [@bicstone])
+- [Docs] [`no-extraneous-dependencies`]: correct peerDependencies option default to `true` ([#1993], thanks [@dwardu])
+- [Docs] [`order`]: Document options required to match ordering example ([#1992], thanks [@silviogutierrez])
+- [Tests] [`no-unresolved`]: add tests for `import()` ([#2012], thanks [@davidbonnet])
+- [Docs] Add import/recommended ruleset to README ([#2034], thanks [@edemaine])
+
+## [2.22.1] - 2020-09-27
+
+### Fixed
+- [`default`]/TypeScript: avoid crash on `export =` with a MemberExpression ([#1841], thanks [@ljharb])
+- [`extensions`]/importType: Fix @/abc being treated as scoped module ([#1854], thanks [@3nuc])
+- allow using rest operator in named export ([#1878], thanks [@foray1010])
+- [`dynamic-import-chunkname`]: allow single quotes to match Webpack support ([#1848], thanks [@straub])
+
+### Changed
+- [`export`]: add tests for a name collision with `export * from` ([#1704], thanks [@tomprats])
+
+## [2.22.0] - 2020-06-26
+
+### Added
+- [`no-unused-modules`]: consider exported TypeScript interfaces, types and enums ([#1819], thanks [@nicolashenry])
+- [`no-cycle`]: allow `maxDepth` option to be `"∞"` (thanks [@ljharb])
+
+### Fixed
+- [`order`]/TypeScript: properly support `import = object` expressions ([#1823], thanks [@manuth])
+- [`no-extraneous-dependencies`]/TypeScript: do not error when importing type from dev dependencies ([#1820], thanks [@fernandopasik])
+- [`default`]: avoid crash with `export =` ([#1822], thanks [@AndrewLeedham])
+- [`order`]/[`newline-after-import`]: ignore TypeScript's "export import object" ([#1830], thanks [@be5invis])
+- [`dynamic-import-chunkname`]/TypeScript: supports `@typescript-eslint/parser` ([#1833], thanks [@noelebrun])
+- [`order`]/TypeScript: ignore ordering of object imports ([#1831], thanks [@manuth])
+- [`namespace`]: do not report on shadowed import names ([#518], thanks [@ljharb])
+- [`export`]: avoid warning on `export * as` non-conflicts ([#1834], thanks [@ljharb])
+
+### Changed
+- [`no-extraneous-dependencies`]: add tests for importing types ([#1824], thanks [@taye])
+- [docs] [`no-default-export`]: Fix docs url ([#1836], thanks [@beatrizrezener])
+- [docs] [`imports-first`]: deprecation info and link to `first` docs ([#1835], thanks [@beatrizrezener])
+
+## [2.21.2] - 2020-06-09
+
+### Fixed
+- [`order`]: avoid a crash on TypeScript’s `export import` syntax ([#1808], thanks [@ljharb])
+- [`newline-after-import`]: consider TypeScript `import =` syntax' ([#1811], thanks [@ljharb])
+- [`no-internal-modules`]: avoid a crash on a named export declaration ([#1814], thanks [@ljharb])
+
+## [2.21.1] - 2020-06-07
+
+### Fixed
+- TypeScript: [`import/named`]: avoid requiring `typescript` when not using TS ([#1805], thanks [@ljharb])
+
+## [2.21.0] - 2020-06-07
+
+### Added
+- [`import/default`]: support default export in TSExportAssignment ([#1528], thanks [@joaovieira])
+- [`no-cycle`]: add `ignoreExternal` option ([#1681], thanks [@sveyret])
+- [`order`]: Add support for TypeScript's "import equals"-expressions ([#1785], thanks [@manuth])
+- [`import/default`]: support default export in TSExportAssignment ([#1689], thanks [@Maxim-Mazurok])
+- [`no-restricted-paths`]: add custom message support ([#1802], thanks [@malykhinvi])
+
+### Fixed
+- [`group-exports`]: Flow type export awareness ([#1702], thanks [@ernestostifano])
+- [`order`]: Recognize pathGroup config for first group ([#1719], [#1724], thanks [@forivall], [@xpl])
+- [`no-unused-modules`]: Fix re-export not counting as usage when used in combination with import ([#1722], thanks [@Ephem])
+- [`no-duplicates`]: Handle TS import type ([#1676], thanks [@kmui2])
+- [`newline-after-import`]: recognize decorators ([#1139], thanks [@atos1990])
+- [`no-unused-modules`]: Revert "[flow] [`no-unused-modules`]: add flow type support" ([#1770], thanks [@Hypnosphi])
+- TypeScript: Add nested namespace handling ([#1763], thanks [@julien1619])
+- [`namespace`]/`ExportMap`: Fix interface declarations for TypeScript ([#1764], thanks [@julien1619])
+- [`no-unused-modules`]: avoid order-dependence ([#1744], thanks [@darkartur])
+- [`no-internal-modules`]: also check `export from` syntax ([#1691], thanks [@adjerbetian])
+- TypeScript: [`export`]: avoid a crash with `export =` ([#1801], thanks [@ljharb])
+
+### Changed
+- [Refactor] [`no-extraneous-dependencies`]: use moduleVisitor ([#1735], thanks [@adamborowski])
+- TypeScript config: Disable [`named`][] ([#1726], thanks [@astorije])
+- [readme] Remove duplicate [`no-unused-modules`] from docs ([#1690], thanks [@arvigeus])
+- [Docs] [`order`]: fix bad inline config ([#1788], thanks [@nickofthyme])
+- [Tests] Add fix for Windows Subsystem for Linux ([#1786], thanks [@manuth])
+- [Docs] [`no-unused-rules`]: Fix docs for unused exports ([#1776], thanks [@barbogast])
+- [eslint] bump minimum v7 version to v7.2.0
+
+## [2.20.2] - 2020-03-28
+
+### Fixed
+- [`order`]: fix `isExternalModule` detect on windows ([#1651], thanks [@fisker])
+- [`order`]: recognize ".." as a "parent" path ([#1658], thanks [@golopot])
+- [`no-duplicates`]: fix fixer on cases with default import ([#1666], thanks [@golopot])
+- [`no-unused-modules`]: Handle `export { default } from` syntax ([#1631], thanks [@richardxia])
+- [`first`]: Add a way to disable `absolute-first` explicitly ([#1664], thanks [@TheCrueltySage])
+- [Docs] [`no-webpack-loader-syntax`]: Updates webpack URLs ([#1751], thanks [@MikeyBeLike])
+
+## [2.20.1] - 2020-02-01
+
+### Fixed
+- [`export`]: Handle function overloading in `*.d.ts` ([#1619], thanks [@IvanGoncharov])
+- [`no-absolute-path`]: fix a crash with invalid import syntax ([#1616], thanks [@ljharb])
+- [`import/external-module-folders` setting] now correctly works with directories containing modules symlinked from `node_modules` ([#1605], thanks [@skozin])
+- [`extensions`]: for invalid code where `name` does not exist, do not crash ([#1613], thanks [@ljharb])
+- [`extensions`]: Fix scope regex ([#1611], thanks [@yordis])
+- [`no-duplicates`]: allow duplicate imports if one is a namespace and the other not ([#1612], thanks [@sveyret])
+- Add some missing rule meta schemas and types ([#1620], thanks [@bmish])
+- [`named`]: for importing from a module which re-exports named exports from a `node_modules` module ([#1569], [#1447], thanks [@redbugz], [@kentcdodds])
+- [`order`]: Fix alphabetize for mixed requires and imports ([#1626], thanks [@wschurman])
+
+### Changed
+- [`import/external-module-folders` setting] behavior is more strict now: it will only match complete path segments ([#1605], thanks [@skozin])
+- [meta] fix "files" field to include/exclude the proper files ([#1635], thanks [@ljharb])
+- [Tests] [`order`]: Add TS import type tests ([#1736], thanks [@kmui2])
+
+## [2.20.0] - 2020-01-10
+
+### Added
+- [`order`]: added `caseInsensitive` as an additional option to `alphabetize` ([#1586], thanks [@dbrewer5])
+- [`no-restricted-paths`]: New `except` option per `zone`, allowing exceptions to be defined for a restricted zone ([#1238], thanks [@rsolomon])
+- [`order`]: add option pathGroupsExcludedImportTypes to allow ordering of external import types ([#1565], thanks [@Mairu])
+
+### Fixed
+- [`no-unused-modules`]: fix usage of [`import/extensions` setting] ([#1560], thanks [@stekycz])
+- [`extensions`]: ignore non-main modules ([#1563], thanks [@saschanaz])
+- TypeScript config: lookup for external modules in @types folder ([#1526], thanks [@joaovieira])
+- [`no-extraneous-dependencies`]: ensure `node.source` is truthy ([#1589], thanks [@ljharb])
+- [`extensions`]: Ignore query strings when checking for extensions ([#1572], thanks [@pcorpet])
+
+### Docs
+- [`extensions`]: improve `ignorePackages` docs ([#1248], thanks [@ivo-stefchev])
+
+## [2.19.1] - 2019-12-08
+
+### Fixed
+- [`no-extraneous-dependencies`]: ensure `node.source` exists
+
+## [2.19.0] - 2019-12-08
+
+### Added
+- [`internal-regex` setting]: regex pattern for marking packages "internal"  ([#1491], thanks [@Librazy])
+- [`group-exports`]: make aggregate module exports valid ([#1472], thanks [@atikenny])
+- [`no-namespace`]: Make rule fixable ([#1401], thanks [@TrevorBurnham])
+- support `parseForESLint` from custom parser ([#1435], thanks [@JounQin])
+- [`no-extraneous-dependencies`]: Implement support for [bundledDependencies](https://npm.github.io/using-pkgs-docs/package-json/types/bundleddependencies.html) ([#1436], thanks [@schmidsi]))
+- [`no-unused-modules`]: add flow type support ([#1542], thanks [@rfermann])
+- [`order`]: Adds support for pathGroups to allow ordering by defined patterns ([#795], [#1386], thanks [@Mairu])
+- [`no-duplicates`]: Add `considerQueryString` option : allow duplicate imports with different query strings ([#1107], thanks [@pcorpet]).
+- [`order`]: Add support for alphabetical sorting of import paths within import groups ([#1360], [#1105], [#629], thanks [@duncanbeevers], [@stropho], [@luczsoma], [@randallreedjr])
+- [`no-commonjs`]: add `allowConditionalRequire` option ([#1439], thanks [@Pessimistress])
+
+### Fixed
+- [`default`]: make error message less confusing ([#1470], thanks [@golopot])
+- Improve performance of `ExportMap.for` by only loading paths when necessary. ([#1519], thanks [@brendo])
+- Support export of a merged TypeScript namespace declaration ([#1495], thanks [@benmunro])
+- [`order`]: fix autofix to not move imports across fn calls ([#1253], thanks [@tihonove])
+- [`prefer-default-export`]: fix false positive with type export ([#1506], thanks [@golopot])
+- [`extensions`]: Fix `ignorePackages` to produce errors ([#1521], thanks [@saschanaz])
+- [`no-unused-modules`]: fix crash due to `export *` ([#1496], thanks [@Taranys])
+- [`no-cycle`]: should not warn for Flow imports ([#1494], thanks [@maxmalov])
+- [`order`]: fix `@someModule` considered as `unknown` instead of `internal` ([#1493], thanks [@aamulumi])
+- [`no-extraneous-dependencies`]: Check `export from` ([#1049], thanks [@marcusdarmstrong])
+
+### Docs
+- [`no-useless-path-segments`]: add docs for option `commonjs` ([#1507], thanks [@golopot])
+
+### Changed
+- [`no-unused-modules`]/`eslint-module-utils`: Avoid superfluous calls and code ([#1551], thanks [@brettz9])
+
 ## [2.18.2] - 2019-07-19
- - Skip warning on type interfaces ([#1425], thanks [@lencioni])
+
+### Fixed
+- Skip warning on type interfaces ([#1425], thanks [@lencioni])
 
 ## [2.18.1] - 2019-07-18
 
 ### Fixed
- - Improve parse perf when using `@typescript-eslint/parser` ([#1409], thanks [@bradzacher])
- - [`prefer-default-export`]: don't warn on TypeAlias & TSTypeAliasDeclaration ([#1377], thanks [@sharmilajesupaul])
- - [`no-unused-modules`]: Exclude package "main"/"bin"/"browser" entry points ([#1404], thanks [@rfermann])
- - [`export`]: false positive for typescript overloads ([#1412], thanks [@golopot])
+- Improve parse perf when using `@typescript-eslint/parser` ([#1409], thanks [@bradzacher])
+- [`prefer-default-export`]: don't warn on TypeAlias & TSTypeAliasDeclaration ([#1377], thanks [@sharmilajesupaul])
+- [`no-unused-modules`]: Exclude package "main"/"bin"/"browser" entry points ([#1404], thanks [@rfermann])
+- [`export`]: false positive for TypeScript overloads ([#1412], thanks [@golopot])
 
 ### Refactors
- - [`no-extraneous-dependencies`], `importType`: remove lodash ([#1419], thanks [@ljharb])
+- [`no-extraneous-dependencies`], `importType`: remove lodash ([#1419], thanks [@ljharb])
 
 ## [2.18.0] - 2019-06-24
 
@@ -25,7 +560,7 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel
 - Support eslint v6 ([#1393], thanks [@sheepsteak])
 - [`order`]: Adds support for correctly sorting unknown types into a single group ([#1375], thanks [@swernerx])
 - [`order`]: add fixer for destructuring commonjs import ([#1372], thanks [@golopot])
-- typescript config: add TS def extensions + defer to TS over JS ([#1366], thanks [@benmosher])
+- TypeScript config: add TS def extensions + defer to TS over JS ([#1366], thanks [@benmosher])
 
 ### Fixed
 - [`no-unused-modules`]: handle ClassDeclaration ([#1371], thanks [@golopot])
@@ -43,12 +578,11 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel
 - [`no-unused-modules`]: make appveyor tests passing ([#1333], thanks [@rfermann])
 - [`named`]: ignore Flow `typeof` imports and `type` exports ([#1345], thanks [@loganfsmyth])
 - [refactor] fix eslint 6 compat by fixing imports (thank [@ljharb])
-- Improve support for Typescript declare structures ([#1356], thanks [@christophercurrie])
+- Improve support for TypeScript declare structures ([#1356], thanks [@christophercurrie])
 
 ### Docs
-- add missing `no-unused-modules` in README ([#1358], thanks [@golopot])
+- add missing [`no-unused-modules`] in README ([#1358], thanks [@golopot])
 - [`no-unused-modules`]: Indicates usage, plugin defaults to no-op, and add description to main README.md ([#1352], thanks [@johndevedu])
-[@christophercurrie]: https://github.com/christophercurrie
 - Document `env` option for `eslint-import-resolver-webpack` ([#1363], thanks [@kgregory])
 
 ## [2.17.2] - 2019-04-16
@@ -68,7 +602,7 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel
 - [`no-useless-path-segments`]: Add `noUselessIndex` option ([#1290], thanks [@timkraut])
 - [`no-duplicates`]: Add autofix ([#1312], thanks [@lydell])
 - Add [`no-unused-modules`] rule ([#1142], thanks [@rfermann])
-- support export type named exports from typescript ([#1304], thanks [@bradennapier] and [@schmod])
+- support export type named exports from TypeScript ([#1304], thanks [@bradennapier] and [@schmod])
 
 ### Fixed
 - [`order`]: Fix interpreting some external modules being interpreted as internal modules ([#793], [#794] thanks [@ephys])
@@ -76,20 +610,20 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel
 - aliased internal modules that look like core modules ([#1297], thanks [@echenley])
 - [`namespace`]: add check for null ExportMap ([#1235], [#1144], thanks [@ljqx])
 - [ExportMap] fix condition for checking if block comment ([#1234], [#1233], thanks [@ljqx])
-- Fix overwriting of dynamic import() CallExpression ([`no-cycle`], [`no-relative-parent-import`], [`no-unresolved`], [`no-useless-path-segments`]) ([#1218], [#1166], [#1035], thanks [@vikr01])
-- [`export`]: false positives for typescript type + value export ([#1319], thanks [@bradzacher])
-- [`export`]: Support typescript namespaces ([#1320], [#1300], thanks [@bradzacher])
+- Fix overwriting of dynamic import() CallExpression ([`no-cycle`], [`no-relative-parent-imports`], [`no-unresolved`], [`no-useless-path-segments`]) ([#1218], [#1166], [#1035], thanks [@vikr01])
+- [`export`]: false positives for TypeScript type + value export ([#1319], thanks [@bradzacher])
+- [`export`]: Support TypeScript namespaces ([#1320], [#1300], thanks [@bradzacher])
 
 ### Docs
-- Update readme for Typescript ([#1256], [#1277], thanks [@kirill-konshin])
+- Update readme for TypeScript ([#1256], [#1277], thanks [@kirill-konshin])
 - make rule names consistent ([#1112], thanks [@feychenie])
 
 ### Tests
 - fix broken tests on master ([#1295], thanks [@jeffshaver] and [@ljharb])
 - [`no-commonjs`]: add tests that show corner cases ([#1308], thanks [@TakeScoop])
 
-
 ## [2.16.0] - 2019-01-29
+
 ### Added
 - `typescript` config ([#1257], thanks [@kirill-konshin])
 
@@ -97,6 +631,7 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel
 - Memory leak of `SourceCode` objects for all parsed dependencies, resolved. (issue [#1266], thanks [@asapach] and [@sergei-startsev] for digging in)
 
 ## [2.15.0] - 2019-01-22
+
 ### Added
 - new rule: [`no-named-export`] ([#1157], thanks [@fsmaia])
 
@@ -105,13 +640,8 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel
 - [`dynamic-import-chunkname`]: Add proper webpack comment parsing ([#1163], thanks [@st-sloth])
 - [`named`]: fix destructuring assignment ([#1232], thanks [@ljqx])
 
-
 ## [2.14.0] - 2018-08-13
-*   69e0187 (HEAD -> master, source/master, origin/master, origin/HEAD) Merge pull request #1151 from jf248/jsx
-|\
-| * e30a757 (source/pr/1151, fork/jsx) Add JSX check to namespace rule
-|/
-* 8252344 (source/pr/1148) Add error to output when module loaded as resolver has invalid API
+
 ### Added
 - [`no-useless-path-segments`]: add commonJS (CJS) support ([#1128], thanks [@1pete])
 - [`namespace`]: add JSX check ([#1151], thanks [@jf248])
@@ -120,13 +650,14 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel
 - [`no-cycle`]: ignore Flow imports ([#1126], thanks [@gajus])
 - fix Flow type imports ([#1106], thanks [@syymza])
 - [`no-relative-parent-imports`]: resolve paths ([#1135], thanks [@chrislloyd])
-- [`import/order`]: fix autofixer when using typescript-eslint-parser ([#1137], thanks [@justinanastos])
+- [`order`]: fix autofixer when using typescript-eslint-parser ([#1137], thanks [@justinanastos])
 - repeat fix from [#797] for [#717], in another place (thanks [@ljharb])
 
 ### Refactors
 - add explicit support for RestElement alongside ExperimentalRestProperty (thanks [@ljharb])
 
 ## [2.13.0] - 2018-06-24
+
 ### Added
 - Add ESLint 5 support ([#1122], thanks [@ai] and [@ljharb])
 - Add [`no-relative-parent-imports`] rule: disallow relative imports from parent directories ([#1093], thanks [@chrislloyd])
@@ -135,12 +666,14 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel
 - `namespace` rule: ensure it works in eslint 5/ecmaVersion 2018 (thanks [@ljharb])
 
 ## [2.12.0] - 2018-05-17
+
 ### Added
 - Ignore type imports for [`named`] rule ([#931], thanks [@mattijsbliek])
 - Add documentation for [`no-useless-path-segments`] rule ([#1068], thanks [@manovotny])
 - `packageDir` option for [`no-extraneous-dependencies`] can be array-valued ([#1085], thanks [@hulkish])
 
 ## [2.11.0] - 2018-04-09
+
 ### Added
 - Fixer for [`first`] ([#1046], thanks [@fengkfengk])
 - `allow-require` option for [`no-commonjs`] rule ([#880], thanks [@futpib])
@@ -149,11 +682,13 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel
 - memory/CPU regression where ASTs were held in memory ([#1058], thanks [@klimashkin]/[@lukeapage])
 
 ## [2.10.0] - 2018-03-29
+
 ### Added
 - Autofixer for [`order`] rule ([#908], thanks [@tihonove])
 - Add [`no-cycle`] rule: reports import cycles.
 
 ## [2.9.0] - 2018-02-21
+
 ### Added
 - Add [`group-exports`] rule: style-guide rule to report use of multiple named exports ([#721], thanks [@robertrossmann])
 - Add [`no-self-import`] rule: forbids a module from importing itself. ([#727], [#449], [#447], thanks [@giodamelio]).
@@ -174,14 +709,17 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel
 - TypeError for missing AST fields from TypeScript ([#842] / [#944], thanks [@alexgorbatchev])
 
 ## [2.7.0] - 2017-07-06
+
 ### Changed
 - [`no-absolute-path`] picks up speed boost, optional AMD support ([#843], thanks [@jseminck])
 
 ## [2.6.1] - 2017-06-29
+
 ### Fixed
 - update bundled node resolver dependency to latest version
 
 ## [2.6.0] - 2017-06-23
+
 ### Changed
 - update tests / peerDeps for ESLint 4.0 compatibility ([#871], thanks [@mastilver])
 - [`memo-parser`] updated to require `filePath` on parser options as it melts
@@ -204,6 +742,7 @@ Yanked due to critical issue in eslint-module-utils with cache key resulting fro
 - Add `allow` option to [`no-unassigned-import`] to allow for files that match the globs ([#671], [#737], thanks [@kevin940726]).
 
 ## [2.3.0] - 2017-05-18
+
 ### Added
 - [`no-anonymous-default-export`] rule: report anonymous default exports ([#712], thanks [@duncanbeevers]).
 - Add new value to [`order`]'s `newlines-between` option to allow newlines inside import groups ([#627], [#628], thanks [@giodamelio])
@@ -218,8 +757,8 @@ Yanked due to critical issue in eslint-module-utils with cache key resulting fro
 - "default is a reserved keyword" in no-maned-default tests by locking down babylon to 6.15.0 (#756, thanks @gmathieu)
 - support scoped modules containing non word characters
 
-
 ## [2.2.0] - 2016-11-07
+
 ### Fixed
 - Corrected a few gaffs in the auto-ignore logic to fix major performance issues
   with projects that did not explicitly ignore `node_modules`. ([#654])
@@ -228,6 +767,7 @@ Yanked due to critical issue in eslint-module-utils with cache key resulting fro
 - [`prefer-default-export`]: fixed crash on export extensions ([#653])
 
 ## [2.1.0] - 2016-11-02
+
 ### Added
 - Add [`no-named-default`] rule: style-guide rule to report use of unnecessarily named default imports ([#596], thanks [@ntdb])
 - [`no-extraneous-dependencies`]: check globs against CWD + absolute path ([#602] + [#630], thanks [@ljharb])
@@ -240,10 +780,12 @@ Yanked due to critical issue in eslint-module-utils with cache key resulting fro
 - Fixed documentation for the default values for the [`order`] rule ([#601])
 
 ## [2.0.1] - 2016-10-06
+
 ### Fixed
 - Fixed code that relied on removed dependencies. ([#604])
 
 ## [2.0.0]! - 2016-09-30
+
 ### Added
 - [`unambiguous`] rule: report modules that are not unambiguously ES modules.
 - `recommended` shared config. Roughly `errors` and `warnings` mixed together,
@@ -269,6 +811,7 @@ Yanked due to critical issue in eslint-module-utils with cache key resulting fro
 - [`no-internal-modules`]: support `@`-scoped packages ([#577]+[#578], thanks [@spalger])
 
 ## [1.16.0] - 2016-09-22
+
 ### Added
 - Added [`no-dynamic-require`] rule: forbid `require()` calls with expressions. ([#567], [#568])
 - Added [`no-internal-modules`] rule: restrict deep package imports to specific folders. ([#485], thanks [@spalger]!)
@@ -279,6 +822,7 @@ Yanked due to critical issue in eslint-module-utils with cache key resulting fro
 - [`default`]: allow re-export of values from ignored files as default ([#545], thanks [@skyrpex])
 
 ## [1.15.0] - 2016-09-12
+
 ### Added
 - Added an `allow` option to [`no-nodejs-modules`] to allow exceptions ([#452], [#509]).
 - Added [`no-absolute-path`] rule ([#530], [#538])
@@ -289,6 +833,7 @@ Yanked due to critical issue in eslint-module-utils with cache key resulting fro
 - [`no-named-as-default-member`] Allow default import to have a property named "default" ([#507], [#508], thanks [@jquense] for both!)
 
 ## [1.14.0] - 2016-08-22
+
 ### Added
 - [`import/parsers` setting]: parse some dependencies (i.e. TypeScript!) with a different parser than the ESLint-configured parser. ([#503])
 
@@ -296,6 +841,7 @@ Yanked due to critical issue in eslint-module-utils with cache key resulting fro
 - [`namespace`] exception for get property from `namespace` import, which are re-export from commonjs module ([#499] fixes [#416], thanks [@wKich])
 
 ## [1.13.0] - 2016-08-11
+
 ### Added
 - `allowComputed` option for [`namespace`] rule. If set to `true`, won't report
   computed member references to namespaces. (see [#456])
@@ -308,15 +854,18 @@ Yanked due to critical issue in eslint-module-utils with cache key resulting fro
   in an imported file. (fixes [#478], thanks [@rhys-vdw])
 
 ## [1.12.0] - 2016-07-26
+
 ### Added
 - [`import/external-module-folders` setting]: a possibility to configure folders for "external" modules ([#444], thanks [@zloirock])
 
 ## [1.11.1] - 2016-07-20
+
 ### Fixed
 - [`newline-after-import`] exception for `switch` branches with `require`s iff parsed as `sourceType:'module'`.
   (still [#441], thanks again [@ljharb])
 
 ## [1.11.0] - 2016-07-17
+
 ### Added
 - Added an `peerDependencies` option to [`no-extraneous-dependencies`] to allow/forbid peer dependencies ([#423], [#428], thanks [@jfmengels]!).
 
@@ -325,20 +874,24 @@ Yanked due to critical issue in eslint-module-utils with cache key resulting fro
   function expression (e.g. `() => require('a') || require('b')`). ([#441], thanks [@ljharb])
 
 ## [1.10.3] - 2016-07-08
+
 ### Fixed
 - removing `Symbol` dependencies (i.e. `for-of` loops) due to Node 0.10 polyfill
   issue (see [#415]). Should not make any discernible semantic difference.
 
 ## [1.10.2] - 2016-07-04
+
 ### Fixed
 - Something horrible happened during `npm prepublish` of 1.10.1.
   Several `rm -rf node_modules && npm i` and `gulp clean && npm prepublish`s later, it is rebuilt and republished as 1.10.2. Thanks [@rhettlivingston] for noticing and reporting!
 
 ## [1.10.1] - 2016-07-02 [YANKED]
+
 ### Added
 - Officially support ESLint 3.x. (peerDependencies updated to `2.x - 3.x`)
 
 ## [1.10.0] - 2016-06-30
+
 ### Added
 - Added new rule [`no-restricted-paths`]. ([#155]/[#371], thanks [@lo1tuma])
 - [`import/core-modules` setting]: allow configuration of additional module names,
@@ -349,14 +902,17 @@ Yanked due to critical issue in eslint-module-utils with cache key resulting fro
 - Fixed crash with `newline-after-import` related to the use of switch cases. (fixes [#386], thanks [@ljharb] for reporting) ([#395])
 
 ## [1.9.2] - 2016-06-21
+
 ### Fixed
 - Issues with ignored/CJS files in [`export`] and [`no-deprecated`] rules. ([#348], [#370])
 
 ## [1.9.1] - 2016-06-16
+
 ### Fixed
 - Reordered precedence for loading resolvers. ([#373])
 
 ## [1.9.0] - 2016-06-10
+
 ### Added
 - Added support TomDoc comments to [`no-deprecated`]. ([#321], thanks [@josh])
 - Added support for loading custom resolvers ([#314], thanks [@le0nik])
@@ -365,6 +921,7 @@ Yanked due to critical issue in eslint-module-utils with cache key resulting fro
 - [`prefer-default-export`] handles `export function` and `export const` in same file ([#359], thanks [@scottnonnenberg])
 
 ## [1.8.1] - 2016-05-23
+
 ### Fixed
 - `export * from 'foo'` now properly ignores a `default` export from `foo`, if any. ([#328]/[#332], thanks [@jkimbo])
   This impacts all static analysis of imported names. ([`default`], [`named`], [`namespace`], [`export`])
@@ -374,6 +931,7 @@ Yanked due to critical issue in eslint-module-utils with cache key resulting fro
 - [`prefer-default-export`] properly handles deep destructuring, `export * from ...`, and files with no exports. ([#342]+[#343], thanks [@scottnonnenberg])
 
 ## [1.8.0] - 2016-05-11
+
 ### Added
 - [`prefer-default-export`], new rule. ([#308], thanks [@gavriguy])
 
@@ -382,6 +940,7 @@ Yanked due to critical issue in eslint-module-utils with cache key resulting fro
 - Make [`no-extraneous-dependencies`] handle scoped packages ([#316], thanks [@jfmengels])
 
 ## [1.7.0] - 2016-05-06
+
 ### Added
 - [`newline-after-import`], new rule. ([#245], thanks [@singles])
 - Added an `optionalDependencies` option to [`no-extraneous-dependencies`] to allow/forbid optional dependencies ([#266], thanks [@jfmengels]).
@@ -396,6 +955,7 @@ Yanked due to critical issue in eslint-module-utils with cache key resulting fro
   module is not resolved. Also, never report for builtins (i.e. `path`). ([#296])
 
 ## [1.6.1] - 2016-04-28
+
 ### Fixed
 - [`no-named-as-default-member`]: don't crash on rest props. ([#281], thanks [@SimenB])
 - support for Node 6: don't pass `null` to `path` functions.
@@ -403,6 +963,7 @@ Yanked due to critical issue in eslint-module-utils with cache key resulting fro
   config ([#288]).
 
 ## [1.6.0] - 2016-04-25
+
 ### Added
 - add [`no-named-as-default-member`] to `warnings` canned config
 - add [`no-extraneous-dependencies`] rule ([#241], thanks [@jfmengels])
@@ -421,6 +982,7 @@ Yanked due to critical issue in eslint-module-utils with cache key resulting fro
 - [`no-named-as-default-member`] had a crash on destructuring in loops (thanks for heads up from [@lemonmade])
 
 ## [1.5.0] - 2016-04-18
+
 ### Added
 - report resolver errors at the top of the linted file
 - add [`no-namespace`] rule ([#239], thanks [@singles])
@@ -433,6 +995,7 @@ Yanked due to critical issue in eslint-module-utils with cache key resulting fro
 - support for Node 0.10, via `es6-*` ponyfills. Using native Map/Set/Symbol.
 
 ## [1.4.0] - 2016-03-25
+
 ### Added
 - Resolver plugin interface v2: more explicit response format that more clearly covers the found-but-core-module case, where there is no path.
   Still backwards-compatible with the original version of the resolver spec.
@@ -443,6 +1006,7 @@ Yanked due to critical issue in eslint-module-utils with cache key resulting fro
 - using `es6-*` ponyfills instead of `babel-runtime`
 
 ## [1.3.0] - 2016-03-20
+
 Major perf improvements. Between parsing only once and ignoring gigantic, non-module `node_modules`,
 there is very little added time.
 
@@ -458,6 +1022,7 @@ memoizing parser, and takes only 27s with naked `babel-eslint` (thus, reparsing
   something that looks like an `export` is detected in the module content.
 
 ## [1.2.0] - 2016-03-19
+
 Thanks [@lencioni] for identifying a huge amount of rework in resolve and kicking
 off a bunch of memoization.
 
@@ -468,12 +1033,12 @@ I'm seeing 62% improvement over my normal test codebase when executing only
 - added caching to core/resolve via [#214], configured via [`import/cache` setting]
 
 ## [1.1.0] - 2016-03-15
+
 ### Added
-- Added an [`ignore`](./docs/rules/no-unresolved.md#ignore) option to [`no-unresolved`] for those pesky files that no
-resolver can find. (still prefer enhancing the Webpack and Node resolvers to
-using it, though). See [#89] for details.
+- Added an [`ignore`](./docs/rules/no-unresolved.md#ignore) option to [`no-unresolved`] for those pesky files that no resolver can find. (still prefer enhancing the Webpack and Node resolvers to using it, though). See [#89] for details.
 
 ## [1.0.4] - 2016-03-11
+
 ### Changed
 - respect hoisting for deep namespaces ([`namespace`]/[`no-deprecated`]) ([#211])
 
@@ -482,42 +1047,45 @@ using it, though). See [#89] for details.
 - correct cache behavior in `eslint_d` for deep namespaces ([#200])
 
 ## [1.0.3] - 2016-02-26
+
 ### Changed
 - no-deprecated follows deep namespaces ([#191])
 
 ### Fixed
-- [`namespace`] no longer flags modules with only a default export as having no
-names. (ns.default is valid ES6)
+- [`namespace`] no longer flags modules with only a default export as having no names. (ns.default is valid ES6)
 
 ## [1.0.2] - 2016-02-26
+
 ### Fixed
 - don't parse imports with no specifiers ([#192])
 
 ## [1.0.1] - 2016-02-25
+
 ### Fixed
 - export `stage-0` shared config
 - documented [`no-deprecated`]
 - deep namespaces are traversed regardless of how they get imported ([#189])
 
 ## [1.0.0] - 2016-02-24
+
 ### Added
-- [`no-deprecated`]: WIP rule to let you know at lint time if you're using
-deprecated functions, constants, classes, or modules.
+- [`no-deprecated`]: WIP rule to let you know at lint time if you're using deprecated functions, constants, classes, or modules.
 
 ### Changed
 - [`namespace`]: support deep namespaces ([#119] via [#157])
 
 ## [1.0.0-beta.0] - 2016-02-13
+
 ### Changed
 - support for (only) ESLint 2.x
-- no longer needs/refers to `import/parser` or `import/parse-options`. Instead,
-ESLint provides the configured parser + options to the rules, and they use that
-to parse dependencies.
+- no longer needs/refers to `import/parser` or `import/parse-options`. Instead, ESLint provides the configured parser + options to the rules, and they use that to parse dependencies.
 
 ### Removed
+
 - `babylon` as default import parser (see Breaking)
 
 ## [0.13.0] - 2016-02-08
+
 ### Added
 - [`no-commonjs`] rule
 - [`no-amd`] rule
@@ -526,25 +1094,26 @@ to parse dependencies.
 - Removed vestigial `no-require` rule. [`no-commonjs`] is more complete.
 
 ## [0.12.2] - 2016-02-06 [YANKED]
+
 Unpublished from npm and re-released as 0.13.0. See [#170].
 
 ## [0.12.1] - 2015-12-17
+
 ### Changed
 - Broke docs for rules out into individual files.
 
 ## [0.12.0] - 2015-12-14
+
 ### Changed
-- Ignore [`import/ignore` setting] if exports are actually found in the parsed module. Does
-this to support use of `jsnext:main` in `node_modules` without the pain of
-managing an allow list or a nuanced deny list.
+- Ignore [`import/ignore` setting] if exports are actually found in the parsed module. Does this to support use of `jsnext:main` in `node_modules` without the pain of managing an allow list or a nuanced deny list.
 
 ## [0.11.0] - 2015-11-27
+
 ### Added
-- Resolver plugins. Now the linter can read Webpack config, properly follow
-aliases and ignore externals, dismisses inline loaders, etc. etc.!
+- Resolver plugins. Now the linter can read Webpack config, properly follow aliases and ignore externals, dismisses inline loaders, etc. etc.!
 
 ## Earlier releases (0.10.1 and younger)
-See [GitHub release notes](https://github.com/benmosher/eslint-plugin-import/releases?after=v0.11.0)
+See [GitHub release notes](https://github.com/import-js/eslint-plugin-import/releases?after=v0.11.0)
 for info on changes for earlier releases.
 
 
@@ -554,9 +1123,13 @@ for info on changes for earlier releases.
 [`import/parsers` setting]: ./README.md#importparsers
 [`import/core-modules` setting]: ./README.md#importcore-modules
 [`import/external-module-folders` setting]: ./README.md#importexternal-module-folders
+[`internal-regex` setting]: ./README.md#importinternal-regex
+[`import/node-version` setting]: ./README.md#importnode-version
 
+[`consistent-type-specifier-style`]: ./docs/rules/consistent-type-specifier-style.md
 [`default`]: ./docs/rules/default.md
 [`dynamic-import-chunkname`]: ./docs/rules/dynamic-import-chunkname.md
+[`enforce-node-protocol-usage`]: ./docs/rules/enforce-node-protocol-usage.md
 [`export`]: ./docs/rules/export.md
 [`exports-last`]: ./docs/rules/exports-last.md
 [`extensions`]: ./docs/rules/extensions.md
@@ -576,7 +1149,9 @@ for info on changes for earlier releases.
 [`no-deprecated`]: ./docs/rules/no-deprecated.md
 [`no-duplicates`]: ./docs/rules/no-duplicates.md
 [`no-dynamic-require`]: ./docs/rules/no-dynamic-require.md
+[`no-empty-named-blocks`]: ./docs/rules/no-empty-named-blocks.md
 [`no-extraneous-dependencies`]: ./docs/rules/no-extraneous-dependencies.md
+[`no-import-module-exports`]: ./docs/rules/no-import-module-exports.md
 [`no-internal-modules`]: ./docs/rules/no-internal-modules.md
 [`no-mutable-exports`]: ./docs/rules/no-mutable-exports.md
 [`no-named-as-default-member`]: ./docs/rules/no-named-as-default-member.md
@@ -585,6 +1160,8 @@ for info on changes for earlier releases.
 [`no-named-export`]: ./docs/rules/no-named-export.md
 [`no-namespace`]: ./docs/rules/no-namespace.md
 [`no-nodejs-modules`]: ./docs/rules/no-nodejs-modules.md
+[`no-relative-packages`]: ./docs/rules/no-relative-packages.md
+[`no-relative-parent-imports`]: ./docs/rules/no-relative-parent-imports.md
 [`no-restricted-paths`]: ./docs/rules/no-restricted-paths.md
 [`no-self-import`]: ./docs/rules/no-self-import.md
 [`no-unassigned-import`]: ./docs/rules/no-unassigned-import.md
@@ -598,375 +1175,896 @@ for info on changes for earlier releases.
 
 [`memo-parser`]: ./memo-parser/README.md
 
-[#1425]: https://github.com/benmosher/eslint-plugin-import/pull/1425
-[#1419]: https://github.com/benmosher/eslint-plugin-import/pull/1419
-[#1412]: https://github.com/benmosher/eslint-plugin-import/pull/1412
-[#1409]: https://github.com/benmosher/eslint-plugin-import/pull/1409
-[#1404]: https://github.com/benmosher/eslint-plugin-import/pull/1404
-[#1393]: https://github.com/benmosher/eslint-plugin-import/pull/1393
-[#1389]: https://github.com/benmosher/eslint-plugin-import/pull/1389
-[#1377]: https://github.com/benmosher/eslint-plugin-import/pull/1377
-[#1375]: https://github.com/benmosher/eslint-plugin-import/pull/1375
-[#1372]: https://github.com/benmosher/eslint-plugin-import/pull/1372
-[#1371]: https://github.com/benmosher/eslint-plugin-import/pull/1371
-[#1370]: https://github.com/benmosher/eslint-plugin-import/pull/1370
-[#1363]: https://github.com/benmosher/eslint-plugin-import/pull/1363
-[#1358]: https://github.com/benmosher/eslint-plugin-import/pull/1358
-[#1356]: https://github.com/benmosher/eslint-plugin-import/pull/1356
-[#1354]: https://github.com/benmosher/eslint-plugin-import/pull/1354
-[#1352]: https://github.com/benmosher/eslint-plugin-import/pull/1352
-[#1347]: https://github.com/benmosher/eslint-plugin-import/pull/1347
-[#1345]: https://github.com/benmosher/eslint-plugin-import/pull/1345
-[#1342]: https://github.com/benmosher/eslint-plugin-import/pull/1342
-[#1340]: https://github.com/benmosher/eslint-plugin-import/pull/1340
-[#1333]: https://github.com/benmosher/eslint-plugin-import/pull/1333
-[#1331]: https://github.com/benmosher/eslint-plugin-import/pull/1331
-[#1330]: https://github.com/benmosher/eslint-plugin-import/pull/1330
-[#1320]: https://github.com/benmosher/eslint-plugin-import/pull/1320
-[#1319]: https://github.com/benmosher/eslint-plugin-import/pull/1319
-[#1312]: https://github.com/benmosher/eslint-plugin-import/pull/1312
-[#1308]: https://github.com/benmosher/eslint-plugin-import/pull/1308
-[#1304]: https://github.com/benmosher/eslint-plugin-import/pull/1304
-[#1297]: https://github.com/benmosher/eslint-plugin-import/pull/1297
-[#1295]: https://github.com/benmosher/eslint-plugin-import/pull/1295
-[#1294]: https://github.com/benmosher/eslint-plugin-import/pull/1294
-[#1290]: https://github.com/benmosher/eslint-plugin-import/pull/1290
-[#1277]: https://github.com/benmosher/eslint-plugin-import/pull/1277
-[#1257]: https://github.com/benmosher/eslint-plugin-import/pull/1257
-[#1235]: https://github.com/benmosher/eslint-plugin-import/pull/1235
-[#1234]: https://github.com/benmosher/eslint-plugin-import/pull/1234
-[#1232]: https://github.com/benmosher/eslint-plugin-import/pull/1232
-[#1218]: https://github.com/benmosher/eslint-plugin-import/pull/1218
-[#1176]: https://github.com/benmosher/eslint-plugin-import/pull/1176
-[#1163]: https://github.com/benmosher/eslint-plugin-import/pull/1163
-[#1157]: https://github.com/benmosher/eslint-plugin-import/pull/1157
-[#1151]: https://github.com/benmosher/eslint-plugin-import/pull/1151
-[#1142]: https://github.com/benmosher/eslint-plugin-import/pull/1142
-[#1137]: https://github.com/benmosher/eslint-plugin-import/pull/1137
-[#1135]: https://github.com/benmosher/eslint-plugin-import/pull/1135
-[#1128]: https://github.com/benmosher/eslint-plugin-import/pull/1128
-[#1126]: https://github.com/benmosher/eslint-plugin-import/pull/1126
-[#1122]: https://github.com/benmosher/eslint-plugin-import/pull/1122
-[#1112]: https://github.com/benmosher/eslint-plugin-import/pull/1112
-[#1106]: https://github.com/benmosher/eslint-plugin-import/pull/1106
-[#1093]: https://github.com/benmosher/eslint-plugin-import/pull/1093
-[#1085]: https://github.com/benmosher/eslint-plugin-import/pull/1085
-[#1068]: https://github.com/benmosher/eslint-plugin-import/pull/1068
-[#1046]: https://github.com/benmosher/eslint-plugin-import/pull/1046
-[#944]: https://github.com/benmosher/eslint-plugin-import/pull/944
-[#912]: https://github.com/benmosher/eslint-plugin-import/pull/912
-[#908]: https://github.com/benmosher/eslint-plugin-import/pull/908
-[#891]: https://github.com/benmosher/eslint-plugin-import/pull/891
-[#889]: https://github.com/benmosher/eslint-plugin-import/pull/889
-[#880]: https://github.com/benmosher/eslint-plugin-import/pull/880
-[#871]: https://github.com/benmosher/eslint-plugin-import/pull/871
-[#858]: https://github.com/benmosher/eslint-plugin-import/pull/858
-[#843]: https://github.com/benmosher/eslint-plugin-import/pull/843
-[#797]: https://github.com/benmosher/eslint-plugin-import/pull/797
-[#794]: https://github.com/benmosher/eslint-plugin-import/pull/794
-[#744]: https://github.com/benmosher/eslint-plugin-import/pull/744
-[#742]: https://github.com/benmosher/eslint-plugin-import/pull/742
-[#737]: https://github.com/benmosher/eslint-plugin-import/pull/737
-[#727]: https://github.com/benmosher/eslint-plugin-import/pull/727
-[#721]: https://github.com/benmosher/eslint-plugin-import/pull/721
-[#712]: https://github.com/benmosher/eslint-plugin-import/pull/712
-[#696]: https://github.com/benmosher/eslint-plugin-import/pull/696
-[#685]: https://github.com/benmosher/eslint-plugin-import/pull/685
-[#680]: https://github.com/benmosher/eslint-plugin-import/pull/680
-[#654]: https://github.com/benmosher/eslint-plugin-import/pull/654
-[#639]: https://github.com/benmosher/eslint-plugin-import/pull/639
-[#632]: https://github.com/benmosher/eslint-plugin-import/pull/632
-[#630]: https://github.com/benmosher/eslint-plugin-import/pull/630
-[#628]: https://github.com/benmosher/eslint-plugin-import/pull/628
-[#596]: https://github.com/benmosher/eslint-plugin-import/pull/596
-[#586]: https://github.com/benmosher/eslint-plugin-import/pull/586
-[#578]: https://github.com/benmosher/eslint-plugin-import/pull/578
-[#568]: https://github.com/benmosher/eslint-plugin-import/pull/568
-[#555]: https://github.com/benmosher/eslint-plugin-import/pull/555
-[#538]: https://github.com/benmosher/eslint-plugin-import/pull/538
-[#527]: https://github.com/benmosher/eslint-plugin-import/pull/527
-[#509]: https://github.com/benmosher/eslint-plugin-import/pull/509
-[#508]: https://github.com/benmosher/eslint-plugin-import/pull/508
-[#503]: https://github.com/benmosher/eslint-plugin-import/pull/503
-[#499]: https://github.com/benmosher/eslint-plugin-import/pull/499
-[#489]: https://github.com/benmosher/eslint-plugin-import/pull/489
-[#485]: https://github.com/benmosher/eslint-plugin-import/pull/485
-[#461]: https://github.com/benmosher/eslint-plugin-import/pull/461
-[#449]: https://github.com/benmosher/eslint-plugin-import/pull/449
-[#444]: https://github.com/benmosher/eslint-plugin-import/pull/444
-[#428]: https://github.com/benmosher/eslint-plugin-import/pull/428
-[#395]: https://github.com/benmosher/eslint-plugin-import/pull/395
-[#371]: https://github.com/benmosher/eslint-plugin-import/pull/371
-[#365]: https://github.com/benmosher/eslint-plugin-import/pull/365
-[#359]: https://github.com/benmosher/eslint-plugin-import/pull/359
-[#343]: https://github.com/benmosher/eslint-plugin-import/pull/343
-[#332]: https://github.com/benmosher/eslint-plugin-import/pull/332
-[#322]: https://github.com/benmosher/eslint-plugin-import/pull/322
-[#321]: https://github.com/benmosher/eslint-plugin-import/pull/321
-[#316]: https://github.com/benmosher/eslint-plugin-import/pull/316
-[#314]: https://github.com/benmosher/eslint-plugin-import/pull/314
-[#308]: https://github.com/benmosher/eslint-plugin-import/pull/308
-[#298]: https://github.com/benmosher/eslint-plugin-import/pull/298
-[#297]: https://github.com/benmosher/eslint-plugin-import/pull/297
-[#296]: https://github.com/benmosher/eslint-plugin-import/pull/296
-[#290]: https://github.com/benmosher/eslint-plugin-import/pull/290
-[#289]: https://github.com/benmosher/eslint-plugin-import/pull/289
-[#288]: https://github.com/benmosher/eslint-plugin-import/pull/288
-[#287]: https://github.com/benmosher/eslint-plugin-import/pull/287
-[#278]: https://github.com/benmosher/eslint-plugin-import/pull/278
-[#261]: https://github.com/benmosher/eslint-plugin-import/pull/261
-[#256]: https://github.com/benmosher/eslint-plugin-import/pull/256
-[#254]: https://github.com/benmosher/eslint-plugin-import/pull/254
-[#250]: https://github.com/benmosher/eslint-plugin-import/pull/250
-[#247]: https://github.com/benmosher/eslint-plugin-import/pull/247
-[#245]: https://github.com/benmosher/eslint-plugin-import/pull/245
-[#243]: https://github.com/benmosher/eslint-plugin-import/pull/243
-[#241]: https://github.com/benmosher/eslint-plugin-import/pull/241
-[#239]: https://github.com/benmosher/eslint-plugin-import/pull/239
-[#228]: https://github.com/benmosher/eslint-plugin-import/pull/228
-[#211]: https://github.com/benmosher/eslint-plugin-import/pull/211
-[#164]: https://github.com/benmosher/eslint-plugin-import/pull/164
-[#157]: https://github.com/benmosher/eslint-plugin-import/pull/157
-
-[#1366]: https://github.com/benmosher/eslint-plugin-import/issues/1366
-[#1334]: https://github.com/benmosher/eslint-plugin-import/issues/1334
-[#1323]: https://github.com/benmosher/eslint-plugin-import/issues/1323
-[#1322]: https://github.com/benmosher/eslint-plugin-import/issues/1322
-[#1300]: https://github.com/benmosher/eslint-plugin-import/issues/1300
-[#1293]: https://github.com/benmosher/eslint-plugin-import/issues/1293
-[#1266]: https://github.com/benmosher/eslint-plugin-import/issues/1266
-[#1256]: https://github.com/benmosher/eslint-plugin-import/issues/1256
-[#1233]: https://github.com/benmosher/eslint-plugin-import/issues/1233
-[#1175]: https://github.com/benmosher/eslint-plugin-import/issues/1175
-[#1166]: https://github.com/benmosher/eslint-plugin-import/issues/1166
-[#1144]: https://github.com/benmosher/eslint-plugin-import/issues/1144
-[#1058]: https://github.com/benmosher/eslint-plugin-import/issues/1058
-[#1035]: https://github.com/benmosher/eslint-plugin-import/issues/1035
-[#931]: https://github.com/benmosher/eslint-plugin-import/issues/931
-[#886]: https://github.com/benmosher/eslint-plugin-import/issues/886
-[#863]: https://github.com/benmosher/eslint-plugin-import/issues/863
-[#842]: https://github.com/benmosher/eslint-plugin-import/issues/842
-[#839]: https://github.com/benmosher/eslint-plugin-import/issues/839
-[#793]: https://github.com/benmosher/eslint-plugin-import/issues/793
-[#720]: https://github.com/benmosher/eslint-plugin-import/issues/720
-[#717]: https://github.com/benmosher/eslint-plugin-import/issues/717
-[#686]: https://github.com/benmosher/eslint-plugin-import/issues/686
-[#671]: https://github.com/benmosher/eslint-plugin-import/issues/671
-[#660]: https://github.com/benmosher/eslint-plugin-import/issues/660
-[#653]: https://github.com/benmosher/eslint-plugin-import/issues/653
-[#627]: https://github.com/benmosher/eslint-plugin-import/issues/627
-[#620]: https://github.com/benmosher/eslint-plugin-import/issues/620
-[#609]: https://github.com/benmosher/eslint-plugin-import/issues/609
-[#604]: https://github.com/benmosher/eslint-plugin-import/issues/604
-[#602]: https://github.com/benmosher/eslint-plugin-import/issues/602
-[#601]: https://github.com/benmosher/eslint-plugin-import/issues/601
-[#592]: https://github.com/benmosher/eslint-plugin-import/issues/592
-[#577]: https://github.com/benmosher/eslint-plugin-import/issues/577
-[#570]: https://github.com/benmosher/eslint-plugin-import/issues/570
-[#567]: https://github.com/benmosher/eslint-plugin-import/issues/567
-[#566]: https://github.com/benmosher/eslint-plugin-import/issues/566
-[#545]: https://github.com/benmosher/eslint-plugin-import/issues/545
-[#530]: https://github.com/benmosher/eslint-plugin-import/issues/530
-[#529]: https://github.com/benmosher/eslint-plugin-import/issues/529
-[#519]: https://github.com/benmosher/eslint-plugin-import/issues/519
-[#507]: https://github.com/benmosher/eslint-plugin-import/issues/507
-[#484]: https://github.com/benmosher/eslint-plugin-import/issues/484
-[#478]: https://github.com/benmosher/eslint-plugin-import/issues/478
-[#456]: https://github.com/benmosher/eslint-plugin-import/issues/456
-[#453]: https://github.com/benmosher/eslint-plugin-import/issues/453
-[#452]: https://github.com/benmosher/eslint-plugin-import/issues/452
-[#447]: https://github.com/benmosher/eslint-plugin-import/issues/447
-[#441]: https://github.com/benmosher/eslint-plugin-import/issues/441
-[#423]: https://github.com/benmosher/eslint-plugin-import/issues/423
-[#416]: https://github.com/benmosher/eslint-plugin-import/issues/416
-[#415]: https://github.com/benmosher/eslint-plugin-import/issues/415
-[#402]: https://github.com/benmosher/eslint-plugin-import/issues/402
-[#386]: https://github.com/benmosher/eslint-plugin-import/issues/386
-[#373]: https://github.com/benmosher/eslint-plugin-import/issues/373
-[#370]: https://github.com/benmosher/eslint-plugin-import/issues/370
-[#348]: https://github.com/benmosher/eslint-plugin-import/issues/348
-[#342]: https://github.com/benmosher/eslint-plugin-import/issues/342
-[#328]: https://github.com/benmosher/eslint-plugin-import/issues/328
-[#317]: https://github.com/benmosher/eslint-plugin-import/issues/317
-[#313]: https://github.com/benmosher/eslint-plugin-import/issues/313
-[#311]: https://github.com/benmosher/eslint-plugin-import/issues/311
-[#306]: https://github.com/benmosher/eslint-plugin-import/issues/306
-[#286]: https://github.com/benmosher/eslint-plugin-import/issues/286
-[#283]: https://github.com/benmosher/eslint-plugin-import/issues/283
-[#281]: https://github.com/benmosher/eslint-plugin-import/issues/281
-[#275]: https://github.com/benmosher/eslint-plugin-import/issues/275
-[#272]: https://github.com/benmosher/eslint-plugin-import/issues/272
-[#270]: https://github.com/benmosher/eslint-plugin-import/issues/270
-[#267]: https://github.com/benmosher/eslint-plugin-import/issues/267
-[#266]: https://github.com/benmosher/eslint-plugin-import/issues/266
-[#216]: https://github.com/benmosher/eslint-plugin-import/issues/216
-[#214]: https://github.com/benmosher/eslint-plugin-import/issues/214
-[#210]: https://github.com/benmosher/eslint-plugin-import/issues/210
-[#200]: https://github.com/benmosher/eslint-plugin-import/issues/200
-[#192]: https://github.com/benmosher/eslint-plugin-import/issues/192
-[#191]: https://github.com/benmosher/eslint-plugin-import/issues/191
-[#189]: https://github.com/benmosher/eslint-plugin-import/issues/189
-[#170]: https://github.com/benmosher/eslint-plugin-import/issues/170
-[#155]: https://github.com/benmosher/eslint-plugin-import/issues/155
-[#119]: https://github.com/benmosher/eslint-plugin-import/issues/119
-[#89]: https://github.com/benmosher/eslint-plugin-import/issues/89
-
-[Unreleased]: https://github.com/benmosher/eslint-plugin-import/compare/v2.18.0...HEAD
-[2.18.0]: https://github.com/benmosher/eslint-plugin-import/compare/v2.17.3...v2.18.0
-[2.17.3]: https://github.com/benmosher/eslint-plugin-import/compare/v2.17.2...v2.17.3
-[2.17.2]: https://github.com/benmosher/eslint-plugin-import/compare/v2.17.1...v2.17.2
-[2.17.1]: https://github.com/benmosher/eslint-plugin-import/compare/v2.17.0...v2.17.1
-[2.17.0]: https://github.com/benmosher/eslint-plugin-import/compare/v2.16.0...v2.17.0
-[2.16.0]: https://github.com/benmosher/eslint-plugin-import/compare/v2.15.0...v2.16.0
-[2.15.0]: https://github.com/benmosher/eslint-plugin-import/compare/v2.14.0...v2.15.0
-[2.14.0]: https://github.com/benmosher/eslint-plugin-import/compare/v2.13.0...v2.14.0
-[2.13.0]: https://github.com/benmosher/eslint-plugin-import/compare/v2.12.0...v2.13.0
-[2.12.0]: https://github.com/benmosher/eslint-plugin-import/compare/v2.11.0...v2.12.0
-[2.11.0]: https://github.com/benmosher/eslint-plugin-import/compare/v2.10.0...v2.11.0
-[2.10.0]: https://github.com/benmosher/eslint-plugin-import/compare/v2.9.0...v2.10.0
-[2.9.0]: https://github.com/benmosher/eslint-plugin-import/compare/v2.8.0...v2.9.0
-[2.8.0]: https://github.com/benmosher/eslint-plugin-import/compare/v2.7.0...v2.8.0
-[2.7.0]: https://github.com/benmosher/eslint-plugin-import/compare/v2.6.1...v2.7.0
-[2.6.1]: https://github.com/benmosher/eslint-plugin-import/compare/v2.6.0...v2.6.1
-[2.6.0]: https://github.com/benmosher/eslint-plugin-import/compare/v2.5.0...v2.6.0
-[2.5.0]: https://github.com/benmosher/eslint-plugin-import/compare/v2.4.0...v2.5.0
-[2.4.0]: https://github.com/benmosher/eslint-plugin-import/compare/v2.3.0...v2.4.0
-[2.3.0]: https://github.com/benmosher/eslint-plugin-import/compare/v2.2.0...v2.3.0
-[2.2.0]: https://github.com/benmosher/eslint-plugin-import/compare/v2.1.0...v2.2.0
-[2.1.0]: https://github.com/benmosher/eslint-plugin-import/compare/v2.0.1...v2.1.0
-[2.0.1]: https://github.com/benmosher/eslint-plugin-import/compare/v2.0.0...v2.0.1
-[2.0.0]: https://github.com/benmosher/eslint-plugin-import/compare/v1.16.0...v2.0.0
-[1.16.0]: https://github.com/benmosher/eslint-plugin-import/compare/v1.15.0...v1.16.0
-[1.15.0]: https://github.com/benmosher/eslint-plugin-import/compare/v1.14.0...v1.15.0
-[1.14.0]: https://github.com/benmosher/eslint-plugin-import/compare/v1.13.0...v1.14.0
-[1.13.0]: https://github.com/benmosher/eslint-plugin-import/compare/v1.12.0...v1.13.0
-[1.12.0]: https://github.com/benmosher/eslint-plugin-import/compare/v1.11.1...v1.12.0
-[1.11.1]: https://github.com/benmosher/eslint-plugin-import/compare/v1.11.0...v1.11.1
-[1.11.0]: https://github.com/benmosher/eslint-plugin-import/compare/v1.10.3...v1.11.0
-[1.10.3]: https://github.com/benmosher/eslint-plugin-import/compare/v1.10.2...v1.10.3
-[1.10.2]: https://github.com/benmosher/eslint-plugin-import/compare/v1.10.1...v1.10.2
-[1.10.1]: https://github.com/benmosher/eslint-plugin-import/compare/v1.10.0...v1.10.1
-[1.10.0]: https://github.com/benmosher/eslint-plugin-import/compare/v1.9.2...v1.10.0
-[1.9.2]: https://github.com/benmosher/eslint-plugin-import/compare/v1.9.1...v1.9.2
-[1.9.1]: https://github.com/benmosher/eslint-plugin-import/compare/v1.9.0...v1.9.1
-[1.9.0]: https://github.com/benmosher/eslint-plugin-import/compare/v1.8.1...v1.9.0
-[1.8.1]: https://github.com/benmosher/eslint-plugin-import/compare/v1.8.0...v1.8.1
-[1.8.0]: https://github.com/benmosher/eslint-plugin-import/compare/v1.7.0...v1.8.0
-[1.7.0]: https://github.com/benmosher/eslint-plugin-import/compare/v1.6.1...v1.7.0
-[1.6.1]: https://github.com/benmosher/eslint-plugin-import/compare/v1.6.0...v1.6.1
-[1.6.0]: https://github.com/benmosher/eslint-plugin-import/compare/v1.5.0...1.6.0
-[1.5.0]: https://github.com/benmosher/eslint-plugin-import/compare/v1.4.0...v1.5.0
-[1.4.0]: https://github.com/benmosher/eslint-plugin-import/compare/v1.3.0...v1.4.0
-[1.3.0]: https://github.com/benmosher/eslint-plugin-import/compare/v1.2.0...v1.3.0
-[1.2.0]: https://github.com/benmosher/eslint-plugin-import/compare/v1.1.0...v1.2.0
-[1.1.0]: https://github.com/benmosher/eslint-plugin-import/compare/v1.0.4...v1.1.0
-[1.0.4]: https://github.com/benmosher/eslint-plugin-import/compare/v1.0.3...v1.0.4
-[1.0.3]: https://github.com/benmosher/eslint-plugin-import/compare/v1.0.2...v1.0.3
-[1.0.2]: https://github.com/benmosher/eslint-plugin-import/compare/v1.0.1...v1.0.2
-[1.0.1]: https://github.com/benmosher/eslint-plugin-import/compare/v1.0.0...v1.0.1
-[1.0.0]: https://github.com/benmosher/eslint-plugin-import/compare/v1.0.0-beta.0...v1.0.0
-[1.0.0-beta.0]: https://github.com/benmosher/eslint-plugin-import/compare/v0.13.0...v1.0.0-beta.0
-[0.13.0]: https://github.com/benmosher/eslint-plugin-import/compare/v0.12.1...v0.13.0
-[0.12.2]: https://github.com/benmosher/eslint-plugin-import/compare/v0.12.1...v0.12.2
-[0.12.1]: https://github.com/benmosher/eslint-plugin-import/compare/v0.12.0...v0.12.1
-[0.12.0]: https://github.com/benmosher/eslint-plugin-import/compare/v0.11.0...v0.12.0
-[0.11.0]: https://github.com/benmosher/eslint-plugin-import/compare/v0.10.1...v0.11.0
+[#3167]: https://github.com/import-js/eslint-plugin-import/pull/3167
+[#3166]: https://github.com/import-js/eslint-plugin-import/pull/3166
+[#3151]: https://github.com/import-js/eslint-plugin-import/pull/3151
+[#3138]: https://github.com/import-js/eslint-plugin-import/pull/3138
+[#3129]: https://github.com/import-js/eslint-plugin-import/pull/3129
+[#3128]: https://github.com/import-js/eslint-plugin-import/pull/3128
+[#3127]: https://github.com/import-js/eslint-plugin-import/pull/3127
+[#3125]: https://github.com/import-js/eslint-plugin-import/pull/3125
+[#3122]: https://github.com/import-js/eslint-plugin-import/pull/3122
+[#3116]: https://github.com/import-js/eslint-plugin-import/pull/3116
+[#3106]: https://github.com/import-js/eslint-plugin-import/pull/3106
+[#3105]: https://github.com/import-js/eslint-plugin-import/pull/3105
+[#3104]: https://github.com/import-js/eslint-plugin-import/pull/3104
+[#3097]: https://github.com/import-js/eslint-plugin-import/pull/3097
+[#3073]: https://github.com/import-js/eslint-plugin-import/pull/3073
+[#3072]: https://github.com/import-js/eslint-plugin-import/pull/3072
+[#3071]: https://github.com/import-js/eslint-plugin-import/pull/3071
+[#3070]: https://github.com/import-js/eslint-plugin-import/pull/3070
+[#3068]: https://github.com/import-js/eslint-plugin-import/pull/3068
+[#3066]: https://github.com/import-js/eslint-plugin-import/pull/3066
+[#3065]: https://github.com/import-js/eslint-plugin-import/pull/3065
+[#3062]: https://github.com/import-js/eslint-plugin-import/pull/3062
+[#3052]: https://github.com/import-js/eslint-plugin-import/pull/3052
+[#3043]: https://github.com/import-js/eslint-plugin-import/pull/3043
+[#3036]: https://github.com/import-js/eslint-plugin-import/pull/3036
+[#3033]: https://github.com/import-js/eslint-plugin-import/pull/3033
+[#3032]: https://github.com/import-js/eslint-plugin-import/pull/3032
+[#3024]: https://github.com/import-js/eslint-plugin-import/pull/3024
+[#3018]: https://github.com/import-js/eslint-plugin-import/pull/3018
+[#3012]: https://github.com/import-js/eslint-plugin-import/pull/3012
+[#3011]: https://github.com/import-js/eslint-plugin-import/pull/3011
+[#3004]: https://github.com/import-js/eslint-plugin-import/pull/3004
+[#2998]: https://github.com/import-js/eslint-plugin-import/pull/2998
+[#2996]: https://github.com/import-js/eslint-plugin-import/pull/2996
+[#2993]: https://github.com/import-js/eslint-plugin-import/pull/2993
+[#2991]: https://github.com/import-js/eslint-plugin-import/pull/2991
+[#2989]: https://github.com/import-js/eslint-plugin-import/pull/2989
+[#2987]: https://github.com/import-js/eslint-plugin-import/pull/2987
+[#2985]: https://github.com/import-js/eslint-plugin-import/pull/2985
+[#2982]: https://github.com/import-js/eslint-plugin-import/pull/2982
+[#2952]: https://github.com/import-js/eslint-plugin-import/pull/2952
+[#2944]: https://github.com/import-js/eslint-plugin-import/pull/2944
+[#2942]: https://github.com/import-js/eslint-plugin-import/pull/2942
+[#2919]: https://github.com/import-js/eslint-plugin-import/pull/2919
+[#2885]: https://github.com/import-js/eslint-plugin-import/pull/2885
+[#2884]: https://github.com/import-js/eslint-plugin-import/pull/2884
+[#2866]: https://github.com/import-js/eslint-plugin-import/pull/2866
+[#2854]: https://github.com/import-js/eslint-plugin-import/pull/2854
+[#2851]: https://github.com/import-js/eslint-plugin-import/pull/2851
+[#2850]: https://github.com/import-js/eslint-plugin-import/pull/2850
+[#2842]: https://github.com/import-js/eslint-plugin-import/pull/2842
+[#2835]: https://github.com/import-js/eslint-plugin-import/pull/2835
+[#2832]: https://github.com/import-js/eslint-plugin-import/pull/2832
+[#2817]: https://github.com/import-js/eslint-plugin-import/pull/2817
+[#2778]: https://github.com/import-js/eslint-plugin-import/pull/2778
+[#2756]: https://github.com/import-js/eslint-plugin-import/pull/2756
+[#2754]: https://github.com/import-js/eslint-plugin-import/pull/2754
+[#2748]: https://github.com/import-js/eslint-plugin-import/pull/2748
+[#2735]: https://github.com/import-js/eslint-plugin-import/pull/2735
+[#2699]: https://github.com/import-js/eslint-plugin-import/pull/2699
+[#2664]: https://github.com/import-js/eslint-plugin-import/pull/2664
+[#2640]: https://github.com/import-js/eslint-plugin-import/pull/2640
+[#2613]: https://github.com/import-js/eslint-plugin-import/pull/2613
+[#2608]: https://github.com/import-js/eslint-plugin-import/pull/2608
+[#2605]: https://github.com/import-js/eslint-plugin-import/pull/2605
+[#2602]: https://github.com/import-js/eslint-plugin-import/pull/2602
+[#2598]: https://github.com/import-js/eslint-plugin-import/pull/2598
+[#2589]: https://github.com/import-js/eslint-plugin-import/pull/2589
+[#2588]: https://github.com/import-js/eslint-plugin-import/pull/2588
+[#2582]: https://github.com/import-js/eslint-plugin-import/pull/2582
+[#2570]: https://github.com/import-js/eslint-plugin-import/pull/2570
+[#2568]: https://github.com/import-js/eslint-plugin-import/pull/2568
+[#2546]: https://github.com/import-js/eslint-plugin-import/pull/2546
+[#2541]: https://github.com/import-js/eslint-plugin-import/pull/2541
+[#2531]: https://github.com/import-js/eslint-plugin-import/pull/2531
+[#2511]: https://github.com/import-js/eslint-plugin-import/pull/2511
+[#2506]: https://github.com/import-js/eslint-plugin-import/pull/2506
+[#2503]: https://github.com/import-js/eslint-plugin-import/pull/2503
+[#2490]: https://github.com/import-js/eslint-plugin-import/pull/2490
+[#2475]: https://github.com/import-js/eslint-plugin-import/pull/2475
+[#2473]: https://github.com/import-js/eslint-plugin-import/pull/2473
+[#2466]: https://github.com/import-js/eslint-plugin-import/pull/2466
+[#2459]: https://github.com/import-js/eslint-plugin-import/pull/2459
+[#2440]: https://github.com/import-js/eslint-plugin-import/pull/2440
+[#2438]: https://github.com/import-js/eslint-plugin-import/pull/2438
+[#2436]: https://github.com/import-js/eslint-plugin-import/pull/2436
+[#2427]: https://github.com/import-js/eslint-plugin-import/pull/2427
+[#2424]: https://github.com/import-js/eslint-plugin-import/pull/2424
+[#2419]: https://github.com/import-js/eslint-plugin-import/pull/2419
+[#2417]: https://github.com/import-js/eslint-plugin-import/pull/2417
+[#2411]: https://github.com/import-js/eslint-plugin-import/pull/2411
+[#2399]: https://github.com/import-js/eslint-plugin-import/pull/2399
+[#2396]: https://github.com/import-js/eslint-plugin-import/pull/2396
+[#2395]: https://github.com/import-js/eslint-plugin-import/pull/2395
+[#2393]: https://github.com/import-js/eslint-plugin-import/pull/2393
+[#2388]: https://github.com/import-js/eslint-plugin-import/pull/2388
+[#2387]: https://github.com/import-js/eslint-plugin-import/pull/2387
+[#2381]: https://github.com/import-js/eslint-plugin-import/pull/2381
+[#2378]: https://github.com/import-js/eslint-plugin-import/pull/2378
+[#2374]: https://github.com/import-js/eslint-plugin-import/pull/2374
+[#2371]: https://github.com/import-js/eslint-plugin-import/pull/2371
+[#2367]: https://github.com/import-js/eslint-plugin-import/pull/2367
+[#2358]: https://github.com/import-js/eslint-plugin-import/pull/2358
+[#2341]: https://github.com/import-js/eslint-plugin-import/pull/2341
+[#2332]: https://github.com/import-js/eslint-plugin-import/pull/2332
+[#2334]: https://github.com/import-js/eslint-plugin-import/pull/2334
+[#2330]: https://github.com/import-js/eslint-plugin-import/pull/2330
+[#2315]: https://github.com/import-js/eslint-plugin-import/pull/2315
+[#2305]: https://github.com/import-js/eslint-plugin-import/pull/2305
+[#2299]: https://github.com/import-js/eslint-plugin-import/pull/2299
+[#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
+[#2280]: https://github.com/import-js/eslint-plugin-import/pull/2280
+[#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
+[#2220]: https://github.com/import-js/eslint-plugin-import/pull/2220
+[#2219]: https://github.com/import-js/eslint-plugin-import/pull/2219
+[#2212]: https://github.com/import-js/eslint-plugin-import/pull/2212
+[#2196]: https://github.com/import-js/eslint-plugin-import/pull/2196
+[#2194]: https://github.com/import-js/eslint-plugin-import/pull/2194
+[#2191]: https://github.com/import-js/eslint-plugin-import/pull/2191
+[#2184]: https://github.com/import-js/eslint-plugin-import/pull/2184
+[#2179]: https://github.com/import-js/eslint-plugin-import/pull/2179
+[#2160]: https://github.com/import-js/eslint-plugin-import/pull/2160
+[#2158]: https://github.com/import-js/eslint-plugin-import/pull/2158
+[#2156]: https://github.com/import-js/eslint-plugin-import/pull/2156
+[#2149]: https://github.com/import-js/eslint-plugin-import/pull/2149
+[#2146]: https://github.com/import-js/eslint-plugin-import/pull/2146
+[#2140]: https://github.com/import-js/eslint-plugin-import/pull/2140
+[#2138]: https://github.com/import-js/eslint-plugin-import/pull/2138
+[#2121]: https://github.com/import-js/eslint-plugin-import/pull/2121
+[#2112]: https://github.com/import-js/eslint-plugin-import/pull/2112
+[#2099]: https://github.com/import-js/eslint-plugin-import/pull/2099
+[#2097]: https://github.com/import-js/eslint-plugin-import/pull/2097
+[#2090]: https://github.com/import-js/eslint-plugin-import/pull/2090
+[#2087]: https://github.com/import-js/eslint-plugin-import/pull/2087
+[#2083]: https://github.com/import-js/eslint-plugin-import/pull/2083
+[#2075]: https://github.com/import-js/eslint-plugin-import/pull/2075
+[#2071]: https://github.com/import-js/eslint-plugin-import/pull/2071
+[#2047]: https://github.com/import-js/eslint-plugin-import/pull/2047
+[#2034]: https://github.com/import-js/eslint-plugin-import/pull/2034
+[#2028]: https://github.com/import-js/eslint-plugin-import/pull/2028
+[#2026]: https://github.com/import-js/eslint-plugin-import/pull/2026
+[#2022]: https://github.com/import-js/eslint-plugin-import/pull/2022
+[#2021]: https://github.com/import-js/eslint-plugin-import/pull/2021
+[#2012]: https://github.com/import-js/eslint-plugin-import/pull/2012
+[#1997]: https://github.com/import-js/eslint-plugin-import/pull/1997
+[#1993]: https://github.com/import-js/eslint-plugin-import/pull/1993
+[#1990]: https://github.com/import-js/eslint-plugin-import/pull/1990
+[#1985]: https://github.com/import-js/eslint-plugin-import/pull/1985
+[#1983]: https://github.com/import-js/eslint-plugin-import/pull/1983
+[#1974]: https://github.com/import-js/eslint-plugin-import/pull/1974
+[#1958]: https://github.com/import-js/eslint-plugin-import/pull/1958
+[#1948]: https://github.com/import-js/eslint-plugin-import/pull/1948
+[#1947]: https://github.com/import-js/eslint-plugin-import/pull/1947
+[#1944]: https://github.com/import-js/eslint-plugin-import/pull/1944
+[#1940]: https://github.com/import-js/eslint-plugin-import/pull/1940
+[#1897]: https://github.com/import-js/eslint-plugin-import/pull/1897
+[#1889]: https://github.com/import-js/eslint-plugin-import/pull/1889
+[#1878]: https://github.com/import-js/eslint-plugin-import/pull/1878
+[#1860]: https://github.com/import-js/eslint-plugin-import/pull/1860
+[#1848]: https://github.com/import-js/eslint-plugin-import/pull/1848
+[#1847]: https://github.com/import-js/eslint-plugin-import/pull/1847
+[#1846]: https://github.com/import-js/eslint-plugin-import/pull/1846
+[#1836]: https://github.com/import-js/eslint-plugin-import/pull/1836
+[#1835]: https://github.com/import-js/eslint-plugin-import/pull/1835
+[#1833]: https://github.com/import-js/eslint-plugin-import/pull/1833
+[#1831]: https://github.com/import-js/eslint-plugin-import/pull/1831
+[#1830]: https://github.com/import-js/eslint-plugin-import/pull/1830
+[#1824]: https://github.com/import-js/eslint-plugin-import/pull/1824
+[#1823]: https://github.com/import-js/eslint-plugin-import/pull/1823
+[#1822]: https://github.com/import-js/eslint-plugin-import/pull/1822
+[#1820]: https://github.com/import-js/eslint-plugin-import/pull/1820
+[#1819]: https://github.com/import-js/eslint-plugin-import/pull/1819
+[#1802]: https://github.com/import-js/eslint-plugin-import/pull/1802
+[#1788]: https://github.com/import-js/eslint-plugin-import/pull/1788
+[#1786]: https://github.com/import-js/eslint-plugin-import/pull/1786
+[#1785]: https://github.com/import-js/eslint-plugin-import/pull/1785
+[#1776]: https://github.com/import-js/eslint-plugin-import/pull/1776
+[#1770]: https://github.com/import-js/eslint-plugin-import/pull/1770
+[#1764]: https://github.com/import-js/eslint-plugin-import/pull/1764
+[#1763]: https://github.com/import-js/eslint-plugin-import/pull/1763
+[#1751]: https://github.com/import-js/eslint-plugin-import/pull/1751
+[#1744]: https://github.com/import-js/eslint-plugin-import/pull/1744
+[#1736]: https://github.com/import-js/eslint-plugin-import/pull/1736
+[#1735]: https://github.com/import-js/eslint-plugin-import/pull/1735
+[#1726]: https://github.com/import-js/eslint-plugin-import/pull/1726
+[#1724]: https://github.com/import-js/eslint-plugin-import/pull/1724
+[#1719]: https://github.com/import-js/eslint-plugin-import/pull/1719
+[#1696]: https://github.com/import-js/eslint-plugin-import/pull/1696
+[#1691]: https://github.com/import-js/eslint-plugin-import/pull/1691
+[#1690]: https://github.com/import-js/eslint-plugin-import/pull/1690
+[#1689]: https://github.com/import-js/eslint-plugin-import/pull/1689
+[#1681]: https://github.com/import-js/eslint-plugin-import/pull/1681
+[#1676]: https://github.com/import-js/eslint-plugin-import/pull/1676
+[#1666]: https://github.com/import-js/eslint-plugin-import/pull/1666
+[#1664]: https://github.com/import-js/eslint-plugin-import/pull/1664
+[#1660]: https://github.com/import-js/eslint-plugin-import/pull/1660
+[#1658]: https://github.com/import-js/eslint-plugin-import/pull/1658
+[#1651]: https://github.com/import-js/eslint-plugin-import/pull/1651
+[#1626]: https://github.com/import-js/eslint-plugin-import/pull/1626
+[#1620]: https://github.com/import-js/eslint-plugin-import/pull/1620
+[#1619]: https://github.com/import-js/eslint-plugin-import/pull/1619
+[#1612]: https://github.com/import-js/eslint-plugin-import/pull/1612
+[#1611]: https://github.com/import-js/eslint-plugin-import/pull/1611
+[#1605]: https://github.com/import-js/eslint-plugin-import/pull/1605
+[#1586]: https://github.com/import-js/eslint-plugin-import/pull/1586
+[#1572]: https://github.com/import-js/eslint-plugin-import/pull/1572
+[#1569]: https://github.com/import-js/eslint-plugin-import/pull/1569
+[#1563]: https://github.com/import-js/eslint-plugin-import/pull/1563
+[#1560]: https://github.com/import-js/eslint-plugin-import/pull/1560
+[#1551]: https://github.com/import-js/eslint-plugin-import/pull/1551
+[#1542]: https://github.com/import-js/eslint-plugin-import/pull/1542
+[#1534]: https://github.com/import-js/eslint-plugin-import/pull/1534
+[#1528]: https://github.com/import-js/eslint-plugin-import/pull/1528
+[#1526]: https://github.com/import-js/eslint-plugin-import/pull/1526
+[#1521]: https://github.com/import-js/eslint-plugin-import/pull/1521
+[#1519]: https://github.com/import-js/eslint-plugin-import/pull/1519
+[#1517]: https://github.com/import-js/eslint-plugin-import/pull/1517
+[#1507]: https://github.com/import-js/eslint-plugin-import/pull/1507
+[#1506]: https://github.com/import-js/eslint-plugin-import/pull/1506
+[#1496]: https://github.com/import-js/eslint-plugin-import/pull/1496
+[#1495]: https://github.com/import-js/eslint-plugin-import/pull/1495
+[#1494]: https://github.com/import-js/eslint-plugin-import/pull/1494
+[#1493]: https://github.com/import-js/eslint-plugin-import/pull/1493
+[#1491]: https://github.com/import-js/eslint-plugin-import/pull/1491
+[#1472]: https://github.com/import-js/eslint-plugin-import/pull/1472
+[#1470]: https://github.com/import-js/eslint-plugin-import/pull/1470
+[#1447]: https://github.com/import-js/eslint-plugin-import/pull/1447
+[#1439]: https://github.com/import-js/eslint-plugin-import/pull/1439
+[#1436]: https://github.com/import-js/eslint-plugin-import/pull/1436
+[#1435]: https://github.com/import-js/eslint-plugin-import/pull/1435
+[#1425]: https://github.com/import-js/eslint-plugin-import/pull/1425
+[#1419]: https://github.com/import-js/eslint-plugin-import/pull/1419
+[#1412]: https://github.com/import-js/eslint-plugin-import/pull/1412
+[#1409]: https://github.com/import-js/eslint-plugin-import/pull/1409
+[#1404]: https://github.com/import-js/eslint-plugin-import/pull/1404
+[#1401]: https://github.com/import-js/eslint-plugin-import/pull/1401
+[#1393]: https://github.com/import-js/eslint-plugin-import/pull/1393
+[#1389]: https://github.com/import-js/eslint-plugin-import/pull/1389
+[#1386]: https://github.com/import-js/eslint-plugin-import/pull/1386
+[#1377]: https://github.com/import-js/eslint-plugin-import/pull/1377
+[#1375]: https://github.com/import-js/eslint-plugin-import/pull/1375
+[#1372]: https://github.com/import-js/eslint-plugin-import/pull/1372
+[#1371]: https://github.com/import-js/eslint-plugin-import/pull/1371
+[#1370]: https://github.com/import-js/eslint-plugin-import/pull/1370
+[#1363]: https://github.com/import-js/eslint-plugin-import/pull/1363
+[#1360]: https://github.com/import-js/eslint-plugin-import/pull/1360
+[#1358]: https://github.com/import-js/eslint-plugin-import/pull/1358
+[#1356]: https://github.com/import-js/eslint-plugin-import/pull/1356
+[#1354]: https://github.com/import-js/eslint-plugin-import/pull/1354
+[#1352]: https://github.com/import-js/eslint-plugin-import/pull/1352
+[#1347]: https://github.com/import-js/eslint-plugin-import/pull/1347
+[#1345]: https://github.com/import-js/eslint-plugin-import/pull/1345
+[#1342]: https://github.com/import-js/eslint-plugin-import/pull/1342
+[#1340]: https://github.com/import-js/eslint-plugin-import/pull/1340
+[#1333]: https://github.com/import-js/eslint-plugin-import/pull/1333
+[#1331]: https://github.com/import-js/eslint-plugin-import/pull/1331
+[#1330]: https://github.com/import-js/eslint-plugin-import/pull/1330
+[#1320]: https://github.com/import-js/eslint-plugin-import/pull/1320
+[#1319]: https://github.com/import-js/eslint-plugin-import/pull/1319
+[#1312]: https://github.com/import-js/eslint-plugin-import/pull/1312
+[#1308]: https://github.com/import-js/eslint-plugin-import/pull/1308
+[#1304]: https://github.com/import-js/eslint-plugin-import/pull/1304
+[#1297]: https://github.com/import-js/eslint-plugin-import/pull/1297
+[#1295]: https://github.com/import-js/eslint-plugin-import/pull/1295
+[#1294]: https://github.com/import-js/eslint-plugin-import/pull/1294
+[#1290]: https://github.com/import-js/eslint-plugin-import/pull/1290
+[#1277]: https://github.com/import-js/eslint-plugin-import/pull/1277
+[#1262]: https://github.com/import-js/eslint-plugin-import/pull/1262
+[#1257]: https://github.com/import-js/eslint-plugin-import/pull/1257
+[#1253]: https://github.com/import-js/eslint-plugin-import/pull/1253
+[#1248]: https://github.com/import-js/eslint-plugin-import/pull/1248
+[#1238]: https://github.com/import-js/eslint-plugin-import/pull/1238
+[#1237]: https://github.com/import-js/eslint-plugin-import/pull/1237
+[#1235]: https://github.com/import-js/eslint-plugin-import/pull/1235
+[#1234]: https://github.com/import-js/eslint-plugin-import/pull/1234
+[#1232]: https://github.com/import-js/eslint-plugin-import/pull/1232
+[#1223]: https://github.com/import-js/eslint-plugin-import/pull/1223
+[#1222]: https://github.com/import-js/eslint-plugin-import/pull/1222
+[#1218]: https://github.com/import-js/eslint-plugin-import/pull/1218
+[#1176]: https://github.com/import-js/eslint-plugin-import/pull/1176
+[#1163]: https://github.com/import-js/eslint-plugin-import/pull/1163
+[#1157]: https://github.com/import-js/eslint-plugin-import/pull/1157
+[#1151]: https://github.com/import-js/eslint-plugin-import/pull/1151
+[#1142]: https://github.com/import-js/eslint-plugin-import/pull/1142
+[#1139]: https://github.com/import-js/eslint-plugin-import/pull/1139
+[#1137]: https://github.com/import-js/eslint-plugin-import/pull/1137
+[#1135]: https://github.com/import-js/eslint-plugin-import/pull/1135
+[#1128]: https://github.com/import-js/eslint-plugin-import/pull/1128
+[#1126]: https://github.com/import-js/eslint-plugin-import/pull/1126
+[#1122]: https://github.com/import-js/eslint-plugin-import/pull/1122
+[#1112]: https://github.com/import-js/eslint-plugin-import/pull/1112
+[#1107]: https://github.com/import-js/eslint-plugin-import/pull/1107
+[#1106]: https://github.com/import-js/eslint-plugin-import/pull/1106
+[#1105]: https://github.com/import-js/eslint-plugin-import/pull/1105
+[#1093]: https://github.com/import-js/eslint-plugin-import/pull/1093
+[#1085]: https://github.com/import-js/eslint-plugin-import/pull/1085
+[#1068]: https://github.com/import-js/eslint-plugin-import/pull/1068
+[#1049]: https://github.com/import-js/eslint-plugin-import/pull/1049
+[#1046]: https://github.com/import-js/eslint-plugin-import/pull/1046
+[#966]: https://github.com/import-js/eslint-plugin-import/pull/966
+[#944]: https://github.com/import-js/eslint-plugin-import/pull/944
+[#912]: https://github.com/import-js/eslint-plugin-import/pull/912
+[#908]: https://github.com/import-js/eslint-plugin-import/pull/908
+[#891]: https://github.com/import-js/eslint-plugin-import/pull/891
+[#889]: https://github.com/import-js/eslint-plugin-import/pull/889
+[#880]: https://github.com/import-js/eslint-plugin-import/pull/880
+[#871]: https://github.com/import-js/eslint-plugin-import/pull/871
+[#858]: https://github.com/import-js/eslint-plugin-import/pull/858
+[#843]: https://github.com/import-js/eslint-plugin-import/pull/843
+[#804]: https://github.com/import-js/eslint-plugin-import/pull/804
+[#797]: https://github.com/import-js/eslint-plugin-import/pull/797
+[#794]: https://github.com/import-js/eslint-plugin-import/pull/794
+[#744]: https://github.com/import-js/eslint-plugin-import/pull/744
+[#742]: https://github.com/import-js/eslint-plugin-import/pull/742
+[#737]: https://github.com/import-js/eslint-plugin-import/pull/737
+[#727]: https://github.com/import-js/eslint-plugin-import/pull/727
+[#721]: https://github.com/import-js/eslint-plugin-import/pull/721
+[#712]: https://github.com/import-js/eslint-plugin-import/pull/712
+[#696]: https://github.com/import-js/eslint-plugin-import/pull/696
+[#685]: https://github.com/import-js/eslint-plugin-import/pull/685
+[#680]: https://github.com/import-js/eslint-plugin-import/pull/680
+[#654]: https://github.com/import-js/eslint-plugin-import/pull/654
+[#639]: https://github.com/import-js/eslint-plugin-import/pull/639
+[#632]: https://github.com/import-js/eslint-plugin-import/pull/632
+[#630]: https://github.com/import-js/eslint-plugin-import/pull/630
+[#629]: https://github.com/import-js/eslint-plugin-import/pull/629
+[#628]: https://github.com/import-js/eslint-plugin-import/pull/628
+[#596]: https://github.com/import-js/eslint-plugin-import/pull/596
+[#586]: https://github.com/import-js/eslint-plugin-import/pull/586
+[#578]: https://github.com/import-js/eslint-plugin-import/pull/578
+[#568]: https://github.com/import-js/eslint-plugin-import/pull/568
+[#555]: https://github.com/import-js/eslint-plugin-import/pull/555
+[#538]: https://github.com/import-js/eslint-plugin-import/pull/538
+[#527]: https://github.com/import-js/eslint-plugin-import/pull/527
+[#518]: https://github.com/import-js/eslint-plugin-import/pull/518
+[#509]: https://github.com/import-js/eslint-plugin-import/pull/509
+[#508]: https://github.com/import-js/eslint-plugin-import/pull/508
+[#503]: https://github.com/import-js/eslint-plugin-import/pull/503
+[#499]: https://github.com/import-js/eslint-plugin-import/pull/499
+[#489]: https://github.com/import-js/eslint-plugin-import/pull/489
+[#485]: https://github.com/import-js/eslint-plugin-import/pull/485
+[#461]: https://github.com/import-js/eslint-plugin-import/pull/461
+[#449]: https://github.com/import-js/eslint-plugin-import/pull/449
+[#444]: https://github.com/import-js/eslint-plugin-import/pull/444
+[#428]: https://github.com/import-js/eslint-plugin-import/pull/428
+[#395]: https://github.com/import-js/eslint-plugin-import/pull/395
+[#371]: https://github.com/import-js/eslint-plugin-import/pull/371
+[#365]: https://github.com/import-js/eslint-plugin-import/pull/365
+[#359]: https://github.com/import-js/eslint-plugin-import/pull/359
+[#343]: https://github.com/import-js/eslint-plugin-import/pull/343
+[#332]: https://github.com/import-js/eslint-plugin-import/pull/332
+[#322]: https://github.com/import-js/eslint-plugin-import/pull/322
+[#321]: https://github.com/import-js/eslint-plugin-import/pull/321
+[#316]: https://github.com/import-js/eslint-plugin-import/pull/316
+[#314]: https://github.com/import-js/eslint-plugin-import/pull/314
+[#308]: https://github.com/import-js/eslint-plugin-import/pull/308
+[#298]: https://github.com/import-js/eslint-plugin-import/pull/298
+[#297]: https://github.com/import-js/eslint-plugin-import/pull/297
+[#296]: https://github.com/import-js/eslint-plugin-import/pull/296
+[#290]: https://github.com/import-js/eslint-plugin-import/pull/290
+[#288]: https://github.com/import-js/eslint-plugin-import/pull/288
+[#261]: https://github.com/import-js/eslint-plugin-import/pull/261
+[#256]: https://github.com/import-js/eslint-plugin-import/pull/256
+[#254]: https://github.com/import-js/eslint-plugin-import/pull/254
+[#250]: https://github.com/import-js/eslint-plugin-import/pull/250
+[#247]: https://github.com/import-js/eslint-plugin-import/pull/247
+[#245]: https://github.com/import-js/eslint-plugin-import/pull/245
+[#243]: https://github.com/import-js/eslint-plugin-import/pull/243
+[#241]: https://github.com/import-js/eslint-plugin-import/pull/241
+[#239]: https://github.com/import-js/eslint-plugin-import/pull/239
+[#228]: https://github.com/import-js/eslint-plugin-import/pull/228
+[#211]: https://github.com/import-js/eslint-plugin-import/pull/211
+[#157]: https://github.com/import-js/eslint-plugin-import/pull/157
+
+[ljharb#37]: https://github.com/ljharb/eslint-plugin-import/pull/37
+
+[#2930]: https://github.com/import-js/eslint-plugin-import/issues/2930
+[#2687]: https://github.com/import-js/eslint-plugin-import/issues/2687
+[#2684]: https://github.com/import-js/eslint-plugin-import/issues/2684
+[#2682]: https://github.com/import-js/eslint-plugin-import/issues/2682
+[#2674]: https://github.com/import-js/eslint-plugin-import/issues/2674
+[#2668]: https://github.com/import-js/eslint-plugin-import/issues/2668
+[#2666]: https://github.com/import-js/eslint-plugin-import/issues/2666
+[#2665]: https://github.com/import-js/eslint-plugin-import/issues/2665
+[#2577]: https://github.com/import-js/eslint-plugin-import/issues/2577
+[#2447]: https://github.com/import-js/eslint-plugin-import/issues/2447
+[#2444]: https://github.com/import-js/eslint-plugin-import/issues/2444
+[#2412]: https://github.com/import-js/eslint-plugin-import/issues/2412
+[#2392]: https://github.com/import-js/eslint-plugin-import/issues/2392
+[#2340]: https://github.com/import-js/eslint-plugin-import/issues/2340
+[#2255]: https://github.com/import-js/eslint-plugin-import/issues/2255
+[#2210]: https://github.com/import-js/eslint-plugin-import/issues/2210
+[#2201]: https://github.com/import-js/eslint-plugin-import/issues/2201
+[#2199]: https://github.com/import-js/eslint-plugin-import/issues/2199
+[#2161]: https://github.com/import-js/eslint-plugin-import/issues/2161
+[#2118]: https://github.com/import-js/eslint-plugin-import/issues/2118
+[#2067]: https://github.com/import-js/eslint-plugin-import/issues/2067
+[#2063]: https://github.com/import-js/eslint-plugin-import/issues/2063
+[#2056]: https://github.com/import-js/eslint-plugin-import/issues/2056
+[#1998]: https://github.com/import-js/eslint-plugin-import/issues/1998
+[#1965]: https://github.com/import-js/eslint-plugin-import/issues/1965
+[#1924]: https://github.com/import-js/eslint-plugin-import/issues/1924
+[#1854]: https://github.com/import-js/eslint-plugin-import/issues/1854
+[#1841]: https://github.com/import-js/eslint-plugin-import/issues/1841
+[#1834]: https://github.com/import-js/eslint-plugin-import/issues/1834
+[#1814]: https://github.com/import-js/eslint-plugin-import/issues/1814
+[#1811]: https://github.com/import-js/eslint-plugin-import/issues/1811
+[#1808]: https://github.com/import-js/eslint-plugin-import/issues/1808
+[#1805]: https://github.com/import-js/eslint-plugin-import/issues/1805
+[#1801]: https://github.com/import-js/eslint-plugin-import/issues/1801
+[#1722]: https://github.com/import-js/eslint-plugin-import/issues/1722
+[#1704]: https://github.com/import-js/eslint-plugin-import/issues/1704
+[#1702]: https://github.com/import-js/eslint-plugin-import/issues/1702
+[#1635]: https://github.com/import-js/eslint-plugin-import/issues/1635
+[#1631]: https://github.com/import-js/eslint-plugin-import/issues/1631
+[#1616]: https://github.com/import-js/eslint-plugin-import/issues/1616
+[#1613]: https://github.com/import-js/eslint-plugin-import/issues/1613
+[#1590]: https://github.com/import-js/eslint-plugin-import/issues/1590
+[#1589]: https://github.com/import-js/eslint-plugin-import/issues/1589
+[#1565]: https://github.com/import-js/eslint-plugin-import/issues/1565
+[#1366]: https://github.com/import-js/eslint-plugin-import/issues/1366
+[#1334]: https://github.com/import-js/eslint-plugin-import/issues/1334
+[#1323]: https://github.com/import-js/eslint-plugin-import/issues/1323
+[#1322]: https://github.com/import-js/eslint-plugin-import/issues/1322
+[#1300]: https://github.com/import-js/eslint-plugin-import/issues/1300
+[#1293]: https://github.com/import-js/eslint-plugin-import/issues/1293
+[#1266]: https://github.com/import-js/eslint-plugin-import/issues/1266
+[#1256]: https://github.com/import-js/eslint-plugin-import/issues/1256
+[#1233]: https://github.com/import-js/eslint-plugin-import/issues/1233
+[#1175]: https://github.com/import-js/eslint-plugin-import/issues/1175
+[#1166]: https://github.com/import-js/eslint-plugin-import/issues/1166
+[#1144]: https://github.com/import-js/eslint-plugin-import/issues/1144
+[#1058]: https://github.com/import-js/eslint-plugin-import/issues/1058
+[#1035]: https://github.com/import-js/eslint-plugin-import/issues/1035
+[#931]: https://github.com/import-js/eslint-plugin-import/issues/931
+[#886]: https://github.com/import-js/eslint-plugin-import/issues/886
+[#863]: https://github.com/import-js/eslint-plugin-import/issues/863
+[#842]: https://github.com/import-js/eslint-plugin-import/issues/842
+[#839]: https://github.com/import-js/eslint-plugin-import/issues/839
+[#795]: https://github.com/import-js/eslint-plugin-import/issues/795
+[#793]: https://github.com/import-js/eslint-plugin-import/issues/793
+[#720]: https://github.com/import-js/eslint-plugin-import/issues/720
+[#717]: https://github.com/import-js/eslint-plugin-import/issues/717
+[#686]: https://github.com/import-js/eslint-plugin-import/issues/686
+[#671]: https://github.com/import-js/eslint-plugin-import/issues/671
+[#660]: https://github.com/import-js/eslint-plugin-import/issues/660
+[#653]: https://github.com/import-js/eslint-plugin-import/issues/653
+[#627]: https://github.com/import-js/eslint-plugin-import/issues/627
+[#620]: https://github.com/import-js/eslint-plugin-import/issues/620
+[#609]: https://github.com/import-js/eslint-plugin-import/issues/609
+[#604]: https://github.com/import-js/eslint-plugin-import/issues/604
+[#602]: https://github.com/import-js/eslint-plugin-import/issues/602
+[#601]: https://github.com/import-js/eslint-plugin-import/issues/601
+[#592]: https://github.com/import-js/eslint-plugin-import/issues/592
+[#577]: https://github.com/import-js/eslint-plugin-import/issues/577
+[#570]: https://github.com/import-js/eslint-plugin-import/issues/570
+[#567]: https://github.com/import-js/eslint-plugin-import/issues/567
+[#566]: https://github.com/import-js/eslint-plugin-import/issues/566
+[#545]: https://github.com/import-js/eslint-plugin-import/issues/545
+[#530]: https://github.com/import-js/eslint-plugin-import/issues/530
+[#529]: https://github.com/import-js/eslint-plugin-import/issues/529
+[#519]: https://github.com/import-js/eslint-plugin-import/issues/519
+[#507]: https://github.com/import-js/eslint-plugin-import/issues/507
+[#484]: https://github.com/import-js/eslint-plugin-import/issues/484
+[#478]: https://github.com/import-js/eslint-plugin-import/issues/478
+[#456]: https://github.com/import-js/eslint-plugin-import/issues/456
+[#453]: https://github.com/import-js/eslint-plugin-import/issues/453
+[#452]: https://github.com/import-js/eslint-plugin-import/issues/452
+[#447]: https://github.com/import-js/eslint-plugin-import/issues/447
+[#441]: https://github.com/import-js/eslint-plugin-import/issues/441
+[#423]: https://github.com/import-js/eslint-plugin-import/issues/423
+[#416]: https://github.com/import-js/eslint-plugin-import/issues/416
+[#415]: https://github.com/import-js/eslint-plugin-import/issues/415
+[#402]: https://github.com/import-js/eslint-plugin-import/issues/402
+[#386]: https://github.com/import-js/eslint-plugin-import/issues/386
+[#373]: https://github.com/import-js/eslint-plugin-import/issues/373
+[#370]: https://github.com/import-js/eslint-plugin-import/issues/370
+[#348]: https://github.com/import-js/eslint-plugin-import/issues/348
+[#342]: https://github.com/import-js/eslint-plugin-import/issues/342
+[#328]: https://github.com/import-js/eslint-plugin-import/issues/328
+[#317]: https://github.com/import-js/eslint-plugin-import/issues/317
+[#313]: https://github.com/import-js/eslint-plugin-import/issues/313
+[#311]: https://github.com/import-js/eslint-plugin-import/issues/311
+[#306]: https://github.com/import-js/eslint-plugin-import/issues/306
+[#283]: https://github.com/import-js/eslint-plugin-import/issues/283
+[#281]: https://github.com/import-js/eslint-plugin-import/issues/281
+[#275]: https://github.com/import-js/eslint-plugin-import/issues/275
+[#272]: https://github.com/import-js/eslint-plugin-import/issues/272
+[#270]: https://github.com/import-js/eslint-plugin-import/issues/270
+[#267]: https://github.com/import-js/eslint-plugin-import/issues/267
+[#266]: https://github.com/import-js/eslint-plugin-import/issues/266
+[#216]: https://github.com/import-js/eslint-plugin-import/issues/216
+[#214]: https://github.com/import-js/eslint-plugin-import/issues/214
+[#210]: https://github.com/import-js/eslint-plugin-import/issues/210
+[#200]: https://github.com/import-js/eslint-plugin-import/issues/200
+[#192]: https://github.com/import-js/eslint-plugin-import/issues/192
+[#191]: https://github.com/import-js/eslint-plugin-import/issues/191
+[#189]: https://github.com/import-js/eslint-plugin-import/issues/189
+[#170]: https://github.com/import-js/eslint-plugin-import/issues/170
+[#155]: https://github.com/import-js/eslint-plugin-import/issues/155
+[#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.31.0...HEAD
+[2.31.0]: https://github.com/import-js/eslint-plugin-import/compare/v2.30.0...v2.31.0
+[2.30.0]: https://github.com/import-js/eslint-plugin-import/compare/v2.29.1...v2.30.0
+[2.29.1]: https://github.com/import-js/eslint-plugin-import/compare/v2.29.0...v2.29.1
+[2.29.0]: https://github.com/import-js/eslint-plugin-import/compare/v2.28.1...v2.29.0
+[2.28.1]: https://github.com/import-js/eslint-plugin-import/compare/v2.28.0...v2.28.1
+[2.28.0]: https://github.com/import-js/eslint-plugin-import/compare/v2.27.5...v2.28.0
+[2.27.5]: https://github.com/import-js/eslint-plugin-import/compare/v2.27.4...v2.27.5
+[2.27.4]: https://github.com/import-js/eslint-plugin-import/compare/v2.27.3...v2.27.4
+[2.27.3]: https://github.com/import-js/eslint-plugin-import/compare/v2.27.2...v2.27.3
+[2.27.2]: https://github.com/import-js/eslint-plugin-import/compare/v2.27.1...v2.27.2
+[2.27.1]: https://github.com/import-js/eslint-plugin-import/compare/v2.27.0...v2.27.1
+[2.27.0]: https://github.com/import-js/eslint-plugin-import/compare/v2.26.0...v2.27.0
+[2.26.0]: https://github.com/import-js/eslint-plugin-import/compare/v2.25.4...v2.26.0
+[2.25.4]: https://github.com/import-js/eslint-plugin-import/compare/v2.25.3...v2.25.4
+[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
+[2.24.2]: https://github.com/import-js/eslint-plugin-import/compare/v2.24.1...v2.24.2
+[2.24.1]: https://github.com/import-js/eslint-plugin-import/compare/v2.24.0...v2.24.1
+[2.24.0]: https://github.com/import-js/eslint-plugin-import/compare/v2.23.4...v2.24.0
+[2.23.4]: https://github.com/import-js/eslint-plugin-import/compare/v2.23.3...v2.23.4
+[2.23.3]: https://github.com/import-js/eslint-plugin-import/compare/v2.23.2...v2.23.3
+[2.23.2]: https://github.com/import-js/eslint-plugin-import/compare/v2.23.1...v2.23.2
+[2.23.1]: https://github.com/import-js/eslint-plugin-import/compare/v2.23.0...v2.23.1
+[2.23.0]: https://github.com/import-js/eslint-plugin-import/compare/v2.22.1...v2.23.0
+[2.22.1]: https://github.com/import-js/eslint-plugin-import/compare/v2.22.0...v2.22.1
+[2.22.0]: https://github.com/import-js/eslint-plugin-import/compare/v2.21.1...v2.22.0
+[2.21.2]: https://github.com/import-js/eslint-plugin-import/compare/v2.21.1...v2.21.2
+[2.21.1]: https://github.com/import-js/eslint-plugin-import/compare/v2.21.0...v2.21.1
+[2.21.0]: https://github.com/import-js/eslint-plugin-import/compare/v2.20.1...v2.21.0
+[2.20.1]: https://github.com/import-js/eslint-plugin-import/compare/v2.20.0...v2.20.1
+[2.20.0]: https://github.com/import-js/eslint-plugin-import/compare/v2.19.1...v2.20.0
+[2.19.1]: https://github.com/import-js/eslint-plugin-import/compare/v2.19.0...v2.19.1
+[2.19.0]: https://github.com/import-js/eslint-plugin-import/compare/v2.18.2...v2.19.0
+[2.18.2]: https://github.com/import-js/eslint-plugin-import/compare/v2.18.1...v2.18.2
+[2.18.1]: https://github.com/import-js/eslint-plugin-import/compare/v2.18.0...v2.18.1
+[2.18.0]: https://github.com/import-js/eslint-plugin-import/compare/v2.17.3...v2.18.0
+[2.17.3]: https://github.com/import-js/eslint-plugin-import/compare/v2.17.2...v2.17.3
+[2.17.2]: https://github.com/import-js/eslint-plugin-import/compare/v2.17.1...v2.17.2
+[2.17.1]: https://github.com/import-js/eslint-plugin-import/compare/v2.17.0...v2.17.1
+[2.17.0]: https://github.com/import-js/eslint-plugin-import/compare/v2.16.0...v2.17.0
+[2.16.0]: https://github.com/import-js/eslint-plugin-import/compare/v2.15.0...v2.16.0
+[2.15.0]: https://github.com/import-js/eslint-plugin-import/compare/v2.14.0...v2.15.0
+[2.14.0]: https://github.com/import-js/eslint-plugin-import/compare/v2.13.0...v2.14.0
+[2.13.0]: https://github.com/import-js/eslint-plugin-import/compare/v2.12.0...v2.13.0
+[2.12.0]: https://github.com/import-js/eslint-plugin-import/compare/v2.11.0...v2.12.0
+[2.11.0]: https://github.com/import-js/eslint-plugin-import/compare/v2.10.0...v2.11.0
+[2.10.0]: https://github.com/import-js/eslint-plugin-import/compare/v2.9.0...v2.10.0
+[2.9.0]: https://github.com/import-js/eslint-plugin-import/compare/v2.8.0...v2.9.0
+[2.8.0]: https://github.com/import-js/eslint-plugin-import/compare/v2.7.0...v2.8.0
+[2.7.0]: https://github.com/import-js/eslint-plugin-import/compare/v2.6.1...v2.7.0
+[2.6.1]: https://github.com/import-js/eslint-plugin-import/compare/v2.6.0...v2.6.1
+[2.6.0]: https://github.com/import-js/eslint-plugin-import/compare/v2.5.0...v2.6.0
+[2.5.0]: https://github.com/import-js/eslint-plugin-import/compare/v2.4.0...v2.5.0
+[2.4.0]: https://github.com/import-js/eslint-plugin-import/compare/v2.3.0...v2.4.0
+[2.3.0]: https://github.com/import-js/eslint-plugin-import/compare/v2.2.0...v2.3.0
+[2.2.0]: https://github.com/import-js/eslint-plugin-import/compare/v2.1.0...v2.2.0
+[2.1.0]: https://github.com/import-js/eslint-plugin-import/compare/v2.0.1...v2.1.0
+[2.0.1]: https://github.com/import-js/eslint-plugin-import/compare/v2.0.0...v2.0.1
+[2.0.0]: https://github.com/import-js/eslint-plugin-import/compare/v1.16.0...v2.0.0
+[1.16.0]: https://github.com/import-js/eslint-plugin-import/compare/v1.15.0...v1.16.0
+[1.15.0]: https://github.com/import-js/eslint-plugin-import/compare/v1.14.0...v1.15.0
+[1.14.0]: https://github.com/import-js/eslint-plugin-import/compare/v1.13.0...v1.14.0
+[1.13.0]: https://github.com/import-js/eslint-plugin-import/compare/v1.12.0...v1.13.0
+[1.12.0]: https://github.com/import-js/eslint-plugin-import/compare/v1.11.1...v1.12.0
+[1.11.1]: https://github.com/import-js/eslint-plugin-import/compare/v1.11.0...v1.11.1
+[1.11.0]: https://github.com/import-js/eslint-plugin-import/compare/v1.10.3...v1.11.0
+[1.10.3]: https://github.com/import-js/eslint-plugin-import/compare/v1.10.2...v1.10.3
+[1.10.2]: https://github.com/import-js/eslint-plugin-import/compare/v1.10.1...v1.10.2
+[1.10.1]: https://github.com/import-js/eslint-plugin-import/compare/v1.10.0...v1.10.1
+[1.10.0]: https://github.com/import-js/eslint-plugin-import/compare/v1.9.2...v1.10.0
+[1.9.2]: https://github.com/import-js/eslint-plugin-import/compare/v1.9.1...v1.9.2
+[1.9.1]: https://github.com/import-js/eslint-plugin-import/compare/v1.9.0...v1.9.1
+[1.9.0]: https://github.com/import-js/eslint-plugin-import/compare/v1.8.1...v1.9.0
+[1.8.1]: https://github.com/import-js/eslint-plugin-import/compare/v1.8.0...v1.8.1
+[1.8.0]: https://github.com/import-js/eslint-plugin-import/compare/v1.7.0...v1.8.0
+[1.7.0]: https://github.com/import-js/eslint-plugin-import/compare/v1.6.1...v1.7.0
+[1.6.1]: https://github.com/import-js/eslint-plugin-import/compare/v1.6.0...v1.6.1
+[1.6.0]: https://github.com/import-js/eslint-plugin-import/compare/v1.5.0...1.6.0
+[1.5.0]: https://github.com/import-js/eslint-plugin-import/compare/v1.4.0...v1.5.0
+[1.4.0]: https://github.com/import-js/eslint-plugin-import/compare/v1.3.0...v1.4.0
+[1.3.0]: https://github.com/import-js/eslint-plugin-import/compare/v1.2.0...v1.3.0
+[1.2.0]: https://github.com/import-js/eslint-plugin-import/compare/v1.1.0...v1.2.0
+[1.1.0]: https://github.com/import-js/eslint-plugin-import/compare/v1.0.4...v1.1.0
+[1.0.4]: https://github.com/import-js/eslint-plugin-import/compare/v1.0.3...v1.0.4
+[1.0.3]: https://github.com/import-js/eslint-plugin-import/compare/v1.0.2...v1.0.3
+[1.0.2]: https://github.com/import-js/eslint-plugin-import/compare/v1.0.1...v1.0.2
+[1.0.1]: https://github.com/import-js/eslint-plugin-import/compare/v1.0.0...v1.0.1
+[1.0.0]: https://github.com/import-js/eslint-plugin-import/compare/v1.0.0-beta.0...v1.0.0
+[1.0.0-beta.0]: https://github.com/import-js/eslint-plugin-import/compare/v0.13.0...v1.0.0-beta.0
+[0.13.0]: https://github.com/import-js/eslint-plugin-import/compare/v0.12.1...v0.13.0
+[0.12.2]: https://github.com/import-js/eslint-plugin-import/compare/v0.12.1...v0.12.2
+[0.12.1]: https://github.com/import-js/eslint-plugin-import/compare/v0.12.0...v0.12.1
+[0.12.0]: https://github.com/import-js/eslint-plugin-import/compare/v0.11.0...v0.12.0
+[0.11.0]: https://github.com/import-js/eslint-plugin-import/compare/v0.10.1...v0.11.0
 
-[@mathieudutour]: https://github.com/mathieudutour
+[@1pete]: https://github.com/1pete
+[@3nuc]: https://github.com/3nuc
+[@aamulumi]: https://github.com/aamulumi
+[@aberezkin]: https://github.com/aberezkin
+[@adamborowski]: https://github.com/adamborowski
+[@adjerbetian]: https://github.com/adjerbetian
+[@AdriAt360]: https://github.com/AdriAt360
+[@ai]: https://github.com/ai
+[@aks-]: https://github.com/aks-
+[@akwodkiewicz]: https://github.com/akwodkiewicz
+[@aladdin-add]: https://github.com/aladdin-add
+[@alex-page]: https://github.com/alex-page
+[@alexgorbatchev]: https://github.com/alexgorbatchev
+[@amsardesai]: https://github.com/amsardesai
+[@andreubotella]: https://github.com/andreubotella
+[@AndrewLeedham]: https://github.com/AndrewLeedham
+[@andyogo]: https://github.com/andyogo
+[@aravindet]: https://github.com/aravindet
+[@arvigeus]: https://github.com/arvigeus
+[@asapach]: https://github.com/asapach
+[@astorije]: https://github.com/astorije
+[@atav32]: https://github.com/atav32
+[@atikenny]: https://github.com/atikenny
+[@atos1990]: https://github.com/atos1990
+[@azyzz228]: https://github.com/azyzz228
+[@barbogast]: https://github.com/barbogast
+[@BarryThePenguin]: https://github.com/BarryThePenguin
+[@be5invis]: https://github.com/be5invis
+[@beatrizrezener]: https://github.com/beatrizrezener
+[@benasher44]: https://github.com/benasher44
+[@benkrejci]: https://github.com/benkrejci
+[@benmosher]: https://github.com/benmosher
+[@benmunro]: https://github.com/benmunro
+[@BenoitZugmeyer]: https://github.com/BenoitZugmeyer
+[@bertyhell]: https://github.com/bertyhell
+[@bicstone]: https://github.com/bicstone
+[@Blasz]: https://github.com/Blasz
+[@bmish]: https://github.com/bmish
+[@borisyankov]: https://github.com/borisyankov
+[@bradennapier]: https://github.com/bradennapier
+[@bradzacher]: https://github.com/bradzacher
+[@brendo]: https://github.com/brendo
+[@brettz9]: https://github.com/brettz9
+[@chabb]: https://github.com/chabb
+[@Chamion]: https://github.com/Chamion
+[@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
+[@christophercurrie]: https://github.com/christophercurrie
+[@cristobal]: https://github.com/cristobal
+[@DamienCassou]: https://github.com/DamienCassou
+[@danny-andrews]: https://github.com/dany-andrews
+[@darkartur]: https://github.com/darkartur
+[@davidbonnet]: https://github.com/davidbonnet
+[@dbrewer5]: https://github.com/dbrewer5
+[@developer-bandi]: https://github.com/developer-bandi
+[@devinrhode2]: https://github.com/devinrhode2
+[@devongovett]: https://github.com/devongovett
+[@dmnd]: https://github.com/dmnd
+[@domdomegg]: https://github.com/domdomegg
+[@duncanbeevers]: https://github.com/duncanbeevers
+[@dwardu]: https://github.com/dwardu
+[@echenley]: https://github.com/echenley
+[@edemaine]: https://github.com/edemaine
+[@eelyafi]: https://github.com/eelyafi
+[@Ephem]: https://github.com/Ephem
+[@ephys]: https://github.com/ephys
+[@eps1lon]: https://github.com/eps1lon
+[@ernestostifano]: https://github.com/ernestostifano
+[@ertrzyiks]: https://github.com/ertrzyiks
+[@fa93hws]: https://github.com/fa93hws
+[@Fdawgs]: https://github.com/Fdawgs
+[@fengkfengk]: https://github.com/fengkfengk
+[@fernandopasik]: https://github.com/fernandopasik
+[@feychenie]: https://github.com/feychenie
+[@fisker]: https://github.com/fisker
+[@FloEdelmann]: https://github.com/FloEdelmann
+[@fooloomanzoo]: https://github.com/fooloomanzoo
+[@foray1010]: https://github.com/foray1010
+[@forivall]: https://github.com/forivall
+[@fsmaia]: https://github.com/fsmaia
+[@fson]: https://github.com/fson
+[@futpib]: https://github.com/futpib
+[@G-Rath]: https://github.com/G-Rath
+[@gajus]: https://github.com/gajus
 [@gausie]: https://github.com/gausie
-[@singles]: https://github.com/singles
+[@gavriguy]: https://github.com/gavriguy
+[@georeith]: https://github.com/georeith
+[@giodamelio]: https://github.com/giodamelio
+[@gnprice]: https://github.com/gnprice
+[@GoldStrikeArch]: https://github.com/GoldStrikeArch
+[@golergka]: https://github.com/golergka
+[@golopot]: https://github.com/golopot
+[@GoodForOneFare]: https://github.com/GoodForOneFare
+[@graingert]: https://github.com/graingert
+[@grit96]: https://github.com/grit96
+[@guilhermelimak]: https://github.com/guilhermelimak
+[@guillaumewuip]: https://github.com/guillaumewuip
+[@hayes]: https://github.com/hayes
+[@himynameisdave]: https://github.com/himynameisdave
+[@hulkish]: https://github.com/hulkish
+[@hyperupcall]: https://github.com/hyperupcall
+[@Hypnosphi]: https://github.com/Hypnosphi
+[@intellix]: https://github.com/intellix
+[@isiahmeadows]: https://github.com/isiahmeadows
+[@IvanGoncharov]: https://github.com/IvanGoncharov
+[@ivo-stefchev]: https://github.com/ivo-stefchev
+[@jablko]: https://github.com/jablko
+[@jakubsta]: https://github.com/jakubsta
+[@jeffshaver]: https://github.com/jeffshaver
+[@jf248]: https://github.com/jf248
 [@jfmengels]: https://github.com/jfmengels
-[@lo1tuma]: https://github.com/lo1tuma
-[@dmnd]: https://github.com/dmnd
-[@lemonmade]: https://github.com/lemonmade
+[@JiangWeixian]: https://github.com/JiangWeixian
 [@jimbolla]: https://github.com/jimbolla
-[@jquense]: https://github.com/jquense
+[@jkimbo]: https://github.com/jkimbo
+[@joaovieira]: https://github.com/joaovieira
+[@joe-matsec]: https://github.com/joe-matsec
+[@joeyguerra]: https://github.com/joeyguerra
+[@johndevedu]: https://github.com/johndevedu
+[@johnthagen]: https://github.com/johnthagen
 [@jonboiser]: https://github.com/jonboiser
-[@taion]: https://github.com/taion
-[@strawbrary]: https://github.com/strawbrary
-[@SimenB]: https://github.com/SimenB
 [@josh]: https://github.com/josh
-[@borisyankov]: https://github.com/borisyankov
-[@gavriguy]: https://github.com/gavriguy
-[@jkimbo]: https://github.com/jkimbo
-[@le0nik]: https://github.com/le0nik
-[@scottnonnenberg]: https://github.com/scottnonnenberg
-[@sindresorhus]: https://github.com/sindresorhus
-[@ljharb]: https://github.com/ljharb
-[@rhettlivingston]: https://github.com/rhettlivingston
-[@zloirock]: https://github.com/zloirock
-[@rhys-vdw]: https://github.com/rhys-vdw
-[@wKich]: https://github.com/wKich
-[@tizmagik]: https://github.com/tizmagik
-[@knpwrs]: https://github.com/knpwrs
-[@spalger]: https://github.com/spalger
-[@preco21]: https://github.com/preco21
-[@skyrpex]: https://github.com/skyrpex
-[@fson]: https://github.com/fson
-[@ntdb]: https://github.com/ntdb
-[@jakubsta]: https://github.com/jakubsta
-[@wtgtybhertgeghgtwtg]: https://github.com/wtgtybhertgeghgtwtg
-[@duncanbeevers]: https://github.com/duncanbeevers
-[@giodamelio]: https://github.com/giodamelio
-[@ntdb]: https://github.com/ntdb
-[@ramasilveyra]: https://github.com/ramasilveyra
-[@sompylasar]: https://github.com/sompylasar
-[@kevin940726]: https://github.com/kevin940726
-[@eelyafi]: https://github.com/eelyafi
-[@mastilver]: https://github.com/mastilver
+[@joshuaobrien]: https://github.com/joshuaobrien
+[@JounQin]: https://github.com/JounQin
+[@jquense]: https://github.com/jquense
 [@jseminck]: https://github.com/jseminck
-[@laysent]: https://github.com/laysent
+[@julien1619]: https://github.com/julien1619
+[@justinanastos]: https://github.com/justinanastos
+[@jwbth]: https://github.com/jwbth
 [@k15a]: https://github.com/k15a
-[@mplewis]: https://github.com/mplewis
-[@rosswarren]: https://github.com/rosswarren
-[@alexgorbatchev]: https://github.com/alexgorbatchev
-[@tihonove]: https://github.com/tihonove
-[@robertrossmann]: https://github.com/robertrossmann
-[@isiahmeadows]: https://github.com/isiahmeadows
-[@graingert]: https://github.com/graingert
-[@danny-andrews]: https://github.com/dany-andrews
-[@fengkfengk]: https://github.com/fengkfengk
-[@futpib]: https://github.com/futpib
+[@kentcdodds]: https://github.com/kentcdodds
+[@kevin940726]: https://github.com/kevin940726
+[@kgregory]: https://github.com/kgregory
+[@kinland]: https://github.com/kinland
+[@kirill-konshin]: https://github.com/kirill-konshin
+[@kiwka]: https://github.com/kiwka
 [@klimashkin]: https://github.com/klimashkin
+[@kmui2]: https://github.com/kmui2
+[@knpwrs]: https://github.com/knpwrs
+[@KostyaZgara]: https://github.com/KostyaZgara
+[@kylemh]: https://github.com/kylemh
+[@laurens-dg]: https://github.com/laurens-dg
+[@laysent]: https://github.com/laysent
+[@le0nik]: https://github.com/le0nik
+[@leipert]: https://github.com/leipert
+[@lemonmade]: https://github.com/lemonmade
+[@lencioni]: https://github.com/lencioni
+[@leonardodino]: https://github.com/leonardodino
+[@Librazy]: https://github.com/Librazy
+[@liby]: https://github.com/liby
+[@lilling]: https://github.com/lilling
+[@liuxingbaoyu]: https://github.com/liuxingbaoyu
+[@ljharb]: https://github.com/ljharb
+[@ljqx]: https://github.com/ljqx
+[@lnuvy]: https://github.com/lnuvy
+[@lo1tuma]: https://github.com/lo1tuma
+[@loganfsmyth]: https://github.com/loganfsmyth
+[@luczsoma]: https://github.com/luczsoma
+[@ludofischer]: https://github.com/ludofischer
+[@Lukas-Kullmann]: https://github.com/Lukas-Kullmann
 [@lukeapage]: https://github.com/lukeapage
+[@lydell]: https://github.com/lydell
+[@magarcia]: https://github.com/magarcia
+[@Mairu]: https://github.com/Mairu
+[@malykhinvi]: https://github.com/malykhinvi
 [@manovotny]: https://github.com/manovotny
+[@manuth]: https://github.com/manuth
+[@marcusdarmstrong]: https://github.com/marcusdarmstrong
+[@mastilver]: https://github.com/mastilver
+[@mathieudutour]: https://github.com/mathieudutour
+[@MatthiasKunnen]: https://github.com/MatthiasKunnen
 [@mattijsbliek]: https://github.com/mattijsbliek
-[@hulkish]: https://github.com/hulkish
-[@chrislloyd]: https://github.com/chrislloyd
-[@ai]: https://github.com/ai
-[@syymza]: https://github.com/syymza
-[@justinanastos]: https://github.com/justinanastos
-[@1pete]: https://github.com/1pete
-[@gajus]: https://github.com/gajus
-[@jf248]: https://github.com/jf248
-[@aravindet]: https://github.com/aravindet
+[@Maxim-Mazurok]: https://github.com/Maxim-Mazurok
+[@maxkomarychev]: https://github.com/maxkomarychev
+[@maxmalov]: https://github.com/maxmalov
+[@meowtec]: https://github.com/meowtec
+[@mgwalker]: https://github.com/mgwalker
+[@mhmadhamster]: https://github.com/MhMadHamster
+[@michaelfaith]: https://github.com/michaelfaith
+[@mihkeleidast]: https://github.com/mihkeleidast
+[@MikeyBeLike]: https://github.com/MikeyBeLike
+[@minervabot]: https://github.com/minervabot
+[@mpint]: https://github.com/mpint
+[@mplewis]: https://github.com/mplewis
+[@mrmckeb]: https://github.com/mrmckeb
+[@msvab]: https://github.com/msvab
+[@mulztob]: https://github.com/mulztob
+[@mx-bernhard]: https://github.com/mx-bernhard
+[@Nfinished]: https://github.com/Nfinished
+[@nickofthyme]: https://github.com/nickofthyme
+[@nicolashenry]: https://github.com/nicolashenry
+[@NishargShah]: https://github.com/NishargShah
+[@noelebrun]: https://github.com/noelebrun
+[@ntdb]: https://github.com/ntdb
+[@nwalters512]: https://github.com/nwalters512
+[@ombene]: https://github.com/ombene
+[@ota-meshi]: https://github.com/ota-meshi
+[@OutdatedVersion]: https://github.com/OutdatedVersion
+[@Pandemic1617]: https://github.com/Pandemic1617
+[@panrafal]: https://github.com/panrafal
+[@paztis]: https://github.com/paztis
+[@pcorpet]: https://github.com/pcorpet
+[@Pearce-Ropion]: https://github.com/Pearce-Ropion
+[@Pessimistress]: https://github.com/Pessimistress
+[@phryneas]: https://github.com/phryneas
+[@pmcelhaney]: https://github.com/pmcelhaney
+[@preco21]: https://github.com/preco21
+[@pri1311]: https://github.com/pri1311
+[@ProdigySim]: https://github.com/ProdigySim
 [@pzhine]: https://github.com/pzhine
-[@st-sloth]: https://github.com/st-sloth
-[@ljqx]: https://github.com/ljqx
-[@kirill-konshin]: https://github.com/kirill-konshin
-[@asapach]: https://github.com/asapach
-[@sergei-startsev]: https://github.com/sergei-startsev
-[@ephys]: https://github.com/ephys
-[@lydell]: https://github.com/lydell
-[@jeffshaver]: https://github.com/jeffshaver
-[@timkraut]: https://github.com/timkraut
-[@TakeScoop]: https://github.com/TakeScoop
+[@ramasilveyra]: https://github.com/ramasilveyra
+[@randallreedjr]: https://github.com/randallreedjr
+[@redbugz]: https://github.com/redbugz
+[@remcohaszing]: https://github.com/remcohaszing
 [@rfermann]: https://github.com/rfermann
-[@bradennapier]: https://github.com/bradennapier
+[@rhettlivingston]: https://github.com/rhettlivingston
+[@rhys-vdw]: https://github.com/rhys-vdw
+[@richardxia]: https://github.com/richardxia
+[@robertrossmann]: https://github.com/robertrossmann
+[@rosswarren]: https://github.com/rosswarren
+[@rperello]: https://github.com/rperello
+[@rsolomon]: https://github.com/rsolomon
+[@RyanGst]: https://github.com/
+[@s-h-a-d-o-w]: https://github.com/s-h-a-d-o-w
+[@saschanaz]: https://github.com/saschanaz
+[@schmidsi]: https://github.com/schmidsi
 [@schmod]: https://github.com/schmod
-[@echenley]: https://github.com/echenley
-[@vikr01]: https://github.com/vikr01
-[@bradzacher]: https://github.com/bradzacher
-[@feychenie]: https://github.com/feychenie
-[@kiwka]: https://github.com/kiwka
-[@loganfsmyth]: https://github.com/loganfsmyth
-[@johndevedu]: https://github.com/johndevedu
-[@charlessuh]: https://github.com/charlessuh
-[@kgregory]: https://github.com/kgregory
-[@christophercurrie]: https://github.com/christophercurrie
-[@alex-page]: https://github.com/alex-page
-[@benmosher]: https://github.com/benmosher
-[@fooloomanzoo]: https://github.com/fooloomanzoo
-[@sheepsteak]: https://github.com/sheepsteak
+[@Schweinepriester]: https://github.com/Schweinepriester
+[@scottnonnenberg]: https://github.com/scottnonnenberg
+[@sergei-startsev]: https://github.com/sergei-startsev
+[@sevenc-nanashi]: https://github.com/sevenc-nanashi
 [@sharmilajesupaul]: https://github.com/sharmilajesupaul
-[@lencioni]: https://github.com/lencioni
+[@sheepsteak]: https://github.com/sheepsteak
+[@silverwind]: https://github.com/silverwind
+[@silviogutierrez]: https://github.com/silviogutierrez
+[@SimenB]: https://github.com/SimenB
+[@simmo]: https://github.com/simmo
+[@sindresorhus]: https://github.com/sindresorhus
+[@singles]: https://github.com/singles
+[@skozin]: https://github.com/skozin
+[@skyrpex]: https://github.com/skyrpex
+[@snewcomer]: https://github.com/snewcomer
+[@sompylasar]: https://github.com/sompylasar
+[@soryy708]: https://github.com/soryy708
+[@sosukesuzuki]: https://github.com/sosukesuzuki
+[@spalger]: https://github.com/spalger
+[@st-sloth]: https://github.com/st-sloth
+[@stekycz]: https://github.com/stekycz
+[@stenin-nikita]: https://github.com/stenin-nikita
+[@stephtr]: https://github.com/stephtr
+[@straub]: https://github.com/straub
+[@strawbrary]: https://github.com/strawbrary
+[@stropho]: https://github.com/stropho
+[@sveyret]: https://github.com/sveyret
+[@swernerx]: https://github.com/swernerx
+[@syymza]: https://github.com/syymza
+[@TakeScoop]: https://github.com/TakeScoop
+[@tapayne88]: https://github.com/tapayne88
+[@Taranys]: https://github.com/Taranys
+[@taye]: https://github.com/taye
+[@TheCrueltySage]: https://github.com/TheCrueltySage
+[@TheJaredWilcurt]: https://github.com/TheJaredWilcurt
+[@tihonove]: https://github.com/tihonove
+[@timkraut]: https://github.com/timkraut
+[@tizmagik]: https://github.com/tizmagik
+[@tomprats]: https://github.com/tomprats
+[@TrevorBurnham]: https://github.com/TrevorBurnham
+[@ttmarek]: https://github.com/ttmarek
+[@unbeauvoyage]: https://github.com/unbeauvoyage
+[@vikr01]: https://github.com/vikr01
+[@wenfangdu]: https://github.com/wenfangdu
+[@wKich]: https://github.com/wKich
+[@wschurman]: https://github.com/wschurman
+[@wtgtybhertgeghgtwtg]: https://github.com/wtgtybhertgeghgtwtg
+[@xM8WVqaG]: https://github.com/xM8WVqaG
+[@xpl]: https://github.com/xpl
+[@Xunnamius]: https://github.com/Xunnamius
+[@yesl-kim]: https://github.com/yesl-kim
+[@yndajas]: https://github.com/yndajas
+[@yordis]: https://github.com/yordis
+[@Zamiell]: https://github.com/Zamiell
+[@zloirock]: https://github.com/zloirock
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 35a6b14b5..871e90ad8 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -1,43 +1,46 @@
 # Contributing
 
-Thanks for your interest in helping out! Here are a **few** _weird_ tricks to
-~~cut your mortgage in half~~ maximize the global net efficiency of your efforts!
+Thanks for your interest in helping out! Here are a **few** _weird_ tricks to ~~cut your mortgage in half~~ maximize the global net efficiency of your efforts!
 
 ## TL;DR: Checklist
 
 When opening an [issue](#issues):
-- [ ] search open/closed issues
-- [ ] discuss bug/enhancement in new or old issue
+
+ - [ ] search open/closed issues
+ - [ ] discuss bug/enhancement in new or old issue
 
 [PR](#prs) time:
-- [ ] write tests
-- [ ] implement feature/fix bug
-- [ ] update docs
-- [ ] make a note in change log
+
+ - [ ] write tests
+ - [ ] implement feature/fix bug
+ - [ ] update docs
+ - [ ] make a note in change log
 
 Remember, you don't need to do it all yourself; any of these are helpful! 😎
 
+## How to get started
+
+If you are new to `eslint`, below are a few resources that will help you to familiarize yourself with the project.
+
+ - Watch [this presentation](https://www.youtube.com/watch?v=2W9tUnALrLg) to learn the fundamental concept of Abstract Syntax Trees (AST) and the way `eslint` works under the hood.
+ - Familiarize yourself with the [AST explorer](https://astexplorer.net/) tool. Look into rules in `docs/rules`, create patterns in the rules, then analyze its AST.
+ - Explore the blog posts on how to create a custom rule. [One blog post](https://blog.yonatan.dev/writing-a-custom-eslint-rule-to-spot-undeclared-props/). [Second blog post](https://betterprogramming.pub/creating-custom-eslint-rules-cdc579694608).
+ - Read the official `eslint` [developer guide](https://eslint.org/docs/latest/developer-guide/architecture/).
+
 ## Issues
 
-### Search open + closed issues for similar cases.
+### Search open + closed issues for similar cases
 
-  You may find an open issue that closely matches what you are thinking. You
-  may also find a closed issue with discussion that either solves your problem
-  or explains why we are unlikely to solve it in the near future.
+  You may find an open issue that closely matches what you are thinking. You may also find a closed issue with discussion that either solves your problem or explains why we are unlikely to solve it in the near future.
 
-  If you find a matching issue that is open, and marked `accepted` and/or `help
-  wanted`, you might want to [open a PR](#prs).
+  If you find a matching issue that is open, and marked `accepted` and/or `help wanted`, you might want to [open a PR](#prs).
 
-### Open an issue.
+### Open an issue
 
-  Let's discuss your issue. Could be as simple as unclear documentation or a
-  wonky config file.
-  If you're suggesting a feature, it might exist and need better
-  documentation, or it might be in process. Even given those, some discussion might
-  be warranted to ensure the enhancement is clear.
+  Let's discuss your issue. Could be as simple as unclear documentation or a wonky config file.
+  If you're suggesting a feature, it might exist and need better documentation, or it might be in process. Even given those, some discussion might be warranted to ensure the enhancement is clear.
 
-  You're welcome to jump right to a PR, but without a discussion, can't make any
-  guarantees about merging.
+  You're welcome to jump right to a PR, but without a discussion, can't make any guarantees about merging.
 
   That said: sometimes seeing the code makes the discussion clearer.😄
 
@@ -45,72 +48,37 @@ This is a helpful contribution all by itself. Thanks!
 
 ## PRs
 
-If you would like to implement something, firstly: thanks! Community contributions
-are a magical thing. Like Redux or [the flux capacitor](https://youtu.be/SR5BfQ4rEqQ?t=2m25s),
-they make open source possible.
+If you would like to implement something, firstly: thanks! Community contributions are a magical thing. Like Redux or [the flux capacitor](https://youtu.be/SR5BfQ4rEqQ?t=2m25s), they make open source possible.
 
 **Working on your first Pull Request?**
 You can learn how from this _free_ series [How to Contribute to an Open Source Project on GitHub](https://egghead.io/series/how-to-contribute-to-an-open-source-project-on-github).
 
 Here are some things to keep in mind when working on a PR:
 
-#### Tests
+**Trying to update an inactive Pull Request?**
+If a PR is open, but unfortunately the author is, for any reason, not available to apply code review fixes or rebase the source branch, then please **do not open a new PR**.
+Instead, paste a link to your own branch in the PR, and the maintainers can pull in your changes and update the existing PR in-place.
 
-A PR that is just failing test cases for an existing issue is very helpful, as this
-can take as much time (if not more) as it takes to implement a new feature or fix
-a bug.
+### Tests
 
-If you only have enough time to write tests, fantastic! Submit away. This is a great
-jumping-off point for a core contributor or even another PR to continue what you've started.
+A PR that is just failing test cases for an existing issue is very helpful, as this can take as much time (if not more) as it takes to implement a new feature or fix a bug.
 
-#### Docs
+If you only have enough time to write tests, fantastic! Submit away. This is a great jumping-off point for a core contributor or even another PR to continue what you've started.
 
-For enhancements to rules, please update the docs in `docs/rules` matching the rule
-filename from `src/rules`.
+### Docs
 
-Also, take a quick look at the rule summary in [README.md] in case it could use tweaking,
-or add a line if you've implemented a new rule.
+For enhancements to rules, please update the docs in `docs/rules` matching the rule filename from `src/rules` or the rule description in `meta.docs.description`. Running `npm run update:eslint-docs` will update the [README.md] and rule doc header.
 
-Bugfixes may not warrant docs changes, though it's worth skimming the existing
-docs to see if there are any relevant caveats that need to be removed.
+Bugfixes may not warrant docs changes, though it's worth skimming the existing docs to see if there are any relevant caveats that need to be removed.
 
-#### Changelog
+### Changelog
 
-Please add a quick blurb to the [**Unreleased**](./CHANGELOG.md#unreleased) section of the change log. Give yourself
-some credit, and please link back to the PR for future reference. This is especially
-helpful for resolver changes, as the resolvers are less frequently modified and published.
+Please add a quick blurb to the [**Unreleased**](./CHANGELOG.md#unreleased) section of the change log. Give yourself some credit, and please link back to the PR for future reference. This is especially helpful for resolver changes, as the resolvers are less frequently modified and published.
 
-Note also that the change log can't magically link back to Github entities (i.e. PRs,
-issues, users) or rules; there are a handful of footnote URL definitions at the bottom.
-You may need to add one or more URL if you've square-bracketed any such items.
+Note also that the change log can't magically link back to Github entities (i.e. PRs, issues, users) or rules; there are a handful of footnote URL definitions at the bottom. You may need to add one or more URL if you've square-bracketed any such items.
 
 ## Code of Conduct
 
-This is not so much a set of guidelines as a reference for what I hope may become
-a shared perspective on the project. I hope to write a longer essay to this end
-in the future. Comments are welcome, I'd like this to be as clear as possible.
-
-### Empathy
-
-People have feelings and perspectives, and people say and believe things for good reasons.
-
-If you find that you summarily disagree with a perspective stated by someone else,
-you likely each have histories that have moved you in opposite directions on a continuum
-that probably does not have a "wrong" or "right" end. It may be that you simply
-are working toward different goals that require different strategies. Every decision
-has pros and cons, and could result in some winners and some losers. It's great to
-discuss this so that both are well-known, and realize that even with infinite discussion,
-cons and losers will likely never go to zero.
-
-Also note that we're not doing brain surgery here, so while it's fine if we spend some time
-understanding each other, cordial disagreement should not be expensive in the
-long run, and we can accept that we will get some things wrong before we get them right (if ever!).
-
-If we can all get together behind the common goal of embracing empathy, everything else should be able to work itself out.
-
-#### Attribution
-
-Thanks for help from http://mozillascience.github.io/working-open-workshop/contributing/
-for inspiration before I wrote this. --ben
+Please familiarize yourself with the [Code of Conduct](https://github.com/import-js/.github/blob/main/CODE_OF_CONDUCT.md).
 
 [README.md]: ./README.md
diff --git a/README.md b/README.md
index ddc95d4f8..885f34873 100644
--- a/README.md
+++ b/README.md
@@ -1,10 +1,11 @@
 # eslint-plugin-import
 
-[![build status](https://travis-ci.org/benmosher/eslint-plugin-import.svg?branch=master)](https://travis-ci.org/benmosher/eslint-plugin-import)
-[![Coverage Status](https://coveralls.io/repos/github/benmosher/eslint-plugin-import/badge.svg?branch=master)](https://coveralls.io/github/benmosher/eslint-plugin-import?branch=master)
-[![win32 build status](https://ci.appveyor.com/api/projects/status/3mw2fifalmjlqf56/branch/master?svg=true)](https://ci.appveyor.com/project/benmosher/eslint-plugin-import/branch/master)
+[![github actions][actions-image]][actions-url]
+[![travis-ci](https://travis-ci.org/import-js/eslint-plugin-import.svg?branch=main)](https://travis-ci.org/import-js/eslint-plugin-import)
+[![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.
 
@@ -12,159 +13,205 @@ This plugin intends to support linting of ES2015+ (ES6+) import/export syntax, a
 
 ## Rules
 
-### Static analysis
+<!-- begin auto-generated rules list -->
 
-* Ensure imports point to a file/module that can be resolved. ([`no-unresolved`])
-* Ensure named imports correspond to a named export in the remote file. ([`named`])
-* Ensure a default export is present, given a default import. ([`default`])
-* Ensure imported namespaces contain dereferenced properties as they are dereferenced. ([`namespace`])
-* Restrict which files can be imported in a given folder ([`no-restricted-paths`])
-* Forbid import of modules using absolute paths ([`no-absolute-path`])
-* Forbid `require()` calls with expressions ([`no-dynamic-require`])
-* Prevent importing the submodules of other modules ([`no-internal-modules`])
-* Forbid webpack loader syntax in imports ([`no-webpack-loader-syntax`])
-* Forbid a module from importing itself ([`no-self-import`])
-* Forbid a module from importing a module with a dependency path back to itself ([`no-cycle`])
-* Prevent unnecessary path segments in import and require statements ([`no-useless-path-segments`])
-* Forbid importing modules from parent directories ([`no-relative-parent-imports`])
-* Forbid modules without any export, and exports not imported by any modules. ([`no-unused-modules`])
-
-[`no-unresolved`]: ./docs/rules/no-unresolved.md
-[`named`]: ./docs/rules/named.md
-[`default`]: ./docs/rules/default.md
-[`namespace`]: ./docs/rules/namespace.md
-[`no-restricted-paths`]: ./docs/rules/no-restricted-paths.md
-[`no-absolute-path`]: ./docs/rules/no-absolute-path.md
-[`no-dynamic-require`]: ./docs/rules/no-dynamic-require.md
-[`no-internal-modules`]: ./docs/rules/no-internal-modules.md
-[`no-webpack-loader-syntax`]: ./docs/rules/no-webpack-loader-syntax.md
-[`no-self-import`]: ./docs/rules/no-self-import.md
-[`no-cycle`]: ./docs/rules/no-cycle.md
-[`no-useless-path-segments`]: ./docs/rules/no-useless-path-segments.md
-[`no-relative-parent-imports`]: ./docs/rules/no-relative-parent-imports.md
-[`no-unused-modules`]: ./docs/rules/no-unused-modules.md
+💼 Configurations enabled in.\
+⚠️ Configurations set to warn in.\
+🚫 Configurations disabled in.\
+❗ Set in the `errors` configuration.\
+☑️ Set in the `recommended` configuration.\
+⌨️ Set in the `typescript` configuration.\
+🚸 Set in the `warnings` configuration.\
+🔧 Automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/user-guide/command-line-interface#--fix).\
+💡 Manually fixable by [editor suggestions](https://eslint.org/docs/latest/use/core-concepts#rule-suggestions).\
+❌ Deprecated.
 
 ### Helpful warnings
 
-
-* Report any invalid exports, i.e. re-export of the same name ([`export`])
-* Report use of exported name as identifier of default export ([`no-named-as-default`])
-* Report use of exported name as property of default export ([`no-named-as-default-member`])
-* Report imported names marked with `@deprecated` documentation tag ([`no-deprecated`])
-* Forbid the use of extraneous packages ([`no-extraneous-dependencies`])
-* Forbid the use of mutable exports with `var` or `let`. ([`no-mutable-exports`])
-* Report modules without exports, or exports without matching import in another module ([`no-unused-modules`])
-
-[`export`]: ./docs/rules/export.md
-[`no-named-as-default`]: ./docs/rules/no-named-as-default.md
-[`no-named-as-default-member`]: ./docs/rules/no-named-as-default-member.md
-[`no-deprecated`]: ./docs/rules/no-deprecated.md
-[`no-extraneous-dependencies`]: ./docs/rules/no-extraneous-dependencies.md
-[`no-mutable-exports`]: ./docs/rules/no-mutable-exports.md
-[`no-unused-modules`]: ./docs/rules/no-unused-modules.md
+| Name                                                                   | Description                                                                           | 💼   | ⚠️    | 🚫 | 🔧 | 💡 | ❌  |
+| :--------------------------------------------------------------------- | :------------------------------------------------------------------------------------ | :--- | :---- | :- | :- | :- | :- |
+| [export](docs/rules/export.md)                                         | Forbid any invalid exports, i.e. re-export of the same name.                          | ❗ ☑️ |       |    |    |    |    |
+| [no-deprecated](docs/rules/no-deprecated.md)                           | Forbid imported names marked with `@deprecated` documentation tag.                    |      |       |    |    |    |    |
+| [no-empty-named-blocks](docs/rules/no-empty-named-blocks.md)           | Forbid empty named import blocks.                                                     |      |       |    | 🔧 | 💡 |    |
+| [no-extraneous-dependencies](docs/rules/no-extraneous-dependencies.md) | Forbid the use of extraneous packages.                                                |      |       |    |    |    |    |
+| [no-mutable-exports](docs/rules/no-mutable-exports.md)                 | Forbid the use of mutable exports with `var` or `let`.                                |      |       |    |    |    |    |
+| [no-named-as-default](docs/rules/no-named-as-default.md)               | Forbid use of exported name as identifier of default export.                          |      | ☑️ 🚸 |    |    |    |    |
+| [no-named-as-default-member](docs/rules/no-named-as-default-member.md) | Forbid use of exported name as property of default export.                            |      | ☑️ 🚸 |    |    |    |    |
+| [no-unused-modules](docs/rules/no-unused-modules.md)                   | Forbid modules without exports, or exports without matching import in another module. |      |       |    |    |    |    |
 
 ### Module systems
 
-* Report potentially ambiguous parse goal (`script` vs. `module`) ([`unambiguous`])
-* Report CommonJS `require` calls and `module.exports` or `exports.*`. ([`no-commonjs`])
-* Report AMD `require` and `define` calls. ([`no-amd`])
-* No Node.js builtin modules. ([`no-nodejs-modules`])
+| Name                                                               | Description                                                          | 💼 | ⚠️ | 🚫 | 🔧 | 💡 | ❌  |
+| :----------------------------------------------------------------- | :------------------------------------------------------------------- | :- | :- | :- | :- | :- | :- |
+| [no-amd](docs/rules/no-amd.md)                                     | Forbid AMD `require` and `define` calls.                             |    |    |    |    |    |    |
+| [no-commonjs](docs/rules/no-commonjs.md)                           | Forbid CommonJS `require` calls and `module.exports` or `exports.*`. |    |    |    |    |    |    |
+| [no-import-module-exports](docs/rules/no-import-module-exports.md) | Forbid import statements with CommonJS module.exports.               |    |    |    | 🔧 |    |    |
+| [no-nodejs-modules](docs/rules/no-nodejs-modules.md)               | Forbid Node.js builtin modules.                                      |    |    |    |    |    |    |
+| [unambiguous](docs/rules/unambiguous.md)                           | Forbid potentially ambiguous parse goal (`script` vs. `module`).     |    |    |    |    |    |    |
 
-[`unambiguous`]: ./docs/rules/unambiguous.md
-[`no-commonjs`]: ./docs/rules/no-commonjs.md
-[`no-amd`]: ./docs/rules/no-amd.md
-[`no-nodejs-modules`]: ./docs/rules/no-nodejs-modules.md
+### Static analysis
 
+| Name                                                                     | Description                                                                                     | 💼   | ⚠️ | 🚫 | 🔧 | 💡 | ❌  |
+| :----------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------- | :--- | :- | :- | :- | :- | :- |
+| [default](docs/rules/default.md)                                         | Ensure a default export is present, given a default import.                                     | ❗ ☑️ |    |    |    |    |    |
+| [enforce-node-protocol-usage](docs/rules/enforce-node-protocol-usage.md) | Enforce either using, or omitting, the `node:` protocol when importing Node.js builtin modules. |      |    |    | 🔧 |    |    |
+| [named](docs/rules/named.md)                                             | Ensure named imports correspond to a named export in the remote file.                           | ❗ ☑️ |    | ⌨️ |    |    |    |
+| [namespace](docs/rules/namespace.md)                                     | Ensure imported namespaces contain dereferenced properties as they are dereferenced.            | ❗ ☑️ |    |    |    |    |    |
+| [no-absolute-path](docs/rules/no-absolute-path.md)                       | Forbid import of modules using absolute paths.                                                  |      |    |    | 🔧 |    |    |
+| [no-cycle](docs/rules/no-cycle.md)                                       | Forbid a module from importing a module with a dependency path back to itself.                  |      |    |    |    |    |    |
+| [no-dynamic-require](docs/rules/no-dynamic-require.md)                   | Forbid `require()` calls with expressions.                                                      |      |    |    |    |    |    |
+| [no-internal-modules](docs/rules/no-internal-modules.md)                 | Forbid importing the submodules of other modules.                                               |      |    |    |    |    |    |
+| [no-relative-packages](docs/rules/no-relative-packages.md)               | Forbid importing packages through relative paths.                                               |      |    |    | 🔧 |    |    |
+| [no-relative-parent-imports](docs/rules/no-relative-parent-imports.md)   | Forbid importing modules from parent directories.                                               |      |    |    |    |    |    |
+| [no-restricted-paths](docs/rules/no-restricted-paths.md)                 | Enforce which files can be imported in a given folder.                                          |      |    |    |    |    |    |
+| [no-self-import](docs/rules/no-self-import.md)                           | Forbid a module from importing itself.                                                          |      |    |    |    |    |    |
+| [no-unresolved](docs/rules/no-unresolved.md)                             | Ensure imports point to a file/module that can be resolved.                                     | ❗ ☑️ |    |    |    |    |    |
+| [no-useless-path-segments](docs/rules/no-useless-path-segments.md)       | Forbid unnecessary path segments in import and require statements.                              |      |    |    | 🔧 |    |    |
+| [no-webpack-loader-syntax](docs/rules/no-webpack-loader-syntax.md)       | Forbid webpack loader syntax in imports.                                                        |      |    |    |    |    |    |
 
 ### Style guide
 
-* Ensure all imports appear before other statements ([`first`])
-* Ensure all exports appear after other statements ([`exports-last`])
-* Report repeated import of the same module in multiple places ([`no-duplicates`])
-* Report namespace imports ([`no-namespace`])
-* Ensure consistent use of file extension within the import path ([`extensions`])
-* Enforce a convention in module import order ([`order`])
-* Enforce a newline after import statements ([`newline-after-import`])
-* Prefer a default export if module exports a single name ([`prefer-default-export`])
-* Limit the maximum number of dependencies a module can have ([`max-dependencies`])
-* Forbid unassigned imports ([`no-unassigned-import`])
-* Forbid named default exports ([`no-named-default`])
-* Forbid default exports ([`no-default-export`])
-* Forbid named exports ([`no-named-export`])
-* Forbid anonymous values as default exports ([`no-anonymous-default-export`])
-* Prefer named exports to be grouped together in a single export declaration ([`group-exports`])
-* Enforce a leading comment with the webpackChunkName for dynamic imports ([`dynamic-import-chunkname`])
-
-[`first`]: ./docs/rules/first.md
-[`exports-last`]: ./docs/rules/exports-last.md
-[`no-duplicates`]: ./docs/rules/no-duplicates.md
-[`no-namespace`]: ./docs/rules/no-namespace.md
-[`extensions`]: ./docs/rules/extensions.md
-[`order`]: ./docs/rules/order.md
-[`newline-after-import`]: ./docs/rules/newline-after-import.md
-[`prefer-default-export`]: ./docs/rules/prefer-default-export.md
-[`max-dependencies`]: ./docs/rules/max-dependencies.md
-[`no-unassigned-import`]: ./docs/rules/no-unassigned-import.md
-[`no-named-default`]: ./docs/rules/no-named-default.md
-[`no-anonymous-default-export`]: ./docs/rules/no-anonymous-default-export.md
-[`group-exports`]: ./docs/rules/group-exports.md
-[`no-default-export`]: ./docs/rules/no-default-export.md
-[`no-named-export`]: ./docs/rules/no-named-export.md
-[`dynamic-import-chunkname`]: ./docs/rules/dynamic-import-chunkname.md
+| Name                                                                             | Description                                                                | 💼 | ⚠️    | 🚫 | 🔧 | 💡 | ❌  |
+| :------------------------------------------------------------------------------- | :------------------------------------------------------------------------- | :- | :---- | :- | :- | :- | :- |
+| [consistent-type-specifier-style](docs/rules/consistent-type-specifier-style.md) | Enforce or ban the use of inline type-only markers for named imports.      |    |       |    | 🔧 |    |    |
+| [dynamic-import-chunkname](docs/rules/dynamic-import-chunkname.md)               | Enforce a leading comment with the webpackChunkName for dynamic imports.   |    |       |    |    | 💡 |    |
+| [exports-last](docs/rules/exports-last.md)                                       | Ensure all exports appear after other statements.                          |    |       |    |    |    |    |
+| [extensions](docs/rules/extensions.md)                                           | Ensure consistent use of file extension within the import path.            |    |       |    |    |    |    |
+| [first](docs/rules/first.md)                                                     | Ensure all imports appear before other statements.                         |    |       |    | 🔧 |    |    |
+| [group-exports](docs/rules/group-exports.md)                                     | Prefer named exports to be grouped together in a single export declaration |    |       |    |    |    |    |
+| [imports-first](docs/rules/imports-first.md)                                     | Replaced by `import/first`.                                                |    |       |    | 🔧 |    | ❌  |
+| [max-dependencies](docs/rules/max-dependencies.md)                               | Enforce the maximum number of dependencies a module can have.              |    |       |    |    |    |    |
+| [newline-after-import](docs/rules/newline-after-import.md)                       | Enforce a newline after import statements.                                 |    |       |    | 🔧 |    |    |
+| [no-anonymous-default-export](docs/rules/no-anonymous-default-export.md)         | Forbid anonymous values as default exports.                                |    |       |    |    |    |    |
+| [no-default-export](docs/rules/no-default-export.md)                             | Forbid default exports.                                                    |    |       |    |    |    |    |
+| [no-duplicates](docs/rules/no-duplicates.md)                                     | Forbid repeated import of the same module in multiple places.              |    | ☑️ 🚸 |    | 🔧 |    |    |
+| [no-named-default](docs/rules/no-named-default.md)                               | Forbid named default exports.                                              |    |       |    |    |    |    |
+| [no-named-export](docs/rules/no-named-export.md)                                 | Forbid named exports.                                                      |    |       |    |    |    |    |
+| [no-namespace](docs/rules/no-namespace.md)                                       | Forbid namespace (a.k.a. "wildcard" `*`) imports.                          |    |       |    | 🔧 |    |    |
+| [no-unassigned-import](docs/rules/no-unassigned-import.md)                       | Forbid unassigned imports                                                  |    |       |    |    |    |    |
+| [order](docs/rules/order.md)                                                     | Enforce a convention in module import order.                               |    |       |    | 🔧 |    |    |
+| [prefer-default-export](docs/rules/prefer-default-export.md)                     | Prefer a default export if module exports a single name or multiple names. |    |       |    |    |    |    |
+
+<!-- end auto-generated rules list -->
+
+## `eslint-plugin-import` for enterprise
+
+Available as part of the Tidelift Subscription.
+
+The maintainers of `eslint-plugin-import` and thousands of other packages are working with Tidelift to deliver commercial support and maintenance for the open source dependencies you use to build your applications. Save time, reduce risk, and improve code health, while paying the maintainers of the exact dependencies you use. [Learn more.](https://tidelift.com/subscription/pkg/npm-eslint-plugin-import?utm_source=npm-eslint-plugin-import&utm_medium=referral&utm_campaign=enterprise&utm_term=repo)
 
 ## Installation
 
 ```sh
-npm install eslint-plugin-import -g
+# inside your project's working tree
+npm install eslint-plugin-import --save-dev
 ```
 
-or if you manage ESLint as a dev dependency:
+### Config - Legacy (`.eslintrc`)
 
-```sh
-# inside your project's working tree
-npm install eslint-plugin-import --save-dev
+All rules are off by default. However, you may extend one of the preset configs, or configure them manually in your `.eslintrc.(yml|json|js)`.
+
+ - Extending a preset config:
+
+```jsonc
+{
+  "extends": [
+     "eslint:recommended",
+     "plugin:import/recommended",
+  ],
+}
 ```
 
-All rules are off by default. However, you may configure them manually
-in your `.eslintrc.(yml|json|js)`, or extend one of the canned configs:
+ - Configuring manually:
 
-```yaml
----
-extends:
-  - eslint:recommended
-  - plugin:import/errors
-  - plugin:import/warnings
-
-# or configure manually:
-plugins:
-  - import
-
-rules:
-  import/no-unresolved: [2, {commonjs: true, amd: true}]
-  import/named: 2
-  import/namespace: 2
-  import/default: 2
-  import/export: 2
-  # etc...
+```jsonc
+{
+  "rules": {
+    "import/no-unresolved": ["error", { "commonjs": true, "amd": true }],
+    "import/named": "error",
+    "import/namespace": "error",
+    "import/default": "error",
+    "import/export": "error",
+    // etc...
+  },
+},
 ```
 
-# Typescript
+### Config - Flat (`eslint.config.js`)
 
-You may use the following shortcut or assemble your own config using the granular settings described below.
+All rules are off by default. However, you may configure them manually in your `eslint.config.(js|cjs|mjs)`, or extend one of the preset configs:
 
-Make sure you have installed [`@typescript-eslint/parser`] which is used in the following configuration. Unfortunately NPM does not allow to list optional peer dependencies.
+```js
+import importPlugin from 'eslint-plugin-import';
+import js from '@eslint/js';
+
+export default [
+  js.configs.recommended,
+  importPlugin.flatConfigs.recommended,
+  {
+    files: ['**/*.{js,mjs,cjs}'],
+    languageOptions: {
+      ecmaVersion: 'latest',
+      sourceType: 'module',
+    },
+    rules: {
+      'no-unused-vars': 'off',
+      'import/no-dynamic-require': 'warn',
+      'import/no-nodejs-modules': 'warn',
+    },
+  },
+];
+```
 
-```yaml
-extends:
-  - eslint:recommended
-  - plugin:import/errors
-  - plugin:import/warnings
-  - plugin:import/typescript # this line does the trick
+## TypeScript
+
+You may use the following snippet or assemble your own config using the granular settings described below it.
+
+Make sure you have installed [`@typescript-eslint/parser`] and [`eslint-import-resolver-typescript`] which are used in the following configuration.
+
+```jsonc
+{
+  "extends": [
+    "eslint:recommended",
+    "plugin:import/recommended",
+// the following lines do the trick
+    "plugin:import/typescript",
+  ],
+  "settings": {
+    "import/resolver": {
+      // You will also need to install and configure the TypeScript resolver
+      // See also https://github.com/import-js/eslint-import-resolver-typescript#configuration
+      "typescript": true,
+      "node": true,
+    },
+  },
+}
 ```
 
-[`@typescript-eslint/parser`]: https://github.com/typescript-eslint/typescript-eslint/tree/master/packages/parser
+[`@typescript-eslint/parser`]: https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser
+[`eslint-import-resolver-typescript`]: https://github.com/import-js/eslint-import-resolver-typescript
 
-# Resolvers
+### Config - Flat with `config()` in `typescript-eslint`
+
+If you are using the `config` method from [`typescript-eslint`](https://github.com/typescript-eslint/typescript-eslint), ensure that the `flatConfig` is included within the `extends` array.
+
+```js
+import tseslint from 'typescript-eslint';
+import importPlugin from 'eslint-plugin-import';
+import js from '@eslint/js';
+
+export default tseslint.config(
+  js.configs.recommended,
+  // other configs...
+  {
+    files: ['**/*.{ts,tsx}'],
+    extends: [importPlugin.flatConfigs.recommended, importPlugin.flatConfigs.typescript],
+    // other configs...
+  }
+);
+```
+
+## Resolvers
 
 With the advent of module bundlers and the current state of modules and module
 syntax specs, it's not always obvious where `import x from 'module'` should look
@@ -181,11 +228,21 @@ runtime (allowing some modules to be included more traditionally via script tags
 In the interest of supporting both of these, v0.11 introduces resolvers.
 
 Currently [Node] and [webpack] resolution have been implemented, but the
-resolvers are just npm packages, so [third party packages are supported](https://github.com/benmosher/eslint-plugin-import/wiki/Resolvers) (and encouraged!).
+resolvers are just npm packages, so [third party packages are supported](https://github.com/import-js/eslint-plugin-import/wiki/Resolvers) (and encouraged!).
 
 You can reference resolvers in several ways (in order of precedence):
 
-- as a conventional `eslint-import-resolver` name, like `eslint-import-resolver-foo`:
+ - as a conventional `eslint-import-resolver` name, like `eslint-import-resolver-foo`:
+
+ ```jsonc
+// .eslintrc
+{
+  "settings": {
+    // uses 'eslint-import-resolver-foo':
+    "import/resolver": "foo",
+  },
+}
+```
 
 ```yaml
 # .eslintrc.yml
@@ -193,6 +250,7 @@ settings:
   # uses 'eslint-import-resolver-foo':
   import/resolver: foo
 ```
+
 ```js
 // .eslintrc.js
 module.exports = {
@@ -204,13 +262,23 @@ module.exports = {
 }
 ```
 
-- with a full npm module name, like `my-awesome-npm-module`:
+ - with a full npm module name, like `my-awesome-npm-module`:
+
+```jsonc
+// .eslintrc
+{
+  "settings": {
+    "import/resolver": "my-awesome-npm-module",
+  },
+}
+```
 
 ```yaml
 # .eslintrc.yml
 settings:
   import/resolver: 'my-awesome-npm-module'
 ```
+
 ```js
 // .eslintrc.js
 module.exports = {
@@ -222,7 +290,7 @@ module.exports = {
 }
 ```
 
-- with a filesystem path to resolver, defined in this example as a `computed property` name:
+ - with a filesystem path to resolver, defined in this example as a `computed property` name:
 
 ```js
 // .eslintrc.js
@@ -238,27 +306,28 @@ module.exports = {
 Relative paths will be resolved relative to the source's nearest `package.json` or
 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
 
-# Settings
+## Settings
 
 You may set the following settings in your `.eslintrc`:
 
-#### `import/extensions`
+### `import/extensions`
 
 A list of file extensions that will be parsed as modules and inspected for
 `export`s.
 
 This defaults to `['.js']`, unless you are using the `react` shared config,
-in which case it is specified as `['.js', '.jsx']`.
+in which case it is specified as `['.js', '.jsx']`. Despite the default,
+if you are using TypeScript (without the `plugin:import/typescript` config
+described above) you must specify the new extensions (`.ts`, and also `.tsx`
+if using React).
 
 ```js
 "settings": {
@@ -290,7 +359,7 @@ factor into the `no-unresolved` rule.
 
 Also, the following `import/ignore` patterns will overrule this list.
 
-#### `import/ignore`
+### `import/ignore`
 
 A list of regex strings that, if matched by a path, will
 not report the matching module if no `export`s are found.
@@ -299,14 +368,18 @@ In practice, this means rules other than [`no-unresolved`](./docs/rules/no-unres
 
 `no-unresolved` has its own [`ignore`](./docs/rules/no-unresolved.md#ignore) setting.
 
-```yaml
-settings:
-  import/ignore:
-    - \.coffee$          # fraught with parse errors
-    - \.(scss|less|css)$ # can't parse unprocessed CSS modules, either
+```jsonc
+{
+  "settings": {
+    "import/ignore": [
+      "\.coffee$", // fraught with parse errors
+      "\.(scss|less|css)$", // can't parse unprocessed CSS modules, either
+    ],
+  },
+}
 ```
 
-#### `import/core-modules`
+### `import/core-modules`
 
 An array of additional modules to consider as "core" modules--modules that should
 be considered resolved but have no path on the filesystem. Your resolver may
@@ -322,10 +395,13 @@ import 'electron'  // without extra config, will be flagged as unresolved!
 that would otherwise be unresolved. To avoid this, you may provide `electron` as a
 core module:
 
-```yaml
-# .eslintrc.yml
-settings:
-  import/core-modules: [ electron ]
+```jsonc
+// .eslintrc
+{
+  "settings": {
+    "import/core-modules": ["electron"],
+  },
+}
 ```
 
 In Electron's specific case, there is a shared config named `electron`
@@ -333,22 +409,40 @@ that specifies this for you.
 
 Contribution of more such shared configs for other platforms are welcome!
 
-#### `import/external-module-folders`
+### `import/external-module-folders`
+
+An array of folders. Resolved modules only from those folders will be considered as "external". By default - `["node_modules"]`. Makes sense if you have configured your path or webpack to handle your internal paths differently and want to consider modules from some folders, for example `bower_components` or `jspm_modules`, as "external".
+
+This option is also useful in a monorepo setup: list here all directories that contain monorepo's packages and they will be treated as external ones no matter which resolver is used.
+
+If you are using `yarn` PnP as your package manager, add the `.yarn` folder and all your installed dependencies will be considered as `external`, instead of `internal`.
+
+Each item in this array is either a folder's name, its subpath, or its absolute prefix path:
+
+ - `jspm_modules` will match any file or folder named `jspm_modules` or which has a direct or non-direct parent named `jspm_modules`, e.g. `/home/me/project/jspm_modules` or `/home/me/project/jspm_modules/some-pkg/index.js`.
 
-An array of folders. Resolved modules only from those folders will be considered as "external". By default - `["node_modules"]`. Makes sense if you have configured your path or webpack to handle your internal paths differently and want to considered modules from some folders, for example `bower_components` or `jspm_modules`, as "external".
+ - `packages/core` will match any path that contains these two segments, for example `/home/me/project/packages/core/src/utils.js`.
 
-#### `import/parsers`
+ - `/home/me/project/packages` will only match files and directories inside this directory, and the directory itself.
+
+Please note that incomplete names are not allowed here so `components` won't match `bower_components` and `packages/ui` won't match `packages/ui-utils` (but will match `packages/ui/utils`).
+
+### `import/parsers`
 
 A map from parsers to file extension arrays. If a file extension is matched, the
 dependency parser will require and use the map key as the parser instead of the
 configured ESLint parser. This is useful if you're inter-op-ing with TypeScript
 directly using webpack, for example:
 
-```yaml
-# .eslintrc.yml
-settings:
-  import/parsers:
-    @typescript-eslint/parser: [ .ts, .tsx ]
+```jsonc
+// .eslintrc
+{
+  "settings": {
+    "import/parsers": {
+      "@typescript-eslint/parser": [".ts", ".tsx"],
+    },
+  },
+}
 ```
 
 In this case, [`@typescript-eslint/parser`](https://www.npmjs.com/package/@typescript-eslint/parser)
@@ -364,12 +458,11 @@ depending on how far down the rabbit hole goes. Submit an issue if you find stra
 behavior beyond here, but steel your heart against the likely outcome of closing
 with `wontfix`.
 
-
-#### `import/resolver`
+### `import/resolver`
 
 See [resolvers](#resolvers).
 
-#### `import/cache`
+### `import/cache`
 
 Settings for cache behavior. Memoization is used at various levels to avoid the copious amount of `fs.statSync`/module parse calls required to correctly report errors.
 
@@ -379,25 +472,63 @@ For long-lasting processes, like [`eslint_d`] or [`eslint-loader`], however, it'
 
 If you never use [`eslint_d`] or [`eslint-loader`], you may set the cache lifetime to `Infinity` and everything should be fine:
 
-```yaml
-# .eslintrc.yml
-settings:
-  import/cache:
-    lifetime: ∞  # or Infinity
+```jsonc
+// .eslintrc
+{
+  "settings": {
+    "import/cache": {
+      "lifetime": "∞", // or Infinity, in a JS config
+    },
+  },
+}
 ```
 
 Otherwise, set some integer, and cache entries will be evicted after that many seconds have elapsed:
 
-```yaml
-# .eslintrc.yml
-settings:
-  import/cache:
-    lifetime: 5  # 30 is the default
+```jsonc
+// .eslintrc
+{
+  "settings": {
+    "import/cache": {
+      "lifetime": 5, // 30 is the default
+    },
+  },
+}
 ```
 
 [`eslint_d`]: https://www.npmjs.com/package/eslint_d
 [`eslint-loader`]: https://www.npmjs.com/package/eslint-loader
 
+### `import/internal-regex`
+
+A regex for packages should be treated as internal. Useful when you are utilizing a monorepo setup or developing a set of packages that depend on each other.
+
+By default, any package referenced from [`import/external-module-folders`](#importexternal-module-folders) will be considered as "external", including packages in a monorepo like yarn workspace or lerna environment. If you want to mark these packages as "internal" this will be useful.
+
+For example, if your packages in a monorepo are all in `@scope`, you can configure `import/internal-regex` like this
+
+```jsonc
+// .eslintrc
+{
+  "settings": {
+    "import/internal-regex": "^@scope/",
+  },
+}
+```
+
+### `import/node-version`
+
+A string that represents the version of Node.js that you are using.
+A falsy value will imply the version of Node.js that you are running ESLint with.
+
+```jsonc
+// .eslintrc
+{
+  "settings": {
+    "import/node-version": "22.3.4",
+  },
+}
+```
 
 ## SublimeLinter-eslint
 
@@ -442,7 +573,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
@@ -463,6 +594,7 @@ I also found that I needed to set `rc_search_limit` to `null`, which removes the
 hierarchy search limit when looking up the directory tree for `.sublimelinterrc`:
 
 In Package Settings / SublimeLinter / User Settings:
+
 ```json
 {
   "user": {
@@ -473,3 +605,8 @@ In Package Settings / SublimeLinter / User Settings:
 
 I believe this defaults to `3`, so you may not need to alter it depending on your
 project folder max depth.
+
+[codecov-image]: https://codecov.io/gh/import-js/eslint-plugin-import/branch/main/graphs/badge.svg
+[codecov-url]: https://app.codecov.io/gh/import-js/eslint-plugin-import/
+[actions-image]: https://img.shields.io/endpoint?url=https://github-actions-badge-u3jn4tfpocch.runkit.sh/import-js/eslint-plugin-import
+[actions-url]: https://github.com/import-js/eslint-plugin-import
diff --git a/RELEASE.md b/RELEASE.md
index e16a58993..6c048dc09 100644
--- a/RELEASE.md
+++ b/RELEASE.md
@@ -1,9 +1,9 @@
 # Release steps
 
-1. create a `release-[x.y.z]` branch from tip of `master` (or whatever release commit)
+1. create a `release-[x.y.z]` branch from tip of `main` (or whatever release commit)
 
    ```bash
-   git checkout master && git pull && git checkout -b release-2.1.0
+   git checkout main && git pull && git checkout -b release-2.1.0
    ```
 
 2. bump `package.json` + update CHANGELOG version links for all releasing packages (i.e., root + any resolvers)
@@ -13,16 +13,16 @@
    at last version's tag.
 
    ```markdown
-   [Unreleased]: https://github.com/benmosher/eslint-plugin-import/compare/v2.0.1...HEAD
-   [2.0.1]: https://github.com/benmosher/eslint-plugin-import/compare/v2.0.0...v2.0.1
+   [Unreleased]: https://github.com/import-js/eslint-plugin-import/compare/v2.0.1...HEAD
+   [2.0.1]: https://github.com/import-js/eslint-plugin-import/compare/v2.0.0...v2.0.1
    ```
 
    becomes
 
    ```markdown
-   [Unreleased]: https://github.com/benmosher/eslint-plugin-import/compare/v2.1.0...HEAD
-   [2.1.0]: https://github.com/benmosher/eslint-plugin-import/compare/v2.0.1...v2.1.0
-   [2.0.1]: https://github.com/benmosher/eslint-plugin-import/compare/v2.0.0...v2.0.1
+   [Unreleased]: https://github.com/import-js/eslint-plugin-import/compare/v2.1.0...HEAD
+   [2.1.0]: https://github.com/import-js/eslint-plugin-import/compare/v2.0.1...v2.1.0
+   [2.0.1]: https://github.com/import-js/eslint-plugin-import/compare/v2.0.0...v2.0.1
    ```
 
    Generally, don't use `npm version` for this because it creates a tag, which I normally
@@ -49,6 +49,6 @@
 7. merge `release-[x.y.z]` into `release` (
    - ideally fast-forward, probably with Git CLI instead of Github
 
-8. merge `release` into `master`
+8. merge `release` into `main`
 
 Done!
diff --git a/SECURITY.md b/SECURITY.md
new file mode 100644
index 000000000..b155f54d5
--- /dev/null
+++ b/SECURITY.md
@@ -0,0 +1,11 @@
+# Security Policy
+
+## Supported Versions
+
+Latest major/minor version is supported only for security updates.
+
+## Reporting a Vulnerability
+
+To report a security vulnerability, please use the
+[Tidelift security contact](https://tidelift.com/security).
+Tidelift will coordinate the fix and disclosure.
diff --git a/appveyor.yml b/appveyor.yml
deleted file mode 100644
index e98547952..000000000
--- a/appveyor.yml
+++ /dev/null
@@ -1,58 +0,0 @@
-# Test against this version of Node.js
-environment:
-  matrix:
-  - nodejs_version: "12"
-  - nodejs_version: "10"
-  - nodejs_version: "8"
-  # - nodejs_version: "6"
-  # - nodejs_version: "4"
-
-matrix:
-  fast_finish: true
-
-  # allow_failures:
-  #   - nodejs_version: "4" # for eslint 5
-
-# platform:
-#   - x86
-#   - x64
-
-# Install scripts. (runs after repo cloning)
-install:
-  # Get the latest stable version of Node.js or io.js
-  - ps: Install-Product node $env:nodejs_version
-
-  # install modules
-  - ps: >-
-      if ($env:nodejs_version -eq "4") {
-        npm install -g npm@3;
-      }
-  - npm install
-
-  # fix symlinks
-  - cmd: git config core.symlinks true
-  - cmd: git reset --hard
-
-  # todo: learn how to do this for all .\resolvers\* on Windows
-  - cd .\resolvers\webpack && npm install && cd ..\..
-  - cd .\resolvers\node && npm install && cd ..\..
-
-# Post-install test scripts.
-test_script:
-
-  # Output useful info for debugging.
-  - node --version
-  - npm --version
-
-  # core tests
-  - npm test
-
-  # resolver tests
-  - cd .\resolvers\webpack && npm test && cd ..\..
-  - cd .\resolvers\node && npm test && cd ..\..
-
-on_success:
-  - npm run coveralls
-
-# Don't actually build.
-build: off
diff --git a/config/electron.js b/config/electron.js
index 6fab4e8b9..f98ff0614 100644
--- a/config/electron.js
+++ b/config/electron.js
@@ -5,4 +5,4 @@ module.exports = {
   settings: {
     'import/core-modules': ['electron'],
   },
-}
+};
diff --git a/config/errors.js b/config/errors.js
index 8f865b90f..127c29a0c 100644
--- a/config/errors.js
+++ b/config/errors.js
@@ -5,10 +5,10 @@
  */
 module.exports = {
   plugins: ['import'],
-  rules: { 'import/no-unresolved': 2
-         , 'import/named': 2
-         , 'import/namespace': 2
-         , 'import/default': 2
-         , 'import/export': 2
-         }
-}
+  rules: { 'import/no-unresolved': 2,
+    'import/named': 2,
+    'import/namespace': 2,
+    'import/default': 2,
+    'import/export': 2,
+  },
+};
diff --git a/config/flat/errors.js b/config/flat/errors.js
new file mode 100644
index 000000000..98c19f824
--- /dev/null
+++ b/config/flat/errors.js
@@ -0,0 +1,14 @@
+/**
+ * unopinionated config. just the things that are necessarily runtime errors
+ * waiting to happen.
+ * @type {Object}
+ */
+module.exports = {
+  rules: {
+    'import/no-unresolved': 2,
+    'import/named': 2,
+    'import/namespace': 2,
+    'import/default': 2,
+    'import/export': 2,
+  },
+};
diff --git a/config/flat/react.js b/config/flat/react.js
new file mode 100644
index 000000000..086747142
--- /dev/null
+++ b/config/flat/react.js
@@ -0,0 +1,19 @@
+/**
+ * Adds `.jsx` as an extension, and enables JSX parsing.
+ *
+ * Even if _you_ aren't using JSX (or .jsx) directly, if your dependencies
+ * define jsnext:main and have JSX internally, you may run into problems
+ * if you don't enable these settings at the top level.
+ */
+module.exports = {
+  settings: {
+    'import/extensions': ['.js', '.jsx', '.mjs', '.cjs'],
+  },
+  languageOptions: {
+    parserOptions: {
+      ecmaFeatures: {
+        jsx: true,
+      },
+    },
+  },
+};
diff --git a/config/flat/recommended.js b/config/flat/recommended.js
new file mode 100644
index 000000000..11bc1f52a
--- /dev/null
+++ b/config/flat/recommended.js
@@ -0,0 +1,26 @@
+/**
+ * The basics.
+ * @type {Object}
+ */
+module.exports = {
+  rules: {
+    // analysis/correctness
+    'import/no-unresolved': 'error',
+    'import/named': 'error',
+    'import/namespace': 'error',
+    'import/default': 'error',
+    'import/export': 'error',
+
+    // red flags (thus, warnings)
+    'import/no-named-as-default': 'warn',
+    'import/no-named-as-default-member': 'warn',
+    'import/no-duplicates': 'warn',
+  },
+
+  // need all these for parsing dependencies (even if _your_ code doesn't need
+  // all of them)
+  languageOptions: {
+    ecmaVersion: 2018,
+    sourceType: 'module',
+  },
+};
diff --git a/config/flat/warnings.js b/config/flat/warnings.js
new file mode 100644
index 000000000..e788ff9cd
--- /dev/null
+++ b/config/flat/warnings.js
@@ -0,0 +1,11 @@
+/**
+ * more opinionated config.
+ * @type {Object}
+ */
+module.exports = {
+  rules: {
+    'import/no-named-as-default': 1,
+    'import/no-named-as-default-member': 1,
+    'import/no-duplicates': 1,
+  },
+};
diff --git a/config/react-native.js b/config/react-native.js
index fbc8652c9..a1aa0ee56 100644
--- a/config/react-native.js
+++ b/config/react-native.js
@@ -10,4 +10,4 @@ module.exports = {
       },
     },
   },
-}
+};
diff --git a/config/react.js b/config/react.js
index fe1b5f2ec..1ae8e1a51 100644
--- a/config/react.js
+++ b/config/react.js
@@ -6,7 +6,6 @@
  * if you don't enable these settings at the top level.
  */
 module.exports = {
-
   settings: {
     'import/extensions': ['.js', '.jsx'],
   },
@@ -14,5 +13,4 @@ module.exports = {
   parserOptions: {
     ecmaFeatures: { jsx: true },
   },
-
-}
+};
diff --git a/config/recommended.js b/config/recommended.js
index 70514eed3..8e7ca9fd0 100644
--- a/config/recommended.js
+++ b/config/recommended.js
@@ -16,7 +16,7 @@ module.exports = {
     // red flags (thus, warnings)
     'import/no-named-as-default': 'warn',
     'import/no-named-as-default-member': 'warn',
-    'import/no-duplicates': 'warn'
+    'import/no-duplicates': 'warn',
   },
 
   // need all these for parsing dependencies (even if _your_ code doesn't need
@@ -25,4 +25,4 @@ module.exports = {
     sourceType: 'module',
     ecmaVersion: 2018,
   },
-}
+};
diff --git a/config/stage-0.js b/config/stage-0.js
index 1a7778486..42419123f 100644
--- a/config/stage-0.js
+++ b/config/stage-0.js
@@ -8,5 +8,5 @@ module.exports = {
   plugins: ['import'],
   rules: {
     'import/no-deprecated': 1,
-  }
-}
+  },
+};
diff --git a/config/typescript.js b/config/typescript.js
index a8efe8e9a..d5eb57a46 100644
--- a/config/typescript.js
+++ b/config/typescript.js
@@ -1,21 +1,34 @@
 /**
- * Adds `.jsx`, `.ts` and `.tsx` as an extension, and enables JSX/TSX parsing.
+ * This config:
+ * 1) adds `.jsx`, `.ts`, `.cts`, `.mts`, and `.tsx` as an extension
+ * 2) enables JSX/TSX parsing
  */
 
-var allExtensions = ['.ts', '.tsx', '.d.ts', '.js', '.jsx'];
+// Omit `.d.ts` because 1) TypeScript compilation already confirms that
+// types are resolved, and 2) it would mask an unresolved
+// `.ts`/`.tsx`/`.js`/`.jsx` implementation.
+const typeScriptExtensions = ['.ts', '.cts', '.mts', '.tsx'];
 
-module.exports = {
+const allExtensions = [...typeScriptExtensions, '.js', '.jsx', '.mjs', '.cjs'];
 
+module.exports = {
   settings: {
     'import/extensions': allExtensions,
+    'import/external-module-folders': ['node_modules', 'node_modules/@types'],
     'import/parsers': {
-      '@typescript-eslint/parser': ['.ts', '.tsx', '.d.ts']
+      '@typescript-eslint/parser': typeScriptExtensions,
     },
     'import/resolver': {
-      'node': {
-        'extensions': allExtensions
-      }
-    }
-  }
+      node: {
+        extensions: allExtensions,
+      },
+    },
+  },
+
+  rules: {
+    // analysis/correctness
 
-}
+    // TypeScript compilation already ensures that named imports exist in the referenced module
+    'import/named': 'off',
+  },
+};
diff --git a/config/warnings.js b/config/warnings.js
index c05eb0722..5d74143b2 100644
--- a/config/warnings.js
+++ b/config/warnings.js
@@ -9,4 +9,4 @@ module.exports = {
     'import/no-named-as-default-member': 1,
     'import/no-duplicates': 1,
   },
-}
+};
diff --git a/docs/rules/consistent-type-specifier-style.md b/docs/rules/consistent-type-specifier-style.md
new file mode 100644
index 000000000..41d98e4e1
--- /dev/null
+++ b/docs/rules/consistent-type-specifier-style.md
@@ -0,0 +1,91 @@
+# import/consistent-type-specifier-style
+
+🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).
+
+<!-- end auto-generated rule header -->
+
+In both Flow and TypeScript you can mark an import as a type-only import by adding a "kind" marker to the import. Both languages support two positions for marker.
+
+**At the top-level** which marks all names in the import as type-only and applies to named, default, and namespace (for TypeScript) specifiers:
+
+```ts
+import type Foo from 'Foo';
+import type {Bar} from 'Bar';
+// ts only
+import type * as Bam from 'Bam';
+// flow only
+import typeof Baz from 'Baz';
+```
+
+**Inline** with to the named import, which marks just the specific name in the import as type-only. An inline specifier is only valid for named specifiers, and not for default or namespace specifiers:
+
+```ts
+import {type Foo} from 'Foo';
+// flow only
+import {typeof Bar} from 'Bar';
+```
+
+## Rule Details
+
+This rule either enforces or bans the use of inline type-only markers for named imports.
+
+This rule includes a fixer that will automatically convert your specifiers to the correct form - however the fixer will not respect your preferences around de-duplicating imports. If this is important to you, consider using the [`import/no-duplicates`] rule.
+
+[`import/no-duplicates`]: ./no-duplicates.md
+
+## Options
+
+The rule accepts a single string option which may be one of:
+
+ - `'prefer-inline'` - enforces that named type-only specifiers are only ever written with an inline marker; and never as part of a top-level, type-only import.
+ - `'prefer-top-level'` - enforces that named type-only specifiers only ever written as part of a top-level, type-only import; and never with an inline marker.
+
+By default the rule will use the `prefer-inline` option.
+
+## Examples
+
+### `prefer-top-level`
+
+❌ Invalid with `["error", "prefer-top-level"]`
+
+```ts
+import {type Foo} from 'Foo';
+import Foo, {type Bar} from 'Foo';
+// flow only
+import {typeof Foo} from 'Foo';
+```
+
+✅ Valid with `["error", "prefer-top-level"]`
+
+```ts
+import type {Foo} from 'Foo';
+import type Foo, {Bar} from 'Foo';
+// flow only
+import typeof {Foo} from 'Foo';
+```
+
+### `prefer-inline`
+
+❌ Invalid with `["error", "prefer-inline"]`
+
+```ts
+import type {Foo} from 'Foo';
+import type Foo, {Bar} from 'Foo';
+// flow only
+import typeof {Foo} from 'Foo';
+```
+
+✅ Valid with `["error", "prefer-inline"]`
+
+```ts
+import {type Foo} from 'Foo';
+import Foo, {type Bar} from 'Foo';
+// flow only
+import {typeof Foo} from 'Foo';
+```
+
+## When Not To Use It
+
+If you aren't using Flow or TypeScript 4.5+, then this rule does not apply and need not be used.
+
+If you don't care about, and don't want to standardize how named specifiers are imported then you should not use this rule.
diff --git a/docs/rules/default.md b/docs/rules/default.md
index f69934468..9f8c919bf 100644
--- a/docs/rules/default.md
+++ b/docs/rules/default.md
@@ -1,5 +1,9 @@
 # import/default
 
+💼 This rule is enabled in the following configs: ❗ `errors`, ☑️ `recommended`.
+
+<!-- end auto-generated rule header -->
+
 If a default import is requested, this rule will report if there is no default
 export in the imported module.
 
@@ -15,7 +19,6 @@ A module path that is [ignored] or not [unambiguously an ES module] will not be
 [ignored]: ../README.md#importignore
 [unambiguously an ES module]: https://github.com/bmeck/UnambiguousJavaScriptGrammar
 
-
 ## Rule Details
 
 Given:
@@ -50,7 +53,6 @@ import bar from './bar' // no default export found in ./bar
 import baz from './baz' // no default export found in ./baz
 ```
 
-
 ## When Not To Use It
 
 If you are using CommonJS and/or modifying the exported namespace of any module at
@@ -61,10 +63,9 @@ either, so such a situation will be reported in the importing module.
 
 ## Further Reading
 
-- Lee Byron's [ES7] export proposal
-- [`import/ignore`] setting
-- [`jsnext:main`] (Rollup)
-
+ - Lee Byron's [ES7] export proposal
+ - [`import/ignore`] setting
+ - [`jsnext:main`] (Rollup)
 
 [ES7]: https://github.com/leebyron/ecmascript-more-export-from
 [`import/ignore`]: ../../README.md#importignore
diff --git a/docs/rules/dynamic-import-chunkname.md b/docs/rules/dynamic-import-chunkname.md
index b1757b4e5..d9ee8d15e 100644
--- a/docs/rules/dynamic-import-chunkname.md
+++ b/docs/rules/dynamic-import-chunkname.md
@@ -1,22 +1,30 @@
-# dynamic imports require a leading comment with a webpackChunkName (dynamic-import-chunkname)
+# import/dynamic-import-chunkname
+
+💡 This rule is manually fixable by [editor suggestions](https://eslint.org/docs/latest/use/core-concepts#rule-suggestions).
+
+<!-- end auto-generated rule header -->
 
 This rule reports any dynamic imports without a webpackChunkName specified in a leading block comment in the proper format.
 
 This rule enforces naming of webpack chunks in dynamic imports. When you don't explicitly name chunks, webpack will autogenerate chunk names that are not consistent across builds, which prevents long-term browser caching.
 
 ## Rule Details
+
 This rule runs against `import()` by default, but can be configured to also run against an alternative dynamic-import function, e.g. 'dynamicImport.'
 You can also configure the regex format you'd like to accept for the webpackChunkName - for example, if we don't want the number 6 to show up in our chunk names:
+
  ```javascript
 {
-  "dynamic-import-chunkname": [2, {
+  "import/dynamic-import-chunkname": [2, {
     importFunctions: ["dynamicImport"],
-    webpackChunknameFormat: "[a-zA-Z0-57-9-/_]"
+    webpackChunknameFormat: "[a-zA-Z0-57-9-/_]+",
+    allowEmpty: false
   }]
 }
 ```
 
 ### invalid
+
 The following patterns are invalid:
 
 ```javascript
@@ -39,12 +47,6 @@ import(
   'someModule',
 );
 
-// using single quotes instead of double quotes
-import(
-  /* webpackChunkName: 'someModule' */
-  'someModule',
-);
-
 // invalid syntax for webpack comment
 import(
   /* totally not webpackChunkName: "someModule" */
@@ -56,8 +58,17 @@ import(
   // webpackChunkName: "someModule"
   'someModule',
 );
+
+// chunk names are disallowed when eager mode is set
+import(
+  /* webpackMode: "eager" */
+  /* webpackChunkName: "someModule" */
+  'someModule',
+)
 ```
+
 ### valid
+
 The following patterns are valid:
 
 ```javascript
@@ -78,6 +89,44 @@ The following patterns are valid:
     /* webpackChunkName: "someModule", webpackPrefetch: true */
     'someModule',
   );
+
+  // using single quotes instead of double quotes
+  import(
+    /* webpackChunkName: 'someModule' */
+    'someModule',
+  );
+```
+
+### `allowEmpty: true`
+
+If you want to allow dynamic imports without a webpackChunkName, you can set `allowEmpty: true` in the rule config. This will allow dynamic imports without a leading comment, or with a leading comment that does not contain a webpackChunkName.
+
+Given `{ "allowEmpty": true }`:
+
+<!-- markdownlint-disable-next-line MD024 -- duplicate header -->
+### valid
+
+The following patterns are valid:
+
+```javascript
+import('someModule');
+
+import(
+  /* webpackChunkName: "someModule" */
+  'someModule',
+);
+```
+<!-- markdownlint-disable-next-line MD024 -- duplicate header -->
+### invalid
+
+The following patterns are invalid:
+
+```javascript
+// incorrectly formatted comment
+import(
+  /*webpackChunkName:"someModule"*/
+  'someModule',
+);
 ```
 
 ## When Not To Use It
diff --git a/docs/rules/enforce-node-protocol-usage.md b/docs/rules/enforce-node-protocol-usage.md
new file mode 100644
index 000000000..8d023b4e9
--- /dev/null
+++ b/docs/rules/enforce-node-protocol-usage.md
@@ -0,0 +1,81 @@
+# import/enforce-node-protocol-usage
+
+🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).
+
+<!-- end auto-generated rule header -->
+
+Enforce either using, or omitting, the `node:` protocol when importing Node.js builtin modules.
+
+## Rule Details
+
+This rule enforces that builtins node imports are using, or omitting, the `node:` protocol.
+
+Determining whether a specifier is a core module depends on the node version being used to run `eslint`.
+This version can be specified in the configuration with the [`import/node-version` setting](../../README.md#importnode-version).
+
+Reasons to prefer using the protocol include:
+
+ - the code is more explicitly and clearly referencing a Node.js built-in module
+
+Reasons to prefer omitting the protocol include:
+
+ - some tools don't support the `node:` protocol
+ - the code is more portable, because import maps and automatic polyfilling can be used
+
+## Options
+
+The rule requires a single string option which may be one of:
+
+ - `'always'` - enforces that builtins node imports are using the `node:` protocol.
+ - `'never'` - enforces that builtins node imports are not using the `node:` protocol.
+
+## Examples
+
+### `'always'`
+
+❌ Invalid
+
+```js
+import fs from 'fs';
+export { promises } from 'fs';
+// require
+const fs = require('fs/promises');
+```
+
+✅ Valid
+
+```js
+import fs from 'node:fs';
+export { promises } from 'node:fs';
+import * as test from 'node:test';
+// require
+const fs = require('node:fs/promises');
+```
+
+### `'never'`
+
+❌ Invalid
+
+```js
+import fs from 'node:fs';
+export { promises } from 'node:fs';
+// require
+const fs = require('node:fs/promises');
+```
+
+✅ Valid
+
+```js
+import fs from 'fs';
+export { promises } from 'fs';
+
+// require
+const fs = require('fs/promises');
+
+// This rule will not enforce not using `node:` protocol when the module is only available under the `node:` protocol.
+import * as test from 'node:test';
+```
+
+## When Not To Use It
+
+If you don't want to consistently enforce using, or omitting, the `node:` protocol when importing Node.js builtin modules.
diff --git a/docs/rules/export.md b/docs/rules/export.md
index e99882be8..54a8a39cf 100644
--- a/docs/rules/export.md
+++ b/docs/rules/export.md
@@ -1,5 +1,9 @@
 # import/export
 
+💼 This rule is enabled in the following configs: ❗ `errors`, ☑️ `recommended`.
+
+<!-- end auto-generated rule header -->
+
 Reports funny business with exports, like repeated exports of names or defaults.
 
 ## Rule Details
@@ -13,6 +17,7 @@ export default makeClass // Multiple default exports.
 ```
 
 or
+
 ```js
 export const foo = function () { /*...*/ } // Multiple exports of name 'foo'.
 
@@ -27,6 +32,6 @@ intent to rename, etc.
 
 ## Further Reading
 
-- Lee Byron's [ES7] export proposal
+ - Lee Byron's [ES7] export proposal
 
 [ES7]: https://github.com/leebyron/ecmascript-more-export-from
diff --git a/docs/rules/exports-last.md b/docs/rules/exports-last.md
index 291daee48..56e947e94 100644
--- a/docs/rules/exports-last.md
+++ b/docs/rules/exports-last.md
@@ -1,7 +1,8 @@
 # import/exports-last
 
-This rule enforces that all exports are declared at the bottom of the file. This rule will report any export declarations that comes before any non-export statements.
+<!-- end auto-generated rule header -->
 
+This rule enforces that all exports are declared at the bottom of the file. This rule will report any export declarations that comes before any non-export statements.
 
 ## This will be reported
 
@@ -43,7 +44,7 @@ export const str = 'foo'
 
 If you don't mind exports being sprinkled throughout a file, you may not want to enable this rule.
 
-#### ES6 exports only
+### ES6 exports only
 
 The exports-last rule is currently only working on ES6 exports. You may not want to enable this rule if you're using CommonJS exports.
 
diff --git a/docs/rules/extensions.md b/docs/rules/extensions.md
index 6e1d2b50a..bd9f3f358 100644
--- a/docs/rules/extensions.md
+++ b/docs/rules/extensions.md
@@ -1,6 +1,8 @@
-# import/extensions - Ensure consistent use of file extension within the import path
+# import/extensions
 
-Some file resolve algorithms allow you to omit the file extension within the import source path. For example the `node` resolver can resolve `./foo/bar` to the absolute path `/User/someone/foo/bar.js` because the `.js` extension is resolved automatically by default. Depending on the resolver you can configure more extensions to get resolved automatically.
+<!-- end auto-generated rule header -->
+
+Some file resolve algorithms allow you to omit the file extension within the import source path. For example the `node` resolver (which does not yet support ESM/`import`) can resolve `./foo/bar` to the absolute path `/User/someone/foo/bar.js` because the `.js` extension is resolved automatically by default in CJS. Depending on the resolver you can configure more extensions to get resolved automatically.
 
 In order to provide a consistent use of file extensions across your code base, this rule can enforce or disallow the use of certain file extensions.
 
@@ -8,13 +10,13 @@ In order to provide a consistent use of file extensions across your code base, t
 
 This rule either takes one string option, one object option, or a string and an object option. If it is the string `"never"` (the default value), then the rule forbids the use for any extension. If it is the string `"always"`, then the rule enforces the use of extensions for all import statements. If it is the string `"ignorePackages"`, then the rule enforces the use of extensions for all import statements except package imports.
 
-```
+```jsonc
 "import/extensions": [<severity>, "never" | "always" | "ignorePackages"]
 ```
 
 By providing an object you can configure each extension separately.
 
-```
+```jsonc
 "import/extensions": [<severity>, {
   <extension>: "never" | "always" | "ignorePackages"
 }]
@@ -24,25 +26,89 @@ By providing an object you can configure each extension separately.
 
 By providing both a string and an object, the string will set the default setting for all extensions, and the object can be used to set granular overrides for specific extensions.
 
-```
+```jsonc
 "import/extensions": [
   <severity>,
   "never" | "always" | "ignorePackages",
   {
-    <extension>: "never" | "always" | "ignorePackages" 
+    <extension>: "never" | "always" | "ignorePackages"
   }
 ]
 ```
 
 For example, `["error", "never", { "svg": "always" }]` would require that all extensions are omitted, except for "svg".
 
+`ignorePackages` can be set as a separate boolean option like this:
+
+```jsonc
+"import/extensions": [
+  <severity>,
+  "never" | "always" | "ignorePackages",
+  {
+    ignorePackages: true | false,
+    pattern: {
+      <extension>: "never" | "always" | "ignorePackages"
+    }
+  }
+]
+```
+
+In that case, if you still want to specify extensions, you can do so inside the **pattern** property.
+Default value of `ignorePackages` is `false`.
+
+By default, `import type` and `export type` style imports/exports are ignored. If you want to check them as well, you can set the `checkTypeImports` option to `true`.
+
+Unfortunately, in more advanced linting setups, such as when employing custom specifier aliases (e.g. you're using `eslint-import-resolver-alias`, `paths` in `tsconfig.json`, etc), this rule can be too coarse-grained when determining which imports to ignore and on which to enforce the config.
+This is especially troublesome if you have import specifiers that [look like externals or builtins](./order.md#how-imports-are-grouped).
+
+Set `pathGroupOverrides` to force this rule to always ignore certain imports and never ignore others.
+`pathGroupOverrides` accepts an array of one or more [`PathGroupOverride`](#pathgroupoverride) objects.
+
+For example:
+
+```jsonc
+"import/extensions": [
+  <severity>,
+  "never" | "always" | "ignorePackages",
+  {
+    ignorePackages: true | false,
+    pattern: {
+      <extension>: "never" | "always" | "ignorePackages"
+    },
+    pathGroupOverrides: [
+      {
+        pattern: "package-name-to-ignore",
+        action: "ignore",
+      },
+      {
+        pattern: "bespoke+alias:{*,*/**}",
+        action: "enforce",
+      }
+    ]
+  }
+]
+```
+
+> \[!NOTE]
+>
+> `pathGroupOverrides` is inspired by [`pathGroups` in `'import/order'`](./order.md#pathgroups) and shares a similar interface.
+> If you're using `pathGroups` already, you may find `pathGroupOverrides` very useful.
+
+### `PathGroupOverride`
+
+|     property     | required |          type           | description                                                     |
+| :--------------: | :------: | :---------------------: | --------------------------------------------------------------- |
+|     `pattern`    |    ☑️     |        `string`         | [Minimatch pattern][16] for specifier matching                  |
+| `patternOptions` |          |        `object`         | [Minimatch options][17]; default: `{nocomment: true}`           |
+|      `action`    |    ☑️     | `"enforce" \| "ignore"` | What action to take on imports whose specifiers match `pattern` |
+
 ### Exception
 
 When disallowing the use of certain extensions this rule makes an exception and allows the use of extension when the file would not be resolvable without extension.
 
 For example, given the following folder structure:
 
-```
+```pt
 ├── foo
 │   ├── bar.js
 │   ├── bar.json
@@ -84,6 +150,14 @@ import express from 'express/index';
 import * as path from 'path';
 ```
 
+The following patterns are considered problems when the configuration is set to "never" and the option "checkTypeImports" is set to `true`:
+
+```js
+import type { Foo } from './foo.ts';
+
+export type { Foo } from './foo.ts';
+```
+
 The following patterns are considered problems when configuration set to "always":
 
 ```js
@@ -93,7 +167,7 @@ import bar from './bar';
 
 import Component from './Component';
 
-import express from 'express';
+import foo from '@/foo';
 ```
 
 The following patterns are not considered problems when configuration set to "always":
@@ -105,9 +179,9 @@ import bar from './bar.json';
 
 import Component from './Component.jsx';
 
-import express from 'express/index.js';
-
 import * as path from 'path';
+
+import foo from '@/foo.js';
 ```
 
 The following patterns are considered problems when configuration set to "ignorePackages":
@@ -132,6 +206,7 @@ import Component from './Component.jsx';
 
 import express from 'express';
 
+import foo from '@/foo'
 ```
 
 The following patterns are not considered problems when configuration set to `['error', 'always', {ignorePackages: true} ]`:
@@ -143,8 +218,22 @@ import baz from 'foo/baz.js';
 
 import express from 'express';
 
+import foo from '@/foo';
+```
+
+The following patterns are considered problems when the configuration is set to "always" and the option "checkTypeImports" is set to `true`:
+
+```js
+import type { Foo } from './foo';
+
+export type { Foo } from './foo';
 ```
 
 ## When Not To Use It
 
 If you are not concerned about a consistent usage of file extension.
+
+In the future, when this rule supports native node ESM resolution, and the plugin is configured to use native rather than transpiled ESM (a config option that is not yet available) - setting this to `always` will have no effect.
+
+[16]: https://www.npmjs.com/package/minimatch#features
+[17]: https://www.npmjs.com/package/minimatch#options
diff --git a/docs/rules/first.md b/docs/rules/first.md
index eada966c8..c765a2973 100644
--- a/docs/rules/first.md
+++ b/docs/rules/first.md
@@ -1,5 +1,9 @@
 # import/first
 
+🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).
+
+<!-- end auto-generated rule header -->
+
 This rule reports any imports that come after non-import
 statements.
 
@@ -45,7 +49,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.
@@ -53,6 +57,7 @@ Given that, see [#255] for the reasoning.
 ### With Fixer
 
 This rule contains a fixer to reorder in-body import to top, the following criteria applied:
+
 1. Never re-order relative to each other, even if `absolute-first` is set.
 2. If an import creates an identifier, and that identifier is referenced at module level *before* the import itself, that won't be re-ordered.
 
@@ -63,8 +68,8 @@ enable this rule.
 
 ## Further Reading
 
-- [`import/order`]: a major step up from `absolute-first`
-- Issue [#255]
+ - [`import/order`]: a major step up from `absolute-first`
+ - Issue [#255]
 
 [`import/order`]: ./order.md
-[#255]: https://github.com/benmosher/eslint-plugin-import/issues/255
+[#255]: https://github.com/import-js/eslint-plugin-import/issues/255
diff --git a/docs/rules/group-exports.md b/docs/rules/group-exports.md
index b0d88f4a0..9fb212de6 100644
--- a/docs/rules/group-exports.md
+++ b/docs/rules/group-exports.md
@@ -1,5 +1,7 @@
 # import/group-exports
 
+<!-- end auto-generated rule header -->
+
 Reports when named exports are not grouped together in a single `export` declaration or when multiple assignments to CommonJS `module.exports` or `exports` object are present in a single file.
 
 **Rationale:** An `export` declaration or `module.exports` assignment can appear anywhere in the code. By requiring a single export declaration all your exports will remain at one place, making it easier to see what exports a module provides.
@@ -26,6 +28,12 @@ export {
 }
 ```
 
+```js
+// Aggregating exports -> ok
+export { default as module1 } from 'module-1'
+export { default as module2 } from 'module-2'
+```
+
 ```js
 // A single exports assignment -> ok
 module.exports = {
@@ -54,6 +62,14 @@ test.another = true
 module.exports = test
 ```
 
+```ts
+const first = true;
+type firstType = boolean
+
+// A single named export declaration (type exports handled separately) -> ok
+export {first}
+export type {firstType}
+```
 
 ### Invalid
 
@@ -63,6 +79,12 @@ export const first = true
 export const second = true
 ```
 
+```js
+// Aggregating exports from the same module -> not ok!
+export { module1 } from 'module-1'
+export { module2 } from 'module-1'
+```
+
 ```js
 // Multiple exports assignments -> not ok!
 exports.first = true
@@ -82,6 +104,15 @@ module.exports.first = true
 module.exports.second = true
 ```
 
+```ts
+type firstType = boolean
+type secondType = any
+
+// Multiple named type export statements -> not ok!
+export type {firstType}
+export type {secondType}
+```
+
 ## When Not To Use It
 
 If you do not mind having your exports spread across the file, you can safely turn this rule off.
diff --git a/docs/rules/imports-first.md b/docs/rules/imports-first.md
new file mode 100644
index 000000000..278e4c472
--- /dev/null
+++ b/docs/rules/imports-first.md
@@ -0,0 +1,9 @@
+# import/imports-first
+
+❌ This rule is deprecated.
+
+🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).
+
+<!-- end auto-generated rule header -->
+
+This rule was **deprecated** in eslint-plugin-import v2.0.0. Please use the corresponding rule [`first`](https://github.com/import-js/eslint-plugin-import/blob/HEAD/docs/rules/first.md).
diff --git a/docs/rules/max-dependencies.md b/docs/rules/max-dependencies.md
index 20d29cf0e..1ecbca64d 100644
--- a/docs/rules/max-dependencies.md
+++ b/docs/rules/max-dependencies.md
@@ -1,25 +1,27 @@
 # import/max-dependencies
 
+<!-- end auto-generated rule header -->
+
 Forbid modules to have too many dependencies (`import` or `require` statements).
 
 This is a useful rule because a module with too many dependencies is a code smell, and usually indicates the module is doing too much and/or should be broken up into smaller modules.
 
 Importing multiple named exports from a single module will only count once (e.g. `import {x, y, z} from './foo'` will only count as a single dependency).
 
-### Options
-
-This rule takes the following option:
+## Options
 
-`max`: The maximum number of dependencies allowed. Anything over will trigger the rule. **Default is 10** if the rule is enabled and no `max` is specified.
-
-You can set the option like this:
+This rule has the following options, with these defaults:
 
 ```js
-"import/max-dependencies": ["error", {"max": 10}]
+"import/max-dependencies": ["error", {
+  "max": 10,
+  "ignoreTypeImports": false,
+}]
 ```
 
+### `max`
 
-## Example
+This option sets the maximum number of dependencies allowed. Anything over will trigger the rule. **Default is 10** if the rule is enabled and no `max` is specified.
 
 Given a max value of `{"max": 2}`:
 
@@ -39,6 +41,30 @@ const anotherA = require('./a'); // still 1
 import {x, y, z} from './foo'; // 2
 ```
 
+### `ignoreTypeImports`
+
+Ignores `type` imports. Type imports are a feature released in TypeScript 3.8, you can [read more here](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-8.html#type-only-imports-and-export). Defaults to `false`.
+
+Given `{"max": 2, "ignoreTypeImports": true}`:
+
+<!-- markdownlint-disable-next-line MD024 -- duplicate header -->
+### Fail
+
+```ts
+import a from './a';
+import b from './b';
+import c from './c';
+```
+
+<!-- markdownlint-disable-next-line MD024 -- duplicate header -->
+### Pass
+
+```ts
+import a from './a';
+import b from './b';
+import type c from './c'; // Doesn't count against max
+```
+
 ## When Not To Use It
 
 If you don't care how many dependencies a module has.
diff --git a/docs/rules/named.md b/docs/rules/named.md
index 01ccb14ae..44f8dc658 100644
--- a/docs/rules/named.md
+++ b/docs/rules/named.md
@@ -1,11 +1,15 @@
 # import/named
 
+💼🚫 This rule is enabled in the following configs: ❗ `errors`, ☑️ `recommended`. This rule is _disabled_ in the ⌨️ `typescript` config.
+
+<!-- end auto-generated rule header -->
+
 Verifies that all named imports are part of the set of named exports in the referenced module.
 
 For `export`, verifies that all named exports exist in the referenced module.
 
 Note: for packages, the plugin will find exported names
-from [`jsnext:main`], if present in `package.json`.
+from [`jsnext:main`] (deprecated) or `module`, if present in `package.json`.
 Redux's npm module includes this key, and thereby is lintable, for example.
 
 A module path that is [ignored] or not [unambiguously an ES module] will not be reported when imported. Note that type imports and exports, as used by [Flow], are always ignored.
@@ -14,7 +18,6 @@ A module path that is [ignored] or not [unambiguously an ES module] will not be
 [unambiguously an ES module]: https://github.com/bmeck/UnambiguousJavaScriptGrammar
 [Flow]: https://flow.org/
 
-
 ## Rule Details
 
 Given:
@@ -90,9 +93,10 @@ runtime, you will likely see false positives with this rule.
 
 ## Further Reading
 
-- [`import/ignore`] setting
-- [`jsnext:main`] (Rollup)
-
+ - [`import/ignore`] setting
+ - [`jsnext:main`] deprecation
+ - [`pkg.module`] (Rollup)
 
-[`jsnext:main`]: https://github.com/rollup/rollup/wiki/jsnext:main
+[`jsnext:main`]: https://github.com/jsforum/jsforum/issues/5
+[`pkg.module`]: https://github.com/rollup/rollup/wiki/pkg.module
 [`import/ignore`]: ../../README.md#importignore
diff --git a/docs/rules/namespace.md b/docs/rules/namespace.md
index 4bbbd378e..1a177f581 100644
--- a/docs/rules/namespace.md
+++ b/docs/rules/namespace.md
@@ -1,5 +1,9 @@
 # import/namespace
 
+💼 This rule is enabled in the following configs: ❗ `errors`, ☑️ `recommended`.
+
+<!-- end auto-generated rule header -->
+
 Enforces names exist at the time they are dereferenced, when imported as a full namespace (i.e. `import * as foo from './foo'; foo.bar();` will report if `bar` is not exported by `./foo`.).
 
 Will report at the import declaration if there are _no_ exported names found.
@@ -26,6 +30,7 @@ redefinition of the namespace in an intermediate scope. Adherence to the ESLint
 For [ES7], reports if an exported namespace would be empty (no names exported from the referenced module.)
 
 Given:
+
 ```js
 // @module ./named-exports
 export const a = 1
@@ -40,7 +45,9 @@ export class ExportedClass { }
 // ES7
 export * as deep from './deep'
 ```
+
 and:
+
 ```js
 // @module ./deep
 export const e = "MC2"
@@ -90,9 +97,9 @@ still can't be statically analyzed any further.
 
 ## Further Reading
 
-- Lee Byron's [ES7] export proposal
-- [`import/ignore`] setting
-- [`jsnext:main`](Rollup)
+ - Lee Byron's [ES7] export proposal
+ - [`import/ignore`] setting
+ - [`jsnext:main`](Rollup)
 
 [ES7]: https://github.com/leebyron/ecmascript-more-export-from
 [`import/ignore`]: ../../README.md#importignore
diff --git a/docs/rules/newline-after-import.md b/docs/rules/newline-after-import.md
index 4883776c9..ef5aeed76 100644
--- a/docs/rules/newline-after-import.md
+++ b/docs/rules/newline-after-import.md
@@ -1,78 +1,159 @@
 # import/newline-after-import
 
+🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).
+
+<!-- end auto-generated rule header -->
+
 Enforces having one or more empty lines after the last top-level import statement or require call.
-+(fixable) The `--fix` option on the [command line] automatically fixes problems reported by this rule.
 
 ## Rule Details
 
-This rule has one option, `count` which sets the number of newlines that are enforced after the last top-level import statement or require call. This option defaults to `1`.
+This rule supports the following options:
+
+ - `count` which sets the number of newlines that are enforced after the last top-level import statement or require call. This option defaults to `1`.
+
+ - `exactCount` which enforce the exact numbers of newlines that is mentioned in `count`. This option defaults to `false`.
+
+ - `considerComments` which enforces the rule on comments after the last import-statement as well when set to true. This option defaults to `false`.
 
 Valid:
 
 ```js
-import defaultExport from './foo'
+import defaultExport from './foo';
 
-const FOO = 'BAR'
+const FOO = 'BAR';
 ```
 
 ```js
-import defaultExport from './foo'
-import { bar }  from 'bar-lib'
+import defaultExport from './foo';
+import { bar }  from 'bar-lib';
 
-const FOO = 'BAR'
+const FOO = 'BAR';
 ```
 
 ```js
-const FOO = require('./foo')
-const BAR = require('./bar')
+const FOO = require('./foo');
+const BAR = require('./bar');
 
-const BAZ = 1
+const BAZ = 1;
 ```
 
 Invalid:
 
 ```js
 import * as foo from 'foo'
-const FOO = 'BAR'
+const FOO = 'BAR';
 ```
 
 ```js
-import * as foo from 'foo'
-const FOO = 'BAR'
+import * as foo from 'foo';
+const FOO = 'BAR';
 
-import { bar }  from 'bar-lib'
+import { bar }  from 'bar-lib';
 ```
 
 ```js
-const FOO = require('./foo')
-const BAZ = 1
-const BAR = require('./bar')
+const FOO = require('./foo');
+const BAZ = 1;
+const BAR = require('./bar');
 ```
 
 With `count` set to `2` this will be considered valid:
 
 ```js
-import defaultExport from './foo'
+import defaultExport from './foo';
 
 
-const FOO = 'BAR'
+const FOO = 'BAR';
+```
+
+```js
+import defaultExport from './foo';
+
+
+
+const FOO = 'BAR';
 ```
 
 With `count` set to `2` these will be considered invalid:
 
+```js
+import defaultExport from './foo';
+const FOO = 'BAR';
+```
+
+```js
+import defaultExport from './foo';
+
+const FOO = 'BAR';
+```
+
+With `count` set to `2` and `exactCount` set to `true` this will be considered valid:
+
+```js
+import defaultExport from './foo';
+
+
+const FOO = 'BAR';
+```
+
+With `count` set to `2` and `exactCount` set to `true` these will be considered invalid:
+
+```js
+import defaultExport from './foo';
+const FOO = 'BAR';
+```
+
+```js
+import defaultExport from './foo';
+
+const FOO = 'BAR';
+```
+
+```js
+import defaultExport from './foo';
+
+
+
+const FOO = 'BAR';
+```
+
+```js
+import defaultExport from './foo';
+
+
+
+
+const FOO = 'BAR';
+```
+
+With `considerComments` set to `false` this will be considered valid:
+
 ```js
 import defaultExport from './foo'
+// some comment here.
 const FOO = 'BAR'
 ```
 
+With `considerComments` set to `true` this will be considered valid:
+
 ```js
 import defaultExport from './foo'
 
+// some comment here.
 const FOO = 'BAR'
 ```
 
+With `considerComments` set to `true` this will be considered invalid:
+
+```js
+import defaultExport from './foo'
+// some comment here.
+const FOO = 'BAR'
+```
 
 ## Example options usage
+
 ```json
 {
   "rules": {
@@ -81,7 +162,6 @@ const FOO = 'BAR'
 }
 ```
 
-
 ## When Not To Use It
 
 If you like to visually group module imports with its usage, you don't want to use this rule.
diff --git a/docs/rules/no-absolute-path.md b/docs/rules/no-absolute-path.md
index 305e8e605..48fb9532b 100644
--- a/docs/rules/no-absolute-path.md
+++ b/docs/rules/no-absolute-path.md
@@ -1,7 +1,13 @@
-# import/no-absolute-path: Forbid import of modules using absolute paths
+# import/no-absolute-path
+
+🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).
+
+<!-- end auto-generated rule header -->
 
 Node.js allows the import of modules using an absolute path such as `/home/xyz/file.js`. That is a bad practice as it ties the code using it to your computer, and therefore makes it unusable in packages distributed on `npm` for instance.
 
+This rule forbids the import of modules using absolute paths.
+
 ## Rule Details
 
 ### Fail
@@ -32,9 +38,9 @@ By default, only ES6 imports and CommonJS `require` calls will have this rule en
 
 You may provide an options object providing true/false for any of
 
-- `esmodule`: defaults to `true`
-- `commonjs`: defaults to `true`
-- `amd`: defaults to `false`
+ - `esmodule`: defaults to `true`
+ - `commonjs`: defaults to `true`
+ - `amd`: defaults to `false`
 
 If `{ amd: true }` is provided, dependency paths for AMD-style `define` and `require`
 calls will be resolved:
diff --git a/docs/rules/no-amd.md b/docs/rules/no-amd.md
index f7146c134..6e592ba75 100644
--- a/docs/rules/no-amd.md
+++ b/docs/rules/no-amd.md
@@ -1,5 +1,7 @@
 # import/no-amd
 
+<!-- end auto-generated rule header -->
+
 Reports `require([array], ...)` and `define([array], ...)` function calls at the
 module scope. Will not report if !=2 arguments, or first argument is not a literal array.
 
@@ -31,5 +33,5 @@ Special thanks to @xjamundx for donating his no-define rule as a start to this.
 
 ## Further Reading
 
-- [`no-commonjs`](./no-commonjs.md): report CommonJS `require` and `exports`
-- Source: https://github.com/xjamundx/eslint-plugin-modules
+ - [`no-commonjs`](./no-commonjs.md): report CommonJS `require` and `exports`
+ - Source: <https://github.com/xjamundx/eslint-plugin-modules>
diff --git a/docs/rules/no-anonymous-default-export.md b/docs/rules/no-anonymous-default-export.md
index c8db89790..70efb8450 100644
--- a/docs/rules/no-anonymous-default-export.md
+++ b/docs/rules/no-anonymous-default-export.md
@@ -1,5 +1,7 @@
 # import/no-anonymous-default-export
 
+<!-- end auto-generated rule header -->
+
 Reports if a module's default export is unnamed. This includes several types of unnamed data types; literals, object expressions, arrays, anonymous functions, arrow functions, and anonymous class declarations.
 
 Ensuring that default exports are named helps improve the grepability of the codebase by encouraging the re-use of the same identifier for the module's default export at its declaration site and at its import sites.
@@ -17,6 +19,7 @@ The complete default configuration looks like this.
   "allowAnonymousClass": false,
   "allowAnonymousFunction": false,
   "allowCallExpression": true, // The true value here is for backward compatibility
+  "allowNew": false,
   "allowLiteral": false,
   "allowObject": false
 }]
@@ -25,6 +28,7 @@ The complete default configuration looks like this.
 ## Rule Details
 
 ### Fail
+
 ```js
 export default []
 
@@ -40,9 +44,12 @@ export default foo(bar)
 export default 123
 
 export default {}
+
+export default new Foo()
 ```
 
 ### Pass
+
 ```js
 const foo = 123
 export default foo
@@ -70,4 +77,7 @@ export default 123
 
 /* eslint import/no-anonymous-default-export: [2, {"allowObject": true}] */
 export default {}
+
+/* eslint import/no-anonymous-default-export: [2, {"allowNew": true}] */
+export default new Foo()
 ```
diff --git a/docs/rules/no-commonjs.md b/docs/rules/no-commonjs.md
index 4353886bf..4dc9c8c5d 100644
--- a/docs/rules/no-commonjs.md
+++ b/docs/rules/no-commonjs.md
@@ -1,5 +1,7 @@
 # import/no-commonjs
 
+<!-- end auto-generated rule header -->
+
 Reports `require([string])` function calls. Will not report if >1 argument,
 or single argument is not a literal string.
 
@@ -27,15 +29,30 @@ If `allowRequire` option is set to `true`, `require` calls are valid:
 
 ```js
 /*eslint no-commonjs: [2, { allowRequire: true }]*/
+var mod = require('./mod');
+```
+
+but `module.exports` is reported as usual.
+
+### Allow conditional require
+
+By default, conditional requires are allowed:
+
+```js
+var a = b && require("c")
 
 if (typeof window !== "undefined") {
   require('that-ugly-thing');
 }
+
+var fs = null;
+try {
+  fs = require("fs")
+} catch (error) {}
 ```
 
-but `module.exports` is reported as usual.
+If the `allowConditionalRequire` option is set to `false`, they will be reported.
 
-This is useful for conditional requires.
 If you don't rely on synchronous module loading, check out [dynamic import](https://github.com/airbnb/babel-plugin-dynamic-import-node).
 
 ### Allow primitive modules
@@ -69,12 +86,11 @@ don't want this rule.
 It is also fairly noisy if you have a larger codebase that is being transitioned
 from CommonJS to ES6 modules.
 
-
 ## Contributors
 
 Special thanks to @xjamundx for donating the module.exports and exports.* bits.
 
 ## Further Reading
 
-- [`no-amd`](./no-amd.md): report on AMD `require`, `define`
-- Source: https://github.com/xjamundx/eslint-plugin-modules
+ - [`no-amd`](./no-amd.md): report on AMD `require`, `define`
+ - Source: <https://github.com/xjamundx/eslint-plugin-modules>
diff --git a/docs/rules/no-cycle.md b/docs/rules/no-cycle.md
index 8819d6704..898b75330 100644
--- a/docs/rules/no-cycle.md
+++ b/docs/rules/no-cycle.md
@@ -1,8 +1,10 @@
 # import/no-cycle
 
+<!-- end auto-generated rule header -->
+
 Ensures that there is no resolvable path back to this module via its dependencies.
 
-This includes cycles of depth 1 (imported module imports me) to `Infinity`, if the
+This includes cycles of depth 1 (imported module imports me) to `"∞"` (or `Infinity`), if the
 [`maxDepth`](#maxdepth) option is not set.
 
 ```js
@@ -20,6 +22,7 @@ import { b } from './dep-b.js' // reported: Dependency cycle detected.
 This rule does _not_ detect imports that resolve directly to the linted module;
 for that, see [`no-self-import`].
 
+This rule ignores type-only imports in Flow and TypeScript syntax (`import type` and `import typeof`), which have no runtime effect.
 
 ## Rule Details
 
@@ -55,6 +58,50 @@ import { b } from './dep-b.js' // not reported as the cycle is at depth 2
 This is not necessarily recommended, but available as a cost/benefit tradeoff mechanism
 for reducing total project lint time, if needed.
 
+#### `ignoreExternal`
+
+An `ignoreExternal` option is available to prevent the cycle detection to expand to external modules:
+
+```js
+/*eslint import/no-cycle: [2, { ignoreExternal: true }]*/
+
+// dep-a.js
+import 'module-b/dep-b.js'
+
+export function a() { /* ... */ }
+```
+
+```js
+// node_modules/module-b/dep-b.js
+import { a } from './dep-a.js' // not reported as this module is external
+```
+
+Its value is `false` by default, but can be set to `true` for reducing total project lint time, if needed.
+
+#### `allowUnsafeDynamicCyclicDependency`
+
+This option disable reporting of errors if a cycle is detected with at least one dynamic import.
+
+```js
+// bar.js
+import { foo } from './foo';
+export const bar = foo;
+
+// foo.js
+export const foo = 'Foo';
+export function getBar() { return import('./bar'); }
+```
+
+> Cyclic dependency are **always** a dangerous anti-pattern as discussed extensively in [#2265](https://github.com/import-js/eslint-plugin-import/issues/2265). Please be extra careful about using this option.
+
+#### `disableScc`
+
+This option disables a pre-processing step that calculates [Strongly Connected Components](https://en.wikipedia.org/wiki/Strongly_connected_component), which are used for avoiding unnecessary work checking files in different SCCs for cycles.
+
+However, under some configurations, this pre-processing may be more expensive than the time it saves.
+
+When this option is `true`, we don't calculate any SCC graph, and check all files for cycles (leading to higher time-complexity). Default is `false`.
+
 ## When Not To Use It
 
 This rule is comparatively computationally expensive. If you are pressed for lint
@@ -63,7 +110,10 @@ this rule enabled.
 
 ## Further Reading
 
-- [Original inspiring issue](https://github.com/benmosher/eslint-plugin-import/issues/941)
-- Rule to detect that module imports itself: [`no-self-import`]
+ - [Original inspiring issue](https://github.com/import-js/eslint-plugin-import/issues/941)
+ - Rule to detect that module imports itself: [`no-self-import`]
+ - [`import/external-module-folders`] setting
 
 [`no-self-import`]: ./no-self-import.md
+
+[`import/external-module-folders`]: ../../README.md#importexternal-module-folders
diff --git a/docs/rules/no-default-export.md b/docs/rules/no-default-export.md
index 4f1a300a2..586d5e745 100644
--- a/docs/rules/no-default-export.md
+++ b/docs/rules/no-default-export.md
@@ -1,4 +1,6 @@
-# `import/no-default-export`
+# import/no-default-export
+
+<!-- end auto-generated rule header -->
 
 Prohibit default exports. Mostly an inverse of [`prefer-default-export`].
 
diff --git a/docs/rules/no-deprecated.md b/docs/rules/no-deprecated.md
index c948b5178..a647d77ad 100644
--- a/docs/rules/no-deprecated.md
+++ b/docs/rules/no-deprecated.md
@@ -1,7 +1,9 @@
-# `import/no-deprecated`
+# import/no-deprecated
+
+<!-- end auto-generated rule header -->
 
 Reports use of a deprecated name, as indicated by a JSDoc block with a `@deprecated`
-tag or TomDoc `Deprecated: ` comment.
+tag or TomDoc `Deprecated:` comment.
 
 using a JSDoc `@deprecated` tag:
 
@@ -43,19 +45,18 @@ export function multiply(six, nine) {
 Only JSDoc is enabled by default. Other documentation styles can be enabled with
 the `import/docstyle` setting.
 
-
 ```yaml
 # .eslintrc.yml
 settings:
   import/docstyle: ['jsdoc', 'tomdoc']
 ```
 
-### Worklist
+## Worklist
 
-- [x] report explicit imports on the import node
-- [x] support namespaces
-  - [x] should bubble up through deep namespaces (#157)
-- [x] report explicit imports at reference time (at the identifier) similar to namespace
-- [x] mark module deprecated if file JSDoc has a @deprecated tag?
-- [ ] don't flag redeclaration of imported, deprecated names
-- [ ] flag destructuring
+ - [x] report explicit imports on the import node
+ - [x] support namespaces
+   - [x] should bubble up through deep namespaces (#157)
+ - [x] report explicit imports at reference time (at the identifier) similar to namespace
+ - [x] mark module deprecated if file JSDoc has a @deprecated tag?
+ - [ ] don't flag redeclaration of imported, deprecated names
+ - [ ] flag destructuring
diff --git a/docs/rules/no-duplicates.md b/docs/rules/no-duplicates.md
index 0641e4418..29c16f15d 100644
--- a/docs/rules/no-duplicates.md
+++ b/docs/rules/no-duplicates.md
@@ -1,17 +1,23 @@
 # import/no-duplicates
 
+⚠️ This rule _warns_ in the following configs: ☑️ `recommended`, 🚸 `warnings`.
+
+🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).
+
+<!-- end auto-generated rule header -->
+
 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`)
-2. this version distinguishes Flow `type` imports from standard imports. ([#334](https://github.com/benmosher/eslint-plugin-import/pull/334))
+2. this version distinguishes Flow `type` imports from standard imports. ([#334](https://github.com/import-js/eslint-plugin-import/pull/334))
 
 ## Rule Details
 
 Valid:
+
 ```js
 import SomeDefaultClass, * as names from './mod'
 // Flow `type` import from same module is fine
@@ -36,6 +42,65 @@ The motivation is that this is likely a result of two developers importing diffe
 names from the same module at different times (and potentially largely different
 locations in the file.) This rule brings both (or n-many) to attention.
 
+### Query Strings
+
+By default, this rule ignores query strings (i.e. paths followed by a question mark), and thus imports from `./mod?a` and `./mod?b` will be considered as duplicates. However you can use the option `considerQueryString` to handle them as different (primarily because browsers will resolve those imports differently).
+
+Config:
+
+```json
+"import/no-duplicates": ["error", {"considerQueryString": true}]
+```
+
+And then the following code becomes valid:
+
+```js
+import minifiedMod from './mod?minify'
+import noCommentsMod from './mod?comments=0'
+import originalMod from './mod'
+```
+
+It will still catch duplicates when using the same module and the exact same query string:
+
+```js
+import SomeDefaultClass from './mod?minify'
+
+// This is invalid, assuming `./mod` and `./mod.js` are the same target:
+import * from './mod.js?minify'
+```
+
+### Inline Type imports
+
+TypeScript 4.5 introduced a new [feature](https://devblogs.microsoft.com/typescript/announcing-typescript-4-5/#type-on-import-names) that allows mixing of named value and type imports. In order to support fixing to an inline type import when duplicate imports are detected, `prefer-inline` can be set to true.
+
+Config:
+
+```json
+"import/no-duplicates": ["error", {"prefer-inline": true}]
+```
+
+<!--tabs-->
+
+❌ Invalid `["error", {"prefer-inline": true}]`
+
+```js
+import { AValue, type AType } from './mama-mia'
+import type { BType } from './mama-mia'
+
+import { CValue } from './papa-mia'
+import type { CType } from './papa-mia'
+```
+
+✅ Valid with `["error", {"prefer-inline": true}]`
+
+```js
+import { AValue, type AType, type BType } from './mama-mia'
+
+import { CValue, type CType } from './papa-mia'
+```
+
+<!--tabs-->
+
 ## When Not To Use It
 
 If the core ESLint version is good enough (i.e. you're _not_ using Flow and you _are_ using [`import/extensions`](./extensions.md)), keep it and don't use this.
diff --git a/docs/rules/no-dynamic-require.md b/docs/rules/no-dynamic-require.md
index 0f7bb6d37..292055fcd 100644
--- a/docs/rules/no-dynamic-require.md
+++ b/docs/rules/no-dynamic-require.md
@@ -1,8 +1,10 @@
-# import/no-dynamic-require: Forbid `require()` calls with expressions
+# import/no-dynamic-require
+
+<!-- end auto-generated rule header -->
 
 The `require` method from CommonJS is used to import modules from different files. Unlike the ES6 `import` syntax, it can be given expressions that will be resolved at runtime. While this is sometimes necessary and useful, in most cases it isn't. Using expressions (for instance, concatenating a path and variable) as the argument makes it harder for tools to do static code analysis, or to find where in the codebase a module is used.
 
-This rule checks every call to `require()` that uses expressions for the module name argument.
+This rule forbids every call to `require()` that uses expressions for the module name argument.
 
 ## Rule Details
 
diff --git a/docs/rules/no-empty-named-blocks.md b/docs/rules/no-empty-named-blocks.md
new file mode 100644
index 000000000..ad83c535f
--- /dev/null
+++ b/docs/rules/no-empty-named-blocks.md
@@ -0,0 +1,49 @@
+# import/no-empty-named-blocks
+
+🔧💡 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix) and manually fixable by [editor suggestions](https://eslint.org/docs/latest/use/core-concepts#rule-suggestions).
+
+<!-- end auto-generated rule header -->
+
+Reports the use of empty named import blocks.
+
+## Rule Details
+
+### Valid
+
+```js
+import { mod } from 'mod'
+import Default, { mod } from 'mod'
+```
+
+When using typescript
+
+```js
+import type { mod } from 'mod'
+```
+
+When using flow
+
+```js
+import typeof { mod } from 'mod'
+```
+
+### Invalid
+
+```js
+import {} from 'mod'
+import Default, {} from 'mod'
+```
+
+When using typescript
+
+```js
+import type Default, {} from 'mod'
+import type {} from 'mod'
+```
+
+When using flow
+
+```js
+import typeof {} from 'mod'
+import typeof Default, {} from 'mod'
+```
diff --git a/docs/rules/no-extraneous-dependencies.md b/docs/rules/no-extraneous-dependencies.md
index 2b66aa25c..848d5bb0d 100644
--- a/docs/rules/no-extraneous-dependencies.md
+++ b/docs/rules/no-extraneous-dependencies.md
@@ -1,19 +1,24 @@
-# import/no-extraneous-dependencies: Forbid the use of extraneous packages
+# import/no-extraneous-dependencies
 
-Forbid the import of external modules that are not declared in the `package.json`'s `dependencies`, `devDependencies`, `optionalDependencies` or `peerDependencies`.
-The closest parent `package.json` will be used. If no `package.json` is found, the rule will not lint anything. This behaviour can be changed with the rule option `packageDir`.
+<!-- end auto-generated rule header -->
+
+Forbid the import of external modules that are not declared in the `package.json`'s `dependencies`, `devDependencies`, `optionalDependencies`, `peerDependencies`, or `bundledDependencies`.
+The closest parent `package.json` will be used. If no `package.json` is found, the rule will not lint anything. This behavior can be changed with the rule option `packageDir`. Normally ignores imports of modules marked internal, but this can be changed with the rule option `includeInternal`. Type imports can be verified by specifying `includeTypes`.
 
 Modules have to be installed for this rule to work.
 
-### Options
+## Options
 
 This rule supports the following options:
 
 `devDependencies`: If set to `false`, then the rule will show an error when `devDependencies` are imported. Defaults to `true`.
+Type imports are ignored by default.
 
 `optionalDependencies`: If set to `false`, then the rule will show an error when `optionalDependencies` are imported. Defaults to `true`.
 
-`peerDependencies`: If set to `false`, then the rule will show an error when `peerDependencies` are imported. Defaults to `false`.
+`peerDependencies`: If set to `false`, then the rule will show an error when `peerDependencies` are imported. Defaults to `true`.
+
+`bundledDependencies`: If set to `false`, then the rule will show an error when `bundledDependencies` are imported. Defaults to `true`.
 
 You can set the options like this:
 
@@ -27,7 +32,13 @@ You can also use an array of globs instead of literal booleans:
 "import/no-extraneous-dependencies": ["error", {"devDependencies": ["**/*.test.js", "**/*.spec.js"]}]
 ```
 
-When using an array of globs, the setting will be set to `true` (no errors reported) if the name of the file being linted matches a single glob in the array, and `false` otherwise.
+When using an array of globs, the setting will be set to `true` (no errors reported) if the name of the file being linted (i.e. not the imported file/module) matches a single glob in the array, and `false` otherwise.
+
+There are 2 boolean options to opt into checking extra imports that are normally ignored: `includeInternal`, which enables the checking of internal modules, and `includeTypes`, which enables checking of type imports in TypeScript.
+
+```js
+"import/no-extraneous-dependencies": ["error", {"includeInternal": true, "includeTypes": true}]
+```
 
 Also there is one more option called `packageDir`, this option is to specify the path to the folder containing package.json.
 
@@ -49,6 +60,7 @@ folder layouts:
 ## Rule Details
 
 Given the following `package.json`:
+
 ```json
 {
   "name": "my-project",
@@ -70,11 +82,13 @@ Given the following `package.json`:
   },
   "peerDependencies": {
     "react": ">=15.0.0 <16.0.0"
-  }
+  },
+  "bundledDependencies": [
+    "@generated/foo",
+  ]
 }
 ```
 
-
 ## Fail
 
 ```js
@@ -90,8 +104,18 @@ var test = require('ava');
 /* eslint import/no-extraneous-dependencies: ["error", {"optionalDependencies": false}] */
 import isArray from 'lodash.isarray';
 var isArray = require('lodash.isarray');
-```
 
+/* eslint import/no-extraneous-dependencies: ["error", {"bundledDependencies": false}] */
+import foo from '"@generated/foo"';
+var foo = require('"@generated/foo"');
+
+/* eslint import/no-extraneous-dependencies: ["error", {"includeInternal": true}] */
+import foo from './foo';
+var foo = require('./foo');
+
+/* eslint import/no-extraneous-dependencies: ["error", {"includeTypes": true}] */
+import type { MyType } from 'foo';
+```
 
 ## Pass
 
@@ -103,12 +127,13 @@ var foo = require('./foo');
 import test from 'ava';
 import find from 'lodash.find';
 import isArray from 'lodash.isarray';
+import foo from '"@generated/foo"';
+import type { MyType } from 'foo';
 
 /* eslint import/no-extraneous-dependencies: ["error", {"peerDependencies": true}] */
 import react from 'react';
 ```
 
-
 ## When Not To Use It
 
 If you do not have a `package.json` file in your project.
diff --git a/docs/rules/no-import-module-exports.md b/docs/rules/no-import-module-exports.md
new file mode 100644
index 000000000..1c5722649
--- /dev/null
+++ b/docs/rules/no-import-module-exports.md
@@ -0,0 +1,81 @@
+# import/no-import-module-exports
+
+🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).
+
+<!-- end auto-generated rule header -->
+
+Reports the use of import declarations with CommonJS exports in any module
+except for the [main module](https://docs.npmjs.com/files/package.json#main).
+
+If you have multiple entry points or are using `js:next` this rule includes an
+`exceptions` option which you can use to exclude those files from the rule.
+
+## Options
+
+### `exceptions`
+
+ - An array of globs. The rule will be omitted from any file that matches a glob
+   in the options array. For example, the following setting will omit the rule
+   in the `some-file.js` file.
+
+```json
+"import/no-import-module-exports": ["error", {
+    "exceptions": ["**/*/some-file.js"]
+}]
+```
+
+## Rule Details
+
+### Fail
+
+```js
+import { stuff } from 'starwars'
+module.exports = thing
+
+import * as allThings from 'starwars'
+exports.bar = thing
+
+import thing from 'other-thing'
+exports.foo = bar
+
+import thing from 'starwars'
+const baz = module.exports = thing
+console.log(baz)
+```
+
+### Pass
+
+Given the following package.json:
+
+```json
+{
+  "main": "lib/index.js",
+}
+```
+
+```js
+import thing from 'other-thing'
+export default thing
+
+const thing = require('thing')
+module.exports = thing
+
+const thing = require('thing')
+exports.foo = bar
+
+import thing from 'otherthing'
+console.log(thing.module.exports)
+
+// in lib/index.js
+import foo from 'path';
+module.exports = foo;
+
+// in some-file.js
+// eslint import/no-import-module-exports: ["error", {"exceptions": ["**/*/some-file.js"]}]
+import foo from 'path';
+module.exports = foo;
+```
+
+### Further Reading
+
+ - [webpack issue #4039](https://github.com/webpack/webpack/issues/4039)
diff --git a/docs/rules/no-internal-modules.md b/docs/rules/no-internal-modules.md
index 8d99c3529..433b55140 100644
--- a/docs/rules/no-internal-modules.md
+++ b/docs/rules/no-internal-modules.md
@@ -1,16 +1,21 @@
 # import/no-internal-modules
 
+<!-- end auto-generated rule header -->
+
 Use this rule to prevent importing the submodules of other modules.
 
 ## Rule Details
 
-This rule has one option, `allow` which is an array of [minimatch/glob patterns](https://github.com/isaacs/node-glob#glob-primer) patterns that whitelist paths and import statements that can be imported with reaching.
+This rule has two mutally exclusive options that are arrays of [minimatch/glob patterns](https://github.com/isaacs/node-glob#glob-primer) patterns:
+
+ - `allow` that include paths and import statements that can be imported with reaching.
+ - `forbid` that exclude paths and import statements that can be imported with reaching.
 
 ### Examples
 
 Given the following folder structure:
 
-```
+```pt
 my-project
 ├── actions
 │   └── getUser.js
@@ -28,12 +33,13 @@ my-project
 ```
 
 And the .eslintrc file:
-```
+
+```json
 {
   ...
   "rules": {
     "import/no-internal-modules": [ "error", {
-      "allow": [ "**/actions/*", "source-map-support/*" ]
+      "allow": [ "**/actions/*", "source-map-support/*" ],
     } ]
   }
 }
@@ -49,6 +55,9 @@ The following patterns are considered problems:
 import { settings } from './app/index'; // Reaching to "./app/index" is not allowed
 import userReducer from './reducer/user'; // Reaching to "./reducer/user" is not allowed
 import configureStore from './redux/configureStore'; // Reaching to "./redux/configureStore" is not allowed
+
+export { settings } from './app/index'; // Reaching to "./app/index" is not allowed
+export * from './reducer/user'; // Reaching to "./reducer/user" is not allowed
 ```
 
 The following patterns are NOT considered problems:
@@ -61,4 +70,67 @@ The following patterns are NOT considered problems:
 import 'source-map-support/register';
 import { settings } from '../app';
 import getUser from '../actions/getUser';
+
+export * from 'source-map-support/register';
+export { settings } from '../app';
+```
+
+Given the following folder structure:
+
+```pt
+my-project
+├── actions
+│   └── getUser.js
+│   └── updateUser.js
+├── reducer
+│   └── index.js
+│   └── user.js
+├── redux
+│   └── index.js
+│   └── configureStore.js
+└── app
+│   └── index.js
+│   └── settings.js
+└── entry.js
+```
+
+And the .eslintrc file:
+
+```json
+{
+  ...
+  "rules": {
+    "import/no-internal-modules": [ "error", {
+      "forbid": [ "**/actions/*", "source-map-support/*" ],
+    } ]
+  }
+}
+```
+
+The following patterns are considered problems:
+
+```js
+/**
+ *  in my-project/entry.js
+ */
+
+import 'source-map-support/register';
+import getUser from '../actions/getUser';
+
+export * from 'source-map-support/register';
+export getUser from '../actions/getUser';
+```
+
+The following patterns are NOT considered problems:
+
+```js
+/**
+ *  in my-project/entry.js
+ */
+
+import 'source-map-support';
+import { getUser } from '../actions';
+
+export * from 'source-map-support';
+export { getUser } from '../actions';
 ```
diff --git a/docs/rules/no-mutable-exports.md b/docs/rules/no-mutable-exports.md
index e161e87b1..ce5162785 100644
--- a/docs/rules/no-mutable-exports.md
+++ b/docs/rules/no-mutable-exports.md
@@ -1,5 +1,7 @@
 # import/no-mutable-exports
 
+<!-- end auto-generated rule header -->
+
 Forbids the use of mutable exports with `var` or `let`.
 
 ## Rule Details
@@ -41,11 +43,11 @@ export function getCount() {} // reported here: exported function is reassigned
 To prevent general reassignment of these identifiers, exported or not, you may
 want to enable the following core ESLint rules:
 
-- [no-func-assign]
-- [no-class-assign]
+ - [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/no-named-as-default-member.md b/docs/rules/no-named-as-default-member.md
index da6ae3f1d..e8935fb7d 100644
--- a/docs/rules/no-named-as-default-member.md
+++ b/docs/rules/no-named-as-default-member.md
@@ -1,5 +1,9 @@
 # import/no-named-as-default-member
 
+⚠️ This rule _warns_ in the following configs: ☑️ `recommended`, 🚸 `warnings`.
+
+<!-- end auto-generated rule header -->
+
 Reports use of an exported name as a property on the default export.
 
 Rationale: Accessing a property that has a name that is shared by an exported
@@ -13,13 +17,12 @@ Furthermore, [in Babel 5 this is actually how things worked][blog]. This was
 fixed in Babel 6. Before upgrading an existing codebase to Babel 6, it can be
 useful to run this lint rule.
 
-
 [blog]: https://kentcdodds.com/blog/misunderstanding-es6-modules-upgrading-babel-tears-and-a-solution
 
-
 ## Rule Details
 
 Given:
+
 ```js
 // foo.js
 export default 'foo';
@@ -27,11 +30,13 @@ export const bar = 'baz';
 ```
 
 ...this would be valid:
+
 ```js
 import foo, {bar} from './foo.js';
 ```
 
 ...and the following would be reported:
+
 ```js
 // Caution: `foo` also has a named export `bar`.
 // Check if you meant to write `import {bar} from './foo.js'` instead.
diff --git a/docs/rules/no-named-as-default.md b/docs/rules/no-named-as-default.md
index 0a92b7b51..043d69942 100644
--- a/docs/rules/no-named-as-default.md
+++ b/docs/rules/no-named-as-default.md
@@ -1,15 +1,20 @@
 # import/no-named-as-default
 
+⚠️ This rule _warns_ in the following configs: ☑️ `recommended`, 🚸 `warnings`.
+
+<!-- end auto-generated rule header -->
+
 Reports use of an exported name as the locally imported name of a default export.
 
 Rationale: using an exported name as the name of the default export is likely...
 
-- *misleading*: others familiar with `foo.js` probably expect the name to be `foo`
-- *a mistake*: only needed to import `bar` and forgot the brackets (the case that is prompting this)
+ - _misleading_: others familiar with `foo.js` probably expect the name to be `foo`
+ - _a mistake_: only needed to import `bar` and forgot the brackets (the case that is prompting this)
 
 ## Rule Details
 
 Given:
+
 ```js
 // foo.js
 export default 'foo';
@@ -17,11 +22,13 @@ export const bar = 'baz';
 ```
 
 ...this would be valid:
+
 ```js
 import foo from './foo.js';
 ```
 
 ...and this would be reported:
+
 ```js
 // message: Using exported name 'bar' as identifier for default export.
 import bar from './foo.js';
@@ -31,7 +38,7 @@ For post-ES2015 `export` extensions, this also prevents exporting the default fr
 
 ```js
 // valid:
-export foo from './foo.js'
+export foo from './foo.js';
 
 // message: Using exported name 'bar' as identifier for default export.
 export bar from './foo.js';
@@ -39,8 +46,8 @@ export bar from './foo.js';
 
 ## Further Reading
 
-- ECMAScript Proposal: [export ns from]
-- ECMAScript Proposal: [export default from]
+ - ECMAScript Proposal: [export ns from]
+ - ECMAScript Proposal: [export default from]
 
 [export ns from]: https://github.com/leebyron/ecmascript-export-ns-from
 [export default from]: https://github.com/leebyron/ecmascript-export-default-from
diff --git a/docs/rules/no-named-default.md b/docs/rules/no-named-default.md
index 86fb41d61..05860cde1 100644
--- a/docs/rules/no-named-default.md
+++ b/docs/rules/no-named-default.md
@@ -1,12 +1,19 @@
 # import/no-named-default
 
+<!-- end auto-generated rule header -->
+
 Reports use of a default export as a locally named import.
 
 Rationale: the syntax exists to import default exports expressively, let's use it.
 
+Note that type imports, as used by [Flow], are always ignored.
+
+[Flow]: https://flow.org/
+
 ## Rule Details
 
 Given:
+
 ```js
 // foo.js
 export default 'foo';
@@ -14,12 +21,14 @@ export const bar = 'baz';
 ```
 
 ...these would be valid:
+
 ```js
 import foo from './foo.js';
 import foo, { bar } from './foo.js';
 ```
 
 ...and these would be reported:
+
 ```js
 // message: Using exported name 'bar' as identifier for default export.
 import { default as foo } from './foo.js';
diff --git a/docs/rules/no-named-export.md b/docs/rules/no-named-export.md
index 0ff881e34..13ea63ad7 100644
--- a/docs/rules/no-named-export.md
+++ b/docs/rules/no-named-export.md
@@ -1,4 +1,6 @@
-# `import/no-named-export`
+# import/no-named-export
+
+<!-- end auto-generated rule header -->
 
 Prohibit named exports. Mostly an inverse of [`no-default-export`].
 
diff --git a/docs/rules/no-namespace.md b/docs/rules/no-namespace.md
index b308d6621..c7346515a 100644
--- a/docs/rules/no-namespace.md
+++ b/docs/rules/no-namespace.md
@@ -1,6 +1,18 @@
 # import/no-namespace
 
-Reports if namespace import is used.
+🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).
+
+<!-- end auto-generated rule header -->
+
+Enforce a convention of not using namespace (a.k.a. "wildcard" `*`) imports.
+
+The rule is auto-fixable when the namespace object is only used for direct member access, e.g. `namespace.a`.
+
+## Options
+
+This rule supports the following options:
+
+ - `ignore`: array of glob strings for modules that should be ignored by the rule.
 
 ## Rule Details
 
@@ -12,10 +24,18 @@ import { a, b }  from './bar'
 import defaultExport, { a, b }  from './foobar'
 ```
 
-...whereas here imports will be reported:
+```js
+/* eslint import/no-namespace: ["error", {ignore: ['*.ext']}] */
+import * as bar from './ignored-module.ext';
+```
+
+Invalid:
 
 ```js
 import * as foo from 'foo';
+```
+
+```js
 import defaultExport, * as foo from 'foo';
 ```
 
diff --git a/docs/rules/no-nodejs-modules.md b/docs/rules/no-nodejs-modules.md
index 225adab22..5cbc90728 100644
--- a/docs/rules/no-nodejs-modules.md
+++ b/docs/rules/no-nodejs-modules.md
@@ -1,12 +1,14 @@
-# import/no-nodejs-modules: No Node.js builtin modules
+# import/no-nodejs-modules
+
+<!-- end auto-generated rule header -->
 
 Forbid the use of Node.js builtin modules. Can be useful for client-side web projects that do not have access to those modules.
 
-### Options
+## Options
 
 This rule supports the following options:
 
-- `allow`: Array of names of allowed modules. Defaults to an empty array.
+ - `allow`: Array of names of allowed modules. Defaults to an empty array.
 
 ## Rule Details
 
diff --git a/docs/rules/no-relative-packages.md b/docs/rules/no-relative-packages.md
new file mode 100644
index 000000000..ed724a9eb
--- /dev/null
+++ b/docs/rules/no-relative-packages.md
@@ -0,0 +1,69 @@
+# import/no-relative-packages
+
+🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).
+
+<!-- end auto-generated rule header -->
+
+Use this rule to prevent importing packages through relative paths.
+
+It's useful in Yarn/Lerna workspaces, where it's possible to import a sibling package using `../package` relative path, while direct `package` is the correct one.
+
+## Examples
+
+Given the following folder structure:
+
+```pt
+my-project
+├── packages
+│   ├── foo
+│   │   ├── index.js
+│   │   └── package.json
+│   └── bar
+│       ├── index.js
+│       └── package.json
+└── entry.js
+```
+
+And the .eslintrc file:
+
+```json
+{
+  ...
+  "rules": {
+    "import/no-relative-packages": "error"
+  }
+}
+```
+
+The following patterns are considered problems:
+
+```js
+/**
+ *  in my-project/packages/foo.js
+ */
+
+import bar from '../bar'; // Import sibling package using relative path
+import entry from '../../entry.js'; // Import from parent package using relative path
+
+/**
+ *  in my-project/entry.js
+ */
+
+import bar from './packages/bar'; // Import child package using relative path
+```
+
+The following patterns are NOT considered problems:
+
+```js
+/**
+ *  in my-project/packages/foo.js
+ */
+
+import bar from 'bar'; // Import sibling package using package name
+
+/**
+ *  in my-project/entry.js
+ */
+
+import bar from 'bar'; // Import sibling package using package name
+```
diff --git a/docs/rules/no-relative-parent-imports.md b/docs/rules/no-relative-parent-imports.md
index 7d6e883cf..c1f978487 100644
--- a/docs/rules/no-relative-parent-imports.md
+++ b/docs/rules/no-relative-parent-imports.md
@@ -1,12 +1,14 @@
 # import/no-relative-parent-imports
 
+<!-- end auto-generated rule header -->
+
 Use this rule to prevent imports to folders in relative parent paths.
 
 This rule is useful for enforcing tree-like folder structures instead of complex graph-like folder structures. While this restriction might be a departure from Node's default resolution style, it can lead large, complex codebases to be easier to maintain. If you've ever had debates over "where to put files" this rule is for you.
 
 To fix violations of this rule there are three general strategies. Given this example:
 
-```
+```pt
 numbers
 └── three.js
 add.js
@@ -30,51 +32,51 @@ You can,
 
 1. Move the file to be in a sibling folder (or higher) of the dependency.
 
-`three.js` could be be in the same folder as `add.js`:
+   `three.js` could be be in the same folder as `add.js`:
 
-```
-three.js
-add.js
-```
+   ```pt
+   three.js
+   add.js
+   ```
 
-or since `add` doesn't have any imports, it could be in it's own directory (namespace):
+   or since `add` doesn't have any imports, it could be in it's own directory (namespace):
 
-```
-math
-└── add.js
-three.js
-```
+   ```pt
+   math
+   └── add.js
+   three.js
+   ```
 
 2. Pass the dependency as an argument at runtime (dependency injection)
 
-```js
-// three.js
-export default function three(add) {
-  return add([1, 2]);
-}
+   ```js
+   // three.js
+   export default function three(add) {
+     return add([1, 2]);
+   }
 
-// somewhere else when you use `three.js`:
-import add from './add';
-import three from './numbers/three';
-console.log(three(add));
-```
+   // somewhere else when you use `three.js`:
+   import add from './add';
+   import three from './numbers/three';
+   console.log(three(add));
+   ```
 
 3. Make the dependency a package so it's globally available to all files in your project:
 
-```js
-import add from 'add'; // from https://www.npmjs.com/package/add
-export default function three() {
-  return add([1,2]);
-}
-```
+   ```js
+   import add from 'add'; // from https://www.npmjs.com/package/add
+   export default function three() {
+     return add([1,2]);
+   }
+   ```
 
 These are (respectively) static, dynamic & global solutions to graph-like dependency resolution.
 
-### Examples
+## Examples
 
 Given the following folder structure:
 
-```
+```pt
 my-project
 ├── lib
 │   ├── a.js
@@ -83,7 +85,8 @@ my-project
 ```
 
 And the .eslintrc file:
-```
+
+```json
 {
   ...
   "rules": {
diff --git a/docs/rules/no-restricted-paths.md b/docs/rules/no-restricted-paths.md
index bad65ab8e..a905226c2 100644
--- a/docs/rules/no-restricted-paths.md
+++ b/docs/rules/no-restricted-paths.md
@@ -1,21 +1,38 @@
-# import/no-restricted-paths: Restrict which files can be imported in a given folder
+# import/no-restricted-paths
+
+<!-- end auto-generated rule header -->
 
 Some projects contain files which are not always meant to be executed in the same environment.
 For example consider a web application that contains specific code for the server and some specific code for the browser/client. In this case you don’t want to import server-only files in your client code.
 
-In order to prevent such scenarios this rule allows you to define restricted zones where you can forbid files from imported if they match a specific path.
+In order to prevent such scenarios this rule allows you to define restricted zones where you can forbid files from being imported if they match a specific path.
 
 ## Rule Details
 
 This rule has one option. The option is an object containing the definition of all restricted `zones` and the optional `basePath` which is used to resolve relative paths within.
 The default value for `basePath` is the current working directory.
-Each zone consists of the `target` path and a `from` path. The `target` is the path where the restricted imports should be applied. The `from` path defines the folder that is not allowed to be used in an import.
+
+Each zone consists of the `target` paths, a `from` paths, and an optional `except` and `message` attribute.
+
+ - `target` contains the paths where the restricted imports should be applied. It can be expressed by
+   - directory string path that matches all its containing files
+   - glob pattern matching all the targeted files
+   - an array of multiple of the two types above
+ - `from` paths define the folders that are not allowed to be used in an import. It can be expressed by
+   - directory string path that matches all its containing files
+   - glob pattern matching all the files restricted to be imported
+   - an array of multiple directory string path
+   - an array of multiple glob patterns
+ - `except` may be defined for a zone, allowing exception paths that would otherwise violate the related `from`. Note that it does not alter the behaviour of `target` in any way.
+   - in case `from` contains only glob patterns, `except` must be an array of glob patterns as well
+   - in case `from` contains only directory path, `except` is relative to `from` and cannot backtrack to a parent directory
+ - `message` - will be displayed in case of the rule violation.
 
 ### Examples
 
 Given the following folder structure:
 
-```
+```pt
 my-project
 ├── client
 │   └── foo.js
@@ -37,3 +54,145 @@ The following patterns are not considered problems when configuration set to `{
 ```js
 import baz from '../client/baz';
 ```
+
+---------------
+
+Given the following folder structure:
+
+```pt
+my-project
+├── client
+│   └── foo.js
+│   └── baz.js
+└── server
+    ├── one
+    │   └── a.js
+    │   └── b.js
+    └── two
+```
+
+and the current file being linted is `my-project/server/one/a.js`.
+
+and the current configuration is set to:
+
+```json
+{ "zones": [ {
+    "target": "./tests/files/restricted-paths/server/one",
+    "from": "./tests/files/restricted-paths/server",
+    "except": ["./one"]
+} ] }
+```
+
+The following pattern is considered a problem:
+
+```js
+import a from '../two/a'
+```
+
+The following pattern is not considered a problem:
+
+```js
+import b from './b'
+
+```
+
+---------------
+
+Given the following folder structure:
+
+```pt
+my-project
+├── client
+    └── foo.js
+    └── sub-module
+        └── bar.js
+        └── baz.js
+
+```
+
+and the current configuration is set to:
+
+```json
+{ "zones": [ {
+    "target": "./tests/files/restricted-paths/client/!(sub-module)/**/*",
+    "from": "./tests/files/restricted-paths/client/sub-module/**/*",
+} ] }
+```
+
+The following import is considered a problem in `my-project/client/foo.js`:
+
+```js
+import a from './sub-module/baz'
+```
+
+The following import is not considered a problem in `my-project/client/sub-module/bar.js`:
+
+```js
+import b from './baz'
+```
+
+---------------
+
+Given the following folder structure:
+
+```pt
+my-project
+└── one
+   └── a.js
+   └── b.js
+└── two
+   └── a.js
+   └── b.js
+└── three
+   └── a.js
+   └── b.js
+```
+
+and the current configuration is set to:
+
+```json
+{
+  "zones": [
+    {
+      "target": ["./tests/files/restricted-paths/two/*", "./tests/files/restricted-paths/three/*"],
+      "from": ["./tests/files/restricted-paths/one", "./tests/files/restricted-paths/three"],
+    }
+  ]
+}
+```
+
+The following patterns are not considered a problem in `my-project/one/b.js`:
+
+```js
+import a from '../three/a'
+```
+
+```js
+import a from './a'
+```
+
+The following pattern is not considered a problem in `my-project/two/b.js`:
+
+```js
+import a from './a'
+```
+
+The following patterns are considered a problem in `my-project/two/a.js`:
+
+```js
+import a from '../one/a'
+```
+
+```js
+import a from '../three/a'
+```
+
+The following patterns are considered a problem in `my-project/three/b.js`:
+
+```js
+import a from '../one/a'
+```
+
+```js
+import a from './a'
+```
diff --git a/docs/rules/no-self-import.md b/docs/rules/no-self-import.md
index bde063f5d..8d8491c50 100644
--- a/docs/rules/no-self-import.md
+++ b/docs/rules/no-self-import.md
@@ -1,4 +1,6 @@
-# Forbid a module from importing itself (`import/no-self-import`)
+# import/no-self-import
+
+<!-- end auto-generated rule header -->
 
 Forbid a module from importing itself. This can sometimes happen during refactoring.
 
diff --git a/docs/rules/no-unassigned-import.md b/docs/rules/no-unassigned-import.md
index fb3065c48..617395e2c 100644
--- a/docs/rules/no-unassigned-import.md
+++ b/docs/rules/no-unassigned-import.md
@@ -1,12 +1,15 @@
-# import/no-unassigned-import: Forbid unassigned imports
+# import/no-unassigned-import
+
+<!-- end auto-generated rule header -->
 
 With both CommonJS' `require` and the ES6 modules' `import` syntax, it is possible to import a module but not to use its result. This can be done explicitly by not assigning the module to as variable. Doing so can mean either of the following things:
-- The module is imported but not used
-- The module has side-effects (like [`should`](https://www.npmjs.com/package/should)). Having side-effects, makes it hard to know whether the module is actually used or can be removed. It can also make it harder to test or mock parts of your application.
+
+ - The module is imported but not used
+ - The module has side-effects (like [`should`](https://www.npmjs.com/package/should)). Having side-effects, makes it hard to know whether the module is actually used or can be removed. It can also make it harder to test or mock parts of your application.
 
 This rule aims to remove modules with side-effects by reporting when a module is imported but not assigned.
 
-### Options
+## Options
 
 This rule supports the following option:
 
@@ -14,7 +17,6 @@ This rule supports the following option:
 
 Note that the globs start from the where the linter is executed (usually project root), but not from each file that includes the source. Learn more in both the pass and fail examples below.
 
-
 ## Fail
 
 ```js
@@ -26,7 +28,6 @@ import '../styles/app.css'
 // {"allow": ["styles/*.css"]}
 ```
 
-
 ## Pass
 
 ```js
diff --git a/docs/rules/no-unresolved.md b/docs/rules/no-unresolved.md
index 30cd8cb2b..ca1da39c0 100644
--- a/docs/rules/no-unresolved.md
+++ b/docs/rules/no-unresolved.md
@@ -1,12 +1,16 @@
 # import/no-unresolved
 
+💼 This rule is enabled in the following configs: ❗ `errors`, ☑️ `recommended`.
+
+<!-- end auto-generated rule header -->
+
 Ensures an imported module can be resolved to a module on the local filesystem,
 as defined by standard Node `require.resolve` behavior.
 
 See [settings](../../README.md#settings) for customization options for the resolution (i.e.
 additional filetypes, `NODE_PATH`, etc.)
 
-This rule can also optionally report on unresolved modules in CommonJS `require('./foo')` calls and AMD `require(['./foo'], function (foo){...})` and `define(['./foo'], function (foo){...})`.
+This rule can also optionally report on unresolved modules in CommonJS `require('./foo')` calls and AMD `require(['./foo'], function (foo) {...})` and `define(['./foo'], function (foo) {...})`.
 
 To enable this, send `{ commonjs: true/false, amd: true/false }` as a rule option.
 Both are disabled by default.
@@ -46,6 +50,7 @@ const { default: x } = require('./foo') // ignored
 ```
 
 Both may be provided, too:
+
 ```js
 /*eslint import/no-unresolved: [2, { commonjs: true, amd: true }]*/
 const { default: x } = require('./foo') // reported if './foo' is not found
@@ -60,7 +65,7 @@ This rule has its own ignore list, separate from [`import/ignore`]. This is beca
 To suppress errors from files that may not be properly resolved by your [resolver settings](../../README.md#resolver-plugins), you may add an `ignore` key with an array of `RegExp` pattern strings:
 
 ```js
-/*eslint import/no-unresolved: [2, { ignore: ['\.img$'] }]*/
+/*eslint import/no-unresolved: [2, { ignore: ['\\.img$'] }]*/
 
 import { x } from './mod' // may be reported, if not resolved to a module
 
@@ -76,16 +81,30 @@ By default, this rule will report paths whose case do not match the underlying f
 const { default: x } = require('./foo') // reported if './foo' is actually './Foo' and caseSensitive: true
 ```
 
+#### `caseSensitiveStrict`
+
+The `caseSensitive` option does not detect case for the current working directory. The `caseSensitiveStrict` option allows checking `cwd` in resolved path. By default, the option is disabled.
+
+```js
+/*eslint import/no-unresolved: [2, { caseSensitiveStrict: true }]*/
+
+// Absolute paths
+import Foo from `/Users/fOo/bar/file.js` // reported, /Users/foo/bar/file.js
+import Foo from `d:/fOo/bar/file.js` // reported, d:/foo/bar/file.js
+
+// Relative paths, cwd is Users/foo/
+import Foo from `./../fOo/bar/file.js` // reported
+```
+
 ## When Not To Use It
 
-If you're using a module bundler other than Node or Webpack, you may end up with
-a lot of false positive reports of missing dependencies.
+If you're using a module bundler other than Node or Webpack, you may end up with a lot of false positive reports of missing dependencies.
 
 ## Further Reading
 
-- [Resolver plugins](../../README.md#resolver-plugins)
-- [Node resolver](https://npmjs.com/package/eslint-import-resolver-node) (default)
-- [Webpack resolver](https://npmjs.com/package/eslint-import-resolver-webpack)
-- [`import/ignore`] global setting
+ - [Resolver plugins](../../README.md#resolvers)
+ - [Node resolver](https://npmjs.com/package/eslint-import-resolver-node) (default)
+ - [Webpack resolver](https://npmjs.com/package/eslint-import-resolver-webpack)
+ - [`import/ignore`] global setting
 
 [`import/ignore`]: ../../README.md#importignore
diff --git a/docs/rules/no-unused-modules.md b/docs/rules/no-unused-modules.md
index 4302bc845..359c341ea 100644
--- a/docs/rules/no-unused-modules.md
+++ b/docs/rules/no-unused-modules.md
@@ -1,19 +1,22 @@
 # import/no-unused-modules
 
+<!-- end auto-generated rule header -->
+
 Reports:
-  - modules without any exports
-  - individual exports not being statically `import`ed or `require`ed from other modules in the same project
 
-Note: dynamic imports are currently not supported.
+ - modules without any exports
+ - individual exports not being statically `import`ed or `require`ed from other modules in the same project
+ - dynamic imports are supported if argument is a literal string
 
 ## Rule Details
 
 ### Usage
 
-In order for this plugin to work, one of the options `missingExports` or `unusedExports` must be enabled (see "Options" section below). In the future, these options will be enabled by default (see https://github.com/benmosher/eslint-plugin-import/issues/1324)
+In order for this plugin to work, at least one of the options `missingExports` or `unusedExports` must be enabled (see "Options" section below). In the future, these options will be enabled by default (see <https://github.com/import-js/eslint-plugin-import/issues/1324>)
 
-Example: 
-```
+Example:
+
+```json
 "rules: {
   ...otherRules,
   "import/no-unused-modules": [1, {"unusedExports": true}]
@@ -24,16 +27,18 @@ Example:
 
 This rule takes the following option:
 
-- **`missingExports`**: if `true`, files without any exports are reported (defaults to `false`)
-- **`unusedExports`**: if `true`, exports without any static usage within other modules are reported (defaults to `false`)
-- `src`: an array with files/paths to be analyzed. It only applies to unused exports. Defaults to `process.cwd()`, if not provided
-- `ignoreExports`: an array with files/paths for which unused exports will not be reported (e.g module entry points in a published package) 
-
+ - **`missingExports`**: if `true`, files without any exports are reported (defaults to `false`)
+ - **`unusedExports`**: if `true`, exports without any static usage within other modules are reported (defaults to `false`)
+ - **`ignoreUnusedTypeExports`**: if `true`, TypeScript type exports without any static usage within other modules are reported (defaults to `false` and has no effect unless `unusedExports` is `true`)
+ - **`src`**: an array with files/paths to be analyzed. It only applies to unused exports. Defaults to `process.cwd()`, if not provided
+ - **`ignoreExports`**: an array with files/paths for which unused exports will not be reported (e.g module entry points in a published package)
 
 ### Example for missing exports
+
 #### The following will be reported
+
 ```js
-const class MyClass { /*...*/ } 
+const class MyClass { /*...*/ }
 
 function makeClass() { return new MyClass(...arguments) }
 ```
@@ -41,52 +46,59 @@ function makeClass() { return new MyClass(...arguments) }
 #### The following will not be reported
 
 ```js
-export default function () { /*...*/ } 
+export default function () { /*...*/ }
 ```
+
 ```js
-export const foo = function () { /*...*/ } 
+export const foo = function () { /*...*/ }
 ```
+
 ```js
 export { foo, bar }
 ```
+
 ```js
 export { foo as bar }
 ```
 
 ### Example for unused exports
+
 given file-f:
+
 ```js
 import { e } from 'file-a'
 import { f } from 'file-b'
-import * from  'file-c'
-export * from 'file-d'
-export { default, i0 } from 'file-e' // both will be reported
+import * as fileC from  'file-c'
+export { default, i0 } from 'file-d' // both will be reported
 
-export const j = 99 // will be reported 
+export const j = 99 // will be reported
 ```
-and file-e:
+
+and file-d:
+
 ```js
 export const i0 = 9 // will not be reported
 export const i1 = 9 // will be reported
 export default () => {} // will not be reported
 ```
-and file-d:
+
+and file-c:
+
 ```js
 export const h = 8 // will not be reported
 export default () => {} // will be reported, as export * only considers named exports and ignores default exports
 ```
-and file-c:
-```js
-export const g = 7 // will not be reported
-export default () => {} // will not be reported
-```
+
 and file-b:
+
 ```js
 import two, { b, c, doAnything } from 'file-a'
 
 export const f = 6 // will not be reported
 ```
+
 and file-a:
+
 ```js
 const b = 2
 const c = 3
@@ -105,7 +117,18 @@ export function doAnything() {
 export default 5 // will not be reported
 ```
 
+### Unused exports with `ignoreUnusedTypeExports` set to `true`
+
+The following will not be reported:
+
+```ts
+export type Foo = {}; // will not be reported
+export interface Foo = {}; // will not be reported
+export enum Foo {}; // will not be reported
+```
+
 #### Important Note
+
 Exports from files listed as a main file (`main`, `browser`, or `bin` fields in `package.json`) will be ignored by default. This only applies if the `package.json` is not set to `private: true`
 
 ## When not to use
diff --git a/docs/rules/no-useless-path-segments.md b/docs/rules/no-useless-path-segments.md
index 6a02eab9f..22c4bf965 100644
--- a/docs/rules/no-useless-path-segments.md
+++ b/docs/rules/no-useless-path-segments.md
@@ -1,12 +1,16 @@
 # import/no-useless-path-segments
 
+🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).
+
+<!-- end auto-generated rule header -->
+
 Use this rule to prevent unnecessary path segments in import and require statements.
 
 ## Rule Details
 
 Given the following folder structure:
 
-```
+```pt
 my-project
 ├── app.js
 ├── footer.js
@@ -14,6 +18,7 @@ my-project
 └── helpers.js
 └── helpers
     └── index.js
+├── index.js
 └── pages
     ├── about.js
     ├── contact.js
@@ -27,10 +32,10 @@ The following patterns are considered problems:
  *  in my-project/app.js
  */
 
-import "./../pages/about.js"; // should be "./pages/about.js"
-import "./../pages/about"; // should be "./pages/about"
-import "../pages/about.js"; // should be "./pages/about.js"
-import "../pages/about"; // should be "./pages/about"
+import "./../my-project/pages/about.js"; // should be "./pages/about.js"
+import "./../my-project/pages/about"; // should be "./pages/about"
+import "../my-project/pages/about.js"; // should be "./pages/about.js"
+import "../my-project/pages/about"; // should be "./pages/about"
 import "./pages//about"; // should be "./pages/about"
 import "./pages/"; // should be "./pages"
 import "./pages/index"; // should be "./pages" (except if there is a ./pages.js file)
@@ -57,6 +62,7 @@ import fs from "fs";
 ### noUselessIndex
 
 If you want to detect unnecessary `/index` or `/index.js` (depending on the specified file extensions, see below) imports in your paths, you can enable the option `noUselessIndex`. By default it is set to `false`:
+
 ```js
 "import/no-useless-path-segments": ["error", {
   noUselessIndex: true,
@@ -72,4 +78,8 @@ import "./pages/index"; // should be "./pages" (auto-fixable)
 import "./pages/index.js"; // should be "./pages" (auto-fixable)
 ```
 
-Note: `noUselessIndex` only avoids ambiguous imports for `.js` files if you haven't specified other resolved file extensions. See [Settings: import/extensions](https://github.com/benmosher/eslint-plugin-import#importextensions) for details.
+Note: `noUselessIndex` only avoids ambiguous imports for `.js` files if you haven't specified other resolved file extensions. See [Settings: import/extensions](https://github.com/import-js/eslint-plugin-import#importextensions) for details.
+
+### commonjs
+
+When set to `true`, this rule checks CommonJS imports. Default to `false`.
diff --git a/docs/rules/no-webpack-loader-syntax.md b/docs/rules/no-webpack-loader-syntax.md
index 37b39a432..291b1c058 100644
--- a/docs/rules/no-webpack-loader-syntax.md
+++ b/docs/rules/no-webpack-loader-syntax.md
@@ -1,13 +1,16 @@
 # import/no-webpack-loader-syntax
 
+<!-- end auto-generated rule header -->
+
 Forbid Webpack loader syntax in imports.
 
-[Webpack](http://webpack.github.io) allows specifying the [loaders](http://webpack.github.io/docs/loaders.html) to use in the import source string using a special syntax like this:
+[Webpack](https://webpack.js.org) allows specifying the [loaders](https://webpack.js.org/concepts/loaders/) to use in the import source string using a special syntax like this:
+
 ```js
 var moduleWithOneLoader = require("my-loader!./my-awesome-module");
 ```
 
-This syntax is non-standard, so it couples the code to Webpack. The recommended way to specify Webpack loader configuration is in a [Webpack configuration file](http://webpack.github.io/docs/loaders.html#loaders-by-config).
+This syntax is non-standard, so it couples the code to Webpack. The recommended way to specify Webpack loader configuration is in a [Webpack configuration file](https://webpack.js.org/concepts/loaders/#configuration).
 
 ## Rule Details
 
diff --git a/docs/rules/order.md b/docs/rules/order.md
index 88ddca46f..4a52b823e 100644
--- a/docs/rules/order.md
+++ b/docs/rules/order.md
@@ -1,10 +1,14 @@
-# import/order: Enforce a convention in module import order
+# import/order
+
+🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).
+
+<!-- end auto-generated rule header -->
 
 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.
-The order is as shown in the following example:
 
-```js
+With the [`groups`][18] option set to `["builtin", "external", "internal", "parent", "sibling", "index", "object", "type"]` the order is as shown in the following example:
+
+```ts
 // 1. node "builtin" modules
 import fs from 'fs';
 import path from 'path';
@@ -22,16 +26,17 @@ import bar from './bar';
 import baz from './bar/baz';
 // 6. "index" of the current directory
 import main from './';
+// 7. "object"-imports (only available in TypeScript)
+import log = console.log;
+// 8. "type" imports (only available in Flow and TypeScript)
+import type { Foo } from 'foo';
 ```
 
-Unassigned imports are ignored, as the order they are imported in may be important.
-
-Statements using the ES6 `import` syntax must appear before any `require()` statements.
-
+See [here][3] for further details on how imports are grouped.
 
 ## Fail
 
-```js
+```ts
 import _ from 'lodash';
 import path from 'path'; // `path` import should occur before import of `lodash`
 
@@ -46,10 +51,9 @@ var path = require('path');
 import foo from './foo'; // `import` statements must be before `require` statement
 ```
 
-
 ## Pass
 
-```js
+```ts
 import path from 'path';
 import _ from 'lodash';
 
@@ -71,101 +75,950 @@ import foo from './foo';
 var path = require('path');
 ```
 
+## Limitations of `--fix`
+
+Unbound imports are assumed to have side effects, and will never be moved/reordered. This can cause other imports to get "stuck" around them, and the fix to fail.
+
+```javascript
+import b from 'b'
+import 'format.css';  // This will prevent --fix from working.
+import a from 'a'
+```
+
+As a workaround, move unbound imports to be entirely above or below bound ones.
+
+```javascript
+import 'format1.css';  // OK
+import b from 'b'
+import a from 'a'
+import 'format2.css';  // OK
+```
+
 ## Options
 
-This rule supports the following options:
+This rule supports the following options (none of which are required):
+
+ - [`groups`][18]
+ - [`pathGroups`][8]
+ - [`pathGroupsExcludedImportTypes`][9]
+ - [`distinctGroup`][32]
+ - [`newlines-between`][20]
+ - [`alphabetize`][30]
+ - [`named`][33]
+ - [`warnOnUnassignedImports`][5]
+ - [`sortTypesGroup`][7]
+ - [`newlines-between-types`][27]
+ - [`consolidateIslands`][25]
+
+---
+
+### `groups`
 
-### `groups: [array]`:
+Valid values: `("builtin" | "external" | "internal" | "unknown" | "parent" | "sibling" | "index" | "object" | "type")[]` \
+Default: `["builtin", "external", "parent", "sibling", "index"]`
 
-How groups are defined, and the order to respect. `groups` must be an array of `string` or [`string`]. The only allowed `string`s are: `"builtin"`, `"external"`, `"internal"`, `"unknown"`, `"parent"`, `"sibling"`, `"index"`. The enforced order is the same as the order of each element in a group. Omitted types are implicitly grouped together as the last element. Example:
-```js
-[
-  'builtin', // Built-in types are first
-  ['sibling', 'parent'], // Then sibling and parent types. They can be mingled together
-  'index', // Then the index file
-  // Then the rest: internal and external type
-]
+Determines which imports are subject to ordering, and how to order
+them. The predefined groups are: `"builtin"`, `"external"`, `"internal"`,
+`"unknown"`, `"parent"`, `"sibling"`, `"index"`, `"object"`, and `"type"`.
+
+The import order enforced by this rule is the same as the order of each group
+in `groups`. Imports belonging to groups omitted from `groups` are lumped
+together at the end.
+
+#### Example
+
+```jsonc
+{
+  "import/order": ["error", {
+    "groups": [
+      // Imports of builtins are first
+      "builtin",
+      // Then sibling and parent imports. They can be mingled together
+      ["sibling", "parent"],
+      // Then index file imports
+      "index",
+      // Then any arcane TypeScript imports
+      "object",
+      // Then the omitted imports: internal, external, type, unknown
+    ],
+  }],
+}
 ```
-The default value is `["builtin", "external", "parent", "sibling", "index"]`.
 
-You can set the options like this:
+#### How Imports Are Grouped
+
+An import (a `ImportDeclaration`, `TSImportEqualsDeclaration`, or `require()` `CallExpression`) is grouped by its type (`"require"` vs `"import"`), its [specifier][4], and any corresponding identifiers.
 
-```js
-"import/order": ["error", {"groups": ["index", "sibling", "parent", "internal", "external", "builtin"]}]
+```ts
+import { identifier1, identifier2 } from 'specifier1';
+import type { MyType } from 'specifier2';
+const identifier3 = require('specifier3');
 ```
 
-### `newlines-between: [ignore|always|always-and-inside-groups|never]`:
+Roughly speaking, the grouping algorithm is as follows:
+
+1. If the import has no corresponding identifiers (e.g. `import './my/thing.js'`), is otherwise "unassigned," or is an unsupported use of `require()`, and [`warnOnUnassignedImports`][5] is disabled, it will be ignored entirely since the order of these imports may be important for their [side-effects][31]
+2. If the import is part of an arcane TypeScript declaration (e.g. `import log = console.log`), it will be considered **object**. However, note that external module references (e.g. `import x = require('z')`) are treated as normal `require()`s and import-exports (e.g. `export import w = y;`) are ignored entirely
+3. If the import is [type-only][6], `"type"` is in `groups`, and [`sortTypesGroup`][7] is disabled, it will be considered **type** (with additional implications if using [`pathGroups`][8] and `"type"` is in [`pathGroupsExcludedImportTypes`][9])
+4. If the import's specifier matches [`import/internal-regex`][28], it will be considered **internal**
+5. If the import's specifier is an absolute path, it will be considered **unknown**
+6. If the import's specifier has the name of a Node.js core module (using [is-core-module][10]), it will be considered **builtin**
+7. If the import's specifier matches [`import/core-modules`][11], it will be considered **builtin**
+8. If the import's specifier is a path relative to the parent directory of its containing file (e.g. starts with `../`), it will be considered **parent**
+9. If the import's specifier is one of `['.', './', './index', './index.js']`, it will be considered **index**
+10. If the import's specifier is a path relative to its containing file (e.g. starts with `./`), it will be considered **sibling**
+11. If the import's specifier is a path pointing to a file outside the current package's root directory (determined using [package-up][12]), it will be considered **external**
+12. If the import's specifier matches [`import/external-module-folders`][29] (defaults to matching anything pointing to files within the current package's `node_modules` directory), it will be considered **external**
+13. If the import's specifier is a path pointing to a file within the current package's root directory (determined using [package-up][12]), it will be considered **internal**
+14. If the import's specifier has a name that looks like a scoped package (e.g. `@scoped/package-name`), it will be considered **external**
+15. If the import's specifier has a name that starts with a word character, it will be considered **external**
+16. If this point is reached, the import will be ignored entirely
+
+At the end of the process, if they co-exist in the same file, all top-level `require()` statements that haven't been ignored are shifted (with respect to their order) below any ES6 `import` or similar declarations. Finally, any type-only declarations are potentially reorganized according to [`sortTypesGroup`][7].
+
+### `pathGroups`
+
+Valid values: `PathGroup[]` \
+Default: `[]`
+
+Sometimes [the predefined groups][18] are not fine-grained enough, especially when using import aliases.
+`pathGroups` defines one or more [`PathGroup`][13]s relative to a predefined group.
+Imports are associated with a [`PathGroup`][13] based on path matching against the import specifier (using [minimatch][14]).
+
+> [!IMPORTANT]
+>
+> Note that, by default, imports grouped as `"builtin"`, `"external"`, or `"object"` will not be considered for further `pathGroups` matching unless they are removed from [`pathGroupsExcludedImportTypes`][9].
+
+#### `PathGroup`
+
+|     property     | required |          type          | description                                                                                                                     |
+| :--------------: | :------: | :--------------------: | ------------------------------------------------------------------------------------------------------------------------------- |
+|     `pattern`    |    ☑️    |        `string`        | [Minimatch pattern][16] for specifier matching                                                                                  |
+| `patternOptions` |          |        `object`        | [Minimatch options][17]; default: `{nocomment: true}`                                                                           |
+|      `group`     |    ☑️    | [predefined group][18] | One of the [predefined groups][18] to which matching imports will be positioned relatively                                      |
+|    `position`    |          |  `"after" \| "before"` | Where, in relation to `group`, matching imports will be positioned; default: same position as `group` (neither before or after) |
+
+#### Example
+
+```jsonc
+{
+  "import/order": ["error", {
+    "pathGroups": [
+      {
+        // Minimatch pattern used to match against specifiers
+        "pattern": "~/**",
+        // The predefined group this PathGroup is defined in relation to
+        "group": "external",
+        // How matching imports will be positioned relative to "group"
+        "position": "after"
+      }
+    ]
+  }]
+}
+```
 
+### `pathGroupsExcludedImportTypes`
 
-Enforces or forbids new lines between import groups:
+Valid values: `("builtin" | "external" | "internal" | "unknown" | "parent" | "sibling" | "index" | "object" | "type")[]` \
+Default: `["builtin", "external", "object"]`
 
-- If set to `ignore`, no errors related to new lines between import groups will be reported (default).
-- If set to `always`, at least one new line between each group will be enforced, and new lines inside a group will be forbidden. To prevent multiple lines between imports, core `no-multiple-empty-lines` rule can be used.
-- If set to `always-and-inside-groups`, it will act like `always` except newlines are allowed inside import groups.
-- If set to `never`, no new lines are allowed in the entire import section.
+By default, imports in certain [groups][18] are excluded from being matched against [`pathGroups`][8] to prevent overeager sorting.
+Use `pathGroupsExcludedImportTypes` to modify which groups are excluded.
 
-With the default group setting, the following will be invalid:
+> [!TIP]
+>
+> If using imports with custom specifier aliases (e.g.
+> you're using `eslint-import-resolver-alias`, `paths` in `tsconfig.json`, etc) that [end up
+> grouped][3] as `"builtin"` or `"external"` imports,
+> remove them from  `pathGroupsExcludedImportTypes` to ensure they are ordered
+> correctly.
 
-```js
+#### Example
+
+```jsonc
+{
+  "import/order": ["error", {
+    "pathGroups": [
+      {
+        "pattern": "@app/**",
+        "group": "external",
+        "position": "after"
+      }
+    ],
+    "pathGroupsExcludedImportTypes": ["builtin"]
+  }]
+}
+```
+
+### `distinctGroup`
+
+Valid values: `boolean` \
+Default: `true`
+
+> [!CAUTION]
+>
+> Currently, `distinctGroup` defaults to `true`. However, in a later update, the
+> default will change to `false`.
+
+This changes how [`PathGroup.position`][13] affects grouping, and is most useful when [`newlines-between`][20] is set to `always` and at least one [`PathGroup`][13] has a `position` property set.
+
+When [`newlines-between`][20] is set to `always` and an import matching a specific [`PathGroup.pattern`][13] is encountered, that import is added to a sort of "sub-group" associated with that [`PathGroup`][13]. Thanks to [`newlines-between`][20], imports in this "sub-group" will have a new line separating them from the rest of the imports in [`PathGroup.group`][13].
+
+This behavior can be undesirable when using [`PathGroup.position`][13] to order imports _within_ [`PathGroup.group`][13] instead of creating a distinct "sub-group". Set `distinctGroup` to `false` to disable the creation of these "sub-groups".
+
+#### Example
+
+```jsonc
+{
+  "import/order": ["error", {
+    "distinctGroup": false,
+    "newlines-between": "always",
+    "pathGroups": [
+      {
+        "pattern": "@app/**",
+        "group": "external",
+        "position": "after"
+      }
+    ]
+  }]
+}
+```
+
+### `newlines-between`
+
+Valid values: `"ignore" | "always" | "always-and-inside-groups" | "never"` \
+Default: `"ignore"`
+
+Enforces or forbids new lines between import groups.
+
+ - If set to `ignore`, no errors related to new lines between import groups will be reported
+
+ - If set to `always`, at least one new line between each group will be enforced, and new lines inside a group will be forbidden
+
+  > [!TIP]
+  >
+  > To prevent multiple lines between imports, the [`no-multiple-empty-lines` rule][21], or a tool like [Prettier][22], can be used.
+
+ - If set to `always-and-inside-groups`, it will act like `always` except new lines are allowed inside import groups
+
+ - If set to `never`, no new lines are allowed in the entire import section
+
+#### Example
+
+With the default [`groups`][18] setting, the following will fail the rule check:
+
+```ts
 /* eslint import/order: ["error", {"newlines-between": "always"}] */
 import fs from 'fs';
 import path from 'path';
-import index from './';
 import sibling from './foo';
+import index from './';
 ```
 
-```js
+```ts
 /* eslint import/order: ["error", {"newlines-between": "always-and-inside-groups"}] */
 import fs from 'fs';
 
 import path from 'path';
-import index from './';
 import sibling from './foo';
+import index from './';
 ```
 
-```js
+```ts
 /* eslint import/order: ["error", {"newlines-between": "never"}] */
 import fs from 'fs';
 import path from 'path';
 
-import index from './';
-
 import sibling from './foo';
+
+import index from './';
 ```
 
-while those will be valid:
+While this will pass:
 
-```js
+```ts
 /* eslint import/order: ["error", {"newlines-between": "always"}] */
 import fs from 'fs';
 import path from 'path';
 
-import index from './';
-
 import sibling from './foo';
+
+import index from './';
 ```
 
-```js
+```ts
 /* eslint import/order: ["error", {"newlines-between": "always-and-inside-groups"}] */
 import fs from 'fs';
 
 import path from 'path';
 
+import sibling from './foo';
+
 import index from './';
+```
 
+```ts
+/* eslint import/order: ["error", {"newlines-between": "never"}] */
+import fs from 'fs';
+import path from 'path';
 import sibling from './foo';
+import index from './';
 ```
 
-```js
-/* eslint import/order: ["error", {"newlines-between": "never"}] */
+### `alphabetize`
+
+Valid values: `{ order?: "asc" | "desc" | "ignore", orderImportKind?: "asc" | "desc" | "ignore", caseInsensitive?: boolean }` \
+Default: `{ order: "ignore", orderImportKind: "ignore", caseInsensitive: false }`
+
+Determine the sort order of imports within each [predefined group][18] or [`PathGroup`][8] alphabetically based on specifier.
+
+> [!NOTE]
+>
+> Imports will be alphabetized based on their _specifiers_, not by their
+> identifiers. For example, `const a = require('z');` will come _after_ `const z = require('a');` when `alphabetize` is set to `{ order: "asc" }`.
+
+Valid properties and their values include:
+
+ - **`order`**: use `"asc"` to sort in ascending order, `"desc"` to sort in descending order, or "ignore" to prevent sorting
+
+ - **`orderImportKind`**: use `"asc"` to sort various _import kinds_, e.g. [type-only and typeof imports][6], in ascending order, `"desc"` to sort them in descending order, or "ignore" to prevent sorting
+
+ - **`caseInsensitive`**: use `true` to ignore case and `false` to consider case when sorting
+
+#### Example
+
+Given the following settings:
+
+```jsonc
+{
+  "import/order": ["error", {
+    "alphabetize": {
+      "order": "asc",
+      "caseInsensitive": true
+    }
+  }]
+}
+```
+
+This will fail the rule check:
+
+```ts
+import React, { PureComponent } from 'react';
+import aTypes from 'prop-types';
+import { compose, apply } from 'xcompose';
+import * as classnames from 'classnames';
+import blist from 'BList';
+```
+
+While this will pass:
+
+```ts
+import blist from 'BList';
+import * as classnames from 'classnames';
+import aTypes from 'prop-types';
+import React, { PureComponent } from 'react';
+import { compose, apply } from 'xcompose';
+```
+
+### `named`
+
+Valid values: `boolean | { enabled: boolean, import?: boolean, export?: boolean, require?: boolean, cjsExports?: boolean, types?: "mixed" | "types-first" | "types-last" }` \
+Default: `false`
+
+Enforce ordering of names within imports and exports.
+
+If set to `true` or `{ enabled: true }`, _all_ named imports must be ordered according to [`alphabetize`][30].
+If set to `false` or `{ enabled: false }`, named imports can occur in any order.
+
+If set to `{ enabled: true, ... }`, and any of the properties `import`, `export`, `require`, or `cjsExports` are set to `false`, named ordering is disabled with respect to the following kind of expressions:
+
+ - `import`:
+
+  ```ts
+  import { Readline } from "readline";
+  ```
+
+ - `export`:
+
+  ```ts
+  export { Readline };
+  // and
+  export { Readline } from "readline";
+  ```
+
+ - `require`:
+
+  ```ts
+  const { Readline } = require("readline");
+  ```
+
+ - `cjsExports`:
+
+  ```ts
+  module.exports.Readline = Readline;
+  // and
+  module.exports = { Readline };
+  ```
+
+Further, the `named.types` option allows you to specify the order of [import identifiers with inline type qualifiers][23] (or "type-only" identifiers/names), e.g. `import { type TypeIdentifier1, normalIdentifier2 } from 'specifier';`.
+
+`named.types` accepts the following values:
+
+ - `types-first`: forces type-only identifiers to occur first
+ - `types-last`: forces type-only identifiers to occur last
+ - `mixed`: sorts all identifiers in alphabetical order
+
+#### Example
+
+Given the following settings:
+
+```jsonc
+{
+  "import/order": ["error", {
+    "named": true,
+    "alphabetize": {
+      "order": "asc"
+    }
+  }]
+}
+```
+
+This will fail the rule check:
+
+```ts
+import { compose, apply } from 'xcompose';
+```
+
+While this will pass:
+
+```ts
+import { apply, compose } from 'xcompose';
+```
+
+### `warnOnUnassignedImports`
+
+Valid values: `boolean` \
+Default: `false`
+
+Warn when "unassigned" imports are out of order.
+Unassigned imports are imports with no corresponding identifiers (e.g. `import './my/thing.js'` or `require('./side-effects.js')`).
+
+> [!NOTE]
+>
+> These warnings are not fixable with `--fix` since unassigned imports might be used for their [side-effects][31],
+> and changing the order of such imports cannot be done safely.
+
+#### Example
+
+Given the following settings:
+
+```jsonc
+{
+  "import/order": ["error", {
+    "warnOnUnassignedImports": true
+  }]
+}
+```
+
+This will fail the rule check:
+
+```ts
 import fs from 'fs';
+import './styles.css';
 import path from 'path';
+```
+
+While this will pass:
+
+```ts
+import fs from 'fs';
+import path from 'path';
+import './styles.css';
+```
+
+### `sortTypesGroup`
+
+Valid values: `boolean` \
+Default: `false`
+
+> [!NOTE]
+>
+> This setting is only meaningful when `"type"` is included in [`groups`][18].
+
+Sort [type-only imports][6] separately from normal non-type imports.
+
+When enabled, the intragroup sort order of [type-only imports][6] will mirror the intergroup ordering of normal imports as defined by [`groups`][18], [`pathGroups`][8], etc.
+
+#### Example
+
+Given the following settings:
+
+```jsonc
+{
+  "import/order": ["error", {
+    "groups": ["type", "builtin", "parent", "sibling", "index"],
+    "alphabetize": { "order": "asc" }
+  }]
+}
+```
+
+This will fail the rule check even though it's logically ordered as we expect (builtins come before parents, parents come before siblings, siblings come before indices), the only difference is we separated type-only imports from normal imports:
+
+```ts
+import type A from "fs";
+import type B from "path";
+import type C from "../foo.js";
+import type D from "./bar.js";
+import type E from './';
+
+import a from "fs";
+import b from "path";
+import c from "../foo.js";
+import d from "./bar.js";
+import e from "./";
+```
+
+This happens because [type-only imports][6] are considered part of one global
+[`"type"` group](#how-imports-are-grouped) by default. However, if we set
+`sortTypesGroup` to `true`:
+
+```jsonc
+{
+  "import/order": ["error", {
+    "groups": ["type", "builtin", "parent", "sibling", "index"],
+    "alphabetize": { "order": "asc" },
+    "sortTypesGroup": true
+  }]
+}
+```
+
+The same example will pass.
+
+### `newlines-between-types`
+
+Valid values: `"ignore" | "always" | "always-and-inside-groups" | "never"` \
+Default: the value of [`newlines-between`][20]
+
+> [!NOTE]
+>
+> This setting is only meaningful when [`sortTypesGroup`][7] is enabled.
+
+`newlines-between-types` is functionally identical to [`newlines-between`][20] except it only enforces or forbids new lines between _[type-only][6] import groups_, which exist only when [`sortTypesGroup`][7] is enabled.
+
+In addition, when determining if a new line is enforceable or forbidden between the type-only imports and the normal imports, `newlines-between-types` takes precedence over [`newlines-between`][20].
+
+#### Example
+
+Given the following settings:
+
+```jsonc
+{
+  "import/order": ["error", {
+    "groups": ["type", "builtin", "parent", "sibling", "index"],
+    "sortTypesGroup": true,
+    "newlines-between": "always"
+  }]
+}
+```
+
+This will fail the rule check:
+
+```ts
+import type A from "fs";
+import type B from "path";
+import type C from "../foo.js";
+import type D from "./bar.js";
+import type E from './';
+
+import a from "fs";
+import b from "path";
+
+import c from "../foo.js";
+
+import d from "./bar.js";
+
+import e from "./";
+```
+
+However, if we set `newlines-between-types` to `"ignore"`:
+
+```jsonc
+{
+  "import/order": ["error", {
+    "groups": ["type", "builtin", "parent", "sibling", "index"],
+    "sortTypesGroup": true,
+    "newlines-between": "always",
+    "newlines-between-types": "ignore"
+  }]
+}
+```
+
+The same example will pass.
+
+Note the new line after `import type E from './';` but before `import a from "fs";`. This new line separates the type-only imports from the normal imports. Its existence is governed by [`newlines-between-types`][27] and _not `newlines-between`_.
+
+> [!IMPORTANT]
+>
+> In certain situations, [`consolidateIslands: true`][25] will take precedence over `newlines-between-types: "never"`, if used, when it comes to the new line separating type-only imports from normal imports.
+
+The next example will pass even though there's a new line preceding the normal import and [`newlines-between`][20] is set to `"never"`:
+
+```jsonc
+{
+  "import/order": ["error", {
+    "groups": ["type", "builtin", "parent", "sibling", "index"],
+    "sortTypesGroup": true,
+    "newlines-between": "never",
+    "newlines-between-types": "always"
+  }]
+}
+```
+
+```ts
+import type A from "fs";
+
+import type B from "path";
+
+import type C from "../foo.js";
+
+import type D from "./bar.js";
+
+import type E from './';
+
+import a from "fs";
+import b from "path";
+import c from "../foo.js";
+import d from "./bar.js";
+import e from "./";
+```
+
+While the following fails due to the new line between the last type import and the first normal import:
+
+```jsonc
+{
+  "import/order": ["error", {
+    "groups": ["type", "builtin", "parent", "sibling", "index"],
+    "sortTypesGroup": true,
+    "newlines-between": "always",
+    "newlines-between-types": "never"
+  }]
+}
+```
+
+```ts
+import type A from "fs";
+import type B from "path";
+import type C from "../foo.js";
+import type D from "./bar.js";
+import type E from './';
+
+import a from "fs";
+
+import b from "path";
+
+import c from "../foo.js";
+
+import d from "./bar.js";
+
+import e from "./";
+```
+
+### `consolidateIslands`
+
+Valid values: `"inside-groups" | "never"` \
+Default: `"never"`
+
+> [!NOTE]
+>
+> This setting is only meaningful when [`newlines-between`][20] and/or [`newlines-between-types`][27] is set to `"always-and-inside-groups"`.
+
+When set to `"inside-groups"`, this ensures imports spanning multiple lines are separated from other imports with a new line while single-line imports are grouped together (and the space between them consolidated) if they belong to the same [group][18] or [`pathGroups`][8].
+
+> [!IMPORTANT]
+>
+> When all of the following are true:
+>
+>  - [`sortTypesGroup`][7] is set to `true`
+>  - `consolidateIslands` is set to `"inside-groups"`
+>  - [`newlines-between`][20] is set to `"always-and-inside-groups"` when [`newlines-between-types`][27] is set to `"never"` (or vice-versa)
+>
+> Then [`newlines-between`][20]/[`newlines-between-types`][27] will yield to
+> `consolidateIslands` and allow new lines to separate multi-line imports
+> regardless of the `"never"` setting.
+>
+> This configuration is useful, for instance, to keep single-line type-only
+> imports stacked tightly together at the bottom of your import block to
+> preserve space while still logically organizing normal imports for quick and
+> pleasant reference.
+
+#### Example
+
+Given the following settings:
+
+```jsonc
+{
+  "import/order": ["error", {
+    "newlines-between": "always-and-inside-groups",
+    "consolidateIslands": "inside-groups"
+  }]
+}
+```
+
+This will fail the rule check:
+
+```ts
+var fs = require('fs');
+var path = require('path');
+var { util1, util2, util3 } = require('util');
+var async = require('async');
+var relParent1 = require('../foo');
+var {
+  relParent21,
+  relParent22,
+  relParent23,
+  relParent24,
+} = require('../');
+var relParent3 = require('../bar');
+var { sibling1,
+  sibling2, sibling3 } = require('./foo');
+var sibling2 = require('./bar');
+var sibling3 = require('./foobar');
+```
+
+While this will succeed (and is what `--fix` would yield):
+
+```ts
+var fs = require('fs');
+var path = require('path');
+var { util1, util2, util3 } = require('util');
+
+var async = require('async');
+
+var relParent1 = require('../foo');
+
+var {
+  relParent21,
+  relParent22,
+  relParent23,
+  relParent24,
+} = require('../');
+
+var relParent3 = require('../bar');
+
+var { sibling1,
+  sibling2, sibling3 } = require('./foo');
+
+var sibling2 = require('./bar');
+var sibling3 = require('./foobar');
+```
+
+Note the intragroup "islands" of grouped single-line imports, as well as multi-line imports, are surrounded by new lines. At the same time, note the typical new lines separating different groups are still maintained thanks to [`newlines-between`][20].
+
+The same holds true for the next example; when given the following settings:
+
+```jsonc
+{
+  "import/order": ["error", {
+    "alphabetize": { "order": "asc" },
+    "groups": ["external", "internal", "index", "type"],
+    "pathGroups": [
+      {
+        "pattern": "dirA/**",
+        "group": "internal",
+        "position": "after"
+      },
+      {
+        "pattern": "dirB/**",
+        "group": "internal",
+        "position": "before"
+      },
+      {
+        "pattern": "dirC/**",
+        "group": "internal"
+      }
+    ],
+    "newlines-between": "always-and-inside-groups",
+    "newlines-between-types": "never",
+    "pathGroupsExcludedImportTypes": [],
+    "sortTypesGroup": true,
+    "consolidateIslands": "inside-groups"
+  }]
+}
+```
+
+> [!IMPORTANT]
+>
+> **Pay special attention to the value of [`pathGroupsExcludedImportTypes`][9]** in this example's settings.
+> Without it, the successful example below would fail.
+> This is because the imports with specifiers starting with "dirA/", "dirB/", and "dirC/" are all [considered part of the `"external"` group](#how-imports-are-grouped), and imports in that group are excluded from [`pathGroups`][8] matching by default.
+>
+> The fix is to remove `"external"` (and, in this example, the others) from [`pathGroupsExcludedImportTypes`][9].
+
+This will fail the rule check:
+
+```ts
+import c from 'Bar';
+import d from 'bar';
+import {
+  aa,
+  bb,
+  cc,
+  dd,
+  ee,
+  ff,
+  gg
+} from 'baz';
+import {
+  hh,
+  ii,
+  jj,
+  kk,
+  ll,
+  mm,
+  nn
+} from 'fizz';
+import a from 'foo';
+import b from 'dirA/bar';
 import index from './';
-import sibling from './foo';
+import type { AA,
+  BB, CC } from 'abc';
+import type { Z } from 'fizz';
+import type {
+  A,
+  B
+} from 'foo';
+import type { C2 } from 'dirB/Bar';
+import type {
+  D2,
+  X2,
+  Y2
+} from 'dirB/bar';
+import type { E2 } from 'dirB/baz';
+import type { C3 } from 'dirC/Bar';
+import type {
+  D3,
+  X3,
+  Y3
+} from 'dirC/bar';
+import type { E3 } from 'dirC/baz';
+import type { F3 } from 'dirC/caz';
+import type { C1 } from 'dirA/Bar';
+import type {
+  D1,
+  X1,
+  Y1
+} from 'dirA/bar';
+import type { E1 } from 'dirA/baz';
+import type { F } from './index.js';
+import type { G } from './aaa.js';
+import type { H } from './bbb';
+```
+
+While this will succeed (and is what `--fix` would yield):
+
+```ts
+import c from 'Bar';
+import d from 'bar';
+
+import {
+  aa,
+  bb,
+  cc,
+  dd,
+  ee,
+  ff,
+  gg
+} from 'baz';
+
+import {
+  hh,
+  ii,
+  jj,
+  kk,
+  ll,
+  mm,
+  nn
+} from 'fizz';
+
+import a from 'foo';
+
+import b from 'dirA/bar';
+
+import index from './';
+
+import type { AA,
+  BB, CC } from 'abc';
+
+import type { Z } from 'fizz';
+
+import type {
+  A,
+  B
+} from 'foo';
+
+import type { C2 } from 'dirB/Bar';
+
+import type {
+  D2,
+  X2,
+  Y2
+} from 'dirB/bar';
+
+import type { E2 } from 'dirB/baz';
+import type { C3 } from 'dirC/Bar';
+
+import type {
+  D3,
+  X3,
+  Y3
+} from 'dirC/bar';
+
+import type { E3 } from 'dirC/baz';
+import type { F3 } from 'dirC/caz';
+import type { C1 } from 'dirA/Bar';
+
+import type {
+  D1,
+  X1,
+  Y1
+} from 'dirA/bar';
+
+import type { E1 } from 'dirA/baz';
+import type { F } from './index.js';
+import type { G } from './aaa.js';
+import type { H } from './bbb';
 ```
 
 ## Related
 
-- [`import/external-module-folders`] setting
+ - [`import/external-module-folders`][29]
+ - [`import/internal-regex`][28]
+ - [`import/core-modules`][11]
 
-[`import/external-module-folders`]: ../../README.md#importexternal-module-folders
+[3]: #how-imports-are-grouped
+[4]: https://nodejs.org/api/esm.html#terminology
+[5]: #warnonunassignedimports
+[6]: https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-8.html#type-only-imports-and-export
+[7]: #sorttypesgroup
+[8]: #pathgroups
+[9]: #pathgroupsexcludedimporttypes
+[10]: https://www.npmjs.com/package/is-core-module
+[11]: ../../README.md#importcore-modules
+[12]: https://www.npmjs.com/package/package-up
+[13]: #pathgroup
+[14]: https://www.npmjs.com/package/minimatch
+[16]: https://www.npmjs.com/package/minimatch#features
+[17]: https://www.npmjs.com/package/minimatch#options
+[18]: #groups
+[20]: #newlines-between
+[21]: https://eslint.org/docs/latest/rules/no-multiple-empty-lines
+[22]: https://prettier.io
+[23]: https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-5.html#type-modifiers-on-import-names
+[25]: #consolidateislands
+[27]: #newlines-between-types
+[28]: ../../README.md#importinternal-regex
+[29]: ../../README.md#importexternal-module-folders
+[30]: #alphabetize
+[31]: https://webpack.js.org/guides/tree-shaking#mark-the-file-as-side-effect-free
+[32]: #distinctgroup
+[33]: #named
diff --git a/docs/rules/prefer-default-export.md b/docs/rules/prefer-default-export.md
index 23e584bcc..e2a7bacd7 100644
--- a/docs/rules/prefer-default-export.md
+++ b/docs/rules/prefer-default-export.md
@@ -1,9 +1,45 @@
 # import/prefer-default-export
 
-When there is only a single export from a module, prefer using default export over named export.
+<!-- end auto-generated rule header -->
+
+In exporting files, this rule checks if there is default export or not.
 
 ## Rule Details
 
+### rule schema
+
+```javascript
+"import/prefer-default-export": [
+    ( "off" | "warn" | "error" ),
+    { "target": "single" | "any" } // default is "single"
+]
+```
+
+### Config Options
+
+There are two options available: `single` and `any`. By default, if you do not specify the option, rule will assume it is `single`.
+
+#### single
+
+**Definition**: When there is only a single export from a module, prefer using default export over named export.
+
+How to setup config file for this rule:
+
+```javascript
+// you can manually specify it
+"rules": {
+    "import/prefer-default-export": [
+        ( "off" | "warn" | "error" ),
+        { "target": "single" }
+    ]
+}
+
+// config setup below will also work
+"rules": {
+    "import/prefer-default-export": "off" | "warn" | "error"
+}
+```
+
 The following patterns are considered warnings:
 
 ```javascript
@@ -22,7 +58,7 @@ The following patterns are not warnings:
 // There is a default export.
 export const foo = 'foo';
 const bar = 'bar';
-export default 'bar';
+export default bar;
 ```
 
 ```javascript
@@ -56,3 +92,94 @@ export { foo as default }
 // Any batch export will disable this rule. The remote module is not inspected.
 export * from './other-module'
 ```
+
+#### any
+
+**Definition**: any exporting file must contain a default export.
+
+How to setup config file for this rule:
+
+```javascript
+// you have to manually specify it
+"rules": {
+    "import/prefer-default-export": [
+        ( "off" | "warn" | "error" ),
+        { "target": "any" }
+    ]
+}
+```
+
+The following patterns are *not* considered warnings:
+
+```javascript
+// good1.js
+
+//has default export
+export default function bar() {};
+```
+
+```javascript
+// good2.js
+
+// has default export
+let foo;
+export { foo as default }
+```
+
+```javascript
+// good3.js
+
+//contains multiple exports AND default export
+export const a = 5;
+export function bar(){};
+let foo;
+export { foo as default }
+```
+
+```javascript
+// good4.js
+
+// does not contain any exports => file is not checked by the rule
+import * as foo from './foo';
+```
+
+```javascript
+// export-star.js
+
+// Any batch export will disable this rule. The remote module is not inspected.
+export * from './other-module'
+```
+
+The following patterns are considered warnings:
+
+```javascript
+// bad1.js
+
+//has 2 named exports, but no default export
+export const foo = 'foo';
+export const bar = 'bar';
+```
+
+```javascript
+// bad2.js
+
+// does not have default export
+let foo, bar;
+export { foo, bar }
+```
+
+```javascript
+// bad3.js
+
+// does not have default export
+export { a, b } from "foo.js"
+```
+
+```javascript
+// bad4.js
+
+// does not have default export
+let item;
+export const foo = item;
+export { item };
+```
diff --git a/docs/rules/unambiguous.md b/docs/rules/unambiguous.md
index 7955c3fbc..e9e5bf73d 100644
--- a/docs/rules/unambiguous.md
+++ b/docs/rules/unambiguous.md
@@ -1,5 +1,7 @@
 # import/unambiguous
 
+<!-- end auto-generated rule header -->
+
 Warn if a `module` could be mistakenly parsed as a `script` by a consumer leveraging
 [Unambiguous JavaScript Grammar] to determine correct parsing goal.
 
@@ -30,6 +32,7 @@ export {} // simple way to mark side-effects-only file as 'module' without any i
 ```
 
 ...whereas the following file would be reported:
+
 ```js
 (function x() { return 42 })()
 ```
@@ -46,9 +49,9 @@ a `module`.
 
 ## Further Reading
 
-- [Unambiguous JavaScript Grammar]
-- [`parserOptions.sourceType`]
-- [node-eps#13](https://github.com/nodejs/node-eps/issues/13)
+ - [Unambiguous JavaScript Grammar]
+ - [`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
-[Unambiguous JavaScript Grammar]: https://github.com/nodejs/node-eps/blob/master/002-es-modules.md#32-determining-if-source-is-an-es-module
+[`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/examples/flat/eslint.config.mjs b/examples/flat/eslint.config.mjs
new file mode 100644
index 000000000..143265265
--- /dev/null
+++ b/examples/flat/eslint.config.mjs
@@ -0,0 +1,26 @@
+import importPlugin from 'eslint-plugin-import';
+import js from '@eslint/js';
+import tsParser from '@typescript-eslint/parser';
+
+export default [
+  js.configs.recommended,
+  importPlugin.flatConfigs.recommended,
+  importPlugin.flatConfigs.react,
+  importPlugin.flatConfigs.typescript,
+  {
+    files: ['**/*.{js,mjs,cjs,jsx,mjsx,ts,tsx,mtsx}'],
+    languageOptions: {
+      parser: tsParser,
+      ecmaVersion: 'latest',
+      sourceType: 'module',
+    },
+    ignores: ['eslint.config.mjs', '**/exports-unused.ts'],
+    rules: {
+      'no-unused-vars': 'off',
+      'import/no-dynamic-require': 'warn',
+      'import/no-nodejs-modules': 'warn',
+      'import/no-unused-modules': ['warn', { unusedExports: true }],
+      'import/no-cycle': 'warn',
+    },
+  },
+];
diff --git a/examples/flat/package.json b/examples/flat/package.json
new file mode 100644
index 000000000..0894d29f2
--- /dev/null
+++ b/examples/flat/package.json
@@ -0,0 +1,17 @@
+{
+  "name": "flat",
+  "version": "1.0.0",
+  "main": "index.js",
+  "scripts": {
+    "lint": "cross-env ESLINT_USE_FLAT_CONFIG=true eslint src --report-unused-disable-directives"
+  },
+  "devDependencies": {
+    "@eslint/js": "^9.5.0",
+    "@types/node": "^20.14.5",
+    "@typescript-eslint/parser": "^7.13.1",
+    "cross-env": "^7.0.3",
+    "eslint": "^8.57.0",
+    "eslint-plugin-import": "file:../..",
+    "typescript": "^5.4.5"
+  }
+}
diff --git a/examples/flat/src/depth-zero.js b/examples/flat/src/depth-zero.js
new file mode 100644
index 000000000..8cfde9979
--- /dev/null
+++ b/examples/flat/src/depth-zero.js
@@ -0,0 +1,3 @@
+import { foo } from "./es6/depth-one-dynamic";
+
+foo();
diff --git a/examples/flat/src/es6/depth-one-dynamic.js b/examples/flat/src/es6/depth-one-dynamic.js
new file mode 100644
index 000000000..ca129fd62
--- /dev/null
+++ b/examples/flat/src/es6/depth-one-dynamic.js
@@ -0,0 +1,3 @@
+export function foo() {}
+
+export const bar = () => import("../depth-zero").then(({foo}) => foo);
diff --git a/examples/flat/src/exports-unused.ts b/examples/flat/src/exports-unused.ts
new file mode 100644
index 000000000..af8061ec2
--- /dev/null
+++ b/examples/flat/src/exports-unused.ts
@@ -0,0 +1,12 @@
+export type ScalarType = string | number;
+export type ObjType = {
+  a: ScalarType;
+  b: ScalarType;
+};
+
+export const a = 13;
+export const b = 18;
+
+const defaultExport: ObjType = { a, b };
+
+export default defaultExport;
diff --git a/examples/flat/src/exports.ts b/examples/flat/src/exports.ts
new file mode 100644
index 000000000..af8061ec2
--- /dev/null
+++ b/examples/flat/src/exports.ts
@@ -0,0 +1,12 @@
+export type ScalarType = string | number;
+export type ObjType = {
+  a: ScalarType;
+  b: ScalarType;
+};
+
+export const a = 13;
+export const b = 18;
+
+const defaultExport: ObjType = { a, b };
+
+export default defaultExport;
diff --git a/examples/flat/src/imports.ts b/examples/flat/src/imports.ts
new file mode 100644
index 000000000..643219ae4
--- /dev/null
+++ b/examples/flat/src/imports.ts
@@ -0,0 +1,7 @@
+//import c from './exports';
+import { a, b } from './exports';
+import type { ScalarType, ObjType } from './exports';
+
+import path from 'path';
+import fs from 'node:fs';
+import console from 'console';
diff --git a/examples/flat/src/jsx.tsx b/examples/flat/src/jsx.tsx
new file mode 100644
index 000000000..970d53cb8
--- /dev/null
+++ b/examples/flat/src/jsx.tsx
@@ -0,0 +1,3 @@
+const Components = () => {
+  return <></>;
+};
diff --git a/examples/flat/tsconfig.json b/examples/flat/tsconfig.json
new file mode 100644
index 000000000..e100bfc98
--- /dev/null
+++ b/examples/flat/tsconfig.json
@@ -0,0 +1,14 @@
+{
+  "compilerOptions": {
+    "jsx": "react-jsx",
+    "lib": ["ESNext"],
+    "target": "ESNext",
+    "module": "ESNext",
+    "rootDir": "./",
+    "moduleResolution": "Bundler",
+    "esModuleInterop": true,
+    "forceConsistentCasingInFileNames": true,
+    "strict": true,
+    "skipLibCheck": true
+  }
+}
diff --git a/examples/legacy/.eslintrc.cjs b/examples/legacy/.eslintrc.cjs
new file mode 100644
index 000000000..90e065c9d
--- /dev/null
+++ b/examples/legacy/.eslintrc.cjs
@@ -0,0 +1,25 @@
+module.exports = {
+  root: true,
+  env: { es2022: true },
+  extends: [
+    'eslint:recommended',
+    'plugin:import/recommended',
+    'plugin:import/react',
+    'plugin:import/typescript',
+  ],
+  settings: {},
+  ignorePatterns: ['.eslintrc.cjs', '**/exports-unused.ts'],
+  parser: '@typescript-eslint/parser',
+  parserOptions: {
+    ecmaVersion: 'latest',
+    sourceType: 'module',
+  },
+  plugins: ['import'],
+  rules: {
+    'no-unused-vars': 'off',
+    'import/no-dynamic-require': 'warn',
+    'import/no-nodejs-modules': 'warn',
+    'import/no-unused-modules': ['warn', { unusedExports: true }],
+    'import/no-cycle': 'warn',
+  },
+};
diff --git a/examples/legacy/package.json b/examples/legacy/package.json
new file mode 100644
index 000000000..e3ca09488
--- /dev/null
+++ b/examples/legacy/package.json
@@ -0,0 +1,16 @@
+{
+  "name": "legacy",
+  "version": "1.0.0",
+  "main": "index.js",
+  "scripts": {
+    "lint": "cross-env ESLINT_USE_FLAT_CONFIG=false eslint src --ext js,jsx,ts,tsx --report-unused-disable-directives"
+  },
+  "devDependencies": {
+    "@types/node": "^20.14.5",
+    "@typescript-eslint/parser": "^7.13.1",
+    "cross-env": "^7.0.3",
+    "eslint": "^8.57.0",
+    "eslint-plugin-import": "file:../..",
+    "typescript": "^5.4.5"
+  }
+}
diff --git a/examples/legacy/src/depth-zero.js b/examples/legacy/src/depth-zero.js
new file mode 100644
index 000000000..8cfde9979
--- /dev/null
+++ b/examples/legacy/src/depth-zero.js
@@ -0,0 +1,3 @@
+import { foo } from "./es6/depth-one-dynamic";
+
+foo();
diff --git a/examples/legacy/src/es6/depth-one-dynamic.js b/examples/legacy/src/es6/depth-one-dynamic.js
new file mode 100644
index 000000000..cda7091cd
--- /dev/null
+++ b/examples/legacy/src/es6/depth-one-dynamic.js
@@ -0,0 +1,3 @@
+export function foo() {}
+
+export const bar = () => import("../depth-zero").then(({ foo }) => foo);
diff --git a/examples/legacy/src/exports-unused.ts b/examples/legacy/src/exports-unused.ts
new file mode 100644
index 000000000..af8061ec2
--- /dev/null
+++ b/examples/legacy/src/exports-unused.ts
@@ -0,0 +1,12 @@
+export type ScalarType = string | number;
+export type ObjType = {
+  a: ScalarType;
+  b: ScalarType;
+};
+
+export const a = 13;
+export const b = 18;
+
+const defaultExport: ObjType = { a, b };
+
+export default defaultExport;
diff --git a/examples/legacy/src/exports.ts b/examples/legacy/src/exports.ts
new file mode 100644
index 000000000..af8061ec2
--- /dev/null
+++ b/examples/legacy/src/exports.ts
@@ -0,0 +1,12 @@
+export type ScalarType = string | number;
+export type ObjType = {
+  a: ScalarType;
+  b: ScalarType;
+};
+
+export const a = 13;
+export const b = 18;
+
+const defaultExport: ObjType = { a, b };
+
+export default defaultExport;
diff --git a/examples/legacy/src/imports.ts b/examples/legacy/src/imports.ts
new file mode 100644
index 000000000..643219ae4
--- /dev/null
+++ b/examples/legacy/src/imports.ts
@@ -0,0 +1,7 @@
+//import c from './exports';
+import { a, b } from './exports';
+import type { ScalarType, ObjType } from './exports';
+
+import path from 'path';
+import fs from 'node:fs';
+import console from 'console';
diff --git a/examples/legacy/src/jsx.tsx b/examples/legacy/src/jsx.tsx
new file mode 100644
index 000000000..970d53cb8
--- /dev/null
+++ b/examples/legacy/src/jsx.tsx
@@ -0,0 +1,3 @@
+const Components = () => {
+  return <></>;
+};
diff --git a/examples/legacy/tsconfig.json b/examples/legacy/tsconfig.json
new file mode 100644
index 000000000..e100bfc98
--- /dev/null
+++ b/examples/legacy/tsconfig.json
@@ -0,0 +1,14 @@
+{
+  "compilerOptions": {
+    "jsx": "react-jsx",
+    "lib": ["ESNext"],
+    "target": "ESNext",
+    "module": "ESNext",
+    "rootDir": "./",
+    "moduleResolution": "Bundler",
+    "esModuleInterop": true,
+    "forceConsistentCasingInFileNames": true,
+    "strict": true,
+    "skipLibCheck": true
+  }
+}
diff --git a/examples/v9/eslint.config.mjs b/examples/v9/eslint.config.mjs
new file mode 100644
index 000000000..7b7534d14
--- /dev/null
+++ b/examples/v9/eslint.config.mjs
@@ -0,0 +1,22 @@
+import importPlugin from 'eslint-plugin-import';
+import js from '@eslint/js';
+
+export default [
+  js.configs.recommended,
+  importPlugin.flatConfigs.recommended,
+  {
+    files: ['**/*.{js,mjs,cjs}'],
+    languageOptions: {
+      ecmaVersion: 'latest',
+      sourceType: 'module',
+    },
+    ignores: ['eslint.config.mjs', 'node_modules/*'],
+    rules: {
+      'no-unused-vars': 'off',
+      'import/no-dynamic-require': 'warn',
+      'import/no-nodejs-modules': 'warn',
+      'import/no-unused-modules': ['warn', { unusedExports: true }],
+      'import/no-cycle': 'warn',
+    },
+  },
+];
diff --git a/examples/v9/package.json b/examples/v9/package.json
new file mode 100644
index 000000000..4746d7400
--- /dev/null
+++ b/examples/v9/package.json
@@ -0,0 +1,14 @@
+{
+  "name": "v9",
+  "version": "1.0.0",
+  "main": "index.js",
+  "type": "module",
+  "scripts": {
+    "lint": "eslint src --report-unused-disable-directives"
+  },
+  "devDependencies": {
+    "@eslint/js": "^9.17.0",
+    "eslint": "^9.17.0",
+    "eslint-plugin-import": "file:../.."
+  }
+}
diff --git a/examples/v9/src/depth-zero.js b/examples/v9/src/depth-zero.js
new file mode 100644
index 000000000..8cfde9979
--- /dev/null
+++ b/examples/v9/src/depth-zero.js
@@ -0,0 +1,3 @@
+import { foo } from "./es6/depth-one-dynamic";
+
+foo();
diff --git a/examples/v9/src/es6/depth-one-dynamic.js b/examples/v9/src/es6/depth-one-dynamic.js
new file mode 100644
index 000000000..ca129fd62
--- /dev/null
+++ b/examples/v9/src/es6/depth-one-dynamic.js
@@ -0,0 +1,3 @@
+export function foo() {}
+
+export const bar = () => import("../depth-zero").then(({foo}) => foo);
diff --git a/examples/v9/src/exports-unused.js b/examples/v9/src/exports-unused.js
new file mode 100644
index 000000000..3c44db68d
--- /dev/null
+++ b/examples/v9/src/exports-unused.js
@@ -0,0 +1,6 @@
+export const a = 13;
+export const b = 18;
+
+const defaultExport = { a, b };
+
+export default defaultExport;
diff --git a/examples/v9/src/exports.js b/examples/v9/src/exports.js
new file mode 100644
index 000000000..3c44db68d
--- /dev/null
+++ b/examples/v9/src/exports.js
@@ -0,0 +1,6 @@
+export const a = 13;
+export const b = 18;
+
+const defaultExport = { a, b };
+
+export default defaultExport;
diff --git a/examples/v9/src/imports.js b/examples/v9/src/imports.js
new file mode 100644
index 000000000..edf133686
--- /dev/null
+++ b/examples/v9/src/imports.js
@@ -0,0 +1,6 @@
+//import c from './exports';
+import { a, b } from './exports';
+
+import path from 'path';
+import fs from 'node:fs';
+import console from 'console';
diff --git a/index.d.ts b/index.d.ts
new file mode 100644
index 000000000..2bef94ec2
--- /dev/null
+++ b/index.d.ts
@@ -0,0 +1,33 @@
+import { ESLint, Linter, Rule } from 'eslint';
+
+declare const plugin: ESLint.Plugin & {
+  meta: {
+    name: string;
+    version: string;
+  };
+  configs: {
+    'recommended': Linter.LegacyConfig;
+    'errors': Linter.LegacyConfig;
+    'warnings': Linter.LegacyConfig;
+    'stage-0': Linter.LegacyConfig;
+    'react': Linter.LegacyConfig;
+    'react-native': Linter.LegacyConfig;
+    'electron': Linter.LegacyConfig;
+    'typescript': Linter.LegacyConfig;
+  };
+  flatConfigs: {
+    'recommended': Linter.FlatConfig;
+    'errors': Linter.FlatConfig;
+    'warnings': Linter.FlatConfig;
+    'stage-0': Linter.FlatConfig;
+    'react': Linter.FlatConfig;
+    'react-native': Linter.FlatConfig;
+    'electron': Linter.FlatConfig;
+    'typescript': Linter.FlatConfig;
+  };
+  rules: {
+    [key: string]: Rule.RuleModule;
+  };
+};
+
+export = plugin;
diff --git a/memo-parser/.nycrc b/memo-parser/.nycrc
new file mode 100644
index 000000000..5d75e2157
--- /dev/null
+++ b/memo-parser/.nycrc
@@ -0,0 +1,19 @@
+{
+	"all": true,
+	"check-coverage": false,
+	"reporter": ["text-summary", "lcov", "text", "html", "json"],
+	"require": [
+		"babel-register"
+	],
+	"sourceMap": true,
+	"instrument": false,
+	"exclude": [
+		"coverage",
+		"test",
+		"tests",
+		"resolvers/*/test",
+		"scripts",
+		"memo-parser",
+		"lib"
+	]
+}
diff --git a/memo-parser/README.md b/memo-parser/README.md
index 8a2a3cb5c..741e0ed4d 100644
--- a/memo-parser/README.md
+++ b/memo-parser/README.md
@@ -1,13 +1,11 @@
 # eslint-plugin-import/memo-parser
 
-
-## NOTE!
+## NOTE
 
 This used to improve performance, but as of ESLint 5 and v2 of this plugin, it seems to just consume a bunch of memory and slightly increase lint times.
 
 **Not recommended for use at this time!**
 
-
 This parser is just a memoizing wrapper around some actual parser.
 
 To configure, just add your _actual_ parser to the `parserOptions`, like so:
diff --git a/memo-parser/index.js b/memo-parser/index.js
index 9fd74c33a..7868b7e95 100644
--- a/memo-parser/index.js
+++ b/memo-parser/index.js
@@ -1,10 +1,10 @@
-"use strict"
+'use strict';
 
-const crypto = require('crypto')
-    , moduleRequire = require('eslint-module-utils/module-require').default
-    , hashObject = require('eslint-module-utils/hash').hashObject
+const crypto = require('crypto');
+const moduleRequire = require('eslint-module-utils/module-require').default;
+const hashObject = require('eslint-module-utils/hash').hashObject;
 
-const cache = new Map()
+const cache = new Map();
 
 // must match ESLint default options or we'll miss the cache every time
 const parserOptions = {
@@ -14,28 +14,28 @@ const parserOptions = {
   tokens: true,
   comment: true,
   attachComment: true,
-}
+};
 
 exports.parse = function parse(content, options) {
-  options = Object.assign({}, options, parserOptions)
+  options = { ...options, ...parserOptions };
 
   if (!options.filePath) {
-    throw new Error("no file path provided!")
+    throw new Error('no file path provided!');
   }
 
-  const keyHash = crypto.createHash('sha256')
-  keyHash.update(content)
-  hashObject(options, keyHash)
+  const keyHash = crypto.createHash('sha256');
+  keyHash.update(content);
+  hashObject(options, keyHash);
 
-  const key = keyHash.digest('hex')
+  const key = keyHash.digest('hex');
 
-  let ast = cache.get(key)
-  if (ast != null) return ast
+  let ast = cache.get(key);
+  if (ast != null) { return ast; }
 
-  const realParser = moduleRequire(options.parser)
+  const realParser = moduleRequire(options.parser);
 
-  ast = realParser.parse(content, options)
-  cache.set(key, ast)
+  ast = realParser.parse(content, options);
+  cache.set(key, ast);
 
-  return ast
-}
+  return ast;
+};
diff --git a/memo-parser/package.json b/memo-parser/package.json
index fa7d12973..b89c3c5ad 100644
--- a/memo-parser/package.json
+++ b/memo-parser/package.json
@@ -1,17 +1,19 @@
 {
   "name": "memo-parser",
-  "version": "0.2.0",
+  "version": "0.2.1",
   "engines": {
     "node": ">=4"
   },
   "description": "Memoizing wrapper for any ESLint-compatible parser module.",
   "main": "index.js",
   "scripts": {
+    "prepublishOnly": "cp ../{LICENSE,.npmrc} ./",
     "test": "echo \"Error: no test specified\" && exit 1"
   },
   "repository": {
     "type": "git",
-    "url": "git+https://github.com/benmosher/eslint-plugin-import.git"
+    "url": "git+https://github.com/import-js/eslint-plugin-import.git",
+    "directory": "memo-parser"
   },
   "keywords": [
     "eslint",
@@ -21,10 +23,13 @@
   "author": "Ben Mosher (me@benmosher.com)",
   "license": "MIT",
   "bugs": {
-    "url": "https://github.com/benmosher/eslint-plugin-import/issues"
+    "url": "https://github.com/import-js/eslint-plugin-import/issues"
   },
-  "homepage": "https://github.com/benmosher/eslint-plugin-import#readme",
+  "homepage": "https://github.com/import-js/eslint-plugin-import#readme",
   "peerDependencies": {
     "eslint": ">=3.5.0"
+  },
+  "dependencies": {
+    "eslint-module-utils": "^2.6.1"
   }
 }
diff --git a/package.json b/package.json
index b63f3af49..125fc426e 100644
--- a/package.json
+++ b/package.json
@@ -1,39 +1,56 @@
 {
   "name": "eslint-plugin-import",
-  "version": "2.18.2",
+  "version": "2.31.0",
   "description": "Import with sanity.",
   "engines": {
     "node": ">=4"
   },
   "main": "lib/index.js",
+  "types": "index.d.ts",
   "directories": {
     "test": "tests"
   },
   "files": [
+    "*.md",
+    "!{CONTRIBUTING,RELEASE}.md",
+    "LICENSE",
+    "docs",
     "lib",
     "config",
-    "memo-parser"
+    "memo-parser/{*.js,LICENSE,*.md}",
+    "index.d.ts"
   ],
   "scripts": {
-    "build": "babel --quiet --out-dir lib src",
     "prebuild": "rimraf lib",
-    "watch": "npm run mocha -- --watch tests/src",
+    "build": "babel --quiet --out-dir lib src",
+    "postbuild": "npm run copy-metafiles",
+    "copy-metafiles": "node --require babel-register ./scripts/copyMetafiles",
+    "watch": "npm run tests-only -- -- --watch",
     "pretest": "linklocal",
-    "posttest": "eslint ./src",
-    "mocha": "cross-env BABEL_ENV=test NODE_PATH=./src nyc -s mocha -R dot --recursive -t 5s",
-    "test": "npm run mocha tests/src",
-    "test-compiled": "npm run prepublish && NODE_PATH=./lib mocha --compilers js:babel-register --recursive tests/src",
-    "test-all": "npm test && for resolver in ./resolvers/*; do cd $resolver && npm test && cd ../..; done",
-    "prepublish": "npm run build",
-    "coveralls": "nyc report --reporter lcovonly && cat ./coverage/lcov.info | coveralls"
+    "posttest": "eslint . && npm run update:eslint-docs -- --check && markdownlint \"**/*.md\"",
+    "mocha": "cross-env BABEL_ENV=test nyc mocha",
+    "tests-only": "npm run mocha tests/src",
+    "test": "npm run tests-only",
+    "test-compiled": "npm run prepublish && BABEL_ENV=testCompiled mocha --compilers js:babel-register tests/src",
+    "test-all": "node --require babel-register ./scripts/testAll",
+    "test-examples": "npm run build && npm run test-example:legacy && npm run test-example:flat && npm run test-example:v9",
+    "test-example:legacy": "cd examples/legacy && npm install && npm run lint",
+    "test-example:flat": "cd examples/flat && npm install && npm run lint",
+    "test-example:v9": "cd examples/v9 && npm install && npm run lint",
+    "test-types": "npx --package typescript@latest tsc --noEmit index.d.ts",
+    "prepublishOnly": "safe-publish-latest && npm run build",
+    "prepublish": "not-in-publish || npm run prepublishOnly",
+    "preupdate:eslint-docs": "npm run build",
+    "update:eslint-docs": "eslint-doc-generator --rule-doc-title-format prefix-name --rule-doc-section-options false --rule-list-split meta.docs.category --ignore-config stage-0 --config-emoji recommended,☑️"
   },
   "repository": {
     "type": "git",
-    "url": "https://github.com/benmosher/eslint-plugin-import"
+    "url": "https://github.com/import-js/eslint-plugin-import"
   },
   "keywords": [
     "eslint",
     "eslintplugin",
+    "eslint-plugin",
     "es6",
     "jsnext",
     "modules",
@@ -43,64 +60,79 @@
   "author": "Ben Mosher <me@benmosher.com>",
   "license": "MIT",
   "bugs": {
-    "url": "https://github.com/benmosher/eslint-plugin-import/issues"
+    "url": "https://github.com/import-js/eslint-plugin-import/issues"
   },
-  "homepage": "https://github.com/benmosher/eslint-plugin-import",
+  "homepage": "https://github.com/import-js/eslint-plugin-import",
   "devDependencies": {
+    "@angular-eslint/template-parser": "^13.5.0",
     "@eslint/import-test-order-redirect-scoped": "file:./tests/files/order-redirect-scoped",
-    "@typescript-eslint/parser": "1.10.3-alpha.13",
+    "@test-scope/some-module": "file:./tests/files/symlinked-module",
+    "@types/eslint": "^8.56.12",
+    "@typescript-eslint/parser": "^2.23.0 || ^3.3.0 || ^4.29.3 || ^5.10.0",
     "babel-cli": "^6.26.0",
     "babel-core": "^6.26.3",
-    "babel-eslint": "^8.2.6",
+    "babel-eslint": "=8.0.3 || ^8.2.6",
     "babel-plugin-istanbul": "^4.1.6",
-    "babel-preset-es2015-argon": "latest",
+    "babel-plugin-module-resolver": "^2.7.1",
+    "babel-preset-airbnb": "^2.6.0",
+    "babel-preset-flow": "^6.23.0",
     "babel-register": "^6.26.0",
-    "babylon": "^6.15.0",
-    "chai": "^4.2.0",
-    "coveralls": "^3.0.2",
+    "babylon": "^6.18.0",
+    "chai": "^4.3.10",
     "cross-env": "^4.0.0",
-    "eslint": "2.x - 6.x",
+    "escope": "^3.6.0",
+    "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9",
+    "eslint-doc-generator": "^1.6.1",
     "eslint-import-resolver-node": "file:./resolvers/node",
-    "eslint-import-resolver-typescript": "^1.0.2",
+    "eslint-import-resolver-typescript": "^1.0.2 || ^1.1.1",
     "eslint-import-resolver-webpack": "file:./resolvers/webpack",
     "eslint-import-test-order-redirect": "file:./tests/files/order-redirect",
     "eslint-module-utils": "file:./utils",
+    "eslint-plugin-eslint-plugin": "^2.3.0",
     "eslint-plugin-import": "2.x",
+    "eslint-plugin-json": "^2.1.2",
+    "find-babel-config": "=1.2.0",
+    "fs-copy-file-sync": "^1.1.1",
+    "glob": "^7.2.3",
+    "in-publish": "^2.0.1",
+    "jackspeak": "=2.1.1",
+    "jsonc-parser": "=3.2.0",
     "linklocal": "^2.8.2",
+    "lodash.isarray": "^4.0.0",
+    "markdownlint-cli": "~0.35",
     "mocha": "^3.5.3",
+    "npm-which": "^3.0.1",
     "nyc": "^11.9.0",
     "redux": "^3.7.2",
-    "rimraf": "^2.6.3",
-    "semver": "^6.0.0",
+    "rimraf": "^2.7.1",
+    "safe-publish-latest": "^2.0.0",
     "sinon": "^2.4.1",
-    "typescript": "~3.2.2",
-    "typescript-eslint-parser": "^22.0.0"
+    "tmp": "^0.2.1",
+    "typescript": "^2.8.1 || ~3.9.5 || ~4.5.2",
+    "typescript-eslint-parser": "^15 || ^20 || ^22"
   },
   "peerDependencies": {
-    "eslint": "2.x - 6.x"
+    "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9"
   },
   "dependencies": {
-    "array-includes": "^3.0.3",
-    "contains-path": "^0.1.0",
-    "debug": "^2.6.9",
-    "doctrine": "1.5.0",
-    "eslint-import-resolver-node": "^0.3.2",
-    "eslint-module-utils": "^2.4.0",
-    "has": "^1.0.3",
-    "minimatch": "^3.0.4",
-    "object.values": "^1.1.0",
-    "read-pkg-up": "^2.0.0",
-    "resolve": "^1.11.0"
-  },
-  "nyc": {
-    "require": [
-      "babel-register"
-    ],
-    "sourceMap": false,
-    "instrument": false,
-    "include": [
-      "src/",
-      "resolvers/"
-    ]
+    "@rtsao/scc": "^1.1.0",
+    "array-includes": "^3.1.8",
+    "array.prototype.findlastindex": "^1.2.5",
+    "array.prototype.flat": "^1.3.3",
+    "array.prototype.flatmap": "^1.3.3",
+    "debug": "^3.2.7",
+    "doctrine": "^2.1.0",
+    "eslint-import-resolver-node": "^0.3.9",
+    "eslint-module-utils": "^2.12.0",
+    "hasown": "^2.0.2",
+    "is-core-module": "^2.16.1",
+    "is-glob": "^4.0.3",
+    "minimatch": "^3.1.2",
+    "object.fromentries": "^2.0.8",
+    "object.groupby": "^1.0.3",
+    "object.values": "^1.2.1",
+    "semver": "^6.3.1",
+    "string.prototype.trimend": "^1.0.9",
+    "tsconfig-paths": "^3.15.0"
   }
 }
diff --git a/resolvers/.eslintrc b/resolvers/.eslintrc
deleted file mode 100644
index 9db33eda4..000000000
--- a/resolvers/.eslintrc
+++ /dev/null
@@ -1,3 +0,0 @@
----
-env:
-  es6: false
diff --git a/resolvers/README.md b/resolvers/README.md
index 05ef4ef32..e55ebb610 100644
--- a/resolvers/README.md
+++ b/resolvers/README.md
@@ -2,14 +2,16 @@
 
 Resolvers must export two names:
 
-### `interfaceVersion => Number`
+## `interfaceVersion => Number`
 
 This document currently describes version 2 of the resolver interface. As such, a resolver implementing this version should
 
 ```js
 export const interfaceVersion = 2
 ```
+
 or
+
 ```js
 exports.interfaceVersion = 2
 ```
@@ -18,9 +20,10 @@ To the extent it is feasible, trailing versions of the resolvers will continue t
 
 Currently, version 1 is assumed if no `interfaceVersion` is available. (didn't think to define it until v2, heh. 😅)
 
-### `resolve(source, file, config) => { found: Boolean, path: String? }`
+## `resolve(source, file, config) => { found: Boolean, path: String? }`
 
 Given:
+
 ```js
 // /some/path/to/module.js
 import ... from './imported-file'
@@ -37,27 +40,29 @@ settings:
     node: { paths: [a, b, c] }
 ```
 
-#### Arguments
+### Arguments
 
 The arguments provided will be:
 
-##### `source`
+#### `source`
+
 the module identifier (`./imported-file`).
 
-##### `file`
+#### `file`
+
 the absolute path to the file making the import (`/some/path/to/module.js`)
 
-##### `config`
+#### `config`
 
 an object provided via the `import/resolver` setting. `my-cool-resolver` will get `["some", "stuff"]` as its `config`, while
   `node` will get `{ "paths": ["a", "b", "c"] }` provided as `config`.
 
-#### Return value
+### Return value
 
 The first resolver to return `{found: true}` is considered the source of truth. The returned object has:
 
-- `found`: `true` if the `source` module can be resolved relative to `file`, else `false`
-- `path`: an absolute path `String` if the module can be located on the filesystem; else, `null`.
+ - `found`: `true` if the `source` module can be resolved relative to `file`, else `false`
+ - `path`: an absolute path `String` if the module can be located on the filesystem; else, `null`.
 
 An example of a `null` path is a Node core module, such as `fs` or `crypto`. These modules can always be resolved, but the path need not be provided as the plugin will not attempt to parse core modules at this time.
 
@@ -68,16 +73,17 @@ If the resolver cannot resolve `source` relative to `file`, it should just retur
 Here is most of the [Node resolver] at the time of this writing. It is just a wrapper around substack/Browserify's synchronous [`resolve`]:
 
 ```js
-var resolve = require('resolve')
+var resolve = require('resolve/sync');
+var isCoreModule = require('is-core-module');
 
 exports.resolve = function (source, file, config) {
-  if (resolve.isCore(source)) return { found: true, path: null }
+  if (isCoreModule(source)) return { found: true, path: null };
   try {
-    return { found: true, path: resolve.sync(source, opts(file, config)) }
+    return { found: true, path: resolve(source, opts(file, config)) };
   } catch (err) {
-    return { found: false }
+    return { found: false };
   }
-}
+};
 ```
 
 [Node resolver]: ./node/index.js
diff --git a/resolvers/node/.npmrc b/resolvers/node/.npmrc
deleted file mode 100644
index 43c97e719..000000000
--- a/resolvers/node/.npmrc
+++ /dev/null
@@ -1 +0,0 @@
-package-lock=false
diff --git a/resolvers/node/.nycrc b/resolvers/node/.nycrc
new file mode 100644
index 000000000..108436087
--- /dev/null
+++ b/resolvers/node/.nycrc
@@ -0,0 +1,15 @@
+{
+	"all": true,
+	"check-coverage": false,
+	"reporter": ["text-summary", "lcov", "text", "html", "json"],
+	"instrument": false,
+	"exclude": [
+		"coverage",
+		"test",
+		"tests",
+		"resolvers/*/test",
+		"scripts",
+		"memo-parser",
+		"lib"
+	]
+}
diff --git a/resolvers/node/CHANGELOG.md b/resolvers/node/CHANGELOG.md
index f0d2358ba..8e11359a9 100644
--- a/resolvers/node/CHANGELOG.md
+++ b/resolvers/node/CHANGELOG.md
@@ -1,52 +1,79 @@
 # 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
 
+## v0.3.9 - 2023-08-08
+ - [fix] restore node 6 compatibility
+
+## v0.3.8 - 2023-08-05
+ - [deps] update `is-core-module`, `resolve`
+ - [eslint] tighten up rules
+
+## v0.3.7 - 2023-01-11
+### Changed
+ - [Refactor] use `is-core-module` directly
+
+## v0.3.6 - 2021-08-15
+### Fixed
+ - when "module" does not exist, fall back to "main" ([#2186], thanks [@ljharb])
+
+## v0.3.5 - 2021-08-08
+### Added
+ - use "module" in the same spot as "jsnext:main" ([#2166], thanks [@MustafaHaddara])
+
+## v0.3.4 - 2020-06-16
+### Added
+ - add `.node` extension ([#1663])
+
+## v0.3.3 - 2020-01-10
+### Changed
+ - [meta] copy LICENSE file to all npm packages on prepublish ([#1595], thanks [@opichals])
+
 ## v0.3.2 - 2018-01-05
 ### Added
-- `.mjs` extension detected by default to support `experimental-modules` (#939)
+ - `.mjs` extension detected by default to support `experimental-modules` ([#939])
 
 ### Deps
-- update `debug`, `resolve`
+ - update `debug`, `resolve`
 
 ## v0.3.1 - 2017-06-23
 ### Changed
-- bumped `debug` dep to match other packages
+ - bumped `debug` dep to match other packages
 
 ## v0.3.0 - 2016-12-15
 ### Changed
-- bumped `resolve` to fix issues with Node builtins (thanks [@SkeLLLa] and [@ljharb])
+ - bumped `resolve` to fix issues with Node builtins (thanks [@SkeLLLa] and [@ljharb])
 
 ### Fixed
-- use `files` in `package.json` to ship only `index.js` ([#531], thanks for noticing [@lukeapage])
+ - use `files` in `package.json` to ship only `index.js` ([#531], thanks for noticing [@lukeapage])
 
 ## v0.2.3 - 2016-08-20
 ### Added
-- debug logging (use `DEBUG=eslint-plugin-import:resolver:node eslint [...]`)
+ - debug logging (use `DEBUG=eslint-plugin-import:resolver:node eslint [...]`)
 
 ## v0.2.2 - 2016-07-14
 ### Fixed
-- Node resolver no longer declares the import plugin as a `peerDependency`. See [#437]
-  for a well-articulated and thoughtful expression of why this doesn't make sense.
-  Thanks [@jasonkarns] for the issue and the PR to fix it ([#438]).
-
-  Also, apologies to the others who expressed this before, but I never understood
-  what the problem was.😅
+ - Node resolver no longer declares the import plugin as a `peerDependency`. See [#437] for a well-articulated and thoughtful expression of why this doesn't make sense. Thanks [@jasonkarns] for the issue and the PR to fix it ([#438]). Also, apologies to the others who expressed this before, but I never understood what the problem was.😅
 
 ## v0.2.1
 ### Fixed
-- find files with `.json` extensions (#333, thanks for noticing @jfmengels)
-
-[#438]: https://github.com/benmosher/eslint-plugin-import/pull/438
+ - find files with `.json` extensions (#333, thanks for noticing @jfmengels)
 
-[#939]: https://github.com/benmosher/eslint-plugin-import/issues/939
-[#531]: https://github.com/benmosher/eslint-plugin-import/issues/531
-[#437]: https://github.com/benmosher/eslint-plugin-import/issues/437
+[#2186]: https://github.com/import-js/eslint-plugin-import/issues/2186
+[#2166]: https://github.com/import-js/eslint-plugin-import/pull/2166
+[#1663]: https://github.com/import-js/eslint-plugin-import/issues/1663
+[#1595]: https://github.com/import-js/eslint-plugin-import/pull/1595
+[#939]: https://github.com/import-js/eslint-plugin-import/issues/939
+[#531]: https://github.com/import-js/eslint-plugin-import/issues/531
+[#438]: https://github.com/import-js/eslint-plugin-import/pull/438
+[#437]: https://github.com/import-js/eslint-plugin-import/issues/437
 
 [@jasonkarns]: https://github.com/jasonkarns
+[@ljharb]: https://github.com/ljharb
 [@lukeapage]: https://github.com/lukeapage
+[@MustafaHaddara]: https://github.com/MustafaHaddara
+[@opichals]: https://github.com/opichals
 [@SkeLLLa]: https://github.com/SkeLLLa
-[@ljharb]: https://github.com/ljharb
diff --git a/resolvers/node/index.js b/resolvers/node/index.js
index b5a1537bf..9e0e753cc 100644
--- a/resolvers/node/index.js
+++ b/resolvers/node/index.js
@@ -1,47 +1,66 @@
-var resolve = require('resolve')
-  , path = require('path')
+'use strict';
 
-var log = require('debug')('eslint-plugin-import:resolver:node')
+const resolve = require('resolve/sync');
+const isCoreModule = require('is-core-module');
+const path = require('path');
 
-exports.interfaceVersion = 2
+const log = require('debug')('eslint-plugin-import:resolver:node');
 
-exports.resolve = function (source, file, config) {
-  log('Resolving:', source, 'from:', file)
-  var resolvedPath
+exports.interfaceVersion = 2;
 
-  if (resolve.isCore(source)) {
-    log('resolved to core')
-    return { found: true, path: null }
-  }
+function opts(file, config, packageFilter) {
+  return Object.assign({ // more closely matches Node (#333)
+    // plus 'mjs' for native modules! (#939)
+    extensions: ['.mjs', '.js', '.json', '.node'],
+  }, config, {
+    // path.resolve will handle paths relative to CWD
+    basedir: path.dirname(path.resolve(file)),
+    packageFilter,
+  });
+}
 
-  try {
-    resolvedPath = resolve.sync(source, opts(file, config))
-    log('Resolved to:', resolvedPath)
-    return { found: true, path: resolvedPath }
-  } catch (err) {
-    log('resolve threw error:', err)
-    return { found: false }
+function identity(x) { return x; }
+
+function packageFilter(pkg, dir, config) {
+  let found = false;
+  const file = path.join(dir, 'dummy.js');
+  if (pkg.module) {
+    try {
+      resolve(String(pkg.module).replace(/^(?:\.\/)?/, './'), opts(file, config, identity));
+      pkg.main = pkg.module;
+      found = true;
+    } catch (err) {
+      log('resolve threw error trying to find pkg.module:', err);
+    }
+  }
+  if (!found && pkg['jsnext:main']) {
+    try {
+      resolve(String(pkg['jsnext:main']).replace(/^(?:\.\/)?/, './'), opts(file, config, identity));
+      pkg.main = pkg['jsnext:main'];
+      found = true;
+    } catch (err) {
+      log('resolve threw error trying to find pkg[\'jsnext:main\']:', err);
+    }
   }
+  return pkg;
 }
 
-function opts(file, config) {
-  return Object.assign({
-      // more closely matches Node (#333)
-      // plus 'mjs' for native modules! (#939)
-      extensions: ['.mjs', '.js', '.json'],
-    },
-    config,
-    {
-      // path.resolve will handle paths relative to CWD
-      basedir: path.dirname(path.resolve(file)),
-      packageFilter: packageFilter,
-
-    })
-}
+exports.resolve = function (source, file, config) {
+  log('Resolving:', source, 'from:', file);
+  let resolvedPath;
 
-function packageFilter(pkg) {
-  if (pkg['jsnext:main']) {
-    pkg['main'] = pkg['jsnext:main']
+  if (isCoreModule(source)) {
+    log('resolved to core');
+    return { found: true, path: null };
   }
-  return pkg
-}
+
+  try {
+    const cachedFilter = function (pkg, dir) { return packageFilter(pkg, dir, config); };
+    resolvedPath = resolve(source, opts(file, config, cachedFilter));
+    log('Resolved to:', resolvedPath);
+    return { found: true, path: resolvedPath };
+  } catch (err) {
+    log('resolve threw error:', err);
+    return { found: false };
+  }
+};
diff --git a/resolvers/node/package.json b/resolvers/node/package.json
index 76529084d..6f6999e6c 100644
--- a/resolvers/node/package.json
+++ b/resolvers/node/package.json
@@ -1,18 +1,20 @@
 {
   "name": "eslint-import-resolver-node",
-  "version": "0.3.2",
+  "version": "0.3.9",
   "description": "Node default behavior import resolution plugin for eslint-plugin-import.",
   "main": "index.js",
   "files": [
     "index.js"
   ],
   "scripts": {
-    "test": "nyc mocha",
-    "coveralls": "nyc report --reporter lcovonly && cd ../.. && coveralls < ./resolvers/node/coverage/lcov.info"
+    "prepublishOnly": "cp ../../{LICENSE,.npmrc} ./",
+    "tests-only": "nyc mocha",
+    "test": "npm run tests-only"
   },
   "repository": {
     "type": "git",
-    "url": "https://github.com/benmosher/eslint-plugin-import"
+    "url": "https://github.com/import-js/eslint-plugin-import",
+    "directory": "resolvers/node"
   },
   "keywords": [
     "eslint",
@@ -24,22 +26,17 @@
   "author": "Ben Mosher (me@benmosher.com)",
   "license": "MIT",
   "bugs": {
-    "url": "https://github.com/benmosher/eslint-plugin-import/issues"
+    "url": "https://github.com/import-js/eslint-plugin-import/issues"
   },
-  "homepage": "https://github.com/benmosher/eslint-plugin-import",
+  "homepage": "https://github.com/import-js/eslint-plugin-import",
   "dependencies": {
-    "debug": "^2.6.9",
-    "resolve": "^1.10.0"
+    "debug": "^3.2.7",
+    "is-core-module": "^2.13.0",
+    "resolve": "^1.22.4"
   },
   "devDependencies": {
     "chai": "^3.5.0",
-    "coveralls": "^3.0.0",
     "mocha": "^3.5.3",
-    "nyc": "^11.7.1"
-  },
-  "nyc": {
-    "exclude": [
-      "test/"
-    ]
+    "nyc": "^11.9.0"
   }
 }
diff --git a/resolvers/node/test/.eslintrc b/resolvers/node/test/.eslintrc
deleted file mode 100644
index 5a1ff85fa..000000000
--- a/resolvers/node/test/.eslintrc
+++ /dev/null
@@ -1,6 +0,0 @@
----
-env:
-  mocha: true
-  es6: false
-rules:
-  quotes: 0
diff --git a/resolvers/node/test/dot-node.node b/resolvers/node/test/dot-node.node
new file mode 100644
index 000000000..e69de29bb
diff --git a/resolvers/node/test/native.js b/resolvers/node/test/native.js
index 821229592..134f23bbc 100644
--- a/resolvers/node/test/native.js
+++ b/resolvers/node/test/native.js
@@ -1 +1 @@
-exports.natively = function () { return "but where do we feature?" }
\ No newline at end of file
+exports.natively = function () { return 'but where do we feature?'; };
diff --git a/resolvers/node/test/native.node b/resolvers/node/test/native.node
new file mode 100644
index 000000000..e69de29bb
diff --git a/resolvers/node/test/package-mains/jsnext/package.json b/resolvers/node/test/package-mains/jsnext/package.json
new file mode 100644
index 000000000..d336fedaf
--- /dev/null
+++ b/resolvers/node/test/package-mains/jsnext/package.json
@@ -0,0 +1,4 @@
+{
+  "main": "lib/index.js",
+  "jsnext:main": "src/index.js"
+}
diff --git a/resolvers/node/test/package-mains/jsnext/src/index.js b/resolvers/node/test/package-mains/jsnext/src/index.js
new file mode 100644
index 000000000..e69de29bb
diff --git a/resolvers/node/test/package-mains/module-and-jsnext/package.json b/resolvers/node/test/package-mains/module-and-jsnext/package.json
new file mode 100644
index 000000000..4b1355aae
--- /dev/null
+++ b/resolvers/node/test/package-mains/module-and-jsnext/package.json
@@ -0,0 +1,5 @@
+{
+  "main": "lib/index.js",
+  "module": "src/index.js",
+  "jsnext:main": "lib/index.js"
+}
diff --git a/resolvers/node/test/package-mains/module-and-jsnext/src/index.js b/resolvers/node/test/package-mains/module-and-jsnext/src/index.js
new file mode 100644
index 000000000..e69de29bb
diff --git a/resolvers/node/test/package-mains/module-broken/main.js b/resolvers/node/test/package-mains/module-broken/main.js
new file mode 100644
index 000000000..f053ebf79
--- /dev/null
+++ b/resolvers/node/test/package-mains/module-broken/main.js
@@ -0,0 +1 @@
+module.exports = {};
diff --git a/resolvers/node/test/package-mains/module-broken/package.json b/resolvers/node/test/package-mains/module-broken/package.json
new file mode 100644
index 000000000..36a318386
--- /dev/null
+++ b/resolvers/node/test/package-mains/module-broken/package.json
@@ -0,0 +1,4 @@
+{
+  "main": "./main.js",
+  "module": "./doesNotExist.js"
+}
diff --git a/resolvers/node/test/package-mains/module/package.json b/resolvers/node/test/package-mains/module/package.json
new file mode 100644
index 000000000..3a80819f8
--- /dev/null
+++ b/resolvers/node/test/package-mains/module/package.json
@@ -0,0 +1,4 @@
+{
+  "main": "lib/index.js",
+  "module": "src/index.js"
+}
diff --git a/resolvers/node/test/package-mains/module/src/index.js b/resolvers/node/test/package-mains/module/src/index.js
new file mode 100644
index 000000000..e69de29bb
diff --git a/resolvers/node/test/package-mains/package.json b/resolvers/node/test/package-mains/package.json
new file mode 100644
index 000000000..0a5fb6e23
--- /dev/null
+++ b/resolvers/node/test/package-mains/package.json
@@ -0,0 +1 @@
+{ "dummy": true }
\ No newline at end of file
diff --git a/resolvers/node/test/packageMains.js b/resolvers/node/test/packageMains.js
new file mode 100644
index 000000000..170b10e1a
--- /dev/null
+++ b/resolvers/node/test/packageMains.js
@@ -0,0 +1,31 @@
+'use strict';
+
+const chai = require('chai');
+const expect = chai.expect;
+const path = require('path');
+
+const resolver = require('../');
+
+const file = path.join(__dirname, 'package-mains', 'dummy.js');
+
+describe('packageMains', function () {
+  it('captures module', function () {
+    expect(resolver.resolve('./module', file)).property('path')
+      .to.equal(path.join(__dirname, 'package-mains', 'module', 'src', 'index.js'));
+  });
+
+  it('captures jsnext', function () {
+    expect(resolver.resolve('./jsnext', file)).property('path')
+      .to.equal(path.join(__dirname, 'package-mains', 'jsnext', 'src', 'index.js'));
+  });
+
+  it('captures module instead of jsnext', function () {
+    expect(resolver.resolve('./module-and-jsnext', file)).property('path')
+      .to.equal(path.join(__dirname, 'package-mains', 'module-and-jsnext', 'src', 'index.js'));
+  });
+
+  it('falls back from a missing "module" to "main"', function () {
+    expect(resolver.resolve('./module-broken', file)).property('path')
+      .to.equal(path.join(__dirname, 'package-mains', 'module-broken', 'main.js'));
+  });
+});
diff --git a/resolvers/node/test/paths.js b/resolvers/node/test/paths.js
index 2b4e7fd60..e6ffdafcd 100644
--- a/resolvers/node/test/paths.js
+++ b/resolvers/node/test/paths.js
@@ -1,49 +1,58 @@
-var expect = require('chai').expect
+const expect = require('chai').expect;
 
-var path = require('path')
-var node = require('../index.js')
+const path = require('path');
+const node = require('../index.js');
 
-describe("paths", function () {
-  it("handles base path relative to CWD", function () {
+describe('paths', function () {
+  it('handles base path relative to CWD', function () {
     expect(node.resolve('../', './test/file.js'))
       .to.have.property('path')
-      .equal(path.resolve(__dirname, '../index.js'))
-  })
-})
+      .equal(path.resolve(__dirname, '../index.js'));
+  });
+});
 
+describe('core', function () {
+  it('returns found, but null path, for core Node modules', function () {
+    const resolved = node.resolve('fs', './test/file.js');
+    expect(resolved).has.property('found', true);
+    expect(resolved).has.property('path', null);
+  });
+});
 
-describe("core", function () {
-  it("returns found, but null path, for core Node modules", function () {
-    var resolved = node.resolve('fs', "./test/file.js")
-    expect(resolved).has.property("found", true)
-    expect(resolved).has.property("path", null)
-  })
-})
+describe('default options', function () {
 
-
-describe("default options", function () {
-
-  it("finds .json files", function () {
+  it('finds .json files', function () {
     expect(node.resolve('./data', './test/file.js'))
       .to.have.property('path')
-      .equal(path.resolve(__dirname, './data.json'))
-  })
+      .equal(path.resolve(__dirname, './data.json'));
+  });
 
   it("ignores .json files if 'extensions' is redefined", function () {
     expect(node.resolve('./data', './test/file.js', { extensions: ['.js'] }))
-      .to.have.property('found', false)
-  })
+      .to.have.property('found', false);
+  });
 
-  it("finds mjs modules, with precedence over .js", function () {
+  it('finds mjs modules, with precedence over .js', function () {
     expect(node.resolve('./native', './test/file.js'))
       .to.have.property('path')
-      .equal(path.resolve(__dirname, './native.mjs'))
-  })
+      .equal(path.resolve(__dirname, './native.mjs'));
+  });
 
-  it("still finds .js if explicit", function () {
-    expect(node.resolve('./native.js', './test/file.js'))
+  it('finds .node modules, with lowest precedence', function () {
+    expect(node.resolve('./native.node', './test/file.js'))
       .to.have.property('path')
-      .equal(path.resolve(__dirname, './native.js'))
-  })
+      .equal(path.resolve(__dirname, './native.node'));
+  });
 
-})
+  it('finds .node modules', function () {
+    expect(node.resolve('./dot-node', './test/file.js'))
+      .to.have.property('path')
+      .equal(path.resolve(__dirname, './dot-node.node'));
+  });
+
+  it('still finds .js if explicit', function () {
+    expect(node.resolve('./native.js', './test/file.js'))
+      .to.have.property('path')
+      .equal(path.resolve(__dirname, './native.js'));
+  });
+});
diff --git a/resolvers/webpack/.npmrc b/resolvers/webpack/.npmrc
deleted file mode 120000
index cba44bb38..000000000
--- a/resolvers/webpack/.npmrc
+++ /dev/null
@@ -1 +0,0 @@
-../../.npmrc
\ No newline at end of file
diff --git a/resolvers/webpack/.nycrc b/resolvers/webpack/.nycrc
new file mode 100644
index 000000000..108436087
--- /dev/null
+++ b/resolvers/webpack/.nycrc
@@ -0,0 +1,15 @@
+{
+	"all": true,
+	"check-coverage": false,
+	"reporter": ["text-summary", "lcov", "text", "html", "json"],
+	"instrument": false,
+	"exclude": [
+		"coverage",
+		"test",
+		"tests",
+		"resolvers/*/test",
+		"scripts",
+		"memo-parser",
+		"lib"
+	]
+}
diff --git a/resolvers/webpack/CHANGELOG.md b/resolvers/webpack/CHANGELOG.md
index 204e0224a..d904a6082 100644
--- a/resolvers/webpack/CHANGELOG.md
+++ b/resolvers/webpack/CHANGELOG.md
@@ -1,168 +1,257 @@
 # 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
 
+## 0.13.10 - 2024-12-10
+- [new] add cache option ([#3100], thanks [@seiyab])
+
+## 0.13.9 - 2024-09-02
+- [refactor] simplify loop ([#3029], thanks [@fregante])
+- [meta] add `repository.directory` field
+- [refactor] avoid hoisting, misc cleanup
+
+## 0.13.8 - 2023-10-22
+ - [refactor] use `hasown` instead of `has`
+ - [deps] update `array.prototype.find`, `is-core-module`, `resolve`
+
+## 0.13.7 - 2023-08-19
+ - [fix] use the `dirname` of the `configPath` as `basedir` ([#2859])
+
+## 0.13.6 - 2023-08-16
+ - [refactor] revert back to `lodash/isEqual`
+
+## 0.13.5 - 2023-08-15
+ - [refactor] replace `lodash/isEqual` usage with `deep-equal`
+ - [refactor] remove `lodash/get` usage
+ - [refactor] switch to a maintained `array.prototype.find` package
+ - [deps] update `resolve`
+
+## 0.13.4 - 2023-08-08
+ - [fix] restore node 6 compatibility
+
+## 0.13.3 - 2023-08-05
+ - [deps] update `is-core-module`, `resolve`, `semver`
+ - [eslint] tighten up rules
+ - [Tests] consolidate eslint config
+ - [Docs] HTTP => HTTPS ([#2287], thanks [@Schweinepriester])
+
+## 0.13.2 - 2021-10-20
+
+### Changed
+ - [meta] expand `engines.node` to include node 17 ([#2268], thanks [@ljharb])
+
+## 0.13.1 - 2021-05-13
+
+### Added
+ - add support for webpack5 'externals function' ([#2023], thanks [@jet2jet])
+
+### Changed
+ - Add warning about async Webpack configs ([#1962], thanks [@ogonkov])
+ - Replace `node-libs-browser` with `is-core-module` ([#1967], thanks [@andersk])
+ - [meta] add "engines" field to document existing requirements
+ - [Refactor] use `is-regex` instead of `instanceof RegExp`
+ - [Refactor] use `Array.isArray` instead of `instanceof Array`
+ - [deps] update `debug`, `interpret`, `is-core-module`, `lodash`, `resolve`
+
+## 0.13.0 - 2020-09-27
+
+### Breaking
+ - [Breaking] Allow to resolve config path relative to working directory (#1276)
+
+## 0.12.2 - 2020-06-16
+
+### Fixed
+ - [fix] provide config fallback ([#1705], thanks [@migueloller])
+
+## 0.12.1 - 2020-01-10
+
+### Changed
+ - [meta] copy LICENSE file to all npm packages on prepublish ([#1595], thanks [@opichals])
+
+## 0.12.0 - 2019-12-07
+
+### Added
+ - [New] enable passing cwd as an option to `eslint-import-resolver-webpack` ([#1503], thanks [@Aghassi])
+
 ## 0.11.1 - 2019-04-13
 
 ### Fixed
-- [fix] match coreLibs after resolveSync in webpack-resolver ([#1297])
+- [fix] match coreLibs after resolveSync in webpack-resolver ([#1297], thanks [@echenley])
 
 ## 0.11.0 - 2018-01-22
 
 ### Added
-- support for `argv` parameter when config is a function. ([#1261], thanks [@keann])
+ - support for `argv` parameter when config is a function. ([#1261], thanks [@keann])
 
 ### Fixed
-- crash when webpack config is an array of functions ([#1219]/[#1220] by [@idudinov])
+ - crash when webpack config is an array of functions ([#1219]/[#1220] by [@idudinov])
 
 ## 0.10.1 - 2018-06-24
 ### Fixed
-- log a useful error in a module bug arises ([#768]/[#767], thanks [@mattkrick])
+ - log a useful error in a module bug arises ([#768]/[#767], thanks [@mattkrick])
 
 ## 0.10.0 - 2018-05-17
 ### Changed
-- cache webpack resolve function, for performance ([#788]/[#1091])
+ - cache webpack resolve function, for performance ([#788]/[#1091])
 
 ## 0.9.0 - 2018-03-29
 ### Breaking
-- Fix with `pnpm` by bumping `resolve` ([#968])
+ - Fix with `pnpm` by bumping `resolve` ([#968])
 
 ## 0.8.4 - 2018-01-05
 ### Changed
-- allow newer version of node-libs-browser ([#969])
+ - allow newer version of node-libs-browser ([#969])
 
 ## 0.8.3 - 2017-06-23
 ### Changed
-- `debug` bumped to match others
+ - `debug` bumped to match others
 
 ## 0.8.2 - 2017-06-22
 ### Changed
-- `webpack` peer dep updated to >= 1.11 (works fine with webpack 3 AFAICT)
+ - `webpack` peer dep updated to >= 1.11 (works fine with webpack 3 AFAICT)
 
 ## 0.8.1 - 2017-01-19
 ### Changed
-- official support for Webpack 2.2.0 (RC), thanks [@graingert]
+ - official support for Webpack 2.2.0 (RC), thanks [@graingert]
 
 ## 0.8.0 - 2016-12-15
 ### Changed
-- bumped `resolve` to fix issues with Node builtins (thanks [@SkeLLLa] and [@ljharb])
-- allow `enhanced-resolve` to be version `>= 2` (thanks [@Kovensky])
+ - bumped `resolve` to fix issues with Node builtins (thanks [@SkeLLLa] and [@ljharb])
+ - allow `enhanced-resolve` to be version `>= 2` (thanks [@Kovensky])
 
 ## 0.7.1
 ### Fixed
-- missing `has` dependency ([#681] + [#683], thanks [@benmvp] + [@ljharb])
+ - missing `has` dependency ([#681] + [#683], thanks [@benmvp] + [@ljharb])
 
 ## 0.7.0
 ### Added
-- Support for explicit Webpack config object in `.eslintrc.*`. ([#572], thanks [@jameslnewell])
-- Added `resolve.modules` to configs for webpack2 support ([#569], thanks [@toshafed])
+ - Support for explicit Webpack config object in `.eslintrc.*`. ([#572], thanks [@jameslnewell])
+ - Added `resolve.modules` to configs for webpack2 support ([#569], thanks [@toshafed])
 
 ## 0.6.0 - 2016-09-13
 ### Added
-- support for config-as-function ([#533], thanks [@grahamb])
+ - support for config-as-function ([#533], thanks [@grahamb])
 
 ## 0.5.1 - 2016-08-11
 ### Fixed
-- don't throw and die if no webpack config is found
+ - don't throw and die if no webpack config is found
 
 ## 0.5.0 - 2016-08-11
 ### Added
-- support for Webpack 2 + `module` package.json key! ([#475], thanks [@taion])
+ - support for Webpack 2 + `module` package.json key! ([#475], thanks [@taion])
 
 ### Changed
-- don't swallow errors, assume config exists ([#435], thanks [@Kovensky])
+ - don't swallow errors, assume config exists ([#435], thanks [@Kovensky])
 
 ## 0.4.0 - 2016-07-17
 ### Added
-- support for `webpack.ResolverPlugin` ([#377], thanks [@Rogeres])
+ - support for `webpack.ResolverPlugin` ([#377], thanks [@Rogeres])
 
 ### Fixed
-- provide string `context` to `externals` functions ([#411] + [#413], thanks [@Satyam])
+ - provide string `context` to `externals` functions ([#411] + [#413], thanks [@Satyam])
 
 ## 0.3.2 - 2016-06-30
 ### Added
-- shared config ([config.js](./config.js)) with barebones settings needed to use this resolver. ([#283])
+ - shared config ([config.js](./config.js)) with barebones settings needed to use this resolver. ([#283])
 
 ### Fixed
-- strip resource query ([#357], thanks [@daltones])
-- allow `externals` to be defined as a function ([#363], thanks [@kesne])
+ - strip resource query ([#357], thanks [@daltones])
+ - allow `externals` to be defined as a function ([#363], thanks [@kesne])
 
 ## 0.3.1 - 2016-06-02
 ### Added
-- debug logging. run with `DEBUG=eslint-plugin-import:*` to see log output.
+ - debug logging. run with `DEBUG=eslint-plugin-import:*` to see log output.
 
 ## 0.3.0 - 2016-06-01
 ### Changed
-- use `enhanced-resolve` to support additional plugins instead of re-implementing
-  aliases, etc.
+ - use `enhanced-resolve` to support additional plugins instead of re-implementing aliases, etc.
 
 ## 0.2.5 - 2016-05-23
 ### Added
-- Added support for multiple webpack configs ([#181], thanks [@GreenGremlin])
+ - Added support for multiple webpack configs ([#181], thanks [@GreenGremlin])
 
 ## 0.2.4 - 2016-04-29
 ### Changed
-- automatically find webpack config with `interpret`-able extensions ([#287], thanks [@taion])
+ - automatically find webpack config with `interpret`-able extensions ([#287], thanks [@taion])
 
 ## 0.2.3 - 2016-04-28
 ### Fixed
-- `interpret` dependency was declared in the wrong `package.json`.
-   Thanks [@jonboiser] for sleuthing ([#286]) and fixing ([#289]).
+ - `interpret` dependency was declared in the wrong `package.json`. Thanks [@jonboiser] for sleuthing ([#286]) and fixing ([#289]).
 
 ## 0.2.2 - 2016-04-27
 ### Added
-- `interpret` configs (such as `.babel.js`).
-  Thanks to [@gausie] for the initial PR ([#164], ages ago! 😅) and [@jquense] for tests ([#278]).
-
-[#1297]: https://github.com/benmosher/eslint-plugin-import/pull/1297
-[#1261]: https://github.com/benmosher/eslint-plugin-import/pull/1261
-[#1220]: https://github.com/benmosher/eslint-plugin-import/pull/1220
-[#1091]: https://github.com/benmosher/eslint-plugin-import/pull/1091
-[#969]: https://github.com/benmosher/eslint-plugin-import/pull/969
-[#968]: https://github.com/benmosher/eslint-plugin-import/pull/968
-[#768]: https://github.com/benmosher/eslint-plugin-import/pull/768
-[#683]: https://github.com/benmosher/eslint-plugin-import/pull/683
-[#572]: https://github.com/benmosher/eslint-plugin-import/pull/572
-[#569]: https://github.com/benmosher/eslint-plugin-import/pull/569
-[#533]: https://github.com/benmosher/eslint-plugin-import/pull/533
-[#413]: https://github.com/benmosher/eslint-plugin-import/pull/413
-[#377]: https://github.com/benmosher/eslint-plugin-import/pull/377
-[#363]: https://github.com/benmosher/eslint-plugin-import/pull/363
-[#289]: https://github.com/benmosher/eslint-plugin-import/pull/289
-[#287]: https://github.com/benmosher/eslint-plugin-import/pull/287
-[#278]: https://github.com/benmosher/eslint-plugin-import/pull/278
-[#181]: https://github.com/benmosher/eslint-plugin-import/pull/181
-[#164]: https://github.com/benmosher/eslint-plugin-import/pull/164
-
-[#1219]: https://github.com/benmosher/eslint-plugin-import/issues/1219
-[#788]: https://github.com/benmosher/eslint-plugin-import/issues/788
-[#767]: https://github.com/benmosher/eslint-plugin-import/issues/767
-[#681]: https://github.com/benmosher/eslint-plugin-import/issues/681
-[#435]: https://github.com/benmosher/eslint-plugin-import/issues/435
-[#411]: https://github.com/benmosher/eslint-plugin-import/issues/411
-[#357]: https://github.com/benmosher/eslint-plugin-import/issues/357
-[#286]: https://github.com/benmosher/eslint-plugin-import/issues/286
-[#283]: https://github.com/benmosher/eslint-plugin-import/issues/283
-
+ - `interpret` configs (such as `.babel.js`). Thanks to [@gausie] for the initial PR ([#164], ages ago! 😅) and [@jquense] for tests ([#278]).
+
+[#3100]: https://github.com/import-js/eslint-plugin-import/pull/3100
+[#3029]: https://github.com/import-js/eslint-plugin-import/pull/3029
+[#2287]: https://github.com/import-js/eslint-plugin-import/pull/2287
+[#2023]: https://github.com/import-js/eslint-plugin-import/pull/2023
+[#1967]: https://github.com/import-js/eslint-plugin-import/pull/1967
+[#1962]: https://github.com/import-js/eslint-plugin-import/pull/1962
+[#1705]: https://github.com/import-js/eslint-plugin-import/pull/1705
+[#1595]: https://github.com/import-js/eslint-plugin-import/pull/1595
+[#1503]: https://github.com/import-js/eslint-plugin-import/pull/1503
+[#1297]: https://github.com/import-js/eslint-plugin-import/pull/1297
+[#1261]: https://github.com/import-js/eslint-plugin-import/pull/1261
+[#1220]: https://github.com/import-js/eslint-plugin-import/pull/1220
+[#1091]: https://github.com/import-js/eslint-plugin-import/pull/1091
+[#969]: https://github.com/import-js/eslint-plugin-import/pull/969
+[#968]: https://github.com/import-js/eslint-plugin-import/pull/968
+[#768]: https://github.com/import-js/eslint-plugin-import/pull/768
+[#683]: https://github.com/import-js/eslint-plugin-import/pull/683
+[#572]: https://github.com/import-js/eslint-plugin-import/pull/572
+[#569]: https://github.com/import-js/eslint-plugin-import/pull/569
+[#533]: https://github.com/import-js/eslint-plugin-import/pull/533
+[#413]: https://github.com/import-js/eslint-plugin-import/pull/413
+[#377]: https://github.com/import-js/eslint-plugin-import/pull/377
+[#363]: https://github.com/import-js/eslint-plugin-import/pull/363
+[#289]: https://github.com/import-js/eslint-plugin-import/pull/289
+[#287]: https://github.com/import-js/eslint-plugin-import/pull/287
+[#278]: https://github.com/import-js/eslint-plugin-import/pull/278
+[#181]: https://github.com/import-js/eslint-plugin-import/pull/181
+[#164]: https://github.com/import-js/eslint-plugin-import/pull/164
+
+[#2859]: https://github.com/import-js/eslint-plugin-import/issues/2859
+[#2268]: https://github.com/import-js/eslint-plugin-import/issues/2268
+[#1219]: https://github.com/import-js/eslint-plugin-import/issues/1219
+[#788]: https://github.com/import-js/eslint-plugin-import/issues/788
+[#767]: https://github.com/import-js/eslint-plugin-import/issues/767
+[#681]: https://github.com/import-js/eslint-plugin-import/issues/681
+[#435]: https://github.com/import-js/eslint-plugin-import/issues/435
+[#411]: https://github.com/import-js/eslint-plugin-import/issues/411
+[#357]: https://github.com/import-js/eslint-plugin-import/issues/357
+[#286]: https://github.com/import-js/eslint-plugin-import/issues/286
+[#283]: https://github.com/import-js/eslint-plugin-import/issues/283
+
+[@Aghassi]: https://github.com/Aghassi
+[@andersk]: https://github.com/andersk
+[@benmvp]: https://github.com/benmvp
+[@daltones]: https://github.com/daltones
+[@echenley]: https://github.com/echenley
+[@fregante]: https://github.com/fregante
 [@gausie]: https://github.com/gausie
-[@jquense]: https://github.com/jquense
-[@taion]: https://github.com/taion
+[@grahamb]: https://github.com/grahamb
+[@graingert]: https://github.com/graingert
 [@GreenGremlin]: https://github.com/GreenGremlin
-[@daltones]: https://github.com/daltones
+[@idudinov]: https://github.com/idudinov
+[@jameslnewell]: https://github.com/jameslnewell
+[@jet2jet]: https://github.com/jet2jet
+[@jquense]: https://github.com/jquense
+[@keann]: https://github.com/keann
 [@kesne]: https://github.com/kesne
-[@Satyam]: https://github.com/Satyam
-[@Rogeres]: https://github.com/Rogeres
 [@Kovensky]: https://github.com/Kovensky
-[@grahamb]: https://github.com/grahamb
-[@jameslnewell]: https://github.com/jameslnewell
-[@toshafed]: https://github.com/toshafed
-[@benmvp]: https://github.com/benmvp
 [@ljharb]: https://github.com/ljharb
-[@SkeLLLa]: https://github.com/SkeLLLa
-[@graingert]: https://github.com/graingert
 [@mattkrick]: https://github.com/mattkrick
-[@idudinov]: https://github.com/idudinov
-[@keann]: https://github.com/keann
+[@migueloller]: https://github.com/migueloller
+[@ogonkov]: https://github.com/ogonkov
+[@opichals]: https://github.com/opichals
+[@Rogeres]: https://github.com/Rogeres
+[@Satyam]: https://github.com/Satyam
+[@Schweinepriester]: https://github.com/Schweinepriester
+[@seiyab]: https://github.com/seiyab
+[@SkeLLLa]: https://github.com/SkeLLLa
+[@taion]: https://github.com/taion
+[@toshafed]: https://github.com/toshafed
diff --git a/resolvers/webpack/README.md b/resolvers/webpack/README.md
index a9869aec4..9b0139689 100644
--- a/resolvers/webpack/README.md
+++ b/resolvers/webpack/README.md
@@ -4,18 +4,22 @@
 
 Webpack-literate module resolution plugin for [`eslint-plugin-import`](https://www.npmjs.com/package/eslint-plugin-import).
 
+> :boom: Only "synchronous" Webpack configs are supported at the moment.
+> If your config returns a `Promise`, this will cause problems.
+> Consider splitting your asynchronous configuration to a separate config.
+
 Published separately to allow pegging to a specific version in case of breaking
 changes.
 
 To use with `eslint-plugin-import`, run:
 
-```
+```sh
 npm i eslint-import-resolver-webpack -g
 ```
 
 or if you manage ESLint as a dev dependency:
 
-```
+```sh
 # inside your project's working tree
 npm install eslint-import-resolver-webpack --save-dev
 ```
@@ -42,7 +46,7 @@ settings:
       config: 'webpack.dev.config.js'
 ```
 
-or with explicit config file name:
+or with explicit config file index:
 
 ```yaml
 ---
@@ -53,6 +57,16 @@ settings:
       config-index: 1   # take the config at index 1
 ```
 
+or with explicit config file path relative to your projects's working directory:
+
+```yaml
+---
+settings:
+  import/resolver:
+    webpack:
+      config: './configs/webpack.dev.config.js'
+```
+
 or with explicit config object:
 
 ```yaml
@@ -79,3 +93,18 @@ settings:
         NODE_ENV: 'local'
         production: true
 ```
+
+If your config is set as a function, it will be evaluated at every resolution. You have an option to prevent this by caching it using the `cache` parameter:
+
+```yaml
+---
+settings:
+  import/resolver:
+    webpack:
+      config: 'webpack.config.js'
+      cache: true
+```
+
+## Support
+
+[Get supported eslint-import-resolver-webpack with the Tidelift Subscription](https://tidelift.com/subscription/pkg/npm-eslint-import-resolver-webpack?utm_source=npm-eslint-import-resolver-webpack&utm_medium=referral&utm_campaign=readme)
diff --git a/resolvers/webpack/config.js b/resolvers/webpack/config.js
index d15f082da..894787d1f 100644
--- a/resolvers/webpack/config.js
+++ b/resolvers/webpack/config.js
@@ -5,4 +5,4 @@ module.exports = {
   settings: {
     'import/resolver': 'webpack',
   },
-}
+};
diff --git a/resolvers/webpack/index.js b/resolvers/webpack/index.js
index 0f75a2840..ae736abe7 100644
--- a/resolvers/webpack/index.js
+++ b/resolvers/webpack/index.js
@@ -1,190 +1,143 @@
-var findRoot = require('find-root')
-  , path = require('path')
-  , get = require('lodash/get')
-  , isEqual = require('lodash/isEqual')
-  , find = require('array-find')
-  , interpret = require('interpret')
-  , fs = require('fs')
-  , coreLibs = require('node-libs-browser')
-  , resolve = require('resolve')
-  , semver = require('semver')
-  , has = require('has')
-
-var log = require('debug')('eslint-plugin-import:resolver:webpack')
-
-exports.interfaceVersion = 2
+'use strict';
+
+const findRoot = require('find-root');
+const path = require('path');
+const isEqual = require('lodash/isEqual');
+const interpret = require('interpret');
+const existsSync = require('fs').existsSync;
+const isCore = require('is-core-module');
+const resolve = require('resolve/sync');
+const semver = require('semver');
+const hasOwn = require('hasown');
+const isRegex = require('is-regex');
+const isArray = Array.isArray;
+const keys = Object.keys;
+const assign = Object.assign;
+
+const log = require('debug')('eslint-plugin-import:resolver:webpack');
+
+exports.interfaceVersion = 2;
 
-/**
- * Find the full path to 'source', given 'file' as a full reference path.
- *
- * resolveImport('./foo', '/Users/ben/bar.js') => '/Users/ben/foo.js'
- * @param  {string} source - the module to resolve; i.e './some-module'
- * @param  {string} file - the importing file's full path; i.e. '/usr/local/bin/file.js'
- * TODO: take options as a third param, with webpack config file name
- * @return {string?} the resolved path to source, undefined if not resolved, or null
- *                   if resolved to a non-FS resource (i.e. script tag at page load)
- */
-exports.resolve = function (source, file, settings) {
-
-  // strip loaders
-  var finalBang = source.lastIndexOf('!')
-  if (finalBang >= 0) {
-    source = source.slice(finalBang + 1)
-  }
-
-  // strip resource query
-  var finalQuestionMark = source.lastIndexOf('?')
-  if (finalQuestionMark >= 0) {
-    source = source.slice(0, finalQuestionMark)
-  }
-
-  var webpackConfig
-
-  var configPath = get(settings, 'config')
-    , configIndex = get(settings, 'config-index')
-    , env = get(settings, 'env')
-    , argv = get(settings, 'argv', {})
-    , packageDir
-
-  log('Config path from settings:', configPath)
-
-  // see if we've got a config path, a config object, an array of config objects or a config function
-  if (!configPath || typeof configPath === 'string') {
-
-      // see if we've got an absolute path
-      if (!configPath || !path.isAbsolute(configPath)) {
-        // if not, find ancestral package.json and use its directory as base for the path
-        packageDir = findRoot(path.resolve(file))
-        if (!packageDir) throw new Error('package not found above ' + file)
-      }
-
-      configPath = findConfigPath(configPath, packageDir)
-
-      log('Config path resolved to:', configPath)
-      if (configPath) {
+function registerCompiler(moduleDescriptor) {
+  if (moduleDescriptor) {
+    if (typeof moduleDescriptor === 'string') {
+      require(moduleDescriptor);
+    } else if (!isArray(moduleDescriptor)) {
+      moduleDescriptor.register(require(moduleDescriptor.module));
+    } else {
+      for (let i = 0; i < moduleDescriptor.length; i++) {
         try {
-          webpackConfig = require(configPath)
-        } catch(e) {
-          console.log('Error resolving webpackConfig', e)
-          throw e
+          registerCompiler(moduleDescriptor[i]);
+          break;
+        } catch (e) {
+          log('Failed to register compiler for moduleDescriptor[]:', i, moduleDescriptor);
         }
-      } else {
-        log('No config path found relative to', file, '; using {}')
-        webpackConfig = {}
       }
-
-      if (webpackConfig && webpackConfig.default) {
-        log('Using ES6 module "default" key instead of module.exports.')
-        webpackConfig = webpackConfig.default
-      }
-
-  } else {
-    webpackConfig = configPath
-    configPath = null
+    }
   }
+}
 
-  if (typeof webpackConfig === 'function') {
-    webpackConfig = webpackConfig(env, argv)
-  }
+function findConfigPath(configPath, packageDir) {
+  const extensions = keys(interpret.extensions).sort(function (a, b) {
+    return a === '.js' ? -1 : b === '.js' ? 1 : a.length - b.length;
+  });
+  let extension;
 
-  if (Array.isArray(webpackConfig)) {
-    webpackConfig = webpackConfig.map(cfg => {
-      if (typeof cfg === 'function') {
-        return cfg(env, argv)
+  if (configPath) {
+    for (let i = extensions.length - 1; i >= 0 && !extension; i--) {
+      const maybeExtension = extensions[i];
+      if (configPath.slice(-maybeExtension.length) === maybeExtension) {
+        extension = maybeExtension;
       }
+    }
 
-      return cfg
-    })
-
-    if (typeof configIndex !== 'undefined' && webpackConfig.length > configIndex) {
-      webpackConfig = webpackConfig[configIndex]
+    // see if we've got an absolute path
+    if (!path.isAbsolute(configPath)) {
+      configPath = path.join(packageDir, configPath);
     }
-    else {
-      webpackConfig = find(webpackConfig, function findFirstWithResolve(config) {
-        return !!config.resolve
-      })
+  } else {
+    for (let i = 0; i < extensions.length && !extension; i++) {
+      const maybeExtension = extensions[i];
+      const maybePath = path.resolve(
+        path.join(packageDir, 'webpack.config' + maybeExtension)
+      );
+      if (existsSync(maybePath)) {
+        configPath = maybePath;
+        extension = maybeExtension;
+      }
     }
   }
 
-  log('Using config: ', webpackConfig)
-
-  // externals
-  if (findExternal(source, webpackConfig.externals, path.dirname(file))) {
-    return { found: true, path: null }
-  }
-
-  // otherwise, resolve "normally"
-  var resolveSync = getResolveSync(configPath, webpackConfig)
-
-  try {
-    return { found: true, path: resolveSync(path.dirname(file), source) }
-  } catch (err) {
-    if (source in coreLibs) {
-      return { found: true, path: coreLibs[source] }
-    }
-
-    log('Error during module resolution:', err)
-    return { found: false }
-  }
+  registerCompiler(interpret.extensions[extension]);
+  return configPath;
 }
 
-var MAX_CACHE = 10
-var _cache = []
-function getResolveSync(configPath, webpackConfig) {
-  var cacheKey = { configPath: configPath, webpackConfig: webpackConfig }
-  var cached = find(_cache, function (entry) { return isEqual(entry.key, cacheKey) })
-  if (!cached) {
-    cached = {
-      key: cacheKey,
-      value: createResolveSync(configPath, webpackConfig),
-    }
-    // put in front and pop last item
-    if (_cache.unshift(cached) > MAX_CACHE) {
-      _cache.pop()
-    }
-  }
-  return cached.value
-}
+function findExternal(source, externals, context, resolveSync) {
+  if (!externals) { return false; }
 
-function createResolveSync(configPath, webpackConfig) {
-  var webpackRequire
-    , basedir = null
+  // string match
+  if (typeof externals === 'string') { return source === externals; }
 
-  if (typeof configPath === 'string') {
-    basedir = path.dirname(configPath)
+  // array: recurse
+  if (isArray(externals)) {
+    return externals.some(function (e) { return findExternal(source, e, context, resolveSync); });
   }
 
-  try {
-    var webpackFilename = resolve.sync('webpack', { basedir, preserveSymlinks: false })
-    var webpackResolveOpts = { basedir: path.dirname(webpackFilename), preserveSymlinks: false }
+  if (isRegex(externals)) {
+    return externals.test(source);
+  }
 
-    webpackRequire = function (id) {
-      return require(resolve.sync(id, webpackResolveOpts))
+  if (typeof externals === 'function') {
+    let functionExternalFound = false;
+    const callback = function (err, value) {
+      if (err) {
+        functionExternalFound = false;
+      } else {
+        functionExternalFound = findExternal(source, value, context, resolveSync);
+      }
+    };
+    // - for prior webpack 5, 'externals function' uses 3 arguments
+    // - for webpack 5, the count of arguments is less than 3
+    if (externals.length === 3) {
+      externals.call(null, context, source, callback);
+    } else {
+      const ctx = {
+        context,
+        request: source,
+        contextInfo: {
+          issuer: '',
+          issuerLayer: null,
+          compiler: '',
+        },
+        getResolve: () => (resolveContext, requestToResolve, cb) => {
+          if (cb) {
+            try {
+              cb(null, resolveSync(resolveContext, requestToResolve));
+            } catch (e) {
+              cb(e);
+            }
+          } else {
+            log('getResolve without callback not supported');
+            return Promise.reject(new Error('Not supported'));
+          }
+        },
+      };
+      const result = externals.call(null, ctx, callback);
+      // todo handling Promise object (using synchronous-promise package?)
+      if (result && typeof result.then === 'function') {
+        log('Asynchronous functions for externals not supported');
+      }
     }
-  } catch (e) {
-    // Something has gone wrong (or we're in a test). Use our own bundled
-    // enhanced-resolve.
-    log('Using bundled enhanced-resolve.')
-    webpackRequire = require
+    return functionExternalFound;
   }
 
-  var enhancedResolvePackage = webpackRequire('enhanced-resolve/package.json')
-  var enhancedResolveVersion = enhancedResolvePackage.version
-  log('enhanced-resolve version:', enhancedResolveVersion)
-
-  var resolveConfig = webpackConfig.resolve || {}
-
-  if (semver.major(enhancedResolveVersion) >= 2) {
-    return createWebpack2ResolveSync(webpackRequire, resolveConfig)
+  // else, vanilla object
+  for (const key in externals) {
+    if (hasOwn(externals, key) && source === key) {
+      return true;
+    }
   }
-
-  return createWebpack1ResolveSync(webpackRequire, resolveConfig, webpackConfig.plugins)
-}
-
-function createWebpack2ResolveSync(webpackRequire, resolveConfig) {
-  var EnhancedResolve = webpackRequire('enhanced-resolve')
-
-  return EnhancedResolve.create.sync(Object.assign({}, webpack2DefaultResolveConfig, resolveConfig))
+  return false;
 }
 
 /**
@@ -192,41 +145,73 @@ function createWebpack2ResolveSync(webpackRequire, resolveConfig) {
  * https://github.com/webpack/webpack/blob/v2.1.0-beta.20/lib/WebpackOptionsDefaulter.js#L72-L87
  * @type {Object}
  */
-var webpack2DefaultResolveConfig = {
+const webpack2DefaultResolveConfig = {
   unsafeCache: true, // Probably a no-op, since how can we cache anything at all here?
   modules: ['node_modules'],
   extensions: ['.js', '.json'],
   aliasFields: ['browser'],
   mainFields: ['browser', 'module', 'main'],
+};
+
+function createWebpack2ResolveSync(webpackRequire, resolveConfig) {
+  const EnhancedResolve = webpackRequire('enhanced-resolve');
+
+  return EnhancedResolve.create.sync(assign({}, webpack2DefaultResolveConfig, resolveConfig));
 }
 
+/**
+ * webpack 1 defaults: https://webpack.github.io/docs/configuration.html#resolve-packagemains
+ * @type {string[]}
+ */
+const webpack1DefaultMains = [
+  'webpack',
+  'browser',
+  'web',
+  'browserify',
+  ['jam', 'main'],
+  'main',
+];
+
+/* eslint-disable */
+// from https://github.com/webpack/webpack/blob/v1.13.0/lib/WebpackOptionsApply.js#L365
+function makeRootPlugin(ModulesInRootPlugin, name, root) {
+  if (typeof root === 'string') {
+    return new ModulesInRootPlugin(name, root);
+  }
+  if (isArray(root)) {
+    return function () {
+      root.forEach(function (root) {
+        this.apply(new ModulesInRootPlugin(name, root));
+      }, this);
+    };
+  }
+  return function () {};
+}
+/* eslint-enable */
+
 // adapted from tests &
 // https://github.com/webpack/webpack/blob/v1.13.0/lib/WebpackOptionsApply.js#L322
 function createWebpack1ResolveSync(webpackRequire, resolveConfig, plugins) {
-  var Resolver = webpackRequire('enhanced-resolve/lib/Resolver')
-  var SyncNodeJsInputFileSystem = webpackRequire('enhanced-resolve/lib/SyncNodeJsInputFileSystem')
-
-  var ModuleAliasPlugin = webpackRequire('enhanced-resolve/lib/ModuleAliasPlugin')
-  var ModulesInDirectoriesPlugin =
-    webpackRequire('enhanced-resolve/lib/ModulesInDirectoriesPlugin')
-  var ModulesInRootPlugin = webpackRequire('enhanced-resolve/lib/ModulesInRootPlugin')
-  var ModuleAsFilePlugin = webpackRequire('enhanced-resolve/lib/ModuleAsFilePlugin')
-  var ModuleAsDirectoryPlugin = webpackRequire('enhanced-resolve/lib/ModuleAsDirectoryPlugin')
-  var DirectoryDescriptionFilePlugin =
-    webpackRequire('enhanced-resolve/lib/DirectoryDescriptionFilePlugin')
-  var DirectoryDefaultFilePlugin =
-    webpackRequire('enhanced-resolve/lib/DirectoryDefaultFilePlugin')
-  var FileAppendPlugin = webpackRequire('enhanced-resolve/lib/FileAppendPlugin')
-  var ResultSymlinkPlugin = webpackRequire('enhanced-resolve/lib/ResultSymlinkPlugin')
-  var DirectoryDescriptionFileFieldAliasPlugin =
-    webpackRequire('enhanced-resolve/lib/DirectoryDescriptionFileFieldAliasPlugin')
-
-  var resolver = new Resolver(new SyncNodeJsInputFileSystem())
+  const Resolver = webpackRequire('enhanced-resolve/lib/Resolver');
+  const SyncNodeJsInputFileSystem = webpackRequire('enhanced-resolve/lib/SyncNodeJsInputFileSystem');
+
+  const ModuleAliasPlugin = webpackRequire('enhanced-resolve/lib/ModuleAliasPlugin');
+  const ModulesInDirectoriesPlugin = webpackRequire('enhanced-resolve/lib/ModulesInDirectoriesPlugin');
+  const ModulesInRootPlugin = webpackRequire('enhanced-resolve/lib/ModulesInRootPlugin');
+  const ModuleAsFilePlugin = webpackRequire('enhanced-resolve/lib/ModuleAsFilePlugin');
+  const ModuleAsDirectoryPlugin = webpackRequire('enhanced-resolve/lib/ModuleAsDirectoryPlugin');
+  const DirectoryDescriptionFilePlugin = webpackRequire('enhanced-resolve/lib/DirectoryDescriptionFilePlugin');
+  const DirectoryDefaultFilePlugin = webpackRequire('enhanced-resolve/lib/DirectoryDefaultFilePlugin');
+  const FileAppendPlugin = webpackRequire('enhanced-resolve/lib/FileAppendPlugin');
+  const ResultSymlinkPlugin = webpackRequire('enhanced-resolve/lib/ResultSymlinkPlugin');
+  const DirectoryDescriptionFileFieldAliasPlugin = webpackRequire('enhanced-resolve/lib/DirectoryDescriptionFileFieldAliasPlugin');
+
+  const resolver = new Resolver(new SyncNodeJsInputFileSystem());
 
   resolver.apply(
     resolveConfig.packageAlias
       ? new DirectoryDescriptionFileFieldAliasPlugin('package.json', resolveConfig.packageAlias)
-      : function() {},
+      : function () {},
     new ModuleAliasPlugin(resolveConfig.alias || {}),
     makeRootPlugin(ModulesInRootPlugin, 'module', resolveConfig.root),
     new ModulesInDirectoriesPlugin(
@@ -243,149 +228,250 @@ function createWebpack1ResolveSync(webpackRequire, resolveConfig, plugins) {
     new DirectoryDefaultFilePlugin(['index']),
     new FileAppendPlugin(resolveConfig.extensions || ['', '.webpack.js', '.web.js', '.js']),
     new ResultSymlinkPlugin()
-  )
-
+  );
 
-  var resolvePlugins = []
+  const resolvePlugins = [];
 
   // support webpack.ResolverPlugin
   if (plugins) {
     plugins.forEach(function (plugin) {
       if (
-        plugin.constructor &&
-        plugin.constructor.name === 'ResolverPlugin' &&
-        Array.isArray(plugin.plugins)
+        plugin.constructor
+        && plugin.constructor.name === 'ResolverPlugin'
+        && isArray(plugin.plugins)
       ) {
-        resolvePlugins.push.apply(resolvePlugins, plugin.plugins)
+        resolvePlugins.push.apply(resolvePlugins, plugin.plugins);
       }
-    })
+    });
   }
 
-  resolver.apply.apply(resolver, resolvePlugins)
+  resolver.apply.apply(resolver, resolvePlugins);
 
-  return function() {
-    return resolver.resolveSync.apply(resolver, arguments)
-  }
+  return function () {
+    return resolver.resolveSync.apply(resolver, arguments);
+  };
 }
 
-/* eslint-disable */
-// from https://github.com/webpack/webpack/blob/v1.13.0/lib/WebpackOptionsApply.js#L365
-function makeRootPlugin(ModulesInRootPlugin, name, root) {
-  if(typeof root === "string")
-    return new ModulesInRootPlugin(name, root);
-  else if(Array.isArray(root)) {
-    return function() {
-      root.forEach(function(root) {
-        this.apply(new ModulesInRootPlugin(name, root));
-      }, this);
+function createResolveSync(configPath, webpackConfig, cwd) {
+  let webpackRequire;
+  let basedir = null;
+
+  if (typeof configPath === 'string') {
+    // This can be changed via the settings passed in when defining the resolver
+    basedir = cwd || path.dirname(configPath);
+    log(`Attempting to load webpack path from ${basedir}`);
+  }
+
+  try {
+    // Attempt to resolve webpack from the given `basedir`
+    const webpackFilename = resolve('webpack', { basedir, preserveSymlinks: false });
+    const webpackResolveOpts = { basedir: path.dirname(webpackFilename), preserveSymlinks: false };
+
+    webpackRequire = function (id) {
+      return require(resolve(id, webpackResolveOpts));
     };
+  } catch (e) {
+    // Something has gone wrong (or we're in a test). Use our own bundled
+    // enhanced-resolve.
+    log('Using bundled enhanced-resolve.');
+    webpackRequire = require;
   }
-  return function() {};
-}
-/* eslint-enable */
 
-function findExternal(source, externals, context) {
-  if (!externals) return false
+  const enhancedResolvePackage = webpackRequire('enhanced-resolve/package.json');
+  const enhancedResolveVersion = enhancedResolvePackage.version;
+  log('enhanced-resolve version:', enhancedResolveVersion);
 
-  // string match
-  if (typeof externals === 'string') return (source === externals)
+  const resolveConfig = webpackConfig.resolve || {};
 
-  // array: recurse
-  if (externals instanceof Array) {
-    return externals.some(function (e) { return findExternal(source, e, context) })
+  if (semver.major(enhancedResolveVersion) >= 2) {
+    return createWebpack2ResolveSync(webpackRequire, resolveConfig);
   }
 
-  if (externals instanceof RegExp) {
-    return externals.test(source)
+  return createWebpack1ResolveSync(webpackRequire, resolveConfig, webpackConfig.plugins);
+}
+
+const MAX_CACHE = 10;
+const _cache = [];
+function getResolveSync(configPath, webpackConfig, cwd) {
+  const cacheKey = { configPath, webpackConfig };
+  for (let i = 0; i < _cache.length; i++) {
+    if (isEqual(_cache[i].key, cacheKey)) {
+      return _cache[i].value;
+    }
   }
 
-  if (typeof externals === 'function') {
-    var functionExternalFound = false
-    externals.call(null, context, source, function(err, value) {
-      if (err) {
-        functionExternalFound = false
-      } else {
-        functionExternalFound = findExternal(source, value, context)
-      }
-    })
-    return functionExternalFound
+  const cached = {
+    key: cacheKey,
+    value: createResolveSync(configPath, webpackConfig, cwd),
+  };
+  // put in front and pop last item
+  if (_cache.unshift(cached) > MAX_CACHE) {
+    _cache.pop();
+  }
+  return cached.value;
+}
+
+const _evalCache = new Map();
+function evaluateFunctionConfigCached(configPath, webpackConfig, env, argv) {
+  const cacheKey = JSON.stringify({ configPath, args: [env, argv] });
+  if (_evalCache.has(cacheKey)) {
+    return _evalCache.get(cacheKey);
   }
+  const cached = webpackConfig(env, argv);
+  _evalCache.set(cacheKey, cached);
 
-  // else, vanilla object
-  for (var key in externals) {
-    if (!has(externals, key)) continue
-    if (source === key) return true
+  while (_evalCache.size > MAX_CACHE) {
+    // remove oldest item
+    _evalCache.delete(_evalCache.keys().next().value);
   }
-  return false
+  return cached;
 }
 
 /**
- * webpack 1 defaults: http://webpack.github.io/docs/configuration.html#resolve-packagemains
- * @type {Array}
+ * Find the full path to 'source', given 'file' as a full reference path.
+ *
+ * resolveImport('./foo', '/Users/ben/bar.js') => '/Users/ben/foo.js'
+ * @param {string} source - the module to resolve; i.e './some-module'
+ * @param {string} file - the importing file's full path; i.e. '/usr/local/bin/file.js'
+ * @param {object} settings - the webpack config file name, as well as cwd
+ * @example
+ * options: {
+ *  // Path to the webpack config
+ *  config: 'webpack.config.js',
+ *  // Path to be used to determine where to resolve webpack from
+ *  // (may differ from the cwd in some cases)
+ *  cwd: process.cwd()
+ * }
+ * @return {string?} the resolved path to source, undefined if not resolved, or null
+ *                   if resolved to a non-FS resource (i.e. script tag at page load)
  */
-var webpack1DefaultMains = [
-  'webpack', 'browser', 'web', 'browserify', ['jam', 'main'], 'main',
-]
+exports.resolve = function (source, file, settings) {
 
-function findConfigPath(configPath, packageDir) {
-  var extensions = Object.keys(interpret.extensions).sort(function(a, b) {
-    return a === '.js' ? -1 : b === '.js' ? 1 : a.length - b.length
-  })
-    , extension
+  // strip loaders
+  const finalBang = source.lastIndexOf('!');
+  if (finalBang >= 0) {
+    source = source.slice(finalBang + 1);
+  }
 
+  // strip resource query
+  const finalQuestionMark = source.lastIndexOf('?');
+  if (finalQuestionMark >= 0) {
+    source = source.slice(0, finalQuestionMark);
+  }
 
-  if (configPath) {
-    // extensions is not reused below, so safe to mutate it here.
-    extensions.reverse()
-    extensions.forEach(function (maybeExtension) {
-      if (extension) {
-        return
-      }
+  let webpackConfig;
 
-      if (configPath.substr(-maybeExtension.length) === maybeExtension) {
-        extension = maybeExtension
-      }
-    })
+  const _configPath = settings && settings.config;
+  /**
+     * Attempt to set the current working directory.
+     * If none is passed, default to the `cwd` where the config is located.
+     */
+  const cwd = settings && settings.cwd;
+  const configIndex = settings && settings['config-index'];
+  const env = settings && settings.env;
+  const argv = settings && typeof settings.argv !== 'undefined' ? settings.argv : {};
+  const shouldCacheFunctionConfig = settings && settings.cache;
+  let packageDir;
+
+  let configPath = typeof _configPath === 'string' && _configPath.startsWith('.')
+    ? path.resolve(_configPath)
+    : _configPath;
+
+  log('Config path from settings:', configPath);
+
+  // see if we've got a config path, a config object, an array of config objects or a config function
+  if (!configPath || typeof configPath === 'string') {
 
     // see if we've got an absolute path
-    if (!path.isAbsolute(configPath)) {
-      configPath = path.join(packageDir, configPath)
+    if (!configPath || !path.isAbsolute(configPath)) {
+      // if not, find ancestral package.json and use its directory as base for the path
+      packageDir = findRoot(path.resolve(file));
+      if (!packageDir) { throw new Error('package not found above ' + file); }
     }
-  } else {
-    extensions.forEach(function (maybeExtension) {
-      if (extension) {
-        return
-      }
 
-      var maybePath = path.resolve(
-        path.join(packageDir, 'webpack.config' + maybeExtension)
-      )
-      if (fs.existsSync(maybePath)) {
-        configPath = maybePath
-        extension = maybeExtension
+    configPath = findConfigPath(configPath, packageDir);
+
+    log('Config path resolved to:', configPath);
+    if (configPath) {
+      try {
+        webpackConfig = require(configPath);
+      } catch (e) {
+        console.log('Error resolving webpackConfig', e);
+        throw e;
       }
-    })
+    } else {
+      log('No config path found relative to', file, '; using {}');
+      webpackConfig = {};
+    }
+
+    if (webpackConfig && webpackConfig.default) {
+      log('Using ES6 module "default" key instead of module.exports.');
+      webpackConfig = webpackConfig.default;
+    }
+
+  } else {
+    webpackConfig = configPath;
+    configPath = null;
   }
 
-  registerCompiler(interpret.extensions[extension])
-  return configPath
-}
+  if (typeof webpackConfig === 'function') {
+    webpackConfig = shouldCacheFunctionConfig
+      ? evaluateFunctionConfigCached(configPath, webpackConfig, env, argv)
+      : webpackConfig(env, argv);
+  }
 
-function registerCompiler(moduleDescriptor) {
-  if(moduleDescriptor) {
-    if(typeof moduleDescriptor === 'string') {
-      require(moduleDescriptor)
-    } else if(!Array.isArray(moduleDescriptor)) {
-      moduleDescriptor.register(require(moduleDescriptor.module))
+  if (isArray(webpackConfig)) {
+    webpackConfig = webpackConfig.map((cfg) => {
+      if (typeof cfg === 'function') {
+        return cfg(env, argv);
+      }
+
+      return cfg;
+    });
+
+    if (typeof configIndex !== 'undefined' && webpackConfig.length > configIndex) {
+      webpackConfig = webpackConfig[configIndex];
     } else {
-      for(var i = 0; i < moduleDescriptor.length; i++) {
-        try {
-          registerCompiler(moduleDescriptor[i])
-          break
-        } catch(e) {
-          log('Failed to register compiler for moduleDescriptor[]:', i, moduleDescriptor)
+      for (let i = 0; i < webpackConfig.length; i++) {
+        if (webpackConfig[i].resolve) {
+          webpackConfig = webpackConfig[i];
+          break;
         }
       }
     }
   }
-}
+
+  if (typeof webpackConfig.then === 'function') {
+    webpackConfig = {};
+
+    console.warn('Webpack config returns a `Promise`; that signature is not supported at the moment. Using empty object instead.');
+  }
+
+  if (webpackConfig == null) {
+    webpackConfig = {};
+
+    console.warn('No webpack configuration with a "resolve" field found. Using empty object instead.');
+  }
+
+  log('Using config: ', webpackConfig);
+
+  const resolveSync = getResolveSync(configPath, webpackConfig, cwd);
+
+  // externals
+  if (findExternal(source, webpackConfig.externals, path.dirname(file), resolveSync)) {
+    return { found: true, path: null };
+  }
+
+  // otherwise, resolve "normally"
+
+  try {
+    return { found: true, path: resolveSync(path.dirname(file), source) };
+  } catch (err) {
+    if (isCore(source)) {
+      return { found: true, path: null };
+    }
+
+    log('Error during module resolution:', err);
+    return { found: false };
+  }
+};
diff --git a/resolvers/webpack/package.json b/resolvers/webpack/package.json
index 69a861c6e..057711e9b 100644
--- a/resolvers/webpack/package.json
+++ b/resolvers/webpack/package.json
@@ -1,12 +1,12 @@
 {
   "name": "eslint-import-resolver-webpack",
-  "version": "0.11.1",
+  "version": "0.13.10",
   "description": "Resolve paths to dependencies, given a webpack.config.js. Plugin for eslint-plugin-import.",
   "main": "index.js",
   "scripts": {
-    "test": "nyc mocha -t 5s",
-    "report": "nyc report --reporter=html",
-    "coveralls": "nyc report --reporter lcovonly && cd ../.. && coveralls < ./resolvers/webpack/coverage/lcov.info"
+    "prepublishOnly": "cp ../../{LICENSE,.npmrc} ./",
+    "tests-only": "nyc mocha -t 5s",
+    "test": "npm run tests-only"
   },
   "files": [
     "index.js",
@@ -14,7 +14,8 @@
   ],
   "repository": {
     "type": "git",
-    "url": "git+https://github.com/benmosher/eslint-plugin-import.git"
+    "url": "git+https://github.com/import-js/eslint-plugin-import.git",
+    "directory": "resolvers/webpack"
   },
   "keywords": [
     "eslint-plugin-import",
@@ -26,20 +27,20 @@
   "author": "Ben Mosher (me@benmosher.com)",
   "license": "MIT",
   "bugs": {
-    "url": "https://github.com/benmosher/eslint-plugin-import/issues"
+    "url": "https://github.com/import-js/eslint-plugin-import/issues"
   },
-  "homepage": "https://github.com/benmosher/eslint-plugin-import/tree/master/resolvers/webpack",
+  "homepage": "https://github.com/import-js/eslint-plugin-import/tree/HEAD/resolvers/webpack",
   "dependencies": {
-    "array-find": "^1.0.0",
-    "debug": "^2.6.8",
-    "enhanced-resolve": "~0.9.0",
+    "debug": "^3.2.7",
+    "enhanced-resolve": "^0.9.1",
     "find-root": "^1.1.0",
-    "has": "^1.0.1",
-    "interpret": "^1.0.0",
-    "lodash": "^4.17.4",
-    "node-libs-browser": "^1.0.0 || ^2.0.0",
-    "resolve": "^1.10.0",
-    "semver": "^5.3.0"
+    "hasown": "^2.0.2",
+    "interpret": "^1.4.0",
+    "is-core-module": "^2.15.1",
+    "is-regex": "^1.2.0",
+    "lodash": "^4.17.21",
+    "resolve": "^2.0.0-next.5",
+    "semver": "^5.7.2"
   },
   "peerDependencies": {
     "eslint-plugin-import": ">=1.4.0",
@@ -47,16 +48,13 @@
   },
   "devDependencies": {
     "babel-plugin-istanbul": "^4.1.6",
-    "babel-preset-es2015-argon": "latest",
     "babel-register": "^6.26.0",
     "chai": "^3.5.0",
-    "coveralls": "^3.0.0",
     "mocha": "^3.5.3",
-    "nyc": "^11.7.1"
+    "nyc": "^11.9.0",
+    "webpack": "https://gist.github.com/ljharb/9cdb687f3806f8e6cb8a365d0b7840eb"
   },
-  "nyc": {
-    "exclude": [
-      "test/"
-    ]
+  "engines": {
+    "node": ">= 6"
   }
 }
diff --git a/resolvers/webpack/test/.eslintrc b/resolvers/webpack/test/.eslintrc
deleted file mode 100644
index 2ad1adee9..000000000
--- a/resolvers/webpack/test/.eslintrc
+++ /dev/null
@@ -1,5 +0,0 @@
----
-env:
-  mocha: true
-rules:
-  quotes: 0
diff --git a/resolvers/webpack/test/alias.js b/resolvers/webpack/test/alias.js
index f8cd210e4..946365b30 100644
--- a/resolvers/webpack/test/alias.js
+++ b/resolvers/webpack/test/alias.js
@@ -1,137 +1,139 @@
-var chai = require('chai')
-  , expect = chai.expect
-  , path = require('path')
+'use strict';
 
-var webpack = require('../index')
+const chai = require('chai');
+const expect = chai.expect;
+const path = require('path');
 
-var file = path.join(__dirname, 'files', 'dummy.js')
+const webpack = require('../index');
 
-describe("resolve.alias", function () {
-  var resolved
-  before(function () { resolved = webpack.resolve('foo', file) })
+const file = path.join(__dirname, 'files', 'dummy.js');
 
-  it("is found", function () { expect(resolved).to.have.property('found', true) })
+describe('resolve.alias', function () {
+  let resolved;
+  before(function () { resolved = webpack.resolve('foo', file); });
 
-  it("is correct", function () {
+  it('is found', function () { expect(resolved).to.have.property('found', true); });
+
+  it('is correct', function () {
     expect(resolved).to.have.property('path')
-      .and.equal(path.join(__dirname, 'files', 'some', 'goofy', 'path', 'foo.js'))
-  })
-})
+      .and.equal(path.join(__dirname, 'files', 'some', 'goofy', 'path', 'foo.js'));
+  });
+});
 
 // todo: reimplement with resolver function / config
-// describe.skip("webpack alias spec", function () {
-//   // from table: http://webpack.github.io/docs/configuration.html#resolve-alias
+// describe.skip('webpack alias spec', function () {
+//   // 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 () {
-//         expect(resolveAlias('xyz', alias)).to.equal(xyz)
-//       })
-//       it("xyz/file: " + (xyzFile.name || xyzFile), function () {
+//       it('xyz: ' + xyz, function () {
+//         expect(resolveAlias('xyz', alias)).to.equal(xyz);
+//       });
+//       it('xyz/file: ' + (xyzFile.name || xyzFile), function () {
 //         if (xyzFile === Error) {
-//           expect(resolveAlias.bind(null, 'xyz/file', alias)).to.throw(xyzFile)
+//           expect(resolveAlias.bind(null, 'xyz/file', alias)).to.throw(xyzFile);
 //         } else {
-//           expect(resolveAlias('xyz/file', alias)).to.equal(xyzFile)
+//           expect(resolveAlias('xyz/file', alias)).to.equal(xyzFile);
 //         }
-//       })
-//     })
+//       });
+//     });
 //   }
 
 //   tableLine( {}
-//            , 'xyz', 'xyz/file' )
+//     , 'xyz', 'xyz/file' );
 
-//   tableLine( { xyz: "/absolute/path/to/file.js" }
-//            , '/absolute/path/to/file.js', 'xyz/file' )
+//   tableLine( { xyz: '/absolute/path/to/file.js' }
+//     , '/absolute/path/to/file.js', 'xyz/file' );
 
-//   tableLine( { xyz$: "/absolute/path/to/file.js" }
-//            ,  "/absolute/path/to/file.js", Error )
+//   tableLine( { xyz$: '/absolute/path/to/file.js' }
+//     ,  '/absolute/path/to/file.js', Error );
 
-//   tableLine( { xyz: "./dir/file.js" }
-//            , './dir/file.js',  'xyz/file' )
+//   tableLine( { xyz: './dir/file.js' }
+//     , './dir/file.js',  'xyz/file' );
 
-//   tableLine( { xyz$: "./dir/file.js" }
-//            , './dir/file.js', Error )
+//   tableLine( { xyz$: './dir/file.js' }
+//     , './dir/file.js', Error );
 
-//   tableLine( { xyz: "/some/dir" }
-//            , '/some/dir', '/some/dir/file' )
+//   tableLine( { xyz: '/some/dir' }
+//     , '/some/dir', '/some/dir/file' );
 
-//   tableLine( { xyz$: "/some/dir" }
-//            , '/some/dir', 'xyz/file' )
+//   tableLine( { xyz$: '/some/dir' }
+//     , '/some/dir', 'xyz/file' );
 
-//   tableLine( { xyz: "./dir" }
-//            , './dir', './dir/file' )
+//   tableLine( { xyz: './dir' }
+//     , './dir', './dir/file' );
 
-//   tableLine( { xyz: "modu" }
-//            , 'modu', 'modu/file' )
+//   tableLine( { xyz: 'modu' }
+//     , 'modu', 'modu/file' );
 
-//   tableLine( { xyz$: "modu" }
-//            , 'modu', 'xyz/file' )
+//   tableLine( { xyz$: 'modu' }
+//     , 'modu', 'xyz/file' );
 
-//   tableLine( { xyz: "modu/some/file.js" }
-//            , 'modu/some/file.js', Error )
+//   tableLine( { xyz: 'modu/some/file.js' }
+//     , 'modu/some/file.js', Error );
 
-//   tableLine( { xyz: "modu/dir" }
-//            , 'modu/dir', 'modu/dir/file' )
+//   tableLine( { xyz: 'modu/dir' }
+//     , 'modu/dir', 'modu/dir/file' );
 
-//   tableLine( { xyz: "xyz/dir" }
-//            , 'xyz/dir',  'xyz/dir/file' )
+//   tableLine( { xyz: 'xyz/dir' }
+//     , 'xyz/dir',  'xyz/dir/file' );
 
-//   tableLine( { xyz$: "xyz/dir" }
-//            , 'xyz/dir', 'xyz/file' )
-// })
+//   tableLine( { xyz$: 'xyz/dir' }
+//     , 'xyz/dir', 'xyz/file' );
+// });
 
-// describe.skip("nested module names", function () {
-//   // from table: http://webpack.github.io/docs/configuration.html#resolve-alias
+// describe.skip('nested module names', function () {
+//   // 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 () {
-//         expect(resolveAlias('top/xyz', alias)).to.equal(xyz)
-//       })
-//       it("top/xyz/file: " + (xyzFile.name || xyzFile), function () {
+//       it('top/xyz: ' + xyz, function () {
+//         expect(resolveAlias('top/xyz', alias)).to.equal(xyz);
+//       });
+//       it('top/xyz/file: ' + (xyzFile.name || xyzFile), function () {
 //         if (xyzFile === Error) {
-//           expect(resolveAlias.bind(null, 'top/xyz/file', alias)).to.throw(xyzFile)
+//           expect(resolveAlias.bind(null, 'top/xyz/file', alias)).to.throw(xyzFile);
 //         } else {
-//           expect(resolveAlias('top/xyz/file', alias)).to.equal(xyzFile)
+//           expect(resolveAlias('top/xyz/file', alias)).to.equal(xyzFile);
 //         }
-//       })
-//     })
+//       });
+//     });
 //   }
 
-//   nestedName( { 'top/xyz': "/absolute/path/to/file.js" }
-//       , '/absolute/path/to/file.js', 'top/xyz/file' )
+//   nestedName( { 'top/xyz': '/absolute/path/to/file.js' }
+//     , '/absolute/path/to/file.js', 'top/xyz/file' );
 
-//   nestedName( { 'top/xyz$': "/absolute/path/to/file.js" }
-//       ,  "/absolute/path/to/file.js", Error )
+//   nestedName( { 'top/xyz$': '/absolute/path/to/file.js' }
+//     ,  '/absolute/path/to/file.js', Error );
 
-//   nestedName( { 'top/xyz': "./dir/file.js" }
-//       , './dir/file.js',  'top/xyz/file' )
+//   nestedName( { 'top/xyz': './dir/file.js' }
+//     , './dir/file.js',  'top/xyz/file' );
 
-//   nestedName( { 'top/xyz$': "./dir/file.js" }
-//       , './dir/file.js', Error )
+//   nestedName( { 'top/xyz$': './dir/file.js' }
+//     , './dir/file.js', Error );
 
-//   nestedName( { 'top/xyz': "/some/dir" }
-//       , '/some/dir', '/some/dir/file' )
+//   nestedName( { 'top/xyz': '/some/dir' }
+//     , '/some/dir', '/some/dir/file' );
 
-//   nestedName( { 'top/xyz$': "/some/dir" }
-//       , '/some/dir', 'top/xyz/file' )
+//   nestedName( { 'top/xyz$': '/some/dir' }
+//     , '/some/dir', 'top/xyz/file' );
 
-//   nestedName( { 'top/xyz': "./dir" }
-//       , './dir', './dir/file' )
+//   nestedName( { 'top/xyz': './dir' }
+//     , './dir', './dir/file' );
 
-//   nestedName( { 'top/xyz': "modu" }
-//       , 'modu', 'modu/file' )
+//   nestedName( { 'top/xyz': 'modu' }
+//     , 'modu', 'modu/file' );
 
-//   nestedName( { 'top/xyz$': "modu" }
-//       , 'modu', 'top/xyz/file' )
+//   nestedName( { 'top/xyz$': 'modu' }
+//     , 'modu', 'top/xyz/file' );
 
-//   nestedName( { 'top/xyz': "modu/some/file.js" }
-//       , 'modu/some/file.js', Error )
+//   nestedName( { 'top/xyz': 'modu/some/file.js' }
+//     , 'modu/some/file.js', Error );
 
-//   nestedName( { 'top/xyz': "modu/dir" }
-//       , 'modu/dir', 'modu/dir/file' )
+//   nestedName( { 'top/xyz': 'modu/dir' }
+//     , 'modu/dir', 'modu/dir/file' );
 
-//   nestedName( { 'top/xyz': "top/xyz/dir" }
-//       , 'top/xyz/dir',  'top/xyz/dir/file' )
+//   nestedName( { 'top/xyz': 'top/xyz/dir' }
+//     , 'top/xyz/dir',  'top/xyz/dir/file' );
 
-//   nestedName( { 'top/xyz$': "top/xyz/dir" }
-//       , 'top/xyz/dir', 'top/xyz/file' )
-// })
+//   nestedName( { 'top/xyz$': 'top/xyz/dir' }
+//     , 'top/xyz/dir', 'top/xyz/file' );
+// });
diff --git a/resolvers/webpack/test/cache.js b/resolvers/webpack/test/cache.js
new file mode 100644
index 000000000..04d6de057
--- /dev/null
+++ b/resolvers/webpack/test/cache.js
@@ -0,0 +1,48 @@
+'use strict';
+
+const chai =  require('chai');
+const expect = chai.expect;
+const path = require('path');
+
+const resolve = require('../index').resolve;
+
+const file = path.join(__dirname, 'files', 'src', 'jsx', 'dummy.js');
+
+describe('cache', function () {
+  it('can distinguish different config files', function () {
+    const setting1 = {
+      config: require(path.join(__dirname, './files/webpack.function.config.js')),
+      argv: {
+        mode: 'test',
+      },
+      cache: true,
+    };
+    expect(resolve('baz', file, setting1)).to.have.property('path')
+      .and.equal(path.join(__dirname, 'files', 'some', 'bar', 'bar.js'));
+    const setting2 = {
+      config: require(path.join(__dirname, './files/webpack.function.config.multiple.js')),
+      cache: true,
+    };
+    expect(resolve('baz', file, setting2)).to.have.property('path')
+      .and.equal(path.join(__dirname, 'files', 'some', 'goofy', 'path', 'foo.js'));
+  });
+
+  it('can distinguish different config', function () {
+    const setting1 = {
+      config: require(path.join(__dirname, './files/webpack.function.config.js')),
+      env: {
+        dummy: true,
+      },
+      cache: true,
+    };
+    expect(resolve('bar', file, setting1)).to.have.property('path')
+      .and.equal(path.join(__dirname, 'files', 'some', 'goofy', 'path', 'bar.js'));
+    const setting2 = {
+      config: require(path.join(__dirname, './files/webpack.function.config.multiple.js')),
+      cache: true,
+    };
+    const result = resolve('bar', file, setting2);
+    expect(result).not.to.have.property('path');
+    expect(result).to.have.property('found').to.be.false;
+  });
+});
diff --git a/resolvers/webpack/test/config-extensions/webpack.config.babel.js b/resolvers/webpack/test/config-extensions/webpack.config.babel.js
index 41dc6c8e8..c8b3cd578 100644
--- a/resolvers/webpack/test/config-extensions/webpack.config.babel.js
+++ b/resolvers/webpack/test/config-extensions/webpack.config.babel.js
@@ -1,9 +1,9 @@
-import path from 'path'
+import path from 'path';
 
 export default {
   resolve: {
     alias: {
-      'foo': path.join(__dirname, 'some', 'goofy', 'path', 'foo.js'),
+      foo: path.join(__dirname, 'some', 'goofy', 'path', 'foo.js'),
     },
     modules: [
       path.join(__dirname, 'src'),
@@ -17,7 +17,7 @@ export default {
   },
 
   externals: [
-    { 'jquery': 'jQuery' },
+    { jquery: 'jQuery' },
     'bootstrap',
   ],
-}
+};
diff --git a/resolvers/webpack/test/config.js b/resolvers/webpack/test/config.js
index 07c6350c5..35f46e66c 100644
--- a/resolvers/webpack/test/config.js
+++ b/resolvers/webpack/test/config.js
@@ -1,138 +1,158 @@
-var chai =  require('chai')
-  , expect = chai.expect
-  , path = require('path')
+'use strict';
 
-var resolve = require('../index').resolve
+const chai =  require('chai');
+const expect = chai.expect;
+const path = require('path');
 
-var file = path.join(__dirname, 'files', 'src', 'jsx', 'dummy.js')
-var extensionFile = path.join(__dirname, 'config-extensions', 'src', 'dummy.js')
+const resolve = require('../index').resolve;
 
-var absoluteSettings = {
+const file = path.join(__dirname, 'files', 'src', 'jsx', 'dummy.js');
+const extensionFile = path.join(__dirname, 'config-extensions', 'src', 'dummy.js');
+
+const absoluteSettings = {
   config: path.join(__dirname, 'files', 'some', 'absolute.path.webpack.config.js'),
-}
+};
 
-describe("config", function () {
-  it("finds webpack.config.js in parent directories", function () {
+describe('config', function () {
+  it('finds webpack.config.js in parent directories', function () {
     expect(resolve('main-module', file)).to.have.property('path')
-        .and.equal(path.join(__dirname, 'files', 'src', 'main-module.js'))
-  })
+      .and.equal(path.join(__dirname, 'files', 'src', 'main-module.js'));
+  });
 
-  it("finds absolute webpack.config.js files", function () {
+  it('finds absolute webpack.config.js files', function () {
     expect(resolve('foo', file, absoluteSettings)).to.have.property('path')
-        .and.equal(path.join(__dirname, 'files', 'some', 'absolutely', 'goofy', 'path', 'foo.js'))
-  })
+      .and.equal(path.join(__dirname, 'files', 'some', 'absolutely', 'goofy', 'path', 'foo.js'));
+  });
 
-  it("finds compile-to-js configs", function () {
-    var settings = {
+  it('finds compile-to-js configs', function () {
+    const settings = {
       config: path.join(__dirname, './files/webpack.config.babel.js'),
-    }
+    };
 
     expect(resolve('main-module', file, settings))
       .to.have.property('path')
-      .and.equal(path.join(__dirname, 'files', 'src', 'main-module.js'))
-  })
+      .and.equal(path.join(__dirname, 'files', 'src', 'main-module.js'));
+  });
 
-  it("finds compile-to-js config in parent directories", function () {
+  it('finds compile-to-js config in parent directories', function () {
     expect(resolve('main-module', extensionFile))
       .to.have.property('path')
-      .and.equal(path.join(__dirname, 'config-extensions', 'src', 'main-module.js'))
-  })
+      .and.equal(path.join(__dirname, 'config-extensions', 'src', 'main-module.js'));
+  });
 
-  it("finds the first config with a resolve section", function () {
-    var settings = {
+  it('finds the first config with a resolve section', function () {
+    const settings = {
       config: path.join(__dirname, './files/webpack.config.multiple.js'),
-    }
+    };
 
     expect(resolve('main-module', file, settings)).to.have.property('path')
-        .and.equal(path.join(__dirname, 'files', 'src', 'main-module.js'))
-  })
+      .and.equal(path.join(__dirname, 'files', 'src', 'main-module.js'));
+  });
 
-  it("finds the config at option config-index", function () {
-    var settings = {
+  it('finds the config at option config-index', function () {
+    const settings = {
       config: path.join(__dirname, './files/webpack.config.multiple.js'),
       'config-index': 2,
-    }
+    };
 
     expect(resolve('foo', file, settings)).to.have.property('path')
-        .and.equal(path.join(__dirname, 'files', 'some', 'goofy', 'path', 'foo.js'))
-  })
+      .and.equal(path.join(__dirname, 'files', 'some', 'goofy', 'path', 'foo.js'));
+  });
 
   it("doesn't swallow config load errors (#435)", function () {
-    var settings = {
+    const settings = {
       config: path.join(__dirname, './files/webpack.config.garbage.js'),
-    }
-    expect(function () { resolve('foo', file, settings) }).to.throw(Error)
-  })
+    };
+    expect(function () { resolve('foo', file, settings); }).to.throw(Error);
+  });
 
-  it("finds config object when config is an object", function () {
-    var settings = {
+  it('finds config object when config is an object', function () {
+    const settings = {
       config: require(path.join(__dirname, 'files', 'some', 'absolute.path.webpack.config.js')),
-    }
+    };
+    expect(resolve('foo', file, settings)).to.have.property('path')
+      .and.equal(path.join(__dirname, 'files', 'some', 'absolutely', 'goofy', 'path', 'foo.js'));
+  });
+
+  it('finds config object when config uses a path relative to working dir', function () {
+    const settings = {
+      config: './test/files/some/absolute.path.webpack.config.js',
+    };
     expect(resolve('foo', file, settings)).to.have.property('path')
-        .and.equal(path.join(__dirname, 'files', 'some', 'absolutely', 'goofy', 'path', 'foo.js'))
-  })
+      .and.equal(path.join(__dirname, 'files', 'some', 'absolutely', 'goofy', 'path', 'foo.js'));
+  });
 
-  it("finds the first config with a resolve section when config is an array of config objects", function () {
-    var settings = {
+  it('finds the first config with a resolve section when config is an array of config objects', function () {
+    const settings = {
       config: require(path.join(__dirname, './files/webpack.config.multiple.js')),
-    }
+    };
 
     expect(resolve('main-module', file, settings)).to.have.property('path')
-        .and.equal(path.join(__dirname, 'files', 'src', 'main-module.js'))
-  })
+      .and.equal(path.join(__dirname, 'files', 'src', 'main-module.js'));
+  });
 
-  it("finds the config at option config-index when config is an array of config objects", function () {
-    var settings = {
+  it('finds the config at option config-index when config is an array of config objects', function () {
+    const settings = {
       config: require(path.join(__dirname, './files/webpack.config.multiple.js')),
       'config-index': 2,
-    }
+    };
 
     expect(resolve('foo', file, settings)).to.have.property('path')
-        .and.equal(path.join(__dirname, 'files', 'some', 'goofy', 'path', 'foo.js'))
-  })
+      .and.equal(path.join(__dirname, 'files', 'some', 'goofy', 'path', 'foo.js'));
+  });
 
-  it('finds the config at option env when config is a function', function() {
-    var settings = {
+  it('finds the config at option env when config is a function', function () {
+    const settings = {
       config: require(path.join(__dirname, './files/webpack.function.config.js')),
       env: {
         dummy: true,
       },
-    }
+    };
 
     expect(resolve('bar', file, settings)).to.have.property('path')
-        .and.equal(path.join(__dirname, 'files', 'some', 'goofy', 'path', 'bar.js'))
-  })
+      .and.equal(path.join(__dirname, 'files', 'some', 'goofy', 'path', 'bar.js'));
+  });
 
-  it('finds the config at option env when config is an array of functions', function() {
-    var settings = {
+  it('finds the config at option env when config is an array of functions', function () {
+    const settings = {
       config: require(path.join(__dirname, './files/webpack.function.config.multiple.js')),
       env: {
         dummy: true,
       },
-    }
+    };
 
     expect(resolve('bar', file, settings)).to.have.property('path')
-        .and.equal(path.join(__dirname, 'files', 'some', 'goofy', 'path', 'bar.js'))
-  })
+      .and.equal(path.join(__dirname, 'files', 'some', 'goofy', 'path', 'bar.js'));
+  });
 
-  it('passes argv to config when it is a function', function() {
-    var settings = {
+  it('passes argv to config when it is a function', function () {
+    const settings = {
       config: require(path.join(__dirname, './files/webpack.function.config.js')),
       argv: {
-        mode: 'test'
-      }
-    }
+        mode: 'test',
+      },
+    };
 
     expect(resolve('baz', file, settings)).to.have.property('path')
-        .and.equal(path.join(__dirname, 'files', 'some', 'bar', 'bar.js'))
-  })
+      .and.equal(path.join(__dirname, 'files', 'some', 'bar', 'bar.js'));
+  });
 
-  it('passes a default empty argv object to config when it is a function', function() {
-    var settings = {
+  it('passes a default empty argv object to config when it is a function', function () {
+    const settings = {
       config: require(path.join(__dirname, './files/webpack.function.config.js')),
-      argv: undefined
-    }
-
-    expect(function () { resolve('baz', file, settings) }).to.not.throw(Error)
-  })
-})
+      argv: undefined,
+    };
+
+    expect(function () { resolve('baz', file, settings); }).to.not.throw(Error);
+  });
+
+  it('prevents async config using', function () {
+    const settings = {
+      config: require(path.join(__dirname, './files/webpack.config.async.js')),
+    };
+    const result = resolve('foo', file, settings);
+
+    expect(result).not.to.have.property('path');
+    expect(result).to.have.property('found').to.be.false;
+  });
+});
diff --git a/resolvers/webpack/test/custom-extensions/webpack.config.js b/resolvers/webpack/test/custom-extensions/webpack.config.js
index ee3444c00..b8d39ed7c 100644
--- a/resolvers/webpack/test/custom-extensions/webpack.config.js
+++ b/resolvers/webpack/test/custom-extensions/webpack.config.js
@@ -1,3 +1,3 @@
 module.exports = {
   resolve: { extensions: ['.js', '.coffee'] },
-}
+};
diff --git a/resolvers/webpack/test/example.js b/resolvers/webpack/test/example.js
new file mode 100644
index 000000000..cd9ece015
--- /dev/null
+++ b/resolvers/webpack/test/example.js
@@ -0,0 +1,11 @@
+'use strict';
+
+const path = require('path');
+
+const resolve = require('../index').resolve;
+
+const file = path.join(__dirname, 'files', 'src', 'dummy.js');
+
+const webpackDir = path.join(__dirname, 'different-package-location');
+
+console.log(resolve('main-module', file, { config: 'webpack.config.js', cwd: webpackDir }));
diff --git a/resolvers/webpack/test/extensions.js b/resolvers/webpack/test/extensions.js
index 94e9bd394..096df7728 100644
--- a/resolvers/webpack/test/extensions.js
+++ b/resolvers/webpack/test/extensions.js
@@ -1,32 +1,33 @@
-var chai =  require('chai')
-  , expect = chai.expect
-  , path = require('path')
+'use strict';
 
-var resolve = require('../index').resolve
+const chai =  require('chai');
+const expect = chai.expect;
+const path = require('path');
 
+const resolve = require('../index').resolve;
 
-var file = path.join(__dirname, 'files', 'dummy.js')
-  , extensions = path.join(__dirname, 'custom-extensions', 'dummy.js')
+const file = path.join(__dirname, 'files', 'dummy.js');
+const extensions = path.join(__dirname, 'custom-extensions', 'dummy.js');
 
-describe("extensions", function () {
-  it("respects the defaults", function () {
+describe('extensions', function () {
+  it('respects the defaults', function () {
     expect(resolve('./foo', file)).to.have.property('path')
-      .and.equal(path.join(__dirname, 'files', 'foo.web.js'))
-  })
+      .and.equal(path.join(__dirname, 'files', 'foo.web.js'));
+  });
 
-  describe("resolve.extensions set", function () {
-    it("works", function () {
+  describe('resolve.extensions set', function () {
+    it('works', function () {
       expect(resolve('./foo', extensions)).to.have.property('path')
-        .and.equal(path.join(__dirname, 'custom-extensions', 'foo.js'))
-    })
+        .and.equal(path.join(__dirname, 'custom-extensions', 'foo.js'));
+    });
 
-    it("replaces defaults", function () {
-      expect(resolve('./baz', extensions)).to.have.property('found', false)
-    })
+    it('replaces defaults', function () {
+      expect(resolve('./baz', extensions)).to.have.property('found', false);
+    });
 
-    it("finds .coffee", function () {
+    it('finds .coffee', function () {
       expect(resolve('./bar', extensions)).to.have.property('path')
-        .and.equal(path.join(__dirname, 'custom-extensions', 'bar.coffee'))
-    })
-  })
-})
+        .and.equal(path.join(__dirname, 'custom-extensions', 'bar.coffee'));
+    });
+  });
+});
diff --git a/resolvers/webpack/test/externals.js b/resolvers/webpack/test/externals.js
index e2e61fbe1..27dec2033 100644
--- a/resolvers/webpack/test/externals.js
+++ b/resolvers/webpack/test/externals.js
@@ -1,33 +1,68 @@
-var chai = require('chai')
-  , expect = chai.expect
-  , path = require('path')
-
-var webpack = require('../index')
-
-var file = path.join(__dirname, 'files', 'dummy.js')
-
-describe("externals", function () {
-  it("works on just a string", function () {
-    var resolved = webpack.resolve('bootstrap', file)
-    expect(resolved).to.have.property('found', true)
-    expect(resolved).to.have.property('path', null)
-  })
-
-  it("works on object-map", function () {
-    var resolved = webpack.resolve('jquery', file)
-    expect(resolved).to.have.property('found', true)
-    expect(resolved).to.have.property('path', null)
-  })
-
-  it("works on a function", function () {
-    var resolved = webpack.resolve('underscore', file)
-    expect(resolved).to.have.property('found', true)
-    expect(resolved).to.have.property('path', null)
-  })
-
-  it("returns null for core modules", function () {
-    var resolved = webpack.resolve('fs', file)
-    expect(resolved).to.have.property('found', true)
-    expect(resolved).to.have.property('path', null)
-  })
-})
+'use strict';
+
+const chai = require('chai');
+const expect = chai.expect;
+const path = require('path');
+const semver = require('semver');
+
+const webpack = require('../index');
+
+const file = path.join(__dirname, 'files', 'dummy.js');
+
+describe('externals', function () {
+  const settingsWebpack5 = {
+    config: require(path.join(__dirname, './files/webpack.config.webpack5.js')),
+  };
+
+  it('works on just a string', function () {
+    const resolved = webpack.resolve('bootstrap', file);
+    expect(resolved).to.have.property('found', true);
+    expect(resolved).to.have.property('path', null);
+  });
+
+  it('works on object-map', function () {
+    const resolved = webpack.resolve('jquery', file);
+    expect(resolved).to.have.property('found', true);
+    expect(resolved).to.have.property('path', null);
+  });
+
+  it('works on a function', function () {
+    const resolved = webpack.resolve('underscore', file);
+    expect(resolved).to.have.property('found', true);
+    expect(resolved).to.have.property('path', null);
+  });
+
+  it('returns null for core modules', function () {
+    const resolved = webpack.resolve('fs', file);
+    expect(resolved).to.have.property('found', true);
+    expect(resolved).to.have.property('path', null);
+  });
+
+  it('works on a function (synchronous) for webpack 5', function () {
+    const resolved = webpack.resolve('underscore', file, settingsWebpack5);
+    expect(resolved).to.have.property('found', true);
+    expect(resolved).to.have.property('path', null);
+  });
+
+  it('works on a function (synchronous) which uses getResolve for webpack 5', function () {
+    const resolved = webpack.resolve('graphql', file, settingsWebpack5);
+    expect(resolved).to.have.property('found', true);
+    expect(resolved).to.have.property('path', null);
+  });
+
+  (semver.satisfies(process.version, '> 6') ? describe : describe.skip)('async function in webpack 5', function () {
+    const settingsWebpack5Async = () => ({
+      config: require(path.join(__dirname, './files/webpack.config.webpack5.async-externals.js')),
+    });
+
+    it('prevents using an asynchronous function for webpack 5', function () {
+      const resolved = webpack.resolve('underscore', file, settingsWebpack5Async());
+      expect(resolved).to.have.property('found', false);
+    });
+
+    it('prevents using a function which uses Promise returned by getResolve for webpack 5', function () {
+      const resolved = webpack.resolve('graphql', file, settingsWebpack5Async());
+      expect(resolved).to.have.property('found', false);
+    });
+  });
+});
diff --git a/resolvers/webpack/test/fallback.js b/resolvers/webpack/test/fallback.js
index ad4b2b4c1..b164209e1 100644
--- a/resolvers/webpack/test/fallback.js
+++ b/resolvers/webpack/test/fallback.js
@@ -1,28 +1,29 @@
-var chai =  require('chai')
-  , expect = chai.expect
-  , path = require('path')
+'use strict';
 
-var resolve = require('../index').resolve
+const chai =  require('chai');
+const expect = chai.expect;
+const path = require('path');
 
+const resolve = require('../index').resolve;
 
-var file = path.join(__dirname, 'files', 'src', 'dummy.js')
+const file = path.join(__dirname, 'files', 'src', 'dummy.js');
 
-describe("fallback", function () {
-  it("works", function () {
+describe('fallback', function () {
+  it('works', function () {
     expect(resolve('fb-module', file)).property('path')
-      .to.equal(path.join(__dirname, 'files', 'fallback', 'fb-module.js'))
-  })
-  it("really works", function () {
+      .to.equal(path.join(__dirname, 'files', 'fallback', 'fb-module.js'));
+  });
+  it('really works', function () {
     expect(resolve('jsx/some-fb-file', file)).property('path')
-      .to.equal(path.join(__dirname, 'files', 'fallback', 'jsx', 'some-fb-file.js'))
-  })
-  it("prefer root", function () {
+      .to.equal(path.join(__dirname, 'files', 'fallback', 'jsx', 'some-fb-file.js'));
+  });
+  it('prefer root', function () {
     expect(resolve('jsx/some-file', file)).property('path')
-      .to.equal(path.join(__dirname, 'files', 'src', 'jsx', 'some-file.js'))
-  })
-  it("supports definition as an array", function () {
-    expect(resolve('fb-module', file, { config: "webpack.array-root.config.js" }))
+      .to.equal(path.join(__dirname, 'files', 'src', 'jsx', 'some-file.js'));
+  });
+  it('supports definition as an array', function () {
+    expect(resolve('fb-module', file, { config: 'webpack.array-root.config.js' }))
       .property('path')
-      .to.equal(path.join(__dirname, 'files', 'fallback', 'fb-module.js'))
-  })
-})
+      .to.equal(path.join(__dirname, 'files', 'fallback', 'fb-module.js'));
+  });
+});
diff --git a/resolvers/webpack/test/files/node_modules/webpack-resolver-plugin-test/index.js b/resolvers/webpack/test/files/node_modules/webpack-resolver-plugin-test/index.js
index 2989f9bab..f23d4af0c 100644
--- a/resolvers/webpack/test/files/node_modules/webpack-resolver-plugin-test/index.js
+++ b/resolvers/webpack/test/files/node_modules/webpack-resolver-plugin-test/index.js
@@ -1,4 +1,4 @@
-var path = require('path');
+var path = require('path')
 
 /**
  * ResolverPlugin
@@ -9,15 +9,15 @@ var path = require('path');
  */
 
 function ResolverPlugin(plugins, types) {
-  if(!Array.isArray(plugins)) plugins = [plugins];
-  if(!types) types = ["normal"];
-  else if(!Array.isArray(types)) types = [types];
+  if(!Array.isArray(plugins)) plugins = [plugins]
+  if(!types) types = ["normal"]
+  else if(!Array.isArray(types)) types = [types]
 
-  this.plugins = plugins;
-  this.types = types;
+  this.plugins = plugins
+  this.types = types
 }
 
-module.exports.ResolverPlugin = ResolverPlugin;
+module.exports.ResolverPlugin = ResolverPlugin
 
 
 /**
@@ -29,29 +29,29 @@ module.exports.ResolverPlugin = ResolverPlugin;
  */
 
 function SimpleResolver(file, source) {
-  this.file = file;
-  this.source = source;
+  this.file = file
+  this.source = source
 }
 
 SimpleResolver.prototype.apply = function (resolver) {
 
-  var file = this.file;
-  var source = this.source;
+  var file = this.file
+  var source = this.source
 
   resolver.plugin('directory', function (request, done) {
 
-    var absolutePath = path.resolve(request.path, request.request);
+    var absolutePath = path.resolve(request.path, request.request)
 
     if (absolutePath === source) {
       resolver.doResolve('file', { request: file }, function (error, result) {
-        return done(undefined, result || undefined);
-      });
+        return done(undefined, result || undefined)
+      })
     }
 
-    return done();
+    return done()
 
-  });
+  })
 
 }
 
-module.exports.SimpleResolver = SimpleResolver;
+module.exports.SimpleResolver = SimpleResolver
diff --git a/resolvers/webpack/test/files/webpack.config.async.js b/resolvers/webpack/test/files/webpack.config.async.js
new file mode 100644
index 000000000..9b7aaa7f4
--- /dev/null
+++ b/resolvers/webpack/test/files/webpack.config.async.js
@@ -0,0 +1,7 @@
+const config = require('./webpack.config.js')
+
+module.exports = function() {
+  return new Promise(function(resolve) {
+    resolve(config)
+  })
+}
diff --git a/resolvers/webpack/test/files/webpack.config.js b/resolvers/webpack/test/files/webpack.config.js
index 7c7c8b3c8..38a4c888b 100644
--- a/resolvers/webpack/test/files/webpack.config.js
+++ b/resolvers/webpack/test/files/webpack.config.js
@@ -23,10 +23,10 @@ module.exports = {
     'bootstrap',
     function (context, request, callback) {
       if (request === 'underscore') {
-        return callback(null, 'underscore');
-      };
-      callback();
-    }
+        return callback(null, 'underscore')
+      }
+      callback()
+    },
   ],
 
   plugins: [
@@ -34,7 +34,7 @@ module.exports = {
       new pluginsTest.SimpleResolver(
         path.join(__dirname, 'some', 'bar', 'bar.js'),
         path.join(__dirname, 'some', 'bar')
-      )
-    ])
-  ]
+      ),
+    ]),
+  ],
 }
diff --git a/resolvers/webpack/test/files/webpack.config.webpack5.async-externals.js b/resolvers/webpack/test/files/webpack.config.webpack5.async-externals.js
new file mode 100644
index 000000000..ba2902b83
--- /dev/null
+++ b/resolvers/webpack/test/files/webpack.config.webpack5.async-externals.js
@@ -0,0 +1,21 @@
+module.exports = {
+  externals: [
+    { 'jquery': 'jQuery' },
+    'bootstrap',
+    async function ({ request },) {
+      if (request === 'underscore') {
+        return 'underscore'
+      }
+    },
+    function ({ request, getResolve }, callback) {
+      if (request === 'graphql') {
+        const resolve = getResolve()
+        // dummy call (some-module should be resolved on __dirname)
+        resolve(__dirname, 'some-module').then(
+          function () { callback(null, 'graphql') },
+          function (e) { callback(e) }
+        )
+      }
+    },
+  ],
+}
diff --git a/resolvers/webpack/test/files/webpack.config.webpack5.js b/resolvers/webpack/test/files/webpack.config.webpack5.js
new file mode 100644
index 000000000..88a12567a
--- /dev/null
+++ b/resolvers/webpack/test/files/webpack.config.webpack5.js
@@ -0,0 +1,27 @@
+module.exports = {
+  externals: [
+    { 'jquery': 'jQuery' },
+    'bootstrap',
+    function ({ request }, callback) {
+      if (request === 'underscore') {
+        return callback(null, 'underscore')
+      }
+      callback()
+    },
+    function ({ request, getResolve }, callback) {
+      if (request === 'graphql') {
+        const resolve = getResolve()
+        // dummy call (some-module should be resolved on __dirname)
+        resolve(__dirname, 'some-module', function (err, value) {
+          if (err) {
+            callback(err)
+          } else {
+            callback(null, 'graphql')
+          }
+        })
+      } else {
+        callback()
+      }
+    },
+  ],
+}
diff --git a/resolvers/webpack/test/files/webpack.function.config.multiple.js b/resolvers/webpack/test/files/webpack.function.config.multiple.js
index 4dbc94bbc..8ab982bbc 100644
--- a/resolvers/webpack/test/files/webpack.function.config.multiple.js
+++ b/resolvers/webpack/test/files/webpack.function.config.multiple.js
@@ -7,6 +7,7 @@ module.exports = [function(env) {
       alias: {
         'foo': path.join(__dirname, 'some', 'goofy', 'path', 'foo.js'),
         'bar': env ? path.join(__dirname, 'some', 'goofy', 'path', 'bar.js') : undefined,
+        'baz': path.join(__dirname, 'some', 'goofy', 'path', 'foo.js'),
         'some-alias': path.join(__dirname, 'some'),
       },
       modules: [
diff --git a/resolvers/webpack/test/loaders.js b/resolvers/webpack/test/loaders.js
index 1d588c77c..e250894a5 100644
--- a/resolvers/webpack/test/loaders.js
+++ b/resolvers/webpack/test/loaders.js
@@ -1,35 +1,33 @@
-var chai =  require('chai')
-  , expect = chai.expect
-  , path = require('path')
+'use strict';
 
-var resolve = require('../index').resolve
+const chai =  require('chai');
+const expect = chai.expect;
+const path = require('path');
 
+const resolve = require('../index').resolve;
 
-var file = path.join(__dirname, 'files', 'dummy.js')
+const file = path.join(__dirname, 'files', 'dummy.js');
 
-describe("inline loader syntax", function () {
-
-  it("strips bang-loaders", function () {
+describe('inline loader syntax', function () {
+  it('strips bang-loaders', function () {
     expect(resolve('css-loader!./src/main-module', file)).to.have.property('path')
-      .and.equal(path.join(__dirname, 'files', 'src', 'main-module.js'))
-  })
+      .and.equal(path.join(__dirname, 'files', 'src', 'main-module.js'));
+  });
 
-  it("strips loader query string", function () {
+  it('strips loader query string', function () {
     expect(resolve('some-loader?param=value!./src/main-module', file)).to.have.property('path')
-      .and.equal(path.join(__dirname, 'files', 'src', 'main-module.js'))
-  })
+      .and.equal(path.join(__dirname, 'files', 'src', 'main-module.js'));
+  });
 
-  it("strips resource query string", function () {
+  it('strips resource query string', function () {
     expect(resolve('./src/main-module?otherParam=otherValue', file))
       .to.have.property('path')
-      .and.equal(path.join(__dirname, 'files', 'src', 'main-module.js'))
-  })
+      .and.equal(path.join(__dirname, 'files', 'src', 'main-module.js'));
+  });
 
-  it("strips everything", function () {
+  it('strips everything', function () {
     expect(resolve('some-loader?param=value!./src/main-module?otherParam=otherValue', file))
       .to.have.property('path')
-      .and.equal(path.join(__dirname, 'files', 'src', 'main-module.js'))
-  })
-
-})
-
+      .and.equal(path.join(__dirname, 'files', 'src', 'main-module.js'));
+  });
+});
diff --git a/resolvers/webpack/test/modules.js b/resolvers/webpack/test/modules.js
index 753ceffc0..066e52a6f 100644
--- a/resolvers/webpack/test/modules.js
+++ b/resolvers/webpack/test/modules.js
@@ -1,21 +1,23 @@
-var chai =  require('chai')
-  , expect = chai.expect
-  , path = require('path')
+'use strict';
 
-var resolve = require('../index').resolve
+const chai =  require('chai');
+const expect = chai.expect;
+const path = require('path');
 
-var file = path.join(__dirname, 'files', 'dummy.js')
+const resolve = require('../index').resolve;
 
-describe("resolve.moduleDirectories", function () {
+const file = path.join(__dirname, 'files', 'dummy.js');
 
-  it("finds a node module", function () {
+describe('resolve.moduleDirectories', function () {
+
+  it('finds a node module', function () {
     expect(resolve('some-module', file)).to.have.property('path')
-      .and.equal(path.join(__dirname, 'files', 'node_modules', 'some-module', 'index.js'))
-  })
+      .and.equal(path.join(__dirname, 'files', 'node_modules', 'some-module', 'index.js'));
+  });
 
-  it("finds a bower module", function () {
+  it('finds a bower module', function () {
     expect(resolve('typeahead.js', file)).to.have.property('path')
-      .and.equal(path.join(__dirname, 'files', 'bower_components', 'typeahead.js'))
-  })
+      .and.equal(path.join(__dirname, 'files', 'bower_components', 'typeahead.js'));
+  });
 
-})
+});
diff --git a/resolvers/webpack/test/package-mains/module-and-jsnext/package.json b/resolvers/webpack/test/package-mains/module-and-jsnext/package.json
new file mode 100644
index 000000000..4b1355aae
--- /dev/null
+++ b/resolvers/webpack/test/package-mains/module-and-jsnext/package.json
@@ -0,0 +1,5 @@
+{
+  "main": "lib/index.js",
+  "module": "src/index.js",
+  "jsnext:main": "lib/index.js"
+}
diff --git a/resolvers/webpack/test/package-mains/module-and-jsnext/src/index.js b/resolvers/webpack/test/package-mains/module-and-jsnext/src/index.js
new file mode 100644
index 000000000..e69de29bb
diff --git a/resolvers/webpack/test/package-mains/module-broken/main.js b/resolvers/webpack/test/package-mains/module-broken/main.js
new file mode 100644
index 000000000..f053ebf79
--- /dev/null
+++ b/resolvers/webpack/test/package-mains/module-broken/main.js
@@ -0,0 +1 @@
+module.exports = {};
diff --git a/resolvers/webpack/test/package-mains/module-broken/package.json b/resolvers/webpack/test/package-mains/module-broken/package.json
new file mode 100644
index 000000000..36a318386
--- /dev/null
+++ b/resolvers/webpack/test/package-mains/module-broken/package.json
@@ -0,0 +1,4 @@
+{
+  "main": "./main.js",
+  "module": "./doesNotExist.js"
+}
diff --git a/resolvers/webpack/test/package-mains/webpack.alt.config.js b/resolvers/webpack/test/package-mains/webpack.alt.config.js
index c31e49a05..b955d9d37 100644
--- a/resolvers/webpack/test/package-mains/webpack.alt.config.js
+++ b/resolvers/webpack/test/package-mains/webpack.alt.config.js
@@ -1,3 +1,3 @@
 exports.resolve = {
-  packageMains: ["main"], // override
-}
+  packageMains: ['main'], // override
+};
diff --git a/resolvers/webpack/test/packageMains.js b/resolvers/webpack/test/packageMains.js
index 63f5b6893..d3ddad9da 100644
--- a/resolvers/webpack/test/packageMains.js
+++ b/resolvers/webpack/test/packageMains.js
@@ -1,47 +1,57 @@
-var chai = require('chai')
-  , expect = chai.expect
-  , path = require('path')
-
-var webpack = require('../')
-
-var file = path.join(__dirname, 'package-mains', 'dummy.js')
-
-
-describe("packageMains", function () {
-
-  it("captures module", function () {
-    expect(webpack.resolve('./module', file)).property('path')
-      .to.equal(path.join(__dirname, 'package-mains', 'module', 'src', 'index.js'))
-  })
-
-  it("captures jsnext", function () {
-    expect(webpack.resolve('./jsnext', file)).property('path')
-      .to.equal(path.join(__dirname, 'package-mains', 'jsnext', 'src', 'index.js'))
-  })
-
-  it("captures webpack", function () {
-    expect(webpack.resolve('./webpack', file)).property('path')
-      .to.equal(path.join(__dirname, 'package-mains', 'webpack', 'webpack.js'))
-  })
-
-  it("captures jam (array path)", function () {
-    expect(webpack.resolve('./jam', file)).property('path')
-      .to.equal(path.join(__dirname, 'package-mains', 'jam', 'jam.js'))
-  })
-
-  it("uses configured packageMains, if provided", function () {
-    expect(webpack.resolve('./webpack', file, { config: 'webpack.alt.config.js' })).property('path')
-      .to.equal(path.join(__dirname, 'package-mains', 'webpack', 'index.js'))
-  })
-
-  it("always defers to module, regardless of config", function () {
-    expect(webpack.resolve('./module', file, { config: 'webpack.alt.config.js' })).property('path')
-      .to.equal(path.join(__dirname, 'package-mains', 'module', 'src', 'index.js'))
-  })
-
-  it("always defers to jsnext:main, regardless of config", function () {
-    expect(webpack.resolve('./jsnext', file, { config: 'webpack.alt.config.js' })).property('path')
-      .to.equal(path.join(__dirname, 'package-mains', 'jsnext', 'src', 'index.js'))
-  })
-
-})
+'use strict';
+
+const chai = require('chai');
+const expect = chai.expect;
+const path = require('path');
+
+const resolver = require('../');
+
+const file = path.join(__dirname, 'package-mains', 'dummy.js');
+
+describe('packageMains', function () {
+
+  it('captures module', function () {
+    expect(resolver.resolve('./module', file)).property('path')
+      .to.equal(path.join(__dirname, 'package-mains', 'module', 'src', 'index.js'));
+  });
+
+  it('captures jsnext', function () {
+    expect(resolver.resolve('./jsnext', file)).property('path')
+      .to.equal(path.join(__dirname, 'package-mains', 'jsnext', 'src', 'index.js'));
+  });
+
+  it('captures module instead of jsnext', function () {
+    expect(resolver.resolve('./module-and-jsnext', file)).property('path')
+      .to.equal(path.join(__dirname, 'package-mains', 'module-and-jsnext', 'src', 'index.js'));
+  });
+
+  it('falls back from a missing "module" to "main"', function () {
+    expect(resolver.resolve('./module-broken', file)).property('path')
+      .to.equal(path.join(__dirname, 'package-mains', 'module-broken', 'main.js'));
+  });
+
+  it('captures webpack', function () {
+    expect(resolver.resolve('./webpack', file)).property('path')
+      .to.equal(path.join(__dirname, 'package-mains', 'webpack', 'webpack.js'));
+  });
+
+  it('captures jam (array path)', function () {
+    expect(resolver.resolve('./jam', file)).property('path')
+      .to.equal(path.join(__dirname, 'package-mains', 'jam', 'jam.js'));
+  });
+
+  it('uses configured packageMains, if provided', function () {
+    expect(resolver.resolve('./webpack', file, { config: 'webpack.alt.config.js' })).property('path')
+      .to.equal(path.join(__dirname, 'package-mains', 'webpack', 'index.js'));
+  });
+
+  it('always defers to module, regardless of config', function () {
+    expect(resolver.resolve('./module', file, { config: 'webpack.alt.config.js' })).property('path')
+      .to.equal(path.join(__dirname, 'package-mains', 'module', 'src', 'index.js'));
+  });
+
+  it('always defers to jsnext:main, regardless of config', function () {
+    expect(resolver.resolve('./jsnext', file, { config: 'webpack.alt.config.js' })).property('path')
+      .to.equal(path.join(__dirname, 'package-mains', 'jsnext', 'src', 'index.js'));
+  });
+});
diff --git a/resolvers/webpack/test/plugins.js b/resolvers/webpack/test/plugins.js
index f37b94518..b964e7c30 100644
--- a/resolvers/webpack/test/plugins.js
+++ b/resolvers/webpack/test/plugins.js
@@ -1,29 +1,31 @@
-var chai = require('chai')
-  , expect = chai.expect
-  , path = require('path')
+'use strict';
 
-var webpack = require('../index')
+const chai = require('chai');
+const expect = chai.expect;
+const path = require('path');
 
-var file = path.join(__dirname, 'files', 'dummy.js')
+const webpack = require('../index');
 
-describe("plugins", function () {
-  var resolved, aliasResolved
+const file = path.join(__dirname, 'files', 'dummy.js');
+
+describe('plugins', function () {
+  let resolved; let aliasResolved;
 
   before(function () {
-    resolved = webpack.resolve('./some/bar', file)
-    aliasResolved = webpack.resolve('some-alias/bar', file)
-  })
+    resolved = webpack.resolve('./some/bar', file);
+    aliasResolved = webpack.resolve('some-alias/bar', file);
+  });
 
-  it("work", function () {
-    expect(resolved).to.have.property('found', true)
-  })
+  it('work', function () {
+    expect(resolved).to.have.property('found', true);
+  });
 
-  it("is correct", function () {
+  it('is correct', function () {
     expect(resolved).to.have.property('path')
-      .and.equal(path.join(__dirname, 'files', 'some', 'bar', 'bar.js'))
-  })
+      .and.equal(path.join(__dirname, 'files', 'some', 'bar', 'bar.js'));
+  });
 
-  it("work with alias", function () {
-    expect(aliasResolved).to.have.property('found', true)
-  })
-})
+  it('work with alias', function () {
+    expect(aliasResolved).to.have.property('found', true);
+  });
+});
diff --git a/resolvers/webpack/test/root.js b/resolvers/webpack/test/root.js
index 4839f3b89..194bb8fc8 100644
--- a/resolvers/webpack/test/root.js
+++ b/resolvers/webpack/test/root.js
@@ -1,36 +1,46 @@
-var chai =  require('chai')
-  , expect = chai.expect
-  , path = require('path')
+'use strict';
 
-var resolve = require('../index').resolve
+const chai =  require('chai');
+const expect = chai.expect;
+const path = require('path');
 
+const resolve = require('../index').resolve;
 
-var file = path.join(__dirname, 'files', 'src', 'dummy.js')
+const file = path.join(__dirname, 'files', 'src', 'dummy.js');
+const webpackDir = path.join(__dirname, 'different-package-location');
 
-describe("root", function () {
-  it("works", function () {
+describe('root', function () {
+  it('works', function () {
     expect(resolve('main-module', file)).property('path')
-      .to.equal(path.join(__dirname, 'files', 'src', 'main-module.js'))
-  })
-  it("really works", function () {
+      .to.equal(path.join(__dirname, 'files', 'src', 'main-module.js'));
+  });
+  it('really works', function () {
     expect(resolve('jsx/some-file', file)).property('path')
-      .to.equal(path.join(__dirname, 'files', 'src', 'jsx', 'some-file.js'))
-  })
-  it("supports definition as an array", function () {
-    expect(resolve('main-module', file, { config: "webpack.array-root.config.js" }))
+      .to.equal(path.join(__dirname, 'files', 'src', 'jsx', 'some-file.js'));
+  });
+  it('supports definition as an array', function () {
+    expect(resolve('main-module', file, { config: 'webpack.array-root.config.js' }))
       .property('path')
-      .to.equal(path.join(__dirname, 'files', 'src', 'main-module.js'))
-    expect(resolve('typeahead', file, { config: "webpack.array-root.config.js" }))
+      .to.equal(path.join(__dirname, 'files', 'src', 'main-module.js'));
+    expect(resolve('typeahead', file, { config: 'webpack.array-root.config.js' }))
       .property('path')
-      .to.equal(path.join(__dirname, 'files', 'bower_components', 'typeahead.js'))
-  })
-  it("supports definition as a function", function () {
-    expect(resolve('main-module', file, { config: "webpack.function.config.js" }))
+      .to.equal(path.join(__dirname, 'files', 'bower_components', 'typeahead.js'));
+  });
+  it('supports definition as a function', function () {
+    expect(resolve('main-module', file, { config: 'webpack.function.config.js' }))
       .property('path')
-      .to.equal(path.join(__dirname, 'files', 'src', 'main-module.js'))
-    expect(resolve('typeahead', file, { config: "webpack.function.config.js" }))
+      .to.equal(path.join(__dirname, 'files', 'src', 'main-module.js'));
+    expect(resolve('typeahead', file, { config: 'webpack.function.config.js' }))
       .property('path')
-      .to.equal(path.join(__dirname, 'files', 'bower_components', 'typeahead.js'))
-  })
-
-})
+      .to.equal(path.join(__dirname, 'files', 'bower_components', 'typeahead.js'));
+  });
+  it('supports passing a different directory to load webpack from', function () {
+    // Webpack should still be able to resolve the config here
+    expect(resolve('main-module', file, { config: 'webpack.config.js', cwd: webpackDir }))
+      .property('path')
+      .to.equal(path.join(__dirname, 'files', 'src', 'main-module.js'));
+    expect(resolve('typeahead', file, { config: 'webpack.config.js', cwd: webpackDir }))
+      .property('path')
+      .to.equal(path.join(__dirname, 'files', 'bower_components', 'typeahead.js'));
+  });
+});
diff --git a/scripts/GetCI/GetCI.psm1 b/scripts/GetCI/GetCI.psm1
new file mode 100644
index 000000000..818ce32fe
--- /dev/null
+++ b/scripts/GetCI/GetCI.psm1
@@ -0,0 +1,12 @@
+function Get-CICommand {
+    $arguments = [System.Collections.ArrayList]$args
+    if ($env:CONFIGURATION -eq "WSL") {
+        $arguments.Insert(0, "wsl");
+    } else {
+        if ($arguments[0] -eq "sudo") {
+        $arguments.RemoveAt(0)
+        }
+    }
+    $arguments.Insert(0, "echo");
+    cmd /c $arguments[0] $arguments[1..$($arguments.Count - 1)];
+}
diff --git a/scripts/ci.cmd b/scripts/ci.cmd
new file mode 100644
index 000000000..04ac20265
--- /dev/null
+++ b/scripts/ci.cmd
@@ -0,0 +1,8 @@
+@echo off
+
+FOR /F "tokens=* usebackq" %%F IN (`powershell -Command "& { Import-Module %~dp0GetCI; Get-CICommand %* }"`) DO (
+    SET args=%%F
+)
+
+echo ^> cmd /c %args%
+cmd /c %args%
diff --git a/scripts/copyMetafiles.js b/scripts/copyMetafiles.js
new file mode 100644
index 000000000..01ff4f36f
--- /dev/null
+++ b/scripts/copyMetafiles.js
@@ -0,0 +1,22 @@
+import path from 'path';
+import copyFileSync from 'fs-copy-file-sync';
+import resolverDirectories from './resolverDirectories';
+
+const files = [
+  'LICENSE',
+  '.npmrc',
+];
+
+const directories = [].concat(
+  'memo-parser',
+  resolverDirectories,
+  'utils',
+);
+
+for (const directory of directories) {
+  for (const file of files) {
+    const destination = path.join(directory, file);
+    copyFileSync(file, destination);
+    console.log(`${file} -> ${destination}`);
+  }
+}
diff --git a/scripts/resolverDirectories.js b/scripts/resolverDirectories.js
new file mode 100644
index 000000000..f0c03a3cc
--- /dev/null
+++ b/scripts/resolverDirectories.js
@@ -0,0 +1,3 @@
+import glob from 'glob';
+
+export default glob.sync('./resolvers/*/');
diff --git a/scripts/testAll.js b/scripts/testAll.js
new file mode 100644
index 000000000..0e4a12c68
--- /dev/null
+++ b/scripts/testAll.js
@@ -0,0 +1,20 @@
+import { spawnSync } from 'child_process';
+import npmWhich from 'npm-which';
+import resolverDirectories from './resolverDirectories';
+
+const npmPath = npmWhich(__dirname).sync('npm');
+const spawnOptions = {
+  stdio: 'inherit',
+};
+
+spawnSync(
+  npmPath,
+  ['test'],
+  { cwd: __dirname, ...spawnOptions });
+
+for (const resolverDir of resolverDirectories) {
+  spawnSync(
+    npmPath,
+    ['test'],
+    { cwd: resolverDir, ...spawnOptions });
+}
diff --git a/src/.eslintrc b/src/.eslintrc
deleted file mode 100644
index 340d66bf6..000000000
--- a/src/.eslintrc
+++ /dev/null
@@ -1,3 +0,0 @@
----
-rules:
-  comma-dangle: [1, "always-multiline"]
diff --git a/src/ExportMap.js b/src/ExportMap.js
deleted file mode 100644
index ebeb4fad7..000000000
--- a/src/ExportMap.js
+++ /dev/null
@@ -1,614 +0,0 @@
-import fs from 'fs'
-
-import doctrine from 'doctrine'
-
-import debug from 'debug'
-
-import { SourceCode } from 'eslint'
-
-import parse from 'eslint-module-utils/parse'
-import resolve from 'eslint-module-utils/resolve'
-import isIgnored, { hasValidExtension } from 'eslint-module-utils/ignore'
-
-import { hashObject } from 'eslint-module-utils/hash'
-import * as unambiguous from 'eslint-module-utils/unambiguous'
-
-const log = debug('eslint-plugin-import:ExportMap')
-
-const exportCache = new Map()
-
-export default class ExportMap {
-  constructor(path) {
-    this.path = path
-    this.namespace = new Map()
-    // todo: restructure to key on path, value is resolver + map of names
-    this.reexports = new Map()
-    /**
-     * star-exports
-     * @type {Set} of () => ExportMap
-     */
-    this.dependencies = new Set()
-    /**
-     * dependencies of this module that are not explicitly re-exported
-     * @type {Map} from path = () => ExportMap
-     */
-    this.imports = new Map()
-    this.errors = []
-  }
-
-  get hasDefault() { return this.get('default') != null } // stronger than this.has
-
-  get size() {
-    let size = this.namespace.size + this.reexports.size
-    this.dependencies.forEach(dep => {
-      const d = dep()
-      // CJS / ignored dependencies won't exist (#717)
-      if (d == null) return
-      size += d.size
-    })
-    return size
-  }
-
-  /**
-   * Note that this does not check explicitly re-exported names for existence
-   * in the base namespace, but it will expand all `export * from '...'` exports
-   * if not found in the explicit namespace.
-   * @param  {string}  name
-   * @return {Boolean} true if `name` is exported by this module.
-   */
-  has(name) {
-    if (this.namespace.has(name)) return true
-    if (this.reexports.has(name)) return true
-
-    // default exports must be explicitly re-exported (#328)
-    if (name !== 'default') {
-      for (let dep of this.dependencies) {
-        let innerMap = dep()
-
-        // todo: report as unresolved?
-        if (!innerMap) continue
-
-        if (innerMap.has(name)) return true
-      }
-    }
-
-    return false
-  }
-
-  /**
-   * ensure that imported name fully resolves.
-   * @param  {[type]}  name [description]
-   * @return {Boolean}      [description]
-   */
-  hasDeep(name) {
-    if (this.namespace.has(name)) return { found: true, path: [this] }
-
-    if (this.reexports.has(name)) {
-      const reexports = this.reexports.get(name)
-          , imported = reexports.getImport()
-
-      // if import is ignored, return explicit 'null'
-      if (imported == null) return { found: true, path: [this] }
-
-      // safeguard against cycles, only if name matches
-      if (imported.path === this.path && reexports.local === name) {
-        return { found: false, path: [this] }
-      }
-
-      const deep = imported.hasDeep(reexports.local)
-      deep.path.unshift(this)
-
-      return deep
-    }
-
-
-    // default exports must be explicitly re-exported (#328)
-    if (name !== 'default') {
-      for (let dep of this.dependencies) {
-        let innerMap = dep()
-        // todo: report as unresolved?
-        if (!innerMap) continue
-
-        // safeguard against cycles
-        if (innerMap.path === this.path) continue
-
-        let innerValue = innerMap.hasDeep(name)
-        if (innerValue.found) {
-          innerValue.path.unshift(this)
-          return innerValue
-        }
-      }
-    }
-
-    return { found: false, path: [this] }
-  }
-
-  get(name) {
-    if (this.namespace.has(name)) return this.namespace.get(name)
-
-    if (this.reexports.has(name)) {
-      const reexports = this.reexports.get(name)
-          , imported = reexports.getImport()
-
-      // if import is ignored, return explicit 'null'
-      if (imported == null) return null
-
-      // safeguard against cycles, only if name matches
-      if (imported.path === this.path && reexports.local === name) return undefined
-
-      return imported.get(reexports.local)
-    }
-
-    // default exports must be explicitly re-exported (#328)
-    if (name !== 'default') {
-      for (let dep of this.dependencies) {
-        let innerMap = dep()
-        // todo: report as unresolved?
-        if (!innerMap) continue
-
-        // safeguard against cycles
-        if (innerMap.path === this.path) continue
-
-        let innerValue = innerMap.get(name)
-        if (innerValue !== undefined) return innerValue
-      }
-    }
-
-    return undefined
-  }
-
-  forEach(callback, thisArg) {
-    this.namespace.forEach((v, n) =>
-      callback.call(thisArg, v, n, this))
-
-    this.reexports.forEach((reexports, name) => {
-      const reexported = reexports.getImport()
-      // can't look up meta for ignored re-exports (#348)
-      callback.call(thisArg, reexported && reexported.get(reexports.local), name, this)
-    })
-
-    this.dependencies.forEach(dep => {
-      const d = dep()
-      // CJS / ignored dependencies won't exist (#717)
-      if (d == null) return
-
-      d.forEach((v, n) =>
-        n !== 'default' && callback.call(thisArg, v, n, this))
-    })
-  }
-
-  // todo: keys, values, entries?
-
-  reportErrors(context, declaration) {
-    context.report({
-      node: declaration.source,
-      message: `Parse errors in imported module '${declaration.source.value}': ` +
-                  `${this.errors
-                        .map(e => `${e.message} (${e.lineNumber}:${e.column})`)
-                        .join(', ')}`,
-    })
-  }
-}
-
-/**
- * parse docs from the first node that has leading comments
- */
-function captureDoc(source, docStyleParsers, ...nodes) {
-  const metadata = {}
-
-  // 'some' short-circuits on first 'true'
-  nodes.some(n => {
-    try {
-
-      let leadingComments
-
-      // n.leadingComments is legacy `attachComments` behavior
-      if ('leadingComments' in n) {
-        leadingComments = n.leadingComments
-      } else if (n.range) {
-        leadingComments = source.getCommentsBefore(n)
-      }
-
-      if (!leadingComments || leadingComments.length === 0) return false
-
-      for (let name in docStyleParsers) {
-        const doc = docStyleParsers[name](leadingComments)
-        if (doc) {
-          metadata.doc = doc
-        }
-      }
-
-      return true
-    } catch (err) {
-      return false
-    }
-  })
-
-  return metadata
-}
-
-const availableDocStyleParsers = {
-  jsdoc: captureJsDoc,
-  tomdoc: captureTomDoc,
-}
-
-/**
- * parse JSDoc from leading comments
- * @param  {...[type]} comments [description]
- * @return {{doc: object}}
- */
-function captureJsDoc(comments) {
-  let doc
-
-  // capture XSDoc
-  comments.forEach(comment => {
-    // skip non-block comments
-    if (comment.type !== 'Block') return
-    try {
-      doc = doctrine.parse(comment.value, { unwrap: true })
-    } catch (err) {
-      /* don't care, for now? maybe add to `errors?` */
-    }
-  })
-
-  return doc
-}
-
-/**
-  * parse TomDoc section from comments
-  */
-function captureTomDoc(comments) {
-  // collect lines up to first paragraph break
-  const lines = []
-  for (let i = 0; i < comments.length; i++) {
-    const comment = comments[i]
-    if (comment.value.match(/^\s*$/)) break
-    lines.push(comment.value.trim())
-  }
-
-  // return doctrine-like object
-  const statusMatch = lines.join(' ').match(/^(Public|Internal|Deprecated):\s*(.+)/)
-  if (statusMatch) {
-    return {
-      description: statusMatch[2],
-      tags: [{
-        title: statusMatch[1].toLowerCase(),
-        description: statusMatch[2],
-      }],
-    }
-  }
-}
-
-ExportMap.get = function (source, context) {
-  const path = resolve(source, context)
-  if (path == null) return null
-
-  return ExportMap.for(childContext(path, context))
-}
-
-ExportMap.for = function (context) {
-  const { path } = context
-
-  const cacheKey = hashObject(context).digest('hex')
-  let exportMap = exportCache.get(cacheKey)
-
-  // return cached ignore
-  if (exportMap === null) return null
-
-  const stats = fs.statSync(path)
-  if (exportMap != null) {
-    // date equality check
-    if (exportMap.mtime - stats.mtime === 0) {
-      return exportMap
-    }
-    // future: check content equality?
-  }
-
-  // check valid extensions first
-  if (!hasValidExtension(path, context)) {
-    exportCache.set(cacheKey, null)
-    return null
-  }
-
-  const content = fs.readFileSync(path, { encoding: 'utf8' })
-
-  // check for and cache ignore
-  if (isIgnored(path, context) || !unambiguous.test(content)) {
-    log('ignored path due to unambiguous regex or ignore settings:', path)
-    exportCache.set(cacheKey, null)
-    return null
-  }
-
-  log('cache miss', cacheKey, 'for path', path)
-  exportMap = ExportMap.parse(path, content, context)
-
-  // ambiguous modules return null
-  if (exportMap == null) return null
-
-  exportMap.mtime = stats.mtime
-
-  exportCache.set(cacheKey, exportMap)
-  return exportMap
-}
-
-
-ExportMap.parse = function (path, content, context) {
-  var m = new ExportMap(path)
-
-  try {
-    var ast = parse(path, content, context)
-  } catch (err) {
-    log('parse error:', path, err)
-    m.errors.push(err)
-    return m // can't continue
-  }
-
-  if (!unambiguous.isModule(ast)) return null
-
-  const docstyle = (context.settings && context.settings['import/docstyle']) || ['jsdoc']
-  const docStyleParsers = {}
-  docstyle.forEach(style => {
-    docStyleParsers[style] = availableDocStyleParsers[style]
-  })
-
-  // attempt to collect module doc
-  if (ast.comments) {
-    ast.comments.some(c => {
-      if (c.type !== 'Block') return false
-      try {
-        const doc = doctrine.parse(c.value, { unwrap: true })
-        if (doc.tags.some(t => t.title === 'module')) {
-          m.doc = doc
-          return true
-        }
-      } catch (err) { /* ignore */ }
-      return false
-    })
-  }
-
-  const namespaces = new Map()
-
-  function remotePath(value) {
-    return resolve.relative(value, path, context.settings)
-  }
-
-  function resolveImport(value) {
-    const rp = remotePath(value)
-    if (rp == null) return null
-    return ExportMap.for(childContext(rp, context))
-  }
-
-  function getNamespace(identifier) {
-    if (!namespaces.has(identifier.name)) return
-
-    return function () {
-      return resolveImport(namespaces.get(identifier.name))
-    }
-  }
-
-  function addNamespace(object, identifier) {
-    const nsfn = getNamespace(identifier)
-    if (nsfn) {
-      Object.defineProperty(object, 'namespace', { get: nsfn })
-    }
-
-    return object
-  }
-
-  function captureDependency(declaration) {
-    if (declaration.source == null) return null
-    const importedSpecifiers = new Set()
-    const supportedTypes = new Set(['ImportDefaultSpecifier', 'ImportNamespaceSpecifier'])
-    if (declaration.specifiers) {
-      declaration.specifiers.forEach(specifier => {
-        if (supportedTypes.has(specifier.type)) {
-          importedSpecifiers.add(specifier.type)
-        }
-        if (specifier.type === 'ImportSpecifier') {
-          importedSpecifiers.add(specifier.imported.name)
-        }
-      })
-    }
-
-    const p = remotePath(declaration.source.value)
-    if (p == null) return null
-    const existing = m.imports.get(p)
-    if (existing != null) return existing.getter
-
-    const getter = thunkFor(p, context)
-    m.imports.set(p, {
-      getter,
-      source: {  // capturing actual node reference holds full AST in memory!
-        value: declaration.source.value,
-        loc: declaration.source.loc,
-      },
-      importedSpecifiers,
-    })
-    return getter
-  }
-
-  const source = makeSourceCode(content, ast)
-
-  ast.body.forEach(function (n) {
-
-    if (n.type === 'ExportDefaultDeclaration') {
-      const exportMeta = captureDoc(source, docStyleParsers, n)
-      if (n.declaration.type === 'Identifier') {
-        addNamespace(exportMeta, n.declaration)
-      }
-      m.namespace.set('default', exportMeta)
-      return
-    }
-
-    if (n.type === 'ExportAllDeclaration') {
-      const getter = captureDependency(n)
-      if (getter) m.dependencies.add(getter)
-      return
-    }
-
-    // capture namespaces in case of later export
-    if (n.type === 'ImportDeclaration') {
-      captureDependency(n)
-      let ns
-      if (n.specifiers.some(s => s.type === 'ImportNamespaceSpecifier' && (ns = s))) {
-        namespaces.set(ns.local.name, n.source.value)
-      }
-      return
-    }
-
-    if (n.type === 'ExportNamedDeclaration') {
-      // capture declaration
-      if (n.declaration != null) {
-        switch (n.declaration.type) {
-          case 'FunctionDeclaration':
-          case 'ClassDeclaration':
-          case 'TypeAlias': // flowtype with babel-eslint parser
-          case 'InterfaceDeclaration':
-          case 'DeclareFunction':
-          case 'TSDeclareFunction':
-          case 'TSEnumDeclaration':
-          case 'TSTypeAliasDeclaration':
-          case 'TSInterfaceDeclaration':
-          case 'TSAbstractClassDeclaration':
-          case 'TSModuleDeclaration':
-            m.namespace.set(n.declaration.id.name, captureDoc(source, docStyleParsers, n))
-            break
-          case 'VariableDeclaration':
-            n.declaration.declarations.forEach((d) =>
-              recursivePatternCapture(d.id,
-                id => m.namespace.set(id.name, captureDoc(source, docStyleParsers, d, n))))
-            break
-        }
-      }
-
-      const nsource = n.source && n.source.value
-      n.specifiers.forEach((s) => {
-        const exportMeta = {}
-        let local
-
-        switch (s.type) {
-          case 'ExportDefaultSpecifier':
-            if (!n.source) return
-            local = 'default'
-            break
-          case 'ExportNamespaceSpecifier':
-            m.namespace.set(s.exported.name, Object.defineProperty(exportMeta, 'namespace', {
-              get() { return resolveImport(nsource) },
-            }))
-            return
-          case 'ExportSpecifier':
-            if (!n.source) {
-              m.namespace.set(s.exported.name, addNamespace(exportMeta, s.local))
-              return
-            }
-            // else falls through
-          default:
-            local = s.local.name
-            break
-        }
-
-        // todo: JSDoc
-        m.reexports.set(s.exported.name, { local, getImport: () => resolveImport(nsource) })
-      })
-    }
-
-    // This doesn't declare anything, but changes what's being exported.
-    if (n.type === 'TSExportAssignment') {
-      const moduleDecl = ast.body.find((bodyNode) =>
-        bodyNode.type === 'TSModuleDeclaration' && bodyNode.id.name === n.expression.name
-      )
-      if (moduleDecl && moduleDecl.body && moduleDecl.body.body) {
-        moduleDecl.body.body.forEach((moduleBlockNode) => {
-          // Export-assignment exports all members in the namespace, explicitly exported or not.
-          const exportedDecl = moduleBlockNode.type === 'ExportNamedDeclaration' ?
-            moduleBlockNode.declaration :
-            moduleBlockNode
-
-          if (exportedDecl.type === 'VariableDeclaration') {
-            exportedDecl.declarations.forEach((decl) =>
-              recursivePatternCapture(decl.id,(id) => m.namespace.set(
-                id.name,
-                captureDoc(source, docStyleParsers, decl, exportedDecl, moduleBlockNode))
-              )
-            )
-          } else {
-            m.namespace.set(
-              exportedDecl.id.name,
-              captureDoc(source, docStyleParsers, moduleBlockNode))
-          }
-        })
-      }
-    }
-  })
-
-  return m
-}
-
-/**
- * The creation of this closure is isolated from other scopes
- * to avoid over-retention of unrelated variables, which has
- * caused memory leaks. See #1266.
- */
-function thunkFor(p, context) {
-  return () => ExportMap.for(childContext(p, context))
-}
-
-
-/**
- * Traverse a pattern/identifier node, calling 'callback'
- * for each leaf identifier.
- * @param  {node}   pattern
- * @param  {Function} callback
- * @return {void}
- */
-export function recursivePatternCapture(pattern, callback) {
-  switch (pattern.type) {
-    case 'Identifier': // base case
-      callback(pattern)
-      break
-
-    case 'ObjectPattern':
-      pattern.properties.forEach(p => {
-        recursivePatternCapture(p.value, callback)
-      })
-      break
-
-    case 'ArrayPattern':
-      pattern.elements.forEach((element) => {
-        if (element == null) return
-        recursivePatternCapture(element, callback)
-      })
-      break
-
-    case 'AssignmentPattern':
-      callback(pattern.left)
-      break
-  }
-}
-
-/**
- * don't hold full context object in memory, just grab what we need.
- */
-function childContext(path, context) {
-  const { settings, parserOptions, parserPath } = context
-  return {
-    settings,
-    parserOptions,
-    parserPath,
-    path,
-  }
-}
-
-
-/**
- * sometimes legacy support isn't _that_ hard... right?
- */
-function makeSourceCode(text, ast) {
-  if (SourceCode.length > 1) {
-    // ESLint 3
-    return new SourceCode(text, ast)
-  } else {
-    // ESLint 4, 5
-    return new SourceCode({ text, ast })
-  }
-}
diff --git a/src/core/importType.js b/src/core/importType.js
index b948ea2bb..32e200f1d 100644
--- a/src/core/importType.js
+++ b/src/core/importType.js
@@ -1,88 +1,127 @@
-import coreModules from 'resolve/lib/core'
-import { join } from 'path'
+import { isAbsolute as nodeIsAbsolute, relative, resolve as nodeResolve } from 'path';
+import isCoreModule from 'is-core-module';
 
-import resolve from 'eslint-module-utils/resolve'
+import resolve from 'eslint-module-utils/resolve';
+import { getContextPackagePath } from './packagePath';
+
+const scopedRegExp = /^@[^/]+\/?[^/]+/;
+export function isScoped(name) {
+  return name && scopedRegExp.test(name);
+}
 
 function baseModule(name) {
   if (isScoped(name)) {
-    const [scope, pkg] = name.split('/')
-    return `${scope}/${pkg}`
+    const [scope, pkg] = name.split('/');
+    return `${scope}/${pkg}`;
   }
-  const [pkg] = name.split('/')
-  return pkg
+  const [pkg] = name.split('/');
+  return pkg;
+}
+
+function isInternalRegexMatch(name, settings) {
+  const internalScope = settings && settings['import/internal-regex'];
+  return internalScope && new RegExp(internalScope).test(name);
 }
 
 export function isAbsolute(name) {
-  return name.indexOf('/') === 0
+  return typeof name === 'string' && nodeIsAbsolute(name);
 }
 
 // path is defined only when a resolver resolves to a non-standard path
 export function isBuiltIn(name, settings, path) {
-  if (path) return false
-  const base = baseModule(name)
-  const extras = (settings && settings['import/core-modules']) || []
-  return coreModules[base] || extras.indexOf(base) > -1
+  if (path || !name) { return false; }
+  const base = baseModule(name);
+  const extras = settings && settings['import/core-modules'] || [];
+  return isCoreModule(base) || extras.indexOf(base) > -1;
 }
 
-function isExternalPath(path, name, settings) {
-  const folders = (settings && settings['import/external-module-folders']) || ['node_modules']
-
-  // extract the part before the first / (redux-saga/effects => redux-saga)
-  const packageName = name.match(/([^/]+)/)[0]
+const moduleRegExp = /^\w/;
+function isModule(name) {
+  return name && moduleRegExp.test(name);
+}
 
-  return !path || folders.some(folder => -1 < path.indexOf(join(folder, packageName)))
+const moduleMainRegExp = /^[\w]((?!\/).)*$/;
+function isModuleMain(name) {
+  return name && moduleMainRegExp.test(name);
 }
 
-const externalModuleRegExp = /^\w/
-function isExternalModule(name, settings, path) {
-  return externalModuleRegExp.test(name) && isExternalPath(path, name, settings)
+function isRelativeToParent(name) {
+  return (/^\.\.$|^\.\.[\\/]/).test(name);
+}
+const indexFiles = ['.', './', './index', './index.js'];
+function isIndex(name) {
+  return indexFiles.indexOf(name) !== -1;
 }
 
-const externalModuleMainRegExp = /^[\w]((?!\/).)*$/
-export function isExternalModuleMain(name, settings, path) {
-  return externalModuleMainRegExp.test(name) && isExternalPath(path, name, settings)
+function isRelativeToSibling(name) {
+  return (/^\.[\\/]/).test(name);
 }
 
-const scopedRegExp = /^@[^/]+\/[^/]+/
-function isScoped(name) {
-  return scopedRegExp.test(name)
+function isExternalPath(path, context) {
+  if (!path) {
+    return false;
+  }
+
+  const { settings } = context;
+  const packagePath = getContextPackagePath(context);
+
+  if (relative(packagePath, path).startsWith('..')) {
+    return true;
+  }
+
+  const folders = settings && settings['import/external-module-folders'] || ['node_modules'];
+  return folders.some((folder) => {
+    const folderPath = nodeResolve(packagePath, folder);
+    const relativePath = relative(folderPath, path);
+    return !relativePath.startsWith('..');
+  });
 }
 
-const scopedMainRegExp = /^@[^/]+\/?[^/]+$/
-export function isScopedMain(name) {
-  return scopedMainRegExp.test(name)
+function isInternalPath(path, context) {
+  if (!path) {
+    return false;
+  }
+  const packagePath = getContextPackagePath(context);
+  return !relative(packagePath, path).startsWith('../');
 }
 
-function isInternalModule(name, settings, path) {
-  const matchesScopedOrExternalRegExp = scopedRegExp.test(name) || externalModuleRegExp.test(name)
-  return (matchesScopedOrExternalRegExp && !isExternalPath(path, name, settings))
+function isExternalLookingName(name) {
+  return isModule(name) || isScoped(name);
 }
 
-function isRelativeToParent(name) {
-  return /^\.\.[\\/]/.test(name)
+function typeTest(name, context, path) {
+  const { settings } = context;
+  if (isInternalRegexMatch(name, settings)) { return 'internal'; }
+  if (isAbsolute(name, settings, path)) { return 'absolute'; }
+  if (isBuiltIn(name, settings, path)) { return 'builtin'; }
+  if (isRelativeToParent(name, settings, path)) { return 'parent'; }
+  if (isIndex(name, settings, path)) { return 'index'; }
+  if (isRelativeToSibling(name, settings, path)) { return 'sibling'; }
+  if (isExternalPath(path, context)) { return 'external'; }
+  if (isInternalPath(path, context)) { return 'internal'; }
+  if (isExternalLookingName(name)) { return 'external'; }
+  return 'unknown';
 }
 
-const indexFiles = ['.', './', './index', './index.js']
-function isIndex(name) {
-  return indexFiles.indexOf(name) !== -1
+export function isExternalModule(name, path, context) {
+  if (arguments.length < 3) {
+    throw new TypeError('isExternalModule: name, path, and context are all required');
+  }
+  return (isModule(name) || isScoped(name)) && typeTest(name, context, path) === 'external';
 }
 
-function isRelativeToSibling(name) {
-  return /^\.[\\/]/.test(name)
+export function isExternalModuleMain(name, path, context) {
+  if (arguments.length < 3) {
+    throw new TypeError('isExternalModule: name, path, and context are all required');
+  }
+  return isModuleMain(name) && typeTest(name, context, path) === 'external';
 }
 
-function typeTest(name, settings, path) {
-  if (isAbsolute(name, settings, path)) { return 'absolute' }
-  if (isBuiltIn(name, settings, path)) { return 'builtin' }
-  if (isInternalModule(name, settings, path)) { return 'internal' }
-  if (isExternalModule(name, settings, path)) { return 'external' }
-  if (isScoped(name, settings, path)) { return 'external' }
-  if (isRelativeToParent(name, settings, path)) { return 'parent' }
-  if (isIndex(name, settings, path)) { return 'index' }
-  if (isRelativeToSibling(name, settings, path)) { return 'sibling' }
-  return 'unknown'
+const scopedMainRegExp = /^@[^/]+\/?[^/]+$/;
+export function isScopedMain(name) {
+  return name && scopedMainRegExp.test(name);
 }
 
 export default function resolveImportType(name, context) {
-  return typeTest(name, context.settings, resolve(name, context))
+  return typeTest(name, context, resolve(name, context));
 }
diff --git a/src/core/packagePath.js b/src/core/packagePath.js
new file mode 100644
index 000000000..f45f54326
--- /dev/null
+++ b/src/core/packagePath.js
@@ -0,0 +1,22 @@
+import { dirname } from 'path';
+import { getPhysicalFilename } from 'eslint-module-utils/contextCompat';
+import pkgUp from 'eslint-module-utils/pkgUp';
+import readPkgUp from 'eslint-module-utils/readPkgUp';
+
+export function getFilePackagePath(filePath) {
+  const fp = pkgUp({ cwd: filePath });
+  return dirname(fp);
+}
+
+export function getContextPackagePath(context) {
+  return getFilePackagePath(getPhysicalFilename(context));
+}
+
+export function getFilePackageName(filePath) {
+  const { pkg, path } = readPkgUp({ cwd: filePath, normalize: false });
+  if (pkg) {
+    // recursion in case of intermediate esm package.json without name found
+    return pkg.name || getFilePackageName(dirname(dirname(path)));
+  }
+  return null;
+}
diff --git a/src/core/sourceType.js b/src/core/sourceType.js
new file mode 100644
index 000000000..5ff92edc9
--- /dev/null
+++ b/src/core/sourceType.js
@@ -0,0 +1,12 @@
+/**
+ * @param {import('eslint').Rule.RuleContext} context
+ * @returns 'module' | 'script' | 'commonjs' | undefined
+ */
+export default function sourceType(context) {
+  if ('sourceType' in context.parserOptions) {
+    return context.parserOptions.sourceType;
+  }
+  if ('languageOptions' in context && context.languageOptions) {
+    return context.languageOptions.sourceType;
+  }
+}
diff --git a/src/core/staticRequire.js b/src/core/staticRequire.js
index 45ed79d79..88b5000c8 100644
--- a/src/core/staticRequire.js
+++ b/src/core/staticRequire.js
@@ -1,10 +1,10 @@
 // todo: merge with module visitor
 export default function isStaticRequire(node) {
-  return node &&
-    node.callee &&
-    node.callee.type === 'Identifier' &&
-    node.callee.name === 'require' &&
-    node.arguments.length === 1 &&
-    node.arguments[0].type === 'Literal' &&
-    typeof node.arguments[0].value === 'string'
+  return node
+    && node.callee
+    && node.callee.type === 'Identifier'
+    && node.callee.name === 'require'
+    && node.arguments.length === 1
+    && node.arguments[0].type === 'Literal'
+    && typeof node.arguments[0].value === 'string';
 }
diff --git a/src/docsUrl.js b/src/docsUrl.js
index 3c01c49ad..92b838c09 100644
--- a/src/docsUrl.js
+++ b/src/docsUrl.js
@@ -1,7 +1,7 @@
-import pkg from '../package.json'
+import pkg from '../package.json';
 
-const repoUrl = 'https://github.com/benmosher/eslint-plugin-import'
+const repoUrl = 'https://github.com/import-js/eslint-plugin-import';
 
 export default function docsUrl(ruleName, commitish = `v${pkg.version}`) {
-  return `${repoUrl}/blob/${commitish}/docs/rules/${ruleName}.md`
+  return `${repoUrl}/blob/${commitish}/docs/rules/${ruleName}.md`;
 }
diff --git a/src/exportMap/builder.js b/src/exportMap/builder.js
new file mode 100644
index 000000000..f7b9006ef
--- /dev/null
+++ b/src/exportMap/builder.js
@@ -0,0 +1,210 @@
+import fs from 'fs';
+
+import doctrine from 'doctrine';
+
+import debug from 'debug';
+
+import parse from 'eslint-module-utils/parse';
+import visit from 'eslint-module-utils/visit';
+import resolve from 'eslint-module-utils/resolve';
+import isIgnored, { hasValidExtension } from 'eslint-module-utils/ignore';
+
+import { hashObject } from 'eslint-module-utils/hash';
+import * as unambiguous from 'eslint-module-utils/unambiguous';
+
+import ExportMap from '.';
+import childContext from './childContext';
+import { isEsModuleInterop } from './typescript';
+import { RemotePath } from './remotePath';
+import ImportExportVisitorBuilder from './visitor';
+
+const log = debug('eslint-plugin-import:ExportMap');
+
+const exportCache = new Map();
+
+/**
+ * The creation of this closure is isolated from other scopes
+ * to avoid over-retention of unrelated variables, which has
+ * caused memory leaks. See #1266.
+ */
+function thunkFor(p, context) {
+  // eslint-disable-next-line no-use-before-define
+  return () => ExportMapBuilder.for(childContext(p, context));
+}
+
+export default class ExportMapBuilder {
+  static get(source, context) {
+    const path = resolve(source, context);
+    if (path == null) { return null; }
+
+    return ExportMapBuilder.for(childContext(path, context));
+  }
+
+  static for(context) {
+    const { path } = context;
+
+    const cacheKey = context.cacheKey || hashObject(context).digest('hex');
+    let exportMap = exportCache.get(cacheKey);
+
+    // return cached ignore
+    if (exportMap === null) { return null; }
+
+    const stats = fs.statSync(path);
+    if (exportMap != null) {
+      // date equality check
+      if (exportMap.mtime - stats.mtime === 0) {
+        return exportMap;
+      }
+      // future: check content equality?
+    }
+
+    // check valid extensions first
+    if (!hasValidExtension(path, context)) {
+      exportCache.set(cacheKey, null);
+      return null;
+    }
+
+    // check for and cache ignore
+    if (isIgnored(path, context)) {
+      log('ignored path due to ignore settings:', path);
+      exportCache.set(cacheKey, null);
+      return null;
+    }
+
+    const content = fs.readFileSync(path, { encoding: 'utf8' });
+
+    // check for and cache unambiguous modules
+    if (!unambiguous.test(content)) {
+      log('ignored path due to unambiguous regex:', path);
+      exportCache.set(cacheKey, null);
+      return null;
+    }
+
+    log('cache miss', cacheKey, 'for path', path);
+    exportMap = ExportMapBuilder.parse(path, content, context);
+
+    // ambiguous modules return null
+    if (exportMap == null) {
+      log('ignored path due to ambiguous parse:', path);
+      exportCache.set(cacheKey, null);
+      return null;
+    }
+
+    exportMap.mtime = stats.mtime;
+
+    // If the visitor keys were not populated, then we shouldn't save anything to the cache,
+    // since the parse results may not be reliable.
+    if (exportMap.visitorKeys) {
+      exportCache.set(cacheKey, exportMap);
+    }
+    return exportMap;
+  }
+
+  static parse(path, content, context) {
+    const exportMap = new ExportMap(path);
+    const isEsModuleInteropTrue = isEsModuleInterop(context);
+
+    let ast;
+    let visitorKeys;
+    try {
+      const result = parse(path, content, context);
+      ast = result.ast;
+      visitorKeys = result.visitorKeys;
+    } catch (err) {
+      exportMap.errors.push(err);
+      return exportMap; // can't continue
+    }
+
+    exportMap.visitorKeys = visitorKeys;
+
+    let hasDynamicImports = false;
+
+    const remotePathResolver = new RemotePath(path, context);
+
+    function processDynamicImport(source) {
+      hasDynamicImports = true;
+      if (source.type !== 'Literal') {
+        return null;
+      }
+      const p = remotePathResolver.resolve(source.value);
+      if (p == null) {
+        return null;
+      }
+      const importedSpecifiers = new Set();
+      importedSpecifiers.add('ImportNamespaceSpecifier');
+      const getter = thunkFor(p, context);
+      exportMap.imports.set(p, {
+        getter,
+        declarations: new Set([{
+          source: {
+          // capturing actual node reference holds full AST in memory!
+            value: source.value,
+            loc: source.loc,
+          },
+          importedSpecifiers,
+          dynamic: true,
+        }]),
+      });
+    }
+
+    visit(ast, visitorKeys, {
+      ImportExpression(node) {
+        processDynamicImport(node.source);
+      },
+      CallExpression(node) {
+        if (node.callee.type === 'Import') {
+          processDynamicImport(node.arguments[0]);
+        }
+      },
+    });
+
+    const unambiguouslyESM = unambiguous.isModule(ast);
+    if (!unambiguouslyESM && !hasDynamicImports) { return null; }
+
+    // attempt to collect module doc
+    if (ast.comments) {
+      ast.comments.some((c) => {
+        if (c.type !== 'Block') { return false; }
+        try {
+          const doc = doctrine.parse(c.value, { unwrap: true });
+          if (doc.tags.some((t) => t.title === 'module')) {
+            exportMap.doc = doc;
+            return true;
+          }
+        } catch (err) { /* ignore */ }
+        return false;
+      });
+    }
+
+    const visitorBuilder = new ImportExportVisitorBuilder(
+      path,
+      context,
+      exportMap,
+      ExportMapBuilder,
+      content,
+      ast,
+      isEsModuleInteropTrue,
+      thunkFor,
+    );
+    ast.body.forEach(function (astNode) {
+      const visitor = visitorBuilder.build(astNode);
+
+      if (visitor[astNode.type]) {
+        visitor[astNode.type].call(visitorBuilder);
+      }
+    });
+
+    if (
+      isEsModuleInteropTrue // esModuleInterop is on in tsconfig
+      && exportMap.namespace.size > 0 // anything is exported
+      && !exportMap.namespace.has('default') // and default isn't added already
+    ) {
+      exportMap.namespace.set('default', {}); // add default export
+    }
+
+    if (unambiguouslyESM) {
+      exportMap.parseGoal = 'Module';
+    }
+    return exportMap;
+  }
+}
diff --git a/src/exportMap/captureDependency.js b/src/exportMap/captureDependency.js
new file mode 100644
index 000000000..9ad37d0e2
--- /dev/null
+++ b/src/exportMap/captureDependency.js
@@ -0,0 +1,60 @@
+export function captureDependency(
+  { source },
+  isOnlyImportingTypes,
+  remotePathResolver,
+  exportMap,
+  context,
+  thunkFor,
+  importedSpecifiers = new Set(),
+) {
+  if (source == null) { return null; }
+
+  const p = remotePathResolver.resolve(source.value);
+  if (p == null) { return null; }
+
+  const declarationMetadata = {
+    // capturing actual node reference holds full AST in memory!
+    source: { value: source.value, loc: source.loc },
+    isOnlyImportingTypes,
+    importedSpecifiers,
+  };
+
+  const existing = exportMap.imports.get(p);
+  if (existing != null) {
+    existing.declarations.add(declarationMetadata);
+    return existing.getter;
+  }
+
+  const getter = thunkFor(p, context);
+  exportMap.imports.set(p, { getter, declarations: new Set([declarationMetadata]) });
+  return getter;
+}
+
+const supportedImportTypes = new Set(['ImportDefaultSpecifier', 'ImportNamespaceSpecifier']);
+
+export function captureDependencyWithSpecifiers(
+  n,
+  remotePathResolver,
+  exportMap,
+  context,
+  thunkFor,
+) {
+  // import type { Foo } (TS and Flow); import typeof { Foo } (Flow)
+  const declarationIsType = n.importKind === 'type' || n.importKind === 'typeof';
+  // import './foo' or import {} from './foo' (both 0 specifiers) is a side effect and
+  // shouldn't be considered to be just importing types
+  let specifiersOnlyImportingTypes = n.specifiers.length > 0;
+  const importedSpecifiers = new Set();
+  n.specifiers.forEach((specifier) => {
+    if (specifier.type === 'ImportSpecifier') {
+      importedSpecifiers.add(specifier.imported.name || specifier.imported.value);
+    } else if (supportedImportTypes.has(specifier.type)) {
+      importedSpecifiers.add(specifier.type);
+    }
+
+    // import { type Foo } (Flow); import { typeof Foo } (Flow)
+    specifiersOnlyImportingTypes = specifiersOnlyImportingTypes
+      && (specifier.importKind === 'type' || specifier.importKind === 'typeof');
+  });
+  captureDependency(n, declarationIsType || specifiersOnlyImportingTypes, remotePathResolver, exportMap, context, thunkFor, importedSpecifiers);
+}
diff --git a/src/exportMap/childContext.js b/src/exportMap/childContext.js
new file mode 100644
index 000000000..8994ac206
--- /dev/null
+++ b/src/exportMap/childContext.js
@@ -0,0 +1,56 @@
+import { hashObject } from 'eslint-module-utils/hash';
+
+let optionsHash = '';
+let prevOptions = '';
+let settingsHash = '';
+let prevSettings = '';
+
+// Replacer function helps us with serializing the parser nested within `languageOptions`.
+function stringifyReplacerFn(_, value) {
+  if (typeof value === 'function') {
+    return String(value);
+  }
+  return value;
+}
+
+/**
+ * don't hold full context object in memory, just grab what we need.
+ * also calculate a cacheKey, where parts of the cacheKey hash are memoized
+ */
+export default function childContext(path, context) {
+  const { settings, parserOptions, parserPath, languageOptions } = context;
+
+  if (JSON.stringify(settings) !== prevSettings) {
+    settingsHash = hashObject({ settings }).digest('hex');
+    prevSettings = JSON.stringify(settings);
+  }
+
+  // We'll use either a combination of `parserOptions` and `parserPath` or `languageOptions`
+  // to construct the cache key, depending on whether this is using a flat config or not.
+  let optionsToken;
+  if (!parserPath && languageOptions) {
+    if (JSON.stringify(languageOptions, stringifyReplacerFn) !== prevOptions) {
+      optionsHash = hashObject({ languageOptions }).digest('hex');
+      prevOptions = JSON.stringify(languageOptions, stringifyReplacerFn);
+    }
+    // For languageOptions, we're just using the hashed options as the options token
+    optionsToken = optionsHash;
+  } else {
+    if (JSON.stringify(parserOptions) !== prevOptions) {
+      optionsHash = hashObject({ parserOptions }).digest('hex');
+      prevOptions = JSON.stringify(parserOptions);
+    }
+    // When not using flat config, we use a combination of the hashed parserOptions
+    // and parserPath as the token
+    optionsToken = String(parserPath) + optionsHash;
+  }
+
+  return {
+    cacheKey: optionsToken + settingsHash + String(path),
+    settings,
+    parserOptions,
+    parserPath,
+    path,
+    languageOptions,
+  };
+}
diff --git a/src/exportMap/doc.js b/src/exportMap/doc.js
new file mode 100644
index 000000000..c721ae25f
--- /dev/null
+++ b/src/exportMap/doc.js
@@ -0,0 +1,90 @@
+import doctrine from 'doctrine';
+
+/**
+ * parse docs from the first node that has leading comments
+ */
+export function captureDoc(source, docStyleParsers, ...nodes) {
+  const metadata = {};
+
+  // 'some' short-circuits on first 'true'
+  nodes.some((n) => {
+    try {
+
+      let leadingComments;
+
+      // n.leadingComments is legacy `attachComments` behavior
+      if ('leadingComments' in n) {
+        leadingComments = n.leadingComments;
+      } else if (n.range) {
+        leadingComments = source.getCommentsBefore(n);
+      }
+
+      if (!leadingComments || leadingComments.length === 0) { return false; }
+
+      for (const name in docStyleParsers) {
+        const doc = docStyleParsers[name](leadingComments);
+        if (doc) {
+          metadata.doc = doc;
+        }
+      }
+
+      return true;
+    } catch (err) {
+      return false;
+    }
+  });
+
+  return metadata;
+}
+
+/**
+ * parse JSDoc from leading comments
+ * @param {object[]} comments
+ * @return {{ doc: object }}
+ */
+function captureJsDoc(comments) {
+  let doc;
+
+  // capture XSDoc
+  comments.forEach((comment) => {
+    // skip non-block comments
+    if (comment.type !== 'Block') { return; }
+    try {
+      doc = doctrine.parse(comment.value, { unwrap: true });
+    } catch (err) {
+      /* don't care, for now? maybe add to `errors?` */
+    }
+  });
+
+  return doc;
+}
+
+/**
+  * parse TomDoc section from comments
+  */
+function captureTomDoc(comments) {
+  // collect lines up to first paragraph break
+  const lines = [];
+  for (let i = 0; i < comments.length; i++) {
+    const comment = comments[i];
+    if (comment.value.match(/^\s*$/)) { break; }
+    lines.push(comment.value.trim());
+  }
+
+  // return doctrine-like object
+  const statusMatch = lines.join(' ').match(/^(Public|Internal|Deprecated):\s*(.+)/);
+  if (statusMatch) {
+    return {
+      description: statusMatch[2],
+      tags: [{
+        title: statusMatch[1].toLowerCase(),
+        description: statusMatch[2],
+      }],
+    };
+  }
+}
+
+export const availableDocStyleParsers = {
+  jsdoc: captureJsDoc,
+  tomdoc: captureTomDoc,
+};
diff --git a/src/exportMap/index.js b/src/exportMap/index.js
new file mode 100644
index 000000000..e4d61638c
--- /dev/null
+++ b/src/exportMap/index.js
@@ -0,0 +1,178 @@
+export default class ExportMap {
+  constructor(path) {
+    this.path = path;
+    this.namespace = new Map();
+    // todo: restructure to key on path, value is resolver + map of names
+    this.reexports = new Map();
+    /**
+     * star-exports
+     * @type {Set<() => ExportMap>}
+     */
+    this.dependencies = new Set();
+    /**
+     * dependencies of this module that are not explicitly re-exported
+     * @type {Map<string, () => ExportMap>}
+     */
+    this.imports = new Map();
+    this.errors = [];
+    /**
+     * type {'ambiguous' | 'Module' | 'Script'}
+     */
+    this.parseGoal = 'ambiguous';
+  }
+
+  get hasDefault() { return this.get('default') != null; } // stronger than this.has
+
+  get size() {
+    let size = this.namespace.size + this.reexports.size;
+    this.dependencies.forEach((dep) => {
+      const d = dep();
+      // CJS / ignored dependencies won't exist (#717)
+      if (d == null) { return; }
+      size += d.size;
+    });
+    return size;
+  }
+
+  /**
+   * Note that this does not check explicitly re-exported names for existence
+   * in the base namespace, but it will expand all `export * from '...'` exports
+   * if not found in the explicit namespace.
+   * @param  {string}  name
+   * @return {boolean} true if `name` is exported by this module.
+   */
+  has(name) {
+    if (this.namespace.has(name)) { return true; }
+    if (this.reexports.has(name)) { return true; }
+
+    // default exports must be explicitly re-exported (#328)
+    if (name !== 'default') {
+      for (const dep of this.dependencies) {
+        const innerMap = dep();
+
+        // todo: report as unresolved?
+        if (!innerMap) { continue; }
+
+        if (innerMap.has(name)) { return true; }
+      }
+    }
+
+    return false;
+  }
+
+  /**
+   * ensure that imported name fully resolves.
+   * @param  {string} name
+   * @return {{ found: boolean, path: ExportMap[] }}
+   */
+  hasDeep(name) {
+    if (this.namespace.has(name)) { return { found: true, path: [this] }; }
+
+    if (this.reexports.has(name)) {
+      const reexports = this.reexports.get(name);
+      const imported = reexports.getImport();
+
+      // if import is ignored, return explicit 'null'
+      if (imported == null) { return { found: true, path: [this] }; }
+
+      // safeguard against cycles, only if name matches
+      if (imported.path === this.path && reexports.local === name) {
+        return { found: false, path: [this] };
+      }
+
+      const deep = imported.hasDeep(reexports.local);
+      deep.path.unshift(this);
+
+      return deep;
+    }
+
+    // default exports must be explicitly re-exported (#328)
+    if (name !== 'default') {
+      for (const dep of this.dependencies) {
+        const innerMap = dep();
+        if (innerMap == null) { return { found: true, path: [this] }; }
+        // todo: report as unresolved?
+        if (!innerMap) { continue; }
+
+        // safeguard against cycles
+        if (innerMap.path === this.path) { continue; }
+
+        const innerValue = innerMap.hasDeep(name);
+        if (innerValue.found) {
+          innerValue.path.unshift(this);
+          return innerValue;
+        }
+      }
+    }
+
+    return { found: false, path: [this] };
+  }
+
+  get(name) {
+    if (this.namespace.has(name)) { return this.namespace.get(name); }
+
+    if (this.reexports.has(name)) {
+      const reexports = this.reexports.get(name);
+      const imported = reexports.getImport();
+
+      // if import is ignored, return explicit 'null'
+      if (imported == null) { return null; }
+
+      // safeguard against cycles, only if name matches
+      if (imported.path === this.path && reexports.local === name) { return undefined; }
+
+      return imported.get(reexports.local);
+    }
+
+    // default exports must be explicitly re-exported (#328)
+    if (name !== 'default') {
+      for (const dep of this.dependencies) {
+        const innerMap = dep();
+        // todo: report as unresolved?
+        if (!innerMap) { continue; }
+
+        // safeguard against cycles
+        if (innerMap.path === this.path) { continue; }
+
+        const innerValue = innerMap.get(name);
+        if (innerValue !== undefined) { return innerValue; }
+      }
+    }
+
+    return undefined;
+  }
+
+  forEach(callback, thisArg) {
+    this.namespace.forEach((v, n) => { callback.call(thisArg, v, n, this); });
+
+    this.reexports.forEach((reexports, name) => {
+      const reexported = reexports.getImport();
+      // can't look up meta for ignored re-exports (#348)
+      callback.call(thisArg, reexported && reexported.get(reexports.local), name, this);
+    });
+
+    this.dependencies.forEach((dep) => {
+      const d = dep();
+      // CJS / ignored dependencies won't exist (#717)
+      if (d == null) { return; }
+
+      d.forEach((v, n) => {
+        if (n !== 'default') {
+          callback.call(thisArg, v, n, this);
+        }
+      });
+    });
+  }
+
+  // todo: keys, values, entries?
+
+  reportErrors(context, declaration) {
+    const msg = this.errors
+      .map((e) => `${e.message} (${e.lineNumber}:${e.column})`)
+      .join(', ');
+    context.report({
+      node: declaration.source,
+      message: `Parse errors in imported module '${declaration.source.value}': ${msg}`,
+    });
+  }
+}
diff --git a/src/exportMap/namespace.js b/src/exportMap/namespace.js
new file mode 100644
index 000000000..370f47579
--- /dev/null
+++ b/src/exportMap/namespace.js
@@ -0,0 +1,39 @@
+import childContext from './childContext';
+import { RemotePath } from './remotePath';
+
+export default class Namespace {
+  constructor(
+    path,
+    context,
+    ExportMapBuilder,
+  ) {
+    this.remotePathResolver = new RemotePath(path, context);
+    this.context = context;
+    this.ExportMapBuilder = ExportMapBuilder;
+    this.namespaces = new Map();
+  }
+
+  resolveImport(value) {
+    const rp = this.remotePathResolver.resolve(value);
+    if (rp == null) { return null; }
+    return this.ExportMapBuilder.for(childContext(rp, this.context));
+  }
+
+  getNamespace(identifier) {
+    if (!this.namespaces.has(identifier.name)) { return; }
+    return () => this.resolveImport(this.namespaces.get(identifier.name));
+  }
+
+  add(object, identifier) {
+    const nsfn = this.getNamespace(identifier);
+    if (nsfn) {
+      Object.defineProperty(object, 'namespace', { get: nsfn });
+    }
+
+    return object;
+  }
+
+  rawSet(name, value) {
+    this.namespaces.set(name, value);
+  }
+}
diff --git a/src/exportMap/patternCapture.js b/src/exportMap/patternCapture.js
new file mode 100644
index 000000000..5bc980641
--- /dev/null
+++ b/src/exportMap/patternCapture.js
@@ -0,0 +1,40 @@
+/**
+ * Traverse a pattern/identifier node, calling 'callback'
+ * for each leaf identifier.
+ * @param  {node}   pattern
+ * @param  {Function} callback
+ * @return {void}
+ */
+export default function recursivePatternCapture(pattern, callback) {
+  switch (pattern.type) {
+    case 'Identifier': // base case
+      callback(pattern);
+      break;
+
+    case 'ObjectPattern':
+      pattern.properties.forEach((p) => {
+        if (p.type === 'ExperimentalRestProperty' || p.type === 'RestElement') {
+          callback(p.argument);
+          return;
+        }
+        recursivePatternCapture(p.value, callback);
+      });
+      break;
+
+    case 'ArrayPattern':
+      pattern.elements.forEach((element) => {
+        if (element == null) { return; }
+        if (element.type === 'ExperimentalRestProperty' || element.type === 'RestElement') {
+          callback(element.argument);
+          return;
+        }
+        recursivePatternCapture(element, callback);
+      });
+      break;
+
+    case 'AssignmentPattern':
+      callback(pattern.left);
+      break;
+    default:
+  }
+}
diff --git a/src/exportMap/remotePath.js b/src/exportMap/remotePath.js
new file mode 100644
index 000000000..0dc5fc095
--- /dev/null
+++ b/src/exportMap/remotePath.js
@@ -0,0 +1,12 @@
+import resolve from 'eslint-module-utils/resolve';
+
+export class RemotePath {
+  constructor(path, context) {
+    this.path = path;
+    this.context = context;
+  }
+
+  resolve(value) {
+    return resolve.relative(value, this.path, this.context.settings);
+  }
+}
diff --git a/src/exportMap/specifier.js b/src/exportMap/specifier.js
new file mode 100644
index 000000000..dfaaf618e
--- /dev/null
+++ b/src/exportMap/specifier.js
@@ -0,0 +1,32 @@
+export default function processSpecifier(specifier, astNode, exportMap, namespace) {
+  const nsource = astNode.source && astNode.source.value;
+  const exportMeta = {};
+  let local;
+
+  switch (specifier.type) {
+    case 'ExportDefaultSpecifier':
+      if (!nsource) { return; }
+      local = 'default';
+      break;
+    case 'ExportNamespaceSpecifier':
+      exportMap.namespace.set(specifier.exported.name, Object.defineProperty(exportMeta, 'namespace', {
+        get() { return namespace.resolveImport(nsource); },
+      }));
+      return;
+    case 'ExportAllDeclaration':
+      exportMap.namespace.set(specifier.exported.name || specifier.exported.value, namespace.add(exportMeta, specifier.source.value));
+      return;
+    case 'ExportSpecifier':
+      if (!astNode.source) {
+        exportMap.namespace.set(specifier.exported.name || specifier.exported.value, namespace.add(exportMeta, specifier.local));
+        return;
+      }
+    // else falls through
+    default:
+      local = specifier.local.name;
+      break;
+  }
+
+  // todo: JSDoc
+  exportMap.reexports.set(specifier.exported.name, { local, getImport: () => namespace.resolveImport(nsource) });
+}
diff --git a/src/exportMap/typescript.js b/src/exportMap/typescript.js
new file mode 100644
index 000000000..7db4356da
--- /dev/null
+++ b/src/exportMap/typescript.js
@@ -0,0 +1,43 @@
+import { dirname } from 'path';
+import { tsConfigLoader } from 'tsconfig-paths/lib/tsconfig-loader';
+import { hashObject } from 'eslint-module-utils/hash';
+
+let ts;
+const tsconfigCache = new Map();
+
+function readTsConfig(context) {
+  const tsconfigInfo = tsConfigLoader({
+    cwd: context.parserOptions && context.parserOptions.tsconfigRootDir || process.cwd(),
+    getEnv: (key) => process.env[key],
+  });
+  try {
+    if (tsconfigInfo.tsConfigPath !== undefined) {
+      // Projects not using TypeScript won't have `typescript` installed.
+      if (!ts) { ts = require('typescript'); } // eslint-disable-line import/no-extraneous-dependencies
+
+      const configFile = ts.readConfigFile(tsconfigInfo.tsConfigPath, ts.sys.readFile);
+      return ts.parseJsonConfigFileContent(
+        configFile.config,
+        ts.sys,
+        dirname(tsconfigInfo.tsConfigPath),
+      );
+    }
+  } catch (e) {
+    // Catch any errors
+  }
+
+  return null;
+}
+
+export function isEsModuleInterop(context) {
+  const cacheKey = hashObject({
+    tsconfigRootDir: context.parserOptions && context.parserOptions.tsconfigRootDir,
+  }).digest('hex');
+  let tsConfig = tsconfigCache.get(cacheKey);
+  if (typeof tsConfig === 'undefined') {
+    tsConfig = readTsConfig(context);
+    tsconfigCache.set(cacheKey, tsConfig);
+  }
+
+  return tsConfig && tsConfig.options ? tsConfig.options.esModuleInterop : false;
+}
diff --git a/src/exportMap/visitor.js b/src/exportMap/visitor.js
new file mode 100644
index 000000000..21c1a7c64
--- /dev/null
+++ b/src/exportMap/visitor.js
@@ -0,0 +1,171 @@
+import includes from 'array-includes';
+import { SourceCode } from 'eslint';
+import { availableDocStyleParsers, captureDoc } from './doc';
+import Namespace from './namespace';
+import processSpecifier from './specifier';
+import { captureDependency, captureDependencyWithSpecifiers } from './captureDependency';
+import recursivePatternCapture from './patternCapture';
+import { RemotePath } from './remotePath';
+
+/**
+ * sometimes legacy support isn't _that_ hard... right?
+ */
+function makeSourceCode(text, ast) {
+  if (SourceCode.length > 1) {
+    // ESLint 3
+    return new SourceCode(text, ast);
+  } else {
+    // ESLint 4, 5
+    return new SourceCode({ text, ast });
+  }
+}
+
+export default class ImportExportVisitorBuilder {
+  constructor(
+    path,
+    context,
+    exportMap,
+    ExportMapBuilder,
+    content,
+    ast,
+    isEsModuleInteropTrue,
+    thunkFor,
+  ) {
+    this.context = context;
+    this.namespace = new Namespace(path, context, ExportMapBuilder);
+    this.remotePathResolver = new RemotePath(path, context);
+    this.source = makeSourceCode(content, ast);
+    this.exportMap = exportMap;
+    this.ast = ast;
+    this.isEsModuleInteropTrue = isEsModuleInteropTrue;
+    this.thunkFor = thunkFor;
+    const docstyle = this.context.settings && this.context.settings['import/docstyle'] || ['jsdoc'];
+    this.docStyleParsers = {};
+    docstyle.forEach((style) => {
+      this.docStyleParsers[style] = availableDocStyleParsers[style];
+    });
+  }
+
+  build(astNode) {
+    return {
+      ExportDefaultDeclaration() {
+        const exportMeta = captureDoc(this.source, this.docStyleParsers, astNode);
+        if (astNode.declaration.type === 'Identifier') {
+          this.namespace.add(exportMeta, astNode.declaration);
+        }
+        this.exportMap.namespace.set('default', exportMeta);
+      },
+      ExportAllDeclaration() {
+        const getter = captureDependency(astNode, astNode.exportKind === 'type', this.remotePathResolver, this.exportMap, this.context, this.thunkFor);
+        if (getter) { this.exportMap.dependencies.add(getter); }
+        if (astNode.exported) {
+          processSpecifier(astNode, astNode.exported, this.exportMap, this.namespace);
+        }
+      },
+      /** capture namespaces in case of later export */
+      ImportDeclaration() {
+        captureDependencyWithSpecifiers(astNode, this.remotePathResolver, this.exportMap, this.context, this.thunkFor);
+        const ns = astNode.specifiers.find((s) => s.type === 'ImportNamespaceSpecifier');
+        if (ns) {
+          this.namespace.rawSet(ns.local.name, astNode.source.value);
+        }
+      },
+      ExportNamedDeclaration() {
+        captureDependencyWithSpecifiers(astNode, this.remotePathResolver, this.exportMap, this.context, this.thunkFor);
+        // capture declaration
+        if (astNode.declaration != null) {
+          switch (astNode.declaration.type) {
+            case 'FunctionDeclaration':
+            case 'ClassDeclaration':
+            case 'TypeAlias': // flowtype with babel-eslint parser
+            case 'InterfaceDeclaration':
+            case 'DeclareFunction':
+            case 'TSDeclareFunction':
+            case 'TSEnumDeclaration':
+            case 'TSTypeAliasDeclaration':
+            case 'TSInterfaceDeclaration':
+            case 'TSAbstractClassDeclaration':
+            case 'TSModuleDeclaration':
+              this.exportMap.namespace.set(astNode.declaration.id.name, captureDoc(this.source, this.docStyleParsers, astNode));
+              break;
+            case 'VariableDeclaration':
+              astNode.declaration.declarations.forEach((d) => {
+                recursivePatternCapture(
+                  d.id,
+                  (id) => this.exportMap.namespace.set(id.name, captureDoc(this.source, this.docStyleParsers, d, astNode)),
+                );
+              });
+              break;
+            default:
+          }
+        }
+        astNode.specifiers.forEach((s) => processSpecifier(s, astNode, this.exportMap, this.namespace));
+      },
+      TSExportAssignment: () => this.typeScriptExport(astNode),
+      ...this.isEsModuleInteropTrue && { TSNamespaceExportDeclaration: () => this.typeScriptExport(astNode) },
+    };
+  }
+
+  // This doesn't declare anything, but changes what's being exported.
+  typeScriptExport(astNode) {
+    const exportedName = astNode.type === 'TSNamespaceExportDeclaration'
+      ? (astNode.id || astNode.name).name
+      : astNode.expression && astNode.expression.name || astNode.expression.id && astNode.expression.id.name || null;
+    const declTypes = [
+      'VariableDeclaration',
+      'ClassDeclaration',
+      'TSDeclareFunction',
+      'TSEnumDeclaration',
+      'TSTypeAliasDeclaration',
+      'TSInterfaceDeclaration',
+      'TSAbstractClassDeclaration',
+      'TSModuleDeclaration',
+    ];
+    const exportedDecls = this.ast.body.filter(({ type, id, declarations }) => includes(declTypes, type) && (
+      id && id.name === exportedName || declarations && declarations.find((d) => d.id.name === exportedName)
+    ));
+    if (exportedDecls.length === 0) {
+      // Export is not referencing any local declaration, must be re-exporting
+      this.exportMap.namespace.set('default', captureDoc(this.source, this.docStyleParsers, astNode));
+      return;
+    }
+    if (
+      this.isEsModuleInteropTrue // esModuleInterop is on in tsconfig
+      && !this.exportMap.namespace.has('default') // and default isn't added already
+    ) {
+      this.exportMap.namespace.set('default', {}); // add default export
+    }
+    exportedDecls.forEach((decl) => {
+      if (decl.type === 'TSModuleDeclaration') {
+        if (decl.body && decl.body.type === 'TSModuleDeclaration') {
+          this.exportMap.namespace.set(decl.body.id.name, captureDoc(this.source, this.docStyleParsers, decl.body));
+        } else if (decl.body && decl.body.body) {
+          decl.body.body.forEach((moduleBlockNode) => {
+            // Export-assignment exports all members in the namespace,
+            // explicitly exported or not.
+            const namespaceDecl = moduleBlockNode.type === 'ExportNamedDeclaration'
+              ? moduleBlockNode.declaration
+              : moduleBlockNode;
+
+            if (!namespaceDecl) {
+              // TypeScript can check this for us; we needn't
+            } else if (namespaceDecl.type === 'VariableDeclaration') {
+              namespaceDecl.declarations.forEach((d) => recursivePatternCapture(d.id, (id) => this.exportMap.namespace.set(
+                id.name,
+                captureDoc(this.source, this.docStyleParsers, decl, namespaceDecl, moduleBlockNode),
+              )),
+              );
+            } else {
+              this.exportMap.namespace.set(
+                namespaceDecl.id.name,
+                captureDoc(this.source, this.docStyleParsers, moduleBlockNode));
+            }
+          });
+        }
+      } else {
+        // Export as default
+        this.exportMap.namespace.set('default', captureDoc(this.source, this.docStyleParsers, decl));
+      }
+    });
+  }
+}
diff --git a/src/importDeclaration.js b/src/importDeclaration.js
index 69af65d97..49446b260 100644
--- a/src/importDeclaration.js
+++ b/src/importDeclaration.js
@@ -1,4 +1,6 @@
-export default function importDeclaration(context) {
-  var ancestors = context.getAncestors()
-  return ancestors[ancestors.length - 1]
+import { getAncestors } from 'eslint-module-utils/contextCompat';
+
+export default function importDeclaration(context, node) {
+  const ancestors = getAncestors(context, node);
+  return ancestors[ancestors.length - 1];
 }
diff --git a/src/index.js b/src/index.js
index d0a98b7cb..6cd7bffe2 100644
--- a/src/index.js
+++ b/src/index.js
@@ -1,16 +1,20 @@
+import { name, version } from '../package.json';
+
 export const rules = {
   'no-unresolved': require('./rules/no-unresolved'),
-  'named': require('./rules/named'),
-  'default': require('./rules/default'),
-  'namespace': require('./rules/namespace'),
+  named: require('./rules/named'),
+  default: require('./rules/default'),
+  namespace: require('./rules/namespace'),
   'no-namespace': require('./rules/no-namespace'),
-  'export': require('./rules/export'),
+  export: require('./rules/export'),
   'no-mutable-exports': require('./rules/no-mutable-exports'),
-  'extensions': require('./rules/extensions'),
+  extensions: require('./rules/extensions'),
   'no-restricted-paths': require('./rules/no-restricted-paths'),
   'no-internal-modules': require('./rules/no-internal-modules'),
   'group-exports': require('./rules/group-exports'),
+  'no-relative-packages': require('./rules/no-relative-packages'),
   'no-relative-parent-imports': require('./rules/no-relative-parent-imports'),
+  'consistent-type-specifier-style': require('./rules/consistent-type-specifier-style'),
 
   'no-self-import': require('./rules/no-self-import'),
   'no-cycle': require('./rules/no-cycle'),
@@ -23,22 +27,25 @@ export const rules = {
   'no-commonjs': require('./rules/no-commonjs'),
   'no-amd': require('./rules/no-amd'),
   'no-duplicates': require('./rules/no-duplicates'),
-  'first': require('./rules/first'),
+  first: require('./rules/first'),
   'max-dependencies': require('./rules/max-dependencies'),
   'no-extraneous-dependencies': require('./rules/no-extraneous-dependencies'),
   'no-absolute-path': require('./rules/no-absolute-path'),
   'no-nodejs-modules': require('./rules/no-nodejs-modules'),
   'no-webpack-loader-syntax': require('./rules/no-webpack-loader-syntax'),
-  'order': require('./rules/order'),
+  order: require('./rules/order'),
   'newline-after-import': require('./rules/newline-after-import'),
   'prefer-default-export': require('./rules/prefer-default-export'),
   'no-default-export': require('./rules/no-default-export'),
   'no-named-export': require('./rules/no-named-export'),
   'no-dynamic-require': require('./rules/no-dynamic-require'),
-  'unambiguous': require('./rules/unambiguous'),
+  unambiguous: require('./rules/unambiguous'),
   'no-unassigned-import': require('./rules/no-unassigned-import'),
   'no-useless-path-segments': require('./rules/no-useless-path-segments'),
   'dynamic-import-chunkname': require('./rules/dynamic-import-chunkname'),
+  'no-import-module-exports': require('./rules/no-import-module-exports'),
+  'no-empty-named-blocks': require('./rules/no-empty-named-blocks'),
+  'enforce-node-protocol-usage': require('./rules/enforce-node-protocol-usage'),
 
   // export
   'exports-last': require('./rules/exports-last'),
@@ -48,20 +55,49 @@ export const rules = {
 
   // deprecated aliases to rules
   'imports-first': require('./rules/imports-first'),
-}
+};
 
 export const configs = {
-  'recommended': require('../config/recommended'),
+  recommended: require('../config/recommended'),
 
-  'errors': require('../config/errors'),
-  'warnings': require('../config/warnings'),
+  errors: require('../config/errors'),
+  warnings: require('../config/warnings'),
 
   // shhhh... work in progress "secret" rules
   'stage-0': require('../config/stage-0'),
 
   // useful stuff for folks using various environments
-  'react': require('../config/react'),
+  react: require('../config/react'),
   'react-native': require('../config/react-native'),
-  'electron': require('../config/electron'),
-  'typescript': require('../config/typescript'),
-}
+  electron: require('../config/electron'),
+  typescript: require('../config/typescript'),
+};
+
+// Base Plugin Object
+const importPlugin = {
+  meta: { name, version },
+  rules,
+};
+
+// Create flat configs (Only ones that declare plugins and parser options need to be different from the legacy config)
+const createFlatConfig = (baseConfig, configName) => ({
+  ...baseConfig,
+  name: `import/${configName}`,
+  plugins: { import: importPlugin },
+});
+
+export const flatConfigs = {
+  recommended: createFlatConfig(
+    require('../config/flat/recommended'),
+    'recommended',
+  ),
+
+  errors: createFlatConfig(require('../config/flat/errors'), 'errors'),
+  warnings: createFlatConfig(require('../config/flat/warnings'), 'warnings'),
+
+  // useful stuff for folks using various environments
+  react: createFlatConfig(require('../config/flat/react'), 'react'),
+  'react-native': createFlatConfig(configs['react-native'], 'react-native'),
+  electron: createFlatConfig(configs.electron, 'electron'),
+  typescript: createFlatConfig(configs.typescript, 'typescript'),
+};
diff --git a/src/rules/consistent-type-specifier-style.js b/src/rules/consistent-type-specifier-style.js
new file mode 100644
index 000000000..84c33ecd8
--- /dev/null
+++ b/src/rules/consistent-type-specifier-style.js
@@ -0,0 +1,238 @@
+import { getSourceCode } from 'eslint-module-utils/contextCompat';
+
+import docsUrl from '../docsUrl';
+
+function isComma(token) {
+  return token.type === 'Punctuator' && token.value === ',';
+}
+
+/**
+ * @param {import('eslint').Rule.Fix[]} fixes
+ * @param {import('eslint').Rule.RuleFixer} fixer
+ * @param {import('eslint').SourceCode.SourceCode} sourceCode
+ * @param {(ImportSpecifier | ImportDefaultSpecifier | ImportNamespaceSpecifier)[]} specifiers
+ * */
+function removeSpecifiers(fixes, fixer, sourceCode, specifiers) {
+  for (const specifier of specifiers) {
+    // remove the trailing comma
+    const token = sourceCode.getTokenAfter(specifier);
+    if (token && isComma(token)) {
+      fixes.push(fixer.remove(token));
+    }
+    fixes.push(fixer.remove(specifier));
+  }
+}
+
+/** @type {(node: import('estree').Node, sourceCode: import('eslint').SourceCode.SourceCode, specifiers: (ImportSpecifier | ImportNamespaceSpecifier)[], kind: 'type' | 'typeof') => string} */
+function getImportText(
+  node,
+  sourceCode,
+  specifiers,
+  kind,
+) {
+  const sourceString = sourceCode.getText(node.source);
+  if (specifiers.length === 0) {
+    return '';
+  }
+
+  const names = specifiers.map((s) => {
+    if (s.imported.name === s.local.name) {
+      return s.imported.name;
+    }
+    return `${s.imported.name} as ${s.local.name}`;
+  });
+  // insert a fresh top-level import
+  return `import ${kind} {${names.join(', ')}} from ${sourceString};`;
+}
+
+/** @type {import('eslint').Rule.RuleModule} */
+module.exports = {
+  meta: {
+    type: 'suggestion',
+    docs: {
+      category: 'Style guide',
+      description: 'Enforce or ban the use of inline type-only markers for named imports.',
+      url: docsUrl('consistent-type-specifier-style'),
+    },
+    fixable: 'code',
+    schema: [
+      {
+        type: 'string',
+        enum: ['prefer-inline', 'prefer-top-level'],
+        default: 'prefer-inline',
+      },
+    ],
+  },
+
+  create(context) {
+    const sourceCode = getSourceCode(context);
+
+    if (context.options[0] === 'prefer-inline') {
+      return {
+        ImportDeclaration(node) {
+          if (node.importKind === 'value' || node.importKind == null) {
+            // top-level value / unknown is valid
+            return;
+          }
+
+          if (
+            // no specifiers (import type {} from '') have no specifiers to mark as inline
+            node.specifiers.length === 0
+            || node.specifiers.length === 1
+            // default imports are both "inline" and "top-level"
+            && (
+              node.specifiers[0].type === 'ImportDefaultSpecifier'
+              // namespace imports are both "inline" and "top-level"
+              || node.specifiers[0].type === 'ImportNamespaceSpecifier'
+            )
+          ) {
+            return;
+          }
+
+          context.report({
+            node,
+            message: 'Prefer using inline {{kind}} specifiers instead of a top-level {{kind}}-only import.',
+            data: {
+              kind: node.importKind,
+            },
+            fix(fixer) {
+              const kindToken = sourceCode.getFirstToken(node, { skip: 1 });
+
+              return [].concat(
+                kindToken ? fixer.remove(kindToken) : [],
+                node.specifiers.map((specifier) => fixer.insertTextBefore(specifier, `${node.importKind} `)),
+              );
+            },
+          });
+        },
+      };
+    }
+
+    // prefer-top-level
+    return {
+      /** @param {import('estree').ImportDeclaration} node */
+      ImportDeclaration(node) {
+        if (
+          // already top-level is valid
+          node.importKind === 'type'
+          || node.importKind === 'typeof'
+          // no specifiers (import {} from '') cannot have inline - so is valid
+          || node.specifiers.length === 0
+          || node.specifiers.length === 1
+          // default imports are both "inline" and "top-level"
+          && (
+            node.specifiers[0].type === 'ImportDefaultSpecifier'
+            // namespace imports are both "inline" and "top-level"
+            || node.specifiers[0].type === 'ImportNamespaceSpecifier'
+          )
+        ) {
+          return;
+        }
+
+        /** @type {typeof node.specifiers} */
+        const typeSpecifiers = [];
+        /** @type {typeof node.specifiers} */
+        const typeofSpecifiers = [];
+        /** @type {typeof node.specifiers} */
+        const valueSpecifiers = [];
+        /** @type {typeof node.specifiers[number]} */
+        let defaultSpecifier = null;
+        for (const specifier of node.specifiers) {
+          if (specifier.type === 'ImportDefaultSpecifier') {
+            defaultSpecifier = specifier;
+            continue;
+          }
+
+          if (specifier.importKind === 'type') {
+            typeSpecifiers.push(specifier);
+          } else if (specifier.importKind === 'typeof') {
+            typeofSpecifiers.push(specifier);
+          } else if (specifier.importKind === 'value' || specifier.importKind == null) {
+            valueSpecifiers.push(specifier);
+          }
+        }
+
+        const typeImport = getImportText(node, sourceCode, typeSpecifiers, 'type');
+        const typeofImport = getImportText(node, sourceCode, typeofSpecifiers, 'typeof');
+        const newImports = `${typeImport}\n${typeofImport}`.trim();
+
+        if (typeSpecifiers.length + typeofSpecifiers.length === node.specifiers.length) {
+          /** @type {('type' | 'typeof')[]} */
+          // all specifiers have inline specifiers - so we replace the entire import
+          const kind = [].concat(
+            typeSpecifiers.length > 0 ? 'type' : [],
+            typeofSpecifiers.length > 0 ? 'typeof' : [],
+          );
+
+          context.report({
+            node,
+            message: 'Prefer using a top-level {{kind}}-only import instead of inline {{kind}} specifiers.',
+            data: {
+              kind: kind.join('/'),
+            },
+            fix(fixer) {
+              return fixer.replaceText(node, newImports);
+            },
+          });
+        } else {
+          // remove specific specifiers and insert new imports for them
+          typeSpecifiers.concat(typeofSpecifiers).forEach((specifier) => {
+            context.report({
+              node: specifier,
+              message: 'Prefer using a top-level {{kind}}-only import instead of inline {{kind}} specifiers.',
+              data: {
+                kind: specifier.importKind,
+              },
+              fix(fixer) {
+                /** @type {import('eslint').Rule.Fix[]} */
+                const fixes = [];
+
+                // if there are no value specifiers, then the other report fixer will be called, not this one
+
+                if (valueSpecifiers.length > 0) {
+                  // import { Value, type Type } from 'mod';
+
+                  // we can just remove the type specifiers
+                  removeSpecifiers(fixes, fixer, sourceCode, typeSpecifiers);
+                  removeSpecifiers(fixes, fixer, sourceCode, typeofSpecifiers);
+
+                  // make the import nicely formatted by also removing the trailing comma after the last value import
+                  // eg
+                  // import { Value, type Type } from 'mod';
+                  // to
+                  // import { Value  } from 'mod';
+                  // not
+                  // import { Value,  } from 'mod';
+                  const maybeComma = sourceCode.getTokenAfter(valueSpecifiers[valueSpecifiers.length - 1]);
+                  if (isComma(maybeComma)) {
+                    fixes.push(fixer.remove(maybeComma));
+                  }
+                } else if (defaultSpecifier) {
+                  // import Default, { type Type } from 'mod';
+
+                  // remove the entire curly block so we don't leave an empty one behind
+                  // NOTE - the default specifier *must* be the first specifier always!
+                  //        so a comma exists that we also have to clean up or else it's bad syntax
+                  const comma = sourceCode.getTokenAfter(defaultSpecifier, isComma);
+                  const closingBrace = sourceCode.getTokenAfter(
+                    node.specifiers[node.specifiers.length - 1],
+                    (token) => token.type === 'Punctuator' && token.value === '}',
+                  );
+                  fixes.push(fixer.removeRange([
+                    comma.range[0],
+                    closingBrace.range[1],
+                  ]));
+                }
+
+                return fixes.concat(
+                  // insert the new imports after the old declaration
+                  fixer.insertTextAfter(node, `\n${newImports}`),
+                );
+              },
+            });
+          });
+        }
+      },
+    };
+  },
+};
diff --git a/src/rules/default.js b/src/rules/default.js
index 7e07800da..0de787c33 100644
--- a/src/rules/default.js
+++ b/src/rules/default.js
@@ -1,41 +1,40 @@
-import Exports from '../ExportMap'
-import docsUrl from '../docsUrl'
+import ExportMapBuilder from '../exportMap/builder';
+import docsUrl from '../docsUrl';
 
 module.exports = {
   meta: {
     type: 'problem',
     docs: {
+      category: 'Static analysis',
+      description: 'Ensure a default export is present, given a default import.',
       url: docsUrl('default'),
     },
+    schema: [],
   },
 
-  create: function (context) {
-
+  create(context) {
     function checkDefault(specifierType, node) {
+      const defaultSpecifier = node.specifiers.find(
+        (specifier) => specifier.type === specifierType,
+      );
 
-      // poor man's Array.find
-      let defaultSpecifier
-      node.specifiers.some((n) => {
-        if (n.type === specifierType) {
-          defaultSpecifier = n
-          return true
-        }
-      })
-
-      if (!defaultSpecifier) return
-      var imports = Exports.get(node.source.value, context)
-      if (imports == null) return
+      if (!defaultSpecifier) { return; }
+      const imports = ExportMapBuilder.get(node.source.value, context);
+      if (imports == null) { return; }
 
       if (imports.errors.length) {
-        imports.reportErrors(context, node)
+        imports.reportErrors(context, node);
       } else if (imports.get('default') === undefined) {
-        context.report(defaultSpecifier, 'No default export found in module.')
+        context.report({
+          node: defaultSpecifier,
+          message: `No default export found in imported module "${node.source.value}".`,
+        });
       }
     }
 
     return {
-      'ImportDeclaration': checkDefault.bind(null, 'ImportDefaultSpecifier'),
-      'ExportNamedDeclaration': checkDefault.bind(null, 'ExportDefaultSpecifier'),
-    }
+      ImportDeclaration: checkDefault.bind(null, 'ImportDefaultSpecifier'),
+      ExportNamedDeclaration: checkDefault.bind(null, 'ExportDefaultSpecifier'),
+    };
   },
-}
+};
diff --git a/src/rules/dynamic-import-chunkname.js b/src/rules/dynamic-import-chunkname.js
index 44fb5611c..12a765008 100644
--- a/src/rules/dynamic-import-chunkname.js
+++ b/src/rules/dynamic-import-chunkname.js
@@ -1,10 +1,14 @@
-import vm from 'vm'
-import docsUrl from '../docsUrl'
+import { getSourceCode } from 'eslint-module-utils/contextCompat';
+import vm from 'vm';
+
+import docsUrl from '../docsUrl';
 
 module.exports = {
   meta: {
     type: 'suggestion',
     docs: {
+      category: 'Style guide',
+      description: 'Enforce a leading comment with the webpackChunkName for dynamic imports.',
       url: docsUrl('dynamic-import-chunkname'),
     },
     schema: [{
@@ -17,94 +21,152 @@ module.exports = {
             type: 'string',
           },
         },
+        allowEmpty: {
+          type: 'boolean',
+        },
         webpackChunknameFormat: {
           type: 'string',
         },
       },
     }],
+    hasSuggestions: true,
   },
 
-  create: function (context) {
-    const config = context.options[0]
-    const { importFunctions = [] } = config || {}
-    const { webpackChunknameFormat = '[0-9a-zA-Z-_/.]+' } = config || {}
+  create(context) {
+    const config = context.options[0];
+    const { importFunctions = [], allowEmpty = false } = config || {};
+    const { webpackChunknameFormat = '([0-9a-zA-Z-_/.]|\\[(request|index)\\])+' } = config || {};
 
-    const paddedCommentRegex = /^ (\S[\s\S]+\S) $/
-    const commentStyleRegex = /^( \w+: ("[^"]*"|\d+|false|true),?)+ $/
-    const chunkSubstrFormat = ` webpackChunkName: "${webpackChunknameFormat}",? `
-    const chunkSubstrRegex = new RegExp(chunkSubstrFormat)
+    const paddedCommentRegex = /^ (\S[\s\S]+\S) $/;
+    const commentStyleRegex = /^( ((webpackChunkName: .+)|((webpackPrefetch|webpackPreload): (true|false|-?[0-9]+))|(webpackIgnore: (true|false))|((webpackInclude|webpackExclude): \/.*\/)|(webpackMode: ["'](lazy|lazy-once|eager|weak)["'])|(webpackExports: (['"]\w+['"]|\[(['"]\w+['"], *)+(['"]\w+['"]*)\]))),?)+ $/;
+    const chunkSubstrFormat = `webpackChunkName: ["']${webpackChunknameFormat}["'],? `;
+    const chunkSubstrRegex = new RegExp(chunkSubstrFormat);
+    const eagerModeFormat = `webpackMode: ["']eager["'],? `;
+    const eagerModeRegex = new RegExp(eagerModeFormat);
 
-    return {
-      CallExpression(node) {
-        if (node.callee.type !== 'Import' && importFunctions.indexOf(node.callee.name) < 0) {
-          return
-        }
+    function run(node, arg) {
+      const sourceCode = getSourceCode(context);
+      const leadingComments = sourceCode.getCommentsBefore
+        ? sourceCode.getCommentsBefore(arg) // This method is available in ESLint >= 4.
+        : sourceCode.getComments(arg).leading; // This method is deprecated in ESLint 7.
 
-        const sourceCode = context.getSourceCode()
-        const arg = node.arguments[0]
-        const leadingComments = sourceCode.getComments(arg).leading
+      if ((!leadingComments || leadingComments.length === 0) && !allowEmpty) {
+        context.report({
+          node,
+          message: 'dynamic imports require a leading comment with the webpack chunkname',
+        });
+        return;
+      }
 
-        if (!leadingComments || leadingComments.length === 0) {
+      let isChunknamePresent = false;
+      let isEagerModePresent = false;
+
+      for (const comment of leadingComments) {
+        if (comment.type !== 'Block') {
           context.report({
             node,
-            message: 'dynamic imports require a leading comment with the webpack chunkname',
-          })
-          return
+            message: 'dynamic imports require a /* foo */ style comment, not a // foo comment',
+          });
+          return;
         }
 
-        let isChunknamePresent = false
-
-        for (const comment of leadingComments) {
-          if (comment.type !== 'Block') {
-            context.report({
-              node,
-              message: 'dynamic imports require a /* foo */ style comment, not a // foo comment',
-            })
-            return
-          }
-
-          if (!paddedCommentRegex.test(comment.value)) {
-            context.report({
-              node,
-              message: `dynamic imports require a block comment padded with spaces - /* foo */`,
-            })
-            return
-          }
-
-          try {
-            // just like webpack itself does
-            vm.runInNewContext(`(function(){return {${comment.value}}})()`)
-          }
-          catch (error) {
-            context.report({
-              node,
-              message: `dynamic imports require a "webpack" comment with valid syntax`,
-            })
-            return
-          }
-
-          if (!commentStyleRegex.test(comment.value)) {
-            context.report({
-              node,
-              message:
-                `dynamic imports require a leading comment in the form /*${chunkSubstrFormat}*/`,
-            })
-            return
-          }
-
-          if (chunkSubstrRegex.test(comment.value)) {
-            isChunknamePresent = true
-          }
+        if (!paddedCommentRegex.test(comment.value)) {
+          context.report({
+            node,
+            message: `dynamic imports require a block comment padded with spaces - /* foo */`,
+          });
+          return;
         }
 
-        if (!isChunknamePresent) {
+        try {
+          // just like webpack itself does
+          vm.runInNewContext(`(function() {return {${comment.value}}})()`);
+        } catch (error) {
+          context.report({
+            node,
+            message: `dynamic imports require a "webpack" comment with valid syntax`,
+          });
+          return;
+        }
+
+        if (!commentStyleRegex.test(comment.value)) {
           context.report({
             node,
             message:
-              `dynamic imports require a leading comment in the form /*${chunkSubstrFormat}*/`,
-          })
+              `dynamic imports require a "webpack" comment with valid syntax`,
+          });
+          return;
         }
-      },
+
+        if (eagerModeRegex.test(comment.value)) {
+          isEagerModePresent = true;
+        }
+
+        if (chunkSubstrRegex.test(comment.value)) {
+          isChunknamePresent = true;
+        }
+      }
+
+      if (isChunknamePresent && isEagerModePresent) {
+        context.report({
+          node,
+          message: 'dynamic imports using eager mode do not need a webpackChunkName',
+          suggest: [
+            {
+              desc: 'Remove webpackChunkName',
+              fix(fixer) {
+                for (const comment of leadingComments) {
+                  if (chunkSubstrRegex.test(comment.value)) {
+                    const replacement = comment.value.replace(chunkSubstrRegex, '').trim().replace(/,$/, '');
+                    if (replacement === '') {
+                      return fixer.remove(comment);
+                    } else {
+                      return fixer.replaceText(comment, `/* ${replacement} */`);
+                    }
+                  }
+                }
+              },
+            },
+            {
+              desc: 'Remove webpackMode',
+              fix(fixer) {
+                for (const comment of leadingComments) {
+                  if (eagerModeRegex.test(comment.value)) {
+                    const replacement = comment.value.replace(eagerModeRegex, '').trim().replace(/,$/, '');
+                    if (replacement === '') {
+                      return fixer.remove(comment);
+                    } else {
+                      return fixer.replaceText(comment, `/* ${replacement} */`);
+                    }
+                  }
+                }
+              },
+            },
+          ],
+        });
+      }
+
+      if (!isChunknamePresent && !allowEmpty && !isEagerModePresent) {
+        context.report({
+          node,
+          message:
+            `dynamic imports require a leading comment in the form /*${chunkSubstrFormat}*/`,
+        });
+      }
     }
+
+    return {
+      ImportExpression(node) {
+        run(node, node.source);
+      },
+
+      CallExpression(node) {
+        if (node.callee.type !== 'Import' && importFunctions.indexOf(node.callee.name) < 0) {
+          return;
+        }
+
+        run(node, node.arguments[0]);
+      },
+    };
   },
-}
+};
diff --git a/src/rules/enforce-node-protocol-usage.js b/src/rules/enforce-node-protocol-usage.js
new file mode 100644
index 000000000..88c31fc72
--- /dev/null
+++ b/src/rules/enforce-node-protocol-usage.js
@@ -0,0 +1,147 @@
+'use strict';
+
+const isCoreModule = require('is-core-module');
+const { default: docsUrl } = require('../docsUrl');
+
+const DO_PREFER_MESSAGE_ID = 'requireNodeProtocol';
+const NEVER_PREFER_MESSAGE_ID = 'forbidNodeProtocol';
+const messages = {
+  [DO_PREFER_MESSAGE_ID]: 'Prefer `node:{{moduleName}}` over `{{moduleName}}`.',
+  [NEVER_PREFER_MESSAGE_ID]: 'Prefer `{{moduleName}}` over `node:{{moduleName}}`.',
+};
+
+function replaceStringLiteral(
+  fixer,
+  node,
+  text,
+  relativeRangeStart,
+  relativeRangeEnd,
+) {
+  const firstCharacterIndex = node.range[0] + 1;
+  const start = Number.isInteger(relativeRangeEnd)
+    ? relativeRangeStart + firstCharacterIndex
+    : firstCharacterIndex;
+  const end = Number.isInteger(relativeRangeEnd)
+    ? relativeRangeEnd + firstCharacterIndex
+    : node.range[1] - 1;
+
+  return fixer.replaceTextRange([start, end], text);
+}
+
+function isStringLiteral(node) {
+  return node.type === 'Literal' && typeof node.value === 'string';
+}
+
+function isStaticRequireWith1Param(node) {
+  return !node.optional
+    && node.callee.type === 'Identifier'
+    && node.callee.name === 'require'
+    // check for only 1 argument
+    && node.arguments.length === 1
+    && node.arguments[0]
+    && isStringLiteral(node.arguments[0]);
+}
+
+function checkAndReport(src, context) {
+  // TODO use src.quasis[0].value.raw
+  if (src.type === 'TemplateLiteral') { return; }
+  const moduleName = 'value' in src ? src.value : src.name;
+  if (typeof moduleName !== 'string') { console.log(src, moduleName); }
+  const { settings } = context;
+  const nodeVersion = settings && settings['import/node-version'];
+  if (
+    typeof nodeVersion !== 'undefined'
+    && (
+      typeof nodeVersion !== 'string'
+      || !(/^[0-9]+\.[0-9]+\.[0-9]+$/).test(nodeVersion)
+    )
+  ) {
+    throw new TypeError('`import/node-version` setting must be a string in the format "10.23.45" (a semver version, with no leading zero)');
+  }
+
+  if (context.options[0] === 'never') {
+    if (!moduleName.startsWith('node:')) { return; }
+
+    const actualModuleName = moduleName.slice(5);
+    if (!isCoreModule(actualModuleName, nodeVersion || undefined)) { return; }
+
+    context.report({
+      node: src,
+      message: messages[NEVER_PREFER_MESSAGE_ID],
+      data: { moduleName: actualModuleName },
+      /** @param {import('eslint').Rule.RuleFixer} fixer */
+      fix(fixer) {
+        return replaceStringLiteral(fixer, src, '', 0, 5);
+      },
+    });
+  } else if (context.options[0] === 'always') {
+    if (
+      moduleName.startsWith('node:')
+      || !isCoreModule(moduleName, nodeVersion || undefined)
+      || !isCoreModule(`node:${moduleName}`, nodeVersion || undefined)
+    ) {
+      return;
+    }
+
+    context.report({
+      node: src,
+      message: messages[DO_PREFER_MESSAGE_ID],
+      data: { moduleName },
+      /** @param {import('eslint').Rule.RuleFixer} fixer */
+      fix(fixer) {
+        return replaceStringLiteral(fixer, src, 'node:', 0, 0);
+      },
+    });
+  } else if (typeof context.options[0] === 'undefined') {
+    throw new Error('Missing option');
+  } else {
+    throw new Error(`Unexpected option: ${context.options[0]}`);
+  }
+}
+
+/** @type {import('eslint').Rule.RuleModule} */
+module.exports = {
+  meta: {
+    type: 'suggestion',
+    docs: {
+      description: 'Enforce either using, or omitting, the `node:` protocol when importing Node.js builtin modules.',
+      recommended: true,
+      category: 'Static analysis',
+      url: docsUrl('enforce-node-protocol-usage'),
+    },
+    fixable: 'code',
+    schema: {
+      type: 'array',
+      minItems: 1,
+      maxItems: 1,
+      items: [
+        {
+          enum: ['always', 'never'],
+        },
+      ],
+    },
+    messages,
+  },
+  create(context) {
+    return {
+      CallExpression(node) {
+        if (!isStaticRequireWith1Param(node)) { return; }
+
+        const arg = node.arguments[0];
+
+        return checkAndReport(arg, context);
+      },
+      ExportNamedDeclaration(node) {
+        return checkAndReport(node.source, context);
+      },
+      ImportDeclaration(node) {
+        return checkAndReport(node.source, context);
+      },
+      ImportExpression(node) {
+        if (!isStringLiteral(node.source)) { return; }
+
+        return checkAndReport(node.source, context);
+      },
+    };
+  },
+};
diff --git a/src/rules/export.js b/src/rules/export.js
index a9fba849e..fbbc39d75 100644
--- a/src/rules/export.js
+++ b/src/rules/export.js
@@ -1,9 +1,10 @@
-import ExportMap, { recursivePatternCapture } from '../ExportMap'
-import docsUrl from '../docsUrl'
-import includes from 'array-includes'
+import ExportMapBuilder from '../exportMap/builder';
+import recursivePatternCapture from '../exportMap/patternCapture';
+import docsUrl from '../docsUrl';
+import includes from 'array-includes';
 
 /*
-Notes on Typescript namespaces aka TSModuleDeclaration:
+Notes on TypeScript namespaces aka TSModuleDeclaration:
 
 There are two forms:
 - active namespaces: namespace Foo {} / module Foo {}
@@ -21,138 +22,214 @@ ambient namespaces:
 - have no other restrictions
 */
 
-const rootProgram = 'root'
-const tsTypePrefix = 'type:'
+const rootProgram = 'root';
+const tsTypePrefix = 'type:';
 
 /**
- * Detect function overloads like:
+ * remove function overloads like:
  * ```ts
  * export function foo(a: number);
  * export function foo(a: string);
- * export function foo(a: number|string) { return a; }
  * ```
  * @param {Set<Object>} nodes
+ */
+function removeTypescriptFunctionOverloads(nodes) {
+  nodes.forEach((node) => {
+    const declType = node.type === 'ExportDefaultDeclaration' ? node.declaration.type : node.parent.type;
+    if (
+      // eslint 6+
+      declType === 'TSDeclareFunction'
+      // eslint 4-5
+      || declType === 'TSEmptyBodyFunctionDeclaration'
+    ) {
+      nodes.delete(node);
+    }
+  });
+}
+
+/**
+ * Detect merging Namespaces with Classes, Functions, or Enums like:
+ * ```ts
+ * export class Foo { }
+ * export namespace Foo { }
+ * ```
+ * @param {Set<Object>} nodes
+ * @returns {boolean}
+ */
+function isTypescriptNamespaceMerging(nodes) {
+  const types = new Set(Array.from(nodes, (node) => node.parent.type));
+  const noNamespaceNodes = Array.from(nodes).filter((node) => node.parent.type !== 'TSModuleDeclaration');
+
+  return types.has('TSModuleDeclaration')
+    && (
+      types.size === 1
+      // Merging with functions
+      || types.size === 2 && (types.has('FunctionDeclaration') || types.has('TSDeclareFunction'))
+      || types.size === 3 && types.has('FunctionDeclaration') && types.has('TSDeclareFunction')
+      // Merging with classes or enums
+      || types.size === 2 && (types.has('ClassDeclaration') || types.has('TSEnumDeclaration')) && noNamespaceNodes.length === 1
+    );
+}
+
+/**
+ * Detect if a typescript namespace node should be reported as multiple export:
+ * ```ts
+ * export class Foo { }
+ * export function Foo();
+ * export namespace Foo { }
+ * ```
+ * @param {Object} node
+ * @param {Set<Object>} nodes
  * @returns {boolean}
  */
-function isTypescriptFunctionOverloads(nodes) {
-  const types = new Set(Array.from(nodes, node => node.parent.type))
-  return types.size === 2 && types.has('TSDeclareFunction') && types.has('FunctionDeclaration')
+function shouldSkipTypescriptNamespace(node, nodes) {
+  const types = new Set(Array.from(nodes, (node) => node.parent.type));
+
+  return !isTypescriptNamespaceMerging(nodes)
+    && node.parent.type === 'TSModuleDeclaration'
+    && (
+      types.has('TSEnumDeclaration')
+      || types.has('ClassDeclaration')
+      || types.has('FunctionDeclaration')
+      || types.has('TSDeclareFunction')
+    );
 }
 
 module.exports = {
   meta: {
     type: 'problem',
     docs: {
+      category: 'Helpful warnings',
+      description: 'Forbid any invalid exports, i.e. re-export of the same name.',
       url: docsUrl('export'),
     },
+    schema: [],
   },
 
-  create: function (context) {
-    const namespace = new Map([[rootProgram, new Map()]])
+  create(context) {
+    const namespace = new Map([[rootProgram, new Map()]]);
 
     function addNamed(name, node, parent, isType) {
       if (!namespace.has(parent)) {
-        namespace.set(parent, new Map())
+        namespace.set(parent, new Map());
       }
-      const named = namespace.get(parent)
+      const named = namespace.get(parent);
 
-      const key = isType ? `${tsTypePrefix}${name}` : name
-      let nodes = named.get(key)
+      const key = isType ? `${tsTypePrefix}${name}` : name;
+      let nodes = named.get(key);
 
       if (nodes == null) {
-        nodes = new Set()
-        named.set(key, nodes)
+        nodes = new Set();
+        named.set(key, nodes);
       }
 
-      nodes.add(node)
+      nodes.add(node);
     }
 
     function getParent(node) {
       if (node.parent && node.parent.type === 'TSModuleBlock') {
-        return node.parent.parent
+        return node.parent.parent;
       }
 
       // just in case somehow a non-ts namespace export declaration isn't directly
       // parented to the root Program node
-      return rootProgram
+      return rootProgram;
     }
 
     return {
-      'ExportDefaultDeclaration': (node) => addNamed('default', node, getParent(node)),
+      ExportDefaultDeclaration(node) {
+        addNamed('default', node, getParent(node));
+      },
 
-      'ExportSpecifier': (node) => addNamed(node.exported.name, node.exported, getParent(node)),
+      ExportSpecifier(node) {
+        addNamed(
+          node.exported.name || node.exported.value,
+          node.exported,
+          getParent(node.parent),
+        );
+      },
 
-      'ExportNamedDeclaration': function (node) {
-        if (node.declaration == null) return
+      ExportNamedDeclaration(node) {
+        if (node.declaration == null) { return; }
 
-        const parent = getParent(node)
-        // support for old typescript versions
-        const isTypeVariableDecl = node.declaration.kind === 'type'
+        const parent = getParent(node);
+        // support for old TypeScript versions
+        const isTypeVariableDecl = node.declaration.kind === 'type';
 
         if (node.declaration.id != null) {
           if (includes([
             'TSTypeAliasDeclaration',
             'TSInterfaceDeclaration',
           ], node.declaration.type)) {
-            addNamed(node.declaration.id.name, node.declaration.id, parent, true)
+            addNamed(node.declaration.id.name, node.declaration.id, parent, true);
           } else {
-            addNamed(node.declaration.id.name, node.declaration.id, parent, isTypeVariableDecl)
+            addNamed(node.declaration.id.name, node.declaration.id, parent, isTypeVariableDecl);
           }
         }
 
         if (node.declaration.declarations != null) {
-          for (let declaration of node.declaration.declarations) {
-            recursivePatternCapture(declaration.id, v =>
-              addNamed(v.name, v, parent, isTypeVariableDecl))
+          for (const declaration of node.declaration.declarations) {
+            recursivePatternCapture(declaration.id, (v) => { addNamed(v.name, v, parent, isTypeVariableDecl); });
           }
         }
       },
 
-      'ExportAllDeclaration': function (node) {
-        if (node.source == null) return // not sure if this is ever true
+      ExportAllDeclaration(node) {
+        if (node.source == null) { return; } // not sure if this is ever true
 
-        const remoteExports = ExportMap.get(node.source.value, context)
-        if (remoteExports == null) return
+        // `export * as X from 'path'` does not conflict
+        if (node.exported && node.exported.name) { return; }
+
+        const remoteExports = ExportMapBuilder.get(node.source.value, context);
+        if (remoteExports == null) { return; }
 
         if (remoteExports.errors.length) {
-          remoteExports.reportErrors(context, node)
-          return
+          remoteExports.reportErrors(context, node);
+          return;
         }
 
-        const parent = getParent(node)
+        const parent = getParent(node);
 
-        let any = false
-        remoteExports.forEach((v, name) =>
-          name !== 'default' &&
-          (any = true) && // poor man's filter
-          addNamed(name, node, parent))
+        let any = false;
+        remoteExports.forEach((v, name) => {
+          if (name !== 'default') {
+            any = true; // poor man's filter
+            addNamed(name, node, parent);
+          }
+        });
 
         if (!any) {
-          context.report(node.source,
-            `No named exports found in module '${node.source.value}'.`)
+          context.report(
+            node.source,
+            `No named exports found in module '${node.source.value}'.`,
+          );
         }
       },
 
-      'Program:exit': function () {
-        for (let [, named] of namespace) {
-          for (let [name, nodes] of named) {
-            if (nodes.size <= 1) continue
+      'Program:exit'() {
+        for (const [, named] of namespace) {
+          for (const [name, nodes] of named) {
+            removeTypescriptFunctionOverloads(nodes);
+
+            if (nodes.size <= 1) { continue; }
 
-            if (isTypescriptFunctionOverloads(nodes)) continue
+            if (isTypescriptNamespaceMerging(nodes)) { continue; }
+
+            for (const node of nodes) {
+              if (shouldSkipTypescriptNamespace(node, nodes)) { continue; }
 
-            for (let node of nodes) {
               if (name === 'default') {
-                context.report(node, 'Multiple default exports.')
+                context.report(node, 'Multiple default exports.');
               } else {
                 context.report(
                   node,
-                  `Multiple exports of name '${name.replace(tsTypePrefix, '')}'.`
-                )
+                  `Multiple exports of name '${name.replace(tsTypePrefix, '')}'.`,
+                );
               }
             }
           }
         }
       },
-    }
+    };
   },
-}
+};
diff --git a/src/rules/exports-last.js b/src/rules/exports-last.js
index fc40cc827..1e79f8339 100644
--- a/src/rules/exports-last.js
+++ b/src/rules/exports-last.js
@@ -1,40 +1,40 @@
-import docsUrl from '../docsUrl'
+import findLastIndex from 'array.prototype.findlastindex';
+
+import docsUrl from '../docsUrl';
 
 function isNonExportStatement({ type }) {
-  return type !== 'ExportDefaultDeclaration' &&
-    type !== 'ExportNamedDeclaration' &&
-    type !== 'ExportAllDeclaration'
+  return type !== 'ExportDefaultDeclaration'
+    && type !== 'ExportNamedDeclaration'
+    && type !== 'ExportAllDeclaration';
 }
 
 module.exports = {
   meta: {
     type: 'suggestion',
     docs: {
+      category: 'Style guide',
+      description: 'Ensure all exports appear after other statements.',
       url: docsUrl('exports-last'),
     },
+    schema: [],
   },
 
-  create: function (context) {
+  create(context) {
     return {
-      Program: function ({ body }) {
-        const lastNonExportStatementIndex = body.reduce(function findLastIndex(acc, item, index) {
-          if (isNonExportStatement(item)) {
-            return index
-          }
-          return acc
-        }, -1)
+      Program({ body }) {
+        const lastNonExportStatementIndex = findLastIndex(body, isNonExportStatement);
 
         if (lastNonExportStatementIndex !== -1) {
-          body.slice(0, lastNonExportStatementIndex).forEach(function checkNonExport(node) {
+          body.slice(0, lastNonExportStatementIndex).forEach((node) => {
             if (!isNonExportStatement(node)) {
               context.report({
                 node,
                 message: 'Export statements should appear at the end of the file',
-              })
+              });
             }
-          })
+          });
         }
       },
-    }
+    };
   },
-}
+};
diff --git a/src/rules/extensions.js b/src/rules/extensions.js
index b72c91bad..2aeef6475 100644
--- a/src/rules/extensions.js
+++ b/src/rules/extensions.js
@@ -1,62 +1,100 @@
-import path from 'path'
+import path from 'path';
 
-import resolve from 'eslint-module-utils/resolve'
-import { isBuiltIn, isExternalModuleMain, isScopedMain } from '../core/importType'
-import docsUrl from '../docsUrl'
+import minimatch from 'minimatch';
+import resolve from 'eslint-module-utils/resolve';
+import { isBuiltIn, isExternalModule, isScoped } from '../core/importType';
+import moduleVisitor from 'eslint-module-utils/moduleVisitor';
+import docsUrl from '../docsUrl';
 
-const enumValues = { enum: [ 'always', 'ignorePackages', 'never' ] }
+const enumValues = { enum: ['always', 'ignorePackages', 'never'] };
 const patternProperties = {
   type: 'object',
   patternProperties: { '.*': enumValues },
-}
+};
 const properties = {
   type: 'object',
   properties: {
-    'pattern': patternProperties,
-    'ignorePackages': { type: 'boolean' },
+    pattern: patternProperties,
+    checkTypeImports: { type: 'boolean' },
+    ignorePackages: { type: 'boolean' },
+    pathGroupOverrides: {
+      type: 'array',
+      items: {
+        type: 'object',
+        properties: {
+          pattern: {
+            type: 'string',
+          },
+          patternOptions: {
+            type: 'object',
+          },
+          action: {
+            type: 'string',
+            enum: ['enforce', 'ignore'],
+          },
+        },
+        additionalProperties: false,
+        required: ['pattern', 'action'],
+      },
+    },
   },
-}
+};
 
 function buildProperties(context) {
 
-    const result = {
-      defaultConfig: 'never',
-      pattern: {},
-      ignorePackages: false,
+  const result = {
+    defaultConfig: 'never',
+    pattern: {},
+    ignorePackages: false,
+  };
+
+  context.options.forEach((obj) => {
+
+    // If this is a string, set defaultConfig to its value
+    if (typeof obj === 'string') {
+      result.defaultConfig = obj;
+      return;
     }
 
-    context.options.forEach(obj => {
+    // If this is not the new structure, transfer all props to result.pattern
+    if (obj.pattern === undefined && obj.ignorePackages === undefined && obj.checkTypeImports === undefined) {
+      Object.assign(result.pattern, obj);
+      return;
+    }
 
-      // If this is a string, set defaultConfig to its value
-      if (typeof obj === 'string') {
-        result.defaultConfig = obj
-        return
-      }
+    // If pattern is provided, transfer all props
+    if (obj.pattern !== undefined) {
+      Object.assign(result.pattern, obj.pattern);
+    }
 
-      // If this is not the new structure, transfer all props to result.pattern
-      if (obj.pattern === undefined && obj.ignorePackages === undefined) {
-        Object.assign(result.pattern, obj)
-        return
-      }
+    // If ignorePackages is provided, transfer it to result
+    if (obj.ignorePackages !== undefined) {
+      result.ignorePackages = obj.ignorePackages;
+    }
 
-      // If pattern is provided, transfer all props
-      if (obj.pattern !== undefined) {
-        Object.assign(result.pattern, obj.pattern)
-      }
+    if (obj.checkTypeImports !== undefined) {
+      result.checkTypeImports = obj.checkTypeImports;
+    }
 
-      // If ignorePackages is provided, transfer it to result
-      if (obj.ignorePackages !== undefined) {
-        result.ignorePackages = obj.ignorePackages
-      }
-    })
+    if (obj.pathGroupOverrides !== undefined) {
+      result.pathGroupOverrides = obj.pathGroupOverrides;
+    }
+  });
 
-    return result
+  if (result.defaultConfig === 'ignorePackages') {
+    result.defaultConfig = 'always';
+    result.ignorePackages = true;
+  }
+
+  return result;
 }
 
 module.exports = {
   meta: {
     type: 'suggestion',
     docs: {
+      category: 'Style guide',
+      description: 'Ensure consistent use of file extension within the import path.',
       url: docsUrl('extensions'),
     },
 
@@ -97,74 +135,108 @@ module.exports = {
     },
   },
 
-  create: function (context) {
+  create(context) {
 
-    const props = buildProperties(context)
+    const props = buildProperties(context);
 
     function getModifier(extension) {
-      return props.pattern[extension] || props.defaultConfig
+      return props.pattern[extension] || props.defaultConfig;
     }
 
-    function isUseOfExtensionRequired(extension, isPackageMain) {
-      return getModifier(extension) === 'always' && (!props.ignorePackages || !isPackageMain)
+    function isUseOfExtensionRequired(extension, isPackage) {
+      return getModifier(extension) === 'always' && (!props.ignorePackages || !isPackage);
     }
 
     function isUseOfExtensionForbidden(extension) {
-      return getModifier(extension) === 'never'
+      return getModifier(extension) === 'never';
     }
 
     function isResolvableWithoutExtension(file) {
-      const extension = path.extname(file)
-      const fileWithoutExtension = file.slice(0, -extension.length)
-      const resolvedFileWithoutExtension = resolve(fileWithoutExtension, context)
+      const extension = path.extname(file);
+      const fileWithoutExtension = file.slice(0, -extension.length);
+      const resolvedFileWithoutExtension = resolve(fileWithoutExtension, context);
+
+      return resolvedFileWithoutExtension === resolve(file, context);
+    }
+
+    function isExternalRootModule(file) {
+      if (file === '.' || file === '..') { return false; }
+      const slashCount = file.split('/').length - 1;
 
-      return resolvedFileWithoutExtension === resolve(file, context)
+      if (slashCount === 0)  { return true; }
+      if (isScoped(file) && slashCount <= 1) { return true; }
+      return false;
+    }
+
+    function computeOverrideAction(pathGroupOverrides, path) {
+      for (let i = 0, l = pathGroupOverrides.length; i < l; i++) {
+        const { pattern, patternOptions, action } = pathGroupOverrides[i];
+        if (minimatch(path, pattern, patternOptions || { nocomment: true })) {
+          return action;
+        }
+      }
     }
 
-    function checkFileExtension(node) {
-      const { source } = node
+    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; }
+
+      const importPathWithQueryString = source.value;
 
-      // bail if the declaration doesn't have a source, e.g. "export { foo };"
-      if (!source) return
+      // If not undefined, the user decided if rules are enforced on this import
+      const overrideAction = computeOverrideAction(
+        props.pathGroupOverrides || [],
+        importPathWithQueryString,
+      );
 
-      const importPath = source.value
+      if (overrideAction === 'ignore') {
+        return;
+      }
 
       // don't enforce anything on builtins
-      if (isBuiltIn(importPath, context.settings)) return
+      if (!overrideAction && isBuiltIn(importPathWithQueryString, context.settings)) { return; }
+
+      const importPath = importPathWithQueryString.replace(/\?(.*)$/, '');
 
-      const resolvedPath = resolve(importPath, context)
+      // don't enforce in root external packages as they may have names with `.js`.
+      // Like `import Decimal from decimal.js`)
+      if (!overrideAction && isExternalRootModule(importPath)) { return; }
+
+      const resolvedPath = resolve(importPath, context);
 
       // get extension from resolved path, if possible.
       // for unresolved, use source value.
-      const extension = path.extname(resolvedPath || importPath).substring(1)
+      const extension = path.extname(resolvedPath || importPath).substring(1);
 
       // determine if this is a module
-      const isPackageMain = isExternalModuleMain(importPath, context.settings)
-        || isScopedMain(importPath)
+      const isPackage = isExternalModule(
+        importPath,
+        resolve(importPath, context),
+        context,
+      ) || isScoped(importPath);
 
       if (!extension || !importPath.endsWith(`.${extension}`)) {
-        const extensionRequired = isUseOfExtensionRequired(extension, isPackageMain)
-        const extensionForbidden = isUseOfExtensionForbidden(extension)
+        // ignore type-only imports and exports
+        if (!props.checkTypeImports && (node.importKind === 'type' || node.exportKind === 'type')) { return; }
+        const extensionRequired = isUseOfExtensionRequired(extension, !overrideAction && isPackage);
+        const extensionForbidden = isUseOfExtensionForbidden(extension);
         if (extensionRequired && !extensionForbidden) {
           context.report({
             node: source,
             message:
-              `Missing file extension ${extension ? `"${extension}" ` : ''}for "${importPath}"`,
-          })
+              `Missing file extension ${extension ? `"${extension}" ` : ''}for "${importPathWithQueryString}"`,
+          });
         }
       } else if (extension) {
         if (isUseOfExtensionForbidden(extension) && isResolvableWithoutExtension(importPath)) {
           context.report({
             node: source,
-            message: `Unexpected use of file extension "${extension}" for "${importPath}"`,
-          })
+            message: `Unexpected use of file extension "${extension}" for "${importPathWithQueryString}"`,
+          });
         }
       }
     }
 
-    return {
-      ImportDeclaration: checkFileExtension,
-      ExportNamedDeclaration: checkFileExtension,
-    }
+    return moduleVisitor(checkFileExtension, { commonjs: true });
   },
-}
+};
diff --git a/src/rules/first.js b/src/rules/first.js
index 7bcd1fa22..e7df26ac9 100644
--- a/src/rules/first.js
+++ b/src/rules/first.js
@@ -1,126 +1,146 @@
-import docsUrl from '../docsUrl'
+import { getDeclaredVariables, getSourceCode } from 'eslint-module-utils/contextCompat';
+
+import docsUrl from '../docsUrl';
+
+function getImportValue(node) {
+  return node.type === 'ImportDeclaration'
+    ? node.source.value
+    : node.moduleReference.expression.value;
+}
 
 module.exports = {
   meta: {
     type: 'suggestion',
     docs: {
+      category: 'Style guide',
+      description: 'Ensure all imports appear before other statements.',
       url: docsUrl('first'),
     },
     fixable: 'code',
+    schema: [
+      {
+        type: 'string',
+        enum: ['absolute-first', 'disable-absolute-first'],
+      },
+    ],
   },
 
-  create: function (context) {
-    function isPossibleDirective (node) {
-      return node.type === 'ExpressionStatement' &&
-        node.expression.type === 'Literal' &&
-        typeof node.expression.value === 'string'
+  create(context) {
+    function isPossibleDirective(node) {
+      return node.type === 'ExpressionStatement'
+        && node.expression.type === 'Literal'
+        && typeof node.expression.value === 'string';
     }
 
     return {
-      'Program': function (n) {
-        const body = n.body
-            , absoluteFirst = context.options[0] === 'absolute-first'
-            , message = 'Import in body of module; reorder to top.'
-            , sourceCode = context.getSourceCode()
-            , originSourceCode = sourceCode.getText()
-        let nonImportCount = 0
-          , anyExpressions = false
-          , anyRelative = false
-          , lastLegalImp = null
-          , errorInfos = []
-          , shouldSort = true
-          , lastSortNodesIndex = 0
-        body.forEach(function (node, index){
+      Program(n) {
+        const body = n.body;
+        if (!body) {
+          return;
+        }
+        const absoluteFirst = context.options[0] === 'absolute-first';
+        const message = 'Import in body of module; reorder to top.';
+        const sourceCode = getSourceCode(context);
+        const originSourceCode = sourceCode.getText();
+        let nonImportCount = 0;
+        let anyExpressions = false;
+        let anyRelative = false;
+        let lastLegalImp = null;
+        const errorInfos = [];
+        let shouldSort = true;
+        let lastSortNodesIndex = 0;
+        body.forEach(function (node, index) {
           if (!anyExpressions && isPossibleDirective(node)) {
-            return
+            return;
           }
 
-          anyExpressions = true
+          anyExpressions = true;
 
-          if (node.type === 'ImportDeclaration') {
+          if (node.type === 'ImportDeclaration' || node.type === 'TSImportEqualsDeclaration') {
             if (absoluteFirst) {
-              if (/^\./.test(node.source.value)) {
-                anyRelative = true
+              if ((/^\./).test(getImportValue(node))) {
+                anyRelative = true;
               } else if (anyRelative) {
                 context.report({
-                  node: node.source,
+                  node: node.type === 'ImportDeclaration' ? node.source : node.moduleReference,
                   message: 'Absolute imports should come before relative imports.',
-                })
+                });
               }
             }
             if (nonImportCount > 0) {
-              for (let variable of context.getDeclaredVariables(node)) {
-                if (!shouldSort) break
-                const references = variable.references
+              for (const variable of getDeclaredVariables(context, node)) {
+                if (!shouldSort) { break; }
+                const references = variable.references;
                 if (references.length) {
-                  for (let reference of references) {
+                  for (const reference of references) {
                     if (reference.identifier.range[0] < node.range[1]) {
-                      shouldSort = false
-                      break
+                      shouldSort = false;
+                      break;
                     }
                   }
                 }
               }
-              shouldSort && (lastSortNodesIndex = errorInfos.length)
+              shouldSort && (lastSortNodesIndex = errorInfos.length);
               errorInfos.push({
                 node,
                 range: [body[index - 1].range[1], node.range[1]],
-              })
+              });
             } else {
-              lastLegalImp = node
+              lastLegalImp = node;
             }
           } else {
-            nonImportCount++
+            nonImportCount++;
           }
-        })
-        if (!errorInfos.length) return
+        });
+        if (!errorInfos.length) { return; }
         errorInfos.forEach(function (errorInfo, index) {
-          const node = errorInfo.node
-              , infos = {
-                node,
-                message,
-              }
+          const node = errorInfo.node;
+          const infos = {
+            node,
+            message,
+          };
           if (index < lastSortNodesIndex) {
             infos.fix = function (fixer) {
-              return fixer.insertTextAfter(node, '')
-            }
+              return fixer.insertTextAfter(node, '');
+            };
           } else if (index === lastSortNodesIndex) {
-            const sortNodes = errorInfos.slice(0, lastSortNodesIndex + 1)
+            const sortNodes = errorInfos.slice(0, lastSortNodesIndex + 1);
             infos.fix = function (fixer) {
               const removeFixers = sortNodes.map(function (_errorInfo) {
-                    return fixer.removeRange(_errorInfo.range)
-                  })
-                  , range = [0, removeFixers[removeFixers.length - 1].range[1]]
+                return fixer.removeRange(_errorInfo.range);
+              });
+              const range = [0, removeFixers[removeFixers.length - 1].range[1]];
               let insertSourceCode = sortNodes.map(function (_errorInfo) {
-                    const nodeSourceCode = String.prototype.slice.apply(
-                      originSourceCode, _errorInfo.range
-                    )
-                    if (/\S/.test(nodeSourceCode[0])) {
-                      return '\n' + nodeSourceCode
-                    }
-                    return nodeSourceCode
-                  }).join('')
-                , insertFixer = null
-                , replaceSourceCode = ''
+                const nodeSourceCode = String.prototype.slice.apply(
+                  originSourceCode, _errorInfo.range,
+                );
+                if ((/\S/).test(nodeSourceCode[0])) {
+                  return `\n${nodeSourceCode}`;
+                }
+                return nodeSourceCode;
+              }).join('');
+              let insertFixer = null;
+              let replaceSourceCode = '';
               if (!lastLegalImp) {
-                  insertSourceCode =
-                    insertSourceCode.trim() + insertSourceCode.match(/^(\s+)/)[0]
+                insertSourceCode = insertSourceCode.trim() + insertSourceCode.match(/^(\s+)/)[0];
               }
-              insertFixer = lastLegalImp ?
-                            fixer.insertTextAfter(lastLegalImp, insertSourceCode) :
-                            fixer.insertTextBefore(body[0], insertSourceCode)
-              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]
-                ) + computedFixer.text)
-              })
-              return fixer.replaceTextRange(range, replaceSourceCode)
-            }
+              insertFixer = lastLegalImp
+                ? fixer.insertTextAfter(lastLegalImp, insertSourceCode)
+                : fixer.insertTextBefore(body[0], insertSourceCode);
+
+              const fixers = [insertFixer].concat(removeFixers);
+              fixers.forEach((computedFixer, i) => {
+                replaceSourceCode += originSourceCode.slice(
+                  fixers[i - 1] ? fixers[i - 1].range[1] : 0, computedFixer.range[0],
+                ) + computedFixer.text;
+              });
+
+              return fixer.replaceTextRange(range, replaceSourceCode);
+            };
           }
-          context.report(infos)
-        })
+          context.report(infos);
+        });
       },
-    }
+    };
   },
-}
+};
diff --git a/src/rules/group-exports.js b/src/rules/group-exports.js
index d650fff87..7978130d3 100644
--- a/src/rules/group-exports.js
+++ b/src/rules/group-exports.js
@@ -1,16 +1,20 @@
-import docsUrl from '../docsUrl'
+import docsUrl from '../docsUrl';
+import values from 'object.values';
+import flat from 'array.prototype.flat';
 
 const meta = {
   type: 'suggestion',
   docs: {
+    category: 'Style guide',
+    description: 'Prefer named exports to be grouped together in a single export declaration',
     url: docsUrl('group-exports'),
   },
-}
+};
 /* eslint-disable max-len */
 const errors = {
   ExportNamedDeclaration: 'Multiple named export declarations; consolidate all named exports into a single export declaration',
   AssignmentExpression: 'Multiple CommonJS exports; consolidate all exports into a single assignment to `module.exports`',
-}
+};
 /* eslint-enable max-len */
 
 /**
@@ -26,80 +30,126 @@ const errors = {
  * @private
  */
 function accessorChain(node) {
-  const chain = []
+  const chain = [];
 
   do {
-    chain.unshift(node.property.name)
+    chain.unshift(node.property.name);
 
     if (node.object.type === 'Identifier') {
-      chain.unshift(node.object.name)
-      break
+      chain.unshift(node.object.name);
+      break;
     }
 
-    node = node.object
-  } while (node.type === 'MemberExpression')
+    node = node.object;
+  } while (node.type === 'MemberExpression');
 
-  return chain
+  return chain;
 }
 
 function create(context) {
   const nodes = {
-    modules: new Set(),
-    commonjs: new Set(),
-  }
+    modules: {
+      set: new Set(),
+      sources: {},
+    },
+    types: {
+      set: new Set(),
+      sources: {},
+    },
+    commonjs: {
+      set: new Set(),
+    },
+  };
 
   return {
     ExportNamedDeclaration(node) {
-      nodes.modules.add(node)
+      const target = node.exportKind === 'type' ? nodes.types : nodes.modules;
+      if (!node.source) {
+        target.set.add(node);
+      } else if (Array.isArray(target.sources[node.source.value])) {
+        target.sources[node.source.value].push(node);
+      } else {
+        target.sources[node.source.value] = [node];
+      }
     },
 
     AssignmentExpression(node) {
       if (node.left.type !== 'MemberExpression') {
-        return
+        return;
       }
 
-      const chain = accessorChain(node.left)
+      const chain = accessorChain(node.left);
 
       // Assignments to module.exports
       // Deeper assignments are ignored since they just modify what's already being exported
       // (ie. module.exports.exported.prop = true is ignored)
       if (chain[0] === 'module' && chain[1] === 'exports' && chain.length <= 3) {
-        nodes.commonjs.add(node)
-        return
+        nodes.commonjs.set.add(node);
+        return;
       }
 
       // Assignments to exports (exports.* = *)
       if (chain[0] === 'exports' && chain.length === 2) {
-        nodes.commonjs.add(node)
-        return
+        nodes.commonjs.set.add(node);
+        return;
       }
     },
 
     'Program:exit': function onExit() {
       // Report multiple `export` declarations (ES2015 modules)
-      if (nodes.modules.size > 1) {
-        nodes.modules.forEach(node => {
+      if (nodes.modules.set.size > 1) {
+        nodes.modules.set.forEach((node) => {
+          context.report({
+            node,
+            message: errors[node.type],
+          });
+        });
+      }
+
+      // Report multiple `aggregated exports` from the same module (ES2015 modules)
+      flat(values(nodes.modules.sources)
+        .filter((nodesWithSource) => Array.isArray(nodesWithSource) && nodesWithSource.length > 1))
+        .forEach((node) => {
+          context.report({
+            node,
+            message: errors[node.type],
+          });
+        });
+
+      // Report multiple `export type` declarations (FLOW ES2015 modules)
+      if (nodes.types.set.size > 1) {
+        nodes.types.set.forEach((node) => {
           context.report({
             node,
             message: errors[node.type],
-          })
-        })
+          });
+        });
       }
 
+      // Report multiple `aggregated type exports` from the same module (FLOW ES2015 modules)
+      flat(values(nodes.types.sources)
+        .filter((nodesWithSource) => Array.isArray(nodesWithSource) && nodesWithSource.length > 1))
+        .forEach((node) => {
+          context.report({
+            node,
+            message: errors[node.type],
+          });
+        });
+
       // Report multiple `module.exports` assignments (CommonJS)
-      if (nodes.commonjs.size > 1) {
-        nodes.commonjs.forEach(node => {
+      if (nodes.commonjs.set.size > 1) {
+        nodes.commonjs.set.forEach((node) => {
           context.report({
             node,
             message: errors[node.type],
-          })
-        })
+          });
+        });
       }
     },
-  }
+  };
 }
 
 module.exports = {
   meta,
   create,
-}
+};
diff --git a/src/rules/imports-first.js b/src/rules/imports-first.js
index 7ed9accc4..966367e99 100644
--- a/src/rules/imports-first.js
+++ b/src/rules/imports-first.js
@@ -1,12 +1,15 @@
-import docsUrl from '../docsUrl'
+import docsUrl from '../docsUrl';
 
-const first = require('./first')
+const first = require('./first');
 
-const newMeta = Object.assign({}, first.meta, {
+const newMeta = {
+  ...first.meta,
   deprecated: true,
   docs: {
+    category: 'Style guide',
+    description: 'Replaced by `import/first`.',
     url: docsUrl('imports-first', '7b25c1cb95ee18acc1531002fd343e1e6031f9ed'),
   },
-})
+};
 
-module.exports = Object.assign({}, first, { meta: newMeta })
+module.exports = { ...first, meta: newMeta };
diff --git a/src/rules/max-dependencies.js b/src/rules/max-dependencies.js
index 7e1fdb101..488e90618 100644
--- a/src/rules/max-dependencies.js
+++ b/src/rules/max-dependencies.js
@@ -1,58 +1,60 @@
-import isStaticRequire from '../core/staticRequire'
-import docsUrl from '../docsUrl'
+import moduleVisitor from 'eslint-module-utils/moduleVisitor';
+import docsUrl from '../docsUrl';
 
-const DEFAULT_MAX = 10
+const DEFAULT_MAX = 10;
+const DEFAULT_IGNORE_TYPE_IMPORTS = false;
+const TYPE_IMPORT = 'type';
 
 const countDependencies = (dependencies, lastNode, context) => {
-  const {max} = context.options[0] || { max: DEFAULT_MAX }
+  const { max } = context.options[0] || { max: DEFAULT_MAX };
 
   if (dependencies.size > max) {
-    context.report(
-      lastNode,
-      `Maximum number of dependencies (${max}) exceeded.`
-    )
+    context.report(lastNode, `Maximum number of dependencies (${max}) exceeded.`);
   }
-}
+};
 
 module.exports = {
   meta: {
     type: 'suggestion',
     docs: {
+      category: 'Style guide',
+      description: 'Enforce the maximum number of dependencies a module can have.',
       url: docsUrl('max-dependencies'),
     },
 
     schema: [
       {
-        'type': 'object',
-        'properties': {
-          'max': { 'type': 'number' },
+        type: 'object',
+        properties: {
+          max: { type: 'number' },
+          ignoreTypeImports: { type: 'boolean' },
         },
-        'additionalProperties': false,
+        additionalProperties: false,
       },
     ],
   },
 
-  create: context => {
-    const dependencies = new Set() // keep track of dependencies
-    let lastNode // keep track of the last node to report on
+  create(context) {
+    const {
+      ignoreTypeImports = DEFAULT_IGNORE_TYPE_IMPORTS,
+    } = context.options[0] || {};
 
-    return {
-      ImportDeclaration(node) {
-        dependencies.add(node.source.value)
-        lastNode = node.source
-      },
-
-      CallExpression(node) {
-        if (isStaticRequire(node)) {
-          const [ requirePath ] = node.arguments
-          dependencies.add(requirePath.value)
-          lastNode = node
-        }
-      },
+    const dependencies = new Set(); // keep track of dependencies
+    let lastNode; // keep track of the last node to report on
 
-      'Program:exit': function () {
-        countDependencies(dependencies, lastNode, context)
+    return {
+      'Program:exit'() {
+        countDependencies(dependencies, lastNode, context);
       },
-    }
+      ...moduleVisitor(
+        (source, { importKind }) => {
+          if (importKind !== TYPE_IMPORT || !ignoreTypeImports) {
+            dependencies.add(source.value);
+          }
+          lastNode = source;
+        },
+        { commonjs: true },
+      ),
+    };
   },
-}
+};
diff --git a/src/rules/named.js b/src/rules/named.js
index cc9199d48..ab5f3103f 100644
--- a/src/rules/named.js
+++ b/src/rules/named.js
@@ -1,71 +1,145 @@
-import * as path from 'path'
-import Exports from '../ExportMap'
-import docsUrl from '../docsUrl'
+import * as path from 'path';
+import { getFilename, getPhysicalFilename } from 'eslint-module-utils/contextCompat';
+
+import ExportMapBuilder from '../exportMap/builder';
+import docsUrl from '../docsUrl';
 
 module.exports = {
   meta: {
     type: 'problem',
     docs: {
+      category: 'Static analysis',
+      description: 'Ensure named imports correspond to a named export in the remote file.',
       url: docsUrl('named'),
     },
+    schema: [
+      {
+        type: 'object',
+        properties: {
+          commonjs: {
+            type: 'boolean',
+          },
+        },
+        additionalProperties: false,
+      },
+    ],
   },
 
-  create: function (context) {
+  create(context) {
+    const options = context.options[0] || {};
+
     function checkSpecifiers(key, type, node) {
       // ignore local exports and type imports/exports
-      if (node.source == null || node.importKind === 'type' ||
-          node.importKind === 'typeof'  || node.exportKind === 'type') {
-        return
+      if (
+        node.source == null
+        || node.importKind === 'type'
+        || node.importKind === 'typeof'
+        || node.exportKind === 'type'
+      ) {
+        return;
       }
 
-      if (!node.specifiers
-            .some(function (im) { return im.type === type })) {
-        return // no named imports/exports
+      if (!node.specifiers.some((im) => im.type === type)) {
+        return; // no named imports/exports
       }
 
-      const imports = Exports.get(node.source.value, context)
-      if (imports == null) return
+      const imports = ExportMapBuilder.get(node.source.value, context);
+      if (imports == null || imports.parseGoal === 'ambiguous') {
+        return;
+      }
 
       if (imports.errors.length) {
-        imports.reportErrors(context, node)
-        return
+        imports.reportErrors(context, node);
+        return;
       }
 
       node.specifiers.forEach(function (im) {
-        if (im.type !== type) return
+        if (
+          im.type !== type
+          // ignore type imports
+          || im.importKind === 'type' || im.importKind === 'typeof'
+        ) {
+          return;
+        }
 
-        // ignore type imports
-        if (im.importKind === 'type' || im.importKind === 'typeof') return
+        const name = im[key].name || im[key].value;
 
-        const deepLookup = imports.hasDeep(im[key].name)
+        const deepLookup = imports.hasDeep(name);
 
         if (!deepLookup.found) {
           if (deepLookup.path.length > 1) {
             const deepPath = deepLookup.path
-              .map(i => path.relative(path.dirname(context.getFilename()), i.path))
-              .join(' -> ')
+              .map((i) => path.relative(path.dirname(getPhysicalFilename(context)), i.path))
+              .join(' -> ');
 
-            context.report(im[key],
-              `${im[key].name} not found via ${deepPath}`)
+            context.report(im[key], `${name} not found via ${deepPath}`);
           } else {
-            context.report(im[key],
-              im[key].name + ' not found in \'' + node.source.value + '\'')
+            context.report(im[key], `${name} not found in '${node.source.value}'`);
           }
         }
-      })
+      });
     }
 
-    return {
-      'ImportDeclaration': checkSpecifiers.bind( null
-                                               , 'imported'
-                                               , 'ImportSpecifier'
-                                               ),
-
-      'ExportNamedDeclaration': checkSpecifiers.bind( null
-                                                    , 'local'
-                                                    , 'ExportSpecifier'
-                                                    ),
+    function checkRequire(node) {
+      if (
+        !options.commonjs
+        || node.type !== 'VariableDeclarator'
+        // return if it's not an object destructure or it's an empty object destructure
+        || !node.id || node.id.type !== 'ObjectPattern' || node.id.properties.length === 0
+        // return if there is no call expression on the right side
+        || !node.init || node.init.type !== 'CallExpression'
+      ) {
+        return;
+      }
+
+      const call = node.init;
+      const [source] = call.arguments;
+      const variableImports = node.id.properties;
+      const variableExports = ExportMapBuilder.get(source.value, context);
+
+      if (
+        // return if it's not a commonjs require statement
+        call.callee.type !== 'Identifier' || call.callee.name !== 'require' || call.arguments.length !== 1
+        // return if it's not a string source
+        || source.type !== 'Literal'
+        || variableExports == null
+        || variableExports.parseGoal === 'ambiguous'
+      ) {
+        return;
+      }
+
+      if (variableExports.errors.length) {
+        variableExports.reportErrors(context, node);
+        return;
+      }
+
+      variableImports.forEach(function (im) {
+        if (im.type !== 'Property' || !im.key || im.key.type !== 'Identifier') {
+          return;
+        }
+
+        const deepLookup = variableExports.hasDeep(im.key.name);
+
+        if (!deepLookup.found) {
+          if (deepLookup.path.length > 1) {
+            const deepPath = deepLookup.path
+              .map((i) => path.relative(path.dirname(getFilename(context)), i.path))
+              .join(' -> ');
+
+            context.report(im.key, `${im.key.name} not found via ${deepPath}`);
+          } else {
+            context.report(im.key, `${im.key.name} not found in '${source.value}'`);
+          }
+        }
+      });
     }
 
+    return {
+      ImportDeclaration: checkSpecifiers.bind(null, 'imported', 'ImportSpecifier'),
+
+      ExportNamedDeclaration: checkSpecifiers.bind(null, 'local', 'ExportSpecifier'),
+
+      VariableDeclarator: checkRequire,
+    };
   },
-}
+};
diff --git a/src/rules/namespace.js b/src/rules/namespace.js
index dd840b86f..b2de7f225 100644
--- a/src/rules/namespace.js
+++ b/src/rules/namespace.js
@@ -1,165 +1,170 @@
-import declaredScope from 'eslint-module-utils/declaredScope'
-import Exports from '../ExportMap'
-import importDeclaration from '../importDeclaration'
-import docsUrl from '../docsUrl'
+import declaredScope from 'eslint-module-utils/declaredScope';
+import ExportMapBuilder from '../exportMap/builder';
+import ExportMap from '../exportMap';
+import importDeclaration from '../importDeclaration';
+import docsUrl from '../docsUrl';
+
+function processBodyStatement(context, namespaces, declaration) {
+  if (declaration.type !== 'ImportDeclaration') { return; }
+
+  if (declaration.specifiers.length === 0) { return; }
+
+  const imports = ExportMapBuilder.get(declaration.source.value, context);
+  if (imports == null) { return null; }
+
+  if (imports.errors.length > 0) {
+    imports.reportErrors(context, declaration);
+    return;
+  }
+
+  declaration.specifiers.forEach((specifier) => {
+    switch (specifier.type) {
+      case 'ImportNamespaceSpecifier':
+        if (!imports.size) {
+          context.report(
+            specifier,
+            `No exported names found in module '${declaration.source.value}'.`,
+          );
+        }
+        namespaces.set(specifier.local.name, imports);
+        break;
+      case 'ImportDefaultSpecifier':
+      case 'ImportSpecifier': {
+        const meta = imports.get(
+        // default to 'default' for default https://i.imgur.com/nj6qAWy.jpg
+          specifier.imported ? specifier.imported.name || specifier.imported.value : 'default',
+        );
+        if (!meta || !meta.namespace) { break; }
+        namespaces.set(specifier.local.name, meta.namespace);
+        break;
+      }
+      default:
+    }
+  });
+}
 
 module.exports = {
   meta: {
     type: 'problem',
     docs: {
+      category: 'Static analysis',
+      description: 'Ensure imported namespaces contain dereferenced properties as they are dereferenced.',
       url: docsUrl('namespace'),
     },
 
     schema: [
       {
-        'type': 'object',
-        'properties': {
-          'allowComputed': {
-            'description':
-              'If `false`, will report computed (and thus, un-lintable) references ' +
-              'to namespace members.',
-            'type': 'boolean',
-            'default': false,
+        type: 'object',
+        properties: {
+          allowComputed: {
+            description: 'If `false`, will report computed (and thus, un-lintable) references to namespace members.',
+            type: 'boolean',
+            default: false,
           },
         },
-        'additionalProperties': false,
+        additionalProperties: false,
       },
     ],
   },
 
   create: function namespaceRule(context) {
-
     // read options
     const {
       allowComputed = false,
-    } = context.options[0] || {}
+    } = context.options[0] || {};
 
-    const namespaces = new Map()
+    const namespaces = new Map();
 
     function makeMessage(last, namepath) {
-       return `'${last.name}' not found in` +
-              (namepath.length > 1 ? ' deeply ' : ' ') +
-              `imported namespace '${namepath.join('.')}'.`
+      return `'${last.name}' not found in ${namepath.length > 1 ? 'deeply ' : ''}imported namespace '${namepath.join('.')}'.`;
     }
 
     return {
-
       // pick up all imports at body entry time, to properly respect hoisting
-      Program: function ({ body }) {
-        function processBodyStatement(declaration) {
-          if (declaration.type !== 'ImportDeclaration') return
-
-          if (declaration.specifiers.length === 0) return
-
-          const imports = Exports.get(declaration.source.value, context)
-          if (imports == null) return null
-
-          if (imports.errors.length) {
-            imports.reportErrors(context, declaration)
-            return
-          }
-
-          for (const specifier of declaration.specifiers) {
-            switch (specifier.type) {
-              case 'ImportNamespaceSpecifier':
-                if (!imports.size) {
-                  context.report(specifier,
-                    `No exported names found in module '${declaration.source.value}'.`)
-                }
-                namespaces.set(specifier.local.name, imports)
-                break
-              case 'ImportDefaultSpecifier':
-              case 'ImportSpecifier': {
-                const meta = imports.get(
-                  // default to 'default' for default http://i.imgur.com/nj6qAWy.jpg
-                  specifier.imported ? specifier.imported.name : 'default')
-                if (!meta || !meta.namespace) break
-                namespaces.set(specifier.local.name, meta.namespace)
-                break
-              }
-            }
-          }
-        }
-        body.forEach(processBodyStatement)
+      Program({ body }) {
+        body.forEach((x) => { processBodyStatement(context, namespaces, x); });
       },
 
       // same as above, but does not add names to local map
-      ExportNamespaceSpecifier: function (namespace) {
-        var declaration = importDeclaration(context)
+      ExportNamespaceSpecifier(namespace) {
+        const declaration = importDeclaration(context, namespace);
 
-        var imports = Exports.get(declaration.source.value, context)
-        if (imports == null) return null
+        const imports = ExportMapBuilder.get(declaration.source.value, context);
+        if (imports == null) { return null; }
 
         if (imports.errors.length) {
-          imports.reportErrors(context, declaration)
-          return
+          imports.reportErrors(context, declaration);
+          return;
         }
 
         if (!imports.size) {
-          context.report(namespace,
-            `No exported names found in module '${declaration.source.value}'.`)
+          context.report(
+            namespace,
+            `No exported names found in module '${declaration.source.value}'.`,
+          );
         }
       },
 
       // todo: check for possible redefinition
 
-      MemberExpression: function (dereference) {
-        if (dereference.object.type !== 'Identifier') return
-        if (!namespaces.has(dereference.object.name)) return
+      MemberExpression(dereference) {
+        if (dereference.object.type !== 'Identifier') { return; }
+        if (!namespaces.has(dereference.object.name)) { return; }
+        if (declaredScope(context, dereference.object.name, dereference) !== 'module') { return; }
 
-        if (dereference.parent.type === 'AssignmentExpression' &&
-            dereference.parent.left === dereference) {
-            context.report(dereference.parent,
-                `Assignment to member of namespace '${dereference.object.name}'.`)
+        if (dereference.parent.type === 'AssignmentExpression' && dereference.parent.left === dereference) {
+          context.report(
+            dereference.parent,
+            `Assignment to member of namespace '${dereference.object.name}'.`,
+          );
         }
 
         // go deep
-        var namespace = namespaces.get(dereference.object.name)
-        var namepath = [dereference.object.name]
+        let namespace = namespaces.get(dereference.object.name);
+        const namepath = [dereference.object.name];
         // while property is namespace and parent is member expression, keep validating
-        while (namespace instanceof Exports &&
-               dereference.type === 'MemberExpression') {
-
+        while (namespace instanceof ExportMap && dereference.type === 'MemberExpression') {
           if (dereference.computed) {
             if (!allowComputed) {
-              context.report(dereference.property,
-                'Unable to validate computed reference to imported namespace \'' +
-                dereference.object.name + '\'.')
+              context.report(
+                dereference.property,
+                `Unable to validate computed reference to imported namespace '${dereference.object.name}'.`,
+              );
             }
-            return
+            return;
           }
 
           if (!namespace.has(dereference.property.name)) {
             context.report(
               dereference.property,
-              makeMessage(dereference.property, namepath))
-            break
+              makeMessage(dereference.property, namepath),
+            );
+            break;
           }
 
-          const exported = namespace.get(dereference.property.name)
-          if (exported == null) return
+          const exported = namespace.get(dereference.property.name);
+          if (exported == null) { return; }
 
           // stash and pop
-          namepath.push(dereference.property.name)
-          namespace = exported.namespace
-          dereference = dereference.parent
+          namepath.push(dereference.property.name);
+          namespace = exported.namespace;
+          dereference = dereference.parent;
         }
-
       },
 
-      VariableDeclarator: function ({ id, init }) {
-        if (init == null) return
-        if (init.type !== 'Identifier') return
-        if (!namespaces.has(init.name)) return
+      VariableDeclarator({ id, init }) {
+        if (init == null) { return; }
+        if (init.type !== 'Identifier') { return; }
+        if (!namespaces.has(init.name)) { return; }
 
         // check for redefinition in intermediate scopes
-        if (declaredScope(context, init.name) !== 'module') return
+        if (declaredScope(context, init.name, init) !== 'module') { return; }
 
         // DFS traverse child namespaces
         function testKey(pattern, namespace, path = [init.name]) {
-          if (!(namespace instanceof Exports)) return
+          if (!(namespace instanceof ExportMap)) { return; }
 
-          if (pattern.type !== 'ObjectPattern') return
+          if (pattern.type !== 'ObjectPattern') { return; }
 
           for (const property of pattern.properties) {
             if (
@@ -167,48 +172,48 @@ module.exports = {
               || property.type === 'RestElement'
               || !property.key
             ) {
-              continue
+              continue;
             }
 
             if (property.key.type !== 'Identifier') {
               context.report({
                 node: property,
                 message: 'Only destructure top-level names.',
-              })
-              continue
+              });
+              continue;
             }
 
             if (!namespace.has(property.key.name)) {
               context.report({
                 node: property,
                 message: makeMessage(property.key, path),
-              })
-              continue
+              });
+              continue;
             }
 
-            path.push(property.key.name)
-            const dependencyExportMap = namespace.get(property.key.name)
+            path.push(property.key.name);
+            const dependencyExportMap = namespace.get(property.key.name);
             // could be null when ignored or ambiguous
             if (dependencyExportMap !== null) {
-              testKey(property.value, dependencyExportMap.namespace, path)
+              testKey(property.value, dependencyExportMap.namespace, path);
             }
-            path.pop()
+            path.pop();
           }
         }
 
-        testKey(id, namespaces.get(init.name))
+        testKey(id, namespaces.get(init.name));
       },
 
-      JSXMemberExpression: function({object, property}) {
-         if (!namespaces.has(object.name)) return
-         var namespace = namespaces.get(object.name)
-         if (!namespace.has(property.name)) {
-           context.report({
-             node: property,
-             message: makeMessage(property, [object.name]),
-           })
-         }
+      JSXMemberExpression({ object, property }) {
+        if (!namespaces.has(object.name)) { return; }
+        const namespace = namespaces.get(object.name);
+        if (!namespace.has(property.name)) {
+          context.report({
+            node: property,
+            message: makeMessage(property, [object.name]),
+          });
+        }
       },
-    }
+    };
   },
-}
+};
diff --git a/src/rules/newline-after-import.js b/src/rules/newline-after-import.js
index f5724ef4a..c645d2bc6 100644
--- a/src/rules/newline-after-import.js
+++ b/src/rules/newline-after-import.js
@@ -3,84 +3,138 @@
  * @author Radek Benkel
  */
 
-import isStaticRequire from '../core/staticRequire'
-import docsUrl from '../docsUrl'
+import { getPhysicalFilename, getScope } from 'eslint-module-utils/contextCompat';
 
-import debug from 'debug'
-const log = debug('eslint-plugin-import:rules:newline-after-import')
+import isStaticRequire from '../core/staticRequire';
+import docsUrl from '../docsUrl';
+
+import debug from 'debug';
+const log = debug('eslint-plugin-import:rules:newline-after-import');
 
 //------------------------------------------------------------------------------
 // Rule Definition
 //------------------------------------------------------------------------------
 
 function containsNodeOrEqual(outerNode, innerNode) {
-    return outerNode.range[0] <= innerNode.range[0] && outerNode.range[1] >= innerNode.range[1]
+  return outerNode.range[0] <= innerNode.range[0] && outerNode.range[1] >= innerNode.range[1];
 }
 
 function getScopeBody(scope) {
-    if (scope.block.type === 'SwitchStatement') {
-      log('SwitchStatement scopes not supported')
-      return null
-    }
+  if (scope.block.type === 'SwitchStatement') {
+    log('SwitchStatement scopes not supported');
+    return null;
+  }
 
-    const { body } = scope.block
-    if (body && body.type === 'BlockStatement') {
-        return body.body
-    }
+  const { body } = scope.block;
+  if (body && body.type === 'BlockStatement') {
+    return body.body;
+  }
 
-    return body
+  return body;
 }
 
 function findNodeIndexInScopeBody(body, nodeToFind) {
-    return body.findIndex((node) => containsNodeOrEqual(node, nodeToFind))
+  return body.findIndex((node) => containsNodeOrEqual(node, nodeToFind));
 }
 
 function getLineDifference(node, nextNode) {
-  return nextNode.loc.start.line - node.loc.end.line
+  return nextNode.loc.start.line - node.loc.end.line;
 }
 
 function isClassWithDecorator(node) {
-  return node.type === 'ClassDeclaration' && node.decorators && node.decorators.length
+  return node.type === 'ClassDeclaration' && node.decorators && node.decorators.length;
+}
+
+function isExportDefaultClass(node) {
+  return node.type === 'ExportDefaultDeclaration' && node.declaration.type === 'ClassDeclaration';
+}
+
+function isExportNameClass(node) {
+
+  return node.type === 'ExportNamedDeclaration' && node.declaration && node.declaration.type === 'ClassDeclaration';
 }
 
 module.exports = {
   meta: {
     type: 'layout',
     docs: {
+      category: 'Style guide',
+      description: 'Enforce a newline after import statements.',
       url: docsUrl('newline-after-import'),
     },
+    fixable: 'whitespace',
     schema: [
       {
-        'type': 'object',
-        'properties': {
-          'count': {
-            'type': 'integer',
-            'minimum': 1,
+        type: 'object',
+        properties: {
+          count: {
+            type: 'integer',
+            minimum: 1,
           },
+          exactCount: { type: 'boolean' },
+          considerComments: { type: 'boolean' },
         },
-        'additionalProperties': false,
+        additionalProperties: false,
       },
     ],
-    fixable: 'whitespace',
   },
-  create: function (context) {
-    let level = 0
-    const requireCalls = []
+  create(context) {
+    let level = 0;
+    const requireCalls = [];
+    const options = {
+      count: 1,
+      exactCount: false,
+      considerComments: false,
+      ...context.options[0],
+    };
 
     function checkForNewLine(node, nextNode, type) {
-      if (isClassWithDecorator(nextNode)) {
-        nextNode = nextNode.decorators[0]
+      if (isExportDefaultClass(nextNode) || isExportNameClass(nextNode)) {
+        const classNode = nextNode.declaration;
+
+        if (isClassWithDecorator(classNode)) {
+          nextNode = classNode.decorators[0];
+        }
+      } else if (isClassWithDecorator(nextNode)) {
+        nextNode = nextNode.decorators[0];
+      }
+
+      const lineDifference = getLineDifference(node, nextNode);
+      const EXPECTED_LINE_DIFFERENCE = options.count + 1;
+
+      if (
+        lineDifference < EXPECTED_LINE_DIFFERENCE
+        || options.exactCount && lineDifference !== EXPECTED_LINE_DIFFERENCE
+      ) {
+        let column = node.loc.start.column;
+
+        if (node.loc.start.line !== node.loc.end.line) {
+          column = 0;
+        }
+
+        context.report({
+          loc: {
+            line: node.loc.end.line,
+            column,
+          },
+          message: `Expected ${options.count} empty line${options.count > 1 ? 's' : ''} after ${type} statement not followed by another ${type}.`,
+          fix: options.exactCount && EXPECTED_LINE_DIFFERENCE < lineDifference ? undefined : (fixer) => fixer.insertTextAfter(
+            node,
+            '\n'.repeat(EXPECTED_LINE_DIFFERENCE - lineDifference),
+          ),
+        });
       }
+    }
 
-      const options = context.options[0] || { count: 1 }
-      const lineDifference = getLineDifference(node, nextNode)
-      const EXPECTED_LINE_DIFFERENCE = options.count + 1
+    function commentAfterImport(node, nextComment, type) {
+      const lineDifference = getLineDifference(node, nextComment);
+      const EXPECTED_LINE_DIFFERENCE = options.count + 1;
 
       if (lineDifference < EXPECTED_LINE_DIFFERENCE) {
-        let column = node.loc.start.column
+        let column = node.loc.start.column;
 
         if (node.loc.start.line !== node.loc.end.line) {
-          column = 0
+          column = 0;
         }
 
         context.report({
@@ -88,61 +142,95 @@ module.exports = {
             line: node.loc.end.line,
             column,
           },
-          message: `Expected ${options.count} empty line${options.count > 1 ? 's' : ''} \
-after ${type} statement not followed by another ${type}.`,
-          fix: fixer => fixer.insertTextAfter(
+          message: `Expected ${options.count} empty line${options.count > 1 ? 's' : ''} after ${type} statement not followed by another ${type}.`,
+          fix: options.exactCount && EXPECTED_LINE_DIFFERENCE < lineDifference ? undefined : (fixer) => fixer.insertTextAfter(
             node,
-            '\n'.repeat(EXPECTED_LINE_DIFFERENCE - lineDifference)
+            '\n'.repeat(EXPECTED_LINE_DIFFERENCE - lineDifference),
           ),
-        })
+        });
       }
     }
 
     function incrementLevel() {
-      level++
+      level++;
     }
     function decrementLevel() {
-      level--
+      level--;
     }
 
-    return {
-      ImportDeclaration: function (node) {
-        const { parent } = node
-        const nodePosition = parent.body.indexOf(node)
-        const nextNode = parent.body[nodePosition + 1]
+    function checkImport(node) {
+      const { parent } = node;
 
-        if (nextNode && nextNode.type !== 'ImportDeclaration') {
-          checkForNewLine(node, nextNode, 'import')
-        }
-      },
-      CallExpression: function(node) {
+      if (!parent || !parent.body) {
+        return;
+      }
+
+      const nodePosition = parent.body.indexOf(node);
+      const nextNode = parent.body[nodePosition + 1];
+      const endLine = node.loc.end.line;
+      let nextComment;
+
+      if (typeof parent.comments !== 'undefined' && options.considerComments) {
+        nextComment = parent.comments.find((o) => o.loc.start.line >= endLine && o.loc.start.line <= endLine + options.count + 1);
+      }
+
+      // skip "export import"s
+      if (node.type === 'TSImportEqualsDeclaration' && node.isExport) {
+        return;
+      }
+
+      if (nextComment && typeof nextComment !== 'undefined') {
+        commentAfterImport(node, nextComment, 'import');
+      } else if (nextNode && nextNode.type !== 'ImportDeclaration' && (nextNode.type !== 'TSImportEqualsDeclaration' || nextNode.isExport)) {
+        checkForNewLine(node, nextNode, 'import');
+      }
+    }
+
+    return {
+      ImportDeclaration: checkImport,
+      TSImportEqualsDeclaration: checkImport,
+      CallExpression(node) {
         if (isStaticRequire(node) && level === 0) {
-          requireCalls.push(node)
+          requireCalls.push(node);
         }
       },
-      'Program:exit': function () {
-        log('exit processing for', context.getFilename())
-        const scopeBody = getScopeBody(context.getScope())
-        log('got scope:', scopeBody)
+      'Program:exit'(node) {
+        log('exit processing for', getPhysicalFilename(context));
+        const scopeBody = getScopeBody(getScope(context, node));
+        log('got scope:', scopeBody);
 
-        requireCalls.forEach(function (node, index) {
-          const nodePosition = findNodeIndexInScopeBody(scopeBody, node)
-          log('node position in scope:', nodePosition)
+        requireCalls.forEach((node, index) => {
+          const nodePosition = findNodeIndexInScopeBody(scopeBody, node);
+          log('node position in scope:', nodePosition);
 
-          const statementWithRequireCall = scopeBody[nodePosition]
-          const nextStatement = scopeBody[nodePosition + 1]
-          const nextRequireCall = requireCalls[index + 1]
+          const statementWithRequireCall = scopeBody[nodePosition];
+          const nextStatement = scopeBody[nodePosition + 1];
+          const nextRequireCall = requireCalls[index + 1];
 
           if (nextRequireCall && containsNodeOrEqual(statementWithRequireCall, nextRequireCall)) {
-            return
+            return;
           }
 
-          if (nextStatement &&
-             (!nextRequireCall || !containsNodeOrEqual(nextStatement, nextRequireCall))) {
-
-            checkForNewLine(statementWithRequireCall, nextStatement, 'require')
+          if (
+            nextStatement && (
+              !nextRequireCall
+              || !containsNodeOrEqual(nextStatement, nextRequireCall)
+            )
+          ) {
+            let nextComment;
+            if (typeof statementWithRequireCall.parent.comments !== 'undefined' && options.considerComments) {
+              const endLine = node.loc.end.line;
+              nextComment = statementWithRequireCall.parent.comments.find((o) => o.loc.start.line >= endLine && o.loc.start.line <= endLine + options.count + 1);
+            }
+
+            if (nextComment && typeof nextComment !== 'undefined') {
+
+              commentAfterImport(statementWithRequireCall, nextComment, 'require');
+            } else {
+              checkForNewLine(statementWithRequireCall, nextStatement, 'require');
+            }
           }
-        })
+        });
       },
       FunctionDeclaration: incrementLevel,
       FunctionExpression: incrementLevel,
@@ -156,6 +244,6 @@ after ${type} statement not followed by another ${type}.`,
       'BlockStatement:exit': decrementLevel,
       'ObjectExpression:exit': decrementLevel,
       'Decorator:exit': decrementLevel,
-    }
+    };
   },
-}
+};
diff --git a/src/rules/no-absolute-path.js b/src/rules/no-absolute-path.js
index 4b7a8fcc2..0dbd8cb86 100644
--- a/src/rules/no-absolute-path.js
+++ b/src/rules/no-absolute-path.js
@@ -1,24 +1,41 @@
-import moduleVisitor, { makeOptionsSchema } from 'eslint-module-utils/moduleVisitor'
-import { isAbsolute } from '../core/importType'
-import docsUrl from '../docsUrl'
+import path from 'path';
+import { getPhysicalFilename } from 'eslint-module-utils/contextCompat';
+import moduleVisitor, { makeOptionsSchema } from 'eslint-module-utils/moduleVisitor';
+
+import { isAbsolute } from '../core/importType';
+import docsUrl from '../docsUrl';
 
 module.exports = {
   meta: {
     type: 'suggestion',
     docs: {
+      category: 'Static analysis',
+      description: 'Forbid import of modules using absolute paths.',
       url: docsUrl('no-absolute-path'),
     },
-    schema: [ makeOptionsSchema() ],
+    fixable: 'code',
+    schema: [makeOptionsSchema()],
   },
 
-  create: function (context) {
+  create(context) {
     function reportIfAbsolute(source) {
       if (isAbsolute(source.value)) {
-        context.report(source, 'Do not import modules using an absolute path')
+        context.report({
+          node: source,
+          message: 'Do not import modules using an absolute path',
+          fix(fixer) {
+            // node.js and web imports work with posix style paths ("/")
+            let relativePath = path.posix.relative(path.dirname(getPhysicalFilename(context)), source.value);
+            if (!relativePath.startsWith('.')) {
+              relativePath = `./${relativePath}`;
+            }
+            return fixer.replaceText(source, JSON.stringify(relativePath));
+          },
+        });
       }
     }
 
-    const options = Object.assign({ esmodule: true, commonjs: true }, context.options[0])
-    return moduleVisitor(reportIfAbsolute, options)
+    const options = { esmodule: true, commonjs: true, ...context.options[0] };
+    return moduleVisitor(reportIfAbsolute, options);
   },
-}
+};
diff --git a/src/rules/no-amd.js b/src/rules/no-amd.js
index bb7c8ed82..05ed0a521 100644
--- a/src/rules/no-amd.js
+++ b/src/rules/no-amd.js
@@ -3,7 +3,9 @@
  * @author Jamund Ferguson
  */
 
-import docsUrl from '../docsUrl'
+import { getScope } from 'eslint-module-utils/contextCompat';
+
+import docsUrl from '../docsUrl';
 
 //------------------------------------------------------------------------------
 // Rule Definition
@@ -13,30 +15,32 @@ module.exports = {
   meta: {
     type: 'suggestion',
     docs: {
+      category: 'Module systems',
+      description: 'Forbid AMD `require` and `define` calls.',
       url: docsUrl('no-amd'),
     },
+    schema: [],
   },
 
-  create: function (context) {
+  create(context) {
     return {
-      'CallExpression': function (node) {
-        if (context.getScope().type !== 'module') return
+      CallExpression(node) {
+        if (getScope(context, node).type !== 'module') { return; }
 
-        if (node.callee.type !== 'Identifier') return
-        if (node.callee.name !== 'require' &&
-            node.callee.name !== 'define') return
+        if (node.callee.type !== 'Identifier') { return; }
+        if (node.callee.name !== 'require' && node.callee.name !== 'define') { return; }
 
         // todo: capture define((require, module, exports) => {}) form?
-        if (node.arguments.length !== 2) return
+        if (node.arguments.length !== 2) { return; }
 
-        const modules = node.arguments[0]
-        if (modules.type !== 'ArrayExpression') return
+        const modules = node.arguments[0];
+        if (modules.type !== 'ArrayExpression') { return; }
 
         // todo: check second arg type? (identifier or callback)
 
-        context.report(node, `Expected imports instead of AMD ${node.callee.name}().`)
+        context.report(node, `Expected imports instead of AMD ${node.callee.name}().`);
       },
-    }
+    };
 
   },
-}
+};
diff --git a/src/rules/no-anonymous-default-export.js b/src/rules/no-anonymous-default-export.js
index 155740450..4f6947e81 100644
--- a/src/rules/no-anonymous-default-export.js
+++ b/src/rules/no-anonymous-default-export.js
@@ -3,8 +3,11 @@
  * @author Duncan Beevers
  */
 
-import docsUrl from '../docsUrl'
-import has from 'has'
+import hasOwn from 'hasown';
+import values from 'object.values';
+import fromEntries from 'object.fromentries';
+
+import docsUrl from '../docsUrl';
 
 const defs = {
   ArrayExpression: {
@@ -50,30 +53,26 @@ const defs = {
     description: 'If `false`, will report default export of a literal',
     message: 'Assign literal to a variable before exporting as module default',
   },
-}
-
-const schemaProperties = Object.keys(defs)
-  .map((key) => defs[key])
-  .reduce((acc, def) => {
-    acc[def.option] = {
-      description: def.description,
-      type: 'boolean',
-    }
+  NewExpression: {
+    option: 'allowNew',
+    description: 'If `false`, will report default export of a class instantiation',
+    message: 'Assign instance to a variable before exporting as module default',
+  },
+};
 
-    return acc
-  }, {})
+const schemaProperties = fromEntries(values(defs).map((def) => [def.option, {
+  description: def.description,
+  type: 'boolean',
+}]));
 
-const defaults = Object.keys(defs)
-  .map((key) => defs[key])
-  .reduce((acc, def) => {
-    acc[def.option] = has(def, 'default') ? def.default : false
-    return acc
-  }, {})
+const defaults = fromEntries(values(defs).map((def) => [def.option, hasOwn(def, 'default') ? def.default : false]));
 
 module.exports = {
   meta: {
     type: 'suggestion',
     docs: {
+      category: 'Style guide',
+      description: 'Forbid anonymous values as default exports.',
       url: docsUrl('no-anonymous-default-export'),
     },
 
@@ -81,24 +80,24 @@ module.exports = {
       {
         type: 'object',
         properties: schemaProperties,
-        'additionalProperties': false,
+        additionalProperties: false,
       },
     ],
   },
 
-  create: function (context) {
-    const options = Object.assign({}, defaults, context.options[0])
+  create(context) {
+    const options = { ...defaults, ...context.options[0] };
 
     return {
-      'ExportDefaultDeclaration': (node) => {
-        const def = defs[node.declaration.type]
+      ExportDefaultDeclaration(node) {
+        const def = defs[node.declaration.type];
 
         // Recognized node type and allowed by configuration,
         //   and has no forbid check, or forbid check return value is truthy
         if (def && !options[def.option] && (!def.forbid || def.forbid(node))) {
-          context.report({ node, message: def.message })
+          context.report({ node, message: def.message });
         }
       },
-    }
+    };
   },
-}
+};
diff --git a/src/rules/no-commonjs.js b/src/rules/no-commonjs.js
index 261654bbf..33b77da59 100644
--- a/src/rules/no-commonjs.js
+++ b/src/rules/no-commonjs.js
@@ -3,46 +3,78 @@
  * @author Jamund Ferguson
  */
 
-import docsUrl from '../docsUrl'
+import { getScope } from 'eslint-module-utils/contextCompat';
 
-const EXPORT_MESSAGE = 'Expected "export" or "export default"'
-    , IMPORT_MESSAGE = 'Expected "import" instead of "require()"'
+import docsUrl from '../docsUrl';
+
+const EXPORT_MESSAGE = 'Expected "export" or "export default"';
+const IMPORT_MESSAGE = 'Expected "import" instead of "require()"';
 
 function normalizeLegacyOptions(options) {
   if (options.indexOf('allow-primitive-modules') >= 0) {
-    return { allowPrimitiveModules: true }
+    return { allowPrimitiveModules: true };
   }
-  return options[0] || {}
+  return options[0] || {};
 }
 
 function allowPrimitive(node, options) {
-  if (!options.allowPrimitiveModules) return false
-  if (node.parent.type !== 'AssignmentExpression') return false
-  return (node.parent.right.type !== 'ObjectExpression')
+  if (!options.allowPrimitiveModules) { return false; }
+  if (node.parent.type !== 'AssignmentExpression') { return false; }
+  return node.parent.right.type !== 'ObjectExpression';
 }
 
 function allowRequire(node, options) {
-  return options.allowRequire
+  return options.allowRequire;
+}
+
+function allowConditionalRequire(node, options) {
+  return options.allowConditionalRequire !== false;
+}
+
+function validateScope(scope) {
+  return scope.variableScope.type === 'module';
+}
+
+// https://github.com/estree/estree/blob/HEAD/es5.md
+function isConditional(node) {
+  if (
+    node.type === 'IfStatement'
+    || node.type === 'TryStatement'
+    || node.type === 'LogicalExpression'
+    || node.type === 'ConditionalExpression'
+  ) {
+    return true;
+  }
+  if (node.parent) { return isConditional(node.parent); }
+  return false;
+}
+
+function isLiteralString(node) {
+  return node.type === 'Literal' && typeof node.value === 'string'
+    || node.type === 'TemplateLiteral' && node.expressions.length === 0;
 }
 
 //------------------------------------------------------------------------------
 // Rule Definition
 //------------------------------------------------------------------------------
 
-const schemaString = { enum: ['allow-primitive-modules'] }
+const schemaString = { enum: ['allow-primitive-modules'] };
 const schemaObject = {
   type: 'object',
   properties: {
-    allowPrimitiveModules: { 'type': 'boolean' },
-    allowRequire: { 'type': 'boolean' },
+    allowPrimitiveModules: { type: 'boolean' },
+    allowRequire: { type: 'boolean' },
+    allowConditionalRequire: { type: 'boolean' },
   },
   additionalProperties: false,
-}
+};
 
 module.exports = {
   meta: {
     type: 'suggestion',
     docs: {
+      category: 'Module systems',
+      description: 'Forbid CommonJS `require` calls and `module.exports` or `exports.*`.',
       url: docsUrl('no-commonjs'),
     },
 
@@ -62,56 +94,50 @@ module.exports = {
     },
   },
 
-  create: function (context) {
-    const options = normalizeLegacyOptions(context.options)
+  create(context) {
+    const options = normalizeLegacyOptions(context.options);
 
     return {
 
-      'MemberExpression': function (node) {
+      MemberExpression(node) {
 
         // module.exports
         if (node.object.name === 'module' && node.property.name === 'exports') {
-          if (allowPrimitive(node, options)) return
-          context.report({ node, message: EXPORT_MESSAGE })
+          if (allowPrimitive(node, options)) { return; }
+          context.report({ node, message: EXPORT_MESSAGE });
         }
 
         // exports.
         if (node.object.name === 'exports') {
-          const isInScope = context.getScope()
+          const isInScope = getScope(context, node)
             .variables
-            .some(variable => variable.name === 'exports')
-          if (! isInScope) {
-            context.report({ node, message: EXPORT_MESSAGE })
+            .some((variable) => variable.name === 'exports');
+          if (!isInScope) {
+            context.report({ node, message: EXPORT_MESSAGE });
           }
         }
 
       },
-      'CallExpression': function (call) {
-        if (context.getScope().type !== 'module') return
-        if (
-          call.parent.type !== 'ExpressionStatement'
-          && call.parent.type !== 'VariableDeclarator'
-          && call.parent.type !== 'AssignmentExpression'
-        ) return
+      CallExpression(call) {
+        if (!validateScope(getScope(context, call))) { return; }
 
-        if (call.callee.type !== 'Identifier') return
-        if (call.callee.name !== 'require') return
+        if (call.callee.type !== 'Identifier') { return; }
+        if (call.callee.name !== 'require') { return; }
 
-        if (call.arguments.length !== 1) return
-        var module = call.arguments[0]
+        if (call.arguments.length !== 1) { return; }
+        if (!isLiteralString(call.arguments[0])) { return; }
 
-        if (module.type !== 'Literal') return
-        if (typeof module.value !== 'string') return
+        if (allowRequire(call, options)) { return; }
 
-        if (allowRequire(call, options)) return
+        if (allowConditionalRequire(call, options) && isConditional(call.parent)) { return; }
 
         // keeping it simple: all 1-string-arg `require` calls are reported
         context.report({
           node: call.callee,
           message: IMPORT_MESSAGE,
-        })
+        });
       },
-    }
+    };
 
   },
-}
+};
diff --git a/src/rules/no-cycle.js b/src/rules/no-cycle.js
index 62da3643b..713503d9f 100644
--- a/src/rules/no-cycle.js
+++ b/src/rules/no-cycle.js
@@ -3,82 +3,178 @@
  * @author Ben Mosher
  */
 
-import Exports from '../ExportMap'
-import moduleVisitor, { makeOptionsSchema } from 'eslint-module-utils/moduleVisitor'
-import docsUrl from '../docsUrl'
+import { getPhysicalFilename } from 'eslint-module-utils/contextCompat';
+import moduleVisitor, { makeOptionsSchema } from 'eslint-module-utils/moduleVisitor';
+import resolve from 'eslint-module-utils/resolve';
+
+import ExportMapBuilder from '../exportMap/builder';
+import StronglyConnectedComponentsBuilder from '../scc';
+import { isExternalModule } from '../core/importType';
+import docsUrl from '../docsUrl';
+
+const traversed = new Set();
+
+function routeString(route) {
+  return route.map((s) => `${s.value}:${s.loc.start.line}`).join('=>');
+}
 
-// todo: cache cycles / deep relationships for faster repeat evaluation
 module.exports = {
   meta: {
     type: 'suggestion',
-    docs: { url: docsUrl('no-cycle') },
+    docs: {
+      category: 'Static analysis',
+      description: 'Forbid a module from importing a module with a dependency path back to itself.',
+      url: docsUrl('no-cycle'),
+    },
     schema: [makeOptionsSchema({
-      maxDepth:{
-        description: 'maximum dependency depth to traverse',
-        type: 'integer',
-        minimum: 1,
+      maxDepth: {
+        anyOf: [
+          {
+            description: 'maximum dependency depth to traverse',
+            type: 'integer',
+            minimum: 1,
+          },
+          {
+            enum: ['∞'],
+            type: 'string',
+          },
+        ],
+      },
+      ignoreExternal: {
+        description: 'ignore external modules',
+        type: 'boolean',
+        default: false,
+      },
+      allowUnsafeDynamicCyclicDependency: {
+        description: 'Allow cyclic dependency if there is at least one dynamic import in the chain',
+        type: 'boolean',
+        default: false,
+      },
+      disableScc: {
+        description: 'When true, don\'t calculate a strongly-connected-components graph. SCC is used to reduce the time-complexity of cycle detection, but adds overhead.',
+        type: 'boolean',
+        default: false,
       },
     })],
   },
 
-  create: function (context) {
-    const myPath = context.getFilename()
-    if (myPath === '<text>') return {} // can't cycle-check a non-file
+  create(context) {
+    const myPath = getPhysicalFilename(context);
+    if (myPath === '<text>') { return {}; } // can't cycle-check a non-file
+
+    const options = context.options[0] || {};
+    const maxDepth = typeof options.maxDepth === 'number' ? options.maxDepth : Infinity;
+    const ignoreModule = (name) => options.ignoreExternal && isExternalModule(
+      name,
+      resolve(name, context),
+      context,
+    );
 
-    const options = context.options[0] || {}
-    const maxDepth = options.maxDepth || Infinity
+    const scc = options.disableScc ? {} : StronglyConnectedComponentsBuilder.get(myPath, context);
 
     function checkSourceValue(sourceNode, importer) {
-      const imported = Exports.get(sourceNode.value, context)
+      if (ignoreModule(sourceNode.value)) {
+        return; // ignore external modules
+      }
+      if (
+        options.allowUnsafeDynamicCyclicDependency && (
+          // Ignore `import()`
+          importer.type === 'ImportExpression'
+          // `require()` calls are always checked (if possible)
+          || importer.type === 'CallExpression' && importer.callee.name !== 'require'
+        )
+      ) {
+        return; // cycle via dynamic import allowed by config
+      }
 
-      if (importer.importKind === 'type') {
-        return // no Flow import resolution
+      if (
+        importer.type === 'ImportDeclaration' && (
+          // import type { Foo } (TS and Flow)
+          importer.importKind === 'type'
+          // import { type Foo } (Flow)
+          || importer.specifiers.every(({ importKind }) => importKind === 'type')
+        )
+      ) {
+        return; // ignore type imports
       }
 
+      const imported = ExportMapBuilder.get(sourceNode.value, context);
+
       if (imported == null) {
-        return  // no-unresolved territory
+        return;  // no-unresolved territory
       }
 
       if (imported.path === myPath) {
-        return  // no-self-import territory
+        return;  // no-self-import territory
       }
 
-      const untraversed = [{mget: () => imported, route:[]}]
-      const traversed = new Set()
-      function detectCycle({mget, route}) {
-        const m = mget()
-        if (m == null) return
-        if (traversed.has(m.path)) return
-        traversed.add(m.path)
-
-        for (let [path, { getter, source }] of m.imports) {
-          if (path === myPath) return true
-          if (traversed.has(path)) continue
+      /* If we're in the same Strongly Connected Component,
+       * Then there exists a path from each node in the SCC to every other node in the SCC,
+       * Then there exists at least one path from them to us and from us to them,
+       * Then we have a cycle between us.
+       */
+      const hasDependencyCycle = options.disableScc || scc[myPath] === scc[imported.path];
+      if (!hasDependencyCycle) {
+        return;
+      }
+
+      const untraversed = [{ mget: () => imported, route: [] }];
+      function detectCycle({ mget, route }) {
+        const m = mget();
+        if (m == null) { return; }
+        if (traversed.has(m.path)) { return; }
+        traversed.add(m.path);
+
+        for (const [path, { getter, declarations }] of m.imports) {
+          // If we're in different SCCs, we can't have a circular dependency
+          if (!options.disableScc && scc[myPath] !== scc[path]) { continue; }
+
+          if (traversed.has(path)) { continue; }
+          const toTraverse = [...declarations].filter(({ source, isOnlyImportingTypes }) => !ignoreModule(source.value)
+            // Ignore only type imports
+            && !isOnlyImportingTypes,
+          );
+
+          /*
+          If cyclic dependency is allowed via dynamic import, skip checking if any module is imported dynamically
+          */
+          if (options.allowUnsafeDynamicCyclicDependency && toTraverse.some((d) => d.dynamic)) { return; }
+
+          /*
+          Only report as a cycle if there are any import declarations that are considered by
+          the rule. For example:
+
+          a.ts:
+          import { foo } from './b' // should not be reported as a cycle
+
+          b.ts:
+          import type { Bar } from './a'
+          */
+          if (path === myPath && toTraverse.length > 0) { return true; }
           if (route.length + 1 < maxDepth) {
-            untraversed.push({
-              mget: getter,
-              route: route.concat(source),
-            })
+            toTraverse.forEach(({ source }) => {
+              untraversed.push({ mget: getter, route: route.concat(source) });
+            });
           }
         }
       }
 
       while (untraversed.length > 0) {
-        const next = untraversed.shift() // bfs!
+        const next = untraversed.shift(); // bfs!
         if (detectCycle(next)) {
-          const message = (next.route.length > 0
+          const message = next.route.length > 0
             ? `Dependency cycle via ${routeString(next.route)}`
-            : 'Dependency cycle detected.')
-          context.report(importer, message)
-          return
+            : 'Dependency cycle detected.';
+          context.report(importer, message);
+          return;
         }
       }
     }
 
-    return moduleVisitor(checkSourceValue, context.options[0])
+    return Object.assign(moduleVisitor(checkSourceValue, context.options[0]), {
+      'Program:exit'() {
+        traversed.clear();
+      },
+    });
   },
-}
-
-function routeString(route) {
-  return route.map(s => `${s.value}:${s.loc.start.line}`).join('=>')
-}
+};
diff --git a/src/rules/no-default-export.js b/src/rules/no-default-export.js
index e1c687c9f..d18f0c48f 100644
--- a/src/rules/no-default-export.js
+++ b/src/rules/no-default-export.js
@@ -1,36 +1,46 @@
+import { getSourceCode } from 'eslint-module-utils/contextCompat';
+
+import docsUrl from '../docsUrl';
+import sourceType from '../core/sourceType';
+
 module.exports = {
   meta: {
     type: 'suggestion',
-    docs: {},
+    docs: {
+      category: 'Style guide',
+      description: 'Forbid default exports.',
+      url: docsUrl('no-default-export'),
+    },
+    schema: [],
   },
 
   create(context) {
     // ignore non-modules
-    if (context.parserOptions.sourceType !== 'module') {
-      return {}
+    if (sourceType(context) !== 'module') {
+      return {};
     }
 
-    const preferNamed = 'Prefer named exports.'
-    const noAliasDefault = ({local}) =>
-      `Do not alias \`${local.name}\` as \`default\`. Just export ` +
-      `\`${local.name}\` itself instead.`
+    const preferNamed = 'Prefer named exports.';
+    const noAliasDefault = ({ local }) => `Do not alias \`${local.name}\` as \`default\`. Just export \`${local.name}\` itself instead.`;
 
     return {
       ExportDefaultDeclaration(node) {
-        context.report({node, message: preferNamed})
+        const { loc } = getSourceCode(context).getFirstTokens(node)[1] || {};
+        context.report({ node, message: preferNamed, loc });
       },
 
       ExportNamedDeclaration(node) {
-        node.specifiers.forEach(specifier => {
-          if (specifier.type === 'ExportDefaultSpecifier' &&
-              specifier.exported.name === 'default') {
-            context.report({node, message: preferNamed})
-          } else if (specifier.type === 'ExportSpecifier' &&
-              specifier.exported.name === 'default') {
-            context.report({node, message: noAliasDefault(specifier)})
-          }
-        })
+        node.specifiers
+          .filter((specifier) => (specifier.exported.name || specifier.exported.value) === 'default')
+          .forEach((specifier) => {
+            const { loc } = getSourceCode(context).getFirstTokens(node)[1] || {};
+            if (specifier.type === 'ExportDefaultSpecifier') {
+              context.report({ node, message: preferNamed, loc });
+            } else if (specifier.type === 'ExportSpecifier') {
+              context.report({ node, message: noAliasDefault(specifier), loc  });
+            }
+          });
       },
-    }
+    };
   },
-}
+};
diff --git a/src/rules/no-deprecated.js b/src/rules/no-deprecated.js
index 7a3130b20..9559046b9 100644
--- a/src/rules/no-deprecated.js
+++ b/src/rules/no-deprecated.js
@@ -1,142 +1,139 @@
-import declaredScope from 'eslint-module-utils/declaredScope'
-import Exports from '../ExportMap'
-import docsUrl from '../docsUrl'
+import declaredScope from 'eslint-module-utils/declaredScope';
+import ExportMapBuilder from '../exportMap/builder';
+import ExportMap from '../exportMap';
+import docsUrl from '../docsUrl';
 
 function message(deprecation) {
-  return 'Deprecated' + (deprecation.description ? ': ' + deprecation.description : '.')
+  return `Deprecated${deprecation.description ? `: ${deprecation.description}` : '.'}`;
 }
 
 function getDeprecation(metadata) {
-  if (!metadata || !metadata.doc) return
+  if (!metadata || !metadata.doc) { return; }
 
-  let deprecation
-  if (metadata.doc.tags.some(t => t.title === 'deprecated' && (deprecation = t))) {
-    return deprecation
-  }
+  return metadata.doc.tags.find((t) => t.title === 'deprecated');
 }
 
 module.exports = {
   meta: {
     type: 'suggestion',
     docs: {
+      category: 'Helpful warnings',
+      description: 'Forbid imported names marked with `@deprecated` documentation tag.',
       url: docsUrl('no-deprecated'),
     },
+    schema: [],
   },
 
-  create: function (context) {
-    const deprecated = new Map()
-        , namespaces = new Map()
+  create(context) {
+    const deprecated = new Map();
+    const namespaces = new Map();
 
     function checkSpecifiers(node) {
-      if (node.type !== 'ImportDeclaration') return
-      if (node.source == null) return // local export, ignore
+      if (node.type !== 'ImportDeclaration') { return; }
+      if (node.source == null) { return; } // local export, ignore
 
-      const imports = Exports.get(node.source.value, context)
-      if (imports == null) return
+      const imports = ExportMapBuilder.get(node.source.value, context);
+      if (imports == null) { return; }
 
-      let moduleDeprecation
-      if (imports.doc &&
-          imports.doc.tags.some(t => t.title === 'deprecated' && (moduleDeprecation = t))) {
-        context.report({ node, message: message(moduleDeprecation) })
+      const moduleDeprecation = imports.doc && imports.doc.tags.find((t) => t.title === 'deprecated');
+      if (moduleDeprecation) {
+        context.report({ node, message: message(moduleDeprecation) });
       }
 
       if (imports.errors.length) {
-        imports.reportErrors(context, node)
-        return
+        imports.reportErrors(context, node);
+        return;
       }
 
       node.specifiers.forEach(function (im) {
-        let imported, local
+        let imported; let local;
         switch (im.type) {
 
-
-          case 'ImportNamespaceSpecifier':{
-            if (!imports.size) return
-            namespaces.set(im.local.name, imports)
-            return
+          case 'ImportNamespaceSpecifier': {
+            if (!imports.size) { return; }
+            namespaces.set(im.local.name, imports);
+            return;
           }
 
           case 'ImportDefaultSpecifier':
-            imported = 'default'
-            local = im.local.name
-            break
+            imported = 'default';
+            local = im.local.name;
+            break;
 
           case 'ImportSpecifier':
-            imported = im.imported.name
-            local = im.local.name
-            break
+            imported = im.imported.name;
+            local = im.local.name;
+            break;
 
-          default: return // can't handle this one
+          default: return; // can't handle this one
         }
 
         // unknown thing can't be deprecated
-        const exported = imports.get(imported)
-        if (exported == null) return
+        const exported = imports.get(imported);
+        if (exported == null) { return; }
 
         // capture import of deep namespace
-        if (exported.namespace) namespaces.set(local, exported.namespace)
+        if (exported.namespace) { namespaces.set(local, exported.namespace); }
 
-        const deprecation = getDeprecation(imports.get(imported))
-        if (!deprecation) return
+        const deprecation = getDeprecation(imports.get(imported));
+        if (!deprecation) { return; }
 
-        context.report({ node: im, message: message(deprecation) })
+        context.report({ node: im, message: message(deprecation) });
 
-        deprecated.set(local, deprecation)
+        deprecated.set(local, deprecation);
 
-      })
+      });
     }
 
     return {
-      'Program': ({ body }) => body.forEach(checkSpecifiers),
+      Program: ({ body }) => body.forEach(checkSpecifiers),
 
-      'Identifier': function (node) {
+      Identifier(node) {
         if (node.parent.type === 'MemberExpression' && node.parent.property === node) {
-          return // handled by MemberExpression
+          return; // handled by MemberExpression
         }
 
         // ignore specifier identifiers
-        if (node.parent.type.slice(0, 6) === 'Import') return
+        if (node.parent.type.slice(0, 6) === 'Import') { return; }
 
-        if (!deprecated.has(node.name)) return
+        if (!deprecated.has(node.name)) { return; }
 
-        if (declaredScope(context, node.name) !== 'module') return
+        if (declaredScope(context, node.name, node) !== 'module') { return; }
         context.report({
           node,
           message: message(deprecated.get(node.name)),
-        })
+        });
       },
 
-      'MemberExpression': function (dereference) {
-        if (dereference.object.type !== 'Identifier') return
-        if (!namespaces.has(dereference.object.name)) return
+      MemberExpression(dereference) {
+        if (dereference.object.type !== 'Identifier') { return; }
+        if (!namespaces.has(dereference.object.name)) { return; }
 
-        if (declaredScope(context, dereference.object.name) !== 'module') return
+        if (declaredScope(context, dereference.object.name, dereference) !== 'module') { return; }
 
         // go deep
-        var namespace = namespaces.get(dereference.object.name)
-        var namepath = [dereference.object.name]
+        let namespace = namespaces.get(dereference.object.name);
+        const namepath = [dereference.object.name];
         // while property is namespace and parent is member expression, keep validating
-        while (namespace instanceof Exports &&
-               dereference.type === 'MemberExpression') {
-
+        while (namespace instanceof ExportMap && dereference.type === 'MemberExpression') {
           // ignore computed parts for now
-          if (dereference.computed) return
+          if (dereference.computed) { return; }
 
-          const metadata = namespace.get(dereference.property.name)
+          const metadata = namespace.get(dereference.property.name);
 
-          if (!metadata) break
-          const deprecation = getDeprecation(metadata)
+          if (!metadata) { break; }
+          const deprecation = getDeprecation(metadata);
 
           if (deprecation) {
-            context.report({ node: dereference.property, message: message(deprecation) })
+            context.report({ node: dereference.property, message: message(deprecation) });
           }
 
           // stash and pop
-          namepath.push(dereference.property.name)
-          namespace = metadata.namespace
-          dereference = dereference.parent
+          namepath.push(dereference.property.name);
+          namespace = metadata.namespace;
+          dereference = dereference.parent;
         }
       },
-    }
+    };
   },
-}
+};
diff --git a/src/rules/no-duplicates.js b/src/rules/no-duplicates.js
index 33e335748..b8c8d848c 100644
--- a/src/rules/no-duplicates.js
+++ b/src/rules/no-duplicates.js
@@ -1,31 +1,81 @@
-import resolve from 'eslint-module-utils/resolve'
-import docsUrl from '../docsUrl'
+import { getSourceCode } from 'eslint-module-utils/contextCompat';
+import resolve from 'eslint-module-utils/resolve';
+import semver from 'semver';
+import flatMap from 'array.prototype.flatmap';
 
-function checkImports(imported, context) {
-  for (const [module, nodes] of imported.entries()) {
-    if (nodes.length > 1) {
-      const message = `'${module}' imported multiple times.`
-      const [first, ...rest] = nodes
-      const sourceCode = context.getSourceCode()
-      const fix = getFix(first, rest, sourceCode)
+import docsUrl from '../docsUrl';
 
-      context.report({
-        node: first.source,
-        message,
-        fix, // Attach the autofix (if any) to the first import.
-      })
+let typescriptPkg;
+try {
+  typescriptPkg = require('typescript/package.json'); // eslint-disable-line import/no-extraneous-dependencies
+} catch (e) { /**/ }
 
-      for (const node of rest) {
-        context.report({
-          node: node.source,
-          message,
-        })
-      }
-    }
-  }
+function isPunctuator(node, value) {
+  return node.type === 'Punctuator' && node.value === value;
+}
+
+// Get the name of the default import of `node`, if any.
+function getDefaultImportName(node) {
+  const defaultSpecifier = node.specifiers
+    .find((specifier) => specifier.type === 'ImportDefaultSpecifier');
+  return defaultSpecifier != null ? defaultSpecifier.local.name : undefined;
+}
+
+// Checks whether `node` has a namespace import.
+function hasNamespace(node) {
+  const specifiers = node.specifiers
+    .filter((specifier) => specifier.type === 'ImportNamespaceSpecifier');
+  return specifiers.length > 0;
+}
+
+// Checks whether `node` has any non-default specifiers.
+function hasSpecifiers(node) {
+  const specifiers = node.specifiers
+    .filter((specifier) => specifier.type === 'ImportSpecifier');
+  return specifiers.length > 0;
+}
+
+// Checks whether `node` has a comment (that ends) on the previous line or on
+// the same line as `node` (starts).
+function hasCommentBefore(node, sourceCode) {
+  return sourceCode.getCommentsBefore(node)
+    .some((comment) => comment.loc.end.line >= node.loc.start.line - 1);
+}
+
+// Checks whether `node` has a comment (that starts) on the same line as `node`
+// (ends).
+function hasCommentAfter(node, sourceCode) {
+  return sourceCode.getCommentsAfter(node)
+    .some((comment) => comment.loc.start.line === node.loc.end.line);
+}
+
+// Checks whether `node` has any comments _inside,_ except inside the `{...}`
+// part (if any).
+function hasCommentInsideNonSpecifiers(node, sourceCode) {
+  const tokens = sourceCode.getTokens(node);
+  const openBraceIndex = tokens.findIndex((token) => isPunctuator(token, '{'));
+  const closeBraceIndex = tokens.findIndex((token) => isPunctuator(token, '}'));
+  // Slice away the first token, since we're no looking for comments _before_
+  // `node` (only inside). If there's a `{...}` part, look for comments before
+  // the `{`, but not before the `}` (hence the `+1`s).
+  const someTokens = openBraceIndex >= 0 && closeBraceIndex >= 0
+    ? tokens.slice(1, openBraceIndex + 1).concat(tokens.slice(closeBraceIndex + 1))
+    : tokens.slice(1);
+  return someTokens.some((token) => sourceCode.getCommentsBefore(token).length > 0);
 }
 
-function getFix(first, rest, sourceCode) {
+// It's not obvious what the user wants to do with comments associated with
+// duplicate imports, so skip imports with comments when autofixing.
+function hasProblematicComments(node, sourceCode) {
+  return (
+    hasCommentBefore(node, sourceCode)
+    || hasCommentAfter(node, sourceCode)
+    || hasCommentInsideNonSpecifiers(node, sourceCode)
+  );
+}
+
+/** @type {(first: import('estree').ImportDeclaration, rest: import('estree').ImportDeclaration[], sourceCode: import('eslint').SourceCode.SourceCode, context: import('eslint').Rule.RuleContext) => import('eslint').Rule.ReportFixer | undefined} */
+function getFix(first, rest, sourceCode, context) {
   // Sorry ESLint <= 3 users, no autofix for you. Autofixing duplicate imports
   // requires multiple `fixer.whatever()` calls in the `fix`: We both need to
   // update the first one, and remove the rest. Support for multiple
@@ -33,7 +83,7 @@ function getFix(first, rest, sourceCode) {
   // `sourceCode.getCommentsBefore` was added in 4.0, so that's an easy thing to
   // check for.
   if (typeof sourceCode.getCommentsBefore !== 'function') {
-    return undefined
+    return undefined;
   }
 
   // Adjusting the first import might make it multiline, which could break
@@ -41,217 +91,289 @@ function getFix(first, rest, sourceCode) {
   // import has comments. Also, if the first import is `import * as ns from
   // './foo'` there's nothing we can do.
   if (hasProblematicComments(first, sourceCode) || hasNamespace(first)) {
-    return undefined
+    return undefined;
   }
 
   const defaultImportNames = new Set(
-    [first, ...rest].map(getDefaultImportName).filter(Boolean)
-  )
+    flatMap([].concat(first, rest || []), (x) => getDefaultImportName(x) || []),
+  );
 
   // Bail if there are multiple different default import names – it's up to the
   // user to choose which one to keep.
   if (defaultImportNames.size > 1) {
-    return undefined
+    return undefined;
   }
 
   // Leave it to the user to handle comments. Also skip `import * as ns from
   // './foo'` imports, since they cannot be merged into another import.
-  const restWithoutComments = rest.filter(node => !(
-    hasProblematicComments(node, sourceCode) ||
-    hasNamespace(node)
-  ))
+  const restWithoutComments = rest.filter((node) => !hasProblematicComments(node, sourceCode) && !hasNamespace(node));
 
   const specifiers = restWithoutComments
-    .map(node => {
-      const tokens = sourceCode.getTokens(node)
-      const openBrace = tokens.find(token => isPunctuator(token, '{'))
-      const closeBrace = tokens.find(token => isPunctuator(token, '}'))
+    .map((node) => {
+      const tokens = sourceCode.getTokens(node);
+      const openBrace = tokens.find((token) => isPunctuator(token, '{'));
+      const closeBrace = tokens.find((token) => isPunctuator(token, '}'));
 
       if (openBrace == null || closeBrace == null) {
-        return undefined
+        return undefined;
       }
 
       return {
         importNode: node,
-        text: sourceCode.text.slice(openBrace.range[1], closeBrace.range[0]),
-        hasTrailingComma: isPunctuator(sourceCode.getTokenBefore(closeBrace), ','),
+        identifiers: sourceCode.text.slice(openBrace.range[1], closeBrace.range[0]).split(','), // Split the text into separate identifiers (retaining any whitespace before or after)
         isEmpty: !hasSpecifiers(node),
-      }
+      };
     })
-    .filter(Boolean)
+    .filter((x) => !!x);
 
-  const unnecessaryImports = restWithoutComments.filter(node =>
-    !hasSpecifiers(node) &&
-    !hasNamespace(node) &&
-    !specifiers.some(specifier => specifier.importNode === node)
-  )
+  const unnecessaryImports = restWithoutComments.filter((node) => !hasSpecifiers(node)
+    && !hasNamespace(node)
+    && !specifiers.some((specifier) => specifier.importNode === node),
+  );
 
-  const shouldAddDefault = getDefaultImportName(first) == null && defaultImportNames.size === 1
-  const shouldAddSpecifiers = specifiers.length > 0
-  const shouldRemoveUnnecessary = unnecessaryImports.length > 0
+  const shouldAddDefault = getDefaultImportName(first) == null && defaultImportNames.size === 1;
+  const shouldAddSpecifiers = specifiers.length > 0;
+  const shouldRemoveUnnecessary = unnecessaryImports.length > 0;
+  const preferInline = context.options[0] && context.options[0]['prefer-inline'];
 
   if (!(shouldAddDefault || shouldAddSpecifiers || shouldRemoveUnnecessary)) {
-    return undefined
+    return undefined;
   }
 
-  return fixer => {
-    const tokens = sourceCode.getTokens(first)
-    const openBrace = tokens.find(token => isPunctuator(token, '{'))
-    const closeBrace = tokens.find(token => isPunctuator(token, '}'))
-    const firstToken = sourceCode.getFirstToken(first)
-    const [defaultImportName] = defaultImportNames
-
-    const firstHasTrailingComma =
-      closeBrace != null &&
-      isPunctuator(sourceCode.getTokenBefore(closeBrace), ',')
-    const firstIsEmpty = !hasSpecifiers(first)
+  /** @type {import('eslint').Rule.ReportFixer} */
+  return (fixer) => {
+    const tokens = sourceCode.getTokens(first);
+    const openBrace = tokens.find((token) => isPunctuator(token, '{'));
+    const closeBrace = tokens.find((token) => isPunctuator(token, '}'));
+    const firstToken = sourceCode.getFirstToken(first);
+    const [defaultImportName] = defaultImportNames;
+
+    const firstHasTrailingComma = closeBrace != null && isPunctuator(sourceCode.getTokenBefore(closeBrace), ',');
+    const firstIsEmpty = !hasSpecifiers(first);
+    const firstExistingIdentifiers = firstIsEmpty
+      ? new Set()
+      : new Set(sourceCode.text.slice(openBrace.range[1], closeBrace.range[0])
+        .split(',')
+        .map((x) => x.trim()),
+      );
 
     const [specifiersText] = specifiers.reduce(
-      ([result, needsComma], specifier) => {
+      ([result, needsComma, existingIdentifiers], specifier) => {
+        const isTypeSpecifier = specifier.importNode.importKind === 'type';
+
+        // a user might set prefer-inline but not have a supporting TypeScript version. Flow does not support inline types so this should fail in that case as well.
+        if (preferInline && (!typescriptPkg || !semver.satisfies(typescriptPkg.version, '>= 4.5'))) {
+          throw new Error('Your version of TypeScript does not support inline type imports.');
+        }
+
+        // Add *only* the new identifiers that don't already exist, and track any new identifiers so we don't add them again in the next loop
+        const [specifierText, updatedExistingIdentifiers] = specifier.identifiers.reduce(([text, set], cur) => {
+          const trimmed = cur.trim(); // Trim whitespace before/after to compare to our set of existing identifiers
+          const curWithType = trimmed.length > 0 && preferInline && isTypeSpecifier ? `type ${cur}` : cur;
+          if (existingIdentifiers.has(trimmed)) {
+            return [text, set];
+          }
+          return [text.length > 0 ? `${text},${curWithType}` : curWithType, set.add(trimmed)];
+        }, ['', existingIdentifiers]);
+
         return [
-          needsComma && !specifier.isEmpty
-            ? `${result},${specifier.text}`
-            : `${result}${specifier.text}`,
+          needsComma && !specifier.isEmpty && specifierText.length > 0
+            ? `${result},${specifierText}`
+            : `${result}${specifierText}`,
           specifier.isEmpty ? needsComma : true,
-        ]
+          updatedExistingIdentifiers,
+        ];
       },
-      ['', !firstHasTrailingComma && !firstIsEmpty]
-    )
-
-    const fixes = []
+      ['', !firstHasTrailingComma && !firstIsEmpty, firstExistingIdentifiers],
+    );
+
+    /** @type {import('eslint').Rule.Fix[]} */
+    const fixes = [];
+
+    if (shouldAddSpecifiers && preferInline && first.importKind === 'type') {
+      // `import type {a} from './foo'` → `import {type a} from './foo'`
+      const typeIdentifierToken = tokens.find((token) => token.type === 'Identifier' && token.value === 'type');
+      fixes.push(fixer.removeRange([typeIdentifierToken.range[0], typeIdentifierToken.range[1] + 1]));
+
+      tokens
+        .filter((token) => firstExistingIdentifiers.has(token.value))
+        .forEach((identifier) => {
+          fixes.push(fixer.replaceTextRange([identifier.range[0], identifier.range[1]], `type ${identifier.value}`));
+        });
+    }
 
     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'`
-      fixes.push(fixer.insertTextAfter(firstToken, ` ${defaultImportName} from`))
+      fixes.push(fixer.insertTextAfter(firstToken, ` ${defaultImportName} from`));
     } else if (shouldAddDefault && openBrace != null && closeBrace != null) {
       // `import {...} from './foo'` → `import def, {...} from './foo'`
-      fixes.push(fixer.insertTextAfter(firstToken, ` ${defaultImportName},`))
+      fixes.push(fixer.insertTextAfter(firstToken, ` ${defaultImportName},`));
       if (shouldAddSpecifiers) {
         // `import def, {...} from './foo'` → `import def, {..., ...} from './foo'`
-        fixes.push(fixer.insertTextBefore(closeBrace, specifiersText))
+        fixes.push(fixer.insertTextBefore(closeBrace, specifiersText));
       }
     } else if (!shouldAddDefault && openBrace == null && shouldAddSpecifiers) {
-      // `import './foo'` → `import {...} from './foo'`
-      fixes.push(fixer.insertTextAfter(firstToken, ` {${specifiersText}} from`))
+      if (first.specifiers.length === 0) {
+        // `import './foo'` → `import {...} from './foo'`
+        fixes.push(fixer.insertTextAfter(firstToken, ` {${specifiersText}} from`));
+      } else {
+        // `import def from './foo'` → `import def, {...} from './foo'`
+        fixes.push(fixer.insertTextAfter(first.specifiers[0], `, {${specifiersText}}`));
+      }
     } else if (!shouldAddDefault && openBrace != null && closeBrace != null) {
       // `import {...} './foo'` → `import {..., ...} from './foo'`
-      fixes.push(fixer.insertTextBefore(closeBrace, specifiersText))
+      fixes.push(fixer.insertTextBefore(closeBrace, specifiersText));
     }
 
     // Remove imports whose specifiers have been moved into the first import.
-    for (const specifier of specifiers) {
-      fixes.push(fixer.remove(specifier.importNode))
-    }
+    specifiers.forEach((specifier) => {
+      const importNode = specifier.importNode;
+      fixes.push(fixer.remove(importNode));
+
+      const charAfterImportRange = [importNode.range[1], importNode.range[1] + 1];
+      const charAfterImport = sourceCode.text.substring(charAfterImportRange[0], charAfterImportRange[1]);
+      if (charAfterImport === '\n') {
+        fixes.push(fixer.removeRange(charAfterImportRange));
+      }
+    });
 
     // Remove imports whose default import has been moved to the first import,
     // and side-effect-only imports that are unnecessary due to the first
     // import.
-    for (const node of unnecessaryImports) {
-      fixes.push(fixer.remove(node))
-    }
-
-    return fixes
-  }
-}
-
-function isPunctuator(node, value) {
-  return node.type === 'Punctuator' && node.value === value
-}
-
-// Get the name of the default import of `node`, if any.
-function getDefaultImportName(node) {
-  const defaultSpecifier = node.specifiers
-    .find(specifier => specifier.type === 'ImportDefaultSpecifier')
-  return defaultSpecifier != null ? defaultSpecifier.local.name : undefined
-}
-
-// Checks whether `node` has a namespace import.
-function hasNamespace(node) {
-  const specifiers = node.specifiers
-    .filter(specifier => specifier.type === 'ImportNamespaceSpecifier')
-  return specifiers.length > 0
-}
+    unnecessaryImports.forEach((node) => {
+      fixes.push(fixer.remove(node));
 
-// Checks whether `node` has any non-default specifiers.
-function hasSpecifiers(node) {
-  const specifiers = node.specifiers
-    .filter(specifier => specifier.type === 'ImportSpecifier')
-  return specifiers.length > 0
-}
+      const charAfterImportRange = [node.range[1], node.range[1] + 1];
+      const charAfterImport = sourceCode.text.substring(charAfterImportRange[0], charAfterImportRange[1]);
+      if (charAfterImport === '\n') {
+        fixes.push(fixer.removeRange(charAfterImportRange));
+      }
+    });
 
-// It's not obvious what the user wants to do with comments associated with
-// duplicate imports, so skip imports with comments when autofixing.
-function hasProblematicComments(node, sourceCode) {
-  return (
-    hasCommentBefore(node, sourceCode) ||
-    hasCommentAfter(node, sourceCode) ||
-    hasCommentInsideNonSpecifiers(node, sourceCode)
-  )
+    return fixes;
+  };
 }
 
-// Checks whether `node` has a comment (that ends) on the previous line or on
-// the same line as `node` (starts).
-function hasCommentBefore(node, sourceCode) {
-  return sourceCode.getCommentsBefore(node)
-    .some(comment => comment.loc.end.line >= node.loc.start.line - 1)
-}
+/** @type {(imported: Map<string, import('estree').ImportDeclaration[]>, context: import('eslint').Rule.RuleContext) => void} */
+function checkImports(imported, context) {
+  for (const [module, nodes] of imported.entries()) {
+    if (nodes.length > 1) {
+      const message = `'${module}' imported multiple times.`;
+      const [first, ...rest] = nodes;
+      const sourceCode = getSourceCode(context);
+      const fix = getFix(first, rest, sourceCode, context);
 
-// Checks whether `node` has a comment (that starts) on the same line as `node`
-// (ends).
-function hasCommentAfter(node, sourceCode) {
-  return sourceCode.getCommentsAfter(node)
-    .some(comment => comment.loc.start.line === node.loc.end.line)
-}
+      context.report({
+        node: first.source,
+        message,
+        fix, // Attach the autofix (if any) to the first import.
+      });
 
-// Checks whether `node` has any comments _inside,_ except inside the `{...}`
-// part (if any).
-function hasCommentInsideNonSpecifiers(node, sourceCode) {
-  const tokens = sourceCode.getTokens(node)
-  const openBraceIndex = tokens.findIndex(token => isPunctuator(token, '{'))
-  const closeBraceIndex = tokens.findIndex(token => isPunctuator(token, '}'))
-  // Slice away the first token, since we're no looking for comments _before_
-  // `node` (only inside). If there's a `{...}` part, look for comments before
-  // the `{`, but not before the `}` (hence the `+1`s).
-  const someTokens = openBraceIndex >= 0 && closeBraceIndex >= 0
-    ? tokens.slice(1, openBraceIndex + 1).concat(tokens.slice(closeBraceIndex + 1))
-    : tokens.slice(1)
-  return someTokens.some(token => sourceCode.getCommentsBefore(token).length > 0)
+      rest.forEach((node) => {
+        context.report({
+          node: node.source,
+          message,
+        });
+      });
+    }
+  }
 }
 
+/** @type {import('eslint').Rule.RuleModule} */
 module.exports = {
   meta: {
     type: 'problem',
     docs: {
+      category: 'Style guide',
+      description: 'Forbid repeated import of the same module in multiple places.',
       url: docsUrl('no-duplicates'),
     },
     fixable: 'code',
+    schema: [
+      {
+        type: 'object',
+        properties: {
+          considerQueryString: {
+            type: 'boolean',
+          },
+          'prefer-inline': {
+            type: 'boolean',
+          },
+        },
+        additionalProperties: false,
+      },
+    ],
   },
 
-  create: function (context) {
-    const imported = new Map()
-    const typesImported = new Map()
+  /** @param {import('eslint').Rule.RuleContext} context */
+  create(context) {
+    /** @type {boolean} */
+    // Prepare the resolver from options.
+    const considerQueryStringOption = context.options[0] && context.options[0].considerQueryString;
+    /** @type {boolean} */
+    const preferInline = context.options[0] && context.options[0]['prefer-inline'];
+    const defaultResolver = (sourcePath) => resolve(sourcePath, context) || sourcePath;
+    const resolver = considerQueryStringOption ? (sourcePath) => {
+      const parts = sourcePath.match(/^([^?]*)\?(.*)$/);
+      if (!parts) {
+        return defaultResolver(sourcePath);
+      }
+      return `${defaultResolver(parts[1])}?${parts[2]}`;
+    } : defaultResolver;
+
+    /** @type {Map<unknown, { imported: Map<string, import('estree').ImportDeclaration[]>, nsImported: Map<string, import('estree').ImportDeclaration[]>, defaultTypesImported: Map<string, import('estree').ImportDeclaration[]>, namedTypesImported: Map<string, import('estree').ImportDeclaration[]>}>} */
+    const moduleMaps = new Map();
+
+    /** @param {import('estree').ImportDeclaration} n */
+    /** @returns {typeof moduleMaps[keyof typeof moduleMaps]} */
+    function getImportMap(n) {
+      if (!moduleMaps.has(n.parent)) {
+        moduleMaps.set(n.parent, /** @type {typeof moduleMaps} */ {
+          imported: new Map(),
+          nsImported: new Map(),
+          defaultTypesImported: new Map(),
+          namedTypesImported: new Map(),
+        });
+      }
+      const map = moduleMaps.get(n.parent);
+      if (!preferInline && n.importKind === 'type') {
+        return n.specifiers.length > 0 && n.specifiers[0].type === 'ImportDefaultSpecifier' ? map.defaultTypesImported : map.namedTypesImported;
+      }
+      if (!preferInline && n.specifiers.some((spec) => spec.importKind === 'type')) {
+        return map.namedTypesImported;
+      }
+
+      return hasNamespace(n) ? map.nsImported : map.imported;
+    }
+
     return {
-      'ImportDeclaration': function (n) {
+      /** @param {import('estree').ImportDeclaration} n */
+      ImportDeclaration(n) {
+        /** @type {string} */
         // resolved path will cover aliased duplicates
-        const resolvedPath = resolve(n.source.value, context) || n.source.value
-        const importMap = n.importKind === 'type' ? typesImported : imported
+        const resolvedPath = resolver(n.source.value);
+        const importMap = getImportMap(n);
 
         if (importMap.has(resolvedPath)) {
-          importMap.get(resolvedPath).push(n)
+          importMap.get(resolvedPath).push(n);
         } else {
-          importMap.set(resolvedPath, [n])
+          importMap.set(resolvedPath, [n]);
         }
       },
 
-      'Program:exit': function () {
-        checkImports(imported, context)
-        checkImports(typesImported, context)
+      'Program:exit'() {
+        for (const map of moduleMaps.values()) {
+          checkImports(map.imported, context);
+          checkImports(map.nsImported, context);
+          checkImports(map.defaultTypesImported, context);
+          checkImports(map.namedTypesImported, context);
+        }
       },
-    }
+    };
   },
-}
+};
diff --git a/src/rules/no-dynamic-require.js b/src/rules/no-dynamic-require.js
index b9ccad27b..f8b369a70 100644
--- a/src/rules/no-dynamic-require.js
+++ b/src/rules/no-dynamic-require.js
@@ -1,36 +1,77 @@
-import docsUrl from '../docsUrl'
+import docsUrl from '../docsUrl';
 
 function isRequire(node) {
-  return node &&
-    node.callee &&
-    node.callee.type === 'Identifier' &&
-    node.callee.name === 'require' &&
-    node.arguments.length >= 1
+  return node
+    && node.callee
+    && node.callee.type === 'Identifier'
+    && node.callee.name === 'require'
+    && node.arguments.length >= 1;
+}
+
+function isDynamicImport(node) {
+  return node
+    && node.callee
+    && node.callee.type === 'Import';
 }
 
 function isStaticValue(arg) {
-  return arg.type === 'Literal' ||
-    (arg.type === 'TemplateLiteral' && arg.expressions.length === 0)
+  return arg.type === 'Literal'
+    || arg.type === 'TemplateLiteral' && arg.expressions.length === 0;
 }
 
+const dynamicImportErrorMessage = 'Calls to import() should use string literals';
+
 module.exports = {
   meta: {
     type: 'suggestion',
     docs: {
+      category: 'Static analysis',
+      description: 'Forbid `require()` calls with expressions.',
       url: docsUrl('no-dynamic-require'),
     },
+    schema: [
+      {
+        type: 'object',
+        properties: {
+          esmodule: {
+            type: 'boolean',
+          },
+        },
+        additionalProperties: false,
+      },
+    ],
   },
 
-  create: function (context) {
+  create(context) {
+    const options = context.options[0] || {};
+
     return {
       CallExpression(node) {
-        if (isRequire(node) && !isStaticValue(node.arguments[0])) {
-          context.report({
+        if (!node.arguments[0] || isStaticValue(node.arguments[0])) {
+          return;
+        }
+        if (isRequire(node)) {
+          return context.report({
             node,
             message: 'Calls to require() should use string literals',
-          })
+          });
+        }
+        if (options.esmodule && isDynamicImport(node)) {
+          return context.report({
+            node,
+            message: dynamicImportErrorMessage,
+          });
         }
       },
-    }
+      ImportExpression(node) {
+        if (!options.esmodule || isStaticValue(node.source)) {
+          return;
+        }
+        return context.report({
+          node,
+          message: dynamicImportErrorMessage,
+        });
+      },
+    };
   },
-}
+};
diff --git a/src/rules/no-empty-named-blocks.js b/src/rules/no-empty-named-blocks.js
new file mode 100644
index 000000000..d68ecee38
--- /dev/null
+++ b/src/rules/no-empty-named-blocks.js
@@ -0,0 +1,107 @@
+import { getSourceCode } from 'eslint-module-utils/contextCompat';
+
+import docsUrl from '../docsUrl';
+
+function getEmptyBlockRange(tokens, index) {
+  const token = tokens[index];
+  const nextToken = tokens[index + 1];
+  const prevToken = tokens[index - 1];
+  let start = token.range[0];
+  const end = nextToken.range[1];
+
+  // Remove block tokens and the previous comma
+  if (prevToken.value === ',' || prevToken.value === 'type' || prevToken.value === 'typeof') {
+    start = prevToken.range[0];
+  }
+
+  return [start, end];
+}
+
+module.exports = {
+  meta: {
+    type: 'suggestion',
+    docs: {
+      category: 'Helpful warnings',
+      description: 'Forbid empty named import blocks.',
+      url: docsUrl('no-empty-named-blocks'),
+    },
+    fixable: 'code',
+    schema: [],
+    hasSuggestions: true,
+  },
+
+  create(context) {
+    const importsWithoutNameds = [];
+
+    return {
+      ImportDeclaration(node) {
+        if (!node.specifiers.some((x) => x.type === 'ImportSpecifier')) {
+          importsWithoutNameds.push(node);
+        }
+      },
+
+      'Program:exit'(program) {
+        const importsTokens = importsWithoutNameds.map((node) => [node, program.tokens.filter((x) => x.range[0] >= node.range[0] && x.range[1] <= node.range[1])]);
+
+        importsTokens.forEach(([node, tokens]) => {
+          tokens.forEach((token) => {
+            const idx = program.tokens.indexOf(token);
+            const nextToken = program.tokens[idx + 1];
+
+            if (nextToken && token.value === '{' && nextToken.value === '}') {
+              const hasOtherIdentifiers = tokens.some((token) => token.type === 'Identifier'
+                  && token.value !== 'from'
+                  && token.value !== 'type'
+                  && token.value !== 'typeof',
+              );
+
+              // If it has no other identifiers it's the only thing in the import, so we can either remove the import
+              // completely or transform it in a side-effects only import
+              if (!hasOtherIdentifiers) {
+                context.report({
+                  node,
+                  message: 'Unexpected empty named import block',
+                  suggest: [
+                    {
+                      desc: 'Remove unused import',
+                      fix(fixer) {
+                        // Remove the whole import
+                        return fixer.remove(node);
+                      },
+                    },
+                    {
+                      desc: 'Remove empty import block',
+                      fix(fixer) {
+                        // Remove the empty block and the 'from' token, leaving the import only for its side
+                        // effects, e.g. `import 'mod'`
+                        const sourceCode = getSourceCode(context);
+                        const fromToken = program.tokens.find((t) => t.value === 'from');
+                        const importToken = program.tokens.find((t) => t.value === 'import');
+                        const hasSpaceAfterFrom = sourceCode.isSpaceBetween(fromToken, sourceCode.getTokenAfter(fromToken));
+                        const hasSpaceAfterImport = sourceCode.isSpaceBetween(importToken, sourceCode.getTokenAfter(fromToken));
+
+                        const [start] = getEmptyBlockRange(program.tokens, idx);
+                        const [, end] = fromToken.range;
+                        const range = [start, hasSpaceAfterFrom ? end + 1 : end];
+
+                        return fixer.replaceTextRange(range, hasSpaceAfterImport ? '' : ' ');
+                      },
+                    },
+                  ],
+                });
+              } else {
+                context.report({
+                  node,
+                  message: 'Unexpected empty named import block',
+                  fix(fixer) {
+                    return fixer.removeRange(getEmptyBlockRange(program.tokens, idx));
+                  },
+                });
+              }
+            }
+          });
+        });
+      },
+    };
+  },
+};
diff --git a/src/rules/no-extraneous-dependencies.js b/src/rules/no-extraneous-dependencies.js
index 647481a37..bf0a1ed47 100644
--- a/src/rules/no-extraneous-dependencies.js
+++ b/src/rules/no-extraneous-dependencies.js
@@ -1,14 +1,33 @@
-import path from 'path'
-import fs from 'fs'
-import readPkgUp from 'read-pkg-up'
-import minimatch from 'minimatch'
-import resolve from 'eslint-module-utils/resolve'
-import importType from '../core/importType'
-import isStaticRequire from '../core/staticRequire'
-import docsUrl from '../docsUrl'
+import path from 'path';
+import fs from 'fs';
+import minimatch from 'minimatch';
+import { getPhysicalFilename } from 'eslint-module-utils/contextCompat';
+import pkgUp from 'eslint-module-utils/pkgUp';
+import resolve from 'eslint-module-utils/resolve';
+import moduleVisitor from 'eslint-module-utils/moduleVisitor';
+
+import importType from '../core/importType';
+import { getFilePackageName } from '../core/packagePath';
+import docsUrl from '../docsUrl';
+
+const depFieldCache = new Map();
 
 function hasKeys(obj = {}) {
-  return Object.keys(obj).length > 0
+  return Object.keys(obj).length > 0;
+}
+
+function arrayOrKeys(arrayOrObject) {
+  return Array.isArray(arrayOrObject) ? arrayOrObject : Object.keys(arrayOrObject);
+}
+
+function readJSON(jsonPath, throwException) {
+  try {
+    return JSON.parse(fs.readFileSync(jsonPath, 'utf8'));
+  } catch (err) {
+    if (throwException) {
+      throw err;
+    }
+  }
 }
 
 function extractDepFields(pkg) {
@@ -17,45 +36,65 @@ function extractDepFields(pkg) {
     devDependencies: pkg.devDependencies || {},
     optionalDependencies: pkg.optionalDependencies || {},
     peerDependencies: pkg.peerDependencies || {},
+    // BundledDeps should be in the form of an array, but object notation is also supported by
+    // `npm`, so we convert it to an array if it is an object
+    bundledDependencies: arrayOrKeys(pkg.bundleDependencies || pkg.bundledDependencies || []),
+  };
+}
+
+function getPackageDepFields(packageJsonPath, throwAtRead) {
+  if (!depFieldCache.has(packageJsonPath)) {
+    const packageJson = readJSON(packageJsonPath, throwAtRead);
+    if (packageJson) {
+      const depFields = extractDepFields(packageJson);
+      depFieldCache.set(packageJsonPath, depFields);
+    }
   }
+
+  return depFieldCache.get(packageJsonPath);
 }
 
 function getDependencies(context, packageDir) {
-  let paths = []
+  let paths = [];
   try {
     const packageContent = {
       dependencies: {},
       devDependencies: {},
       optionalDependencies: {},
       peerDependencies: {},
-    }
+      bundledDependencies: [],
+    };
 
     if (packageDir && packageDir.length > 0) {
       if (!Array.isArray(packageDir)) {
-        paths = [path.resolve(packageDir)]
+        paths = [path.resolve(packageDir)];
       } else {
-        paths = packageDir.map(dir => path.resolve(dir))
+        paths = packageDir.map((dir) => path.resolve(dir));
       }
     }
 
     if (paths.length > 0) {
       // use rule config to find package.json
-      paths.forEach(dir => {
-        const _packageContent = extractDepFields(
-          JSON.parse(fs.readFileSync(path.join(dir, 'package.json'), 'utf8'))
-        )
-        Object.keys(packageContent).forEach(depsKey =>
-          Object.assign(packageContent[depsKey], _packageContent[depsKey])
-        )
-      })
+      paths.forEach((dir) => {
+        const packageJsonPath = path.join(dir, 'package.json');
+        const _packageContent = getPackageDepFields(packageJsonPath, paths.length === 1);
+        if (_packageContent) {
+          Object.keys(packageContent).forEach((depsKey) => {
+            Object.assign(packageContent[depsKey], _packageContent[depsKey]);
+          });
+        }
+      });
     } else {
+      const packageJsonPath = pkgUp({
+        cwd: getPhysicalFilename(context),
+        normalize: false,
+      });
+
       // use closest package.json
       Object.assign(
         packageContent,
-        extractDepFields(
-          readPkgUp.sync({cwd: context.getFilename(), normalize: false}).pkg
-        )
-      )
+        getPackageDepFields(packageJsonPath, false),
+      );
     }
 
     if (![
@@ -63,140 +102,207 @@ function getDependencies(context, packageDir) {
       packageContent.devDependencies,
       packageContent.optionalDependencies,
       packageContent.peerDependencies,
+      packageContent.bundledDependencies,
     ].some(hasKeys)) {
-      return null
+      return null;
     }
 
-    return packageContent
+    return packageContent;
   } catch (e) {
     if (paths.length > 0 && e.code === 'ENOENT') {
       context.report({
         message: 'The package.json file could not be found.',
         loc: { line: 0, column: 0 },
-      })
+      });
     }
     if (e.name === 'JSONError' || e instanceof SyntaxError) {
       context.report({
-        message: 'The package.json file could not be parsed: ' + e.message,
+        message: `The package.json file could not be parsed: ${e.message}`,
         loc: { line: 0, column: 0 },
-      })
+      });
     }
 
-    return null
+    return null;
   }
 }
 
 function missingErrorMessage(packageName) {
-  return `'${packageName}' should be listed in the project's dependencies. ` +
-    `Run 'npm i -S ${packageName}' to add it`
+  return `'${packageName}' should be listed in the project's dependencies. Run 'npm i -S ${packageName}' to add it`;
 }
 
 function devDepErrorMessage(packageName) {
-  return `'${packageName}' should be listed in the project's dependencies, not devDependencies.`
+  return `'${packageName}' should be listed in the project's dependencies, not devDependencies.`;
 }
 
 function optDepErrorMessage(packageName) {
-  return `'${packageName}' should be listed in the project's dependencies, ` +
-    `not optionalDependencies.`
+  return `'${packageName}' should be listed in the project's dependencies, not optionalDependencies.`;
+}
+
+function getModuleOriginalName(name) {
+  const [first, second] = name.split('/');
+  return first.startsWith('@') ? `${first}/${second}` : first;
+}
+
+function getModuleRealName(resolved) {
+  return getFilePackageName(resolved);
+}
+
+function checkDependencyDeclaration(deps, packageName, declarationStatus) {
+  const newDeclarationStatus = declarationStatus || {
+    isInDeps: false,
+    isInDevDeps: false,
+    isInOptDeps: false,
+    isInPeerDeps: false,
+    isInBundledDeps: false,
+  };
+
+  // in case of sub package.json inside a module
+  // check the dependencies on all hierarchy
+  const packageHierarchy = [];
+  const packageNameParts = packageName ? packageName.split('/') : [];
+  packageNameParts.forEach((namePart, index) => {
+    if (!namePart.startsWith('@')) {
+      const ancestor = packageNameParts.slice(0, index + 1).join('/');
+      packageHierarchy.push(ancestor);
+    }
+  });
+
+  return packageHierarchy.reduce((result, ancestorName) => ({
+    isInDeps: result.isInDeps || deps.dependencies[ancestorName] !== undefined,
+    isInDevDeps: result.isInDevDeps || deps.devDependencies[ancestorName] !== undefined,
+    isInOptDeps: result.isInOptDeps || deps.optionalDependencies[ancestorName] !== undefined,
+    isInPeerDeps: result.isInPeerDeps || deps.peerDependencies[ancestorName] !== undefined,
+    isInBundledDeps:
+        result.isInBundledDeps || deps.bundledDependencies.indexOf(ancestorName) !== -1,
+  }), newDeclarationStatus);
 }
 
 function reportIfMissing(context, deps, depsOptions, node, name) {
-  // Do not report when importing types
-  if (node.importKind === 'type') {
-    return
+  // Do not report when importing types unless option is enabled
+  if (
+    !depsOptions.verifyTypeImports
+    && (
+      node.importKind === 'type'
+      || node.importKind === 'typeof'
+      || node.exportKind === 'type'
+      || Array.isArray(node.specifiers) && node.specifiers.length && node.specifiers.every((specifier) => specifier.importKind === 'type' || specifier.importKind === 'typeof')
+    )
+  ) {
+    return;
   }
 
-  if (importType(name, context) !== 'external') {
-    return
+  const typeOfImport = importType(name, context);
+
+  if (
+    typeOfImport !== 'external'
+    && (typeOfImport !== 'internal' || !depsOptions.verifyInternalDeps)
+  ) {
+    return;
   }
 
-  const resolved = resolve(name, context)
-  if (!resolved) { return }
-
-  const splitName = name.split('/')
-  const packageName = splitName[0][0] === '@'
-    ? splitName.slice(0, 2).join('/')
-    : splitName[0]
-  const isInDeps = deps.dependencies[packageName] !== undefined
-  const isInDevDeps = deps.devDependencies[packageName] !== undefined
-  const isInOptDeps = deps.optionalDependencies[packageName] !== undefined
-  const isInPeerDeps = deps.peerDependencies[packageName] !== undefined
-
-  if (isInDeps ||
-    (depsOptions.allowDevDeps && isInDevDeps) ||
-    (depsOptions.allowPeerDeps && isInPeerDeps) ||
-    (depsOptions.allowOptDeps && isInOptDeps)
+  const resolved = resolve(name, context);
+  if (!resolved) { return; }
+
+  const importPackageName = getModuleOriginalName(name);
+  let declarationStatus = checkDependencyDeclaration(deps, importPackageName);
+
+  if (
+    declarationStatus.isInDeps
+    || depsOptions.allowDevDeps && declarationStatus.isInDevDeps
+    || depsOptions.allowPeerDeps && declarationStatus.isInPeerDeps
+    || depsOptions.allowOptDeps && declarationStatus.isInOptDeps
+    || depsOptions.allowBundledDeps && declarationStatus.isInBundledDeps
   ) {
-    return
+    return;
   }
 
-  if (isInDevDeps && !depsOptions.allowDevDeps) {
-    context.report(node, devDepErrorMessage(packageName))
-    return
+  // test the real name from the resolved package.json
+  // if not aliased imports (alias/react for example), importPackageName can be misinterpreted
+  const realPackageName = getModuleRealName(resolved);
+  if (realPackageName && realPackageName !== importPackageName) {
+    declarationStatus = checkDependencyDeclaration(deps, realPackageName, declarationStatus);
+
+    if (
+      declarationStatus.isInDeps
+      || depsOptions.allowDevDeps && declarationStatus.isInDevDeps
+      || depsOptions.allowPeerDeps && declarationStatus.isInPeerDeps
+      || depsOptions.allowOptDeps && declarationStatus.isInOptDeps
+      || depsOptions.allowBundledDeps && declarationStatus.isInBundledDeps
+    ) {
+      return;
+    }
   }
 
-  if (isInOptDeps && !depsOptions.allowOptDeps) {
-    context.report(node, optDepErrorMessage(packageName))
-    return
+  if (declarationStatus.isInDevDeps && !depsOptions.allowDevDeps) {
+    context.report(node, devDepErrorMessage(realPackageName || importPackageName));
+    return;
   }
 
-  context.report(node, missingErrorMessage(packageName))
+  if (declarationStatus.isInOptDeps && !depsOptions.allowOptDeps) {
+    context.report(node, optDepErrorMessage(realPackageName || importPackageName));
+    return;
+  }
+
+  context.report(node, missingErrorMessage(realPackageName || importPackageName));
 }
 
 function testConfig(config, filename) {
   // Simplest configuration first, either a boolean or nothing.
   if (typeof config === 'boolean' || typeof config === 'undefined') {
-    return config
+    return config;
   }
   // Array of globs.
-  return config.some(c => (
-    minimatch(filename, c) ||
-    minimatch(filename, path.join(process.cwd(), c))
-  ))
+  return config.some((c) => minimatch(filename, c)
+    || minimatch(filename, path.join(process.cwd(), c)),
+  );
 }
 
 module.exports = {
   meta: {
     type: 'problem',
     docs: {
+      category: 'Helpful warnings',
+      description: 'Forbid the use of extraneous packages.',
       url: docsUrl('no-extraneous-dependencies'),
     },
 
     schema: [
       {
-        'type': 'object',
-        'properties': {
-          'devDependencies': { 'type': ['boolean', 'array'] },
-          'optionalDependencies': { 'type': ['boolean', 'array'] },
-          'peerDependencies': { 'type': ['boolean', 'array'] },
-          'packageDir': { 'type': ['string', 'array'] },
+        type: 'object',
+        properties: {
+          devDependencies: { type: ['boolean', 'array'] },
+          optionalDependencies: { type: ['boolean', 'array'] },
+          peerDependencies: { type: ['boolean', 'array'] },
+          bundledDependencies: { type: ['boolean', 'array'] },
+          packageDir: { type: ['string', 'array'] },
+          includeInternal: { type: ['boolean'] },
+          includeTypes: { type: ['boolean'] },
         },
-        'additionalProperties': false,
+        additionalProperties: false,
       },
     ],
   },
 
-  create: function (context) {
-    const options = context.options[0] || {}
-    const filename = context.getFilename()
-    const deps = getDependencies(context, options.packageDir) || extractDepFields({})
+  create(context) {
+    const options = context.options[0] || {};
+    const filename = getPhysicalFilename(context);
+    const deps = getDependencies(context, options.packageDir) || extractDepFields({});
 
     const depsOptions = {
       allowDevDeps: testConfig(options.devDependencies, filename) !== false,
       allowOptDeps: testConfig(options.optionalDependencies, filename) !== false,
       allowPeerDeps: testConfig(options.peerDependencies, filename) !== false,
-    }
+      allowBundledDeps: testConfig(options.bundledDependencies, filename) !== false,
+      verifyInternalDeps: !!options.includeInternal,
+      verifyTypeImports: !!options.includeTypes,
+    };
 
-    // todo: use module visitor from module-utils core
-    return {
-      ImportDeclaration: function (node) {
-        reportIfMissing(context, deps, depsOptions, node, node.source.value)
-      },
-      CallExpression: function handleRequires(node) {
-        if (isStaticRequire(node)) {
-          reportIfMissing(context, deps, depsOptions, node, node.arguments[0].value)
-        }
-      },
-    }
+    return moduleVisitor((source, node) => {
+      reportIfMissing(context, deps, depsOptions, node, source.value);
+    }, { commonjs: true });
   },
-}
+
+  'Program:exit'() {
+    depFieldCache.clear();
+  },
+};
diff --git a/src/rules/no-import-module-exports.js b/src/rules/no-import-module-exports.js
new file mode 100644
index 000000000..bf6fba61b
--- /dev/null
+++ b/src/rules/no-import-module-exports.js
@@ -0,0 +1,86 @@
+import minimatch from 'minimatch';
+import path from 'path';
+import { getPhysicalFilename, getSourceCode } from 'eslint-module-utils/contextCompat';
+import pkgUp from 'eslint-module-utils/pkgUp';
+
+function getEntryPoint(context) {
+  const pkgPath = pkgUp({ cwd: getPhysicalFilename(context) });
+  try {
+    return require.resolve(path.dirname(pkgPath));
+  } catch (error) {
+    // Assume the package has no entrypoint (e.g. CLI packages)
+    // in which case require.resolve would throw.
+    return null;
+  }
+}
+
+function findScope(context, identifier) {
+  const { scopeManager } = getSourceCode(context);
+
+  return scopeManager && scopeManager.scopes.slice().reverse().find((scope) => scope.variables.some((variable) => variable.identifiers.some((node) => node.name === identifier)));
+}
+
+function findDefinition(objectScope, identifier) {
+  const variable = objectScope.variables.find((variable) => variable.name === identifier);
+  return variable.defs.find((def) => def.name.name === identifier);
+}
+
+module.exports = {
+  meta: {
+    type: 'problem',
+    docs: {
+      category: 'Module systems',
+      description: 'Forbid import statements with CommonJS module.exports.',
+      recommended: true,
+    },
+    fixable: 'code',
+    schema: [
+      {
+        type: 'object',
+        properties: {
+          exceptions: { type: 'array' },
+        },
+        additionalProperties: false,
+      },
+    ],
+  },
+  create(context) {
+    const importDeclarations = [];
+    const entryPoint = getEntryPoint(context);
+    const options = context.options[0] || {};
+    let alreadyReported = false;
+
+    function report(node) {
+      const fileName = getPhysicalFilename(context);
+      const isEntryPoint = entryPoint === fileName;
+      const isIdentifier = node.object.type === 'Identifier';
+      const hasKeywords = (/^(module|exports)$/).test(node.object.name);
+      const objectScope = hasKeywords && findScope(context, node.object.name);
+      const variableDefinition = objectScope && findDefinition(objectScope, node.object.name);
+      const isImportBinding = variableDefinition && variableDefinition.type === 'ImportBinding';
+      const hasCJSExportReference = hasKeywords && (!objectScope || objectScope.type === 'module');
+      const isException = !!options.exceptions && options.exceptions.some((glob) => minimatch(fileName, glob));
+
+      if (isIdentifier && hasCJSExportReference && !isEntryPoint && !isException && !isImportBinding) {
+        importDeclarations.forEach((importDeclaration) => {
+          context.report({
+            node: importDeclaration,
+            message: `Cannot use import declarations in modules that export using CommonJS (module.exports = 'foo' or exports.bar = 'hi')`,
+          });
+        });
+        alreadyReported = true;
+      }
+    }
+
+    return {
+      ImportDeclaration(node) {
+        importDeclarations.push(node);
+      },
+      MemberExpression(node) {
+        if (!alreadyReported) {
+          report(node);
+        }
+      },
+    };
+  },
+};
diff --git a/src/rules/no-internal-modules.js b/src/rules/no-internal-modules.js
index 9987dfd5c..5ed456547 100644
--- a/src/rules/no-internal-modules.js
+++ b/src/rules/no-internal-modules.js
@@ -1,102 +1,144 @@
-import minimatch from 'minimatch'
+import minimatch from 'minimatch';
 
-import resolve from 'eslint-module-utils/resolve'
-import importType from '../core/importType'
-import isStaticRequire from '../core/staticRequire'
-import docsUrl from '../docsUrl'
+import resolve from 'eslint-module-utils/resolve';
+import importType from '../core/importType';
+import moduleVisitor from 'eslint-module-utils/moduleVisitor';
+import docsUrl from '../docsUrl';
 
 module.exports = {
   meta: {
     type: 'suggestion',
     docs: {
+      category: 'Static analysis',
+      description: 'Forbid importing the submodules of other modules.',
       url: docsUrl('no-internal-modules'),
     },
 
     schema: [
       {
-        type: 'object',
-        properties: {
-          allow: {
-            type: 'array',
-            items: {
-              type: 'string',
+        anyOf: [
+          {
+            type: 'object',
+            properties: {
+              allow: {
+                type: 'array',
+                items: {
+                  type: 'string',
+                },
+              },
             },
+            additionalProperties: false,
           },
-        },
-        additionalProperties: false,
+          {
+            type: 'object',
+            properties: {
+              forbid: {
+                type: 'array',
+                items: {
+                  type: 'string',
+                },
+              },
+            },
+            additionalProperties: false,
+          },
+        ],
       },
     ],
   },
 
   create: function noReachingInside(context) {
-    const options = context.options[0] || {}
-    const allowRegexps = (options.allow || []).map(p => minimatch.makeRe(p))
-
-    // test if reaching to this destination is allowed
-    function reachingAllowed(importPath) {
-      return allowRegexps.some(re => re.test(importPath))
-    }
+    const options = context.options[0] || {};
+    const allowRegexps = (options.allow || []).map((p) => minimatch.makeRe(p));
+    const forbidRegexps = (options.forbid || []).map((p) => minimatch.makeRe(p));
 
     // minimatch patterns are expected to use / path separators, like import
     // statements, so normalize paths to use the same
     function normalizeSep(somePath) {
-      return somePath.split('\\').join('/')
+      return somePath.split('\\').join('/');
     }
 
-    // find a directory that is being reached into, but which shouldn't be
-    function isReachViolation(importPath) {
-      const steps = normalizeSep(importPath)
+    function toSteps(somePath) {
+      return normalizeSep(somePath)
         .split('/')
+        .filter((step) => step && step !== '.')
         .reduce((acc, step) => {
-          if (!step || step === '.') {
-            return acc
-          } else if (step === '..') {
-            return acc.slice(0, -1)
-          } else {
-            return acc.concat(step)
+          if (step === '..') {
+            return acc.slice(0, -1);
           }
-        }, [])
+          return acc.concat(step);
+        }, []);
+    }
+
+    // test if reaching to this destination is allowed
+    function reachingAllowed(importPath) {
+      return allowRegexps.some((re) => re.test(importPath));
+    }
+
+    // test if reaching to this destination is forbidden
+    function reachingForbidden(importPath) {
+      return forbidRegexps.some((re) => re.test(importPath));
+    }
 
-      const nonScopeSteps = steps.filter(step => step.indexOf('@') !== 0)
-      if (nonScopeSteps.length <= 1) return false
+    function isAllowViolation(importPath) {
+      const steps = toSteps(importPath);
+
+      const nonScopeSteps = steps.filter((step) => step.indexOf('@') !== 0);
+      if (nonScopeSteps.length <= 1) { return false; }
 
       // before trying to resolve, see if the raw import (with relative
       // segments resolved) matches an allowed pattern
-      const justSteps = steps.join('/')
-      if (reachingAllowed(justSteps) || reachingAllowed(`/${justSteps}`)) return false
+      const justSteps = steps.join('/');
+      if (reachingAllowed(justSteps) || reachingAllowed(`/${justSteps}`)) { return false; }
 
       // if the import statement doesn't match directly, try to match the
       // resolved path if the import is resolvable
-      const resolved = resolve(importPath, context)
-      if (!resolved || reachingAllowed(normalizeSep(resolved))) return false
+      const resolved = resolve(importPath, context);
+      if (!resolved || reachingAllowed(normalizeSep(resolved))) { return false; }
 
       // this import was not allowed by the allowed paths, and reaches
       // so it is a violation
-      return true
+      return true;
+    }
+
+    function isForbidViolation(importPath) {
+      const steps = toSteps(importPath);
+
+      // before trying to resolve, see if the raw import (with relative
+      // segments resolved) matches a forbidden pattern
+      const justSteps = steps.join('/');
+
+      if (reachingForbidden(justSteps) || reachingForbidden(`/${justSteps}`)) { return true; }
+
+      // if the import statement doesn't match directly, try to match the
+      // resolved path if the import is resolvable
+      const resolved = resolve(importPath, context);
+      if (resolved && reachingForbidden(normalizeSep(resolved))) { return true; }
+
+      // this import was not forbidden by the forbidden paths so it is not a violation
+      return false;
     }
 
+    // find a directory that is being reached into, but which shouldn't be
+    const isReachViolation = options.forbid ? isForbidViolation : isAllowViolation;
+
     function checkImportForReaching(importPath, node) {
-      const potentialViolationTypes = ['parent', 'index', 'sibling', 'external', 'internal']
-      if (potentialViolationTypes.indexOf(importType(importPath, context)) !== -1 &&
-        isReachViolation(importPath)
+      const potentialViolationTypes = ['parent', 'index', 'sibling', 'external', 'internal'];
+      if (
+        potentialViolationTypes.indexOf(importType(importPath, context)) !== -1
+        && isReachViolation(importPath)
       ) {
         context.report({
           node,
           message: `Reaching to "${importPath}" is not allowed.`,
-        })
+        });
       }
     }
 
-    return {
-      ImportDeclaration(node) {
-        checkImportForReaching(node.source.value, node.source)
-      },
-      CallExpression(node) {
-        if (isStaticRequire(node)) {
-          const [ firstArgument ] = node.arguments
-          checkImportForReaching(firstArgument.value, firstArgument)
-        }
+    return moduleVisitor(
+      (source) => {
+        checkImportForReaching(source.value, source);
       },
-    }
+      { commonjs: true },
+    );
   },
-}
+};
diff --git a/src/rules/no-mutable-exports.js b/src/rules/no-mutable-exports.js
index 0908162bd..0a0e128dc 100644
--- a/src/rules/no-mutable-exports.js
+++ b/src/rules/no-mutable-exports.js
@@ -1,56 +1,62 @@
-import docsUrl from '../docsUrl'
+import { getScope } from 'eslint-module-utils/contextCompat';
 
+import docsUrl from '../docsUrl';
+
+/** @type {import('eslint').Rule.RuleModule} */
 module.exports = {
   meta: {
     type: 'suggestion',
     docs: {
+      category: 'Helpful warnings',
+      description: 'Forbid the use of mutable exports with `var` or `let`.',
       url: docsUrl('no-mutable-exports'),
     },
+    schema: [],
   },
 
-  create: function (context) {
+  create(context) {
     function checkDeclaration(node) {
-      const {kind} = node
+      const { kind } = node;
       if (kind === 'var' || kind === 'let') {
-        context.report(node, `Exporting mutable '${kind}' binding, use 'const' instead.`)
+        context.report(node, `Exporting mutable '${kind}' binding, use 'const' instead.`);
       }
     }
 
-    function checkDeclarationsInScope({variables}, name) {
-      for (let variable of variables) {
-        if (variable.name === name) {
-          for (let def of variable.defs) {
-            if (def.type === 'Variable' && def.parent) {
-              checkDeclaration(def.parent)
-            }
-          }
-        }
-      }
-    }
-
-    function handleExportDefault(node) {
-      const scope = context.getScope()
-
-      if (node.declaration.name) {
-        checkDeclarationsInScope(scope, node.declaration.name)
-      }
+    /** @type {(scope: import('eslint').Scope.Scope, name: string) => void} */
+    function checkDeclarationsInScope({ variables }, name) {
+      variables
+        .filter((variable) => variable.name === name)
+        .forEach((variable) => {
+          variable.defs
+            .filter((def) => def.type === 'Variable' && def.parent)
+            .forEach((def) => {
+              checkDeclaration(def.parent);
+            });
+        });
     }
 
-    function handleExportNamed(node) {
-      const scope = context.getScope()
+    return {
+      /** @param {import('estree').ExportDefaultDeclaration} node */
+      ExportDefaultDeclaration(node) {
+        const scope = getScope(context, node);
 
-      if (node.declaration)  {
-        checkDeclaration(node.declaration)
-      } else if (!node.source) {
-        for (let specifier of node.specifiers) {
-          checkDeclarationsInScope(scope, specifier.local.name)
+        if ('name' in node.declaration && node.declaration.name) {
+          checkDeclarationsInScope(scope, node.declaration.name);
         }
-      }
-    }
-
-    return {
-      'ExportDefaultDeclaration': handleExportDefault,
-      'ExportNamedDeclaration': handleExportNamed,
-    }
+      },
+
+      /** @param {import('estree').ExportNamedDeclaration} node */
+      ExportNamedDeclaration(node) {
+        const scope = getScope(context, node);
+
+        if ('declaration' in node && node.declaration)  {
+          checkDeclaration(node.declaration);
+        } else if (!('source' in node) || !node.source) {
+          node.specifiers.forEach((specifier) => {
+            checkDeclarationsInScope(scope, specifier.local.name);
+          });
+        }
+      },
+    };
   },
-}
+};
diff --git a/src/rules/no-named-as-default-member.js b/src/rules/no-named-as-default-member.js
index b7c3c7582..c6abc46a8 100644
--- a/src/rules/no-named-as-default-member.js
+++ b/src/rules/no-named-as-default-member.js
@@ -4,9 +4,9 @@
  * @copyright 2016 Desmond Brand. All rights reserved.
  * See LICENSE in root directory for full license.
  */
-import Exports from '../ExportMap'
-import importDeclaration from '../importDeclaration'
-import docsUrl from '../docsUrl'
+import ExportMapBuilder from '../exportMap/builder';
+import importDeclaration from '../importDeclaration';
+import docsUrl from '../docsUrl';
 
 //------------------------------------------------------------------------------
 // Rule Definition
@@ -16,86 +16,76 @@ module.exports = {
   meta: {
     type: 'suggestion',
     docs: {
+      category: 'Helpful warnings',
+      description: 'Forbid use of exported name as property of default export.',
       url: docsUrl('no-named-as-default-member'),
     },
+    schema: [],
   },
 
-  create: function(context) {
-
-    const fileImports = new Map()
-    const allPropertyLookups = new Map()
-
-    function handleImportDefault(node) {
-      const declaration = importDeclaration(context)
-      const exportMap = Exports.get(declaration.source.value, context)
-      if (exportMap == null) return
-
-      if (exportMap.errors.length) {
-        exportMap.reportErrors(context, declaration)
-        return
-      }
-
-      fileImports.set(node.local.name, {
-        exportMap,
-        sourcePath: declaration.source.value,
-      })
-    }
+  create(context) {
+    const fileImports = new Map();
+    const allPropertyLookups = new Map();
 
     function storePropertyLookup(objectName, propName, node) {
-      const lookups = allPropertyLookups.get(objectName) || []
-      lookups.push({node, propName})
-      allPropertyLookups.set(objectName, lookups)
+      const lookups = allPropertyLookups.get(objectName) || [];
+      lookups.push({ node, propName });
+      allPropertyLookups.set(objectName, lookups);
     }
 
-    function handlePropLookup(node) {
-      const objectName = node.object.name
-      const propName = node.property.name
-      storePropertyLookup(objectName, propName, node)
-    }
+    return {
+      ImportDefaultSpecifier(node) {
+        const declaration = importDeclaration(context, node);
+        const exportMap = ExportMapBuilder.get(declaration.source.value, context);
+        if (exportMap == null) { return; }
 
-    function handleDestructuringAssignment(node) {
-      const isDestructure = (
-        node.id.type === 'ObjectPattern' &&
-        node.init != null &&
-        node.init.type === 'Identifier'
-      )
-      if (!isDestructure) return
+        if (exportMap.errors.length) {
+          exportMap.reportErrors(context, declaration);
+          return;
+        }
 
-      const objectName = node.init.name
-      for (const { key } of node.id.properties) {
-        if (key == null) continue  // true for rest properties
-        storePropertyLookup(objectName, key.name, key)
-      }
-    }
+        fileImports.set(node.local.name, {
+          exportMap,
+          sourcePath: declaration.source.value,
+        });
+      },
 
-    function handleProgramExit() {
-      allPropertyLookups.forEach((lookups, objectName) => {
-        const fileImport = fileImports.get(objectName)
-        if (fileImport == null) return
+      MemberExpression(node) {
+        const objectName = node.object.name;
+        const propName = node.property.name;
+        storePropertyLookup(objectName, propName, node);
+      },
 
-        for (const {propName, node} of lookups) {
-          // the default import can have a "default" property
-          if (propName === 'default') continue
-          if (!fileImport.exportMap.namespace.has(propName)) continue
+      VariableDeclarator(node) {
+        const isDestructure = node.id.type === 'ObjectPattern'
+          && node.init != null
+          && node.init.type === 'Identifier';
+        if (!isDestructure) { return; }
 
-          context.report({
-            node,
-            message: (
-              `Caution: \`${objectName}\` also has a named export ` +
-              `\`${propName}\`. Check if you meant to write ` +
-              `\`import {${propName}} from '${fileImport.sourcePath}'\` ` +
-              'instead.'
-            ),
-          })
+        const objectName = node.init.name;
+        for (const { key } of node.id.properties) {
+          if (key == null) { continue; }  // true for rest properties
+          storePropertyLookup(objectName, key.name, key);
         }
-      })
-    }
+      },
 
-    return {
-      'ImportDefaultSpecifier': handleImportDefault,
-      'MemberExpression': handlePropLookup,
-      'VariableDeclarator': handleDestructuringAssignment,
-      'Program:exit': handleProgramExit,
-    }
+      'Program:exit'() {
+        allPropertyLookups.forEach((lookups, objectName) => {
+          const fileImport = fileImports.get(objectName);
+          if (fileImport == null) { return; }
+
+          for (const { propName, node } of lookups) {
+            // the default import can have a "default" property
+            if (propName === 'default') { continue; }
+            if (!fileImport.exportMap.namespace.has(propName)) { continue; }
+
+            context.report({
+              node,
+              message: `Caution: \`${objectName}\` also has a named export \`${propName}\`. Check if you meant to write \`import {${propName}} from '${fileImport.sourcePath}'\` instead.`,
+            });
+          }
+        });
+      },
+    };
   },
-}
+};
diff --git a/src/rules/no-named-as-default.js b/src/rules/no-named-as-default.js
index ad6a8ee6d..dacd294f4 100644
--- a/src/rules/no-named-as-default.js
+++ b/src/rules/no-named-as-default.js
@@ -1,42 +1,88 @@
-import Exports from '../ExportMap'
-import importDeclaration from '../importDeclaration'
-import docsUrl from '../docsUrl'
+import ExportMapBuilder from '../exportMap/builder';
+import importDeclaration from '../importDeclaration';
+import docsUrl from '../docsUrl';
 
 module.exports = {
   meta: {
     type: 'problem',
     docs: {
+      category: 'Helpful warnings',
+      description: 'Forbid use of exported name as identifier of default export.',
       url: docsUrl('no-named-as-default'),
     },
+    schema: [],
   },
 
-  create: function (context) {
+  create(context) {
     function checkDefault(nameKey, defaultSpecifier) {
+      /**
+       * For ImportDefaultSpecifier we're interested in the "local" name (`foo` for `import {bar as foo} ...`)
+       * For ExportDefaultSpecifier we're interested in the "exported" name (`foo` for `export {bar as foo} ...`)
+       */
+      const analyzedName = defaultSpecifier[nameKey].name;
+
       // #566: default is a valid specifier
-      if (defaultSpecifier[nameKey].name === 'default') return
+      if (analyzedName === 'default') { return; }
+
+      const declaration = importDeclaration(context, defaultSpecifier);
+      /** @type {import('../exportMap').default | null} */
+      const importedModule = ExportMapBuilder.get(declaration.source.value, context);
+      if (importedModule == null) { return; }
 
-      var declaration = importDeclaration(context)
+      if (importedModule.errors.length > 0) {
+        importedModule.reportErrors(context, declaration);
+        return;
+      }
 
-      var imports = Exports.get(declaration.source.value, context)
-      if (imports == null) return
+      if (!importedModule.hasDefault) {
+        // The rule is triggered for default imports/exports, so if the imported module has no default
+        // this means we're dealing with incorrect source code anyway
+        return;
+      }
 
-      if (imports.errors.length) {
-        imports.reportErrors(context, declaration)
-        return
+      if (!importedModule.has(analyzedName)) {
+        // The name used locally for the default import was not even used in the imported module.
+        return;
       }
 
-      if (imports.has('default') &&
-          imports.has(defaultSpecifier[nameKey].name)) {
+      /**
+       * FIXME: We can verify if a default and a named export are pointing to the same symbol only
+       * if they are both `reexports`. In case one of the symbols is not a re-export, but defined
+       * in the file, the ExportMap structure has no info about what actually is being exported --
+       * the value in the `namespace` Map is an empty object.
+       *
+       * To solve this, it would require not relying on the ExportMap, but on some other way of
+       * accessing the imported module and its exported values.
+       *
+       * Additionally, although `ExportMap.get` is a unified way to get info from both `reexports`
+       * and `namespace` maps, it does not return valid output we need here, and I think this is
+       * related to the "cycle safeguards" in the `get` function.
+       */
 
-        context.report(defaultSpecifier,
-          'Using exported name \'' + defaultSpecifier[nameKey].name +
-          '\' as identifier for default export.')
+      if (importedModule.reexports.has(analyzedName) && importedModule.reexports.has('default')) {
+        const thingImportedWithNamedImport = importedModule.reexports.get(analyzedName).getImport();
+        const thingImportedWithDefaultImport = importedModule.reexports.get('default').getImport();
 
+        // Case: both imports point to the same file and they both refer to the same symbol in this file.
+        if (
+          thingImportedWithNamedImport.path === thingImportedWithDefaultImport.path
+          && thingImportedWithNamedImport.local === thingImportedWithDefaultImport.local
+        ) {
+          // #1594: the imported module exports the same thing via a default export and a named export
+          return;
+        }
       }
+
+      context.report(
+        defaultSpecifier,
+        `Using exported name '${defaultSpecifier[nameKey].name}' as identifier for default ${nameKey === 'local' ? `import` : `export`}.`,
+      );
+
     }
+
     return {
-      'ImportDefaultSpecifier': checkDefault.bind(null, 'local'),
-      'ExportDefaultSpecifier': checkDefault.bind(null, 'exported'),
-    }
+      ImportDefaultSpecifier: checkDefault.bind(null, 'local'),
+      ExportDefaultSpecifier: checkDefault.bind(null, 'exported'),
+    };
   },
-}
+};
diff --git a/src/rules/no-named-default.js b/src/rules/no-named-default.js
index 86f24ef6d..1ed0e31df 100644
--- a/src/rules/no-named-default.js
+++ b/src/rules/no-named-default.js
@@ -1,24 +1,31 @@
-import docsUrl from '../docsUrl'
+import docsUrl from '../docsUrl';
 
 module.exports = {
   meta: {
     type: 'suggestion',
     docs: {
+      category: 'Style guide',
+      description: 'Forbid named default exports.',
       url: docsUrl('no-named-default'),
     },
+    schema: [],
   },
 
-  create: function (context) {
+  create(context) {
     return {
-      'ImportDeclaration': function (node) {
+      ImportDeclaration(node) {
         node.specifiers.forEach(function (im) {
-          if (im.type === 'ImportSpecifier' && im.imported.name === 'default') {
+          if (im.importKind === 'type' || im.importKind === 'typeof') {
+            return;
+          }
+
+          if (im.type === 'ImportSpecifier' && (im.imported.name || im.imported.value) === 'default') {
             context.report({
               node: im.local,
-              message: `Use default import syntax to import '${im.local.name}'.` })
+              message: `Use default import syntax to import '${im.local.name}'.` });
           }
-        })
+        });
       },
-    }
+    };
   },
-}
+};
diff --git a/src/rules/no-named-export.js b/src/rules/no-named-export.js
index 2fa639201..fc9b2c48d 100644
--- a/src/rules/no-named-export.js
+++ b/src/rules/no-named-export.js
@@ -1,34 +1,40 @@
-import docsUrl from '../docsUrl'
+import sourceType from '../core/sourceType';
+import docsUrl from '../docsUrl';
 
 module.exports = {
   meta: {
     type: 'suggestion',
-    docs: { url: docsUrl('no-named-export') },
+    docs: {
+      category: 'Style guide',
+      description: 'Forbid named exports.',
+      url: docsUrl('no-named-export'),
+    },
+    schema: [],
   },
 
   create(context) {
     // ignore non-modules
-    if (context.parserOptions.sourceType !== 'module') {
-      return {}
+    if (sourceType(context) !== 'module') {
+      return {};
     }
 
-    const message = 'Named exports are not allowed.'
+    const message = 'Named exports are not allowed.';
 
     return {
       ExportAllDeclaration(node) {
-        context.report({node, message})
+        context.report({ node, message });
       },
 
       ExportNamedDeclaration(node) {
         if (node.specifiers.length === 0) {
-          return context.report({node, message})
+          return context.report({ node, message });
         }
 
-        const someNamed = node.specifiers.some(specifier => specifier.exported.name !== 'default')
+        const someNamed = node.specifiers.some((specifier) => (specifier.exported.name || specifier.exported.value) !== 'default');
         if (someNamed) {
-          context.report({node, message})
+          context.report({ node, message });
         }
       },
-    }
+    };
   },
-}
+};
diff --git a/src/rules/no-namespace.js b/src/rules/no-namespace.js
index 3dbedca50..7ab60bd21 100644
--- a/src/rules/no-namespace.js
+++ b/src/rules/no-namespace.js
@@ -3,26 +3,170 @@
  * @author Radek Benkel
  */
 
-import docsUrl from '../docsUrl'
+import minimatch from 'minimatch';
+import { getScope, getSourceCode } from 'eslint-module-utils/contextCompat';
 
-//------------------------------------------------------------------------------
-// Rule Definition
-//------------------------------------------------------------------------------
+import docsUrl from '../docsUrl';
 
+/**
+ * @param {MemberExpression} memberExpression
+ * @returns {string} the name of the member in the object expression, e.g. the `x` in `namespace.x`
+ */
+function getMemberPropertyName(memberExpression) {
+  return memberExpression.property.type === 'Identifier'
+    ? memberExpression.property.name
+    : memberExpression.property.value;
+}
+
+/**
+ * @param {ScopeManager} scopeManager
+ * @param {ASTNode} node
+ * @return {Set<string>}
+ */
+function getVariableNamesInScope(scopeManager, node) {
+  let currentNode = node;
+  let scope = scopeManager.acquire(currentNode);
+  while (scope == null) {
+    currentNode = currentNode.parent;
+    scope = scopeManager.acquire(currentNode, true);
+  }
+  return new Set(scope.variables.concat(scope.upper.variables).map((variable) => variable.name));
+}
+
+/**
+ *
+ * @param {*} names
+ * @param {*} nameConflicts
+ * @param {*} namespaceName
+ */
+function generateLocalNames(names, nameConflicts, namespaceName) {
+  const localNames = {};
+  names.forEach((name) => {
+    let localName;
+    if (!nameConflicts[name].has(name)) {
+      localName = name;
+    } else if (!nameConflicts[name].has(`${namespaceName}_${name}`)) {
+      localName = `${namespaceName}_${name}`;
+    } else {
+      for (let i = 1; i < Infinity; i++) {
+        if (!nameConflicts[name].has(`${namespaceName}_${name}_${i}`)) {
+          localName = `${namespaceName}_${name}_${i}`;
+          break;
+        }
+      }
+    }
+    localNames[name] = localName;
+  });
+  return localNames;
+}
+
+/**
+ * @param {Identifier[]} namespaceIdentifiers
+ * @returns {boolean} `true` if the namespace variable is more than just a glorified constant
+ */
+function usesNamespaceAsObject(namespaceIdentifiers) {
+  return !namespaceIdentifiers.every((identifier) => {
+    const parent = identifier.parent;
+
+    // `namespace.x` or `namespace['x']`
+    return (
+      parent
+      && parent.type === 'MemberExpression'
+      && (parent.property.type === 'Identifier' || parent.property.type === 'Literal')
+    );
+  });
+}
 
 module.exports = {
   meta: {
     type: 'suggestion',
     docs: {
+      category: 'Style guide',
+      description: 'Forbid namespace (a.k.a. "wildcard" `*`) imports.',
       url: docsUrl('no-namespace'),
     },
+    fixable: 'code',
+    schema: [{
+      type: 'object',
+      properties: {
+        ignore: {
+          type: 'array',
+          items: {
+            type: 'string',
+          },
+          uniqueItems: true,
+        },
+      },
+    }],
   },
 
-  create: function (context) {
+  create(context) {
+    const firstOption = context.options[0] || {};
+    const ignoreGlobs = firstOption.ignore;
+
     return {
-      'ImportNamespaceSpecifier': function (node) {
-        context.report(node, `Unexpected namespace import.`)
+      ImportNamespaceSpecifier(node) {
+        if (ignoreGlobs && ignoreGlobs.find((glob) => minimatch(node.parent.source.value, glob, { matchBase: true }))) {
+          return;
+        }
+
+        const scopeVariables = getScope(context, node).variables;
+        const namespaceVariable = scopeVariables.find((variable) => variable.defs[0].node === node);
+        const namespaceReferences = namespaceVariable.references;
+        const namespaceIdentifiers = namespaceReferences.map((reference) => reference.identifier);
+        const canFix = namespaceIdentifiers.length > 0 && !usesNamespaceAsObject(namespaceIdentifiers);
+
+        context.report({
+          node,
+          message: `Unexpected namespace import.`,
+          fix: canFix && ((fixer) => {
+            const { scopeManager } = getSourceCode(context);
+            const fixes = [];
+
+            // Pass 1: Collect variable names that are already in scope for each reference we want
+            // to transform, so that we can be sure that we choose non-conflicting import names
+            const importNameConflicts = {};
+            namespaceIdentifiers.forEach((identifier) => {
+              const parent = identifier.parent;
+              if (parent && parent.type === 'MemberExpression') {
+                const importName = getMemberPropertyName(parent);
+                const localConflicts = getVariableNamesInScope(scopeManager, parent);
+                if (!importNameConflicts[importName]) {
+                  importNameConflicts[importName] = localConflicts;
+                } else {
+                  localConflicts.forEach((c) => importNameConflicts[importName].add(c));
+                }
+              }
+            });
+
+            // Choose new names for each import
+            const importNames = Object.keys(importNameConflicts);
+            const importLocalNames = generateLocalNames(
+              importNames,
+              importNameConflicts,
+              namespaceVariable.name,
+            );
+
+            // Replace the ImportNamespaceSpecifier with a list of ImportSpecifiers
+            const namedImportSpecifiers = importNames.map((importName) => importName === importLocalNames[importName]
+              ? importName
+              : `${importName} as ${importLocalNames[importName]}`,
+            );
+            fixes.push(fixer.replaceText(node, `{ ${namedImportSpecifiers.join(', ')} }`));
+
+            // Pass 2: Replace references to the namespace with references to the named imports
+            namespaceIdentifiers.forEach((identifier) => {
+              const parent = identifier.parent;
+              if (parent && parent.type === 'MemberExpression') {
+                const importName = getMemberPropertyName(parent);
+                fixes.push(fixer.replaceText(parent, importLocalNames[importName]));
+              }
+            });
+
+            return fixes;
+          }),
+        });
       },
-    }
+    };
   },
-}
+};
diff --git a/src/rules/no-nodejs-modules.js b/src/rules/no-nodejs-modules.js
index 125bb5f3f..82594bb60 100644
--- a/src/rules/no-nodejs-modules.js
+++ b/src/rules/no-nodejs-modules.js
@@ -1,10 +1,10 @@
-import importType from '../core/importType'
-import isStaticRequire from '../core/staticRequire'
-import docsUrl from '../docsUrl'
+import importType from '../core/importType';
+import moduleVisitor from 'eslint-module-utils/moduleVisitor';
+import docsUrl from '../docsUrl';
 
 function reportIfMissing(context, node, allowed, name) {
   if (allowed.indexOf(name) === -1 && importType(name, context) === 'builtin') {
-    context.report(node, 'Do not import Node.js builtin module "' + name + '"')
+    context.report(node, `Do not import Node.js builtin module "${name}"`);
   }
 }
 
@@ -12,23 +12,33 @@ module.exports = {
   meta: {
     type: 'suggestion',
     docs: {
+      category: 'Module systems',
+      description: 'Forbid Node.js builtin modules.',
       url: docsUrl('no-nodejs-modules'),
     },
+    schema: [
+      {
+        type: 'object',
+        properties: {
+          allow: {
+            type: 'array',
+            uniqueItems: true,
+            items: {
+              type: 'string',
+            },
+          },
+        },
+        additionalProperties: false,
+      },
+    ],
   },
 
-  create: function (context) {
-    const options = context.options[0] || {}
-    const allowed = options.allow || []
+  create(context) {
+    const options = context.options[0] || {};
+    const allowed = options.allow || [];
 
-    return {
-      ImportDeclaration: function handleImports(node) {
-        reportIfMissing(context, node, allowed, node.source.value)
-      },
-      CallExpression: function handleRequires(node) {
-        if (isStaticRequire(node)) {
-          reportIfMissing(context, node, allowed, node.arguments[0].value)
-        }
-      },
-    }
+    return moduleVisitor((source, node) => {
+      reportIfMissing(context, node, allowed, source.value);
+    }, { commonjs: true });
   },
-}
+};
diff --git a/src/rules/no-relative-packages.js b/src/rules/no-relative-packages.js
new file mode 100644
index 000000000..ebc280ff9
--- /dev/null
+++ b/src/rules/no-relative-packages.js
@@ -0,0 +1,72 @@
+import path from 'path';
+import readPkgUp from 'eslint-module-utils/readPkgUp';
+
+import { getPhysicalFilename } from 'eslint-module-utils/contextCompat';
+import resolve from 'eslint-module-utils/resolve';
+import moduleVisitor, { makeOptionsSchema } from 'eslint-module-utils/moduleVisitor';
+import importType from '../core/importType';
+import docsUrl from '../docsUrl';
+
+/** @param {string} filePath */
+function toPosixPath(filePath) {
+  return filePath.replace(/\\/g, '/');
+}
+
+function findNamedPackage(filePath) {
+  const found = readPkgUp({ cwd: filePath });
+  if (found.pkg && !found.pkg.name) {
+    return findNamedPackage(path.join(found.path, '../..'));
+  }
+  return found;
+}
+
+function checkImportForRelativePackage(context, importPath, node) {
+  const potentialViolationTypes = ['parent', 'index', 'sibling'];
+  if (potentialViolationTypes.indexOf(importType(importPath, context)) === -1) {
+    return;
+  }
+
+  const resolvedImport = resolve(importPath, context);
+  const resolvedContext = getPhysicalFilename(context);
+
+  if (!resolvedImport || !resolvedContext) {
+    return;
+  }
+
+  const importPkg = findNamedPackage(resolvedImport);
+  const contextPkg = findNamedPackage(resolvedContext);
+
+  if (importPkg.pkg && contextPkg.pkg && importPkg.pkg.name !== contextPkg.pkg.name) {
+    const importBaseName = path.basename(importPath);
+    const importRoot = path.dirname(importPkg.path);
+    const properPath = path.relative(importRoot, resolvedImport);
+    const properImport = path.join(
+      importPkg.pkg.name,
+      path.dirname(properPath),
+      importBaseName === path.basename(importRoot) ? '' : importBaseName,
+    );
+    context.report({
+      node,
+      message: `Relative import from another package is not allowed. Use \`${properImport}\` instead of \`${importPath}\``,
+      fix: (fixer) => fixer.replaceText(node, JSON.stringify(toPosixPath(properImport)))
+      ,
+    });
+  }
+}
+
+module.exports = {
+  meta: {
+    type: 'suggestion',
+    docs: {
+      category: 'Static analysis',
+      description: 'Forbid importing packages through relative paths.',
+      url: docsUrl('no-relative-packages'),
+    },
+    fixable: 'code',
+    schema: [makeOptionsSchema()],
+  },
+
+  create(context) {
+    return moduleVisitor((source) => checkImportForRelativePackage(context, source.value, source), context.options[0]);
+  },
+};
diff --git a/src/rules/no-relative-parent-imports.js b/src/rules/no-relative-parent-imports.js
index 544525755..94972d3dd 100644
--- a/src/rules/no-relative-parent-imports.js
+++ b/src/rules/no-relative-parent-imports.js
@@ -1,49 +1,49 @@
-import moduleVisitor, { makeOptionsSchema } from 'eslint-module-utils/moduleVisitor'
-import docsUrl from '../docsUrl'
-import { basename, dirname, relative } from 'path'
-import resolve from 'eslint-module-utils/resolve'
+import { basename, dirname, relative } from 'path';
+import { getPhysicalFilename } from 'eslint-module-utils/contextCompat';
+import moduleVisitor, { makeOptionsSchema } from 'eslint-module-utils/moduleVisitor';
+import resolve from 'eslint-module-utils/resolve';
 
-import importType from '../core/importType'
+import importType from '../core/importType';
+import docsUrl from '../docsUrl';
 
 module.exports = {
   meta: {
     type: 'suggestion',
     docs: {
+      category: 'Static analysis',
+      description: 'Forbid importing modules from parent directories.',
       url: docsUrl('no-relative-parent-imports'),
     },
     schema: [makeOptionsSchema()],
   },
 
   create: function noRelativePackages(context) {
-    const myPath = context.getFilename()
-    if (myPath === '<text>') return {} // can't check a non-file
+    const myPath = getPhysicalFilename(context);
+    if (myPath === '<text>') { return {}; } // can't check a non-file
 
     function checkSourceValue(sourceNode) {
-      const depPath = sourceNode.value
+      const depPath = sourceNode.value;
 
       if (importType(depPath, context) === 'external') { // ignore packages
-        return
+        return;
       }
 
-      const absDepPath = resolve(depPath, context)
+      const absDepPath = resolve(depPath, context);
 
       if (!absDepPath) { // unable to resolve path
-        return
+        return;
       }
 
-      const relDepPath = relative(dirname(myPath), absDepPath)
+      const relDepPath = relative(dirname(myPath), absDepPath);
 
       if (importType(relDepPath, context) === 'parent') {
         context.report({
           node: sourceNode,
-          message: 'Relative imports from parent directories are not allowed. ' +
-            `Please either pass what you're importing through at runtime ` +
-            `(dependency injection), move \`${basename(myPath)}\` to same ` +
-            `directory as \`${depPath}\` or consider making \`${depPath}\` a package.`,
-        })
+          message: `Relative imports from parent directories are not allowed. Please either pass what you're importing through at runtime (dependency injection), move \`${basename(myPath)}\` to same directory as \`${depPath}\` or consider making \`${depPath}\` a package.`,
+        });
       }
     }
 
-    return moduleVisitor(checkSourceValue, context.options[0])
+    return moduleVisitor(checkSourceValue, context.options[0]);
   },
-}
+};
diff --git a/src/rules/no-restricted-paths.js b/src/rules/no-restricted-paths.js
index 0d906f631..2e1bc608c 100644
--- a/src/rules/no-restricted-paths.js
+++ b/src/rules/no-restricted-paths.js
@@ -1,14 +1,33 @@
-import containsPath from 'contains-path'
-import path from 'path'
+import path from 'path';
+import { getPhysicalFilename } from 'eslint-module-utils/contextCompat';
+import resolve from 'eslint-module-utils/resolve';
+import moduleVisitor from 'eslint-module-utils/moduleVisitor';
+import isGlob from 'is-glob';
+import { Minimatch } from 'minimatch';
 
-import resolve from 'eslint-module-utils/resolve'
-import isStaticRequire from '../core/staticRequire'
-import docsUrl from '../docsUrl'
+import importType from '../core/importType';
+import docsUrl from '../docsUrl';
+
+const containsPath = (filepath, target) => {
+  const relative = path.relative(target, filepath);
+  return relative === '' || !relative.startsWith('..');
+};
+
+function isMatchingTargetPath(filename, targetPath) {
+  if (isGlob(targetPath)) {
+    const mm = new Minimatch(targetPath);
+    return mm.match(filename);
+  }
+
+  return containsPath(filename, targetPath);
+}
 
 module.exports = {
   meta: {
     type: 'problem',
     docs: {
+      category: 'Static analysis',
+      description: 'Enforce which files can be imported in a given folder.',
       url: docsUrl('no-restricted-paths'),
     },
 
@@ -22,8 +41,36 @@ module.exports = {
             items: {
               type: 'object',
               properties: {
-                target: { type: 'string' },
-                from: { type: 'string' },
+                target: {
+                  anyOf: [
+                    { type: 'string' },
+                    {
+                      type: 'array',
+                      items: { type: 'string' },
+                      uniqueItems: true,
+                      minLength: 1,
+                    },
+                  ],
+                },
+                from: {
+                  anyOf: [
+                    { type: 'string' },
+                    {
+                      type: 'array',
+                      items: { type: 'string' },
+                      uniqueItems: true,
+                      minLength: 1,
+                    },
+                  ],
+                },
+                except: {
+                  type: 'array',
+                  items: {
+                    type: 'string',
+                  },
+                  uniqueItems: true,
+                },
+                message: { type: 'string' },
               },
               additionalProperties: false,
             },
@@ -36,46 +83,164 @@ module.exports = {
   },
 
   create: function noRestrictedPaths(context) {
-    const options = context.options[0] || {}
-    const restrictedPaths = options.zones || []
-    const basePath = options.basePath || process.cwd()
-    const currentFilename = context.getFilename()
-    const matchingZones = restrictedPaths.filter((zone) => {
-      const targetPath = path.resolve(basePath, zone.target)
+    const options = context.options[0] || {};
+    const restrictedPaths = options.zones || [];
+    const basePath = options.basePath || process.cwd();
+    const currentFilename = getPhysicalFilename(context);
+    const matchingZones = restrictedPaths.filter(
+      (zone) => [].concat(zone.target)
+        .map((target) => path.resolve(basePath, target))
+        .some((targetPath) => isMatchingTargetPath(currentFilename, targetPath)),
+    );
 
-      return containsPath(currentFilename, targetPath)
-    })
+    function isValidExceptionPath(absoluteFromPath, absoluteExceptionPath) {
+      const relativeExceptionPath = path.relative(absoluteFromPath, absoluteExceptionPath);
 
-    function checkForRestrictedImportPath(importPath, node) {
-        const absoluteImportPath = resolve(importPath, context)
+      return importType(relativeExceptionPath, context) !== 'parent';
+    }
 
-        if (!absoluteImportPath) {
-          return
-        }
+    function areBothGlobPatternAndAbsolutePath(areGlobPatterns) {
+      return areGlobPatterns.some((isGlob) => isGlob) && areGlobPatterns.some((isGlob) => !isGlob);
+    }
+
+    function reportInvalidExceptionPath(node) {
+      context.report({
+        node,
+        message: 'Restricted path exceptions must be descendants of the configured `from` path for that zone.',
+      });
+    }
 
-        matchingZones.forEach((zone) => {
-          const absoluteFrom = path.resolve(basePath, zone.from)
+    function reportInvalidExceptionMixedGlobAndNonGlob(node) {
+      context.report({
+        node,
+        message: 'Restricted path `from` must contain either only glob patterns or none',
+      });
+    }
 
-          if (containsPath(absoluteImportPath, absoluteFrom)) {
-            context.report({
-              node,
-              message: `Unexpected path "${importPath}" imported in restricted zone.`,
-            })
-          }
-        })
+    function reportInvalidExceptionGlob(node) {
+      context.report({
+        node,
+        message: 'Restricted path exceptions must be glob patterns when `from` contains glob patterns',
+      });
     }
 
-    return {
-      ImportDeclaration(node) {
-        checkForRestrictedImportPath(node.source.value, node.source)
-      },
-      CallExpression(node) {
-        if (isStaticRequire(node)) {
-          const [ firstArgument ] = node.arguments
+    function computeMixedGlobAndAbsolutePathValidator() {
+      return {
+        isPathRestricted: () => true,
+        hasValidExceptions: false,
+        reportInvalidException: reportInvalidExceptionMixedGlobAndNonGlob,
+      };
+    }
+
+    function computeGlobPatternPathValidator(absoluteFrom, zoneExcept) {
+      let isPathException;
+
+      const mm = new Minimatch(absoluteFrom);
+      const isPathRestricted = (absoluteImportPath) => mm.match(absoluteImportPath);
+      const hasValidExceptions = zoneExcept.every(isGlob);
+
+      if (hasValidExceptions) {
+        const exceptionsMm = zoneExcept.map((except) => new Minimatch(except));
+        isPathException = (absoluteImportPath) => exceptionsMm.some((mm) => mm.match(absoluteImportPath));
+      }
+
+      const reportInvalidException = reportInvalidExceptionGlob;
+
+      return {
+        isPathRestricted,
+        hasValidExceptions,
+        isPathException,
+        reportInvalidException,
+      };
+    }
+
+    function computeAbsolutePathValidator(absoluteFrom, zoneExcept) {
+      let isPathException;
+
+      const isPathRestricted = (absoluteImportPath) => containsPath(absoluteImportPath, absoluteFrom);
+
+      const absoluteExceptionPaths = zoneExcept
+        .map((exceptionPath) => path.resolve(absoluteFrom, exceptionPath));
+      const hasValidExceptions = absoluteExceptionPaths
+        .every((absoluteExceptionPath) => isValidExceptionPath(absoluteFrom, absoluteExceptionPath));
+
+      if (hasValidExceptions) {
+        isPathException = (absoluteImportPath) => absoluteExceptionPaths.some(
+          (absoluteExceptionPath) => containsPath(absoluteImportPath, absoluteExceptionPath),
+        );
+      }
 
-          checkForRestrictedImportPath(firstArgument.value, firstArgument)
+      const reportInvalidException = reportInvalidExceptionPath;
+
+      return {
+        isPathRestricted,
+        hasValidExceptions,
+        isPathException,
+        reportInvalidException,
+      };
+    }
+
+    function reportInvalidExceptions(validators, node) {
+      validators.forEach((validator) => validator.reportInvalidException(node));
+    }
+
+    function reportImportsInRestrictedZone(validators, node, importPath, customMessage) {
+      validators.forEach(() => {
+        context.report({
+          node,
+          message: `Unexpected path "{{importPath}}" imported in restricted zone.${customMessage ? ` ${customMessage}` : ''}`,
+          data: { importPath },
+        });
+      });
+    }
+
+    const makePathValidators = (zoneFrom, zoneExcept = []) => {
+      const allZoneFrom = [].concat(zoneFrom);
+      const areGlobPatterns = allZoneFrom.map(isGlob);
+
+      if (areBothGlobPatternAndAbsolutePath(areGlobPatterns)) {
+        return [computeMixedGlobAndAbsolutePathValidator()];
+      }
+
+      const isGlobPattern = areGlobPatterns.every((isGlob) => isGlob);
+
+      return allZoneFrom.map((singleZoneFrom) => {
+        const absoluteFrom = path.resolve(basePath, singleZoneFrom);
+
+        if (isGlobPattern) {
+          return computeGlobPatternPathValidator(absoluteFrom, zoneExcept);
         }
-      },
+        return computeAbsolutePathValidator(absoluteFrom, zoneExcept);
+      });
+    };
+
+    const validators = [];
+
+    function checkForRestrictedImportPath(importPath, node) {
+      const absoluteImportPath = resolve(importPath, context);
+
+      if (!absoluteImportPath) {
+        return;
+      }
+
+      matchingZones.forEach((zone, index) => {
+        if (!validators[index]) {
+          validators[index] = makePathValidators(zone.from, zone.except);
+        }
+
+        const applicableValidatorsForImportPath = validators[index].filter((validator) => validator.isPathRestricted(absoluteImportPath));
+
+        const validatorsWithInvalidExceptions = applicableValidatorsForImportPath.filter((validator) => !validator.hasValidExceptions);
+        reportInvalidExceptions(validatorsWithInvalidExceptions, node);
+
+        const applicableValidatorsForImportPathExcludingExceptions = applicableValidatorsForImportPath
+          .filter((validator) => validator.hasValidExceptions && !validator.isPathException(absoluteImportPath));
+        reportImportsInRestrictedZone(applicableValidatorsForImportPathExcludingExceptions, node, importPath, zone.message);
+      });
     }
+
+    return moduleVisitor((source) => {
+      checkForRestrictedImportPath(source.value, source);
+    }, { commonjs: true });
   },
-}
+};
diff --git a/src/rules/no-self-import.js b/src/rules/no-self-import.js
index b869d46e0..99c534270 100644
--- a/src/rules/no-self-import.js
+++ b/src/rules/no-self-import.js
@@ -3,19 +3,21 @@
  * @author Gio d'Amelio
  */
 
-import resolve from 'eslint-module-utils/resolve'
-import isStaticRequire from '../core/staticRequire'
-import docsUrl from '../docsUrl'
+import { getPhysicalFilename } from 'eslint-module-utils/contextCompat';
+import resolve from 'eslint-module-utils/resolve';
+import moduleVisitor from 'eslint-module-utils/moduleVisitor';
+
+import docsUrl from '../docsUrl';
 
 function isImportingSelf(context, node, requireName) {
-  const filePath = context.getFilename()
+  const filePath = getPhysicalFilename(context);
 
   // If the input is from stdin, this test can't fail
   if (filePath !== '<text>' && filePath === resolve(requireName, context)) {
     context.report({
-        node,
-        message: 'Module imports itself.',
-    })
+      node,
+      message: 'Module imports itself.',
+    });
   }
 }
 
@@ -23,23 +25,17 @@ module.exports = {
   meta: {
     type: 'problem',
     docs: {
-      description: 'Forbid a module from importing itself',
+      category: 'Static analysis',
+      description: 'Forbid a module from importing itself.',
       recommended: true,
       url: docsUrl('no-self-import'),
     },
 
     schema: [],
   },
-  create: function (context) {
-    return {
-      ImportDeclaration(node) {
-        isImportingSelf(context, node, node.source.value)
-      },
-      CallExpression(node) {
-        if (isStaticRequire(node)) {
-          isImportingSelf(context, node, node.arguments[0].value)
-        }
-      },
-    }
+  create(context) {
+    return moduleVisitor((source, node) => {
+      isImportingSelf(context, node, source.value);
+    }, { commonjs: true });
   },
-}
+};
diff --git a/src/rules/no-unassigned-import.js b/src/rules/no-unassigned-import.js
index 5ea637e67..fec232afe 100644
--- a/src/rules/no-unassigned-import.js
+++ b/src/rules/no-unassigned-import.js
@@ -1,54 +1,56 @@
-import path from 'path'
-import minimatch from 'minimatch'
+import path from 'path';
+import minimatch from 'minimatch';
+import { getPhysicalFilename } from 'eslint-module-utils/contextCompat';
 
-import isStaticRequire from '../core/staticRequire'
-import docsUrl from '../docsUrl'
+import isStaticRequire from '../core/staticRequire';
+import docsUrl from '../docsUrl';
 
 function report(context, node) {
   context.report({
     node,
     message: 'Imported module should be assigned',
-  })
+  });
 }
 
 function testIsAllow(globs, filename, source) {
   if (!Array.isArray(globs)) {
-    return false // default doesn't allow any patterns
+    return false; // default doesn't allow any patterns
   }
 
-  let filePath
+  let filePath;
 
   if (source[0] !== '.' && source[0] !== '/') { // a node module
-    filePath = source
+    filePath = source;
   } else {
-    filePath = path.resolve(path.dirname(filename), source) // get source absolute path
+    filePath = path.resolve(path.dirname(filename), source); // get source absolute path
   }
 
-  return globs.find(glob => (
-    minimatch(filePath, glob) ||
-    minimatch(filePath, path.join(process.cwd(), glob))
-  )) !== undefined
+  return globs.find((glob) => minimatch(filePath, glob)
+    || minimatch(filePath, path.join(process.cwd(), glob)),
+  ) !== undefined;
 }
 
 function create(context) {
-  const options = context.options[0] || {}
-  const filename = context.getFilename()
-  const isAllow = source => testIsAllow(options.allow, filename, source)
+  const options = context.options[0] || {};
+  const filename = getPhysicalFilename(context);
+  const isAllow = (source) => testIsAllow(options.allow, filename, source);
 
   return {
     ImportDeclaration(node) {
       if (node.specifiers.length === 0 && !isAllow(node.source.value)) {
-        report(context, node)
+        report(context, node);
       }
     },
     ExpressionStatement(node) {
-      if (node.expression.type === 'CallExpression' &&
-        isStaticRequire(node.expression) &&
-        !isAllow(node.expression.arguments[0].value)) {
-        report(context, node.expression)
+      if (
+        node.expression.type === 'CallExpression'
+        && isStaticRequire(node.expression)
+        && !isAllow(node.expression.arguments[0].value)
+      ) {
+        report(context, node.expression);
       }
     },
-  }
+  };
 }
 
 module.exports = {
@@ -56,24 +58,26 @@ module.exports = {
   meta: {
     type: 'suggestion',
     docs: {
+      category: 'Style guide',
+      description: 'Forbid unassigned imports',
       url: docsUrl('no-unassigned-import'),
     },
     schema: [
       {
-        'type': 'object',
-        'properties': {
-          'devDependencies': { 'type': ['boolean', 'array'] },
-          'optionalDependencies': { 'type': ['boolean', 'array'] },
-          'peerDependencies': { 'type': ['boolean', 'array'] },
-          'allow': {
-            'type': 'array',
-            'items': {
-              'type': 'string',
+        type: 'object',
+        properties: {
+          devDependencies: { type: ['boolean', 'array'] },
+          optionalDependencies: { type: ['boolean', 'array'] },
+          peerDependencies: { type: ['boolean', 'array'] },
+          allow: {
+            type: 'array',
+            items: {
+              type: 'string',
             },
           },
         },
-        'additionalProperties': false,
+        additionalProperties: false,
       },
     ],
   },
-}
+};
diff --git a/src/rules/no-unresolved.js b/src/rules/no-unresolved.js
index 8436e4c92..8216cdf1f 100644
--- a/src/rules/no-unresolved.js
+++ b/src/rules/no-unresolved.js
@@ -3,47 +3,58 @@
  * @author Ben Mosher
  */
 
-import resolve, { CASE_SENSITIVE_FS, fileExistsWithCaseSync } from 'eslint-module-utils/resolve'
-import ModuleCache from 'eslint-module-utils/ModuleCache'
-import moduleVisitor, { makeOptionsSchema } from 'eslint-module-utils/moduleVisitor'
-import docsUrl from '../docsUrl'
+import resolve, { CASE_SENSITIVE_FS, fileExistsWithCaseSync } from 'eslint-module-utils/resolve';
+import ModuleCache from 'eslint-module-utils/ModuleCache';
+import moduleVisitor, { makeOptionsSchema } from 'eslint-module-utils/moduleVisitor';
+import docsUrl from '../docsUrl';
 
 module.exports = {
   meta: {
     type: 'problem',
     docs: {
+      category: 'Static analysis',
+      description: 'Ensure imports point to a file/module that can be resolved.',
       url: docsUrl('no-unresolved'),
     },
 
-    schema: [ makeOptionsSchema({
-      caseSensitive: { type: 'boolean', default: true },
-    })],
+    schema: [
+      makeOptionsSchema({
+        caseSensitive: { type: 'boolean', default: true },
+        caseSensitiveStrict: { type: 'boolean', default: false },
+      }),
+    ],
   },
 
-  create: function (context) {
+  create(context) {
+    const options = context.options[0] || {};
 
-    function checkSourceValue(source) {
-      const shouldCheckCase = !CASE_SENSITIVE_FS &&
-        (!context.options[0] || context.options[0].caseSensitive !== false)
+    function checkSourceValue(source, node) {
+      // ignore type-only imports and exports
+      if (node.importKind === 'type' || node.exportKind === 'type') {
+        return;
+      }
 
-      const resolvedPath = resolve(source.value, context)
+      const caseSensitive = !CASE_SENSITIVE_FS && options.caseSensitive !== false;
+      const caseSensitiveStrict = !CASE_SENSITIVE_FS && options.caseSensitiveStrict;
 
-      if (resolvedPath === undefined) {
-        context.report(source,
-          `Unable to resolve path to module '${source.value}'.`)
-      }
+      const resolvedPath = resolve(source.value, context);
 
-      else if (shouldCheckCase) {
-        const cacheSettings = ModuleCache.getSettings(context.settings)
-        if (!fileExistsWithCaseSync(resolvedPath, cacheSettings)) {
-          context.report(source,
-            `Casing of ${source.value} does not match the underlying filesystem.`)
+      if (resolvedPath === undefined) {
+        context.report(
+          source,
+          `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.`,
+          );
         }
-
       }
     }
 
-    return moduleVisitor(checkSourceValue, context.options[0])
-
+    return moduleVisitor(checkSourceValue, options);
   },
-}
+};
diff --git a/src/rules/no-unused-modules.js b/src/rules/no-unused-modules.js
index 47cd11c0d..86302a0ea 100644
--- a/src/rules/no-unused-modules.js
+++ b/src/rules/no-unused-modules.js
@@ -4,150 +4,397 @@
  * @author René Fermann
  */
 
-import Exports from '../ExportMap'
-import resolve from 'eslint-module-utils/resolve'
-import docsUrl from '../docsUrl'
-import { dirname, join } from 'path'
-import readPkgUp from 'read-pkg-up'
-import values from 'object.values'
-import includes from 'array-includes'
-
-// eslint/lib/util/glob-util has been moved to eslint/lib/util/glob-utils with version 5.3
-// and has been moved to eslint/lib/cli-engine/file-enumerator in version 6
-let listFilesToProcess
-try {
-  var FileEnumerator = require('eslint/lib/cli-engine/file-enumerator').FileEnumerator
-  listFilesToProcess = function (src) {
-    var e = new FileEnumerator()
-    return Array.from(e.iterateFiles(src), ({ filePath, ignored }) => ({
-      ignored,
-      filename: filePath,
-    }))
+import { getPhysicalFilename } from 'eslint-module-utils/contextCompat';
+import { getFileExtensions } from 'eslint-module-utils/ignore';
+import resolve from 'eslint-module-utils/resolve';
+import visit from 'eslint-module-utils/visit';
+import { dirname, join } from 'path';
+import readPkgUp from 'eslint-module-utils/readPkgUp';
+import values from 'object.values';
+import includes from 'array-includes';
+import flatMap from 'array.prototype.flatmap';
+
+import ExportMapBuilder from '../exportMap/builder';
+import recursivePatternCapture from '../exportMap/patternCapture';
+import docsUrl from '../docsUrl';
+
+/**
+ * Attempt to load the internal `FileEnumerator` class, which has existed in a couple
+ * of different places, depending on the version of `eslint`.  Try requiring it from both
+ * locations.
+ * @returns Returns the `FileEnumerator` class if its requirable, otherwise `undefined`.
+ */
+function requireFileEnumerator() {
+  let FileEnumerator;
+
+  // Try getting it from the eslint private / deprecated api
+  try {
+    ({ FileEnumerator } = require('eslint/use-at-your-own-risk'));
+  } catch (e) {
+    // Absorb this if it's MODULE_NOT_FOUND
+    if (e.code !== 'MODULE_NOT_FOUND') {
+      throw e;
+    }
+
+    // If not there, then try getting it from eslint/lib/cli-engine/file-enumerator (moved there in v6)
+    try {
+      ({ FileEnumerator } = require('eslint/lib/cli-engine/file-enumerator'));
+    } catch (e) {
+      // Absorb this if it's MODULE_NOT_FOUND
+      if (e.code !== 'MODULE_NOT_FOUND') {
+        throw e;
+      }
+    }
+  }
+  return FileEnumerator;
+}
+
+/**
+ * Given a FileEnumerator class, instantiate and load the list of files.
+ * @param FileEnumerator the `FileEnumerator` class from `eslint`'s internal api
+ * @param {string} src path to the src root
+ * @param {string[]} extensions list of supported extensions
+ * @returns {{ filename: string, ignored: boolean }[]} list of files to operate on
+ */
+function listFilesUsingFileEnumerator(FileEnumerator, src, extensions) {
+  // We need to know whether this is being run with flat config in order to
+  // determine how to report errors if FileEnumerator throws due to a lack of eslintrc.
+
+  const { ESLINT_USE_FLAT_CONFIG } = process.env;
+
+  // This condition is sufficient to test in v8, since the environment variable is necessary to turn on flat config
+  let isUsingFlatConfig = ESLINT_USE_FLAT_CONFIG && process.env.ESLINT_USE_FLAT_CONFIG !== 'false';
+
+  // In the case of using v9, we can check the `shouldUseFlatConfig` function
+  // If this function is present, then we assume it's v9
+  try {
+    const { shouldUseFlatConfig } = require('eslint/use-at-your-own-risk');
+    isUsingFlatConfig = shouldUseFlatConfig && ESLINT_USE_FLAT_CONFIG !== 'false';
+  } catch (_) {
+    // We don't want to throw here, since we only want to update the
+    // boolean if the function is available.
   }
-} catch (e1) {
+
+  const enumerator = new FileEnumerator({
+    extensions,
+  });
+
   try {
-    listFilesToProcess = require('eslint/lib/util/glob-utils').listFilesToProcess
-  } catch (e2) {
-    listFilesToProcess = require('eslint/lib/util/glob-util').listFilesToProcess
+    return Array.from(
+      enumerator.iterateFiles(src),
+      ({ filePath, ignored }) => ({ filename: filePath, ignored }),
+    );
+  } catch (e) {
+    // If we're using flat config, and FileEnumerator throws due to a lack of eslintrc,
+    // then we want to throw an error so that the user knows about this rule's reliance on
+    // the legacy config.
+    if (
+      isUsingFlatConfig
+      && e.message.includes('No ESLint configuration found')
+    ) {
+      throw new Error(`
+Due to the exclusion of certain internal ESLint APIs when using flat config,
+the import/no-unused-modules rule requires an .eslintrc file to know which
+files to ignore (even when using flat config).
+The .eslintrc file only needs to contain "ignorePatterns", or can be empty if
+you do not want to ignore any files.
+
+See https://github.com/import-js/eslint-plugin-import/issues/3079
+for additional context.
+`);
+    }
+    // If this isn't the case, then we'll just let the error bubble up
+    throw e;
   }
 }
 
-const EXPORT_DEFAULT_DECLARATION = 'ExportDefaultDeclaration'
-const EXPORT_NAMED_DECLARATION = 'ExportNamedDeclaration'
-const EXPORT_ALL_DECLARATION = 'ExportAllDeclaration'
-const IMPORT_DECLARATION = 'ImportDeclaration'
-const IMPORT_NAMESPACE_SPECIFIER = 'ImportNamespaceSpecifier'
-const IMPORT_DEFAULT_SPECIFIER = 'ImportDefaultSpecifier'
-const VARIABLE_DECLARATION = 'VariableDeclaration'
-const FUNCTION_DECLARATION = 'FunctionDeclaration'
-const CLASS_DECLARATION = 'ClassDeclaration'
-const DEFAULT = 'default'
-
-let preparationDone = false
-const importList = new Map()
-const exportList = new Map()
-const ignoredFiles = new Set()
-
-const isNodeModule = path => {
-  return /\/(node_modules)\//.test(path)
+/**
+ * Attempt to require old versions of the file enumeration capability from v6 `eslint` and earlier, and use
+ * those functions to provide the list of files to operate on
+ * @param {string} src path to the src root
+ * @param {string[]} extensions list of supported extensions
+ * @returns {string[]} list of files to operate on
+ */
+function listFilesWithLegacyFunctions(src, extensions) {
+  try {
+    // eslint/lib/util/glob-util has been moved to eslint/lib/util/glob-utils with version 5.3
+    const { listFilesToProcess: originalListFilesToProcess } = require('eslint/lib/util/glob-utils');
+    // Prevent passing invalid options (extensions array) to old versions of the function.
+    // https://github.com/eslint/eslint/blob/v5.16.0/lib/util/glob-utils.js#L178-L280
+    // https://github.com/eslint/eslint/blob/v5.2.0/lib/util/glob-util.js#L174-L269
+
+    return originalListFilesToProcess(src, {
+      extensions,
+    });
+  } catch (e) {
+    // Absorb this if it's MODULE_NOT_FOUND
+    if (e.code !== 'MODULE_NOT_FOUND') {
+      throw e;
+    }
+
+    // Last place to try (pre v5.3)
+    const {
+      listFilesToProcess: originalListFilesToProcess,
+    } = require('eslint/lib/util/glob-util');
+    const patterns = src.concat(
+      flatMap(
+        src,
+        (pattern) => extensions.map((extension) => (/\*\*|\*\./).test(pattern) ? pattern : `${pattern}/**/*${extension}`),
+      ),
+    );
+
+    return originalListFilesToProcess(patterns);
+  }
 }
 
+/**
+ * Given a src pattern and list of supported extensions, return a list of files to process
+ * with this rule.
+ * @param {string} src - file, directory, or glob pattern of files to act on
+ * @param {string[]} extensions - list of supported file extensions
+ * @returns {string[] | { filename: string, ignored: boolean }[]} the list of files that this rule will evaluate.
+ */
+function listFilesToProcess(src, extensions) {
+  const FileEnumerator = requireFileEnumerator();
+
+  // If we got the FileEnumerator, then let's go with that
+  if (FileEnumerator) {
+    return listFilesUsingFileEnumerator(FileEnumerator, src, extensions);
+  }
+  // If not, then we can try even older versions of this capability (listFilesToProcess)
+  return listFilesWithLegacyFunctions(src, extensions);
+}
+
+const EXPORT_DEFAULT_DECLARATION = 'ExportDefaultDeclaration';
+const EXPORT_NAMED_DECLARATION = 'ExportNamedDeclaration';
+const EXPORT_ALL_DECLARATION = 'ExportAllDeclaration';
+const IMPORT_DECLARATION = 'ImportDeclaration';
+const IMPORT_NAMESPACE_SPECIFIER = 'ImportNamespaceSpecifier';
+const IMPORT_DEFAULT_SPECIFIER = 'ImportDefaultSpecifier';
+const VARIABLE_DECLARATION = 'VariableDeclaration';
+const FUNCTION_DECLARATION = 'FunctionDeclaration';
+const CLASS_DECLARATION = 'ClassDeclaration';
+const IDENTIFIER = 'Identifier';
+const OBJECT_PATTERN = 'ObjectPattern';
+const ARRAY_PATTERN = 'ArrayPattern';
+const TS_INTERFACE_DECLARATION = 'TSInterfaceDeclaration';
+const TS_TYPE_ALIAS_DECLARATION = 'TSTypeAliasDeclaration';
+const TS_ENUM_DECLARATION = 'TSEnumDeclaration';
+const DEFAULT = 'default';
+
+function forEachDeclarationIdentifier(declaration, cb) {
+  if (declaration) {
+    const isTypeDeclaration = declaration.type === TS_INTERFACE_DECLARATION
+      || declaration.type === TS_TYPE_ALIAS_DECLARATION
+      || declaration.type === TS_ENUM_DECLARATION;
+
+    if (
+      declaration.type === FUNCTION_DECLARATION
+      || declaration.type === CLASS_DECLARATION
+      || isTypeDeclaration
+    ) {
+      cb(declaration.id.name, isTypeDeclaration);
+    } else if (declaration.type === VARIABLE_DECLARATION) {
+      declaration.declarations.forEach(({ id }) => {
+        if (id.type === OBJECT_PATTERN) {
+          recursivePatternCapture(id, (pattern) => {
+            if (pattern.type === IDENTIFIER) {
+              cb(pattern.name, false);
+            }
+          });
+        } else if (id.type === ARRAY_PATTERN) {
+          id.elements.forEach(({ name }) => {
+            cb(name, false);
+          });
+        } else {
+          cb(id.name, false);
+        }
+      });
+    }
+  }
+}
+
+/**
+ * List of imports per file.
+ *
+ * Represented by a two-level Map to a Set of identifiers. The upper-level Map
+ * keys are the paths to the modules containing the imports, while the
+ * lower-level Map keys are the paths to the files which are being imported
+ * from. Lastly, the Set of identifiers contains either names being imported
+ * or a special AST node name listed above (e.g ImportDefaultSpecifier).
+ *
+ * For example, if we have a file named foo.js containing:
+ *
+ *   import { o2 } from './bar.js';
+ *
+ * Then we will have a structure that looks like:
+ *
+ *   Map { 'foo.js' => Map { 'bar.js' => Set { 'o2' } } }
+ *
+ * @type {Map<string, Map<string, Set<string>>>}
+ */
+const importList = new Map();
+
+/**
+ * List of exports per file.
+ *
+ * Represented by a two-level Map to an object of metadata. The upper-level Map
+ * keys are the paths to the modules containing the exports, while the
+ * lower-level Map keys are the specific identifiers or special AST node names
+ * being exported. The leaf-level metadata object at the moment only contains a
+ * `whereUsed` property, which contains a Set of paths to modules that import
+ * the name.
+ *
+ * For example, if we have a file named bar.js containing the following exports:
+ *
+ *   const o2 = 'bar';
+ *   export { o2 };
+ *
+ * And a file named foo.js containing the following import:
+ *
+ *   import { o2 } from './bar.js';
+ *
+ * Then we will have a structure that looks like:
+ *
+ *   Map { 'bar.js' => Map { 'o2' => { whereUsed: Set { 'foo.js' } } } }
+ *
+ * @type {Map<string, Map<string, object>>}
+ */
+const exportList = new Map();
+
+const visitorKeyMap = new Map();
+
+/** @type {Set<string>} */
+const ignoredFiles = new Set();
+const filesOutsideSrc = new Set();
+
+const isNodeModule = (path) => (/\/(node_modules)\//).test(path);
+
 /**
  * read all files matching the patterns in src and ignoreExports
  *
  * return all files matching src pattern, which are not matching the ignoreExports pattern
+ * @type {(src: string, ignoreExports: string, context: import('eslint').Rule.RuleContext) => Set<string>}
  */
-const resolveFiles = (src, ignoreExports) => {
-  const srcFiles = new Set()
-  const srcFileList = listFilesToProcess(src)
+function resolveFiles(src, ignoreExports, context) {
+  const extensions = Array.from(getFileExtensions(context.settings));
+
+  const srcFileList = listFilesToProcess(src, extensions);
 
   // prepare list of ignored files
-  const ignoredFilesList =  listFilesToProcess(ignoreExports)
-  ignoredFilesList.forEach(({ filename }) => ignoredFiles.add(filename))
+  const ignoredFilesList = listFilesToProcess(ignoreExports, extensions);
+
+  // The modern api will return a list of file paths, rather than an object
+  if (ignoredFilesList.length && typeof ignoredFilesList[0] === 'string') {
+    ignoredFilesList.forEach((filename) => ignoredFiles.add(filename));
+  } else {
+    ignoredFilesList.forEach(({ filename }) => ignoredFiles.add(filename));
+  }
 
   // prepare list of source files, don't consider files from node_modules
-  srcFileList.filter(({ filename }) => !isNodeModule(filename)).forEach(({ filename }) => {
-    srcFiles.add(filename)
-  })
-  return srcFiles
+  const resolvedFiles = srcFileList.length && typeof srcFileList[0] === 'string'
+    ? srcFileList.filter((filePath) => !isNodeModule(filePath))
+    : flatMap(srcFileList, ({ filename }) => isNodeModule(filename) ? [] : filename);
+
+  return new Set(resolvedFiles);
 }
 
 /**
  * parse all source files and build up 2 maps containing the existing imports and exports
  */
 const prepareImportsAndExports = (srcFiles, context) => {
-  const exportAll = new Map()
-  srcFiles.forEach(file => {
-    const exports = new Map()
-    const imports = new Map()
-    const currentExports = Exports.get(file, context)
+  const exportAll = new Map();
+  srcFiles.forEach((file) => {
+    const exports = new Map();
+    const imports = new Map();
+    const currentExports = ExportMapBuilder.get(file, context);
     if (currentExports) {
-      const { dependencies, reexports, imports: localImportList, namespace  } = currentExports
-
+      const {
+        dependencies,
+        reexports,
+        imports: localImportList,
+        namespace,
+        visitorKeys,
+      } = currentExports;
+
+      visitorKeyMap.set(file, visitorKeys);
       // dependencies === export * from
-      const currentExportAll = new Set()
-      dependencies.forEach(value => {
-        currentExportAll.add(value().path)
-      })
-      exportAll.set(file, currentExportAll)
+      const currentExportAll = new Set();
+      dependencies.forEach((getDependency) => {
+        const dependency = getDependency();
+        if (dependency === null) {
+          return;
+        }
+
+        currentExportAll.add(dependency.path);
+      });
+      exportAll.set(file, currentExportAll);
 
       reexports.forEach((value, key) => {
         if (key === DEFAULT) {
-          exports.set(IMPORT_DEFAULT_SPECIFIER, { whereUsed: new Set() })
+          exports.set(IMPORT_DEFAULT_SPECIFIER, { whereUsed: new Set() });
         } else {
-          exports.set(key, { whereUsed: new Set() })
+          exports.set(key, { whereUsed: new Set() });
         }
-        const reexport =  value.getImport()
+        const reexport = value.getImport();
         if (!reexport) {
-          return
+          return;
         }
-        let localImport = imports.get(reexport.path)
-        let currentValue
+        let localImport = imports.get(reexport.path);
+        let currentValue;
         if (value.local === DEFAULT) {
-          currentValue = IMPORT_DEFAULT_SPECIFIER
+          currentValue = IMPORT_DEFAULT_SPECIFIER;
         } else {
-          currentValue = value.local
+          currentValue = value.local;
         }
         if (typeof localImport !== 'undefined') {
-          localImport = new Set([...localImport, currentValue])
+          localImport = new Set([...localImport, currentValue]);
         } else {
-          localImport = new Set([currentValue])
+          localImport = new Set([currentValue]);
         }
-        imports.set(reexport.path, localImport)
-      })
+        imports.set(reexport.path, localImport);
+      });
 
       localImportList.forEach((value, key) => {
         if (isNodeModule(key)) {
-          return
+          return;
         }
-        imports.set(key, value.importedSpecifiers)
-      })
-      importList.set(file, imports)
+        const localImport = imports.get(key) || new Set();
+        value.declarations.forEach(({ importedSpecifiers }) => {
+          importedSpecifiers.forEach((specifier) => {
+            localImport.add(specifier);
+          });
+        });
+        imports.set(key, localImport);
+      });
+      importList.set(file, imports);
 
       // build up export list only, if file is not ignored
       if (ignoredFiles.has(file)) {
-        return
+        return;
       }
       namespace.forEach((value, key) => {
         if (key === DEFAULT) {
-          exports.set(IMPORT_DEFAULT_SPECIFIER, { whereUsed: new Set() })
+          exports.set(IMPORT_DEFAULT_SPECIFIER, { whereUsed: new Set() });
         } else {
-          exports.set(key, { whereUsed: new Set() })
+          exports.set(key, { whereUsed: new Set() });
         }
-      })
+      });
     }
-    exports.set(EXPORT_ALL_DECLARATION, { whereUsed: new Set() })
-    exports.set(IMPORT_NAMESPACE_SPECIFIER, { whereUsed: new Set() })
-    exportList.set(file, exports)
-  })
+    exports.set(EXPORT_ALL_DECLARATION, { whereUsed: new Set() });
+    exports.set(IMPORT_NAMESPACE_SPECIFIER, { whereUsed: new Set() });
+    exportList.set(file, exports);
+  });
   exportAll.forEach((value, key) => {
-    value.forEach(val => {
-      const currentExports = exportList.get(val)
-      const currentExport = currentExports.get(EXPORT_ALL_DECLARATION)
-      currentExport.whereUsed.add(key)
-    })
-  })
-}
+    value.forEach((val) => {
+      const currentExports = exportList.get(val);
+      if (currentExports) {
+        const currentExport = currentExports.get(EXPORT_ALL_DECLARATION);
+        currentExport.whereUsed.add(key);
+      }
+    });
+  });
+};
 
 /**
  * traverse through all imports and add the respective path to the whereUsed-list
@@ -156,126 +403,146 @@ const prepareImportsAndExports = (srcFiles, context) => {
 const determineUsage = () => {
   importList.forEach((listValue, listKey) => {
     listValue.forEach((value, key) => {
-      const exports = exportList.get(key)
+      const exports = exportList.get(key);
       if (typeof exports !== 'undefined') {
-        value.forEach(currentImport => {
-          let specifier
+        value.forEach((currentImport) => {
+          let specifier;
           if (currentImport === IMPORT_NAMESPACE_SPECIFIER) {
-            specifier = IMPORT_NAMESPACE_SPECIFIER
+            specifier = IMPORT_NAMESPACE_SPECIFIER;
           } else if (currentImport === IMPORT_DEFAULT_SPECIFIER) {
-            specifier = IMPORT_DEFAULT_SPECIFIER
+            specifier = IMPORT_DEFAULT_SPECIFIER;
           } else {
-            specifier = currentImport
+            specifier = currentImport;
           }
           if (typeof specifier !== 'undefined') {
-            const exportStatement = exports.get(specifier)
+            const exportStatement = exports.get(specifier);
             if (typeof exportStatement !== 'undefined') {
-              const { whereUsed } = exportStatement
-              whereUsed.add(listKey)
-              exports.set(specifier, { whereUsed })
+              const { whereUsed } = exportStatement;
+              whereUsed.add(listKey);
+              exports.set(specifier, { whereUsed });
             }
           }
-        })
+        });
       }
-    })
-  })
-}
+    });
+  });
+};
 
-const getSrc = src => {
+const getSrc = (src) => {
   if (src) {
-    return src
+    return src;
   }
-  return [process.cwd()]
-}
+  return [process.cwd()];
+};
 
 /**
  * prepare the lists of existing imports and exports - should only be executed once at
  * the start of a new eslint run
  */
+/** @type {Set<string>} */
+let srcFiles;
+let lastPrepareKey;
 const doPreparation = (src, ignoreExports, context) => {
-  const srcFiles = resolveFiles(getSrc(src), ignoreExports)
-  prepareImportsAndExports(srcFiles, context)
-  determineUsage()
-  preparationDone = true
-}
+  const prepareKey = JSON.stringify({
+    src: (src || []).sort(),
+    ignoreExports: (ignoreExports || []).sort(),
+    extensions: Array.from(getFileExtensions(context.settings)).sort(),
+  });
+  if (prepareKey === lastPrepareKey) {
+    return;
+  }
+
+  importList.clear();
+  exportList.clear();
+  ignoredFiles.clear();
+  filesOutsideSrc.clear();
+
+  srcFiles = resolveFiles(getSrc(src), ignoreExports, context);
+  prepareImportsAndExports(srcFiles, context);
+  determineUsage();
+  lastPrepareKey = prepareKey;
+};
 
-const newNamespaceImportExists = specifiers =>
-  specifiers.some(({ type }) => type === IMPORT_NAMESPACE_SPECIFIER)
+const newNamespaceImportExists = (specifiers) => specifiers.some(({ type }) => type === IMPORT_NAMESPACE_SPECIFIER);
 
-const newDefaultImportExists = specifiers =>
-  specifiers.some(({ type }) => type === IMPORT_DEFAULT_SPECIFIER)
+const newDefaultImportExists = (specifiers) => specifiers.some(({ type }) => type === IMPORT_DEFAULT_SPECIFIER);
 
-const fileIsInPkg = file => {
-  const { path, pkg } = readPkgUp.sync({cwd: file, normalize: false})
-  const basePath = dirname(path)
+const fileIsInPkg = (file) => {
+  const { path, pkg } = readPkgUp({ cwd: file });
+  const basePath = dirname(path);
 
-  const checkPkgFieldString = pkgField => {
+  const checkPkgFieldString = (pkgField) => {
     if (join(basePath, pkgField) === file) {
-        return true
-      }
-  }
+      return true;
+    }
+  };
 
-  const checkPkgFieldObject = pkgField => {
-      const pkgFieldFiles = values(pkgField).map(value => join(basePath, value))
-      if (includes(pkgFieldFiles, file)) {
-        return true
-      }
-  }
+  const checkPkgFieldObject = (pkgField) => {
+    const pkgFieldFiles = flatMap(values(pkgField), (value) => typeof value === 'boolean' ? [] : join(basePath, value));
+
+    if (includes(pkgFieldFiles, file)) {
+      return true;
+    }
+  };
 
-  const checkPkgField = pkgField => {
+  const checkPkgField = (pkgField) => {
     if (typeof pkgField === 'string') {
-      return checkPkgFieldString(pkgField)
+      return checkPkgFieldString(pkgField);
     }
 
     if (typeof pkgField === 'object') {
-      return checkPkgFieldObject(pkgField)
+      return checkPkgFieldObject(pkgField);
     }
-  }
+  };
 
   if (pkg.private === true) {
-    return false
+    return false;
   }
 
   if (pkg.bin) {
     if (checkPkgField(pkg.bin)) {
-      return true
+      return true;
     }
   }
 
   if (pkg.browser) {
     if (checkPkgField(pkg.browser)) {
-      return true
+      return true;
     }
   }
 
   if (pkg.main) {
     if (checkPkgFieldString(pkg.main)) {
-      return true
+      return true;
     }
   }
 
-  return false
-}
+  return false;
+};
 
 module.exports = {
   meta: {
-    docs: { url: docsUrl('no-unused-modules') },
+    type: 'suggestion',
+    docs: {
+      category: 'Helpful warnings',
+      description: 'Forbid modules without exports, or exports without matching import in another module.',
+      url: docsUrl('no-unused-modules'),
+    },
     schema: [{
       properties: {
         src: {
           description: 'files/paths to be analyzed (only for unused exports)',
           type: 'array',
-          minItems: 1,
+          uniqueItems: true,
           items: {
             type: 'string',
             minLength: 1,
           },
         },
         ignoreExports: {
-          description:
-            'files/paths for which unused exports will not be reported (e.g module entry points)',
+          description: 'files/paths for which unused exports will not be reported (e.g module entry points)',
           type: 'array',
-          minItems: 1,
+          uniqueItems: true,
           items: {
             type: 'string',
             minLength: 1,
@@ -289,509 +556,517 @@ module.exports = {
           description: 'report exports without any usage',
           type: 'boolean',
         },
-      },
-      not: {
-        properties: {
-          unusedExports: { enum: [false] },
-          missingExports: { enum: [false] },
+        ignoreUnusedTypeExports: {
+          description: 'ignore type exports without any usage',
+          type: 'boolean',
         },
       },
-      anyOf:[{
-        not: {
+      anyOf: [
+        {
           properties: {
             unusedExports: { enum: [true] },
+            src: {
+              minItems: 1,
+            },
           },
+          required: ['unusedExports'],
         },
-        required: ['missingExports'],
-      }, {
-        not: {
+        {
           properties: {
             missingExports: { enum: [true] },
           },
+          required: ['missingExports'],
         },
-        required: ['unusedExports'],
-      }, {
-        properties: {
-          unusedExports: { enum: [true] },
-        },
-        required: ['unusedExports'],
-      }, {
-        properties: {
-          missingExports: { enum: [true] },
-        },
-        required: ['missingExports'],
-      }],
+      ],
     }],
   },
 
-  create: context => {
+  create(context) {
     const {
       src,
       ignoreExports = [],
       missingExports,
       unusedExports,
-    } = context.options[0] || {}
+      ignoreUnusedTypeExports,
+    } = context.options[0] || {};
 
-    if (unusedExports && !preparationDone) {
-      doPreparation(src, ignoreExports, context)
+    if (unusedExports) {
+      doPreparation(src, ignoreExports, context);
     }
 
-    const file = context.getFilename()
+    const file = getPhysicalFilename(context);
 
-    const checkExportPresence = node => {
+    const checkExportPresence = (node) => {
       if (!missingExports) {
-        return
+        return;
       }
 
       if (ignoredFiles.has(file)) {
-        return
+        return;
       }
 
-      const exportCount = exportList.get(file)
-      const exportAll = exportCount.get(EXPORT_ALL_DECLARATION)
-      const namespaceImports = exportCount.get(IMPORT_NAMESPACE_SPECIFIER)
+      const exportCount = exportList.get(file);
+      const exportAll = exportCount.get(EXPORT_ALL_DECLARATION);
+      const namespaceImports = exportCount.get(IMPORT_NAMESPACE_SPECIFIER);
 
-      exportCount.delete(EXPORT_ALL_DECLARATION)
-      exportCount.delete(IMPORT_NAMESPACE_SPECIFIER)
-      if (missingExports && exportCount.size < 1) {
+      exportCount.delete(EXPORT_ALL_DECLARATION);
+      exportCount.delete(IMPORT_NAMESPACE_SPECIFIER);
+      if (exportCount.size < 1) {
         // node.body[0] === 'undefined' only happens, if everything is commented out in the file
         // being linted
-        context.report(node.body[0] ? node.body[0] : node, 'No exports found')
+        context.report(node.body[0] ? node.body[0] : node, 'No exports found');
       }
-      exportCount.set(EXPORT_ALL_DECLARATION, exportAll)
-      exportCount.set(IMPORT_NAMESPACE_SPECIFIER, namespaceImports)
-    }
+      exportCount.set(EXPORT_ALL_DECLARATION, exportAll);
+      exportCount.set(IMPORT_NAMESPACE_SPECIFIER, namespaceImports);
+    };
 
-    const checkUsage = (node, exportedValue) => {
+    const checkUsage = (node, exportedValue, isTypeExport) => {
       if (!unusedExports) {
-        return
+        return;
+      }
+
+      if (isTypeExport && ignoreUnusedTypeExports) {
+        return;
       }
 
       if (ignoredFiles.has(file)) {
-        return
+        return;
       }
 
       if (fileIsInPkg(file)) {
-        return
+        return;
       }
 
-      // refresh list of source files
-      const srcFiles = resolveFiles(getSrc(src), ignoreExports)
+      if (filesOutsideSrc.has(file)) {
+        return;
+      }
 
       // make sure file to be linted is included in source files
       if (!srcFiles.has(file)) {
-        return
+        srcFiles = resolveFiles(getSrc(src), ignoreExports, context);
+        if (!srcFiles.has(file)) {
+          filesOutsideSrc.add(file);
+          return;
+        }
       }
 
-      exports = exportList.get(file)
+      exports = exportList.get(file);
+
+      if (!exports) {
+        console.error(`file \`${file}\` has no exports. Please update to the latest, and if it still happens, report this on https://github.com/import-js/eslint-plugin-import/issues/2866!`);
+      }
 
       // special case: export * from
-      const exportAll = exports.get(EXPORT_ALL_DECLARATION)
+      const exportAll = exports.get(EXPORT_ALL_DECLARATION);
       if (typeof exportAll !== 'undefined' && exportedValue !== IMPORT_DEFAULT_SPECIFIER) {
         if (exportAll.whereUsed.size > 0) {
-          return
+          return;
         }
       }
 
       // special case: namespace import
-      const namespaceImports = exports.get(IMPORT_NAMESPACE_SPECIFIER)
+      const namespaceImports = exports.get(IMPORT_NAMESPACE_SPECIFIER);
       if (typeof namespaceImports !== 'undefined') {
         if (namespaceImports.whereUsed.size > 0) {
-          return
+          return;
         }
       }
 
-      const exportStatement = exports.get(exportedValue)
+      // exportsList will always map any imported value of 'default' to 'ImportDefaultSpecifier'
+      const exportsKey = exportedValue === DEFAULT ? IMPORT_DEFAULT_SPECIFIER : exportedValue;
+
+      const exportStatement = exports.get(exportsKey);
 
-      const value = exportedValue === IMPORT_DEFAULT_SPECIFIER ? DEFAULT : exportedValue
+      const value = exportsKey === IMPORT_DEFAULT_SPECIFIER ? DEFAULT : exportsKey;
 
-      if (typeof exportStatement !== 'undefined'){
+      if (typeof exportStatement !== 'undefined') {
         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`,
+        );
       }
-    }
+    };
 
     /**
      * only useful for tools like vscode-eslint
      *
      * update lists of existing exports during runtime
      */
-    const updateExportUsage = node => {
+    const updateExportUsage = (node) => {
       if (ignoredFiles.has(file)) {
-        return
+        return;
       }
 
-      let exports = exportList.get(file)
+      let exports = exportList.get(file);
 
       // new module has been created during runtime
       // include it in further processing
       if (typeof exports === 'undefined') {
-        exports = new Map()
+        exports = new Map();
       }
 
-      const newExports = new Map()
-      const newExportIdentifiers = new Set()
+      const newExports = new Map();
+      const newExportIdentifiers = new Set();
 
       node.body.forEach(({ type, declaration, specifiers }) => {
         if (type === EXPORT_DEFAULT_DECLARATION) {
-          newExportIdentifiers.add(IMPORT_DEFAULT_SPECIFIER)
+          newExportIdentifiers.add(IMPORT_DEFAULT_SPECIFIER);
         }
         if (type === EXPORT_NAMED_DECLARATION) {
           if (specifiers.length > 0) {
-            specifiers.forEach(specifier => {
+            specifiers.forEach((specifier) => {
               if (specifier.exported) {
-                newExportIdentifiers.add(specifier.exported.name)
+                newExportIdentifiers.add(specifier.exported.name || specifier.exported.value);
               }
-            })
-          }
-          if (declaration) {
-            if (
-              declaration.type === FUNCTION_DECLARATION ||
-              declaration.type === CLASS_DECLARATION
-            ) {
-              newExportIdentifiers.add(declaration.id.name)
-            }
-            if (declaration.type === VARIABLE_DECLARATION) {
-              declaration.declarations.forEach(({ id }) => {
-                newExportIdentifiers.add(id.name)
-              })
-            }
+            });
           }
+          forEachDeclarationIdentifier(declaration, (name) => {
+            newExportIdentifiers.add(name);
+          });
         }
-      })
+      });
 
       // old exports exist within list of new exports identifiers: add to map of new exports
       exports.forEach((value, key) => {
         if (newExportIdentifiers.has(key)) {
-          newExports.set(key, value)
+          newExports.set(key, value);
         }
-      })
+      });
 
       // new export identifiers added: add to map of new exports
-      newExportIdentifiers.forEach(key => {
+      newExportIdentifiers.forEach((key) => {
         if (!exports.has(key)) {
-          newExports.set(key, { whereUsed: new Set() })
+          newExports.set(key, { whereUsed: new Set() });
         }
-      })
+      });
 
       // preserve information about namespace imports
-      let exportAll = exports.get(EXPORT_ALL_DECLARATION)
-      let namespaceImports = exports.get(IMPORT_NAMESPACE_SPECIFIER)
+      const exportAll = exports.get(EXPORT_ALL_DECLARATION);
+      let namespaceImports = exports.get(IMPORT_NAMESPACE_SPECIFIER);
 
       if (typeof namespaceImports === 'undefined') {
-        namespaceImports = { whereUsed: new Set() }
+        namespaceImports = { whereUsed: new Set() };
       }
 
-      newExports.set(EXPORT_ALL_DECLARATION, exportAll)
-      newExports.set(IMPORT_NAMESPACE_SPECIFIER, namespaceImports)
-      exportList.set(file, newExports)
-    }
+      newExports.set(EXPORT_ALL_DECLARATION, exportAll);
+      newExports.set(IMPORT_NAMESPACE_SPECIFIER, namespaceImports);
+      exportList.set(file, newExports);
+    };
 
     /**
      * only useful for tools like vscode-eslint
      *
      * update lists of existing imports during runtime
      */
-    const updateImportUsage = node => {
+    const updateImportUsage = (node) => {
       if (!unusedExports) {
-        return
+        return;
       }
 
-      let oldImportPaths = importList.get(file)
+      let oldImportPaths = importList.get(file);
       if (typeof oldImportPaths === 'undefined') {
-        oldImportPaths = new Map()
+        oldImportPaths = new Map();
       }
 
-      const oldNamespaceImports = new Set()
-      const newNamespaceImports = new Set()
+      const oldNamespaceImports = new Set();
+      const newNamespaceImports = new Set();
 
-      const oldExportAll = new Set()
-      const newExportAll = new Set()
+      const oldExportAll = new Set();
+      const newExportAll = new Set();
 
-      const oldDefaultImports = new Set()
-      const newDefaultImports = new Set()
+      const oldDefaultImports = new Set();
+      const newDefaultImports = new Set();
 
-      const oldImports = new Map()
-      const newImports = new Map()
+      const oldImports = new Map();
+      const newImports = new Map();
       oldImportPaths.forEach((value, key) => {
         if (value.has(EXPORT_ALL_DECLARATION)) {
-          oldExportAll.add(key)
+          oldExportAll.add(key);
         }
         if (value.has(IMPORT_NAMESPACE_SPECIFIER)) {
-          oldNamespaceImports.add(key)
+          oldNamespaceImports.add(key);
         }
         if (value.has(IMPORT_DEFAULT_SPECIFIER)) {
-          oldDefaultImports.add(key)
+          oldDefaultImports.add(key);
         }
-        value.forEach(val => {
-          if (val !== IMPORT_NAMESPACE_SPECIFIER &&
-              val !== IMPORT_DEFAULT_SPECIFIER) {
-               oldImports.set(val, key)
-             }
-        })
-      })
+        value.forEach((val) => {
+          if (
+            val !== IMPORT_NAMESPACE_SPECIFIER
+            && val !== IMPORT_DEFAULT_SPECIFIER
+          ) {
+            oldImports.set(val, key);
+          }
+        });
+      });
 
-      node.body.forEach(astNode => {
-        let resolvedPath
+      function processDynamicImport(source) {
+        if (source.type !== 'Literal') {
+          return null;
+        }
+        const p = resolve(source.value, context);
+        if (p == null) {
+          return null;
+        }
+        newNamespaceImports.add(p);
+      }
+
+      visit(node, visitorKeyMap.get(file), {
+        ImportExpression(child) {
+          processDynamicImport(child.source);
+        },
+        CallExpression(child) {
+          if (child.callee.type === 'Import') {
+            processDynamicImport(child.arguments[0]);
+          }
+        },
+      });
+
+      node.body.forEach((astNode) => {
+        let resolvedPath;
 
         // support for export { value } from 'module'
         if (astNode.type === EXPORT_NAMED_DECLARATION) {
           if (astNode.source) {
-            resolvedPath = resolve(astNode.source.raw.replace(/('|")/g, ''), context)
-            astNode.specifiers.forEach(specifier => {
-              let name
-              if (specifier.exported.name === DEFAULT) {
-                name = IMPORT_DEFAULT_SPECIFIER
+            resolvedPath = resolve(astNode.source.raw.replace(/('|")/g, ''), context);
+            astNode.specifiers.forEach((specifier) => {
+              const name = specifier.local.name || specifier.local.value;
+              if (name === DEFAULT) {
+                newDefaultImports.add(resolvedPath);
               } else {
-                name = specifier.local.name
+                newImports.set(name, resolvedPath);
               }
-              newImports.set(name, resolvedPath)
-            })
+            });
           }
         }
 
         if (astNode.type === EXPORT_ALL_DECLARATION) {
-          resolvedPath = resolve(astNode.source.raw.replace(/('|")/g, ''), context)
-          newExportAll.add(resolvedPath)
+          resolvedPath = resolve(astNode.source.raw.replace(/('|")/g, ''), context);
+          newExportAll.add(resolvedPath);
         }
 
         if (astNode.type === IMPORT_DECLARATION) {
-          resolvedPath = resolve(astNode.source.raw.replace(/('|")/g, ''), context)
+          resolvedPath = resolve(astNode.source.raw.replace(/('|")/g, ''), context);
           if (!resolvedPath) {
-            return
+            return;
           }
 
           if (isNodeModule(resolvedPath)) {
-            return
+            return;
           }
 
           if (newNamespaceImportExists(astNode.specifiers)) {
-            newNamespaceImports.add(resolvedPath)
+            newNamespaceImports.add(resolvedPath);
           }
 
           if (newDefaultImportExists(astNode.specifiers)) {
-            newDefaultImports.add(resolvedPath)
+            newDefaultImports.add(resolvedPath);
           }
 
-          astNode.specifiers.forEach(specifier => {
-            if (specifier.type === IMPORT_DEFAULT_SPECIFIER ||
-                specifier.type === IMPORT_NAMESPACE_SPECIFIER) {
-              return
-            }
-            newImports.set(specifier.imported.name, resolvedPath)
-          })
+          astNode.specifiers
+            .filter((specifier) => specifier.type !== IMPORT_DEFAULT_SPECIFIER && specifier.type !== IMPORT_NAMESPACE_SPECIFIER)
+            .forEach((specifier) => {
+              newImports.set(specifier.imported.name || specifier.imported.value, resolvedPath);
+            });
         }
-      })
+      });
 
-      newExportAll.forEach(value => {
+      newExportAll.forEach((value) => {
         if (!oldExportAll.has(value)) {
-          let imports = oldImportPaths.get(value)
+          let imports = oldImportPaths.get(value);
           if (typeof imports === 'undefined') {
-            imports = new Set()
+            imports = new Set();
           }
-          imports.add(EXPORT_ALL_DECLARATION)
-          oldImportPaths.set(value, imports)
+          imports.add(EXPORT_ALL_DECLARATION);
+          oldImportPaths.set(value, imports);
 
-          let exports = exportList.get(value)
-          let currentExport
+          let exports = exportList.get(value);
+          let currentExport;
           if (typeof exports !== 'undefined') {
-            currentExport = exports.get(EXPORT_ALL_DECLARATION)
+            currentExport = exports.get(EXPORT_ALL_DECLARATION);
           } else {
-            exports = new Map()
-            exportList.set(value, exports)
+            exports = new Map();
+            exportList.set(value, exports);
           }
 
           if (typeof currentExport !== 'undefined') {
-            currentExport.whereUsed.add(file)
+            currentExport.whereUsed.add(file);
           } else {
-            const whereUsed = new Set()
-            whereUsed.add(file)
-            exports.set(EXPORT_ALL_DECLARATION, { whereUsed })
+            const whereUsed = new Set();
+            whereUsed.add(file);
+            exports.set(EXPORT_ALL_DECLARATION, { whereUsed });
           }
         }
-      })
+      });
 
-      oldExportAll.forEach(value => {
+      oldExportAll.forEach((value) => {
         if (!newExportAll.has(value)) {
-          const imports = oldImportPaths.get(value)
-          imports.delete(EXPORT_ALL_DECLARATION)
+          const imports = oldImportPaths.get(value);
+          imports.delete(EXPORT_ALL_DECLARATION);
 
-          const exports = exportList.get(value)
+          const exports = exportList.get(value);
           if (typeof exports !== 'undefined') {
-            const currentExport = exports.get(EXPORT_ALL_DECLARATION)
+            const currentExport = exports.get(EXPORT_ALL_DECLARATION);
             if (typeof currentExport !== 'undefined') {
-              currentExport.whereUsed.delete(file)
+              currentExport.whereUsed.delete(file);
             }
           }
         }
-      })
+      });
 
-      newDefaultImports.forEach(value => {
+      newDefaultImports.forEach((value) => {
         if (!oldDefaultImports.has(value)) {
-          let imports = oldImportPaths.get(value)
+          let imports = oldImportPaths.get(value);
           if (typeof imports === 'undefined') {
-            imports = new Set()
+            imports = new Set();
           }
-          imports.add(IMPORT_DEFAULT_SPECIFIER)
-          oldImportPaths.set(value, imports)
+          imports.add(IMPORT_DEFAULT_SPECIFIER);
+          oldImportPaths.set(value, imports);
 
-          let exports = exportList.get(value)
-          let currentExport
+          let exports = exportList.get(value);
+          let currentExport;
           if (typeof exports !== 'undefined') {
-            currentExport = exports.get(IMPORT_DEFAULT_SPECIFIER)
+            currentExport = exports.get(IMPORT_DEFAULT_SPECIFIER);
           } else {
-            exports = new Map()
-            exportList.set(value, exports)
+            exports = new Map();
+            exportList.set(value, exports);
           }
 
           if (typeof currentExport !== 'undefined') {
-            currentExport.whereUsed.add(file)
+            currentExport.whereUsed.add(file);
           } else {
-            const whereUsed = new Set()
-            whereUsed.add(file)
-            exports.set(IMPORT_DEFAULT_SPECIFIER, { whereUsed })
+            const whereUsed = new Set();
+            whereUsed.add(file);
+            exports.set(IMPORT_DEFAULT_SPECIFIER, { whereUsed });
           }
         }
-      })
+      });
 
-      oldDefaultImports.forEach(value => {
+      oldDefaultImports.forEach((value) => {
         if (!newDefaultImports.has(value)) {
-          const imports = oldImportPaths.get(value)
-          imports.delete(IMPORT_DEFAULT_SPECIFIER)
+          const imports = oldImportPaths.get(value);
+          imports.delete(IMPORT_DEFAULT_SPECIFIER);
 
-          const exports = exportList.get(value)
+          const exports = exportList.get(value);
           if (typeof exports !== 'undefined') {
-            const currentExport = exports.get(IMPORT_DEFAULT_SPECIFIER)
+            const currentExport = exports.get(IMPORT_DEFAULT_SPECIFIER);
             if (typeof currentExport !== 'undefined') {
-              currentExport.whereUsed.delete(file)
+              currentExport.whereUsed.delete(file);
             }
           }
         }
-      })
+      });
 
-      newNamespaceImports.forEach(value => {
+      newNamespaceImports.forEach((value) => {
         if (!oldNamespaceImports.has(value)) {
-          let imports = oldImportPaths.get(value)
+          let imports = oldImportPaths.get(value);
           if (typeof imports === 'undefined') {
-            imports = new Set()
+            imports = new Set();
           }
-          imports.add(IMPORT_NAMESPACE_SPECIFIER)
-          oldImportPaths.set(value, imports)
+          imports.add(IMPORT_NAMESPACE_SPECIFIER);
+          oldImportPaths.set(value, imports);
 
-          let exports = exportList.get(value)
-          let currentExport
+          let exports = exportList.get(value);
+          let currentExport;
           if (typeof exports !== 'undefined') {
-            currentExport = exports.get(IMPORT_NAMESPACE_SPECIFIER)
+            currentExport = exports.get(IMPORT_NAMESPACE_SPECIFIER);
           } else {
-            exports = new Map()
-            exportList.set(value, exports)
+            exports = new Map();
+            exportList.set(value, exports);
           }
 
           if (typeof currentExport !== 'undefined') {
-            currentExport.whereUsed.add(file)
+            currentExport.whereUsed.add(file);
           } else {
-            const whereUsed = new Set()
-            whereUsed.add(file)
-            exports.set(IMPORT_NAMESPACE_SPECIFIER, { whereUsed })
+            const whereUsed = new Set();
+            whereUsed.add(file);
+            exports.set(IMPORT_NAMESPACE_SPECIFIER, { whereUsed });
           }
         }
-      })
+      });
 
-      oldNamespaceImports.forEach(value => {
+      oldNamespaceImports.forEach((value) => {
         if (!newNamespaceImports.has(value)) {
-          const imports = oldImportPaths.get(value)
-          imports.delete(IMPORT_NAMESPACE_SPECIFIER)
+          const imports = oldImportPaths.get(value);
+          imports.delete(IMPORT_NAMESPACE_SPECIFIER);
 
-          const exports = exportList.get(value)
+          const exports = exportList.get(value);
           if (typeof exports !== 'undefined') {
-            const currentExport = exports.get(IMPORT_NAMESPACE_SPECIFIER)
+            const currentExport = exports.get(IMPORT_NAMESPACE_SPECIFIER);
             if (typeof currentExport !== 'undefined') {
-              currentExport.whereUsed.delete(file)
+              currentExport.whereUsed.delete(file);
             }
           }
         }
-      })
+      });
 
       newImports.forEach((value, key) => {
         if (!oldImports.has(key)) {
-          let imports = oldImportPaths.get(value)
+          let imports = oldImportPaths.get(value);
           if (typeof imports === 'undefined') {
-            imports = new Set()
+            imports = new Set();
           }
-          imports.add(key)
-          oldImportPaths.set(value, imports)
+          imports.add(key);
+          oldImportPaths.set(value, imports);
 
-          let exports = exportList.get(value)
-          let currentExport
+          let exports = exportList.get(value);
+          let currentExport;
           if (typeof exports !== 'undefined') {
-            currentExport = exports.get(key)
+            currentExport = exports.get(key);
           } else {
-            exports = new Map()
-            exportList.set(value, exports)
+            exports = new Map();
+            exportList.set(value, exports);
           }
 
           if (typeof currentExport !== 'undefined') {
-            currentExport.whereUsed.add(file)
+            currentExport.whereUsed.add(file);
           } else {
-            const whereUsed = new Set()
-            whereUsed.add(file)
-            exports.set(key, { whereUsed })
+            const whereUsed = new Set();
+            whereUsed.add(file);
+            exports.set(key, { whereUsed });
           }
         }
-      })
+      });
 
       oldImports.forEach((value, key) => {
         if (!newImports.has(key)) {
-          const imports = oldImportPaths.get(value)
-          imports.delete(key)
+          const imports = oldImportPaths.get(value);
+          imports.delete(key);
 
-          const exports = exportList.get(value)
+          const exports = exportList.get(value);
           if (typeof exports !== 'undefined') {
-            const currentExport = exports.get(key)
+            const currentExport = exports.get(key);
             if (typeof currentExport !== 'undefined') {
-              currentExport.whereUsed.delete(file)
+              currentExport.whereUsed.delete(file);
             }
           }
         }
-      })
-    }
+      });
+    };
 
     return {
-      'Program:exit': node => {
-        updateExportUsage(node)
-        updateImportUsage(node)
-        checkExportPresence(node)
+      'Program:exit'(node) {
+        updateExportUsage(node);
+        updateImportUsage(node);
+        checkExportPresence(node);
       },
-      'ExportDefaultDeclaration': node => {
-        checkUsage(node, IMPORT_DEFAULT_SPECIFIER)
+      ExportDefaultDeclaration(node) {
+        checkUsage(node, IMPORT_DEFAULT_SPECIFIER, false);
       },
-      'ExportNamedDeclaration': node => {
-        node.specifiers.forEach(specifier => {
-            checkUsage(node, specifier.exported.name)
-        })
-        if (node.declaration) {
-          if (
-            node.declaration.type === FUNCTION_DECLARATION ||
-            node.declaration.type === CLASS_DECLARATION
-          ) {
-            checkUsage(node, node.declaration.id.name)
-          }
-          if (node.declaration.type === VARIABLE_DECLARATION) {
-            node.declaration.declarations.forEach(declaration => {
-              checkUsage(node, declaration.id.name)
-            })
-          }
-        }
+      ExportNamedDeclaration(node) {
+        node.specifiers.forEach((specifier) => {
+          checkUsage(specifier, specifier.exported.name || specifier.exported.value, false);
+        });
+        forEachDeclarationIdentifier(node.declaration, (name, isTypeExport) => {
+          checkUsage(node, name, isTypeExport);
+        });
       },
-    }
+    };
   },
-}
+};
diff --git a/src/rules/no-useless-path-segments.js b/src/rules/no-useless-path-segments.js
index ea72e6c54..2d8dd3526 100644
--- a/src/rules/no-useless-path-segments.js
+++ b/src/rules/no-useless-path-segments.js
@@ -3,11 +3,12 @@
  * @author Thomas Grainger
  */
 
-import { getFileExtensions } from 'eslint-module-utils/ignore'
-import moduleVisitor from 'eslint-module-utils/moduleVisitor'
-import resolve from 'eslint-module-utils/resolve'
-import path from 'path'
-import docsUrl from '../docsUrl'
+import { getPhysicalFilename } from 'eslint-module-utils/contextCompat';
+import { getFileExtensions } from 'eslint-module-utils/ignore';
+import moduleVisitor from 'eslint-module-utils/moduleVisitor';
+import resolve from 'eslint-module-utils/resolve';
+import path from 'path';
+import docsUrl from '../docsUrl';
 
 /**
  * convert a potentially relative path from node utils into a true
@@ -23,26 +24,30 @@ import docsUrl from '../docsUrl'
  * @returns {string} relative posix path that always starts with a ./
  **/
 function toRelativePath(relativePath) {
-  const stripped = relativePath.replace(/\/$/g, '') // Remove trailing /
+  const stripped = relativePath.replace(/\/$/g, ''); // Remove trailing /
 
-  return /^((\.\.)|(\.))($|\/)/.test(stripped) ? stripped : `./${stripped}`
+  return (/^((\.\.)|(\.))($|\/)/).test(stripped) ? stripped : `./${stripped}`;
 }
 
 function normalize(fn) {
-  return toRelativePath(path.posix.normalize(fn))
+  return toRelativePath(path.posix.normalize(fn));
 }
 
 function countRelativeParents(pathSegments) {
-  return pathSegments.reduce((sum, pathSegment) => pathSegment === '..' ? sum + 1 : sum, 0)
+  return pathSegments.filter((x) => x === '..').length;
 }
 
 module.exports = {
   meta: {
     type: 'suggestion',
     docs: {
+      category: 'Static analysis',
+      description: 'Forbid unnecessary path segments in import and require statements.',
       url: docsUrl('no-useless-path-segments'),
     },
 
+    fixable: 'code',
+
     schema: [
       {
         type: 'object',
@@ -53,80 +58,78 @@ module.exports = {
         additionalProperties: false,
       },
     ],
-
-    fixable: 'code',
   },
 
   create(context) {
-    const currentDir = path.dirname(context.getFilename())
-    const options = context.options[0]
+    const currentDir = path.dirname(getPhysicalFilename(context));
+    const options = context.options[0];
 
     function checkSourceValue(source) {
-      const { value: importPath } = source
+      const { value: importPath } = source;
 
       function reportWithProposedPath(proposedPath) {
         context.report({
           node: source,
           // Note: Using messageIds is not possible due to the support for ESLint 2 and 3
           message: `Useless path segments for "${importPath}", should be "${proposedPath}"`,
-          fix: fixer => proposedPath && fixer.replaceText(source, JSON.stringify(proposedPath)),
-        })
+          fix: (fixer) => proposedPath && fixer.replaceText(source, JSON.stringify(proposedPath)),
+        });
       }
 
       // Only relative imports are relevant for this rule --> Skip checking
       if (!importPath.startsWith('.')) {
-        return
+        return;
       }
 
       // Report rule violation if path is not the shortest possible
-      const resolvedPath = resolve(importPath, context)
-      const normedPath = normalize(importPath)
-      const resolvedNormedPath = resolve(normedPath, context)
+      const resolvedPath = resolve(importPath, context);
+      const normedPath = normalize(importPath);
+      const resolvedNormedPath = resolve(normedPath, context);
       if (normedPath !== importPath && resolvedPath === resolvedNormedPath) {
-        return reportWithProposedPath(normedPath)
+        return reportWithProposedPath(normedPath);
       }
 
-      const fileExtensions = getFileExtensions(context.settings)
+      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)
       if (options && options.noUselessIndex && regexUnnecessaryIndex.test(importPath)) {
-        const parentDirectory = path.dirname(importPath)
+        const parentDirectory = path.dirname(importPath);
 
         // Try to find ambiguous imports
         if (parentDirectory !== '.' && parentDirectory !== '..') {
-          for (let fileExtension of fileExtensions) {
+          for (const fileExtension of fileExtensions) {
             if (resolve(`${parentDirectory}${fileExtension}`, context)) {
-              return reportWithProposedPath(`${parentDirectory}/`)
+              return reportWithProposedPath(`${parentDirectory}/`);
             }
           }
         }
 
-        return reportWithProposedPath(parentDirectory)
+        return reportWithProposedPath(parentDirectory);
       }
 
       // Path is shortest possible + starts from the current directory --> Return directly
       if (importPath.startsWith('./')) {
-        return
+        return;
       }
 
       // Path is not existing --> Return directly (following code requires path to be defined)
       if (resolvedPath === undefined) {
-        return
+        return;
       }
 
-      const expected = path.relative(currentDir, resolvedPath) // Expected import path
-      const expectedSplit = expected.split(path.sep) // Split by / or \ (depending on OS)
-      const importPathSplit = importPath.replace(/^\.\//, '').split('/')
-      const countImportPathRelativeParents = countRelativeParents(importPathSplit)
-      const countExpectedRelativeParents = countRelativeParents(expectedSplit)
-      const diff = countImportPathRelativeParents - countExpectedRelativeParents
+      const expected = path.relative(currentDir, resolvedPath); // Expected import path
+      const expectedSplit = expected.split(path.sep); // Split by / or \ (depending on OS)
+      const importPathSplit = importPath.replace(/^\.\//, '').split('/');
+      const countImportPathRelativeParents = countRelativeParents(importPathSplit);
+      const countExpectedRelativeParents = countRelativeParents(expectedSplit);
+      const diff = countImportPathRelativeParents - countExpectedRelativeParents;
 
       // Same number of relative parents --> Paths are the same --> Return directly
       if (diff <= 0) {
-        return
+        return;
       }
 
       // Report and propose minimal number of required relative parents
@@ -135,11 +138,11 @@ module.exports = {
           importPathSplit
             .slice(0, countExpectedRelativeParents)
             .concat(importPathSplit.slice(countImportPathRelativeParents + diff))
-            .join('/')
-        )
-      )
+            .join('/'),
+        ),
+      );
     }
 
-    return moduleVisitor(checkSourceValue, options)
+    return moduleVisitor(checkSourceValue, options);
   },
-}
+};
diff --git a/src/rules/no-webpack-loader-syntax.js b/src/rules/no-webpack-loader-syntax.js
index 723f47269..6ca7d603d 100644
--- a/src/rules/no-webpack-loader-syntax.js
+++ b/src/rules/no-webpack-loader-syntax.js
@@ -1,11 +1,9 @@
-import isStaticRequire from '../core/staticRequire'
-import docsUrl from '../docsUrl'
+import moduleVisitor from 'eslint-module-utils/moduleVisitor';
+import docsUrl from '../docsUrl';
 
 function reportIfNonStandard(context, node, name) {
-  if (name.indexOf('!') !== -1) {
-    context.report(node, `Unexpected '!' in '${name}'. ` +
-      'Do not use import syntax to configure webpack loaders.'
-    )
+  if (name && name.indexOf('!') !== -1) {
+    context.report(node, `Unexpected '!' in '${name}'. Do not use import syntax to configure webpack loaders.`);
   }
 }
 
@@ -13,20 +11,16 @@ module.exports = {
   meta: {
     type: 'problem',
     docs: {
+      category: 'Static analysis',
+      description: 'Forbid webpack loader syntax in imports.',
       url: docsUrl('no-webpack-loader-syntax'),
     },
+    schema: [],
   },
 
-  create: function (context) {
-    return {
-      ImportDeclaration: function handleImports(node) {
-        reportIfNonStandard(context, node, node.source.value)
-      },
-      CallExpression: function handleRequires(node) {
-        if (isStaticRequire(node)) {
-          reportIfNonStandard(context, node, node.arguments[0].value)
-        }
-      },
-    }
+  create(context) {
+    return moduleVisitor((source, node) => {
+      reportIfNonStandard(context, node, source.value);
+    }, { commonjs: true });
   },
-}
+};
diff --git a/src/rules/order.js b/src/rules/order.js
index 3d3e1b96b..ee339e14f 100644
--- a/src/rules/order.js
+++ b/src/rules/order.js
@@ -1,370 +1,853 @@
-'use strict'
+'use strict';
 
-import importType from '../core/importType'
-import isStaticRequire from '../core/staticRequire'
-import docsUrl from '../docsUrl'
+import minimatch from 'minimatch';
+import includes from 'array-includes';
+import groupBy from 'object.groupby';
+import { getScope, getSourceCode } from 'eslint-module-utils/contextCompat';
+import trimEnd from 'string.prototype.trimend';
 
-const defaultGroups = ['builtin', 'external', 'parent', 'sibling', 'index']
+import importType from '../core/importType';
+import isStaticRequire from '../core/staticRequire';
+import docsUrl from '../docsUrl';
+
+const categories = {
+  named: 'named',
+  import: 'import',
+  exports: 'exports',
+};
+
+const defaultGroups = ['builtin', 'external', 'parent', 'sibling', 'index'];
 
 // REPORTING AND FIXING
 
 function reverse(array) {
-  return array.map(function (v) {
-    return {
-      name: v.name,
-      rank: -v.rank,
-      node: v.node,
-    }
-  }).reverse()
+  return array.map((v) => ({ ...v, rank: -v.rank })).reverse();
 }
 
 function getTokensOrCommentsAfter(sourceCode, node, count) {
-  let currentNodeOrToken = node
-  const result = []
+  let currentNodeOrToken = node;
+  const result = [];
   for (let i = 0; i < count; i++) {
-    currentNodeOrToken = sourceCode.getTokenOrCommentAfter(currentNodeOrToken)
+    currentNodeOrToken = sourceCode.getTokenOrCommentAfter(currentNodeOrToken);
     if (currentNodeOrToken == null) {
-      break
+      break;
     }
-    result.push(currentNodeOrToken)
+    result.push(currentNodeOrToken);
   }
-  return result
+  return result;
 }
 
 function getTokensOrCommentsBefore(sourceCode, node, count) {
-  let currentNodeOrToken = node
-  const result = []
+  let currentNodeOrToken = node;
+  const result = [];
   for (let i = 0; i < count; i++) {
-    currentNodeOrToken = sourceCode.getTokenOrCommentBefore(currentNodeOrToken)
+    currentNodeOrToken = sourceCode.getTokenOrCommentBefore(currentNodeOrToken);
     if (currentNodeOrToken == null) {
-      break
+      break;
     }
-    result.push(currentNodeOrToken)
+    result.push(currentNodeOrToken);
   }
-  return result.reverse()
+  return result.reverse();
 }
 
 function takeTokensAfterWhile(sourceCode, node, condition) {
-  const tokens = getTokensOrCommentsAfter(sourceCode, node, 100)
-  const result = []
+  const tokens = getTokensOrCommentsAfter(sourceCode, node, 100);
+  const result = [];
   for (let i = 0; i < tokens.length; i++) {
     if (condition(tokens[i])) {
-      result.push(tokens[i])
-    }
-    else {
-      break
+      result.push(tokens[i]);
+    } else {
+      break;
     }
   }
-  return result
+  return result;
 }
 
 function takeTokensBeforeWhile(sourceCode, node, condition) {
-  const tokens = getTokensOrCommentsBefore(sourceCode, node, 100)
-  const result = []
+  const tokens = getTokensOrCommentsBefore(sourceCode, node, 100);
+  const result = [];
   for (let i = tokens.length - 1; i >= 0; i--) {
     if (condition(tokens[i])) {
-      result.push(tokens[i])
-    }
-    else {
-      break
+      result.push(tokens[i]);
+    } else {
+      break;
     }
   }
-  return result.reverse()
+  return result.reverse();
 }
 
 function findOutOfOrder(imported) {
   if (imported.length === 0) {
-    return []
+    return [];
   }
-  let maxSeenRankNode = imported[0]
+  let maxSeenRankNode = imported[0];
   return imported.filter(function (importedModule) {
-    const res = importedModule.rank < maxSeenRankNode.rank
+    const res = importedModule.rank < maxSeenRankNode.rank;
     if (maxSeenRankNode.rank < importedModule.rank) {
-      maxSeenRankNode = importedModule
+      maxSeenRankNode = importedModule;
     }
-    return res
-  })
+    return res;
+  });
 }
 
 function findRootNode(node) {
-  let parent = node
+  let parent = node;
   while (parent.parent != null && parent.parent.body == null) {
-    parent = parent.parent
+    parent = parent.parent;
   }
-  return parent
+  return parent;
+}
+
+function commentOnSameLineAs(node) {
+  return (token) => (token.type === 'Block' ||  token.type === 'Line')
+      && token.loc.start.line === token.loc.end.line
+      && token.loc.end.line === node.loc.end.line;
 }
 
 function findEndOfLineWithComments(sourceCode, node) {
-  const tokensToEndOfLine = takeTokensAfterWhile(sourceCode, node, commentOnSameLineAs(node))
-  let endOfTokens = tokensToEndOfLine.length > 0
+  const tokensToEndOfLine = takeTokensAfterWhile(sourceCode, node, commentOnSameLineAs(node));
+  const endOfTokens = tokensToEndOfLine.length > 0
     ? tokensToEndOfLine[tokensToEndOfLine.length - 1].range[1]
-    : node.range[1]
-  let result = endOfTokens
+    : node.range[1];
+  let result = endOfTokens;
   for (let i = endOfTokens; i < sourceCode.text.length; i++) {
     if (sourceCode.text[i] === '\n') {
-      result = i + 1
-      break
+      result = i + 1;
+      break;
     }
     if (sourceCode.text[i] !== ' ' && sourceCode.text[i] !== '\t' && sourceCode.text[i] !== '\r') {
-      break
+      break;
     }
-    result = i + 1
+    result = i + 1;
   }
-  return result
-}
-
-function commentOnSameLineAs(node) {
-  return token => (token.type === 'Block' ||  token.type === 'Line') &&
-      token.loc.start.line === token.loc.end.line &&
-      token.loc.end.line === node.loc.end.line
+  return result;
 }
 
 function findStartOfLineWithComments(sourceCode, node) {
-  const tokensToEndOfLine = takeTokensBeforeWhile(sourceCode, node, commentOnSameLineAs(node))
-  let startOfTokens = tokensToEndOfLine.length > 0 ? tokensToEndOfLine[0].range[0] : node.range[0]
-  let result = startOfTokens
+  const tokensToEndOfLine = takeTokensBeforeWhile(sourceCode, node, commentOnSameLineAs(node));
+  const startOfTokens = tokensToEndOfLine.length > 0 ? tokensToEndOfLine[0].range[0] : node.range[0];
+  let result = startOfTokens;
   for (let i = startOfTokens - 1; i > 0; i--) {
     if (sourceCode.text[i] !== ' ' && sourceCode.text[i] !== '\t') {
-      break
+      break;
     }
-    result = i
+    result = i;
   }
-  return result
+  return result;
 }
 
-function isPlainRequireModule(node) {
+function findSpecifierStart(sourceCode, node) {
+  let token;
+
+  do {
+    token = sourceCode.getTokenBefore(node);
+  } while (token.value !== ',' && token.value !== '{');
+
+  return token.range[1];
+}
+
+function findSpecifierEnd(sourceCode, node) {
+  let token;
+
+  do {
+    token = sourceCode.getTokenAfter(node);
+  } while (token.value !== ',' && token.value !== '}');
+
+  return token.range[0];
+}
+
+function isRequireExpression(expr) {
+  return expr != null
+    && expr.type === 'CallExpression'
+    && expr.callee != null
+    && expr.callee.name === 'require'
+    && expr.arguments != null
+    && expr.arguments.length === 1
+    && expr.arguments[0].type === 'Literal';
+}
+
+function isSupportedRequireModule(node) {
   if (node.type !== 'VariableDeclaration') {
-    return false
+    return false;
   }
   if (node.declarations.length !== 1) {
-    return false
+    return false;
   }
-  const decl = node.declarations[0]
-  const result = decl.id &&
-    (decl.id.type === 'Identifier' || decl.id.type === 'ObjectPattern') &&
-    decl.init != null &&
-    decl.init.type === 'CallExpression' &&
-    decl.init.callee != null &&
-    decl.init.callee.name === 'require' &&
-    decl.init.arguments != null &&
-    decl.init.arguments.length === 1 &&
-    decl.init.arguments[0].type === 'Literal'
-  return result
+  const decl = node.declarations[0];
+  const isPlainRequire = decl.id
+    && (decl.id.type === 'Identifier' || decl.id.type === 'ObjectPattern')
+    && isRequireExpression(decl.init);
+  const isRequireWithMemberExpression = decl.id
+    && (decl.id.type === 'Identifier' || decl.id.type === 'ObjectPattern')
+    && decl.init != null
+    && decl.init.type === 'CallExpression'
+    && decl.init.callee != null
+    && decl.init.callee.type === 'MemberExpression'
+    && isRequireExpression(decl.init.callee.object);
+  return isPlainRequire || isRequireWithMemberExpression;
 }
 
 function isPlainImportModule(node) {
-  return node.type === 'ImportDeclaration' && node.specifiers != null && node.specifiers.length > 0
+  return node.type === 'ImportDeclaration' && node.specifiers != null && node.specifiers.length > 0;
+}
+
+function isPlainImportEquals(node) {
+  return node.type === 'TSImportEqualsDeclaration' && node.moduleReference.expression;
+}
+
+function isCJSExports(context, node) {
+  if (
+    node.type === 'MemberExpression'
+    && node.object.type === 'Identifier'
+    && node.property.type === 'Identifier'
+    && node.object.name === 'module'
+    && node.property.name === 'exports'
+  ) {
+    return getScope(context, node).variables.findIndex((variable) => variable.name === 'module') === -1;
+  }
+  if (
+    node.type === 'Identifier'
+    && node.name === 'exports'
+  ) {
+    return getScope(context, node).variables.findIndex((variable) => variable.name === 'exports') === -1;
+  }
+}
+
+function getNamedCJSExports(context, node) {
+  if (node.type !== 'MemberExpression') {
+    return;
+  }
+  const result = [];
+  let root = node;
+  let parent = null;
+  while (root.type === 'MemberExpression') {
+    if (root.property.type !== 'Identifier') {
+      return;
+    }
+    result.unshift(root.property.name);
+    parent = root;
+    root = root.object;
+  }
+
+  if (isCJSExports(context, root)) {
+    return result;
+  }
+
+  if (isCJSExports(context, parent)) {
+    return result.slice(1);
+  }
 }
 
 function canCrossNodeWhileReorder(node) {
-  return isPlainRequireModule(node) || isPlainImportModule(node)
+  return isSupportedRequireModule(node) || isPlainImportModule(node) || isPlainImportEquals(node);
 }
 
 function canReorderItems(firstNode, secondNode) {
-  const parent = firstNode.parent
-  const firstIndex = parent.body.indexOf(firstNode)
-  const secondIndex = parent.body.indexOf(secondNode)
-  const nodesBetween = parent.body.slice(firstIndex, secondIndex + 1)
-  for (var nodeBetween of nodesBetween) {
+  const parent = firstNode.parent;
+  const [firstIndex, secondIndex] = [
+    parent.body.indexOf(firstNode),
+    parent.body.indexOf(secondNode),
+  ].sort();
+  const nodesBetween = parent.body.slice(firstIndex, secondIndex + 1);
+  for (const nodeBetween of nodesBetween) {
     if (!canCrossNodeWhileReorder(nodeBetween)) {
-      return false
+      return false;
     }
   }
-  return true
+  return true;
 }
 
-function fixOutOfOrder(context, firstNode, secondNode, order) {
-  const sourceCode = context.getSourceCode()
+function makeImportDescription(node) {
+  if (node.type === 'export') {
+    if (node.node.exportKind === 'type') {
+      return 'type export';
+    }
+    return 'export';
+  }
+  if (node.node.importKind === 'type') {
+    return 'type import';
+  }
+  if (node.node.importKind === 'typeof') {
+    return 'typeof import';
+  }
+  return 'import';
+}
+
+function fixOutOfOrder(context, firstNode, secondNode, order, category) {
+  const isNamed = category === categories.named;
+  const isExports = category === categories.exports;
+  const sourceCode = getSourceCode(context);
 
-  const firstRoot = findRootNode(firstNode.node)
-  let firstRootStart = findStartOfLineWithComments(sourceCode, firstRoot)
-  const firstRootEnd = findEndOfLineWithComments(sourceCode, firstRoot)
+  const {
+    firstRoot,
+    secondRoot,
+  } = isNamed ? {
+    firstRoot: firstNode.node,
+    secondRoot: secondNode.node,
+  } : {
+    firstRoot: findRootNode(firstNode.node),
+    secondRoot: findRootNode(secondNode.node),
+  };
 
-  const secondRoot = findRootNode(secondNode.node)
-  let secondRootStart = findStartOfLineWithComments(sourceCode, secondRoot)
-  let secondRootEnd = findEndOfLineWithComments(sourceCode, secondRoot)
-  const canFix = canReorderItems(firstRoot, secondRoot)
+  const {
+    firstRootStart,
+    firstRootEnd,
+    secondRootStart,
+    secondRootEnd,
+  } = isNamed ? {
+    firstRootStart: findSpecifierStart(sourceCode, firstRoot),
+    firstRootEnd: findSpecifierEnd(sourceCode, firstRoot),
+    secondRootStart: findSpecifierStart(sourceCode, secondRoot),
+    secondRootEnd: findSpecifierEnd(sourceCode, secondRoot),
+  } : {
+    firstRootStart: findStartOfLineWithComments(sourceCode, firstRoot),
+    firstRootEnd: findEndOfLineWithComments(sourceCode, firstRoot),
+    secondRootStart: findStartOfLineWithComments(sourceCode, secondRoot),
+    secondRootEnd: findEndOfLineWithComments(sourceCode, secondRoot),
+  };
 
-  let newCode = sourceCode.text.substring(secondRootStart, secondRootEnd)
-  if (newCode[newCode.length - 1] !== '\n') {
-    newCode = newCode + '\n'
+  if (firstNode.displayName === secondNode.displayName) {
+    if (firstNode.alias) {
+      firstNode.displayName = `${firstNode.displayName} as ${firstNode.alias}`;
+    }
+    if (secondNode.alias) {
+      secondNode.displayName = `${secondNode.displayName} as ${secondNode.alias}`;
+    }
   }
 
-  const message = '`' + secondNode.name + '` import should occur ' + order +
-      ' import of `' + firstNode.name + '`'
+  const firstImport = `${makeImportDescription(firstNode)} of \`${firstNode.displayName}\``;
+  const secondImport = `\`${secondNode.displayName}\` ${makeImportDescription(secondNode)}`;
+  const message = `${secondImport} should occur ${order} ${firstImport}`;
+
+  if (isNamed) {
+    const firstCode = sourceCode.text.slice(firstRootStart, firstRoot.range[1]);
+    const firstTrivia = sourceCode.text.slice(firstRoot.range[1], firstRootEnd);
+    const secondCode = sourceCode.text.slice(secondRootStart, secondRoot.range[1]);
+    const secondTrivia = sourceCode.text.slice(secondRoot.range[1], secondRootEnd);
+
+    if (order === 'before') {
+      const trimmedTrivia = trimEnd(secondTrivia);
+      const gapCode = sourceCode.text.slice(firstRootEnd, secondRootStart - 1);
+      const whitespaces = secondTrivia.slice(trimmedTrivia.length);
+      context.report({
+        node: secondNode.node,
+        message,
+        fix: (fixer) => fixer.replaceTextRange(
+          [firstRootStart, secondRootEnd],
+          `${secondCode},${trimmedTrivia}${firstCode}${firstTrivia}${gapCode}${whitespaces}`,
+        ),
+      });
+    } else if (order === 'after') {
+      const trimmedTrivia = trimEnd(firstTrivia);
+      const gapCode = sourceCode.text.slice(secondRootEnd + 1, firstRootStart);
+      const whitespaces = firstTrivia.slice(trimmedTrivia.length);
+      context.report({
+        node: secondNode.node,
+        message,
+        fix: (fixes) => fixes.replaceTextRange(
+          [secondRootStart, firstRootEnd],
+          `${gapCode}${firstCode},${trimmedTrivia}${secondCode}${whitespaces}`,
+        ),
+      });
+    }
+  } else {
+    const canFix = isExports || canReorderItems(firstRoot, secondRoot);
+    let newCode = sourceCode.text.substring(secondRootStart, secondRootEnd);
+
+    if (newCode[newCode.length - 1] !== '\n') {
+      newCode = `${newCode}\n`;
+    }
 
-  if (order === 'before') {
-    context.report({
-      node: secondNode.node,
-      message: message,
-      fix: canFix && (fixer =>
-        fixer.replaceTextRange(
+    if (order === 'before') {
+      context.report({
+        node: secondNode.node,
+        message,
+        fix: canFix && ((fixer) => fixer.replaceTextRange(
           [firstRootStart, secondRootEnd],
-          newCode + sourceCode.text.substring(firstRootStart, secondRootStart)
+          newCode + sourceCode.text.substring(firstRootStart, secondRootStart),
         )),
-    })
-  } else if (order === 'after') {
-    context.report({
-      node: secondNode.node,
-      message: message,
-      fix: canFix && (fixer =>
-        fixer.replaceTextRange(
+      });
+    } else if (order === 'after') {
+      context.report({
+        node: secondNode.node,
+        message,
+        fix: canFix && ((fixer) => fixer.replaceTextRange(
           [secondRootStart, firstRootEnd],
-          sourceCode.text.substring(secondRootEnd, firstRootEnd) + newCode
+          sourceCode.text.substring(secondRootEnd, firstRootEnd) + newCode,
         )),
-    })
+      });
+    }
   }
 }
 
-function reportOutOfOrder(context, imported, outOfOrder, order) {
+function reportOutOfOrder(context, imported, outOfOrder, order, category) {
   outOfOrder.forEach(function (imp) {
     const found = imported.find(function hasHigherRank(importedItem) {
-      return importedItem.rank > imp.rank
-    })
-    fixOutOfOrder(context, found, imp, order)
-  })
+      return importedItem.rank > imp.rank;
+    });
+    fixOutOfOrder(context, found, imp, order, category);
+  });
 }
 
-function makeOutOfOrderReport(context, imported) {
-  const outOfOrder = findOutOfOrder(imported)
+function makeOutOfOrderReport(context, imported, category) {
+  const outOfOrder = findOutOfOrder(imported);
   if (!outOfOrder.length) {
-    return
+    return;
   }
+
   // There are things to report. Try to minimize the number of reported errors.
-  const reversedImported = reverse(imported)
-  const reversedOrder = findOutOfOrder(reversedImported)
+  const reversedImported = reverse(imported);
+  const reversedOrder = findOutOfOrder(reversedImported);
   if (reversedOrder.length < outOfOrder.length) {
-    reportOutOfOrder(context, reversedImported, reversedOrder, 'after')
-    return
+    reportOutOfOrder(context, reversedImported, reversedOrder, 'after', category);
+    return;
+  }
+  reportOutOfOrder(context, imported, outOfOrder, 'before', category);
+}
+
+const compareString = (a, b) => {
+  if (a < b) {
+    return -1;
+  }
+  if (a > b) {
+    return 1;
   }
-  reportOutOfOrder(context, imported, outOfOrder, 'before')
+  return 0;
+};
+
+/** Some parsers (languages without types) don't provide ImportKind */
+const DEFAULT_IMPORT_KIND = 'value';
+const getNormalizedValue = (node, toLowerCase) => {
+  const value = node.value;
+  return toLowerCase ? String(value).toLowerCase() : value;
+};
+
+function getSorter(alphabetizeOptions) {
+  const multiplier = alphabetizeOptions.order === 'asc' ? 1 : -1;
+  const orderImportKind = alphabetizeOptions.orderImportKind;
+  const multiplierImportKind = orderImportKind !== 'ignore'
+    && (alphabetizeOptions.orderImportKind === 'asc' ? 1 : -1);
+
+  return function importsSorter(nodeA, nodeB) {
+    const importA = getNormalizedValue(nodeA, alphabetizeOptions.caseInsensitive);
+    const importB = getNormalizedValue(nodeB, alphabetizeOptions.caseInsensitive);
+    let result = 0;
+
+    if (!includes(importA, '/') && !includes(importB, '/')) {
+      result = compareString(importA, importB);
+    } else {
+      const A = importA.split('/');
+      const B = importB.split('/');
+      const a = A.length;
+      const b = B.length;
+
+      for (let i = 0; i < Math.min(a, b); i++) {
+        // Skip comparing the first path segment, if they are relative segments for both imports
+        if (i === 0 && ((A[i] === '.' || A[i] === '..') && (B[i] === '.' || B[i] === '..'))) {
+          // If one is sibling and the other parent import, no need to compare at all, since the paths belong in different groups
+          if (A[i] !== B[i]) { break; }
+          continue;
+        }
+        result = compareString(A[i], B[i]);
+        if (result) { break; }
+      }
+
+      if (!result && a !== b) {
+        result = a < b ? -1 : 1;
+      }
+    }
+
+    result = result * multiplier;
+
+    // In case the paths are equal (result === 0), sort them by importKind
+    if (!result && multiplierImportKind) {
+      result = multiplierImportKind * compareString(
+        nodeA.node.importKind || DEFAULT_IMPORT_KIND,
+        nodeB.node.importKind || DEFAULT_IMPORT_KIND,
+      );
+    }
+
+    return result;
+  };
+}
+
+function mutateRanksToAlphabetize(imported, alphabetizeOptions) {
+  const groupedByRanks = groupBy(imported, (item) => item.rank);
+
+  const sorterFn = getSorter(alphabetizeOptions);
+
+  // sort group keys so that they can be iterated on in order
+  const groupRanks = Object.keys(groupedByRanks).sort(function (a, b) {
+    return a - b;
+  });
+
+  // sort imports locally within their group
+  groupRanks.forEach(function (groupRank) {
+    groupedByRanks[groupRank].sort(sorterFn);
+  });
+
+  // assign globally unique rank to each import
+  let newRank = 0;
+  const alphabetizedRanks = groupRanks.reduce(function (acc, groupRank) {
+    groupedByRanks[groupRank].forEach(function (importedItem) {
+      acc[`${importedItem.value}|${importedItem.node.importKind}`] = parseInt(groupRank, 10) + newRank;
+      newRank += 1;
+    });
+    return acc;
+  }, {});
+
+  // mutate the original group-rank with alphabetized-rank
+  imported.forEach(function (importedItem) {
+    importedItem.rank = alphabetizedRanks[`${importedItem.value}|${importedItem.node.importKind}`];
+  });
 }
 
 // DETECTING
 
-function computeRank(context, ranks, name, type) {
-  return ranks[importType(name, context)] +
-    (type === 'import' ? 0 : 100)
+function computePathRank(ranks, pathGroups, path, maxPosition) {
+  for (let i = 0, l = pathGroups.length; i < l; i++) {
+    const { pattern, patternOptions, group, position = 1 } = pathGroups[i];
+    if (minimatch(path, pattern, patternOptions || { nocomment: true })) {
+      return ranks[group] + position / maxPosition;
+    }
+  }
 }
 
-function registerNode(context, node, name, type, ranks, imported) {
-  const rank = computeRank(context, ranks, name, type)
+function computeRank(context, ranks, importEntry, excludedImportTypes, isSortingTypesGroup) {
+  let impType;
+  let rank;
+
+  const isTypeGroupInGroups = ranks.omittedTypes.indexOf('type') === -1;
+  const isTypeOnlyImport = importEntry.node.importKind === 'type';
+  const isExcludedFromPathRank = isTypeOnlyImport && isTypeGroupInGroups && excludedImportTypes.has('type');
+
+  if (importEntry.type === 'import:object') {
+    impType = 'object';
+  } else if (isTypeOnlyImport && isTypeGroupInGroups && !isSortingTypesGroup) {
+    impType = 'type';
+  } else {
+    impType = importType(importEntry.value, context);
+  }
+
+  if (!excludedImportTypes.has(impType) && !isExcludedFromPathRank) {
+    rank = computePathRank(ranks.groups, ranks.pathGroups, importEntry.value, ranks.maxPosition);
+  }
+
+  if (typeof rank === 'undefined') {
+    rank = ranks.groups[impType];
+
+    if (typeof rank === 'undefined') {
+      return -1;
+    }
+  }
+
+  if (isTypeOnlyImport && isSortingTypesGroup) {
+    rank = ranks.groups.type + rank / 10;
+  }
+
+  if (importEntry.type !== 'import' && !importEntry.type.startsWith('import:')) {
+    rank += 100;
+  }
+
+  return rank;
+}
+
+function registerNode(context, importEntry, ranks, imported, excludedImportTypes, isSortingTypesGroup) {
+  const rank = computeRank(context, ranks, importEntry, excludedImportTypes, isSortingTypesGroup);
   if (rank !== -1) {
-    imported.push({name, rank, node})
+    let importNode = importEntry.node;
+
+    if (importEntry.type === 'require' && importNode.parent.parent.type === 'VariableDeclaration') {
+      importNode = importNode.parent.parent;
+    }
+
+    imported.push({
+      ...importEntry,
+      rank,
+      isMultiline: importNode.loc.end.line !== importNode.loc.start.line,
+    });
   }
 }
 
-function isInVariableDeclarator(node) {
-  return node &&
-    (node.type === 'VariableDeclarator' || isInVariableDeclarator(node.parent))
+function getRequireBlock(node) {
+  let n = node;
+  // Handle cases like `const baz = require('foo').bar.baz`
+  // and `const foo = require('foo')()`
+  while (
+    n.parent.type === 'MemberExpression' && n.parent.object === n
+    || n.parent.type === 'CallExpression' && n.parent.callee === n
+  ) {
+    n = n.parent;
+  }
+  if (
+    n.parent.type === 'VariableDeclarator'
+    && n.parent.parent.type === 'VariableDeclaration'
+    && n.parent.parent.parent.type === 'Program'
+  ) {
+    return n.parent.parent.parent;
+  }
 }
 
-const types = ['builtin', 'external', 'internal', 'unknown', 'parent', 'sibling', 'index']
+const types = ['builtin', 'external', 'internal', 'unknown', 'parent', 'sibling', 'index', 'object', 'type'];
 
 // Creates an object with type-rank pairs.
 // Example: { index: 0, sibling: 1, parent: 1, external: 1, builtin: 2, internal: 2 }
 // Will throw an error if it contains a type that does not exist, or has a duplicate
 function convertGroupsToRanks(groups) {
-  const rankObject = groups.reduce(function(res, group, index) {
-    if (typeof group === 'string') {
-      group = [group]
-    }
-    group.forEach(function(groupItem) {
+  const rankObject = groups.reduce(function (res, group, index) {
+    [].concat(group).forEach(function (groupItem) {
       if (types.indexOf(groupItem) === -1) {
-        throw new Error('Incorrect configuration of the rule: Unknown type `' +
-          JSON.stringify(groupItem) + '`')
+        throw new Error(`Incorrect configuration of the rule: Unknown type \`${JSON.stringify(groupItem)}\``);
       }
       if (res[groupItem] !== undefined) {
-        throw new Error('Incorrect configuration of the rule: `' + groupItem + '` is duplicated')
+        throw new Error(`Incorrect configuration of the rule: \`${groupItem}\` is duplicated`);
       }
-      res[groupItem] = index
-    })
-    return res
-  }, {})
+      res[groupItem] = index * 2;
+    });
+    return res;
+  }, {});
 
-  const omittedTypes = types.filter(function(type) {
-    return rankObject[type] === undefined
-  })
+  const omittedTypes = types.filter(function (type) {
+    return typeof rankObject[type] === 'undefined';
+  });
 
-  return omittedTypes.reduce(function(res, type) {
-    res[type] = groups.length
-    return res
-  }, rankObject)
+  const ranks = omittedTypes.reduce(function (res, type) {
+    res[type] = groups.length * 2;
+    return res;
+  }, rankObject);
+
+  return { groups: ranks, omittedTypes };
+}
+
+function convertPathGroupsForRanks(pathGroups) {
+  const after = {};
+  const before = {};
+
+  const transformed = pathGroups.map((pathGroup, index) => {
+    const { group, position: positionString } = pathGroup;
+    let position = 0;
+    if (positionString === 'after') {
+      if (!after[group]) {
+        after[group] = 1;
+      }
+      position = after[group]++;
+    } else if (positionString === 'before') {
+      if (!before[group]) {
+        before[group] = [];
+      }
+      before[group].push(index);
+    }
+
+    return { ...pathGroup, position };
+  });
+
+  let maxPosition = 1;
+
+  Object.keys(before).forEach((group) => {
+    const groupLength = before[group].length;
+    before[group].forEach((groupIndex, index) => {
+      transformed[groupIndex].position = -1 * (groupLength - index);
+    });
+    maxPosition = Math.max(maxPosition, groupLength);
+  });
+
+  Object.keys(after).forEach((key) => {
+    const groupNextPosition = after[key];
+    maxPosition = Math.max(maxPosition, groupNextPosition - 1);
+  });
+
+  return {
+    pathGroups: transformed,
+    maxPosition: maxPosition > 10 ? Math.pow(10, Math.ceil(Math.log10(maxPosition))) : 10,
+  };
 }
 
 function fixNewLineAfterImport(context, previousImport) {
-  const prevRoot = findRootNode(previousImport.node)
+  const prevRoot = findRootNode(previousImport.node);
   const tokensToEndOfLine = takeTokensAfterWhile(
-    context.getSourceCode(), prevRoot, commentOnSameLineAs(prevRoot))
+    getSourceCode(context),
+    prevRoot,
+    commentOnSameLineAs(prevRoot),
+  );
 
-  let endOfLine = prevRoot.range[1]
+  let endOfLine = prevRoot.range[1];
   if (tokensToEndOfLine.length > 0) {
-    endOfLine = tokensToEndOfLine[tokensToEndOfLine.length - 1].range[1]
+    endOfLine = tokensToEndOfLine[tokensToEndOfLine.length - 1].range[1];
   }
-  return (fixer) => fixer.insertTextAfterRange([prevRoot.range[0], endOfLine], '\n')
+  return (fixer) => fixer.insertTextAfterRange([prevRoot.range[0], endOfLine], '\n');
 }
 
 function removeNewLineAfterImport(context, currentImport, previousImport) {
-  const sourceCode = context.getSourceCode()
-  const prevRoot = findRootNode(previousImport.node)
-  const currRoot = findRootNode(currentImport.node)
+  const sourceCode = getSourceCode(context);
+  const prevRoot = findRootNode(previousImport.node);
+  const currRoot = findRootNode(currentImport.node);
   const rangeToRemove = [
     findEndOfLineWithComments(sourceCode, prevRoot),
     findStartOfLineWithComments(sourceCode, currRoot),
-  ]
-  if (/^\s*$/.test(sourceCode.text.substring(rangeToRemove[0], rangeToRemove[1]))) {
-    return (fixer) => fixer.removeRange(rangeToRemove)
+  ];
+  if ((/^\s*$/).test(sourceCode.text.substring(rangeToRemove[0], rangeToRemove[1]))) {
+    return (fixer) => fixer.removeRange(rangeToRemove);
   }
-  return undefined
+  return undefined;
 }
 
-function makeNewlinesBetweenReport (context, imported, newlinesBetweenImports) {
+function makeNewlinesBetweenReport(context, imported, newlinesBetweenImports_, newlinesBetweenTypeOnlyImports_, distinctGroup, isSortingTypesGroup, isConsolidatingSpaceBetweenImports) {
   const getNumberOfEmptyLinesBetween = (currentImport, previousImport) => {
-    const linesBetweenImports = context.getSourceCode().lines.slice(
+    const linesBetweenImports = getSourceCode(context).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
-  }
-  let previousImport = imported[0]
+    return linesBetweenImports.filter((line) => !line.trim().length).length;
+  };
+  const getIsStartOfDistinctGroup = (currentImport, previousImport) => currentImport.rank - 1 >= previousImport.rank;
+  let previousImport = imported[0];
 
-  imported.slice(1).forEach(function(currentImport) {
-    const emptyLinesBetween = getNumberOfEmptyLinesBetween(currentImport, previousImport)
+  imported.slice(1).forEach(function (currentImport) {
+    const emptyLinesBetween = getNumberOfEmptyLinesBetween(
+      currentImport,
+      previousImport,
+    );
 
-    if (newlinesBetweenImports === 'always'
-        || newlinesBetweenImports === 'always-and-inside-groups') {
-      if (currentImport.rank !== previousImport.rank && emptyLinesBetween === 0) {
-        context.report({
-          node: previousImport.node,
-          message: 'There should be at least one empty line between import groups',
-          fix: fixNewLineAfterImport(context, previousImport, currentImport),
-        })
-      } else if (currentImport.rank === previousImport.rank
-        && emptyLinesBetween > 0
-        && newlinesBetweenImports !== 'always-and-inside-groups') {
+    const isStartOfDistinctGroup = getIsStartOfDistinctGroup(
+      currentImport,
+      previousImport,
+    );
+
+    const isTypeOnlyImport = currentImport.node.importKind === 'type';
+    const isPreviousImportTypeOnlyImport = previousImport.node.importKind === 'type';
+
+    const isNormalImportNextToTypeOnlyImportAndRelevant =      isTypeOnlyImport !== isPreviousImportTypeOnlyImport && isSortingTypesGroup;
+
+    const isTypeOnlyImportAndRelevant = isTypeOnlyImport && isSortingTypesGroup;
+
+    // In the special case where newlinesBetweenImports and consolidateIslands
+    // want the opposite thing, consolidateIslands wins
+    const newlinesBetweenImports =      isSortingTypesGroup
+      && isConsolidatingSpaceBetweenImports
+      && (previousImport.isMultiline || currentImport.isMultiline)
+      && newlinesBetweenImports_ === 'never'
+      ? 'always-and-inside-groups'
+      : newlinesBetweenImports_;
+
+    // In the special case where newlinesBetweenTypeOnlyImports and
+    // consolidateIslands want the opposite thing, consolidateIslands wins
+    const newlinesBetweenTypeOnlyImports =      isSortingTypesGroup
+      && isConsolidatingSpaceBetweenImports
+      && (isNormalImportNextToTypeOnlyImportAndRelevant
+        || previousImport.isMultiline
+        || currentImport.isMultiline)
+      && newlinesBetweenTypeOnlyImports_ === 'never'
+      ? 'always-and-inside-groups'
+      : newlinesBetweenTypeOnlyImports_;
+
+    const isNotIgnored =      isTypeOnlyImportAndRelevant
+        && newlinesBetweenTypeOnlyImports !== 'ignore'
+      || !isTypeOnlyImportAndRelevant && newlinesBetweenImports !== 'ignore';
+
+    if (isNotIgnored) {
+      const shouldAssertNewlineBetweenGroups =        (isTypeOnlyImportAndRelevant || isNormalImportNextToTypeOnlyImportAndRelevant)
+          && (newlinesBetweenTypeOnlyImports === 'always'
+            || newlinesBetweenTypeOnlyImports === 'always-and-inside-groups')
+        || !isTypeOnlyImportAndRelevant && !isNormalImportNextToTypeOnlyImportAndRelevant
+          && (newlinesBetweenImports === 'always'
+            || newlinesBetweenImports === 'always-and-inside-groups');
+
+      const shouldAssertNoNewlineWithinGroup =        (isTypeOnlyImportAndRelevant || isNormalImportNextToTypeOnlyImportAndRelevant)
+          && newlinesBetweenTypeOnlyImports !== 'always-and-inside-groups'
+        || !isTypeOnlyImportAndRelevant && !isNormalImportNextToTypeOnlyImportAndRelevant
+          && newlinesBetweenImports !== 'always-and-inside-groups';
+
+      const shouldAssertNoNewlineBetweenGroup =        !isSortingTypesGroup
+        || !isNormalImportNextToTypeOnlyImportAndRelevant
+        || newlinesBetweenTypeOnlyImports === 'never';
+
+      const isTheNewlineBetweenImportsInTheSameGroup = distinctGroup && currentImport.rank === previousImport.rank
+      || !distinctGroup && !isStartOfDistinctGroup;
+
+      // Let's try to cut down on linting errors sent to the user
+      let alreadyReported = false;
+
+      if (shouldAssertNewlineBetweenGroups) {
+        if (currentImport.rank !== previousImport.rank && emptyLinesBetween === 0) {
+          if (distinctGroup || isStartOfDistinctGroup) {
+            alreadyReported = true;
+            context.report({
+              node: previousImport.node,
+              message: 'There should be at least one empty line between import groups',
+              fix: fixNewLineAfterImport(context, previousImport),
+            });
+          }
+        } else if (emptyLinesBetween > 0 && shouldAssertNoNewlineWithinGroup) {
+          if (isTheNewlineBetweenImportsInTheSameGroup) {
+            alreadyReported = true;
+            context.report({
+              node: previousImport.node,
+              message: 'There should be no empty line within import group',
+              fix: removeNewLineAfterImport(context, currentImport, previousImport),
+            });
+          }
+        }
+      } else if (emptyLinesBetween > 0 && shouldAssertNoNewlineBetweenGroup) {
+        alreadyReported = true;
         context.report({
           node: previousImport.node,
-          message: 'There should be no empty line within import group',
+          message: 'There should be no empty line between import groups',
           fix: removeNewLineAfterImport(context, currentImport, previousImport),
-        })
+        });
+      }
+
+      if (!alreadyReported && isConsolidatingSpaceBetweenImports) {
+        if (emptyLinesBetween === 0 && currentImport.isMultiline) {
+          context.report({
+            node: previousImport.node,
+            message: 'There should be at least one empty line between this import and the multi-line import that follows it',
+            fix: fixNewLineAfterImport(context, previousImport),
+          });
+        } else if (emptyLinesBetween === 0 && previousImport.isMultiline) {
+          context.report({
+            node: previousImport.node,
+            message: 'There should be at least one empty line between this multi-line import and the import that follows it',
+            fix: fixNewLineAfterImport(context, previousImport),
+          });
+        } else if (
+          emptyLinesBetween > 0
+          && !previousImport.isMultiline
+          && !currentImport.isMultiline
+          && isTheNewlineBetweenImportsInTheSameGroup
+        ) {
+          context.report({
+            node: previousImport.node,
+            message:
+              'There should be no empty lines between this single-line import and the single-line import that follows it',
+            fix: removeNewLineAfterImport(context, currentImport, previousImport),
+          });
+        }
       }
-    } else if (emptyLinesBetween > 0) {
-      context.report({
-        node: previousImport.node,
-        message: 'There should be no empty line between import groups',
-        fix: removeNewLineAfterImport(context, currentImport, previousImport),
-      })
     }
 
-    previousImport = currentImport
-  })
+    previousImport = currentImport;
+  });
+}
+
+function getAlphabetizeConfig(options) {
+  const alphabetize = options.alphabetize || {};
+  const order = alphabetize.order || 'ignore';
+  const orderImportKind = alphabetize.orderImportKind || 'ignore';
+  const caseInsensitive = alphabetize.caseInsensitive || false;
+
+  return { order, orderImportKind, caseInsensitive };
 }
 
+// TODO, semver-major: Change the default of "distinctGroup" from true to false
+const defaultDistinctGroup = true;
+
 module.exports = {
   meta: {
     type: 'suggestion',
     docs: {
+      category: 'Style guide',
+      description: 'Enforce a convention in module import order.',
       url: docsUrl('order'),
     },
 
@@ -376,6 +859,37 @@ module.exports = {
           groups: {
             type: 'array',
           },
+          pathGroupsExcludedImportTypes: {
+            type: 'array',
+          },
+          distinctGroup: {
+            type: 'boolean',
+            default: defaultDistinctGroup,
+          },
+          pathGroups: {
+            type: 'array',
+            items: {
+              type: 'object',
+              properties: {
+                pattern: {
+                  type: 'string',
+                },
+                patternOptions: {
+                  type: 'object',
+                },
+                group: {
+                  type: 'string',
+                  enum: types,
+                },
+                position: {
+                  type: 'string',
+                  enum: ['after', 'before'],
+                },
+              },
+              additionalProperties: false,
+              required: ['pattern', 'group'],
+            },
+          },
           'newlines-between': {
             enum: [
               'ignore',
@@ -384,70 +898,397 @@ module.exports = {
               'never',
             ],
           },
+          'newlines-between-types': {
+            enum: [
+              'ignore',
+              'always',
+              'always-and-inside-groups',
+              'never',
+            ],
+          },
+          consolidateIslands: {
+            enum: [
+              'inside-groups',
+              'never',
+            ],
+          },
+          sortTypesGroup: {
+            type: 'boolean',
+            default: false,
+          },
+          named: {
+            default: false,
+            oneOf: [{
+              type: 'boolean',
+            }, {
+              type: 'object',
+              properties: {
+                enabled: { type: 'boolean' },
+                import: { type: 'boolean' },
+                export: { type: 'boolean' },
+                require: { type: 'boolean' },
+                cjsExports: { type: 'boolean' },
+                types: {
+                  type: 'string',
+                  enum: [
+                    'mixed',
+                    'types-first',
+                    'types-last',
+                  ],
+                },
+              },
+              additionalProperties: false,
+            }],
+          },
+          alphabetize: {
+            type: 'object',
+            properties: {
+              caseInsensitive: {
+                type: 'boolean',
+                default: false,
+              },
+              order: {
+                enum: ['ignore', 'asc', 'desc'],
+                default: 'ignore',
+              },
+              orderImportKind: {
+                enum: ['ignore', 'asc', 'desc'],
+                default: 'ignore',
+              },
+            },
+            additionalProperties: false,
+          },
+          warnOnUnassignedImports: {
+            type: 'boolean',
+            default: false,
+          },
         },
         additionalProperties: false,
+        dependencies: {
+          'newlines-between-types': {
+            properties: {
+              sortTypesGroup: { enum: [true] },
+            },
+            required: ['sortTypesGroup'],
+          },
+          consolidateIslands: {
+            anyOf: [{
+              properties: {
+                'newlines-between': { enum: ['always-and-inside-groups'] },
+              },
+              required: ['newlines-between'],
+            }, {
+              properties: {
+                'newlines-between-types': { enum: ['always-and-inside-groups'] },
+              },
+              required: ['newlines-between-types'],
+            }] },
+        },
       },
     ],
   },
 
-  create: function importOrderRule (context) {
-    const options = context.options[0] || {}
-    const newlinesBetweenImports = options['newlines-between'] || 'ignore'
-    let ranks
+  create(context) {
+    const options = context.options[0] || {};
+    const newlinesBetweenImports = options['newlines-between'] || 'ignore';
+    const newlinesBetweenTypeOnlyImports = options['newlines-between-types'] || newlinesBetweenImports;
+    const pathGroupsExcludedImportTypes = new Set(options.pathGroupsExcludedImportTypes || ['builtin', 'external', 'object']);
+    const sortTypesGroup = options.sortTypesGroup;
+    const consolidateIslands = options.consolidateIslands || 'never';
+
+    const named = {
+      types: 'mixed',
+      ...typeof options.named === 'object' ? {
+        ...options.named,
+        import: 'import' in options.named ? options.named.import : options.named.enabled,
+        export: 'export' in options.named ? options.named.export : options.named.enabled,
+        require: 'require' in options.named ? options.named.require : options.named.enabled,
+        cjsExports: 'cjsExports' in options.named ? options.named.cjsExports : options.named.enabled,
+      } : {
+        import: options.named,
+        export: options.named,
+        require: options.named,
+        cjsExports: options.named,
+      },
+    };
+
+    const namedGroups = named.types === 'mixed' ? [] : named.types === 'types-last' ? ['value'] : ['type'];
+    const alphabetize = getAlphabetizeConfig(options);
+    const distinctGroup = options.distinctGroup == null ? defaultDistinctGroup : !!options.distinctGroup;
+    let ranks;
 
     try {
-      ranks = convertGroupsToRanks(options.groups || defaultGroups)
+      const { pathGroups, maxPosition } = convertPathGroupsForRanks(options.pathGroups || []);
+      const { groups, omittedTypes } = convertGroupsToRanks(options.groups || defaultGroups);
+      ranks = {
+        groups,
+        omittedTypes,
+        pathGroups,
+        maxPosition,
+      };
     } catch (error) {
       // Malformed configuration
       return {
-        Program: function(node) {
-          context.report(node, error.message)
+        Program(node) {
+          context.report(node, error.message);
         },
+      };
+    }
+    const importMap = new Map();
+    const exportMap = new Map();
+
+    const isTypeGroupInGroups = ranks.omittedTypes.indexOf('type') === -1;
+    const isSortingTypesGroup = isTypeGroupInGroups && sortTypesGroup;
+
+    function getBlockImports(node) {
+      if (!importMap.has(node)) {
+        importMap.set(node, []);
       }
+      return importMap.get(node);
     }
-    let imported = []
-    let level = 0
 
-    function incrementLevel() {
-      level++
+    function getBlockExports(node) {
+      if (!exportMap.has(node)) {
+        exportMap.set(node, []);
+      }
+      return exportMap.get(node);
     }
-    function decrementLevel() {
-      level--
+
+    function makeNamedOrderReport(context, namedImports) {
+      if (namedImports.length > 1) {
+        const imports = namedImports.map(
+          (namedImport) => {
+            const kind = namedImport.kind || 'value';
+            const rank = namedGroups.findIndex((entry) => [].concat(entry).indexOf(kind) > -1);
+
+            return {
+              displayName: namedImport.value,
+              rank: rank === -1 ? namedGroups.length : rank,
+              ...namedImport,
+              value: `${namedImport.value}:${namedImport.alias || ''}`,
+            };
+          });
+
+        if (alphabetize.order !== 'ignore') {
+          mutateRanksToAlphabetize(imports, alphabetize);
+        }
+
+        makeOutOfOrderReport(context, imports, categories.named);
+      }
     }
 
     return {
-      ImportDeclaration: function handleImports(node) {
-        if (node.specifiers.length) { // Ignoring unassigned imports
-          const name = node.source.value
-          registerNode(context, node, name, 'import', ranks, imported)
+      ImportDeclaration(node) {
+        // Ignoring unassigned imports unless warnOnUnassignedImports is set
+        if (node.specifiers.length || options.warnOnUnassignedImports) {
+          const name = node.source.value;
+          registerNode(
+            context,
+            {
+              node,
+              value: name,
+              displayName: name,
+              type: 'import',
+            },
+            ranks,
+            getBlockImports(node.parent),
+            pathGroupsExcludedImportTypes,
+            isSortingTypesGroup,
+          );
+
+          if (named.import) {
+            makeNamedOrderReport(
+              context,
+              node.specifiers.filter(
+                (specifier) => specifier.type === 'ImportSpecifier').map(
+                (specifier) => ({
+                  node: specifier,
+                  value: specifier.imported.name,
+                  type: 'import',
+                  kind: specifier.importKind,
+                  ...specifier.local.range[0] !== specifier.imported.range[0] && {
+                    alias: specifier.local.name,
+                  },
+                }),
+              ),
+            );
+          }
         }
       },
-      CallExpression: function handleRequires(node) {
-        if (level !== 0 || !isStaticRequire(node) || !isInVariableDeclarator(node.parent)) {
-          return
+      TSImportEqualsDeclaration(node) {
+        // skip "export import"s
+        if (node.isExport) {
+          return;
         }
-        const name = node.arguments[0].value
-        registerNode(context, node, name, 'require', ranks, imported)
-      },
-      'Program:exit': function reportAndReset() {
-        makeOutOfOrderReport(context, imported)
 
-        if (newlinesBetweenImports !== 'ignore') {
-          makeNewlinesBetweenReport(context, imported, newlinesBetweenImports)
+        let displayName;
+        let value;
+        let type;
+        if (node.moduleReference.type === 'TSExternalModuleReference') {
+          value = node.moduleReference.expression.value;
+          displayName = value;
+          type = 'import';
+        } else {
+          value = '';
+          displayName = getSourceCode(context).getText(node.moduleReference);
+          type = 'import:object';
         }
 
-        imported = []
+        registerNode(
+          context,
+          {
+            node,
+            value,
+            displayName,
+            type,
+          },
+          ranks,
+          getBlockImports(node.parent),
+          pathGroupsExcludedImportTypes,
+          isSortingTypesGroup,
+        );
       },
-      FunctionDeclaration: incrementLevel,
-      FunctionExpression: incrementLevel,
-      ArrowFunctionExpression: incrementLevel,
-      BlockStatement: incrementLevel,
-      ObjectExpression: incrementLevel,
-      'FunctionDeclaration:exit': decrementLevel,
-      'FunctionExpression:exit': decrementLevel,
-      'ArrowFunctionExpression:exit': decrementLevel,
-      'BlockStatement:exit': decrementLevel,
-      'ObjectExpression:exit': decrementLevel,
-    }
+      CallExpression(node) {
+        if (!isStaticRequire(node)) {
+          return;
+        }
+        const block = getRequireBlock(node);
+        if (!block) {
+          return;
+        }
+        const name = node.arguments[0].value;
+        registerNode(
+          context,
+          {
+            node,
+            value: name,
+            displayName: name,
+            type: 'require',
+          },
+          ranks,
+          getBlockImports(block),
+          pathGroupsExcludedImportTypes,
+          isSortingTypesGroup,
+        );
+      },
+      ...named.require && {
+        VariableDeclarator(node) {
+          if (node.id.type === 'ObjectPattern' && isRequireExpression(node.init)) {
+            for (let i = 0; i < node.id.properties.length; i++) {
+              if (
+                node.id.properties[i].key.type !== 'Identifier'
+                || node.id.properties[i].value.type !== 'Identifier'
+              ) {
+                return;
+              }
+            }
+            makeNamedOrderReport(
+              context,
+              node.id.properties.map((prop) => ({
+                node: prop,
+                value: prop.key.name,
+                type: 'require',
+                ...prop.key.range[0] !== prop.value.range[0] && {
+                  alias: prop.value.name,
+                },
+              })),
+            );
+          }
+        },
+      },
+      ...named.export && {
+        ExportNamedDeclaration(node) {
+          makeNamedOrderReport(
+            context,
+            node.specifiers.map((specifier) => ({
+              node: specifier,
+              value: specifier.local.name,
+              type: 'export',
+              kind: specifier.exportKind,
+              ...specifier.local.range[0] !== specifier.exported.range[0] && {
+                alias: specifier.exported.name,
+              },
+            })),
+          );
+        },
+      },
+      ...named.cjsExports && {
+        AssignmentExpression(node) {
+          if (node.parent.type === 'ExpressionStatement') {
+            if (isCJSExports(context, node.left)) {
+              if (node.right.type === 'ObjectExpression') {
+                for (let i = 0; i < node.right.properties.length; i++) {
+                  if (
+                    !node.right.properties[i].key
+                    || node.right.properties[i].key.type !== 'Identifier'
+                    || !node.right.properties[i].value
+                    || node.right.properties[i].value.type !== 'Identifier'
+                  ) {
+                    return;
+                  }
+                }
+
+                makeNamedOrderReport(
+                  context,
+                  node.right.properties.map((prop) => ({
+                    node: prop,
+                    value: prop.key.name,
+                    type: 'export',
+                    ...prop.key.range[0] !== prop.value.range[0] && {
+                      alias: prop.value.name,
+                    },
+                  })),
+                );
+              }
+            } else {
+              const nameParts = getNamedCJSExports(context, node.left);
+              if (nameParts && nameParts.length > 0) {
+                const name = nameParts.join('.');
+                getBlockExports(node.parent.parent).push({
+                  node,
+                  value: name,
+                  displayName: name,
+                  type: 'export',
+                  rank: 0,
+                });
+              }
+            }
+          }
+        },
+      },
+      'Program:exit'() {
+        importMap.forEach((imported) => {
+          if (newlinesBetweenImports !== 'ignore' || newlinesBetweenTypeOnlyImports !== 'ignore') {
+            makeNewlinesBetweenReport(
+              context,
+              imported,
+              newlinesBetweenImports,
+              newlinesBetweenTypeOnlyImports,
+              distinctGroup,
+              isSortingTypesGroup,
+              consolidateIslands === 'inside-groups'
+                && (newlinesBetweenImports === 'always-and-inside-groups'
+                  || newlinesBetweenTypeOnlyImports === 'always-and-inside-groups'),
+            );
+          }
+
+          if (alphabetize.order !== 'ignore') {
+            mutateRanksToAlphabetize(imported, alphabetize);
+          }
+
+          makeOutOfOrderReport(context, imported, categories.import);
+        });
+
+        exportMap.forEach((exported) => {
+          if (alphabetize.order !== 'ignore') {
+            mutateRanksToAlphabetize(exported, alphabetize);
+            makeOutOfOrderReport(context, exported, categories.exports);
+          }
+        });
+
+        importMap.clear();
+        exportMap.clear();
+      },
+    };
   },
-}
+};
diff --git a/src/rules/prefer-default-export.js b/src/rules/prefer-default-export.js
index 59c26d11e..581f02502 100644
--- a/src/rules/prefer-default-export.js
+++ b/src/rules/prefer-default-export.js
@@ -1,92 +1,116 @@
-'use strict'
+'use strict';
 
-import docsUrl from '../docsUrl'
+import docsUrl from '../docsUrl';
+
+const SINGLE_EXPORT_ERROR_MESSAGE = 'Prefer default export on a file with single export.';
+const ANY_EXPORT_ERROR_MESSAGE = 'Prefer default export to be present on every file that has export.';
 
 module.exports = {
   meta: {
     type: 'suggestion',
     docs: {
+      category: 'Style guide',
+      description: 'Prefer a default export if module exports a single name or multiple names.',
       url: docsUrl('prefer-default-export'),
     },
+    schema: [{
+      type: 'object',
+      properties: {
+        target: {
+          type: 'string',
+          enum: ['single', 'any'],
+          default: 'single',
+        },
+      },
+      additionalProperties: false,
+    }],
   },
 
-  create: function(context) {
-    let specifierExportCount = 0
-    let hasDefaultExport = false
-    let hasStarExport = false
-    let namedExportNode = null
-
+  create(context) {
+    let specifierExportCount = 0;
+    let hasDefaultExport = false;
+    let hasStarExport = false;
+    let hasTypeExport = false;
+    let namedExportNode = null;
+    // get options. by default we look into files with single export
+    const { target = 'single' } =  context.options[0] || {};
     function captureDeclaration(identifierOrPattern) {
-      if (identifierOrPattern.type === 'ObjectPattern') {
+      if (identifierOrPattern && identifierOrPattern.type === 'ObjectPattern') {
         // recursively capture
         identifierOrPattern.properties
-          .forEach(function(property) {
-            captureDeclaration(property.value)
-          })
-      } else {
+          .forEach(function (property) {
+            captureDeclaration(property.value);
+          });
+      } else if (identifierOrPattern && identifierOrPattern.type === 'ArrayPattern') {
+        identifierOrPattern.elements
+          .forEach(captureDeclaration);
+      } else  {
       // assume it's a single standard identifier
-        specifierExportCount++
+        specifierExportCount++;
       }
     }
 
     return {
-      'ExportDefaultSpecifier': function() {
-        hasDefaultExport = true
+      ExportDefaultSpecifier() {
+        hasDefaultExport = true;
       },
 
-      'ExportSpecifier': function(node) {
-        if (node.exported.name === 'default') {
-          hasDefaultExport = true
+      ExportSpecifier(node) {
+        if ((node.exported.name || node.exported.value) === 'default') {
+          hasDefaultExport = true;
         } else {
-          specifierExportCount++
-          namedExportNode = node
+          specifierExportCount++;
+          namedExportNode = node;
         }
       },
 
-      'ExportNamedDeclaration': function(node) {
+      ExportNamedDeclaration(node) {
         // if there are specifiers, node.declaration should be null
-        if (!node.declaration) return
-
-        // don't warn on single type aliases, declarations, or interfaces
-        if (node.exportKind === 'type') return
+        if (!node.declaration) { return; }
 
-        const { type } = node.declaration
+        const { type } = node.declaration;
 
         if (
-          type === 'TSTypeAliasDeclaration' ||
-          type === 'TypeAlias' ||
-          type === 'TSInterfaceDeclaration' ||
-          type === 'InterfaceDeclaration'
+          type === 'TSTypeAliasDeclaration'
+          || type === 'TypeAlias'
+          || type === 'TSInterfaceDeclaration'
+          || type === 'InterfaceDeclaration'
         ) {
-          return
+          specifierExportCount++;
+          hasTypeExport = true;
+          return;
         }
 
         if (node.declaration.declarations) {
-          node.declaration.declarations.forEach(function(declaration) {
-            captureDeclaration(declaration.id)
-          })
-        }
-        else {
+          node.declaration.declarations.forEach(function (declaration) {
+            captureDeclaration(declaration.id);
+          });
+        } else {
           // captures 'export function foo() {}' syntax
-          specifierExportCount++
+          specifierExportCount++;
         }
 
-        namedExportNode = node
+        namedExportNode = node;
       },
 
-      'ExportDefaultDeclaration': function() {
-        hasDefaultExport = true
+      ExportDefaultDeclaration() {
+        hasDefaultExport = true;
       },
 
-      'ExportAllDeclaration': function() {
-        hasStarExport = true
+      ExportAllDeclaration() {
+        hasStarExport = true;
       },
 
-      'Program:exit': function() {
-        if (specifierExportCount === 1 && !hasDefaultExport && !hasStarExport) {
-          context.report(namedExportNode, 'Prefer default export.')
+      'Program:exit'() {
+        if (hasDefaultExport || hasStarExport || hasTypeExport) {
+          return;
+        }
+        if (target === 'single' && specifierExportCount === 1) {
+          context.report(namedExportNode, SINGLE_EXPORT_ERROR_MESSAGE);
+        } else if (target === 'any' && specifierExportCount > 0) {
+          context.report(namedExportNode, ANY_EXPORT_ERROR_MESSAGE);
         }
       },
-    }
+    };
   },
-}
+};
diff --git a/src/rules/unambiguous.js b/src/rules/unambiguous.js
index 7ec38c2cb..2491fad3e 100644
--- a/src/rules/unambiguous.js
+++ b/src/rules/unambiguous.js
@@ -3,33 +3,37 @@
  * @author Ben Mosher
  */
 
-import { isModule } from 'eslint-module-utils/unambiguous'
-import docsUrl from '../docsUrl'
+import { isModule } from 'eslint-module-utils/unambiguous';
+import docsUrl from '../docsUrl';
+import sourceType from '../core/sourceType';
 
 module.exports = {
   meta: {
     type: 'suggestion',
     docs: {
+      category: 'Module systems',
+      description: 'Forbid potentially ambiguous parse goal (`script` vs. `module`).',
       url: docsUrl('unambiguous'),
     },
+    schema: [],
   },
 
-  create: function (context) {
+  create(context) {
     // ignore non-modules
-    if (context.parserOptions.sourceType !== 'module') {
-      return {}
+    if (sourceType(context) !== 'module') {
+      return {};
     }
 
     return {
-      Program: function (ast) {
+      Program(ast) {
         if (!isModule(ast)) {
           context.report({
             node: ast,
             message: 'This module could be parsed as a valid script.',
-          })
+          });
         }
       },
-    }
+    };
 
   },
-}
+};
diff --git a/src/scc.js b/src/scc.js
new file mode 100644
index 000000000..c2b2c637d
--- /dev/null
+++ b/src/scc.js
@@ -0,0 +1,92 @@
+import calculateScc from '@rtsao/scc';
+import { hashObject } from 'eslint-module-utils/hash';
+import resolve from 'eslint-module-utils/resolve';
+import ExportMapBuilder from './exportMap/builder';
+import childContext from './exportMap/childContext';
+
+let cache = new Map();
+
+export default class StronglyConnectedComponentsBuilder {
+  static clearCache() {
+    cache = new Map();
+  }
+
+  static get(source, context) {
+    const path = resolve(source, context);
+    if (path == null) { return null; }
+    return StronglyConnectedComponentsBuilder.for(childContext(path, context));
+  }
+
+  static for(context) {
+    const settingsHash = hashObject({
+      settings: context.settings,
+      parserOptions: context.parserOptions,
+      parserPath: context.parserPath,
+    }).digest('hex');
+    const cacheKey = context.path + settingsHash;
+    if (cache.has(cacheKey)) {
+      return cache.get(cacheKey);
+    }
+    const scc = StronglyConnectedComponentsBuilder.calculate(context);
+    const visitedFiles = Object.keys(scc);
+    visitedFiles.forEach((filePath) => cache.set(filePath + settingsHash, scc));
+    return scc;
+  }
+
+  static calculate(context) {
+    const exportMap = ExportMapBuilder.for(context);
+    const adjacencyList = this.exportMapToAdjacencyList(exportMap);
+    const calculatedScc = calculateScc(adjacencyList);
+    return StronglyConnectedComponentsBuilder.calculatedSccToPlainObject(calculatedScc);
+  }
+
+  /** @returns {Map<string, Set<string>>} for each dep, what are its direct deps */
+  static exportMapToAdjacencyList(initialExportMap) {
+    const adjacencyList = new Map();
+    // BFS
+    function visitNode(exportMap) {
+      if (!exportMap) {
+        return;
+      }
+      exportMap.imports.forEach((v, importedPath) => {
+        const from = exportMap.path;
+        const to = importedPath;
+
+        // Ignore type-only imports, because we care only about SCCs of value imports
+        const toTraverse = [...v.declarations].filter(({ isOnlyImportingTypes }) => !isOnlyImportingTypes);
+        if (toTraverse.length === 0) { return; }
+
+        if (!adjacencyList.has(from)) {
+          adjacencyList.set(from, new Set());
+        }
+
+        if (adjacencyList.get(from).has(to)) {
+          return; // prevent endless loop
+        }
+        adjacencyList.get(from).add(to);
+        visitNode(v.getter());
+      });
+    }
+    visitNode(initialExportMap);
+    // Fill gaps
+    adjacencyList.forEach((values) => {
+      values.forEach((value) => {
+        if (!adjacencyList.has(value)) {
+          adjacencyList.set(value, new Set());
+        }
+      });
+    });
+    return adjacencyList;
+  }
+
+  /** @returns {Record<string, number>} for each key, its SCC's index */
+  static calculatedSccToPlainObject(sccs) {
+    const obj = {};
+    sccs.forEach((scc, index) => {
+      scc.forEach((node) => {
+        obj[node] = index;
+      });
+    });
+    return obj;
+  }
+}
diff --git a/test/mocha.opts b/test/mocha.opts
new file mode 100644
index 000000000..0be6eb2fd
--- /dev/null
+++ b/test/mocha.opts
@@ -0,0 +1,3 @@
+--reporter=dot
+--recursive
+-t 5s
diff --git a/tests/.eslintrc b/tests/.eslintrc
deleted file mode 100644
index 700a3d688..000000000
--- a/tests/.eslintrc
+++ /dev/null
@@ -1,6 +0,0 @@
----
-env:
-  mocha: true
-rules:
-  no-unused-expressions: 0
-  max-len: 0
diff --git a/tests/dep-time-travel.sh b/tests/dep-time-travel.sh
index 996ed0b1c..665ca1ccf 100755
--- a/tests/dep-time-travel.sh
+++ b/tests/dep-time-travel.sh
@@ -2,21 +2,39 @@
 
 # expected: ESLINT_VERSION numeric env var
 
-npm install --no-save eslint@$ESLINT_VERSION --ignore-scripts || true
+echo "installing ${ESLINT_VERSION} in node ${TRAVIS_NODE_VERSION} with TS parser ${TS_PARSER:-default}..."
 
-# completely remove the new typescript parser for ESLint < v5
-if [[ "$ESLINT_VERSION" -lt "5" ]]; then
+export NPM_CONFIG_LEGACY_PEER_DEPS=true
+
+if [[ "$ESLINT_VERSION" -lt "7" ]]; then
+  echo "Removing @angular-eslint/template-parser..."
+  npm uninstall --no-save @angular-eslint/template-parser
+fi
+
+npm install --no-save "eslint@${ESLINT_VERSION}" --ignore-scripts
+
+if [[ -n "$TS_PARSER" ]]; then # if TS parser is manually set, always use it
+  echo "Downgrading @typescript-eslint/parser..."
+  npm i --no-save "@typescript-eslint/parser@${TS_PARSER}"
+elif [[ "$ESLINT_VERSION" -lt "5" ]]; then # completely remove the new TypeScript parser for ESLint < v5
   echo "Removing @typescript-eslint/parser..."
   npm uninstall --no-save @typescript-eslint/parser
+elif [[ "$TRAVIS_NODE_VERSION" -lt "10" ]]; then # TS parser 3 requires node 10+
+  npm i --no-save "@typescript-eslint/parser@3"
+elif [[ "$TRAVIS_NODE_VERSION" -lt "12" ]]; then # TS parser 4 requires node 12+
+  npm i --no-save "@typescript-eslint/parser@4"
 fi
 
-# use these alternate typescript dependencies for ESLint < v4
+# use these alternate TypeScript dependencies for ESLint < v4
 if [[ "$ESLINT_VERSION" -lt "4" ]]; then
   echo "Downgrading babel-eslint..."
   npm i --no-save babel-eslint@8.0.3
 
   echo "Downgrading TypeScript dependencies..."
   npm i --no-save typescript-eslint-parser@15 typescript@2.8.1
+elif [[ "$ESLINT_VERSION" -lt "7" ]]; then
+  echo "Downgrading TypeScript dependencies..."
+  npm i --no-save typescript-eslint-parser@20
 fi
 
 # typescript-eslint-parser 1.1.1+ is not compatible with node 6
@@ -24,3 +42,11 @@ if [[ "$TRAVIS_NODE_VERSION" -lt "8" ]]; then
   echo "Downgrading eslint-import-resolver-typescript..."
   npm i --no-save eslint-import-resolver-typescript@1.0.2
 fi
+
+if [ "${ESLINT_VERSION}" = '8' ]; then
+  # This is a workaround for the crash in the initial processing of the ESLint class.
+  echo "Installing self"
+  npm i --no-save eslint-plugin-import@'.' -f
+  echo "Build self"
+  npm run build
+fi
diff --git a/tests/files/.eslintrc b/tests/files/.eslintrc
deleted file mode 100644
index 5970c5fa1..000000000
--- a/tests/files/.eslintrc
+++ /dev/null
@@ -1,5 +0,0 @@
----
-parser: 'babel-eslint'
-parserOptions:
-  ecmaFeatures:
-    jsx: true
diff --git a/tests/files/.eslintrc.js b/tests/files/.eslintrc.js
new file mode 100644
index 000000000..6f2529801
--- /dev/null
+++ b/tests/files/.eslintrc.js
@@ -0,0 +1,321 @@
+const eslintPkg = require('eslint/package.json');
+const semver = require('semver');
+
+const supportsArbitraryModuleNamespaceIdentifierNames = semver.satisfies(eslintPkg.version, '>= 8.7');
+
+const config = {
+  "parser": "babel-eslint",
+  "parserOptions": {
+    "sourceType": "module",
+    "ecmaVersion": 8
+  },
+  "rules": {
+    "accessor-pairs": 0,
+    "array-bracket-newline": 0,
+    "array-bracket-spacing": 0,
+    "array-callback-return": 0,
+    "array-element-newline": 0,
+    "arrow-body-style": 0,
+    "arrow-parens": 0,
+    "arrow-spacing": 0,
+    "block-scoped-var": 0,
+    "block-spacing": 0,
+    "brace-style": 0,
+    "callback-return": 0,
+    "camelcase": 0,
+    "capitalized-comments": 0,
+    "class-methods-use-this": 0,
+    "comma-dangle": 0,
+    "comma-spacing": 0,
+    "comma-style": 0,
+    "complexity": 0,
+    "computed-property-spacing": 0,
+    "consistent-return": 0,
+    "consistent-this": 0,
+    "constructor-super": 0,
+    "curly": 0,
+    "default-case": 0,
+    "dot-location": 0,
+    "dot-notation": 0,
+    "eol-last": 0,
+    "eqeqeq": 0,
+    "for-direction": 0,
+    "func-call-spacing": 0,
+    "func-name-matching": 0,
+    "func-names": 0,
+    "func-style": 0,
+    "function-paren-newline": 0,
+    "generator-star-spacing": 0,
+    "getter-return": 0,
+    "global-require": 0,
+    "guard-for-in": 0,
+    "handle-callback-err": 0,
+    "id-blacklist": 0,
+    "id-length": 0,
+    "id-match": 0,
+    "implicit-arrow-linebreak": 0,
+    "indent": 0,
+    "indent-legacy": 0,
+    "init-declarations": 0,
+    "jsx-quotes": 0,
+    "key-spacing": 0,
+    "keyword-spacing": 0,
+    "line-comment-position": 0,
+    "linebreak-style": 0,
+    "lines-around-comment": 0,
+    "lines-around-directive": 0,
+    "lines-between-class-members": 0,
+    "max-classes-per-file": 0,
+    "max-depth": 0,
+    "max-len": 0,
+    "max-lines": 0,
+    "max-lines-per-function": 0,
+    "max-nested-callbacks": 0,
+    "max-params": 0,
+    "max-statements": 0,
+    "max-statements-per-line": 0,
+    "multiline-comment-style": 0,
+    "multiline-ternary": 0,
+    "new-cap": 0,
+    "new-parens": 0,
+    "newline-after-var": 0,
+    "newline-before-return": 0,
+    "newline-per-chained-call": 0,
+    "no-alert": 0,
+    "no-array-constructor": 0,
+    "no-async-promise-executor": 0,
+    "no-await-in-loop": 0,
+    "no-bitwise": 0,
+    "no-buffer-constructor": 0,
+    "no-caller": 0,
+    "no-case-declarations": 0,
+    "no-catch-shadow": 0,
+    "no-class-assign": 0,
+    "no-compare-neg-zero": 0,
+    "no-cond-assign": 0,
+    "no-confusing-arrow": 0,
+    "no-console": 0,
+    "no-const-assign": 0,
+    "no-constant-condition": 0,
+    "no-continue": 0,
+    "no-control-regex": 0,
+    "no-debugger": 0,
+    "no-delete-var": 0,
+    "no-div-regex": 0,
+    "no-dupe-args": 0,
+    "no-dupe-class-members": 0,
+    "no-dupe-keys": 0,
+    "no-duplicate-case": 0,
+    "no-duplicate-imports": 0,
+    "no-else-return": 0,
+    "no-empty": 0,
+    "no-empty-character-class": 0,
+    "no-empty-function": 0,
+    "no-empty-pattern": 0,
+    "no-eq-null": 0,
+    "no-eval": 0,
+    "no-ex-assign": 0,
+    "no-extend-native": 0,
+    "no-extra-bind": 0,
+    "no-extra-boolean-cast": 0,
+    "no-extra-label": 0,
+    "no-extra-parens": 0,
+    "no-extra-semi": 0,
+    "no-fallthrough": 0,
+    "no-floating-decimal": 0,
+    "no-func-assign": 0,
+    "no-global-assign": 0,
+    "no-implicit-coercion": 0,
+    "no-implicit-globals": 0,
+    "no-implied-eval": 0,
+    "no-inline-comments": 0,
+    "no-inner-declarations": 0,
+    "no-invalid-regexp": 0,
+    "no-invalid-this": 0,
+    "no-irregular-whitespace": 0,
+    "no-iterator": 0,
+    "no-label-var": 0,
+    "no-labels": 0,
+    "no-lone-blocks": 0,
+    "no-lonely-if": 0,
+    "no-loop-func": 0,
+    "no-magic-numbers": 0,
+    "no-misleading-character-class": 0,
+    "no-mixed-operators": 0,
+    "no-mixed-requires": 0,
+    "no-mixed-spaces-and-tabs": 0,
+    "no-multi-assign": 0,
+    "no-multi-spaces": 0,
+    "no-multi-str": 0,
+    "no-multiple-empty-lines": 0,
+    "no-native-reassign": 0,
+    "no-negated-condition": 0,
+    "no-negated-in-lhs": 0,
+    "no-nested-ternary": 0,
+    "no-new": 0,
+    "no-new-func": 0,
+    "no-new-object": 0,
+    "no-new-require": 0,
+    "no-new-symbol": 0,
+    "no-new-wrappers": 0,
+    "no-obj-calls": 0,
+    "no-octal": 0,
+    "no-octal-escape": 0,
+    "no-param-reassign": 0,
+    "no-path-concat": 0,
+    "no-plusplus": 0,
+    "no-process-env": 0,
+    "no-process-exit": 0,
+    "no-proto": 0,
+    "no-prototype-builtins": 0,
+    "no-redeclare": 0,
+    "no-regex-spaces": 0,
+    "no-restricted-globals": 0,
+    "no-restricted-imports": 0,
+    "no-restricted-modules": 0,
+    "no-restricted-properties": 0,
+    "no-restricted-syntax": 0,
+    "no-return-assign": 0,
+    "no-return-await": 0,
+    "no-script-url": 0,
+    "no-self-assign": 0,
+    "no-self-compare": 0,
+    "no-sequences": 0,
+    "no-shadow": 0,
+    "no-shadow-restricted-names": 0,
+    "no-spaced-func": 0,
+    "no-sparse-arrays": 0,
+    "no-sync": 0,
+    "no-tabs": 0,
+    "no-template-curly-in-string": 0,
+    "no-ternary": 0,
+    "no-this-before-super": 0,
+    "no-throw-literal": 0,
+    "no-trailing-spaces": 0,
+    "no-undef": 0,
+    "no-undef-init": 0,
+    "no-undefined": 0,
+    "no-underscore-dangle": 0,
+    "no-unexpected-multiline": 0,
+    "no-unmodified-loop-condition": 0,
+    "no-unneeded-ternary": 0,
+    "no-unreachable": 0,
+    "no-unsafe-finally": 0,
+    "no-unsafe-negation": 0,
+    "no-unused-expressions": 0,
+    "no-unused-labels": 0,
+    "no-unused-vars": 0,
+    "no-use-before-define": 0,
+    "no-useless-call": 0,
+    "no-useless-catch": 0,
+    "no-useless-computed-key": 0,
+    "no-useless-concat": 0,
+    "no-useless-constructor": 0,
+    "no-useless-escape": 0,
+    "no-useless-rename": 0,
+    "no-useless-return": 0,
+    "no-var": 0,
+    "no-void": 0,
+    "no-warning-comments": 0,
+    "no-whitespace-before-property": 0,
+    "no-with": 0,
+    "nonblock-statement-body-position": 0,
+    "object-curly-newline": 0,
+    "object-curly-spacing": 0,
+    "object-property-newline": 0,
+    "object-shorthand": 0,
+    "one-var": 0,
+    "one-var-declaration-per-line": 0,
+    "operator-assignment": 0,
+    "operator-linebreak": 0,
+    "padded-blocks": 0,
+    "padding-line-between-statements": 0,
+    "prefer-arrow-callback": 0,
+    "prefer-const": 0,
+    "prefer-destructuring": 0,
+    "prefer-named-capture-group": 0,
+    "prefer-numeric-literals": 0,
+    "prefer-object-spread": 0,
+    "prefer-promise-reject-errors": 0,
+    "prefer-reflect": 0,
+    "prefer-rest-params": 0,
+    "prefer-spread": 0,
+    "prefer-template": 0,
+    "quote-props": 0,
+    "quotes": 0,
+    "radix": 0,
+    "require-atomic-updates": 0,
+    "require-await": 0,
+    "require-jsdoc": 0,
+    "require-unicode-regexp": 0,
+    "require-yield": 0,
+    "rest-spread-spacing": 0,
+    "semi": 0,
+    "semi-spacing": 0,
+    "semi-style": 0,
+    "sort-imports": 0,
+    "sort-keys": 0,
+    "sort-vars": 0,
+    "space-before-blocks": 0,
+    "space-before-function-paren": 0,
+    "space-in-parens": 0,
+    "space-infix-ops": 0,
+    "space-unary-ops": 0,
+    "spaced-comment": 0,
+    "strict": 0,
+    "switch-colon-spacing": 0,
+    "symbol-description": 0,
+    "template-curly-spacing": 0,
+    "template-tag-spacing": 0,
+    "unicode-bom": 0,
+    "use-isnan": 0,
+    "valid-jsdoc": 0,
+    "valid-typeof": 0,
+    "vars-on-top": 0,
+    "wrap-iife": 0,
+    "wrap-regex": 0,
+    "yield-star-spacing": 0,
+    "yoda": 0,
+    "import/no-unresolved": 0,
+    "import/named": 0,
+    "import/namespace": 0,
+    "import/default": 0,
+    "import/export": 0,
+    "import/no-named-as-default": 0,
+    "import/no-named-as-default-member": 0,
+    "import/no-duplicates": 0,
+    "import/no-extraneous-dependencies": 0,
+    "import/unambiguous": 0
+  },
+  ignorePatterns: [
+    "default-export-namespace-string.js",
+    "default-export-string.js",
+    "export-default-string-and-named.js",
+    "no-unused-modules/arbitrary-module-namespace-identifier-name-a.js",
+    "no-unused-modules/arbitrary-module-namespace-identifier-name-b.js",
+    "no-unused-modules/arbitrary-module-namespace-identifier-name-c.js"
+  ],
+}
+
+if (supportsArbitraryModuleNamespaceIdentifierNames) {
+  config.ignorePatterns = [];
+  config.overrides = [
+    // For parsing arbitrary module namespace names
+    {
+      "files": [
+        "default-export-namespace-string.js",
+        "default-export-string.js",
+        "export-default-string-and-named.js",
+        "no-unused-modules/arbitrary-module-namespace-identifier-name-a.js",
+        "no-unused-modules/arbitrary-module-namespace-identifier-name-b.js",
+        "no-unused-modules/arbitrary-module-namespace-identifier-name-c.js"
+      ],
+      "parser": "espree",
+      "parserOptions": {
+        "ecmaVersion": 2022
+      }
+    }
+  ];
+}
+
+module.exports = config;
diff --git a/tests/files/bundled-dependencies/as-array-bundle-deps/node_modules/@generated/bar/index.js b/tests/files/bundled-dependencies/as-array-bundle-deps/node_modules/@generated/bar/index.js
new file mode 100644
index 000000000..e69de29bb
diff --git a/tests/files/bundled-dependencies/as-array-bundle-deps/node_modules/@generated/foo/index.js b/tests/files/bundled-dependencies/as-array-bundle-deps/node_modules/@generated/foo/index.js
new file mode 100644
index 000000000..e69de29bb
diff --git a/tests/files/bundled-dependencies/as-array-bundle-deps/package.json b/tests/files/bundled-dependencies/as-array-bundle-deps/package.json
new file mode 100644
index 000000000..ef9c675ed
--- /dev/null
+++ b/tests/files/bundled-dependencies/as-array-bundle-deps/package.json
@@ -0,0 +1,4 @@
+{
+  "dummy": true,
+  "bundleDependencies": ["@generated/foo"]
+}
diff --git a/tests/files/bundled-dependencies/as-object/node_modules/@generated/bar/index.js b/tests/files/bundled-dependencies/as-object/node_modules/@generated/bar/index.js
new file mode 100644
index 000000000..e69de29bb
diff --git a/tests/files/bundled-dependencies/as-object/node_modules/@generated/foo/index.js b/tests/files/bundled-dependencies/as-object/node_modules/@generated/foo/index.js
new file mode 100644
index 000000000..e69de29bb
diff --git a/tests/files/bundled-dependencies/as-object/package.json b/tests/files/bundled-dependencies/as-object/package.json
new file mode 100644
index 000000000..1a5baff5a
--- /dev/null
+++ b/tests/files/bundled-dependencies/as-object/package.json
@@ -0,0 +1,4 @@
+{
+  "dummy": true,
+  "bundledDependencies": {"@generated/foo": "latest"}
+}
diff --git a/tests/files/bundled-dependencies/race-condition/node_modules/@generated/bar/index.js b/tests/files/bundled-dependencies/race-condition/node_modules/@generated/bar/index.js
new file mode 100644
index 000000000..e69de29bb
diff --git a/tests/files/bundled-dependencies/race-condition/node_modules/@generated/foo/index.js b/tests/files/bundled-dependencies/race-condition/node_modules/@generated/foo/index.js
new file mode 100644
index 000000000..e69de29bb
diff --git a/tests/files/bundled-dependencies/race-condition/package.json b/tests/files/bundled-dependencies/race-condition/package.json
new file mode 100644
index 000000000..827ecc583
--- /dev/null
+++ b/tests/files/bundled-dependencies/race-condition/package.json
@@ -0,0 +1,5 @@
+{
+  "dummy": true,
+  "bundledDependencies": {"@generated/bar": "latest"},
+  "bundleDependencies": ["@generated/foo"]
+}
diff --git a/tests/files/color.js b/tests/files/color.js
new file mode 100644
index 000000000..dcdbf84ac
--- /dev/null
+++ b/tests/files/color.js
@@ -0,0 +1 @@
+export const example = 'example';
diff --git a/tests/files/component.html b/tests/files/component.html
new file mode 100644
index 000000000..b63f55e0b
--- /dev/null
+++ b/tests/files/component.html
@@ -0,0 +1,139 @@
+<header class="flex">
+  <svg width="40" height="40" viewBox="0 0 262 163">
+    <polygon
+      id="Path"
+      fill="#ffffff"
+      points="130.68 104.59 97.49 52.71 97.44 96.3 40.24 0 0 0 0 162.57 39.79 162.57 39.92 66.39 96.53 158.26"
+    ></polygon>
+    <polygon
+      id="Path"
+      fill="#ffffff"
+      points="97.5 41.79 137.24 41.79 137.33 41.33 137.33 0 97.54 0 97.49 41.33"
+    ></polygon>
+    <path
+      d="M198.66,86.86 C189.139872,86.6795216 180.538723,92.516445 177.19,101.43 C182.764789,93.0931021 193.379673,89.7432211 202.73,93.37 C207.05,95.13 212.73,97.97 217.23,96.45 C212.950306,90.4438814 206.034895,86.8725952 198.66,86.86 L198.66,86.86 Z"
+      id="Path"
+      fill="#96D8E9"
+    ></path>
+    <path
+      d="M243.75,106.42 C243.75,101.55 241.1,100.42 235.6,98.42 C231.52,97 226.89,95.4 223.52,91 C222.86,90.13 222.25,89.15 221.6,88.11 C220.14382,85.4164099 218.169266,83.037429 215.79,81.11 C212.58,78.75 208.37,77.6 202.91,77.6 C191.954261,77.6076705 182.084192,84.2206169 177.91,94.35 C183.186964,87.0278244 191.956716,83.0605026 200.940147,83.9314609 C209.923578,84.8024193 217.767888,90.3805017 221.54,98.58 C223.424615,101.689762 227.141337,103.174819 230.65,102.22 C236.02,101.07 235.65,106.15 243.76,107.87 L243.75,106.42 Z"
+      id="Path"
+      fill="#48C4E5"
+    ></path>
+    <path
+      d="M261.46,105.38 L261.46,105.27 C261.34,73.03 235.17,45.45 202.91,45.45 C183.207085,45.4363165 164.821777,55.3450614 154,71.81 L153.79,71.45 L137.23,45.45 L97.5,45.4499858 L135.25,104.57 L98.41,162.57 L137,162.57 L153.79,136.78 L170.88,162.57 L209.48,162.57 L174.48,107.49 C173.899005,106.416838 173.583536,105.220114 173.56,104 C173.557346,96.2203871 176.64661,88.7586448 182.147627,83.2576275 C187.648645,77.7566101 195.110387,74.6673462 202.89,74.67 C219.11,74.67 221.82,84.37 225.32,88.93 C232.23,97.93 246.03,93.99 246.03,105.73 L246.03,105.73 C246.071086,108.480945 247.576662,111.001004 249.979593,112.340896 C252.382524,113.680787 255.317747,113.636949 257.679593,112.225896 C260.041438,110.814842 261.471086,108.250945 261.43,105.5 L261.43,105.5 L261.43,105.38 L261.46,105.38 Z"
+      id="Path"
+      fill="#ffffff"
+    ></path>
+    <path
+      d="M261.5,113.68 C261.892278,116.421801 261.504116,119.218653 260.38,121.75 C258.18,126.84 254.51,125.14 254.51,125.14 C254.51,125.14 251.35,123.6 253.27,120.65 C255.4,117.36 259.61,117.74 261.5,113.68 Z"
+      id="Path"
+      fill="#022f56"
+    ></path>
+  </svg>
+  <h1>Welcome to {{ title }}!</h1>
+</header>
+<main>
+  <h2>Resources &amp; Tools</h2>
+  <p>Thank you for using and showing some ♥ for Nx.</p>
+  <div class="flex github-star-container">
+    <a
+      href="https://github.com/nrwl/nx"
+      target="_blank"
+      rel="noopener noreferrer"
+    >
+      If you like Nx, please give it a star:
+      <div class="github-star-badge">
+        <svg
+          class="material-icons"
+          xmlns="http://www.w3.org/2000/svg"
+          width="24"
+          height="24"
+          viewBox="0 0 24 24"
+        >
+          <path d="M0 0h24v24H0z" fill="none" />
+          <path
+            d="M12 17.27L18.18 21l-1.64-7.03L22 9.24l-7.19-.61L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21z"
+          />
+        </svg>
+        Star
+      </div>
+    </a>
+  </div>
+  <p>Here are some links to help you get started.</p>
+  <ul class="resources">
+    <li class="col-span-2">
+      <a class="resource flex" href="https://nxplaybook.com/p/nx-workspaces">
+        Nx video course
+      </a>
+    </li>
+    <li class="col-span-2">
+      <a
+        class="resource flex"
+        href="https://nx.dev/latest/angular/getting-started/intro"
+      >
+        Nx video tutorial
+      </a>
+    </li>
+    <li class="col-span-2">
+      <a
+        class="resource flex"
+        href="https://nx.dev/latest/angular/tutorial/01-create-application"
+      >
+        Interactive tutorial
+      </a>
+    </li>
+    <li class="col-span-2">
+      <a class="resource flex" href="https://nx.app/">
+        <svg
+          width="36"
+          height="36"
+          viewBox="0 0 120 120"
+          fill="none"
+          xmlns="http://www.w3.org/2000/svg"
+        >
+          <path
+            d="M120 15V30C103.44 30 90 43.44 90 60C90 76.56 76.56 90 60 90C43.44 90 30 103.44 30 120H15C6.72 120 0 113.28 0 105V15C0 6.72 6.72 0 15 0H105C113.28 0 120 6.72 120 15Z"
+            fill="#0E2039"
+          />
+          <path
+            d="M120 30V105C120 113.28 113.28 120 105 120H30C30 103.44 43.44 90 60 90C76.56 90 90 76.56 90 60C90 43.44 103.44 30 120 30Z"
+            fill="white"
+          />
+        </svg>
+        <span class="gutter-left">Nx Cloud</span>
+      </a>
+    </li>
+  </ul>
+  <h2>Next Steps</h2>
+  <p>Here are some things you can do with Nx.</p>
+  <details open>
+    <summary>Add UI library</summary>
+    <pre>
+  # Generate UI lib
+  nx g @nrwl/angular:lib ui
+
+  # Add a component
+  nx g @nrwl/angular:component xyz --project ui</pre
+    >
+  </details>
+  <details>
+    <summary>View dependency graph</summary>
+    <pre>nx dep-graph</pre>
+  </details>
+  <details>
+    <summary>Run affected commands</summary>
+    <pre>
+  # see what's been affected by changes
+  nx affected:dep-graph
+
+  # run tests for current changes
+  nx affected:test
+
+  # run e2e tests for current changes
+  nx affected:e2e
+  </pre
+    >
+  </details>
+</main>
+
diff --git a/tests/files/cycles/depth-one.js b/tests/files/cycles/depth-one.js
deleted file mode 100644
index 748f65f84..000000000
--- a/tests/files/cycles/depth-one.js
+++ /dev/null
@@ -1,2 +0,0 @@
-import foo from "./depth-zero"
-export { foo }
diff --git a/tests/files/cycles/es6/depth-one-dynamic.js b/tests/files/cycles/es6/depth-one-dynamic.js
new file mode 100644
index 000000000..32dd3db4e
--- /dev/null
+++ b/tests/files/cycles/es6/depth-one-dynamic.js
@@ -0,0 +1 @@
+export const bar = () => import("../depth-zero").then(({foo}) => foo);
diff --git a/tests/files/cycles/es6/depth-one-reexport.js b/tests/files/cycles/es6/depth-one-reexport.js
new file mode 100644
index 000000000..df509fa51
--- /dev/null
+++ b/tests/files/cycles/es6/depth-one-reexport.js
@@ -0,0 +1 @@
+export { foo } from "../depth-zero";
diff --git a/tests/files/cycles/es6/depth-one.js b/tests/files/cycles/es6/depth-one.js
new file mode 100644
index 000000000..9caa76250
--- /dev/null
+++ b/tests/files/cycles/es6/depth-one.js
@@ -0,0 +1,2 @@
+import foo from "../depth-zero"
+export { foo }
diff --git a/tests/files/cycles/depth-three-indirect.js b/tests/files/cycles/es6/depth-three-indirect.js
similarity index 100%
rename from tests/files/cycles/depth-three-indirect.js
rename to tests/files/cycles/es6/depth-three-indirect.js
diff --git a/tests/files/cycles/depth-three-star.js b/tests/files/cycles/es6/depth-three-star.js
similarity index 100%
rename from tests/files/cycles/depth-three-star.js
rename to tests/files/cycles/es6/depth-three-star.js
diff --git a/tests/files/cycles/depth-two.js b/tests/files/cycles/es6/depth-two.js
similarity index 100%
rename from tests/files/cycles/depth-two.js
rename to tests/files/cycles/es6/depth-two.js
diff --git a/tests/files/cycles/external-depth-two.js b/tests/files/cycles/external-depth-two.js
new file mode 100644
index 000000000..fbb6bfcbb
--- /dev/null
+++ b/tests/files/cycles/external-depth-two.js
@@ -0,0 +1,2 @@
+import { foo } from "cycles/external/depth-one"
+export { foo }
diff --git a/tests/files/cycles/external/depth-one.js b/tests/files/cycles/external/depth-one.js
new file mode 100644
index 000000000..9caa76250
--- /dev/null
+++ b/tests/files/cycles/external/depth-one.js
@@ -0,0 +1,2 @@
+import foo from "../depth-zero"
+export { foo }
diff --git a/tests/files/cycles/flow-typeof.js b/tests/files/cycles/flow-typeof.js
new file mode 100644
index 000000000..7c63f9ab7
--- /dev/null
+++ b/tests/files/cycles/flow-typeof.js
@@ -0,0 +1,4 @@
+// @flow
+import typeof Foo from './depth-zero';
+import { typeof Bar } from './depth-zero';
+import typeof { Bar } from './depth-zero';
diff --git a/tests/files/cycles/flow-types-depth-one.js b/tests/files/cycles/flow-types-depth-one.js
new file mode 100644
index 000000000..f8a7a4b47
--- /dev/null
+++ b/tests/files/cycles/flow-types-depth-one.js
@@ -0,0 +1,6 @@
+// @flow
+
+import type { FooType } from './flow-types-depth-two';
+import { type BarType, bar } from './flow-types-depth-two';
+
+export { bar }
diff --git a/tests/files/cycles/flow-types-depth-two.js b/tests/files/cycles/flow-types-depth-two.js
new file mode 100644
index 000000000..64a0a8359
--- /dev/null
+++ b/tests/files/cycles/flow-types-depth-two.js
@@ -0,0 +1 @@
+import { foo } from './es6/depth-one'
diff --git a/tests/files/cycles/flow-types-only-importing-multiple-types.js b/tests/files/cycles/flow-types-only-importing-multiple-types.js
new file mode 100644
index 000000000..ab61606fd
--- /dev/null
+++ b/tests/files/cycles/flow-types-only-importing-multiple-types.js
@@ -0,0 +1,3 @@
+// @flow
+
+import { type FooType, type BarType } from './depth-zero';
diff --git a/tests/files/cycles/flow-types-only-importing-type.js b/tests/files/cycles/flow-types-only-importing-type.js
new file mode 100644
index 000000000..b407da987
--- /dev/null
+++ b/tests/files/cycles/flow-types-only-importing-type.js
@@ -0,0 +1,3 @@
+// @flow
+
+import type { FooType } from './depth-zero';
diff --git a/tests/files/cycles/flow-types-some-type-imports.js b/tests/files/cycles/flow-types-some-type-imports.js
new file mode 100644
index 000000000..9008ba1af
--- /dev/null
+++ b/tests/files/cycles/flow-types-some-type-imports.js
@@ -0,0 +1,3 @@
+// @flow
+
+import { foo, type BarType } from './depth-zero'
diff --git a/tests/files/cycles/flow-types.js b/tests/files/cycles/flow-types.js
new file mode 100644
index 000000000..fbfb69f30
--- /dev/null
+++ b/tests/files/cycles/flow-types.js
@@ -0,0 +1,6 @@
+// @flow
+
+import type { FooType } from './flow-types-depth-two';
+import { type BarType } from './flow-types-depth-two';
+
+export const bar = 1;
diff --git a/tests/files/cycles/ignore/.eslintrc b/tests/files/cycles/ignore/.eslintrc
new file mode 100644
index 000000000..896eda6a3
--- /dev/null
+++ b/tests/files/cycles/ignore/.eslintrc
@@ -0,0 +1,5 @@
+{
+  "rules": {
+    "import/no-cycle": 0
+  }
+}
diff --git a/tests/files/cycles/ignore/index.js b/tests/files/cycles/ignore/index.js
new file mode 100644
index 000000000..211fd972f
--- /dev/null
+++ b/tests/files/cycles/ignore/index.js
@@ -0,0 +1,2 @@
+import { foo } from "../depth-zero";
+export { foo };
diff --git a/tests/files/cycles/intermediate-ignore.js b/tests/files/cycles/intermediate-ignore.js
new file mode 100644
index 000000000..1ba6fba79
--- /dev/null
+++ b/tests/files/cycles/intermediate-ignore.js
@@ -0,0 +1,2 @@
+import foo from "./ignore";
+export { foo };
diff --git a/tests/files/default-export-namespace-string.js b/tests/files/default-export-namespace-string.js
new file mode 100644
index 000000000..5b4a01ab7
--- /dev/null
+++ b/tests/files/default-export-namespace-string.js
@@ -0,0 +1 @@
+export * as "default" from "./named-exports";
diff --git a/tests/files/default-export-string.js b/tests/files/default-export-string.js
new file mode 100644
index 000000000..4f68b517e
--- /dev/null
+++ b/tests/files/default-export-string.js
@@ -0,0 +1,3 @@
+function foo() { return 'bar' }
+
+export { foo as "default" }
diff --git a/tests/files/dynamic-import-in-commonjs.js b/tests/files/dynamic-import-in-commonjs.js
new file mode 100644
index 000000000..259feb4cd
--- /dev/null
+++ b/tests/files/dynamic-import-in-commonjs.js
@@ -0,0 +1,5 @@
+async function doSomething() {
+  await import('./bar.js');
+}
+
+exports.something = 'hello';
diff --git a/tests/files/empty-named-blocks.js b/tests/files/empty-named-blocks.js
new file mode 100644
index 000000000..4640c7f8d
--- /dev/null
+++ b/tests/files/empty-named-blocks.js
@@ -0,0 +1 @@
+import {} from './bar.js';
diff --git a/tests/files/export-default-string-and-named.js b/tests/files/export-default-string-and-named.js
new file mode 100644
index 000000000..62c7d13c4
--- /dev/null
+++ b/tests/files/export-default-string-and-named.js
@@ -0,0 +1,4 @@
+const bar = "bar";
+export function foo() {}
+
+export { bar as "default" }
diff --git a/tests/files/export-star-2/middle.js b/tests/files/export-star-2/middle.js
new file mode 100644
index 000000000..2fc07cd9a
--- /dev/null
+++ b/tests/files/export-star-2/middle.js
@@ -0,0 +1 @@
+export * as myName from './upstream';
diff --git a/tests/files/export-star-2/upstream.js b/tests/files/export-star-2/upstream.js
new file mode 100644
index 000000000..cc798ff50
--- /dev/null
+++ b/tests/files/export-star-2/upstream.js
@@ -0,0 +1 @@
+export const a = 1;
diff --git a/tests/files/export-star-3/b.ts b/tests/files/export-star-3/b.ts
new file mode 100644
index 000000000..5a91d016f
--- /dev/null
+++ b/tests/files/export-star-3/b.ts
@@ -0,0 +1 @@
+export * as b from './c';
diff --git a/tests/files/export-star-3/c.ts b/tests/files/export-star-3/c.ts
new file mode 100644
index 000000000..e69de29bb
diff --git a/tests/files/export-star-4/module/feature.jsx b/tests/files/export-star-4/module/feature.jsx
new file mode 100644
index 000000000..82fab1e50
--- /dev/null
+++ b/tests/files/export-star-4/module/feature.jsx
@@ -0,0 +1,3 @@
+export function func() {
+    console.log('Hello world');
+}
diff --git a/tests/files/export-star-4/module/index.ts b/tests/files/export-star-4/module/index.ts
new file mode 100644
index 000000000..42a9f556a
--- /dev/null
+++ b/tests/files/export-star-4/module/index.ts
@@ -0,0 +1 @@
+export * from './feature';
diff --git a/tests/files/export-star/extfield.js b/tests/files/export-star/extfield.js
new file mode 100644
index 000000000..7a4e8a723
--- /dev/null
+++ b/tests/files/export-star/extfield.js
@@ -0,0 +1 @@
+export default 42;
diff --git a/tests/files/export-star/extfield2.js b/tests/files/export-star/extfield2.js
new file mode 100644
index 000000000..5f2610afc
--- /dev/null
+++ b/tests/files/export-star/extfield2.js
@@ -0,0 +1 @@
+export default NaN;
diff --git a/tests/files/export-star/models.js b/tests/files/export-star/models.js
new file mode 100644
index 000000000..1091770f7
--- /dev/null
+++ b/tests/files/export-star/models.js
@@ -0,0 +1,2 @@
+export * as ExtfieldModel from './extfield';
+export * as Extfield2Model from './extfield2';
diff --git a/tests/files/flowtypes.js b/tests/files/flowtypes.js
index 2df247147..4282cf80b 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/files/foo-bar-resolver-no-version.js b/tests/files/foo-bar-resolver-no-version.js
index 89d4eb13e..2a2d45185 100644
--- a/tests/files/foo-bar-resolver-no-version.js
+++ b/tests/files/foo-bar-resolver-no-version.js
@@ -1,14 +1,12 @@
-var path = require('path')
-
-exports.resolveImport = function (modulePath, sourceFile, config) {
-  var sourceFileName = path.basename(sourceFile)
-  if (sourceFileName === 'foo.js') {
-    return path.join(__dirname, 'bar.jsx')
-  }
-  else if (sourceFileName === 'exception.js') {
-    throw new Error('foo-bar-resolver-v1 resolveImport test exception')
-  }
-  else {
-    return undefined
-  }
-}
+var path = require('path')
+
+exports.resolveImport = function (modulePath, sourceFile, config) {
+  var sourceFileName = path.basename(sourceFile)
+  if (sourceFileName === 'foo.js') {
+    return path.join(__dirname, 'bar.jsx')
+  }
+  if (sourceFileName === 'exception.js') {
+    throw new Error('foo-bar-resolver-v1 resolveImport test exception')
+  }
+  return undefined;
+}
diff --git a/tests/files/foo-bar-resolver-v1.js b/tests/files/foo-bar-resolver-v1.js
index 8aafe7aa4..7ba97cb55 100644
--- a/tests/files/foo-bar-resolver-v1.js
+++ b/tests/files/foo-bar-resolver-v1.js
@@ -1,16 +1,14 @@
-var path = require('path')
-
-exports.resolveImport = function (modulePath, sourceFile, config) {
-  var sourceFileName = path.basename(sourceFile)
-  if (sourceFileName === 'foo.js') {
-    return path.join(__dirname, 'bar.jsx')
-  }
-  else if (sourceFileName === 'exception.js') {
-    throw new Error('foo-bar-resolver-v1 resolveImport test exception')
-  }
-  else {
-    return undefined
-  }
-}
-
-exports.interfaceVersion = 1
+var path = require('path')
+
+exports.resolveImport = function (modulePath, sourceFile, config) {
+  var sourceFileName = path.basename(sourceFile)
+  if (sourceFileName === 'foo.js') {
+    return path.join(__dirname, 'bar.jsx');
+  }
+  if (sourceFileName === 'exception.js') {
+    throw new Error('foo-bar-resolver-v1 resolveImport test exception');
+  }
+  return undefined;
+};
+
+exports.interfaceVersion = 1;
diff --git a/tests/files/foo-bar-resolver-v2.js b/tests/files/foo-bar-resolver-v2.js
index 9bb68171b..13135e392 100644
--- a/tests/files/foo-bar-resolver-v2.js
+++ b/tests/files/foo-bar-resolver-v2.js
@@ -1,16 +1,14 @@
-var path = require('path')
-
-exports.resolve = function (modulePath, sourceFile, config) {
-  var sourceFileName = path.basename(sourceFile)
-  if (sourceFileName === 'foo.js') {
-    return { found: true, path: path.join(__dirname, 'bar.jsx') }
-  }
-  else if (sourceFileName === 'exception.js') {
-    throw new Error('foo-bar-resolver-v2 resolve test exception')
-  }
-  else {
-    return { found: false }
-  }
-}
-
-exports.interfaceVersion = 2
+var path = require('path')
+
+exports.resolve = function (modulePath, sourceFile, config) {
+  var sourceFileName = path.basename(sourceFile)
+  if (sourceFileName === 'foo.js') {
+    return { found: true, path: path.join(__dirname, 'bar.jsx') }
+  }
+  if (sourceFileName === 'exception.js') {
+    throw new Error('foo-bar-resolver-v2 resolve test exception')
+  }
+  return { found: false };
+};
+
+exports.interfaceVersion = 2;
diff --git a/tests/files/internal-modules/package.json b/tests/files/internal-modules/package.json
index e69de29bb..0967ef424 100644
--- a/tests/files/internal-modules/package.json
+++ b/tests/files/internal-modules/package.json
@@ -0,0 +1 @@
+{}
diff --git a/tests/files/internal-modules/test.js b/tests/files/internal-modules/test.js
new file mode 100644
index 000000000..e69de29bb
diff --git a/tests/files/internal-modules/typescript/plugin2/app/index.ts b/tests/files/internal-modules/typescript/plugin2/app/index.ts
new file mode 100644
index 000000000..e69de29bb
diff --git a/tests/files/internal-modules/typescript/plugin2/index.ts b/tests/files/internal-modules/typescript/plugin2/index.ts
new file mode 100644
index 000000000..e69de29bb
diff --git a/tests/files/internal-modules/typescript/plugin2/internal.ts b/tests/files/internal-modules/typescript/plugin2/internal.ts
new file mode 100644
index 000000000..e69de29bb
diff --git a/tests/files/internal-modules/typescript/plugins.ts b/tests/files/internal-modules/typescript/plugins.ts
new file mode 100644
index 000000000..e69de29bb
diff --git a/tests/files/issue210.config.flat.js b/tests/files/issue210.config.flat.js
new file mode 100644
index 000000000..c894376f4
--- /dev/null
+++ b/tests/files/issue210.config.flat.js
@@ -0,0 +1,3 @@
+exports.languageOptions = {
+  sourceType: 'module',
+}
diff --git a/tests/files/jsx/bar/baz.jsx b/tests/files/jsx/bar/baz.jsx
new file mode 100644
index 000000000..ab0eb07e9
--- /dev/null
+++ b/tests/files/jsx/bar/baz.jsx
@@ -0,0 +1,16 @@
+
+export function Baz1() {
+  return (
+    <div>
+    </div>
+  );
+}
+
+// Fragment Syntax
+export function Baz2() {
+  return (
+    <div>
+      <span>Baz2</span>
+    </div>
+  );
+}
\ No newline at end of file
diff --git a/tests/files/jsx/bar/index.js b/tests/files/jsx/bar/index.js
new file mode 100644
index 000000000..2d36b837e
--- /dev/null
+++ b/tests/files/jsx/bar/index.js
@@ -0,0 +1,2 @@
+export * from "./baz.jsx";
+export { Qux1, Qux2 } from "./qux.jsx";
\ No newline at end of file
diff --git a/tests/files/jsx/bar/qux.jsx b/tests/files/jsx/bar/qux.jsx
new file mode 100644
index 000000000..9325207d7
--- /dev/null
+++ b/tests/files/jsx/bar/qux.jsx
@@ -0,0 +1,16 @@
+
+export function Qux1() {
+  return (
+    <div>
+      <p>Qux1</p>
+    </div>
+  );
+}
+
+export function Qux2() {
+  return (
+    <div>
+      <p>Qux1</p>
+    </div>
+  );;
+}
\ No newline at end of file
diff --git a/tests/files/jsx/re-export.js b/tests/files/jsx/re-export.js
new file mode 100644
index 000000000..70f8509aa
--- /dev/null
+++ b/tests/files/jsx/re-export.js
@@ -0,0 +1 @@
+export * from './named.jsx'
\ No newline at end of file
diff --git a/tests/files/just-json-files/.eslintrc.json b/tests/files/just-json-files/.eslintrc.json
new file mode 100644
index 000000000..4fbf13a72
--- /dev/null
+++ b/tests/files/just-json-files/.eslintrc.json
@@ -0,0 +1,22 @@
+{
+  "root": true,
+
+  "plugins": ["import", "json"],
+
+  "rules": {
+    "import/no-unused-modules": [
+      "error",
+      {
+        "missingExports": false,
+        "unusedExports": true
+      }
+    ]
+  },
+
+  "overrides": [
+    {
+      "files": "*.json",
+      "extends": "plugin:json/recommended"
+    }
+  ]
+}
diff --git a/tests/files/just-json-files/eslint.config.js b/tests/files/just-json-files/eslint.config.js
new file mode 100644
index 000000000..b1bf2070b
--- /dev/null
+++ b/tests/files/just-json-files/eslint.config.js
@@ -0,0 +1,28 @@
+var jsonPlugin = require('eslint-plugin-json');
+
+if (!jsonPlugin.processors.json) {
+  jsonPlugin.processors.json = jsonPlugin.processors['.json'];
+}
+
+module.exports = [
+  {
+    files: ['tests/files/just-json-files/*.json'],
+    plugins:{
+      json: jsonPlugin,
+    },
+    processor: 'json/json',
+    rules: Object.assign(
+      {},
+      {
+        'import/no-unused-modules': [
+          'error',
+          {
+            'missingExports': false,
+            'unusedExports': true,
+          },
+        ],
+      },
+      jsonPlugin.configs.recommended.rules
+    )
+  },
+];
diff --git a/tests/files/just-json-files/invalid.json b/tests/files/just-json-files/invalid.json
new file mode 100644
index 000000000..7edb2fa5b
--- /dev/null
+++ b/tests/files/just-json-files/invalid.json
@@ -0,0 +1 @@
+,
diff --git a/tests/files/load-error-resolver.js b/tests/files/load-error-resolver.js
index aa9d33010..7cf7cfa1b 100644
--- a/tests/files/load-error-resolver.js
+++ b/tests/files/load-error-resolver.js
@@ -1 +1 @@
-throw new Error('TEST ERROR')
+throw new SyntaxError('TEST SYNTAX ERROR')
diff --git a/tests/files/minified/no-newline.js b/tests/files/minified/no-newline.js
new file mode 100644
index 000000000..261989a75
--- /dev/null
+++ b/tests/files/minified/no-newline.js
@@ -0,0 +1,3 @@
+function y() {
+    console.log("y");
+}export {y};
diff --git a/tests/files/minified/one-line-no-semi-renamed.js b/tests/files/minified/one-line-no-semi-renamed.js
new file mode 100644
index 000000000..601f9c7b7
--- /dev/null
+++ b/tests/files/minified/one-line-no-semi-renamed.js
@@ -0,0 +1 @@
+function a(){console.log('foo')}export{a as foo};
diff --git a/tests/files/minified/one-line-no-semi.js b/tests/files/minified/one-line-no-semi.js
new file mode 100644
index 000000000..09a632811
--- /dev/null
+++ b/tests/files/minified/one-line-no-semi.js
@@ -0,0 +1 @@
+function a(){return true}export{a};
diff --git a/tests/files/minified/one-line.js b/tests/files/minified/one-line.js
new file mode 100644
index 000000000..b79898eb3
--- /dev/null
+++ b/tests/files/minified/one-line.js
@@ -0,0 +1 @@
+function a(){console.log("foo")};export{a};
diff --git a/tests/files/missing-entrypoint/package.json b/tests/files/missing-entrypoint/package.json
new file mode 100644
index 000000000..4138a88f4
--- /dev/null
+++ b/tests/files/missing-entrypoint/package.json
@@ -0,0 +1,3 @@
+{
+  "bin": "./cli.js"
+}
diff --git a/tests/files/named-export-collision/a.js b/tests/files/named-export-collision/a.js
new file mode 100644
index 000000000..cb04b2cb2
--- /dev/null
+++ b/tests/files/named-export-collision/a.js
@@ -0,0 +1 @@
+export const FOO = 'a-foobar';
diff --git a/tests/files/named-export-collision/b.js b/tests/files/named-export-collision/b.js
new file mode 100644
index 000000000..ebf954ee0
--- /dev/null
+++ b/tests/files/named-export-collision/b.js
@@ -0,0 +1 @@
+export const FOO = 'b-foobar';
diff --git a/tests/files/named-exports.js b/tests/files/named-exports.js
index f2881c10c..d8b17bb90 100644
--- a/tests/files/named-exports.js
+++ b/tests/files/named-exports.js
@@ -13,9 +13,9 @@ export class ExportedClass {
 
 // destructuring exports
 
-export var { destructuredProp } = {}
+export var { destructuredProp, ...restProps } = {}
          , { destructingAssign = null } = {}
          , { destructingAssign: destructingRenamedAssign = null } = {}
-         , [ arrayKeyProp ] = []
+         , [ arrayKeyProp, ...arrayRestKeyProps ] = []
          , [ { deepProp } ] = []
          , { arr: [ ,, deepSparseElement ] } = {}
diff --git a/tests/files/no-named-as-default/exports.js b/tests/files/no-named-as-default/exports.js
new file mode 100644
index 000000000..62402634f
--- /dev/null
+++ b/tests/files/no-named-as-default/exports.js
@@ -0,0 +1,4 @@
+const variable = 1;
+
+export { variable };
+export default variable;
diff --git a/tests/files/no-named-as-default/misleading-re-exports.js b/tests/files/no-named-as-default/misleading-re-exports.js
new file mode 100644
index 000000000..8d36a0866
--- /dev/null
+++ b/tests/files/no-named-as-default/misleading-re-exports.js
@@ -0,0 +1,2 @@
+export { variable as something } from './exports';
+export { something as default } from './something';
diff --git a/tests/files/no-named-as-default/no-default-export.js b/tests/files/no-named-as-default/no-default-export.js
new file mode 100644
index 000000000..db3074797
--- /dev/null
+++ b/tests/files/no-named-as-default/no-default-export.js
@@ -0,0 +1 @@
+export const foobar = 4;
diff --git a/tests/files/no-named-as-default/re-exports.js b/tests/files/no-named-as-default/re-exports.js
new file mode 100644
index 000000000..20306c182
--- /dev/null
+++ b/tests/files/no-named-as-default/re-exports.js
@@ -0,0 +1,2 @@
+export { something as default } from "./something";
+export { something } from "./something";
diff --git a/tests/files/no-named-as-default/something.js b/tests/files/no-named-as-default/something.js
new file mode 100644
index 000000000..d8fd6851b
--- /dev/null
+++ b/tests/files/no-named-as-default/something.js
@@ -0,0 +1 @@
+export const something = 42;
diff --git a/tests/files/no-unused-modules/arbitrary-module-namespace-identifier-name-a.js b/tests/files/no-unused-modules/arbitrary-module-namespace-identifier-name-a.js
new file mode 100644
index 000000000..7ad810de8
--- /dev/null
+++ b/tests/files/no-unused-modules/arbitrary-module-namespace-identifier-name-a.js
@@ -0,0 +1,2 @@
+const foo = 333
+export { foo as "foo" }
diff --git a/tests/files/no-unused-modules/arbitrary-module-namespace-identifier-name-b.js b/tests/files/no-unused-modules/arbitrary-module-namespace-identifier-name-b.js
new file mode 100644
index 000000000..fa7652725
--- /dev/null
+++ b/tests/files/no-unused-modules/arbitrary-module-namespace-identifier-name-b.js
@@ -0,0 +1 @@
+import { "foo" as foo } from "./arbitrary-module-namespace-identifier-name-a.js"
diff --git a/tests/files/no-unused-modules/arbitrary-module-namespace-identifier-name-c.js b/tests/files/no-unused-modules/arbitrary-module-namespace-identifier-name-c.js
new file mode 100644
index 000000000..7ad810de8
--- /dev/null
+++ b/tests/files/no-unused-modules/arbitrary-module-namespace-identifier-name-c.js
@@ -0,0 +1,2 @@
+const foo = 333
+export { foo as "foo" }
diff --git a/tests/files/no-unused-modules/browserObject/package.json b/tests/files/no-unused-modules/browserObject/package.json
index 28272c6fe..7cf213f81 100644
--- a/tests/files/no-unused-modules/browserObject/package.json
+++ b/tests/files/no-unused-modules/browserObject/package.json
@@ -1,5 +1,6 @@
 {
   "browser": {
-    "browserObject": "./index.js"
+    "browserObject": "./index.js",
+    "an-ignored-module": false
   }
 }
diff --git a/tests/files/no-unused-modules/cjs.js b/tests/files/no-unused-modules/cjs.js
new file mode 100644
index 000000000..d5d7fbb98
--- /dev/null
+++ b/tests/files/no-unused-modules/cjs.js
@@ -0,0 +1,7 @@
+// Simple import extracted from 'redux-starter-kit' compiled file
+
+function isPlain(val) {
+  return true;
+}
+
+exports.isPlain = isPlain;
diff --git a/tests/files/no-unused-modules/destructuring-a.js b/tests/files/no-unused-modules/destructuring-a.js
new file mode 100644
index 000000000..1da867def
--- /dev/null
+++ b/tests/files/no-unused-modules/destructuring-a.js
@@ -0,0 +1 @@
+import {a, b} from "./destructuring-b";
diff --git a/tests/files/no-unused-modules/destructuring-b.js b/tests/files/no-unused-modules/destructuring-b.js
new file mode 100644
index 000000000..b477a5b6f
--- /dev/null
+++ b/tests/files/no-unused-modules/destructuring-b.js
@@ -0,0 +1,2 @@
+const obj = {a: 1, dummy: {b: 2}};
+export const {a, dummy: {b}} = obj;
diff --git a/tests/files/no-unused-modules/dynamic-import-js-2.js b/tests/files/no-unused-modules/dynamic-import-js-2.js
new file mode 100644
index 000000000..3de28a65d
--- /dev/null
+++ b/tests/files/no-unused-modules/dynamic-import-js-2.js
@@ -0,0 +1,13 @@
+const importPath = './exports-for-dynamic-js';
+class A {
+    method() {
+        const c = import(importPath)
+    }
+}
+
+
+class B {
+    method() {
+        const c = import('i-do-not-exist')
+    }
+}
diff --git a/tests/files/no-unused-modules/dynamic-import-js.js b/tests/files/no-unused-modules/dynamic-import-js.js
new file mode 100644
index 000000000..36bf5c313
--- /dev/null
+++ b/tests/files/no-unused-modules/dynamic-import-js.js
@@ -0,0 +1,5 @@
+class A {
+    method() {
+        const c = import('./exports-for-dynamic-js')
+    }
+}
diff --git a/tests/files/no-unused-modules/exports-for-dynamic-js-2.js b/tests/files/no-unused-modules/exports-for-dynamic-js-2.js
new file mode 100644
index 000000000..19082862f
--- /dev/null
+++ b/tests/files/no-unused-modules/exports-for-dynamic-js-2.js
@@ -0,0 +1,5 @@
+export const a = 10;
+export const b = 20;
+export const c = 30;
+const d = 40;
+export default d;
diff --git a/tests/files/no-unused-modules/exports-for-dynamic-js.js b/tests/files/no-unused-modules/exports-for-dynamic-js.js
new file mode 100644
index 000000000..06d938e40
--- /dev/null
+++ b/tests/files/no-unused-modules/exports-for-dynamic-js.js
@@ -0,0 +1,5 @@
+export const a = 10
+export const b = 20
+export const c = 30
+const d = 40
+export default d
diff --git a/tests/files/no-unused-modules/file-0.js b/tests/files/no-unused-modules/file-0.js
index a5319b5fc..6b5cc71bc 100644
--- a/tests/files/no-unused-modules/file-0.js
+++ b/tests/files/no-unused-modules/file-0.js
@@ -11,3 +11,4 @@ import {q} from './file-q'
 export * from './file-n'
 export { default, o0, o3 } from './file-o'
 export { p } from './file-p'
+import s from './file-s'
diff --git a/tests/files/no-unused-modules/file-destructured-1.js b/tests/files/no-unused-modules/file-destructured-1.js
new file mode 100644
index 000000000..f2223ac3b
--- /dev/null
+++ b/tests/files/no-unused-modules/file-destructured-1.js
@@ -0,0 +1,2 @@
+export const { destructured } = {};
+export const { destructured2 } = {};
diff --git a/tests/files/no-unused-modules/file-destructured-2.js b/tests/files/no-unused-modules/file-destructured-2.js
new file mode 100644
index 000000000..06dc48a9d
--- /dev/null
+++ b/tests/files/no-unused-modules/file-destructured-2.js
@@ -0,0 +1 @@
+import { destructured } from './file-destructured-1';
\ No newline at end of file
diff --git a/tests/files/no-unused-modules/file-s.js b/tests/files/no-unused-modules/file-s.js
new file mode 100644
index 000000000..86587470b
--- /dev/null
+++ b/tests/files/no-unused-modules/file-s.js
@@ -0,0 +1 @@
+export { default } from './file-o'
diff --git a/tests/files/no-unused-modules/filte-r.js b/tests/files/no-unused-modules/filte-r.js
new file mode 100644
index 000000000..c5b0dbbfe
--- /dev/null
+++ b/tests/files/no-unused-modules/filte-r.js
@@ -0,0 +1 @@
+export * from './cjs'
diff --git a/tests/files/no-unused-modules/flow/flow-0.js b/tests/files/no-unused-modules/flow/flow-0.js
new file mode 100644
index 000000000..b5e5d8b01
--- /dev/null
+++ b/tests/files/no-unused-modules/flow/flow-0.js
@@ -0,0 +1 @@
+import { type FooType, type FooInterface } from './flow-2';
diff --git a/tests/files/no-unused-modules/flow/flow-1.js b/tests/files/no-unused-modules/flow/flow-1.js
new file mode 100644
index 000000000..4828eb575
--- /dev/null
+++ b/tests/files/no-unused-modules/flow/flow-1.js
@@ -0,0 +1,3 @@
+// @flow strict
+export type Bar = number;
+export interface BarInterface {};
diff --git a/tests/files/no-unused-modules/flow/flow-2.js b/tests/files/no-unused-modules/flow/flow-2.js
new file mode 100644
index 000000000..0c632c247
--- /dev/null
+++ b/tests/files/no-unused-modules/flow/flow-2.js
@@ -0,0 +1,3 @@
+// @flow strict
+export type FooType = string;
+export interface FooInterface {};
diff --git a/tests/files/no-unused-modules/flow/flow-3.js b/tests/files/no-unused-modules/flow/flow-3.js
new file mode 100644
index 000000000..ade5393a7
--- /dev/null
+++ b/tests/files/no-unused-modules/flow/flow-3.js
@@ -0,0 +1 @@
+import type { FooType, FooInterface } from './flow-4';
diff --git a/tests/files/no-unused-modules/flow/flow-4.js b/tests/files/no-unused-modules/flow/flow-4.js
new file mode 100644
index 000000000..0c632c247
--- /dev/null
+++ b/tests/files/no-unused-modules/flow/flow-4.js
@@ -0,0 +1,3 @@
+// @flow strict
+export type FooType = string;
+export interface FooInterface {};
diff --git a/tests/files/no-unused-modules/import-export-1.js b/tests/files/no-unused-modules/import-export-1.js
new file mode 100644
index 000000000..218c3cff7
--- /dev/null
+++ b/tests/files/no-unused-modules/import-export-1.js
@@ -0,0 +1,2 @@
+export const a = 5;
+export const b = 'b';
diff --git a/tests/files/no-unused-modules/import-export-2.js b/tests/files/no-unused-modules/import-export-2.js
new file mode 100644
index 000000000..9cfb2747b
--- /dev/null
+++ b/tests/files/no-unused-modules/import-export-2.js
@@ -0,0 +1,2 @@
+import { a } from './import-export-1';
+export { b } from './import-export-1';
diff --git a/tests/files/no-unused-modules/jsx/file-jsx-a.jsx b/tests/files/no-unused-modules/jsx/file-jsx-a.jsx
new file mode 100644
index 000000000..1de6d020c
--- /dev/null
+++ b/tests/files/no-unused-modules/jsx/file-jsx-a.jsx
@@ -0,0 +1,3 @@
+import {b} from './file-jsx-b';
+
+export const a = b + 1;
diff --git a/tests/files/no-unused-modules/jsx/file-jsx-b.jsx b/tests/files/no-unused-modules/jsx/file-jsx-b.jsx
new file mode 100644
index 000000000..202103085
--- /dev/null
+++ b/tests/files/no-unused-modules/jsx/file-jsx-b.jsx
@@ -0,0 +1 @@
+export const b = 2;
diff --git a/tests/files/no-unused-modules/prefix-child.js b/tests/files/no-unused-modules/prefix-child.js
new file mode 100644
index 000000000..bb1843d11
--- /dev/null
+++ b/tests/files/no-unused-modules/prefix-child.js
@@ -0,0 +1 @@
+export const foo = 1;
diff --git a/tests/files/no-unused-modules/prefix-parent-bom.js b/tests/files/no-unused-modules/prefix-parent-bom.js
new file mode 100644
index 000000000..46b6da280
--- /dev/null
+++ b/tests/files/no-unused-modules/prefix-parent-bom.js
@@ -0,0 +1 @@
+import {foo} from './prefix-child.js';
diff --git a/tests/files/no-unused-modules/prefix-parent-bomhashbang.js b/tests/files/no-unused-modules/prefix-parent-bomhashbang.js
new file mode 100644
index 000000000..4f5d82969
--- /dev/null
+++ b/tests/files/no-unused-modules/prefix-parent-bomhashbang.js
@@ -0,0 +1,2 @@
+#!/usr/bin/env node
+import {foo} from './prefix-child.js';
diff --git a/tests/files/no-unused-modules/prefix-parent-hashbang.js b/tests/files/no-unused-modules/prefix-parent-hashbang.js
new file mode 100644
index 000000000..db2bf5332
--- /dev/null
+++ b/tests/files/no-unused-modules/prefix-parent-hashbang.js
@@ -0,0 +1,2 @@
+#!/usr/bin/env node
+import {foo} from './prefix-child.js';
diff --git a/tests/files/no-unused-modules/prefix-parent.js b/tests/files/no-unused-modules/prefix-parent.js
new file mode 100644
index 000000000..4f5d82969
--- /dev/null
+++ b/tests/files/no-unused-modules/prefix-parent.js
@@ -0,0 +1,2 @@
+#!/usr/bin/env node
+import {foo} from './prefix-child.js';
diff --git a/tests/files/no-unused-modules/renameDefault-2/ComponentA.js b/tests/files/no-unused-modules/renameDefault-2/ComponentA.js
new file mode 100644
index 000000000..b4517920f
--- /dev/null
+++ b/tests/files/no-unused-modules/renameDefault-2/ComponentA.js
@@ -0,0 +1 @@
+export default function ComponentA() {}
diff --git a/tests/files/no-unused-modules/renameDefault-2/ComponentB.js b/tests/files/no-unused-modules/renameDefault-2/ComponentB.js
new file mode 100644
index 000000000..72e0f2ee7
--- /dev/null
+++ b/tests/files/no-unused-modules/renameDefault-2/ComponentB.js
@@ -0,0 +1 @@
+export default function ComponentB() {}
diff --git a/tests/files/no-unused-modules/renameDefault-2/components.js b/tests/files/no-unused-modules/renameDefault-2/components.js
new file mode 100644
index 000000000..5a72952a3
--- /dev/null
+++ b/tests/files/no-unused-modules/renameDefault-2/components.js
@@ -0,0 +1,2 @@
+export { default as ComponentA } from "./ComponentA";
+export { default as ComponentB } from "./ComponentB";
diff --git a/tests/files/no-unused-modules/renameDefault-2/usage.js b/tests/files/no-unused-modules/renameDefault-2/usage.js
new file mode 100644
index 000000000..7298baa55
--- /dev/null
+++ b/tests/files/no-unused-modules/renameDefault-2/usage.js
@@ -0,0 +1 @@
+import { ComponentA, ComponentB } from './components'
diff --git a/tests/files/no-unused-modules/renameDefault/Component.js b/tests/files/no-unused-modules/renameDefault/Component.js
new file mode 100644
index 000000000..c6be8faf0
--- /dev/null
+++ b/tests/files/no-unused-modules/renameDefault/Component.js
@@ -0,0 +1 @@
+export default function Component() {}
diff --git a/tests/files/no-unused-modules/renameDefault/components.js b/tests/files/no-unused-modules/renameDefault/components.js
new file mode 100644
index 000000000..4a877cb1f
--- /dev/null
+++ b/tests/files/no-unused-modules/renameDefault/components.js
@@ -0,0 +1 @@
+export { default as Component } from './Component'
diff --git a/tests/files/no-unused-modules/renameDefault/usage.js b/tests/files/no-unused-modules/renameDefault/usage.js
new file mode 100644
index 000000000..6ee988988
--- /dev/null
+++ b/tests/files/no-unused-modules/renameDefault/usage.js
@@ -0,0 +1 @@
+import { Component } from './components'
diff --git a/tests/files/no-unused-modules/typescript/dynamic-import-ts.ts b/tests/files/no-unused-modules/typescript/dynamic-import-ts.ts
new file mode 100644
index 000000000..10a17c3b1
--- /dev/null
+++ b/tests/files/no-unused-modules/typescript/dynamic-import-ts.ts
@@ -0,0 +1,6 @@
+class A {
+    method() {
+        const c = import('./exports-for-dynamic-ts')
+    }
+}
+
diff --git a/tests/files/no-unused-modules/typescript/exports-for-dynamic-ts.ts b/tests/files/no-unused-modules/typescript/exports-for-dynamic-ts.ts
new file mode 100644
index 000000000..566eb7c7d
--- /dev/null
+++ b/tests/files/no-unused-modules/typescript/exports-for-dynamic-ts.ts
@@ -0,0 +1,5 @@
+export const ts_a = 10
+export const ts_b = 20
+export const ts_c = 30
+const ts_d = 40
+export default ts_d
diff --git a/tests/files/no-unused-modules/typescript/file-ts-a-import-type.ts b/tests/files/no-unused-modules/typescript/file-ts-a-import-type.ts
new file mode 100644
index 000000000..357d890b9
--- /dev/null
+++ b/tests/files/no-unused-modules/typescript/file-ts-a-import-type.ts
@@ -0,0 +1,9 @@
+import type {b} from './file-ts-b-used-as-type';
+import type {c} from './file-ts-c-used-as-type';
+import type {d} from './file-ts-d-used-as-type';
+import type {e} from './file-ts-e-used-as-type';
+
+const a: typeof b = 2;
+const a2: c = {};
+const a3: d = {};
+const a4: typeof e = undefined;
diff --git a/tests/files/no-unused-modules/typescript/file-ts-a.ts b/tests/files/no-unused-modules/typescript/file-ts-a.ts
new file mode 100644
index 000000000..2e7984cb9
--- /dev/null
+++ b/tests/files/no-unused-modules/typescript/file-ts-a.ts
@@ -0,0 +1,8 @@
+import {b} from './file-ts-b';
+import {c} from './file-ts-c';
+import {d} from './file-ts-d';
+import {e} from './file-ts-e';
+
+const a = b + 1 + e.f;
+const a2: c = {};
+const a3: d = {};
diff --git a/tests/files/no-unused-modules/typescript/file-ts-b-unused.ts b/tests/files/no-unused-modules/typescript/file-ts-b-unused.ts
new file mode 100644
index 000000000..202103085
--- /dev/null
+++ b/tests/files/no-unused-modules/typescript/file-ts-b-unused.ts
@@ -0,0 +1 @@
+export const b = 2;
diff --git a/tests/files/no-unused-modules/typescript/file-ts-b-used-as-type.ts b/tests/files/no-unused-modules/typescript/file-ts-b-used-as-type.ts
new file mode 100644
index 000000000..202103085
--- /dev/null
+++ b/tests/files/no-unused-modules/typescript/file-ts-b-used-as-type.ts
@@ -0,0 +1 @@
+export const b = 2;
diff --git a/tests/files/no-unused-modules/typescript/file-ts-b.ts b/tests/files/no-unused-modules/typescript/file-ts-b.ts
new file mode 100644
index 000000000..202103085
--- /dev/null
+++ b/tests/files/no-unused-modules/typescript/file-ts-b.ts
@@ -0,0 +1 @@
+export const b = 2;
diff --git a/tests/files/no-unused-modules/typescript/file-ts-c-unused.ts b/tests/files/no-unused-modules/typescript/file-ts-c-unused.ts
new file mode 100644
index 000000000..aedf4062b
--- /dev/null
+++ b/tests/files/no-unused-modules/typescript/file-ts-c-unused.ts
@@ -0,0 +1 @@
+export interface c {};
diff --git a/tests/files/no-unused-modules/typescript/file-ts-c-used-as-type.ts b/tests/files/no-unused-modules/typescript/file-ts-c-used-as-type.ts
new file mode 100644
index 000000000..aedf4062b
--- /dev/null
+++ b/tests/files/no-unused-modules/typescript/file-ts-c-used-as-type.ts
@@ -0,0 +1 @@
+export interface c {};
diff --git a/tests/files/no-unused-modules/typescript/file-ts-c.ts b/tests/files/no-unused-modules/typescript/file-ts-c.ts
new file mode 100644
index 000000000..aedf4062b
--- /dev/null
+++ b/tests/files/no-unused-modules/typescript/file-ts-c.ts
@@ -0,0 +1 @@
+export interface c {};
diff --git a/tests/files/no-unused-modules/typescript/file-ts-d-unused.ts b/tests/files/no-unused-modules/typescript/file-ts-d-unused.ts
new file mode 100644
index 000000000..7679b3de0
--- /dev/null
+++ b/tests/files/no-unused-modules/typescript/file-ts-d-unused.ts
@@ -0,0 +1 @@
+export type d = {};
diff --git a/tests/files/no-unused-modules/typescript/file-ts-d-used-as-type.ts b/tests/files/no-unused-modules/typescript/file-ts-d-used-as-type.ts
new file mode 100644
index 000000000..7679b3de0
--- /dev/null
+++ b/tests/files/no-unused-modules/typescript/file-ts-d-used-as-type.ts
@@ -0,0 +1 @@
+export type d = {};
diff --git a/tests/files/no-unused-modules/typescript/file-ts-d.ts b/tests/files/no-unused-modules/typescript/file-ts-d.ts
new file mode 100644
index 000000000..7679b3de0
--- /dev/null
+++ b/tests/files/no-unused-modules/typescript/file-ts-d.ts
@@ -0,0 +1 @@
+export type d = {};
diff --git a/tests/files/no-unused-modules/typescript/file-ts-e-unused.ts b/tests/files/no-unused-modules/typescript/file-ts-e-unused.ts
new file mode 100644
index 000000000..d1787a11a
--- /dev/null
+++ b/tests/files/no-unused-modules/typescript/file-ts-e-unused.ts
@@ -0,0 +1 @@
+export enum e { f };
diff --git a/tests/files/no-unused-modules/typescript/file-ts-e-used-as-type.ts b/tests/files/no-unused-modules/typescript/file-ts-e-used-as-type.ts
new file mode 100644
index 000000000..d1787a11a
--- /dev/null
+++ b/tests/files/no-unused-modules/typescript/file-ts-e-used-as-type.ts
@@ -0,0 +1 @@
+export enum e { f };
diff --git a/tests/files/no-unused-modules/typescript/file-ts-e.ts b/tests/files/no-unused-modules/typescript/file-ts-e.ts
new file mode 100644
index 000000000..d1787a11a
--- /dev/null
+++ b/tests/files/no-unused-modules/typescript/file-ts-e.ts
@@ -0,0 +1 @@
+export enum e { f };
diff --git a/tests/files/no-unused-modules/typescript/file-ts-f-import-type.ts b/tests/files/no-unused-modules/typescript/file-ts-f-import-type.ts
new file mode 100644
index 000000000..dd8204377
--- /dev/null
+++ b/tests/files/no-unused-modules/typescript/file-ts-f-import-type.ts
@@ -0,0 +1 @@
+import type {g} from './file-ts-g-used-as-type'
diff --git a/tests/files/no-unused-modules/typescript/file-ts-f.ts b/tests/files/no-unused-modules/typescript/file-ts-f.ts
new file mode 100644
index 000000000..f3a1ca7ab
--- /dev/null
+++ b/tests/files/no-unused-modules/typescript/file-ts-f.ts
@@ -0,0 +1 @@
+import {g} from './file-ts-g';
diff --git a/tests/files/no-unused-modules/typescript/file-ts-g-used-as-type.ts b/tests/files/no-unused-modules/typescript/file-ts-g-used-as-type.ts
new file mode 100644
index 000000000..fe5318fbe
--- /dev/null
+++ b/tests/files/no-unused-modules/typescript/file-ts-g-used-as-type.ts
@@ -0,0 +1 @@
+export interface g {}
diff --git a/tests/files/no-unused-modules/typescript/file-ts-g.ts b/tests/files/no-unused-modules/typescript/file-ts-g.ts
new file mode 100644
index 000000000..fe5318fbe
--- /dev/null
+++ b/tests/files/no-unused-modules/typescript/file-ts-g.ts
@@ -0,0 +1 @@
+export interface g {}
diff --git a/tests/files/node_modules/@generated/bar/index.js b/tests/files/node_modules/@generated/bar/index.js
new file mode 100644
index 000000000..e69de29bb
diff --git a/tests/files/node_modules/@generated/bar/package.json b/tests/files/node_modules/@generated/bar/package.json
new file mode 100644
index 000000000..b70db688d
--- /dev/null
+++ b/tests/files/node_modules/@generated/bar/package.json
@@ -0,0 +1,3 @@
+{
+  "name": "@generated/bar"
+}
diff --git a/tests/files/node_modules/@generated/foo/index.js b/tests/files/node_modules/@generated/foo/index.js
new file mode 100644
index 000000000..e69de29bb
diff --git a/tests/files/node_modules/@generated/foo/package.json b/tests/files/node_modules/@generated/foo/package.json
new file mode 100644
index 000000000..c5d0d6b33
--- /dev/null
+++ b/tests/files/node_modules/@generated/foo/package.json
@@ -0,0 +1,3 @@
+{
+  "name": "@generated/foo"
+}
diff --git a/tests/files/node_modules/@org/not-a-dependency/package.json b/tests/files/node_modules/@org/not-a-dependency/package.json
new file mode 100644
index 000000000..a81c5f291
--- /dev/null
+++ b/tests/files/node_modules/@org/not-a-dependency/package.json
@@ -0,0 +1,3 @@
+{
+  "name": "@org/not-a-dependency"
+}
diff --git a/tests/files/node_modules/@org/package/package.json b/tests/files/node_modules/@org/package/package.json
new file mode 100644
index 000000000..7cb5d73da
--- /dev/null
+++ b/tests/files/node_modules/@org/package/package.json
@@ -0,0 +1,3 @@
+{
+  "name": "@org/package"
+}
diff --git a/tests/files/node_modules/a/package.json b/tests/files/node_modules/a/package.json
new file mode 100644
index 000000000..44d21f1fa
--- /dev/null
+++ b/tests/files/node_modules/a/package.json
@@ -0,0 +1,3 @@
+{
+  "name": "a"
+}
diff --git a/tests/files/node_modules/chai/package.json b/tests/files/node_modules/chai/package.json
new file mode 100644
index 000000000..00acdd2ca
--- /dev/null
+++ b/tests/files/node_modules/chai/package.json
@@ -0,0 +1,3 @@
+{
+  "name": "chai"
+}
diff --git a/tests/files/node_modules/es6-module/package.json b/tests/files/node_modules/es6-module/package.json
new file mode 100644
index 000000000..0bff4dda0
--- /dev/null
+++ b/tests/files/node_modules/es6-module/package.json
@@ -0,0 +1,3 @@
+{
+  "name": "es6-module"
+}
diff --git a/tests/files/node_modules/eslint-import-resolver-foo/index.js b/tests/files/node_modules/eslint-import-resolver-foo/index.js
new file mode 120000
index 000000000..d194dba0d
--- /dev/null
+++ b/tests/files/node_modules/eslint-import-resolver-foo/index.js
@@ -0,0 +1 @@
+../../foo-bar-resolver-v2.js
\ No newline at end of file
diff --git a/tests/files/node_modules/eslint-import-resolver-foo/package.json b/tests/files/node_modules/eslint-import-resolver-foo/package.json
new file mode 100644
index 000000000..190e8e6e4
--- /dev/null
+++ b/tests/files/node_modules/eslint-import-resolver-foo/package.json
@@ -0,0 +1,3 @@
+{
+  "name": "eslint-import-resolver-foo"
+}
diff --git a/tests/files/node_modules/esm-package-not-in-pkg-json/esm-module/index.js b/tests/files/node_modules/esm-package-not-in-pkg-json/esm-module/index.js
new file mode 100644
index 000000000..e69de29bb
diff --git a/tests/files/node_modules/esm-package-not-in-pkg-json/esm-module/package.json b/tests/files/node_modules/esm-package-not-in-pkg-json/esm-module/package.json
new file mode 100644
index 000000000..0c58fec1b
--- /dev/null
+++ b/tests/files/node_modules/esm-package-not-in-pkg-json/esm-module/package.json
@@ -0,0 +1,4 @@
+{
+  "sideEffects": false,
+  "module": "./index.js"
+}
diff --git a/tests/files/node_modules/esm-package-not-in-pkg-json/index.js b/tests/files/node_modules/esm-package-not-in-pkg-json/index.js
new file mode 100644
index 000000000..e69de29bb
diff --git a/tests/files/node_modules/esm-package-not-in-pkg-json/package.json b/tests/files/node_modules/esm-package-not-in-pkg-json/package.json
new file mode 100644
index 000000000..fa7f3c043
--- /dev/null
+++ b/tests/files/node_modules/esm-package-not-in-pkg-json/package.json
@@ -0,0 +1,5 @@
+{
+  "name": "esm-package-not-in-pkg-json",
+  "main": "index.js",
+  "version": "1.0.0"
+}
diff --git a/tests/files/node_modules/esm-package/esm-module/index.js b/tests/files/node_modules/esm-package/esm-module/index.js
new file mode 100644
index 000000000..e69de29bb
diff --git a/tests/files/node_modules/esm-package/esm-module/package.json b/tests/files/node_modules/esm-package/esm-module/package.json
new file mode 100644
index 000000000..0c58fec1b
--- /dev/null
+++ b/tests/files/node_modules/esm-package/esm-module/package.json
@@ -0,0 +1,4 @@
+{
+  "sideEffects": false,
+  "module": "./index.js"
+}
diff --git a/tests/files/node_modules/esm-package/index.js b/tests/files/node_modules/esm-package/index.js
new file mode 100644
index 000000000..e69de29bb
diff --git a/tests/files/node_modules/esm-package/package.json b/tests/files/node_modules/esm-package/package.json
new file mode 100644
index 000000000..ddad1a531
--- /dev/null
+++ b/tests/files/node_modules/esm-package/package.json
@@ -0,0 +1,5 @@
+{
+  "name": "esm-package",
+  "main": "index.js",
+  "version": "1.0.0"
+}
diff --git a/tests/files/node_modules/exceljs/package.json b/tests/files/node_modules/exceljs/package.json
index 70d59eaaa..f2412292d 100644
--- a/tests/files/node_modules/exceljs/package.json
+++ b/tests/files/node_modules/exceljs/package.json
@@ -1,3 +1,4 @@
 {
+  "name": "exceljs",
   "main": "./excel.js"
 }
diff --git a/tests/files/node_modules/jquery/package.json b/tests/files/node_modules/jquery/package.json
new file mode 100644
index 000000000..e0563fbf4
--- /dev/null
+++ b/tests/files/node_modules/jquery/package.json
@@ -0,0 +1,3 @@
+{
+  "name": "jquery"
+}
diff --git a/tests/files/node_modules/jsx-module/package.json b/tests/files/node_modules/jsx-module/package.json
new file mode 100644
index 000000000..6edbe5fc9
--- /dev/null
+++ b/tests/files/node_modules/jsx-module/package.json
@@ -0,0 +1,3 @@
+{
+  "name": "jsx-module"
+}
diff --git a/tests/files/node_modules/left-pad b/tests/files/node_modules/left-pad
deleted file mode 120000
index dbbbe75d2..000000000
--- a/tests/files/node_modules/left-pad
+++ /dev/null
@@ -1 +0,0 @@
-not-a-dependency
\ No newline at end of file
diff --git a/tests/files/node_modules/left-pad/index.js b/tests/files/node_modules/left-pad/index.js
new file mode 100644
index 000000000..e69de29bb
diff --git a/tests/files/node_modules/left-pad/not-a-dependency b/tests/files/node_modules/left-pad/not-a-dependency
new file mode 100644
index 000000000..e69de29bb
diff --git a/tests/files/node_modules/left-pad/package.json b/tests/files/node_modules/left-pad/package.json
new file mode 100644
index 000000000..a95a5e067
--- /dev/null
+++ b/tests/files/node_modules/left-pad/package.json
@@ -0,0 +1,3 @@
+{
+  "name": "left-pad"
+}
diff --git a/tests/files/node_modules/not-a-dependency/package.json b/tests/files/node_modules/not-a-dependency/package.json
new file mode 100644
index 000000000..857233121
--- /dev/null
+++ b/tests/files/node_modules/not-a-dependency/package.json
@@ -0,0 +1,3 @@
+{
+  "name": "not-a-dependency"
+}
diff --git a/tests/files/node_modules/react b/tests/files/node_modules/react
deleted file mode 120000
index dbbbe75d2..000000000
--- a/tests/files/node_modules/react
+++ /dev/null
@@ -1 +0,0 @@
-not-a-dependency
\ No newline at end of file
diff --git a/tests/files/node_modules/react/index.js b/tests/files/node_modules/react/index.js
new file mode 100644
index 000000000..8b1378917
--- /dev/null
+++ b/tests/files/node_modules/react/index.js
@@ -0,0 +1 @@
+
diff --git a/tests/files/node_modules/react/not-a-dependency b/tests/files/node_modules/react/not-a-dependency
new file mode 100644
index 000000000..e69de29bb
diff --git a/tests/files/node_modules/react/package.json b/tests/files/node_modules/react/package.json
new file mode 100644
index 000000000..bcbea4166
--- /dev/null
+++ b/tests/files/node_modules/react/package.json
@@ -0,0 +1,3 @@
+{
+  "name": "react"
+}
diff --git a/tests/files/node_modules/rxjs/index.js b/tests/files/node_modules/rxjs/index.js
new file mode 100644
index 000000000..ea9b101e1
--- /dev/null
+++ b/tests/files/node_modules/rxjs/index.js
@@ -0,0 +1 @@
+export default function () {}
diff --git a/tests/files/node_modules/rxjs/operators/index.js b/tests/files/node_modules/rxjs/operators/index.js
new file mode 100644
index 000000000..ea9b101e1
--- /dev/null
+++ b/tests/files/node_modules/rxjs/operators/index.js
@@ -0,0 +1 @@
+export default function () {}
diff --git a/tests/files/node_modules/rxjs/operators/package.json b/tests/files/node_modules/rxjs/operators/package.json
new file mode 100644
index 000000000..c857f8e31
--- /dev/null
+++ b/tests/files/node_modules/rxjs/operators/package.json
@@ -0,0 +1,5 @@
+{
+  "name": "rxjs/operators",
+  "version": "1.0.0",
+  "main": "index.js"
+}
diff --git a/tests/files/node_modules/rxjs/package.json b/tests/files/node_modules/rxjs/package.json
new file mode 100644
index 000000000..4fb9c6fa6
--- /dev/null
+++ b/tests/files/node_modules/rxjs/package.json
@@ -0,0 +1,5 @@
+{
+  "name": "rxjs",
+  "version": "1.0.0",
+  "main": "index.js"
+}
diff --git a/tests/files/package-named/index.js b/tests/files/package-named/index.js
new file mode 100644
index 000000000..ea9b101e1
--- /dev/null
+++ b/tests/files/package-named/index.js
@@ -0,0 +1 @@
+export default function () {}
diff --git a/tests/files/package-named/package.json b/tests/files/package-named/package.json
new file mode 100644
index 000000000..dbda7111f
--- /dev/null
+++ b/tests/files/package-named/package.json
@@ -0,0 +1,5 @@
+{
+  "name": "package-named",
+  "description": "Standard, named package",
+  "main": "index.js"
+}
\ No newline at end of file
diff --git a/tests/files/package-scoped/index.js b/tests/files/package-scoped/index.js
new file mode 100644
index 000000000..ea9b101e1
--- /dev/null
+++ b/tests/files/package-scoped/index.js
@@ -0,0 +1 @@
+export default function () {}
diff --git a/tests/files/package-scoped/package.json b/tests/files/package-scoped/package.json
new file mode 100644
index 000000000..a2d81cbae
--- /dev/null
+++ b/tests/files/package-scoped/package.json
@@ -0,0 +1,5 @@
+{
+  "name": "@scope/package-named",
+  "description": "Scoped, named package",
+  "main": "index.js"
+}
diff --git a/tests/files/package.json b/tests/files/package.json
index 0a60f28d3..365f02b6e 100644
--- a/tests/files/package.json
+++ b/tests/files/package.json
@@ -9,11 +9,14 @@
   },
   "dependencies": {
     "@org/package": "^1.0.0",
+    "esm-package": "^1.0.0",
     "jquery": "^3.1.0",
     "lodash.cond": "^4.3.0",
-    "pkg-up": "^1.0.0"
+    "find-up": "^1.0.0",
+    "rxjs": "^1.0.0"
   },
   "optionalDependencies": {
     "lodash.isarray": "^4.0.0"
-  }
+  },
+  "bundledDependencies": ["@generated/foo"]
 }
diff --git a/tests/files/package/index.js b/tests/files/package/index.js
new file mode 100644
index 000000000..ea9b101e1
--- /dev/null
+++ b/tests/files/package/index.js
@@ -0,0 +1 @@
+export default function () {}
diff --git a/tests/files/package/package.json b/tests/files/package/package.json
new file mode 100644
index 000000000..ad83f1ea7
--- /dev/null
+++ b/tests/files/package/package.json
@@ -0,0 +1,4 @@
+{
+  "description": "Unnamed package for reaching through main field - rxjs style",
+  "main": "index.js"
+}
\ No newline at end of file
diff --git a/tests/files/re-export-common-star.js b/tests/files/re-export-common-star.js
new file mode 100644
index 000000000..89a3196b1
--- /dev/null
+++ b/tests/files/re-export-common-star.js
@@ -0,0 +1 @@
+export * from './common'
diff --git a/tests/files/re-export-node_modules.js b/tests/files/re-export-node_modules.js
new file mode 100644
index 000000000..53a8ed162
--- /dev/null
+++ b/tests/files/re-export-node_modules.js
@@ -0,0 +1 @@
+export * from 'eslint'
diff --git a/tests/files/restricted-paths/client/one/a.js b/tests/files/restricted-paths/client/one/a.js
new file mode 100644
index 000000000..e69de29bb
diff --git a/tests/files/restricted-paths/server/c.ts b/tests/files/restricted-paths/server/c.ts
new file mode 100644
index 000000000..e69de29bb
diff --git a/tests/files/restricted-paths/server/one/a.js b/tests/files/restricted-paths/server/one/a.js
new file mode 100644
index 000000000..e69de29bb
diff --git a/tests/files/restricted-paths/server/one/b.js b/tests/files/restricted-paths/server/one/b.js
new file mode 100644
index 000000000..e69de29bb
diff --git a/tests/files/restricted-paths/server/three/a.js b/tests/files/restricted-paths/server/three/a.js
new file mode 100644
index 000000000..e69de29bb
diff --git a/tests/files/restricted-paths/server/two-new/a.js b/tests/files/restricted-paths/server/two-new/a.js
new file mode 100644
index 000000000..e69de29bb
diff --git a/tests/files/restricted-paths/server/two/a.js b/tests/files/restricted-paths/server/two/a.js
new file mode 100644
index 000000000..e69de29bb
diff --git a/tests/files/symlinked-module/index.js b/tests/files/symlinked-module/index.js
new file mode 100644
index 000000000..b1c6ea436
--- /dev/null
+++ b/tests/files/symlinked-module/index.js
@@ -0,0 +1 @@
+export default {}
diff --git a/tests/files/symlinked-module/package.json b/tests/files/symlinked-module/package.json
new file mode 100644
index 000000000..722be5c3c
--- /dev/null
+++ b/tests/files/symlinked-module/package.json
@@ -0,0 +1,5 @@
+{
+  "name": "@test-scope/some-module",
+  "version": "1.0.0",
+  "private": true
+}
diff --git a/tests/files/typescript-d-ts/.eslintrc b/tests/files/typescript-d-ts/.eslintrc
new file mode 100644
index 000000000..f22e9cb62
--- /dev/null
+++ b/tests/files/typescript-d-ts/.eslintrc
@@ -0,0 +1,12 @@
+{
+  "overrides": [
+    {
+      "files": "**.ts",
+      "parser": "@typescript-eslint/parser",
+      "extends": "../../../config/typescript",
+      "rules": {
+        "import/export": "error",
+      },
+    },
+  ],
+}
diff --git a/tests/files/typescript-d-ts/file1.ts b/tests/files/typescript-d-ts/file1.ts
new file mode 100644
index 000000000..872c30e8a
--- /dev/null
+++ b/tests/files/typescript-d-ts/file1.ts
@@ -0,0 +1,6 @@
+declare namespace ts {
+  const x: string;
+  export { x };
+}
+
+export = ts;
diff --git a/tests/files/typescript-d-ts/file2.ts b/tests/files/typescript-d-ts/file2.ts
new file mode 100644
index 000000000..e8ed5afca
--- /dev/null
+++ b/tests/files/typescript-d-ts/file2.ts
@@ -0,0 +1 @@
+export * from './file1.ts'
diff --git a/tests/files/typescript-declare-interface.d.ts b/tests/files/typescript-declare-interface.d.ts
new file mode 100644
index 000000000..b572b62e9
--- /dev/null
+++ b/tests/files/typescript-declare-interface.d.ts
@@ -0,0 +1,11 @@
+declare interface foo {
+  a: string;
+}
+
+declare namespace SomeNamespace {
+  type foobar = foo & {
+    b: string;
+  }
+}
+
+export = SomeNamespace
diff --git a/tests/files/typescript-declare-module.ts b/tests/files/typescript-declare-module.ts
new file mode 100644
index 000000000..8a9e304e9
--- /dev/null
+++ b/tests/files/typescript-declare-module.ts
@@ -0,0 +1,3 @@
+declare module "typescript-declare-module-foo" {
+  export const foo: string;
+}
diff --git a/tests/files/typescript-declare-nested.d.ts b/tests/files/typescript-declare-nested.d.ts
new file mode 100644
index 000000000..dc6b0049a
--- /dev/null
+++ b/tests/files/typescript-declare-nested.d.ts
@@ -0,0 +1,15 @@
+declare namespace foo {
+  interface SomeInterface {
+    a: string;
+  }
+}
+
+declare namespace foo.bar {
+  interface SomeOtherInterface {
+    b: string;
+  }
+
+  function MyFunction();
+}
+
+export = foo;
diff --git a/tests/files/typescript-default.ts b/tests/files/typescript-default.ts
new file mode 100644
index 000000000..6d9a8f42c
--- /dev/null
+++ b/tests/files/typescript-default.ts
@@ -0,0 +1 @@
+export default function foobar() {};
diff --git a/tests/files/typescript-export-as-default-namespace/index.d.ts b/tests/files/typescript-export-as-default-namespace/index.d.ts
new file mode 100644
index 000000000..953c3410b
--- /dev/null
+++ b/tests/files/typescript-export-as-default-namespace/index.d.ts
@@ -0,0 +1,3 @@
+export as namespace Foo
+
+export function bar(): void
diff --git a/tests/files/typescript-export-as-default-namespace/tsconfig.json b/tests/files/typescript-export-as-default-namespace/tsconfig.json
new file mode 100644
index 000000000..a72ee3e88
--- /dev/null
+++ b/tests/files/typescript-export-as-default-namespace/tsconfig.json
@@ -0,0 +1,5 @@
+{
+    "compilerOptions": {
+        "esModuleInterop": true
+    }
+}
diff --git a/tests/files/typescript-export-assign-default-namespace/index.d.ts b/tests/files/typescript-export-assign-default-namespace/index.d.ts
new file mode 100644
index 000000000..2ad4822f7
--- /dev/null
+++ b/tests/files/typescript-export-assign-default-namespace/index.d.ts
@@ -0,0 +1,3 @@
+export = FooBar;
+
+declare namespace FooBar {}
diff --git a/tests/files/typescript-export-assign-default-namespace/tsconfig.json b/tests/files/typescript-export-assign-default-namespace/tsconfig.json
new file mode 100644
index 000000000..a72ee3e88
--- /dev/null
+++ b/tests/files/typescript-export-assign-default-namespace/tsconfig.json
@@ -0,0 +1,5 @@
+{
+    "compilerOptions": {
+        "esModuleInterop": true
+    }
+}
diff --git a/tests/files/typescript-export-assign-default-reexport.ts b/tests/files/typescript-export-assign-default-reexport.ts
new file mode 100644
index 000000000..2fd502539
--- /dev/null
+++ b/tests/files/typescript-export-assign-default-reexport.ts
@@ -0,0 +1,2 @@
+import { getFoo } from './typescript';
+export = getFoo;
diff --git a/tests/files/typescript-export-assign-default.d.ts b/tests/files/typescript-export-assign-default.d.ts
new file mode 100644
index 000000000..f871ed926
--- /dev/null
+++ b/tests/files/typescript-export-assign-default.d.ts
@@ -0,0 +1,3 @@
+export = foobar;
+
+declare const foobar: number;
diff --git a/tests/files/typescript-export-assign-function.ts b/tests/files/typescript-export-assign-function.ts
new file mode 100644
index 000000000..930d6dace
--- /dev/null
+++ b/tests/files/typescript-export-assign-function.ts
@@ -0,0 +1 @@
+export = function foo() {};
diff --git a/tests/files/typescript-export-assign-mixed.d.ts b/tests/files/typescript-export-assign-mixed.d.ts
new file mode 100644
index 000000000..8bf4c34b8
--- /dev/null
+++ b/tests/files/typescript-export-assign-mixed.d.ts
@@ -0,0 +1,11 @@
+export = foobar;
+
+declare function foobar(): void;
+declare namespace foobar {
+  type MyType = string
+  enum MyEnum {
+    Foo,
+    Bar,
+    Baz
+  }
+}
diff --git a/tests/files/typescript-export-assign-namespace-merged.d.ts b/tests/files/typescript-export-assign-namespace-merged.d.ts
new file mode 100644
index 000000000..377a10d20
--- /dev/null
+++ b/tests/files/typescript-export-assign-namespace-merged.d.ts
@@ -0,0 +1,41 @@
+export = AssignedNamespace;
+
+declare namespace AssignedNamespace {
+  type MyType = string
+  enum MyEnum {
+    Foo,
+    Bar,
+    Baz
+  }
+}
+
+declare namespace AssignedNamespace {
+  interface Foo {
+    native: string | number
+    typedef: MyType
+    enum: MyEnum
+  }
+
+  abstract class Bar {
+    abstract foo(): Foo
+
+    method();
+  }
+
+  export function getFoo() : MyType;
+
+  export module MyModule {
+    export function ModuleFunction();
+  }
+
+  export namespace MyNamespace {
+    export function NamespaceFunction();
+
+    export module NSModule {
+      export function NSModuleFunction();
+    }
+  }
+
+  // Export-assignment exports all members in the namespace, explicitly exported or not.
+  // interface NotExported {}
+}
diff --git a/tests/files/typescript-export-assign.d.ts b/tests/files/typescript-export-assign-namespace.d.ts
similarity index 100%
rename from tests/files/typescript-export-assign.d.ts
rename to tests/files/typescript-export-assign-namespace.d.ts
diff --git a/tests/files/typescript-export-assign-object/index.ts b/tests/files/typescript-export-assign-object/index.ts
new file mode 100644
index 000000000..8899e3fba
--- /dev/null
+++ b/tests/files/typescript-export-assign-object/index.ts
@@ -0,0 +1,5 @@
+const someObj = {
+  FooBar: 12,
+};
+
+export = someObj;
diff --git a/tests/files/typescript-export-assign-object/tsconfig.json b/tests/files/typescript-export-assign-object/tsconfig.json
new file mode 100644
index 000000000..a72ee3e88
--- /dev/null
+++ b/tests/files/typescript-export-assign-object/tsconfig.json
@@ -0,0 +1,5 @@
+{
+    "compilerOptions": {
+        "esModuleInterop": true
+    }
+}
diff --git a/tests/files/typescript-export-assign-property.ts b/tests/files/typescript-export-assign-property.ts
new file mode 100644
index 000000000..8dc2b9981
--- /dev/null
+++ b/tests/files/typescript-export-assign-property.ts
@@ -0,0 +1,3 @@
+const AnalyticsNode = { Analytics: {} };
+
+export = AnalyticsNode.Analytics;
diff --git a/tests/files/typescript-export-react-test-renderer/index.d.ts b/tests/files/typescript-export-react-test-renderer/index.d.ts
new file mode 100644
index 000000000..ff70c7135
--- /dev/null
+++ b/tests/files/typescript-export-react-test-renderer/index.d.ts
@@ -0,0 +1,19 @@
+// case from @types/react-test-renderer
+
+export {};
+
+export interface ReactTestRendererJSON {
+    type: string;
+    props: { [propName: string]: any };
+    children: null | ReactTestRendererNode[];
+}
+export type ReactTestRendererNode = ReactTestRendererJSON | string;
+export interface ReactTestRendererTree extends ReactTestRendererJSON {
+    nodeType: 'component' | 'host';
+    instance: any;
+    rendered: null | ReactTestRendererTree | ReactTestRendererTree[];
+}
+
+export function create(nextElement: any, options?: any): any;
+
+export function act(callback: () => Promise<any>): Promise<undefined>;
diff --git a/tests/files/typescript-export-react-test-renderer/tsconfig.json b/tests/files/typescript-export-react-test-renderer/tsconfig.json
new file mode 100644
index 000000000..a72ee3e88
--- /dev/null
+++ b/tests/files/typescript-export-react-test-renderer/tsconfig.json
@@ -0,0 +1,5 @@
+{
+    "compilerOptions": {
+        "esModuleInterop": true
+    }
+}
diff --git a/tests/files/typescript-extended-config/index.d.ts b/tests/files/typescript-extended-config/index.d.ts
new file mode 100644
index 000000000..2ad4822f7
--- /dev/null
+++ b/tests/files/typescript-extended-config/index.d.ts
@@ -0,0 +1,3 @@
+export = FooBar;
+
+declare namespace FooBar {}
diff --git a/tests/files/typescript-extended-config/tsconfig.base.json b/tests/files/typescript-extended-config/tsconfig.base.json
new file mode 100644
index 000000000..2f9804271
--- /dev/null
+++ b/tests/files/typescript-extended-config/tsconfig.base.json
@@ -0,0 +1,5 @@
+{
+  "compilerOptions": {
+    "esModuleInterop": true
+  }
+}
diff --git a/tests/files/typescript-extended-config/tsconfig.json b/tests/files/typescript-extended-config/tsconfig.json
new file mode 100644
index 000000000..97a330960
--- /dev/null
+++ b/tests/files/typescript-extended-config/tsconfig.json
@@ -0,0 +1,4 @@
+{
+  "extends": "./tsconfig.base.json",
+  "compilerOptions": {}
+}
diff --git a/tests/files/typescript-no-compiler-options/index.d.ts b/tests/files/typescript-no-compiler-options/index.d.ts
new file mode 100644
index 000000000..953c3410b
--- /dev/null
+++ b/tests/files/typescript-no-compiler-options/index.d.ts
@@ -0,0 +1,3 @@
+export as namespace Foo
+
+export function bar(): void
diff --git a/tests/files/typescript-no-compiler-options/tsconfig.json b/tests/files/typescript-no-compiler-options/tsconfig.json
new file mode 100644
index 000000000..2c63c0851
--- /dev/null
+++ b/tests/files/typescript-no-compiler-options/tsconfig.json
@@ -0,0 +1,2 @@
+{
+}
diff --git a/tests/files/typescript.ts b/tests/files/typescript.ts
index 7f90314e4..f8fe2e8e0 100644
--- a/tests/files/typescript.ts
+++ b/tests/files/typescript.ts
@@ -23,14 +23,14 @@ export function getFoo() : MyType {
 }
 
 export module MyModule {
-  export function ModuleFunction(){}
+  export function ModuleFunction() {}
 }
 
 export namespace MyNamespace {
-  export function NamespaceFunction(){}
+  export function NamespaceFunction() {}
 
   export module NSModule {
-    export function NSModuleFunction(){}
+    export function NSModuleFunction() {}
   }
 }
 
diff --git a/tests/files/unused-modules-reexport-crash/src/App.tsx b/tests/files/unused-modules-reexport-crash/src/App.tsx
new file mode 100644
index 000000000..c797a976c
--- /dev/null
+++ b/tests/files/unused-modules-reexport-crash/src/App.tsx
@@ -0,0 +1,5 @@
+import { hello } from './magic/test'
+
+hello();
+
+export default function App() {};
diff --git a/tests/files/unused-modules-reexport-crash/src/index.tsx b/tests/files/unused-modules-reexport-crash/src/index.tsx
new file mode 100644
index 000000000..124b1745d
--- /dev/null
+++ b/tests/files/unused-modules-reexport-crash/src/index.tsx
@@ -0,0 +1,3 @@
+import App from './App';
+
+export const x = App
\ No newline at end of file
diff --git a/tests/files/unused-modules-reexport-crash/src/magic/index.js b/tests/files/unused-modules-reexport-crash/src/magic/index.js
new file mode 100644
index 000000000..ac3f46bb1
--- /dev/null
+++ b/tests/files/unused-modules-reexport-crash/src/magic/index.js
@@ -0,0 +1 @@
+export * from './test'
diff --git a/tests/files/unused-modules-reexport-crash/src/magic/test.js b/tests/files/unused-modules-reexport-crash/src/magic/test.js
new file mode 100644
index 000000000..a6d74afd9
--- /dev/null
+++ b/tests/files/unused-modules-reexport-crash/src/magic/test.js
@@ -0,0 +1,7 @@
+export function hello() {
+	console.log('hello!!');
+}
+
+export function unused() {
+	console.log('im unused!!');
+}
\ No newline at end of file
diff --git a/tests/files/webpack.config.js b/tests/files/webpack.config.js
index 980c32425..bbe81b359 100644
--- a/tests/files/webpack.config.js
+++ b/tests/files/webpack.config.js
@@ -2,5 +2,9 @@ module.exports = {
   resolve: {
     extensions: ['', '.js', '.jsx'],
     root: __dirname,
+    alias: {
+      'alias/chai$': 'chai', // alias for no-extraneous-dependencies tests
+      'alias/esm-package': 'esm-package' // alias for no-extraneous-dependencies tests
+    }
   },
 }
diff --git a/tests/files/with-typescript-dev-dependencies/package.json b/tests/files/with-typescript-dev-dependencies/package.json
new file mode 100644
index 000000000..f859f5085
--- /dev/null
+++ b/tests/files/with-typescript-dev-dependencies/package.json
@@ -0,0 +1,5 @@
+{
+  "devDependencies": {
+    "a": "*"
+  }
+}
diff --git a/tests/index.js b/tests/index.js
new file mode 100644
index 000000000..abc02b839
--- /dev/null
+++ b/tests/index.js
@@ -0,0 +1 @@
+export * from './files';
diff --git a/tests/src/cli.js b/tests/src/cli.js
index 93a4d43d7..60b8382d0 100644
--- a/tests/src/cli.js
+++ b/tests/src/cli.js
@@ -1,24 +1,169 @@
 /**
  * tests that require fully booting up ESLint
  */
-import { expect } from 'chai'
-import { CLIEngine } from 'eslint'
+import path from 'path';
+
+import { expect } from 'chai';
+import { CLIEngine, ESLint } from 'eslint';
+import eslintPkg from 'eslint/package.json';
+import semver from 'semver';
+import * as importPlugin from '../../src/index';
 
 describe('CLI regression tests', function () {
   describe('issue #210', function () {
-    let cli
+    let eslint;
+    let cli;
     before(function () {
-      cli = new CLIEngine({
-        useEslintrc: false,
-        configFile: './tests/files/issue210.config.js',
-        rulePaths: ['./src/rules'],
-        rules: {
-          'named': 2,
-        },
-      })
-    })
+      if (ESLint) {
+        if (semver.satisfies(eslintPkg.version, '>= 9')) {
+          eslint = new ESLint({
+            overrideConfigFile: './tests/files/issue210.config.flat.js',
+            overrideConfig: {
+              rules: {
+                'import/named': 2,
+              },
+            },
+            plugins: { 'eslint-plugin-import': importPlugin },
+          });
+        } else {
+          eslint = new ESLint({
+            useEslintrc: false,
+            overrideConfigFile: './tests/files/issue210.config.js',
+            rulePaths: ['./src/rules'],
+            overrideConfig: {
+              rules: {
+                named: 2,
+              },
+            },
+            plugins: { 'eslint-plugin-import': importPlugin },
+          });
+        }
+      } else {
+        cli = new CLIEngine({
+          useEslintrc: false,
+          configFile: './tests/files/issue210.config.js',
+          rulePaths: ['./src/rules'],
+          rules: {
+            named: 2,
+          },
+        });
+        cli.addPlugin('eslint-plugin-import', importPlugin);
+      }
+    });
     it("doesn't throw an error on gratuitous, erroneous self-reference", function () {
-      expect(() => cli.executeOnFiles(['./tests/files/issue210.js'])).not.to.throw(Error)
-    })
-  })
-})
+      if (eslint) {
+        return eslint.lintFiles(['./tests/files/issue210.js'])
+          .catch(() => expect.fail());
+      } else {
+        expect(() => cli.executeOnFiles(['./tests/files/issue210.js'])).not.to.throw();
+      }
+    });
+  });
+
+  describe('issue #1645', function () {
+    let eslint;
+    let cli;
+    beforeEach(function () {
+      if (semver.satisfies(eslintPkg.version, '< 6')) {
+        this.skip();
+      } else {
+        if (ESLint) {
+          if (semver.satisfies(eslintPkg.version, '>= 9')) {
+            eslint = new ESLint({
+              overrideConfigFile: './tests/files/just-json-files/eslint.config.js',
+              plugins: { 'eslint-plugin-import': importPlugin },
+            });
+          } else {
+            eslint = new ESLint({
+              useEslintrc: false,
+              overrideConfigFile: './tests/files/just-json-files/.eslintrc.json',
+              rulePaths: ['./src/rules'],
+              ignore: false,
+              plugins: { 'eslint-plugin-import': importPlugin },
+            });
+          }
+        } else {
+          cli = new CLIEngine({
+            useEslintrc: false,
+            configFile: './tests/files/just-json-files/.eslintrc.json',
+            rulePaths: ['./src/rules'],
+            ignore: false,
+          });
+          cli.addPlugin('eslint-plugin-import', importPlugin);
+        }
+      }
+    });
+
+    it('throws an error on invalid JSON', () => {
+      const invalidJSON = './tests/files/just-json-files/invalid.json';
+      if (eslint) {
+        return eslint.lintFiles([invalidJSON]).then((results) => {
+          expect(results).to.eql(
+            [
+              {
+                filePath: path.resolve(invalidJSON),
+                messages: [
+                  {
+                    column: 2,
+                    endColumn: 3,
+                    endLine: 1,
+                    line: 1,
+                    message: 'Expected a JSON object, array or literal.',
+                    nodeType: results[0].messages[0].nodeType, // we don't care about this one
+                    ruleId: 'json/*',
+                    severity: 2,
+                    source: results[0].messages[0].source, // NewLine-characters might differ depending on git-settings
+                  },
+                ],
+                errorCount: 1,
+                ...semver.satisfies(eslintPkg.version, '>= 7.32 || ^8.0.0') && {
+                  fatalErrorCount: 0,
+                },
+                warningCount: 0,
+                fixableErrorCount: 0,
+                fixableWarningCount: 0,
+                source: results[0].source, // NewLine-characters might differ depending on git-settings
+                ...semver.satisfies(eslintPkg.version, '>= 8.8') && {
+                  suppressedMessages: [],
+                },
+                usedDeprecatedRules: results[0].usedDeprecatedRules, // we don't care about this one
+              },
+            ],
+          );
+        });
+      } else {
+        const results = cli.executeOnFiles([invalidJSON]);
+        expect(results).to.eql({
+          results: [
+            {
+              filePath: path.resolve(invalidJSON),
+              messages: [
+                {
+                  column: 2,
+                  endColumn: 3,
+                  endLine: 1,
+                  line: 1,
+                  message: 'Expected a JSON object, array or literal.',
+                  nodeType: results.results[0].messages[0].nodeType, // we don't care about this one
+                  ruleId: 'json/*',
+                  severity: 2,
+                  source: results.results[0].messages[0].source, // NewLine-characters might differ depending on git-settings
+                },
+              ],
+              errorCount: 1,
+              warningCount: 0,
+              fixableErrorCount: 0,
+              fixableWarningCount: 0,
+              source: results.results[0].source, // NewLine-characters might differ depending on git-settings
+            },
+          ],
+          errorCount: 1,
+          warningCount: 0,
+          fixableErrorCount: 0,
+          fixableWarningCount: 0,
+          usedDeprecatedRules: results.usedDeprecatedRules, // we don't care about this one
+        });
+      }
+    });
+  });
+});
diff --git a/tests/src/config/typescript.js b/tests/src/config/typescript.js
new file mode 100644
index 000000000..e3fdd1099
--- /dev/null
+++ b/tests/src/config/typescript.js
@@ -0,0 +1,14 @@
+import path from 'path';
+import { expect } from 'chai';
+
+const config = require(path.join(__dirname, '..', '..', '..', 'config', 'typescript'));
+
+describe('config typescript', () => {
+  // https://github.com/import-js/eslint-plugin-import/issues/1525
+  it('should mark @types paths as external', () => {
+    const externalModuleFolders = config.settings['import/external-module-folders'];
+    expect(externalModuleFolders).to.exist;
+    expect(externalModuleFolders).to.contain('node_modules');
+    expect(externalModuleFolders).to.contain('node_modules/@types');
+  });
+});
diff --git a/tests/src/core/docsUrl.js b/tests/src/core/docsUrl.js
index 2ba778a4a..91055bf26 100644
--- a/tests/src/core/docsUrl.js
+++ b/tests/src/core/docsUrl.js
@@ -1,14 +1,14 @@
-import { expect } from 'chai'
+import { expect } from 'chai';
 
-import pkg from '../../../package.json'
-import docsUrl from '../../../src/docsUrl'
+import pkg from '../../../package.json';
+import docsUrl from '../../../src/docsUrl';
 
 describe('docsUrl', function () {
   it('returns the rule documentation URL when given a rule name', function () {
-    expect(docsUrl('foo')).to.equal(`https://github.com/benmosher/eslint-plugin-import/blob/v${pkg.version}/docs/rules/foo.md`)
-  })
+    expect(docsUrl('foo')).to.equal(`https://github.com/import-js/eslint-plugin-import/blob/v${pkg.version}/docs/rules/foo.md`);
+  });
 
   it('supports an optional commit-ish parameter', function () {
-    expect(docsUrl('foo', 'bar')).to.equal('https://github.com/benmosher/eslint-plugin-import/blob/bar/docs/rules/foo.md')
-  })
-})
+    expect(docsUrl('foo', 'bar')).to.equal('https://github.com/import-js/eslint-plugin-import/blob/bar/docs/rules/foo.md');
+  });
+});
diff --git a/tests/src/core/eslintParser.js b/tests/src/core/eslintParser.js
new file mode 100644
index 000000000..f53a394de
--- /dev/null
+++ b/tests/src/core/eslintParser.js
@@ -0,0 +1,7 @@
+module.exports = {
+  parseForESLint() {
+    return {
+      ast: {},
+    };
+  },
+};
diff --git a/tests/src/core/getExports.js b/tests/src/core/getExports.js
index 44edcf629..a02edb85c 100644
--- a/tests/src/core/getExports.js
+++ b/tests/src/core/getExports.js
@@ -1,186 +1,208 @@
-import { expect } from  'chai'
-import semver from 'semver'
-import eslintPkg from 'eslint/package.json'
-import ExportMap from '../../../src/ExportMap'
-
-import * as fs from 'fs'
-
-import { getFilename } from '../utils'
-import * as unambiguous from 'eslint-module-utils/unambiguous'
+import { expect } from  'chai';
+import fs from 'fs';
+import semver from 'semver';
+import sinon from 'sinon';
+import eslintPkg from 'eslint/package.json';
+import { test as testUnambiguous } from 'eslint-module-utils/unambiguous';
+import typescriptPkg from 'typescript/package.json';
+import * as tsConfigLoader from 'tsconfig-paths/lib/tsconfig-loader';
+
+import ExportMapBuilder from '../../../src/exportMap/builder';
+import { getFilename } from '../utils';
+
+const babelPath = require.resolve('babel-eslint');
+const hypotheticalLocation = babelPath.replace('index.js', 'visitor-keys.js');
+const isVisitorKeysSupported = fs.existsSync(hypotheticalLocation);
 
 describe('ExportMap', function () {
-  const fakeContext = {
-    getFilename: getFilename,
-    settings: {},
-    parserPath: 'babel-eslint',
-  }
+  const fakeContext = Object.assign(
+    semver.satisfies(eslintPkg.version, '>= 7.28') ? {
+      getFilename() { throw new Error('Should call getPhysicalFilename() instead of getFilename()'); },
+      getPhysicalFilename: getFilename,
+    } : {
+      getFilename,
+    },
+    {
+      settings: {},
+      parserPath: require.resolve('babel-eslint'),
+    },
+  );
 
   it('handles ExportAllDeclaration', function () {
-    var imports
+    let imports;
     expect(function () {
-      imports = ExportMap.get('./export-all', fakeContext)
-    }).not.to.throw(Error)
+      imports = ExportMapBuilder.get('./export-all', fakeContext);
+    }).not.to.throw(Error);
 
-    expect(imports).to.exist
-    expect(imports.has('foo')).to.be.true
+    expect(imports).to.exist;
+    expect(imports.has('foo')).to.be.true;
 
-  })
+  });
 
-  it('returns a cached copy on subsequent requests', function () {
-    expect(ExportMap.get('./named-exports', fakeContext))
-      .to.exist.and.equal(ExportMap.get('./named-exports', fakeContext))
-  })
+  (isVisitorKeysSupported ? it : it.skip)('returns a cached copy on subsequent requests', function () {
+    expect(ExportMapBuilder.get('./named-exports', fakeContext))
+      .to.exist.and.equal(ExportMapBuilder.get('./named-exports', fakeContext));
+  });
+
+  it('does not return a cached copy if the parse does not yield a visitor keys', function () {
+    const mockContext = {
+      ...fakeContext,
+      parserPath: 'not-real',
+    };
+    expect(ExportMapBuilder.get('./named-exports', mockContext))
+      .to.exist.and.not.equal(ExportMapBuilder.get('./named-exports', mockContext));
+  });
 
   it('does not return a cached copy after modification', (done) => {
-    const firstAccess = ExportMap.get('./mutator', fakeContext)
-    expect(firstAccess).to.exist
+    const firstAccess = ExportMapBuilder.get('./mutator', fakeContext);
+    expect(firstAccess).to.exist;
 
     // mutate (update modified time)
-    const newDate = new Date()
+    const newDate = new Date();
     fs.utimes(getFilename('mutator.js'), newDate, newDate, (error) => {
-      expect(error).not.to.exist
-      expect(ExportMap.get('./mutator', fakeContext)).not.to.equal(firstAccess)
-      done()
-    })
-  })
+      expect(error).not.to.exist;
+      expect(ExportMapBuilder.get('./mutator', fakeContext)).not.to.equal(firstAccess);
+      done();
+    });
+  });
 
   it('does not return a cached copy with different settings', () => {
-    const firstAccess = ExportMap.get('./named-exports', fakeContext)
-    expect(firstAccess).to.exist
+    const firstAccess = ExportMapBuilder.get('./named-exports', fakeContext);
+    expect(firstAccess).to.exist;
 
-    const differentSettings = Object.assign(
-      {},
-      fakeContext,
-      { parserPath: 'espree' })
+    const differentSettings = {
+      ...fakeContext,
+      parserPath: 'espree',
+    };
 
-    expect(ExportMap.get('./named-exports', differentSettings))
+    expect(ExportMapBuilder.get('./named-exports', differentSettings))
       .to.exist.and
-      .not.to.equal(firstAccess)
-  })
+      .not.to.equal(firstAccess);
+  });
 
   it('does not throw for a missing file', function () {
-    var imports
+    let imports;
     expect(function () {
-      imports = ExportMap.get('./does-not-exist', fakeContext)
-    }).not.to.throw(Error)
+      imports = ExportMapBuilder.get('./does-not-exist', fakeContext);
+    }).not.to.throw(Error);
 
-    expect(imports).not.to.exist
+    expect(imports).not.to.exist;
 
-  })
+  });
 
   it('exports explicit names for a missing file in exports', function () {
-    var imports
+    let imports;
     expect(function () {
-      imports = ExportMap.get('./exports-missing', fakeContext)
-    }).not.to.throw(Error)
+      imports = ExportMapBuilder.get('./exports-missing', fakeContext);
+    }).not.to.throw(Error);
 
-    expect(imports).to.exist
-    expect(imports.has('bar')).to.be.true
+    expect(imports).to.exist;
+    expect(imports.has('bar')).to.be.true;
 
-  })
+  });
 
   it('finds exports for an ES7 module with babel-eslint', function () {
-    const path = getFilename('jsx/FooES7.js')
-        , contents = fs.readFileSync(path, { encoding: 'utf8' })
-    var imports = ExportMap.parse(
+    const path = getFilename('jsx/FooES7.js');
+    const contents = fs.readFileSync(path, { encoding: 'utf8' });
+    const imports = ExportMapBuilder.parse(
       path,
       contents,
-      { parserPath: 'babel-eslint', settings: {} }
-    )
+      { parserPath: 'babel-eslint', settings: {} },
+    );
 
-    expect(imports, 'imports').to.exist
-    expect(imports.errors).to.be.empty
-    expect(imports.get('default'), 'default export').to.exist
-    expect(imports.has('Bar')).to.be.true
-  })
+    expect(imports, 'imports').to.exist;
+    expect(imports.errors).to.be.empty;
+    expect(imports.get('default'), 'default export').to.exist;
+    expect(imports.has('Bar')).to.be.true;
+  });
 
   context('deprecation metadata', function () {
 
     function jsdocTests(parseContext, lineEnding) {
       context('deprecated imports', function () {
-        let imports
+        let imports;
         before('parse file', function () {
-          const path = getFilename('deprecated.js')
-              , contents = fs.readFileSync(path, { encoding: 'utf8' }).replace(/[\r]\n/g, lineEnding)
-          imports = ExportMap.parse(path, contents, parseContext)
+          const path = getFilename('deprecated.js');
+          const contents = fs.readFileSync(path, { encoding: 'utf8' }).replace(/[\r]\n/g, lineEnding);
+          imports = ExportMapBuilder.parse(path, contents, parseContext);
 
           // sanity checks
-          expect(imports.errors).to.be.empty
-        })
+          expect(imports.errors).to.be.empty;
+        });
 
         it('works with named imports.', function () {
-          expect(imports.has('fn')).to.be.true
+          expect(imports.has('fn')).to.be.true;
 
           expect(imports.get('fn'))
-            .to.have.nested.property('doc.tags[0].title', 'deprecated')
+            .to.have.nested.property('doc.tags[0].title', 'deprecated');
           expect(imports.get('fn'))
-            .to.have.nested.property('doc.tags[0].description', 'please use \'x\' instead.')
-        })
+            .to.have.nested.property('doc.tags[0].description', 'please use \'x\' instead.');
+        });
 
         it('works with default imports.', function () {
-          expect(imports.has('default')).to.be.true
-          const importMeta = imports.get('default')
+          expect(imports.has('default')).to.be.true;
+          const importMeta = imports.get('default');
 
-          expect(importMeta).to.have.nested.property('doc.tags[0].title', 'deprecated')
-          expect(importMeta).to.have.nested.property('doc.tags[0].description', 'this is awful, use NotAsBadClass.')
-        })
+          expect(importMeta).to.have.nested.property('doc.tags[0].title', 'deprecated');
+          expect(importMeta).to.have.nested.property('doc.tags[0].description', 'this is awful, use NotAsBadClass.');
+        });
 
         it('works with variables.', function () {
-          expect(imports.has('MY_TERRIBLE_ACTION')).to.be.true
-          const importMeta = imports.get('MY_TERRIBLE_ACTION')
+          expect(imports.has('MY_TERRIBLE_ACTION')).to.be.true;
+          const importMeta = imports.get('MY_TERRIBLE_ACTION');
 
           expect(importMeta).to.have.nested.property(
-            'doc.tags[0].title', 'deprecated')
+            'doc.tags[0].title', 'deprecated');
           expect(importMeta).to.have.nested.property(
-            'doc.tags[0].description', 'please stop sending/handling this action type.')
-        })
+            'doc.tags[0].description', 'please stop sending/handling this action type.');
+        });
 
         context('multi-line variables', function () {
           it('works for the first one', function () {
-            expect(imports.has('CHAIN_A')).to.be.true
-            const importMeta = imports.get('CHAIN_A')
+            expect(imports.has('CHAIN_A')).to.be.true;
+            const importMeta = imports.get('CHAIN_A');
 
             expect(importMeta).to.have.nested.property(
-              'doc.tags[0].title', 'deprecated')
+              'doc.tags[0].title', 'deprecated');
             expect(importMeta).to.have.nested.property(
-              'doc.tags[0].description', 'this chain is awful')
-          })
+              'doc.tags[0].description', 'this chain is awful');
+          });
           it('works for the second one', function () {
-            expect(imports.has('CHAIN_B')).to.be.true
-            const importMeta = imports.get('CHAIN_B')
+            expect(imports.has('CHAIN_B')).to.be.true;
+            const importMeta = imports.get('CHAIN_B');
 
             expect(importMeta).to.have.nested.property(
-              'doc.tags[0].title', 'deprecated')
+              'doc.tags[0].title', 'deprecated');
             expect(importMeta).to.have.nested.property(
-              'doc.tags[0].description', 'so awful')
-          })
+              'doc.tags[0].description', 'so awful');
+          });
           it('works for the third one, etc.', function () {
-            expect(imports.has('CHAIN_C')).to.be.true
-            const importMeta = imports.get('CHAIN_C')
+            expect(imports.has('CHAIN_C')).to.be.true;
+            const importMeta = imports.get('CHAIN_C');
 
             expect(importMeta).to.have.nested.property(
-              'doc.tags[0].title', 'deprecated')
+              'doc.tags[0].title', 'deprecated');
             expect(importMeta).to.have.nested.property(
-              'doc.tags[0].description', 'still terrible')
-          })
-        })
-      })
+              'doc.tags[0].description', 'still terrible');
+          });
+        });
+      });
 
       context('full module', function () {
-        let imports
+        let imports;
         before('parse file', function () {
-          const path = getFilename('deprecated-file.js')
-              , contents = fs.readFileSync(path, { encoding: 'utf8' })
-          imports = ExportMap.parse(path, contents, parseContext)
+          const path = getFilename('deprecated-file.js');
+          const contents = fs.readFileSync(path, { encoding: 'utf8' });
+          imports = ExportMapBuilder.parse(path, contents, parseContext);
 
           // sanity checks
-          expect(imports.errors).to.be.empty
-        })
+          expect(imports.errors).to.be.empty;
+        });
 
         it('has JSDoc metadata', function () {
-          expect(imports.doc).to.exist
-        })
-      })
+          expect(imports.doc).to.exist;
+        });
+      });
     }
 
     context('default parser', function () {
@@ -192,7 +214,7 @@ describe('ExportMap', function () {
           attachComment: true,
         },
         settings: {},
-      }, '\n')
+      }, '\n');
       jsdocTests({
         parserPath: 'espree',
         parserOptions: {
@@ -201,8 +223,8 @@ describe('ExportMap', function () {
           attachComment: true,
         },
         settings: {},
-      }, '\r\n')
-    })
+      }, '\r\n');
+    });
 
     context('babel-eslint', function () {
       jsdocTests({
@@ -213,7 +235,7 @@ describe('ExportMap', function () {
           attachComment: true,
         },
         settings: {},
-      }, '\n')
+      }, '\n');
       jsdocTests({
         parserPath: 'babel-eslint',
         parserOptions: {
@@ -222,194 +244,238 @@ describe('ExportMap', function () {
           attachComment: true,
         },
         settings: {},
-      }, '\r\n')
-    })
-  })
+      }, '\r\n');
+    });
+  });
 
   context('exported static namespaces', function () {
-    const espreeContext = { parserPath: 'espree', parserOptions: { ecmaVersion: 2015, sourceType: 'module' }, settings: {} }
-    const babelContext = { parserPath: 'babel-eslint', parserOptions: { ecmaVersion: 2015, sourceType: 'module' }, settings: {} }
+    const espreeContext = { parserPath: 'espree', parserOptions: { ecmaVersion: 2015, sourceType: 'module' }, settings: {} };
+    const babelContext = { parserPath: 'babel-eslint', parserOptions: { ecmaVersion: 2015, sourceType: 'module' }, settings: {} };
 
     it('works with espree & traditional namespace exports', function () {
-      const path = getFilename('deep/a.js')
-          , contents = fs.readFileSync(path, { encoding: 'utf8' })
-      const a = ExportMap.parse(path, contents, espreeContext)
-      expect(a.errors).to.be.empty
-      expect(a.get('b').namespace).to.exist
-      expect(a.get('b').namespace.has('c')).to.be.true
-    })
+      const path = getFilename('deep/a.js');
+      const contents = fs.readFileSync(path, { encoding: 'utf8' });
+      const a = ExportMapBuilder.parse(path, contents, espreeContext);
+      expect(a.errors).to.be.empty;
+      expect(a.get('b').namespace).to.exist;
+      expect(a.get('b').namespace.has('c')).to.be.true;
+    });
 
     it('captures namespace exported as default', function () {
-      const path = getFilename('deep/default.js')
-          , contents = fs.readFileSync(path, { encoding: 'utf8' })
-      const def = ExportMap.parse(path, contents, espreeContext)
-      expect(def.errors).to.be.empty
-      expect(def.get('default').namespace).to.exist
-      expect(def.get('default').namespace.has('c')).to.be.true
-    })
+      const path = getFilename('deep/default.js');
+      const contents = fs.readFileSync(path, { encoding: 'utf8' });
+      const def = ExportMapBuilder.parse(path, contents, espreeContext);
+      expect(def.errors).to.be.empty;
+      expect(def.get('default').namespace).to.exist;
+      expect(def.get('default').namespace.has('c')).to.be.true;
+    });
 
     it('works with babel-eslint & ES7 namespace exports', function () {
-      const path = getFilename('deep-es7/a.js')
-          , contents = fs.readFileSync(path, { encoding: 'utf8' })
-      const a = ExportMap.parse(path, contents, babelContext)
-      expect(a.errors).to.be.empty
-      expect(a.get('b').namespace).to.exist
-      expect(a.get('b').namespace.has('c')).to.be.true
-    })
-  })
+      const path = getFilename('deep-es7/a.js');
+      const contents = fs.readFileSync(path, { encoding: 'utf8' });
+      const a = ExportMapBuilder.parse(path, contents, babelContext);
+      expect(a.errors).to.be.empty;
+      expect(a.get('b').namespace).to.exist;
+      expect(a.get('b').namespace.has('c')).to.be.true;
+    });
+  });
 
   context('deep namespace caching', function () {
-    const espreeContext = { parserPath: 'espree', parserOptions: { ecmaVersion: 2015, sourceType: 'module' }, settings: {} }
-    let a
+    const espreeContext = { parserPath: 'espree', parserOptions: { ecmaVersion: 2015, sourceType: 'module' }, settings: {} };
+    let a;
     before('sanity check and prime cache', function (done) {
       // first version
       fs.writeFileSync(getFilename('deep/cache-2.js'),
-        fs.readFileSync(getFilename('deep/cache-2a.js')))
+        fs.readFileSync(getFilename('deep/cache-2a.js')));
 
-      const path = getFilename('deep/cache-1.js')
-          , contents = fs.readFileSync(path, { encoding: 'utf8' })
-      a = ExportMap.parse(path, contents, espreeContext)
-      expect(a.errors).to.be.empty
+      const path = getFilename('deep/cache-1.js');
+      const contents = fs.readFileSync(path, { encoding: 'utf8' });
+      a = ExportMapBuilder.parse(path, contents, espreeContext);
+      expect(a.errors).to.be.empty;
 
-      expect(a.get('b').namespace).to.exist
-      expect(a.get('b').namespace.has('c')).to.be.true
+      expect(a.get('b').namespace).to.exist;
+      expect(a.get('b').namespace.has('c')).to.be.true;
 
       // wait ~1s, cache check is 1s resolution
       setTimeout(function reup() {
-        fs.unlinkSync(getFilename('deep/cache-2.js'))
+        fs.unlinkSync(getFilename('deep/cache-2.js'));
         // swap in a new file and touch it
         fs.writeFileSync(getFilename('deep/cache-2.js'),
-          fs.readFileSync(getFilename('deep/cache-2b.js')))
-        done()
-      }, 1100)
-    })
+          fs.readFileSync(getFilename('deep/cache-2b.js')));
+        done();
+      }, 1100);
+    });
 
     it('works', function () {
-      expect(a.get('b').namespace.has('c')).to.be.false
-    })
+      expect(a.get('b').namespace.has('c')).to.be.false;
+    });
 
-    after('remove test file', (done) => fs.unlink(getFilename('deep/cache-2.js'), done))
-  })
+    after('remove test file', (done) => fs.unlink(getFilename('deep/cache-2.js'), done));
+  });
 
   context('Map API', function () {
     context('#size', function () {
 
-      it('counts the names', () => expect(ExportMap.get('./named-exports', fakeContext))
-        .to.have.property('size', 10))
+      it('counts the names', () => expect(ExportMapBuilder.get('./named-exports', fakeContext))
+        .to.have.property('size', 12));
 
-      it('includes exported namespace size', () => expect(ExportMap.get('./export-all', fakeContext))
-        .to.have.property('size', 1))
+      it('includes exported namespace size', () => expect(ExportMapBuilder.get('./export-all', fakeContext))
+        .to.have.property('size', 1));
 
-    })
-  })
+    });
+  });
 
   context('issue #210: self-reference', function () {
     it(`doesn't crash`, function () {
-      expect(() => ExportMap.get('./narcissist', fakeContext)).not.to.throw(Error)
-    })
+      expect(() => ExportMapBuilder.get('./narcissist', fakeContext)).not.to.throw(Error);
+    });
     it(`'has' circular reference`, function () {
-      expect(ExportMap.get('./narcissist', fakeContext))
-        .to.exist.and.satisfy(m => m.has('soGreat'))
-    })
+      expect(ExportMapBuilder.get('./narcissist', fakeContext))
+        .to.exist.and.satisfy((m) => m.has('soGreat'));
+    });
     it(`can 'get' circular reference`, function () {
-      expect(ExportMap.get('./narcissist', fakeContext))
-        .to.exist.and.satisfy(m => m.get('soGreat') != null)
-    })
-  })
+      expect(ExportMapBuilder.get('./narcissist', fakeContext))
+        .to.exist.and.satisfy((m) => m.get('soGreat') != null);
+    });
+  });
 
   context('issue #478: never parse non-whitelist extensions', function () {
-    const context = Object.assign({}, fakeContext,
-      { settings: { 'import/extensions': ['.js'] } })
+    const context = {
+      ...fakeContext,
+      settings: { 'import/extensions': ['.js'] },
+    };
 
-    let imports
+    let imports;
     before('load imports', function () {
-      imports = ExportMap.get('./typescript.ts', context)
-    })
+      imports = ExportMapBuilder.get('./typescript.ts', context);
+    });
 
     it('returns nothing for a TypeScript file', function () {
-      expect(imports).not.to.exist
-    })
+      expect(imports).not.to.exist;
+    });
 
-  })
+  });
 
   context('alternate parsers', function () {
-
     const configs = [
       // ['string form', { 'typescript-eslint-parser': '.ts' }],
-    ]
+    ];
 
-    if (semver.satisfies(eslintPkg.version, '>5.0.0')) {
-      configs.push(['array form', { '@typescript-eslint/parser': ['.ts', '.tsx'] }])
+    if (semver.satisfies(eslintPkg.version, '>5')) {
+      configs.push(['array form', { '@typescript-eslint/parser': ['.ts', '.tsx'] }]);
     }
 
-    if (semver.satisfies(eslintPkg.version, '<6.0.0')) {
-      configs.push(['array form', { 'typescript-eslint-parser': ['.ts', '.tsx'] }])
+    if (semver.satisfies(eslintPkg.version, '<6') && semver.satisfies(typescriptPkg.version, '<4')) {
+      configs.push(['array form', { 'typescript-eslint-parser': ['.ts', '.tsx'] }]);
     }
 
     configs.forEach(([description, parserConfig]) => {
 
       describe(description, function () {
-        const context = Object.assign({}, fakeContext,
-          { settings: {
+        const context = {
+          ...fakeContext,
+          settings: {
             'import/extensions': ['.js'],
             'import/parsers': parserConfig,
-          } })
+          },
+        };
 
-        let imports
+        let imports;
         before('load imports', function () {
-          this.timeout(20000)  // takes a long time :shrug:
-          imports = ExportMap.get('./typescript.ts', context)
-        })
+          this.timeout(20e3);  // takes a long time :shrug:
+          sinon.spy(tsConfigLoader, 'tsConfigLoader');
+          imports = ExportMapBuilder.get('./typescript.ts', context);
+        });
+        after('clear spies', function () {
+          tsConfigLoader.tsConfigLoader.restore();
+        });
 
         it('returns something for a TypeScript file', function () {
-          expect(imports).to.exist
-        })
+          expect(imports).to.exist;
+        });
 
         it('has no parse errors', function () {
-          expect(imports).property('errors').to.be.empty
-        })
+          expect(imports).property('errors').to.be.empty;
+        });
 
         it('has exported function', function () {
-          expect(imports.has('getFoo')).to.be.true
-        })
+          expect(imports.has('getFoo')).to.be.true;
+        });
 
         it('has exported typedef', function () {
-          expect(imports.has('MyType')).to.be.true
-        })
+          expect(imports.has('MyType')).to.be.true;
+        });
 
         it('has exported enum', function () {
-          expect(imports.has('MyEnum')).to.be.true
-        })
+          expect(imports.has('MyEnum')).to.be.true;
+        });
 
         it('has exported interface', function () {
-          expect(imports.has('Foo')).to.be.true
-        })
+          expect(imports.has('Foo')).to.be.true;
+        });
 
         it('has exported abstract class', function () {
-          expect(imports.has('Bar')).to.be.true
-        })
-      })
-    })
-
-  })
+          expect(imports.has('Bar')).to.be.true;
+        });
+
+        it('should cache tsconfig until tsconfigRootDir parser option changes', function () {
+          const customContext = {
+            ...context,
+            parserOptions: {
+              tsconfigRootDir: null,
+            },
+          };
+          expect(tsConfigLoader.tsConfigLoader.callCount).to.equal(0);
+          ExportMapBuilder.parse('./baz.ts', 'export const baz = 5', customContext);
+          expect(tsConfigLoader.tsConfigLoader.callCount).to.equal(1);
+          ExportMapBuilder.parse('./baz.ts', 'export const baz = 5', customContext);
+          expect(tsConfigLoader.tsConfigLoader.callCount).to.equal(1);
+
+          const differentContext = {
+            ...context,
+            parserOptions: {
+              tsconfigRootDir: process.cwd(),
+            },
+          };
+
+          ExportMapBuilder.parse('./baz.ts', 'export const baz = 5', differentContext);
+          expect(tsConfigLoader.tsConfigLoader.callCount).to.equal(2);
+        });
+
+        it('should cache after parsing for an ambiguous module', function () {
+          const source = './typescript-declare-module.ts';
+          const parseSpy = sinon.spy(ExportMapBuilder, 'parse');
+
+          expect(ExportMapBuilder.get(source, context)).to.be.null;
+
+          ExportMapBuilder.get(source, context);
+
+          expect(parseSpy.callCount).to.equal(1);
+
+          parseSpy.restore();
+        });
+      });
+    });
+  });
 
   // todo: move to utils
   describe('unambiguous regex', function () {
-
     const testFiles = [
       ['deep/b.js', true],
       ['bar.js', true],
       ['deep-es7/b.js', true],
       ['common.js', false],
-    ]
+      ['./minified/no-newline.js', true],
+      ['./minified/one-line-no-semi-renamed.js', true],
+      ['./minified/one-line-no-semi.js', true],
+      ['./minified/one-line.js', true],
+    ];
 
-    for (let [testFile, expectedRegexResult] of testFiles) {
+    for (const [testFile, expectedRegexResult] of testFiles) {
       it(`works for ${testFile} (${expectedRegexResult})`, function () {
-        const content = fs.readFileSync('./tests/files/' + testFile, 'utf8')
-        expect(unambiguous.test(content)).to.equal(expectedRegexResult)
-      })
+        const content = fs.readFileSync(`./tests/files/${testFile}`, 'utf8');
+        expect(testUnambiguous(content)).to.equal(expectedRegexResult);
+      });
     }
-
-  })
-
-})
+  });
+});
diff --git a/tests/src/core/hash.js b/tests/src/core/hash.js
index f8dd4b49a..785b8abc3 100644
--- a/tests/src/core/hash.js
+++ b/tests/src/core/hash.js
@@ -1,76 +1,76 @@
-import { expect } from 'chai'
+import { expect } from 'chai';
 
-import hashify, { hashArray, hashObject } from 'eslint-module-utils/hash'
+import hashify, { hashArray, hashObject } from 'eslint-module-utils/hash';
 
-const createHash = require('crypto').createHash
+const createHash = require('crypto').createHash;
 
 function expectHash(actualHash, expectedString) {
-  const expectedHash = createHash('sha256')
-  expectedHash.update(expectedString)
-  expect(actualHash.digest('hex'), 'to be a hex digest of sha256 hash of string <' + expectedString + '>').to.equal(expectedHash.digest('hex'))
+  const expectedHash = createHash('sha256');
+  expectedHash.update(expectedString);
+  expect(actualHash.digest('hex'), `to be a hex digest of sha256 hash of string <${expectedString}>`).to.equal(expectedHash.digest('hex'));
 }
 
 describe('hash', function () {
   describe('hashify', function () {
     it('handles null', function () {
-      expectHash(hashify(null), 'null')
-    })
+      expectHash(hashify(null), 'null');
+    });
 
     it('handles undefined', function () {
-      expectHash(hashify(undefined), 'undefined')
-    })
+      expectHash(hashify(undefined), 'undefined');
+    });
 
     it('handles numbers', function () {
-      expectHash(hashify(123.456), '123.456')
-    })
+      expectHash(hashify(123.456), '123.456');
+    });
 
     it('handles strings', function () {
-      expectHash(hashify('a string'), '"a string"')
-    })
+      expectHash(hashify('a string'), '"a string"');
+    });
 
     it('handles Array instances', function () {
-      expectHash(hashify([ 'a string' ]), '["a string",]')
-    })
+      expectHash(hashify(['a string']), '["a string",]');
+    });
 
     it('handles empty Array instances', function () {
-      expectHash(hashify([]), '[]')
-    })
+      expectHash(hashify([]), '[]');
+    });
 
     it('handles Object instances', function () {
-      expectHash(hashify({ foo: 123.456, 'a key': 'a value' }), '{"a key":"a value","foo":123.456,}')
-    })
+      expectHash(hashify({ foo: 123.456, 'a key': 'a value' }), '{"a key":"a value","foo":123.456,}');
+    });
 
     it('handles nested Object instances', function () {
-      expectHash(hashify({ foo: 123.456, 'a key': 'a value', obj: { abc: { def: 'ghi' } } }), '{"a key":"a value","foo":123.456,"obj":{"abc":{"def":"ghi",},},}')
-    })
+      expectHash(hashify({ foo: 123.456, 'a key': 'a value', obj: { abc: { def: 'ghi' } } }), '{"a key":"a value","foo":123.456,"obj":{"abc":{"def":"ghi",},},}');
+    });
 
     it('handles nested Object and Array instances', function () {
-      expectHash(hashify({ foo: 123.456, 'a key': 'a value', obj: { arr: [ { def: 'ghi' } ] } }), '{"a key":"a value","foo":123.456,"obj":{"arr":[{"def":"ghi",},],},}')
-    })
-  })
+      expectHash(hashify({ foo: 123.456, 'a key': 'a value', obj: { arr: [{ def: 'ghi' }] } }), '{"a key":"a value","foo":123.456,"obj":{"arr":[{"def":"ghi",},],},}');
+    });
+  });
 
   describe('hashArray', function () {
     it('handles Array instances', function () {
-      expectHash(hashArray([ 'a string' ]), '["a string",]')
-    })
+      expectHash(hashArray(['a string']), '["a string",]');
+    });
 
     it('handles empty Array instances', function () {
-      expectHash(hashArray([]), '[]')
-    })
-  })
+      expectHash(hashArray([]), '[]');
+    });
+  });
 
   describe('hashObject', function () {
     it('handles Object instances', function () {
-      expectHash(hashObject({ foo: 123.456, 'a key': 'a value' }), '{"a key":"a value","foo":123.456,}')
-    })
+      expectHash(hashObject({ foo: 123.456, 'a key': 'a value' }), '{"a key":"a value","foo":123.456,}');
+    });
 
     it('handles nested Object instances', function () {
-      expectHash(hashObject({ foo: 123.456, 'a key': 'a value', obj: { abc: { def: 'ghi' } } }), '{"a key":"a value","foo":123.456,"obj":{"abc":{"def":"ghi",},},}')
-    })
+      expectHash(hashObject({ foo: 123.456, 'a key': 'a value', obj: { abc: { def: 'ghi' } } }), '{"a key":"a value","foo":123.456,"obj":{"abc":{"def":"ghi",},},}');
+    });
 
     it('handles nested Object and Array instances', function () {
-      expectHash(hashObject({ foo: 123.456, 'a key': 'a value', obj: { arr: [ { def: 'ghi' } ] } }), '{"a key":"a value","foo":123.456,"obj":{"arr":[{"def":"ghi",},],},}')
-    })
-  })
+      expectHash(hashObject({ foo: 123.456, 'a key': 'a value', obj: { arr: [{ def: 'ghi' }] } }), '{"a key":"a value","foo":123.456,"obj":{"arr":[{"def":"ghi",},],},}');
+    });
+  });
 
-})
+});
diff --git a/tests/src/core/ignore.js b/tests/src/core/ignore.js
index 8870158a5..321278136 100644
--- a/tests/src/core/ignore.js
+++ b/tests/src/core/ignore.js
@@ -1,90 +1,90 @@
-import { expect } from 'chai'
+import { expect } from 'chai';
 
-import isIgnored, { getFileExtensions, hasValidExtension } from 'eslint-module-utils/ignore'
+import isIgnored, { getFileExtensions, hasValidExtension } from 'eslint-module-utils/ignore';
 
-import * as utils from '../utils'
+import * as utils from '../utils';
 
 describe('ignore', function () {
   describe('isIgnored', function () {
     it('ignores paths with extensions other than .js', function () {
-      const testContext = utils.testContext({})
+      const testContext = utils.testContext({});
 
-      expect(isIgnored('../files/foo.js', testContext)).to.equal(false)
+      expect(isIgnored('../files/foo.js', testContext)).to.equal(false);
 
-      expect(isIgnored('../files/bar.jsx', testContext)).to.equal(true)
+      expect(isIgnored('../files/bar.jsx', testContext)).to.equal(true);
 
-      expect(isIgnored('../files/typescript.ts', testContext)).to.equal(true)
+      expect(isIgnored('../files/typescript.ts', testContext)).to.equal(true);
 
-      expect(isIgnored('../files/ignore.invalid.extension', testContext)).to.equal(true)
-    })
+      expect(isIgnored('../files/ignore.invalid.extension', testContext)).to.equal(true);
+    });
 
     it('ignores paths with invalid extensions when configured with import/extensions', function () {
-      const testContext = utils.testContext({ 'import/extensions': [ '.js', '.jsx', '.ts' ] })
+      const testContext = utils.testContext({ 'import/extensions': ['.js', '.jsx', '.ts'] });
 
-      expect(isIgnored('../files/foo.js', testContext)).to.equal(false)
+      expect(isIgnored('../files/foo.js', testContext)).to.equal(false);
 
-      expect(isIgnored('../files/bar.jsx', testContext)).to.equal(false)
+      expect(isIgnored('../files/bar.jsx', testContext)).to.equal(false);
 
-      expect(isIgnored('../files/typescript.ts', testContext)).to.equal(false)
+      expect(isIgnored('../files/typescript.ts', testContext)).to.equal(false);
 
-      expect(isIgnored('../files/ignore.invalid.extension', testContext)).to.equal(true)
-    })
-  })
+      expect(isIgnored('../files/ignore.invalid.extension', testContext)).to.equal(true);
+    });
+  });
 
   describe('hasValidExtension', function () {
     it('assumes only .js as valid by default', function () {
-      const testContext = utils.testContext({})
+      const testContext = utils.testContext({});
 
-      expect(hasValidExtension('../files/foo.js', testContext)).to.equal(true)
+      expect(hasValidExtension('../files/foo.js', testContext)).to.equal(true);
 
-      expect(hasValidExtension('../files/foo.jsx', testContext)).to.equal(false)
+      expect(hasValidExtension('../files/foo.jsx', testContext)).to.equal(false);
 
-      expect(hasValidExtension('../files/foo.css', testContext)).to.equal(false)
+      expect(hasValidExtension('../files/foo.css', testContext)).to.equal(false);
 
-      expect(hasValidExtension('../files/foo.invalid.extension', testContext)).to.equal(false)
-    })
+      expect(hasValidExtension('../files/foo.invalid.extension', testContext)).to.equal(false);
+    });
 
     it('can be configured with import/extensions', function () {
-      const testContext = utils.testContext({ 'import/extensions': [ '.foo', '.bar' ] })
+      const testContext = utils.testContext({ 'import/extensions': ['.foo', '.bar'] });
 
-      expect(hasValidExtension('../files/foo.foo', testContext)).to.equal(true)
+      expect(hasValidExtension('../files/foo.foo', testContext)).to.equal(true);
 
-      expect(hasValidExtension('../files/foo.bar', testContext)).to.equal(true)
+      expect(hasValidExtension('../files/foo.bar', testContext)).to.equal(true);
 
-      expect(hasValidExtension('../files/foo.js', testContext)).to.equal(false)
-    })
-  })
+      expect(hasValidExtension('../files/foo.js', testContext)).to.equal(false);
+    });
+  });
 
   describe('getFileExtensions', function () {
     it('returns a set with the file extension ".js" if "import/extensions" is not configured', function () {
-      const fileExtensions = getFileExtensions({})
+      const fileExtensions = getFileExtensions({});
 
-      expect(fileExtensions).to.include('.js')
-    })
+      expect(fileExtensions).to.include('.js');
+    });
 
     it('returns a set with the file extensions configured in "import/extension"', function () {
       const settings = {
         'import/extensions': ['.js', '.jsx'],
-      }
+      };
 
-      const fileExtensions = getFileExtensions(settings)
+      const fileExtensions = getFileExtensions(settings);
 
-      expect(fileExtensions).to.include('.js')
-      expect(fileExtensions).to.include('.jsx')
-    })
+      expect(fileExtensions).to.include('.js');
+      expect(fileExtensions).to.include('.jsx');
+    });
 
     it('returns a set with the file extensions configured in "import/extension" and "import/parsers"', function () {
       const settings = {
         'import/parsers': {
           'typescript-eslint-parser': ['.ts', '.tsx'],
         },
-      }
+      };
 
-      const fileExtensions = getFileExtensions(settings)
+      const fileExtensions = getFileExtensions(settings);
 
-      expect(fileExtensions).to.include('.js') // If "import/extensions" is not configured, this is the default
-      expect(fileExtensions).to.include('.ts')
-      expect(fileExtensions).to.include('.tsx')
-    })
-  })
-})
+      expect(fileExtensions).to.include('.js'); // If "import/extensions" is not configured, this is the default
+      expect(fileExtensions).to.include('.ts');
+      expect(fileExtensions).to.include('.tsx');
+    });
+  });
+});
diff --git a/tests/src/core/importType.js b/tests/src/core/importType.js
index f60063991..c4dca866e 100644
--- a/tests/src/core/importType.js
+++ b/tests/src/core/importType.js
@@ -1,137 +1,286 @@
-import { expect } from 'chai'
-import * as path from 'path'
+import { expect } from 'chai';
+import * as path from 'path';
+import isCoreModule from 'is-core-module';
 
-import importType from 'core/importType'
+import importType, { isExternalModule, isScoped, isAbsolute } from 'core/importType';
 
-import { testContext } from '../utils'
+import { testContext, testFilePath } from '../utils';
 
 describe('importType(name)', function () {
-  const context = testContext()
-  const pathToTestFiles = path.join(__dirname, '..', '..', 'files')
-
-  it("should return 'absolute' for paths starting with a /", function() {
-    expect(importType('/', context)).to.equal('absolute')
-    expect(importType('/path', context)).to.equal('absolute')
-    expect(importType('/some/path', context)).to.equal('absolute')
-  })
-
-  it("should return 'builtin' for node.js modules", function() {
-    expect(importType('fs', context)).to.equal('builtin')
-    expect(importType('path', context)).to.equal('builtin')
-  })
-
-  it("should return 'external' for non-builtin modules without a relative path", function() {
-    expect(importType('lodash', context)).to.equal('external')
-    expect(importType('async', context)).to.equal('external')
-    expect(importType('chalk', context)).to.equal('external')
-    expect(importType('foo', context)).to.equal('external')
-    expect(importType('lodash.find', context)).to.equal('external')
-    expect(importType('lodash/fp', context)).to.equal('external')
-  })
-
-  it("should return 'external' for scopes packages", function() {
-    expect(importType('@cycle/core', context)).to.equal('external')
-    expect(importType('@cycle/dom', context)).to.equal('external')
-    expect(importType('@some-thing/something', context)).to.equal('external')
-    expect(importType('@some-thing/something/some-module', context)).to.equal('external')
-    expect(importType('@some-thing/something/some-directory/someModule.js', context)).to.equal('external')
-  })
-
-  it("should return 'external' for external modules that redirect to its parent module using package.json", function() {
-    expect(importType('eslint-import-test-order-redirect/module', context)).to.equal('external')
-    expect(importType('@eslint/import-test-order-redirect-scoped/module', context)).to.equal('external')
-  })
+  const context = testContext();
+  const pathToTestFiles = path.join(__dirname, '..', '..', 'files');
+
+  it("should return 'absolute' for paths starting with a /", function () {
+    expect(importType('/', context)).to.equal('absolute');
+    expect(importType('/path', context)).to.equal('absolute');
+    expect(importType('/some/path', context)).to.equal('absolute');
+  });
+
+  it("should return 'builtin' for node.js modules", function () {
+    ['fs', 'fs/promises', 'path'].filter((x) => isCoreModule(x)).forEach((x) => {
+      expect(importType(x, context)).to.equal('builtin');
+      if (isCoreModule(`node:${x}`)) {
+        expect(importType(`node:${x}`, context)).to.equal('builtin');
+      }
+    });
+  });
+
+  it("should return 'external' for non-builtin modules without a relative path", function () {
+    expect(importType('lodash', context)).to.equal('external');
+    expect(importType('async', context)).to.equal('external');
+    expect(importType('chalk', context)).to.equal('external');
+    expect(importType('foo', context)).to.equal('external');
+    expect(importType('lodash.find', context)).to.equal('external');
+    expect(importType('lodash/fp', context)).to.equal('external');
+  });
+
+  it("should return 'external' for scopes packages", function () {
+    expect(importType('@cycle/', context)).to.equal('external');
+    expect(importType('@cycle/core', context)).to.equal('external');
+    expect(importType('@cycle/dom', context)).to.equal('external');
+    expect(importType('@some-thing/something', context)).to.equal('external');
+    expect(importType('@some-thing/something/some-module', context)).to.equal('external');
+    expect(importType('@some-thing/something/some-directory/someModule.js', context)).to.equal('external');
+  });
+
+  it("should return 'external' for external modules that redirect to its parent module using package.json", function () {
+    expect(importType('eslint-import-test-order-redirect/module', context)).to.equal('external');
+    expect(importType('@eslint/import-test-order-redirect-scoped/module', context)).to.equal('external');
+  });
 
   it("should return 'internal' for non-builtins resolved outside of node_modules", function () {
-    const pathContext = testContext({ "import/resolver": { node: { paths: [pathToTestFiles] } } })
-    expect(importType('importType', pathContext)).to.equal('internal')
-  })
-
-  it.skip("should return 'internal' for scoped packages resolved outside of node_modules", function () {
-    const pathContext = testContext({ "import/resolver": { node: { paths: [pathToTestFiles] } } })
-    expect(importType('@importType/index', pathContext)).to.equal('internal')
-  })
-    
+    const pathContext = testContext({ 'import/resolver': { node: { paths: [pathToTestFiles] } } });
+    expect(importType('importType', pathContext)).to.equal('internal');
+  });
+
+  it("should return 'internal' for scoped packages resolved outside of node_modules", function () {
+    const pathContext = testContext({ 'import/resolver': { node: { paths: [pathToTestFiles] } } });
+    expect(importType('@importType/index', pathContext)).to.equal('internal');
+  });
+
   it("should return 'internal' for internal modules that are referenced by aliases", function () {
-    const pathContext = testContext({ 'import/resolver': { node: { paths: [pathToTestFiles] } } })
-    expect(importType('@my-alias/fn', pathContext)).to.equal('internal')
-  })
+    const pathContext = testContext({ 'import/resolver': { node: { paths: [pathToTestFiles] } } });
+    expect(importType('@my-alias/fn', pathContext)).to.equal('internal');
+    expect(importType('@importType', pathContext)).to.equal('internal');
+  });
 
   it("should return 'internal' for aliased internal modules that look like core modules (node resolver)", function () {
-    const pathContext = testContext({ 'import/resolver': { node: { paths: [pathToTestFiles] } } })
-    expect(importType('constants/index', pathContext)).to.equal('internal')
-    expect(importType('constants/', pathContext)).to.equal('internal')
+    const pathContext = testContext({ 'import/resolver': { node: { paths: [pathToTestFiles] } } });
+    expect(importType('constants/index', pathContext)).to.equal('internal');
+    expect(importType('constants/', pathContext)).to.equal('internal');
     // resolves exact core modules over internal modules
-    expect(importType('constants', pathContext)).to.equal('builtin')
-  })
+    expect(importType('constants', pathContext)).to.equal('builtin');
+  });
 
   it("should return 'internal' for aliased internal modules that look like core modules (webpack resolver)", function () {
-    const webpackConfig = { resolve: { modules: [pathToTestFiles, 'node_modules'] } }
-    const pathContext = testContext({ 'import/resolver': { webpack: { config: webpackConfig } } })
-    expect(importType('constants/index', pathContext)).to.equal('internal')
-    expect(importType('constants/', pathContext)).to.equal('internal')
-    expect(importType('constants', pathContext)).to.equal('internal')
-  })
-
-  it("should return 'parent' for internal modules that go through the parent", function() {
-    expect(importType('../foo', context)).to.equal('parent')
-    expect(importType('../../foo', context)).to.equal('parent')
-    expect(importType('../bar/foo', context)).to.equal('parent')
-  })
-
-  it("should return 'sibling' for internal modules that are connected to one of the siblings", function() {
-    expect(importType('./foo', context)).to.equal('sibling')
-    expect(importType('./foo/bar', context)).to.equal('sibling')
-    expect(importType('./importType', context)).to.equal('sibling')
-    expect(importType('./importType/', context)).to.equal('sibling')
-    expect(importType('./importType/index', context)).to.equal('sibling')
-    expect(importType('./importType/index.js', context)).to.equal('sibling')
-  })
-
-  it("should return 'index' for sibling index file", function() {
-    expect(importType('.', context)).to.equal('index')
-    expect(importType('./', context)).to.equal('index')
-    expect(importType('./index', context)).to.equal('index')
-    expect(importType('./index.js', context)).to.equal('index')
-  })
-
-  it("should return 'unknown' for any unhandled cases", function() {
-    expect(importType('@malformed', context)).to.equal('unknown')
-    expect(importType('  /malformed', context)).to.equal('unknown')
-    expect(importType('   foo', context)).to.equal('unknown')
-  })
-
-  it("should return 'builtin' for additional core modules", function() {
+    const webpackConfig = { resolve: { modules: [pathToTestFiles, 'node_modules'] } };
+    const pathContext = testContext({ 'import/resolver': { webpack: { config: webpackConfig } } });
+    expect(importType('constants/index', pathContext)).to.equal('internal');
+    expect(importType('constants/', pathContext)).to.equal('internal');
+    expect(importType('constants', pathContext)).to.equal('internal');
+  });
+
+  it("should return 'internal' for aliased internal modules that are found, even if they are not discernible as scoped", function () {
+    // `@` for internal modules is a common alias and is different from scoped names.
+    // Scoped names are prepended with `@` (e.g. `@scoped/some-file.js`) whereas `@`
+    // as an alias by itelf is the full root name (e.g. `@/some-file.js`).
+    const alias = { '@': path.join(pathToTestFiles, 'internal-modules') };
+    const webpackConfig = { resolve: { alias } };
+    const pathContext = testContext({ 'import/resolver': { webpack: { config: webpackConfig } } });
+    expect(importType('@/api/service', pathContext)).to.equal('internal');
+    expect(importType('@/does-not-exist', pathContext)).to.equal('unknown');
+  });
+
+  it("should return 'parent' for internal modules that go through the parent", function () {
+    expect(importType('../foo', context)).to.equal('parent');
+    expect(importType('../../foo', context)).to.equal('parent');
+    expect(importType('../bar/foo', context)).to.equal('parent');
+  });
+
+  it("should return 'sibling' for internal modules that are connected to one of the siblings", function () {
+    expect(importType('./foo', context)).to.equal('sibling');
+    expect(importType('./foo/bar', context)).to.equal('sibling');
+    expect(importType('./importType', context)).to.equal('sibling');
+    expect(importType('./importType/', context)).to.equal('sibling');
+    expect(importType('./importType/index', context)).to.equal('sibling');
+    expect(importType('./importType/index.js', context)).to.equal('sibling');
+  });
+
+  it("should return 'index' for sibling index file", function () {
+    expect(importType('.', context)).to.equal('index');
+    expect(importType('./', context)).to.equal('index');
+    expect(importType('./index', context)).to.equal('index');
+    expect(importType('./index.js', context)).to.equal('index');
+  });
+
+  it("should return 'unknown' for any unhandled cases", function () {
+    expect(importType('  /malformed', context)).to.equal('unknown');
+    expect(importType('   foo', context)).to.equal('unknown');
+    expect(importType('-/no-such-path', context)).to.equal('unknown');
+  });
+
+  it("should return 'builtin' for additional core modules", function () {
     // without extra config, should be marked external
-    expect(importType('electron', context)).to.equal('external')
-    expect(importType('@org/foobar', context)).to.equal('external')
-
-    const electronContext = testContext({ 'import/core-modules': ['electron'] })
-    expect(importType('electron', electronContext)).to.equal('builtin')
-
-    const scopedContext = testContext({ 'import/core-modules': ['@org/foobar'] })
-    expect(importType('@org/foobar', scopedContext)).to.equal('builtin')
-  })
-
-  it("should return 'builtin' for resources inside additional core modules", function() {
-    const electronContext = testContext({ 'import/core-modules': ['electron'] })
-    expect(importType('electron/some/path/to/resource.json', electronContext)).to.equal('builtin')
-
-    const scopedContext = testContext({ 'import/core-modules': ['@org/foobar'] })
-    expect(importType('@org/foobar/some/path/to/resource.json', scopedContext)).to.equal('builtin')
-  })
-
-  it("should return 'external' for module from 'node_modules' with default config", function() {
-    expect(importType('resolve', context)).to.equal('external')
-  })
-
-  it("should return 'internal' for module from 'node_modules' if 'node_modules' missed in 'external-module-folders'", function() {
-    const foldersContext = testContext({ 'import/external-module-folders': [] })
-    expect(importType('resolve', foldersContext)).to.equal('internal')
-  })
-
-  it("should return 'external' for module from 'node_modules' if 'node_modules' contained in 'external-module-folders'", function() {
-    const foldersContext = testContext({ 'import/external-module-folders': ['node_modules'] })
-    expect(importType('resolve', foldersContext)).to.equal('external')
-  })
-})
+    expect(importType('electron', context)).to.equal('external');
+    expect(importType('@org/foobar', context)).to.equal('external');
+
+    const electronContext = testContext({ 'import/core-modules': ['electron'] });
+    expect(importType('electron', electronContext)).to.equal('builtin');
+
+    const scopedContext = testContext({ 'import/core-modules': ['@org/foobar'] });
+    expect(importType('@org/foobar', scopedContext)).to.equal('builtin');
+  });
+
+  it("should return 'builtin' for resources inside additional core modules", function () {
+    const electronContext = testContext({ 'import/core-modules': ['electron'] });
+    expect(importType('electron/some/path/to/resource.json', electronContext)).to.equal('builtin');
+
+    const scopedContext = testContext({ 'import/core-modules': ['@org/foobar'] });
+    expect(importType('@org/foobar/some/path/to/resource.json', scopedContext)).to.equal('builtin');
+  });
+
+  it("should return 'external' for module from 'node_modules' with default config", function () {
+    expect(importType('resolve', context)).to.equal('external');
+  });
+
+  it("should return 'internal' for module from 'node_modules' if 'node_modules' missed in 'external-module-folders'", function () {
+    const foldersContext = testContext({ 'import/external-module-folders': [] });
+    expect(importType('chai', foldersContext)).to.equal('internal');
+  });
+
+  it("should return 'internal' for module from 'node_modules' if its name matched 'internal-regex'", function () {
+    const foldersContext = testContext({ 'import/internal-regex': '^@org' });
+    expect(importType('@org/foobar', foldersContext)).to.equal('internal');
+  });
+
+  it("should return 'external' for module from 'node_modules' if its name did not match 'internal-regex'", function () {
+    const foldersContext = testContext({ 'import/internal-regex': '^@bar' });
+    expect(importType('@org/foobar', foldersContext)).to.equal('external');
+  });
+
+  it("should return 'external' for module from 'node_modules' if 'node_modules' contained in 'external-module-folders'", function () {
+    const foldersContext = testContext({ 'import/external-module-folders': ['node_modules'] });
+    expect(importType('resolve', foldersContext)).to.equal('external');
+  });
+
+  it('returns "external" for a scoped symlinked module', function () {
+    const foldersContext = testContext({
+      'import/resolver': 'node',
+      'import/external-module-folders': ['node_modules'],
+    });
+    expect(importType('@test-scope/some-module', foldersContext)).to.equal('external');
+  });
+
+  // We're using Webpack resolver here since it resolves all symlinks, which means that
+  // directory path will not contain node_modules/<package-name> but will point to the
+  // actual directory inside 'files' instead
+  it('returns "external" for a scoped module from a symlinked directory which name is contained in "external-module-folders" (webpack resolver)', function () {
+    const foldersContext = testContext({
+      'import/resolver': 'webpack',
+      'import/external-module-folders': ['symlinked-module'],
+    });
+    expect(importType('@test-scope/some-module', foldersContext)).to.equal('external');
+  });
+
+  it('returns "internal" for a scoped module from a symlinked directory which incomplete name is contained in "external-module-folders" (webpack resolver)', function () {
+    const foldersContext_1 = testContext({
+      'import/resolver': 'webpack',
+      'import/external-module-folders': ['symlinked-mod'],
+    });
+    expect(importType('@test-scope/some-module', foldersContext_1)).to.equal('internal');
+
+    const foldersContext_2 = testContext({
+      'import/resolver': 'webpack',
+      'import/external-module-folders': ['linked-module'],
+    });
+    expect(importType('@test-scope/some-module', foldersContext_2)).to.equal('internal');
+  });
+
+  it('returns "external" for a scoped module from a symlinked directory which partial path is contained in "external-module-folders" (webpack resolver)', function () {
+    const originalFoldersContext = testContext({
+      'import/resolver': 'webpack',
+      'import/external-module-folders': [],
+    });
+    expect(importType('@test-scope/some-module', originalFoldersContext)).to.equal('internal');
+
+    const foldersContext = testContext({
+      'import/resolver': 'webpack',
+      'import/external-module-folders': ['symlinked-module'],
+    });
+    expect(importType('@test-scope/some-module', foldersContext)).to.equal('external');
+  });
+
+  it('returns "internal" for a scoped module from a symlinked directory which partial path w/ incomplete segment is contained in "external-module-folders" (webpack resolver)', function () {
+    const foldersContext_1 = testContext({
+      'import/resolver': 'webpack',
+      'import/external-module-folders': ['files/symlinked-mod'],
+    });
+    expect(importType('@test-scope/some-module', foldersContext_1)).to.equal('internal');
+
+    const foldersContext_2 = testContext({
+      'import/resolver': 'webpack',
+      'import/external-module-folders': ['ymlinked-module'],
+    });
+    expect(importType('@test-scope/some-module', foldersContext_2)).to.equal('internal');
+  });
+
+  it('returns "external" for a scoped module from a symlinked directory which partial path ending w/ slash is contained in "external-module-folders" (webpack resolver)', function () {
+    const foldersContext = testContext({
+      'import/resolver': 'webpack',
+      'import/external-module-folders': ['symlinked-module/'],
+    });
+    expect(importType('@test-scope/some-module', foldersContext)).to.equal('external');
+  });
+
+  it('returns "internal" for a scoped module from a symlinked directory when "external-module-folders" contains an absolute path resembling directory‘s relative path (webpack resolver)', function () {
+    const foldersContext = testContext({
+      'import/resolver': 'webpack',
+      'import/external-module-folders': ['/symlinked-module'],
+    });
+    expect(importType('@test-scope/some-module', foldersContext)).to.equal('internal');
+  });
+
+  it('returns "external" for a scoped module from a symlinked directory which absolute path is contained in "external-module-folders" (webpack resolver)', function () {
+    const foldersContext = testContext({
+      'import/resolver': 'webpack',
+      'import/external-module-folders': [testFilePath('symlinked-module')],
+    });
+    expect(importType('@test-scope/some-module', foldersContext)).to.equal('external');
+  });
+
+  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', 'E:\\path\\to\\node_modules\\foo', testContext({
+      settings: { 'import/external-module-folders': ['E:\\path\\to\\node_modules'] },
+    }))).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', '/path/to/node_modules/foo', testContext({
+      settings: { 'import/external-module-folders': ['/path/to/node_modules'] },
+    }))).to.equal(true);
+  });
+
+  it('correctly identifies scoped modules with `isScoped`', () => {
+    expect(isScoped('@/abc')).to.equal(false);
+    expect(isScoped('@/abc/def')).to.equal(false);
+    expect(isScoped('@a/abc')).to.equal(true);
+    expect(isScoped('@a/abc/def')).to.equal(true);
+  });
+});
+
+describe('isAbsolute', () => {
+  it('does not throw on a non-string', () => {
+    expect(() => isAbsolute()).not.to.throw();
+    expect(() => isAbsolute(null)).not.to.throw();
+    expect(() => isAbsolute(true)).not.to.throw();
+    expect(() => isAbsolute(false)).not.to.throw();
+    expect(() => isAbsolute(0)).not.to.throw();
+    expect(() => isAbsolute(NaN)).not.to.throw();
+  });
+});
diff --git a/tests/src/core/parse.js b/tests/src/core/parse.js
index 4d8b5ab58..b21326890 100644
--- a/tests/src/core/parse.js
+++ b/tests/src/core/parse.js
@@ -1,62 +1,155 @@
-import * as fs from 'fs'
-import { expect } from 'chai'
-import sinon from 'sinon'
-import parse from 'eslint-module-utils/parse'
+import * as fs from 'fs';
+import { expect } from 'chai';
+import sinon from 'sinon';
+import parse from 'eslint-module-utils/parse';
 
-import { getFilename } from '../utils'
+import { getFilename } from '../utils';
 
 describe('parse(content, { settings, ecmaFeatures })', function () {
-  const path = getFilename('jsx.js')
-  const parseStubParser = require('./parseStubParser')
-  const parseStubParserPath = require.resolve('./parseStubParser')
-  let content
+  const path = getFilename('jsx.js');
+  const parseStubParser = require('./parseStubParser');
+  const parseStubParserPath = require.resolve('./parseStubParser');
+  const eslintParser = require('./eslintParser');
+  const eslintParserPath = require.resolve('./eslintParser');
+  let content;
 
-  before((done) =>
-    fs.readFile(path, { encoding: 'utf8' },
-      (err, f) => { if (err) { done(err) } else { content = f; done() }}))
+  before((done) => {
+    fs.readFile(
+      path,
+      { encoding: 'utf8' },
+      (err, f) => {
+        if (err) {
+          done(err);
+        } else {
+          content = f; done();
+        }
+      },
+    );
+  });
 
   it('doesn\'t support JSX by default', function () {
-    expect(() => parse(path, content, { parserPath: 'espree' })).to.throw(Error)
-  })
+    expect(() => parse(path, content, { parserPath: 'espree' })).to.throw(Error);
+  });
 
   it('infers jsx from ecmaFeatures when using stock parser', function () {
     expect(() => parse(path, content, { settings: {}, parserPath: 'espree', parserOptions: { ecmaVersion: 2015, sourceType: 'module', ecmaFeatures: { jsx: true } } }))
-      .not.to.throw(Error)
-  })
+      .not.to.throw(Error);
+  });
 
   it('passes expected parserOptions to custom parser', function () {
-    const parseSpy = sinon.spy()
-    const parserOptions = { ecmaFeatures: { jsx: true } }
-    parseStubParser.parse = parseSpy
-    parse(path, content, { settings: {}, parserPath: parseStubParserPath, parserOptions: parserOptions })
-    expect(parseSpy.callCount, 'custom parser to be called once').to.equal(1)
-    expect(parseSpy.args[0][0], 'custom parser to get content as its first argument').to.equal(content)
-    expect(parseSpy.args[0][1], 'custom parser to get an object as its second argument').to.be.an('object')
-    expect(parseSpy.args[0][1], 'custom parser to clone the parserOptions object').to.not.equal(parserOptions)
+    const parseSpy = sinon.spy();
+    const parserOptions = { ecmaFeatures: { jsx: true } };
+    parseStubParser.parse = parseSpy;
+    parse(path, content, { settings: {}, parserPath: parseStubParserPath, parserOptions });
+    expect(parseSpy.callCount, 'custom parser to be called once').to.equal(1);
+    expect(parseSpy.args[0][0], 'custom parser to get content as its first argument').to.equal(content);
+    expect(parseSpy.args[0][1], 'custom parser to get an object as its second argument').to.be.an('object');
+    expect(parseSpy.args[0][1], 'custom parser to clone the parserOptions object').to.not.equal(parserOptions);
     expect(parseSpy.args[0][1], 'custom parser to get ecmaFeatures in parserOptions which is a clone of ecmaFeatures passed in')
       .to.have.property('ecmaFeatures')
-        .that.is.eql(parserOptions.ecmaFeatures)
-        .and.is.not.equal(parserOptions.ecmaFeatures)
-    expect(parseSpy.args[0][1], 'custom parser to get parserOptions.attachComment equal to true').to.have.property('attachComment', true)
-    expect(parseSpy.args[0][1], 'custom parser to get parserOptions.tokens equal to true').to.have.property('tokens', true)
-    expect(parseSpy.args[0][1], 'custom parser to get parserOptions.range equal to true').to.have.property('range', true)
-    expect(parseSpy.args[0][1], 'custom parser to get parserOptions.filePath equal to the full path of the source file').to.have.property('filePath', path)
-  })
+      .that.is.eql(parserOptions.ecmaFeatures)
+      .and.is.not.equal(parserOptions.ecmaFeatures);
+    expect(parseSpy.args[0][1], 'custom parser to get parserOptions.attachComment equal to true').to.have.property('attachComment', true);
+    expect(parseSpy.args[0][1], 'custom parser to get parserOptions.tokens equal to true').to.have.property('tokens', true);
+    expect(parseSpy.args[0][1], 'custom parser to get parserOptions.range equal to true').to.have.property('range', true);
+    expect(parseSpy.args[0][1], 'custom parser to get parserOptions.filePath equal to the full path of the source file').to.have.property('filePath', path);
+  });
+
+  it('passes with custom `parseForESLint` parser', function () {
+    const parseForESLintSpy = sinon.spy(eslintParser, 'parseForESLint');
+    const parseSpy = sinon.spy();
+    eslintParser.parse = parseSpy;
+    parse(path, content, { settings: {}, parserPath: eslintParserPath });
+    expect(parseForESLintSpy.callCount, 'custom `parseForESLint` parser to be called once').to.equal(1);
+    expect(parseSpy.callCount, '`parseForESLint` takes higher priority than `parse`').to.equal(0);
+  });
 
   it('throws on context == null', function () {
-    expect(parse.bind(null, path, content, null)).to.throw(Error)
-  })
+    expect(parse.bind(null, path, content, null)).to.throw(Error);
+  });
 
   it('throws on unable to resolve parserPath', function () {
-    expect(parse.bind(null, path, content, { settings: {}, parserPath: null })).to.throw(Error)
-  })
+    expect(parse.bind(null, path, content, { settings: {}, parserPath: null })).to.throw(Error);
+  });
 
   it('takes the alternate parser specified in settings', function () {
-    const parseSpy = sinon.spy()
-    const parserOptions = { ecmaFeatures: { jsx: true } }
-    parseStubParser.parse = parseSpy
-    expect(parse.bind(null, path, content, { settings: { 'import/parsers': { [parseStubParserPath]: [ '.js' ] } }, parserPath: null, parserOptions: parserOptions })).not.to.throw(Error)
-    expect(parseSpy.callCount, 'custom parser to be called once').to.equal(1)
-  })
-
-})
+    const parseSpy = sinon.spy();
+    const parserOptions = { ecmaFeatures: { jsx: true } };
+    parseStubParser.parse = parseSpy;
+    expect(parse.bind(null, path, content, { settings: { 'import/parsers': { [parseStubParserPath]: ['.js'] } }, parserPath: null, parserOptions })).not.to.throw(Error);
+    expect(parseSpy.callCount, 'custom parser to be called once').to.equal(1);
+  });
+
+  it('throws on invalid languageOptions', function () {
+    expect(parse.bind(null, path, content, { settings: {}, parserPath: null, languageOptions: null })).to.throw(Error);
+  });
+
+  it('throws on non-object languageOptions.parser', function () {
+    expect(parse.bind(null, path, content, { settings: {}, parserPath: null, languageOptions: { parser: 'espree' } })).to.throw(Error);
+  });
+
+  it('throws on null languageOptions.parser', function () {
+    expect(parse.bind(null, path, content, { settings: {}, parserPath: null, languageOptions: { parser: null } })).to.throw(Error);
+  });
+
+  it('throws on empty languageOptions.parser', function () {
+    expect(parse.bind(null, path, content, { settings: {}, parserPath: null, languageOptions: { parser: {} } })).to.throw(Error);
+  });
+
+  it('throws on non-function languageOptions.parser.parse', function () {
+    expect(parse.bind(null, path, content, { settings: {}, parserPath: null, languageOptions: { parser: { parse: 'espree' } } })).to.throw(Error);
+  });
+
+  it('throws on non-function languageOptions.parser.parse', function () {
+    expect(parse.bind(null, path, content, { settings: {}, parserPath: null, languageOptions: { parser: { parseForESLint: 'espree' } } })).to.throw(Error);
+  });
+
+  it('requires only one of the parse methods', function () {
+    expect(parse.bind(null, path, content, { settings: {}, parserPath: null, languageOptions: { parser: { parseForESLint: () => ({ ast: {} }) } } })).not.to.throw(Error);
+  });
+
+  it('uses parse from languageOptions.parser', function () {
+    const parseSpy = sinon.spy();
+    expect(parse.bind(null, path, content, { settings: {}, languageOptions: { parser: { parse: parseSpy } } })).not.to.throw(Error);
+    expect(parseSpy.callCount, 'passed parser to be called once').to.equal(1);
+  });
+
+  it('uses parseForESLint from languageOptions.parser', function () {
+    const parseSpy = sinon.spy(() => ({ ast: {} }));
+    expect(parse.bind(null, path, content, { settings: {}, languageOptions: { parser: { parseForESLint: parseSpy } } })).not.to.throw(Error);
+    expect(parseSpy.callCount, 'passed parser to be called once').to.equal(1);
+  });
+
+  it('prefers parsers specified in the settings over languageOptions.parser', () => {
+    const parseSpy = sinon.spy();
+    parseStubParser.parse = parseSpy;
+    expect(parse.bind(null, path, content, { settings: { 'import/parsers': { [parseStubParserPath]: ['.js'] } }, parserPath: null, languageOptions: { parser: { parse() {} } } })).not.to.throw(Error);
+    expect(parseSpy.callCount, 'custom parser to be called once').to.equal(1);
+  });
+
+  it('ignores parser options from language options set to null', () => {
+    const parseSpy = sinon.spy();
+    parseStubParser.parse = parseSpy;
+    expect(parse.bind(null, path, content, { settings: {}, parserPath: 'espree', languageOptions: { parserOptions: null }, parserOptions: { sourceType: 'module', ecmaVersion: 2015, ecmaFeatures: { jsx: true } } })).not.to.throw(Error);
+  });
+
+  it('prefers languageOptions.parserOptions over parserOptions', () => {
+    const parseSpy = sinon.spy();
+    parseStubParser.parse = parseSpy;
+    expect(parse.bind(null, path, content, { settings: {}, parserPath: 'espree', languageOptions: { parserOptions: { sourceType: 'module', ecmaVersion: 2015, ecmaFeatures: { jsx: true } } }, parserOptions: { sourceType: 'script' } })).not.to.throw(Error);
+  });
+
+  it('passes ecmaVersion and sourceType from languageOptions to parser', () => {
+    const parseSpy = sinon.spy();
+    const languageOptions = { ecmaVersion: 'latest', sourceType: 'module', parserOptions: { ecmaFeatures: { jsx: true } } };
+    parseStubParser.parse = parseSpy;
+    parse(path, content, { settings: {}, parserPath: parseStubParserPath, languageOptions });
+    expect(parseSpy.args[0][1], 'custom parser to clone the parserOptions object').to.not.equal(languageOptions);
+    expect(parseSpy.args[0][1], 'custom parser to get ecmaFeatures in parserOptions which is a clone of ecmaFeatures passed in')
+      .to.have.property('ecmaFeatures')
+      .that.is.eql(languageOptions.parserOptions.ecmaFeatures)
+      .and.is.not.equal(languageOptions.parserOptions.ecmaFeatures);
+    expect(parseSpy.args[0][1], 'custom parser to get ecmaVersion in parserOptions from languageOptions').to.have.property('ecmaVersion', languageOptions.ecmaVersion);
+    expect(parseSpy.args[0][1], 'custom parser to get sourceType in parserOptions from languageOptions').to.have.property('sourceType', languageOptions.sourceType);
+  });
+});
diff --git a/tests/src/core/parseStubParser.js b/tests/src/core/parseStubParser.js
index 81daace43..4ed17d9dd 100644
--- a/tests/src/core/parseStubParser.js
+++ b/tests/src/core/parseStubParser.js
@@ -1,4 +1,4 @@
 // this stub must be in a separate file to require from parse via moduleRequire
 module.exports = {
-  parse: function () {},
-}
+  parse() {},
+};
diff --git a/tests/src/core/resolve.js b/tests/src/core/resolve.js
index b9a906324..0db9b05f4 100644
--- a/tests/src/core/resolve.js
+++ b/tests/src/core/resolve.js
@@ -1,260 +1,468 @@
-import { expect } from 'chai'
+import { expect } from 'chai';
+import eslintPkg from 'eslint/package.json';
+import semver from 'semver';
 
-import resolve, { CASE_SENSITIVE_FS, fileExistsWithCaseSync } from 'eslint-module-utils/resolve'
-import ModuleCache from 'eslint-module-utils/ModuleCache'
+import resolve, { CASE_SENSITIVE_FS, fileExistsWithCaseSync } from 'eslint-module-utils/resolve';
 
-import * as path from 'path'
-import * as fs from 'fs'
-import * as utils from '../utils'
+import * as path from 'path';
+import * as fs from 'fs';
+import * as utils from '../utils';
 
 describe('resolve', function () {
+  // We don't want to test for a specific stack, just that it was there in the error message.
+  function replaceErrorStackForTest(str) {
+    return typeof str === 'string' ? str.replace(/(\n\s+at .+:\d+\)?)+$/, '\n<stack-was-here>') : str;
+  }
+
   it('throws on bad parameters', function () {
-    expect(resolve.bind(null, null, null)).to.throw(Error)
-  })
+    expect(resolve.bind(null, null, null)).to.throw(Error);
+  });
 
   it('resolves via a custom resolver with interface version 1', function () {
-    const testContext = utils.testContext({ 'import/resolver': './foo-bar-resolver-v1' })
+    const testContext = utils.testContext({ 'import/resolver': './foo-bar-resolver-v1' });
 
-    expect(resolve( '../files/foo'
-                      , Object.assign({}, testContext, { getFilename: function () { return utils.getFilename('foo.js') } })
-                      )).to.equal(utils.testFilePath('./bar.jsx'))
+    expect(resolve(
+      '../files/foo',
+      { ...testContext, getFilename() { return utils.getFilename('foo.js'); } },
+    )).to.equal(utils.testFilePath('./bar.jsx'));
 
-    expect(resolve( '../files/exception'
-                      , Object.assign({}, testContext, { getFilename: function () { return utils.getFilename('exception.js') } })
-                    )).to.equal(undefined)
+    expect(resolve(
+      '../files/exception',
+      { ...testContext, getFilename() { return utils.getFilename('exception.js'); } },
+    )).to.equal(undefined);
 
-    expect(resolve( '../files/not-found'
-                      , Object.assign({}, testContext, { getFilename: function () { return utils.getFilename('not-found.js') } })
-                    )).to.equal(undefined)
-  })
+    expect(resolve(
+      '../files/not-found',
+      { ...testContext, getFilename() { return utils.getFilename('not-found.js'); } },
+    )).to.equal(undefined);
+  });
 
   it('resolves via a custom resolver with interface version 1 assumed if not specified', function () {
-    const testContext = utils.testContext({ 'import/resolver': './foo-bar-resolver-no-version' })
+    const testContext = utils.testContext({ 'import/resolver': './foo-bar-resolver-no-version' });
 
-    expect(resolve( '../files/foo'
-                      , Object.assign({}, testContext, { getFilename: function () { return utils.getFilename('foo.js') } })
-                      )).to.equal(utils.testFilePath('./bar.jsx'))
+    expect(resolve(
+      '../files/foo',
+      { ...testContext, getFilename() { return utils.getFilename('foo.js'); } },
+    )).to.equal(utils.testFilePath('./bar.jsx'));
 
-    expect(resolve( '../files/exception'
-                      , Object.assign({}, testContext, { getFilename: function () { return utils.getFilename('exception.js') } })
-                    )).to.equal(undefined)
+    expect(resolve(
+      '../files/exception',
+      { ...testContext, getFilename() { return utils.getFilename('exception.js'); } },
+    )).to.equal(undefined);
 
-    expect(resolve( '../files/not-found'
-                      , Object.assign({}, testContext, { getFilename: function () { return utils.getFilename('not-found.js') } })
-                    )).to.equal(undefined)
-  })
+    expect(resolve(
+      '../files/not-found',
+      { ...testContext, getFilename() { return utils.getFilename('not-found.js'); } },
+    )).to.equal(undefined);
+  });
 
   it('resolves via a custom resolver with interface version 2', function () {
-    const testContext = utils.testContext({ 'import/resolver': './foo-bar-resolver-v2' })
-    const testContextReports = []
+    const testContext = utils.testContext({ 'import/resolver': './foo-bar-resolver-v2' });
+    const testContextReports = [];
     testContext.report = function (reportInfo) {
-      testContextReports.push(reportInfo)
-    }
-
-    expect(resolve( '../files/foo'
-                      , Object.assign({}, testContext, { getFilename: function () { return utils.getFilename('foo.js') } })
-                      )).to.equal(utils.testFilePath('./bar.jsx'))
-
-    testContextReports.length = 0
-    expect(resolve( '../files/exception'
-                      , Object.assign({}, testContext, { getFilename: function () { return utils.getFilename('exception.js') } })
-                    )).to.equal(undefined)
-    expect(testContextReports[0]).to.be.an('object')
-    expect(testContextReports[0].message).to.equal('Resolve error: foo-bar-resolver-v2 resolve test exception')
-    expect(testContextReports[0].loc).to.eql({ line: 1, column: 0 })
-
-    testContextReports.length = 0
-    expect(resolve( '../files/not-found'
-                      , Object.assign({}, testContext, { getFilename: function () { return utils.getFilename('not-found.js') } })
-                    )).to.equal(undefined)
-    expect(testContextReports.length).to.equal(0)
-  })
+      testContextReports.push(reportInfo);
+    };
+
+    expect(resolve(
+      '../files/foo',
+      { ...testContext, getFilename() { return utils.getFilename('foo.js'); } },
+    )).to.equal(utils.testFilePath('./bar.jsx'));
+
+    testContextReports.length = 0;
+    expect(resolve(
+      '../files/exception',
+      { ...testContext, getFilename() { return utils.getFilename('exception.js'); } },
+    )).to.equal(undefined);
+    expect(testContextReports[0]).to.be.an('object');
+    expect(replaceErrorStackForTest(testContextReports[0].message)).to.equal('Resolve error: foo-bar-resolver-v2 resolve test exception\n<stack-was-here>');
+    expect(testContextReports[0].loc).to.eql({ line: 1, column: 0 });
+
+    testContextReports.length = 0;
+    expect(resolve(
+      '../files/not-found',
+      { ...testContext, getFilename() { return utils.getFilename('not-found.js'); } },
+    )).to.equal(undefined);
+    expect(testContextReports.length).to.equal(0);
+  });
 
   it('respects import/resolver as array of strings', function () {
-    const testContext = utils.testContext({ 'import/resolver': [ './foo-bar-resolver-v2', './foo-bar-resolver-v1' ] })
+    const testContext = utils.testContext({ 'import/resolver': ['./foo-bar-resolver-v2', './foo-bar-resolver-v1'] });
 
-    expect(resolve( '../files/foo'
-                      , Object.assign({}, testContext, { getFilename: function () { return utils.getFilename('foo.js') } })
-                      )).to.equal(utils.testFilePath('./bar.jsx'))
-  })
+    expect(resolve(
+      '../files/foo',
+      { ...testContext, getFilename() { return utils.getFilename('foo.js'); } },
+    )).to.equal(utils.testFilePath('./bar.jsx'));
+  });
 
   it('respects import/resolver as object', function () {
-    const testContext = utils.testContext({ 'import/resolver': { './foo-bar-resolver-v2': {} } })
+    const testContext = utils.testContext({ 'import/resolver': { './foo-bar-resolver-v2': {} } });
 
-    expect(resolve( '../files/foo'
-                      , Object.assign({}, testContext, { getFilename: function () { return utils.getFilename('foo.js') } })
-                      )).to.equal(utils.testFilePath('./bar.jsx'))
-  })
+    expect(resolve(
+      '../files/foo',
+      { ...testContext, getFilename() { return utils.getFilename('foo.js'); } },
+    )).to.equal(utils.testFilePath('./bar.jsx'));
+  });
 
   it('respects import/resolver as array of objects', function () {
-    const testContext = utils.testContext({ 'import/resolver': [ { './foo-bar-resolver-v2': {} }, { './foo-bar-resolver-v1': {} } ] })
+    const testContext = utils.testContext({ 'import/resolver': [{ './foo-bar-resolver-v2': {} }, { './foo-bar-resolver-v1': {} }] });
 
-    expect(resolve( '../files/foo'
-                      , Object.assign({}, testContext, { getFilename: function () { return utils.getFilename('foo.js') } })
-                      )).to.equal(utils.testFilePath('./bar.jsx'))
-  })
+    expect(resolve(
+      '../files/foo',
+      { ...testContext, getFilename() { return utils.getFilename('foo.js'); } },
+    )).to.equal(utils.testFilePath('./bar.jsx'));
+  });
+
+  it('finds resolvers from the source files rather than eslint-module-utils', function () {
+    const testContext = utils.testContext({ 'import/resolver': { foo: {} } });
+
+    expect(resolve(
+      '../files/foo',
+      { ...testContext, getFilename() { return utils.getFilename('foo.js'); } },
+    )).to.equal(utils.testFilePath('./bar.jsx'));
+  });
 
   it('reports invalid import/resolver config', function () {
-    const testContext = utils.testContext({ 'import/resolver': 123.456 })
-    const testContextReports = []
+    const testContext = utils.testContext({ 'import/resolver': 123.456 });
+    const testContextReports = [];
     testContext.report = function (reportInfo) {
-      testContextReports.push(reportInfo)
-    }
-
-    testContextReports.length = 0
-    expect(resolve( '../files/foo'
-                      , Object.assign({}, testContext, { getFilename: function () { return utils.getFilename('foo.js') } })
-                    )).to.equal(undefined)
-    expect(testContextReports[0]).to.be.an('object')
-    expect(testContextReports[0].message).to.equal('Resolve error: invalid resolver config')
-    expect(testContextReports[0].loc).to.eql({ line: 1, column: 0 })
-  })
+      testContextReports.push(reportInfo);
+    };
+
+    testContextReports.length = 0;
+    expect(resolve(
+      '../files/foo',
+      { ...testContext, getFilename() { return utils.getFilename('foo.js'); } },
+    )).to.equal(undefined);
+    expect(testContextReports[0]).to.be.an('object');
+    expect(testContextReports[0].message).to.equal('Resolve error: invalid resolver config');
+    expect(testContextReports[0].loc).to.eql({ line: 1, column: 0 });
+  });
 
   it('reports loaded resolver with invalid interface', function () {
     const resolverName = './foo-bar-resolver-invalid';
     const testContext = utils.testContext({ 'import/resolver': resolverName });
-    const testContextReports = []
+    const testContextReports = [];
     testContext.report = function (reportInfo) {
-      testContextReports.push(reportInfo)
-    }
-    testContextReports.length = 0
-    expect(resolve( '../files/foo'
-                      , Object.assign({}, testContext, { getFilename: function () { return utils.getFilename('foo.js') } })
-                    )).to.equal(undefined)
-    expect(testContextReports[0]).to.be.an('object')
-    expect(testContextReports[0].message).to.equal(`Resolve error: ${resolverName} with invalid interface loaded as resolver`)
-    expect(testContextReports[0].loc).to.eql({ line: 1, column: 0 })
-  })
+      testContextReports.push(reportInfo);
+    };
+    testContextReports.length = 0;
+    expect(resolve(
+      '../files/foo',
+      { ...testContext, getFilename() { return utils.getFilename('foo.js'); } },
+    )).to.equal(undefined);
+    expect(testContextReports[0]).to.be.an('object');
+    expect(testContextReports[0].message).to.equal(`Resolve error: ${resolverName} with invalid interface loaded as resolver`);
+    expect(testContextReports[0].loc).to.eql({ line: 1, column: 0 });
+  });
 
   it('respects import/resolve extensions', function () {
-    const testContext = utils.testContext({ 'import/resolve': { 'extensions': ['.jsx'] }})
+    const testContext = utils.testContext({ 'import/resolve': { extensions: ['.jsx'] } });
 
-    expect(resolve( './jsx/MyCoolComponent'
-                      , testContext
-                      )).to.equal(utils.testFilePath('./jsx/MyCoolComponent.jsx'))
-  })
+    expect(resolve(
+      './jsx/MyCoolComponent',
+      testContext,
+    )).to.equal(utils.testFilePath('./jsx/MyCoolComponent.jsx'));
+  });
 
   it('reports load exception in a user resolver', function () {
-    const testContext = utils.testContext({ 'import/resolver': './load-error-resolver' })
-    const testContextReports = []
+    const testContext = utils.testContext({ 'import/resolver': './load-error-resolver' });
+    const testContextReports = [];
     testContext.report = function (reportInfo) {
-      testContextReports.push(reportInfo)
+      testContextReports.push(reportInfo);
+    };
+
+    expect(resolve(
+      '../files/exception',
+      { ...testContext, getFilename() { return utils.getFilename('exception.js'); } },
+    )).to.equal(undefined);
+    expect(testContextReports[0]).to.be.an('object');
+    expect(replaceErrorStackForTest(testContextReports[0].message)).to.equal('Resolve error: SyntaxError: TEST SYNTAX ERROR\n<stack-was-here>');
+    expect(testContextReports[0].loc).to.eql({ line: 1, column: 0 });
+  });
+
+  // context.getPhysicalFilename() is available in ESLint 7.28+
+  (semver.satisfies(eslintPkg.version, '>= 7.28') ? describe : describe.skip)('getPhysicalFilename()', () => {
+    function unexpectedCallToGetFilename() {
+      throw new Error('Expected to call to getPhysicalFilename() instead of getFilename()');
     }
 
-    expect(resolve( '../files/exception'
-                      , Object.assign({}, testContext, { getFilename: function () { return utils.getFilename('exception.js') } })
-                    )).to.equal(undefined)
-    expect(testContextReports[0]).to.be.an('object')
-    expect(testContextReports[0].message).to.equal('Resolve error: TEST ERROR')
-    expect(testContextReports[0].loc).to.eql({ line: 1, column: 0 })
-  })
-
-  const caseDescribe = (!CASE_SENSITIVE_FS ? describe : describe.skip)
+    it('resolves via a custom resolver with interface version 1', function () {
+      const testContext = utils.testContext({ 'import/resolver': './foo-bar-resolver-v1' });
+
+      expect(resolve(
+        '../files/foo',
+        { ...testContext, getFilename: unexpectedCallToGetFilename, getPhysicalFilename() { return utils.getFilename('foo.js'); } },
+      )).to.equal(utils.testFilePath('./bar.jsx'));
+
+      expect(resolve(
+        '../files/exception',
+        { ...testContext, getFilename: unexpectedCallToGetFilename, getPhysicalFilename() { return utils.getFilename('exception.js'); } },
+      )).to.equal(undefined);
+
+      expect(resolve(
+        '../files/not-found',
+        { ...testContext, getFilename: unexpectedCallToGetFilename, getPhysicalFilename() { return utils.getFilename('not-found.js'); } },
+      )).to.equal(undefined);
+    });
+
+    it('resolves via a custom resolver with interface version 1 assumed if not specified', function () {
+      const testContext = utils.testContext({ 'import/resolver': './foo-bar-resolver-no-version' });
+
+      expect(resolve(
+        '../files/foo',
+        { ...testContext, getFilename: unexpectedCallToGetFilename, getPhysicalFilename() { return utils.getFilename('foo.js'); } },
+      )).to.equal(utils.testFilePath('./bar.jsx'));
+
+      expect(resolve(
+        '../files/exception',
+        { ...testContext, getFilename: unexpectedCallToGetFilename, getPhysicalFilename() { return utils.getFilename('exception.js'); } },
+      )).to.equal(undefined);
+
+      expect(resolve(
+        '../files/not-found',
+        { ...testContext, getFilename: unexpectedCallToGetFilename, getPhysicalFilename() { return utils.getFilename('not-found.js'); } },
+      )).to.equal(undefined);
+    });
+
+    it('resolves via a custom resolver with interface version 2', function () {
+      const testContext = utils.testContext({ 'import/resolver': './foo-bar-resolver-v2' });
+      const testContextReports = [];
+      testContext.report = function (reportInfo) {
+        testContextReports.push(reportInfo);
+      };
+
+      expect(resolve(
+        '../files/foo',
+        { ...testContext, getFilename: unexpectedCallToGetFilename, getPhysicalFilename() { return utils.getFilename('foo.js'); } },
+      )).to.equal(utils.testFilePath('./bar.jsx'));
+
+      testContextReports.length = 0;
+      expect(resolve(
+        '../files/exception',
+        { ...testContext, getFilename: unexpectedCallToGetFilename, getPhysicalFilename() { return utils.getFilename('exception.js'); } },
+      )).to.equal(undefined);
+      expect(testContextReports[0]).to.be.an('object');
+      expect(replaceErrorStackForTest(testContextReports[0].message)).to.equal('Resolve error: foo-bar-resolver-v2 resolve test exception\n<stack-was-here>');
+      expect(testContextReports[0].loc).to.eql({ line: 1, column: 0 });
+
+      testContextReports.length = 0;
+      expect(resolve(
+        '../files/not-found',
+        { ...testContext, getFilename: unexpectedCallToGetFilename, getPhysicalFilename() { return utils.getFilename('not-found.js'); } },
+      )).to.equal(undefined);
+      expect(testContextReports.length).to.equal(0);
+    });
+
+    it('respects import/resolver as array of strings', function () {
+      const testContext = utils.testContext({ 'import/resolver': ['./foo-bar-resolver-v2', './foo-bar-resolver-v1'] });
+
+      expect(resolve(
+        '../files/foo',
+        { ...testContext, getFilename: unexpectedCallToGetFilename, getPhysicalFilename() { return utils.getFilename('foo.js'); } },
+      )).to.equal(utils.testFilePath('./bar.jsx'));
+    });
+
+    it('respects import/resolver as object', function () {
+      const testContext = utils.testContext({ 'import/resolver': { './foo-bar-resolver-v2': {} } });
+
+      expect(resolve(
+        '../files/foo',
+        { ...testContext, getFilename: unexpectedCallToGetFilename, getPhysicalFilename() { return utils.getFilename('foo.js'); } },
+      )).to.equal(utils.testFilePath('./bar.jsx'));
+    });
+
+    it('respects import/resolver as array of objects', function () {
+      const testContext = utils.testContext({ 'import/resolver': [{ './foo-bar-resolver-v2': {} }, { './foo-bar-resolver-v1': {} }] });
+
+      expect(resolve(
+        '../files/foo',
+        { ...testContext, getFilename: unexpectedCallToGetFilename, getPhysicalFilename() { return utils.getFilename('foo.js'); } },
+      )).to.equal(utils.testFilePath('./bar.jsx'));
+    });
+
+    it('finds resolvers from the source files rather than eslint-module-utils', function () {
+      const testContext = utils.testContext({ 'import/resolver': { foo: {} } });
+
+      expect(resolve(
+        '../files/foo',
+        { ...testContext, getFilename: unexpectedCallToGetFilename, getPhysicalFilename() { return utils.getFilename('foo.js'); } },
+      )).to.equal(utils.testFilePath('./bar.jsx'));
+    });
+
+    it('reports invalid import/resolver config', function () {
+      const testContext = utils.testContext({ 'import/resolver': 123.456 });
+      const testContextReports = [];
+      testContext.report = function (reportInfo) {
+        testContextReports.push(reportInfo);
+      };
+
+      testContextReports.length = 0;
+      expect(resolve(
+        '../files/foo',
+        { ...testContext, getFilename: unexpectedCallToGetFilename, getPhysicalFilename() { return utils.getFilename('foo.js'); } },
+      )).to.equal(undefined);
+      expect(testContextReports[0]).to.be.an('object');
+      expect(testContextReports[0].message).to.equal('Resolve error: invalid resolver config');
+      expect(testContextReports[0].loc).to.eql({ line: 1, column: 0 });
+    });
+
+    it('reports loaded resolver with invalid interface', function () {
+      const resolverName = './foo-bar-resolver-invalid';
+      const testContext = utils.testContext({ 'import/resolver': resolverName });
+      const testContextReports = [];
+      testContext.report = function (reportInfo) {
+        testContextReports.push(reportInfo);
+      };
+      testContextReports.length = 0;
+      expect(resolve(
+        '../files/foo',
+        { ...testContext, getFilename: unexpectedCallToGetFilename, getPhysicalFilename() { return utils.getFilename('foo.js'); } },
+      )).to.equal(undefined);
+      expect(testContextReports[0]).to.be.an('object');
+      expect(testContextReports[0].message).to.equal(`Resolve error: ${resolverName} with invalid interface loaded as resolver`);
+      expect(testContextReports[0].loc).to.eql({ line: 1, column: 0 });
+    });
+
+    it('respects import/resolve extensions', function () {
+      const testContext = utils.testContext({ 'import/resolve': { extensions: ['.jsx'] } });
+
+      expect(resolve(
+        './jsx/MyCoolComponent',
+        testContext,
+      )).to.equal(utils.testFilePath('./jsx/MyCoolComponent.jsx'));
+    });
+
+    it('reports load exception in a user resolver', function () {
+      const testContext = utils.testContext({ 'import/resolver': './load-error-resolver' });
+      const testContextReports = [];
+      testContext.report = function (reportInfo) {
+        testContextReports.push(reportInfo);
+      };
+
+      expect(resolve(
+        '../files/exception',
+        { ...testContext, getFilename: unexpectedCallToGetFilename, getPhysicalFilename() { return utils.getFilename('exception.js'); } },
+      )).to.equal(undefined);
+      expect(testContextReports[0]).to.be.an('object');
+      expect(replaceErrorStackForTest(testContextReports[0].message)).to.equal('Resolve error: SyntaxError: TEST SYNTAX ERROR\n<stack-was-here>');
+      expect(testContextReports[0].loc).to.eql({ line: 1, column: 0 });
+    });
+  });
+
+  const caseDescribe = !CASE_SENSITIVE_FS ? describe : describe.skip;
   caseDescribe('case sensitivity', function () {
-    let file
-    const testContext = utils.testContext({ 'import/resolve': { 'extensions': ['.jsx'] }})
+    let file;
+    const testContext = utils.testContext({
+      'import/resolve': { extensions: ['.jsx'] },
+      'import/cache': { lifetime: 0 },
+    });
+    const testSettings = testContext.settings;
     before('resolve', function () {
       file = resolve(
       // Note the case difference 'MyUncoolComponent' vs 'MyUnCoolComponent'
-        './jsx/MyUncoolComponent', testContext)
-    })
+        './jsx/MyUncoolComponent', testContext);
+    });
     it('resolves regardless of case', function () {
-      expect(file, 'path to ./jsx/MyUncoolComponent').to.exist
-    })
+      expect(file, 'path to ./jsx/MyUncoolComponent').to.exist;
+    });
     it('detects case does not match FS', function () {
-      expect(fileExistsWithCaseSync(file, ModuleCache.getSettings(testContext)))
-        .to.be.false
-    })
+      expect(fileExistsWithCaseSync(file, testSettings))
+        .to.equal(false);
+    });
     it('detecting case does not include parent folder path (issue #720)', function () {
-      const f = path.join(process.cwd().toUpperCase(), './tests/files/jsx/MyUnCoolComponent.jsx')
-      expect(fileExistsWithCaseSync(f, ModuleCache.getSettings(testContext), true))
-        .to.be.true
-    })
-  })
+      const f = path.join(process.cwd().toUpperCase(), './tests/files/jsx/MyUnCoolComponent.jsx');
+      expect(fileExistsWithCaseSync(f, testSettings))
+        .to.equal(true);
+    });
+    it('detecting case should include parent folder path', function () {
+      const f = path.join(process.cwd().toUpperCase(), './tests/files/jsx/MyUnCoolComponent.jsx');
+      expect(fileExistsWithCaseSync(f, testSettings, true))
+        .to.equal(false);
+    });
+  });
 
   describe('rename cache correctness', function () {
     const context = utils.testContext({
-      'import/cache': { 'lifetime': 1 },
-    })
+      'import/cache': { lifetime: 1 },
+    });
 
-    const infiniteContexts = [ '∞', 'Infinity' ].map(inf => [inf,
+    const infiniteContexts = ['∞', 'Infinity'].map((inf) => [inf,
       utils.testContext({
-        'import/cache': { 'lifetime': inf },
-      })])
-
+        'import/cache': { lifetime: inf },
+      })]);
 
     const pairs = [
       ['./CaseyKasem.js', './CASEYKASEM2.js'],
-    ]
+    ];
 
     pairs.forEach(([original, changed]) => {
       describe(`${original} => ${changed}`, function () {
 
         before('sanity check', function () {
-          expect(resolve(original, context)).to.exist
-          expect(resolve(changed, context)).not.to.exist
-        })
+          expect(resolve(original, context)).to.exist;
+          expect(resolve(changed, context)).not.to.exist;
+        });
 
         // settings are part of cache key
         before('warm up infinite entries', function () {
-          infiniteContexts.forEach(([,c]) => {
-            expect(resolve(original, c)).to.exist
-          })
-        })
+          infiniteContexts.forEach(([, c]) => {
+            expect(resolve(original, c)).to.exist;
+          });
+        });
 
         before('rename', function (done) {
           fs.rename(
             utils.testFilePath(original),
             utils.testFilePath(changed),
-            done)
-        })
+            done);
+        });
 
-        before('verify rename', (done) =>
-          fs.exists(
-            utils.testFilePath(changed),
-            exists => done(exists ? null : new Error('new file does not exist'))))
+        before('verify rename', (done) => fs.exists(
+          utils.testFilePath(changed),
+          (exists) => done(exists ? null : new Error('new file does not exist'))));
 
         it('gets cached values within cache lifetime', function () {
           // get cached values initially
-          expect(resolve(original, context)).to.exist
-        })
+          expect(resolve(original, context)).to.exist;
+        });
 
         it('gets updated values immediately', function () {
           // get cached values initially
-          expect(resolve(changed, context)).to.exist
-        })
+          expect(resolve(changed, context)).to.exist;
+        });
 
         // special behavior for infinity
         describe('infinite cache', function () {
-          this.timeout(1500)
+          this.timeout(1.5e3);
 
-          before((done) => setTimeout(done, 1100))
+          before((done) => setTimeout(done, 1100));
 
           infiniteContexts.forEach(([inf, infiniteContext]) => {
             it(`lifetime: ${inf} still gets cached values after ~1s`, function () {
-              expect(resolve(original, infiniteContext), original).to.exist
-            })
-          })
+              expect(resolve(original, infiniteContext), original).to.exist;
+            });
+          });
 
-        })
+        });
 
         describe('finite cache', function () {
-          this.timeout(1200)
-          before((done) => setTimeout(done, 1000))
+          this.timeout(1.2e3);
+          before((done) => setTimeout(done, 1000));
           it('gets correct values after cache lifetime', function () {
-            expect(resolve(original, context)).not.to.exist
-            expect(resolve(changed, context)).to.exist
-          })
-        })
+            expect(resolve(original, context)).not.to.exist;
+            expect(resolve(changed, context)).to.exist;
+          });
+        });
 
         after('restore original case', function (done) {
           fs.rename(
             utils.testFilePath(changed),
             utils.testFilePath(original),
-            done)
-        })
-      })
-    })
-  })
-
-})
+            done,
+          );
+        });
+      });
+    });
+  });
+
+});
diff --git a/tests/src/exportMap/childContext.js b/tests/src/exportMap/childContext.js
new file mode 100644
index 000000000..5bc53fdb0
--- /dev/null
+++ b/tests/src/exportMap/childContext.js
@@ -0,0 +1,121 @@
+import { expect } from 'chai';
+import { hashObject } from 'eslint-module-utils/hash';
+
+import childContext from '../../../src/exportMap/childContext';
+
+describe('childContext', () => {
+  const settings = {
+    setting1: true,
+    setting2: false,
+  };
+  const parserOptions = {
+    ecmaVersion: 'latest',
+    sourceType: 'module',
+  };
+  const parserPath = 'path/to/parser';
+  const path = 'path/to/src/file';
+  const languageOptions = {
+    ecmaVersion: 2024,
+    sourceType: 'module',
+    parser: {
+      parseForESLint() { return 'parser1'; },
+    },
+  };
+  const languageOptionsHash = hashObject({ languageOptions }).digest('hex');
+  const parserOptionsHash = hashObject({ parserOptions }).digest('hex');
+  const settingsHash = hashObject({ settings }).digest('hex');
+
+  // https://github.com/import-js/eslint-plugin-import/issues/3051
+  it('should pass context properties through, if present', () => {
+    const mockContext = {
+      settings,
+      parserOptions,
+      parserPath,
+      languageOptions,
+    };
+
+    const result = childContext(path, mockContext);
+
+    expect(result.settings).to.deep.equal(settings);
+    expect(result.parserOptions).to.deep.equal(parserOptions);
+    expect(result.parserPath).to.equal(parserPath);
+    expect(result.languageOptions).to.deep.equal(languageOptions);
+  });
+
+  it('should add path and cacheKey to context', () => {
+    const mockContext = {
+      settings,
+      parserOptions,
+      parserPath,
+    };
+
+    const result = childContext(path, mockContext);
+
+    expect(result.path).to.equal(path);
+    expect(result.cacheKey).to.be.a('string');
+  });
+
+  it('should construct cache key out of languageOptions if present', () => {
+    const mockContext = {
+      settings,
+      languageOptions,
+    };
+
+    const result = childContext(path, mockContext);
+
+    expect(result.cacheKey).to.equal(languageOptionsHash + settingsHash + path);
+  });
+
+  it('should use the same cache key upon multiple calls', () => {
+    const mockContext = {
+      settings,
+      languageOptions,
+    };
+
+    let result = childContext(path, mockContext);
+
+    const expectedCacheKey = languageOptionsHash + settingsHash + path;
+    expect(result.cacheKey).to.equal(expectedCacheKey);
+
+    result = childContext(path, mockContext);
+    expect(result.cacheKey).to.equal(expectedCacheKey);
+  });
+
+  it('should update cacheKey if different languageOptions are passed in', () => {
+    const mockContext = {
+      settings,
+      languageOptions,
+    };
+
+    let result = childContext(path, mockContext);
+
+    const firstCacheKey = languageOptionsHash + settingsHash + path;
+    expect(result.cacheKey).to.equal(firstCacheKey);
+
+    // Second run with different parser function
+    mockContext.languageOptions = {
+      ...languageOptions,
+      parser: {
+        parseForESLint() { return 'parser2'; },
+      },
+    };
+
+    result = childContext(path, mockContext);
+
+    const secondCacheKey = hashObject({ languageOptions: mockContext.languageOptions }).digest('hex') + settingsHash + path;
+    expect(result.cacheKey).to.not.equal(firstCacheKey);
+    expect(result.cacheKey).to.equal(secondCacheKey);
+  });
+
+  it('should construct cache key out of parserOptions and parserPath if no languageOptions', () => {
+    const mockContext = {
+      settings,
+      parserOptions,
+      parserPath,
+    };
+
+    const result = childContext(path, mockContext);
+
+    expect(result.cacheKey).to.equal(String(parserPath) + parserOptionsHash + settingsHash + path);
+  });
+});
diff --git a/tests/src/package.js b/tests/src/package.js
index 9f66c6607..c56bd1333 100644
--- a/tests/src/package.js
+++ b/tests/src/package.js
@@ -1,65 +1,71 @@
-var expect = require('chai').expect
+const expect = require('chai').expect;
 
-var path = require('path')
-  , fs = require('fs')
+const path = require('path');
+const fs = require('fs');
 
 function isJSFile(f) {
-  return path.extname(f) === '.js'
+  return path.extname(f) === '.js';
 }
 
 describe('package', function () {
-  let pkg = path.join(process.cwd(), 'src')
-    , module
+  const pkg = path.join(process.cwd(), 'src');
+  let module;
 
   before('is importable', function () {
-    module = require(pkg)
-  })
+    module = require(pkg);
+  });
 
   it('exists', function () {
-    expect(module).to.exist
-  })
+    expect(module).to.exist;
+  });
 
   it('has every rule', function (done) {
 
     fs.readdir(
       path.join(pkg, 'rules')
-    , function (err, files) {
-        expect(err).not.to.exist
+      , function (err, files) {
+        expect(err).not.to.exist;
 
         files.filter(isJSFile).forEach(function (f) {
-          expect(module.rules).to.have
-            .property(path.basename(f, '.js'))
-        })
+          expect(module.rules).to.have.property(path.basename(f, '.js'));
+        });
 
-        done()
-      })
-  })
+        done();
+      });
+  });
 
   it('exports all configs', function (done) {
     fs.readdir(path.join(process.cwd(), 'config'), function (err, files) {
-      if (err) { done(err); return }
-      files.filter(isJSFile).forEach(file => {
-        if (file[0] === '.') return
-        expect(module.configs).to.have.property(path.basename(file, '.js'))
-      })
-      done()
-    })
-  })
+      if (err) { done(err); return; }
+      files.filter(isJSFile).forEach((file) => {
+        if (file[0] === '.') { return; }
+        expect(module.configs).to.have.property(path.basename(file, '.js'));
+      });
+      done();
+    });
+  });
+
+  function getRulePath(ruleName) {
+    // 'require' does not work with dynamic paths because of the compilation step by babel
+    // (which resolves paths according to the root folder configuration)
+    // the usage of require.resolve on a static path gets around this
+    return path.resolve(require.resolve('rules/no-unresolved'), '..', ruleName);
+  }
 
   it('has configs only for rules that exist', function () {
-    for (let configFile in module.configs) {
-      let preamble = 'import/'
+    for (const configFile in module.configs) {
+      const preamble = 'import/';
 
-      for (let rule in module.configs[configFile].rules) {
-        expect(() => require('rules/'+rule.slice(preamble.length)))
-          .not.to.throw(Error)
+      for (const rule in module.configs[configFile].rules) {
+        expect(() => require(getRulePath(rule.slice(preamble.length))))
+          .not.to.throw(Error);
       }
     }
-  })
+  });
 
   it('marks deprecated rules in their metadata', function () {
-    expect(module.rules['imports-first'].meta.deprecated).to.be.true
-    expect(module.rules['first'].meta.deprecated).not.to.be.true
-  })
+    expect(module.rules['imports-first'].meta.deprecated).to.be.true;
+    expect(module.rules.first.meta.deprecated).not.to.be.true;
+  });
 
-})
+});
diff --git a/tests/src/rule-tester.js b/tests/src/rule-tester.js
new file mode 100644
index 000000000..103f2fd6f
--- /dev/null
+++ b/tests/src/rule-tester.js
@@ -0,0 +1,47 @@
+import { RuleTester } from 'eslint';
+import { version as eslintVersion } from 'eslint/package.json';
+import semver from 'semver';
+
+export const usingFlatConfig = semver.major(eslintVersion) >= 9;
+
+export function withoutAutofixOutput(test) {
+  return { ...test, ...usingFlatConfig || { output: test.code } };
+}
+
+class FlatCompatRuleTester {
+  constructor(testerConfig = { parserOptions: { sourceType: 'script' } }) {
+    this._tester = new RuleTester(FlatCompatRuleTester._flatCompat(testerConfig));
+  }
+
+  run(ruleName, rule, tests) {
+    this._tester.run(ruleName, rule, {
+      valid: tests.valid.map((t) => FlatCompatRuleTester._flatCompat(t)),
+      invalid: tests.invalid.map((t) => FlatCompatRuleTester._flatCompat(t)),
+    });
+  }
+
+  static _flatCompat(config) {
+    if (!config || !usingFlatConfig || typeof config !== 'object') {
+      return config;
+    }
+
+    const { parser, parserOptions = {}, languageOptions = {}, ...remainingConfig  } = config;
+    const { ecmaVersion, sourceType, ...remainingParserOptions } = parserOptions;
+    const parserObj = typeof parser === 'string' ? require(parser) : parser;
+
+    return {
+      ...remainingConfig,
+      languageOptions: {
+        ...languageOptions,
+        ...parserObj ? { parser: parserObj } : {},
+        ...ecmaVersion ? { ecmaVersion } : {},
+        ...sourceType ? { sourceType } : {},
+        parserOptions: {
+          ...remainingParserOptions,
+        },
+      },
+    };
+  }
+}
+
+export { FlatCompatRuleTester as RuleTester };
diff --git a/tests/src/rules/consistent-type-specifier-style.js b/tests/src/rules/consistent-type-specifier-style.js
new file mode 100644
index 000000000..139457ff6
--- /dev/null
+++ b/tests/src/rules/consistent-type-specifier-style.js
@@ -0,0 +1,430 @@
+import { RuleTester } from '../rule-tester';
+import { test, parsers, tsVersionSatisfies, eslintVersionSatisfies, typescriptEslintParserSatisfies } from '../utils';
+
+const rule = require('rules/consistent-type-specifier-style');
+
+const COMMON_TESTS = {
+  valid: [
+    //
+    // prefer-top-level
+    //
+    test({
+      code: "import Foo from 'Foo';",
+      options: ['prefer-top-level'],
+    }),
+    test({
+      code: "import type Foo from 'Foo';",
+      options: ['prefer-top-level'],
+    }),
+    test({
+      code: "import { Foo } from 'Foo';",
+      options: ['prefer-top-level'],
+    }),
+    test({
+      code: "import { Foo as Bar } from 'Foo';",
+      options: ['prefer-top-level'],
+    }),
+    test({
+      code: "import * as Foo from 'Foo';",
+      options: ['prefer-top-level'],
+    }),
+    test({
+      code: "import 'Foo';",
+      options: ['prefer-top-level'],
+    }),
+    test({
+      code: "import {} from 'Foo';",
+      options: ['prefer-top-level'],
+    }),
+    test({
+      code: "import type {} from 'Foo';",
+      options: ['prefer-top-level'],
+    }),
+    test({
+      code: "import type { Foo } from 'Foo';",
+      options: ['prefer-top-level'],
+    }),
+    test({
+      code: "import type { Foo as Bar } from 'Foo';",
+      options: ['prefer-top-level'],
+    }),
+    test({
+      code: "import type { Foo, Bar, Baz, Bam } from 'Foo';",
+      options: ['prefer-top-level'],
+    }),
+
+    //
+    // prefer-inline
+    //
+    test({
+      code: "import Foo from 'Foo';",
+      options: ['prefer-inline'],
+    }),
+    test({
+      code: "import type Foo from 'Foo';",
+      options: ['prefer-inline'],
+    }),
+    test({
+      code: "import { Foo } from 'Foo';",
+      options: ['prefer-inline'],
+    }),
+    test({
+      code: "import { Foo as Bar } from 'Foo';",
+      options: ['prefer-inline'],
+    }),
+    test({
+      code: "import * as Foo from 'Foo';",
+      options: ['prefer-inline'],
+    }),
+    test({
+      code: "import 'Foo';",
+      options: ['prefer-inline'],
+    }),
+    test({
+      code: "import {} from 'Foo';",
+      options: ['prefer-inline'],
+    }),
+    test({
+      code: "import type {} from 'Foo';",
+      options: ['prefer-inline'],
+    }),
+    test({
+      code: "import { type Foo } from 'Foo';",
+      options: ['prefer-inline'],
+    }),
+    test({
+      code: "import { type Foo as Bar } from 'Foo';",
+      options: ['prefer-inline'],
+    }),
+    test({
+      code: "import { type Foo, type Bar, Baz, Bam } from 'Foo';",
+      options: ['prefer-inline'],
+    }),
+  ],
+  invalid: [
+    //
+    // prefer-top-level
+    //
+    {
+      code: "import { type Foo } from 'Foo';",
+      output: "import type {Foo} from 'Foo';",
+      options: ['prefer-top-level'],
+      errors: [{
+        message: 'Prefer using a top-level type-only import instead of inline type specifiers.',
+        type: 'ImportDeclaration',
+      }],
+    },
+    {
+      code: "import { type Foo as Bar } from 'Foo';",
+      output: "import type {Foo as Bar} from 'Foo';",
+      options: ['prefer-top-level'],
+      errors: [{
+        message: 'Prefer using a top-level type-only import instead of inline type specifiers.',
+        type: 'ImportDeclaration',
+      }],
+    },
+    {
+      code: "import { type Foo, type Bar } from 'Foo';",
+      output: "import type {Foo, Bar} from 'Foo';",
+      options: ['prefer-top-level'],
+      errors: [{
+        message: 'Prefer using a top-level type-only import instead of inline type specifiers.',
+        type: 'ImportDeclaration',
+      }],
+    },
+    {
+      code: "import { Foo, type Bar } from 'Foo';",
+      output: "import { Foo  } from 'Foo';\nimport type {Bar} from 'Foo';",
+      options: ['prefer-top-level'],
+      errors: [{
+        message: 'Prefer using a top-level type-only import instead of inline type specifiers.',
+        type: 'ImportSpecifier',
+      }],
+    },
+    {
+      code: "import { type Foo, Bar } from 'Foo';",
+      output: "import {  Bar } from 'Foo';\nimport type {Foo} from 'Foo';",
+      options: ['prefer-top-level'],
+      errors: [{
+        message: 'Prefer using a top-level type-only import instead of inline type specifiers.',
+        type: 'ImportSpecifier',
+      }],
+    },
+    {
+      code: "import Foo, { type Bar } from 'Foo';",
+      output: "import Foo from 'Foo';\nimport type {Bar} from 'Foo';",
+      options: ['prefer-top-level'],
+      errors: [{
+        message: 'Prefer using a top-level type-only import instead of inline type specifiers.',
+        type: 'ImportSpecifier',
+      }],
+    },
+    {
+      code: "import Foo, { type Bar, Baz } from 'Foo';",
+      output: "import Foo, {  Baz } from 'Foo';\nimport type {Bar} from 'Foo';",
+      options: ['prefer-top-level'],
+      errors: [{
+        message: 'Prefer using a top-level type-only import instead of inline type specifiers.',
+        type: 'ImportSpecifier',
+      }],
+    },
+    // https://github.com/import-js/eslint-plugin-import/issues/2753
+    {
+      code: `\
+import { Component, type ComponentProps } from "package-1";
+import {
+  Component1,
+  Component2,
+  Component3,
+  Component4,
+  Component5,
+} from "package-2";`,
+      output: `\
+import { Component  } from "package-1";
+import type {ComponentProps} from "package-1";
+import {
+  Component1,
+  Component2,
+  Component3,
+  Component4,
+  Component5,
+} from "package-2";`,
+      options: ['prefer-top-level'],
+      errors: [{
+        message: 'Prefer using a top-level type-only import instead of inline type specifiers.',
+        type: 'ImportSpecifier',
+      }],
+    },
+
+    //
+    // prefer-inline
+    //
+    {
+      code: "import type { Foo } from 'Foo';",
+      output: "import  { type Foo } from 'Foo';",
+      options: ['prefer-inline'],
+      errors: [{
+        message: 'Prefer using inline type specifiers instead of a top-level type-only import.',
+        type: 'ImportDeclaration',
+      }],
+    },
+    {
+      code: "import type { Foo, Bar, Baz } from 'Foo';",
+      output: "import  { type Foo, type Bar, type Baz } from 'Foo';",
+      options: ['prefer-inline'],
+      errors: [{
+        message: 'Prefer using inline type specifiers instead of a top-level type-only import.',
+        type: 'ImportDeclaration',
+      }],
+    },
+  ],
+};
+
+const TS_ONLY = {
+  valid: [
+    //
+    // always valid
+    //
+    test({ code: "import type * as Foo from 'Foo';" }),
+  ],
+  invalid: [],
+};
+
+const FLOW_ONLY = {
+  valid: [
+    //
+    // prefer-top-level
+    //
+    {
+      code: "import typeof Foo from 'Foo';",
+      options: ['prefer-top-level'],
+    },
+    {
+      code: "import typeof { Foo, Bar, Baz, Bam } from 'Foo';",
+      options: ['prefer-top-level'],
+    },
+
+    //
+    // prefer-inline
+    //
+    {
+      code: "import typeof Foo from 'Foo';",
+      options: ['prefer-inline'],
+    },
+    {
+      code: "import { typeof Foo } from 'Foo';",
+      options: ['prefer-inline'],
+    },
+    {
+      code: "import { typeof Foo, typeof Bar, typeof Baz, typeof Bam } from 'Foo';",
+      options: ['prefer-inline'],
+    },
+    {
+      code: "import { type Foo, type Bar, typeof Baz, typeof Bam } from 'Foo';",
+      options: ['prefer-inline'],
+    },
+  ],
+  invalid: [
+    //
+    // prefer-top-level
+    //
+    {
+      code: "import { typeof Foo } from 'Foo';",
+      output: "import typeof {Foo} from 'Foo';",
+      options: ['prefer-top-level'],
+      errors: [{
+        message: 'Prefer using a top-level typeof-only import instead of inline typeof specifiers.',
+        type: 'ImportDeclaration',
+      }],
+    },
+    {
+      code: "import { typeof Foo as Bar } from 'Foo';",
+      output: "import typeof {Foo as Bar} from 'Foo';",
+      options: ['prefer-top-level'],
+      errors: [{
+        message: 'Prefer using a top-level typeof-only import instead of inline typeof specifiers.',
+        type: 'ImportDeclaration',
+      }],
+    },
+    {
+      code: "import { type Foo, typeof Bar } from 'Foo';",
+      output: "import type {Foo} from 'Foo';\nimport typeof {Bar} from 'Foo';",
+      options: ['prefer-top-level'],
+      errors: [{
+        message: 'Prefer using a top-level type/typeof-only import instead of inline type/typeof specifiers.',
+        type: 'ImportDeclaration',
+      }],
+    },
+    {
+      code: "import { typeof Foo, typeof Bar } from 'Foo';",
+      output: "import typeof {Foo, Bar} from 'Foo';",
+      options: ['prefer-top-level'],
+      errors: [{
+        message: 'Prefer using a top-level typeof-only import instead of inline typeof specifiers.',
+        type: 'ImportDeclaration',
+      }],
+    },
+    {
+      code: "import { Foo, typeof Bar } from 'Foo';",
+      output: "import { Foo  } from 'Foo';\nimport typeof {Bar} from 'Foo';",
+      options: ['prefer-top-level'],
+      errors: [{
+        message: 'Prefer using a top-level typeof-only import instead of inline typeof specifiers.',
+        type: 'ImportSpecifier',
+      }],
+    },
+    {
+      code: "import { typeof Foo, Bar } from 'Foo';",
+      output: "import {  Bar } from 'Foo';\nimport typeof {Foo} from 'Foo';",
+      options: ['prefer-top-level'],
+      errors: [{
+        message: 'Prefer using a top-level typeof-only import instead of inline typeof specifiers.',
+        type: 'ImportSpecifier',
+      }],
+    },
+    {
+      code: "import { Foo, type Bar, typeof Baz } from 'Foo';",
+      output: "import { Foo   } from 'Foo';\nimport type {Bar} from 'Foo';\nimport typeof {Baz} from 'Foo';",
+      options: ['prefer-top-level'],
+      errors: [
+        {
+          message: 'Prefer using a top-level type-only import instead of inline type specifiers.',
+          type: 'ImportSpecifier',
+        },
+        {
+          message: 'Prefer using a top-level typeof-only import instead of inline typeof specifiers.',
+          type: 'ImportSpecifier',
+        },
+      ],
+    },
+    {
+      code: "import Foo, { typeof Bar } from 'Foo';",
+      output: "import Foo from 'Foo';\nimport typeof {Bar} from 'Foo';",
+      options: ['prefer-top-level'],
+      errors: [{
+        message: 'Prefer using a top-level typeof-only import instead of inline typeof specifiers.',
+        type: 'ImportSpecifier',
+      }],
+    },
+    {
+      code: "import Foo, { typeof Bar, Baz } from 'Foo';",
+      output: "import Foo, {  Baz } from 'Foo';\nimport typeof {Bar} from 'Foo';",
+      options: ['prefer-top-level'],
+      errors: [{
+        message: 'Prefer using a top-level typeof-only import instead of inline typeof specifiers.',
+        type: 'ImportSpecifier',
+      }],
+    },
+
+    //
+    // prefer-inline
+    //
+    {
+      code: "import typeof { Foo } from 'Foo';",
+      output: "import  { typeof Foo } from 'Foo';",
+      options: ['prefer-inline'],
+      errors: [{
+        message: 'Prefer using inline typeof specifiers instead of a top-level typeof-only import.',
+        type: 'ImportDeclaration',
+      }],
+    },
+    {
+      code: "import typeof { Foo, Bar, Baz } from 'Foo';",
+      output: "import  { typeof Foo, typeof Bar, typeof Baz } from 'Foo';",
+      options: ['prefer-inline'],
+      errors: [{
+        message: 'Prefer using inline typeof specifiers instead of a top-level typeof-only import.',
+        type: 'ImportDeclaration',
+      }],
+    },
+  ],
+};
+
+context('TypeScript', () => {
+  // inline type specifiers weren't supported prior to TS v4.5
+  if (!parsers.TS_NEW || !tsVersionSatisfies('>= 4.5') || !typescriptEslintParserSatisfies('>= 5.7.0')) {
+    return;
+  }
+
+  const ruleTester = new RuleTester({
+    parser: parsers.TS_NEW,
+    parserOptions: {
+      ecmaVersion: 6,
+      sourceType: 'module',
+    },
+  });
+  ruleTester.run('consistent-type-specifier-style', rule, {
+    valid: [].concat(
+      COMMON_TESTS.valid,
+      TS_ONLY.valid,
+    ),
+    invalid: [].concat(
+      COMMON_TESTS.invalid,
+      TS_ONLY.invalid,
+    ),
+  });
+});
+
+context('Babel/Flow', () => {
+  if (!eslintVersionSatisfies('> 3')) {
+    return;
+  }
+
+  const ruleTester = new RuleTester({
+    parser: parsers.BABEL_OLD,
+    parserOptions: {
+      ecmaVersion: 6,
+      sourceType: 'module',
+    },
+  });
+  ruleTester.run('consistent-type-specifier-style', rule, {
+    valid: [].concat(
+      COMMON_TESTS.valid,
+      FLOW_ONLY.valid,
+    ),
+    invalid: [].concat(
+      COMMON_TESTS.invalid,
+      FLOW_ONLY.invalid,
+    ),
+  });
+});
diff --git a/tests/src/rules/default.js b/tests/src/rules/default.js
index c02b36448..1df57a23a 100644
--- a/tests/src/rules/default.js
+++ b/tests/src/rules/default.js
@@ -1,25 +1,28 @@
-import { test, SYNTAX_CASES } from '../utils'
-import { RuleTester } from 'eslint'
+import path from 'path';
+import { test, testVersion, SYNTAX_CASES, getTSParsers, parsers } from '../utils';
+import { RuleTester } from '../rule-tester';
+import semver from 'semver';
+import { version as tsEslintVersion } from 'typescript-eslint-parser/package.json';
 
-import { CASE_SENSITIVE_FS } from 'eslint-module-utils/resolve'
+import { CASE_SENSITIVE_FS } from 'eslint-module-utils/resolve';
 
-var ruleTester = new RuleTester()
-  , rule = require('rules/default')
+const ruleTester = new RuleTester();
+const rule = require('rules/default');
 
 ruleTester.run('default', rule, {
-  valid: [
+  valid: [].concat(
     test({ code: 'import "./malformed.js"' }),
 
-    test({code: 'import foo from "./empty-folder";'}),
-    test({code: 'import { foo } from "./default-export";'}),
-    test({code: 'import foo from "./default-export";'}),
-    test({code: 'import foo from "./mixed-exports";'}),
+    test({ code: 'import foo from "./empty-folder";' }),
+    test({ code: 'import { foo } from "./default-export";' }),
+    test({ code: 'import foo from "./default-export";' }),
+    test({ code: 'import foo from "./mixed-exports";' }),
     test({
-      code: 'import bar from "./default-export";'}),
+      code: 'import bar from "./default-export";' }),
     test({
-      code: 'import CoolClass from "./default-class";'}),
+      code: 'import CoolClass from "./default-class";' }),
     test({
-      code: 'import bar, { baz } from "./default-export";'}),
+      code: 'import bar, { baz } from "./default-export";' }),
 
     // core modules always have a default
     test({ code: 'import crypto from "crypto";' }),
@@ -27,20 +30,20 @@ ruleTester.run('default', rule, {
     test({ code: 'import common from "./common";' }),
 
     // es7 export syntax
-    test({ code: 'export bar from "./bar"'
-         , parser: require.resolve('babel-eslint') }),
+    test({ code: 'export bar from "./bar"',
+      parser: parsers.BABEL_OLD }),
     test({ code: 'export { default as bar } from "./bar"' }),
-    test({ code: 'export bar, { foo } from "./bar"'
-         , parser: require.resolve('babel-eslint') }),
+    test({ code: 'export bar, { foo } from "./bar"',
+      parser: parsers.BABEL_OLD }),
     test({ code: 'export { default as bar, foo } from "./bar"' }),
-    test({ code: 'export bar, * as names from "./bar"'
-         , parser: require.resolve('babel-eslint') }),
+    test({ code: 'export bar, * as names from "./bar"',
+      parser: parsers.BABEL_OLD }),
 
     // sanity check
     test({ code: 'export {a} from "./named-exports"' }),
     test({
       code: 'import twofer from "./trampoline"',
-      parser: require.resolve('babel-eslint'),
+      parser: parsers.BABEL_OLD,
     }),
 
     // jsx
@@ -68,31 +71,39 @@ ruleTester.run('default', rule, {
     // from no-errors
     test({
       code: "import Foo from './jsx/FooES7.js';",
-      parser: require.resolve('babel-eslint'),
+      parser: parsers.BABEL_OLD,
     }),
 
     // #545: more ES7 cases
     test({
       code: "import bar from './default-export-from.js';",
-      parser: require.resolve('babel-eslint'),
+      parser: parsers.BABEL_OLD,
     }),
     test({
       code: "import bar from './default-export-from-named.js';",
-      parser: require.resolve('babel-eslint'),
+      parser: parsers.BABEL_OLD,
     }),
     test({
       code: "import bar from './default-export-from-ignored.js';",
       settings: { 'import/ignore': ['common'] },
-      parser: require.resolve('babel-eslint'),
+      parser: parsers.BABEL_OLD,
     }),
     test({
       code: "export bar from './default-export-from-ignored.js';",
       settings: { 'import/ignore': ['common'] },
-      parser: require.resolve('babel-eslint'),
+      parser: parsers.BABEL_OLD,
     }),
 
+    // es2022: Arbitrary module namespace identifier names
+    testVersion('>= 8.7', () => ({
+      code: 'export { "default" as bar } from "./bar"',
+      parserOptions: {
+        ecmaVersion: 2022,
+      },
+    })),
+
     ...SYNTAX_CASES,
-  ],
+  ),
 
   invalid: [
     test({
@@ -102,44 +113,39 @@ ruleTester.run('default', rule, {
 
     test({
       code: 'import baz from "./named-exports";',
-      errors: [{ message: 'No default export found in module.'
-               , type: 'ImportDefaultSpecifier'}]}),
-
-    test({
-      code: "import Foo from './jsx/FooES7.js';",
-      errors: ["Parse errors in imported module './jsx/FooES7.js': Unexpected token = (6:16)"],
-    }),
+      errors: [{ message: 'No default export found in imported module "./named-exports".',
+        type: 'ImportDefaultSpecifier' }] }),
 
     // es7 export syntax
     test({
       code: 'export baz from "./named-exports"',
-      parser: require.resolve('babel-eslint'),
-      errors: ['No default export found in module.'],
+      parser: parsers.BABEL_OLD,
+      errors: ['No default export found in imported module "./named-exports".'],
     }),
     test({
       code: 'export baz, { bar } from "./named-exports"',
-      parser: require.resolve('babel-eslint'),
-      errors: ['No default export found in module.'],
+      parser: parsers.BABEL_OLD,
+      errors: ['No default export found in imported module "./named-exports".'],
     }),
     test({
       code: 'export baz, * as names from "./named-exports"',
-      parser: require.resolve('babel-eslint'),
-      errors: ['No default export found in module.'],
+      parser: parsers.BABEL_OLD,
+      errors: ['No default export found in imported module "./named-exports".'],
     }),
     // exports default from a module with no default
     test({
       code: 'import twofer from "./broken-trampoline"',
-      parser: require.resolve('babel-eslint'),
-      errors: ['No default export found in module.'],
+      parser: parsers.BABEL_OLD,
+      errors: ['No default export found in imported module "./broken-trampoline".'],
     }),
 
     // #328: * exports do not include default
     test({
       code: 'import barDefault from "./re-export"',
-      errors: [`No default export found in module.`],
+      errors: ['No default export found in imported module "./re-export".'],
     }),
   ],
-})
+});
 
 // #311: import of mismatched case
 if (!CASE_SENSITIVE_FS) {
@@ -152,8 +158,159 @@ if (!CASE_SENSITIVE_FS) {
     invalid: [
       test({
         code: 'import bar from "./Named-Exports"',
-        errors: ['No default export found in module.'],
+        errors: ['No default export found in imported module "./Named-Exports".'],
       }),
     ],
-  })
+  });
 }
+
+context('TypeScript', function () {
+  getTSParsers().forEach((parser) => {
+    ruleTester.run(`default`, rule, {
+      valid: [].concat(
+        test({
+          code: `import foobar from "./typescript-default"`,
+          parser,
+          settings: {
+            'import/parsers': { [parser]: ['.ts'] },
+            'import/resolver': { 'eslint-import-resolver-typescript': true },
+          },
+        }),
+        test({
+          code: `import foobar from "./typescript-export-assign-default"`,
+          parser,
+          settings: {
+            'import/parsers': { [parser]: ['.ts'] },
+            'import/resolver': { 'eslint-import-resolver-typescript': true },
+          },
+        }),
+        test({
+          code: `import foobar from "./typescript-export-assign-function"`,
+          parser,
+          settings: {
+            'import/parsers': { [parser]: ['.ts'] },
+            'import/resolver': { 'eslint-import-resolver-typescript': true },
+          },
+        }),
+        semver.satisfies(tsEslintVersion, '>= 22') ? test({
+          code: `import foobar from "./typescript-export-assign-mixed"`,
+          parser,
+          settings: {
+            'import/parsers': { [parser]: ['.ts'] },
+            'import/resolver': { 'eslint-import-resolver-typescript': true },
+          },
+        }) : [],
+        test({
+          code: `import foobar from "./typescript-export-assign-default-reexport"`,
+          parser,
+          settings: {
+            'import/parsers': { [parser]: ['.ts'] },
+            'import/resolver': { 'eslint-import-resolver-typescript': true },
+          },
+        }),
+        test({
+          code: `import React from "./typescript-export-assign-default-namespace"`,
+          parser,
+          settings: {
+            'import/parsers': { [parser]: ['.ts'] },
+            'import/resolver': { 'eslint-import-resolver-typescript': true },
+          },
+          parserOptions: {
+            tsconfigRootDir: path.resolve(__dirname, '../../files/typescript-export-assign-default-namespace/'),
+          },
+        }),
+        test({
+          code: `import Foo from "./typescript-export-as-default-namespace"`,
+          parser,
+          settings: {
+            'import/parsers': { [parser]: ['.ts'] },
+            'import/resolver': { 'eslint-import-resolver-typescript': true },
+          },
+          parserOptions: {
+            tsconfigRootDir: path.resolve(__dirname, '../../files/typescript-export-as-default-namespace/'),
+          },
+        }),
+        test({
+          code: `import Foo from "./typescript-export-react-test-renderer"`,
+          parser,
+          settings: {
+            'import/parsers': { [parser]: ['.ts'] },
+            'import/resolver': { 'eslint-import-resolver-typescript': true },
+          },
+          parserOptions: {
+            tsconfigRootDir: path.resolve(__dirname, '../../files/typescript-export-react-test-renderer/'),
+          },
+        }),
+        test({
+          code: `import Foo from "./typescript-extended-config"`,
+          parser,
+          settings: {
+            'import/parsers': { [parser]: ['.ts'] },
+            'import/resolver': { 'eslint-import-resolver-typescript': true },
+          },
+          parserOptions: {
+            tsconfigRootDir: path.resolve(__dirname, '../../files/typescript-extended-config/'),
+          },
+        }),
+        test({
+          code: `import foobar from "./typescript-export-assign-property"`,
+          parser,
+          settings: {
+            'import/parsers': { [parser]: ['.ts'] },
+            'import/resolver': { 'eslint-import-resolver-typescript': true },
+          },
+        }),
+      ),
+
+      invalid: [
+        test({
+          code: `import foobar from "./typescript"`,
+          parser,
+          settings: {
+            'import/parsers': { [parser]: ['.ts'] },
+            'import/resolver': { 'eslint-import-resolver-typescript': true },
+          },
+          errors: ['No default export found in imported module "./typescript".'],
+        }),
+        test({
+          code: `import React from "./typescript-export-assign-default-namespace"`,
+          parser,
+          settings: {
+            'import/parsers': { [parser]: ['.ts'] },
+            'import/resolver': { 'eslint-import-resolver-typescript': true },
+          },
+          errors: ['No default export found in imported module "./typescript-export-assign-default-namespace".'],
+        }),
+        test({
+          code: `import FooBar from "./typescript-export-as-default-namespace"`,
+          parser,
+          settings: {
+            'import/parsers': { [parser]: ['.ts'] },
+            'import/resolver': { 'eslint-import-resolver-typescript': true },
+          },
+          errors: ['No default export found in imported module "./typescript-export-as-default-namespace".'],
+        }),
+        test({
+          code: `import Foo from "./typescript-export-as-default-namespace"`,
+          parser,
+          settings: {
+            'import/parsers': { [parser]: ['.ts'] },
+            'import/resolver': { 'eslint-import-resolver-typescript': true },
+          },
+          parserOptions: {
+            tsconfigRootDir: path.resolve(__dirname, '../../files/typescript-no-compiler-options/'),
+          },
+          errors: [
+            {
+              message: 'No default export found in imported module "./typescript-export-as-default-namespace".',
+              line: 1,
+              column: 8,
+              endLine: 1,
+              endColumn: 11,
+            },
+          ],
+        }),
+      ],
+    });
+  });
+});
diff --git a/tests/src/rules/dynamic-import-chunkname.js b/tests/src/rules/dynamic-import-chunkname.js
index d19667c16..e8f97475d 100644
--- a/tests/src/rules/dynamic-import-chunkname.js
+++ b/tests/src/rules/dynamic-import-chunkname.js
@@ -1,27 +1,34 @@
-import { SYNTAX_CASES } from '../utils'
-import { RuleTester } from 'eslint'
+import { SYNTAX_CASES, getTSParsers, parsers } from '../utils';
+import { RuleTester, withoutAutofixOutput } from '../rule-tester';
+import semver from 'semver';
 
-const rule = require('rules/dynamic-import-chunkname')
-const ruleTester = new RuleTester()
+const rule = require('rules/dynamic-import-chunkname');
+const ruleTester = new RuleTester();
 
-const commentFormat = '[0-9a-zA-Z-_/.]+'
-const pickyCommentFormat = '[a-zA-Z-_/.]+'
-const options = [{ importFunctions: ['dynamicImport'] }]
+const commentFormat = '([0-9a-zA-Z-_/.]|\\[(request|index)\\])+';
+const pickyCommentFormat = '[a-zA-Z-_/.]+';
+const options = [{ importFunctions: ['dynamicImport'] }];
 const pickyCommentOptions = [{
   importFunctions: ['dynamicImport'],
   webpackChunknameFormat: pickyCommentFormat,
-}]
+}];
+const allowEmptyOptions = [{
+  importFunctions: ['dynamicImport'],
+  allowEmpty: true,
+}];
 const multipleImportFunctionOptions = [{
   importFunctions: ['dynamicImport', 'definitelyNotStaticImport'],
-}]
-const parser = require.resolve('babel-eslint')
+}];
+const parser = parsers.BABEL_OLD;
 
-const noLeadingCommentError = 'dynamic imports require a leading comment with the webpack chunkname'
-const nonBlockCommentError = 'dynamic imports require a /* foo */ style comment, not a // foo comment'
-const noPaddingCommentError = 'dynamic imports require a block comment padded with spaces - /* foo */'
-const invalidSyntaxCommentError = 'dynamic imports require a "webpack" comment with valid syntax'
-const commentFormatError = `dynamic imports require a leading comment in the form /* webpackChunkName: "${commentFormat}",? */`
-const pickyCommentFormatError = `dynamic imports require a leading comment in the form /* webpackChunkName: "${pickyCommentFormat}",? */`
+const noLeadingCommentError = 'dynamic imports require a leading comment with the webpack chunkname';
+const nonBlockCommentError = 'dynamic imports require a /* foo */ style comment, not a // foo comment';
+const noPaddingCommentError = 'dynamic imports require a block comment padded with spaces - /* foo */';
+const invalidSyntaxCommentError = 'dynamic imports require a "webpack" comment with valid syntax';
+const commentFormatError = `dynamic imports require a "webpack" comment with valid syntax`;
+const chunkNameFormatError = `dynamic imports require a leading comment in the form /*webpackChunkName: ["']${commentFormat}["'],? */`;
+const pickyChunkNameFormatError = `dynamic imports require a leading comment in the form /*webpackChunkName: ["']${pickyCommentFormat}["'],? */`;
+const eagerModeError = `dynamic imports using eager mode do not need a webpackChunkName`;
 
 ruleTester.run('dynamic-import-chunkname', rule, {
   valid: [
@@ -52,10 +59,47 @@ ruleTester.run('dynamic-import-chunkname', rule, {
         'someModule'
       )`,
       options: pickyCommentOptions,
-      errors: [{
-        message: pickyCommentFormatError,
-        type: 'CallExpression',
-      }],
+    },
+    {
+      code: `dynamicImport(
+        /* webpackChunkName: "[request]" */
+        'someModule'
+      )`,
+      options,
+    },
+    {
+      code: `dynamicImport(
+        /* webpackChunkName: "my-chunk-[request]-custom" */
+        'someModule'
+      )`,
+      options,
+    },
+    {
+      code: `dynamicImport(
+        /* webpackChunkName: '[index]' */
+        'someModule'
+      )`,
+      options,
+    },
+    {
+      code: `dynamicImport(
+        /* webpackChunkName: 'my-chunk.[index].with-index' */
+        'someModule'
+      )`,
+      options,
+    },
+    {
+      code: `import('test')`,
+      options: allowEmptyOptions,
+      parser,
+    },
+    {
+      code: `import(
+        /* webpackMode: "lazy" */
+        'test'
+      )`,
+      options: allowEmptyOptions,
+      parser,
     },
     {
       code: `import(
@@ -131,6 +175,32 @@ ruleTester.run('dynamic-import-chunkname', rule, {
       options,
       parser,
     },
+    {
+      code: `import(
+        /* webpackChunkName: "someModule" */
+        /* webpackPrefetch: 12 */
+        'test'
+      )`,
+      options,
+      parser,
+    },
+    {
+      code: `import(
+        /* webpackChunkName: "someModule" */
+        /* webpackPrefetch: -30 */
+        'test'
+      )`,
+      options,
+      parser,
+    },
+    {
+      code: `import(
+        /* webpackChunkName: 'someModule' */
+        'someModule'
+      )`,
+      options,
+      parser,
+    },
     {
       code: `import(
         /* webpackChunkName: "someModule" */
@@ -138,16 +208,222 @@ ruleTester.run('dynamic-import-chunkname', rule, {
       )`,
       options: pickyCommentOptions,
       parser,
-      errors: [{
-        message: pickyCommentFormatError,
-        type: 'CallExpression',
-      }],
+    },
+    {
+      code: `import(
+        /* webpackChunkName: "[request]" */
+        'someModule'
+      )`,
+      options,
+      parser,
+    },
+    {
+      code: `import(
+        /* webpackChunkName: "my-chunk-[request]-custom" */
+        'someModule'
+      )`,
+      options,
+      parser,
+    },
+    {
+      code: `import(
+        /* webpackChunkName: '[index]' */
+        'someModule'
+      )`,
+      options,
+      parser,
+    },
+    {
+      code: `import(
+        /* webpackChunkName: 'my-chunk.[index].with-index' */
+        'someModule'
+      )`,
+      options,
+      parser,
+    },
+    {
+      code: `import(
+        /* webpackChunkName: "someModule" */
+        /* webpackInclude: /\\.json$/ */
+        'someModule'
+      )`,
+      options,
+      parser,
+    },
+    {
+      code: `import(
+        /* webpackChunkName: "someModule", webpackInclude: /\\.json$/ */
+        'someModule'
+      )`,
+      options,
+      parser,
+    },
+    {
+      code: `import(
+        /* webpackChunkName: "someModule" */
+        /* webpackExclude: /\\.json$/ */
+        'someModule'
+      )`,
+      options,
+      parser,
+    },
+    {
+      code: `import(
+        /* webpackChunkName: "someModule", webpackExclude: /\\.json$/ */
+        'someModule'
+      )`,
+      options,
+      parser,
+    },
+    {
+      code: `import(
+        /* webpackChunkName: "someModule" */
+        /* webpackPreload: true */
+        'someModule'
+      )`,
+      options,
+      parser,
+    },
+    {
+      code: `import(
+        /* webpackChunkName: "someModule" */
+        /* webpackPreload: 0 */
+        'someModule'
+      )`,
+      options,
+      parser,
+    },
+    {
+      code: `import(
+        /* webpackChunkName: "someModule" */
+        /* webpackPreload: -2 */
+        'someModule'
+      )`,
+      options,
+      parser,
+    },
+    {
+      code: `import(
+        /* webpackChunkName: "someModule", webpackPreload: false */
+        'someModule'
+      )`,
+      options,
+      parser,
+    },
+    {
+      code: `import(
+        /* webpackChunkName: "someModule" */
+        /* webpackIgnore: false */
+        'someModule'
+      )`,
+      options,
+      parser,
+    },
+    {
+      code: `import(
+        /* webpackChunkName: "someModule", webpackIgnore: true */
+        'someModule'
+      )`,
+      options,
+      parser,
+    },
+    {
+      code: `import(
+        /* webpackChunkName: "someModule" */
+        /* webpackMode: "lazy" */
+        'someModule'
+      )`,
+      options,
+      parser,
+    },
+    {
+      code: `import(
+        /* webpackChunkName: 'someModule', webpackMode: 'lazy' */
+        'someModule'
+      )`,
+      options,
+      parser,
+    },
+    {
+      code: `import(
+        /* webpackChunkName: "someModule" */
+        /* webpackMode: "lazy-once" */
+        'someModule'
+      )`,
+      options,
+      parser,
+    },
+    {
+      code: `import(
+        /* webpackMode: "eager" */
+        'someModule'
+      )`,
+      options,
+      parser,
+    },
+    {
+      code: `import(
+        /* webpackChunkName: "someModule" */
+        /* webpackMode: "weak" */
+        'someModule'
+      )`,
+      options,
+      parser,
+    },
+    {
+      code: `import(
+        /* webpackChunkName: "someModule" */
+        /* webpackExports: "default" */
+        'someModule'
+      )`,
+      options,
+      parser,
+    },
+    {
+      code: `import(
+        /* webpackChunkName: "someModule", webpackExports: "named" */
+        'someModule'
+      )`,
+      options,
+      parser,
+    },
+    {
+      code: `import(
+        /* webpackChunkName: "someModule" */
+        /* webpackExports: ["default", "named"] */
+        'someModule'
+      )`,
+      options,
+      parser,
+    },
+    {
+      code: `import(
+        /* webpackChunkName: 'someModule', webpackExports: ['default', 'named'] */
+        'someModule'
+      )`,
+      options,
+      parser,
+    },
+    {
+      code: `import(
+        /* webpackChunkName: "someModule" */
+        /* webpackInclude: /\\.json$/ */
+        /* webpackExclude: /\\.json$/ */
+        /* webpackPrefetch: true */
+        /* webpackPreload: true */
+        /* webpackIgnore: false */
+        /* webpackMode: "lazy" */
+        /* webpackExports: ["default", "named"] */
+        'someModule'
+      )`,
+      options,
+      parser,
     },
     ...SYNTAX_CASES,
   ],
 
   invalid: [
-    {
+    withoutAutofixOutput({
       code: `import(
         // webpackChunkName: "someModule"
         'someModule'
@@ -158,8 +434,8 @@ ruleTester.run('dynamic-import-chunkname', rule, {
         message: nonBlockCommentError,
         type: 'CallExpression',
       }],
-    },
-    {
+    }),
+    withoutAutofixOutput({
       code: 'import(\'test\')',
       options,
       parser,
@@ -167,8 +443,8 @@ ruleTester.run('dynamic-import-chunkname', rule, {
         message: noLeadingCommentError,
         type: 'CallExpression',
       }],
-    },
-    {
+    }),
+    withoutAutofixOutput({
       code: `import(
         /* webpackChunkName: someModule */
         'someModule'
@@ -179,20 +455,32 @@ ruleTester.run('dynamic-import-chunkname', rule, {
         message: invalidSyntaxCommentError,
         type: 'CallExpression',
       }],
-    },
-    {
+    }),
+    withoutAutofixOutput({
       code: `import(
-        /* webpackChunkName: 'someModule' */
+        /* webpackChunkName: "someModule' */
         'someModule'
       )`,
       options,
       parser,
       errors: [{
-        message: commentFormatError,
+        message: invalidSyntaxCommentError,
         type: 'CallExpression',
       }],
-    },
-    {
+    }),
+    withoutAutofixOutput({
+      code: `import(
+        /* webpackChunkName: 'someModule" */
+        'someModule'
+      )`,
+      options,
+      parser,
+      errors: [{
+        message: invalidSyntaxCommentError,
+        type: 'CallExpression',
+      }],
+    }),
+    withoutAutofixOutput({
       code: `import(
         /* webpackChunkName "someModule" */
         'someModule'
@@ -203,8 +491,8 @@ ruleTester.run('dynamic-import-chunkname', rule, {
         message: invalidSyntaxCommentError,
         type: 'CallExpression',
       }],
-    },
-    {
+    }),
+    withoutAutofixOutput({
       code: `import(
         /* webpackChunkName:"someModule" */
         'someModule'
@@ -215,8 +503,44 @@ ruleTester.run('dynamic-import-chunkname', rule, {
         message: commentFormatError,
         type: 'CallExpression',
       }],
-    },
-    {
+    }),
+    withoutAutofixOutput({
+      code: `import(
+        /* webpackChunkName: true */
+        'someModule'
+      )`,
+      options,
+      parser,
+      errors: [{
+        message: chunkNameFormatError,
+        type: 'CallExpression',
+      }],
+    }),
+    withoutAutofixOutput({
+      code: `import(
+        /* webpackChunkName: "my-module-[id]" */
+        'someModule'
+      )`,
+      options,
+      parser,
+      errors: [{
+        message: chunkNameFormatError,
+        type: 'CallExpression',
+      }],
+    }),
+    withoutAutofixOutput({
+      code: `import(
+        /* webpackChunkName: ["request"] */
+        'someModule'
+      )`,
+      options,
+      parser,
+      errors: [{
+        message: chunkNameFormatError,
+        type: 'CallExpression',
+      }],
+    }),
+    withoutAutofixOutput({
       code: `import(
         /*webpackChunkName: "someModule"*/
         'someModule'
@@ -227,8 +551,8 @@ ruleTester.run('dynamic-import-chunkname', rule, {
         message: noPaddingCommentError,
         type: 'CallExpression',
       }],
-    },
-    {
+    }),
+    withoutAutofixOutput({
       code: `import(
         /* webpackChunkName  :  "someModule" */
         'someModule'
@@ -239,8 +563,8 @@ ruleTester.run('dynamic-import-chunkname', rule, {
         message: commentFormatError,
         type: 'CallExpression',
       }],
-    },
-    {
+    }),
+    withoutAutofixOutput({
       code: `import(
         /* webpackChunkName: "someModule" ; */
         'someModule'
@@ -251,8 +575,8 @@ ruleTester.run('dynamic-import-chunkname', rule, {
         message: invalidSyntaxCommentError,
         type: 'CallExpression',
       }],
-    },
-    {
+    }),
+    withoutAutofixOutput({
       code: `import(
         /* totally not webpackChunkName: "someModule" */
         'someModule'
@@ -263,8 +587,8 @@ ruleTester.run('dynamic-import-chunkname', rule, {
         message: invalidSyntaxCommentError,
         type: 'CallExpression',
       }],
-    },
-    {
+    }),
+    withoutAutofixOutput({
       code: `import(
         /* webpackPrefetch: true */
         /* webpackChunk: "someModule" */
@@ -276,8 +600,8 @@ ruleTester.run('dynamic-import-chunkname', rule, {
         message: commentFormatError,
         type: 'CallExpression',
       }],
-    },
-    {
+    }),
+    withoutAutofixOutput({
       code: `import(
         /* webpackPrefetch: true, webpackChunk: "someModule" */
         'someModule'
@@ -288,8 +612,8 @@ ruleTester.run('dynamic-import-chunkname', rule, {
         message: commentFormatError,
         type: 'CallExpression',
       }],
-    },
-    {
+    }),
+    withoutAutofixOutput({
       code: `import(
         /* webpackChunkName: "someModule123" */
         'someModule'
@@ -297,11 +621,143 @@ ruleTester.run('dynamic-import-chunkname', rule, {
       options: pickyCommentOptions,
       parser,
       errors: [{
-        message: pickyCommentFormatError,
+        message: pickyChunkNameFormatError,
         type: 'CallExpression',
       }],
-    },
-    {
+    }),
+    withoutAutofixOutput({
+      code: `import(
+        /* webpackPrefetch: "module", webpackChunkName: "someModule" */
+        'someModule'
+      )`,
+      options,
+      parser,
+      errors: [{
+        message: commentFormatError,
+        type: 'CallExpression',
+      }],
+    }),
+    withoutAutofixOutput({
+      code: `import(
+        /* webpackPreload: "module", webpackChunkName: "someModule" */
+        'someModule'
+      )`,
+      options,
+      parser,
+      errors: [{
+        message: commentFormatError,
+        type: 'CallExpression',
+      }],
+    }),
+    withoutAutofixOutput({
+      code: `import(
+        /* webpackIgnore: "no", webpackChunkName: "someModule" */
+        'someModule'
+      )`,
+      options,
+      parser,
+      errors: [{
+        message: commentFormatError,
+        type: 'CallExpression',
+      }],
+    }),
+    withoutAutofixOutput({
+      code: `import(
+        /* webpackInclude: "someModule", webpackChunkName: "someModule" */
+        'someModule'
+      )`,
+      options,
+      parser,
+      errors: [{
+        message: commentFormatError,
+        type: 'CallExpression',
+      }],
+    }),
+    withoutAutofixOutput({
+      code: `import(
+        /* webpackInclude: true, webpackChunkName: "someModule" */
+        'someModule'
+      )`,
+      options,
+      parser,
+      errors: [{
+        message: commentFormatError,
+        type: 'CallExpression',
+      }],
+    }),
+    withoutAutofixOutput({
+      code: `import(
+        /* webpackExclude: "someModule", webpackChunkName: "someModule" */
+        'someModule'
+      )`,
+      options,
+      parser,
+      errors: [{
+        message: commentFormatError,
+        type: 'CallExpression',
+      }],
+    }),
+    withoutAutofixOutput({
+      code: `import(
+        /* webpackExclude: true, webpackChunkName: "someModule" */
+        'someModule'
+      )`,
+      options,
+      parser,
+      errors: [{
+        message: commentFormatError,
+        type: 'CallExpression',
+      }],
+    }),
+    withoutAutofixOutput({
+      code: `import(
+        /* webpackMode: "fast", webpackChunkName: "someModule" */
+        'someModule'
+      )`,
+      options,
+      parser,
+      errors: [{
+        message: commentFormatError,
+        type: 'CallExpression',
+      }],
+    }),
+    withoutAutofixOutput({
+      code: `import(
+        /* webpackMode: true, webpackChunkName: "someModule" */
+        'someModule'
+      )`,
+      options,
+      parser,
+      errors: [{
+        message: commentFormatError,
+        type: 'CallExpression',
+      }],
+    }),
+    withoutAutofixOutput({
+      code: `import(
+        /* webpackExports: true, webpackChunkName: "someModule" */
+        'someModule'
+      )`,
+      options,
+      parser,
+      errors: [{
+        message: commentFormatError,
+        type: 'CallExpression',
+      }],
+    }),
+    withoutAutofixOutput({
+      code: `import(
+        /* webpackExports: /default/, webpackChunkName: "someModule" */
+        'someModule'
+      )`,
+      options,
+      parser,
+      errors: [{
+        message: commentFormatError,
+        type: 'CallExpression',
+      }],
+    }),
+    withoutAutofixOutput({
       code: `dynamicImport(
         /* webpackChunkName "someModule" */
         'someModule'
@@ -311,8 +767,8 @@ ruleTester.run('dynamic-import-chunkname', rule, {
         message: invalidSyntaxCommentError,
         type: 'CallExpression',
       }],
-    },
-    {
+    }),
+    withoutAutofixOutput({
       code: `definitelyNotStaticImport(
         /* webpackChunkName "someModule" */
         'someModule'
@@ -322,8 +778,8 @@ ruleTester.run('dynamic-import-chunkname', rule, {
         message: invalidSyntaxCommentError,
         type: 'CallExpression',
       }],
-    },
-    {
+    }),
+    withoutAutofixOutput({
       code: `dynamicImport(
         // webpackChunkName: "someModule"
         'someModule'
@@ -333,16 +789,16 @@ ruleTester.run('dynamic-import-chunkname', rule, {
         message: nonBlockCommentError,
         type: 'CallExpression',
       }],
-    },
-    {
+    }),
+    withoutAutofixOutput({
       code: 'dynamicImport(\'test\')',
       options,
       errors: [{
         message: noLeadingCommentError,
         type: 'CallExpression',
       }],
-    },
-    {
+    }),
+    withoutAutofixOutput({
       code: `dynamicImport(
         /* webpackChunkName: someModule */
         'someModule'
@@ -352,19 +808,8 @@ ruleTester.run('dynamic-import-chunkname', rule, {
         message: invalidSyntaxCommentError,
         type: 'CallExpression',
       }],
-    },
-    {
-      code: `dynamicImport(
-        /* webpackChunkName: 'someModule' */
-        'someModule'
-      )`,
-      options,
-      errors: [{
-        message: commentFormatError,
-        type: 'CallExpression',
-      }],
-    },
-    {
+    }),
+    withoutAutofixOutput({
       code: `dynamicImport(
         /* webpackChunkName "someModule" */
         'someModule'
@@ -374,8 +819,8 @@ ruleTester.run('dynamic-import-chunkname', rule, {
         message: invalidSyntaxCommentError,
         type: 'CallExpression',
       }],
-    },
-    {
+    }),
+    withoutAutofixOutput({
       code: `dynamicImport(
         /* webpackChunkName:"someModule" */
         'someModule'
@@ -385,17 +830,843 @@ ruleTester.run('dynamic-import-chunkname', rule, {
         message: commentFormatError,
         type: 'CallExpression',
       }],
-    },
-    {
+    }),
+    withoutAutofixOutput({
       code: `dynamicImport(
         /* webpackChunkName: "someModule123" */
         'someModule'
       )`,
       options: pickyCommentOptions,
       errors: [{
-        message: pickyCommentFormatError,
+        message: pickyChunkNameFormatError,
         type: 'CallExpression',
       }],
-    },
+    }),
+    withoutAutofixOutput({
+      code: `import(
+        /* webpackChunkName: "someModule" */
+        /* webpackMode: "eager" */
+        'someModule'
+      )`,
+      options,
+      parser,
+      errors: [{
+        message: eagerModeError,
+        type: 'CallExpression',
+        suggestions: [
+          {
+            desc: 'Remove webpackChunkName',
+            output: `import(
+        ${''}
+        /* webpackMode: "eager" */
+        'someModule'
+      )`,
+          },
+          {
+            desc: 'Remove webpackMode',
+            output: `import(
+        /* webpackChunkName: "someModule" */
+        ${''}
+        'someModule'
+      )`,
+          },
+        ],
+      }],
+    }),
   ],
-})
+});
+
+context('TypeScript', () => {
+  getTSParsers().forEach((typescriptParser) => {
+    const nodeType = typescriptParser === parsers.TS_OLD || typescriptParser === parsers.TS_NEW && semver.satisfies(require('@typescript-eslint/parser/package.json').version, '^2')
+      ? 'CallExpression'
+      : 'ImportExpression';
+
+    ruleTester.run('dynamic-import-chunkname', rule, {
+      valid: [
+        {
+          code: `import('test')`,
+          options: allowEmptyOptions,
+          parser: typescriptParser,
+        },
+        {
+          code: `import(
+            /* webpackMode: "lazy" */
+            'test'
+          )`,
+          options: allowEmptyOptions,
+          parser: typescriptParser,
+        },
+        {
+          code: `import(
+            /* webpackChunkName: "someModule" */
+            'test'
+          )`,
+          options,
+          parser: typescriptParser,
+        },
+        {
+          code: `import(
+            /* webpackChunkName: "Some_Other_Module" */
+            "test"
+          )`,
+          options,
+          parser: typescriptParser,
+        },
+        {
+          code: `import(
+            /* webpackChunkName: "SomeModule123" */
+            "test"
+          )`,
+          options,
+          parser: typescriptParser,
+        },
+        {
+          code: `import(
+            /* webpackChunkName: "someModule", webpackPrefetch: true */
+            'test'
+          )`,
+          options,
+          parser: typescriptParser,
+        },
+        {
+          code: `import(
+            /* webpackChunkName: "someModule", webpackPrefetch: true, */
+            'test'
+          )`,
+          options,
+          parser: typescriptParser,
+        },
+        {
+          code: `import(
+            /* webpackPrefetch: true, webpackChunkName: "someModule" */
+            'test'
+          )`,
+          options,
+          parser: typescriptParser,
+        },
+        {
+          code: `import(
+            /* webpackPrefetch: true, webpackChunkName: "someModule", */
+            'test'
+          )`,
+          options,
+          parser: typescriptParser,
+        },
+        {
+          code: `import(
+            /* webpackPrefetch: true */
+            /* webpackChunkName: "someModule" */
+            'test'
+          )`,
+          options,
+          parser: typescriptParser,
+        },
+        {
+          code: `import(
+            /* webpackChunkName: "someModule" */
+            /* webpackPrefetch: true */
+            'test'
+          )`,
+          options,
+          parser: typescriptParser,
+        },
+        {
+          code: `import(
+            /* webpackChunkName: "someModule" */
+            /* webpackPrefetch: 11 */
+            'test'
+          )`,
+          options,
+          parser: typescriptParser,
+        },
+        {
+          code: `import(
+            /* webpackChunkName: "someModule" */
+            /* webpackPrefetch: -11 */
+            'test'
+          )`,
+          options,
+          parser: typescriptParser,
+        },
+        {
+          code: `import(
+            /* webpackChunkName: "someModule" */
+            'someModule'
+          )`,
+          options: pickyCommentOptions,
+          parser: typescriptParser,
+        },
+        {
+          code: `import(
+            /* webpackChunkName: 'someModule' */
+            'test'
+          )`,
+          options,
+          parser: typescriptParser,
+        },
+        {
+          code: `import(
+            /* webpackChunkName: "[request]" */
+            'someModule'
+          )`,
+          options,
+          parser: typescriptParser,
+        },
+        {
+          code: `import(
+            /* webpackChunkName: "my-chunk-[request]-custom" */
+            'someModule'
+          )`,
+          options,
+          parser: typescriptParser,
+        },
+        {
+          code: `import(
+            /* webpackChunkName: '[index]' */
+            'someModule'
+          )`,
+          options,
+          parser: typescriptParser,
+        },
+        {
+          code: `import(
+            /* webpackChunkName: 'my-chunk.[index].with-index' */
+            'someModule'
+          )`,
+          options,
+          parser: typescriptParser,
+        },
+        {
+          code: `import(
+            /* webpackChunkName: "someModule" */
+            /* webpackInclude: /\\.json$/ */
+            'someModule'
+          )`,
+          options,
+          parser: typescriptParser,
+        },
+        {
+          code: `import(
+            /* webpackChunkName: "someModule", webpackInclude: /\\.json$/ */
+            'someModule'
+          )`,
+          options,
+          parser: typescriptParser,
+        },
+        {
+          code: `import(
+            /* webpackChunkName: "someModule" */
+            /* webpackExclude: /\\.json$/ */
+            'someModule'
+          )`,
+          options,
+          parser: typescriptParser,
+        },
+        {
+          code: `import(
+            /* webpackChunkName: "someModule", webpackExclude: /\\.json$/ */
+            'someModule'
+          )`,
+          options,
+          parser: typescriptParser,
+        },
+        {
+          code: `import(
+            /* webpackChunkName: "someModule" */
+            /* webpackPreload: true */
+            'someModule'
+          )`,
+          options,
+          parser: typescriptParser,
+        },
+        {
+          code: `import(
+            /* webpackChunkName: "someModule", webpackPreload: false */
+            'someModule'
+          )`,
+          options,
+          parser: typescriptParser,
+        },
+        {
+          code: `import(
+            /* webpackChunkName: "someModule" */
+            /* webpackIgnore: false */
+            'someModule'
+          )`,
+          options,
+          parser: typescriptParser,
+        },
+        {
+          code: `import(
+            /* webpackChunkName: "someModule", webpackIgnore: true */
+            'someModule'
+          )`,
+          options,
+          parser: typescriptParser,
+        },
+        {
+          code: `import(
+            /* webpackChunkName: 'someModule', webpackMode: 'lazy' */
+            'someModule'
+          )`,
+          options,
+          parser: typescriptParser,
+        },
+        {
+          code: `import(
+            /* webpackChunkName: "someModule" */
+            /* webpackMode: "lazy-once" */
+            'someModule'
+          )`,
+          options,
+          parser: typescriptParser,
+        },
+        {
+          code: `import(
+            /* webpackChunkName: "someModule" */
+            /* webpackMode: "lazy" */
+            'someModule'
+          )`,
+          options,
+          parser: typescriptParser,
+        },
+        {
+          code: `import(
+            /* webpackChunkName: "someModule" */
+            /* webpackMode: "weak" */
+            'someModule'
+          )`,
+          options,
+          parser: typescriptParser,
+        },
+        {
+          code: `import(
+            /* webpackChunkName: "someModule" */
+            /* webpackExports: "default" */
+            'someModule'
+          )`,
+          options,
+          parser: typescriptParser,
+        },
+        {
+          code: `import(
+            /* webpackChunkName: "someModule", webpackExports: "named" */
+            'someModule'
+          )`,
+          options,
+          parser: typescriptParser,
+        },
+        {
+          code: `import(
+            /* webpackChunkName: "someModule" */
+            /* webpackExports: ["default", "named"] */
+            'someModule'
+          )`,
+          options,
+          parser: typescriptParser,
+        },
+        {
+          code: `import(
+            /* webpackChunkName: 'someModule', webpackExports: ['default', 'named'] */
+            'someModule'
+          )`,
+          options,
+          parser: typescriptParser,
+        },
+        {
+          code: `import(
+            /* webpackChunkName: "someModule" */
+            /* webpackInclude: /\\.json$/ */
+            /* webpackExclude: /\\.json$/ */
+            /* webpackPrefetch: true */
+            /* webpackPreload: true */
+            /* webpackIgnore: false */
+            /* webpackMode: "lazy" */
+            /* webpackExports: ["default", "named"] */
+            'someModule'
+          )`,
+          options,
+          parser: typescriptParser,
+        },
+        {
+          code: `import(
+            /* webpackMode: "eager" */
+            'someModule'
+          )`,
+          options,
+          parser: typescriptParser,
+        },
+      ],
+      invalid: [
+        withoutAutofixOutput({
+          code: `import(
+            // webpackChunkName: "someModule"
+            'someModule'
+          )`,
+          options,
+          parser: typescriptParser,
+          errors: [{
+            message: nonBlockCommentError,
+            type: nodeType,
+          }],
+        }),
+        withoutAutofixOutput({
+          code: 'import(\'test\')',
+          options,
+          parser: typescriptParser,
+          errors: [{
+            message: noLeadingCommentError,
+            type: nodeType,
+          }],
+        }),
+        withoutAutofixOutput({
+          code: `import(
+            /* webpackChunkName: someModule */
+            'someModule'
+          )`,
+          options,
+          parser: typescriptParser,
+          errors: [{
+            message: invalidSyntaxCommentError,
+            type: nodeType,
+          }],
+        }),
+        withoutAutofixOutput({
+          code: `import(
+            /* webpackChunkName "someModule' */
+            'someModule'
+          )`,
+          options,
+          parser: typescriptParser,
+          errors: [{
+            message: invalidSyntaxCommentError,
+            type: nodeType,
+          }],
+        }),
+        withoutAutofixOutput({
+          code: `import(
+            /* webpackChunkName 'someModule" */
+            'someModule'
+          )`,
+          options,
+          parser: typescriptParser,
+          errors: [{
+            message: invalidSyntaxCommentError,
+            type: nodeType,
+          }],
+        }),
+        withoutAutofixOutput({
+          code: `import(
+            /* webpackChunkName "someModule" */
+            'someModule'
+          )`,
+          options,
+          parser: typescriptParser,
+          errors: [{
+            message: invalidSyntaxCommentError,
+            type: nodeType,
+          }],
+        }),
+        withoutAutofixOutput({
+          code: `import(
+            /* webpackChunkName:"someModule" */
+            'someModule'
+          )`,
+          options,
+          parser: typescriptParser,
+          errors: [{
+            message: commentFormatError,
+            type: nodeType,
+          }],
+        }),
+        withoutAutofixOutput({
+          code: `import(
+            /*webpackChunkName: "someModule"*/
+            'someModule'
+          )`,
+          options,
+          parser: typescriptParser,
+          errors: [{
+            message: noPaddingCommentError,
+            type: nodeType,
+          }],
+        }),
+        withoutAutofixOutput({
+          code: `import(
+            /* webpackChunkName  :  "someModule" */
+            'someModule'
+          )`,
+          options,
+          parser: typescriptParser,
+          errors: [{
+            message: commentFormatError,
+            type: nodeType,
+          }],
+        }),
+        withoutAutofixOutput({
+          code: `import(
+            /* webpackChunkName: "someModule" ; */
+            'someModule'
+          )`,
+          options,
+          parser: typescriptParser,
+          errors: [{
+            message: invalidSyntaxCommentError,
+            type: nodeType,
+          }],
+        }),
+        withoutAutofixOutput({
+          code: `import(
+            /* totally not webpackChunkName: "someModule" */
+            'someModule'
+          )`,
+          options,
+          parser: typescriptParser,
+          errors: [{
+            message: invalidSyntaxCommentError,
+            type: nodeType,
+          }],
+        }),
+        withoutAutofixOutput({
+          code: `import(
+            /* webpackPrefetch: true */
+            /* webpackChunk: "someModule" */
+            'someModule'
+          )`,
+          options,
+          parser: typescriptParser,
+          errors: [{
+            message: commentFormatError,
+            type: nodeType,
+          }],
+        }),
+        withoutAutofixOutput({
+          code: `import(
+            /* webpackPrefetch: true, webpackChunk: "someModule" */
+            'someModule'
+          )`,
+          options,
+          parser: typescriptParser,
+          errors: [{
+            message: commentFormatError,
+            type: nodeType,
+          }],
+        }),
+        withoutAutofixOutput({
+          code: `import(
+            /* webpackChunkName: true */
+            'someModule'
+          )`,
+          options,
+          parser: typescriptParser,
+          errors: [{
+            message: chunkNameFormatError,
+            type: nodeType,
+          }],
+        }),
+        withoutAutofixOutput({
+          code: `import(
+            /* webpackChunkName: "my-module-[id]" */
+            'someModule'
+          )`,
+          options,
+          parser: typescriptParser,
+          errors: [{
+            message: chunkNameFormatError,
+            type: nodeType,
+          }],
+        }),
+        withoutAutofixOutput({
+          code: `import(
+            /* webpackChunkName: ["request"] */
+            'someModule'
+          )`,
+          options,
+          parser: typescriptParser,
+          errors: [{
+            message: chunkNameFormatError,
+            type: nodeType,
+          }],
+        }),
+        withoutAutofixOutput({
+          code: `import(
+            /* webpackChunkName: "someModule123" */
+            'someModule'
+          )`,
+          options: pickyCommentOptions,
+          parser: typescriptParser,
+          errors: [{
+            message: pickyChunkNameFormatError,
+            type: nodeType,
+          }],
+        }),
+        withoutAutofixOutput({
+          code: `import(
+            /* webpackPrefetch: "module", webpackChunkName: "someModule" */
+            'someModule'
+          )`,
+          options,
+          parser: typescriptParser,
+          errors: [{
+            message: commentFormatError,
+            type: nodeType,
+          }],
+        }),
+        withoutAutofixOutput({
+          code: `import(
+            /* webpackPreload: "module", webpackChunkName: "someModule" */
+            'someModule'
+          )`,
+          options,
+          parser: typescriptParser,
+          errors: [{
+            message: commentFormatError,
+            type: nodeType,
+          }],
+        }),
+        withoutAutofixOutput({
+          code: `import(
+            /* webpackIgnore: "no", webpackChunkName: "someModule" */
+            'someModule'
+          )`,
+          options,
+          parser: typescriptParser,
+          errors: [{
+            message: commentFormatError,
+            type: nodeType,
+          }],
+        }),
+        withoutAutofixOutput({
+          code: `import(
+            /* webpackInclude: "someModule", webpackChunkName: "someModule" */
+            'someModule'
+          )`,
+          options,
+          parser: typescriptParser,
+          errors: [{
+            message: commentFormatError,
+            type: nodeType,
+          }],
+        }),
+        withoutAutofixOutput({
+          code: `import(
+            /* webpackInclude: true, webpackChunkName: "someModule" */
+            'someModule'
+          )`,
+          options,
+          parser: typescriptParser,
+          errors: [{
+            message: commentFormatError,
+            type: nodeType,
+          }],
+        }),
+        withoutAutofixOutput({
+          code: `import(
+            /* webpackExclude: "someModule", webpackChunkName: "someModule" */
+            'someModule'
+          )`,
+          options,
+          parser: typescriptParser,
+          errors: [{
+            message: commentFormatError,
+            type: nodeType,
+          }],
+        }),
+        withoutAutofixOutput({
+          code: `import(
+            /* webpackExclude: true, webpackChunkName: "someModule" */
+            'someModule'
+          )`,
+          options,
+          parser: typescriptParser,
+          errors: [{
+            message: commentFormatError,
+            type: nodeType,
+          }],
+        }),
+        withoutAutofixOutput({
+          code: `import(
+            /* webpackMode: "fast", webpackChunkName: "someModule" */
+            'someModule'
+          )`,
+          options,
+          parser: typescriptParser,
+          errors: [{
+            message: commentFormatError,
+            type: nodeType,
+          }],
+        }),
+        withoutAutofixOutput({
+          code: `import(
+            /* webpackMode: true, webpackChunkName: "someModule" */
+            'someModule'
+          )`,
+          options,
+          parser: typescriptParser,
+          errors: [{
+            message: commentFormatError,
+            type: nodeType,
+          }],
+        }),
+        withoutAutofixOutput({
+          code: `import(
+            /* webpackExports: true, webpackChunkName: "someModule" */
+            'someModule'
+          )`,
+          options,
+          parser: typescriptParser,
+          errors: [{
+            message: commentFormatError,
+            type: nodeType,
+          }],
+        }),
+        withoutAutofixOutput({
+          code: `import(
+            /* webpackExports: /default/, webpackChunkName: "someModule" */
+            'someModule'
+          )`,
+          options,
+          parser: typescriptParser,
+          errors: [{
+            message: commentFormatError,
+            type: nodeType,
+          }],
+        }),
+        withoutAutofixOutput({
+          code: `import(
+            /* webpackChunkName: "someModule", webpackMode: "eager" */
+            'someModule'
+          )`,
+          options,
+          parser: typescriptParser,
+          errors: [{
+            message: eagerModeError,
+            type: nodeType,
+            suggestions: [
+              {
+                desc: 'Remove webpackChunkName',
+                output: `import(
+            /* webpackMode: "eager" */
+            'someModule'
+          )`,
+              },
+              {
+                desc: 'Remove webpackMode',
+                output: `import(
+            /* webpackChunkName: "someModule" */
+            'someModule'
+          )`,
+              },
+            ],
+          }],
+        }),
+        withoutAutofixOutput({
+          code: `
+            import(
+              /* webpackMode: "eager", webpackChunkName: "someModule" */
+              'someModule'
+            )
+          `,
+          options,
+          parser: typescriptParser,
+          errors: [{
+            message: eagerModeError,
+            type: nodeType,
+            suggestions: [
+              {
+                desc: 'Remove webpackChunkName',
+                output: `
+            import(
+              /* webpackMode: "eager" */
+              'someModule'
+            )
+          `,
+              },
+              {
+                desc: 'Remove webpackMode',
+                output: `
+            import(
+              /* webpackChunkName: "someModule" */
+              'someModule'
+            )
+          `,
+              },
+            ],
+          }],
+        }),
+        withoutAutofixOutput({
+          code: `
+            import(
+              /* webpackMode: "eager", webpackPrefetch: true, webpackChunkName: "someModule" */
+              'someModule'
+            )
+          `,
+          options,
+          parser: typescriptParser,
+          errors: [{
+            message: eagerModeError,
+            type: nodeType,
+            suggestions: [
+              {
+                desc: 'Remove webpackChunkName',
+                output: `
+            import(
+              /* webpackMode: "eager", webpackPrefetch: true */
+              'someModule'
+            )
+          `,
+              },
+              {
+                desc: 'Remove webpackMode',
+                output: `
+            import(
+              /* webpackPrefetch: true, webpackChunkName: "someModule" */
+              'someModule'
+            )
+          `,
+              },
+            ],
+          }],
+        }),
+        withoutAutofixOutput({
+          code: `
+            import(
+              /* webpackChunkName: "someModule" */
+              /* webpackMode: "eager" */
+              'someModule'
+            )
+          `,
+          options,
+          parser: typescriptParser,
+          errors: [{
+            message: eagerModeError,
+            type: nodeType,
+            suggestions: [
+              {
+                desc: 'Remove webpackChunkName',
+                output: `
+            import(
+              ${''}
+              /* webpackMode: "eager" */
+              'someModule'
+            )
+          `,
+              },
+              {
+                desc: 'Remove webpackMode',
+                output: `
+            import(
+              /* webpackChunkName: "someModule" */
+              ${''}
+              'someModule'
+            )
+          `,
+              },
+            ],
+          }],
+        }),
+      ],
+    });
+  });
+});
diff --git a/tests/src/rules/enforce-node-protocol-usage.js b/tests/src/rules/enforce-node-protocol-usage.js
new file mode 100644
index 000000000..f9bbcbad1
--- /dev/null
+++ b/tests/src/rules/enforce-node-protocol-usage.js
@@ -0,0 +1,345 @@
+import { RuleTester } from '../rule-tester';
+import flatMap from 'array.prototype.flatmap';
+import { satisfies } from 'semver';
+
+import { test, testVersion } from '../utils';
+
+const ruleTester = new RuleTester();
+const rule = require('rules/enforce-node-protocol-usage');
+
+const preferUsingProtocol = ['always'];
+const preferNotUsingProtocol = ['never'];
+const useNewerParser = { ecmaVersion: 2021 };
+
+const actualModules = ['fs', 'fs/promises', 'buffer', 'child_process', 'timers/promises'];
+
+const settings = {
+  'import/node-version': '16.0.0', // the node: prefix is only available as of `^14.18 || >= 16`
+};
+
+const invalidTests = [].concat(
+  flatMap(actualModules, (moduleName) => [].concat(
+    {
+      code: `import x from "${moduleName}";`,
+      output: `import x from "node:${moduleName}";`,
+      options: preferUsingProtocol,
+      errors: [
+        { messageId: 'requireNodeProtocol', data: { moduleName } },
+      ],
+    },
+    {
+      code: `export {promises} from "${moduleName}";`,
+      output: `export {promises} from "node:${moduleName}";`,
+      options: preferUsingProtocol,
+      errors: [
+        { messageId: 'requireNodeProtocol', data: { moduleName } },
+      ],
+    },
+    testVersion('>= 7', () => ({
+      code: `
+        async function foo() {
+          const x = await import('${moduleName}');
+        }
+      `,
+      output: `
+        async function foo() {
+          const x = await import('node:${moduleName}');
+        }
+      `,
+      options: preferUsingProtocol,
+      parserOptions: useNewerParser,
+      errors: [
+        { messageId: 'requireNodeProtocol', data: { moduleName } },
+      ],
+    })),
+  )),
+
+  {
+    code: 'import fs from "fs/promises";',
+    output: 'import fs from "node:fs/promises";',
+    options: preferUsingProtocol,
+    errors: [
+      {
+        messageId: 'requireNodeProtocol',
+        data: { moduleName: 'fs/promises' },
+      },
+    ],
+    settings,
+  },
+  {
+    code: 'export {default} from "fs/promises";',
+    output: 'export {default} from "node:fs/promises";',
+    options: preferUsingProtocol,
+    errors: [
+      {
+        messageId: 'requireNodeProtocol',
+        data: { moduleName: 'fs/promises' },
+      },
+    ],
+    settings,
+  },
+  testVersion('>= 7', () => ({
+    code: `
+      async function foo() {
+        const fs = await import('fs/promises');
+      }
+    `,
+    output: `
+      async function foo() {
+        const fs = await import('node:fs/promises');
+      }
+    `,
+    options: preferUsingProtocol,
+    parserOptions: useNewerParser,
+    errors: [
+      {
+        messageId: 'requireNodeProtocol',
+        data: { moduleName: 'fs/promises' },
+      },
+    ],
+    settings,
+  })),
+  {
+    code: 'import {promises} from "fs";',
+    output: 'import {promises} from "node:fs";',
+    options: preferUsingProtocol,
+    errors: [
+      { messageId: 'requireNodeProtocol', data: { moduleName: 'fs' } },
+    ],
+  },
+  {
+    code: 'export {default as promises} from "fs";',
+    output: 'export {default as promises} from "node:fs";',
+    options: preferUsingProtocol,
+    errors: [
+      { messageId: 'requireNodeProtocol', data: { moduleName: 'fs' } },
+    ],
+  },
+  testVersion('>= 7', () => ({
+    code: `
+      async function foo() {
+        const fs = await import("fs/promises");
+      }
+    `,
+    output: `
+      async function foo() {
+        const fs = await import("node:fs/promises");
+      }
+    `,
+    options: preferUsingProtocol,
+    parserOptions: useNewerParser,
+    errors: [
+      {
+        messageId: 'requireNodeProtocol',
+        data: { moduleName: 'fs/promises' },
+      },
+    ],
+    settings,
+  })),
+  {
+    code: 'import "buffer";',
+    output: 'import "node:buffer";',
+    options: preferUsingProtocol,
+    errors: [
+      {
+        messageId: 'requireNodeProtocol',
+        data: { moduleName: 'buffer' },
+      },
+    ],
+  },
+  {
+    code: 'import "child_process";',
+    output: 'import "node:child_process";',
+    options: preferUsingProtocol,
+    errors: [
+      {
+        messageId: 'requireNodeProtocol',
+        data: { moduleName: 'child_process' },
+      },
+    ],
+  },
+  {
+    code: 'import "timers/promises";',
+    output: 'import "node:timers/promises";',
+    options: preferUsingProtocol,
+    errors: [
+      {
+        messageId: 'requireNodeProtocol',
+        data: { moduleName: 'timers/promises' },
+      },
+    ],
+    settings,
+  },
+  {
+    code: 'const {promises} = require("fs")',
+    output: 'const {promises} = require("node:fs")',
+    options: preferUsingProtocol,
+    errors: [
+      { messageId: 'requireNodeProtocol', data: { moduleName: 'fs' } },
+    ],
+  },
+  {
+    code: 'const fs = require("fs/promises")',
+    output: 'const fs = require("node:fs/promises")',
+    options: preferUsingProtocol,
+    errors: [
+      {
+        messageId: 'requireNodeProtocol',
+        data: { moduleName: 'fs/promises' },
+      },
+    ],
+    settings,
+  },
+);
+
+ruleTester.run('enforce-node-protocol-usage', rule, {
+  valid: [].concat(
+    test({
+      code: 'import unicorn from "unicorn";',
+      options: preferUsingProtocol,
+    }),
+    test({
+      code: 'import fs from "./fs";',
+      options: preferUsingProtocol,
+    }),
+    test({
+      code: 'import fs from "unknown-builtin-module";',
+      options: preferUsingProtocol,
+    }),
+    test({
+      code: 'import fs from "node:fs";',
+      options: preferUsingProtocol,
+    }),
+    testVersion('>= 7', () => ({
+      code: `
+        async function foo() {
+          const fs = await import(fs);
+        }
+      `,
+      options: preferUsingProtocol,
+      parserOptions: useNewerParser,
+    })),
+    testVersion('>= 7', () => ({
+      code: `
+        async function foo() {
+          const fs = await import(0);
+        }
+      `,
+      options: preferUsingProtocol,
+      parserOptions: useNewerParser,
+    })),
+    testVersion('>= 7', () => ({
+      code: `
+        async function foo() {
+          const fs = await import(\`fs\`);
+        }
+      `,
+      options: preferUsingProtocol,
+      parserOptions: useNewerParser,
+    })),
+    test({
+      code: 'import "punycode/";',
+      options: preferUsingProtocol,
+    }),
+    test({
+      code: 'const fs = require("node:fs");',
+      options: preferUsingProtocol,
+    }),
+    test({
+      code: 'const fs = require("node:fs/promises");',
+      options: preferUsingProtocol,
+      settings,
+    }),
+    test({
+      code: 'const fs = require(fs);',
+      options: preferUsingProtocol,
+    }),
+    test({
+      code: 'const fs = notRequire("fs");',
+      options: preferUsingProtocol,
+    }),
+    test({
+      code: 'const fs = foo.require("fs");',
+      options: preferUsingProtocol,
+    }),
+    test({
+      code: 'const fs = require.resolve("fs");',
+      options: preferUsingProtocol,
+    }),
+    test({
+      code: 'const fs = require(`fs`);',
+      options: preferUsingProtocol,
+    }),
+    testVersion('>= 7', () => ({
+      code: 'const fs = require?.("fs");',
+      parserOptions: useNewerParser,
+      options: preferUsingProtocol,
+    })),
+    test({
+      code: 'const fs = require("fs", extra);',
+      options: preferUsingProtocol,
+    }),
+    test({
+      code: 'const fs = require();',
+      options: preferUsingProtocol,
+    }),
+    test({
+      code: 'const fs = require(...["fs"]);',
+      options: preferUsingProtocol,
+    }),
+    test({
+      code: 'const fs = require("unicorn");',
+      options: preferUsingProtocol,
+    }),
+    test({
+      code: 'import fs from "fs";',
+      options: preferNotUsingProtocol,
+    }),
+    test({
+      code: 'const fs = require("fs");',
+      options: preferNotUsingProtocol,
+    }),
+    test({
+      code: 'const fs = require("fs/promises");',
+      options: preferNotUsingProtocol,
+      settings,
+    }),
+    test({
+      code: 'import "punycode/";',
+      options: preferNotUsingProtocol,
+    }),
+
+    // should not report if the module requires `node:` protocol
+    test({
+      code: 'const fs = require("node:test");',
+      options: preferNotUsingProtocol,
+      settings,
+    }),
+  ),
+
+  invalid: [].concat(
+    // Prefer using the protocol
+    // in node versions without `node:`, the rule should not report
+    satisfies('^14.18 || >= 16') ? invalidTests.map((testCase) => test({
+      ...testCase,
+      errors: testCase.errors.map(({ messageId, data, ...testCase }) => ({
+        ...testCase,
+        message: rule.meta.messages[messageId].replace(/{{moduleName}}/g, data.moduleName),
+      })),
+    })) : [],
+
+    // Prefer not using the protocol: flip the output and code
+    invalidTests.map((testCase) => test({
+      ...testCase,
+      code: testCase.output,
+      options: preferNotUsingProtocol,
+      output: testCase.code,
+      // eslint-disable-next-line no-unused-vars
+      errors: testCase.errors.map(({ messageId, data, ...testCase }) => ({
+        ...testCase,
+        message: rule.meta.messages.forbidNodeProtocol.replace(/{{moduleName}}/g, data.moduleName),
+      })),
+      settings,
+    })),
+  ),
+});
diff --git a/tests/src/rules/export.js b/tests/src/rules/export.js
index a858250e2..338501511 100644
--- a/tests/src/rules/export.js
+++ b/tests/src/rules/export.js
@@ -1,22 +1,23 @@
-import { test, SYNTAX_CASES, getTSParsers } from '../utils'
+import { test, testFilePath, SYNTAX_CASES, getTSParsers, testVersion } from '../utils';
 
-import { RuleTester } from 'eslint'
+import { RuleTester } from '../rule-tester';
+import eslintPkg from 'eslint/package.json';
+import semver from 'semver';
+import { version as tsEslintVersion } from 'typescript-eslint-parser/package.json';
 
-var ruleTester = new RuleTester()
-  , rule = require('rules/export')
+const ruleTester = new RuleTester();
+const rule = require('rules/export');
 
 ruleTester.run('export', rule, {
-  valid: [
+  valid: [].concat(
     test({ code: 'import "./malformed.js"' }),
 
     // default
     test({ code: 'var foo = "foo"; export default foo;' }),
-    test({ code: 'export var foo = "foo"; export var bar = "bar";'}),
+    test({ code: 'export var foo = "foo"; export var bar = "bar";' }),
     test({ code: 'export var foo = "foo", bar = "bar";' }),
     test({ code: 'export var { foo, bar } = object;' }),
     test({ code: 'export var [ foo, bar ] = array;' }),
-    test({ code: 'export var { foo, bar } = object;' }),
-    test({ code: 'export var [ foo, bar ] = array;' }),
     test({ code: 'let foo; export { foo, foo as bar }' }),
     test({ code: 'let bar; export { bar }; export * from "./export-all"' }),
     test({ code: 'export * from "./export-all"' }),
@@ -25,10 +26,48 @@ ruleTester.run('export', rule, {
     // #328: "export * from" does not export a default
     test({ code: 'export default foo; export * from "./bar"' }),
 
-    ...SYNTAX_CASES,
-  ],
+    SYNTAX_CASES,
+
+    test({
+      code: `
+        import * as A from './named-export-collision/a';
+        import * as B from './named-export-collision/b';
+
+        export { A, B };
+      `,
+    }),
+    testVersion('>= 6', () => ({
+      code: `
+        export * as A from './named-export-collision/a';
+        export * as B from './named-export-collision/b';
+      `,
+      parserOptions: {
+        ecmaVersion: 2020,
+      },
+    })) || [],
+
+    getTSParsers().map((parser) => ({
+      code: `
+        export default function foo(param: string): boolean;
+        export default function foo(param: string, param1: number): boolean;
+        export default function foo(param: string, param1?: number): boolean {
+          return param && param1;
+        }
+      `,
+      parser,
+    })),
+    getTSParsers().map((parser) => ({
+      code: `
+        export default function foo(param: string): boolean;
+        export default function foo(param: string, param1?: number): boolean {
+          return param && param1;
+        }
+      `,
+      parser,
+    })),
+  ),
 
-  invalid: [
+  invalid: [].concat(
     // multiple defaults
     // test({
     //   code: 'export default foo; export default bar',
@@ -63,22 +102,31 @@ ruleTester.run('export', rule, {
     // }),
     test({
       code: 'let foo; export { foo }; export * from "./export-all"',
-      errors: ['Multiple exports of name \'foo\'.',
-               'Multiple exports of name \'foo\'.'],
+      errors: [
+        'Multiple exports of name \'foo\'.',
+        'Multiple exports of name \'foo\'.',
+      ],
     }),
-    // test({ code: 'export * from "./default-export"'
-    //      , errors: [{ message: 'No named exports found in module ' +
-    //                            '\'./default-export\'.'
-    //                 , type: 'Literal' }] }),
+    // test({
+    //   code: 'export * from "./default-export"',
+    //   errors: [
+    //     {
+    //       message: 'No named exports found in module \'./default-export\'.',
+    //       type: 'Literal',
+    //     },
+    //   ],
+    // }),
 
     // note: Espree bump to Acorn 4+ changed this test's error message.
     //       `npm up` first if it's failing.
     test({
       code: 'export * from "./malformed.js"',
-      errors: [{
-        message: "Parse errors in imported module './malformed.js': 'return' outside of function (1:1)",
-        type: 'Literal',
-      }],
+      errors: [
+        {
+          message: "Parse errors in imported module './malformed.js': 'return' outside of function (1:1)",
+          type: 'Literal',
+        },
+      ],
     }),
 
     // test({
@@ -98,68 +146,104 @@ ruleTester.run('export', rule, {
     //   errors: ['Parsing error: Duplicate export \'bar\''],
     // }),
 
-
     // #328: "export * from" does not export a default
     test({
       code: 'export * from "./default-export"',
       errors: [`No named exports found in module './default-export'.`],
     }),
-  ],
-})
 
+    // es2022: Arbitrary module namespace identifier names
+    testVersion('>= 8.7', () => ({
+      code: 'let foo; export { foo as "foo" }; export * from "./export-all"',
+      errors: [
+        'Multiple exports of name \'foo\'.',
+        'Multiple exports of name \'foo\'.',
+      ],
+      parserOptions: {
+        ecmaVersion: 2022,
+      },
+    })),
+
+    getTSParsers().map((parser) => ({
+      code: `
+        export default function a(): void;
+        export default function a() {}
+        export { x as default };
+      `,
+      errors: [
+        'Multiple default exports.',
+        'Multiple default exports.',
+      ],
+      parser,
+    })),
+  ),
+});
 
-context('Typescript', function () {
+context('TypeScript', function () {
   getTSParsers().forEach((parser) => {
     const parserConfig = {
-      parser: parser,
+      parser,
       settings: {
         'import/parsers': { [parser]: ['.ts'] },
         'import/resolver': { 'eslint-import-resolver-typescript': true },
       },
-    }
+    };
 
     ruleTester.run('export', rule, {
-      valid: [
+      valid: [].concat(
         // type/value name clash
-        test(Object.assign({
+        test({
           code: `
             export const Foo = 1;
             export type Foo = number;
           `,
-        }, parserConfig)),
-        test(Object.assign({
+          ...parserConfig,
+        }),
+        test({
           code: `
             export const Foo = 1;
             export interface Foo {}
           `,
-        }, parserConfig)),
+          ...parserConfig,
+        }),
 
-        test(Object.assign({
+        semver.satisfies(tsEslintVersion, '>= 22') ? test({
+          code: `
+            export function fff(a: string);
+            export function fff(a: number);
+          `,
+          ...parserConfig,
+        }) : [],
+
+        semver.satisfies(tsEslintVersion, '>= 22') ? test({
           code: `
             export function fff(a: string);
             export function fff(a: number);
             export function fff(a: string|number) {};
           `,
-        }, parserConfig)),
+          ...parserConfig,
+        }) : [],
 
         // namespace
-        test(Object.assign({
+        test({
           code: `
             export const Bar = 1;
             export namespace Foo {
               export const Bar = 1;
             }
           `,
-        }, parserConfig)),
-        test(Object.assign({
+          ...parserConfig,
+        }),
+        test({
           code: `
             export type Bar = string;
             export namespace Foo {
               export type Bar = string;
             }
           `,
-        }, parserConfig)),
-        test(Object.assign({
+          ...parserConfig,
+        }),
+        test({
           code: `
             export const Bar = 1;
             export type Bar = string;
@@ -168,8 +252,9 @@ context('Typescript', function () {
               export type Bar = string;
             }
           `,
-        }, parserConfig)),
-        test(Object.assign({
+          ...parserConfig,
+        }),
+        test({
           code: `
             export namespace Foo {
               export const Foo = 1;
@@ -181,11 +266,106 @@ context('Typescript', function () {
               }
             }
           `,
-        }, parserConfig)),
-      ],
-      invalid: [
+          ...parserConfig,
+        }),
+        semver.satisfies(eslintPkg.version, '>= 6') ? [
+          test({
+            code: `
+              export class Foo { }
+              export namespace Foo { }
+              export namespace Foo {
+                export class Bar {}
+              }
+            `,
+            ...parserConfig,
+          }),
+          test({
+            code: `
+              export function Foo();
+              export namespace Foo { }
+            `,
+            ...parserConfig,
+          }),
+          test({
+            code: `
+              export function Foo(a: string);
+              export namespace Foo { }
+            `,
+            ...parserConfig,
+          }),
+          test({
+            code: `
+              export function Foo(a: string);
+              export function Foo(a: number);
+              export namespace Foo { }
+            `,
+            ...parserConfig,
+          }),
+          test({
+            code: `
+              export enum Foo { }
+              export namespace Foo { }
+            `,
+            ...parserConfig,
+          }),
+        ] : [],
+        test({
+          code: 'export * from "./file1.ts"',
+          filename: testFilePath('typescript-d-ts/file-2.ts'),
+          ...parserConfig,
+        }),
+
+        semver.satisfies(eslintPkg.version, '>= 6') ? [
+          test({
+            code: `
+              export * as A from './named-export-collision/a';
+              export * as B from './named-export-collision/b';
+            `,
+            parser,
+          }),
+        ] : [],
+
+        // Exports in ambient modules
+        test({
+          code: `
+            declare module "a" {
+              const Foo = 1;
+              export {Foo as default};
+            }
+            declare module "b" {
+              const Bar = 2;
+              export {Bar as default};
+            }
+          `,
+          ...parserConfig,
+        }),
+        test({
+          code: `
+            declare module "a" {
+              const Foo = 1;
+              export {Foo as default};
+            }
+            const Bar = 2;
+            export {Bar as default};
+          `,
+          ...parserConfig,
+        }),
+
+        semver.satisfies(process.version, '< 8') && semver.satisfies(eslintPkg.version, '< 6') ? [] : test({
+          ...parserConfig,
+          code: `
+            export * from './module';
+          `,
+          filename: testFilePath('export-star-4/index.js'),
+          settings: {
+            ...parserConfig.settings,
+            'import/extensions': ['.js', '.ts', '.jsx'],
+          },
+        }),
+      ),
+      invalid: [].concat(
         // type/value name clash
-        test(Object.assign({
+        test({
           code: `
             export type Foo = string;
             export type Foo = number;
@@ -200,10 +380,11 @@ context('Typescript', function () {
               line: 3,
             },
           ],
-        }, parserConfig)),
+          ...parserConfig,
+        }),
 
         // namespace
-        test(Object.assign({
+        test({
           code: `
             export const a = 1
             export namespace Foo {
@@ -221,8 +402,9 @@ context('Typescript', function () {
               line: 5,
             },
           ],
-        }, parserConfig)),
-        test(Object.assign({
+          ...parserConfig,
+        }),
+        test({
           code: `
             declare module 'foo' {
               const Foo = 1;
@@ -240,8 +422,9 @@ context('Typescript', function () {
               line: 5,
             },
           ],
-        }, parserConfig)),
-        test(Object.assign({
+          ...parserConfig,
+        }),
+        test({
           code: `
             export namespace Foo {
               export namespace Bar {
@@ -272,8 +455,161 @@ context('Typescript', function () {
               line: 9,
             },
           ],
-        }, parserConfig)),
-      ],
-    })
-  })
-})
+          ...parserConfig,
+        }),
+        semver.satisfies(eslintPkg.version, '< 6') ? [] : [
+          test({
+            code: `
+              export class Foo { }
+              export class Foo { }
+              export namespace Foo { }
+            `,
+            errors: [
+              {
+                message: `Multiple exports of name 'Foo'.`,
+                line: 2,
+              },
+              {
+                message: `Multiple exports of name 'Foo'.`,
+                line: 3,
+              },
+            ],
+            ...parserConfig,
+          }),
+          test({
+            code: `
+              export enum Foo { }
+              export enum Foo { }
+              export namespace Foo { }
+            `,
+            errors: [
+              {
+                message: `Multiple exports of name 'Foo'.`,
+                line: 2,
+              },
+              {
+                message: `Multiple exports of name 'Foo'.`,
+                line: 3,
+              },
+            ],
+            ...parserConfig,
+          }),
+          test({
+            code: `
+              export enum Foo { }
+              export class Foo { }
+              export namespace Foo { }
+            `,
+            errors: [
+              {
+                message: `Multiple exports of name 'Foo'.`,
+                line: 2,
+              },
+              {
+                message: `Multiple exports of name 'Foo'.`,
+                line: 3,
+              },
+            ],
+            ...parserConfig,
+          }),
+          test({
+            code: `
+              export const Foo = 'bar';
+              export class Foo { }
+              export namespace Foo { }
+            `,
+            errors: [
+              {
+                message: `Multiple exports of name 'Foo'.`,
+                line: 2,
+              },
+              {
+                message: `Multiple exports of name 'Foo'.`,
+                line: 3,
+              },
+            ],
+            ...parserConfig,
+          }),
+          test({
+            code: `
+              export function Foo() { };
+              export class Foo { }
+              export namespace Foo { }
+            `,
+            errors: [
+              {
+                message: `Multiple exports of name 'Foo'.`,
+                line: 2,
+              },
+              {
+                message: `Multiple exports of name 'Foo'.`,
+                line: 3,
+              },
+            ],
+            ...parserConfig,
+          }),
+          test({
+            code: `
+              export const Foo = 'bar';
+              export function Foo() { };
+              export namespace Foo { }
+            `,
+            errors: [
+              {
+                message: `Multiple exports of name 'Foo'.`,
+                line: 2,
+              },
+              {
+                message: `Multiple exports of name 'Foo'.`,
+                line: 3,
+              },
+            ],
+            ...parserConfig,
+          }),
+          test({
+            code: `
+              export const Foo = 'bar';
+              export namespace Foo { }
+            `,
+            errors: [
+              {
+                message: `Multiple exports of name 'Foo'.`,
+                line: 2,
+              },
+              {
+                message: `Multiple exports of name 'Foo'.`,
+                line: 3,
+              },
+            ],
+            ...parserConfig,
+          }),
+        ],
+
+        // Exports in ambient modules
+        test({
+          code: `
+            declare module "a" {
+              const Foo = 1;
+              export {Foo as default};
+            }
+            const Bar = 2;
+            export {Bar as default};
+            const Baz = 3;
+            export {Baz as default};
+          `,
+          errors: [
+            {
+              message: 'Multiple default exports.',
+              line: 7,
+            },
+            {
+              message: 'Multiple default exports.',
+              line: 9,
+            },
+          ],
+          ...parserConfig,
+        }),
+      ),
+    });
+  });
+});
diff --git a/tests/src/rules/exports-last.js b/tests/src/rules/exports-last.js
index 871c62e85..a676ae044 100644
--- a/tests/src/rules/exports-last.js
+++ b/tests/src/rules/exports-last.js
@@ -1,14 +1,13 @@
-import { test } from '../utils'
+import { test } from '../utils';
 
-import { RuleTester } from 'eslint'
-import rule from 'rules/exports-last'
+import { RuleTester } from '../rule-tester';
+import rule from 'rules/exports-last';
 
-const ruleTester = new RuleTester()
+const ruleTester = new RuleTester();
 
-const error = type => ({
-  ruleId: 'exports-last',
+const error = (type) => ({
   message: 'Export statements should appear at the end of the file',
-  type
+  type,
 });
 
 ruleTester.run('exports-last', rule, {
@@ -121,4 +120,4 @@ ruleTester.run('exports-last', rule, {
       ],
     }),
   ],
-})
+});
diff --git a/tests/src/rules/extensions.js b/tests/src/rules/extensions.js
index d7b97bea0..8843713e3 100644
--- a/tests/src/rules/extensions.js
+++ b/tests/src/rules/extensions.js
@@ -1,27 +1,37 @@
-import { RuleTester } from 'eslint'
-import rule from 'rules/extensions'
-import { test, testFilePath } from '../utils'
+import { RuleTester } from '../rule-tester';
+import rule from 'rules/extensions';
+import { getTSParsers, test, testFilePath, parsers } from '../utils';
 
-const ruleTester = new RuleTester()
+const ruleTester = new RuleTester();
+const ruleTesterWithTypeScriptImports = new RuleTester({
+  settings: {
+    'import/resolver': {
+      typescript: {
+        alwaysTryTypes: true,
+      },
+    },
+  },
+});
 
 ruleTester.run('extensions', rule, {
   valid: [
+    test({ code: 'import a from "@/a"' }),
     test({ code: 'import a from "a"' }),
     test({ code: 'import dot from "./file.with.dot"' }),
     test({
       code: 'import a from "a/index.js"',
-      options: [ 'always' ],
+      options: ['always'],
     }),
     test({
       code: 'import dot from "./file.with.dot.js"',
-      options: [ 'always' ],
+      options: ['always'],
     }),
     test({
       code: [
         'import a from "a"',
         'import packageConfig from "./package.json"',
       ].join('\n'),
-      options: [ { json: 'always', js: 'never' } ],
+      options: [{ json: 'always', js: 'never' }],
     }),
     test({
       code: [
@@ -29,8 +39,8 @@ ruleTester.run('extensions', rule, {
         'import component from "./bar.jsx"',
         'import data from "./bar.json"',
       ].join('\n'),
-      options: [ 'never' ],
-      settings: { 'import/resolve': { 'extensions': [ '.js', '.jsx', '.json' ] } },
+      options: ['never'],
+      settings: { 'import/resolve': { extensions: ['.js', '.jsx', '.json'] } },
     }),
 
     test({
@@ -39,8 +49,8 @@ ruleTester.run('extensions', rule, {
         'import barjson from "./bar.json"',
         'import barhbs from "./bar.hbs"',
       ].join('\n'),
-      options: [ 'always', { js: 'never', jsx: 'never' } ],
-      settings: { 'import/resolve': { 'extensions': [ '.js', '.jsx', '.json', '.hbs' ] } },
+      options: ['always', { js: 'never', jsx: 'never' }],
+      settings: { 'import/resolve': { extensions: ['.js', '.jsx', '.json', '.hbs'] } },
     }),
 
     test({
@@ -48,25 +58,25 @@ ruleTester.run('extensions', rule, {
         'import bar from "./bar.js"',
         'import pack from "./package"',
       ].join('\n'),
-      options: [ 'never', { js: 'always', json: 'never' } ],
-      settings: { 'import/resolve': { 'extensions': [ '.js', '.json' ] } },
+      options: ['never', { js: 'always', json: 'never' }],
+      settings: { 'import/resolve': { extensions: ['.js', '.json'] } },
     }),
 
     // unresolved (#271/#295)
     test({ code: 'import path from "path"' }),
-    test({ code: 'import path from "path"', options: [ 'never' ] }),
-    test({ code: 'import path from "path"', options: [ 'always' ] }),
-    test({ code: 'import thing from "./fake-file.js"', options: [ 'always' ] }),
-    test({ code: 'import thing from "non-package"', options: [ 'never' ] }),
+    test({ code: 'import path from "path"', options: ['never'] }),
+    test({ code: 'import path from "path"', options: ['always'] }),
+    test({ code: 'import thing from "./fake-file.js"', options: ['always'] }),
+    test({ code: 'import thing from "non-package"', options: ['never'] }),
 
     test({
       code: `
         import foo from './foo.js'
         import bar from './bar.json'
-        import Component from './Component'
+        import Component from './Component.jsx'
         import express from 'express'
       `,
-      options: [ 'ignorePackages' ],
+      options: ['ignorePackages'],
     }),
 
     test({
@@ -76,7 +86,7 @@ ruleTester.run('extensions', rule, {
         import Component from './Component.jsx'
         import express from 'express'
       `,
-      options: [ 'always', {ignorePackages: true} ],
+      options: ['always', { ignorePackages: true }],
     }),
 
     test({
@@ -86,17 +96,17 @@ ruleTester.run('extensions', rule, {
         import Component from './Component'
         import express from 'express'
       `,
-      options: [ 'never', {ignorePackages: true} ],
+      options: ['never', { ignorePackages: true }],
     }),
 
     test({
       code: 'import exceljs from "exceljs"',
-      options: [ 'always', { js: 'never', jsx: 'never' } ],
+      options: ['always', { js: 'never', jsx: 'never' }],
       filename: testFilePath('./internal-modules/plugins/plugin.js'),
       settings: {
         'import/resolver': {
-          'node': { 'extensions': [ '.js', '.jsx', '.json' ] },
-          'webpack': { 'config': 'webpack.empty.config.js' },
+          node: { extensions: ['.js', '.jsx', '.json'] },
+          webpack: { config: 'webpack.empty.config.js' },
         },
       },
     }),
@@ -107,38 +117,58 @@ ruleTester.run('extensions', rule, {
         'export { foo } from "./foo.js"',
         'let bar; export { bar }',
       ].join('\n'),
-      options: [ 'always' ],
+      options: ['always'],
     }),
     test({
       code: [
         'export { foo } from "./foo"',
         'let bar; export { bar }',
       ].join('\n'),
-      options: [ 'never' ],
+      options: ['never'],
+    }),
+
+    // Root packages should be ignored and they are names not files
+    test({
+      code: [
+        'import lib from "pkg.js"',
+        'import lib2 from "pgk/package"',
+        'import lib3 from "@name/pkg.js"',
+      ].join('\n'),
+      options: ['never'],
+    }),
+
+    // Query strings.
+    test({
+      code: 'import bare from "./foo?a=True.ext"',
+      options: ['never'],
+    }),
+    test({
+      code: 'import bare from "./foo.js?a=True"',
+      options: ['always'],
+    }),
+
+    test({
+      code: [
+        'import lib from "pkg"',
+        'import lib2 from "pgk/package.js"',
+        'import lib3 from "@name/pkg"',
+      ].join('\n'),
+      options: ['always'],
     }),
   ],
 
   invalid: [
     test({
       code: 'import a from "a/index.js"',
-      errors: [ {
+      errors: [{
         message: 'Unexpected use of file extension "js" for "a/index.js"',
         line: 1,
         column: 15,
-      } ],
-    }),
-    test({
-      code: 'import a from "a"',
-      options: [ 'always' ],
-      errors: [ {
-        message: 'Missing file extension "js" for "a"',
-        line: 1,
-        column: 15,
-      } ],
+      }],
     }),
     test({
       code: 'import dot from "./file.with.dot"',
-      options: [ 'always' ],
+      options: ['always'],
       errors: [
         {
           message: 'Missing file extension "js" for "./file.with.dot"',
@@ -152,8 +182,8 @@ ruleTester.run('extensions', rule, {
         'import a from "a/index.js"',
         'import packageConfig from "./package"',
       ].join('\n'),
-      options: [ { json: 'always', js: 'never' } ],
-      settings: { 'import/resolve': { 'extensions': [ '.js', '.json' ] } },
+      options: [{ json: 'always', js: 'never' }],
+      settings: { 'import/resolve': { extensions: ['.js', '.json'] } },
       errors: [
         {
           message: 'Unexpected use of file extension "js" for "a/index.js"',
@@ -173,13 +203,13 @@ ruleTester.run('extensions', rule, {
         'import component from "./bar.jsx"',
         'import data from "./bar.json"',
       ].join('\n'),
-      options: [ 'never' ],
-      settings: { 'import/resolve': { 'extensions': [ '.js', '.jsx', '.json' ] } },
+      options: ['never'],
+      settings: { 'import/resolve': { extensions: ['.js', '.jsx', '.json'] } },
       errors: [
         {
-            message: 'Unexpected use of file extension "js" for "./bar.js"',
-            line: 1,
-            column: 17,
+          message: 'Unexpected use of file extension "js" for "./bar.js"',
+          line: 1,
+          column: 17,
         },
       ],
     }),
@@ -189,13 +219,13 @@ ruleTester.run('extensions', rule, {
         'import component from "./bar.jsx"',
         'import data from "./bar.json"',
       ].join('\n'),
-      options: [ { json: 'always', js: 'never', jsx: 'never' } ],
-      settings: { 'import/resolve': { 'extensions': [ '.js', '.jsx', '.json' ] } },
+      options: [{ json: 'always', js: 'never', jsx: 'never' }],
+      settings: { 'import/resolve': { extensions: ['.js', '.jsx', '.json'] } },
       errors: [
         {
-            message: 'Unexpected use of file extension "js" for "./bar.js"',
-            line: 1,
-            column: 17,
+          message: 'Unexpected use of file extension "js" for "./bar.js"',
+          line: 1,
+          column: 17,
         },
       ],
     }),
@@ -205,13 +235,13 @@ ruleTester.run('extensions', rule, {
         'import component from "./bar.jsx"',
         'import data from "./bar.json"',
       ].join('\n'),
-      options: [ { json: 'always', js: 'never', jsx: 'never' } ],
-      settings: { 'import/resolve': { 'extensions': [ '.jsx', '.json', '.js' ] } },
+      options: [{ json: 'always', js: 'never', jsx: 'never' }],
+      settings: { 'import/resolve': { extensions: ['.jsx', '.json', '.js'] } },
       errors: [
         {
-            message: 'Unexpected use of file extension "jsx" for "./bar.jsx"',
-            line: 1,
-            column: 23,
+          message: 'Unexpected use of file extension "jsx" for "./bar.jsx"',
+          line: 1,
+          column: 23,
         },
       ],
     }),
@@ -225,7 +255,7 @@ ruleTester.run('extensions', rule, {
         },
       ],
       options: ['never', { js: 'always', jsx: 'always' }],
-      settings: { 'import/resolve': { 'extensions': ['.coffee', '.js'] } },
+      settings: { 'import/resolve': { extensions: ['.coffee', '.js'] } },
     }),
 
     test({
@@ -234,13 +264,33 @@ ruleTester.run('extensions', rule, {
         'import barjson from "./bar.json"',
         'import barnone from "./bar"',
       ].join('\n'),
-      options: [ 'always', { json: 'always', js: 'never', jsx: 'never' } ],
-      settings: { 'import/resolve': { 'extensions': [ '.js', '.jsx', '.json' ] } },
+      options: ['always', { json: 'always', js: 'never', jsx: 'never' }],
+      settings: { 'import/resolve': { extensions: ['.js', '.jsx', '.json'] } },
       errors: [
         {
-            message: 'Unexpected use of file extension "js" for "./bar.js"',
-            line: 1,
-            column: 19,
+          message: 'Unexpected use of file extension "js" for "./bar.js"',
+          line: 1,
+          column: 19,
+        },
+      ],
+    }),
+
+    test({
+      code: [
+        'import barjs from "."',
+        'import barjs2 from ".."',
+      ].join('\n'),
+      options: ['always'],
+      errors: [
+        {
+          message: 'Missing file extension "js" for "."',
+          line: 1,
+          column: 19,
+        },
+        {
+          message: 'Missing file extension "js" for ".."',
+          line: 2,
+          column: 20,
         },
       ],
     }),
@@ -251,13 +301,13 @@ ruleTester.run('extensions', rule, {
         'import barjson from "./bar.json"',
         'import barnone from "./bar"',
       ].join('\n'),
-      options: [ 'never', { json: 'always', js: 'never', jsx: 'never' } ],
-      settings: { 'import/resolve': { 'extensions': [ '.js', '.jsx', '.json' ] } },
+      options: ['never', { json: 'always', js: 'never', jsx: 'never' }],
+      settings: { 'import/resolve': { extensions: ['.js', '.jsx', '.json'] } },
       errors: [
         {
-            message: 'Unexpected use of file extension "js" for "./bar.js"',
-            line: 1,
-            column: 19,
+          message: 'Unexpected use of file extension "js" for "./bar.js"',
+          line: 1,
+          column: 19,
         },
       ],
     }),
@@ -265,27 +315,75 @@ ruleTester.run('extensions', rule, {
     // unresolved (#271/#295)
     test({
       code: 'import thing from "./fake-file.js"',
-      options: [ 'never' ],
+      options: ['never'],
       errors: [
         {
-            message: 'Unexpected use of file extension "js" for "./fake-file.js"',
-            line: 1,
-            column: 19,
+          message: 'Unexpected use of file extension "js" for "./fake-file.js"',
+          line: 1,
+          column: 19,
         },
       ],
     }),
     test({
-      code: 'import thing from "non-package"',
-      options: [ 'always' ],
+      code: 'import thing from "non-package/test"',
+      options: ['always'],
+      errors: [
+        {
+          message: 'Missing file extension for "non-package/test"',
+          line: 1,
+          column: 19,
+        },
+      ],
+    }),
+
+    test({
+      code: 'import thing from "@name/pkg/test"',
+      options: ['always'],
+      errors: [
+        {
+          message: 'Missing file extension for "@name/pkg/test"',
+          line: 1,
+          column: 19,
+        },
+      ],
+    }),
+
+    test({
+      code: 'import thing from "@name/pkg/test.js"',
+      options: ['never'],
       errors: [
         {
-            message: 'Missing file extension for "non-package"',
-            line: 1,
-            column: 19,
+          message: 'Unexpected use of file extension "js" for "@name/pkg/test.js"',
+          line: 1,
+          column: 19,
         },
       ],
     }),
 
+    test({
+      code: `
+        import foo from './foo.js'
+        import bar from './bar.json'
+        import Component from './Component'
+        import baz from 'foo/baz'
+        import baw from '@scoped/baw/import'
+        import chart from '@/configs/chart'
+        import express from 'express'
+      `,
+      options: ['always', { ignorePackages: true }],
+      errors: [
+        {
+          message: 'Missing file extension for "./Component"',
+          line: 4,
+          column: 31,
+        },
+        {
+          message: 'Missing file extension for "@/configs/chart"',
+          line: 7,
+          column: 27,
+        },
+      ],
+    }),
 
     test({
       code: `
@@ -293,18 +391,21 @@ ruleTester.run('extensions', rule, {
         import bar from './bar.json'
         import Component from './Component'
         import baz from 'foo/baz'
+        import baw from '@scoped/baw/import'
+        import chart from '@/configs/chart'
         import express from 'express'
       `,
-      options: [ 'always', {ignorePackages: true} ],
+      options: ['ignorePackages'],
       errors: [
         {
           message: 'Missing file extension for "./Component"',
           line: 4,
           column: 31,
-        }, {
-          message: 'Missing file extension for "foo/baz"',
-          line: 5,
-          column: 25,
+        },
+        {
+          message: 'Missing file extension for "@/configs/chart"',
+          line: 7,
+          column: 27,
         },
       ],
     }),
@@ -327,7 +428,23 @@ ruleTester.run('extensions', rule, {
           column: 31,
         },
       ],
-      options: [ 'never', {ignorePackages: true} ],
+      options: ['never', { ignorePackages: true }],
+    }),
+
+    test({
+      code: `
+        import foo from './foo.js'
+        import bar from './bar.json'
+        import Component from './Component.jsx'
+      `,
+      errors: [
+        {
+          message: 'Unexpected use of file extension "jsx" for "./Component.jsx"',
+          line: 4,
+          column: 31,
+        },
+      ],
+      options: ['always', { pattern: { jsx: 'never' } }],
     }),
 
     // export (#964)
@@ -336,7 +453,7 @@ ruleTester.run('extensions', rule, {
         'export { foo } from "./foo"',
         'let bar; export { bar }',
       ].join('\n'),
-      options: [ 'always' ],
+      options: ['always'],
       errors: [
         {
           message: 'Missing file extension for "./foo"',
@@ -350,7 +467,7 @@ ruleTester.run('extensions', rule, {
         'export { foo } from "./foo.js"',
         'let bar; export { bar }',
       ].join('\n'),
-      options: [ 'never' ],
+      options: ['never'],
       errors: [
         {
           message: 'Unexpected use of file extension "js" for "./foo.js"',
@@ -359,5 +476,407 @@ ruleTester.run('extensions', rule, {
         },
       ],
     }),
+
+    // Query strings.
+    test({
+      code: 'import withExtension from "./foo.js?a=True"',
+      options: ['never'],
+      errors: [
+        {
+          message: 'Unexpected use of file extension "js" for "./foo.js?a=True"',
+          line: 1,
+          column: 27,
+        },
+      ],
+    }),
+    test({
+      code: 'import withoutExtension from "./foo?a=True.ext"',
+      options: ['always'],
+      errors: [
+        {
+          message: 'Missing file extension for "./foo?a=True.ext"',
+          line: 1,
+          column: 30,
+        },
+      ],
+    }),
+    // require (#1230)
+    test({
+      code: [
+        'const { foo } = require("./foo")',
+        'export { foo }',
+      ].join('\n'),
+      options: ['always'],
+      errors: [
+        {
+          message: 'Missing file extension for "./foo"',
+          line: 1,
+          column: 25,
+        },
+      ],
+    }),
+    test({
+      code: [
+        'const { foo } = require("./foo.js")',
+        'export { foo }',
+      ].join('\n'),
+      options: ['never'],
+      errors: [
+        {
+          message: 'Unexpected use of file extension "js" for "./foo.js"',
+          line: 1,
+          column: 25,
+        },
+      ],
+    }),
+
+    // export { } from
+    test({
+      code: 'export { foo } from "./foo"',
+      options: ['always'],
+      errors: [
+        {
+          message: 'Missing file extension for "./foo"',
+          line: 1,
+          column: 21,
+        },
+      ],
+    }),
+    test({
+      code: `
+        import foo from "@/ImNotAScopedModule";
+        import chart from '@/configs/chart';
+      `,
+      options: ['always'],
+      errors: [
+        {
+          message: 'Missing file extension for "@/ImNotAScopedModule"',
+          line: 2,
+        },
+        {
+          message: 'Missing file extension for "@/configs/chart"',
+          line: 3,
+        },
+      ],
+    }),
+    test({
+      code: 'export { foo } from "./foo.js"',
+      options: ['never'],
+      errors: [
+        {
+          message: 'Unexpected use of file extension "js" for "./foo.js"',
+          line: 1,
+          column: 21,
+        },
+      ],
+    }),
+
+    // export * from
+    test({
+      code: 'export * from "./foo"',
+      options: ['always'],
+      errors: [
+        {
+          message: 'Missing file extension for "./foo"',
+          line: 1,
+          column: 15,
+        },
+      ],
+    }),
+    test({
+      code: 'export * from "./foo.js"',
+      options: ['never'],
+      errors: [
+        {
+          message: 'Unexpected use of file extension "js" for "./foo.js"',
+          line: 1,
+          column: 15,
+        },
+      ],
+    }),
+    test({
+      code: 'import foo from "@/ImNotAScopedModule.js"',
+      options: ['never'],
+      errors: [
+        {
+          message: 'Unexpected use of file extension "js" for "@/ImNotAScopedModule.js"',
+          line: 1,
+        },
+      ],
+    }),
+    test({
+      code: `
+        import _ from 'lodash';
+        import m from '@test-scope/some-module/index.js';
+
+        import bar from './bar';
+      `,
+      options: ['never'],
+      settings: {
+        'import/resolver': 'webpack',
+        'import/external-module-folders': ['node_modules', 'symlinked-module'],
+      },
+      errors: [
+        {
+          message: 'Unexpected use of file extension "js" for "@test-scope/some-module/index.js"',
+          line: 3,
+        },
+      ],
+    }),
+
+    // TODO: properly ignore packages resolved via relative imports
+    test({
+      code: [
+        'import * as test from "."',
+      ].join('\n'),
+      filename: testFilePath('./internal-modules/test.js'),
+      options: ['ignorePackages'],
+      errors: [
+        {
+          message: 'Missing file extension for "."',
+          line: 1,
+        },
+      ],
+    }),
+    // TODO: properly ignore packages resolved via relative imports
+    test({
+      code: [
+        'import * as test from ".."',
+      ].join('\n'),
+      filename: testFilePath('./internal-modules/plugins/plugin.js'),
+      options: ['ignorePackages'],
+      errors: [
+        {
+          message: 'Missing file extension for ".."',
+          line: 1,
+        },
+      ],
+    }),
   ],
-})
+});
+
+describe('TypeScript', () => {
+  getTSParsers()
+    // Type-only imports were added in TypeScript ESTree 2.23.0
+    .filter((parser) => parser !== parsers.TS_OLD)
+    .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,
+          }),
+          test({
+            code: 'export type { MyType } 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,
+          }),
+          test({
+            code: 'export { MyType } from "./typescript-declare";',
+            errors: ['Missing file extension for "./typescript-declare"'],
+            options: [
+              'always',
+              { ts: 'never', tsx: 'never', js: 'never', jsx: 'never' },
+            ],
+            parser,
+          }),
+          test({
+            code: 'import type T from "./typescript-declare";',
+            errors: ['Missing file extension for "./typescript-declare"'],
+            options: [
+              'always',
+              { ts: 'never', tsx: 'never', js: 'never', jsx: 'never', checkTypeImports: true },
+            ],
+            parser,
+          }),
+          test({
+            code: 'export type { MyType } from "./typescript-declare";',
+            errors: ['Missing file extension for "./typescript-declare"'],
+            options: [
+              'always',
+              { ts: 'never', tsx: 'never', js: 'never', jsx: 'never', checkTypeImports: true },
+            ],
+            parser,
+          }),
+        ],
+      });
+      ruleTesterWithTypeScriptImports.run(`${parser}: (with TS resolver) extensions are enforced for type imports/export when checkTypeImports is set`, rule, {
+        valid: [
+          test({
+            code: 'import type { MyType } from "./typescript-declare.ts";',
+            options: [
+              'always',
+              { checkTypeImports: true },
+            ],
+            parser,
+          }),
+          test({
+            code: 'export type { MyType } from "./typescript-declare.ts";',
+            options: [
+              'always',
+              { checkTypeImports: true },
+            ],
+            parser,
+          }),
+
+          // pathGroupOverrides: no patterns match good bespoke specifiers
+          test({
+            code: `
+              import { ErrorMessage as UpstreamErrorMessage } from '@black-flag/core/util';
+
+              import { $instances } from 'rootverse+debug:src.ts';
+              import { $exists } from 'rootverse+bfe:src/symbols.ts';
+
+              import type { Entries } from 'type-fest';
+            `,
+            parser,
+            options: [
+              'always',
+              {
+                ignorePackages: true,
+                checkTypeImports: true,
+                pathGroupOverrides: [
+                  {
+                    pattern: 'multiverse{*,*/**}',
+                    action: 'enforce',
+                  },
+                ],
+              },
+            ],
+          }),
+          // pathGroupOverrides: an enforce pattern matches good bespoke specifiers
+          test({
+            code: `
+              import { ErrorMessage as UpstreamErrorMessage } from '@black-flag/core/util';
+
+              import { $instances } from 'rootverse+debug:src.ts';
+              import { $exists } from 'rootverse+bfe:src/symbols.ts';
+
+              import type { Entries } from 'type-fest';
+            `,
+            parser,
+            options: [
+              'always',
+              {
+                ignorePackages: true,
+                checkTypeImports: true,
+                pathGroupOverrides: [
+                  {
+                    pattern: 'rootverse{*,*/**}',
+                    action: 'enforce',
+                  },
+                ],
+              },
+            ],
+          }),
+          // pathGroupOverrides: an ignore pattern matches bad bespoke specifiers
+          test({
+            code: `
+              import { ErrorMessage as UpstreamErrorMessage } from '@black-flag/core/util';
+
+              import { $instances } from 'rootverse+debug:src';
+              import { $exists } from 'rootverse+bfe:src/symbols';
+
+              import type { Entries } from 'type-fest';
+            `,
+            parser,
+            options: [
+              'always',
+              {
+                ignorePackages: true,
+                checkTypeImports: true,
+                pathGroupOverrides: [
+                  {
+                    pattern: 'multiverse{*,*/**}',
+                    action: 'enforce',
+                  },
+                  {
+                    pattern: 'rootverse{*,*/**}',
+                    action: 'ignore',
+                  },
+                ],
+              },
+            ],
+          }),
+        ],
+        invalid: [
+          test({
+            code: 'import type { MyType } from "./typescript-declare";',
+            errors: ['Missing file extension "ts" for "./typescript-declare"'],
+            options: [
+              'always',
+              { checkTypeImports: true },
+            ],
+            parser,
+          }),
+          test({
+            code: 'export type { MyType } from "./typescript-declare";',
+            errors: ['Missing file extension "ts" for "./typescript-declare"'],
+            options: [
+              'always',
+              { checkTypeImports: true },
+            ],
+            parser,
+          }),
+
+          // pathGroupOverrides: an enforce pattern matches bad bespoke specifiers
+          test({
+            code: `
+              import { ErrorMessage as UpstreamErrorMessage } from '@black-flag/core/util';
+
+              import { $instances } from 'rootverse+debug:src';
+              import { $exists } from 'rootverse+bfe:src/symbols';
+
+              import type { Entries } from 'type-fest';
+            `,
+            parser,
+            options: [
+              'always',
+              {
+                ignorePackages: true,
+                checkTypeImports: true,
+                pathGroupOverrides: [
+                  {
+                    pattern: 'rootverse{*,*/**}',
+                    action: 'enforce',
+                  },
+                  {
+                    pattern: 'universe{*,*/**}',
+                    action: 'ignore',
+                  },
+                ],
+              },
+            ],
+            errors: [
+              {
+                message: 'Missing file extension for "rootverse+debug:src"',
+                line: 4,
+              },
+              {
+                message: 'Missing file extension for "rootverse+bfe:src/symbols"',
+                line: 5,
+              },
+            ],
+          }),
+        ],
+      });
+    });
+});
diff --git a/tests/src/rules/first.js b/tests/src/rules/first.js
index 6a0fcdd64..52b71db86 100644
--- a/tests/src/rules/first.js
+++ b/tests/src/rules/first.js
@@ -1,67 +1,127 @@
-import { test } from '../utils'
+import { test, getTSParsers, testVersion } from '../utils';
+import fs from 'fs';
+import path from 'path';
 
-import { RuleTester } from 'eslint'
+import { RuleTester } from '../rule-tester';
 
-const ruleTester = new RuleTester()
-    , rule = require('rules/first')
+const ruleTester = new RuleTester();
+const rule = require('rules/first');
 
 ruleTester.run('first', rule, {
-  valid: [
-    test({ code: "import { x } from './foo'; import { y } from './bar';\
-                  export { x, y }" })
-  , test({ code: "import { x } from 'foo'; import { y } from './bar'" })
-  , test({ code: "import { x } from './foo'; import { y } from 'bar'" })
-  , test({ code: "'use directive';\
-                  import { x } from 'foo';" })
-  ,
-  ],
+  valid: [].concat(
+    test({
+      code: "import { x } from './foo'; import { y } from './bar';\
+            export { x, y }",
+    }),
+    test({ code: "import { x } from 'foo'; import { y } from './bar'" }),
+    test({ code: "import { x } from './foo'; import { y } from 'bar'" }),
+    test({
+      code: "import { x } from './foo'; import { y } from 'bar'",
+      options: ['disable-absolute-first'],
+    }),
+    test({
+      code: "'use directive';\
+            import { x } from 'foo';",
+    }),
+    testVersion('>= 7', () => ({
+      // issue #2210
+      code: String(fs.readFileSync(path.join(__dirname, '../../files/component.html'))),
+      parser: require.resolve('@angular-eslint/template-parser'),
+    })),
+  ),
   invalid: [
-    test({ code: "import { x } from './foo';\
-                  export { x };\
-                  import { y } from './bar';"
-         , errors: 1
-         , output: "import { x } from './foo';\
-                  import { y } from './bar';\
-                  export { x };"
-         })
-  , test({ code: "import { x } from './foo';\
-                  export { x };\
-                  import { y } from './bar';\
-                  import { z } from './baz';"
-         , errors: 2
-         , output: "import { x } from './foo';\
-                  import { y } from './bar';\
-                  import { z } from './baz';\
-                  export { x };"
-         })
-  , test({ code: "import { x } from './foo'; import { y } from 'bar'"
-         , options: ['absolute-first']
-         , errors: 1
-         })
-  , test({ code: "import { x } from 'foo';\
-                  'use directive';\
-                  import { y } from 'bar';"
-         , errors: 1
-         , output: "import { x } from 'foo';\
-                  import { y } from 'bar';\
-                  'use directive';"
-         })
-  , test({ code: "var a = 1;\
-                  import { y } from './bar';\
-                  if (true) { x() };\
-                  import { x } from './foo';\
-                  import { z } from './baz';"
-         , errors: 3
-         , output: "import { y } from './bar';\
-                  var a = 1;\
-                  if (true) { x() };\
-                  import { x } from './foo';\
-                  import { z } from './baz';"
-  })
-  , test({ code: "if (true) { console.log(1) }import a from 'b'"
-         , errors: 1
-         , output: "import a from 'b'\nif (true) { console.log(1) }"
-  })
-  ,
-  ]
-})
+    test({
+      code: "import { x } from './foo';\
+              export { x };\
+              import { y } from './bar';",
+      errors: 1,
+      output: "import { x } from './foo';\
+              import { y } from './bar';\
+              export { x };",
+    }),
+    test({
+      code: "import { x } from './foo';\
+              export { x };\
+              import { y } from './bar';\
+              import { z } from './baz';",
+      errors: 2,
+      output: "import { x } from './foo';\
+              import { y } from './bar';\
+              import { z } from './baz';\
+              export { x };",
+    }),
+    test({
+      code: "import { x } from './foo'; import { y } from 'bar'",
+      options: ['absolute-first'],
+      errors: 1,
+    }),
+    test({
+      code: "import { x } from 'foo';\
+              'use directive';\
+              import { y } from 'bar';",
+      errors: 1,
+      output: "import { x } from 'foo';\
+              import { y } from 'bar';\
+              'use directive';",
+    }),
+    test({
+      code: "var a = 1;\
+              import { y } from './bar';\
+              if (true) { x() };\
+              import { x } from './foo';\
+              import { z } from './baz';",
+      errors: 3,
+      output: "import { y } from './bar';\
+              var a = 1;\
+              if (true) { x() };\
+              import { x } from './foo';\
+              import { z } from './baz';",
+    }),
+    test({
+      code: "if (true) { console.log(1) }import a from 'b'",
+      errors: 1,
+      output: "import a from 'b'\nif (true) { console.log(1) }",
+    }),
+  ],
+});
+
+context('TypeScript', function () {
+  getTSParsers()
+    .forEach((parser) => {
+      const parserConfig = {
+        parser,
+        settings: {
+          'import/parsers': { [parser]: ['.ts'] },
+          'import/resolver': { 'eslint-import-resolver-typescript': true },
+        },
+      };
+
+      ruleTester.run('order', rule, {
+        valid: [
+          test({
+            code: `
+              import y = require('bar');
+              import { x } from 'foo';
+              import z = require('baz');
+            `,
+            ...parserConfig,
+          }),
+        ],
+        invalid: [
+          test({
+            code: `
+              import { x } from './foo';
+              import y = require('bar');
+            `,
+            options: ['absolute-first'],
+            ...parserConfig,
+            errors: [
+              {
+                message: 'Absolute imports should come before relative imports.',
+              },
+            ],
+          }),
+        ],
+      });
+    });
+});
diff --git a/tests/src/rules/group-exports.js b/tests/src/rules/group-exports.js
index 3b08997e3..6f05bc866 100644
--- a/tests/src/rules/group-exports.js
+++ b/tests/src/rules/group-exports.js
@@ -1,14 +1,25 @@
-import { test } from '../utils'
-import { RuleTester } from 'eslint'
-import rule from 'rules/group-exports'
+import { test } from '../utils';
+import { RuleTester } from '../rule-tester';
+import rule from 'rules/group-exports';
+import { resolve } from 'path';
+import { default as babelPresetFlow } from 'babel-preset-flow';
 
 /* eslint-disable max-len */
 const errors = {
   named: 'Multiple named export declarations; consolidate all named exports into a single export declaration',
   commonjs: 'Multiple CommonJS exports; consolidate all exports into a single assignment to `module.exports`',
-}
+};
 /* eslint-enable max-len */
-const ruleTester = new RuleTester()
+const ruleTester = new RuleTester({
+  parser: resolve(__dirname, '../../../node_modules/babel-eslint'),
+  parserOptions: {
+    babelOptions: {
+      configFile: false,
+      babelrc: false,
+      presets: [babelPresetFlow],
+    },
+  },
+});
 
 ruleTester.run('group-exports', rule, {
   valid: [
@@ -45,7 +56,11 @@ ruleTester.run('group-exports', rule, {
       // test
       export default {}
     ` }),
-    test({ code: 'module.exports = {} '}),
+    test({ code: `
+      export { default as module1 } from './module-1'
+      export { default as module2 } from './module-2'
+    ` }),
+    test({ code: 'module.exports = {} ' }),
     test({ code: `
       module.exports = { test: true,
         another: false }
@@ -99,6 +114,27 @@ ruleTester.run('group-exports', rule, {
       unrelated = 'assignment'
       module.exports.test = true
     ` }),
+    test({ code: `
+      type firstType = {
+        propType: string
+      };
+      const first = {};
+      export type { firstType };
+      export { first };
+    ` }),
+    test({ code: `
+      type firstType = {
+        propType: string
+      };
+      type secondType = {
+        propType: string
+      };
+      export type { firstType, secondType };
+    ` }),
+    test({ code: `
+      export type { type1A, type1B } from './module-1'
+      export { method1 } from './module-1'
+    ` }),
   ],
   invalid: [
     test({
@@ -111,6 +147,16 @@ ruleTester.run('group-exports', rule, {
         errors.named,
       ],
     }),
+    test({
+      code: `
+        export { method1 } from './module-1'
+        export { method2 } from './module-1'
+      `,
+      errors: [
+        errors.named,
+        errors.named,
+      ],
+    }),
     test({
       code: `
         module.exports = {}
@@ -217,5 +263,33 @@ ruleTester.run('group-exports', rule, {
         errors.commonjs,
       ],
     }),
+    test({
+      code: `
+        type firstType = {
+          propType: string
+        };
+        type secondType = {
+          propType: string
+        };
+        const first = {};
+        export type { firstType };
+        export type { secondType };
+        export { first };
+      `,
+      errors: [
+        errors.named,
+        errors.named,
+      ],
+    }),
+    test({
+      code: `
+        export type { type1 } from './module-1'
+        export type { type2 } from './module-1'
+      `,
+      errors: [
+        errors.named,
+        errors.named,
+      ],
+    }),
   ],
-})
+});
diff --git a/tests/src/rules/max-dependencies.js b/tests/src/rules/max-dependencies.js
index ee35b648f..959ee68de 100644
--- a/tests/src/rules/max-dependencies.js
+++ b/tests/src/rules/max-dependencies.js
@@ -1,9 +1,9 @@
-import { test } from '../utils'
+import { test, getTSParsers, parsers } from '../utils';
 
-import { RuleTester } from 'eslint'
+import { RuleTester } from '../rule-tester';
 
-const ruleTester = new RuleTester()
-    , rule = require('rules/max-dependencies')
+const ruleTester = new RuleTester();
+const rule = require('rules/max-dependencies');
 
 ruleTester.run('max-dependencies', rule, {
   valid: [
@@ -21,7 +21,7 @@ ruleTester.run('max-dependencies', rule, {
       }],
     }),
 
-    test({ code: 'import {x, y, z} from "./foo"'}),
+    test({ code: 'import {x, y, z} from "./foo"' }),
   ],
   invalid: [
     test({
@@ -66,7 +66,7 @@ ruleTester.run('max-dependencies', rule, {
 
     test({
       code: 'import type { x } from \'./foo\'; import type { y } from \'./bar\'',
-      parser: require.resolve('babel-eslint'),
+      parser: parsers.BABEL_OLD,
       options: [{
         max: 1,
       }],
@@ -74,5 +74,61 @@ ruleTester.run('max-dependencies', rule, {
         'Maximum number of dependencies (1) exceeded.',
       ],
     }),
+
+    test({
+      code: 'import type { x } from \'./foo\'; import type { y } from \'./bar\'; import type { z } from \'./baz\'',
+      parser: parsers.BABEL_OLD,
+      options: [{
+        max: 2,
+        ignoreTypeImports: false,
+      }],
+      errors: [
+        'Maximum number of dependencies (2) exceeded.',
+      ],
+    }),
   ],
-})
+});
+
+describe('TypeScript', () => {
+  getTSParsers()
+    // Type-only imports were added in TypeScript ESTree 2.23.0
+    .filter((parser) => parser !== parsers.TS_OLD)
+    .forEach((parser) => {
+      ruleTester.run(`max-dependencies (${parser.replace(process.cwd(), '.')})`, rule, {
+        valid: [
+          test({
+            code: 'import type { x } from \'./foo\'; import { y } from \'./bar\';',
+            parser,
+            options: [{
+              max: 1,
+              ignoreTypeImports: true,
+            }],
+          }),
+        ],
+        invalid: [
+          test({
+            code: 'import type { x } from \'./foo\'; import type { y } from \'./bar\'',
+            parser,
+            options: [{
+              max: 1,
+            }],
+            errors: [
+              'Maximum number of dependencies (1) exceeded.',
+            ],
+          }),
+
+          test({
+            code: 'import type { x } from \'./foo\'; import type { y } from \'./bar\'; import type { z } from \'./baz\'',
+            parser,
+            options: [{
+              max: 2,
+              ignoreTypeImports: false,
+            }],
+            errors: [
+              'Maximum number of dependencies (2) exceeded.',
+            ],
+          }),
+        ],
+      });
+    });
+});
diff --git a/tests/src/rules/named.js b/tests/src/rules/named.js
index ec8a1dbec..51a76c129 100644
--- a/tests/src/rules/named.js
+++ b/tests/src/rules/named.js
@@ -1,43 +1,43 @@
-import { test, SYNTAX_CASES, getTSParsers } from '../utils'
-import { RuleTester } from 'eslint'
+import { test, SYNTAX_CASES, getTSParsers, testFilePath, testVersion, parsers } from '../utils';
+import { RuleTester, usingFlatConfig } from '../rule-tester';
+import path from 'path';
 
-import { CASE_SENSITIVE_FS } from 'eslint-module-utils/resolve'
+import { CASE_SENSITIVE_FS } from 'eslint-module-utils/resolve';
 
+const ruleTester = new RuleTester();
+const rule = require('rules/named');
 
-var ruleTester = new RuleTester()
-  , rule = require('rules/named')
-
-function error(name, module) {
-  return { message: name + ' not found in \'' + module + '\''
-         , type: 'Identifier' }
+function error(name, module, type = 'Identifier') {
+  return { message: `${name} not found in '${module}'`, type };
 }
 
 ruleTester.run('named', rule, {
   valid: [
     test({ code: 'import "./malformed.js"' }),
 
-    test({code: 'import { foo } from "./bar"'}),
-    test({code: 'import { foo } from "./empty-module"'}),
-    test({code: 'import bar from "./bar.js"'}),
-    test({code: 'import bar, { foo } from "./bar.js"'}),
-    test({code: 'import {a, b, d} from "./named-exports"'}),
-    test({code: 'import {ExportedClass} from "./named-exports"'}),
-    test({code: 'import { destructingAssign } from "./named-exports"'}),
-    test({code: 'import { destructingRenamedAssign } from "./named-exports"'}),
-    test({code: 'import { ActionTypes } from "./qc"'}),
-    test({code: 'import {a, b, c, d} from "./re-export"'}),
-
-    test({ code: 'import { jsxFoo } from "./jsx/AnotherComponent"'
-         , settings: { 'import/resolve': { 'extensions': ['.js', '.jsx'] } } }),
+    test({ code: 'import { foo } from "./bar"' }),
+    test({ code: 'import { foo } from "./empty-module"' }),
+    test({ code: 'import bar from "./bar.js"' }),
+    test({ code: 'import bar, { foo } from "./bar.js"' }),
+    test({ code: 'import {a, b, d} from "./named-exports"' }),
+    test({ code: 'import {ExportedClass} from "./named-exports"' }),
+    test({ code: 'import { destructingAssign } from "./named-exports"' }),
+    test({ code: 'import { destructingRenamedAssign } from "./named-exports"' }),
+    test({ code: 'import { ActionTypes } from "./qc"' }),
+    test({ code: 'import {a, b, c, d} from "./re-export"' }),
+    test({ code: 'import {a, b, c} from "./re-export-common-star"' }),
+    test({ code: 'import {RuleTester} from "./re-export-node_modules"' }),
+
+    test({ code: 'import { jsxFoo } from "./jsx/AnotherComponent"',
+      settings: { 'import/resolve': { extensions: ['.js', '.jsx'] } } }),
 
     // validate that eslint-disable-line silences this properly
-    test({code: 'import {a, b, d} from "./common"; ' +
-                '// eslint-disable-line named' }),
+    test({ code: `import {a, b, d} from "./common"; // eslint-disable-line ${usingFlatConfig ? 'rule-to-test/' : ''}named` }),
 
     test({ code: 'import { foo, bar } from "./re-export-names"' }),
 
-    test({ code: 'import { foo, bar } from "./common"'
-         , settings: { 'import/ignore': ['common'] } }),
+    test({ code: 'import { foo, bar } from "./common"',
+      settings: { 'import/ignore': ['common'] } }),
 
     // ignore core modules by default
     test({ code: 'import { foo } from "crypto"' }),
@@ -53,15 +53,15 @@ ruleTester.run('named', rule, {
     // es7
     test({
       code: 'export bar, { foo } from "./bar"',
-      parser: require.resolve('babel-eslint'),
+      parser: parsers.BABEL_OLD,
     }),
     test({
       code: 'import { foo, bar } from "./named-trampoline"',
-      parser: require.resolve('babel-eslint'),
+      parser: parsers.BABEL_OLD,
     }),
 
     // regression tests
-    test({ code: 'let foo; export { foo as bar }'}),
+    test({ code: 'let foo; export { foo as bar }' }),
 
     // destructured exports
     test({ code: 'import { destructuredProp } from "./named-exports"' }),
@@ -72,43 +72,43 @@ ruleTester.run('named', rule, {
     // should ignore imported/exported flow types, even if they don’t exist
     test({
       code: 'import type { MissingType } from "./flowtypes"',
-      parser: require.resolve('babel-eslint'),
+      parser: parsers.BABEL_OLD,
     }),
     test({
       code: 'import typeof { MissingType } from "./flowtypes"',
-      parser: require.resolve('babel-eslint'),
+      parser: parsers.BABEL_OLD,
     }),
     test({
       code: 'import type { MyOpaqueType } from "./flowtypes"',
-      parser: require.resolve('babel-eslint'),
+      parser: parsers.BABEL_OLD,
     }),
     test({
       code: 'import typeof { MyOpaqueType } from "./flowtypes"',
-      parser: require.resolve('babel-eslint'),
+      parser: parsers.BABEL_OLD,
     }),
     test({
       code: 'import { type MyOpaqueType, MyClass } from "./flowtypes"',
-      parser: require.resolve('babel-eslint'),
+      parser: parsers.BABEL_OLD,
     }),
     test({
       code: 'import { typeof MyOpaqueType, MyClass } from "./flowtypes"',
-      parser: require.resolve('babel-eslint'),
+      parser: parsers.BABEL_OLD,
     }),
     test({
       code: 'import typeof MissingType from "./flowtypes"',
-      parser: require.resolve('babel-eslint'),
+      parser: parsers.BABEL_OLD,
     }),
     test({
       code: 'import typeof * as MissingType from "./flowtypes"',
-      parser: require.resolve('babel-eslint'),
+      parser: parsers.BABEL_OLD,
     }),
     test({
       code: 'export type { MissingType } from "./flowtypes"',
-      parser: require.resolve('babel-eslint'),
+      parser: parsers.BABEL_OLD,
     }),
     test({
       code: 'export type { MyOpaqueType } from "./flowtypes"',
-      parser: require.resolve('babel-eslint'),
+      parser: parsers.BABEL_OLD,
     }),
 
     // jsnext
@@ -144,36 +144,93 @@ ruleTester.run('named', rule, {
       code: 'import { common } from "./re-export-default"',
     }),
 
+    // destructured requires with commonjs option
+    test({
+      code: 'const { destructuredProp } = require("./named-exports")',
+      options: [{ commonjs: true }],
+    }),
+    test({
+      code: 'let { arrayKeyProp } = require("./named-exports")',
+      options: [{ commonjs: true }],
+    }),
+    test({
+      code: 'const { deepProp } = require("./named-exports")',
+      options: [{ commonjs: true }],
+    }),
+
+    test({
+      code: 'const { foo, bar } = require("./re-export-names")',
+      options: [{ commonjs: true }],
+    }),
+
+    test({
+      code: 'const { baz } = require("./bar")',
+      errors: [error('baz', './bar')],
+    }),
+
+    test({
+      code: 'const { baz } = require("./bar")',
+      errors: [error('baz', './bar')],
+      options: [{ commonjs: false }],
+    }),
+
+    test({
+      code: 'const { default: defExport } = require("./bar")',
+      options: [{ commonjs: true }],
+    }),
+
     ...SYNTAX_CASES,
-  ],
 
-  invalid: [
+    ...[].concat(testVersion('>= 6', () => ({
+      code: `import { ExtfieldModel, Extfield2Model } from './models';`,
+      filename: testFilePath('./export-star/downstream.js'),
+      parserOptions: {
+        sourceType: 'module',
+        ecmaVersion: 2020,
+      },
+    })),
+
+    testVersion('>=7.8.0', () => ({ code: 'const { something } = require("./dynamic-import-in-commonjs")',
+      parserOptions: { ecmaVersion: 2021 },
+      options: [{ commonjs: true }],
+    })),
+
+    testVersion('>=7.8.0', () => ({ code: 'import { something } from "./dynamic-import-in-commonjs"',
+      parserOptions: { ecmaVersion: 2021 } })),
+
+    // es2022: Arbitrary module namespace identifier names
+    testVersion('>= 8.7', () => ({
+      code: 'import { "foo" as foo } from "./bar"', parserOptions: { ecmaVersion: 2022 } })),
+    testVersion('>= 8.7', () => ({
+      code: 'import { "foo" as foo } from "./empty-module"', parserOptions: { ecmaVersion: 2022 } })),
+    ),
+  ],
 
-    test({ code: 'import { somethingElse } from "./test-module"'
-         , errors: [ error('somethingElse', './test-module') ] }),
+  invalid: [].concat(
+    test({ code: 'import { somethingElse } from "./test-module"',
+      errors: [error('somethingElse', './test-module')] }),
 
-    test({code: 'import { baz } from "./bar"',
-      errors: [error('baz', './bar')]}),
+    test({ code: 'import { baz } from "./bar"',
+      errors: [error('baz', './bar')] }),
 
     // test multiple
-    test({code: 'import { baz, bop } from "./bar"',
-      errors: [error('baz', './bar'), error('bop', './bar')]}),
+    test({ code: 'import { baz, bop } from "./bar"',
+      errors: [error('baz', './bar'), error('bop', './bar')] }),
 
-    test({code: 'import {a, b, c} from "./named-exports"',
-      errors: [error('c', './named-exports')]}),
+    test({ code: 'import {a, b, c} from "./named-exports"',
+      errors: [error('c', './named-exports')] }),
 
-    test({code: 'import { a } from "./default-export"',
-      errors: [error('a', './default-export')]}),
+    test({ code: 'import { a } from "./default-export"',
+      errors: [error('a', './default-export')] }),
 
-    test({code: 'import { ActionTypess } from "./qc"',
-      errors: [error('ActionTypess', './qc')]}),
+    test({ code: 'import { ActionTypess } from "./qc"',
+      errors: [error('ActionTypess', './qc')] }),
 
-    test({code: 'import {a, b, c, d, e} from "./re-export"',
-      errors: [error('e', './re-export')]}),
+    test({ code: 'import {a, b, c, d, e} from "./re-export"',
+      errors: [error('e', './re-export')] }),
 
     test({
       code: 'import { a } from "./re-export-names"',
-      options: [2, 'es6-only'],
       errors: [error('a', './re-export-names')],
     }),
 
@@ -186,18 +243,42 @@ ruleTester.run('named', rule, {
     // es7
     test({
       code: 'export bar2, { bar } from "./bar"',
-      parser: require.resolve('babel-eslint'),
+      parser: parsers.BABEL_OLD,
       errors: ["bar not found in './bar'"],
     }),
     test({
       code: 'import { foo, bar, baz } from "./named-trampoline"',
-      parser: require.resolve('babel-eslint'),
+      parser: parsers.BABEL_OLD,
       errors: ["baz not found in './named-trampoline'"],
     }),
     test({
       code: 'import { baz } from "./broken-trampoline"',
-      parser: require.resolve('babel-eslint'),
-      errors: ["baz not found via broken-trampoline.js -> named-exports.js"],
+      parser: parsers.BABEL_OLD,
+      errors: ['baz not found via broken-trampoline.js -> named-exports.js'],
+    }),
+
+    test({
+      code: 'const { baz } = require("./bar")',
+      errors: [error('baz', './bar')],
+      options: [{ commonjs: true }],
+    }),
+
+    test({
+      code: 'let { baz } = require("./bar")',
+      errors: [error('baz', './bar')],
+      options: [{ commonjs: true }],
+    }),
+
+    test({
+      code: 'const { baz: bar, bop } = require("./bar"), { a } = require("./re-export-names")',
+      errors: [error('baz', './bar'), error('bop', './bar'), error('a', './re-export-names')],
+      options: [{ commonjs: true }],
+    }),
+
+    test({
+      code: 'const { default: defExport } = require("./named-exports")',
+      errors: [error('default', './named-exports')],
+      options: [{ commonjs: true }],
     }),
 
     // parse errors
@@ -212,7 +293,7 @@ ruleTester.run('named', rule, {
 
     test({
       code: 'import  { type MyOpaqueType, MyMissingClass } from "./flowtypes"',
-      parser: require.resolve('babel-eslint'),
+      parser: parsers.BABEL_OLD,
       errors: ["MyMissingClass not found in './flowtypes'"],
     }),
 
@@ -240,14 +321,30 @@ ruleTester.run('named', rule, {
       errors: ["bap not found in './re-export-default'"],
     }),
 
-
     // #328: * exports do not include default
     test({
       code: 'import { default as barDefault } from "./re-export"',
       errors: [`default not found in './re-export'`],
     }),
-  ],
-})
+
+    // es2022: Arbitrary module namespace identifier names
+    testVersion('>= 8.7', () => ({
+      code: 'import { "somethingElse" as somethingElse } from "./test-module"',
+      errors: [error('somethingElse', './test-module', 'Literal')],
+      parserOptions: { ecmaVersion: 2022 },
+    })),
+    testVersion('>= 8.7', () => ({
+      code: 'import { "baz" as baz, "bop" as bop } from "./bar"',
+      errors: [error('baz', './bar', 'Literal'), error('bop', './bar', 'Literal')],
+      parserOptions: { ecmaVersion: 2022 },
+    })),
+    testVersion('>= 8.7', () => ({
+      code: 'import { "default" as barDefault } from "./re-export"',
+      errors: [`default not found in './re-export'`],
+      parserOptions: { ecmaVersion: 2022 },
+    })),
+  ),
+});
 
 // #311: import of mismatched case
 if (!CASE_SENSITIVE_FS) {
@@ -263,7 +360,7 @@ if (!CASE_SENSITIVE_FS) {
         errors: [`foo not found in './Named-Exports'`],
       }),
     ],
-  })
+  });
 }
 
 // export-all
@@ -279,105 +376,145 @@ ruleTester.run('named (export *)', rule, {
       errors: [`bar not found in './export-all'`],
     }),
   ],
-})
+});
 
-
-context('Typescript', function () {
+context('TypeScript', function () {
   getTSParsers().forEach((parser) => {
-    ['typescript', 'typescript-declare', 'typescript-export-assign'].forEach((source) => {
-      ruleTester.run(`named`, rule, {
-        valid: [
-          test({
-            code: `import { MyType } from "./${source}"`,
-            parser: parser,
-            settings: {
-              'import/parsers': { [parser]: ['.ts'] },
-              'import/resolver': { 'eslint-import-resolver-typescript': true },
-            },
-          }),
-          test({
-            code: `import { Foo } from "./${source}"`,
-            parser: parser,
-            settings: {
-              'import/parsers': { [parser]: ['.ts'] },
-              'import/resolver': { 'eslint-import-resolver-typescript': true },
-            },
-          }),
-          test({
-            code: `import { Bar } from "./${source}"`,
-            parser: parser,
-            settings: {
-              'import/parsers': { [parser]: ['.ts'] },
-              'import/resolver': { 'eslint-import-resolver-typescript': true },
-            },
-          }),
-          test({
+    const settings = {
+      'import/parsers': { [parser]: ['.ts'] },
+      'import/resolver': { 'eslint-import-resolver-typescript': true },
+    };
+
+    let valid = [
+      test({
+        code: `import x from './typescript-export-assign-object'`,
+        parser,
+        parserOptions: {
+          tsconfigRootDir: path.resolve(__dirname, '../../files/typescript-export-assign-object/'),
+        },
+        settings,
+      }),
+    ];
+    const invalid = [
+      // TODO: uncomment this test
+      // test({
+      //   code: `import {a} from './export-star-3/b';`,
+      //   filename: testFilePath('./export-star-3/a.js'),
+      //   parser,
+      //   settings,
+      //   errors: [
+      //     { message: 'a not found in ./export-star-3/b' },
+      //   ],
+      // }),
+      test({
+        code: `import { NotExported } from './typescript-export-assign-object'`,
+        parser,
+        parserOptions: {
+          tsconfigRootDir: path.resolve(__dirname, '../../files/typescript-export-assign-object/'),
+        },
+        settings,
+        errors: [{
+          message: `NotExported not found in './typescript-export-assign-object'`,
+          type: 'Identifier',
+        }],
+      }),
+      test({
+        // `export =` syntax creates a default export only
+        code: `import { FooBar } from './typescript-export-assign-object'`,
+        parser,
+        parserOptions: {
+          tsconfigRootDir: path.resolve(__dirname, '../../files/typescript-export-assign-object/'),
+        },
+        settings,
+        errors: [{
+          message: `FooBar not found in './typescript-export-assign-object'`,
+          type: 'Identifier',
+        }],
+      }),
+    ];
+
+    [
+      'typescript',
+      'typescript-declare',
+      'typescript-export-assign-namespace',
+      'typescript-export-assign-namespace-merged',
+    ].forEach((source) => {
+      valid = valid.concat(
+        test({
+          code: `import { MyType } from "./${source}"`,
+          parser,
+          settings,
+        }),
+        test({
+          code: `import { Foo } from "./${source}"`,
+          parser,
+          settings,
+        }),
+        test({
+          code: `import { Bar } from "./${source}"`,
+          parser,
+          settings,
+        }),
+        source === 'typescript-declare'
+          ? testVersion('> 5', () => ({
+            code: `import { getFoo } from "./${source}"`,
+            parser,
+            settings,
+          }))
+          : test({
             code: `import { getFoo } from "./${source}"`,
-            parser: parser,
-            settings: {
-              'import/parsers': { [parser]: ['.ts'] },
-              'import/resolver': { 'eslint-import-resolver-typescript': true },
-            },
-          }),
-          test({
-            code: `import { MyEnum } from "./${source}"`,
-            parser: parser,
-            settings: {
-              'import/parsers': { [parser]: ['.ts'] },
-              'import/resolver': { 'eslint-import-resolver-typescript': true },
-            },
-          }),
-          test({
-            code: `
+            parser,
+            settings,
+          })
+        ,
+        test({
+          code: `import { MyEnum } from "./${source}"`,
+          parser,
+          settings,
+        }),
+        test({
+          code: `
               import { MyModule } from "./${source}"
               MyModule.ModuleFunction()
             `,
-            parser: parser,
-            settings: {
-              'import/parsers': { [parser]: ['.ts'] },
-              'import/resolver': { 'eslint-import-resolver-typescript': true },
-            },
-          }),
-          test({
-            code: `
+          parser,
+          settings,
+        }),
+        test({
+          code: `
               import { MyNamespace } from "./${source}"
               MyNamespace.NSModule.NSModuleFunction()
             `,
-            parser: parser,
-            settings: {
-              'import/parsers': { [parser]: ['.ts'] },
-              'import/resolver': { 'eslint-import-resolver-typescript': true },
-            },
-          }),
-        ],
-
-        invalid: [
-          test({
-            code: `import { MissingType } from "./${source}"`,
-            parser: parser,
-            settings: {
-              'import/parsers': { [parser]: ['.ts'] },
-              'import/resolver': { 'eslint-import-resolver-typescript': true },
-            },
-            errors: [{
-              message: `MissingType not found in './${source}'`,
-              type: 'Identifier',
-            }],
-          }),
-          test({
-            code: `import { NotExported } from "./${source}"`,
-            parser: parser,
-            settings: {
-              'import/parsers': { [parser]: ['.ts'] },
-              'import/resolver': { 'eslint-import-resolver-typescript': true },
-            },
-            errors: [{
-              message: `NotExported not found in './${source}'`,
-              type: 'Identifier',
-            }],
-          }),
-        ],
-      })
-    })
-  })
-})
+          parser,
+          settings,
+        }),
+      );
+
+      invalid.push(
+        test({
+          code: `import { MissingType } from "./${source}"`,
+          parser,
+          settings,
+          errors: [{
+            message: `MissingType not found in './${source}'`,
+            type: 'Identifier',
+          }],
+        }),
+        test({
+          code: `import { NotExported } from "./${source}"`,
+          parser,
+          settings,
+          errors: [{
+            message: `NotExported not found in './${source}'`,
+            type: 'Identifier',
+          }],
+        }),
+      );
+    });
+
+    ruleTester.run(`named [TypeScript]`, rule, {
+      valid,
+      invalid,
+    });
+  });
+});
diff --git a/tests/src/rules/namespace.js b/tests/src/rules/namespace.js
index cfc6305d5..2a31d57e1 100644
--- a/tests/src/rules/namespace.js
+++ b/tests/src/rules/namespace.js
@@ -1,25 +1,22 @@
-import { test, SYNTAX_CASES } from '../utils'
-import { RuleTester } from 'eslint'
-
-var ruleTester = new RuleTester({ env: { es6: true }})
-  , rule = require('rules/namespace')
+import { test, SYNTAX_CASES, getTSParsers, testVersion, testFilePath, parsers } from '../utils';
+import { RuleTester } from '../rule-tester';
+import flatMap from 'array.prototype.flatmap';
 
+const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 6 } });
+const rule = require('rules/namespace');
 
 function error(name, namespace) {
-  return { message: `'${name}' not found in imported namespace '${namespace}'.` }
+  return { message: `'${name}' not found in imported namespace '${namespace}'.` };
 }
 
 const valid = [
   test({ code: 'import "./malformed.js"' }),
 
-  test({ code: "import * as foo from './empty-folder';"}),
-  test({ code: 'import * as names from "./named-exports"; ' +
-               'console.log((names.b).c); ' }),
+  test({ code: "import * as foo from './empty-folder';" }),
+  test({ code: 'import * as names from "./named-exports"; console.log((names.b).c); ' }),
 
-  test({ code: 'import * as names from "./named-exports"; ' +
-               'console.log(names.a);' }),
-  test({ code: 'import * as names from "./re-export-names"; ' +
-               'console.log(names.foo);' }),
+  test({ code: 'import * as names from "./named-exports"; console.log(names.a);' }),
+  test({ code: 'import * as names from "./re-export-names"; console.log(names.foo);' }),
   test({
     code: "import * as elements from './jsx';",
     parserOptions: {
@@ -28,63 +25,88 @@ const valid = [
       ecmaVersion: 2015,
     },
   }),
+  // import re-exported jsx files, where jsx file exports a string
+  test({
+    code: `
+      import * as foo from "./jsx/re-export.js";
+      console.log(foo.jsxFoo);
+    `,
+    settings: {
+      'import/extensions': ['.js', '.jsx'],
+    },
+  }),
+  // import re-exported jsx files, where jsx files export functions that return html tags
+  test({
+    code: `
+      import * as foo from "./jsx/bar/index.js";
+      console.log(foo.Baz1);
+      console.log(foo.Baz2);
+      console.log(foo.Qux1);
+      console.log(foo.Qux2);
+    `,
+    settings: {
+      'import/extensions': ['.js', '.jsx'],
+    },
+    parserOptions: {
+      ecmaFeatures: {
+        jsx: true,
+      },
+    },
+  }),
+
   test({ code: "import * as foo from './common';" }),
 
   // destructuring namespaces
-  test({ code: 'import * as names from "./named-exports";' +
-               'const { a } = names' }),
-  test({ code: 'import * as names from "./named-exports";' +
-               'const { d: c } = names' }),
-  test({ code: 'import * as names from "./named-exports";' +
-               'const { c } = foo\n' +
-               '    , { length } = "names"\n' +
-               '    , alt = names' }),
+  test({ code: 'import * as names from "./named-exports"; const { a } = names' }),
+  test({ code: 'import * as names from "./named-exports"; const { d: c } = names' }),
+  test({
+    code: `
+      import * as names from "./named-exports";
+      const { c } = foo,
+        { length } = "names",
+        alt = names;
+      `,
+  }),
   // deep destructuring only cares about top level
-  test({ code: 'import * as names from "./named-exports";' +
-               'const { ExportedClass: { length } } = names' }),
+  test({ code: 'import * as names from "./named-exports"; const { ExportedClass: { length } } = names' }),
 
   // detect scope redefinition
-  test({ code: 'import * as names from "./named-exports";' +
-               'function b(names) { const { c } = names }' }),
-  test({ code: 'import * as names from "./named-exports";' +
-               'function b() { let names = null; const { c } = names }' }),
-  test({ code: 'import * as names from "./named-exports";' +
-               'const x = function names() { const { c } = names }' }),
-
+  test({ code: 'import * as names from "./named-exports"; function b(names) { const { c } = names }' }),
+  test({ code: 'import * as names from "./named-exports"; function b() { let names = null; const { c } = names }' }),
+  test({ code: 'import * as names from "./named-exports"; const x = function names() { const { c } = names }' }),
 
   /////////
   // es7 //
   /////////
-  test({ code: 'export * as names from "./named-exports"'
-       , parser: require.resolve('babel-eslint') }),
-  test({ code: 'export defport, * as names from "./named-exports"'
-       , parser: require.resolve('babel-eslint') }),
+  test({ code: 'export * as names from "./named-exports"',
+    parser: parsers.BABEL_OLD }),
+  test({ code: 'export defport, * as names from "./named-exports"',
+    parser: parsers.BABEL_OLD }),
   // non-existent is handled by no-unresolved
-  test({ code: 'export * as names from "./does-not-exist"'
-       , parser: require.resolve('babel-eslint') }),
+  test({ code: 'export * as names from "./does-not-exist"',
+    parser: parsers.BABEL_OLD }),
 
   test({
     code: 'import * as Endpoints from "./issue-195/Endpoints"; console.log(Endpoints.Users)',
-    parser: require.resolve('babel-eslint'),
+    parser: parsers.BABEL_OLD,
   }),
 
   // respect hoisting
   test({
     code:
-      'function x() { console.log((names.b).c); } ' +
-      'import * as names from "./named-exports"; ',
+      'function x() { console.log((names.b).c); } import * as names from "./named-exports"; ',
   }),
 
   // names.default is valid export
   test({ code: "import * as names from './default-export';" }),
   test({ code: "import * as names from './default-export'; console.log(names.default)" }),
   test({
-   code: 'export * as names from "./default-export"',
-   parser: require.resolve('babel-eslint'),
+    code: 'export * as names from "./default-export"',
+    parser: parsers.BABEL_OLD,
   }),
   test({
     code: 'export defport, * as names from "./default-export"',
-    parser: require.resolve('babel-eslint'),
+    parser: parsers.BABEL_OLD,
   }),
 
   // #456: optionally ignore computed references
@@ -102,7 +124,7 @@ const valid = [
   }),
   test({
     code: `import * as names from './named-exports'; const {a, b, ...rest} = names;`,
-    parser: require.resolve('babel-eslint'),
+    parser: parsers.BABEL_OLD,
   }),
 
   // #1144: should handle re-export CommonJS as namespace
@@ -120,23 +142,108 @@ const valid = [
     },
   }),
 
+  // Typescript
+  ...flatMap(getTSParsers(), (parser) => [
+    test({
+      code: `
+        import * as foo from "./typescript-declare-nested"
+        foo.bar.MyFunction()
+      `,
+      parser,
+      settings: {
+        'import/parsers': { [parser]: ['.ts'] },
+        'import/resolver': { 'eslint-import-resolver-typescript': true },
+      },
+    }),
+
+    test({
+      code: `import { foobar } from "./typescript-declare-interface"`,
+      parser,
+      settings: {
+        'import/parsers': { [parser]: ['.ts'] },
+        'import/resolver': { 'eslint-import-resolver-typescript': true },
+      },
+    }),
+
+    test({
+      code: 'export * from "typescript/lib/typescript.d"',
+      parser,
+      settings: {
+        'import/parsers': { [parser]: ['.ts'] },
+        'import/resolver': { 'eslint-import-resolver-typescript': true },
+      },
+    }),
+
+    test({
+      code: 'export = function name() {}',
+      parser,
+      settings: {
+        'import/parsers': { [parser]: ['.ts'] },
+        'import/resolver': { 'eslint-import-resolver-typescript': true },
+      },
+    }),
+  ]),
+
   ...SYNTAX_CASES,
-]
 
-const invalid = [
-  test({ code: "import * as names from './named-exports'; " +
-               ' console.log(names.c);'
-       , errors: [error('c', 'names')] }),
+  test({
+    code: `
+    import * as color from './color';
+    export const getBackgroundFromColor = (color) => color.bg;
+    export const getExampleColor = () => color.example
+    `,
+  }),
 
-  test({ code: "import * as names from './named-exports';" +
-               " console.log(names['a']);"
-       , errors: ["Unable to validate computed reference to imported namespace 'names'."] }),
+  ...[].concat(testVersion('>= 6', () => ({
+    code: `
+      import * as middle from './middle';
+
+      console.log(middle.myName);
+    `,
+    filename: testFilePath('export-star-2/downstream.js'),
+    parserOptions: {
+      ecmaVersion: 2020,
+    },
+  })),
+  // es2022: Arbitrary module namespace identifier names
+  testVersion('>= 8.7', () => ({
+    code: "import * as names from './default-export-string';",
+    parserOptions: { ecmaVersion: 2022 },
+  })),
+  testVersion('>= 8.7', () => ({
+    code: "import * as names from './default-export-string'; console.log(names.default)",
+    parserOptions: { ecmaVersion: 2022 },
+  })),
+  testVersion('>= 8.7', () => ({
+    code: "import * as names from './default-export-namespace-string';",
+    parserOptions: { ecmaVersion: 2022 },
+  })),
+  testVersion('>= 8.7', () => ({
+    code: "import * as names from './default-export-namespace-string'; console.log(names.default)",
+    parserOptions: { ecmaVersion: 2022 },
+  })),
+  testVersion('>= 8.7', () => ({
+    code: `import { "b" as b } from "./deep/a"; console.log(b.c.d.e)`,
+    parserOptions: { ecmaVersion: 2022 },
+  })),
+  testVersion('>= 8.7', () => ({
+    code: `import { "b" as b } from "./deep/a"; var {c:{d:{e}}} = b`,
+    parserOptions: { ecmaVersion: 2022 },
+  }))),
+];
+
+const invalid = [].concat(
+  test({ code: "import * as names from './named-exports'; console.log(names.c)",
+    errors: [error('c', 'names')] }),
+
+  test({ code: "import * as names from './named-exports'; console.log(names['a']);",
+    errors: ["Unable to validate computed reference to imported namespace 'names'."] }),
 
   // assignment warning (from no-reassign)
-  test({ code: 'import * as foo from \'./bar\'; foo.foo = \'y\';'
-       , errors: [{ message: 'Assignment to member of namespace \'foo\'.'}] }),
-  test({ code: 'import * as foo from \'./bar\'; foo.x = \'y\';'
-       , errors: ['Assignment to member of namespace \'foo\'.', "'x' not found in imported namespace 'foo'."] }),
+  test({ code: 'import * as foo from \'./bar\'; foo.foo = \'y\';',
+    errors: [{ message: 'Assignment to member of namespace \'foo\'.' }] }),
+  test({ code: 'import * as foo from \'./bar\'; foo.x = \'y\';',
+    errors: ['Assignment to member of namespace \'foo\'.', "'x' not found in imported namespace 'foo'."] }),
 
   // invalid destructuring
   test({
@@ -152,8 +259,7 @@ const invalid = [
     errors: [{ type: 'Property', message: "'c' not found in imported namespace 'names'." }],
   }),
   test({
-    code: 'import * as names from "./named-exports";' +
-           'const { c: { d } } = names',
+    code: 'import * as names from "./named-exports"; const { c: { d } } = names',
     errors: [{ type: 'Property', message: "'c' not found in imported namespace 'names'." }],
   }),
 
@@ -163,7 +269,7 @@ const invalid = [
 
   test({
     code: 'import * as Endpoints from "./issue-195/Endpoints"; console.log(Endpoints.Foo)',
-    parser: require.resolve('babel-eslint'),
+    parser: parsers.BABEL_OLD,
     errors: ["'Foo' not found in imported namespace 'Endpoints'."],
   }),
 
@@ -178,20 +284,16 @@ const invalid = [
 
   test({
     code: "import b from './deep/default'; console.log(b.e)",
-    errors: [ "'e' not found in imported namespace 'b'." ],
+    errors: ["'e' not found in imported namespace 'b'."],
   }),
 
   // respect hoisting
   test({
-    code:
-      'console.log(names.c);' +
-      "import * as names from './named-exports'; ",
+    code: `console.log(names.c); import * as names from './named-exports';`,
     errors: [error('c', 'names')],
   }),
   test({
-    code:
-      'function x() { console.log(names.c) } ' +
-      "import * as names from './named-exports'; ",
+    code: `function x() { console.log(names.c) } import * as names from './named-exports';`,
     errors: [error('c', 'names')],
   }),
 
@@ -212,53 +314,65 @@ const invalid = [
     },
   }),
 
-]
+  // es2022: Arbitrary module namespace identifier names
+  testVersion('>= 8.7', () => ({
+    code: `import { "b" as b } from "./deep/a"; console.log(b.e)`,
+    errors: ["'e' not found in imported namespace 'b'."],
+    parserOptions: { ecmaVersion: 2022 },
+  })),
+  testVersion('>= 8.7', () => ({
+    code: `import { "b" as b } from "./deep/a"; console.log(b.c.e)`,
+    errors: ["'e' not found in deeply imported namespace 'b.c'."],
+    parserOptions: { ecmaVersion: 2022 },
+  })),
+);
 
 ///////////////////////
 // deep dereferences //
 //////////////////////
-;[['deep', require.resolve('espree')], ['deep-es7', require.resolve('babel-eslint')]].forEach(function ([folder, parser]) { // close over params
+[['deep', require.resolve('espree')], ['deep-es7', parsers.BABEL_OLD]].forEach(function ([folder, parser]) { // close over params
   valid.push(
     test({ parser, code: `import * as a from "./${folder}/a"; console.log(a.b.c.d.e)` }),
     test({ parser, code: `import { b } from "./${folder}/a"; console.log(b.c.d.e)` }),
     test({ parser, code: `import * as a from "./${folder}/a"; console.log(a.b.c.d.e.f)` }),
     test({ parser, code: `import * as a from "./${folder}/a"; var {b:{c:{d:{e}}}} = a` }),
-    test({ parser, code: `import { b } from "./${folder}/a"; var {c:{d:{e}}} = b` }))
-
+    test({ parser, code: `import { b } from "./${folder}/a"; var {c:{d:{e}}} = b` }),
     // deep namespaces should include explicitly exported defaults
     test({ parser, code: `import * as a from "./${folder}/a"; console.log(a.b.default)` }),
+  );
 
   invalid.push(
     test({
       parser,
       code: `import * as a from "./${folder}/a"; console.log(a.b.e)`,
-      errors: [ "'e' not found in deeply imported namespace 'a.b'." ],
+      errors: ["'e' not found in deeply imported namespace 'a.b'."],
     }),
     test({
       parser,
       code: `import { b } from "./${folder}/a"; console.log(b.e)`,
-      errors: [ "'e' not found in imported namespace 'b'." ],
+      errors: ["'e' not found in imported namespace 'b'."],
     }),
     test({
       parser,
       code: `import * as a from "./${folder}/a"; console.log(a.b.c.e)`,
-      errors: [ "'e' not found in deeply imported namespace 'a.b.c'." ],
+      errors: ["'e' not found in deeply imported namespace 'a.b.c'."],
     }),
     test({
       parser,
       code: `import { b } from "./${folder}/a"; console.log(b.c.e)`,
-      errors: [ "'e' not found in deeply imported namespace 'b.c'." ],
+      errors: ["'e' not found in deeply imported namespace 'b.c'."],
     }),
     test({
       parser,
       code: `import * as a from "./${folder}/a"; var {b:{ e }} = a`,
-      errors: [ "'e' not found in deeply imported namespace 'a.b'." ],
+      errors: ["'e' not found in deeply imported namespace 'a.b'."],
     }),
     test({
       parser,
       code: `import * as a from "./${folder}/a"; var {b:{c:{ e }}} = a`,
-      errors: [ "'e' not found in deeply imported namespace 'a.b.c'." ],
-    }))
-})
+      errors: ["'e' not found in deeply imported namespace 'a.b.c'."],
+    }),
+  );
+});
 
-ruleTester.run('namespace', rule, { valid, invalid })
+ruleTester.run('namespace', rule, { valid, invalid });
diff --git a/tests/src/rules/newline-after-import.js b/tests/src/rules/newline-after-import.js
index 490fad97d..984e89855 100644
--- a/tests/src/rules/newline-after-import.js
+++ b/tests/src/rules/newline-after-import.js
@@ -1,15 +1,19 @@
-import { RuleTester } from 'eslint'
+import { RuleTester, withoutAutofixOutput } from '../rule-tester';
+import flatMap from 'array.prototype.flatmap';
+import semver from 'semver';
+import { version as tsEslintVersion } from 'typescript-eslint-parser/package.json';
 
-const IMPORT_ERROR_MESSAGE = 'Expected 1 empty line after import statement not followed by another import.'
-const IMPORT_ERROR_MESSAGE_MULTIPLE = (count) => {
-    return `Expected ${count} empty lines after import statement not followed by another import.`
-}
-const REQUIRE_ERROR_MESSAGE = 'Expected 1 empty line after require statement not followed by another require.'
+import { getTSParsers, parsers, testVersion } from '../utils';
 
-const ruleTester = new RuleTester()
+const IMPORT_ERROR_MESSAGE = 'Expected 1 empty line after import statement not followed by another import.';
+const IMPORT_ERROR_MESSAGE_MULTIPLE = (count) => `Expected ${count} empty lines after import statement not followed by another import.`;
+const REQUIRE_ERROR_MESSAGE = 'Expected 1 empty line after require statement not followed by another require.';
+const REQUIRE_ERROR_MESSAGE_MULTIPLE = (count) => `Expected ${count} empty lines after require statement not followed by another require.`;
+
+const ruleTester = new RuleTester();
 
 ruleTester.run('newline-after-import', require('rules/newline-after-import'), {
-  valid: [
+  valid: [].concat(
     `var path = require('path');\nvar foo = require('foo');\n`,
     `require('foo');`,
     `switch ('foo') { case 'bar': require('baz'); }`,
@@ -17,13 +21,54 @@ ruleTester.run('newline-after-import', require('rules/newline-after-import'), {
       code: `
         const x = () => require('baz')
             , y = () => require('bar')`,
-      parserOptions: { ecmaVersion: 6 } ,
+      parserOptions: { ecmaVersion: 6 },
+    },
+    {
+      code: `
+        const x = () => require('baz')
+            , y = () => require('bar')
+
+        // some comment here
+      `,
+      parserOptions: { ecmaVersion: 6 },
+      options: [{ considerComments: true }],
     },
     {
       code: `const x = () => require('baz') && require('bar')`,
-      parserOptions: { ecmaVersion: 6 } ,
+      parserOptions: { ecmaVersion: 6 },
+    },
+    {
+      code: `
+        const x = () => require('baz') && require('bar')
+
+        // Some random single line comment
+        var bar = 42;
+      `,
+      parserOptions: { ecmaVersion: 6 },
+      options: [{ considerComments: true }],
+    },
+    {
+      code: `
+        const x = () => require('baz') && require('bar')
+
+        // Some random single line comment
+        var bar = 42;
+      `,
+      parserOptions: { ecmaVersion: 6 },
+      options: [{ considerComments: true, count: 1, exactCount: true }],
+    },
+    {
+      code: `
+        const x = () => require('baz') && require('bar')
+        /**
+         * some multiline comment here
+         * another line of comment
+        **/
+        var bar = 42;
+      `,
+      parserOptions: { ecmaVersion: 6 },
     },
-    `function x(){ require('baz'); }`,
+    `function x() { require('baz'); }`,
     `a(require('b'), require('c'), require('d'));`,
     `function foo() {
       switch (renderData.modalViewKey) {
@@ -65,7 +110,7 @@ ruleTester.run('newline-after-import', require('rules/newline-after-import'), {
           return somethingElse();
       }
     }`,
-      parserOptions: { ecmaVersion: 2015, sourceType: 'module' },
+    parserOptions: { ecmaVersion: 2015, sourceType: 'module' },
     },
     {
       code: `import path from 'path';\nimport foo from 'foo';\n`,
@@ -86,12 +131,52 @@ ruleTester.run('newline-after-import', require('rules/newline-after-import'), {
     {
       code: `import foo from 'foo';\n\n\nvar bar = 'bar';`,
       parserOptions: { ecmaVersion: 2015, sourceType: 'module' },
-      options: [{ 'count': 2 }],
+      options: [{ count: 2 }],
+    },
+    {
+      code: `import foo from 'foo';\n\n\nvar bar = 'bar';`,
+      parserOptions: { ecmaVersion: 2015, sourceType: 'module' },
+      options: [{ count: 2, exactCount: true }],
+    },
+    {
+      code: `import foo from 'foo';\n\nvar bar = 'bar';`,
+      parserOptions: { ecmaVersion: 2015, sourceType: 'module' },
+      options: [{ count: 1, exactCount: true }],
+    },
+    {
+      code: `import foo from 'foo';\n\n// Some random comment\nvar bar = 'bar';`,
+      parserOptions: { ecmaVersion: 2015, sourceType: 'module' },
+      options: [{ count: 2, exactCount: true }],
+    },
+    {
+      code: `import foo from 'foo';\n// Some random comment\nvar bar = 'bar';`,
+      parserOptions: { ecmaVersion: 2015, sourceType: 'module' },
+      options: [{ count: 1, exactCount: true }],
+    },
+    {
+      code: `import foo from 'foo';\n\n\n// Some random comment\nvar bar = 'bar';`,
+      parserOptions: { ecmaVersion: 2015, sourceType: 'module' },
+      options: [{ count: 2, exactCount: true, considerComments: true }],
+    },
+    {
+      code: `import foo from 'foo';\n\n// Some random comment\nvar bar = 'bar';`,
+      parserOptions: { ecmaVersion: 2015, sourceType: 'module' },
+      options: [{ count: 1, exactCount: true, considerComments: true }],
+    },
+    {
+      code: `/**\n * A leading comment\n */\nimport foo from 'foo';\n\n// Some random comment\nexport {foo};`,
+      parserOptions: { ecmaVersion: 2015, sourceType: 'module' },
+      options: [{ count: 2, exactCount: true }],
+    },
+    {
+      code: `import foo from 'foo';\n\n\nvar bar = 'bar';`,
+      parserOptions: { ecmaVersion: 2015, sourceType: 'module' },
+      options: [{ count: 1 }],
     },
     {
       code: `import foo from 'foo';\n\n\n\n\nvar bar = 'bar';`,
       parserOptions: { ecmaVersion: 2015, sourceType: 'module' },
-      options: [{ 'count': 4 }],
+      options: [{ count: 4 }],
     },
     {
       code: `var foo = require('foo-module');\n\nvar foo = 'bar';`,
@@ -100,12 +185,27 @@ ruleTester.run('newline-after-import', require('rules/newline-after-import'), {
     {
       code: `var foo = require('foo-module');\n\n\nvar foo = 'bar';`,
       parserOptions: { ecmaVersion: 2015, sourceType: 'module' },
-      options: [{ 'count': 2 }],
+      options: [{ count: 2 }],
     },
     {
       code: `var foo = require('foo-module');\n\n\n\n\nvar foo = 'bar';`,
       parserOptions: { ecmaVersion: 2015, sourceType: 'module' },
-      options: [{ 'count': 4 }],
+      options: [{ count: 4 }],
+    },
+    {
+      code: `var foo = require('foo-module');\n\n\n\n\nvar foo = 'bar';`,
+      parserOptions: { ecmaVersion: 2015, sourceType: 'module' },
+      options: [{ count: 4, exactCount: true }],
+    },
+    {
+      code: `var foo = require('foo-module');\n\n// Some random comment\n\n\nvar foo = 'bar';`,
+      parserOptions: { ecmaVersion: 2015, sourceType: 'module' },
+      options: [{ count: 4, exactCount: true }],
+    },
+    {
+      code: `var foo = require('foo-module');\n\n\n\n\n// Some random comment\nvar foo = 'bar';`,
+      parserOptions: { ecmaVersion: 2015, sourceType: 'module' },
+      options: [{ count: 4, exactCount: true, considerComments: true }],
     },
     {
       code: `require('foo-module');\n\nvar foo = 'bar';`,
@@ -158,82 +258,285 @@ ruleTester.run('newline-after-import', require('rules/newline-after-import'), {
         class App {}
       `,
       parserOptions: { ecmaVersion: 2015, sourceType: 'module' },
-      parser: require.resolve('babel-eslint'),
+      parser: parsers.BABEL_OLD,
     },
     {
       code: `var foo = require('foo');\n\n@SomeDecorator(foo)\nclass Foo {}`,
       parserOptions: { ecmaVersion: 2015, sourceType: 'module' },
-      parser: require.resolve('babel-eslint'),
+      parser: parsers.BABEL_OLD,
+    },
+    {
+      code: `// issue 1004\nimport foo from 'foo';\n\n@SomeDecorator(foo)\nexport default class Test {}`,
+      parserOptions: { sourceType: 'module' },
+      parser: parsers.BABEL_OLD,
+    },
+    {
+      code: `// issue 1004\nconst foo = require('foo');\n\n@SomeDecorator(foo)\nexport default class Test {}`,
+      parserOptions: { sourceType: 'module' },
+      parser: parsers.BABEL_OLD,
     },
-  ],
+    flatMap(getTSParsers(), (parser) => [].concat(
+      {
+        code: `
+          import { ExecaReturnValue } from 'execa';
+          import execa = require('execa');
+        `,
+        parser,
+        parserOptions: { ecmaVersion: 2015, sourceType: 'module' },
+      },
+      {
+        code: `
+          import execa = require('execa');
+          import { ExecaReturnValue } from 'execa';
+        `,
+        parser,
+        parserOptions: { ecmaVersion: 2015, sourceType: 'module' },
+      },
+      {
+        code: `
+          import { ExecaReturnValue } from 'execa';
+          import execa = require('execa');
+          import { ExecbReturnValue } from 'execb';
+        `,
+        parser,
+        parserOptions: { ecmaVersion: 2015, sourceType: 'module' },
+      },
+      {
+        code: `
+          import execa = require('execa');
+          import { ExecaReturnValue } from 'execa';
+          import execb = require('execb');
+        `,
+        parser,
+        parserOptions: { ecmaVersion: 2015, sourceType: 'module' },
+      },
+      parser !== parsers.TS_OLD || semver.satisfies(tsEslintVersion, '>= 22') ? {
+        code: `
+          export import a = obj;\nf(a);
+        `,
+        parser,
+        parserOptions: { ecmaVersion: 2015, sourceType: 'module' },
+      } : [],
+      parser !== parsers.TS_OLD || semver.satisfies(tsEslintVersion, '>= 22') ? {
+        code: `
+          import { a } from "./a";
 
-  invalid: [
+          export namespace SomeNamespace {
+              export import a2 = a;
+              f(a);
+          }`,
+        parser,
+        parserOptions: { ecmaVersion: 2015, sourceType: 'module' },
+      } : [],
+      {
+        code: `
+          import stub from './stub';
+
+          export {
+              stub
+          }
+        `,
+        parser,
+        parserOptions: { ecmaVersion: 2015, sourceType: 'module' },
+      },
+      {
+        code: `
+        import { ns } from 'namespace';
+        import Bar = ns.baz.foo.Bar;
+
+        export import Foo = ns.baz.bar.Foo;
+      `,
+        parser,
+        parserOptions: { ecmaVersion: 2015, sourceType: 'module' },
+      },
+    )),
+    {
+      code: `
+        import stub from './stub';
+
+        export {
+            stub
+        }
+      `,
+      parserOptions: { ecmaVersion: 2015, sourceType: 'module' },
+    },
+    {
+      code: `
+        import path from 'path';
+        import foo from 'foo';
+        /**
+         * some multiline comment here
+         * another line of comment
+        **/
+        var bar = 42;
+      `,
+      parserOptions: { ecmaVersion: 2015, sourceType: 'module' },
+    },
+    {
+      code: `
+        import path from 'path';import foo from 'foo';
+
+        /**
+         * some multiline comment here
+         * another line of comment
+        **/
+        var bar = 42;
+      `,
+      parserOptions: { ecmaVersion: 2015, sourceType: 'module' },
+      options: [{ considerComments: true }],
+    },
+    {
+      code: `
+        import path from 'path';
+        import foo from 'foo';
+
+        // Some random single line comment
+        var bar = 42;
+      `,
+      parserOptions: { ecmaVersion: 2015, sourceType: 'module' },
+    },
+    {
+      code: `var foo = require('foo-module');\n\n\n// Some random comment\nvar foo = 'bar';`,
+      options: [{ count: 2, considerComments: true }],
+    },
+    {
+      code: `var foo = require('foo-module');\n\n\n/**\n * Test comment\n */\nvar foo = 'bar';`,
+      options: [{ count: 2, considerComments: true }],
+    },
+    {
+      code: `const foo = require('foo');\n\n\n// some random comment\nconst bar = function() {};`,
+      options: [{ count: 2, exactCount: true, considerComments: true }],
+      parserOptions: { ecmaVersion: 2015 },
+    },
+  ),
+
+  invalid: [].concat(
+    {
+      code: `
+        import { A, B, C, D } from
+        '../path/to/my/module/in/very/far/directory'
+        // some comment
+        var foo = 'bar';
+      `,
+      output: `
+        import { A, B, C, D } from
+        '../path/to/my/module/in/very/far/directory'
+
+        // some comment
+        var foo = 'bar';
+      `,
+      errors: [{
+        line: 3,
+        column: 1,
+        message: IMPORT_ERROR_MESSAGE,
+      }],
+      parserOptions: { ecmaVersion: 2015, sourceType: 'module' },
+      options: [{ considerComments: true }],
+    },
+    {
+      code: `
+        import path from 'path';
+        import foo from 'foo';
+        /**
+         * some multiline comment here
+         * another line of comment
+        **/
+        var bar = 42;
+      `,
+      output: `
+        import path from 'path';
+        import foo from 'foo';\n
+        /**
+         * some multiline comment here
+         * another line of comment
+        **/
+        var bar = 42;
+      `,
+      errors: [{
+        line: 3,
+        column: 9,
+        message: IMPORT_ERROR_MESSAGE,
+      }],
+      parserOptions: { ecmaVersion: 2015, sourceType: 'module' },
+      options: [{ considerComments: true }],
+    },
+    {
+      code: `
+        import path from 'path';
+        import foo from 'foo';
+        // Some random single line comment
+        var bar = 42;
+      `,
+      output: `
+        import path from 'path';
+        import foo from 'foo';\n
+        // Some random single line comment
+        var bar = 42;
+      `,
+      errors: [{
+        line: 3,
+        column: 9,
+        message: IMPORT_ERROR_MESSAGE,
+      }],
+      parserOptions: { ecmaVersion: 2015, sourceType: 'module' },
+      options: [{ considerComments: true, count: 1 }],
+    },
     {
       code: `import foo from 'foo';\nexport default function() {};`,
       output: `import foo from 'foo';\n\nexport default function() {};`,
-      errors: [ {
+      errors: [{
         line: 1,
         column: 1,
         message: IMPORT_ERROR_MESSAGE,
-      } ],
+      }],
       parserOptions: { ecmaVersion: 2015, sourceType: 'module' },
     },
     {
       code: `import foo from 'foo';\n\nexport default function() {};`,
       output: `import foo from 'foo';\n\n\nexport default function() {};`,
-      options: [{ 'count': 2 }],
-      errors: [ {
+      options: [{ count: 2 }],
+      errors: [{
         line: 1,
         column: 1,
         message: IMPORT_ERROR_MESSAGE_MULTIPLE(2),
-      } ],
+      }],
       parserOptions: { ecmaVersion: 2015, sourceType: 'module' },
     },
     {
       code: `var foo = require('foo-module');\nvar something = 123;`,
       output: `var foo = require('foo-module');\n\nvar something = 123;`,
-      errors: [ {
+      errors: [{
         line: 1,
         column: 1,
         message: REQUIRE_ERROR_MESSAGE,
-      } ],
+      }],
       parserOptions: { ecmaVersion: 2015, sourceType: 'module' },
     },
     {
       code: `import foo from 'foo';\nexport default function() {};`,
       output: `import foo from 'foo';\n\nexport default function() {};`,
-      options: [{ 'count': 1 }],
-      errors: [ {
+      options: [{ count: 1 }],
+      errors: [{
         line: 1,
         column: 1,
         message: IMPORT_ERROR_MESSAGE,
-      } ],
-      parserOptions: { ecmaVersion: 2015, sourceType: 'module' },
-    },
-    {
-      code: `var foo = require('foo-module');\nvar something = 123;`,
-      output: `var foo = require('foo-module');\n\nvar something = 123;`,
-      errors: [ {
-        line: 1,
-        column: 1,
-        message: REQUIRE_ERROR_MESSAGE,
-      } ],
+      }],
       parserOptions: { ecmaVersion: 2015, sourceType: 'module' },
     },
     {
       code: `import foo from 'foo';\nvar a = 123;\n\nimport { bar } from './bar-lib';\nvar b=456;`,
       output: `import foo from 'foo';\n\nvar a = 123;\n\nimport { bar } from './bar-lib';\n\nvar b=456;`,
       errors: [
-      {
-        line: 1,
-        column: 1,
-        message: IMPORT_ERROR_MESSAGE,
-      },
-      {
-        line: 4,
-        column: 1,
-        message: IMPORT_ERROR_MESSAGE,
-      }],
+        {
+          line: 1,
+          column: 1,
+          message: IMPORT_ERROR_MESSAGE,
+        },
+        {
+          line: 4,
+          column: 1,
+          message: IMPORT_ERROR_MESSAGE,
+        }],
       parserOptions: { ecmaVersion: 2015, sourceType: 'module' },
     },
     {
@@ -271,20 +574,20 @@ ruleTester.run('newline-after-import', require('rules/newline-after-import'), {
     {
       code: `var path = require('path');\nvar foo = require('foo');\nvar bar = 42;`,
       output: `var path = require('path');\nvar foo = require('foo');\n\nvar bar = 42;`,
-      errors: [ {
+      errors: [{
         line: 2,
         column: 1,
         message: REQUIRE_ERROR_MESSAGE,
-      } ],
+      }],
     },
     {
       code: `var assign = Object.assign || require('object-assign');\nvar foo = require('foo');\nvar bar = 42;`,
       output: `var assign = Object.assign || require('object-assign');\nvar foo = require('foo');\n\nvar bar = 42;`,
-      errors: [ {
+      errors: [{
         line: 2,
         column: 1,
         message: REQUIRE_ERROR_MESSAGE,
-      } ],
+      }],
     },
     {
       code: `require('a');\nfoo(require('b'), require('c'), require('d'));\nrequire('d');\nvar foo = 'bar';`,
@@ -311,44 +614,264 @@ ruleTester.run('newline-after-import', require('rules/newline-after-import'), {
     {
       code: `import path from 'path';\nimport foo from 'foo';\nvar bar = 42;`,
       output: `import path from 'path';\nimport foo from 'foo';\n\nvar bar = 42;`,
-      errors: [ {
+      errors: [{
         line: 2,
         column: 1,
         message: IMPORT_ERROR_MESSAGE,
-      } ],
+      }],
       parserOptions: { ecmaVersion: 2015, sourceType: 'module' },
     },
     {
       code: `import path from 'path';import foo from 'foo';var bar = 42;`,
       output: `import path from 'path';import foo from 'foo';\n\nvar bar = 42;`,
-      errors: [ {
+      errors: [{
         line: 1,
         column: 25,
         message: IMPORT_ERROR_MESSAGE,
-      } ],
+      }],
       parserOptions: { ecmaVersion: 2015, sourceType: 'module' },
     },
     {
       code: `import foo from 'foo';\n@SomeDecorator(foo)\nclass Foo {}`,
       output: `import foo from 'foo';\n\n@SomeDecorator(foo)\nclass Foo {}`,
-      errors: [ {
+      errors: [{
         line: 1,
         column: 1,
         message: IMPORT_ERROR_MESSAGE,
-      } ],
+      }],
       parserOptions: { ecmaVersion: 2015, sourceType: 'module' },
-      parser: require.resolve('babel-eslint'),
+      parser: parsers.BABEL_OLD,
     },
     {
       code: `var foo = require('foo');\n@SomeDecorator(foo)\nclass Foo {}`,
       output: `var foo = require('foo');\n\n@SomeDecorator(foo)\nclass Foo {}`,
-      errors: [ {
+      errors: [{
         line: 1,
         column: 1,
         message: REQUIRE_ERROR_MESSAGE,
-      } ],
+      }],
+      parserOptions: { ecmaVersion: 2015, sourceType: 'module' },
+      parser: parsers.BABEL_OLD,
+    },
+    {
+      code: `// issue 10042\nimport foo from 'foo';\n@SomeDecorator(foo)\nexport default class Test {}`,
+      output: `// issue 10042\nimport foo from 'foo';\n\n@SomeDecorator(foo)\nexport default class Test {}`,
+      errors: [{
+        line: 2,
+        column: 1,
+        message: IMPORT_ERROR_MESSAGE,
+      }],
+      parserOptions: { sourceType: 'module' },
+      parser: parsers.BABEL_OLD,
+    },
+    {
+      code: `// issue 1004\nconst foo = require('foo');\n@SomeDecorator(foo)\nexport default class Test {}`,
+      output: `// issue 1004\nconst foo = require('foo');\n\n@SomeDecorator(foo)\nexport default class Test {}`,
+      errors: [{
+        line: 2,
+        column: 1,
+        message: REQUIRE_ERROR_MESSAGE,
+      }],
+      parserOptions: { sourceType: 'module' },
+      parser: parsers.BABEL_OLD,
+    },
+    testVersion('>= 6', () => ({
+      code: `
+        // issue 1784
+        import { map } from 'rxjs/operators';
+        @Component({})
+        export class Test {}
+      `,
+      output: `
+        // issue 1784
+        import { map } from 'rxjs/operators';
+
+        @Component({})
+        export class Test {}
+      `,
+      errors: [
+        {
+          line: 3,
+          column: 9,
+          message: IMPORT_ERROR_MESSAGE,
+        },
+      ],
+      parserOptions: { sourceType: 'module' },
+      parser: parsers.BABEL_OLD,
+    })) || [],
+    {
+      code: `import foo from 'foo';\n\nexport default function() {};`,
+      output: `import foo from 'foo';\n\n\nexport default function() {};`,
+      options: [{ count: 2, exactCount: true }],
+      errors: [{
+        line: 1,
+        column: 1,
+        message: IMPORT_ERROR_MESSAGE_MULTIPLE(2),
+      }],
+      parserOptions: { ecmaVersion: 2015, sourceType: 'module' },
+    },
+    withoutAutofixOutput({
+      code: `import foo from 'foo';\n\n\n\nexport default function() {};`,
+      options: [{ count: 2, exactCount: true }],
+      errors: [{
+        line: 1,
+        column: 1,
+        message: IMPORT_ERROR_MESSAGE_MULTIPLE(2),
+      }],
+      parserOptions: { ecmaVersion: 2015, sourceType: 'module' },
+    }),
+    withoutAutofixOutput({
+      code: `import foo from 'foo';\n\n\n\n\nexport default function() {};`,
+      options: [{ count: 2, exactCount: true }],
+      errors: [{
+        line: 1,
+        column: 1,
+        message: IMPORT_ERROR_MESSAGE_MULTIPLE(2),
+      }],
+      parserOptions: { ecmaVersion: 2015, sourceType: 'module' },
+    }),
+    {
+      code: `import foo from 'foo';\n// some random comment\nexport default function() {};`,
+      output: `import foo from 'foo';\n\n// some random comment\nexport default function() {};`,
+      options: [{ count: 2, exactCount: true }],
+      errors: [{
+        line: 1,
+        column: 1,
+        message: IMPORT_ERROR_MESSAGE_MULTIPLE(2),
+      }],
+      parserOptions: { ecmaVersion: 2015, sourceType: 'module' },
+    },
+    withoutAutofixOutput({
+      code: `import foo from 'foo';\n// some random comment\n\n\nexport default function() {};`,
+      options: [{ count: 2, exactCount: true }],
+      errors: [{
+        line: 1,
+        column: 1,
+        message: IMPORT_ERROR_MESSAGE_MULTIPLE(2),
+      }],
+      parserOptions: { ecmaVersion: 2015, sourceType: 'module' },
+    }),
+    withoutAutofixOutput({
+      code: `import foo from 'foo';\n// some random comment\n\n\n\nexport default function() {};`,
+      options: [{ count: 2, exactCount: true }],
+      errors: [{
+        line: 1,
+        column: 1,
+        message: IMPORT_ERROR_MESSAGE_MULTIPLE(2),
+      }],
+      parserOptions: { ecmaVersion: 2015, sourceType: 'module' },
+    }),
+    {
+      code: `import foo from 'foo';\n// some random comment\nexport default function() {};`,
+      output: `import foo from 'foo';\n\n\n// some random comment\nexport default function() {};`,
+      options: [{ count: 2, exactCount: true, considerComments: true }],
+      errors: [{
+        line: 1,
+        column: 1,
+        message: IMPORT_ERROR_MESSAGE_MULTIPLE(2),
+      }],
+      parserOptions: { ecmaVersion: 2015, sourceType: 'module' },
+    },
+    {
+      code: `import foo from 'foo';\n\n// some random comment\nexport default function() {};`,
+      output: `import foo from 'foo';\n\n\n// some random comment\nexport default function() {};`,
+      options: [{ count: 2, exactCount: true, considerComments: true }],
+      errors: [{
+        line: 1,
+        column: 1,
+        message: IMPORT_ERROR_MESSAGE_MULTIPLE(2),
+      }],
       parserOptions: { ecmaVersion: 2015, sourceType: 'module' },
-      parser: require.resolve('babel-eslint'),
     },
-  ],
-})
+    withoutAutofixOutput({
+      code: `import foo from 'foo';\n\n\n\n// some random comment\nexport default function() {};`,
+      options: [{ count: 2, exactCount: true, considerComments: true }],
+      errors: [{
+        line: 1,
+        column: 1,
+        message: IMPORT_ERROR_MESSAGE_MULTIPLE(2),
+      }],
+      parserOptions: { ecmaVersion: 2015, sourceType: 'module' },
+    }),
+    withoutAutofixOutput({
+      code: `
+        import foo from 'foo';
+
+
+        // Some random single line comment
+        var bar = 42;
+      `,
+      errors: [{
+        line: 2,
+        column: 9,
+        message: IMPORT_ERROR_MESSAGE,
+      }],
+      parserOptions: { ecmaVersion: 2015, sourceType: 'module' },
+      options: [{ considerComments: true, count: 1, exactCount: true }],
+    }),
+    {
+      code: `import foo from 'foo';export default function() {};`,
+      output: `import foo from 'foo';\n\nexport default function() {};`,
+      options: [{ count: 1, exactCount: true }],
+      errors: [{
+        line: 1,
+        column: 1,
+        message: IMPORT_ERROR_MESSAGE,
+      }],
+      parserOptions: { ecmaVersion: 2015, sourceType: 'module' },
+    },
+    withoutAutofixOutput({
+      code: `const foo = require('foo');\n\n\n\nconst bar = function() {};`,
+      options: [{ count: 2, exactCount: true }],
+      errors: [{
+        line: 1,
+        column: 1,
+        message: REQUIRE_ERROR_MESSAGE_MULTIPLE(2),
+      }],
+      parserOptions: { ecmaVersion: 2015 },
+    }),
+    withoutAutofixOutput({
+      code: `const foo = require('foo');\n\n\n\n// some random comment\nconst bar = function() {};`,
+      options: [{ count: 2, exactCount: true }],
+      errors: [{
+        line: 1,
+        column: 1,
+        message: REQUIRE_ERROR_MESSAGE_MULTIPLE(2),
+      }],
+      parserOptions: { ecmaVersion: 2015 },
+    }),
+    {
+      code: `import foo from 'foo';// some random comment\nexport default function() {};`,
+      output: `import foo from 'foo';\n\n// some random comment\nexport default function() {};`,
+      options: [{ count: 1, exactCount: true, considerComments: true }],
+      errors: [{
+        line: 1,
+        column: 1,
+        message: IMPORT_ERROR_MESSAGE,
+      }],
+      parserOptions: { ecmaVersion: 2015, considerComments: true, sourceType: 'module' },
+    },
+    {
+      code: `var foo = require('foo-module');\nvar foo = require('foo-module');\n\n// Some random comment\nvar foo = 'bar';`,
+      output: `var foo = require('foo-module');\nvar foo = require('foo-module');\n\n\n// Some random comment\nvar foo = 'bar';`,
+      errors: [{
+        line: 2,
+        column: 1,
+        message: REQUIRE_ERROR_MESSAGE_MULTIPLE(2),
+      }],
+      parserOptions: { ecmaVersion: 2015, sourceType: 'module' },
+      options: [{ considerComments: true, count: 2 }],
+    },
+    {
+      code: `var foo = require('foo-module');\n\n/**\n * Test comment\n */\nvar foo = 'bar';`,
+      output: `var foo = require('foo-module');\n\n\n/**\n * Test comment\n */\nvar foo = 'bar';`,
+      errors: [{
+        line: 1,
+        column: 1,
+        message: REQUIRE_ERROR_MESSAGE_MULTIPLE(2),
+      }],
+      parserOptions: { ecmaVersion: 2015 },
+      options: [{ considerComments: true, count: 2 }],
+    },
+  ),
+});
diff --git a/tests/src/rules/no-absolute-path.js b/tests/src/rules/no-absolute-path.js
index 8689997b4..bcf215137 100644
--- a/tests/src/rules/no-absolute-path.js
+++ b/tests/src/rules/no-absolute-path.js
@@ -1,31 +1,30 @@
-import { test } from '../utils'
+import { test } from '../utils';
 
-import { RuleTester } from 'eslint'
+import { RuleTester } from '../rule-tester';
 
-const ruleTester = new RuleTester()
-    , rule = require('rules/no-absolute-path')
+const ruleTester = new RuleTester();
+const rule = require('rules/no-absolute-path');
 
 const error = {
-  ruleId: 'no-absolute-path',
   message: 'Do not import modules using an absolute path',
-}
+};
 
 ruleTester.run('no-absolute-path', rule, {
   valid: [
-    test({ code: 'import _ from "lodash"'}),
-    test({ code: 'import find from "lodash.find"'}),
-    test({ code: 'import foo from "./foo"'}),
-    test({ code: 'import foo from "../foo"'}),
-    test({ code: 'import foo from "foo"'}),
-    test({ code: 'import foo from "./"'}),
-    test({ code: 'import foo from "@scope/foo"'}),
-    test({ code: 'var _ = require("lodash")'}),
-    test({ code: 'var find = require("lodash.find")'}),
-    test({ code: 'var foo = require("./foo")'}),
-    test({ code: 'var foo = require("../foo")'}),
-    test({ code: 'var foo = require("foo")'}),
-    test({ code: 'var foo = require("./")'}),
-    test({ code: 'var foo = require("@scope/foo")'}),
+    test({ code: 'import _ from "lodash"' }),
+    test({ code: 'import find from "lodash.find"' }),
+    test({ code: 'import foo from "./foo"' }),
+    test({ code: 'import foo from "../foo"' }),
+    test({ code: 'import foo from "foo"' }),
+    test({ code: 'import foo from "./"' }),
+    test({ code: 'import foo from "@scope/foo"' }),
+    test({ code: 'var _ = require("lodash")' }),
+    test({ code: 'var find = require("lodash.find")' }),
+    test({ code: 'var foo = require("./foo")' }),
+    test({ code: 'var foo = require("../foo")' }),
+    test({ code: 'var foo = require("foo")' }),
+    test({ code: 'var foo = require("./")' }),
+    test({ code: 'var foo = require("@scope/foo")' }),
 
     test({ code: 'import events from "events"' }),
     test({ code: 'import path from "path"' }),
@@ -54,48 +53,74 @@ ruleTester.run('no-absolute-path', rule, {
   invalid: [
     test({
       code: 'import f from "/foo"',
+      filename: '/foo/bar/index.js',
       errors: [error],
+      output: 'import f from ".."',
+    }),
+    test({
+      code: 'import f from "/foo/bar/baz.js"',
+      filename: '/foo/bar/index.js',
+      errors: [error],
+      output: 'import f from "./baz.js"',
     }),
     test({
       code: 'import f from "/foo/path"',
+      filename: '/foo/bar/index.js',
       errors: [error],
+      output: 'import f from "../path"',
     }),
     test({
       code: 'import f from "/some/path"',
+      filename: '/foo/bar/index.js',
       errors: [error],
+      output: 'import f from "../../some/path"',
     }),
     test({
       code: 'import f from "/some/path"',
+      filename: '/foo/bar/index.js',
       options: [{ amd: true }],
       errors: [error],
+      output: 'import f from "../../some/path"',
     }),
     test({
       code: 'var f = require("/foo")',
+      filename: '/foo/bar/index.js',
       errors: [error],
+      output: 'var f = require("..")',
     }),
     test({
       code: 'var f = require("/foo/path")',
+      filename: '/foo/bar/index.js',
       errors: [error],
+      output: 'var f = require("../path")',
     }),
     test({
       code: 'var f = require("/some/path")',
+      filename: '/foo/bar/index.js',
       errors: [error],
+      output: 'var f = require("../../some/path")',
     }),
     test({
       code: 'var f = require("/some/path")',
+      filename: '/foo/bar/index.js',
       options: [{ amd: true }],
       errors: [error],
+      output: 'var f = require("../../some/path")',
     }),
     // validate amd
     test({
       code: 'require(["/some/path"], function (f) { /* ... */ })',
+      filename: '/foo/bar/index.js',
       options: [{ amd: true }],
       errors: [error],
+      output: 'require(["../../some/path"], function (f) { /* ... */ })',
     }),
     test({
       code: 'define(["/some/path"], function (f) { /* ... */ })',
+      filename: '/foo/bar/index.js',
       options: [{ amd: true }],
       errors: [error],
+      output: 'define(["../../some/path"], function (f) { /* ... */ })',
     }),
   ],
-})
+});
diff --git a/tests/src/rules/no-amd.js b/tests/src/rules/no-amd.js
index 62de5ac26..6b66578df 100644
--- a/tests/src/rules/no-amd.js
+++ b/tests/src/rules/no-amd.js
@@ -1,8 +1,8 @@
-import { RuleTester } from 'eslint'
-import eslintPkg from 'eslint/package.json'
-import semver from 'semver'
+import { RuleTester } from '../rule-tester';
+import eslintPkg from 'eslint/package.json';
+import semver from 'semver';
 
-var ruleTester = new RuleTester()
+const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2015, sourceType: 'module' } });
 
 ruleTester.run('no-amd', require('rules/no-amd'), {
   valid: [
@@ -12,7 +12,7 @@ ruleTester.run('no-amd', require('rules/no-amd'), {
 
     'require("x")',
     // 2-args, not an array
-		'require("x", "y")',
+    'require("x", "y")',
     // random other function
     'setTimeout(foo, 100)',
     // non-identifier callee
@@ -25,13 +25,13 @@ ruleTester.run('no-amd', require('rules/no-amd'), {
     // unmatched arg types/number
     'define(0, 1, 2)',
     'define("a")',
-	],
+  ],
 
-	invalid: semver.satisfies(eslintPkg.version, '< 4.0.0') ? [] : [
-      { code: 'define([], function() {})', errors: [ { message: 'Expected imports instead of AMD define().' }] },
-      { code: 'define(["a"], function(a) { console.log(a); })', errors: [ { message: 'Expected imports instead of AMD define().' }] },
+  invalid: semver.satisfies(eslintPkg.version, '< 4.0.0') ? [] : [
+    { code: 'define([], function() {})', errors: [{ message: 'Expected imports instead of AMD define().' }] },
+    { code: 'define(["a"], function(a) { console.log(a); })', errors: [{ message: 'Expected imports instead of AMD define().' }] },
 
-      { code: 'require([], function() {})', errors: [ { message: 'Expected imports instead of AMD require().' }] },
-      { code: 'require(["a"], function(a) { console.log(a); })', errors: [ { message: 'Expected imports instead of AMD require().' }] },
-	],
-})
+    { code: 'require([], function() {})', errors: [{ message: 'Expected imports instead of AMD require().' }] },
+    { code: 'require(["a"], function(a) { console.log(a); })', errors: [{ message: 'Expected imports instead of AMD require().' }] },
+  ],
+});
diff --git a/tests/src/rules/no-anonymous-default-export.js b/tests/src/rules/no-anonymous-default-export.js
index c872cf4d0..37b3009f0 100644
--- a/tests/src/rules/no-anonymous-default-export.js
+++ b/tests/src/rules/no-anonymous-default-export.js
@@ -1,55 +1,62 @@
-import { test, SYNTAX_CASES } from '../utils'
+import { test, testVersion, SYNTAX_CASES } from '../utils';
 
-import { RuleTester } from 'eslint'
+import { RuleTester } from '../rule-tester';
 
-const ruleTester = new RuleTester()
-const rule = require('rules/no-anonymous-default-export')
+const ruleTester = new RuleTester();
+const rule = require('rules/no-anonymous-default-export');
 
 ruleTester.run('no-anonymous-default-export', rule, {
-    valid: [
-      // Exports with identifiers are valid
-      test({ code: 'const foo = 123\nexport default foo' }),
-      test({ code: 'export default function foo() {}'}),
-      test({ code: 'export default class MyClass {}'}),
-
-      // Allow each forbidden type with appropriate option
-      test({ code: 'export default []', options: [{ allowArray: true }] }),
-      test({ code: 'export default () => {}', options: [{ allowArrowFunction: true }] }),
-      test({ code: 'export default class {}', options: [{ allowAnonymousClass: true }] }),
-      test({ code: 'export default function() {}', options: [{ allowAnonymousFunction: true }] }),
-      test({ code: 'export default 123', options: [{ allowLiteral: true }] }),
-      test({ code: 'export default \'foo\'', options: [{ allowLiteral: true }] }),
-      test({ code: 'export default `foo`', options: [{ allowLiteral: true }] }),
-      test({ code: 'export default {}', options: [{ allowObject: true }] }),
-      test({ code: 'export default foo(bar)', options: [{ allowCallExpression: true }] }),
-
-      // Allow forbidden types with multiple options
-      test({ code: 'export default 123', options: [{ allowLiteral: true, allowObject: true }] }),
-      test({ code: 'export default {}', options: [{ allowLiteral: true, allowObject: true }] }),
-
-      // Sanity check unrelated export syntaxes
-      test({ code: 'export * from \'foo\'' }),
-      test({ code: 'const foo = 123\nexport { foo }' }),
-      test({ code: 'const foo = 123\nexport { foo as default }' }),
-
-      // Allow call expressions by default for backwards compatibility
-      test({ code: 'export default foo(bar)' }),
-
-      ...SYNTAX_CASES,
-    ],
-
-    invalid: [
-      test({ code: 'export default []', errors: [{ message: 'Assign array to a variable before exporting as module default' }] }),
-      test({ code: 'export default () => {}', errors: [{ message: 'Assign arrow function to a variable before exporting as module default' }] }),
-      test({ code: 'export default class {}', errors: [{ message: 'Unexpected default export of anonymous class' }] }),
-      test({ code: 'export default function() {}', errors: [{ message: 'Unexpected default export of anonymous function' }] }),
-      test({ code: 'export default 123', errors: [{ message: 'Assign literal to a variable before exporting as module default' }] }),
-      test({ code: 'export default \'foo\'', errors: [{ message: 'Assign literal to a variable before exporting as module default' }] }),
-      test({ code: 'export default `foo`', errors: [{ message: 'Assign literal to a variable before exporting as module default' }] }),
-      test({ code: 'export default {}', errors: [{ message: 'Assign object to a variable before exporting as module default' }] }),
-      test({ code: 'export default foo(bar)', options: [{ allowCallExpression: false }], errors: [{ message: 'Assign call result to a variable before exporting as module default' }] }),
-
-      // Test failure with non-covering exception
-      test({ code: 'export default 123', options: [{ allowObject: true }], errors: [{ message: 'Assign literal to a variable before exporting as module default' }] }),
-    ],
-})
+  valid: [].concat(
+    // Exports with identifiers are valid
+    test({ code: 'const foo = 123\nexport default foo' }),
+    test({ code: 'export default function foo() {}' }),
+    test({ code: 'export default class MyClass {}' }),
+
+    // Allow each forbidden type with appropriate option
+    test({ code: 'export default []', options: [{ allowArray: true }] }),
+    test({ code: 'export default () => {}', options: [{ allowArrowFunction: true }] }),
+    test({ code: 'export default class {}', options: [{ allowAnonymousClass: true }] }),
+    test({ code: 'export default function() {}', options: [{ allowAnonymousFunction: true }] }),
+    test({ code: 'export default 123', options: [{ allowLiteral: true }] }),
+    test({ code: 'export default \'foo\'', options: [{ allowLiteral: true }] }),
+    test({ code: 'export default `foo`', options: [{ allowLiteral: true }] }),
+    test({ code: 'export default {}', options: [{ allowObject: true }] }),
+    test({ code: 'export default foo(bar)', options: [{ allowCallExpression: true }] }),
+    test({ code: 'export default new Foo()', options: [{ allowNew: true }] }),
+
+    // Allow forbidden types with multiple options
+    test({ code: 'export default 123', options: [{ allowLiteral: true, allowObject: true }] }),
+    test({ code: 'export default {}', options: [{ allowLiteral: true, allowObject: true }] }),
+
+    // Sanity check unrelated export syntaxes
+    test({ code: 'export * from \'foo\'' }),
+    test({ code: 'const foo = 123\nexport { foo }' }),
+    test({ code: 'const foo = 123\nexport { foo as default }' }),
+    // es2022: Arbitrary module namespace identifier names
+    testVersion('>= 8.7', () => ({
+      code: 'const foo = 123\nexport { foo as "default" }',
+      parserOptions: { ecmaVersion: 2022 },
+    })),
+
+    // Allow call expressions by default for backwards compatibility
+    test({ code: 'export default foo(bar)' }),
+
+    ...SYNTAX_CASES,
+  ),
+
+  invalid: [
+    test({ code: 'export default []', errors: [{ message: 'Assign array to a variable before exporting as module default' }] }),
+    test({ code: 'export default () => {}', errors: [{ message: 'Assign arrow function to a variable before exporting as module default' }] }),
+    test({ code: 'export default class {}', errors: [{ message: 'Unexpected default export of anonymous class' }] }),
+    test({ code: 'export default function() {}', errors: [{ message: 'Unexpected default export of anonymous function' }] }),
+    test({ code: 'export default 123', errors: [{ message: 'Assign literal to a variable before exporting as module default' }] }),
+    test({ code: 'export default \'foo\'', errors: [{ message: 'Assign literal to a variable before exporting as module default' }] }),
+    test({ code: 'export default `foo`', errors: [{ message: 'Assign literal to a variable before exporting as module default' }] }),
+    test({ code: 'export default {}', errors: [{ message: 'Assign object to a variable before exporting as module default' }] }),
+    test({ code: 'export default foo(bar)', options: [{ allowCallExpression: false }], errors: [{ message: 'Assign call result to a variable before exporting as module default' }] }),
+    test({ code: 'export default new Foo()', errors: [{ message: 'Assign instance to a variable before exporting as module default' }] }),
+
+    // Test failure with non-covering exception
+    test({ code: 'export default 123', options: [{ allowObject: true }], errors: [{ message: 'Assign literal to a variable before exporting as module default' }] }),
+  ],
+});
diff --git a/tests/src/rules/no-commonjs.js b/tests/src/rules/no-commonjs.js
index 8ca8fde50..3211c085a 100644
--- a/tests/src/rules/no-commonjs.js
+++ b/tests/src/rules/no-commonjs.js
@@ -1,11 +1,11 @@
-import { RuleTester } from 'eslint'
-import eslintPkg from 'eslint/package.json'
-import semver from 'semver'
+import { RuleTester, withoutAutofixOutput } from '../rule-tester';
+import eslintPkg from 'eslint/package.json';
+import semver from 'semver';
 
-const EXPORT_MESSAGE = 'Expected "export" or "export default"'
-    , IMPORT_MESSAGE = 'Expected "import" instead of "require()"'
+const EXPORT_MESSAGE = 'Expected "export" or "export default"';
+const IMPORT_MESSAGE = 'Expected "import" instead of "require()"';
 
-const ruleTester = new RuleTester()
+const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2015, sourceType: 'module' } });
 
 ruleTester.run('no-commonjs', require('rules/no-commonjs'), {
   valid: [
@@ -13,19 +13,18 @@ ruleTester.run('no-commonjs', require('rules/no-commonjs'), {
     // imports
     { code: 'import "x";', parserOptions: { ecmaVersion: 2015, sourceType: 'module' } },
     { code: 'import x from "x"', parserOptions: { ecmaVersion: 2015, sourceType: 'module' } },
-    { code: 'import x from "x"', parserOptions: { ecmaVersion: 2015, sourceType: 'module' } },
     { code: 'import { x } from "x"', parserOptions: { ecmaVersion: 2015, sourceType: 'module' } },
 
     // exports
     { code: 'export default "x"', parserOptions: { ecmaVersion: 2015, sourceType: 'module' } },
     { code: 'export function house() {}', parserOptions: { ecmaVersion: 2015, sourceType: 'module' } },
     {
-      code:
-      'function someFunc() {\n'+
-      '  const exports = someComputation();\n'+
-      '\n'+
-      '  expect(exports.someProp).toEqual({ a: \'value\' });\n'+
-      '}',
+      code: `
+        function someFunc() {
+          const exports = someComputation();
+          expect(exports.someProp).toEqual({ a: 'value' });
+        }
+      `,
       parserOptions: { ecmaVersion: 2015, sourceType: 'module' },
     },
 
@@ -38,6 +37,7 @@ ruleTester.run('no-commonjs', require('rules/no-commonjs'), {
     { code: "var bar = require('./bar', true);" },
     { code: "var bar = proxyquire('./bar');" },
     { code: "var bar = require('./ba' + 'r');" },
+    { code: 'var bar = require(`x${1}`);', parserOptions: { ecmaVersion: 2015 } },
     { code: 'var zero = require(0);' },
     { code: 'require("x")', options: [{ allowRequire: true }] },
 
@@ -50,36 +50,60 @@ ruleTester.run('no-commonjs', require('rules/no-commonjs'), {
     // commonJS rules should be scoped to commonJS spec. `rootRequire` is not
     // recognized by this commonJS plugin.
     { code: 'rootRequire("x")', options: [{ allowRequire: true }] },
-    { code: 'rootRequire("x")', options: [{ allowRequire: false}] },
+    { code: 'rootRequire("x")', options: [{ allowRequire: false }] },
 
     { code: 'module.exports = function () {}', options: ['allow-primitive-modules'] },
     { code: 'module.exports = function () {}', options: [{ allowPrimitiveModules: true }] },
     { code: 'module.exports = "foo"', options: ['allow-primitive-modules'] },
     { code: 'module.exports = "foo"', options: [{ allowPrimitiveModules: true }] },
+
+    { code: 'if (typeof window !== "undefined") require("x")', options: [{ allowRequire: true }] },
+    { code: 'if (typeof window !== "undefined") require("x")', options: [{ allowRequire: false }] },
+    { code: 'if (typeof window !== "undefined") { require("x") }', options: [{ allowRequire: true }] },
+    { code: 'if (typeof window !== "undefined") { require("x") }', options: [{ allowRequire: false }] },
+
+    { code: 'try { require("x") } catch (error) {}' },
   ],
 
   invalid: [
 
     // imports
-    ...(semver.satisfies(eslintPkg.version, '< 4.0.0') ? [] : [
-      { code: 'var x = require("x")', errors: [ { message: IMPORT_MESSAGE }] },
-      { code: 'x = require("x")', errors: [ { message: IMPORT_MESSAGE }] },
-      { code: 'require("x")', errors: [ { message: IMPORT_MESSAGE }] },
-    ]),
+    ...semver.satisfies(eslintPkg.version, '< 4.0.0') ? [] : [
+      withoutAutofixOutput({ code: 'var x = require("x")', errors: [{ message: IMPORT_MESSAGE }] }),
+      withoutAutofixOutput({ code: 'x = require("x")', errors: [{ message: IMPORT_MESSAGE }] }),
+      withoutAutofixOutput({ code: 'require("x")', errors: [{ message: IMPORT_MESSAGE }] }),
+      withoutAutofixOutput({ code: 'require(`x`)',
+        parserOptions: { ecmaVersion: 2015 },
+        errors: [{ message: IMPORT_MESSAGE }],
+      }),
+
+      withoutAutofixOutput({ code: 'if (typeof window !== "undefined") require("x")',
+        options: [{ allowConditionalRequire: false }],
+        errors: [{ message: IMPORT_MESSAGE }],
+      }),
+      withoutAutofixOutput({ code: 'if (typeof window !== "undefined") { require("x") }',
+        options: [{ allowConditionalRequire: false }],
+        errors: [{ message: IMPORT_MESSAGE }],
+      }),
+      withoutAutofixOutput({ code: 'try { require("x") } catch (error) {}',
+        options: [{ allowConditionalRequire: false }],
+        errors: [{ message: IMPORT_MESSAGE }],
+      }),
+    ],
 
     // exports
-    { code: 'exports.face = "palm"', errors: [ { message: EXPORT_MESSAGE }] },
-    { code: 'module.exports.face = "palm"', errors: [ { message: EXPORT_MESSAGE }] },
-    { code: 'module.exports = face', errors: [ { message: EXPORT_MESSAGE }] },
-    { code: 'exports = module.exports = {}', errors: [ { message: EXPORT_MESSAGE }] },
-    { code: 'var x = module.exports = {}', errors: [ { message: EXPORT_MESSAGE }] },
-    { code: 'module.exports = {}',
+    withoutAutofixOutput({ code: 'exports.face = "palm"', errors: [{ message: EXPORT_MESSAGE }] }),
+    withoutAutofixOutput({ code: 'module.exports.face = "palm"', errors: [{ message: EXPORT_MESSAGE }] }),
+    withoutAutofixOutput({ code: 'module.exports = face', errors: [{ message: EXPORT_MESSAGE }] }),
+    withoutAutofixOutput({ code: 'exports = module.exports = {}', errors: [{ message: EXPORT_MESSAGE }] }),
+    withoutAutofixOutput({ code: 'var x = module.exports = {}', errors: [{ message: EXPORT_MESSAGE }] }),
+    withoutAutofixOutput({ code: 'module.exports = {}',
       options: ['allow-primitive-modules'],
-      errors: [ { message: EXPORT_MESSAGE }],
-    },
-    { code: 'var x = module.exports',
+      errors: [{ message: EXPORT_MESSAGE }],
+    }),
+    withoutAutofixOutput({ code: 'var x = module.exports',
       options: ['allow-primitive-modules'],
-      errors: [ { message: EXPORT_MESSAGE }],
-    },
+      errors: [{ message: EXPORT_MESSAGE }],
+    }),
   ],
-})
+});
diff --git a/tests/src/rules/no-cycle.js b/tests/src/rules/no-cycle.js
index 18fe88af1..ae4baab66 100644
--- a/tests/src/rules/no-cycle.js
+++ b/tests/src/rules/no-cycle.js
@@ -1,21 +1,28 @@
-import { test as _test, testFilePath } from '../utils'
+import { parsers, test as _test, testFilePath, testVersion as _testVersion } from '../utils';
 
-import { RuleTester } from 'eslint'
+import { RuleTester } from '../rule-tester';
+import flatMap from 'array.prototype.flatmap';
 
-const ruleTester = new RuleTester()
-    , rule = require('rules/no-cycle')
+const ruleTester = new RuleTester();
+const rule = require('rules/no-cycle');
 
-const error = message => ({ ruleId: 'no-cycle', message })
+const error = (message) => ({ message });
 
-const test = def => _test(Object.assign(def, {
+const test = (def) => _test({
   filename: testFilePath('./cycles/depth-zero.js'),
-}))
+  ...def,
+});
+const testVersion = (specifier, t) => _testVersion(specifier, () => ({
+  filename: testFilePath('./cycles/depth-zero.js'),
+  ...t(),
+}));
 
-// describe.only("no-cycle", () => {
-ruleTester.run('no-cycle', rule, {
-  valid: [
+const testDialects = ['es6'];
+
+const cases = {
+  valid: [].concat(
     // this rule doesn't care if the cycle length is 0
-    test({ code: 'import foo from "./foo.js"'}),
+    test({ code: 'import foo from "./foo.js"' }),
 
     test({ code: 'import _ from "lodash"' }),
     test({ code: 'import foo from "@scope/foo"' }),
@@ -33,93 +40,272 @@ ruleTester.run('no-cycle', rule, {
       filename: '<text>',
     }),
     test({
-      code: 'import { foo } from "./depth-two"',
-      options: [{ maxDepth: 1 }],
-    }),
-    test({
-      code: 'import { foo, bar } from "./depth-two"',
-      options: [{ maxDepth: 1 }],
-    }),
-    test({
-      code: 'import("./depth-two").then(function({ foo }){})',
-      options: [{ maxDepth: 1 }],
-      parser: require.resolve('babel-eslint'),
+      code: 'import { foo } from "cycles/external/depth-one"',
+      options: [{ ignoreExternal: true }],
+      settings: {
+        'import/resolver': 'webpack',
+        'import/external-module-folders': ['cycles/external'],
+      },
     }),
     test({
-      code: 'import type { FooType } from "./depth-one"',
-      parser: require.resolve('babel-eslint'),
+      code: 'import { foo } from "./external-depth-two"',
+      options: [{ ignoreExternal: true }],
+      settings: {
+        'import/resolver': 'webpack',
+        'import/external-module-folders': ['cycles/external'],
+      },
     }),
+
+    flatMap(testDialects, (testDialect) => [
+      test({
+        code: `import { foo } from "./${testDialect}/depth-two"`,
+        options: [{ maxDepth: 1 }],
+      }),
+      test({
+        code: `import { foo, bar } from "./${testDialect}/depth-two"`,
+        options: [{ maxDepth: 1 }],
+      }),
+      test({
+        code: `import("./${testDialect}/depth-two").then(function({ foo }) {})`,
+        options: [{ maxDepth: 1 }],
+        parser: parsers.BABEL_OLD,
+      }),
+      test({
+        code: `import type { FooType } from "./${testDialect}/depth-one"`,
+        parser: parsers.BABEL_OLD,
+      }),
+      test({
+        code: `import type { FooType, BarType } from "./${testDialect}/depth-one"`,
+        parser: parsers.BABEL_OLD,
+      }),
+      test({
+        code: `function bar(){ return import("./${testDialect}/depth-one"); } // #2265 1`,
+        options: [{ allowUnsafeDynamicCyclicDependency: true }],
+        parser: parsers.BABEL_OLD,
+      }),
+      test({
+        code: `import { foo } from "./${testDialect}/depth-one-dynamic"; // #2265 2`,
+        options: [{ allowUnsafeDynamicCyclicDependency: true }],
+        parser: parsers.BABEL_OLD,
+      }),
+    ].concat(parsers.TS_NEW ? [
+      test({
+        code: `function bar(){ return import("./${testDialect}/depth-one"); } // #2265 3`,
+        options: [{ allowUnsafeDynamicCyclicDependency: true }],
+        parser: parsers.TS_NEW,
+      }),
+      test({
+        code: `import { foo } from "./${testDialect}/depth-one-dynamic"; // #2265 4`,
+        options: [{ allowUnsafeDynamicCyclicDependency: true }],
+        parser: parsers.TS_NEW,
+      }),
+    ] : [])),
+
     test({
-      code: 'import type { FooType, BarType } from "./depth-one"',
-      parser: require.resolve('babel-eslint'),
+      code: 'import { bar } from "./flow-types"',
+      parser: parsers.BABEL_OLD,
     }),
-  ],
-  invalid: [
     test({
-      code: 'import { foo } from "./depth-one"',
-      errors: [error(`Dependency cycle detected.`)],
+      code: 'import { bar } from "./flow-types-only-importing-type"',
+      parser: parsers.BABEL_OLD,
     }),
     test({
-      code: 'import { foo } from "./depth-one"',
-      options: [{ maxDepth: 1 }],
-      errors: [error(`Dependency cycle detected.`)],
+      code: 'import { bar } from "./flow-types-only-importing-multiple-types"',
+      parser: parsers.BABEL_OLD,
     }),
     test({
-      code: 'const { foo } = require("./depth-one")',
-      errors: [error(`Dependency cycle detected.`)],
-      options: [{ commonjs: true }],
+      code: 'import { bar } from "./flow-typeof"',
+      parser: parsers.BABEL_OLD,
     }),
+  ),
+
+  invalid: [].concat(
     test({
-      code: 'require(["./depth-one"], d1 => {})',
+      code: 'import { bar } from "./flow-types-some-type-imports"',
+      parser: parsers.BABEL_OLD,
       errors: [error(`Dependency cycle detected.`)],
-      options: [{ amd: true }],
     }),
     test({
-      code: 'define(["./depth-one"], d1 => {})',
+      code: 'import { foo } from "cycles/external/depth-one"',
       errors: [error(`Dependency cycle detected.`)],
-      options: [{ amd: true }],
-    }),
-    test({
-      code: 'import { foo } from "./depth-two"',
-      errors: [error(`Dependency cycle via ./depth-one:1`)],
-    }),
-    test({
-      code: 'import { foo } from "./depth-two"',
-      options: [{ maxDepth: 2 }],
-      errors: [error(`Dependency cycle via ./depth-one:1`)],
-    }),
-    test({
-      code: 'const { foo } = require("./depth-two")',
-      errors: [error(`Dependency cycle via ./depth-one:1`)],
-      options: [{ commonjs: true }],
-    }),
-    test({
-      code: 'import { two } from "./depth-three-star"',
-      errors: [error(`Dependency cycle via ./depth-two:1=>./depth-one:1`)],
+      settings: {
+        'import/resolver': 'webpack',
+        'import/external-module-folders': ['cycles/external'],
+      },
     }),
     test({
-      code: 'import one, { two, three } from "./depth-three-star"',
-      errors: [error(`Dependency cycle via ./depth-two:1=>./depth-one:1`)],
-    }),
-    test({
-      code: 'import { bar } from "./depth-three-indirect"',
-      errors: [error(`Dependency cycle via ./depth-two:1=>./depth-one:1`)],
+      code: 'import { foo } from "./external-depth-two"',
+      errors: [error(`Dependency cycle via cycles/external/depth-one:1`)],
+      settings: {
+        'import/resolver': 'webpack',
+        'import/external-module-folders': ['cycles/external'],
+      },
     }),
+
+    // Ensure behavior does not change for those tests, with or without `
+    flatMap(testDialects, (testDialect) => flatMap([
+      {},
+      { allowUnsafeDynamicCyclicDependency: true },
+    ], (opts) => [
+      test({
+        code: `import { foo } from "./${testDialect}/depth-one"`,
+        options: [{ ...opts }],
+        errors: [error(`Dependency cycle detected.`)],
+      }),
+      test({
+        code: `import { foo } from "./${testDialect}/depth-one"`,
+        options: [{ ...opts, maxDepth: 1 }],
+        errors: [error(`Dependency cycle detected.`)],
+      }),
+      test({
+        code: `const { foo } = require("./${testDialect}/depth-one")`,
+        errors: [error(`Dependency cycle detected.`)],
+        options: [{ ...opts, commonjs: true }],
+      }),
+      test({
+        code: `require(["./${testDialect}/depth-one"], d1 => {})`,
+        errors: [error(`Dependency cycle detected.`)],
+        options: [{ ...opts, amd: true }],
+      }),
+      test({
+        code: `define(["./${testDialect}/depth-one"], d1 => {})`,
+        errors: [error(`Dependency cycle detected.`)],
+        options: [{ ...opts, amd: true }],
+      }),
+      test({
+        code: `import { foo } from "./${testDialect}/depth-one-reexport"`,
+        options: [{ ...opts }],
+        errors: [error(`Dependency cycle detected.`)],
+      }),
+      test({
+        code: `import { foo } from "./${testDialect}/depth-two"`,
+        options: [{ ...opts }],
+        errors: [error(`Dependency cycle via ./depth-one:1`)],
+      }),
+      test({
+        code: `import { foo } from "./${testDialect}/depth-two"`,
+        options: [{ ...opts, maxDepth: 2 }],
+        errors: [error(`Dependency cycle via ./depth-one:1`)],
+      }),
+      test({
+        code: `const { foo } = require("./${testDialect}/depth-two")`,
+        errors: [error(`Dependency cycle via ./depth-one:1`)],
+        options: [{ ...opts, commonjs: true }],
+      }),
+      test({
+        code: `import { two } from "./${testDialect}/depth-three-star"`,
+        options: [{ ...opts }],
+        errors: [error(`Dependency cycle via ./depth-two:1=>./depth-one:1`)],
+      }),
+      test({
+        code: `import one, { two, three } from "./${testDialect}/depth-three-star"`,
+        options: [{ ...opts }],
+        errors: [error(`Dependency cycle via ./depth-two:1=>./depth-one:1`)],
+      }),
+      test({
+        code: `import { bar } from "./${testDialect}/depth-three-indirect"`,
+        options: [{ ...opts }],
+        errors: [error(`Dependency cycle via ./depth-two:1=>./depth-one:1`)],
+      }),
+      test({
+        code: `import { bar } from "./${testDialect}/depth-three-indirect"`,
+        options: [{ ...opts }],
+        errors: [error(`Dependency cycle via ./depth-two:1=>./depth-one:1`)],
+        parser: parsers.BABEL_OLD,
+      }),
+      test({
+        code: `import { foo } from "./${testDialect}/depth-two"`,
+        options: [{ ...opts, maxDepth: Infinity }],
+        errors: [error(`Dependency cycle via ./depth-one:1`)],
+      }),
+      test({
+        code: `import { foo } from "./${testDialect}/depth-two"`,
+        options: [{ ...opts, maxDepth: '∞' }],
+        errors: [error(`Dependency cycle via ./depth-one:1`)],
+      }),
+    ]).concat([
+      test({
+        code: `import("./${testDialect}/depth-three-star")`,
+        errors: [error(`Dependency cycle via ./depth-two:1=>./depth-one:1`)],
+        parser: parsers.BABEL_OLD,
+      }),
+      test({
+        code: `import("./${testDialect}/depth-three-indirect")`,
+        errors: [error(`Dependency cycle via ./depth-two:1=>./depth-one:1`)],
+        parser: parsers.BABEL_OLD,
+      }),
+      test({
+        code: `function bar(){ return import("./${testDialect}/depth-one"); } // #2265 5`,
+        errors: [error(`Dependency cycle detected.`)],
+        parser: parsers.BABEL_OLD,
+      }),
+    ]).concat(
+      testVersion('> 3', () => ({ // Dynamic import is not properly caracterized with eslint < 4
+        code: `import { foo } from "./${testDialect}/depth-one-dynamic"; // #2265 6`,
+        errors: [error(`Dependency cycle detected.`)],
+        parser: parsers.BABEL_OLD,
+      })),
+    ).concat(parsers.TS_NEW ? [
+      test({
+        code: `function bar(){ return import("./${testDialect}/depth-one"); } // #2265 7`,
+        errors: [error(`Dependency cycle detected.`)],
+        parser: parsers.TS_NEW,
+      }),
+      test({
+        code: `import { foo } from "./${testDialect}/depth-one-dynamic"; // #2265 8`,
+        errors: [error(`Dependency cycle detected.`)],
+        parser: parsers.TS_NEW,
+      }),
+    ] : [])),
+
     test({
-      code: 'import { bar } from "./depth-three-indirect"',
-      errors: [error(`Dependency cycle via ./depth-two:1=>./depth-one:1`)],
-      parser: require.resolve('babel-eslint'),
+      code: 'import { bar } from "./flow-types-depth-one"',
+      parser: parsers.BABEL_OLD,
+      errors: [error(`Dependency cycle via ./flow-types-depth-two:4=>./es6/depth-one:1`)],
     }),
     test({
-      code: 'import("./depth-three-star")',
-      errors: [error(`Dependency cycle via ./depth-two:1=>./depth-one:1`)],
-      parser: require.resolve('babel-eslint'),
+      code: 'import { foo } from "./intermediate-ignore"',
+      errors: [
+        {
+          message: 'Dependency cycle via ./ignore:1',
+          line: 1,
+        },
+      ],
     }),
     test({
-      code: 'import("./depth-three-indirect")',
-      errors: [error(`Dependency cycle via ./depth-two:1=>./depth-one:1`)],
-      parser: require.resolve('babel-eslint'),
+      code: 'import { foo } from "./ignore"',
+      errors: [
+        {
+          message: 'Dependency cycle detected.',
+          line: 1,
+        },
+      ],
     }),
-  ],
-})
-// })
+  ),
+};
+
+ruleTester.run('no-cycle', rule, {
+  valid: flatMap(cases.valid, (testCase) => [
+    testCase,
+    {
+      ...testCase,
+      code: `${testCase.code} // disableScc=true`,
+      options: [{
+        ...testCase.options && testCase.options[0] || {},
+        disableScc: true,
+      }],
+    },
+  ]),
+
+  invalid: flatMap(cases.invalid, (testCase) => [
+    testCase,
+    {
+      ...testCase,
+      code: `${testCase.code} // disableScc=true`,
+      options: [{
+        ...testCase.options && testCase.options[0] || {},
+        disableScc: true,
+      }],
+    },
+  ]),
+});
diff --git a/tests/src/rules/no-default-export.js b/tests/src/rules/no-default-export.js
index dd71c167e..8434ee148 100644
--- a/tests/src/rules/no-default-export.js
+++ b/tests/src/rules/no-default-export.js
@@ -1,12 +1,21 @@
-import { test } from '../utils'
+import { parsers, test, testVersion } from '../utils';
 
-import { RuleTester } from 'eslint'
+import { RuleTester } from '../rule-tester';
 
-const ruleTester = new RuleTester()
-    , rule = require('rules/no-default-export')
+const ruleTester = new RuleTester();
+const rule = require('rules/no-default-export');
 
 ruleTester.run('no-default-export', rule, {
   valid: [
+    test({
+      code: 'module.exports = function foo() {}',
+      parserOptions: {
+        sourceType: 'script',
+      },
+    }),
+    test({
+      code: 'module.exports = function foo() {}',
+    }),
     test({
       code: `
         export const foo = 'foo';
@@ -58,7 +67,7 @@ ruleTester.run('no-default-export', rule, {
     }),
     test({
       code: 'export { a, b } from "foo.js"',
-      parser: require.resolve('babel-eslint'),
+      parser: parsers.BABEL_OLD,
     }),
 
     // no exports at all
@@ -74,49 +83,104 @@ ruleTester.run('no-default-export', rule, {
 
     test({
       code: `export type UserId = number;`,
-      parser: require.resolve('babel-eslint'),
+      parser: parsers.BABEL_OLD,
     }),
     test({
       code: 'export foo from "foo.js"',
-      parser: require.resolve('babel-eslint'),
+      parser: parsers.BABEL_OLD,
     }),
     test({
       code: `export Memory, { MemoryValue } from './Memory'`,
-      parser: require.resolve('babel-eslint'),
+      parser: parsers.BABEL_OLD,
     }),
   ],
-  invalid: [
-    test({
+  invalid: [].concat(
+    testVersion('> 2', () => ({
       code: 'export default function bar() {};',
-      errors: [{
-        ruleId: 'ExportDefaultDeclaration',
-        message: 'Prefer named exports.',
-      }],
-    }),
-    test({
+      errors: [
+        {
+          type: 'ExportDefaultDeclaration',
+          message: 'Prefer named exports.',
+          line: 1,
+          column: 8,
+        },
+      ],
+    })),
+    testVersion('> 2', () => ({
       code: `
         export const foo = 'foo';
         export default bar;`,
-      errors: [{
-        ruleId: 'ExportDefaultDeclaration',
-        message: 'Prefer named exports.',
-      }],
-    }),
+      errors: [
+        {
+          type: 'ExportDefaultDeclaration',
+          message: 'Prefer named exports.',
+          line: 3,
+          column: 16,
+        },
+      ],
+    })),
+    testVersion('> 2', () => ({
+      code: 'export default class Bar {};',
+      errors: [
+        {
+          type: 'ExportDefaultDeclaration',
+          message: 'Prefer named exports.',
+          line: 1,
+          column: 8,
+        },
+      ],
+    })),
+    testVersion('> 2', () => ({
+      code: 'export default function() {};',
+      errors: [
+        {
+          type: 'ExportDefaultDeclaration',
+          message: 'Prefer named exports.',
+          line: 1,
+          column: 8,
+        },
+      ],
+    })),
+    testVersion('> 2', () => ({
+      code: 'export default class {};',
+      errors: [
+        {
+          type: 'ExportDefaultDeclaration',
+          message: 'Prefer named exports.',
+          line: 1,
+          column: 8,
+        },
+      ],
+    })),
     test({
       code: 'let foo; export { foo as default }',
-      errors: [{
-        ruleId: 'ExportNamedDeclaration',
-        message: 'Do not alias `foo` as `default`. Just export `foo` itself ' +
-          'instead.',
-      }],
+      errors: [
+        {
+          type: 'ExportNamedDeclaration',
+          message: 'Do not alias `foo` as `default`. Just export `foo` itself instead.',
+        },
+      ],
     }),
     test({
       code: 'export default from "foo.js"',
-      parser: require.resolve('babel-eslint'),
-      errors: [{
-        ruleId: 'ExportNamedDeclaration',
-        message: 'Prefer named exports.',
-      }],
-    }),
-  ],
-})
+      parser: parsers.BABEL_OLD,
+      errors: [
+        {
+          type: 'ExportNamedDeclaration',
+          message: 'Prefer named exports.',
+        },
+      ],
+    }),
+    // es2022: Arbitrary module namespae identifier names
+    testVersion('>= 8.7', () => ({
+      code: 'let foo; export { foo as "default" }',
+      errors: [
+        {
+          type: 'ExportNamedDeclaration',
+          message: 'Do not alias `foo` as `default`. Just export `foo` itself instead.',
+        },
+      ],
+      parserOptions: { ecmaVersion: 2022 },
+    })),
+  ),
+});
diff --git a/tests/src/rules/no-deprecated.js b/tests/src/rules/no-deprecated.js
index 28b8734f7..ad51d23c2 100644
--- a/tests/src/rules/no-deprecated.js
+++ b/tests/src/rules/no-deprecated.js
@@ -1,9 +1,9 @@
-import { test, SYNTAX_CASES, getTSParsers } from '../utils'
+import { test, SYNTAX_CASES, getTSParsers } from '../utils';
 
-import { RuleTester } from 'eslint'
+import { RuleTester } from '../rule-tester';
 
-const ruleTester = new RuleTester()
-    , rule = require('rules/no-deprecated')
+const ruleTester = new RuleTester();
+const rule = require('rules/no-deprecated');
 
 ruleTester.run('no-deprecated', rule, {
   valid: [
@@ -15,16 +15,16 @@ ruleTester.run('no-deprecated', rule, {
 
     test({
       code: "import { fn } from './deprecated'",
-      settings: { 'import/docstyle': ['tomdoc'] }
+      settings: { 'import/docstyle': ['tomdoc'] },
     }),
 
     test({
       code: "import { fine } from './tomdoc-deprecated'",
-      settings: { 'import/docstyle': ['tomdoc'] }
+      settings: { 'import/docstyle': ['tomdoc'] },
     }),
     test({
       code: "import { _undocumented } from './tomdoc-deprecated'",
-      settings: { 'import/docstyle': ['tomdoc'] }
+      settings: { 'import/docstyle': ['tomdoc'] },
     }),
 
     // naked namespace is fine
@@ -38,7 +38,6 @@ ruleTester.run('no-deprecated', rule, {
       code: "import { deepDep } from './deep-deprecated'; function x(deepDep) { console.log(deepDep.MY_TERRIBLE_ACTION) }",
     }),
 
-
     ...SYNTAX_CASES,
   ],
   invalid: [
@@ -70,7 +69,7 @@ ruleTester.run('no-deprecated', rule, {
     test({
       code: "import { fn } from './tomdoc-deprecated'",
       settings: { 'import/docstyle': ['tomdoc'] },
-      errors: ["Deprecated: This function is terrible."],
+      errors: ['Deprecated: This function is terrible.'],
     }),
 
     test({
@@ -174,7 +173,7 @@ ruleTester.run('no-deprecated', rule, {
       ],
     }),
   ],
-})
+});
 
 ruleTester.run('no-deprecated: hoisting', rule, {
   valid: [
@@ -196,33 +195,35 @@ ruleTester.run('no-deprecated: hoisting', rule, {
     }),
 
   ],
-})
+});
 
-describe('Typescript', function () {
+describe('TypeScript', function () {
   getTSParsers().forEach((parser) => {
     const parserConfig = {
-      parser: parser,
+      parser,
       settings: {
         'import/parsers': { [parser]: ['.ts'] },
         'import/resolver': { 'eslint-import-resolver-typescript': true },
       },
-    }
+    };
 
     ruleTester.run(parser, rule, {
       valid: [
-        test(Object.assign({
+        test({
           code: 'import * as hasDeprecated from \'./ts-deprecated.ts\'',
-        }, parserConfig)),
+          ...parserConfig,
+        }),
       ],
       invalid: [
-        test(Object.assign({
+        test({
           code: 'import { foo } from \'./ts-deprecated.ts\'; console.log(foo())',
           errors: [
             { type: 'ImportSpecifier', message: 'Deprecated: don\'t use this!' },
             { type: 'Identifier', message: 'Deprecated: don\'t use this!' },
-          ]},
-          parserConfig)),
+          ],
+          ...parserConfig,
+        }),
       ],
-    })
-  })
-})
+    });
+  });
+});
diff --git a/tests/src/rules/no-duplicates.js b/tests/src/rules/no-duplicates.js
index 29b080466..cf57a3d59 100644
--- a/tests/src/rules/no-duplicates.js
+++ b/tests/src/rules/no-duplicates.js
@@ -1,14 +1,19 @@
-import * as path from 'path'
-import { test as testUtil } from '../utils'
+import * as path from 'path';
+import { test as testUtil, getNonDefaultParsers, parsers, tsVersionSatisfies, typescriptEslintParserSatisfies } from '../utils';
+import jsxConfig from '../../../config/react';
 
-import { RuleTester } from 'eslint'
+import { RuleTester, withoutAutofixOutput } from '../rule-tester';
+import eslintPkg from 'eslint/package.json';
+import semver from 'semver';
+import flatMap from 'array.prototype.flatmap';
 
-const ruleTester = new RuleTester()
-    , rule = require('rules/no-duplicates')
+const ruleTester = new RuleTester();
+const rule = require('rules/no-duplicates');
 
-const test = process.env.ESLINT_VERSION === '3' || process.env.ESLINT_VERSION === '2'
-  ? t => testUtil(Object.assign({}, t, {output: t.code}))
-  : testUtil
+// autofix only possible with eslint 4+
+const test = semver.satisfies(eslintPkg.version, '< 4')
+  ? (t) => testUtil({ ...t, output: t.code })
+  : testUtil;
 
 ruleTester.run('no-duplicates', rule, {
   valid: [
@@ -17,13 +22,32 @@ ruleTester.run('no-duplicates', rule, {
     test({ code: "import { x } from './foo'; import { y } from './bar'" }),
 
     // #86: every unresolved module should not show up as 'null' and duplicate
-    test({ code: 'import foo from "234artaf";' +
-                 'import { shoop } from "234q25ad"' }),
+    test({ code: 'import foo from "234artaf"; import { shoop } from "234q25ad"' }),
 
     // #225: ignore duplicate if is a flow type import
     test({
       code: "import { x } from './foo'; import type { y } from './foo'",
-      parser: require.resolve('babel-eslint'),
+      parser: parsers.BABEL_OLD,
+    }),
+
+    // #1107: Using different query strings that trigger different webpack loaders.
+    test({
+      code: "import x from './bar?optionX'; import y from './bar?optionY';",
+      options: [{ considerQueryString: true }],
+      settings: { 'import/resolver': 'webpack' },
+    }),
+    test({
+      code: "import x from './foo'; import y from './bar';",
+      options: [{ considerQueryString: true }],
+      settings: { 'import/resolver': 'webpack' },
+    }),
+
+    // #1538: It is impossible to import namespace and other in one line, so allow this.
+    test({
+      code: "import * as ns from './foo'; import {y} from './foo'",
+    }),
+    test({
+      code: "import {y} from './foo'; import * as ns from './foo'",
     }),
   ],
   invalid: [
@@ -43,28 +67,48 @@ ruleTester.run('no-duplicates', rule, {
     test({
       code: "import { x } from './bar'; import { y } from 'bar';",
       output: "import { x , y } from './bar'; ",
-      settings: { 'import/resolve': {
-        paths: [path.join( process.cwd()
-                         , 'tests', 'files'
-                         )] }},
+      settings: {
+        'import/resolve': {
+          paths: [path.join(process.cwd(), 'tests', 'files')],
+        },
+      },
       errors: 2, // path ends up hardcoded
-     }),
+    }),
 
-    // #86: duplicate unresolved modules should be flagged
+    // #1107: Using different query strings that trigger different webpack loaders.
     test({
+      code: "import x from './bar.js?optionX'; import y from './bar?optionX';",
+      settings: { 'import/resolver': 'webpack' },
+      errors: 2, // path ends up hardcoded
+    }),
+    test({
+      code: "import x from './bar?optionX'; import y from './bar?optionY';",
+      settings: { 'import/resolver': 'webpack' },
+      errors: 2, // path ends up hardcoded
+    }),
+
+    // #1107: Using same query strings that trigger the same loader.
+    test({
+      code: "import x from './bar?optionX'; import y from './bar.js?optionX';",
+      options: [{ considerQueryString: true }],
+      settings: { 'import/resolver': 'webpack' },
+      errors: 2, // path ends up hardcoded
+    }),
+
+    // #86: duplicate unresolved modules should be flagged
+    // Autofix bail because of different default import names.
+    test(withoutAutofixOutput({
       code: "import foo from 'non-existent'; import bar from 'non-existent';",
-      // Autofix bail because of different default import names.
-      output: "import foo from 'non-existent'; import bar from 'non-existent';",
       errors: [
         "'non-existent' imported multiple times.",
         "'non-existent' imported multiple times.",
       ],
-    }),
+    })),
 
     test({
       code: "import type { x } from './foo'; import type { y } from './foo'",
       output: "import type { x , y } from './foo'; ",
-      parser: require.resolve('babel-eslint'),
+      parser: parsers.BABEL_OLD,
       errors: ['\'./foo\' imported multiple times.', '\'./foo\' imported multiple times.'],
     }),
 
@@ -86,6 +130,36 @@ ruleTester.run('no-duplicates', rule, {
       errors: ['\'./foo\' imported multiple times.', '\'./foo\' imported multiple times.'],
     }),
 
+    // These test cases use duplicate import identifiers, which causes a fatal parsing error using ESPREE (default) and TS_OLD.
+    ...flatMap([parsers.BABEL_OLD, parsers.TS_NEW], (parser) => {
+      if (!parser) { return []; } // TS_NEW is not always available
+      return [
+        // #2347: duplicate identifiers should be removed
+        test({
+          code: "import {a} from './foo'; import { a } from './foo'",
+          output: "import {a} from './foo'; ",
+          errors: ['\'./foo\' imported multiple times.', '\'./foo\' imported multiple times.'],
+          parser,
+        }),
+
+        // #2347: duplicate identifiers should be removed
+        test({
+          code: "import {a,b} from './foo'; import { b, c } from './foo'; import {b,c,d} from './foo'",
+          output: "import {a,b, c ,d} from './foo';  ",
+          errors: ['\'./foo\' imported multiple times.', '\'./foo\' imported multiple times.', '\'./foo\' imported multiple times.'],
+          parser,
+        }),
+
+        // #2347: duplicate identifiers should be removed, but not if they are adjacent to comments
+        test({
+          code: "import {a} from './foo'; import { a/*,b*/ } from './foo'",
+          output: "import {a, a/*,b*/ } from './foo'; ",
+          errors: ['\'./foo\' imported multiple times.', '\'./foo\' imported multiple times.'],
+          parser,
+        }),
+      ];
+    }),
+
     test({
       code: "import {x} from './foo'; import {} from './foo'; import {/*c*/} from './foo'; import {y} from './foo'",
       output: "import {x/*c*/,y} from './foo';   ",
@@ -128,6 +202,12 @@ ruleTester.run('no-duplicates', rule, {
       errors: ['\'./foo\' imported multiple times.', '\'./foo\' imported multiple times.'],
     }),
 
+    test({
+      code: "import def from './foo'; import {x} from './foo'",
+      output: "import def, {x} from './foo'; ",
+      errors: ['\'./foo\' imported multiple times.', '\'./foo\' imported multiple times.'],
+    }),
+
     test({
       code: "import {x} from './foo'; import def from './foo'",
       output: "import def, {x} from './foo'; ",
@@ -146,10 +226,16 @@ ruleTester.run('no-duplicates', rule, {
       errors: ['\'./foo\' imported multiple times.', '\'./foo\' imported multiple times.'],
     }),
 
+    // Autofix bail because cannot merge namespace imports.
+    test(withoutAutofixOutput({
+      code: "import * as ns1 from './foo'; import * as ns2 from './foo'",
+      errors: ['\'./foo\' imported multiple times.', '\'./foo\' imported multiple times.'],
+    })),
+
     test({
-      code: "import * as ns from './foo'; import {y} from './foo'",
-      // Autofix bail because first import is a namespace import.
-      output: "import * as ns from './foo'; import {y} from './foo'",
+      code: "import * as ns from './foo'; import {x} from './foo'; import {y} from './foo'",
+      // Autofix could merge some imports, but not the namespace import.
+      output: "import * as ns from './foo'; import {x,y} from './foo'; ",
       errors: ['\'./foo\' imported multiple times.', '\'./foo\' imported multiple times.'],
     }),
 
@@ -157,192 +243,516 @@ ruleTester.run('no-duplicates', rule, {
       code: "import {x} from './foo'; import * as ns from './foo'; import {y} from './foo'; import './foo'",
       // Autofix could merge some imports, but not the namespace import.
       output: "import {x,y} from './foo'; import * as ns from './foo';  ",
-      errors: ['\'./foo\' imported multiple times.', '\'./foo\' imported multiple times.', '\'./foo\' imported multiple times.', '\'./foo\' imported multiple times.'],
+      errors: ['\'./foo\' imported multiple times.', '\'./foo\' imported multiple times.', '\'./foo\' imported multiple times.'],
     }),
 
-    test({
+    // Autofix bail because of comment.
+    test(withoutAutofixOutput({
       code: `
         // some-tool-disable-next-line
         import {x} from './foo'
         import {//y\ny} from './foo'
       `,
-      // Autofix bail because of comment.
-      output: `
-        // some-tool-disable-next-line
-        import {x} from './foo'
-        import {//y\ny} from './foo'
-      `,
       errors: ['\'./foo\' imported multiple times.', '\'./foo\' imported multiple times.'],
-    }),
-
-    test({
+    })),
+    // Autofix bail because of comment.
+    test(withoutAutofixOutput({
       code: `
         import {x} from './foo'
         // some-tool-disable-next-line
         import {y} from './foo'
       `,
-      // Autofix bail because of comment.
-      output: `
-        import {x} from './foo'
-        // some-tool-disable-next-line
-        import {y} from './foo'
-      `,
       errors: ['\'./foo\' imported multiple times.', '\'./foo\' imported multiple times.'],
-    }),
-
-    test({
+    })),
+    // Autofix bail because of comment.
+    test(withoutAutofixOutput({
       code: `
         import {x} from './foo' // some-tool-disable-line
         import {y} from './foo'
       `,
-      // Autofix bail because of comment.
-      output: `
-        import {x} from './foo' // some-tool-disable-line
-        import {y} from './foo'
-      `,
       errors: ['\'./foo\' imported multiple times.', '\'./foo\' imported multiple times.'],
-    }),
-
-    test({
+    })),
+    // Autofix bail because of comment.
+    test(withoutAutofixOutput({
       code: `
         import {x} from './foo'
         import {y} from './foo' // some-tool-disable-line
       `,
-      // Autofix bail because of comment.
-      output: `
-        import {x} from './foo'
-        import {y} from './foo' // some-tool-disable-line
-      `,
       errors: ['\'./foo\' imported multiple times.', '\'./foo\' imported multiple times.'],
-    }),
-
-    test({
+    })),
+    // Autofix bail because of comment.
+    test(withoutAutofixOutput({
       code: `
         import {x} from './foo'
         /* comment */ import {y} from './foo'
       `,
-      // Autofix bail because of comment.
-      output: `
-        import {x} from './foo'
-        /* comment */ import {y} from './foo'
-      `,
       errors: ['\'./foo\' imported multiple times.', '\'./foo\' imported multiple times.'],
-    }),
-
-    test({
+    })),
+    // Autofix bail because of comment.
+    test(withoutAutofixOutput({
       code: `
         import {x} from './foo'
         import {y} from './foo' /* comment
         multiline */
       `,
-      // Autofix bail because of comment.
-      output: `
-        import {x} from './foo'
-        import {y} from './foo' /* comment
-        multiline */
-      `,
       errors: ['\'./foo\' imported multiple times.', '\'./foo\' imported multiple times.'],
-    }),
+    })),
 
     test({
       code: `
-        import {x} from './foo'
-        import {y} from './foo'
-        // some-tool-disable-next-line
+import {x} from './foo'
+import {y} from './foo'
+// some-tool-disable-next-line
       `,
       // Not autofix bail.
       output: `
-        import {x,y} from './foo'
-        
-        // some-tool-disable-next-line
+import {x,y} from './foo'
+// some-tool-disable-next-line
       `,
       errors: ['\'./foo\' imported multiple times.', '\'./foo\' imported multiple times.'],
     }),
 
     test({
       code: `
-        import {x} from './foo'
-        // comment
+import {x} from './foo'
+// comment
 
-        import {y} from './foo'
+import {y} from './foo'
       `,
       // Not autofix bail.
       output: `
-        import {x,y} from './foo'
-        // comment
+import {x,y} from './foo'
+// comment
 
-        
       `,
       errors: ['\'./foo\' imported multiple times.', '\'./foo\' imported multiple times.'],
     }),
-
-    test({
+    // Autofix bail because of comment.
+    test(withoutAutofixOutput({
       code: `
         import {x} from './foo'
         import/* comment */{y} from './foo'
       `,
-      // Autofix bail because of comment.
-      output: `
-        import {x} from './foo'
-        import/* comment */{y} from './foo'
-      `,
       errors: ['\'./foo\' imported multiple times.', '\'./foo\' imported multiple times.'],
-    }),
-
-    test({
+    })),
+    // Autofix bail because of comment.
+    test(withoutAutofixOutput({
       code: `
         import {x} from './foo'
         import/* comment */'./foo'
       `,
-      // Autofix bail because of comment.
-      output: `
-        import {x} from './foo'
-        import/* comment */'./foo'
-      `,
       errors: ['\'./foo\' imported multiple times.', '\'./foo\' imported multiple times.'],
-    }),
-
-    test({
+    })),
+    // Autofix bail because of comment.
+    test(withoutAutofixOutput({
       code: `
         import {x} from './foo'
         import{y}/* comment */from './foo'
       `,
-      // Autofix bail because of comment.
-      output: `
+      errors: ['\'./foo\' imported multiple times.', '\'./foo\' imported multiple times.'],
+    })),
+    // Autofix bail because of comment.
+    test(withoutAutofixOutput({
+      code: `
         import {x} from './foo'
-        import{y}/* comment */from './foo'
+        import{y}from/* comment */'./foo'
+      `,
+      errors: ['\'./foo\' imported multiple times.', '\'./foo\' imported multiple times.'],
+    })),
+    // Autofix bail because of comment.
+    test(withoutAutofixOutput({
+      code: `
+        import {x} from
+        // some-tool-disable-next-line
+        './foo'
+        import {y} from './foo'
       `,
       errors: ['\'./foo\' imported multiple times.', '\'./foo\' imported multiple times.'],
+    })),
+
+    // #2027 long import list generate empty lines
+    test({
+      code: "import { Foo } from './foo';\nimport { Bar } from './foo';\nexport const value = {}",
+      output: "import { Foo , Bar } from './foo';\nexport const value = {}",
+      errors: ['\'./foo\' imported multiple times.', '\'./foo\' imported multiple times.'],
+    }),
+
+    // #2027 long import list generate empty lines
+    test({
+      code: "import { Foo } from './foo';\nimport Bar from './foo';\nexport const value = {}",
+      output: "import Bar, { Foo } from './foo';\nexport const value = {}",
+      errors: ['\'./foo\' imported multiple times.', '\'./foo\' imported multiple times.'],
     }),
 
     test({
       code: `
-        import {x} from './foo'
-        import{y}from/* comment */'./foo'
+        import {
+          DEFAULT_FILTER_KEYS,
+          BULK_DISABLED,
+        } from '../constants';
+        import React from 'react';
+        import {
+          BULK_ACTIONS_ENABLED
+        } from '../constants';
+        ${''}
+        const TestComponent = () => {
+          return <div>
+          </div>;
+        }
+        ${''}
+        export default TestComponent;
       `,
-      // Autofix bail because of comment.
       output: `
-        import {x} from './foo'
-        import{y}from/* comment */'./foo'
+        import {
+          DEFAULT_FILTER_KEYS,
+          BULK_DISABLED,
+        ${''}
+          BULK_ACTIONS_ENABLED
+        } from '../constants';
+        import React from 'react';
+                ${''}
+        const TestComponent = () => {
+          return <div>
+          </div>;
+        }
+        ${''}
+        export default TestComponent;
       `,
-      errors: ['\'./foo\' imported multiple times.', '\'./foo\' imported multiple times.'],
+      errors: ["'../constants' imported multiple times.", "'../constants' imported multiple times."],
+      ...jsxConfig,
     }),
 
     test({
       code: `
-        import {x} from
-        // some-tool-disable-next-line
-        './foo'
-        import {y} from './foo'
+        import {A1,} from 'foo';
+        import {B1,} from 'foo';
+        import {C1,} from 'foo';
+
+        import {
+          A2,
+        } from 'bar';
+        import {
+          B2,
+        } from 'bar';
+        import {
+          C2,
+        } from 'bar';
+
       `,
-      // Autofix bail because of comment.
       output: `
-        import {x} from
-        // some-tool-disable-next-line
-        './foo'
-        import {y} from './foo'
+        import {A1,B1,C1} from 'foo';
+                ${''}
+        import {
+          A2,
+        ${''}
+          B2,
+          C2} from 'bar';
+                ${''}
       `,
-      errors: ['\'./foo\' imported multiple times.', '\'./foo\' imported multiple times.'],
+      errors: [
+        {
+          message: "'foo' imported multiple times.",
+          line: 2,
+          column: 27,
+        },
+        {
+          message: "'foo' imported multiple times.",
+          line: 3,
+          column: 27,
+        },
+        {
+          message: "'foo' imported multiple times.",
+          line: 4,
+          column: 27,
+        },
+        {
+          message: "'bar' imported multiple times.",
+          line: 8,
+          column: 16,
+        },
+        {
+          message: "'bar' imported multiple times.",
+          line: 11,
+          column: 16,
+        },
+        {
+          message: "'bar' imported multiple times.",
+          line: 14,
+          column: 16,
+        },
+      ],
+      ...jsxConfig,
     }),
   ],
-})
+});
+
+context('TypeScript', function () {
+  getNonDefaultParsers()
+    // Type-only imports were added in TypeScript ESTree 2.23.0
+    .filter((parser) => parser !== parsers.TS_OLD)
+    .forEach((parser) => {
+      const parserConfig = {
+        parser,
+        settings: {
+          'import/parsers': { [parser]: ['.ts'] },
+          'import/resolver': { 'eslint-import-resolver-typescript': true },
+        },
+      };
+
+      const valid = [
+        // #1667: ignore duplicate if is a typescript type import
+        test({
+          code: "import type { x } from './foo'; import y from './foo'",
+          ...parserConfig,
+        }),
+        test({
+          code: "import type x from './foo'; import type y from './bar'",
+          ...parserConfig,
+        }),
+        test({
+          code: "import type {x} from './foo'; import type {y} from './bar'",
+          ...parserConfig,
+        }),
+        test({
+          code: "import type x from './foo'; import type {y} from './foo'",
+          ...parserConfig,
+        }),
+        test({
+          code: `
+            import type {} from './module';
+            import {} from './module2';
+          `,
+          ...parserConfig,
+        }),
+        test({
+          code: `
+            import type { Identifier } from 'module';
+
+            declare module 'module2' {
+              import type { Identifier } from 'module';
+            }
+
+            declare module 'module3' {
+              import type { Identifier } from 'module';
+            }
+          `,
+          ...parserConfig,
+        }),
+      ].concat(!tsVersionSatisfies('>= 4.5') || !typescriptEslintParserSatisfies('>= 5.7.0') ? [] : [
+        // #2470: ignore duplicate if is a typescript inline type import
+        test({
+          code: "import { type x } from './foo'; import y from './foo'",
+          ...parserConfig,
+        }),
+        test({
+          code: "import { type x } from './foo'; import { y } from './foo'",
+          ...parserConfig,
+        }),
+        test({
+          code: "import { type x } from './foo'; import type y from 'foo'",
+          ...parserConfig,
+        }),
+      ]);
+
+      const invalid = [
+        test(withoutAutofixOutput({
+          code: "import type x from './foo'; import type y from './foo'",
+          ...parserConfig,
+          errors: [
+            {
+              line: 1,
+              column: 20,
+              message: "'./foo' imported multiple times.",
+            },
+            {
+              line: 1,
+              column: 48,
+              message: "'./foo' imported multiple times.",
+            },
+          ],
+        })),
+        test({
+          code: "import type x from './foo'; import type x from './foo'",
+          output: "import type x from './foo'; ",
+          ...parserConfig,
+          errors: [
+            {
+              line: 1,
+              column: 20,
+              message: "'./foo' imported multiple times.",
+            },
+            {
+              line: 1,
+              column: 48,
+              message: "'./foo' imported multiple times.",
+            },
+          ],
+        }),
+        test({
+          code: "import type {x} from './foo'; import type {y} from './foo'",
+          ...parserConfig,
+          output: `import type {x,y} from './foo'; `,
+          errors: [
+            {
+              line: 1,
+              column: 22,
+              message: "'./foo' imported multiple times.",
+            },
+            {
+              line: 1,
+              column: 52,
+              message: "'./foo' imported multiple times.",
+            },
+          ],
+        }),
+      ].concat(!tsVersionSatisfies('>= 4.5') || !typescriptEslintParserSatisfies('>= 5.7.0') ? [] : [
+        test({
+          code: "import {type x} from './foo'; import type {y} from './foo'",
+          ...parserConfig,
+          options: [{ 'prefer-inline': false }],
+          output: `import {type x,y} from './foo'; `,
+          errors: [
+            {
+              line: 1,
+              column: 22,
+              message: "'./foo' imported multiple times.",
+            },
+            {
+              line: 1,
+              column: 52,
+              message: "'./foo' imported multiple times.",
+            },
+          ],
+        }),
+        test({
+          code: "import {type x} from 'foo'; import type {y} from 'foo'",
+          ...parserConfig,
+          options: [{ 'prefer-inline': true }],
+          output: `import {type x,type y} from 'foo'; `,
+          errors: [
+            {
+              line: 1,
+              column: 22,
+              message: "'foo' imported multiple times.",
+            },
+            {
+              line: 1,
+              column: 50,
+              message: "'foo' imported multiple times.",
+            },
+          ],
+        }),
+        test({
+          code: "import type {x} from 'foo'; import {type y} from 'foo'",
+          ...parserConfig,
+          options: [{ 'prefer-inline': true }],
+          output: `import {type x,type y} from 'foo'; `,
+          errors: [
+            {
+              line: 1,
+              column: 22,
+              message: "'foo' imported multiple times.",
+            },
+            {
+              line: 1,
+              column: 50,
+              message: "'foo' imported multiple times.",
+            },
+          ],
+        }),
+        test({
+          code: "import {type x} from 'foo'; import type {y} from 'foo'",
+          ...parserConfig,
+          output: `import {type x,y} from 'foo'; `,
+          errors: [
+            {
+              line: 1,
+              column: 22,
+              message: "'foo' imported multiple times.",
+            },
+            {
+              line: 1,
+              column: 50,
+              message: "'foo' imported multiple times.",
+            },
+          ],
+        }),
+        test({
+          code: "import {type x} from './foo'; import {type y} from './foo'",
+          ...parserConfig,
+          options: [{ 'prefer-inline': true }],
+          output: `import {type x,type y} from './foo'; `,
+          errors: [
+            {
+              line: 1,
+              column: 22,
+              message: "'./foo' imported multiple times.",
+            },
+            {
+              line: 1,
+              column: 52,
+              message: "'./foo' imported multiple times.",
+            },
+          ],
+        }),
+        test({
+          code: "import {type x} from './foo'; import {type y} from './foo'",
+          ...parserConfig,
+          output: `import {type x,type y} from './foo'; `,
+          errors: [
+            {
+              line: 1,
+              column: 22,
+              message: "'./foo' imported multiple times.",
+            },
+            {
+              line: 1,
+              column: 52,
+              message: "'./foo' imported multiple times.",
+            },
+          ],
+        }),
+        test({
+          code: "import {AValue, type x, BValue} from './foo'; import {type y} from './foo'",
+          ...parserConfig,
+          output: `import {AValue, type x, BValue,type y} from './foo'; `,
+          errors: [
+            {
+              line: 1,
+              column: 38,
+              message: "'./foo' imported multiple times.",
+            },
+            {
+              line: 1,
+              column: 68,
+              message: "'./foo' imported multiple times.",
+            },
+          ],
+        }),
+        // #2834 Detect duplicates across type and regular imports
+        test({
+          code: "import {AValue} from './foo'; import type {AType} from './foo'",
+          ...parserConfig,
+          options: [{ 'prefer-inline': true }],
+          output: `import {AValue,type AType} from './foo'; `,
+          errors: [
+            {
+              line: 1,
+              column: 22,
+              message: "'./foo' imported multiple times.",
+            },
+            {
+              line: 1,
+              column: 56,
+              message: "'./foo' imported multiple times.",
+            },
+          ],
+        }),
+      ]);
+
+      ruleTester.run('no-duplicates', rule, {
+        valid,
+        invalid,
+      });
+    });
+});
diff --git a/tests/src/rules/no-dynamic-require.js b/tests/src/rules/no-dynamic-require.js
index 8793d0dd8..fc7cf2b06 100644
--- a/tests/src/rules/no-dynamic-require.js
+++ b/tests/src/rules/no-dynamic-require.js
@@ -1,28 +1,121 @@
-import { test } from '../utils'
+import { parsers, test, testVersion } from '../utils';
 
-import { RuleTester } from 'eslint'
+import { RuleTester } from '../rule-tester';
+import flatMap from 'array.prototype.flatmap';
 
-const ruleTester = new RuleTester()
-    , rule = require('rules/no-dynamic-require')
+const ruleTester = new RuleTester();
+const rule = require('rules/no-dynamic-require');
 
 const error = {
-  ruleId: 'no-dynamic-require',
   message: 'Calls to require() should use string literals',
-}
+};
+
+const dynamicImportError = {
+  message: 'Calls to import() should use string literals',
+};
 
 ruleTester.run('no-dynamic-require', rule, {
   valid: [
-    test({ code: 'import _ from "lodash"'}),
-    test({ code: 'require("foo")'}),
-    test({ code: 'require(`foo`)'}),
-    test({ code: 'require("./foo")'}),
-    test({ code: 'require("@scope/foo")'}),
-    test({ code: 'require()'}),
-    test({ code: 'require("./foo", "bar" + "okay")'}),
-    test({ code: 'var foo = require("foo")'}),
-    test({ code: 'var foo = require(`foo`)'}),
-    test({ code: 'var foo = require("./foo")'}),
-    test({ code: 'var foo = require("@scope/foo")'}),
+    test({ code: 'import _ from "lodash"' }),
+    test({ code: 'require("foo")' }),
+    test({ code: 'require(`foo`)' }),
+    test({ code: 'require("./foo")' }),
+    test({ code: 'require("@scope/foo")' }),
+    test({ code: 'require()' }),
+    test({ code: 'require("./foo", "bar" + "okay")' }),
+    test({ code: 'var foo = require("foo")' }),
+    test({ code: 'var foo = require(`foo`)' }),
+    test({ code: 'var foo = require("./foo")' }),
+    test({ code: 'var foo = require("@scope/foo")' }),
+
+    //dynamic import
+    ...flatMap([parsers.ESPREE, parsers.BABEL_OLD], (parser) => {
+      const _test = parser === parsers.ESPREE
+        ? (testObj) => testVersion('>= 6.2.0', () => testObj)
+        : (testObj) => test(testObj);
+      return [].concat(
+        _test({
+          code: 'import("foo")',
+          options: [{ esmodule: true }],
+          parser,
+          parserOptions: {
+            ecmaVersion: 2020,
+          },
+        }),
+        _test({
+          code: 'import(`foo`)',
+          options: [{ esmodule: true }],
+          parser,
+          parserOptions: {
+            ecmaVersion: 2020,
+          },
+        }),
+        _test({
+          code: 'import("./foo")',
+          options: [{ esmodule: true }],
+          parser,
+          parserOptions: {
+            ecmaVersion: 2020,
+          },
+        }),
+        _test({
+          code: 'import("@scope/foo")',
+          options: [{ esmodule: true }],
+          parser,
+          parserOptions: {
+            ecmaVersion: 2020,
+          },
+        }),
+        _test({
+          code: 'var foo = import("foo")',
+          options: [{ esmodule: true }],
+          parser,
+          parserOptions: {
+            ecmaVersion: 2020,
+          },
+        }),
+        _test({
+          code: 'var foo = import(`foo`)',
+          options: [{ esmodule: true }],
+          parser,
+          parserOptions: {
+            ecmaVersion: 2020,
+          },
+        }),
+        _test({
+          code: 'var foo = import("./foo")',
+          options: [{ esmodule: true }],
+          parser,
+          parserOptions: {
+            ecmaVersion: 2020,
+          },
+        }),
+        _test({
+          code: 'var foo = import("@scope/foo")',
+          options: [{ esmodule: true }],
+          parser,
+          parserOptions: {
+            ecmaVersion: 2020,
+          },
+        }),
+        _test({
+          code: 'import("../" + name)',
+          errors: [dynamicImportError],
+          parser,
+          parserOptions: {
+            ecmaVersion: 2020,
+          },
+        }),
+        _test({
+          code: 'import(`../${name}`)',
+          errors: [dynamicImportError],
+          parser,
+          parserOptions: {
+            ecmaVersion: 2020,
+          },
+        }),
+      );
+    }),
   ],
   invalid: [
     test({
@@ -44,6 +137,60 @@ ruleTester.run('no-dynamic-require', rule, {
     test({
       code: 'require(name + "foo", "bar")',
       errors: [error],
+      options: [{ esmodule: true }],
+    }),
+
+    // dynamic import
+    ...flatMap([parsers.ESPREE, parsers.BABEL_OLD], (parser) => {
+      const _test = parser === parsers.ESPREE
+        ? (testObj) => testVersion('>= 6.2.0', () => testObj)
+        : (testObj) => test(testObj);
+      return [].concat(
+        _test({
+          code: 'import("../" + name)',
+          errors: [dynamicImportError],
+          options: [{ esmodule: true }],
+          parser,
+          parserOptions: {
+            ecmaVersion: 2020,
+          },
+        }),
+        _test({
+          code: 'import(`../${name}`)',
+          errors: [dynamicImportError],
+          options: [{ esmodule: true }],
+          parser,
+          parserOptions: {
+            ecmaVersion: 2020,
+          },
+        }),
+        _test({
+          code: 'import(name)',
+          errors: [dynamicImportError],
+          options: [{ esmodule: true }],
+          parser,
+          parserOptions: {
+            ecmaVersion: 2020,
+          },
+        }),
+        _test({
+          code: 'import(name())',
+          errors: [dynamicImportError],
+          options: [{ esmodule: true }],
+          parser,
+          parserOptions: {
+            ecmaVersion: 2020,
+          },
+        }),
+      );
+    }),
+    test({
+      code: 'require(`foo${x}`)',
+      errors: [error],
+    }),
+    test({
+      code: 'var foo = require(`foo${x}`)',
+      errors: [error],
     }),
   ],
-})
+});
diff --git a/tests/src/rules/no-empty-named-blocks.js b/tests/src/rules/no-empty-named-blocks.js
new file mode 100644
index 000000000..d9514a845
--- /dev/null
+++ b/tests/src/rules/no-empty-named-blocks.js
@@ -0,0 +1,116 @@
+import { parsers, test } from '../utils';
+
+import { RuleTester } from '../rule-tester';
+
+const ruleTester = new RuleTester();
+const rule = require('rules/no-empty-named-blocks');
+
+function generateSuggestionsTestCases(cases, parser) {
+  return cases.map((code) => test({
+    code,
+    parser,
+    errors: [{
+      message: 'Unexpected empty named import block',
+      suggestions: [
+        {
+          desc: 'Remove unused import',
+          output: '',
+        },
+        {
+          desc: 'Remove empty import block',
+          output: `import 'mod';`,
+        },
+      ],
+    }],
+  }));
+}
+
+ruleTester.run('no-empty-named-blocks', rule, {
+  valid: [].concat(
+    test({ code: `import 'mod';` }),
+    test({ code: `import Default from 'mod';` }),
+    test({ code: `import { Named } from 'mod';` }),
+    test({ code: `import Default, { Named } from 'mod';` }),
+    test({ code: `import * as Namespace from 'mod';` }),
+
+    // Typescript
+    parsers.TS_NEW ? [
+      test({ code: `import type Default from 'mod';`, parser: parsers.TS_NEW }),
+      test({ code: `import type { Named } from 'mod';`, parser: parsers.TS_NEW }),
+      test({ code: `import type Default, { Named } from 'mod';`, parser: parsers.TS_NEW }),
+      test({ code: `import type * as Namespace from 'mod';`, parser: parsers.TS_NEW }),
+    ] : [],
+
+    // Flow
+    test({ code: `import typeof Default from 'mod'; // babel old`, parser: parsers.BABEL_OLD }),
+    test({ code: `import typeof { Named } from 'mod'; // babel old`, parser: parsers.BABEL_OLD }),
+    test({ code: `import typeof Default, { Named } from 'mod'; // babel old`, parser: parsers.BABEL_OLD }),
+    test({
+      code: `
+        module.exports = {
+          rules: {
+            'keyword-spacing': ['error', {overrides: {}}],
+          }
+        };
+      `,
+    }),
+    test({
+      code: `
+        import { DESCRIPTORS, NODE } from '../helpers/constants';
+        // ...
+        import { timeLimitedPromise } from '../helpers/helpers';
+        // ...
+        import { DESCRIPTORS2 } from '../helpers/constants';
+      `,
+    }),
+  ),
+  invalid: [].concat(
+    test({
+      code: `import Default, {} from 'mod';`,
+      output: `import Default from 'mod';`,
+      errors: ['Unexpected empty named import block'],
+    }),
+    generateSuggestionsTestCases([
+      `import {} from 'mod';`,
+      `import{}from'mod';`,
+      `import {} from'mod';`,
+      `import {}from 'mod';`,
+    ]),
+
+    // Typescript
+    parsers.TS_NEW ? [].concat(
+      generateSuggestionsTestCases(
+        [
+          `import type {} from 'mod';`,
+          `import type {}from 'mod';`,
+          `import type{}from 'mod';`,
+          `import type {}from'mod';`,
+        ],
+        parsers.TS_NEW,
+      ),
+      test({
+        code: `import type Default, {} from 'mod';`,
+        output: `import type Default from 'mod';`,
+        parser: parsers.TS_NEW,
+        errors: ['Unexpected empty named import block'],
+      }),
+    ) : [],
+
+    // Flow
+    generateSuggestionsTestCases(
+      [
+        `import typeof {} from 'mod';`,
+        `import typeof {}from 'mod';`,
+        `import typeof {} from'mod';`,
+        `import typeof{}from'mod';`,
+      ],
+      parsers.BABEL_OLD,
+    ),
+    test({
+      code: `import typeof Default, {} from 'mod';`,
+      output: `import typeof Default from 'mod';`,
+      parser: parsers.BABEL_OLD,
+      errors: ['Unexpected empty named import block'],
+    }),
+  ),
+});
diff --git a/tests/src/rules/no-extraneous-dependencies.js b/tests/src/rules/no-extraneous-dependencies.js
index b9d24580e..4a465eb39 100644
--- a/tests/src/rules/no-extraneous-dependencies.js
+++ b/tests/src/rules/no-extraneous-dependencies.js
@@ -1,265 +1,328 @@
-import { test } from '../utils'
-import * as path from 'path'
-import * as fs from 'fs'
+import { getTSParsers, parsers, test, testFilePath } from '../utils';
+import typescriptConfig from '../../../config/typescript';
+import path from 'path';
+import fs from 'fs';
 
-import { RuleTester } from 'eslint'
-const ruleTester = new RuleTester()
-    , rule = require('rules/no-extraneous-dependencies')
+import { RuleTester } from '../rule-tester';
+import flatMap from 'array.prototype.flatmap';
 
-const packageDirWithSyntaxError = path.join(__dirname, '../../files/with-syntax-error')
+const ruleTester = new RuleTester();
+const typescriptRuleTester = new RuleTester(typescriptConfig);
+const rule = require('rules/no-extraneous-dependencies');
+
+const packageDirWithSyntaxError = path.join(__dirname, '../../files/with-syntax-error');
 const packageFileWithSyntaxErrorMessage = (() => {
   try {
-    JSON.parse(fs.readFileSync(path.join(packageDirWithSyntaxError, 'package.json')))
+    JSON.parse(fs.readFileSync(path.join(packageDirWithSyntaxError, 'package.json')));
   } catch (error) {
-    return error.message
+    return error.message;
   }
-})()
-const packageDirWithFlowTyped = path.join(__dirname, '../../files/with-flow-typed')
-const packageDirMonoRepoRoot = path.join(__dirname, '../../files/monorepo')
-const packageDirMonoRepoWithNested = path.join(__dirname, '../../files/monorepo/packages/nested-package')
-const packageDirWithEmpty = path.join(__dirname, '../../files/empty')
+})();
+const packageDirWithFlowTyped = path.join(__dirname, '../../files/with-flow-typed');
+const packageDirWithTypescriptDevDependencies = path.join(__dirname, '../../files/with-typescript-dev-dependencies');
+const packageDirMonoRepoRoot = path.join(__dirname, '../../files/monorepo');
+const packageDirMonoRepoWithNested = path.join(__dirname, '../../files/monorepo/packages/nested-package');
+const packageDirWithEmpty = path.join(__dirname, '../../files/empty');
+const packageDirBundleDeps = path.join(__dirname, '../../files/bundled-dependencies/as-array-bundle-deps');
+const packageDirBundledDepsAsObject = path.join(__dirname, '../../files/bundled-dependencies/as-object');
+const packageDirBundledDepsRaceCondition = path.join(__dirname, '../../files/bundled-dependencies/race-condition');
+const emptyPackageDir = path.join(__dirname, '../../files/empty-folder');
+
+const {
+  dependencies: deps,
+  devDependencies: devDeps,
+} = require('../../files/package.json');
 
 ruleTester.run('no-extraneous-dependencies', rule, {
   valid: [
-    test({ code: 'import "lodash.cond"'}),
-    test({ code: 'import "pkg-up"'}),
-    test({ code: 'import foo, { bar } from "lodash.cond"'}),
-    test({ code: 'import foo, { bar } from "pkg-up"'}),
-    test({ code: 'import "eslint"'}),
-    test({ code: 'import "eslint/lib/api"'}),
-    test({ code: 'require("lodash.cond")'}),
-    test({ code: 'require("pkg-up")'}),
-    test({ code: 'var foo = require("lodash.cond")'}),
-    test({ code: 'var foo = require("pkg-up")'}),
-    test({ code: 'import "fs"'}),
-    test({ code: 'import "./foo"'}),
-    test({ code: 'import "lodash.isarray"'}),
-    test({ code: 'import "@org/package"'}),
+    ...flatMap(Object.keys(deps).concat(Object.keys(devDeps)), (pkg) => [
+      test({ code: `import "${pkg}"` }),
+      test({ code: `import foo, { bar } from "${pkg}"` }),
+      test({ code: `require("${pkg}")` }),
+      test({ code: `var foo = require("${pkg}")` }),
+      test({ code: `export { foo } from "${pkg}"` }),
+      test({ code: `export * from "${pkg}"` }),
+    ]),
+    test({ code: 'import "eslint/lib/api"' }),
+    test({ code: 'import "fs"' }),
+    test({ code: 'import "./foo"' }),
 
     test({ code: 'import "electron"', settings: { 'import/core-modules': ['electron'] } }),
-    test({ code: 'import "eslint"' }),
     test({
       code: 'import "eslint"',
-      options: [{peerDependencies: true}],
+      options: [{ peerDependencies: true }],
     }),
 
     // 'project' type
     test({
       code: 'import "importType"',
-      settings: { 'import/resolver': { node: { paths: [ path.join(__dirname, '../../files') ] } } },
+      settings: { 'import/resolver': { node: { paths: [path.join(__dirname, '../../files')] } } },
     }),
     test({
       code: 'import chai from "chai"',
-      options: [{devDependencies: ['*.spec.js']}],
+      options: [{ devDependencies: ['*.spec.js'] }],
       filename: 'foo.spec.js',
     }),
     test({
       code: 'import chai from "chai"',
-      options: [{devDependencies: ['*.spec.js']}],
+      options: [{ devDependencies: ['*.spec.js'] }],
       filename: path.join(process.cwd(), 'foo.spec.js'),
     }),
     test({
       code: 'import chai from "chai"',
-      options: [{devDependencies: ['*.test.js', '*.spec.js']}],
-      filename: path.join(process.cwd(), 'foo.spec.js'),
-    }),
-    test({
-      code: 'import chai from "chai"',
-      options: [{devDependencies: ['*.test.js', '*.spec.js']}],
+      options: [{ devDependencies: ['*.test.js', '*.spec.js'] }],
       filename: path.join(process.cwd(), 'foo.spec.js'),
     }),
     test({ code: 'require(6)' }),
     test({
       code: 'import "doctrine"',
-      options: [{packageDir: path.join(__dirname, '../../../')}],
+      options: [{ packageDir: path.join(__dirname, '../../../') }],
     }),
     test({
       code: 'import type MyType from "myflowtyped";',
-      options: [{packageDir: packageDirWithFlowTyped}],
-      parser: require.resolve('babel-eslint'),
+      options: [{ packageDir: packageDirWithFlowTyped }],
+      parser: parsers.BABEL_OLD,
+    }),
+    test({
+      code: `
+        // @flow
+        import typeof TypeScriptModule from 'typescript';
+      `,
+      options: [{ packageDir: packageDirWithFlowTyped }],
+      parser: parsers.BABEL_OLD,
     }),
     test({
       code: 'import react from "react";',
-      options: [{packageDir: packageDirMonoRepoWithNested}],
+      options: [{ packageDir: packageDirMonoRepoWithNested }],
     }),
     test({
       code: 'import leftpad from "left-pad";',
-      options: [{packageDir: [packageDirMonoRepoWithNested, packageDirMonoRepoRoot]}],
+      options: [{ packageDir: [packageDirMonoRepoWithNested, packageDirMonoRepoRoot] }],
     }),
     test({
       code: 'import leftpad from "left-pad";',
-      options: [{packageDir: packageDirMonoRepoRoot}],
+      options: [{ packageDir: packageDirMonoRepoRoot }],
     }),
     test({
-      code: 'import react from "react";',
-      options: [{packageDir: [packageDirMonoRepoRoot, packageDirMonoRepoWithNested]}],
+      code: 'import leftpad from "left-pad";',
+      options: [{ packageDir: [emptyPackageDir, packageDirMonoRepoRoot] }],
     }),
     test({
       code: 'import leftpad from "left-pad";',
-      options: [{packageDir: [packageDirMonoRepoRoot, packageDirMonoRepoWithNested]}],
+      options: [{ packageDir: [packageDirMonoRepoRoot, emptyPackageDir] }],
+    }),
+    test({
+      code: 'import react from "react";',
+      options: [{ packageDir: [packageDirMonoRepoRoot, packageDirMonoRepoWithNested] }],
     }),
     test({
       code: 'import leftpad from "left-pad";',
-      options: [{packageDir: [packageDirMonoRepoWithNested, packageDirMonoRepoRoot]}],
+      options: [{ packageDir: [packageDirMonoRepoRoot, packageDirMonoRepoWithNested] }],
     }),
     test({
       code: 'import rightpad from "right-pad";',
-      options: [{packageDir: [packageDirMonoRepoRoot, packageDirMonoRepoWithNested]}],
+      options: [{ packageDir: [packageDirMonoRepoRoot, packageDirMonoRepoWithNested] }],
+    }),
+    test({ code: 'import foo from "@generated/foo"' }),
+    test({
+      code: 'import foo from "@generated/foo"',
+      options: [{ packageDir: packageDirBundleDeps }],
+    }),
+    test({
+      code: 'import foo from "@generated/foo"',
+      options: [{ packageDir: packageDirBundledDepsAsObject }],
+    }),
+    test({
+      code: 'import foo from "@generated/foo"',
+      options: [{ packageDir: packageDirBundledDepsRaceCondition }],
+    }),
+    test({ code: 'export function getToken() {}' }),
+    test({ code: 'export class Component extends React.Component {}' }),
+    test({ code: 'export function Component() {}' }),
+    test({ code: 'export const Component = () => {}' }),
+
+    test({
+      code: 'import "not-a-dependency"',
+      filename: path.join(packageDirMonoRepoRoot, 'foo.js'),
+      options: [{ packageDir: packageDirMonoRepoRoot }],
+      settings: { 'import/core-modules': ['not-a-dependency'] },
+    }),
+    test({
+      code: 'import "@generated/bar/module"',
+      settings: { 'import/core-modules': ['@generated/bar'] },
+    }),
+    test({
+      code: 'import "@generated/bar/and/sub/path"',
+      settings: { 'import/core-modules': ['@generated/bar'] },
+    }),
+    // check if "rxjs" dependency declaration fix the "rxjs/operators subpackage
+    test({
+      code: 'import "rxjs/operators"',
+    }),
+
+    test({
+      code: 'import "esm-package/esm-module";',
+    }),
+
+    test({
+      code: `
+        import "alias/esm-package/esm-module";
+        import 'expose-loader?exposes[]=$&exposes[]=jQuery!jquery';
+      `,
+      settings: { 'import/resolver': 'webpack' },
+    }),
+
+    test({
+      code: 'import "@custom-internal-alias/api/service";',
+      settings: {
+        'import/resolver': {
+          webpack: {
+            config: {
+              resolve: {
+                alias: {
+                  '@custom-internal-alias': testFilePath('internal-modules'),
+                },
+              },
+            },
+          },
+        },
+      },
     }),
   ],
   invalid: [
     test({
       code: 'import "not-a-dependency"',
       filename: path.join(packageDirMonoRepoRoot, 'foo.js'),
-      options: [{packageDir: packageDirMonoRepoRoot }],
+      options: [{ packageDir: packageDirMonoRepoRoot }],
       errors: [{
-        ruleId: 'no-extraneous-dependencies',
         message: '\'not-a-dependency\' should be listed in the project\'s dependencies. Run \'npm i -S not-a-dependency\' to add it',
       }],
     }),
     test({
       code: 'import "not-a-dependency"',
       filename: path.join(packageDirMonoRepoWithNested, 'foo.js'),
-      options: [{packageDir: packageDirMonoRepoRoot}],
+      options: [{ packageDir: packageDirMonoRepoRoot }],
       errors: [{
-        ruleId: 'no-extraneous-dependencies',
         message: '\'not-a-dependency\' should be listed in the project\'s dependencies. Run \'npm i -S not-a-dependency\' to add it',
       }],
     }),
     test({
       code: 'import "not-a-dependency"',
-      options: [{packageDir: packageDirMonoRepoRoot}],
+      options: [{ packageDir: packageDirMonoRepoRoot }],
       errors: [{
-        ruleId: 'no-extraneous-dependencies',
         message: '\'not-a-dependency\' should be listed in the project\'s dependencies. Run \'npm i -S not-a-dependency\' to add it',
       }],
     }),
     test({
       code: 'import "not-a-dependency"',
       errors: [{
-        ruleId: 'no-extraneous-dependencies',
         message: '\'not-a-dependency\' should be listed in the project\'s dependencies. Run \'npm i -S not-a-dependency\' to add it',
       }],
     }),
     test({
       code: 'var donthaveit = require("@org/not-a-dependency")',
       errors: [{
-        ruleId: 'no-extraneous-dependencies',
         message: '\'@org/not-a-dependency\' should be listed in the project\'s dependencies. Run \'npm i -S @org/not-a-dependency\' to add it',
       }],
     }),
     test({
       code: 'var donthaveit = require("@org/not-a-dependency/foo")',
       errors: [{
-        ruleId: 'no-extraneous-dependencies',
         message: '\'@org/not-a-dependency\' should be listed in the project\'s dependencies. Run \'npm i -S @org/not-a-dependency\' to add it',
       }],
     }),
     test({
       code: 'import "eslint"',
-      options: [{devDependencies: false, peerDependencies: false}],
+      options: [{ devDependencies: false, peerDependencies: false }],
       errors: [{
-        ruleId: 'no-extraneous-dependencies',
         message: '\'eslint\' should be listed in the project\'s dependencies, not devDependencies.',
       }],
     }),
     test({
       code: 'import "lodash.isarray"',
-      options: [{optionalDependencies: false}],
+      options: [{ optionalDependencies: false }],
       errors: [{
-        ruleId: 'no-extraneous-dependencies',
         message: '\'lodash.isarray\' should be listed in the project\'s dependencies, not optionalDependencies.',
       }],
     }),
     test({
       code: 'var foo = require("not-a-dependency")',
       errors: [{
-        ruleId: 'no-extraneous-dependencies',
         message: '\'not-a-dependency\' should be listed in the project\'s dependencies. Run \'npm i -S not-a-dependency\' to add it',
       }],
     }),
     test({
       code: 'var glob = require("glob")',
-      options: [{devDependencies: false}],
+      options: [{ devDependencies: false }],
       errors: [{
-        ruleId: 'no-extraneous-dependencies',
         message: '\'glob\' should be listed in the project\'s dependencies, not devDependencies.',
       }],
     }),
     test({
       code: 'import chai from "chai"',
-      options: [{devDependencies: ['*.test.js']}],
+      options: [{ devDependencies: ['*.test.js'] }],
       filename: 'foo.tes.js',
       errors: [{
-        ruleId: 'no-extraneous-dependencies',
         message: '\'chai\' should be listed in the project\'s dependencies, not devDependencies.',
       }],
     }),
     test({
       code: 'import chai from "chai"',
-      options: [{devDependencies: ['*.test.js']}],
+      options: [{ devDependencies: ['*.test.js'] }],
       filename: path.join(process.cwd(), 'foo.tes.js'),
       errors: [{
-        ruleId: 'no-extraneous-dependencies',
         message: '\'chai\' should be listed in the project\'s dependencies, not devDependencies.',
       }],
     }),
     test({
       code: 'import chai from "chai"',
-      options: [{devDependencies: ['*.test.js', '*.spec.js']}],
+      options: [{ devDependencies: ['*.test.js', '*.spec.js'] }],
       filename: 'foo.tes.js',
       errors: [{
-        ruleId: 'no-extraneous-dependencies',
         message: '\'chai\' should be listed in the project\'s dependencies, not devDependencies.',
       }],
     }),
     test({
       code: 'import chai from "chai"',
-      options: [{devDependencies: ['*.test.js', '*.spec.js']}],
+      options: [{ devDependencies: ['*.test.js', '*.spec.js'] }],
       filename: path.join(process.cwd(), 'foo.tes.js'),
       errors: [{
-        ruleId: 'no-extraneous-dependencies',
         message: '\'chai\' should be listed in the project\'s dependencies, not devDependencies.',
       }],
     }),
     test({
       code: 'var eslint = require("lodash.isarray")',
-      options: [{optionalDependencies: false}],
+      options: [{ optionalDependencies: false }],
       errors: [{
-        ruleId: 'no-extraneous-dependencies',
         message: '\'lodash.isarray\' should be listed in the project\'s dependencies, not optionalDependencies.',
       }],
     }),
     test({
       code: 'import "not-a-dependency"',
-      options: [{packageDir: path.join(__dirname, '../../../')}],
+      options: [{ packageDir: path.join(__dirname, '../../../') }],
       errors: [{
-        ruleId: 'no-extraneous-dependencies',
         message: '\'not-a-dependency\' should be listed in the project\'s dependencies. Run \'npm i -S not-a-dependency\' to add it',
       }],
     }),
     test({
       code: 'import "bar"',
-      options: [{packageDir: path.join(__dirname, './doesn-exist/')}],
+      options: [{ packageDir: path.join(__dirname, './doesn-exist/') }],
       errors: [{
-        ruleId: 'no-extraneous-dependencies',
         message: 'The package.json file could not be found.',
       }],
     }),
     test({
       code: 'import foo from "foo"',
-      options: [{packageDir: packageDirWithSyntaxError}],
+      options: [{ packageDir: packageDirWithSyntaxError }],
       errors: [{
-        ruleId: 'no-extraneous-dependencies',
-        message: 'The package.json file could not be parsed: ' + packageFileWithSyntaxErrorMessage,
+        message: `The package.json file could not be parsed: ${packageFileWithSyntaxErrorMessage}`,
       }],
     }),
     test({
       code: 'import leftpad from "left-pad";',
       filename: path.join(packageDirMonoRepoWithNested, 'foo.js'),
-      options: [{packageDir: packageDirMonoRepoWithNested}],
+      options: [{ packageDir: packageDirMonoRepoWithNested }],
       errors: [{
-        ruleId: 'no-extraneous-dependencies',
         message: "'left-pad' should be listed in the project's dependencies. Run 'npm i -S left-pad' to add it",
       }],
     }),
@@ -267,27 +330,193 @@ ruleTester.run('no-extraneous-dependencies', rule, {
       code: 'import react from "react";',
       filename: path.join(packageDirMonoRepoRoot, 'foo.js'),
       errors: [{
-        ruleId: 'no-extraneous-dependencies',
         message: "'react' should be listed in the project's dependencies. Run 'npm i -S react' to add it",
       }],
     }),
     test({
       code: 'import react from "react";',
       filename: path.join(packageDirMonoRepoWithNested, 'foo.js'),
-      options: [{packageDir: packageDirMonoRepoRoot}],
+      options: [{ packageDir: packageDirMonoRepoRoot }],
       errors: [{
-        ruleId: 'no-extraneous-dependencies',
         message: "'react' should be listed in the project's dependencies. Run 'npm i -S react' to add it",
       }],
     }),
     test({
       code: 'import "react";',
       filename: path.join(packageDirWithEmpty, 'index.js'),
-      options: [{packageDir: packageDirWithEmpty}],
+      options: [{ packageDir: packageDirWithEmpty }],
       errors: [{
-        ruleId: 'no-extraneous-dependencies',
         message: "'react' should be listed in the project's dependencies. Run 'npm i -S react' to add it",
       }],
     }),
+    test({
+      code: 'import bar from "@generated/bar"',
+      errors: ["'@generated/bar' should be listed in the project's dependencies. Run 'npm i -S @generated/bar' to add it"],
+    }),
+    test({
+      code: 'import foo from "@generated/foo"',
+      options: [{ bundledDependencies: false }],
+      errors: ["'@generated/foo' should be listed in the project's dependencies. Run 'npm i -S @generated/foo' to add it"],
+    }),
+    test({
+      code: 'import bar from "@generated/bar"',
+      options: [{ packageDir: packageDirBundledDepsRaceCondition }],
+      errors: ["'@generated/bar' should be listed in the project's dependencies. Run 'npm i -S @generated/bar' to add it"],
+    }),
+    test({
+      code: 'export { foo } from "not-a-dependency";',
+      errors: [{
+        message: '\'not-a-dependency\' should be listed in the project\'s dependencies. Run \'npm i -S not-a-dependency\' to add it',
+      }],
+    }),
+    test({
+      code: 'export * from "not-a-dependency";',
+      errors: [{
+        message: '\'not-a-dependency\' should be listed in the project\'s dependencies. Run \'npm i -S not-a-dependency\' to add it',
+      }],
+    }),
+    test({
+      code: 'import chai from "alias/chai";',
+      settings: { 'import/resolver': 'webpack' },
+      errors: [{
+        // missing dependency is chai not alias
+        message: "'chai' should be listed in the project's dependencies. Run 'npm i -S chai' to add it",
+      }],
+    }),
+
+    test({
+      code: 'import "esm-package-not-in-pkg-json/esm-module";',
+      errors: [{
+        message: `'esm-package-not-in-pkg-json' should be listed in the project's dependencies. Run 'npm i -S esm-package-not-in-pkg-json' to add it`,
+      }],
+    }),
+
+    test({
+      code: 'import "not-a-dependency"',
+      settings: {
+        'import/resolver': { node: { paths: [path.join(__dirname, '../../files')] } },
+        'import/internal-regex': '^not-a-dependency.*',
+      },
+      options: [{ includeInternal: true }],
+      errors: [{
+        message: '\'not-a-dependency\' should be listed in the project\'s dependencies. Run \'npm i -S not-a-dependency\' to add it',
+      }],
+    }),
+  ],
+});
+
+describe('TypeScript', () => {
+  getTSParsers()
+    // Type-only imports were added in TypeScript ESTree 2.23.0
+    .filter((parser) => parser !== parsers.TS_OLD)
+    .forEach((parser) => {
+      const parserConfig = {
+        parser,
+        settings: {
+          'import/parsers': { [parser]: ['.ts'] },
+          'import/resolver': ['node', 'typescript'],
+        },
+      };
+
+      ruleTester.run('no-extraneous-dependencies', rule, {
+        valid: [
+          test({
+            code: 'import type T from "a";',
+            options: [{ packageDir: packageDirWithTypescriptDevDependencies, devDependencies: false }],
+            ...parserConfig,
+          }),
+
+          test({
+            code: 'import type { T } from "a"; export type { T };',
+            options: [{ packageDir: packageDirWithTypescriptDevDependencies, devDependencies: false }],
+            ...parserConfig,
+          }),
+
+          test({
+            code: 'export type { T } from "a";',
+            options: [{ packageDir: packageDirWithTypescriptDevDependencies, devDependencies: false }],
+            ...parserConfig,
+          }),
+        ],
+        invalid: [
+          test({
+            code: 'import T from "a";',
+            options: [{ packageDir: packageDirWithTypescriptDevDependencies, devDependencies: false }],
+            errors: [{ message: "'a' should be listed in the project's dependencies, not devDependencies." }],
+            ...parserConfig,
+          }),
+
+          test({ code: 'import type T from "a";',
+            options: [{
+              packageDir: packageDirWithTypescriptDevDependencies,
+              devDependencies: false,
+              includeTypes: true,
+            }],
+            errors: [{ message: "'a' should be listed in the project's dependencies, not devDependencies." }],
+            ...parserConfig,
+          }),
+        ],
+      });
+    });
+});
+
+typescriptRuleTester.run('no-extraneous-dependencies typescript type imports', rule, {
+  valid: [
+    test({
+      code: 'import type MyType from "not-a-dependency";',
+      filename: testFilePath('./no-unused-modules/typescript/file-ts-a.ts'),
+      parser: parsers.BABEL_OLD,
+    }),
+    test({
+      code: 'import type { MyType } from "not-a-dependency";',
+      filename: testFilePath('./no-unused-modules/typescript/file-ts-a.ts'),
+      parser: parsers.BABEL_OLD,
+    }),
+    test({
+      code: 'import { type MyType } from "not-a-dependency";',
+      filename: testFilePath('./no-unused-modules/typescript/file-ts-a.ts'),
+      parser: parsers.BABEL_OLD,
+    }),
+    test({
+      code: 'import { type MyType, type OtherType } from "not-a-dependency";',
+      filename: testFilePath('./no-unused-modules/typescript/file-ts-a.ts'),
+      parser: parsers.BABEL_OLD,
+    }),
+  ],
+  invalid: [
+    test({
+      code: 'import type { MyType } from "not-a-dependency";',
+      options: [{ includeTypes: true }],
+      filename: testFilePath('./no-unused-modules/typescript/file-ts-a.ts'),
+      parser: parsers.BABEL_OLD,
+      errors: [{
+        message: `'not-a-dependency' should be listed in the project's dependencies. Run 'npm i -S not-a-dependency' to add it`,
+      }],
+    }),
+    test({
+      code: `import type { Foo } from 'not-a-dependency';`,
+      options: [{ includeTypes: true }],
+      filename: testFilePath('./no-unused-modules/typescript/file-ts-a.ts'),
+      parser: parsers.BABEL_OLD,
+      errors: [{
+        message: `'not-a-dependency' should be listed in the project's dependencies. Run 'npm i -S not-a-dependency' to add it`,
+      }],
+    }),
+    test({
+      code: 'import Foo, { type MyType } from "not-a-dependency";',
+      filename: testFilePath('./no-unused-modules/typescript/file-ts-a.ts'),
+      parser: parsers.BABEL_OLD,
+      errors: [{
+        message: `'not-a-dependency' should be listed in the project's dependencies. Run 'npm i -S not-a-dependency' to add it`,
+      }],
+    }),
+    test({
+      code: 'import { type MyType, Foo } from "not-a-dependency";',
+      filename: testFilePath('./no-unused-modules/typescript/file-ts-a.ts'),
+      parser: parsers.BABEL_OLD,
+      errors: [{
+        message: `'not-a-dependency' should be listed in the project's dependencies. Run 'npm i -S not-a-dependency' to add it`,
+      }],
+    }),
   ],
-})
+});
diff --git a/tests/src/rules/no-import-module-exports.js b/tests/src/rules/no-import-module-exports.js
new file mode 100644
index 000000000..5738f8c52
--- /dev/null
+++ b/tests/src/rules/no-import-module-exports.js
@@ -0,0 +1,163 @@
+import path from 'path';
+import { RuleTester } from '../rule-tester';
+
+import { eslintVersionSatisfies, test, testVersion } from '../utils';
+
+const ruleTester = new RuleTester({
+  parserOptions: { ecmaVersion: 6, sourceType: 'module' },
+});
+const rule = require('rules/no-import-module-exports');
+
+const error = {
+  message: `Cannot use import declarations in modules that export using CommonJS (module.exports = 'foo' or exports.bar = 'hi')`,
+  type: 'ImportDeclaration',
+};
+
+ruleTester.run('no-import-module-exports', rule, {
+  valid: [].concat(
+    test({
+      code: `
+        const thing = require('thing')
+        module.exports = thing
+      `,
+    }),
+    test({
+      code: `
+        import thing from 'otherthing'
+        console.log(thing.module.exports)
+      `,
+    }),
+    test({
+      code: `
+        import thing from 'other-thing'
+        export default thing
+      `,
+    }),
+    test({
+      code: `
+        const thing = require('thing')
+        exports.foo = bar
+      `,
+    }),
+    eslintVersionSatisfies('>= 4') ? test({
+      code: `
+        import { module } from 'qunit'
+        module.skip('A test', function () {})
+      `,
+    }) : [],
+    test({
+      code: `
+        import foo from 'path';
+        module.exports = foo;
+      `,
+      // When the file matches the entry point defined in package.json
+      // See tests/files/package.json
+      filename: path.join(process.cwd(), 'tests/files/index.js'),
+    }),
+    test({
+      code: `
+        import foo from 'path';
+        module.exports = foo;
+      `,
+      filename: path.join(process.cwd(), 'tests/files/some/other/entry-point.js'),
+      options: [{ exceptions: ['**/*/other/entry-point.js'] }],
+    }),
+    test({
+      code: `
+        import * as process from 'process';
+        console.log(process.env);
+      `,
+      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: `
+        import { stuff } from 'starwars'
+        module.exports = thing
+      `,
+      errors: [error],
+    }),
+    test({
+      code: `
+        import thing from 'starwars'
+        const baz = module.exports = thing
+        console.log(baz)
+      `,
+      errors: [error],
+    }),
+    test({
+      code: `
+        import * as allThings from 'starwars'
+        exports.bar = thing
+      `,
+      errors: [error],
+    }),
+    test({
+      code: `
+        import thing from 'other-thing'
+        exports.foo = bar
+      `,
+      errors: [error],
+    }),
+    test({
+      code: `
+        import foo from 'path';
+        module.exports = foo;
+      `,
+      filename: path.join(process.cwd(), 'tests/files/some/other/entry-point.js'),
+      options: [{ exceptions: ['**/*/other/file.js'] }],
+      errors: [error],
+    }),
+  ],
+});
diff --git a/tests/src/rules/no-internal-modules.js b/tests/src/rules/no-internal-modules.js
index 8ed1c623e..9fa91ea3d 100644
--- a/tests/src/rules/no-internal-modules.js
+++ b/tests/src/rules/no-internal-modules.js
@@ -1,12 +1,14 @@
-import { RuleTester } from 'eslint'
-import rule from 'rules/no-internal-modules'
+import { RuleTester } from '../rule-tester';
+import flatMap from 'array.prototype.flatmap';
+import rule from 'rules/no-internal-modules';
 
-import { test, testFilePath } from '../utils'
+import { test, testFilePath, getTSParsers } from '../utils';
 
-const ruleTester = new RuleTester()
+const ruleTester = new RuleTester();
 
 ruleTester.run('no-internal-modules', rule, {
   valid: [
+    // imports
     test({
       code: 'import a from "./plugin2"',
       filename: testFilePath('./internal-modules/plugins/plugin.js'),
@@ -39,63 +41,175 @@ ruleTester.run('no-internal-modules', rule, {
     test({
       code: 'import b from "../../api/service"',
       filename: testFilePath('./internal-modules/plugins/plugin2/internal.js'),
-      options: [ {
-        allow: [ '**/api/*' ],
-      } ],
+      options: [{
+        allow: ['**/api/*'],
+      }],
     }),
     test({
       code: 'import "jquery/dist/jquery"',
       filename: testFilePath('./internal-modules/plugins/plugin2/internal.js'),
-      options: [ {
-        allow: [ 'jquery/dist/*' ],
-      } ],
+      options: [{
+        allow: ['jquery/dist/*'],
+      }],
     }),
     test({
       code: 'import "./app/index.js";\nimport "./app/index"',
       filename: testFilePath('./internal-modules/plugins/plugin2/internal.js'),
-      options: [ {
-        allow: [ '**/index{.js,}' ],
-      } ],
+      options: [{
+        allow: ['**/index{.js,}'],
+      }],
+    }),
+    test({
+      code: 'import a from "./plugin2/thing"',
+      filename: testFilePath('./internal-modules/plugins/plugin.js'),
+      options: [{
+        forbid: ['**/api/*'],
+      }],
+    }),
+    test({
+      code: 'const a = require("./plugin2/thing")',
+      filename: testFilePath('./internal-modules/plugins/plugin.js'),
+      options: [{
+        forbid: ['**/api/*'],
+      }],
+    }),
+    test({
+      code: 'import b from "app/a"',
+      filename: testFilePath('./internal-modules/plugins/plugin2/internal.js'),
+      options: [{
+        forbid: ['app/**/**'],
+      }],
+    }),
+    test({
+      code: 'import b from "@org/package"',
+      filename: testFilePath('./internal-modules/plugins/plugin2/internal.js'),
+      options: [{
+        forbid: ['@org/package/*'],
+      }],
+    }),
+    // exports
+    test({
+      code: 'export {a} from "./internal.js"',
+      filename: testFilePath('./internal-modules/plugins/plugin2/index.js'),
+    }),
+    test({
+      code: 'export * from "lodash.get"',
+      filename: testFilePath('./internal-modules/plugins/plugin2/index.js'),
+    }),
+    test({
+      code: 'export {b} from "@org/package"',
+      filename: testFilePath('./internal-modules/plugins/plugin2/internal.js'),
+    }),
+    test({
+      code: 'export {b} from "../../api/service"',
+      filename: testFilePath('./internal-modules/plugins/plugin2/internal.js'),
+      options: [{
+        allow: ['**/api/*'],
+      }],
+    }),
+    test({
+      code: 'export * from "jquery/dist/jquery"',
+      filename: testFilePath('./internal-modules/plugins/plugin2/internal.js'),
+      options: [{
+        allow: ['jquery/dist/*'],
+      }],
+    }),
+    test({
+      code: 'export * from "./app/index.js";\nexport * from "./app/index"',
+      filename: testFilePath('./internal-modules/plugins/plugin2/internal.js'),
+      options: [{
+        allow: ['**/index{.js,}'],
+      }],
+    }),
+    test({
+      code: `
+        export class AuthHelper {
+
+          static checkAuth(auth) {
+          }
+        }
+      `,
+    }),
+    ...flatMap(getTSParsers(), (parser) => [
+      test({
+        code: `
+          export class AuthHelper {
+
+            public static checkAuth(auth?: string): boolean {
+            }
+          }
+        `,
+        parser,
+      }),
+    ]),
+    test({
+      code: 'export * from "./plugin2/thing"',
+      filename: testFilePath('./internal-modules/plugins/plugin.js'),
+      options: [{
+        forbid: ['**/api/*'],
+      }],
+    }),
+    test({
+      code: 'export * from "app/a"',
+      filename: testFilePath('./internal-modules/plugins/plugin2/internal.js'),
+      options: [{
+        forbid: ['app/**/**'],
+      }],
+    }),
+    test({
+      code: 'export { b } from "@org/package"',
+      filename: testFilePath('./internal-modules/plugins/plugin2/internal.js'),
+      options: [{
+        forbid: ['@org/package/*'],
+      }],
+    }),
+    test({
+      code: 'export * from "./app/index.js";\nexport * from "./app/index"',
+      filename: testFilePath('./internal-modules/plugins/plugin2/internal.js'),
+      options: [{
+        forbid: ['**/index.ts'],
+      }],
     }),
   ],
 
   invalid: [
+    // imports
     test({
       code: 'import "./plugin2/index.js";\nimport "./plugin2/app/index"',
       filename: testFilePath('./internal-modules/plugins/plugin.js'),
-      options: [ {
-        allow: [ '*/index.js' ],
-      } ],
-      errors: [ {
+      options: [{
+        allow: ['*/index.js'],
+      }],
+      errors: [{
         message: 'Reaching to "./plugin2/app/index" is not allowed.',
         line: 2,
         column: 8,
-      } ],
+      }],
     }),
     test({
       code: 'import "./app/index.js"',
       filename: testFilePath('./internal-modules/plugins/plugin2/internal.js'),
-      errors: [ {
+      errors: [{
         message: 'Reaching to "./app/index.js" is not allowed.',
         line: 1,
         column: 8,
-      } ],
+      }],
     }),
     test({
       code: 'import b from "./plugin2/internal"',
       filename: testFilePath('./internal-modules/plugins/plugin.js'),
-      errors: [ {
+      errors: [{
         message: 'Reaching to "./plugin2/internal" is not allowed.',
         line: 1,
         column: 15,
-      } ],
+      }],
     }),
     test({
       code: 'import a from "../api/service/index"',
       filename: testFilePath('./internal-modules/plugins/plugin.js'),
-      options: [ {
-        allow: [ '**/internal-modules/*' ],
-      } ],
+      options: [{
+        allow: ['**/internal-modules/*'],
+      }],
       errors: [
         {
           message: 'Reaching to "../api/service/index" is not allowed.',
@@ -126,5 +240,188 @@ ruleTester.run('no-internal-modules', rule, {
         },
       ],
     }),
+    test({
+      code: 'import "./app/index.js"',
+      filename: testFilePath('./internal-modules/plugins/plugin2/internal.js'),
+      options: [{
+        forbid: ['*/app/*'],
+      }],
+      errors: [{
+        message: 'Reaching to "./app/index.js" is not allowed.',
+        line: 1,
+        column: 8,
+      }],
+    }),
+    test({
+      code: 'import b from "@org/package"',
+      filename: testFilePath('./internal-modules/plugins/plugin2/internal.js'),
+      options: [{
+        forbid: ['@org/**'],
+      }],
+      errors: [{
+        message: 'Reaching to "@org/package" is not allowed.',
+        line: 1,
+        column: 15,
+      }],
+    }),
+    test({
+      code: 'import b from "app/a/b"',
+      filename: testFilePath('./internal-modules/plugins/plugin2/internal.js'),
+      options: [{
+        forbid: ['app/**/**'],
+      }],
+      errors: [{
+        message: 'Reaching to "app/a/b" is not allowed.',
+        line: 1,
+        column: 15,
+      }],
+    }),
+    test({
+      code: 'import get from "lodash.get"',
+      filename: testFilePath('./internal-modules/plugins/plugin2/index.js'),
+      options: [{
+        forbid: ['lodash.*'],
+      }],
+      errors: [{
+        message: 'Reaching to "lodash.get" is not allowed.',
+        line: 1,
+        column: 17,
+      }],
+    }),
+    test({
+      code: 'import "./app/index.js";\nimport "./app/index"',
+      filename: testFilePath('./internal-modules/plugins/plugin2/internal.js'),
+      options: [{
+        forbid: ['**/index{.js,}'],
+      }],
+      errors: [{
+        message: 'Reaching to "./app/index.js" is not allowed.',
+        line: 1,
+        column: 8,
+      }, {
+        message: 'Reaching to "./app/index" is not allowed.',
+        line: 2,
+        column: 8,
+      }],
+    }),
+    test({
+      code: 'import "@/api/service";',
+      options: [{
+        forbid: ['**/api/*'],
+      }],
+      errors: [{
+        message: 'Reaching to "@/api/service" is not allowed.',
+        line: 1,
+        column: 8,
+      }],
+      settings: {
+        'import/resolver': {
+          webpack: {
+            config: {
+              resolve: {
+                alias: {
+                  '@': testFilePath('internal-modules'),
+                },
+              },
+            },
+          },
+        },
+      },
+    }),
+    // exports
+    test({
+      code: 'export * from "./plugin2/index.js";\nexport * from "./plugin2/app/index"',
+      filename: testFilePath('./internal-modules/plugins/plugin.js'),
+      options: [{
+        allow: ['*/index.js'],
+      }],
+      errors: [{
+        message: 'Reaching to "./plugin2/app/index" is not allowed.',
+        line: 2,
+        column: 15,
+      }],
+    }),
+    test({
+      code: 'export * from "./app/index.js"',
+      filename: testFilePath('./internal-modules/plugins/plugin2/internal.js'),
+      errors: [{
+        message: 'Reaching to "./app/index.js" is not allowed.',
+        line: 1,
+        column: 15,
+      }],
+    }),
+    test({
+      code: 'export {b} from "./plugin2/internal"',
+      filename: testFilePath('./internal-modules/plugins/plugin.js'),
+      errors: [{
+        message: 'Reaching to "./plugin2/internal" is not allowed.',
+        line: 1,
+        column: 17,
+      }],
+    }),
+    test({
+      code: 'export {a} from "../api/service/index"',
+      filename: testFilePath('./internal-modules/plugins/plugin.js'),
+      options: [{
+        allow: ['**/internal-modules/*'],
+      }],
+      errors: [
+        {
+          message: 'Reaching to "../api/service/index" is not allowed.',
+          line: 1,
+          column: 17,
+        },
+      ],
+    }),
+    test({
+      code: 'export {b} from "@org/package/internal"',
+      filename: testFilePath('./internal-modules/plugins/plugin2/internal.js'),
+      errors: [
+        {
+          message: 'Reaching to "@org/package/internal" is not allowed.',
+          line: 1,
+          column: 17,
+        },
+      ],
+    }),
+    test({
+      code: 'export {get} from "debug/node"',
+      filename: testFilePath('./internal-modules/plugins/plugin.js'),
+      errors: [
+        {
+          message: 'Reaching to "debug/node" is not allowed.',
+          line: 1,
+          column: 19,
+        },
+      ],
+    }),
+    test({
+      code: 'export * from "./plugin2/thing"',
+      filename: testFilePath('./internal-modules/plugins/plugin.js'),
+      options: [{
+        forbid: ['**/plugin2/*'],
+      }],
+      errors: [
+        {
+          message: 'Reaching to "./plugin2/thing" is not allowed.',
+          line: 1,
+          column: 15,
+        },
+      ],
+    }),
+    test({
+      code: 'export * from "app/a"',
+      filename: testFilePath('./internal-modules/plugins/plugin2/internal.js'),
+      options: [{
+        forbid: ['**'],
+      }],
+      errors: [
+        {
+          message: 'Reaching to "app/a" is not allowed.',
+          line: 1,
+          column: 15,
+        },
+      ],
+    }),
   ],
-})
+});
diff --git a/tests/src/rules/no-mutable-exports.js b/tests/src/rules/no-mutable-exports.js
index 6d83566b7..ff9643b0d 100644
--- a/tests/src/rules/no-mutable-exports.js
+++ b/tests/src/rules/no-mutable-exports.js
@@ -1,39 +1,43 @@
-import {test} from '../utils'
-import {RuleTester} from 'eslint'
-import rule from 'rules/no-mutable-exports'
+import { parsers, test, testVersion } from '../utils';
+import { RuleTester } from '../rule-tester';
+import rule from 'rules/no-mutable-exports';
 
-const ruleTester = new RuleTester()
+const ruleTester = new RuleTester();
 
 ruleTester.run('no-mutable-exports', rule, {
-  valid: [
-    test({ code: 'export const count = 1'}),
-    test({ code: 'export function getCount() {}'}),
-    test({ code: 'export class Counter {}'}),
-    test({ code: 'export default count = 1'}),
-    test({ code: 'export default function getCount() {}'}),
-    test({ code: 'export default class Counter {}'}),
-    test({ code: 'const count = 1\nexport { count }'}),
-    test({ code: 'const count = 1\nexport { count as counter }'}),
-    test({ code: 'const count = 1\nexport default count'}),
-    test({ code: 'const count = 1\nexport { count as default }'}),
-    test({ code: 'function getCount() {}\nexport { getCount }'}),
-    test({ code: 'function getCount() {}\nexport { getCount as getCounter }'}),
-    test({ code: 'function getCount() {}\nexport default getCount'}),
-    test({ code: 'function getCount() {}\nexport { getCount as default }'}),
-    test({ code: 'class Counter {}\nexport { Counter }'}),
-    test({ code: 'class Counter {}\nexport { Counter as Count }'}),
-    test({ code: 'class Counter {}\nexport default Counter'}),
-    test({ code: 'class Counter {}\nexport { Counter as default }'}),
+  valid: [].concat(
+    test({ code: 'export const count = 1' }),
+    test({ code: 'export function getCount() {}' }),
+    test({ code: 'export class Counter {}' }),
+    test({ code: 'export default count = 1' }),
+    test({ code: 'export default function getCount() {}' }),
+    test({ code: 'export default class Counter {}' }),
+    test({ code: 'const count = 1\nexport { count }' }),
+    test({ code: 'const count = 1\nexport { count as counter }' }),
+    test({ code: 'const count = 1\nexport default count' }),
+    test({ code: 'const count = 1\nexport { count as default }' }),
+    test({ code: 'function getCount() {}\nexport { getCount }' }),
+    test({ code: 'function getCount() {}\nexport { getCount as getCounter }' }),
+    test({ code: 'function getCount() {}\nexport default getCount' }),
+    test({ code: 'function getCount() {}\nexport { getCount as default }' }),
+    test({ code: 'class Counter {}\nexport { Counter }' }),
+    test({ code: 'class Counter {}\nexport { Counter as Count }' }),
+    test({ code: 'class Counter {}\nexport default Counter' }),
+    test({ code: 'class Counter {}\nexport { Counter as default }' }),
     test({
-      parser: require.resolve('babel-eslint'),
+      parser: parsers.BABEL_OLD,
       code: 'export Something from "./something";',
     }),
     test({
-      parser: require.resolve('babel-eslint'),
+      parser: parsers.BABEL_OLD,
       code: 'type Foo = {}\nexport type {Foo}',
     }),
-  ],
-  invalid: [
+    // es2022: Arbitrary module namespace identifier names
+    testVersion('>= 8.7', () => ({
+      code: 'const count = 1\nexport { count as "counter" }', parserOptions: { ecmaVersion: 2022 },
+    })),
+  ),
+  invalid: [].concat(
     test({
       code: 'export let count = 1',
       errors: ['Exporting mutable \'let\' binding, use \'const\' instead.'],
@@ -66,6 +70,12 @@ ruleTester.run('no-mutable-exports', rule, {
       code: 'var count = 1\nexport default count',
       errors: ['Exporting mutable \'var\' binding, use \'const\' instead.'],
     }),
+    // es2022: Arbitrary module namespace identifier names
+    testVersion('>= 8.7', () => ({
+      code: 'let count = 1\nexport { count as "counter" }',
+      errors: ['Exporting mutable \'let\' binding, use \'const\' instead.'],
+      parserOptions: { ecmaVersion: 2022 },
+    })),
 
     // todo: undeclared globals
     // test({
@@ -76,5 +86,5 @@ ruleTester.run('no-mutable-exports', rule, {
     //   code: 'count = 1\nexport default count',
     //   errors: ['Exporting mutable global binding, use \'const\' instead.'],
     // }),
-  ],
-})
+  ),
+});
diff --git a/tests/src/rules/no-named-as-default-member.js b/tests/src/rules/no-named-as-default-member.js
index b5e294d19..5c00224ed 100644
--- a/tests/src/rules/no-named-as-default-member.js
+++ b/tests/src/rules/no-named-as-default-member.js
@@ -1,60 +1,63 @@
-import { test, SYNTAX_CASES } from '../utils'
-import {RuleTester} from 'eslint'
-import rule from 'rules/no-named-as-default-member'
+import { test, testVersion, SYNTAX_CASES } from '../utils';
+import { RuleTester } from '../rule-tester';
+import rule from 'rules/no-named-as-default-member';
 
-const ruleTester = new RuleTester()
+const ruleTester = new RuleTester();
 
 ruleTester.run('no-named-as-default-member', rule, {
-  valid: [
-    test({code: 'import bar, {foo} from "./bar";'}),
-    test({code: 'import bar from "./bar"; const baz = bar.baz'}),
-    test({code: 'import {foo} from "./bar"; const baz = foo.baz;'}),
-    test({code: 'import * as named from "./named-exports"; const a = named.a'}),
-    test({code: 'import foo from "./default-export-default-property"; const a = foo.default'}),
+  valid: [].concat(
+    test({ code: 'import bar, {foo} from "./bar";' }),
+    test({ code: 'import bar from "./bar"; const baz = bar.baz' }),
+    test({ code: 'import {foo} from "./bar"; const baz = foo.baz;' }),
+    test({ code: 'import * as named from "./named-exports"; const a = named.a' }),
+    test({ code: 'import foo from "./default-export-default-property"; const a = foo.default' }),
+
+    // es2022: Arbitrary module namespace identifier names
+    testVersion('>= 8.7', () => ({
+      code: 'import bar, { foo } from "./export-default-string-and-named"',
+      parserOptions: { ecmaVersion: 2022 },
+    })),
 
     ...SYNTAX_CASES,
-  ],
+  ),
 
-  invalid: [
+  invalid: [].concat(
     test({
       code: 'import bar from "./bar"; const foo = bar.foo;',
       errors: [{
-        message: (
-          'Caution: `bar` also has a named export `foo`. ' +
-          'Check if you meant to write `import {foo} from \'./bar\'` instead.'
-        ),
+        message: 'Caution: `bar` also has a named export `foo`. Check if you meant to write `import {foo} from \'./bar\'` instead.',
         type: 'MemberExpression',
       }],
     }),
     test({
       code: 'import bar from "./bar"; bar.foo();',
       errors: [{
-        message: (
-          'Caution: `bar` also has a named export `foo`. ' +
-          'Check if you meant to write `import {foo} from \'./bar\'` instead.'
-        ),
+        message: 'Caution: `bar` also has a named export `foo`. Check if you meant to write `import {foo} from \'./bar\'` instead.',
         type: 'MemberExpression',
       }],
     }),
     test({
       code: 'import bar from "./bar"; const {foo} = bar;',
       errors: [{
-        message: (
-          'Caution: `bar` also has a named export `foo`. ' +
-          'Check if you meant to write `import {foo} from \'./bar\'` instead.'
-        ),
+        message: 'Caution: `bar` also has a named export `foo`. Check if you meant to write `import {foo} from \'./bar\'` instead.',
         type: 'Identifier',
       }],
     }),
     test({
       code: 'import bar from "./bar"; const {foo: foo2, baz} = bar;',
       errors: [{
-        message: (
-          'Caution: `bar` also has a named export `foo`. ' +
-          'Check if you meant to write `import {foo} from \'./bar\'` instead.'
-        ),
+        message: 'Caution: `bar` also has a named export `foo`. Check if you meant to write `import {foo} from \'./bar\'` instead.',
         type: 'Identifier',
       }],
     }),
-  ],
-})
+    // es2022: Arbitrary module namespace identifier names
+    testVersion('>= 8.7', () => ({
+      code: 'import bar from "./export-default-string-and-named"; const foo = bar.foo;',
+      errors: [{
+        message: 'Caution: `bar` also has a named export `foo`. Check if you meant to write `import {foo} from \'./export-default-string-and-named\'` instead.',
+        type: 'MemberExpression',
+      }],
+      parserOptions: { ecmaVersion: 2022 },
+    })),
+  ),
+});
diff --git a/tests/src/rules/no-named-as-default.js b/tests/src/rules/no-named-as-default.js
index a5e0f041f..349372067 100644
--- a/tests/src/rules/no-named-as-default.js
+++ b/tests/src/rules/no-named-as-default.js
@@ -1,54 +1,116 @@
-import { test, SYNTAX_CASES } from '../utils'
-import { RuleTester } from 'eslint'
+import { test, testVersion, SYNTAX_CASES, parsers } from '../utils';
+import { RuleTester } from '../rule-tester';
 
-const ruleTester = new RuleTester()
-    , rule = require('rules/no-named-as-default')
+const ruleTester = new RuleTester();
+const rule = require('rules/no-named-as-default');
 
 ruleTester.run('no-named-as-default', rule, {
-  valid: [
+  valid: [].concat(
     test({ code: 'import "./malformed.js"' }),
 
-    test({code: 'import bar, { foo } from "./bar";'}),
-    test({code: 'import bar, { foo } from "./empty-folder";'}),
+    test({ code: 'import bar, { foo } from "./bar";' }),
+    test({ code: 'import bar, { foo } from "./empty-folder";' }),
 
     // es7
-    test({ code: 'export bar, { foo } from "./bar";'
-         , parser: require.resolve('babel-eslint') }),
-    test({ code: 'export bar from "./bar";'
-         , parser: require.resolve('babel-eslint') }),
+    test({
+      code: 'export bar, { foo } from "./bar";',
+      parser: parsers.BABEL_OLD,
+    }),
+    test({
+      code: 'export bar from "./bar";',
+      parser: parsers.BABEL_OLD,
+    }),
 
     // #566: don't false-positive on `default` itself
-    test({ code: 'export default from "./bar";'
-         , parser: require.resolve('babel-eslint') }),
+    test({
+      code: 'export default from "./bar";',
+      parser: parsers.BABEL_OLD,
+    }),
+
+    // es2022: Arbitrary module namespae identifier names
+    testVersion('>= 8.7', () => ({
+      code: 'import bar, { foo } from "./export-default-string-and-named"',
+      parserOptions: { ecmaVersion: 2022 },
+    })),
+
+    // #1594: Allow importing as default if object is exported both as default and named
+    test({ code: 'import something from "./no-named-as-default/re-exports.js";' }),
+    test({
+      code: 'import { something } from "./no-named-as-default/re-exports.js";',
+    }),
+    test({
+      code: 'import myOwnNameForVariable from "./no-named-as-default/exports.js";',
+    }),
+    test({
+      code: 'import { variable } from "./no-named-as-default/exports.js";',
+    }),
+    test({
+      code: 'import variable from "./no-named-as-default/misleading-re-exports.js";',
+    }),
+    test({
+      // incorrect import
+      code: 'import foobar from "./no-named-as-default/no-default-export.js";',
+    }),
+    // same tests, but for exports
+    test({
+      code: 'export something from "./no-named-as-default/re-exports.js";',
+      parser: parsers.BABEL_OLD,
+    }),
+    test({
+      code: 'export { something } from "./no-named-as-default/re-exports.js";',
+    }),
+    test({
+      code: 'export myOwnNameForVariable from "./no-named-as-default/exports.js";',
+      parser: parsers.BABEL_OLD,
+    }),
+    test({
+      code: 'export { variable } from "./no-named-as-default/exports.js";',
+    }),
+    test({
+      code: 'export variable from "./no-named-as-default/misleading-re-exports.js";',
+      parser: parsers.BABEL_OLD,
+    }),
+    test({
+      code: 'export foobar from "./no-named-as-default/no-default-export.js";',
+      parser: parsers.BABEL_OLD,
+    }),
 
     ...SYNTAX_CASES,
-  ],
+  ),
 
-  invalid: [
+  invalid: [].concat(
     test({
       code: 'import foo from "./bar";',
-      errors: [ {
-        message: 'Using exported name \'foo\' as identifier for default export.'
-      , type: 'ImportDefaultSpecifier' } ] }),
+      errors: [{
+        message: 'Using exported name \'foo\' as identifier for default import.',
+        type: 'ImportDefaultSpecifier',
+      }],
+    }),
     test({
       code: 'import foo, { foo as bar } from "./bar";',
-      errors: [ {
-        message: 'Using exported name \'foo\' as identifier for default export.'
-      , type: 'ImportDefaultSpecifier' } ] }),
+      errors: [{
+        message: 'Using exported name \'foo\' as identifier for default import.',
+        type: 'ImportDefaultSpecifier',
+      }],
+    }),
 
     // es7
     test({
       code: 'export foo from "./bar";',
-      parser: require.resolve('babel-eslint'),
-      errors: [ {
-        message: 'Using exported name \'foo\' as identifier for default export.'
-      , type: 'ExportDefaultSpecifier' } ] }),
+      parser: parsers.BABEL_OLD,
+      errors: [{
+        message: 'Using exported name \'foo\' as identifier for default export.',
+        type: 'ExportDefaultSpecifier',
+      }],
+    }),
     test({
       code: 'export foo, { foo as bar } from "./bar";',
-      parser: require.resolve('babel-eslint'),
-      errors: [ {
-        message: 'Using exported name \'foo\' as identifier for default export.'
-    , type: 'ExportDefaultSpecifier' } ] }),
+      parser: parsers.BABEL_OLD,
+      errors: [{
+        message: 'Using exported name \'foo\' as identifier for default export.',
+        type: 'ExportDefaultSpecifier',
+      }],
+    }),
 
     test({
       code: 'import foo from "./malformed.js"',
@@ -57,5 +119,49 @@ ruleTester.run('no-named-as-default', rule, {
         type: 'Literal',
       }],
     }),
-  ],
-})
+
+    // es2022: Arbitrary module namespae identifier names
+    testVersion('>= 8.7', () => ({
+      code: 'import foo from "./export-default-string-and-named"',
+      errors: [{
+        message: 'Using exported name \'foo\' as identifier for default import.',
+        type: 'ImportDefaultSpecifier',
+      }],
+      parserOptions: { ecmaVersion: 2022 },
+    })),
+    testVersion('>= 8.7', () => ({
+      code: 'import foo, { foo as bar } from "./export-default-string-and-named"',
+      errors: [{
+        message: 'Using exported name \'foo\' as identifier for default import.',
+        type: 'ImportDefaultSpecifier',
+      }],
+      parserOptions: { ecmaVersion: 2022 },
+    })),
+
+    // #1594: Allow importing as default if object is exported both as default and named
+    test({
+      code: 'import something from "./no-named-as-default/misleading-re-exports.js";',
+      parser: parsers.BABEL_OLD,
+      errors: [{
+        message: 'Using exported name \'something\' as identifier for default import.',
+        type: 'ImportDefaultSpecifier',
+      }],
+    }),
+    // The only cases that are not covered by the fix
+    test({
+      code: 'import variable from "./no-named-as-default/exports.js";',
+      errors: [{
+        message: 'Using exported name \'variable\' as identifier for default import.',
+        type: 'ImportDefaultSpecifier',
+      }],
+    }),
+    test({
+      code: 'export variable from "./no-named-as-default/exports.js";',
+      parser: parsers.BABEL_OLD,
+      errors: [{
+        message: 'Using exported name \'variable\' as identifier for default export.',
+        type: 'ExportDefaultSpecifier',
+      }],
+    }),
+  ),
+});
diff --git a/tests/src/rules/no-named-default.js b/tests/src/rules/no-named-default.js
index f9109fa8f..d36e26c44 100644
--- a/tests/src/rules/no-named-default.js
+++ b/tests/src/rules/no-named-default.js
@@ -1,25 +1,35 @@
-import { test, SYNTAX_CASES } from '../utils'
-import { RuleTester } from 'eslint'
+import { test, testVersion, SYNTAX_CASES, parsers } from '../utils';
+import { RuleTester } from '../rule-tester';
 
-const ruleTester = new RuleTester()
-    , rule = require('rules/no-named-default')
+const ruleTester = new RuleTester();
+const rule = require('rules/no-named-default');
 
 ruleTester.run('no-named-default', rule, {
   valid: [
-    test({code: 'import bar from "./bar";'}),
-    test({code: 'import bar, { foo } from "./bar";'}),
+    test({ code: 'import bar from "./bar";' }),
+    test({ code: 'import bar, { foo } from "./bar";' }),
+
+    // Should ignore imported flow types
+    test({
+      code: 'import { type default as Foo } from "./bar";',
+      parser: parsers.BABEL_OLD,
+    }),
+    test({
+      code: 'import { typeof default as Foo } from "./bar";',
+      parser: parsers.BABEL_OLD,
+    }),
 
     ...SYNTAX_CASES,
   ],
 
-  invalid: [
+  invalid: [].concat(
     /*test({
       code: 'import { default } from "./bar";',
       errors: [{
         message: 'Use default import syntax to import \'default\'.',
         type: 'Identifier',
       }],
-      parser: require.resolve('babel-eslint'),
+      parser: parsers.BABEL_OLD,
     }),*/
     test({
       code: 'import { default as bar } from "./bar";',
@@ -35,5 +45,17 @@ ruleTester.run('no-named-default', rule, {
         type: 'Identifier',
       }],
     }),
-  ],
-})
+
+    // es2022: Arbitrary module namespae identifier names
+    testVersion('>= 8.7', () => ({
+      code: 'import { "default" as bar } from "./bar";',
+      errors: [{
+        message: 'Use default import syntax to import \'bar\'.',
+        type: 'Identifier',
+      }],
+      parserOptions: {
+        ecmaVersion: 2022,
+      },
+    })) || [],
+  ),
+});
diff --git a/tests/src/rules/no-named-export.js b/tests/src/rules/no-named-export.js
index c4ef9c9c7..41f8e8f02 100644
--- a/tests/src/rules/no-named-export.js
+++ b/tests/src/rules/no-named-export.js
@@ -1,11 +1,20 @@
-import { RuleTester } from 'eslint'
-import { test } from '../utils'
+import { RuleTester } from '../rule-tester';
+import { parsers, test, testVersion } from '../utils';
 
-const ruleTester = new RuleTester()
-    , rule = require('rules/no-named-export')
+const ruleTester = new RuleTester();
+const rule = require('rules/no-named-export');
 
 ruleTester.run('no-named-export', rule, {
-  valid: [
+  valid: [].concat(
+    test({
+      code: 'module.export.foo = function () {}',
+      parserOptions: {
+        sourceType: 'script',
+      },
+    }),
+    test({
+      code: 'module.export.foo = function () {}',
+    }),
     test({
       code: 'export default function bar() {};',
     }),
@@ -14,7 +23,7 @@ ruleTester.run('no-named-export', rule, {
     }),
     test({
       code: 'export default from "foo.js"',
-      parser: require.resolve('babel-eslint'),
+      parser: parsers.BABEL_OLD,
     }),
 
     // no exports at all
@@ -27,7 +36,13 @@ ruleTester.run('no-named-export', rule, {
     test({
       code: `import {default as foo} from './foo';`,
     }),
-  ],
+
+    // es2022: Arbitrary module namespae identifier names
+    testVersion('>= 8.7', () => ({
+      code: 'let foo; export { foo as "default" }',
+      parserOptions: { ecmaVersion: 2022 },
+    })),
+  ),
   invalid: [
     test({
       code: `
@@ -35,10 +50,10 @@ ruleTester.run('no-named-export', rule, {
         export const bar = 'bar';
       `,
       errors: [{
-        ruleId: 'ExportNamedDeclaration',
+        type: 'ExportNamedDeclaration',
         message: 'Named exports are not allowed.',
       }, {
-        ruleId: 'ExportNamedDeclaration',
+        type: 'ExportNamedDeclaration',
         message: 'Named exports are not allowed.',
       }],
     }),
@@ -47,7 +62,7 @@ ruleTester.run('no-named-export', rule, {
         export const foo = 'foo';
         export default bar;`,
       errors: [{
-        ruleId: 'ExportNamedDeclaration',
+        type: 'ExportNamedDeclaration',
         message: 'Named exports are not allowed.',
       }],
     }),
@@ -57,17 +72,17 @@ ruleTester.run('no-named-export', rule, {
         export function bar() {};
       `,
       errors: [{
-        ruleId: 'ExportNamedDeclaration',
+        type: 'ExportNamedDeclaration',
         message: 'Named exports are not allowed.',
       }, {
-        ruleId: 'ExportNamedDeclaration',
+        type: 'ExportNamedDeclaration',
         message: 'Named exports are not allowed.',
       }],
     }),
     test({
       code: `export const foo = 'foo';`,
       errors: [{
-        ruleId: 'ExportNamedDeclaration',
+        type: 'ExportNamedDeclaration',
         message: 'Named exports are not allowed.',
       }],
     }),
@@ -77,35 +92,35 @@ ruleTester.run('no-named-export', rule, {
         export { foo };
       `,
       errors: [{
-        ruleId: 'ExportNamedDeclaration',
+        type: 'ExportNamedDeclaration',
         message: 'Named exports are not allowed.',
       }],
     }),
     test({
       code: `let foo, bar; export { foo, bar }`,
       errors: [{
-        ruleId: 'ExportNamedDeclaration',
+        type: 'ExportNamedDeclaration',
         message: 'Named exports are not allowed.',
       }],
     }),
     test({
       code: `export const { foo, bar } = item;`,
       errors: [{
-        ruleId: 'ExportNamedDeclaration',
+        type: 'ExportNamedDeclaration',
         message: 'Named exports are not allowed.',
       }],
     }),
     test({
       code: `export const { foo, bar: baz } = item;`,
       errors: [{
-        ruleId: 'ExportNamedDeclaration',
+        type: 'ExportNamedDeclaration',
         message: 'Named exports are not allowed.',
       }],
     }),
     test({
       code: `export const { foo: { bar, baz } } = item;`,
       errors: [{
-        ruleId: 'ExportNamedDeclaration',
+        type: 'ExportNamedDeclaration',
         message: 'Named exports are not allowed.',
       }],
     }),
@@ -116,65 +131,65 @@ ruleTester.run('no-named-export', rule, {
         export { item };
       `,
       errors: [{
-        ruleId: 'ExportNamedDeclaration',
+        type: 'ExportNamedDeclaration',
         message: 'Named exports are not allowed.',
       }, {
-        ruleId: 'ExportNamedDeclaration',
+        type: 'ExportNamedDeclaration',
         message: 'Named exports are not allowed.',
       }],
     }),
     test({
       code: `export * from './foo';`,
       errors: [{
-        ruleId: 'ExportAllDeclaration',
+        type: 'ExportAllDeclaration',
         message: 'Named exports are not allowed.',
       }],
     }),
     test({
       code: `export const { foo } = { foo: "bar" };`,
       errors: [{
-        ruleId: 'ExportNamedDeclaration',
+        type: 'ExportNamedDeclaration',
         message: 'Named exports are not allowed.',
       }],
     }),
     test({
       code: `export const { foo: { bar } } = { foo: { bar: "baz" } };`,
       errors: [{
-        ruleId: 'ExportNamedDeclaration',
+        type: 'ExportNamedDeclaration',
         message: 'Named exports are not allowed.',
       }],
     }),
     test({
       code: 'export { a, b } from "foo.js"',
-      parser: require.resolve('babel-eslint'),
+      parser: parsers.BABEL_OLD,
       errors: [{
-        ruleId: 'ExportNamedDeclaration',
+        type: 'ExportNamedDeclaration',
         message: 'Named exports are not allowed.',
       }],
     }),
     test({
       code: `export type UserId = number;`,
-      parser: require.resolve('babel-eslint'),
+      parser: parsers.BABEL_OLD,
       errors: [{
-        ruleId: 'ExportNamedDeclaration',
+        type: 'ExportNamedDeclaration',
         message: 'Named exports are not allowed.',
       }],
     }),
     test({
       code: 'export foo from "foo.js"',
-      parser: require.resolve('babel-eslint'),
+      parser: parsers.BABEL_OLD,
       errors: [{
-        ruleId: 'ExportNamedDeclaration',
+        type: 'ExportNamedDeclaration',
         message: 'Named exports are not allowed.',
       }],
     }),
     test({
       code: `export Memory, { MemoryValue } from './Memory'`,
-      parser: require.resolve('babel-eslint'),
+      parser: parsers.BABEL_OLD,
       errors: [{
-        ruleId: 'ExportNamedDeclaration',
+        type: 'ExportNamedDeclaration',
         message: 'Named exports are not allowed.',
       }],
     }),
   ],
-})
+});
diff --git a/tests/src/rules/no-namespace.js b/tests/src/rules/no-namespace.js
index d9ef3423c..f5cd967a2 100644
--- a/tests/src/rules/no-namespace.js
+++ b/tests/src/rules/no-namespace.js
@@ -1,44 +1,111 @@
-import { RuleTester } from 'eslint'
+import { RuleTester, withoutAutofixOutput } from '../rule-tester';
+import eslintPkg from 'eslint/package.json';
+import semver from 'semver';
+import { test } from '../utils';
 
-const ERROR_MESSAGE = 'Unexpected namespace import.'
+const ERROR_MESSAGE = 'Unexpected namespace import.';
 
-const ruleTester = new RuleTester()
+const ruleTester = new RuleTester();
+
+// --fix functionality requires ESLint 5+
+const FIX_TESTS = semver.satisfies(eslintPkg.version, '>5.0.0') ? [
+  test({
+    code: `
+      import * as foo from './foo';
+      florp(foo.bar);
+      florp(foo['baz']);
+    `.trim(),
+    output: `
+      import { bar, baz } from './foo';
+      florp(bar);
+      florp(baz);
+    `.trim(),
+    errors: [{
+      line: 1,
+      column: 8,
+      message: ERROR_MESSAGE,
+    }],
+  }),
+  test({
+    code: `
+      import * as foo from './foo';
+      const bar = 'name conflict';
+      const baz = 'name conflict';
+      const foo_baz = 'name conflict';
+      florp(foo.bar);
+      florp(foo['baz']);
+    `.trim(),
+    output: `
+      import { bar as foo_bar, baz as foo_baz_1 } from './foo';
+      const bar = 'name conflict';
+      const baz = 'name conflict';
+      const foo_baz = 'name conflict';
+      florp(foo_bar);
+      florp(foo_baz_1);
+    `.trim(),
+    errors: [{
+      line: 1,
+      column: 8,
+      message: ERROR_MESSAGE,
+    }],
+  }),
+  test({
+    code: `
+      import * as foo from './foo';
+      function func(arg) {
+        florp(foo.func);
+        florp(foo['arg']);
+      }
+    `.trim(),
+    output: `
+      import { func as foo_func, arg as foo_arg } from './foo';
+      function func(arg) {
+        florp(foo_func);
+        florp(foo_arg);
+      }
+    `.trim(),
+    errors: [{
+      line: 1,
+      column: 8,
+      message: ERROR_MESSAGE,
+    }],
+  }),
+] : [];
 
 ruleTester.run('no-namespace', require('rules/no-namespace'), {
   valid: [
-    { code: "import { a, b } from 'foo';", parserOptions: { ecmaVersion: 2015, sourceType: 'module' } },
-    { code: "import { a, b } from './foo';", parserOptions: { ecmaVersion: 2015, sourceType: 'module' } },
-    { code: "import bar from 'bar';", parserOptions: { ecmaVersion: 2015, sourceType: 'module' } },
-    { code: "import bar from './bar';", parserOptions: { ecmaVersion: 2015, sourceType: 'module' } },
+    { code: 'import { a, b } from \'foo\';', parserOptions: { ecmaVersion: 2015, sourceType: 'module' } },
+    { code: 'import { a, b } from \'./foo\';', parserOptions: { ecmaVersion: 2015, sourceType: 'module' } },
+    { code: 'import bar from \'bar\';', parserOptions: { ecmaVersion: 2015, sourceType: 'module' } },
+    { code: 'import bar from \'./bar\';', parserOptions: { ecmaVersion: 2015, sourceType: 'module' } },
+    { code: 'import * as bar from \'./ignored-module.ext\';', parserOptions: { ecmaVersion: 2015, sourceType: 'module' }, options: [{ ignore: ['*.ext'] }] },
   ],
 
   invalid: [
-    {
-      code: "import * as foo from 'foo';",
-      errors: [ {
+    test(withoutAutofixOutput({
+      code: 'import * as foo from \'foo\';',
+      errors: [{
         line: 1,
         column: 8,
         message: ERROR_MESSAGE,
-      } ],
-      parserOptions: { ecmaVersion: 2015, sourceType: 'module' },
-    },
-    {
-      code: "import defaultExport, * as foo from 'foo';",
-      errors: [ {
+      }],
+    })),
+    test(withoutAutofixOutput({
+      code: 'import defaultExport, * as foo from \'foo\';',
+      errors: [{
         line: 1,
         column: 23,
         message: ERROR_MESSAGE,
-      } ],
-      parserOptions: { ecmaVersion: 2015, sourceType: 'module' },
-    },
-    {
-      code: "import * as foo from './foo';",
-      errors: [ {
+      }],
+    })),
+    test(withoutAutofixOutput({
+      code: 'import * as foo from \'./foo\';',
+      errors: [{
         line: 1,
         column: 8,
         message: ERROR_MESSAGE,
-      } ],
-      parserOptions: { ecmaVersion: 2015, sourceType: 'module' },
-    },
+      }],
+    })),
+    ...FIX_TESTS,
   ],
-})
+});
diff --git a/tests/src/rules/no-nodejs-modules.js b/tests/src/rules/no-nodejs-modules.js
index b5e55fafc..cf131ffee 100644
--- a/tests/src/rules/no-nodejs-modules.js
+++ b/tests/src/rules/no-nodejs-modules.js
@@ -1,31 +1,31 @@
-import { test } from '../utils'
+import { test } from '../utils';
 
-import { RuleTester } from 'eslint'
+import { RuleTester } from '../rule-tester';
+const isCore = require('is-core-module');
 
-const ruleTester = new RuleTester()
-    , rule = require('rules/no-nodejs-modules')
+const ruleTester = new RuleTester();
+const rule = require('rules/no-nodejs-modules');
 
-const error = message => ({
-  ruleId: 'no-nodejs-modules',
+const error = (message) => ({
   message,
-})
+});
 
 ruleTester.run('no-nodejs-modules', rule, {
-  valid: [
-    test({ code: 'import _ from "lodash"'}),
-    test({ code: 'import find from "lodash.find"'}),
-    test({ code: 'import foo from "./foo"'}),
-    test({ code: 'import foo from "../foo"'}),
-    test({ code: 'import foo from "foo"'}),
-    test({ code: 'import foo from "./"'}),
-    test({ code: 'import foo from "@scope/foo"'}),
-    test({ code: 'var _ = require("lodash")'}),
-    test({ code: 'var find = require("lodash.find")'}),
-    test({ code: 'var foo = require("./foo")'}),
-    test({ code: 'var foo = require("../foo")'}),
-    test({ code: 'var foo = require("foo")'}),
-    test({ code: 'var foo = require("./")'}),
-    test({ code: 'var foo = require("@scope/foo")'}),
+  valid: [].concat(
+    test({ code: 'import _ from "lodash"' }),
+    test({ code: 'import find from "lodash.find"' }),
+    test({ code: 'import foo from "./foo"' }),
+    test({ code: 'import foo from "../foo"' }),
+    test({ code: 'import foo from "foo"' }),
+    test({ code: 'import foo from "./"' }),
+    test({ code: 'import foo from "@scope/foo"' }),
+    test({ code: 'var _ = require("lodash")' }),
+    test({ code: 'var find = require("lodash.find")' }),
+    test({ code: 'var foo = require("./foo")' }),
+    test({ code: 'var foo = require("../foo")' }),
+    test({ code: 'var foo = require("foo")' }),
+    test({ code: 'var foo = require("./")' }),
+    test({ code: 'var foo = require("@scope/foo")' }),
     test({
       code: 'import events from "events"',
       options: [{
@@ -56,8 +56,42 @@ ruleTester.run('no-nodejs-modules', rule, {
         allow: ['path', 'events'],
       }],
     }),
-  ],
-  invalid: [
+    isCore('node:events') ? [
+      test({
+        code: 'import events from "node:events"',
+        options: [{
+          allow: ['node:events'],
+        }],
+      }),
+      test({
+        code: 'var events = require("node:events")',
+        options: [{
+          allow: ['node:events'],
+        }],
+      }),
+    ] : [],
+    isCore('node:path') ? [
+      test({
+        code: 'import path from "node:path"',
+        options: [{
+          allow: ['node:path'],
+        }],
+      }),
+      test({
+        code: 'var path = require("node:path")',
+        options: [{
+          allow: ['node:path'],
+        }],
+      }),
+    ] : [],
+    isCore('node:path') && isCore('node:events') ? test({
+      code: 'import path from "node:path";import events from "node:events"',
+      options: [{
+        allow: ['node:path', 'node:events'],
+      }],
+    }) : [],
+  ),
+  invalid: [].concat(
     test({
       code: 'import path from "path"',
       errors: [error('Do not import Node.js builtin module "path"')],
@@ -81,5 +115,32 @@ ruleTester.run('no-nodejs-modules', rule, {
       }],
       errors: [error('Do not import Node.js builtin module "fs"')],
     }),
-  ],
-})
+    isCore('node:path') ? [
+      test({
+        code: 'import path from "node:path"',
+        errors: [error('Do not import Node.js builtin module "node:path"')],
+      }),
+      test({
+        code: 'var path = require("node:path")',
+        errors: [error('Do not import Node.js builtin module "node:path"')],
+      }),
+    ] : [],
+    isCore('node:fs') ? [
+      test({
+        code: 'import fs from "node:fs"',
+        errors: [error('Do not import Node.js builtin module "node:fs"')],
+      }),
+      test({
+        code: 'var fs = require("node:fs")',
+        errors: [error('Do not import Node.js builtin module "node:fs"')],
+      }),
+      test({
+        code: 'import fs from "node:fs"',
+        options: [{
+          allow: ['node:path'],
+        }],
+        errors: [error('Do not import Node.js builtin module "node:fs"')],
+      }),
+    ] : [],
+  ),
+});
diff --git a/tests/src/rules/no-relative-packages.js b/tests/src/rules/no-relative-packages.js
new file mode 100644
index 000000000..9b424506c
--- /dev/null
+++ b/tests/src/rules/no-relative-packages.js
@@ -0,0 +1,83 @@
+import { RuleTester } from '../rule-tester';
+import rule from 'rules/no-relative-packages';
+import { normalize } from 'path';
+
+import { test, testFilePath } from '../utils';
+
+const ruleTester = new RuleTester();
+
+ruleTester.run('no-relative-packages', rule, {
+  valid: [
+    test({
+      code: 'import foo from "./index.js"',
+      filename: testFilePath('./package/index.js'),
+    }),
+    test({
+      code: 'import bar from "../bar"',
+      filename: testFilePath('./package/index.js'),
+    }),
+    test({
+      code: 'import {foo} from "a"',
+      filename: testFilePath('./package-named/index.js'),
+    }),
+    test({
+      code: 'const bar = require("../bar.js")',
+      filename: testFilePath('./package/index.js'),
+    }),
+    test({
+      code: 'const bar = require("../not/a/file/path.js")',
+      filename: testFilePath('./package/index.js'),
+    }),
+    test({
+      code: 'import "package"',
+      filename: testFilePath('./package/index.js'),
+    }),
+    test({
+      code: 'require("../bar.js")',
+      filename: testFilePath('./package/index.js'),
+    }),
+  ],
+
+  invalid: [
+    test({
+      code: 'import foo from "./package-named"',
+      filename: testFilePath('./bar.js'),
+      errors: [{
+        message: 'Relative import from another package is not allowed. Use `package-named` instead of `./package-named`',
+        line: 1,
+        column: 17,
+      }],
+      output: 'import foo from "package-named"',
+    }),
+    test({
+      code: 'import foo from "../package-named"',
+      filename: testFilePath('./package/index.js'),
+      errors: [{
+        message: 'Relative import from another package is not allowed. Use `package-named` instead of `../package-named`',
+        line: 1,
+        column: 17,
+      }],
+      output: 'import foo from "package-named"',
+    }),
+    test({
+      code: 'import foo from "../package-scoped"',
+      filename: testFilePath('./package/index.js'),
+      errors: [{
+        message: `Relative import from another package is not allowed. Use \`${normalize('@scope/package-named')}\` instead of \`../package-scoped\``,
+        line: 1,
+        column: 17,
+      }],
+      output: `import foo from "@scope/package-named"`,
+    }),
+    test({
+      code: 'import bar from "../bar"',
+      filename: testFilePath('./package-named/index.js'),
+      errors: [{
+        message: `Relative import from another package is not allowed. Use \`${normalize('eslint-plugin-import/tests/files/bar')}\` instead of \`../bar\``,
+        line: 1,
+        column: 17,
+      }],
+      output: `import bar from "eslint-plugin-import/tests/files/bar"`,
+    }),
+  ],
+});
diff --git a/tests/src/rules/no-relative-parent-imports.js b/tests/src/rules/no-relative-parent-imports.js
index 05ef9d8bf..93c8b97aa 100644
--- a/tests/src/rules/no-relative-parent-imports.js
+++ b/tests/src/rules/no-relative-parent-imports.js
@@ -1,13 +1,13 @@
-import { RuleTester } from 'eslint'
-import rule from 'rules/no-relative-parent-imports'
-import { test as _test, testFilePath } from '../utils'
+import { RuleTester } from '../rule-tester';
+import rule from 'rules/no-relative-parent-imports';
+import { parsers, test as _test, testFilePath } from '../utils';
 
-const test = def => _test(Object.assign(def, {
+const test = (def) => _test(Object.assign(def, {
   filename: testFilePath('./internal-modules/plugins/plugin2/index.js'),
-  parser: require.resolve('babel-eslint'),
-}))
+  parser: parsers.BABEL_OLD,
+}));
 
-const ruleTester = new RuleTester()
+const ruleTester = new RuleTester();
 
 ruleTester.run('no-relative-parent-imports', rule, {
   valid: [
@@ -55,32 +55,32 @@ ruleTester.run('no-relative-parent-imports', rule, {
   invalid: [
     test({
       code: 'import foo from "../plugin.js"',
-      errors: [ {
+      errors: [{
         message: 'Relative imports from parent directories are not allowed. Please either pass what you\'re importing through at runtime (dependency injection), move `index.js` to same directory as `../plugin.js` or consider making `../plugin.js` a package.',
         line: 1,
         column: 17,
-      } ],
+      }],
     }),
     test({
       code: 'require("../plugin.js")',
       options: [{ commonjs: true }],
-      errors: [ {
+      errors: [{
         message: 'Relative imports from parent directories are not allowed. Please either pass what you\'re importing through at runtime (dependency injection), move `index.js` to same directory as `../plugin.js` or consider making `../plugin.js` a package.',
         line: 1,
         column: 9,
-      } ],
+      }],
     }),
     test({
       code: 'import("../plugin.js")',
-      errors: [ {
+      errors: [{
         message: 'Relative imports from parent directories are not allowed. Please either pass what you\'re importing through at runtime (dependency injection), move `index.js` to same directory as `../plugin.js` or consider making `../plugin.js` a package.',
         line: 1,
         column: 8,
-      } ],
+      }],
     }),
     test({
       code: 'import foo from "./../plugin.js"',
-      errors: [ {
+      errors: [{
         message: 'Relative imports from parent directories are not allowed. Please either pass what you\'re importing through at runtime (dependency injection), move `index.js` to same directory as `./../plugin.js` or consider making `./../plugin.js` a package.',
         line: 1,
         column: 17,
@@ -88,7 +88,7 @@ ruleTester.run('no-relative-parent-imports', rule, {
     }),
     test({
       code: 'import foo from "../../api/service"',
-      errors: [ {
+      errors: [{
         message: 'Relative imports from parent directories are not allowed. Please either pass what you\'re importing through at runtime (dependency injection), move `index.js` to same directory as `../../api/service` or consider making `../../api/service` a package.',
         line: 1,
         column: 17,
@@ -96,11 +96,11 @@ ruleTester.run('no-relative-parent-imports', rule, {
     }),
     test({
       code: 'import("../../api/service")',
-      errors: [ {
+      errors: [{
         message: 'Relative imports from parent directories are not allowed. Please either pass what you\'re importing through at runtime (dependency injection), move `index.js` to same directory as `../../api/service` or consider making `../../api/service` a package.',
         line: 1,
         column: 8,
       }],
     }),
   ],
-})
+});
diff --git a/tests/src/rules/no-restricted-paths.js b/tests/src/rules/no-restricted-paths.js
index 13f8472cb..c3382ad08 100644
--- a/tests/src/rules/no-restricted-paths.js
+++ b/tests/src/rules/no-restricted-paths.js
@@ -1,74 +1,362 @@
-import { RuleTester } from 'eslint'
-import rule from 'rules/no-restricted-paths'
+import { RuleTester } from '../rule-tester';
+import rule from 'rules/no-restricted-paths';
 
-import { test, testFilePath } from '../utils'
+import { getTSParsers, test, testFilePath } from '../utils';
 
-const ruleTester = new RuleTester()
+const ruleTester = new RuleTester();
 
 ruleTester.run('no-restricted-paths', rule, {
-  valid: [
+  valid: [].concat(
     test({
       code: 'import a from "../client/a.js"',
       filename: testFilePath('./restricted-paths/server/b.js'),
-      options: [ {
-        zones: [ { target: './tests/files/restricted-paths/server', from: './tests/files/restricted-paths/other' } ],
-      } ],
+      options: [
+        {
+          zones: [
+            {
+              target: './tests/files/restricted-paths/server',
+              from: './tests/files/restricted-paths/other',
+            },
+          ],
+        },
+      ],
+    }),
+    test({
+      code: 'import a from "../client/a.js"',
+      filename: testFilePath('./restricted-paths/server/b.js'),
+      options: [
+        {
+          zones: [
+            {
+              target: '**/*',
+              from: './tests/files/restricted-paths/other',
+            },
+          ],
+        },
+      ],
+    }),
+    test({
+      code: 'import a from "../client/a.js"',
+      filename: testFilePath('./restricted-paths/client/b.js'),
+      options: [
+        {
+          zones: [
+            {
+              target: './tests/files/restricted-paths/!(client)/**/*',
+              from: './tests/files/restricted-paths/client/**/*',
+            },
+          ],
+        },
+      ],
     }),
     test({
       code: 'const a = require("../client/a.js")',
       filename: testFilePath('./restricted-paths/server/b.js'),
-      options: [ {
-        zones: [ { target: './tests/files/restricted-paths/server', from: './tests/files/restricted-paths/other' } ],
-      } ],
+      options: [
+        {
+          zones: [
+            {
+              target: './tests/files/restricted-paths/server',
+              from: './tests/files/restricted-paths/other',
+            },
+          ],
+        },
+      ],
     }),
     test({
       code: 'import b from "../server/b.js"',
       filename: testFilePath('./restricted-paths/client/a.js'),
-      options: [ {
-        zones: [ { target: './tests/files/restricted-paths/client', from: './tests/files/restricted-paths/other' } ],
-      } ],
+      options: [
+        {
+          zones: [
+            {
+              target: './tests/files/restricted-paths/client',
+              from: './tests/files/restricted-paths/other',
+            },
+          ],
+        },
+      ],
+    }),
+    test({
+      code: 'import a from "./a.js"',
+      filename: testFilePath('./restricted-paths/server/one/a.js'),
+      options: [
+        {
+          zones: [
+            {
+              target: './tests/files/restricted-paths/server/one',
+              from: './tests/files/restricted-paths/server',
+              except: ['./one'],
+            },
+          ],
+        },
+      ],
+    }),
+    test({
+      code: 'import a from "../two/a.js"',
+      filename: testFilePath('./restricted-paths/server/one/a.js'),
+      options: [
+        {
+          zones: [
+            {
+              target: './tests/files/restricted-paths/server/one',
+              from: './tests/files/restricted-paths/server',
+              except: ['./two'],
+            },
+          ],
+        },
+      ],
+    }),
+    test({
+      code: 'import a from "../one/a.js"',
+      filename: testFilePath('./restricted-paths/server/two-new/a.js'),
+      options: [
+        {
+          zones: [
+            {
+              target: './tests/files/restricted-paths/server/two',
+              from: './tests/files/restricted-paths/server',
+              except: [],
+            },
+          ],
+        },
+      ],
+    }),
+    test({
+      code: 'import A from "../two/a.js"',
+      filename: testFilePath('./restricted-paths/server/one/a.js'),
+      options: [
+        {
+          zones: [
+            {
+              target: '**/*',
+              from: './tests/files/restricted-paths/server/**/*',
+              except: ['**/a.js'],
+            },
+          ],
+        },
+      ],
     }),
 
+    // support of arrays for from and target
+    // array with single element
+    test({
+      code: 'import a from "../client/a.js"',
+      filename: testFilePath('./restricted-paths/server/b.js'),
+      options: [
+        {
+          zones: [
+            {
+              target: ['./tests/files/restricted-paths/server'],
+              from: './tests/files/restricted-paths/other',
+            },
+          ],
+        },
+      ],
+    }),
+    test({
+      code: 'import a from "../client/a.js"',
+      filename: testFilePath('./restricted-paths/server/b.js'),
+      options: [
+        {
+          zones: [
+            {
+              target: './tests/files/restricted-paths/server',
+              from: ['./tests/files/restricted-paths/other'],
+            },
+          ],
+        },
+      ],
+    }),
+    // array with multiple elements
+    test({
+      code: 'import a from "../one/a.js"',
+      filename: testFilePath('./restricted-paths/server/two-new/a.js'),
+      options: [
+        {
+          zones: [
+            {
+              target: ['./tests/files/restricted-paths/server/two', './tests/files/restricted-paths/server/three'],
+              from: './tests/files/restricted-paths/server',
+            },
+          ],
+        },
+      ],
+    }),
+    test({
+      code: 'import a from "../one/a.js"',
+      filename: testFilePath('./restricted-paths/server/two-new/a.js'),
+      options: [
+        {
+          zones: [
+            {
+              target: './tests/files/restricted-paths/server',
+              from: ['./tests/files/restricted-paths/server/two', './tests/files/restricted-paths/server/three'],
+              except: [],
+            },
+          ],
+        },
+      ],
+    }),
+    // array with multiple glob patterns in from
+    test({
+      code: 'import a from "../client/a.js"',
+      filename: testFilePath('./restricted-paths/client/b.js'),
+      options: [
+        {
+          zones: [
+            {
+              target: './tests/files/restricted-paths/!(client)/**/*',
+              from: ['./tests/files/restricted-paths/client/*', './tests/files/restricted-paths/client/one/*'],
+            },
+          ],
+        },
+      ],
+    }),
+    // array with mix of glob and non glob patterns in target
+    test({
+      code: 'import a from "../client/a.js"',
+      filename: testFilePath('./restricted-paths/client/b.js'),
+      options: [
+        {
+          zones: [
+            {
+              target: ['./tests/files/restricted-paths/!(client)/**/*', './tests/files/restricted-paths/client/a/'],
+              from: './tests/files/restricted-paths/client/**/*',
+            },
+          ],
+        },
+      ],
+    }),
 
     // irrelevant function calls
     test({ code: 'notrequire("../server/b.js")' }),
     test({
       code: 'notrequire("../server/b.js")',
       filename: testFilePath('./restricted-paths/client/a.js'),
-        options: [ {
-          zones: [ { target: './tests/files/restricted-paths/client', from: './tests/files/restricted-paths/server' } ],
-        } ], }),
+      options: [
+        {
+          zones: [
+            {
+              target: './tests/files/restricted-paths/client',
+              from: './tests/files/restricted-paths/server',
+            },
+          ],
+        },
+      ],
+    }),
 
     // no config
     test({ code: 'require("../server/b.js")' }),
     test({ code: 'import b from "../server/b.js"' }),
 
     // builtin (ignore)
-    test({ code: 'require("os")' })
-  ],
+    test({ code: 'require("os")' }),
+  ),
 
-  invalid: [
+  invalid: [].concat(
     test({
-      code: 'import b from "../server/b.js"',
+      code: 'import b from "../server/b.js"; // 1',
+      filename: testFilePath('./restricted-paths/client/a.js'),
+      options: [
+        {
+          zones: [
+            {
+              target: './tests/files/restricted-paths/client',
+              from: './tests/files/restricted-paths/server',
+            },
+          ],
+        },
+      ],
+      errors: [
+        {
+          message: 'Unexpected path "../server/b.js" imported in restricted zone.',
+          line: 1,
+          column: 15,
+        },
+      ],
+    }),
+    test({
+      code: 'import b from "../server/b.js"; // 2',
+      filename: testFilePath('./restricted-paths/client/a.js'),
+      options: [
+        {
+          zones: [
+            {
+              target: './tests/files/restricted-paths/client/**/*',
+              from: './tests/files/restricted-paths/server',
+            },
+          ],
+        },
+      ],
+      errors: [
+        {
+          message: 'Unexpected path "../server/b.js" imported in restricted zone.',
+          line: 1,
+          column: 15,
+        },
+      ],
+    }),
+    // TODO: fix test on windows
+    process.platform === 'win32' ? [] : test({
+      code: 'import b from "../server/b.js";',
+      filename: testFilePath('./restricted-paths/client/a.js'),
+      options: [
+        {
+          zones: [
+            {
+              target: './tests/files/restricted-paths/client/*.js',
+              from: './tests/files/restricted-paths/server',
+            },
+          ],
+        },
+      ],
+      errors: [
+        {
+          message: 'Unexpected path "../server/b.js" imported in restricted zone.',
+          line: 1,
+          column: 15,
+        },
+      ],
+    }),
+    test({
+      code: 'import b from "../server/b.js"; // 2 ter',
       filename: testFilePath('./restricted-paths/client/a.js'),
-      options: [ {
-        zones: [ { target: './tests/files/restricted-paths/client', from: './tests/files/restricted-paths/server' } ],
-      } ],
-      errors: [ {
-        message: 'Unexpected path "../server/b.js" imported in restricted zone.',
-        line: 1,
-        column: 15,
-      } ],
+      options: [
+        {
+          zones: [
+            {
+              target: './tests/files/restricted-paths/client/**',
+              from: './tests/files/restricted-paths/server',
+            },
+          ],
+        },
+      ],
+      errors: [
+        {
+          message: 'Unexpected path "../server/b.js" imported in restricted zone.',
+          line: 1,
+          column: 15,
+        },
+      ],
     }),
     test({
       code: 'import a from "../client/a"\nimport c from "./c"',
       filename: testFilePath('./restricted-paths/server/b.js'),
-      options: [ {
-        zones: [
-          { target: './tests/files/restricted-paths/server', from: './tests/files/restricted-paths/client' },
-          { target: './tests/files/restricted-paths/server', from: './tests/files/restricted-paths/server/c.js' },
-        ],
-      } ],
+      options: [
+        {
+          zones: [
+            {
+              target: './tests/files/restricted-paths/server',
+              from: './tests/files/restricted-paths/client',
+            },
+            {
+              target: './tests/files/restricted-paths/server',
+              from: './tests/files/restricted-paths/server/c.js',
+            },
+          ],
+        },
+      ],
       errors: [
         {
           message: 'Unexpected path "../client/a" imported in restricted zone.',
@@ -83,29 +371,611 @@ ruleTester.run('no-restricted-paths', rule, {
       ],
     }),
     test({
-      code: 'import b from "../server/b.js"',
+      code: 'import b from "../server/b.js"; // 3',
       filename: testFilePath('./restricted-paths/client/a.js'),
-      options: [ {
-        zones: [ { target: './client', from: './server' } ],
-        basePath: testFilePath('./restricted-paths'),
-      } ],
-      errors: [ {
-        message: 'Unexpected path "../server/b.js" imported in restricted zone.',
-        line: 1,
-        column: 15,
-      } ],
+      options: [
+        {
+          zones: [
+            {
+              target: './client',
+              from: './server',
+            },
+          ],
+          basePath: testFilePath('./restricted-paths'),
+        },
+      ],
+      errors: [
+        {
+          message: 'Unexpected path "../server/b.js" imported in restricted zone.',
+          line: 1,
+          column: 15,
+        },
+      ],
     }),
     test({
       code: 'const b = require("../server/b.js")',
       filename: testFilePath('./restricted-paths/client/a.js'),
-      options: [ {
-        zones: [ { target: './tests/files/restricted-paths/client', from: './tests/files/restricted-paths/server' } ],
-      } ],
-      errors: [ {
-        message: 'Unexpected path "../server/b.js" imported in restricted zone.',
-        line: 1,
-        column: 19,
-      } ],
-    }),
-  ],
-})
+      options: [
+        {
+          zones: [
+            {
+              target: './tests/files/restricted-paths/client',
+              from: './tests/files/restricted-paths/server',
+            },
+          ],
+        },
+      ],
+      errors: [
+        {
+          message: 'Unexpected path "../server/b.js" imported in restricted zone.',
+          line: 1,
+          column: 19,
+        },
+      ],
+    }),
+    test({
+      code: 'import b from "../two/a.js"',
+      filename: testFilePath('./restricted-paths/server/one/a.js'),
+      options: [
+        {
+          zones: [
+            {
+              target: './tests/files/restricted-paths/server/one',
+              from: './tests/files/restricted-paths/server',
+              except: ['./one'],
+            },
+          ],
+        },
+      ],
+      errors: [
+        {
+          message: 'Unexpected path "../two/a.js" imported in restricted zone.',
+          line: 1,
+          column: 15,
+        },
+      ],
+    }),
+    test({
+      code: 'import b from "../two/a.js"',
+      filename: testFilePath('./restricted-paths/server/one/a.js'),
+      options: [
+        {
+          zones: [
+            {
+              target: './tests/files/restricted-paths/server/one',
+              from: './tests/files/restricted-paths/server',
+              except: ['./one'],
+              message: 'Custom message',
+            },
+          ],
+        },
+      ],
+      errors: [
+        {
+          message: 'Unexpected path "../two/a.js" imported in restricted zone. Custom message',
+          line: 1,
+          column: 15,
+        },
+      ],
+    }),
+    test({
+      code: 'import b from "../two/a.js"',
+      filename: testFilePath('./restricted-paths/server/one/a.js'),
+      options: [
+        {
+          zones: [
+            {
+              target: './tests/files/restricted-paths/server/one',
+              from: './tests/files/restricted-paths/server',
+              except: ['../client/a'],
+            },
+          ],
+        },
+      ],
+      errors: [
+        {
+          message: 'Restricted path exceptions must be descendants of the configured `from` path for that zone.',
+          line: 1,
+          column: 15,
+        },
+      ],
+    }),
+    test({
+      code: 'import A from "../two/a.js"',
+      filename: testFilePath('./restricted-paths/server/one/a.js'),
+      options: [
+        {
+          zones: [
+            {
+              target: '**/*',
+              from: './tests/files/restricted-paths/server/**/*',
+            },
+          ],
+        },
+      ],
+      errors: [
+        {
+          message: 'Unexpected path "../two/a.js" imported in restricted zone.',
+          line: 1,
+          column: 15,
+        },
+      ],
+    }),
+    test({
+      code: 'import A from "../two/a.js"',
+      filename: testFilePath('./restricted-paths/server/one/a.js'),
+      options: [
+        {
+          zones: [
+            {
+              target: '**/*',
+              from: './tests/files/restricted-paths/server/**/*',
+              except: ['a.js'],
+            },
+          ],
+        },
+      ],
+      errors: [
+        {
+          message: 'Restricted path exceptions must be glob patterns when `from` contains glob patterns',
+          line: 1,
+          column: 15,
+        },
+      ],
+    }),
+
+    // support of arrays for from and target
+    // array with single element
+    test({
+      code: 'import b from "../server/b.js"; // 4',
+      filename: testFilePath('./restricted-paths/client/a.js'),
+      options: [
+        {
+          zones: [
+            {
+              target: ['./tests/files/restricted-paths/client'],
+              from: './tests/files/restricted-paths/server',
+            },
+          ],
+        },
+      ],
+      errors: [
+        {
+          message: 'Unexpected path "../server/b.js" imported in restricted zone.',
+          line: 1,
+          column: 15,
+        },
+      ],
+    }),
+    test({
+      code: 'import b from "../server/b.js"; // 5',
+      filename: testFilePath('./restricted-paths/client/a.js'),
+      options: [
+        {
+          zones: [
+            {
+              target: './tests/files/restricted-paths/client',
+              from: ['./tests/files/restricted-paths/server'],
+            },
+          ],
+        },
+      ],
+      errors: [
+        {
+          message: 'Unexpected path "../server/b.js" imported in restricted zone.',
+          line: 1,
+          column: 15,
+        },
+      ],
+    }),
+    // array with multiple elements
+    test({
+      code: 'import b from "../server/b.js"; // 6',
+      filename: testFilePath('./restricted-paths/client/a.js'),
+      options: [
+        {
+          zones: [
+            {
+              target: ['./tests/files/restricted-paths/client/one', './tests/files/restricted-paths/client'],
+              from: './tests/files/restricted-paths/server',
+            },
+          ],
+        },
+      ],
+      errors: [
+        {
+          message: 'Unexpected path "../server/b.js" imported in restricted zone.',
+          line: 1,
+          column: 15,
+        },
+      ],
+    }),
+    test({
+      code: 'import b from "../server/one/b.js"\nimport a from "../server/two/a.js"',
+      filename: testFilePath('./restricted-paths/client/a.js'),
+      options: [
+        {
+          zones: [
+            {
+              target: './tests/files/restricted-paths/client',
+              from: ['./tests/files/restricted-paths/server/one', './tests/files/restricted-paths/server/two'],
+            },
+          ],
+        },
+      ],
+      errors: [
+        {
+          message: 'Unexpected path "../server/one/b.js" imported in restricted zone.',
+          line: 1,
+          column: 15,
+        },
+        {
+          message: 'Unexpected path "../server/two/a.js" imported in restricted zone.',
+          line: 2,
+          column: 15,
+        },
+      ],
+    }),
+    // array with multiple glob patterns in from
+    test({
+      code: 'import b from "../server/one/b.js"\nimport a from "../server/two/a.js"',
+      filename: testFilePath('./restricted-paths/client/a.js'),
+      options: [
+        {
+          zones: [
+            {
+              target: './tests/files/restricted-paths/client',
+              from: ['./tests/files/restricted-paths/server/one/*', './tests/files/restricted-paths/server/two/*'],
+            },
+          ],
+        },
+      ],
+      errors: [
+        {
+          message: 'Unexpected path "../server/one/b.js" imported in restricted zone.',
+          line: 1,
+          column: 15,
+        },
+        {
+          message: 'Unexpected path "../server/two/a.js" imported in restricted zone.',
+          line: 2,
+          column: 15,
+        },
+      ],
+    }),
+    // array with mix of glob and non glob patterns in target
+    test({
+      code: 'import b from "../server/b.js"; // 7',
+      filename: testFilePath('./restricted-paths/client/a.js'),
+      options: [
+        {
+          zones: [
+            {
+              target: ['./tests/files/restricted-paths/client/one', './tests/files/restricted-paths/client/**/*'],
+              from: './tests/files/restricted-paths/server',
+            },
+          ],
+        },
+      ],
+      errors: [
+        {
+          message: 'Unexpected path "../server/b.js" imported in restricted zone.',
+          line: 1,
+          column: 15,
+        },
+      ],
+    }),
+    // configuration format
+    test({
+      code: 'import A from "../two/a.js"',
+      filename: testFilePath('./restricted-paths/server/one/a.js'),
+      options: [
+        {
+          zones: [
+            {
+              target: '**/*',
+              from: ['./tests/files/restricted-paths/server/**/*'],
+              except: ['a.js'],
+            },
+          ],
+        },
+      ],
+      errors: [
+        {
+          message: 'Restricted path exceptions must be glob patterns when `from` contains glob patterns',
+          line: 1,
+          column: 15,
+        },
+      ],
+    }),
+    test({
+      code: 'import b from "../server/one/b.js"',
+      filename: testFilePath('./restricted-paths/client/a.js'),
+      options: [
+        {
+          zones: [
+            {
+              target: './tests/files/restricted-paths/client',
+              from: ['./tests/files/restricted-paths/server/one', './tests/files/restricted-paths/server/two/*'],
+            },
+          ],
+        },
+      ],
+      errors: [
+        {
+          message: 'Restricted path `from` must contain either only glob patterns or none',
+          line: 1,
+          column: 15,
+        },
+      ],
+    }),
+  ),
+});
+
+context('Typescript', function () {
+  getTSParsers().forEach((parser) => {
+    const settings = {
+      'import/parsers': { [parser]: ['.ts'] },
+      'import/resolver': { 'eslint-import-resolver-typescript': true },
+    };
+    ruleTester.run('no-restricted-paths', rule, {
+      valid: [
+        test({
+          code: 'import type a from "../client/a.ts"',
+          filename: testFilePath('./restricted-paths/server/b.ts'),
+          options: [{
+            zones: [{ target: './tests/files/restricted-paths/server', from: './tests/files/restricted-paths/other' }],
+          }],
+          parser,
+          settings,
+        }),
+        test({
+          code: 'import type a from "../client/a.ts"',
+          filename: testFilePath('./restricted-paths/server/b.ts'),
+          options: [{
+            zones: [{ target: '**/*', from: './tests/files/restricted-paths/other' }],
+          }],
+          parser,
+          settings,
+        }),
+        test({
+          code: 'import type a from "../client/a.ts"',
+          filename: testFilePath('./restricted-paths/client/b.ts'),
+          options: [{
+            zones: [{
+              target: './tests/files/restricted-paths/!(client)/**/*',
+              from: './tests/files/restricted-paths/client/**/*',
+            }],
+          }],
+          parser,
+          settings,
+        }),
+        test({
+          code: 'import type b from "../server/b.ts"',
+          filename: testFilePath('./restricted-paths/client/a.ts'),
+          options: [{
+            zones: [{ target: './tests/files/restricted-paths/client', from: './tests/files/restricted-paths/other' }],
+          }],
+          parser,
+          settings,
+        }),
+        test({
+          code: 'import type a from "./a.ts"',
+          filename: testFilePath('./restricted-paths/server/one/a.ts'),
+          options: [{
+            zones: [{
+              target: './tests/files/restricted-paths/server/one',
+              from: './tests/files/restricted-paths/server',
+              except: ['./one'],
+            }],
+          }],
+          parser,
+          settings,
+        }),
+        test({
+          code: 'import type a from "../two/a.ts"',
+          filename: testFilePath('./restricted-paths/server/one/a.ts'),
+          options: [{
+            zones: [{
+              target: './tests/files/restricted-paths/server/one',
+              from: './tests/files/restricted-paths/server',
+              except: ['./two'],
+            }],
+          }],
+          parser,
+          settings,
+        }),
+        test({
+          code: 'import type a from "../one/a.ts"',
+          filename: testFilePath('./restricted-paths/server/two-new/a.ts'),
+          options: [{
+            zones: [{
+              target: './tests/files/restricted-paths/server/two',
+              from: './tests/files/restricted-paths/server',
+              except: [],
+            }],
+          }],
+          parser,
+          settings,
+        }),
+        test({
+          code: 'import type A from "../two/a.ts"',
+          filename: testFilePath('./restricted-paths/server/one/a.ts'),
+          options: [{
+            zones: [{
+              target: '**/*',
+              from: './tests/files/restricted-paths/server/**/*',
+              except: ['**/a.js'],
+            }],
+          }],
+          parser,
+          settings,
+        }),
+        // no config
+        test({ code: 'import type b from "../server/b.js"', parser, settings }),
+        test({ code: 'import type * as b from "../server/b.js"', parser, settings }),
+      ],
+      invalid: [
+        test({
+          code: 'import type b from "../server/b"',
+          filename: testFilePath('./restricted-paths/client/a.ts'),
+          options: [{
+            zones: [{ target: './tests/files/restricted-paths/client', from: './tests/files/restricted-paths/server' }],
+          }],
+          errors: [{
+            message: 'Unexpected path "../server/b" imported in restricted zone.',
+            line: 1,
+            column: 20,
+          }],
+          parser,
+          settings,
+        }),
+        test({
+          code: 'import type b from "../server/b"',
+          filename: testFilePath('./restricted-paths/client/a.ts'),
+          options: [{
+            zones: [{ target: './tests/files/restricted-paths/client/**/*', from: './tests/files/restricted-paths/server' }],
+          }],
+          errors: [{
+            message: 'Unexpected path "../server/b" imported in restricted zone.',
+            line: 1,
+            column: 20,
+          }],
+          parser,
+          settings,
+        }),
+        test({
+          code: 'import type a from "../client/a"\nimport type c from "./c.ts"',
+          filename: testFilePath('./restricted-paths/server/b.ts'),
+          options: [{
+            zones: [
+              {
+                target: './tests/files/restricted-paths/server',
+                from: ['./tests/files/restricted-paths/client', './tests/files/restricted-paths/server/c.ts'],
+              },
+            ],
+          }],
+          errors: [
+            {
+              message: 'Unexpected path "../client/a" imported in restricted zone.',
+              line: 1,
+              column: 20,
+            },
+            {
+              message: 'Unexpected path "./c.ts" imported in restricted zone.',
+              line: 2,
+              column: 20,
+            },
+          ],
+          parser,
+          settings,
+        }),
+        test({
+          code: 'import type b from "../server/b"',
+          filename: testFilePath('./restricted-paths/client/a'),
+          options: [{
+            zones: [{ target: './client', from: './server' }],
+            basePath: testFilePath('./restricted-paths'),
+          }],
+          errors: [{
+            message: 'Unexpected path "../server/b" imported in restricted zone.',
+            line: 1,
+            column: 20,
+          }],
+          parser,
+          settings,
+        }),
+        test({
+          code: 'import type b from "../two/a"',
+          filename: testFilePath('./restricted-paths/server/one/a.ts'),
+          options: [{
+            zones: [{
+              target: './tests/files/restricted-paths/server/one',
+              from: './tests/files/restricted-paths/server',
+              except: ['./one'],
+            }],
+          }],
+          errors: [{
+            message: 'Unexpected path "../two/a" imported in restricted zone.',
+            line: 1,
+            column: 20,
+          }],
+          parser,
+          settings,
+        }),
+        test({
+          code: 'import type b from "../two/a"',
+          filename: testFilePath('./restricted-paths/server/one/a'),
+          options: [{
+            zones: [{
+              target: './tests/files/restricted-paths/server/one',
+              from: './tests/files/restricted-paths/server',
+              except: ['./one'],
+              message: 'Custom message',
+            }],
+          }],
+          errors: [{
+            message: 'Unexpected path "../two/a" imported in restricted zone. Custom message',
+            line: 1,
+            column: 20,
+          }],
+          parser,
+          settings,
+        }),
+        test({
+          code: 'import type b from "../two/a"',
+          filename: testFilePath('./restricted-paths/server/one/a.ts'),
+          options: [{
+            zones: [{
+              target: './tests/files/restricted-paths/server/one',
+              from: './tests/files/restricted-paths/server',
+              except: ['../client/a'],
+            }],
+          }],
+          errors: [{
+            message: 'Restricted path exceptions must be descendants of the configured `from` path for that zone.',
+            line: 1,
+            column: 20,
+          }],
+          parser,
+          settings,
+        }),
+        test({
+          code: 'import type A from "../two/a"',
+          filename: testFilePath('./restricted-paths/server/one/a.ts'),
+          options: [{
+            zones: [{
+              target: '**/*',
+              from: './tests/files/restricted-paths/server/**/*',
+            }],
+          }],
+          errors: [{
+            message: 'Unexpected path "../two/a" imported in restricted zone.',
+            line: 1,
+            column: 20,
+          }],
+          parser,
+          settings,
+        }),
+        test({
+          code: 'import type A from "../two/a"',
+          filename: testFilePath('./restricted-paths/server/one/a.ts'),
+          options: [{
+            zones: [{
+              target: '**/*',
+              from: './tests/files/restricted-paths/server/**/*',
+              except: ['a.ts'],
+            }],
+          }],
+          errors: [{
+            message: 'Restricted path exceptions must be glob patterns when `from` contains glob patterns',
+            line: 1,
+            column: 20,
+          }],
+          parser,
+          settings,
+        }),
+      ],
+    });
+  });
+});
diff --git a/tests/src/rules/no-self-import.js b/tests/src/rules/no-self-import.js
index f8549b49e..dd2ea1bf2 100644
--- a/tests/src/rules/no-self-import.js
+++ b/tests/src/rules/no-self-import.js
@@ -1,14 +1,13 @@
-import { test, testFilePath } from '../utils'
+import { test, testFilePath } from '../utils';
 
-import { RuleTester } from 'eslint'
+import { RuleTester } from '../rule-tester';
 
-const ruleTester = new RuleTester()
-    , rule = require('rules/no-self-import')
+const ruleTester = new RuleTester();
+const rule = require('rules/no-self-import');
 
 const error = {
-  ruleId: 'no-self-import',
   message: 'Module imports itself.',
-}
+};
 
 ruleTester.run('no-self-import', rule, {
   valid: [
@@ -118,4 +117,4 @@ ruleTester.run('no-self-import', rule, {
       filename: testFilePath('./no-self-import-folder/index.js'),
     }),
   ],
-})
+});
diff --git a/tests/src/rules/no-unassigned-import.js b/tests/src/rules/no-unassigned-import.js
index 92b276999..b73246ac0 100644
--- a/tests/src/rules/no-unassigned-import.js
+++ b/tests/src/rules/no-unassigned-import.js
@@ -1,78 +1,76 @@
-import { test } from '../utils'
-import * as path from 'path'
+import { test } from '../utils';
+import * as path from 'path';
 
-import { RuleTester } from 'eslint'
+import { RuleTester } from '../rule-tester';
 
-const ruleTester = new RuleTester()
-    , rule = require('rules/no-unassigned-import')
+const ruleTester = new RuleTester();
+const rule = require('rules/no-unassigned-import');
 
 const error = {
-  ruleId: 'no-unassigned-import',
-  message: 'Imported module should be assigned'
-}
+  message: 'Imported module should be assigned',
+};
 
 ruleTester.run('no-unassigned-import', rule, {
   valid: [
-    test({ code: 'import _ from "lodash"'}),
-    test({ code: 'import _, {foo} from "lodash"'}),
-    test({ code: 'import _, {foo as bar} from "lodash"'}),
-    test({ code: 'import {foo as bar} from "lodash"'}),
-    test({ code: 'import * as _ from "lodash"'}),
-    test({ code: 'import _ from "./"'}),
-    test({ code: 'const _ = require("lodash")'}),
-    test({ code: 'const {foo} = require("lodash")'}),
-    test({ code: 'const {foo: bar} = require("lodash")'}),
-    test({ code: 'const [a, b] = require("lodash")'}),
-    test({ code: 'const _ = require("lodash")'}),
-    test({ code: 'const _ = require("./")'}),
-    test({ code: 'foo(require("lodash"))'}),
-    test({ code: 'require("lodash").foo'}),
-    test({ code: 'require("lodash").foo()'}),
-    test({ code: 'require("lodash")()'}),
+    test({ code: 'import _ from "lodash"' }),
+    test({ code: 'import _, {foo} from "lodash"' }),
+    test({ code: 'import _, {foo as bar} from "lodash"' }),
+    test({ code: 'import {foo as bar} from "lodash"' }),
+    test({ code: 'import * as _ from "lodash"' }),
+    test({ code: 'import _ from "./"' }),
+    test({ code: 'const _ = require("lodash")' }),
+    test({ code: 'const {foo} = require("lodash")' }),
+    test({ code: 'const {foo: bar} = require("lodash")' }),
+    test({ code: 'const [a, b] = require("lodash")' }),
+    test({ code: 'const _ = require("./")' }),
+    test({ code: 'foo(require("lodash"))' }),
+    test({ code: 'require("lodash").foo' }),
+    test({ code: 'require("lodash").foo()' }),
+    test({ code: 'require("lodash")()' }),
     test({
       code: 'import "app.css"',
-      options: [{ 'allow': ['**/*.css'] }],
+      options: [{ allow: ['**/*.css'] }],
     }),
     test({
       code: 'import "app.css";',
-      options: [{ 'allow': ['*.css'] }],
+      options: [{ allow: ['*.css'] }],
     }),
     test({
       code: 'import "./app.css"',
-      options: [{ 'allow': ['**/*.css'] }],
+      options: [{ allow: ['**/*.css'] }],
     }),
     test({
       code: 'import "foo/bar"',
-      options: [{ 'allow': ['foo/**'] }],
+      options: [{ allow: ['foo/**'] }],
     }),
     test({
       code: 'import "foo/bar"',
-      options: [{ 'allow': ['foo/bar'] }],
+      options: [{ allow: ['foo/bar'] }],
     }),
     test({
       code: 'import "../dir/app.css"',
-      options: [{ 'allow': ['**/*.css'] }],
+      options: [{ allow: ['**/*.css'] }],
     }),
     test({
       code: 'import "../dir/app.js"',
-      options: [{ 'allow': ['**/dir/**'] }],
+      options: [{ allow: ['**/dir/**'] }],
     }),
     test({
       code: 'require("./app.css")',
-      options: [{ 'allow': ['**/*.css'] }],
+      options: [{ allow: ['**/*.css'] }],
     }),
     test({
       code: 'import "babel-register"',
-      options: [{ 'allow': ['babel-register'] }],
+      options: [{ allow: ['babel-register'] }],
     }),
     test({
       code: 'import "./styles/app.css"',
-      options: [{ 'allow': ['src/styles/**'] }],
+      options: [{ allow: ['src/styles/**'] }],
       filename: path.join(process.cwd(), 'src/app.js'),
     }),
     test({
       code: 'import "../scripts/register.js"',
-      options: [{ 'allow': ['src/styles/**', '**/scripts/*.js'] }],
+      options: [{ allow: ['src/styles/**', '**/scripts/*.js'] }],
       filename: path.join(process.cwd(), 'src/app.js'),
     }),
   ],
@@ -87,24 +85,24 @@ ruleTester.run('no-unassigned-import', rule, {
     }),
     test({
       code: 'import "./app.css"',
-      options: [{ 'allow': ['**/*.js'] }],
+      options: [{ allow: ['**/*.js'] }],
       errors: [error],
     }),
     test({
       code: 'import "./app.css"',
-      options: [{ 'allow': ['**/dir/**'] }],
+      options: [{ allow: ['**/dir/**'] }],
       errors: [error],
     }),
     test({
       code: 'require("./app.css")',
-      options: [{ 'allow': ['**/*.js'] }],
+      options: [{ allow: ['**/*.js'] }],
       errors: [error],
     }),
     test({
       code: 'import "./styles/app.css"',
-      options: [{ 'allow': ['styles/*.css'] }],
+      options: [{ allow: ['styles/*.css'] }],
       filename: path.join(process.cwd(), 'src/app.js'),
       errors: [error],
     }),
   ],
-})
+});
diff --git a/tests/src/rules/no-unresolved.js b/tests/src/rules/no-unresolved.js
index 124ac8483..c6e300c5d 100644
--- a/tests/src/rules/no-unresolved.js
+++ b/tests/src/rules/no-unresolved.js
@@ -1,36 +1,46 @@
-import * as path from 'path'
+import path from 'path';
 
-import { test, SYNTAX_CASES } from '../utils'
+import { getTSParsers, test, SYNTAX_CASES, testVersion, parsers } from '../utils';
 
-import { CASE_SENSITIVE_FS } from 'eslint-module-utils/resolve'
+import { CASE_SENSITIVE_FS } from 'eslint-module-utils/resolve';
 
-import { RuleTester } from 'eslint'
+import { RuleTester } from '../rule-tester';
 
-var ruleTester = new RuleTester()
-  , rule = require('rules/no-unresolved')
+const ruleTester = new RuleTester();
+const rule = require('rules/no-unresolved');
 
 function runResolverTests(resolver) {
   // redefine 'test' to set a resolver
   // thus 'rest'. needed something 4-chars-long for formatting simplicity
   function rest(specs) {
-    specs.settings = Object.assign({},
-      specs.settings,
-      { 'import/resolver': resolver }
-    )
-
-    return test(specs)
+    return test({
+      ...specs,
+      settings: {
+        ...specs.settings,
+        'import/resolver': resolver,
+        'import/cache': { lifetime: 0 },
+      },
+    });
   }
 
   ruleTester.run(`no-unresolved (${resolver})`, rule, {
-    valid: [
+    valid: [].concat(
       test({ code: 'import "./malformed.js"' }),
 
       rest({ code: 'import foo from "./bar";' }),
       rest({ code: "import bar from './bar.js';" }),
       rest({ code: "import {someThing} from './test-module';" }),
       rest({ code: "import fs from 'fs';" }),
-      rest({ code: "import('fs');"
-           , parser: require.resolve('babel-eslint') }),
+      rest({
+        code: "import('fs');",
+        parser: parsers.BABEL_OLD,
+      }),
+
+      // check with eslint parser
+      testVersion('>= 7', () => rest({
+        code: "import('fs');",
+        parserOptions: { ecmaVersion: 2021 },
+      })) || [],
 
       rest({ code: 'import * as foo from "a"' }),
 
@@ -39,156 +49,225 @@ function runResolverTests(resolver) {
       rest({ code: 'let foo; export { foo }' }),
 
       // stage 1 proposal for export symmetry,
-      rest({ code: 'export * as bar from "./bar"'
-           , parser: require.resolve('babel-eslint') }),
-      rest({ code: 'export bar from "./bar"'
-           , parser: require.resolve('babel-eslint') }),
+      rest({
+        code: 'export * as bar from "./bar"',
+        parser: parsers.BABEL_OLD,
+      }),
+      rest({
+        code: 'export bar from "./bar"',
+        parser: parsers.BABEL_OLD,
+      }),
       rest({ code: 'import foo from "./jsx/MyUnCoolComponent.jsx"' }),
 
       // commonjs setting
-      rest({ code: 'var foo = require("./bar")'
-           , options: [{ commonjs: true }]}),
-      rest({ code: 'require("./bar")'
-           , options: [{ commonjs: true }]}),
-      rest({ code: 'require("./does-not-exist")'
-           , options: [{ commonjs: false }]}),
+      rest({
+        code: 'var foo = require("./bar")',
+        options: [{ commonjs: true }],
+      }),
+      rest({
+        code: 'require("./bar")',
+        options: [{ commonjs: true }],
+      }),
+      rest({
+        code: 'require("./does-not-exist")',
+        options: [{ commonjs: false }],
+      }),
       rest({ code: 'require("./does-not-exist")' }),
 
       // amd setting
-      rest({ code: 'require(["./bar"], function (bar) {})'
-           , options: [{ amd: true }]}),
-      rest({ code: 'define(["./bar"], function (bar) {})'
-           , options: [{ amd: true }]}),
-      rest({ code: 'require(["./does-not-exist"], function (bar) {})'
-           , options: [{ amd: false }]}),
-      // magic modules: http://git.io/vByan
-      rest({ code: 'define(["require", "exports", "module"], function (r, e, m) { })'
-           , options: [{ amd: true }]}),
+      rest({
+        code: 'require(["./bar"], function (bar) {})',
+        options: [{ amd: true }],
+      }),
+      rest({
+        code: 'define(["./bar"], function (bar) {})',
+        options: [{ amd: true }],
+      }),
+      rest({
+        code: 'require(["./does-not-exist"], function (bar) {})',
+        options: [{ amd: false }],
+      }),
+      // magic modules: https://github.com/requirejs/requirejs/wiki/Differences-between-the-simplified-CommonJS-wrapper-and-standard-AMD-define#magic-modules
+      rest({
+        code: 'define(["require", "exports", "module"], function (r, e, m) { })',
+        options: [{ amd: true }],
+      }),
 
       // don't validate without callback param
-      rest({ code: 'require(["./does-not-exist"])'
-           , options: [{ amd: true }]}),
+      rest({
+        code: 'require(["./does-not-exist"])',
+        options: [{ amd: true }],
+      }),
       rest({ code: 'define(["./does-not-exist"], function (bar) {})' }),
 
       // stress tests
-      rest({ code: 'require("./does-not-exist", "another arg")'
-           , options: [{ commonjs: true, amd: true }]}),
-      rest({ code: 'proxyquire("./does-not-exist")'
-           , options: [{ commonjs: true, amd: true }]}),
-      rest({ code: '(function() {})("./does-not-exist")'
-           , options: [{ commonjs: true, amd: true }]}),
-      rest({ code: 'define([0, foo], function (bar) {})'
-           , options: [{ amd: true }]}),
-      rest({ code: 'require(0)'
-           , options: [{ commonjs: true }]}),
-      rest({ code: 'require(foo)'
-           , options: [{ commonjs: true }]}),
-    ],
+      rest({
+        code: 'require("./does-not-exist", "another arg")',
+        options: [{ commonjs: true, amd: true }],
+      }),
+      rest({
+        code: 'proxyquire("./does-not-exist")',
+        options: [{ commonjs: true, amd: true }],
+      }),
+      rest({
+        code: '(function() {})("./does-not-exist")',
+        options: [{ commonjs: true, amd: true }],
+      }),
+      rest({
+        code: 'define([0, foo], function (bar) {})',
+        options: [{ amd: true }],
+      }),
+      rest({
+        code: 'require(0)',
+        options: [{ commonjs: true }],
+      }),
+      rest({
+        code: 'require(foo)',
+        options: [{ commonjs: true }],
+      }),
+    ),
 
-    invalid: [
+    invalid: [].concat(
       rest({
         code: 'import reallyfake from "./reallyfake/module"',
         settings: { 'import/ignore': ['^\\./fake/'] },
-        errors: [{ message: 'Unable to resolve path to module ' +
-                            '\'./reallyfake/module\'.' }],
+        errors: [
+          { message: 'Unable to resolve path to module \'./reallyfake/module\'.' },
+        ],
       }),
 
-
       rest({
         code: "import bar from './baz';",
-        errors: [{ message: "Unable to resolve path to module './baz'."
-                 , type: 'Literal' }],
+        errors: [
+          {
+            message: "Unable to resolve path to module './baz'.",
+            type: 'Literal',
+          },
+        ],
       }),
-      rest({ code: "import bar from './baz';"
-           , errors: [{ message: "Unable to resolve path to module './baz'."
-                      , type: 'Literal',
-                      }] }),
       rest({
         code: "import bar from './empty-folder';",
-        errors: [{ message: "Unable to resolve path to module './empty-folder'."
-                 , type: 'Literal',
-                 }]}),
+        errors: [
+          {
+            message: "Unable to resolve path to module './empty-folder'.",
+            type: 'Literal',
+          },
+        ],
+      }),
 
       // sanity check that this module is _not_ found without proper settings
       rest({
         code: "import { DEEP } from 'in-alternate-root';",
-        errors: [{ message: 'Unable to resolve path to ' +
-                            "module 'in-alternate-root'."
-                 , type: 'Literal',
-                 }]}),
+        errors: [
+          {
+            message: 'Unable to resolve path to module \'in-alternate-root\'.',
+            type: 'Literal',
+          },
+        ],
+      }),
       rest({
-      code: "import('in-alternate-root').then(function({DEEP}){});",
-      errors: [{ message: 'Unable to resolve path to ' +
-                          "module 'in-alternate-root'."
-                , type: 'Literal',
-                }],
-      parser: require.resolve('babel-eslint')}),
+        code: "import('in-alternate-root').then(function({DEEP}) {});",
+        errors: [
+          {
+            message: 'Unable to resolve path to module \'in-alternate-root\'.',
+            type: 'Literal',
+          },
+        ],
+        parser: parsers.BABEL_OLD,
+      }),
 
-      rest({ code: 'export { foo } from "./does-not-exist"'
-           , errors: ["Unable to resolve path to module './does-not-exist'."] }),
+      rest({
+        code: 'export { foo } from "./does-not-exist"',
+        errors: ["Unable to resolve path to module './does-not-exist'."],
+      }),
       rest({
         code: 'export * from "./does-not-exist"',
         errors: ["Unable to resolve path to module './does-not-exist'."],
       }),
 
+      // check with eslint parser
+      testVersion('>= 7', () => rest({
+        code: "import('in-alternate-root').then(function({DEEP}) {});",
+        errors: [
+          {
+            message: 'Unable to resolve path to module \'in-alternate-root\'.',
+            type: 'Literal',
+          },
+        ],
+        parserOptions: { ecmaVersion: 2021 },
+      })) || [],
+
       // export symmetry proposal
-      rest({ code: 'export * as bar from "./does-not-exist"'
-           , parser: require.resolve('babel-eslint')
-           , errors: ["Unable to resolve path to module './does-not-exist'."],
-           }),
-      rest({ code: 'export bar from "./does-not-exist"'
-           , parser: require.resolve('babel-eslint')
-           , errors: ["Unable to resolve path to module './does-not-exist'."],
-           }),
+      rest({
+        code: 'export * as bar from "./does-not-exist"',
+        parser: parsers.BABEL_OLD,
+        errors: ["Unable to resolve path to module './does-not-exist'."],
+      }),
+      rest({
+        code: 'export bar from "./does-not-exist"',
+        parser: parsers.BABEL_OLD,
+        errors: ["Unable to resolve path to module './does-not-exist'."],
+      }),
 
       // commonjs setting
       rest({
         code: 'var bar = require("./baz")',
         options: [{ commonjs: true }],
-        errors: [{
-          message: "Unable to resolve path to module './baz'.",
-          type: 'Literal',
-        }],
+        errors: [
+          {
+            message: "Unable to resolve path to module './baz'.",
+            type: 'Literal',
+          },
+        ],
       }),
       rest({
         code: 'require("./baz")',
         options: [{ commonjs: true }],
-        errors: [{
-          message: "Unable to resolve path to module './baz'.",
-          type: 'Literal',
-        }],
+        errors: [
+          {
+            message: "Unable to resolve path to module './baz'.",
+            type: 'Literal',
+          },
+        ],
       }),
 
       // amd
       rest({
         code: 'require(["./baz"], function (bar) {})',
         options: [{ amd: true }],
-        errors: [{
-          message: "Unable to resolve path to module './baz'.",
-          type: 'Literal',
-        }],
+        errors: [
+          {
+            message: "Unable to resolve path to module './baz'.",
+            type: 'Literal',
+          },
+        ],
       }),
       rest({
         code: 'define(["./baz"], function (bar) {})',
         options: [{ amd: true }],
-        errors: [{
-          message: "Unable to resolve path to module './baz'.",
-          type: 'Literal',
-        }],
+        errors: [
+          {
+            message: "Unable to resolve path to module './baz'.",
+            type: 'Literal',
+          },
+        ],
       }),
       rest({
         code: 'define(["./baz", "./bar", "./does-not-exist"], function (bar) {})',
         options: [{ amd: true }],
-        errors: [{
-          message: "Unable to resolve path to module './baz'.",
-          type: 'Literal',
-        },{
-          message: "Unable to resolve path to module './does-not-exist'.",
-          type: 'Literal',
-        }],
+        errors: [
+          {
+            message: "Unable to resolve path to module './baz'.",
+            type: 'Literal',
+          },
+          {
+            message: "Unable to resolve path to module './does-not-exist'.",
+            type: 'Literal',
+          },
+        ],
       }),
-    ],
-  })
+    ),
+  });
 
   ruleTester.run(`issue #333 (${resolver})`, rule, {
     valid: [
@@ -204,14 +283,18 @@ function runResolverTests(resolver) {
       }),
     ],
     invalid: [
-        rest({
-          code: 'import bar from "./foo.json"',
-          errors: ["Unable to resolve path to module './foo.json'."],
-        }),
+      rest({
+        code: 'import bar from "./foo.json"',
+        errors: ["Unable to resolve path to module './foo.json'."],
+      }),
     ],
-  })
+  });
 
   if (!CASE_SENSITIVE_FS) {
+    const relativePath = './tests/files/jsx/MyUnCoolComponent.jsx';
+    const cwd = process.cwd();
+    const mismatchedPath = path.join(cwd.toUpperCase(), relativePath).replace(/\\/g, '/');
+
     ruleTester.run('case sensitivity', rule, {
       valid: [
         rest({ // test with explicit flag
@@ -231,12 +314,35 @@ function runResolverTests(resolver) {
           errors: [`Casing of ./jsx/MyUncoolComponent.jsx does not match the underlying filesystem.`],
         }),
       ],
-    })
+    });
+
+    ruleTester.run('case sensitivity strict', rule, {
+      valid: [
+        // #1259 issue
+        rest({ // caseSensitiveStrict is disabled by default
+          code: `import foo from "${mismatchedPath}"`,
+        }),
+      ],
+
+      invalid: [
+        // #1259 issue
+        rest({ // test with enabled caseSensitiveStrict option
+          code: `import foo from "${mismatchedPath}"`,
+          options: [{ caseSensitiveStrict: true }],
+          errors: [`Casing of ${mismatchedPath} does not match the underlying filesystem.`],
+        }),
+        rest({ // test with enabled caseSensitiveStrict option and disabled caseSensitive
+          code: `import foo from "${mismatchedPath}"`,
+          options: [{ caseSensitiveStrict: true, caseSensitive: false }],
+          errors: [`Casing of ${mismatchedPath} does not match the underlying filesystem.`],
+        }),
+      ],
+    });
   }
 
 }
 
-['node', 'webpack'].forEach(runResolverTests)
+['node', 'webpack'].forEach(runResolverTests);
 
 ruleTester.run('no-unresolved (import/resolve legacy)', rule, {
   valid: [
@@ -244,33 +350,38 @@ ruleTester.run('no-unresolved (import/resolve legacy)', rule, {
       code: "import { DEEP } from 'in-alternate-root';",
       settings: {
         'import/resolve': {
-          'paths': [path.join( process.cwd()
-                             , 'tests', 'files', 'alternate-root')],
+          paths: [
+            path.join(process.cwd(), 'tests', 'files', 'alternate-root'),
+          ],
         },
       },
     }),
 
     test({
-      code: "import { DEEP } from 'in-alternate-root'; " +
-            "import { bar } from 'src-bar';",
-      settings: {'import/resolve': { 'paths': [
-        path.join('tests', 'files', 'src-root'),
-        path.join('tests', 'files', 'alternate-root'),
-      ]}}}),
+      code: "import { DEEP } from 'in-alternate-root'; import { bar } from 'src-bar';",
+      settings: {
+        'import/resolve': {
+          paths: [
+            path.join('tests', 'files', 'src-root'),
+            path.join('tests', 'files', 'alternate-root'),
+          ],
+        },
+      },
+    }),
 
     test({
       code: 'import * as foo from "jsx-module/foo"',
-      settings: { 'import/resolve': { 'extensions': ['.jsx'] } },
+      settings: { 'import/resolve': { extensions: ['.jsx'] } },
     }),
   ],
 
   invalid: [
     test({
       code: 'import * as foo from "jsx-module/foo"',
-      errors: [ "Unable to resolve path to module 'jsx-module/foo'." ],
+      errors: ["Unable to resolve path to module 'jsx-module/foo'."],
     }),
   ],
-})
+});
 
 ruleTester.run('no-unresolved (webpack-specific)', rule, {
   valid: [
@@ -290,62 +401,61 @@ ruleTester.run('no-unresolved (webpack-specific)', rule, {
       // default webpack config in files/webpack.config.js knows about jsx
       code: 'import * as foo from "jsx-module/foo"',
       settings: {
-        'import/resolver': { 'webpack': { 'config': 'webpack.empty.config.js' } },
+        'import/resolver': { webpack: { config: 'webpack.empty.config.js' } },
       },
-      errors: [ "Unable to resolve path to module 'jsx-module/foo'." ],
+      errors: ["Unable to resolve path to module 'jsx-module/foo'."],
     }),
   ],
-})
-
+});
 
 ruleTester.run('no-unresolved ignore list', rule, {
   valid: [
     test({
       code: 'import "./malformed.js"',
-      options: [{ ignore: ['\.png$', '\.gif$']}],
+      options: [{ ignore: ['.png$', '.gif$'] }],
     }),
     test({
       code: 'import "./test.giffy"',
-      options: [{ ignore: ['\.png$', '\.gif$']}],
+      options: [{ ignore: ['.png$', '.gif$'] }],
     }),
 
     test({
       code: 'import "./test.gif"',
-      options: [{ ignore: ['\.png$', '\.gif$']}],
+      options: [{ ignore: ['.png$', '.gif$'] }],
     }),
 
     test({
       code: 'import "./test.png"',
-      options: [{ ignore: ['\.png$', '\.gif$']}],
+      options: [{ ignore: ['.png$', '.gif$'] }],
     }),
   ],
 
-  invalid:[
+  invalid: [
     test({
       code: 'import "./test.gif"',
-      options: [{ ignore: ['\.png$']}],
-      errors: [ "Unable to resolve path to module './test.gif'." ],
+      options: [{ ignore: ['.png$'] }],
+      errors: ["Unable to resolve path to module './test.gif'."],
     }),
 
     test({
       code: 'import "./test.png"',
-      options: [{ ignore: ['\.gif$']}],
-      errors: [ "Unable to resolve path to module './test.png'." ],
+      options: [{ ignore: ['.gif$'] }],
+      errors: ["Unable to resolve path to module './test.png'."],
     }),
   ],
-})
+});
 
 ruleTester.run('no-unresolved unknown resolver', rule, {
   valid: [],
 
-  invalid:[
+  invalid: [
 
     // logs resolver load error
     test({
       code: 'import "./malformed.js"',
-      settings: { 'import/resolver': 'foo' },
+      settings: { 'import/resolver': 'doesnt-exist' },
       errors: [
-        `Resolve error: unable to load resolver "foo".`,
+        `Resolve error: unable to load resolver "doesnt-exist".`,
         `Unable to resolve path to module './malformed.js'.`,
       ],
     }),
@@ -353,15 +463,15 @@ ruleTester.run('no-unresolved unknown resolver', rule, {
     // only logs resolver message once
     test({
       code: 'import "./malformed.js"; import "./fake.js"',
-      settings: { 'import/resolver': 'foo' },
+      settings: { 'import/resolver': 'doesnt-exist' },
       errors: [
-        `Resolve error: unable to load resolver "foo".`,
+        `Resolve error: unable to load resolver "doesnt-exist".`,
         `Unable to resolve path to module './malformed.js'.`,
         `Unable to resolve path to module './fake.js'.`,
       ],
     }),
   ],
-})
+});
 
 ruleTester.run('no-unresolved electron', rule, {
   valid: [
@@ -370,15 +480,62 @@ ruleTester.run('no-unresolved electron', rule, {
       settings: { 'import/core-modules': ['electron'] },
     }),
   ],
-  invalid:[
+  invalid: [
     test({
       code: 'import "electron"',
       errors: [`Unable to resolve path to module 'electron'.`],
     }),
   ],
-})
+});
 
 ruleTester.run('no-unresolved syntax verification', rule, {
   valid: SYNTAX_CASES,
-  invalid:[],
-})
+  invalid: [],
+});
+
+// https://github.com/import-js/eslint-plugin-import/issues/2024
+ruleTester.run('import() with built-in parser', rule, {
+  valid: [].concat(
+    testVersion('>=7', () => ({
+      code: "import('fs');",
+      parserOptions: { ecmaVersion: 2021 },
+    })) || [],
+  ),
+  invalid: [].concat(
+    testVersion('>=7', () => ({
+      code: 'import("./does-not-exist-l0w9ssmcqy9").then(() => {})',
+      parserOptions: { ecmaVersion: 2021 },
+      errors: ["Unable to resolve path to module './does-not-exist-l0w9ssmcqy9'."],
+    })) || [],
+  ),
+});
+
+context('TypeScript', () => {
+  // Type-only imports were added in TypeScript ESTree 2.23.0
+  getTSParsers().filter((x) => x !== parsers.TS_OLD).forEach((parser) => {
+    ruleTester.run(`${parser}: no-unresolved ignore type-only`, rule, {
+      valid: [
+        test({
+          code: 'import type { JSONSchema7Type } from "@types/json-schema";',
+          parser,
+        }),
+        test({
+          code: 'export type { JSONSchema7Type } from "@types/json-schema";',
+          parser,
+        }),
+      ],
+      invalid: [
+        test({
+          code: 'import { JSONSchema7Type } from "@types/json-schema";',
+          errors: ["Unable to resolve path to module '@types/json-schema'."],
+          parser,
+        }),
+        test({
+          code: 'export { JSONSchema7Type } from "@types/json-schema";',
+          errors: ["Unable to resolve path to module '@types/json-schema'."],
+          parser,
+        }),
+      ],
+    });
+  });
+});
diff --git a/tests/src/rules/no-unused-modules.js b/tests/src/rules/no-unused-modules.js
index 8050b5693..a15d2c237 100644
--- a/tests/src/rules/no-unused-modules.js
+++ b/tests/src/rules/no-unused-modules.js
@@ -1,42 +1,111 @@
-import { test, testFilePath } from '../utils'
+import { test, testVersion, testFilePath, getTSParsers, parsers } from '../utils';
+import jsxConfig from '../../../config/react';
+import typescriptConfig from '../../../config/typescript';
 
-import { RuleTester } from 'eslint'
-import { expect } from 'chai'
-import fs from 'fs'
+import { RuleTester } from '../rule-tester';
+import { expect } from 'chai';
+import { execSync } from 'child_process';
+import fs from 'fs';
+import eslintPkg from 'eslint/package.json';
+import path from 'path';
+import process from 'process';
+import semver from 'semver';
 
-const ruleTester = new RuleTester()
-    , rule = require('rules/no-unused-modules')
+let FlatRuleTester;
+try {
+  ({ FlatRuleTester } = require('eslint/use-at-your-own-risk'));
+} catch (e) { /**/ }
 
-const error = message => ({ ruleId: 'no-unused-modules', message })
+// TODO: figure out why these tests fail in eslint 4 and 5
+const isESLint4TODO = semver.satisfies(eslintPkg.version, '^4 || ^5');
+const isESLint9 = semver.satisfies(eslintPkg.version, '>=9');
+
+const ruleTester = new RuleTester();
+const typescriptRuleTester = new RuleTester(typescriptConfig);
+const jsxRuleTester = new RuleTester(jsxConfig);
+const rule = require('rules/no-unused-modules');
+
+const error = (message) => ({ message });
 
 const missingExportsOptions = [{
   missingExports: true,
-}]
+}];
 
 const unusedExportsOptions = [{
   unusedExports: true,
   src: [testFilePath('./no-unused-modules/**/*.js')],
   ignoreExports: [testFilePath('./no-unused-modules/*ignored*.js')],
-}]
+}];
+
+const unusedExportsTypescriptOptions = [{
+  unusedExports: true,
+  src: [testFilePath('./no-unused-modules/typescript')],
+  ignoreExports: undefined,
+}];
+
+const unusedExportsTypescriptIgnoreUnusedTypesOptions = [{
+  unusedExports: true,
+  ignoreUnusedTypeExports: true,
+  src: [testFilePath('./no-unused-modules/typescript')],
+  ignoreExports: undefined,
+}];
+
+const unusedExportsJsxOptions = [{
+  unusedExports: true,
+  src: [testFilePath('./no-unused-modules/jsx')],
+  ignoreExports: undefined,
+}];
 
 // tests for missing exports
 ruleTester.run('no-unused-modules', rule, {
   valid: [
-    test({ code: 'export default function noOptions() {}' }),
-    test({ options: missingExportsOptions,
-           code: 'export default () => 1'}),
-    test({ options: missingExportsOptions,
-           code: 'export const a = 1'}),
-    test({ options: missingExportsOptions,
-           code: 'const a = 1; export { a }'}),
-    test({ options: missingExportsOptions,
-           code: 'function a() { return true }; export { a }'}),
-    test({ options: missingExportsOptions,
-           code: 'const a = 1; const b = 2; export { a, b }'}),
-    test({ options: missingExportsOptions,
-           code: 'const a = 1; export default a'}),
-    test({ options: missingExportsOptions,
-           code: 'export class Foo {}'}),
+    test({
+      code: 'export default function noOptions() {}',
+    }),
+    test({
+      options: missingExportsOptions,
+      code: 'export default () => 1',
+    }),
+    test({
+      options: missingExportsOptions,
+      code: 'export const a = 1',
+    }),
+    test({
+      options: missingExportsOptions,
+      code: 'const a = 1; export { a }',
+    }),
+    test({
+      options: missingExportsOptions,
+      code: 'function a() { return true }; export { a }',
+    }),
+    test({
+      options: missingExportsOptions,
+      code: 'const a = 1; const b = 2; export { a, b }',
+    }),
+    test({
+      options: missingExportsOptions,
+      code: 'const a = 1; export default a',
+    }),
+    test({
+      options: missingExportsOptions,
+      code: 'export class Foo {}',
+    }),
+    test({
+      options: missingExportsOptions,
+      code: 'export const [foobar] = [];',
+    }),
+    test({
+      options: missingExportsOptions,
+      code: 'export const [foobar] = foobarFactory();',
+    }),
+    test({
+      options: missingExportsOptions,
+      code: `
+        export default function NewComponent () {
+          return 'I am new component'
+        }
+      `,
+    }),
   ],
   invalid: [
     test({
@@ -50,592 +119,1405 @@ ruleTester.run('no-unused-modules', rule, {
       errors: [error(`No exports found`)],
     }),
   ],
-})
-
+});
 
-// tests for  exports
+// tests for exports
 ruleTester.run('no-unused-modules', rule, {
   valid: [
-
-    test({ options: unusedExportsOptions,
-           code: 'import { o2 } from "./file-o";export default () => 12',
-           filename: testFilePath('./no-unused-modules/file-a.js')}),
-    test({ options: unusedExportsOptions,
-           code: 'export const b = 2',
-           filename: testFilePath('./no-unused-modules/file-b.js')}),
-    test({ options: unusedExportsOptions,
-           code: 'const c1 = 3; function c2() { return 3 }; export { c1, c2 }',
-           filename: testFilePath('./no-unused-modules/file-c.js')}),
-    test({ options: unusedExportsOptions,
-           code: 'export function d() { return 4 }',
-           filename: testFilePath('./no-unused-modules/file-d.js')}),
-    test({ options: unusedExportsOptions,
-           code: 'export class q { q0() {} }',
-           filename: testFilePath('./no-unused-modules/file-q.js')}),
-    test({ options: unusedExportsOptions,
-           code: 'const e0 = 5; export { e0 as e }',
-           filename: testFilePath('./no-unused-modules/file-e.js')}),
-    test({ options: unusedExportsOptions,
-           code: 'const l0 = 5; const l = 10; export { l0 as l1, l }; export default () => {}',
-           filename: testFilePath('./no-unused-modules/file-l.js')}),
-    test({ options: unusedExportsOptions,
-           code: 'const o0 = 0; const o1 = 1; export { o0, o1 as o2 }; export default () => {}',
-           filename: testFilePath('./no-unused-modules/file-o.js')}),
-    ],
+    test({
+      options: unusedExportsOptions,
+      code: 'import { o2 } from "./file-o";export default () => 12',
+      filename: testFilePath('./no-unused-modules/file-a.js'),
+      parser: parsers.BABEL_OLD,
+    }),
+    test({
+      options: unusedExportsOptions,
+      code: 'export const b = 2',
+      filename: testFilePath('./no-unused-modules/file-b.js'),
+      parser: parsers.BABEL_OLD,
+    }),
+    test({
+      options: unusedExportsOptions,
+      code: 'const c1 = 3; function c2() { return 3 }; export { c1, c2 }',
+      filename: testFilePath('./no-unused-modules/file-c.js'),
+      parser: parsers.BABEL_OLD,
+    }),
+    test({
+      options: unusedExportsOptions,
+      code: 'export function d() { return 4 }',
+      filename: testFilePath('./no-unused-modules/file-d.js'),
+      parser: parsers.BABEL_OLD,
+    }),
+    test({
+      options: unusedExportsOptions,
+      code: 'export class q { q0() {} }',
+      filename: testFilePath('./no-unused-modules/file-q.js'),
+      parser: parsers.BABEL_OLD,
+    }),
+    test({
+      options: unusedExportsOptions,
+      code: 'const e0 = 5; export { e0 as e }',
+      filename: testFilePath('./no-unused-modules/file-e.js'),
+      parser: parsers.BABEL_OLD,
+    }),
+    test({
+      options: unusedExportsOptions,
+      code: 'const l0 = 5; const l = 10; export { l0 as l1, l }; export default () => {}',
+      filename: testFilePath('./no-unused-modules/file-l.js'),
+      parser: parsers.BABEL_OLD,
+    }),
+    test({
+      options: unusedExportsOptions,
+      code: 'const o0 = 0; const o1 = 1; export { o0, o1 as o2 }; export default () => {}',
+      filename: testFilePath('./no-unused-modules/file-o.js'),
+      parser: parsers.BABEL_OLD,
+    }),
+    test({
+      options: unusedExportsOptions,
+      code: `
+        export const [o0, o2] = createLoadingAndErrorSelectors(
+          AUTH_USER
+        );
+      `,
+      filename: testFilePath('./no-unused-modules/file-o.js'),
+    }),
+  ],
   invalid: [
-    test({ options: unusedExportsOptions,
-           code: `import eslint from 'eslint'
-           import fileA from './file-a'
-           import { b } from './file-b'
-           import { c1, c2 } from './file-c'
-           import { d } from './file-d'
-           import { e } from './file-e'
-           import { e2 } from './file-e'
-           import { h2 } from './file-h'
-           import * as l from './file-l'
-           export * from './file-n'
-           export { default, o0, o3 } from './file-o'
-           export { p } from './file-p'`,
-           filename: testFilePath('./no-unused-modules/file-0.js'),
-           errors: [
-             error(`exported declaration 'default' not used within other modules`),
-             error(`exported declaration 'o0' not used within other modules`),
-             error(`exported declaration 'o3' not used within other modules`),
-             error(`exported declaration 'p' not used within other modules`),
-           ]}),
-    test({ options: unusedExportsOptions,
-           code: `const n0 = 'n0'; const n1 = 42; export { n0, n1 }; export default () => {}`,
-           filename: testFilePath('./no-unused-modules/file-n.js'),
-           errors: [error(`exported declaration 'default' not used within other modules`)]}),
-  ],
-})
+    test({
+      options: unusedExportsOptions,
+      code: `
+        import eslint from 'eslint'
+        import fileA from './file-a'
+        import { b } from './file-b'
+        import { c1, c2 } from './file-c'
+        import { d } from './file-d'
+        import { e } from './file-e'
+        import { e2 } from './file-e'
+        import { h2 } from './file-h'
+        import * as l from './file-l'
+        export * from './file-n'
+        export { default, o0, o3 } from './file-o'
+        export { p } from './file-p'
+        import s from './file-s'
+      `,
+      filename: testFilePath('./no-unused-modules/file-0.js'),
+      errors: [
+        {
+          message: `exported declaration 'default' not used within other modules`,
+          line: 12,
+          column: 18,
+        },
+        {
+          message: `exported declaration 'o0' not used within other modules`,
+          line: 12,
+          column: 27,
+        },
+        {
+          message: `exported declaration 'o3' not used within other modules`,
+          line: 12,
+          column: 31,
+        },
+        error(`exported declaration 'p' not used within other modules`),
+      ],
+    }),
+    test({
+      options: unusedExportsOptions,
+      code: `const n0 = 'n0'; const n1 = 42; export { n0, n1 }; export default () => {}`,
+      filename: testFilePath('./no-unused-modules/file-n.js'),
+      errors: [error(`exported declaration 'default' not used within other modules`)],
+    }),
+  ],
+});
 
 // test for unused exports
 ruleTester.run('no-unused-modules', rule, {
   valid: [],
   invalid: [
-    test({ options: unusedExportsOptions,
-           code: 'export default () => 13',
-           filename: testFilePath('./no-unused-modules/file-f.js'),
-           errors: [error(`exported declaration 'default' not used within other modules`)]}),
-    test({ options: unusedExportsOptions,
-           code: 'export const g = 2',
-           filename: testFilePath('./no-unused-modules/file-g.js'),
-           errors: [error(`exported declaration 'g' not used within other modules`)]}),
-    test({ options: unusedExportsOptions,
-           code: 'const h1 = 3; function h2() { return 3 }; const h3 = true; export { h1, h2, h3 }',
-           filename: testFilePath('./no-unused-modules/file-h.js'),
-           errors: [error(`exported declaration 'h1' not used within other modules`)]}),
-    test({ options: unusedExportsOptions,
-           code: 'const i1 = 3; function i2() { return 3 }; export { i1, i2 }',
-           filename: testFilePath('./no-unused-modules/file-i.js'),
-           errors: [
-             error(`exported declaration 'i1' not used within other modules`),
-             error(`exported declaration 'i2' not used within other modules`),
-           ]}),
-    test({ options: unusedExportsOptions,
-           code: 'export function j() { return 4 }',
-           filename: testFilePath('./no-unused-modules/file-j.js'),
-           errors: [error(`exported declaration 'j' not used within other modules`)]}),
-    test({ options: unusedExportsOptions,
-           code: 'export class q { q0() {} }',
-           filename: testFilePath('./no-unused-modules/file-q.js'),
-           errors: [error(`exported declaration 'q' not used within other modules`)]}),
-    test({ options: unusedExportsOptions,
-           code: 'const k0 = 5; export { k0 as k }',
-           filename: testFilePath('./no-unused-modules/file-k.js'),
-           errors: [error(`exported declaration 'k' not used within other modules`)]}),
-  ],
-})
-
-// // test for export from 
+    test({
+      options: unusedExportsOptions,
+      code: 'export default () => 13',
+      filename: testFilePath('./no-unused-modules/file-f.js'),
+      errors: [error(`exported declaration 'default' not used within other modules`)],
+    }),
+    test({
+      options: unusedExportsOptions,
+      code: 'export const g = 2',
+      filename: testFilePath('./no-unused-modules/file-g.js'),
+      errors: [error(`exported declaration 'g' not used within other modules`)],
+    }),
+    test({
+      options: unusedExportsOptions,
+      code: 'const h1 = 3; function h2() { return 3 }; const h3 = true; export { h1, h2, h3 }',
+      filename: testFilePath('./no-unused-modules/file-h.js'),
+      errors: [error(`exported declaration 'h1' not used within other modules`)],
+    }),
+    test({
+      options: unusedExportsOptions,
+      code: 'const i1 = 3; function i2() { return 3 }; export { i1, i2 }',
+      filename: testFilePath('./no-unused-modules/file-i.js'),
+      errors: [
+        error(`exported declaration 'i1' not used within other modules`),
+        error(`exported declaration 'i2' not used within other modules`),
+      ],
+    }),
+    test({
+      options: unusedExportsOptions,
+      code: 'export function j() { return 4 }',
+      filename: testFilePath('./no-unused-modules/file-j.js'),
+      errors: [error(`exported declaration 'j' not used within other modules`)],
+    }),
+    test({
+      options: unusedExportsOptions,
+      code: 'export class q { q0() {} }',
+      filename: testFilePath('./no-unused-modules/file-q.js'),
+      errors: [error(`exported declaration 'q' not used within other modules`)],
+    }),
+    test({
+      options: unusedExportsOptions,
+      code: 'const k0 = 5; export { k0 as k }',
+      filename: testFilePath('./no-unused-modules/file-k.js'),
+      errors: [error(`exported declaration 'k' not used within other modules`)],
+    }),
+  ],
+});
+
+describe('dynamic imports', function () {
+  if (semver.satisfies(eslintPkg.version, '< 6')) {
+    beforeEach(function () {
+      this.skip();
+    });
+    return;
+  }
+
+  this.timeout(10e3);
+
+  // test for unused exports with `import()`
+  ruleTester.run('no-unused-modules', rule, {
+    valid: [].concat(
+      testVersion('< 9', () => ({
+        options: unusedExportsOptions,
+        code: `
+            export const a = 10
+            export const b = 20
+            export const c = 30
+            const d = 40
+            export default d
+            `,
+        parser: parsers.BABEL_OLD,
+        filename: testFilePath('./no-unused-modules/exports-for-dynamic-js.js'),
+      })),
+    ),
+    invalid: [].concat(
+      testVersion('< 9', () => ({
+        options: unusedExportsOptions,
+        code: `
+        export const a = 10
+        export const b = 20
+        export const c = 30
+        const d = 40
+        export default d
+        `,
+        parser: parsers.BABEL_OLD,
+        filename: testFilePath('./no-unused-modules/exports-for-dynamic-js-2.js'),
+        errors: [
+          error(`exported declaration 'a' not used within other modules`),
+          error(`exported declaration 'b' not used within other modules`),
+          error(`exported declaration 'c' not used within other modules`),
+          error(`exported declaration 'default' not used within other modules`),
+        ] })),
+    ),
+  });
+  typescriptRuleTester.run('no-unused-modules', rule, {
+    valid: [
+      test({
+        options: unusedExportsTypescriptOptions,
+        code: `
+            export const ts_a = 10
+            export const ts_b = 20
+            export const ts_c = 30
+            const ts_d = 40
+            export default ts_d
+            `,
+        parser: parsers.TS_NEW,
+        filename: testFilePath('./no-unused-modules/typescript/exports-for-dynamic-ts.ts'),
+      }),
+      test({
+        code: `
+        import App from './App';
+      `,
+        filename: testFilePath('./unused-modules-reexport-crash/src/index.tsx'),
+        parser: parsers.TS_NEW,
+        options: [{
+          unusedExports: true,
+          ignoreExports: ['**/magic/**'],
+        }],
+      }),
+    ],
+    invalid: [
+    ],
+  });
+});
+
+// // test for export from
 ruleTester.run('no-unused-modules', rule, {
-  valid: [],
+  valid: [
+    test({
+      options: unusedExportsOptions,
+      code: `export { default } from './file-o'`,
+      filename: testFilePath('./no-unused-modules/file-s.js'),
+    }),
+  ],
   invalid: [
-    test({ options: unusedExportsOptions,
-           code: `export { k } from '${testFilePath('./no-unused-modules/file-k.js')}'`,
-           filename: testFilePath('./no-unused-modules/file-j.js'),
-           errors: [error(`exported declaration 'k' not used within other modules`)]}),
+    test({
+      options: unusedExportsOptions,
+      code: `export { k } from '${testFilePath('./no-unused-modules/file-k.js')}'`,
+      filename: testFilePath('./no-unused-modules/file-j.js'),
+      errors: [error(`exported declaration 'k' not used within other modules`)],
+    }),
   ],
-})
+});
 
 ruleTester.run('no-unused-modules', rule, {
   valid: [
-    test({ options: unusedExportsOptions,
-           code: 'const k0 = 5; export { k0 as k }',
-           filename: testFilePath('./no-unused-modules/file-k.js')}),
+    test({
+      options: unusedExportsOptions,
+      code: 'const k0 = 5; export { k0 as k }',
+      filename: testFilePath('./no-unused-modules/file-k.js'),
+    }),
   ],
   invalid: [],
-})
+});
 
 // test for ignored files
 ruleTester.run('no-unused-modules', rule, {
   valid: [
-    test({ options: unusedExportsOptions,
+    test({
+      options: unusedExportsOptions,
       code: 'export default () => 14',
-      filename: testFilePath('./no-unused-modules/file-ignored-a.js')}),
-    test({ options: unusedExportsOptions,
+      filename: testFilePath('./no-unused-modules/file-ignored-a.js'),
+    }),
+    test({
+      options: unusedExportsOptions,
       code: 'export const b = 2',
-      filename: testFilePath('./no-unused-modules/file-ignored-b.js')}),
-    test({ options: unusedExportsOptions,
+      filename: testFilePath('./no-unused-modules/file-ignored-b.js'),
+    }),
+    test({
+      options: unusedExportsOptions,
       code: 'const c1 = 3; function c2() { return 3 }; export { c1, c2 }',
-      filename: testFilePath('./no-unused-modules/file-ignored-c.js')}),
-    test({ options: unusedExportsOptions,
+      filename: testFilePath('./no-unused-modules/file-ignored-c.js'),
+    }),
+    test({
+      options: unusedExportsOptions,
       code: 'export function d() { return 4 }',
-      filename: testFilePath('./no-unused-modules/file-ignored-d.js')}),
-    test({ options: unusedExportsOptions,
+      filename: testFilePath('./no-unused-modules/file-ignored-d.js'),
+    }),
+    test({
+      options: unusedExportsOptions,
       code: 'const f = 5; export { f as e }',
-      filename: testFilePath('./no-unused-modules/file-ignored-e.js')}),
-    test({ options: unusedExportsOptions,
+      filename: testFilePath('./no-unused-modules/file-ignored-e.js'),
+    }),
+    test({
+      options: unusedExportsOptions,
       code: 'const l0 = 5; const l = 10; export { l0 as l1, l }; export default () => {}',
-      filename: testFilePath('./no-unused-modules/file-ignored-l.js')}),
-    ],
+      filename: testFilePath('./no-unused-modules/file-ignored-l.js'),
+    }),
+  ],
   invalid: [],
-})
+});
 
 // add named import for file with default export
 ruleTester.run('no-unused-modules', rule, {
   valid: [
-    test({ options: unusedExportsOptions,
+    test({
+      options: unusedExportsOptions,
       code: `import { f } from '${testFilePath('./no-unused-modules/file-f.js')}'`,
-      filename: testFilePath('./no-unused-modules/file-0.js')}),
-    ],
+      filename: testFilePath('./no-unused-modules/file-0.js'),
+    }),
+  ],
   invalid: [
-    test({ options: unusedExportsOptions,
-           code: 'export default () => 15',
-           filename: testFilePath('./no-unused-modules/file-f.js'),
-           errors: [error(`exported declaration 'default' not used within other modules`)]}),
-    ],
-})
+    test({
+      options: unusedExportsOptions,
+      code: 'export default () => 15',
+      filename: testFilePath('./no-unused-modules/file-f.js'),
+      errors: [error(`exported declaration 'default' not used within other modules`)],
+    }),
+  ],
+});
 
 // add default import for file with default export
 ruleTester.run('no-unused-modules', rule, {
   valid: [
-    test({ options: unusedExportsOptions,
-           code: `import f from '${testFilePath('./no-unused-modules/file-f.js')}'`,
-           filename: testFilePath('./no-unused-modules/file-0.js')}),
-    test({ options: unusedExportsOptions,
-           code: 'export default () => 16',
-           filename: testFilePath('./no-unused-modules/file-f.js')}),
-    ],
+    test({
+      options: unusedExportsOptions,
+      code: `import f from '${testFilePath('./no-unused-modules/file-f.js')}'`,
+      filename: testFilePath('./no-unused-modules/file-0.js'),
+    }),
+    test({
+      options: unusedExportsOptions,
+      code: 'export default () => 16',
+      filename: testFilePath('./no-unused-modules/file-f.js'),
+    }),
+  ],
   invalid: [],
-})
+});
 
 // add default import for file with named export
 ruleTester.run('no-unused-modules', rule, {
   valid: [
-    test({ options: unusedExportsOptions,
-           code: `import g from '${testFilePath('./no-unused-modules/file-g.js')}';import {h} from '${testFilePath('./no-unused-modules/file-gg.js')}'`,
-           filename: testFilePath('./no-unused-modules/file-0.js')}),
-    ],
+    test({
+      options: unusedExportsOptions,
+      code: `import g from '${testFilePath('./no-unused-modules/file-g.js')}';import {h} from '${testFilePath('./no-unused-modules/file-gg.js')}'`,
+      filename: testFilePath('./no-unused-modules/file-0.js'),
+    }),
+  ],
   invalid: [
-    test({ options: unusedExportsOptions,
-            code: 'export const g = 2',
-            filename: testFilePath('./no-unused-modules/file-g.js'),
-            errors: [error(`exported declaration 'g' not used within other modules`)]})],
-})
+    test({
+      options: unusedExportsOptions,
+      code: 'export const g = 2',
+      filename: testFilePath('./no-unused-modules/file-g.js'),
+      errors: [error(`exported declaration 'g' not used within other modules`)],
+    })],
+});
 
 // add named import for file with named export
 ruleTester.run('no-unused-modules', rule, {
   valid: [
-    test({ options: unusedExportsOptions,
-           code: `import { g } from '${testFilePath('./no-unused-modules/file-g.js')}'; import eslint from 'eslint'`,
-           filename: testFilePath('./no-unused-modules/file-0.js')}),
-    test({ options: unusedExportsOptions,
-            code: 'export const g = 2',
-            filename: testFilePath('./no-unused-modules/file-g.js')}),
-    ],
+    test({
+      options: unusedExportsOptions,
+      code: `import { g } from '${testFilePath('./no-unused-modules/file-g.js')}'; import eslint from 'eslint'`,
+      filename: testFilePath('./no-unused-modules/file-0.js'),
+    }),
+    test({
+      options: unusedExportsOptions,
+      code: 'export const g = 2',
+      filename: testFilePath('./no-unused-modules/file-g.js'),
+    }),
+  ],
   invalid: [],
-})
+});
 
 // add different named import for file with named export
 ruleTester.run('no-unused-modules', rule, {
   valid: [
-    test({ options: unusedExportsOptions,
-           code: `import { c } from '${testFilePath('./no-unused-modules/file-b.js')}'`,
-           filename: testFilePath('./no-unused-modules/file-0.js')}),
+    test({
+      options: unusedExportsOptions,
+      code: `import { c } from '${testFilePath('./no-unused-modules/file-b.js')}'`,
+      filename: testFilePath('./no-unused-modules/file-0.js'),
+    }),
   ],
   invalid: [
-    test({ options: unusedExportsOptions,
-           code: 'export const b = 2',
-           filename: testFilePath('./no-unused-modules/file-b.js'),
-           errors: [error(`exported declaration 'b' not used within other modules`)]}),
+    test({
+      options: unusedExportsOptions,
+      code: 'export const b = 2',
+      filename: testFilePath('./no-unused-modules/file-b.js'),
+      errors: [error(`exported declaration 'b' not used within other modules`)],
+    }),
   ],
-})
+});
 
 // add renamed named import for file with named export
 ruleTester.run('no-unused-modules', rule, {
   valid: [
-    test({ options: unusedExportsOptions,
-           code: `import { g as g1 } from '${testFilePath('./no-unused-modules/file-g.js')}'; import eslint from 'eslint'`,
-           filename: testFilePath('./no-unused-modules/file-0.js')}),
-    test({ options: unusedExportsOptions,
-            code: 'export const g = 2',
-            filename: testFilePath('./no-unused-modules/file-g.js')}),
-    ],
+    test({
+      options: unusedExportsOptions,
+      code: `import { g as g1 } from '${testFilePath('./no-unused-modules/file-g.js')}'; import eslint from 'eslint'`,
+      filename: testFilePath('./no-unused-modules/file-0.js'),
+    }),
+    test({
+      options: unusedExportsOptions,
+      code: 'export const g = 2',
+      filename: testFilePath('./no-unused-modules/file-g.js'),
+    }),
+  ],
   invalid: [],
-})
+});
 
 // add different renamed named import for file with named export
 ruleTester.run('no-unused-modules', rule, {
   valid: [
-    test({ options: unusedExportsOptions,
-           code: `import { g1 as g } from '${testFilePath('./no-unused-modules/file-g.js')}'`,
-           filename: testFilePath('./no-unused-modules/file-0.js')}),
+    test({
+      options: unusedExportsOptions,
+      code: `import { g1 as g } from '${testFilePath('./no-unused-modules/file-g.js')}'`,
+      filename: testFilePath('./no-unused-modules/file-0.js'),
+    }),
   ],
   invalid: [
-    test({ options: unusedExportsOptions,
-           code: 'export const g = 2',
-           filename: testFilePath('./no-unused-modules/file-g.js'),
-           errors: [error(`exported declaration 'g' not used within other modules`)]}),
+    test({
+      options: unusedExportsOptions,
+      code: 'export const g = 2',
+      filename: testFilePath('./no-unused-modules/file-g.js'),
+      errors: [error(`exported declaration 'g' not used within other modules`)],
+    }),
   ],
-})
+});
 
 // remove default import for file with default export
 ruleTester.run('no-unused-modules', rule, {
   valid: [
-    test({ options: unusedExportsOptions,
-           code: `import { a1, a2 } from '${testFilePath('./no-unused-modules/file-a.js')}'`,
-           filename: testFilePath('./no-unused-modules/file-0.js')}),
+    test({
+      options: unusedExportsOptions,
+      code: `import { a1, a2 } from '${testFilePath('./no-unused-modules/file-a.js')}'`,
+      filename: testFilePath('./no-unused-modules/file-0.js'),
+    }),
   ],
   invalid: [
-    test({ options: unusedExportsOptions,
-           code: 'export default () => 17',
-           filename: testFilePath('./no-unused-modules/file-a.js'),
-           errors: [error(`exported declaration 'default' not used within other modules`)]}),
+    test({
+      options: unusedExportsOptions,
+      code: 'export default () => 17',
+      filename: testFilePath('./no-unused-modules/file-a.js'),
+      errors: [error(`exported declaration 'default' not used within other modules`)],
+    }),
   ],
-})
+});
 
 // add namespace import for file with unused exports
 ruleTester.run('no-unused-modules', rule, {
   valid: [],
   invalid: [
-    test({ options: unusedExportsOptions,
-           code: 'const m0 = 5; const m = 10; export { m0 as m1, m }; export default () => {}',
-           filename: testFilePath('./no-unused-modules/file-m.js'),
-           errors: [
-             error(`exported declaration 'm1' not used within other modules`),
-             error(`exported declaration 'm' not used within other modules`),
-             error(`exported declaration 'default' not used within other modules`),
-          ]}),
-  ],
-})
+    test({
+      options: unusedExportsOptions,
+      code: 'const m0 = 5; const m = 10; export { m0 as m1, m }; export default () => {}',
+      filename: testFilePath('./no-unused-modules/file-m.js'),
+      errors: [
+        error(`exported declaration 'm1' not used within other modules`),
+        error(`exported declaration 'm' not used within other modules`),
+        error(`exported declaration 'default' not used within other modules`),
+      ],
+    }),
+  ],
+});
 ruleTester.run('no-unused-modules', rule, {
   valid: [
-    test({ options: unusedExportsOptions,
-           code: `import * as m from '${testFilePath('./no-unused-modules/file-m.js')}'; import unknown from 'unknown-module'`,
-           filename: testFilePath('./no-unused-modules/file-0.js')}),
-    test({ options: unusedExportsOptions,
-           code: 'const m0 = 5; const m = 10; export { m0 as m1, m }; export default () => {}',
-           filename: testFilePath('./no-unused-modules/file-m.js')}),
+    test({
+      options: unusedExportsOptions,
+      code: `import * as m from '${testFilePath('./no-unused-modules/file-m.js')}'; import unknown from 'unknown-module'`,
+      filename: testFilePath('./no-unused-modules/file-0.js'),
+    }),
+    test({
+      options: unusedExportsOptions,
+      code: 'const m0 = 5; const m = 10; export { m0 as m1, m }; export default () => {}',
+      filename: testFilePath('./no-unused-modules/file-m.js'),
+    }),
   ],
   invalid: [],
-})
+});
 
 // remove all exports
 ruleTester.run('no-unused-modules', rule, {
   valid: [
-    test({ options: unusedExportsOptions,
-           code: `/* import * as m from '${testFilePath('./no-unused-modules/file-m.js')}' */`,
-           filename: testFilePath('./no-unused-modules/file-0.js')}),
+    test({
+      options: unusedExportsOptions,
+      code: `/* import * as m from '${testFilePath('./no-unused-modules/file-m.js')}' */`,
+      filename: testFilePath('./no-unused-modules/file-0.js'),
+    }),
   ],
   invalid: [
-    test({ options: unusedExportsOptions,
+    test({
+      options: unusedExportsOptions,
       code: 'const m0 = 5; const m = 10; export { m0 as m1, m }; export default () => {}',
       filename: testFilePath('./no-unused-modules/file-m.js'),
       errors: [
         error(`exported declaration 'm1' not used within other modules`),
         error(`exported declaration 'm' not used within other modules`),
         error(`exported declaration 'default' not used within other modules`),
-     ]}),
+      ],
+    }),
   ],
-})
+});
 
 ruleTester.run('no-unused-modules', rule, {
   valid: [
-    test({ options: unusedExportsOptions,
-           code: `export * from '${testFilePath('./no-unused-modules/file-m.js')}';`,
-           filename: testFilePath('./no-unused-modules/file-0.js')}),
+    test({
+      options: unusedExportsOptions,
+      code: `export * from '${testFilePath('./no-unused-modules/file-m.js')}';`,
+      filename: testFilePath('./no-unused-modules/file-0.js'),
+    }),
   ],
   invalid: [],
-})
+});
 ruleTester.run('no-unused-modules', rule, {
   valid: [],
   invalid: [
-    test({ options: unusedExportsOptions,
-           code: 'const m0 = 5; const m = 10; export { m0 as m1, m }; export default () => {}',
-           filename: testFilePath('./no-unused-modules/file-m.js'),
-           errors: [error(`exported declaration 'default' not used within other modules`)]}),
+    test({
+      options: unusedExportsOptions,
+      code: 'const m0 = 5; const m = 10; export { m0 as m1, m }; export default () => {}',
+      filename: testFilePath('./no-unused-modules/file-m.js'),
+      errors: [error(`exported declaration 'default' not used within other modules`)],
+    }),
   ],
-})
+});
 
 ruleTester.run('no-unused-modules', rule, {
   valid: [],
   invalid: [
-    test({ options: unusedExportsOptions,
-           code: `export { m1, m} from '${testFilePath('./no-unused-modules/file-m.js')}';`,
-           filename: testFilePath('./no-unused-modules/file-0.js'),
-           errors: [
-             error(`exported declaration 'm1' not used within other modules`),
-             error(`exported declaration 'm' not used within other modules`),
-           ]}),
-    test({ options: unusedExportsOptions,
-           code: 'const m0 = 5; const m = 10; export { m0 as m1, m }; export default () => {}',
-           filename: testFilePath('./no-unused-modules/file-m.js'),
-           errors: [error(`exported declaration 'default' not used within other modules`)]}),
-  ],
-})
+    test({
+      options: unusedExportsOptions,
+      code: `export { m1, m} from '${testFilePath('./no-unused-modules/file-m.js')}';`,
+      filename: testFilePath('./no-unused-modules/file-0.js'),
+      errors: [
+        error(`exported declaration 'm1' not used within other modules`),
+        error(`exported declaration 'm' not used within other modules`),
+      ],
+    }),
+    test({
+      options: unusedExportsOptions,
+      code: 'const m0 = 5; const m = 10; export { m0 as m1, m }; export default () => {}',
+      filename: testFilePath('./no-unused-modules/file-m.js'),
+      errors: [error(`exported declaration 'default' not used within other modules`)],
+    }),
+  ],
+});
 
 ruleTester.run('no-unused-modules', rule, {
   valid: [
-    // test({ options: unusedExportsOptions,
-    //        code: `export { default, m1 } from '${testFilePath('./no-unused-modules/file-m.js')}';`,
-    //        filename: testFilePath('./no-unused-modules/file-0.js')}),
+    /* TODO:
+    test({
+      options: unusedExportsOptions,
+      code: `export { default, m1 } from '${testFilePath('./no-unused-modules/file-m.js')}';`,
+      filename: testFilePath('./no-unused-modules/file-0.js')
+    }),
+    */
   ],
   invalid: [
-    test({ options: unusedExportsOptions,
-           code: `export { default, m1 } from '${testFilePath('./no-unused-modules/file-m.js')}';`,
-           filename: testFilePath('./no-unused-modules/file-0.js'),
-           errors: [
-             error(`exported declaration 'default' not used within other modules`),
-             error(`exported declaration 'm1' not used within other modules`),
-           ]}),
-    test({ options: unusedExportsOptions,
-           code: 'const m0 = 5; const m = 10; export { m0 as m1, m }; export default () => {}',
-           filename: testFilePath('./no-unused-modules/file-m.js'),
-           errors: [error(`exported declaration 'm' not used within other modules`)]}),
-  ],
-})
-
-describe('test behaviour for new file', () => {
+    test({
+      options: unusedExportsOptions,
+      code: `export { default, m1 } from '${testFilePath('./no-unused-modules/file-m.js')}';`,
+      filename: testFilePath('./no-unused-modules/file-0.js'),
+      errors: [
+        error(`exported declaration 'default' not used within other modules`),
+        error(`exported declaration 'm1' not used within other modules`),
+      ],
+    }),
+    test({
+      options: unusedExportsOptions,
+      code: 'const m0 = 5; const m = 10; export { m0 as m1, m }; export default () => {}',
+      filename: testFilePath('./no-unused-modules/file-m.js'),
+      errors: [error(`exported declaration 'm' not used within other modules`)],
+    }),
+  ],
+});
+
+// Test that import and export in the same file both counts as usage
+ruleTester.run('no-unused-modules', rule, {
+  valid: [
+    test({
+      options: unusedExportsOptions,
+      code: `export const a = 5;export const b = 't1'`,
+      filename: testFilePath('./no-unused-modules/import-export-1.js'),
+    }),
+  ],
+  invalid: [],
+});
+
+describe('renameDefault', () => {
+  ruleTester.run('no-unused-modules', rule, {
+    valid: [
+      test({
+        options: unusedExportsOptions,
+        code: 'export { default as Component } from "./Component"',
+        filename: testFilePath('./no-unused-modules/renameDefault/components.js'),
+      }),
+      test({
+        options: unusedExportsOptions,
+        code: 'export default function Component() {}',
+        filename: testFilePath('./no-unused-modules/renameDefault/Component.js'),
+      }),
+    ],
+    invalid: [],
+  });
+  ruleTester.run('no-unused-modules', rule, {
+    valid: [
+      test({
+        options: unusedExportsOptions,
+        code: 'export { default as ComponentA } from "./ComponentA";export { default as ComponentB } from "./ComponentB";',
+        filename: testFilePath('./no-unused-modules/renameDefault-2/components.js'),
+      }),
+      test({
+        options: unusedExportsOptions,
+        code: 'export default function ComponentA() {};',
+        filename: testFilePath('./no-unused-modules/renameDefault-2/ComponentA.js'),
+      }),
+    ],
+    invalid: [],
+  });
+});
+
+describe('test behavior for new file', () => {
   before(() => {
-    fs.writeFileSync(testFilePath('./no-unused-modules/file-added-0.js'), '', {encoding: 'utf8'})
-  })
+    fs.writeFileSync(testFilePath('./no-unused-modules/file-added-0.js'), '', { encoding: 'utf8', flag: 'w' });
+  });
 
   // add import in newly created file
   ruleTester.run('no-unused-modules', rule, {
     valid: [
-      test({ options: unusedExportsOptions,
-             code: `import * as m from '${testFilePath('./no-unused-modules/file-m.js')}'`,
-             filename: testFilePath('./no-unused-modules/file-added-0.js')}),
-      test({ options: unusedExportsOptions,
-             code: 'const m0 = 5; const m = 10; export { m0 as m1, m }; export default () => {}',
-             filename: testFilePath('./no-unused-modules/file-m.js')}),
+      test({
+        options: unusedExportsOptions,
+        code: `import * as m from '${testFilePath('./no-unused-modules/file-m.js')}'`,
+        filename: testFilePath('./no-unused-modules/file-added-0.js'),
+      }),
+      test({
+        options: unusedExportsOptions,
+        code: 'const m0 = 5; const m = 10; export { m0 as m1, m }; export default () => {}',
+        filename: testFilePath('./no-unused-modules/file-m.js'),
+      }),
     ],
     invalid: [],
-  })
+  });
 
   // add export for newly created file
   ruleTester.run('no-unused-modules', rule, {
     valid: [],
     invalid: [
-      test({ options: unusedExportsOptions,
-             code: `export default () => {2}`,
-             filename: testFilePath('./no-unused-modules/file-added-0.js'),
-             errors: [error(`exported declaration 'default' not used within other modules`)]}),
-      ],
-  })
+      test({
+        options: unusedExportsOptions,
+        code: `export default () => {2}`,
+        filename: testFilePath('./no-unused-modules/file-added-0.js'),
+        errors: [error(`exported declaration 'default' not used within other modules`)],
+      }),
+    ],
+  });
 
   ruleTester.run('no-unused-modules', rule, {
     valid: [
-      test({ options: unusedExportsOptions,
-             code: `import def from '${testFilePath('./no-unused-modules/file-added-0.js')}'`,
-             filename: testFilePath('./no-unused-modules/file-0.js')}),
-      test({ options: unusedExportsOptions,
-             code: `export default () => {}`,
-             filename: testFilePath('./no-unused-modules/file-added-0.js')}),
+      test({
+        options: unusedExportsOptions,
+        code: `import def from '${testFilePath('./no-unused-modules/file-added-0.js')}'`,
+        filename: testFilePath('./no-unused-modules/file-0.js'),
+      }),
+      test({
+        options: unusedExportsOptions,
+        code: `export default () => {}`,
+        filename: testFilePath('./no-unused-modules/file-added-0.js'),
+      }),
     ],
     invalid: [],
-  })
+  });
 
   // export * only considers named imports. default imports still need to be reported
   ruleTester.run('no-unused-modules', rule, {
     valid: [
-      test({ options: unusedExportsOptions,
-             code: `export * from '${testFilePath('./no-unused-modules/file-added-0.js')}'`,
-             filename: testFilePath('./no-unused-modules/file-0.js')}),
+      test({
+        options: unusedExportsOptions,
+        code: `export * from '${testFilePath('./no-unused-modules/file-added-0.js')}'`,
+        filename: testFilePath('./no-unused-modules/file-0.js'),
+      }),
+      // Test export * from 'external-compiled-library'
+      test({
+        options: unusedExportsOptions,
+        code: `export * from 'external-compiled-library'`,
+        filename: testFilePath('./no-unused-modules/file-r.js'),
+      }),
     ],
     invalid: [
-      test({ options: unusedExportsOptions,
-             code: `export const z = 'z';export default () => {}`,
-             filename: testFilePath('./no-unused-modules/file-added-0.js'),
-             errors: [error(`exported declaration 'default' not used within other modules`)]}),
+      test({
+        options: unusedExportsOptions,
+        code: `export const z = 'z';export default () => {}`,
+        filename: testFilePath('./no-unused-modules/file-added-0.js'),
+        errors: [error(`exported declaration 'default' not used within other modules`)],
+      }),
     ],
-  })
+  });
   ruleTester.run('no-unused-modules', rule, {
     valid: [
-      test({ options: unusedExportsOptions,
-             code: `export const a = 2`,
-             filename: testFilePath('./no-unused-modules/file-added-0.js')}),
+      test({
+        options: unusedExportsOptions,
+        code: `export const a = 2`,
+        filename: testFilePath('./no-unused-modules/file-added-0.js'),
+      }),
     ],
     invalid: [],
-  })
+  });
 
   // remove export *. all exports need to be reported
   ruleTester.run('no-unused-modules', rule, {
     valid: [],
     invalid: [
-      test({ options: unusedExportsOptions,
-             code: `export { a } from '${testFilePath('./no-unused-modules/file-added-0.js')}'`,
-             filename: testFilePath('./no-unused-modules/file-0.js'),
-             errors: [error(`exported declaration 'a' not used within other modules`)]}),
-      test({ options: unusedExportsOptions,
-             code: `export const z = 'z';export default () => {}`,
-             filename: testFilePath('./no-unused-modules/file-added-0.js'),
-             errors: [
-               error(`exported declaration 'z' not used within other modules`),
-               error(`exported declaration 'default' not used within other modules`),
-            ]}),
+      test({
+        options: unusedExportsOptions,
+        code: `export { a } from '${testFilePath('./no-unused-modules/file-added-0.js')}'`,
+        filename: testFilePath('./no-unused-modules/file-0.js'),
+        errors: [error(`exported declaration 'a' not used within other modules`)],
+      }),
+      test({
+        options: unusedExportsOptions,
+        code: `export const z = 'z';export default () => {}`,
+        filename: testFilePath('./no-unused-modules/file-added-0.js'),
+        errors: [
+          error(`exported declaration 'z' not used within other modules`),
+          error(`exported declaration 'default' not used within other modules`),
+        ],
+      }),
     ],
-  })
-
+  });
 
-  describe('test behaviour for new file', () => {
+  describe('test behavior for new file', () => {
     before(() => {
-      fs.writeFileSync(testFilePath('./no-unused-modules/file-added-1.js'), '', {encoding: 'utf8'})
-    })
+      fs.writeFileSync(testFilePath('./no-unused-modules/file-added-1.js'), '', { encoding: 'utf8', flag: 'w' });
+    });
     ruleTester.run('no-unused-modules', rule, {
       valid: [
-        test({ options: unusedExportsOptions,
-               code: `export * from '${testFilePath('./no-unused-modules/file-added-1.js')}'`,
-               filename: testFilePath('./no-unused-modules/file-0.js')}),
+        test({
+          options: unusedExportsOptions,
+          code: `export * from '${testFilePath('./no-unused-modules/file-added-1.js')}'`,
+          filename: testFilePath('./no-unused-modules/file-0.js'),
+        }),
       ],
       invalid: [
-        test({ options: unusedExportsOptions,
-               code: `export const z = 'z';export default () => {}`,
-               filename: testFilePath('./no-unused-modules/file-added-1.js'),
-               errors: [error(`exported declaration 'default' not used within other modules`)]}),
+        test({
+          options: unusedExportsOptions,
+          code: `export const z = 'z';export default () => {}`,
+          filename: testFilePath('./no-unused-modules/file-added-1.js'),
+          errors: [error(`exported declaration 'default' not used within other modules`)],
+        }),
       ],
-    })
+    });
     after(() => {
       if (fs.existsSync(testFilePath('./no-unused-modules/file-added-1.js'))) {
-        fs.unlinkSync(testFilePath('./no-unused-modules/file-added-1.js'))
+        fs.unlinkSync(testFilePath('./no-unused-modules/file-added-1.js'));
       }
-    })
-  })
+    });
+  });
 
   after(() => {
     if (fs.existsSync(testFilePath('./no-unused-modules/file-added-0.js'))) {
-      fs.unlinkSync(testFilePath('./no-unused-modules/file-added-0.js'))
+      fs.unlinkSync(testFilePath('./no-unused-modules/file-added-0.js'));
     }
-  })
-})
+  });
+});
 
-describe('test behaviour for new file', () => {
+describe('test behavior for new file', () => {
   before(() => {
-    fs.writeFileSync(testFilePath('./no-unused-modules/file-added-2.js'), '', {encoding: 'utf8'})
-  })
+    fs.writeFileSync(testFilePath('./no-unused-modules/file-added-2.js'), '', { encoding: 'utf8', flag: 'w' });
+  });
   ruleTester.run('no-unused-modules', rule, {
     valid: [
-      test({ options: unusedExportsOptions,
-             code: `import added from '${testFilePath('./no-unused-modules/file-added-2.js')}'`,
-             filename: testFilePath('./no-unused-modules/file-added-1.js')}),
-      test({ options: unusedExportsOptions,
-             code: `export default () => {}`,
-             filename: testFilePath('./no-unused-modules/file-added-2.js')}),
+      test({
+        options: unusedExportsOptions,
+        code: `import added from '${testFilePath('./no-unused-modules/file-added-2.js')}'`,
+        filename: testFilePath('./no-unused-modules/file-added-1.js'),
+      }),
+      test({
+        options: unusedExportsOptions,
+        code: `export default () => {}`,
+        filename: testFilePath('./no-unused-modules/file-added-2.js'),
+      }),
     ],
     invalid: [],
-  })
+  });
   after(() => {
     if (fs.existsSync(testFilePath('./no-unused-modules/file-added-2.js'))) {
-      fs.unlinkSync(testFilePath('./no-unused-modules/file-added-2.js'))
+      fs.unlinkSync(testFilePath('./no-unused-modules/file-added-2.js'));
     }
-  })
-})
+  });
+});
 
-describe('test behaviour for new file', () => {
+describe('test behavior for new file', () => {
   before(() => {
-    fs.writeFileSync(testFilePath('./no-unused-modules/file-added-3.js'), '', {encoding: 'utf8'})
-  })
+    fs.writeFileSync(testFilePath('./no-unused-modules/file-added-3.js'), '', { encoding: 'utf8', flag: 'w' });
+  });
   ruleTester.run('no-unused-modules', rule, {
     valid: [
-      test({ options: unusedExportsOptions,
-             code: `import { added } from '${testFilePath('./no-unused-modules/file-added-3.js')}'`,
-             filename: testFilePath('./no-unused-modules/file-added-1.js')}),
-      test({ options: unusedExportsOptions,
-             code: `export const added = () => {}`,
-             filename: testFilePath('./no-unused-modules/file-added-3.js')}),
+      test({
+        options: unusedExportsOptions,
+        code: `import { added } from '${testFilePath('./no-unused-modules/file-added-3.js')}'`,
+        filename: testFilePath('./no-unused-modules/file-added-1.js'),
+      }),
+      test({
+        options: unusedExportsOptions,
+        code: `export const added = () => {}`,
+        filename: testFilePath('./no-unused-modules/file-added-3.js'),
+      }),
     ],
     invalid: [],
-  })
+  });
   after(() => {
     if (fs.existsSync(testFilePath('./no-unused-modules/file-added-3.js'))) {
-      fs.unlinkSync(testFilePath('./no-unused-modules/file-added-3.js'))
+      fs.unlinkSync(testFilePath('./no-unused-modules/file-added-3.js'));
     }
-  })
-})
+  });
+});
 
-describe('test behaviour for new file', () => {
+describe('test behavior for destructured exports', () => {
+  ruleTester.run('no-unused-modules', rule, {
+    valid: [
+      test({
+        options: unusedExportsOptions,
+        code: `import { destructured } from '${testFilePath('./no-unused-modules/file-destructured-1.js')}'`,
+        filename: testFilePath('./no-unused-modules/file-destructured-2.js'),
+      }),
+      test({
+        options: unusedExportsOptions,
+        code: `export const { destructured } = {};`,
+        filename: testFilePath('./no-unused-modules/file-destructured-1.js'),
+      }),
+    ],
+    invalid: [
+      test({
+        options: unusedExportsOptions,
+        code: `export const { destructured2 } = {};`,
+        filename: testFilePath('./no-unused-modules/file-destructured-1.js'),
+        errors: [`exported declaration 'destructured2' not used within other modules`],
+      }),
+    ],
+  });
+});
+
+describe('test behavior for new file', () => {
   before(() => {
-    fs.writeFileSync(testFilePath('./no-unused-modules/file-added-4.js.js'), '', {encoding: 'utf8'})
-  })
+    fs.writeFileSync(testFilePath('./no-unused-modules/file-added-4.js.js'), '', { encoding: 'utf8', flag: 'w' });
+  });
   ruleTester.run('no-unused-modules', rule, {
     valid: [
-      test({ options: unusedExportsOptions,
-             code: `import * as added from '${testFilePath('./no-unused-modules/file-added-4.js.js')}'`,
-             filename: testFilePath('./no-unused-modules/file-added-1.js')}),
-      test({ options: unusedExportsOptions,
-             code: `export const added = () => {}; export default () => {}`,
-             filename: testFilePath('./no-unused-modules/file-added-4.js.js')}),
+      test({
+        options: unusedExportsOptions,
+        code: `import * as added from '${testFilePath('./no-unused-modules/file-added-4.js.js')}'`,
+        filename: testFilePath('./no-unused-modules/file-added-1.js'),
+      }),
+      test({
+        options: unusedExportsOptions,
+        code: `export const added = () => {}; export default () => {}`,
+        filename: testFilePath('./no-unused-modules/file-added-4.js.js'),
+      }),
     ],
     invalid: [],
-  })
+  });
   after(() => {
     if (fs.existsSync(testFilePath('./no-unused-modules/file-added-4.js.js'))) {
-      fs.unlinkSync(testFilePath('./no-unused-modules/file-added-4.js.js'))
+      fs.unlinkSync(testFilePath('./no-unused-modules/file-added-4.js.js'));
     }
-  })
-})
+  });
+});
 
 describe('do not report missing export for ignored file', () => {
   ruleTester.run('no-unused-modules', rule, {
     valid: [
-      test({ options: [{
-               src: [testFilePath('./no-unused-modules/**/*.js')],
-               ignoreExports: [testFilePath('./no-unused-modules/*ignored*.js')],
-               missingExports: true,
-              }],
-             code: 'export const test = true',
-             filename: testFilePath('./no-unused-modules/file-ignored-a.js')}),
+      test({
+        options: [{
+          src: [testFilePath('./no-unused-modules/**/*.js')],
+          ignoreExports: [testFilePath('./no-unused-modules/*ignored*.js')],
+          missingExports: true,
+        }],
+        code: 'export const test = true',
+        filename: testFilePath('./no-unused-modules/file-ignored-a.js'),
+      }),
     ],
     invalid: [],
-  })
-})
+  });
+});
 
 // lint file not available in `src`
 ruleTester.run('no-unused-modules', rule, {
   valid: [
-    test({ options: unusedExportsOptions,
-            code: `export const jsxFoo = 'foo'; export const jsxBar = 'bar'`,
-            filename: testFilePath('../jsx/named.jsx')}),
+    test({
+      options: unusedExportsOptions,
+      code: `export const jsxFoo = 'foo'; export const jsxBar = 'bar'`,
+      filename: testFilePath('../jsx/named.jsx'),
+    }),
   ],
   invalid: [],
-})
+});
 
 describe('do not report unused export for files mentioned in package.json', () => {
   ruleTester.run('no-unused-modules', rule, {
     valid: [
-      test({ options: unusedExportsOptions,
-             code: 'export const bin = "bin"',
-             filename: testFilePath('./no-unused-modules/bin.js')}),
-      test({ options: unusedExportsOptions,
-             code: 'export const binObject = "binObject"',
-             filename: testFilePath('./no-unused-modules/binObject/index.js')}),
-      test({ options: unusedExportsOptions,
-             code: 'export const browser = "browser"',
-             filename: testFilePath('./no-unused-modules/browser.js')}),
-      test({ options: unusedExportsOptions,
-             code: 'export const browserObject = "browserObject"',
-             filename: testFilePath('./no-unused-modules/browserObject/index.js')}),
-      test({ options: unusedExportsOptions,
-             code: 'export const main = "main"',
-             filename: testFilePath('./no-unused-modules/main/index.js')}),
+      test({
+        options: unusedExportsOptions,
+        code: 'export const bin = "bin"',
+        filename: testFilePath('./no-unused-modules/bin.js'),
+      }),
+      test({
+        options: unusedExportsOptions,
+        code: 'export const binObject = "binObject"',
+        filename: testFilePath('./no-unused-modules/binObject/index.js'),
+      }),
+      test({
+        options: unusedExportsOptions,
+        code: 'export const browser = "browser"',
+        filename: testFilePath('./no-unused-modules/browser.js'),
+      }),
+      test({
+        options: unusedExportsOptions,
+        code: 'export const browserObject = "browserObject"',
+        filename: testFilePath('./no-unused-modules/browserObject/index.js'),
+      }),
+      test({
+        options: unusedExportsOptions,
+        code: 'export const main = "main"',
+        filename: testFilePath('./no-unused-modules/main/index.js'),
+      }),
     ],
     invalid: [
-      test({ options: unusedExportsOptions,
-             code: 'export const privatePkg = "privatePkg"',
-             filename: testFilePath('./no-unused-modules/privatePkg/index.js'),
-             errors: [error(`exported declaration 'privatePkg' not used within other modules`)]}),
+      test({
+        options: unusedExportsOptions,
+        code: 'export const privatePkg = "privatePkg"',
+        filename: testFilePath('./no-unused-modules/privatePkg/index.js'),
+        errors: [error(`exported declaration 'privatePkg' not used within other modules`)],
+      }),
+    ],
+  });
+});
+
+describe('Avoid errors if re-export all from umd compiled library', () => {
+  ruleTester.run('no-unused-modules', rule, {
+    valid: [
+      test({
+        options: unusedExportsOptions,
+        code: `export * from '${testFilePath('./no-unused-modules/bin.js')}'`,
+        filename: testFilePath('./no-unused-modules/main/index.js'),
+      }),
     ],
-  })
-})
+    invalid: [],
+  });
+});
+
+context('TypeScript', function () {
+  getTSParsers().forEach((parser) => {
+    typescriptRuleTester.run('no-unused-modules', rule, {
+      valid: [].concat(
+        test({
+          options: unusedExportsTypescriptOptions,
+          code: `
+          import {b} from './file-ts-b';
+          import {c} from './file-ts-c';
+          import {d} from './file-ts-d';
+          import {e} from './file-ts-e';
+
+          const a = b + 1 + e.f;
+          const a2: c = {};
+          const a3: d = {};
+          `,
+          parser,
+          filename: testFilePath('./no-unused-modules/typescript/file-ts-a.ts'),
+        }),
+        test({
+          options: unusedExportsTypescriptOptions,
+          code: `export const b = 2;`,
+          parser,
+          filename: testFilePath('./no-unused-modules/typescript/file-ts-b.ts'),
+        }),
+        test({
+          options: unusedExportsTypescriptOptions,
+          code: `export interface c {};`,
+          parser,
+          filename: testFilePath('./no-unused-modules/typescript/file-ts-c.ts'),
+        }),
+        test({
+          options: unusedExportsTypescriptOptions,
+          code: `export type d = {};`,
+          parser,
+          filename: testFilePath('./no-unused-modules/typescript/file-ts-d.ts'),
+        }),
+        test({
+          options: unusedExportsTypescriptOptions,
+          code: `export enum e { f };`,
+          parser,
+          filename: testFilePath('./no-unused-modules/typescript/file-ts-e.ts'),
+        }),
+        test({
+          options: unusedExportsTypescriptOptions,
+          code: `
+          import type {b} from './file-ts-b-used-as-type';
+          import type {c} from './file-ts-c-used-as-type';
+          import type {d} from './file-ts-d-used-as-type';
+          import type {e} from './file-ts-e-used-as-type';
+
+          const a: typeof b = 2;
+          const a2: c = {};
+          const a3: d = {};
+          const a4: typeof e = undefined;
+          `,
+          parser,
+          filename: testFilePath('./no-unused-modules/typescript/file-ts-a-import-type.ts'),
+        }),
+        test({
+          options: unusedExportsTypescriptOptions,
+          code: `export const b = 2;`,
+          parser,
+          filename: testFilePath('./no-unused-modules/typescript/file-ts-b-used-as-type.ts'),
+        }),
+        test({
+          options: unusedExportsTypescriptOptions,
+          code: `export interface c {};`,
+          parser,
+          filename: testFilePath('./no-unused-modules/typescript/file-ts-c-used-as-type.ts'),
+        }),
+        test({
+          options: unusedExportsTypescriptOptions,
+          code: `export type d = {};`,
+          parser,
+          filename: testFilePath('./no-unused-modules/typescript/file-ts-d-used-as-type.ts'),
+        }),
+        test({
+          options: unusedExportsTypescriptOptions,
+          code: `export enum e { f };`,
+          parser,
+          filename: testFilePath('./no-unused-modules/typescript/file-ts-e-used-as-type.ts'),
+        }),
+        // Should also be valid when the exporting files are linted before the importing ones
+        isESLint4TODO ? [] : test({
+          options: unusedExportsTypescriptOptions,
+          code: `export interface g {}`,
+          parser,
+          filename: testFilePath('./no-unused-modules/typescript/file-ts-g.ts'),
+        }),
+        test({
+          options: unusedExportsTypescriptOptions,
+          code: `import {g} from './file-ts-g';`,
+          parser,
+          filename: testFilePath('./no-unused-modules/typescript/file-ts-f.ts'),
+        }),
+        isESLint4TODO ? [] : test({
+          options: unusedExportsTypescriptOptions,
+          code: `export interface g {}; /* used-as-type */`,
+          parser,
+          filename: testFilePath('./no-unused-modules/typescript/file-ts-g-used-as-type.ts'),
+        }),
+        test({
+          options: unusedExportsTypescriptOptions,
+          code: `import type {g} from './file-ts-g';`,
+          parser,
+          filename: testFilePath('./no-unused-modules/typescript/file-ts-f-import-type.ts'),
+        }),
+      ),
+      invalid: [].concat(
+        test({
+          options: unusedExportsTypescriptOptions,
+          code: `export const b = 2;`,
+          parser,
+          filename: testFilePath('./no-unused-modules/typescript/file-ts-b-unused.ts'),
+          errors: [
+            error(`exported declaration 'b' not used within other modules`),
+          ],
+        }),
+        test({
+          options: unusedExportsTypescriptOptions,
+          code: `export interface c {};`,
+          parser,
+          filename: testFilePath('./no-unused-modules/typescript/file-ts-c-unused.ts'),
+          errors: [
+            error(`exported declaration 'c' not used within other modules`),
+          ],
+        }),
+        test({
+          options: unusedExportsTypescriptOptions,
+          code: `export type d = {};`,
+          parser,
+          filename: testFilePath('./no-unused-modules/typescript/file-ts-d-unused.ts'),
+          errors: [
+            error(`exported declaration 'd' not used within other modules`),
+          ],
+        }),
+        test({
+          options: unusedExportsTypescriptOptions,
+          code: `export enum e { f };`,
+          parser,
+          filename: testFilePath('./no-unused-modules/typescript/file-ts-e-unused.ts'),
+          errors: [
+            error(`exported declaration 'e' not used within other modules`),
+          ],
+        }),
+      ),
+    });
+  });
+});
+
+describe('ignoreUnusedTypeExports', () => {
+  getTSParsers().forEach((parser) => {
+    typescriptRuleTester.run('no-unused-modules', rule, {
+      valid: [
+        // unused vars should not report
+        test({
+          options: unusedExportsTypescriptIgnoreUnusedTypesOptions,
+          code: `export interface c {};`,
+          parser,
+          filename: testFilePath(
+            './no-unused-modules/typescript/file-ts-c-unused.ts',
+          ),
+        }),
+        test({
+          options: unusedExportsTypescriptIgnoreUnusedTypesOptions,
+          code: `export type d = {};`,
+          parser,
+          filename: testFilePath(
+            './no-unused-modules/typescript/file-ts-d-unused.ts',
+          ),
+        }),
+        test({
+          options: unusedExportsTypescriptIgnoreUnusedTypesOptions,
+          code: `export enum e { f };`,
+          parser,
+          filename: testFilePath(
+            './no-unused-modules/typescript/file-ts-e-unused.ts',
+          ),
+        }),
+        // used vars should not report
+        test({
+          options: unusedExportsTypescriptIgnoreUnusedTypesOptions,
+          code: `export interface c {};`,
+          parser,
+          filename: testFilePath(
+            './no-unused-modules/typescript/file-ts-c-used-as-type.ts',
+          ),
+        }),
+        test({
+          options: unusedExportsTypescriptIgnoreUnusedTypesOptions,
+          code: `export type d = {};`,
+          parser,
+          filename: testFilePath(
+            './no-unused-modules/typescript/file-ts-d-used-as-type.ts',
+          ),
+        }),
+        test({
+          options: unusedExportsTypescriptIgnoreUnusedTypesOptions,
+          code: `export enum e { f };`,
+          parser,
+          filename: testFilePath(
+            './no-unused-modules/typescript/file-ts-e-used-as-type.ts',
+          ),
+        }),
+      ],
+      invalid: [],
+    });
+  });
+});
+
+describe('correctly work with JSX only files', () => {
+  jsxRuleTester.run('no-unused-modules', rule, {
+    valid: [
+      test({
+        options: unusedExportsJsxOptions,
+        code: 'import a from "file-jsx-a";',
+        parser: parsers.BABEL_OLD,
+        filename: testFilePath('./no-unused-modules/jsx/file-jsx-a.jsx'),
+      }),
+    ],
+    invalid: [
+      test({
+        options: unusedExportsJsxOptions,
+        code: `export const b = 2;`,
+        parser: parsers.BABEL_OLD,
+        filename: testFilePath('./no-unused-modules/jsx/file-jsx-b.jsx'),
+        errors: [
+          error(`exported declaration 'b' not used within other modules`),
+        ],
+      }),
+    ],
+  });
+});
+
+describe('ignore flow types', () => {
+  ruleTester.run('no-unused-modules', rule, {
+    valid: [
+      test({
+        options: unusedExportsOptions,
+        code: 'import { type FooType, type FooInterface } from "./flow-2";',
+        parser: parsers.BABEL_OLD,
+        filename: testFilePath('./no-unused-modules/flow/flow-0.js'),
+      }),
+      test({
+        options: unusedExportsOptions,
+        code: `// @flow strict
+               export type FooType = string;
+               export interface FooInterface {};
+               `,
+        parser: parsers.BABEL_OLD,
+        filename: testFilePath('./no-unused-modules/flow/flow-2.js'),
+      }),
+      test({
+        options: unusedExportsOptions,
+        code: 'import type { FooType, FooInterface } from "./flow-4";',
+        parser: parsers.BABEL_OLD,
+        filename: testFilePath('./no-unused-modules/flow/flow-3.js'),
+      }),
+      test({
+        options: unusedExportsOptions,
+        code: `// @flow strict
+               export type FooType = string;
+               export interface FooInterface {};
+               `,
+        parser: parsers.BABEL_OLD,
+        filename: testFilePath('./no-unused-modules/flow/flow-4.js'),
+      }),
+      test({
+        options: unusedExportsOptions,
+        code: `// @flow strict
+               export type Bar = number;
+               export interface BarInterface {};
+               `,
+        parser: parsers.BABEL_OLD,
+        filename: testFilePath('./no-unused-modules/flow/flow-1.js'),
+      }),
+    ],
+    invalid: [],
+  });
+});
+
+describe('support (nested) destructuring assignment', () => {
+  ruleTester.run('no-unused-modules', rule, {
+    valid: [
+      test({
+        options: unusedExportsOptions,
+        code: 'import {a, b} from "./destructuring-b";',
+        parser: parsers.BABEL_OLD,
+        filename: testFilePath('./no-unused-modules/destructuring-a.js'),
+      }),
+      test({
+        options: unusedExportsOptions,
+        code: 'const obj = {a: 1, dummy: {b: 2}}; export const {a, dummy: {b}} = obj;',
+        parser: parsers.BABEL_OLD,
+        filename: testFilePath('./no-unused-modules/destructuring-b.js'),
+      }),
+    ],
+    invalid: [],
+  });
+});
+
+describe('support ES2022 Arbitrary module namespace identifier names', () => {
+  ruleTester.run('no-unused-module', rule, {
+    valid: [].concat(
+      testVersion('>= 8.7', () => ({
+        options: unusedExportsOptions,
+        code: `import { "foo" as foo } from "./arbitrary-module-namespace-identifier-name-a"`,
+        parserOptions: { ecmaVersion: 2022 },
+        filename: testFilePath('./no-unused-modules/arbitrary-module-namespace-identifier-name-b.js'),
+      })),
+      testVersion('>= 8.7', () => ({
+        options: unusedExportsOptions,
+        code: 'const foo = 333;\nexport { foo as "foo" }',
+        parserOptions: { ecmaVersion: 2022 },
+        filename: testFilePath('./no-unused-modules/arbitrary-module-namespace-identifier-name-a.js'),
+      })),
+    ),
+    invalid: [].concat(
+      testVersion('>= 8.7', () => ({
+        options: unusedExportsOptions,
+        code: 'const foo = 333\nexport { foo as "foo" }',
+        parserOptions: { ecmaVersion: 2022 },
+        filename: testFilePath('./no-unused-modules/arbitrary-module-namespace-identifier-name-c.js'),
+        errors: [
+          error(`exported declaration 'foo' not used within other modules`),
+        ],
+      })),
+    ),
+  });
+});
+
+describe('parser ignores prefixes like BOM and hashbang', () => {
+  // bom, hashbang
+  ruleTester.run('no-unused-modules', rule, {
+    valid: [
+      test({
+        options: unusedExportsOptions,
+        code: 'export const foo = 1;\n',
+        filename: testFilePath('./no-unused-modules/prefix-child.js'),
+      }),
+      test({
+        options: unusedExportsOptions,
+        code: `\uFEFF#!/usr/bin/env node\nimport {foo} from './prefix-child.js';\n`,
+        filename: testFilePath('./no-unused-modules/prefix-parent-bom.js'),
+      }),
+    ],
+    invalid: [],
+  });
+  // no bom, hashbang
+  ruleTester.run('no-unused-modules', rule, {
+    valid: [
+      test({
+        options: unusedExportsOptions,
+        code: 'export const foo = 1;\n',
+        filename: testFilePath('./no-unused-modules/prefix-child.js'),
+      }),
+      test({
+        options: unusedExportsOptions,
+        code: `#!/usr/bin/env node\nimport {foo} from './prefix-child.js';\n`,
+        filename: testFilePath('./no-unused-modules/prefix-parent-hashbang.js'),
+      }),
+    ],
+    invalid: [],
+  });
+  // bom, no hashbang
+  ruleTester.run('no-unused-modules', rule, {
+    valid: [
+      test({
+        options: unusedExportsOptions,
+        code: 'export const foo = 1;\n',
+        filename: testFilePath('./no-unused-modules/prefix-child.js'),
+      }),
+      test({
+        options: unusedExportsOptions,
+        code: `\uFEFF#!/usr/bin/env node\nimport {foo} from './prefix-child.js';\n`,
+        filename: testFilePath('./no-unused-modules/prefix-parent-bomhashbang.js'),
+      }),
+    ],
+    invalid: [],
+  });
+  // no bom, no hashbang
+  ruleTester.run('no-unused-modules', rule, {
+    valid: [
+      test({
+        options: unusedExportsOptions,
+        code: 'export const foo = 1;\n',
+        filename: testFilePath('./no-unused-modules/prefix-child.js'),
+      }),
+      test({
+        options: unusedExportsOptions,
+        code: `import {foo} from './prefix-child.js';\n`,
+        filename: testFilePath('./no-unused-modules/prefix-parent.js'),
+      }),
+    ],
+    invalid: [],
+  });
+});
+
+(FlatRuleTester ? describe : describe.skip)('supports flat eslint', () => {
+  it('passes', () => {
+    const flatRuleTester = new FlatRuleTester();
+    flatRuleTester.run('no-unused-modules', rule, {
+      valid: [{
+        options: unusedExportsOptions,
+        code: 'import { o2 } from "./file-o"; export default () => 12',
+        filename: testFilePath('./no-unused-modules/file-a.js'),
+      }],
+      invalid: [{
+        options: unusedExportsOptions,
+        code: 'export default () => 13',
+        filename: testFilePath('./no-unused-modules/file-f.js'),
+        errors: [error(`exported declaration 'default' not used within other modules`)],
+      }],
+    });
+  });
+});
+
+(isESLint9 ? describe : describe.skip)('with eslint 9+', () => {
+  it('provides meaningful error when eslintrc is not present', () => {
+    const tmp = require('tmp');
+
+    // Create temp directory outside of project root
+    const tempDir = tmp.dirSync({ unsafeCleanup: true });
+
+    // Copy example project to temp directory
+    fs.cpSync(path.join(process.cwd(), 'examples/v9'), tempDir.name, { recursive: true });
+
+    let errorMessage = '';
+
+    // Build the plugin
+    try {
+      execSync('npm run build');
+    } catch (_) {
+      /* ignore */
+    }
+
+    // Install the plugin and run the lint command in the temp directory
+    try {
+      execSync(`npm install -D ${process.cwd()} && npm run lint`, { cwd: tempDir.name });
+    } catch (error) {
+      errorMessage = error.stderr.toString();
+    }
+
+    // Verify that the error message is as expected
+    expect(errorMessage).to.contain('the import/no-unused-modules rule requires an .eslintrc file');
+
+    // Cleanup
+    tempDir.removeCallback();
+  }).timeout(100000);
+});
diff --git a/tests/src/rules/no-useless-path-segments.js b/tests/src/rules/no-useless-path-segments.js
index 366e75354..87f7a73e9 100644
--- a/tests/src/rules/no-useless-path-segments.js
+++ b/tests/src/rules/no-useless-path-segments.js
@@ -1,8 +1,8 @@
-import { test } from '../utils'
-import { RuleTester } from 'eslint'
+import { parsers, test } from '../utils';
+import { RuleTester } from '../rule-tester';
 
-const ruleTester = new RuleTester()
-const rule = require('rules/no-useless-path-segments')
+const ruleTester = new RuleTester();
+const rule = require('rules/no-useless-path-segments');
 
 function runResolverTests(resolver) {
   ruleTester.run(`no-useless-path-segments (${resolver})`, rule, {
@@ -28,95 +28,111 @@ function runResolverTests(resolver) {
       test({ code: 'import "./malformed"', options: [{ noUselessIndex: true }] }), // ./malformed directory does not exist
       test({ code: 'import "./importType"', options: [{ noUselessIndex: true }] }), // ./importType.js does not exist
 
-      test({ code: 'import(".")'
-           , parser: require.resolve('babel-eslint') }),
-      test({ code: 'import("..")'
-           , parser: require.resolve('babel-eslint') }),
-      test({ code: 'import("fs").then(function(fs){})'
-           , parser: require.resolve('babel-eslint') }),
+      test({ code: 'import(".")',
+        parser: parsers.BABEL_OLD }),
+      test({ code: 'import("..")',
+        parser: parsers.BABEL_OLD }),
+      test({ code: 'import("fs").then(function(fs) {})',
+        parser: parsers.BABEL_OLD }),
     ],
 
     invalid: [
       // CommonJS modules
       test({
         code: 'require("./../files/malformed.js")',
+        output: 'require("../files/malformed.js")',
         options: [{ commonjs: true }],
-        errors: [ 'Useless path segments for "./../files/malformed.js", should be "../files/malformed.js"'],
+        errors: ['Useless path segments for "./../files/malformed.js", should be "../files/malformed.js"'],
       }),
       test({
         code: 'require("./../files/malformed")',
+        output: 'require("../files/malformed")',
         options: [{ commonjs: true }],
-        errors: [ 'Useless path segments for "./../files/malformed", should be "../files/malformed"'],
+        errors: ['Useless path segments for "./../files/malformed", should be "../files/malformed"'],
       }),
       test({
         code: 'require("../files/malformed.js")',
+        output: 'require("./malformed.js")',
         options: [{ commonjs: true }],
-        errors: [ 'Useless path segments for "../files/malformed.js", should be "./malformed.js"'],
+        errors: ['Useless path segments for "../files/malformed.js", should be "./malformed.js"'],
       }),
       test({
         code: 'require("../files/malformed")',
+        output: 'require("./malformed")',
         options: [{ commonjs: true }],
-        errors: [ 'Useless path segments for "../files/malformed", should be "./malformed"'],
+        errors: ['Useless path segments for "../files/malformed", should be "./malformed"'],
       }),
       test({
         code: 'require("./test-module/")',
+        output: 'require("./test-module")',
         options: [{ commonjs: true }],
-        errors: [ 'Useless path segments for "./test-module/", should be "./test-module"'],
+        errors: ['Useless path segments for "./test-module/", should be "./test-module"'],
       }),
       test({
         code: 'require("./")',
+        output: 'require(".")',
         options: [{ commonjs: true }],
-        errors: [ 'Useless path segments for "./", should be "."'],
+        errors: ['Useless path segments for "./", should be "."'],
       }),
       test({
         code: 'require("../")',
+        output: 'require("..")',
         options: [{ commonjs: true }],
-        errors: [ 'Useless path segments for "../", should be ".."'],
+        errors: ['Useless path segments for "../", should be ".."'],
       }),
       test({
         code: 'require("./deep//a")',
+        output: 'require("./deep/a")',
         options: [{ commonjs: true }],
-        errors: [ 'Useless path segments for "./deep//a", should be "./deep/a"'],
+        errors: ['Useless path segments for "./deep//a", should be "./deep/a"'],
       }),
 
       // CommonJS modules + noUselessIndex
       test({
         code: 'require("./bar/index.js")',
+        output: 'require("./bar/")',
         options: [{ commonjs: true, noUselessIndex: true }],
         errors: ['Useless path segments for "./bar/index.js", should be "./bar/"'], // ./bar.js exists
       }),
       test({
         code: 'require("./bar/index")',
+        output: 'require("./bar/")',
         options: [{ commonjs: true, noUselessIndex: true }],
         errors: ['Useless path segments for "./bar/index", should be "./bar/"'], // ./bar.js exists
       }),
       test({
         code: 'require("./importPath/")',
+        output: 'require("./importPath")',
         options: [{ commonjs: true, noUselessIndex: true }],
         errors: ['Useless path segments for "./importPath/", should be "./importPath"'], // ./importPath.js does not exist
       }),
       test({
         code: 'require("./importPath/index.js")',
+        output: 'require("./importPath")',
         options: [{ commonjs: true, noUselessIndex: true }],
         errors: ['Useless path segments for "./importPath/index.js", should be "./importPath"'], // ./importPath.js does not exist
       }),
       test({
         code: 'require("./importType/index")',
+        output: 'require("./importType")',
         options: [{ commonjs: true, noUselessIndex: true }],
         errors: ['Useless path segments for "./importType/index", should be "./importType"'], // ./importPath.js does not exist
       }),
       test({
         code: 'require("./index")',
+        output: 'require(".")',
         options: [{ commonjs: true, noUselessIndex: true }],
         errors: ['Useless path segments for "./index", should be "."'],
       }),
       test({
         code: 'require("../index")',
+        output: 'require("..")',
         options: [{ commonjs: true, noUselessIndex: true }],
         errors: ['Useless path segments for "../index", should be ".."'],
       }),
       test({
         code: 'require("../index.js")',
+        output: 'require("..")',
         options: [{ commonjs: true, noUselessIndex: true }],
         errors: ['Useless path segments for "../index.js", should be ".."'],
       }),
@@ -124,95 +140,114 @@ function runResolverTests(resolver) {
       // ES modules
       test({
         code: 'import "./../files/malformed.js"',
-        errors: [ 'Useless path segments for "./../files/malformed.js", should be "../files/malformed.js"'],
+        output: 'import "../files/malformed.js"',
+        errors: ['Useless path segments for "./../files/malformed.js", should be "../files/malformed.js"'],
       }),
       test({
         code: 'import "./../files/malformed"',
-        errors: [ 'Useless path segments for "./../files/malformed", should be "../files/malformed"'],
+        output: 'import "../files/malformed"',
+        errors: ['Useless path segments for "./../files/malformed", should be "../files/malformed"'],
       }),
       test({
         code: 'import "../files/malformed.js"',
-        errors: [ 'Useless path segments for "../files/malformed.js", should be "./malformed.js"'],
+        output: 'import "./malformed.js"',
+        errors: ['Useless path segments for "../files/malformed.js", should be "./malformed.js"'],
       }),
       test({
         code: 'import "../files/malformed"',
-        errors: [ 'Useless path segments for "../files/malformed", should be "./malformed"'],
+        output: 'import "./malformed"',
+        errors: ['Useless path segments for "../files/malformed", should be "./malformed"'],
       }),
       test({
         code: 'import "./test-module/"',
-        errors: [ 'Useless path segments for "./test-module/", should be "./test-module"'],
+        output: 'import "./test-module"',
+        errors: ['Useless path segments for "./test-module/", should be "./test-module"'],
       }),
       test({
         code: 'import "./"',
-        errors: [ 'Useless path segments for "./", should be "."'],
+        output: 'import "."',
+        errors: ['Useless path segments for "./", should be "."'],
       }),
       test({
         code: 'import "../"',
-        errors: [ 'Useless path segments for "../", should be ".."'],
+        output: 'import ".."',
+        errors: ['Useless path segments for "../", should be ".."'],
       }),
       test({
         code: 'import "./deep//a"',
-        errors: [ 'Useless path segments for "./deep//a", should be "./deep/a"'],
+        output: 'import "./deep/a"',
+        errors: ['Useless path segments for "./deep//a", should be "./deep/a"'],
       }),
 
       // ES modules + noUselessIndex
       test({
         code: 'import "./bar/index.js"',
+        output: 'import "./bar/"',
         options: [{ noUselessIndex: true }],
         errors: ['Useless path segments for "./bar/index.js", should be "./bar/"'], // ./bar.js exists
       }),
       test({
         code: 'import "./bar/index"',
+        output: 'import "./bar/"',
         options: [{ noUselessIndex: true }],
         errors: ['Useless path segments for "./bar/index", should be "./bar/"'], // ./bar.js exists
       }),
       test({
         code: 'import "./importPath/"',
+        output: 'import "./importPath"',
         options: [{ noUselessIndex: true }],
         errors: ['Useless path segments for "./importPath/", should be "./importPath"'], // ./importPath.js does not exist
       }),
       test({
         code: 'import "./importPath/index.js"',
+        output: 'import "./importPath"',
         options: [{ noUselessIndex: true }],
         errors: ['Useless path segments for "./importPath/index.js", should be "./importPath"'], // ./importPath.js does not exist
       }),
       test({
         code: 'import "./importPath/index"',
+        output: 'import "./importPath"',
         options: [{ noUselessIndex: true }],
         errors: ['Useless path segments for "./importPath/index", should be "./importPath"'], // ./importPath.js does not exist
       }),
       test({
         code: 'import "./index"',
+        output: 'import "."',
         options: [{ noUselessIndex: true }],
         errors: ['Useless path segments for "./index", should be "."'],
       }),
       test({
         code: 'import "../index"',
+        output: 'import ".."',
         options: [{ noUselessIndex: true }],
         errors: ['Useless path segments for "../index", should be ".."'],
       }),
       test({
         code: 'import "../index.js"',
+        output: 'import ".."',
         options: [{ noUselessIndex: true }],
         errors: ['Useless path segments for "../index.js", should be ".."'],
       }),
       test({
         code: 'import("./")',
-        errors: [ 'Useless path segments for "./", should be "."'],
-        parser: require.resolve('babel-eslint'),
+        output: 'import(".")',
+        errors: ['Useless path segments for "./", should be "."'],
+        parser: parsers.BABEL_OLD,
       }),
       test({
         code: 'import("../")',
-        errors: [ 'Useless path segments for "../", should be ".."'],
-        parser: require.resolve('babel-eslint'),
+        output: 'import("..")',
+        errors: ['Useless path segments for "../", should be ".."'],
+        parser: parsers.BABEL_OLD,
       }),
       test({
         code: 'import("./deep//a")',
-        errors: [ 'Useless path segments for "./deep//a", should be "./deep/a"'],
-        parser: require.resolve('babel-eslint'),
+        output: 'import("./deep/a")',
+        errors: ['Useless path segments for "./deep//a", should be "./deep/a"'],
+        parser: parsers.BABEL_OLD,
       }),
     ],
-  })
+  });
 }
 
-['node', 'webpack'].forEach(runResolverTests)
+['node', 'webpack'].forEach(runResolverTests);
diff --git a/tests/src/rules/no-webpack-loader-syntax.js b/tests/src/rules/no-webpack-loader-syntax.js
index 23a1190fb..86114b36c 100644
--- a/tests/src/rules/no-webpack-loader-syntax.js
+++ b/tests/src/rules/no-webpack-loader-syntax.js
@@ -1,25 +1,26 @@
-import { test } from '../utils'
+import { test, getTSParsers, parsers } from '../utils';
 
-import { RuleTester } from 'eslint'
+import { RuleTester } from '../rule-tester';
+import semver from 'semver';
 
-const ruleTester = new RuleTester()
-    , rule = require('rules/no-webpack-loader-syntax')
+const ruleTester = new RuleTester();
+const rule = require('rules/no-webpack-loader-syntax');
 
-const message = 'Do not use import syntax to configure webpack loaders.'
+const message = 'Do not use import syntax to configure webpack loaders.';
 
 ruleTester.run('no-webpack-loader-syntax', rule, {
   valid: [
-    test({ code: 'import _ from "lodash"'}),
-    test({ code: 'import find from "lodash.find"'}),
-    test({ code: 'import foo from "./foo.css"'}),
-    test({ code: 'import data from "@scope/my-package/data.json"'}),
-    test({ code: 'var _ = require("lodash")'}),
-    test({ code: 'var find = require("lodash.find")'}),
-    test({ code: 'var foo = require("./foo")'}),
-    test({ code: 'var foo = require("../foo")'}),
-    test({ code: 'var foo = require("foo")'}),
-    test({ code: 'var foo = require("./")'}),
-    test({ code: 'var foo = require("@scope/foo")'}),
+    test({ code: 'import _ from "lodash"' }),
+    test({ code: 'import find from "lodash.find"' }),
+    test({ code: 'import foo from "./foo.css"' }),
+    test({ code: 'import data from "@scope/my-package/data.json"' }),
+    test({ code: 'var _ = require("lodash")' }),
+    test({ code: 'var find = require("lodash.find")' }),
+    test({ code: 'var foo = require("./foo")' }),
+    test({ code: 'var foo = require("../foo")' }),
+    test({ code: 'var foo = require("foo")' }),
+    test({ code: 'var foo = require("./")' }),
+    test({ code: 'var foo = require("@scope/foo")' }),
   ],
   invalid: [
     test({
@@ -71,4 +72,27 @@ ruleTester.run('no-webpack-loader-syntax', rule, {
       ],
     }),
   ],
-})
+});
+
+context('TypeScript', function () {
+  getTSParsers().forEach((parser) => {
+    const parserConfig = {
+      parser,
+      settings: {
+        'import/parsers': { [parser]: ['.ts'] },
+        'import/resolver': { 'eslint-import-resolver-typescript': true },
+      },
+    };
+    // @typescript-eslint/parser@5+ throw error for invalid module specifiers at parsing time.
+    // https://github.com/typescript-eslint/typescript-eslint/releases/tag/v5.0.0
+    if (!(parser === parsers.TS_NEW && semver.satisfies(require('@typescript-eslint/parser/package.json').version, '>= 5'))) {
+      ruleTester.run('no-webpack-loader-syntax', rule, {
+        valid: [
+          test({ code: 'import { foo } from\nalert()', ...parserConfig }),
+          test({ code: 'import foo from\nalert()', ...parserConfig }),
+        ],
+        invalid: [],
+      });
+    }
+  });
+});
diff --git a/tests/src/rules/order.js b/tests/src/rules/order.js
index 426c40a10..362fa595a 100644
--- a/tests/src/rules/order.js
+++ b/tests/src/rules/order.js
@@ -1,13 +1,25 @@
-import { test, testVersion, getTSParsers } from '../utils'
+import { test, getTSParsers, getNonDefaultParsers, testFilePath, parsers } from '../utils';
 
-import { RuleTester } from 'eslint'
+import { RuleTester, withoutAutofixOutput } from '../rule-tester';
+import eslintPkg from 'eslint/package.json';
+import semver from 'semver';
+import flatMap from 'array.prototype.flatmap';
+import { resolve } from 'path';
+import isCoreModule from 'is-core-module';
+import { default as babelPresetFlow } from 'babel-preset-flow';
 
-const ruleTester = new RuleTester()
-    , rule = require('rules/order')
-
-function withoutAutofixOutput(test) {
-  return Object.assign({}, test, { output: test.code })
-}
+const ruleTester = new RuleTester();
+const flowRuleTester = new RuleTester({
+  parser: resolve(__dirname, '../../../node_modules/babel-eslint'),
+  parserOptions: {
+    babelOptions: {
+      configFile: false,
+      babelrc: false,
+      presets: [babelPresetFlow],
+    },
+  },
+});
+const rule = require('rules/order');
 
 ruleTester.run('order', rule, {
   valid: [
@@ -19,9 +31,10 @@ ruleTester.run('order', rule, {
         var relParent1 = require('../foo');
         var relParent2 = require('../foo/bar');
         var relParent3 = require('../');
+        var relParent4 = require('..');
         var sibling = require('./foo');
         var index = require('./');`,
-      }),
+    }),
     // Default order using import
     test({
       code: `
@@ -32,7 +45,7 @@ ruleTester.run('order', rule, {
         import relParent3 from '../';
         import sibling, {foo3} from './foo';
         import index from './';`,
-      }),
+    }),
     // Multiple module of the same rank next to each other
     test({
       code: `
@@ -41,7 +54,7 @@ ruleTester.run('order', rule, {
         var path = require('path');
         var _ = require('lodash');
         var async = require('async');`,
-      }),
+    }),
     // Overriding order to be the reverse of the default order
     test({
       code: `
@@ -53,7 +66,7 @@ ruleTester.run('order', rule, {
         var async = require('async');
         var fs = require('fs');
       `,
-      options: [{groups: ['index', 'sibling', 'parent', 'external', 'builtin']}],
+      options: [{ groups: ['index', 'sibling', 'parent', 'external', 'builtin'] }],
     }),
     // Ignore dynamic requires
     test({
@@ -70,7 +83,7 @@ ruleTester.run('order', rule, {
         var result = add(1, 2);
         var _ = require('lodash');`,
     }),
-    // Ignore requires that are not at the top-level
+    // Ignore requires that are not at the top-level #1
     test({
       code: `
         var index = require('./');
@@ -82,6 +95,18 @@ ruleTester.run('order', rule, {
           require('fs');
         }`,
     }),
+    // Ignore requires that are not at the top-level #2
+    test({
+      code: `
+        const foo = [
+          require('./foo'),
+          require('fs'),
+        ]`,
+    }),
+    // Ignore requires in template literal (#1936)
+    test({
+      code: "const foo = `${require('./a')} ${require('fs')}`",
+    }),
     // Ignore unknown/invalid cases
     test({
       code: `
@@ -100,21 +125,21 @@ ruleTester.run('order', rule, {
         var unknown7 = require('/unknown7');
         var index = require('./');
         var unknown8 = require('/unknown8');
-    `}),
+    ` }),
     // Ignoring unassigned values by default (require)
     test({
       code: `
         require('./foo');
         require('fs');
         var path = require('path');
-    `}),
+    ` }),
     // Ignoring unassigned values by default (import)
     test({
       code: `
         import './foo';
         import 'fs';
         import path from 'path';
-    `}),
+    ` }),
     // No imports
     test({
       code: `
@@ -122,7 +147,7 @@ ruleTester.run('order', rule, {
           return a + b;
         }
         var foo;
-    `}),
+    ` }),
     // Grouping import types
     test({
       code: `
@@ -135,10 +160,38 @@ ruleTester.run('order', rule, {
         var async = require('async');
         var relParent1 = require('../foo');
       `,
-      options: [{groups: [
+      options: [{ groups: [
         ['builtin', 'index'],
         ['sibling', 'parent', 'external'],
-      ]}],
+      ] }],
+    }),
+    // Grouping import types and alphabetize
+    test({
+      code: `
+        import async from 'async';
+        import fs from 'fs';
+        import path from 'path';
+
+        import index from '.';
+        import relParent3 from '../';
+        import relParent1 from '../foo';
+        import sibling from './foo';
+      `,
+      options: [{ groups: [
+        ['builtin', 'external'],
+      ], alphabetize: { order: 'asc', caseInsensitive: true } }],
+    }),
+    test({
+      code: `
+      import { fooz } from '../baz.js'
+      import { foo } from './bar.js'
+      `,
+      options: [{
+        alphabetize: { order: 'asc', caseInsensitive: true },
+        groups: ['builtin', 'external', 'internal', ['parent', 'sibling', 'index'], 'object'],
+        'newlines-between': 'always',
+        warnOnUnassignedImports: true,
+      }],
     }),
     // Omitted types should implicitly be considered as the last type
     test({
@@ -146,11 +199,11 @@ ruleTester.run('order', rule, {
         var index = require('./');
         var path = require('path');
       `,
-      options: [{groups: [
+      options: [{ groups: [
         'index',
         ['sibling', 'parent', 'external'],
         // missing 'builtin'
-      ]}],
+      ] }],
     }),
     // Mixing require and import should have import up top
     test({
@@ -164,7 +217,30 @@ ruleTester.run('order', rule, {
         var index = require('./');
       `,
     }),
-    // Addijg unknown import types (e.g. using an resolver alias via babel) to the groups.
+    ...flatMap(getTSParsers(), (parser) => [
+      // Export equals expressions should be on top alongside with ordinary import-statements.
+      test({
+        code: `
+          import async, {foo1} from 'async';
+          import relParent2, {foo2} from '../foo/bar';
+          import sibling, {foo3} from './foo';
+          var fs = require('fs');
+          var util = require("util");
+          var relParent1 = require('../foo');
+          var relParent3 = require('../');
+          var index = require('./');
+        `,
+        parser,
+      }),
+
+      test({
+        code: `
+          export import CreateSomething = _CreateSomething;
+        `,
+        parser,
+      }),
+    ]),
+    // Adding unknown import types (e.g. using a resolver alias via babel) to the groups.
     test({
       code: `
         import fs from 'fs';
@@ -175,7 +251,7 @@ ruleTester.run('order', rule, {
         groups: ['builtin', 'external', 'unknown', 'parent', 'sibling', 'index'],
       }],
     }),
-    // Using unknown import types (e.g. using an resolver alias via babel) with
+    // Using unknown import types (e.g. using a resolver alias via babel) with
     // an alternative custom group list.
     test({
       code: `
@@ -184,10 +260,10 @@ ruleTester.run('order', rule, {
         import fs from 'fs';
         import { add } from './helper';`,
       options: [{
-        groups: [ 'unknown', 'builtin', 'external', 'parent', 'sibling', 'index' ],
+        groups: ['unknown', 'builtin', 'external', 'parent', 'sibling', 'index'],
       }],
     }),
-    // Using unknown import types (e.g. using an resolver alias via babel)
+    // Using unknown import types (e.g. using a resolver alias via babel)
     // Option: newlines-between: 'always'
     test({
       code: `
@@ -196,7 +272,13 @@ ruleTester.run('order', rule, {
         import { Input } from '-/components/Input';
         import { Button } from '-/components/Button';
 
-        import { add } from './helper';`,
+        import p from '..';
+        import q from '../';
+
+        import { add } from './helper';
+
+        import i from '.';
+        import j from './';`,
       options: [
         {
           'newlines-between': 'always',
@@ -204,6 +286,149 @@ ruleTester.run('order', rule, {
         },
       ],
     }),
+
+    // Using pathGroups to customize ordering, position 'after'
+    test({
+      code: `
+        import fs from 'fs';
+        import _ from 'lodash';
+        import { Input } from '~/components/Input';
+        import { Button } from '#/components/Button';
+        import { add } from './helper';`,
+      options: [{
+        pathGroups: [
+          { pattern: '~/**', group: 'external', position: 'after' },
+          { pattern: '#/**', group: 'external', position: 'after' },
+        ],
+      }],
+    }),
+    // pathGroup without position means "equal" with group
+    test({
+      code: `
+        import fs from 'fs';
+        import { Input } from '~/components/Input';
+        import async from 'async';
+        import { Button } from '#/components/Button';
+        import _ from 'lodash';
+        import { add } from './helper';`,
+      options: [{
+        pathGroups: [
+          { pattern: '~/**', group: 'external' },
+          { pattern: '#/**', group: 'external' },
+        ],
+      }],
+    }),
+    // Using pathGroups to customize ordering, position 'before'
+    test({
+      code: `
+        import fs from 'fs';
+
+        import { Input } from '~/components/Input';
+
+        import { Button } from '#/components/Button';
+
+        import _ from 'lodash';
+
+        import { add } from './helper';`,
+      options: [{
+        'newlines-between': 'always',
+        pathGroups: [
+          { pattern: '~/**', group: 'external', position: 'before' },
+          { pattern: '#/**', group: 'external', position: 'before' },
+        ],
+      }],
+    }),
+    // Using pathGroups to customize ordering, with patternOptions
+    test({
+      code: `
+        import fs from 'fs';
+
+        import _ from 'lodash';
+
+        import { Input } from '~/components/Input';
+
+        import { Button } from '!/components/Button';
+
+        import { add } from './helper';`,
+      options: [{
+        'newlines-between': 'always',
+        pathGroups: [
+          { pattern: '~/**', group: 'external', position: 'after' },
+          { pattern: '!/**', patternOptions: { nonegate: true }, group: 'external', position: 'after' },
+        ],
+      }],
+    }),
+    // Using pathGroups to customize ordering for imports that are recognized as 'external'
+    // by setting pathGroupsExcludedImportTypes without 'external'
+    test({
+      code: `
+        import fs from 'fs';
+
+        import { Input } from '@app/components/Input';
+
+        import { Button } from '@app2/components/Button';
+
+        import _ from 'lodash';
+
+        import { add } from './helper';`,
+      options: [{
+        'newlines-between': 'always',
+        pathGroupsExcludedImportTypes: ['builtin'],
+        pathGroups: [
+          { pattern: '@app/**', group: 'external', position: 'before' },
+          { pattern: '@app2/**', group: 'external', position: 'before' },
+        ],
+      }],
+    }),
+    // Using pathGroups (a test case for https://github.com/import-js/eslint-plugin-import/pull/1724)
+    test({
+      code: `
+        import fs from 'fs';
+        import external from 'external';
+        import externalTooPlease from './make-me-external';
+
+        import sibling from './sibling';`,
+      options: [{
+        'newlines-between': 'always',
+        pathGroupsExcludedImportTypes: [],
+        pathGroups: [
+          { pattern: './make-me-external', group: 'external' },
+        ],
+        groups: [['builtin', 'external'], 'internal', 'parent', 'sibling', 'index'],
+      }],
+    }),
+    // Monorepo setup, using Webpack resolver, workspace folder name in external-module-folders
+    test({
+      code: `
+        import _ from 'lodash';
+        import m from '@test-scope/some-module';
+
+        import bar from './bar';
+      `,
+      options: [{
+        'newlines-between': 'always',
+      }],
+      settings: {
+        'import/resolver': 'webpack',
+        'import/external-module-folders': ['node_modules', 'symlinked-module'],
+      },
+    }),
+    // Monorepo setup, using Node resolver (doesn't resolve symlinks)
+    test({
+      code: `
+        import _ from 'lodash';
+        import m from '@test-scope/some-module';
+
+        import bar from './bar';
+      `,
+      options: [{
+        'newlines-between': 'always',
+      }],
+      settings: {
+        'import/resolver': 'node',
+        'import/external-module-folders': ['node_modules', 'symlinked-module'],
+      },
+    }),
     // Option: newlines-between: 'always'
     test({
       code: `
@@ -305,7 +530,7 @@ ruleTester.run('order', rule, {
         },
       ],
     }),
-    // Option newlines-between: 'always' with multiline imports #1
+    // Option newlines-between: 'always' with multi-line imports #1
     test({
       code: `
         import path from 'path';
@@ -319,9 +544,9 @@ ruleTester.run('order', rule, {
         } from 'bar';
         import external from 'external'
       `,
-      options: [{ 'newlines-between': 'always' }]
+      options: [{ 'newlines-between': 'always' }],
     }),
-    // Option newlines-between: 'always' with multiline imports #2
+    // Option newlines-between: 'always' with multi-line imports #2
     test({
       code: `
         import path from 'path';
@@ -330,9 +555,9 @@ ruleTester.run('order', rule, {
 
         import external from 'external'
       `,
-      options: [{ 'newlines-between': 'always' }]
+      options: [{ 'newlines-between': 'always' }],
     }),
-    // Option newlines-between: 'always' with multiline imports #3
+    // Option newlines-between: 'always' with multi-line imports #3
     test({
       code: `
         import foo
@@ -341,7 +566,7 @@ ruleTester.run('order', rule, {
         import bar
           from './sibling';
       `,
-      options: [{ 'newlines-between': 'always' }]
+      options: [{ 'newlines-between': 'always' }],
     }),
     // Option newlines-between: 'always' with not assigned import #1
     test({
@@ -353,7 +578,7 @@ ruleTester.run('order', rule, {
 
         import _ from 'lodash';
       `,
-      options: [{ 'newlines-between': 'always' }]
+      options: [{ 'newlines-between': 'always' }],
     }),
     // Option newlines-between: 'never' with not assigned import #2
     test({
@@ -363,7 +588,7 @@ ruleTester.run('order', rule, {
         import 'something-else';
         import _ from 'lodash';
       `,
-      options: [{ 'newlines-between': 'never' }]
+      options: [{ 'newlines-between': 'never' }],
     }),
     // Option newlines-between: 'always' with not assigned require #1
     test({
@@ -375,7 +600,7 @@ ruleTester.run('order', rule, {
 
         var _ = require('lodash');
       `,
-      options: [{ 'newlines-between': 'always' }]
+      options: [{ 'newlines-between': 'always' }],
     }),
     // Option newlines-between: 'never' with not assigned require #2
     test({
@@ -385,7 +610,7 @@ ruleTester.run('order', rule, {
         require('something-else');
         var _ = require('lodash');
       `,
-      options: [{ 'newlines-between': 'never' }]
+      options: [{ 'newlines-between': 'never' }],
     }),
     // Option newlines-between: 'never' should ignore nested require statement's #1
     test({
@@ -402,7 +627,7 @@ ruleTester.run('order', rule, {
           }
         }
       `,
-      options: [{ 'newlines-between': 'never' }]
+      options: [{ 'newlines-between': 'never' }],
     }),
     // Option newlines-between: 'always' should ignore nested require statement's #2
     test({
@@ -418,7 +643,7 @@ ruleTester.run('order', rule, {
           }
         }
       `,
-      options: [{ 'newlines-between': 'always' }]
+      options: [{ 'newlines-between': 'always' }],
     }),
     // Option: newlines-between: 'always-and-inside-groups'
     test({
@@ -446,946 +671,6248 @@ ruleTester.run('order', rule, {
         },
       ],
     }),
-  ],
-  invalid: [
-    // builtin before external module (require)
+    // Option newlines-between: 'always-and-inside-groups' and consolidateIslands: true
     test({
       code: `
-        var async = require('async');
-        var fs = require('fs');
-      `,
-      output: `
         var fs = require('fs');
+        var path = require('path');
+        var util = require('util');
+
         var async = require('async');
+
+        var relParent1 = require('../foo');
+
+        var {
+          relParent2 } = require('../');
+
+        var relParent3 = require('../bar');
+
+        var sibling = require('./foo');
+        var sibling2 = require('./bar');
+        var sibling3 = require('./foobar');
       `,
-      errors: [{
-        ruleId: 'order',
-        message: '`fs` import should occur before import of `async`',
-      }],
+      options: [
+        {
+          'newlines-between': 'always-and-inside-groups',
+          consolidateIslands: 'inside-groups',
+        },
+      ],
     }),
-    // fix order with spaces on the end of line
+    // Option alphabetize: {order: 'ignore'}
     test({
       code: `
-        var async = require('async');
-        var fs = require('fs');${' '}
-      `,
-      output: `
-        var fs = require('fs');${' '}
-        var async = require('async');
+        import a from 'foo';
+        import b from 'bar';
+
+        import index from './';
       `,
-      errors: [{
-        ruleId: 'order',
-        message: '`fs` import should occur before import of `async`',
+      options: [{
+        groups: ['external', 'index'],
+        alphabetize: { order: 'ignore' },
       }],
     }),
-    // fix order with comment on the end of line
+    // Option alphabetize: {order: 'asc'}
     test({
       code: `
-        var async = require('async');
-        var fs = require('fs'); /* comment */
-      `,
-      output: `
-        var fs = require('fs'); /* comment */
-        var async = require('async');
+        import c from 'Bar';
+        import b from 'bar';
+        import a from 'foo';
+
+        import index from './';
       `,
-      errors: [{
-        ruleId: 'order',
-        message: '`fs` import should occur before import of `async`',
+      options: [{
+        groups: ['external', 'index'],
+        alphabetize: { order: 'asc' },
       }],
     }),
-    // fix order with comments at the end and start of line
+    // Option alphabetize: {order: 'desc'}
     test({
       code: `
-        /* comment1 */  var async = require('async'); /* comment2 */
-        /* comment3 */  var fs = require('fs'); /* comment4 */
-      `,
-      output: `
-        /* comment3 */  var fs = require('fs'); /* comment4 */
-        /* comment1 */  var async = require('async'); /* comment2 */
+        import a from 'foo';
+        import b from 'bar';
+        import c from 'Bar';
+
+        import index from './';
       `,
-      errors: [{
-        ruleId: 'order',
-        message: '`fs` import should occur before import of `async`',
+      options: [{
+        groups: ['external', 'index'],
+        alphabetize: { order: 'desc' },
       }],
     }),
-    // fix order with few comments at the end and start of line
+    // Option alphabetize: {order: 'asc'} and move nested import entries closer to the main import entry
     test({
       code: `
-        /* comment0 */  /* comment1 */  var async = require('async'); /* comment2 */
-        /* comment3 */  var fs = require('fs'); /* comment4 */
-      `,
-      output: `
-        /* comment3 */  var fs = require('fs'); /* comment4 */
-        /* comment0 */  /* comment1 */  var async = require('async'); /* comment2 */
+        import a from "foo";
+        import c from "foo/bar";
+        import d from "foo/barfoo";
+        import b from "foo-bar";
       `,
-      errors: [{
-        ruleId: 'order',
-        message: '`fs` import should occur before import of `async`',
-      }],
+      options: [{ alphabetize: { order: 'asc' } }],
     }),
-    // fix order with windows end of lines
-    test({
-      code:
-        `/* comment0 */  /* comment1 */  var async = require('async'); /* comment2 */` + `\r\n` +
-        `/* comment3 */  var fs = require('fs'); /* comment4 */` + `\r\n`
-      ,
-      output:
-        `/* comment3 */  var fs = require('fs'); /* comment4 */` + `\r\n` +
-        `/* comment0 */  /* comment1 */  var async = require('async'); /* comment2 */` + `\r\n`
-      ,
-      errors: [{
-        ruleId: 'order',
-        message: '`fs` import should occur before import of `async`',
-      }],
-    }),
-    // fix order with multilines comments at the end and start of line
+    // Option alphabetize: {order: 'asc'} and move nested import entries closer to the main import entry
     test({
       code: `
-        /* multiline1
-          comment1 */  var async = require('async'); /* multiline2
-          comment2 */  var fs = require('fs'); /* multiline3
-          comment3 */
-      `,
-      output: `
-        /* multiline1
-          comment1 */  var fs = require('fs');` + ' '  + `
-  var async = require('async'); /* multiline2
-          comment2 *//* multiline3
-          comment3 */
+        import a from "foo";
+        import c from "foo/foobar/bar";
+        import d from "foo/foobar/barfoo";
+        import b from "foo-bar";
       `,
-      errors: [{
-        ruleId: 'order',
-        message: '`fs` import should occur before import of `async`',
-      }],
+      options: [{ alphabetize: { order: 'asc' } }],
     }),
-    // fix destructured commonjs import
+    // Option alphabetize: {order: 'desc'} and move nested import entries closer to the main import entry
     test({
       code: `
-        var {b} = require('async');
-        var {a} = require('fs');
-      `,
-      output: `
-        var {a} = require('fs');
-        var {b} = require('async');
+        import b from "foo-bar";
+        import d from "foo/barfoo";
+        import c from "foo/bar";
+        import a from "foo";
       `,
-      errors: [{
-        ruleId: 'order',
-        message: '`fs` import should occur before import of `async`',
-      }],
+      options: [{ alphabetize: { order: 'desc' } }],
     }),
-    // fix order of multile import
+    // Option alphabetize: {order: 'desc'} and move nested import entries closer to the main import entry with file names having non-alphanumeric characters.
     test({
       code: `
-        var async = require('async');
-        var fs =
-          require('fs');
-      `,
-      output: `
-        var fs =
-          require('fs');
-        var async = require('async');
-      `,
-      errors: [{
-        ruleId: 'order',
-        message: '`fs` import should occur before import of `async`',
+        import b from "foo-bar";
+        import c from "foo,bar";
+        import d from "foo/barfoo";
+        import a from "foo";`,
+      options: [{
+        alphabetize: { order: 'desc' },
       }],
     }),
-    // fix order at the end of file
+    // Option alphabetize with newlines-between: {order: 'asc', newlines-between: 'always'}
     test({
       code: `
-        var async = require('async');
-        var fs = require('fs');`,
-      output: `
-        var fs = require('fs');
-        var async = require('async');` + '\n',
-      errors: [{
-        ruleId: 'order',
-        message: '`fs` import should occur before import of `async`',
+        import b from 'Bar';
+        import c from 'bar';
+        import a from 'foo';
+
+        import index from './';
+      `,
+      options: [{
+        groups: ['external', 'index'],
+        alphabetize: { order: 'asc' },
+        'newlines-between': 'always',
       }],
     }),
-    // builtin before external module (import)
+    // Alphabetize with require
     test({
       code: `
-        import async from 'async';
-        import fs from 'fs';
-      `,
-      output: `
-        import fs from 'fs';
-        import async from 'async';
+        import { hello } from './hello';
+        import { int } from './int';
+        const blah = require('./blah');
+        const { cello } = require('./cello');
       `,
-      errors: [{
-        ruleId: 'order',
-        message: '`fs` import should occur before import of `async`',
-      }],
+      options: [
+        {
+          alphabetize: {
+            order: 'asc',
+          },
+        },
+      ],
     }),
-    // builtin before external module (mixed import and require)
+    // Order of imports with similar names
     test({
       code: `
-        var async = require('async');
-        import fs from 'fs';
+        import React from 'react';
+        import { BrowserRouter } from 'react-router-dom';
       `,
-      output: `
-        import fs from 'fs';
-        var async = require('async');
+      options: [
+        {
+          alphabetize: {
+            order: 'asc',
+          },
+        },
+      ],
+    }),
+    test({
+      code: `
+        import { UserInputError } from 'apollo-server-express';
+
+        import { new as assertNewEmail } from '~/Assertions/Email';
       `,
-      errors: [{
-        ruleId: 'order',
-        message: '`fs` import should occur before import of `async`',
+      options: [{
+        alphabetize: {
+          caseInsensitive: true,
+          order: 'asc',
+        },
+        pathGroups: [
+          { pattern: '~/*', group: 'internal' },
+        ],
+        groups: [
+          'builtin',
+          'external',
+          'internal',
+          'parent',
+          'sibling',
+          'index',
+        ],
+        'newlines-between': 'always',
       }],
     }),
-    // external before parent
     test({
       code: `
-        var parent = require('../parent');
-        var async = require('async');
-      `,
-      output: `
-        var async = require('async');
-        var parent = require('../parent');
+        import { ReactElement, ReactNode } from 'react';
+
+        import { util } from 'Internal/lib';
+
+        import { parent } from '../parent';
+
+        import { sibling } from './sibling';
       `,
-      errors: [{
-        ruleId: 'order',
-        message: '`async` import should occur before import of `../parent`',
+      options: [{
+        alphabetize: {
+          caseInsensitive: true,
+          order: 'asc',
+        },
+        pathGroups: [
+          { pattern: 'Internal/**/*', group: 'internal' },
+        ],
+        groups: [
+          'builtin',
+          'external',
+          'internal',
+          'parent',
+          'sibling',
+          'index',
+        ],
+        'newlines-between': 'always',
+        pathGroupsExcludedImportTypes: [],
       }],
     }),
-    // parent before sibling
+    ...flatMap(getTSParsers, (parser) => [
+      // Order of the `import ... = require(...)` syntax
+      test({
+        code: `
+          import blah = require('./blah');
+          import { hello } from './hello';`,
+        parser,
+        options: [
+          {
+            alphabetize: {
+              order: 'asc',
+            },
+          },
+        ],
+      }),
+      // Order of object-imports
+      test({
+        code: `
+          import blah = require('./blah');
+          import log = console.log;`,
+        parser,
+        options: [
+          {
+            alphabetize: {
+              order: 'asc',
+            },
+          },
+        ],
+      }),
+      // Object-imports should not be forced to be alphabetized
+      test({
+        code: `
+          import debug = console.debug;
+          import log = console.log;`,
+        parser,
+        options: [
+          {
+            alphabetize: {
+              order: 'asc',
+            },
+          },
+        ],
+      }),
+      test({
+        code: `
+          import log = console.log;
+          import debug = console.debug;`,
+        parser,
+        options: [
+          {
+            alphabetize: {
+              order: 'asc',
+            },
+          },
+        ],
+      }),
+      test({
+        code: `
+          import { a } from "./a";
+          export namespace SomeNamespace {
+              export import a2 = a;
+          }
+        `,
+        parser,
+        options: [
+          {
+            groups: ['external', 'index'],
+            alphabetize: { order: 'asc' },
+          },
+        ],
+      }),
+    ]),
+    // Using `@/*` to alias internal modules
     test({
       code: `
-        var sibling = require('./sibling');
-        var parent = require('../parent');
+        import fs from 'fs';
+
+        import express from 'express';
+
+        import service from '@/api/service';
+
+        import fooParent from '../foo';
+
+        import fooSibling from './foo';
+
+        import index from './';
+
+        import internalDoesNotExistSoIsUnknown from '@/does-not-exist';
       `,
-      output: `
-        var parent = require('../parent');
-        var sibling = require('./sibling');
+      options: [
+        {
+          groups: ['builtin', 'external', 'internal', 'parent', 'sibling', 'index', 'unknown'],
+          'newlines-between': 'always',
+        },
+      ],
+      settings: {
+        'import/resolver': {
+          webpack: {
+            config: {
+              resolve: {
+                alias: {
+                  '@': testFilePath('internal-modules'),
+                },
+              },
+            },
+          },
+        },
+      },
+    }),
+    // Option pathGroup[].distinctGroup: 'true' does not prevent 'position' properties from affecting the visible grouping
+    test({
+      code: `
+        import A from 'a';
+
+        import C from 'c';
+
+        import B from 'b';
       `,
-      errors: [{
-        ruleId: 'order',
-        message: '`../parent` import should occur before import of `./sibling`',
-      }],
+      options: [
+        {
+          'newlines-between': 'always',
+          distinctGroup: true,
+          pathGroupsExcludedImportTypes: [],
+          pathGroups: [
+            {
+              pattern: 'a',
+              group: 'external',
+              position: 'before',
+            },
+            {
+              pattern: 'b',
+              group: 'external',
+              position: 'after',
+            },
+          ],
+        },
+      ],
     }),
-    // sibling before index
+    // Option pathGroup[].distinctGroup: 'false' should prevent 'position' properties from affecting the visible grouping
     test({
       code: `
-        var index = require('./');
-        var sibling = require('./sibling');
+        import A from 'a';
+        import C from 'c';
+        import B from 'b';
       `,
-      output: `
-        var sibling = require('./sibling');
-        var index = require('./');
+      options: [
+        {
+          'newlines-between': 'always',
+          distinctGroup: false,
+          pathGroupsExcludedImportTypes: [],
+          pathGroups: [
+            {
+              pattern: 'a',
+              group: 'external',
+              position: 'before',
+            },
+            {
+              pattern: 'b',
+              group: 'external',
+              position: 'after',
+            },
+          ],
+        },
+      ],
+    }),
+    // Option pathGroup[].distinctGroup: 'false' should prevent 'position' properties from affecting the visible grouping 2
+    test({
+      code: `
+        import A from 'a';
+
+        import b from './b';
+        import B from './B';
       `,
-      errors: [{
-        ruleId: 'order',
-        message: '`./sibling` import should occur before import of `./`',
+      options: [
+        {
+          'newlines-between': 'always',
+          distinctGroup: false,
+          pathGroupsExcludedImportTypes: [],
+          pathGroups: [
+            {
+              pattern: 'a',
+              group: 'external',
+            },
+            {
+              pattern: 'b',
+              group: 'internal',
+              position: 'before',
+            },
+          ],
+        },
+      ],
+    }),
+    // Option pathGroup[].distinctGroup: 'false' should prevent 'position' properties from affecting the visible grouping 3
+    test({
+      code: `
+        import A from "baz";
+        import B from "Bar";
+        import C from "Foo";
+
+        import D from "..";
+        import E from "../";
+        import F from "../baz";
+        import G from "../Bar";
+        import H from "../Foo";
+
+        import I from ".";
+        import J from "./baz";
+        import K from "./Bar";
+        import L from "./Foo";
+      `,
+      options: [
+        {
+          alphabetize: {
+            caseInsensitive: false,
+            order: 'asc',
+          },
+          'newlines-between': 'always',
+          groups: [
+            ['builtin', 'external', 'internal', 'unknown', 'object', 'type'],
+            'parent',
+            ['sibling', 'index'],
+          ],
+          distinctGroup: false,
+          pathGroupsExcludedImportTypes: [],
+          pathGroups: [
+            {
+              pattern: './',
+              group: 'sibling',
+              position: 'before',
+            },
+            {
+              pattern: '.',
+              group: 'sibling',
+              position: 'before',
+            },
+            {
+              pattern: '..',
+              group: 'parent',
+              position: 'before',
+            },
+            {
+              pattern: '../',
+              group: 'parent',
+              position: 'before',
+            },
+            {
+              pattern: '[a-z]*',
+              group: 'external',
+              position: 'before',
+            },
+            {
+              pattern: '../[a-z]*',
+              group: 'parent',
+              position: 'before',
+            },
+            {
+              pattern: './[a-z]*',
+              group: 'sibling',
+              position: 'before',
+            },
+          ],
+        },
+      ],
+    }),
+    // orderImportKind option that is not used
+    test({
+      code: `
+        import B from './B';
+        import b from './b';
+      `,
+      options: [
+        {
+          alphabetize: { order: 'asc', orderImportKind: 'asc', caseInsensitive: true },
+        },
+      ],
+    }),
+    // named import order
+    test({
+      code: `
+        import { a, B as C, Z } from './Z';
+        const { D, n: c, Y } = require('./Z');
+        export { C, D };
+        export { A, B, C as default } from "./Z";
+
+        const { ["ignore require-statements with non-identifier imports"]: z, d } = require("./Z");
+        exports = { ["ignore exports statements with non-identifiers"]: Z, D };
+      `,
+      options: [{
+        named: true,
+        alphabetize: { order: 'asc', caseInsensitive: true },
       }],
     }),
-    // Multiple errors
     test({
       code: `
-        var sibling = require('./sibling');
-        var async = require('async');
-        var fs = require('fs');
+        const { b, A } = require('./Z');
       `,
-      errors: [{
-        ruleId: 'order',
-        message: '`async` import should occur before import of `./sibling`',
-      }, {
-        ruleId: 'order',
-        message: '`fs` import should occur before import of `./sibling`',
+      options: [{
+        named: true,
+        alphabetize: { order: 'desc' },
       }],
     }),
-    // Uses 'after' wording if it creates less errors
     test({
       code: `
-        var index = require('./');
-        var fs = require('fs');
-        var path = require('path');
-        var _ = require('lodash');
-        var foo = require('foo');
-        var bar = require('bar');
+        import { A, B } from "./Z";
+        export { Z, A } from "./Z";
+        export { N, P } from "./Z";
+        const { X, Y } = require("./Z");
       `,
-      output: `
-        var fs = require('fs');
-        var path = require('path');
-        var _ = require('lodash');
-        var foo = require('foo');
-        var bar = require('bar');
-        var index = require('./');
+      options: [{
+        named: {
+          require: true,
+          import: true,
+          export: false,
+        },
+      }],
+    }),
+    test({
+      code: `
+        import { B, A } from "./Z";
+        const { D, C } = require("./Z");
+        export { B, A } from "./Z";
       `,
-      errors: [{
-        ruleId: 'order',
-        message: '`./` import should occur after import of `bar`',
+      options: [{
+        named: {
+          require: false,
+          import: false,
+          export: false,
+        },
       }],
     }),
-    // Overriding order to be the reverse of the default order
     test({
       code: `
-        var fs = require('fs');
-        var index = require('./');
+        import { B, A, R } from "foo";
+        const { D, O, G } = require("tunes");
+        export { B, A, Z } from "foo";
       `,
-      output: `
-        var index = require('./');
-        var fs = require('fs');
+      options: [{
+        named: { enabled: false },
+      }],
+    }),
+    test({
+      code: `
+        import { A as A, A as B, A as C } from "./Z";
+        const { a, a: b, a: c } = require("./Z");
       `,
-      options: [{groups: ['index', 'sibling', 'parent', 'external', 'builtin']}],
-      errors: [{
-        ruleId: 'order',
-        message: '`./` import should occur before import of `fs`',
+      options: [{
+        named: true,
       }],
     }),
-    // member expression of require
-    test(withoutAutofixOutput({
+    test({
       code: `
-        var foo = require('./foo').bar;
-        var fs = require('fs');
+        import { A, B, C } from "./Z";
+        exports = { A, B, C };
+        module.exports = { a: A, b: B, c: C };
       `,
-      errors: [{
-        ruleId: 'order',
-        message: '`fs` import should occur before import of `./foo`',
+      options: [{
+        named: {
+          cjsExports: true,
+        },
+        alphabetize: { order: 'asc' },
       }],
-    })),
-    // nested member expression of require
-    test(withoutAutofixOutput({
+    }),
+    test({
       code: `
-        var foo = require('./foo').bar.bar.bar;
-        var fs = require('fs');
+        module.exports.A = { };
+        module.exports.A.B = { };
+        module.exports.B = { };
+        exports.C = { };
       `,
-      errors: [{
-        ruleId: 'order',
-        message: '`fs` import should occur before import of `./foo`',
+      options: [{
+        named: {
+          cjsExports: true,
+        },
+        alphabetize: { order: 'asc' },
       }],
-    })),
-    // fix near nested member expression of require with newlines
-    test(withoutAutofixOutput({
+    }),
+    // ensure other assignments are untouched
+    test({
       code: `
-        var foo = require('./foo').bar
-          .bar
-          .bar;
-        var fs = require('fs');
+        var exports = null;
+        var module = null;
+        exports = { };
+        module = { };
+        module.exports = { };
+        module.exports.U = { };
+        module.exports.N = { };
+        module.exports.C = { };
+        exports.L = { };
+        exports.E = { };
       `,
-      errors: [{
-        ruleId: 'order',
-        message: '`fs` import should occur before import of `./foo`',
+      options: [{
+        named: {
+          cjsExports: true,
+        },
+        alphabetize: { order: 'asc' },
       }],
-    })),
-    // fix nested member expression of require with newlines
-    test(withoutAutofixOutput({
+    }),
+    test({
       code: `
-        var foo = require('./foo');
-        var fs = require('fs').bar
-          .bar
-          .bar;
+        exports["B"] = { };
+        exports["C"] = { };
+        exports["A"] = { };
       `,
-      errors: [{
-        ruleId: 'order',
-        message: '`fs` import should occur before import of `./foo`',
+      options: [{
+        named: {
+          cjsExports: true,
+        },
+        alphabetize: { order: 'asc' },
       }],
-    })),
-    // Grouping import types
+    }),
+  ],
+  invalid: [
+    // builtin before external module (require)
     test({
       code: `
+        var async = require('async');
         var fs = require('fs');
-        var index = require('./');
-        var sibling = require('./foo');
-        var path = require('path');
       `,
       output: `
         var fs = require('fs');
-        var index = require('./');
-        var path = require('path');
-        var sibling = require('./foo');
+        var async = require('async');
       `,
-      options: [{groups: [
-        ['builtin', 'index'],
-        ['sibling', 'parent', 'external'],
-      ]}],
       errors: [{
-        ruleId: 'order',
-        message: '`path` import should occur before import of `./foo`',
+        message: '`fs` import should occur before import of `async`',
       }],
     }),
-    // Omitted types should implicitly be considered as the last type
+    // fix order with spaces on the end of line
     test({
       code: `
-        var path = require('path');
         var async = require('async');
+        var fs = require('fs');${' '}
       `,
       output: `
+        var fs = require('fs');${' '}
         var async = require('async');
-        var path = require('path');
       `,
-      options: [{groups: [
-        'index',
-        ['sibling', 'parent', 'external', 'internal'],
-        // missing 'builtin'
-      ]}],
       errors: [{
-        ruleId: 'order',
-        message: '`async` import should occur before import of `path`',
+        message: '`fs` import should occur before import of `async`',
       }],
     }),
-    // Setting the order for an unknown type
-    // should make the rule trigger an error and do nothing else
+    // fix order with comment on the end of line
     test({
       code: `
         var async = require('async');
-        var index = require('./');
+        var fs = require('fs'); /* comment */
+      `,
+      output: `
+        var fs = require('fs'); /* comment */
+        var async = require('async');
       `,
-      options: [{groups: [
-        'index',
-        ['sibling', 'parent', 'UNKNOWN', 'internal'],
-      ]}],
       errors: [{
-        ruleId: 'order',
-        message: 'Incorrect configuration of the rule: Unknown type `"UNKNOWN"`',
+        message: '`fs` import should occur before import of `async`',
       }],
     }),
-    // Type in an array can't be another array, too much nesting
+    // fix order with comments at the end and start of line
     test({
       code: `
-        var async = require('async');
-        var index = require('./');
+        /* comment1 */  var async = require('async'); /* comment2 */
+        /* comment3 */  var fs = require('fs'); /* comment4 */
+      `,
+      output: `
+        /* comment3 */  var fs = require('fs'); /* comment4 */
+        /* comment1 */  var async = require('async'); /* comment2 */
       `,
-      options: [{groups: [
-        'index',
-        ['sibling', 'parent', ['builtin'], 'internal'],
-      ]}],
       errors: [{
-        ruleId: 'order',
-        message: 'Incorrect configuration of the rule: Unknown type `["builtin"]`',
+        message: '`fs` import should occur before import of `async`',
       }],
     }),
-    // No numbers
+    // fix order with few comments at the end and start of line
     test({
       code: `
-        var async = require('async');
-        var index = require('./');
+        /* comment0 */  /* comment1 */  var async = require('async'); /* comment2 */
+        /* comment3 */  var fs = require('fs'); /* comment4 */
+      `,
+      output: `
+        /* comment3 */  var fs = require('fs'); /* comment4 */
+        /* comment0 */  /* comment1 */  var async = require('async'); /* comment2 */
       `,
-      options: [{groups: [
-        'index',
-        ['sibling', 'parent', 2, 'internal'],
-      ]}],
       errors: [{
-        ruleId: 'order',
-        message: 'Incorrect configuration of the rule: Unknown type `2`',
+        message: '`fs` import should occur before import of `async`',
       }],
     }),
-    // Duplicate
+    // fix order with windows end of lines
+    test({
+      code: `/* comment0 */  /* comment1 */  var async = require('async'); /* comment2 */` + `\r\n` + `/* comment3 */  var fs = require('fs'); /* comment4 */` + `\r\n`,
+      output: `/* comment3 */  var fs = require('fs'); /* comment4 */` + `\r\n` + `/* comment0 */  /* comment1 */  var async = require('async'); /* comment2 */` + `\r\n`,
+      errors: [{
+        message: '`fs` import should occur before import of `async`',
+      }],
+    }),
+    // fix order with multi-lines comments at the end and start of line
     test({
       code: `
-        var async = require('async');
-        var index = require('./');
+        /* multi-line1
+          comment1 */  var async = require('async'); /* multi-line2
+          comment2 */  var fs = require('fs'); /* multi-line3
+          comment3 */
+      `,
+      output: `
+        /* multi-line1
+          comment1 */  var fs = require('fs');` + ' '  + `
+  var async = require('async'); /* multi-line2
+          comment2 *//* multi-line3
+          comment3 */
       `,
-      options: [{groups: [
-        'index',
-        ['sibling', 'parent', 'parent', 'internal'],
-      ]}],
       errors: [{
-        ruleId: 'order',
-        message: 'Incorrect configuration of the rule: `parent` is duplicated',
+        message: '`fs` import should occur before import of `async`',
       }],
     }),
-    // Mixing require and import should have import up top
+    // fix destructured commonjs import
     test({
       code: `
-        import async, {foo1} from 'async';
-        import relParent2, {foo2} from '../foo/bar';
-        var fs = require('fs');
-        var relParent1 = require('../foo');
-        var relParent3 = require('../');
-        import sibling, {foo3} from './foo';
-        var index = require('./');
+        var {b} = require('async');
+        var {a} = require('fs');
       `,
       output: `
-        import async, {foo1} from 'async';
-        import relParent2, {foo2} from '../foo/bar';
-        import sibling, {foo3} from './foo';
-        var fs = require('fs');
-        var relParent1 = require('../foo');
-        var relParent3 = require('../');
-        var index = require('./');
+        var {a} = require('fs');
+        var {b} = require('async');
       `,
       errors: [{
-        ruleId: 'order',
-        message: '`./foo` import should occur before import of `fs`',
+        message: '`fs` import should occur before import of `async`',
       }],
     }),
+    // fix order of multi-line import
     test({
       code: `
-        var fs = require('fs');
-        import async, {foo1} from 'async';
-        import relParent2, {foo2} from '../foo/bar';
+        var async = require('async');
+        var fs =
+          require('fs');
       `,
       output: `
-        import async, {foo1} from 'async';
-        import relParent2, {foo2} from '../foo/bar';
-        var fs = require('fs');
+        var fs =
+          require('fs');
+        var async = require('async');
       `,
       errors: [{
-        ruleId: 'order',
-        message: '`fs` import should occur after import of `../foo/bar`',
+        message: '`fs` import should occur before import of `async`',
       }],
     }),
-    // Default order using import with custom import alias
+    // fix order at the end of file
     test({
       code: `
-        import { Button } from '-/components/Button';
-        import { add } from './helper';
+        var async = require('async');
+        var fs = require('fs');`,
+      output: `
+        var fs = require('fs');
+        var async = require('async');` + '\n',
+      errors: [{
+        message: '`fs` import should occur before import of `async`',
+      }],
+    }),
+    // builtin before external module (import)
+    test({
+      code: `
+        import async from 'async';
         import fs from 'fs';
       `,
       output: `
         import fs from 'fs';
-        import { Button } from '-/components/Button';
-        import { add } from './helper';
+        import async from 'async';
       `,
-      options: [
-        {
-          groups: ['builtin', 'external', 'unknown', 'parent', 'sibling', 'index'],
-        },
-      ],
-      errors: [
-        {
-          line: 4,
-          message: '`fs` import should occur before import of `-/components/Button`',
-        },
-      ],
+      errors: [{
+        message: '`fs` import should occur before import of `async`',
+      }],
     }),
-    // Default order using import with custom import alias
+    // builtin before external module (mixed import and require)
     test({
       code: `
+        var async = require('async');
         import fs from 'fs';
-        import { Button } from '-/components/Button';
-        import { LinkButton } from '-/components/Link';
-        import { add } from './helper';
       `,
       output: `
         import fs from 'fs';
-
-        import { Button } from '-/components/Button';
-        import { LinkButton } from '-/components/Link';
-
-        import { add } from './helper';
+        var async = require('async');
       `,
-      options: [
-        {
-          groups: ['builtin', 'external', 'unknown', 'parent', 'sibling', 'index'],
-          'newlines-between': 'always',
-        },
-      ],
-      errors: [
-        {
-          line: 2,
-          message: 'There should be at least one empty line between import groups',
-        },
-        {
-          line: 4,
-          message: 'There should be at least one empty line between import groups',
-        },
-      ],
+      errors: [{
+        message: '`fs` import should occur before import of `async`',
+      }],
     }),
-    // Option newlines-between: 'never' - should report unnecessary line between groups
+    // external before parent
     test({
       code: `
-        var fs = require('fs');
-        var index = require('./');
-        var path = require('path');
-
-        var sibling = require('./foo');
-
-        var relParent1 = require('../foo');
-        var relParent3 = require('../');
+        var parent = require('../parent');
         var async = require('async');
       `,
       output: `
-        var fs = require('fs');
-        var index = require('./');
-        var path = require('path');
-        var sibling = require('./foo');
-        var relParent1 = require('../foo');
-        var relParent3 = require('../');
         var async = require('async');
+        var parent = require('../parent');
       `,
-      options: [
-        {
-          groups: [
-            ['builtin', 'index'],
-            ['sibling'],
-            ['parent', 'external'],
-          ],
-          'newlines-between': 'never',
-        },
-      ],
-      errors: [
-        {
-          line: 4,
-          message: 'There should be no empty line between import groups',
-        },
-        {
-          line: 6,
-          message: 'There should be no empty line between import groups',
-        },
-      ],
+      errors: [{
+        message: '`async` import should occur before import of `../parent`',
+      }],
     }),
-    // Fix newlines-between with comments after
+    // parent before sibling
     test({
       code: `
-        var fs = require('fs'); /* comment */
-
-        var index = require('./');
+        var sibling = require('./sibling');
+        var parent = require('../parent');
       `,
       output: `
-        var fs = require('fs'); /* comment */
-        var index = require('./');
+        var parent = require('../parent');
+        var sibling = require('./sibling');
       `,
-      options: [
-        {
-          groups: [['builtin'], ['index']],
-          'newlines-between': 'never',
-        },
-      ],
-      errors: [
-        {
-          line: 2,
-          message: 'There should be no empty line between import groups',
-        },
-      ],
+      errors: [{
+        message: '`../parent` import should occur before import of `./sibling`',
+      }],
     }),
-    // Cannot fix newlines-between with multiline comment after
+    // sibling before index
     test({
       code: `
-        var fs = require('fs'); /* multiline
-        comment */
-
         var index = require('./');
+        var sibling = require('./sibling');
       `,
       output: `
-        var fs = require('fs'); /* multiline
-        comment */
-
+        var sibling = require('./sibling');
         var index = require('./');
       `,
-      options: [
-        {
-          groups: [['builtin'], ['index']],
-          'newlines-between': 'never',
-        },
-      ],
-      errors: [
-        {
-          line: 2,
-          message: 'There should be no empty line between import groups',
-        },
-      ],
+      errors: [{
+        message: '`./sibling` import should occur before import of `./`',
+      }],
     }),
-    // Option newlines-between: 'always' - should report lack of newline between groups
+    // Multiple errors
+    ...semver.satisfies(eslintPkg.version, '< 3.0.0') ? [] : [
+      test({
+        code: `
+          var sibling = require('./sibling');
+          var async = require('async');
+          var fs = require('fs');
+        `,
+        output: `
+          var async = require('async');
+          var sibling = require('./sibling');
+          var fs = require('fs');
+        `,
+        errors: [{
+          message: '`async` import should occur before import of `./sibling`',
+        }, {
+          message: '`fs` import should occur before import of `./sibling`',
+        }],
+      }),
+    ],
+    // Uses 'after' wording if it creates less errors
     test({
       code: `
-        var fs = require('fs');
         var index = require('./');
+        var fs = require('fs');
         var path = require('path');
-        var sibling = require('./foo');
-        var relParent1 = require('../foo');
-        var relParent3 = require('../');
-        var async = require('async');
+        var _ = require('lodash');
+        var foo = require('foo');
+        var bar = require('bar');
       `,
       output: `
         var fs = require('fs');
-        var index = require('./');
         var path = require('path');
-
-        var sibling = require('./foo');
-
-        var relParent1 = require('../foo');
-        var relParent3 = require('../');
-        var async = require('async');
-      `,
-      options: [
-        {
-          groups: [
-            ['builtin', 'index'],
-            ['sibling'],
-            ['parent', 'external'],
-          ],
-          'newlines-between': 'always',
-        },
-      ],
-      errors: [
-        {
-          line: 4,
-          message: 'There should be at least one empty line between import groups',
-        },
-        {
-          line: 5,
-          message: 'There should be at least one empty line between import groups',
-        },
-      ],
+        var _ = require('lodash');
+        var foo = require('foo');
+        var bar = require('bar');
+        var index = require('./');
+      `,
+      errors: [{
+        message: '`./` import should occur after import of `bar`',
+      }],
     }),
-    // Option newlines-between: 'always' should report unnecessary empty lines space between import groups
+    // Overriding order to be the reverse of the default order
     test({
       code: `
         var fs = require('fs');
-
-        var path = require('path');
         var index = require('./');
-
-        var sibling = require('./foo');
-
-        var async = require('async');
       `,
       output: `
-        var fs = require('fs');
-        var path = require('path');
         var index = require('./');
-
-        var sibling = require('./foo');
-        var async = require('async');
-      `,
-      options: [
-        {
-          groups: [
-            ['builtin', 'index'],
-            ['sibling', 'parent', 'external'],
-          ],
-          'newlines-between': 'always',
-        },
-      ],
-      errors: [
-        {
-          line: 2,
-          message: 'There should be no empty line within import group',
-        },
-        {
-          line: 7,
-          message: 'There should be no empty line within import group',
-        },
-      ],
-    }),
-    // Option newlines-between: 'never' cannot fix if there are other statements between imports
-    test({
-      code: `
-        import path from 'path';
-        import 'loud-rejection';
-
-        import 'something-else';
-        import _ from 'lodash';
-      `,
-      output: `
-        import path from 'path';
-        import 'loud-rejection';
-
-        import 'something-else';
-        import _ from 'lodash';
-      `,
-      options: [{ 'newlines-between': 'never' }],
-      errors: [
-        {
-          line: 2,
-          message: 'There should be no empty line between import groups',
-        },
-      ],
-    }),
-    // Option newlines-between: 'always' should report missing empty lines when using not assigned imports
-    test({
-      code: `
-        import path from 'path';
-        import 'loud-rejection';
-        import 'something-else';
-        import _ from 'lodash';
-      `,
-      output: `
-        import path from 'path';
-
-        import 'loud-rejection';
-        import 'something-else';
-        import _ from 'lodash';
-      `,
-      options: [{ 'newlines-between': 'always' }],
-      errors: [
-        {
-          line: 2,
-          message: 'There should be at least one empty line between import groups',
-        },
-      ],
-    }),
-    // fix missing empty lines with single line comment after
-    test({
-      code: `
-        import path from 'path'; // comment
-        import _ from 'lodash';
-      `,
-      output: `
-        import path from 'path'; // comment
-
-        import _ from 'lodash';
-      `,
-      options: [{ 'newlines-between': 'always' }],
-      errors: [
-        {
-          line: 2,
-          message: 'There should be at least one empty line between import groups',
-        },
-      ],
-    }),
-    // fix missing empty lines with few line block comment after
-    test({
-      code: `
-        import path from 'path'; /* comment */ /* comment */
-        import _ from 'lodash';
-      `,
-      output: `
-        import path from 'path'; /* comment */ /* comment */
-
-        import _ from 'lodash';
-      `,
-      options: [{ 'newlines-between': 'always' }],
-      errors: [
-        {
-          line: 2,
-          message: 'There should be at least one empty line between import groups',
-        },
-      ],
-    }),
-    // fix missing empty lines with single line block comment after
-    test({
-      code: `
-        import path from 'path'; /* 1
-        2 */
-        import _ from 'lodash';
-      `,
-      output: `
-        import path from 'path';
- /* 1
-        2 */
-        import _ from 'lodash';
+        var fs = require('fs');
       `,
-      options: [{ 'newlines-between': 'always' }],
-      errors: [
-        {
-          line: 2,
-          message: 'There should be at least one empty line between import groups',
-        },
-      ],
+      options: [{ groups: ['index', 'sibling', 'parent', 'external', 'builtin'] }],
+      errors: [{
+        message: '`./` import should occur before import of `fs`',
+      }],
     }),
-
-    // reorder fix cannot cross non import or require
+    // member expression of require
     test(withoutAutofixOutput({
       code: `
-        var async = require('async');
-        fn_call();
+        var foo = require('./foo').bar;
         var fs = require('fs');
       `,
       errors: [{
-        ruleId: 'order',
-        message: '`fs` import should occur before import of `async`',
+        message: '`fs` import should occur before import of `./foo`',
       }],
     })),
-    // reorder cannot cross non plain requires
+    // nested member expression of require
     test(withoutAutofixOutput({
       code: `
-        var async = require('async');
-        var a = require('./value.js')(a);
+        var foo = require('./foo').bar.bar.bar;
         var fs = require('fs');
       `,
       errors: [{
-        ruleId: 'order',
-        message: '`fs` import should occur before import of `async`',
+        message: '`fs` import should occur before import of `./foo`',
       }],
     })),
-    // reorder fixes cannot be applied to non plain requires #1
+    // fix near nested member expression of require with newlines
     test(withoutAutofixOutput({
       code: `
-        var async = require('async');
-        var fs = require('fs')(a);
+        var foo = require('./foo').bar
+          .bar
+          .bar;
+        var fs = require('fs');
       `,
       errors: [{
-        ruleId: 'order',
-        message: '`fs` import should occur before import of `async`',
+        message: '`fs` import should occur before import of `./foo`',
       }],
     })),
-    // reorder fixes cannot be applied to non plain requires #2
+    // fix nested member expression of require with newlines
     test(withoutAutofixOutput({
       code: `
-        var async = require('async')(a);
-        var fs = require('fs');
+        var foo = require('./foo');
+        var fs = require('fs').bar
+          .bar
+          .bar;
       `,
       errors: [{
-        ruleId: 'order',
-        message: '`fs` import should occur before import of `async`',
+        message: '`fs` import should occur before import of `./foo`',
       }],
     })),
-    // cannot require in case of not assignement require
-    test(withoutAutofixOutput({
+    // Grouping import types
+    test({
       code: `
-        var async = require('async');
-        require('./aa');
         var fs = require('fs');
+        var index = require('./');
+        var sibling = require('./foo');
+        var path = require('path');
+      `,
+      output: `
+        var fs = require('fs');
+        var index = require('./');
+        var path = require('path');
+        var sibling = require('./foo');
       `,
+      options: [{ groups: [
+        ['builtin', 'index'],
+        ['sibling', 'parent', 'external'],
+      ] }],
       errors: [{
-        ruleId: 'order',
-        message: '`fs` import should occur before import of `async`',
+        message: '`path` import should occur before import of `./foo`',
       }],
-    })),
-    // reorder cannot cross function call (import statement)
-    test(withoutAutofixOutput({
+    }),
+    // Omitted types should implicitly be considered as the last type
+    test({
       code: `
-        import async from 'async';
-        fn_call();
-        import fs from 'fs';
+        var path = require('path');
+        var async = require('async');
+      `,
+      output: `
+        var async = require('async');
+        var path = require('path');
       `,
+      options: [{ groups: [
+        'index',
+        ['sibling', 'parent', 'external', 'internal'],
+        // missing 'builtin'
+      ] }],
       errors: [{
-        ruleId: 'order',
-        message: '`fs` import should occur before import of `async`',
+        message: '`async` import should occur before import of `path`',
       }],
-    })),
-    // reorder cannot cross variable assignemet (import statement)
-    test(withoutAutofixOutput({
+    }),
+    // Setting the order for an unknown type
+    // should make the rule trigger an error and do nothing else
+    test({
       code: `
-        import async from 'async';
-        var a = 1;
-        import fs from 'fs';
+        var async = require('async');
+        var index = require('./');
       `,
+      options: [{ groups: [
+        'index',
+        ['sibling', 'parent', 'UNKNOWN', 'internal'],
+      ] }],
       errors: [{
-        ruleId: 'order',
-        message: '`fs` import should occur before import of `async`',
+        message: 'Incorrect configuration of the rule: Unknown type `"UNKNOWN"`',
       }],
-    })),
-    // reorder cannot cross non plain requires (import statement)
-    test(withoutAutofixOutput({
+    }),
+    // Type in an array can't be another array, too much nesting
+    test({
       code: `
-        import async from 'async';
-        var a = require('./value.js')(a);
-        import fs from 'fs';
+        var async = require('async');
+        var index = require('./');
       `,
+      options: [{ groups: [
+        'index',
+        ['sibling', 'parent', ['builtin'], 'internal'],
+      ] }],
       errors: [{
-        ruleId: 'order',
-        message: '`fs` import should occur before import of `async`',
+        message: 'Incorrect configuration of the rule: Unknown type `["builtin"]`',
       }],
-    })),
-    // cannot reorder in case of not assignement import
-    test(withoutAutofixOutput({
+    }),
+    // No numbers
+    test({
       code: `
-        import async from 'async';
-        import './aa';
-        import fs from 'fs';
+        var async = require('async');
+        var index = require('./');
       `,
+      options: [{ groups: [
+        'index',
+        ['sibling', 'parent', 2, 'internal'],
+      ] }],
       errors: [{
-        ruleId: 'order',
-        message: '`fs` import should occur before import of `async`',
+        message: 'Incorrect configuration of the rule: Unknown type `2`',
       }],
-    })),
-    ...getTSParsers().map(parser => ({
+    }),
+    // Duplicate
+    test({
       code: `
         var async = require('async');
+        var index = require('./');
+      `,
+      options: [{ groups: [
+        'index',
+        ['sibling', 'parent', 'parent', 'internal'],
+      ] }],
+      errors: [{
+        message: 'Incorrect configuration of the rule: `parent` is duplicated',
+      }],
+    }),
+    // Mixing require and import should have import up top
+    test({
+      code: `
+        import async, {foo1} from 'async';
+        import relParent2, {foo2} from '../foo/bar';
         var fs = require('fs');
+        var relParent1 = require('../foo');
+        var relParent3 = require('../');
+        import sibling, {foo3} from './foo';
+        var index = require('./');
       `,
       output: `
+        import async, {foo1} from 'async';
+        import relParent2, {foo2} from '../foo/bar';
+        import sibling, {foo3} from './foo';
         var fs = require('fs');
-        var async = require('async');
+        var relParent1 = require('../foo');
+        var relParent3 = require('../');
+        var index = require('./');
       `,
-      parser,
       errors: [{
-        ruleId: 'order',
-        message: '`fs` import should occur before import of `async`',
+        message: '`./foo` import should occur before import of `fs`',
+      }],
+    }),
+    test({
+      code: `
+        var fs = require('fs');
+        import async, {foo1} from 'async';
+        import relParent2, {foo2} from '../foo/bar';
+      `,
+      output: `
+        import async, {foo1} from 'async';
+        import relParent2, {foo2} from '../foo/bar';
+        var fs = require('fs');
+      `,
+      errors: [{
+        message: '`fs` import should occur after import of `../foo/bar`',
       }],
+    }),
+    ...flatMap(getTSParsers(), (parser) => [
+      // Order of the `import ... = require(...)` syntax
+      test({
+        code: `
+          var fs = require('fs');
+          import async, {foo1} from 'async';
+          import bar = require("../foo/bar");
+        `,
+        output: `
+          import async, {foo1} from 'async';
+          import bar = require("../foo/bar");
+          var fs = require('fs');
+        `,
+        parser,
+        errors: [{
+          message: '`fs` import should occur after import of `../foo/bar`',
+        }],
+      }),
+      test({
+        code: `
+          var async = require('async');
+          var fs = require('fs');
+        `,
+        output: `
+          var fs = require('fs');
+          var async = require('async');
+        `,
+        parser,
+        errors: [{
+          message: '`fs` import should occur before import of `async`',
+        }],
+      }),
+      test({
+        code: `
+          import sync = require('sync');
+          import async, {foo1} from 'async';
+
+          import index from './';
+        `,
+        output: `
+          import async, {foo1} from 'async';
+          import sync = require('sync');
+
+          import index from './';
+        `,
+        options: [{
+          groups: ['external', 'index'],
+          alphabetize: { order: 'asc' },
+        }],
+        parser,
+        errors: [{
+          message: '`async` import should occur before import of `sync`',
+        }],
+      }),
+      // Order of object-imports
+      test({
+        code: `
+          import log = console.log;
+          import blah = require('./blah');`,
+        parser,
+        errors: [{
+          message: '`./blah` import should occur before import of `console.log`',
+        }],
+      }),
+    ]),
+    // Default order using import with custom import alias
+    test({
+      code: `
+        import { Button } from '-/components/Button';
+        import { add } from './helper';
+        import fs from 'fs';
+      `,
+      output: `
+        import fs from 'fs';
+        import { Button } from '-/components/Button';
+        import { add } from './helper';
+      `,
+      options: [
+        {
+          groups: ['builtin', 'external', 'unknown', 'parent', 'sibling', 'index'],
+        },
+      ],
+      errors: [
+        {
+          line: 4,
+          message: '`fs` import should occur before import of `-/components/Button`',
+        },
+      ],
+    }),
+    // Default order using import with custom import alias
+    test({
+      code: `
+        import fs from 'fs';
+        import { Button } from '-/components/Button';
+        import { LinkButton } from '-/components/Link';
+        import { add } from './helper';
+      `,
+      output: `
+        import fs from 'fs';
+
+        import { Button } from '-/components/Button';
+        import { LinkButton } from '-/components/Link';
+
+        import { add } from './helper';
+      `,
+      options: [
+        {
+          groups: ['builtin', 'external', 'unknown', 'parent', 'sibling', 'index'],
+          'newlines-between': 'always',
+        },
+      ],
+      errors: [
+        {
+          line: 2,
+          message: 'There should be at least one empty line between import groups',
+        },
+        {
+          line: 4,
+          message: 'There should be at least one empty line between import groups',
+        },
+      ],
+    }),
+    // Option newlines-between: 'never' - should report unnecessary line between groups
+    test({
+      code: `
+        var fs = require('fs');
+        var index = require('./');
+        var path = require('path');
+
+        var sibling = require('./foo');
+
+        var relParent1 = require('../foo');
+        var relParent3 = require('../');
+        var async = require('async');
+      `,
+      output: `
+        var fs = require('fs');
+        var index = require('./');
+        var path = require('path');
+        var sibling = require('./foo');
+        var relParent1 = require('../foo');
+        var relParent3 = require('../');
+        var async = require('async');
+      `,
+      options: [
+        {
+          groups: [
+            ['builtin', 'index'],
+            ['sibling'],
+            ['parent', 'external'],
+          ],
+          'newlines-between': 'never',
+        },
+      ],
+      errors: [
+        {
+          line: 4,
+          message: 'There should be no empty line between import groups',
+        },
+        {
+          line: 6,
+          message: 'There should be no empty line between import groups',
+        },
+      ],
+    }),
+    // Fix newlines-between with comments after
+    test({
+      code: `
+        var fs = require('fs'); /* comment */
+
+        var index = require('./');
+      `,
+      output: `
+        var fs = require('fs'); /* comment */
+        var index = require('./');
+      `,
+      options: [
+        {
+          groups: [['builtin'], ['index']],
+          'newlines-between': 'never',
+        },
+      ],
+      errors: [
+        {
+          line: 2,
+          message: 'There should be no empty line between import groups',
+        },
+      ],
+    }),
+    // Cannot fix newlines-between with multi-line comment after
+    test(withoutAutofixOutput({
+      code: `
+        var fs = require('fs'); /* multi-line
+        comment */
+
+        var index = require('./');
+      `,
+      options: [
+        {
+          groups: [['builtin'], ['index']],
+          'newlines-between': 'never',
+        },
+      ],
+      errors: [
+        {
+          line: 2,
+          message: 'There should be no empty line between import groups',
+        },
+      ],
     })),
-  ].filter((t) => !!t),
-})
+    // Option newlines-between: 'always' - should report lack of newline between groups
+    test({
+      code: `
+        var fs = require('fs');
+        var index = require('./');
+        var path = require('path');
+        var sibling = require('./foo');
+        var relParent1 = require('../foo');
+        var relParent3 = require('../');
+        var async = require('async');
+      `,
+      output: `
+        var fs = require('fs');
+        var index = require('./');
+        var path = require('path');
+
+        var sibling = require('./foo');
+
+        var relParent1 = require('../foo');
+        var relParent3 = require('../');
+        var async = require('async');
+      `,
+      options: [
+        {
+          groups: [
+            ['builtin', 'index'],
+            ['sibling'],
+            ['parent', 'external'],
+          ],
+          'newlines-between': 'always',
+        },
+      ],
+      errors: [
+        {
+          line: 4,
+          message: 'There should be at least one empty line between import groups',
+        },
+        {
+          line: 5,
+          message: 'There should be at least one empty line between import groups',
+        },
+      ],
+    }),
+    // Option newlines-between: 'always' should report unnecessary empty lines space between import groups
+    test({
+      code: `
+        var fs = require('fs');
+
+        var path = require('path');
+        var index = require('./');
+
+        var sibling = require('./foo');
+
+        var async = require('async');
+      `,
+      output: `
+        var fs = require('fs');
+        var path = require('path');
+        var index = require('./');
+
+        var sibling = require('./foo');
+        var async = require('async');
+      `,
+      options: [
+        {
+          groups: [
+            ['builtin', 'index'],
+            ['sibling', 'parent', 'external'],
+          ],
+          'newlines-between': 'always',
+        },
+      ],
+      errors: [
+        {
+          line: 2,
+          message: 'There should be no empty line within import group',
+        },
+        {
+          line: 7,
+          message: 'There should be no empty line within import group',
+        },
+      ],
+    }),
+    // Option newlines-between: 'never' with unassigned imports and warnOnUnassignedImports disabled
+    // newline is preserved to match existing behavior
+    test(withoutAutofixOutput({
+      code: `
+        import path from 'path';
+        import 'loud-rejection';
+
+        import 'something-else';
+        import _ from 'lodash';
+      `,
+      options: [{ 'newlines-between': 'never', warnOnUnassignedImports: false }],
+      errors: [
+        {
+          line: 2,
+          message: 'There should be no empty line between import groups',
+        },
+      ],
+    })),
+    // Option newlines-between: 'never' with unassigned imports and warnOnUnassignedImports enabled
+    test({
+      code: `
+        import path from 'path';
+        import 'loud-rejection';
+
+        import 'something-else';
+        import _ from 'lodash';
+      `,
+      output: `
+        import path from 'path';
+        import 'loud-rejection';
+        import 'something-else';
+        import _ from 'lodash';
+      `,
+      options: [{ 'newlines-between': 'never', warnOnUnassignedImports: true }],
+      errors: [
+        {
+          line: 3,
+          message: 'There should be no empty line between import groups',
+        },
+      ],
+    }),
+    // Option newlines-between: 'never' cannot fix if there are other statements between imports
+    test(withoutAutofixOutput({
+      code: `
+        import path from 'path';
+        export const abc = 123;
+
+        import 'something-else';
+        import _ from 'lodash';
+      `,
+      options: [{ 'newlines-between': 'never' }],
+      errors: [
+        {
+          line: 2,
+          message: 'There should be no empty line between import groups',
+        },
+      ],
+    })),
+    // Option newlines-between: 'always' should report missing empty lines when using not assigned imports
+    test({
+      code: `
+        import path from 'path';
+        import 'loud-rejection';
+        import 'something-else';
+        import _ from 'lodash';
+      `,
+      output: `
+        import path from 'path';
+
+        import 'loud-rejection';
+        import 'something-else';
+        import _ from 'lodash';
+      `,
+      options: [{ 'newlines-between': 'always' }],
+      errors: [
+        {
+          line: 2,
+          message: 'There should be at least one empty line between import groups',
+        },
+      ],
+    }),
+    // fix missing empty lines with single line comment after
+    test({
+      code: `
+        import path from 'path'; // comment
+        import _ from 'lodash';
+      `,
+      output: `
+        import path from 'path'; // comment
+
+        import _ from 'lodash';
+      `,
+      options: [{ 'newlines-between': 'always' }],
+      errors: [
+        {
+          line: 2,
+          message: 'There should be at least one empty line between import groups',
+        },
+      ],
+    }),
+    // fix missing empty lines with few line block comment after
+    test({
+      code: `
+        import path from 'path'; /* comment */ /* comment */
+        import _ from 'lodash';
+      `,
+      output: `
+        import path from 'path'; /* comment */ /* comment */
+
+        import _ from 'lodash';
+      `,
+      options: [{ 'newlines-between': 'always' }],
+      errors: [
+        {
+          line: 2,
+          message: 'There should be at least one empty line between import groups',
+        },
+      ],
+    }),
+    // fix missing empty lines with single line block comment after
+    test({
+      code: `
+        import path from 'path'; /* 1
+        2 */
+        import _ from 'lodash';
+      `,
+      output: `
+        import path from 'path';
+ /* 1
+        2 */
+        import _ from 'lodash';
+      `,
+      options: [{ 'newlines-between': 'always' }],
+      errors: [
+        {
+          line: 2,
+          message: 'There should be at least one empty line between import groups',
+        },
+      ],
+    }),
+    // reorder fix cannot cross function call on moving below #1
+    test(withoutAutofixOutput({
+      code: `
+        const local = require('./local');
+
+        fn_call();
+
+        const global1 = require('global1');
+        const global2 = require('global2');
+
+        fn_call();
+      `,
+      errors: [{
+        message: '`./local` import should occur after import of `global2`',
+      }],
+    })),
+    // reorder fix cannot cross function call on moving below #2
+    test(withoutAutofixOutput({
+      code: `
+        const local = require('./local');
+        fn_call();
+        const global1 = require('global1');
+        const global2 = require('global2');
+
+        fn_call();
+      `,
+      errors: [{
+        message: '`./local` import should occur after import of `global2`',
+      }],
+    })),
+    // reorder fix cannot cross function call on moving below #3
+    test(withoutAutofixOutput({
+      code: `
+        const local1 = require('./local1');
+        const local2 = require('./local2');
+        const local3 = require('./local3');
+        const local4 = require('./local4');
+        fn_call();
+        const global1 = require('global1');
+        const global2 = require('global2');
+        const global3 = require('global3');
+        const global4 = require('global4');
+        const global5 = require('global5');
+        fn_call();
+      `,
+      errors: [
+        '`./local1` import should occur after import of `global5`',
+        '`./local2` import should occur after import of `global5`',
+        '`./local3` import should occur after import of `global5`',
+        '`./local4` import should occur after import of `global5`',
+      ],
+    })),
+    // reorder fix cannot cross function call on moving below
+    test(withoutAutofixOutput({
+      code: `
+        const local = require('./local');
+        const global1 = require('global1');
+        const global2 = require('global2');
+        fn_call();
+        const global3 = require('global3');
+
+        fn_call();
+      `,
+      errors: [{
+        message: '`./local` import should occur after import of `global3`',
+      }],
+    })),
+    // reorder fix cannot cross function call on moving below
+    // fix imports that not crosses function call only
+    test({
+      code: `
+        const local1 = require('./local1');
+        const global1 = require('global1');
+        const global2 = require('global2');
+        fn_call();
+        const local2 = require('./local2');
+        const global3 = require('global3');
+        const global4 = require('global4');
+
+        fn_call();
+      `,
+      output: `
+        const local1 = require('./local1');
+        const global1 = require('global1');
+        const global2 = require('global2');
+        fn_call();
+        const global3 = require('global3');
+        const global4 = require('global4');
+        const local2 = require('./local2');
+
+        fn_call();
+      `,
+      errors: [
+        '`./local1` import should occur after import of `global4`',
+        '`./local2` import should occur after import of `global4`',
+      ],
+    }),
+    // pathGroup with position 'after'
+    test({
+      code: `
+        import fs from 'fs';
+        import _ from 'lodash';
+        import { add } from './helper';
+        import { Input } from '~/components/Input';
+        `,
+      output: `
+        import fs from 'fs';
+        import _ from 'lodash';
+        import { Input } from '~/components/Input';
+        import { add } from './helper';
+        `,
+      options: [{
+        pathGroups: [
+          { pattern: '~/**', group: 'external', position: 'after' },
+        ],
+      }],
+      errors: [{
+        message: '`~/components/Input` import should occur before import of `./helper`',
+      }],
+    }),
+    // pathGroup without position
+    test({
+      code: `
+        import fs from 'fs';
+        import _ from 'lodash';
+        import { add } from './helper';
+        import { Input } from '~/components/Input';
+        import async from 'async';
+        `,
+      output: `
+        import fs from 'fs';
+        import _ from 'lodash';
+        import { Input } from '~/components/Input';
+        import async from 'async';
+        import { add } from './helper';
+        `,
+      options: [{
+        pathGroups: [
+          { pattern: '~/**', group: 'external' },
+        ],
+      }],
+      errors: [{
+        message: '`./helper` import should occur after import of `async`',
+      }],
+    }),
+    // pathGroup with position 'before'
+    test({
+      code: `
+        import fs from 'fs';
+        import _ from 'lodash';
+        import { add } from './helper';
+        import { Input } from '~/components/Input';
+        `,
+      output: `
+        import fs from 'fs';
+        import { Input } from '~/components/Input';
+        import _ from 'lodash';
+        import { add } from './helper';
+        `,
+      options: [{
+        pathGroups: [
+          { pattern: '~/**', group: 'external', position: 'before' },
+        ],
+      }],
+      errors: [{
+        message: '`~/components/Input` import should occur before import of `lodash`',
+      }],
+    }),
+    // multiple pathGroup with different positions for same group, fix for 'after'
+    test({
+      code: `
+        import fs from 'fs';
+        import { Import } from '$/components/Import';
+        import _ from 'lodash';
+        import { Output } from '~/components/Output';
+        import { Input } from '#/components/Input';
+        import { add } from './helper';
+        import { Export } from '-/components/Export';
+        `,
+      output: `
+        import fs from 'fs';
+        import { Export } from '-/components/Export';
+        import { Import } from '$/components/Import';
+        import _ from 'lodash';
+        import { Output } from '~/components/Output';
+        import { Input } from '#/components/Input';
+        import { add } from './helper';
+        `,
+      options: [{
+        pathGroups: [
+          { pattern: '~/**', group: 'external', position: 'after' },
+          { pattern: '#/**', group: 'external', position: 'after' },
+          { pattern: '-/**', group: 'external', position: 'before' },
+          { pattern: '$/**', group: 'external', position: 'before' },
+        ],
+      }],
+      errors: [
+        {
+          message: '`-/components/Export` import should occur before import of `$/components/Import`',
+        },
+      ],
+    }),
+
+    // multiple pathGroup with different positions for same group, fix for 'before'
+    test({
+      code: `
+        import fs from 'fs';
+        import { Export } from '-/components/Export';
+        import { Import } from '$/components/Import';
+        import _ from 'lodash';
+        import { Input } from '#/components/Input';
+        import { add } from './helper';
+        import { Output } from '~/components/Output';
+        `,
+      output: `
+        import fs from 'fs';
+        import { Export } from '-/components/Export';
+        import { Import } from '$/components/Import';
+        import _ from 'lodash';
+        import { Output } from '~/components/Output';
+        import { Input } from '#/components/Input';
+        import { add } from './helper';
+        `,
+      options: [{
+        pathGroups: [
+          { pattern: '~/**', group: 'external', position: 'after' },
+          { pattern: '#/**', group: 'external', position: 'after' },
+          { pattern: '-/**', group: 'external', position: 'before' },
+          { pattern: '$/**', group: 'external', position: 'before' },
+        ],
+      }],
+      errors: [
+        {
+          message: '`~/components/Output` import should occur before import of `#/components/Input`',
+        },
+      ],
+    }),
+
+    // pathGroups overflowing to previous/next groups
+    test({
+      code: `
+        import path from 'path';
+        import { namespace } from '@namespace';
+        import { a } from 'a';
+        import { b } from 'b';
+        import { c } from 'c';
+        import { d } from 'd';
+        import { e } from 'e';
+        import { f } from 'f';
+        import { g } from 'g';
+        import { h } from 'h';
+        import { i } from 'i';
+        import { j } from 'j';
+        import { k } from 'k';`,
+      output: `
+        import path from 'path';
+
+        import { namespace } from '@namespace';
+
+        import { a } from 'a';
+
+        import { b } from 'b';
+
+        import { c } from 'c';
+
+        import { d } from 'd';
+
+        import { e } from 'e';
+
+        import { f } from 'f';
+
+        import { g } from 'g';
+
+        import { h } from 'h';
+
+        import { i } from 'i';
+
+        import { j } from 'j';
+        import { k } from 'k';`,
+      options: [
+        {
+          groups: [
+            'builtin',
+            'external',
+            'internal',
+          ],
+          pathGroups: [
+            { pattern: '@namespace', group: 'external', position: 'after' },
+            { pattern: 'a', group: 'internal', position: 'before' },
+            { pattern: 'b', group: 'internal', position: 'before' },
+            { pattern: 'c', group: 'internal', position: 'before' },
+            { pattern: 'd', group: 'internal', position: 'before' },
+            { pattern: 'e', group: 'internal', position: 'before' },
+            { pattern: 'f', group: 'internal', position: 'before' },
+            { pattern: 'g', group: 'internal', position: 'before' },
+            { pattern: 'h', group: 'internal', position: 'before' },
+            { pattern: 'i', group: 'internal', position: 'before' },
+          ],
+          'newlines-between': 'always',
+          pathGroupsExcludedImportTypes: ['builtin'],
+        },
+      ],
+      settings: {
+        'import/internal-regex': '^(a|b|c|d|e|f|g|h|i|j|k)(\\/|$)',
+      },
+      errors: Array.from({ length: 11 }, () => 'There should be at least one empty line between import groups'),
+    }),
+
+    // rankings that overflow to double-digit ranks
+    test({
+      code: `
+        import external from 'external';
+        import a from '@namespace/a';
+        import b from '@namespace/b';
+        import { parent } from '../../parent';
+        import local from './local';
+        import './side-effect';`,
+      output: `
+        import external from 'external';
+
+        import a from '@namespace/a';
+        import b from '@namespace/b';
+
+        import { parent } from '../../parent';
+
+        import local from './local';
+        import './side-effect';`,
+      options: [
+        {
+          alphabetize: {
+            order: 'asc',
+            caseInsensitive: true,
+          },
+          groups: ['type', 'builtin', 'external', 'internal', 'parent', 'sibling', 'index', 'object'],
+          'newlines-between': 'always',
+          pathGroups: [
+            { pattern: '@namespace', group: 'external', position: 'after' },
+            { pattern: '@namespace/**', group: 'external', position: 'after' },
+          ],
+          pathGroupsExcludedImportTypes: ['@namespace'],
+        },
+      ],
+      errors: [
+        'There should be at least one empty line between import groups',
+        'There should be at least one empty line between import groups',
+        'There should be at least one empty line between import groups',
+      ],
+    }),
+
+    // reorder fix cannot cross non import or require
+    test(withoutAutofixOutput({
+      code: `
+        var async = require('async');
+        fn_call();
+        var fs = require('fs');
+      `,
+      errors: [{
+        message: '`fs` import should occur before import of `async`',
+      }],
+    })),
+    // reorder fix cannot cross function call on moving below (from #1252)
+    test(withoutAutofixOutput({
+      code: `
+        const env = require('./config');
+
+        Object.keys(env);
+
+        const http = require('http');
+        const express = require('express');
+
+        http.createServer(express());
+      `,
+      errors: [{
+        message: '`./config` import should occur after import of `express`',
+      }],
+    })),
+    // reorder cannot cross non plain requires
+    test(withoutAutofixOutput({
+      code: `
+        var async = require('async');
+        var a = require('./value.js')(a);
+        var fs = require('fs');
+      `,
+      errors: [{
+        message: '`fs` import should occur before import of `async`',
+      }],
+    })),
+    // reorder fixes cannot be applied to non plain requires #1
+    test(withoutAutofixOutput({
+      code: `
+        var async = require('async');
+        var fs = require('fs')(a);
+      `,
+      errors: [{
+        message: '`fs` import should occur before import of `async`',
+      }],
+    })),
+    // reorder fixes cannot be applied to non plain requires #2
+    test(withoutAutofixOutput({
+      code: `
+        var async = require('async')(a);
+        var fs = require('fs');
+      `,
+      errors: [{
+        message: '`fs` import should occur before import of `async`',
+      }],
+    })),
+    // cannot require in case of not assignment require
+    test(withoutAutofixOutput({
+      code: `
+        var async = require('async');
+        require('./aa');
+        var fs = require('fs');
+      `,
+      errors: [{
+        message: '`fs` import should occur before import of `async`',
+      }],
+    })),
+    // reorder cannot cross function call (import statement)
+    test(withoutAutofixOutput({
+      code: `
+        import async from 'async';
+        fn_call();
+        import fs from 'fs';
+      `,
+      errors: [{
+        message: '`fs` import should occur before import of `async`',
+      }],
+    })),
+    // reorder cannot cross variable assignment (import statement)
+    test(withoutAutofixOutput({
+      code: `
+        import async from 'async';
+        var a = 1;
+        import fs from 'fs';
+      `,
+      errors: [{
+        message: '`fs` import should occur before import of `async`',
+      }],
+    })),
+    // reorder cannot cross non plain requires (import statement)
+    test(withoutAutofixOutput({
+      code: `
+        import async from 'async';
+        var a = require('./value.js')(a);
+        import fs from 'fs';
+      `,
+      errors: [{
+        message: '`fs` import should occur before import of `async`',
+      }],
+    })),
+    // cannot reorder in case of not assignment import
+    test(withoutAutofixOutput({
+      code: `
+        import async from 'async';
+        import './aa';
+        import fs from 'fs';
+      `,
+      errors: [{
+        message: '`fs` import should occur before import of `async`',
+      }],
+    })),
+    // Option alphabetize: {order: 'asc'}
+    test({
+      code: `
+        import b from 'bar';
+        import c from 'Bar';
+        import a from 'foo';
+
+        import index from './';
+      `,
+      output: `
+        import c from 'Bar';
+        import b from 'bar';
+        import a from 'foo';
+
+        import index from './';
+      `,
+      options: [{
+        groups: ['external', 'index'],
+        alphabetize: { order: 'asc' },
+      }],
+      errors: [{
+        message: '`Bar` import should occur before import of `bar`',
+      }],
+    }),
+    // Option alphabetize: {order: 'desc'}
+    test({
+      code: `
+        import a from 'foo';
+        import c from 'Bar';
+        import b from 'bar';
+
+        import index from './';
+      `,
+      output: `
+        import a from 'foo';
+        import b from 'bar';
+        import c from 'Bar';
+
+        import index from './';
+      `,
+      options: [{
+        groups: ['external', 'index'],
+        alphabetize: { order: 'desc' },
+      }],
+      errors: [{
+        message: '`bar` import should occur before import of `Bar`',
+      }],
+    }),
+    // Option alphabetize: {order: 'asc'} and move nested import entries closer to the main import entry
+    test({
+      code: `
+        import a from "foo";
+        import b from "foo-bar";
+        import c from "foo/bar";
+        import d from "foo/barfoo";
+      `,
+      options: [{
+        alphabetize: { order: 'asc' },
+      }],
+      output: `
+        import a from "foo";
+        import c from "foo/bar";
+        import d from "foo/barfoo";
+        import b from "foo-bar";
+      `,
+      errors: [{
+        message: '`foo-bar` import should occur after import of `foo/barfoo`',
+      }],
+    }),
+    // Option alphabetize {order: 'asc': caseInsensitive: true}
+    test({
+      code: `
+        import b from 'foo';
+        import a from 'Bar';
+
+        import index from './';
+      `,
+      output: `
+        import a from 'Bar';
+        import b from 'foo';
+
+        import index from './';
+      `,
+      options: [{
+        groups: ['external', 'index'],
+        alphabetize: { order: 'asc', caseInsensitive: true },
+      }],
+      errors: [{
+        message: '`Bar` import should occur before import of `foo`',
+      }],
+    }),
+    // Option alphabetize {order: 'desc': caseInsensitive: true}
+    test({
+      code: `
+        import a from 'Bar';
+        import b from 'foo';
+
+        import index from './';
+      `,
+      output: `
+        import b from 'foo';
+        import a from 'Bar';
+
+        import index from './';
+      `,
+      options: [{
+        groups: ['external', 'index'],
+        alphabetize: { order: 'desc', caseInsensitive: true },
+      }],
+      errors: [{
+        message: '`foo` import should occur before import of `Bar`',
+      }],
+    }),
+    // Option alphabetize {order: 'asc'} and require with member expression
+    test({
+      code: `
+        const b = require('./b').get();
+        const a = require('./a');
+      `,
+      output: `
+        const a = require('./a');
+        const b = require('./b').get();
+      `,
+      options: [{
+        alphabetize: { order: 'asc' },
+      }],
+      errors: [{
+        message: '`./a` import should occur before import of `./b`',
+      }],
+    }),
+    // Alphabetize with parent paths
+    test({
+      code: `
+        import a from '../a';
+        import p from '..';
+      `,
+      output: `
+        import p from '..';
+        import a from '../a';
+      `,
+      options: [{
+        groups: ['external', 'index'],
+        alphabetize: { order: 'asc' },
+      }],
+      errors: [{
+        message: '`..` import should occur before import of `../a`',
+      }],
+    }),
+    // Option pathGroup[].distinctGroup: 'false' should error when newlines are incorrect 2
+    test({
+      code: `
+        import A from 'a';
+        import C from './c';
+      `,
+      output: `
+        import A from 'a';
+
+        import C from './c';
+      `,
+      options: [
+        {
+          'newlines-between': 'always',
+          distinctGroup: false,
+          pathGroupsExcludedImportTypes: [],
+        },
+      ],
+      errors: [{
+        message: 'There should be at least one empty line between import groups',
+      }],
+    }),
+    // Option pathGroup[].distinctGroup: 'false' should error when newlines are incorrect 2
+    test({
+      code: `
+        import A from 'a';
+
+        import C from 'c';
+      `,
+      output: `
+        import A from 'a';
+        import C from 'c';
+      `,
+      options: [
+        {
+          'newlines-between': 'always',
+          distinctGroup: false,
+          pathGroupsExcludedImportTypes: [],
+          pathGroups: [
+            {
+              pattern: 'a',
+              group: 'external',
+              position: 'before',
+            },
+            {
+              pattern: 'c',
+              group: 'external',
+              position: 'after',
+            },
+          ],
+        },
+      ],
+      errors: [{
+        message: 'There should be no empty line within import group',
+      }],
+    }),
+    // named import order
+    test({
+      code: `
+        var { B, A: R } = require("./Z");
+        import { O as G, D } from "./Z";
+        import { K, L, J } from "./Z";
+        export { Z, X, Y } from "./Z";
+      `,
+      output: `
+        var { A: R, B } = require("./Z");
+        import { D, O as G } from "./Z";
+        import { J, K, L } from "./Z";
+        export { X, Y, Z } from "./Z";
+      `,
+      options: [{
+        named: true,
+        alphabetize: { order: 'asc' },
+      }],
+      errors: [{
+        message: '`A` import should occur before import of `B`',
+      }, {
+        message: '`D` import should occur before import of `O`',
+      }, {
+        message: '`J` import should occur before import of `K`',
+      }, {
+        message: '`Z` export should occur after export of `Y`',
+      }],
+    }),
+    test({
+      code: `
+        import { D, C } from "./Z";
+        var { B, A } = require("./Z");
+        export { B, A };
+      `,
+      output: `
+        import { C, D } from "./Z";
+        var { B, A } = require("./Z");
+        export { A, B };
+      `,
+      options: [{
+        named: {
+          require: false,
+          import: true,
+          export: true,
+        },
+        alphabetize: { order: 'asc' },
+      }],
+      errors: [{
+        message: '`C` import should occur before import of `D`',
+      }, {
+        message: '`A` export should occur before export of `B`',
+      }],
+    }),
+    test({
+      code: `
+        import { A as B, A as C, A } from "./Z";
+        export { A, A as D, A as B, A as C } from "./Z";
+        const { a: b, a: c, a } = require("./Z");
+      `,
+      output: `
+        import { A, A as B, A as C } from "./Z";
+        export { A, A as B, A as C, A as D } from "./Z";
+        const { a, a: b, a: c } = require("./Z");
+      `,
+      options: [{
+        named: true,
+        alphabetize: { order: 'asc' },
+      }],
+      errors: [{
+        message: '`A` import should occur before import of `A as B`',
+      }, {
+        message: '`A as D` export should occur after export of `A as C`',
+      }, {
+        message: '`a` import should occur before import of `a as b`',
+      }],
+    }),
+    test({
+      code: `
+        import { A, B, C } from "./Z";
+        exports = { B, C, A };
+        module.exports = { c: C, a: A, b: B };
+      `,
+      output: `
+        import { A, B, C } from "./Z";
+        exports = { A, B, C };
+        module.exports = { a: A, b: B, c: C };
+      `,
+      options: [{
+        named: {
+          cjsExports: true,
+        },
+        alphabetize: { order: 'asc' },
+      }],
+      errors: [{
+        message: '`A` export should occur before export of `B`',
+      }, {
+        message: '`c` export should occur after export of `b`',
+      }],
+    }),
+    test({
+      code: `
+        exports.B = { };
+        module.exports.A = { };
+        module.exports.C = { };
+      `,
+      output: `
+        module.exports.A = { };
+        exports.B = { };
+        module.exports.C = { };
+      `,
+      options: [{
+        named: {
+          cjsExports: true,
+        },
+        alphabetize: { order: 'asc' },
+      }],
+      errors: [{
+        message: '`A` export should occur before export of `B`',
+      }],
+    }),
+    test({
+      code: `
+        exports.A.C = { };
+        module.exports.A.A = { };
+        exports.A.B = { };
+      `,
+      output: `
+        module.exports.A.A = { };
+        exports.A.B = { };
+        exports.A.C = { };
+      `,
+      options: [{
+        named: {
+          cjsExports: true,
+        },
+        alphabetize: { order: 'asc' },
+      }],
+      errors: [{
+        message: '`A.C` export should occur after export of `A.B`',
+      }],
+    }),
+    // multi-line named specifiers & trailing commas
+    test({
+      code: `
+        const {
+          F: O,
+          O: B,
+          /* Hello World */
+          A: R
+        } = require("./Z");
+        import {
+          Y,
+          X,
+        } from "./Z";
+        export {
+          Z, A,
+          B
+        } from "./Z";
+        module.exports = {
+          a: A, o: O,
+          b: B
+        };
+      `,
+      output: `
+        const {
+          /* Hello World */
+          A: R,
+          F: O,
+          O: B
+        } = require("./Z");
+        import {
+          X,
+          Y,
+        } from "./Z";
+        export { A,
+          B,
+          Z
+        } from "./Z";
+        module.exports = {
+          a: A,
+          b: B, o: O
+        };
+      `,
+      options: [{
+        named: {
+          enabled: true,
+        },
+        alphabetize: { order: 'asc' },
+      }],
+      errors: [{
+        message: '`A` import should occur before import of `F`',
+      }, {
+        message: '`X` import should occur before import of `Y`',
+      }, {
+        message: '`Z` export should occur after export of `B`',
+      }, {
+        message: '`b` export should occur before export of `o`',
+      }],
+    }),
+    // Alphabetize with require
+    ...semver.satisfies(eslintPkg.version, '< 3.0.0') ? [] : [
+      test({
+        code: `
+          const { cello } = require('./cello');
+          import { int } from './int';
+          const blah = require('./blah');
+          import { hello } from './hello';
+        `,
+        output: `
+          import { int } from './int';
+          const { cello } = require('./cello');
+          const blah = require('./blah');
+          import { hello } from './hello';
+        `,
+        errors: [{
+          message: '`./int` import should occur before import of `./cello`',
+        }, {
+          message: '`./hello` import should occur before import of `./cello`',
+        }],
+      }),
+    ],
+    // Option newlines-between: 'always-and-inside-groups' and consolidateIslands: true
+    test({
+      code: `
+        var fs = require('fs');
+        var path = require('path');
+        var { util1, util2, util3 } = require('util');
+        var async = require('async');
+        var relParent1 = require('../foo');
+        var {
+          relParent21,
+          relParent22,
+          relParent23,
+          relParent24,
+        } = require('../');
+        var relParent3 = require('../bar');
+        var { sibling1,
+          sibling2, sibling3 } = require('./foo');
+        var sibling2 = require('./bar');
+        var sibling3 = require('./foobar');
+      `,
+      output: `
+        var fs = require('fs');
+        var path = require('path');
+        var { util1, util2, util3 } = require('util');
+
+        var async = require('async');
+
+        var relParent1 = require('../foo');
+
+        var {
+          relParent21,
+          relParent22,
+          relParent23,
+          relParent24,
+        } = require('../');
+
+        var relParent3 = require('../bar');
+
+        var { sibling1,
+          sibling2, sibling3 } = require('./foo');
+
+        var sibling2 = require('./bar');
+        var sibling3 = require('./foobar');
+      `,
+      options: [
+        {
+          'newlines-between': 'always-and-inside-groups',
+          consolidateIslands: 'inside-groups',
+        },
+      ],
+      errors: [
+        {
+          message: 'There should be at least one empty line between import groups',
+          line: 4,
+        },
+        {
+          message: 'There should be at least one empty line between import groups',
+          line: 5,
+        },
+        {
+          message: 'There should be at least one empty line between this import and the multi-line import that follows it',
+          line: 6,
+        },
+        {
+          message: 'There should be at least one empty line between this multi-line import and the import that follows it',
+          line: 12,
+        },
+        {
+          message: 'There should be at least one empty line between import groups',
+          line: 13,
+        },
+        {
+          message: 'There should be at least one empty line between this multi-line import and the import that follows it',
+          line: 15,
+        },
+      ],
+    }),
+    test({
+      code: `
+        var fs = require('fs');
+
+        var path = require('path');
+
+        var { util1, util2, util3 } = require('util');
+
+        var async = require('async');
+
+        var relParent1 = require('../foo');
+
+        var {
+          relParent21,
+          relParent22,
+          relParent23,
+          relParent24,
+        } = require('../');
+
+        var relParent3 = require('../bar');
+
+        var { sibling1,
+          sibling2, sibling3 } = require('./foo');
+
+        var sibling2 = require('./bar');
+
+        var sibling3 = require('./foobar');
+      `,
+      output: `
+        var fs = require('fs');
+        var path = require('path');
+        var { util1, util2, util3 } = require('util');
+
+        var async = require('async');
+
+        var relParent1 = require('../foo');
+
+        var {
+          relParent21,
+          relParent22,
+          relParent23,
+          relParent24,
+        } = require('../');
+
+        var relParent3 = require('../bar');
+
+        var { sibling1,
+          sibling2, sibling3 } = require('./foo');
+
+        var sibling2 = require('./bar');
+        var sibling3 = require('./foobar');
+      `,
+      options: [
+        {
+          'newlines-between': 'always-and-inside-groups',
+          consolidateIslands: 'inside-groups',
+        },
+      ],
+      errors: [
+        {
+          message: 'There should be no empty lines between this single-line import and the single-line import that follows it',
+          line: 2,
+        },
+        {
+          message: 'There should be no empty lines between this single-line import and the single-line import that follows it',
+          line: 4,
+        },
+        {
+          message: 'There should be no empty lines between this single-line import and the single-line import that follows it',
+          line: 24,
+        },
+      ],
+    }),
+  ].filter(Boolean),
+});
+
+context('TypeScript', function () {
+  getNonDefaultParsers()
+    // Type-only imports were added in TypeScript ESTree 2.23.0
+    .filter((parser) => parser !== parsers.TS_OLD)
+    .forEach((parser) => {
+      const supportsTypeSpecifiers = semver.satisfies(require('@typescript-eslint/parser/package.json').version, '>= 5');
+      const supportsImportTypeSpecifiers = parser !== parsers.TS_NEW || supportsTypeSpecifiers;
+      const supportsExportTypeSpecifiers = parser === parsers.TS_NEW && supportsTypeSpecifiers;
+      const parserConfig = {
+        parser,
+        settings: {
+          'import/parsers': { [parser]: ['.ts'] },
+          'import/resolver': { 'eslint-import-resolver-typescript': true },
+        },
+      };
+
+      ruleTester.run('order', rule, {
+        valid: [].concat(
+          // #1667: typescript type import support
+
+          // Option alphabetize: {order: 'asc'}
+          test({
+            code: `
+              import c from 'Bar';
+              import type { C } from 'Bar';
+              import b from 'bar';
+              import a from 'foo';
+              import type { A } from 'foo';
+
+              import index from './';
+            `,
+            ...parserConfig,
+            options: [
+              {
+                groups: ['external', 'index'],
+                alphabetize: { order: 'asc' },
+              },
+            ],
+          }),
+          // Option alphabetize: {order: 'desc'}
+          test({
+            code: `
+              import a from 'foo';
+              import type { A } from 'foo';
+              import b from 'bar';
+              import c from 'Bar';
+              import type { C } from 'Bar';
+
+              import index from './';
+            `,
+            ...parserConfig,
+            options: [
+              {
+                groups: ['external', 'index'],
+                alphabetize: { order: 'desc' },
+              },
+            ],
+          }),
+          // Option alphabetize: {order: 'asc'} with type group
+          test({
+            code: `
+              import c from 'Bar';
+              import b from 'bar';
+              import a from 'foo';
+
+              import index from './';
+
+              import type { C } from 'Bar';
+              import type { A } from 'foo';
+            `,
+            ...parserConfig,
+            options: [
+              {
+                groups: ['external', 'index', 'type'],
+                alphabetize: { order: 'asc' },
+              },
+            ],
+          }),
+          // Option alphabetize: {order: 'asc'} with type group & path group
+          test({
+            code: `
+              import c from 'Bar';
+              import a from 'foo';
+
+              import b from 'dirA/bar';
+
+              import index from './';
+
+              import type { C } from 'dirA/Bar';
+              import type { A } from 'foo';
+            `,
+            ...parserConfig,
+            options: [
+              {
+                alphabetize: { order: 'asc' },
+                groups: ['external', 'internal', 'index', 'type'],
+                pathGroups: [
+                  {
+                    pattern: 'dirA/**',
+                    group: 'internal',
+                  },
+                ],
+                'newlines-between': 'always',
+                pathGroupsExcludedImportTypes: ['type'],
+              },
+            ],
+          }),
+          // Option alphabetize: {order: 'asc'} with path group
+          test({
+            code: `
+              import c from 'Bar';
+              import type { A } from 'foo';
+              import a from 'foo';
+
+              import type { C } from 'dirA/Bar';
+              import b from 'dirA/bar';
+
+              import index from './';
+            `,
+            ...parserConfig,
+            options: [
+              {
+                alphabetize: { order: 'asc' },
+                groups: ['external', 'internal', 'index'],
+                pathGroups: [
+                  {
+                    pattern: 'dirA/**',
+                    group: 'internal',
+                  },
+                ],
+                'newlines-between': 'always',
+                pathGroupsExcludedImportTypes: [],
+              },
+            ],
+          }),
+          // Option alphabetize: {order: 'desc'} with type group
+          test({
+            code: `
+              import a from 'foo';
+              import b from 'bar';
+              import c from 'Bar';
+
+              import index from './';
+
+              import type { A } from 'foo';
+              import type { C } from 'Bar';
+            `,
+            ...parserConfig,
+            options: [
+              {
+                groups: ['external', 'index', 'type'],
+                alphabetize: { order: 'desc' },
+              },
+            ],
+          }),
+          test({
+            code: `
+              import { Partner } from '@models/partner/partner';
+              import { PartnerId } from '@models/partner/partner-id';
+            `,
+            ...parserConfig,
+            options: [
+              {
+                alphabetize: { order: 'asc' },
+              },
+            ],
+          }),
+          test({
+            code: `
+              import { serialize, parse, mapFieldErrors } from '@vtaits/form-schema';
+              import type { GetFieldSchema } from '@vtaits/form-schema';
+              import { useMemo, useCallback } from 'react';
+              import type { ReactElement, ReactNode } from 'react';
+              import { Form } from 'react-final-form';
+              import type { FormProps as FinalFormProps } from 'react-final-form';
+            `,
+            ...parserConfig,
+            options: [
+              {
+                alphabetize: { order: 'asc' },
+              },
+            ],
+          }),
+          // Imports inside module declaration
+          test({
+            code: `
+              import type { CopyOptions } from 'fs';
+              import type { ParsedPath } from 'path';
+
+              declare module 'my-module' {
+                import type { CopyOptions } from 'fs';
+                import type { ParsedPath } from 'path';
+              }
+            `,
+            ...parserConfig,
+            options: [
+              {
+                alphabetize: { order: 'asc' },
+              },
+            ],
+          }),
+          test({
+            code: `
+              import { useLazyQuery, useQuery } from "@apollo/client";
+              import { useEffect } from "react";
+            `,
+            options: [
+              {
+                groups: ['builtin', 'external', 'internal', 'parent', 'sibling', 'index', 'object', 'type'],
+                pathGroups: [
+                  {
+                    pattern: 'react',
+                    group: 'external',
+                    position: 'before',
+                  },
+                ],
+                'newlines-between': 'always',
+                alphabetize: {
+                  order: 'asc',
+                  caseInsensitive: true,
+                },
+              },
+            ],
+          }),
+          isCoreModule('node:child_process') && isCoreModule('node:fs/promises') ? [
+            test({
+              code: `
+                import express from 'express';
+                import log4js from 'log4js';
+                import chpro from 'node:child_process';
+                // import fsp from 'node:fs/promises';
+              `,
+              options: [{
+                groups: [
+                  [
+                    'builtin',
+                    'external',
+                    'internal',
+                    'parent',
+                    'sibling',
+                    'index',
+                    'object',
+                    'type',
+                  ],
+                ],
+              }],
+            }),
+          ] : [],
+          // Option sortTypesGroup: false (default)
+          test({
+            code: `
+              import c from 'Bar';
+              import a from 'foo';
+
+              import type { C } from 'dirA/Bar';
+              import b from 'dirA/bar';
+              import type { D } from 'dirA/bar';
+
+              import index from './';
+
+              import type { AA } from 'abc';
+              import type { A } from 'foo';
+            `,
+            ...parserConfig,
+            options: [
+              {
+                alphabetize: { order: 'asc' },
+                groups: ['external', 'internal', 'index', 'type'],
+                pathGroups: [
+                  {
+                    pattern: 'dirA/**',
+                    group: 'internal',
+                  },
+                ],
+                'newlines-between': 'always',
+                pathGroupsExcludedImportTypes: [],
+              },
+            ],
+          }),
+          test({
+            code: `
+              import c from 'Bar';
+              import a from 'foo';
+
+              import type { C } from 'dirA/Bar';
+              import b from 'dirA/bar';
+              import type { D } from 'dirA/bar';
+
+              import index from './';
+
+              import type { AA } from 'abc';
+              import type { A } from 'foo';
+            `,
+            ...parserConfig,
+            options: [
+              {
+                alphabetize: { order: 'asc' },
+                groups: ['external', 'internal', 'index', 'type'],
+                pathGroups: [
+                  {
+                    pattern: 'dirA/**',
+                    group: 'internal',
+                  },
+                ],
+                'newlines-between': 'always',
+                pathGroupsExcludedImportTypes: [],
+                sortTypesGroup: false,
+              },
+            ],
+          }),
+          // Option sortTypesGroup: true and 'type' in pathGroupsExcludedImportTypes
+          test({
+            code: `
+              import c from 'Bar';
+              import a from 'foo';
+
+              import b from 'dirA/bar';
+
+              import index from './';
+
+              import type { AA } from 'abc';
+              import type { C } from 'dirA/Bar';
+              import type { D } from 'dirA/bar';
+              import type { A } from 'foo';
+            `,
+            ...parserConfig,
+            options: [
+              {
+                alphabetize: { order: 'asc' },
+                groups: ['external', 'internal', 'index', 'type'],
+                pathGroups: [
+                  {
+                    pattern: 'dirA/**',
+                    group: 'internal',
+                  },
+                ],
+                'newlines-between': 'always',
+                pathGroupsExcludedImportTypes: ['type'],
+                sortTypesGroup: true,
+              },
+            ],
+          }),
+          // Option sortTypesGroup: true and 'type' omitted from groups
+          test({
+            code: `
+              import c from 'Bar';
+              import type { AA } from 'abc';
+              import a from 'foo';
+              import type { A } from 'foo';
+
+              import type { C } from 'dirA/Bar';
+              import b from 'dirA/bar';
+              import type { D } from 'dirA/bar';
+
+              import index from './';
+            `,
+            ...parserConfig,
+            options: [
+              {
+                alphabetize: { order: 'asc' },
+                groups: ['external', 'internal', 'index'],
+                pathGroups: [
+                  {
+                    pattern: 'dirA/**',
+                    group: 'internal',
+                  },
+                ],
+                'newlines-between': 'always',
+                pathGroupsExcludedImportTypes: [],
+                // Becomes a no-op without "type" in groups
+                sortTypesGroup: true,
+              },
+            ],
+          }),
+          test({
+            code: `
+              import c from 'Bar';
+              import type { AA } from 'abc';
+              import a from 'foo';
+              import type { A } from 'foo';
+
+              import type { C } from 'dirA/Bar';
+              import b from 'dirA/bar';
+              import type { D } from 'dirA/bar';
+
+              import index from './';
+            `,
+            ...parserConfig,
+            options: [
+              {
+                alphabetize: { order: 'asc' },
+                groups: ['external', 'internal', 'index'],
+                pathGroups: [
+                  {
+                    pattern: 'dirA/**',
+                    group: 'internal',
+                  },
+                ],
+                'newlines-between': 'always',
+                pathGroupsExcludedImportTypes: [],
+              },
+            ],
+          }),
+          // Option sortTypesGroup: true and newlines-between-types defaults to the value of newlines-between
+          test({
+            code: `
+              import c from 'Bar';
+              import a from 'foo';
+
+              import b from 'dirA/bar';
+
+              import index from './';
+
+              import type { AA } from 'abc';
+              import type { A } from 'foo';
+
+              import type { C } from 'dirA/Bar';
+              import type { D } from 'dirA/bar';
+            `,
+            ...parserConfig,
+            options: [
+              {
+                alphabetize: { order: 'asc' },
+                groups: ['external', 'internal', 'index', 'type'],
+                pathGroups: [
+                  {
+                    pattern: 'dirA/**',
+                    group: 'internal',
+                  },
+                ],
+                'newlines-between': 'always',
+                pathGroupsExcludedImportTypes: [],
+                sortTypesGroup: true,
+              },
+            ],
+          }),
+          // Option: sortTypesGroup: true and newlines-between-types: 'always' (takes precedence over newlines-between between type-only and normal imports)
+          test({
+            code: `
+              import c from 'Bar';
+              import a from 'foo';
+              import b from 'dirA/bar';
+              import index from './';
+
+              import type { AA } from 'abc';
+              import type { A } from 'foo';
+
+              import type { C } from 'dirA/Bar';
+              import type { D } from 'dirA/bar';
+            `,
+            ...parserConfig,
+            options: [
+              {
+                alphabetize: { order: 'asc' },
+                groups: ['external', 'internal', 'index', 'type'],
+                pathGroups: [
+                  {
+                    pattern: 'dirA/**',
+                    group: 'internal',
+                  },
+                ],
+                'newlines-between': 'never',
+                'newlines-between-types': 'always',
+                pathGroupsExcludedImportTypes: [],
+                sortTypesGroup: true,
+              },
+            ],
+          }),
+          // Option: sortTypesGroup: true and newlines-between-types: 'never' (takes precedence over newlines-between between type-only and normal imports)
+          test({
+            code: `
+              import c from 'Bar';
+              import a from 'foo';
+
+              import b from 'dirA/bar';
+
+              import index from './';
+              import type { AA } from 'abc';
+              import type { A } from 'foo';
+              import type { C } from 'dirA/Bar';
+              import type { D } from 'dirA/bar';
+            `,
+            ...parserConfig,
+            options: [
+              {
+                alphabetize: { order: 'asc' },
+                groups: ['external', 'internal', 'index', 'type'],
+                pathGroups: [
+                  {
+                    pattern: 'dirA/**',
+                    group: 'internal',
+                  },
+                ],
+                'newlines-between': 'always',
+                'newlines-between-types': 'never',
+                pathGroupsExcludedImportTypes: [],
+                sortTypesGroup: true,
+              },
+            ],
+          }),
+          // Option: sortTypesGroup: true and newlines-between-types: 'ignore'
+          test({
+            code: `
+              import c from 'Bar';
+              import a from 'foo';
+              import b from 'dirA/bar';
+              import index from './';
+              import type { AA } from 'abc';
+
+              import type { A } from 'foo';
+              import type { C } from 'dirA/Bar';
+              import type { D } from 'dirA/bar';
+            `,
+            ...parserConfig,
+            options: [
+              {
+                alphabetize: { order: 'asc' },
+                groups: ['external', 'internal', 'index', 'type'],
+                pathGroups: [
+                  {
+                    pattern: 'dirA/**',
+                    group: 'internal',
+                  },
+                ],
+                'newlines-between': 'never',
+                'newlines-between-types': 'ignore',
+                pathGroupsExcludedImportTypes: [],
+                sortTypesGroup: true,
+              },
+            ],
+          }),
+          // Option: sortTypesGroup: true and newlines-between-types: 'always-and-inside-groups'
+          test({
+            code: `
+              import c from 'Bar';
+              import a from 'foo';
+              import b from 'dirA/bar';
+              import index from './';
+
+              import type { AA } from 'abc';
+
+              import type { A } from 'foo';
+
+              import type { C } from 'dirA/Bar';
+
+              import type { D } from 'dirA/bar';
+            `,
+            ...parserConfig,
+            options: [
+              {
+                alphabetize: { order: 'asc' },
+                groups: ['external', 'internal', 'index', 'type'],
+                pathGroups: [
+                  {
+                    pattern: 'dirA/**',
+                    group: 'internal',
+                  },
+                ],
+                'newlines-between': 'never',
+                'newlines-between-types': 'always-and-inside-groups',
+                pathGroupsExcludedImportTypes: [],
+                sortTypesGroup: true,
+              },
+            ],
+          }),
+          // Option: sortTypesGroup: true puts type imports in the same order as regular imports (from issue #2441, PR #2615)
+          test({
+            code: `
+              import type A from "fs";
+              import type B from "path";
+              import type C from "../foo.js";
+              import type D from "./bar.js";
+              import type E from './';
+
+              import a from "fs";
+              import b from "path";
+              import c from "../foo.js";
+              import d from "./bar.js";
+              import e from "./";
+            `,
+            ...parserConfig,
+            options: [
+              {
+                groups: ['type', 'builtin', 'parent', 'sibling', 'index'],
+                alphabetize: {
+                  order: 'asc',
+                  caseInsensitive: true,
+                },
+                sortTypesGroup: true,
+              },
+            ],
+          }),
+          // Options: sortTypesGroup + newlines-between-types example #1 from the documentation (pass)
+          test({
+            code: `
+              import type A from "fs";
+              import type B from "path";
+              import type C from "../foo.js";
+              import type D from "./bar.js";
+              import type E from './';
+
+              import a from "fs";
+              import b from "path";
+
+              import c from "../foo.js";
+
+              import d from "./bar.js";
+
+              import e from "./";
+            `,
+            ...parserConfig,
+            options: [
+              {
+                groups: ['type', 'builtin', 'parent', 'sibling', 'index'],
+                sortTypesGroup: true,
+                'newlines-between': 'always',
+                'newlines-between-types': 'ignore',
+              },
+            ],
+          }),
+          test({
+            code: `
+              import a from "fs";
+              import b from "path";
+
+              import c from "../foo.js";
+
+              import d from "./bar.js";
+
+              import e from "./";
+
+              import type A from "fs";
+              import type B from "path";
+              import type C from "../foo.js";
+              import type D from "./bar.js";
+              import type E from './';
+            `,
+            ...parserConfig,
+            options: [
+              {
+                groups: ['builtin', 'parent', 'sibling', 'index', 'type'],
+                sortTypesGroup: true,
+                'newlines-between': 'always',
+                'newlines-between-types': 'ignore',
+              },
+            ],
+          }),
+          // Options: sortTypesGroup + newlines-between-types example #2 from the documentation (pass)
+          test({
+            code: `
+              import type A from "fs";
+              import type B from "path";
+
+              import type C from "../foo.js";
+
+              import type D from "./bar.js";
+
+              import type E from './';
+
+              import a from "fs";
+              import b from "path";
+              import c from "../foo.js";
+              import d from "./bar.js";
+              import e from "./";
+            `,
+            ...parserConfig,
+            options: [
+              {
+                groups: ['type', 'builtin', 'parent', 'sibling', 'index'],
+                sortTypesGroup: true,
+                'newlines-between': 'never',
+                'newlines-between-types': 'always',
+              },
+            ],
+          }),
+          test({
+            code: `
+              import a from "fs";
+              import b from "path";
+              import c from "../foo.js";
+              import d from "./bar.js";
+              import e from "./";
+
+              import type A from "fs";
+              import type B from "path";
+
+              import type C from "../foo.js";
+
+              import type D from "./bar.js";
+
+              import type E from './';
+            `,
+            ...parserConfig,
+            options: [
+              {
+                groups: ['builtin', 'parent', 'sibling', 'index', 'type'],
+                sortTypesGroup: true,
+                'newlines-between': 'never',
+                'newlines-between-types': 'always',
+              },
+            ],
+          }),
+          // Ensure the rule doesn't choke and die on absolute paths trying to pass NaN around
+          test({
+            code: `
+              import fs from 'fs';
+
+              import '@scoped/package';
+              import type { B } from 'fs';
+
+              import type { A1 } from '/bad/bad/bad/bad';
+              import './a/b/c';
+              import type { A2 } from '/bad/bad/bad/bad';
+              import type { A3 } from '/bad/bad/bad/bad';
+              import type { D1 } from '/bad/bad/not/good';
+              import type { D2 } from '/bad/bad/not/good';
+              import type { D3 } from '/bad/bad/not/good';
+
+              import type { C } from '@something/else';
+
+              import type { E } from './index.js';
+            `,
+            ...parserConfig,
+            options: [
+              {
+                alphabetize: { order: 'asc' },
+                groups: ['builtin', 'type', 'unknown', 'external'],
+                sortTypesGroup: true,
+                'newlines-between': 'always',
+              },
+            ],
+          }),
+          // Ensure the rule doesn't choke and die when right-hand-side AssignmentExpression properties lack a "key" attribute (e.g. SpreadElement)
+          test({
+            code: `
+              // https://prettier.io/docs/en/options.html
+
+              module.exports = {
+                  ...require('@xxxx/.prettierrc.js'),
+              };
+            `,
+            ...parserConfig,
+            options: [{ named: { enabled: true } }],
+          }),
+          // Option: sortTypesGroup: true and newlines-between-types: 'always-and-inside-groups' and consolidateIslands: 'inside-groups'
+          test({
+            code: `
+              import c from 'Bar';
+              import d from 'bar';
+
+              import {
+                aa,
+                bb,
+                cc,
+                dd,
+                ee,
+                ff,
+                gg
+              } from 'baz';
+
+              import {
+                hh,
+                ii,
+                jj,
+                kk,
+                ll,
+                mm,
+                nn
+              } from 'fizz';
+
+              import a from 'foo';
+
+              import b from 'dirA/bar';
+
+              import index from './';
+
+              import type { AA,
+                BB, CC } from 'abc';
+
+              import type { Z } from 'fizz';
+
+              import type {
+                A,
+                B
+              } from 'foo';
+
+              import type { C2 } from 'dirB/Bar';
+
+              import type {
+                D2,
+                X2,
+                Y2
+              } from 'dirB/bar';
+
+              import type { E2 } from 'dirB/baz';
+
+              import type { C3 } from 'dirC/Bar';
+
+              import type {
+                D3,
+                X3,
+                Y3
+              } from 'dirC/bar';
+
+              import type { E3 } from 'dirC/baz';
+              import type { F3 } from 'dirC/caz';
+
+              import type { C1 } from 'dirA/Bar';
+
+              import type {
+                D1,
+                X1,
+                Y1
+              } from 'dirA/bar';
+
+              import type { E1 } from 'dirA/baz';
+
+              import type { F } from './index.js';
+
+              import type { G } from './aaa.js';
+              import type { H } from './bbb';
+            `,
+            ...parserConfig,
+            options: [
+              {
+                alphabetize: { order: 'asc' },
+                groups: ['external', 'internal', 'index', 'type'],
+                pathGroups: [
+                  {
+                    pattern: 'dirA/**',
+                    group: 'internal',
+                    position: 'after',
+                  },
+                  {
+                    pattern: 'dirB/**',
+                    group: 'internal',
+                    position: 'before',
+                  },
+                  {
+                    pattern: 'dirC/**',
+                    group: 'internal',
+                  },
+                ],
+                'newlines-between': 'always-and-inside-groups',
+                'newlines-between-types': 'always-and-inside-groups',
+                pathGroupsExcludedImportTypes: [],
+                sortTypesGroup: true,
+                consolidateIslands: 'inside-groups',
+              },
+            ],
+          }),
+          // Option: sortTypesGroup: true and newlines-between-types: 'always-and-inside-groups' and consolidateIslands: 'never' (default)
+          test({
+            code: `
+              import c from 'Bar';
+              import d from 'bar';
+
+              import {
+                aa,
+                bb,
+                cc,
+                dd,
+                ee,
+                ff,
+                gg
+              } from 'baz';
+
+              import {
+                hh,
+                ii,
+                jj,
+                kk,
+                ll,
+                mm,
+                nn
+              } from 'fizz';
+
+              import a from 'foo';
+
+              import b from 'dirA/bar';
+
+              import index from './';
+
+              import type { AA,
+                BB, CC } from 'abc';
+
+              import type { Z } from 'fizz';
+
+              import type {
+                A,
+                B
+              } from 'foo';
+
+              import type { C2 } from 'dirB/Bar';
+
+              import type {
+                D2,
+                X2,
+                Y2
+              } from 'dirB/bar';
+
+              import type { E2 } from 'dirB/baz';
+
+              import type { C3 } from 'dirC/Bar';
+
+              import type {
+                D3,
+                X3,
+                Y3
+              } from 'dirC/bar';
+
+              import type { E3 } from 'dirC/baz';
+              import type { F3 } from 'dirC/caz';
+
+              import type { C1 } from 'dirA/Bar';
+
+              import type {
+                D1,
+                X1,
+                Y1
+              } from 'dirA/bar';
+
+              import type { E1 } from 'dirA/baz';
+
+              import type { F } from './index.js';
+
+              import type { G } from './aaa.js';
+              import type { H } from './bbb';
+            `,
+            ...parserConfig,
+            options: [
+              {
+                alphabetize: { order: 'asc' },
+                groups: ['external', 'internal', 'index', 'type'],
+                pathGroups: [
+                  {
+                    pattern: 'dirA/**',
+                    group: 'internal',
+                    position: 'after',
+                  },
+                  {
+                    pattern: 'dirB/**',
+                    group: 'internal',
+                    position: 'before',
+                  },
+                  {
+                    pattern: 'dirC/**',
+                    group: 'internal',
+                  },
+                ],
+                'newlines-between': 'always-and-inside-groups',
+                'newlines-between-types': 'always-and-inside-groups',
+                pathGroupsExcludedImportTypes: [],
+                sortTypesGroup: true,
+                consolidateIslands: 'never',
+              },
+            ],
+          }),
+          // Ensure consolidateIslands: 'inside-groups', newlines-between: 'always-and-inside-groups', and newlines-between-types: 'never' do not fight for dominance
+          test({
+            code: `
+              import makeVanillaYargs from 'yargs/yargs';
+
+              import { createDebugLogger } from 'multiverse+rejoinder';
+
+              import { globalDebuggerNamespace } from 'rootverse+bfe:src/constant.ts';
+              import { ErrorMessage, type KeyValueEntry } from 'rootverse+bfe:src/error.ts';
+
+              import {
+                $artificiallyInvoked,
+                $canonical,
+                $exists,
+                $genesis
+              } from 'rootverse+bfe:src/symbols.ts';
+
+              import type {
+                Entries,
+                LiteralUnion,
+                OmitIndexSignature,
+                Promisable,
+                StringKeyOf
+              } from 'type-fest';
+            `,
+            ...parserConfig,
+            options: [
+              {
+                alphabetize: {
+                  order: 'asc',
+                  orderImportKind: 'asc',
+                  caseInsensitive: true,
+                },
+                named: {
+                  enabled: true,
+                  types: 'types-last',
+                },
+                groups: [
+                  'builtin',
+                  'external',
+                  'internal',
+                  ['parent', 'sibling', 'index'],
+                  ['object', 'type'],
+                ],
+                pathGroups: [
+                  {
+                    pattern: 'multiverse{*,*/**}',
+                    group: 'external',
+                    position: 'after',
+                  },
+                  {
+                    pattern: 'rootverse{*,*/**}',
+                    group: 'external',
+                    position: 'after',
+                  },
+                  {
+                    pattern: 'universe{*,*/**}',
+                    group: 'external',
+                    position: 'after',
+                  },
+                ],
+                distinctGroup: true,
+                pathGroupsExcludedImportTypes: ['builtin', 'object'],
+                'newlines-between': 'always-and-inside-groups',
+                'newlines-between-types': 'never',
+                sortTypesGroup: true,
+                consolidateIslands: 'inside-groups',
+              },
+            ],
+          }),
+          // Ensure consolidateIslands: 'inside-groups', newlines-between: 'never', and newlines-between-types: 'always-and-inside-groups' do not fight for dominance
+          test({
+            code: `
+              import makeVanillaYargs from 'yargs/yargs';
+              import { createDebugLogger } from 'multiverse+rejoinder';
+              import { globalDebuggerNamespace } from 'rootverse+bfe:src/constant.ts';
+              import { ErrorMessage, type KeyValueEntry } from 'rootverse+bfe:src/error.ts';
+              import { $artificiallyInvoked } from 'rootverse+bfe:src/symbols.ts';
+
+              import type {
+                Entries,
+                LiteralUnion,
+                OmitIndexSignature,
+                Promisable,
+                StringKeyOf
+              } from 'type-fest';
+            `,
+            ...parserConfig,
+            options: [
+              {
+                alphabetize: {
+                  order: 'asc',
+                  orderImportKind: 'asc',
+                  caseInsensitive: true,
+                },
+                named: {
+                  enabled: true,
+                  types: 'types-last',
+                },
+                groups: [
+                  'builtin',
+                  'external',
+                  'internal',
+                  ['parent', 'sibling', 'index'],
+                  ['object', 'type'],
+                ],
+                pathGroups: [
+                  {
+                    pattern: 'multiverse{*,*/**}',
+                    group: 'external',
+                    position: 'after',
+                  },
+                  {
+                    pattern: 'rootverse{*,*/**}',
+                    group: 'external',
+                    position: 'after',
+                  },
+                  {
+                    pattern: 'universe{*,*/**}',
+                    group: 'external',
+                    position: 'after',
+                  },
+                ],
+                distinctGroup: true,
+                pathGroupsExcludedImportTypes: ['builtin', 'object'],
+                'newlines-between': 'never',
+                'newlines-between-types': 'always-and-inside-groups',
+                sortTypesGroup: true,
+                consolidateIslands: 'inside-groups',
+              },
+            ],
+          }),
+          test({
+            code: `
+              import c from 'Bar';
+              import d from 'bar';
+
+              import {
+                aa,
+                bb,
+                cc,
+                dd,
+                ee,
+                ff,
+                gg
+              } from 'baz';
+
+              import {
+                hh,
+                ii,
+                jj,
+                kk,
+                ll,
+                mm,
+                nn
+              } from 'fizz';
+
+              import a from 'foo';
+              import b from 'dirA/bar';
+              import index from './';
+
+              import type { AA,
+                BB, CC } from 'abc';
+
+              import type { Z } from 'fizz';
+
+              import type {
+                A,
+                B
+              } from 'foo';
+
+              import type { C2 } from 'dirB/Bar';
+
+              import type {
+                D2,
+                X2,
+                Y2
+              } from 'dirB/bar';
+
+              import type { E2 } from 'dirB/baz';
+
+              import type { C3 } from 'dirC/Bar';
+
+              import type {
+                D3,
+                X3,
+                Y3
+              } from 'dirC/bar';
+
+              import type { E3 } from 'dirC/baz';
+              import type { F3 } from 'dirC/caz';
+
+              import type { C1 } from 'dirA/Bar';
+
+              import type {
+                D1,
+                X1,
+                Y1
+              } from 'dirA/bar';
+
+              import type { E1 } from 'dirA/baz';
+
+              import type { F } from './index.js';
+
+              import type { G } from './aaa.js';
+              import type { H } from './bbb';
+            `,
+            ...parserConfig,
+            options: [
+              {
+                alphabetize: { order: 'asc' },
+                groups: ['external', 'internal', 'index', 'type'],
+                pathGroups: [
+                  {
+                    pattern: 'dirA/**',
+                    group: 'internal',
+                    position: 'after',
+                  },
+                  {
+                    pattern: 'dirB/**',
+                    group: 'internal',
+                    position: 'before',
+                  },
+                  {
+                    pattern: 'dirC/**',
+                    group: 'internal',
+                  },
+                ],
+                'newlines-between': 'never',
+                'newlines-between-types': 'always-and-inside-groups',
+                pathGroupsExcludedImportTypes: [],
+                sortTypesGroup: true,
+                consolidateIslands: 'inside-groups',
+              },
+            ],
+          }),
+          test({
+            code: `
+              import assert from 'assert';
+              import { isNativeError } from 'util/types';
+
+              import { runNoRejectOnBadExit } from '@-xun/run';
+              import { TrialError } from 'named-app-errors';
+              import { resolve as resolverLibrary } from 'resolve.exports';
+
+              import { ${supportsImportTypeSpecifiers ? 'toAbsolutePath, type AbsolutePath' : 'AbsolutePath, toAbsolutePath'} } from 'rootverse+project-utils:src/fs.ts';
+
+              import type { PackageJson } from 'type-fest';
+              // Some comment about remembering to do something
+              import type { XPackageJson } from 'rootverse:src/assets/config/_package.json.ts';
+            `,
+            ...parserConfig,
+            options: [
+              {
+                alphabetize: {
+                  order: 'asc',
+                  orderImportKind: 'asc',
+                  caseInsensitive: true,
+                },
+                named: {
+                  enabled: true,
+                  types: 'types-last',
+                },
+                groups: [
+                  'builtin',
+                  'external',
+                  'internal',
+                  ['parent', 'sibling', 'index'],
+                  ['object', 'type'],
+                ],
+                pathGroups: [
+                  {
+                    pattern: 'rootverse{*,*/**}',
+                    group: 'external',
+                    position: 'after',
+                  },
+                ],
+                distinctGroup: true,
+                pathGroupsExcludedImportTypes: ['builtin', 'object'],
+                'newlines-between': 'always-and-inside-groups',
+                'newlines-between-types': 'never',
+                sortTypesGroup: true,
+                consolidateIslands: 'inside-groups',
+              },
+            ],
+          }),
+
+          // Documentation passing example #1 for newlines-between
+          test({
+            code: `
+              import fs from 'fs';
+              import path from 'path';
+
+              import sibling from './foo';
+
+              import index from './';
+            `,
+            ...parserConfig,
+            options: [
+              {
+                'newlines-between': 'always',
+              },
+            ],
+          }),
+          // Documentation passing example #2 for newlines-between
+          test({
+            code: `
+              import fs from 'fs';
+
+              import path from 'path';
+
+              import sibling from './foo';
+
+              import index from './';
+            `,
+            ...parserConfig,
+            options: [
+              {
+                'newlines-between': 'always-and-inside-groups',
+              },
+            ],
+          }),
+          // Documentation passing example #3 for newlines-between
+          test({
+            code: `
+              import fs from 'fs';
+              import path from 'path';
+              import sibling from './foo';
+              import index from './';
+            `,
+            ...parserConfig,
+            options: [
+              {
+                'newlines-between': 'never',
+              },
+            ],
+          }),
+          // Documentation passing example #1 for alphabetize
+          test({
+            code: `
+              import blist2 from 'blist';
+              import blist from 'BList';
+              import * as classnames from 'classnames';
+              import aTypes from 'prop-types';
+              import React, { PureComponent } from 'react';
+              import { compose, apply } from 'xcompose';
+            `,
+            ...parserConfig,
+            options: [
+              {
+                alphabetize: {
+                  order: 'asc',
+                  caseInsensitive: true,
+                },
+              },
+            ],
+          }),
+          // (not an example, but we also test caseInsensitive: false for completeness)
+          test({
+            code: `
+              import blist from 'BList';
+              import blist2 from 'blist';
+              import * as classnames from 'classnames';
+              import aTypes from 'prop-types';
+              import React, { PureComponent } from 'react';
+              import { compose, apply } from 'xcompose';
+            `,
+            ...parserConfig,
+            options: [
+              {
+                alphabetize: {
+                  order: 'asc',
+                  caseInsensitive: false,
+                },
+              },
+            ],
+          }),
+          // Documentation passing example #1 for named
+          test({
+            code: `
+              import { apply, compose } from 'xcompose';
+            `,
+            ...parserConfig,
+            options: [
+              {
+                named: true,
+                alphabetize: {
+                  order: 'asc',
+                },
+              },
+            ],
+          }),
+          // Documentation passing example #1 for warnOnUnassignedImports
+          test({
+            code: `
+              import fs from 'fs';
+              import path from 'path';
+              import './styles.css';
+            `,
+            ...parserConfig,
+            options: [
+              {
+                warnOnUnassignedImports: true,
+              },
+            ],
+          }),
+          // Documentation passing example #1 for sortTypesGroup
+          test({
+            code: `
+              import type A from "fs";
+              import type B from "path";
+              import type C from "../foo.js";
+              import type D from "./bar.js";
+              import type E from './';
+
+              import a from "fs";
+              import b from "path";
+              import c from "../foo.js";
+              import d from "./bar.js";
+              import e from "./";
+            `,
+            ...parserConfig,
+            options: [
+              {
+                groups: ['type', 'builtin', 'parent', 'sibling', 'index'],
+                alphabetize: { order: 'asc' },
+                sortTypesGroup: true,
+              },
+            ],
+          }),
+          // (not an example, but we also test the reverse for completeness)
+          test({
+            code: `
+              import a from "fs";
+              import b from "path";
+              import c from "../foo.js";
+              import d from "./bar.js";
+              import e from "./";
+
+              import type A from "fs";
+              import type B from "path";
+              import type C from "../foo.js";
+              import type D from "./bar.js";
+              import type E from './';
+            `,
+            ...parserConfig,
+            options: [
+              {
+                groups: ['builtin', 'parent', 'sibling', 'index', 'type'],
+                sortTypesGroup: true,
+              },
+            ],
+          }),
+          // Documentation passing example #1 for newlines-between-types
+          test({
+            code: `
+              import type A from "fs";
+              import type B from "path";
+              import type C from "../foo.js";
+              import type D from "./bar.js";
+              import type E from './';
+
+              import a from "fs";
+              import b from "path";
+
+              import c from "../foo.js";
+
+              import d from "./bar.js";
+
+              import e from "./";
+            `,
+            ...parserConfig,
+            options: [
+              {
+                groups: ['type', 'builtin', 'parent', 'sibling', 'index'],
+                sortTypesGroup: true,
+                'newlines-between': 'always',
+                'newlines-between-types': 'ignore',
+              },
+            ],
+          }),
+          // (not an example, but we also test the reverse for completeness)
+          test({
+            code: `
+              import a from "fs";
+              import b from "path";
+
+              import c from "../foo.js";
+
+              import d from "./bar.js";
+
+              import e from "./";
+
+              import type A from "fs";
+              import type B from "path";
+              import type C from "../foo.js";
+              import type D from "./bar.js";
+              import type E from './';
+            `,
+            ...parserConfig,
+            options: [
+              {
+                groups: ['builtin', 'parent', 'sibling', 'index', 'type'],
+                sortTypesGroup: true,
+                'newlines-between': 'always',
+                'newlines-between-types': 'ignore',
+              },
+            ],
+          }),
+          // Documentation passing example #2 for newlines-between-types
+          test({
+            code: `
+              import type A from "fs";
+              import type B from "path";
+
+              import type C from "../foo.js";
+
+              import type D from "./bar.js";
+
+              import type E from './';
+
+              import a from "fs";
+              import b from "path";
+              import c from "../foo.js";
+              import d from "./bar.js";
+              import e from "./";
+            `,
+            ...parserConfig,
+            options: [
+              {
+                groups: ['type', 'builtin', 'parent', 'sibling', 'index'],
+                sortTypesGroup: true,
+                'newlines-between': 'never',
+                'newlines-between-types': 'always',
+              },
+            ],
+          }),
+          // (not an example, but we also test the reverse for completeness)
+          test({
+            code: `
+              import a from "fs";
+              import b from "path";
+              import c from "../foo.js";
+              import d from "./bar.js";
+              import e from "./";
+
+              import type A from "fs";
+              import type B from "path";
+
+              import type C from "../foo.js";
+
+              import type D from "./bar.js";
+
+              import type E from './';
+            `,
+            ...parserConfig,
+            options: [
+              {
+                groups: ['builtin', 'parent', 'sibling', 'index', 'type'],
+                sortTypesGroup: true,
+                'newlines-between': 'never',
+                'newlines-between-types': 'always',
+              },
+            ],
+          }),
+          // Documentation passing example #1 for consolidateIslands
+          test({
+            code: `
+              var fs = require('fs');
+              var path = require('path');
+              var { util1, util2, util3 } = require('util');
+
+              var async = require('async');
+
+              var relParent1 = require('../foo');
+
+              var {
+                relParent21,
+                relParent22,
+                relParent23,
+                relParent24,
+              } = require('../');
+
+              var relParent3 = require('../bar');
+
+              var { sibling1,
+                sibling2, sibling3 } = require('./foo');
+
+              var sibling2 = require('./bar');
+              var sibling3 = require('./foobar');
+            `,
+            ...parserConfig,
+            options: [
+              {
+                'newlines-between': 'always-and-inside-groups',
+                consolidateIslands: 'inside-groups',
+              },
+            ],
+          }),
+          // Documentation passing example #2 for consolidateIslands
+          test({
+            code: `
+              import c from 'Bar';
+              import d from 'bar';
+
+              import {
+                aa,
+                bb,
+                cc,
+                dd,
+                ee,
+                ff,
+                gg
+              } from 'baz';
+
+              import {
+                hh,
+                ii,
+                jj,
+                kk,
+                ll,
+                mm,
+                nn
+              } from 'fizz';
+
+              import a from 'foo';
+
+              import b from 'dirA/bar';
+
+              import index from './';
+
+              import type { AA,
+                BB, CC } from 'abc';
+
+              import type { Z } from 'fizz';
+
+              import type {
+                A,
+                B
+              } from 'foo';
+
+              import type { C2 } from 'dirB/Bar';
+
+              import type {
+                D2,
+                X2,
+                Y2
+              } from 'dirB/bar';
+
+              import type { E2 } from 'dirB/baz';
+              import type { C3 } from 'dirC/Bar';
+
+              import type {
+                D3,
+                X3,
+                Y3
+              } from 'dirC/bar';
+
+              import type { E3 } from 'dirC/baz';
+              import type { F3 } from 'dirC/caz';
+              import type { C1 } from 'dirA/Bar';
+
+              import type {
+                D1,
+                X1,
+                Y1
+              } from 'dirA/bar';
+
+              import type { E1 } from 'dirA/baz';
+              import type { F } from './index.js';
+              import type { G } from './aaa.js';
+              import type { H } from './bbb';
+            `,
+            ...parserConfig,
+            options: [
+              {
+                alphabetize: { order: 'asc' },
+                groups: ['external', 'internal', 'index', 'type'],
+                pathGroups: [
+                  {
+                    pattern: 'dirA/**',
+                    group: 'internal',
+                    position: 'after',
+                  },
+                  {
+                    pattern: 'dirB/**',
+                    group: 'internal',
+                    position: 'before',
+                  },
+                  {
+                    pattern: 'dirC/**',
+                    group: 'internal',
+                  },
+                ],
+                'newlines-between': 'always-and-inside-groups',
+                'newlines-between-types': 'never',
+                pathGroupsExcludedImportTypes: [],
+                sortTypesGroup: true,
+                consolidateIslands: 'inside-groups',
+              },
+            ],
+          }),
+          // (not an example, but we also test the reverse for completeness)
+          test({
+            code: `
+              import type { AA,
+                BB, CC } from 'abc';
+
+              import type { Z } from 'fizz';
+
+              import type {
+                A,
+                B
+              } from 'foo';
+
+              import type { C2 } from 'dirB/Bar';
+
+              import type {
+                D2,
+                X2,
+                Y2
+              } from 'dirB/bar';
+
+              import type { E2 } from 'dirB/baz';
+              import type { C3 } from 'dirC/Bar';
+
+              import type {
+                D3,
+                X3,
+                Y3
+              } from 'dirC/bar';
+
+              import type { E3 } from 'dirC/baz';
+              import type { F3 } from 'dirC/caz';
+              import type { C1 } from 'dirA/Bar';
+
+              import type {
+                D1,
+                X1,
+                Y1
+              } from 'dirA/bar';
+
+              import type { E1 } from 'dirA/baz';
+              import type { F } from './index.js';
+              import type { G } from './aaa.js';
+              import type { H } from './bbb';
+
+              import c from 'Bar';
+              import d from 'bar';
+
+              import {
+                aa,
+                bb,
+                cc,
+                dd,
+                ee,
+                ff,
+                gg
+              } from 'baz';
+
+              import {
+                hh,
+                ii,
+                jj,
+                kk,
+                ll,
+                mm,
+                nn
+              } from 'fizz';
+
+              import a from 'foo';
+
+              import b from 'dirA/bar';
+
+              import index from './';
+            `,
+            ...parserConfig,
+            options: [
+              {
+                alphabetize: { order: 'asc' },
+                groups: ['type', 'external', 'internal', 'index'],
+                pathGroups: [
+                  {
+                    pattern: 'dirA/**',
+                    group: 'internal',
+                    position: 'after',
+                  },
+                  {
+                    pattern: 'dirB/**',
+                    group: 'internal',
+                    position: 'before',
+                  },
+                  {
+                    pattern: 'dirC/**',
+                    group: 'internal',
+                  },
+                ],
+                'newlines-between': 'always-and-inside-groups',
+                'newlines-between-types': 'never',
+                pathGroupsExcludedImportTypes: [],
+                sortTypesGroup: true,
+                consolidateIslands: 'inside-groups',
+              },
+            ],
+          }),
+        ),
+        invalid: [].concat(
+          // Option alphabetize: {order: 'asc'}
+          test({
+            code: `
+              import b from 'bar';
+              import c from 'Bar';
+              import type { C } from 'Bar';
+              import a from 'foo';
+              import type { A } from 'foo';
+
+              import index from './';
+            `,
+            output: `
+              import c from 'Bar';
+              import type { C } from 'Bar';
+              import b from 'bar';
+              import a from 'foo';
+              import type { A } from 'foo';
+
+              import index from './';
+            `,
+            ...parserConfig,
+            options: [
+              {
+                groups: ['external', 'index'],
+                alphabetize: { order: 'asc' },
+              },
+            ],
+            errors: [
+              {
+                message: semver.satisfies(eslintPkg.version, '< 3')
+                  ? '`bar` import should occur after type import of `Bar`'
+                  : /(`bar` import should occur after type import of `Bar`)|(`Bar` type import should occur before import of `bar`)/,
+              },
+            ],
+          }),
+          // Option alphabetize: {order: 'desc'}
+          test({
+            code: `
+              import a from 'foo';
+              import type { A } from 'foo';
+              import c from 'Bar';
+              import type { C } from 'Bar';
+              import b from 'bar';
+
+              import index from './';
+            `,
+            output: `
+              import a from 'foo';
+              import type { A } from 'foo';
+              import b from 'bar';
+              import c from 'Bar';
+              import type { C } from 'Bar';
+
+              import index from './';
+            `,
+            ...parserConfig,
+            options: [
+              {
+                groups: ['external', 'index'],
+                alphabetize: { order: 'desc' },
+              },
+            ],
+            errors: [
+              {
+                message: semver.satisfies(eslintPkg.version, '< 3')
+                  ? '`bar` import should occur before import of `Bar`'
+                  : /(`bar` import should occur before import of `Bar`)|(`Bar` import should occur after import of `bar`)/,
+              },
+            ],
+          }),
+          // Option alphabetize: {order: 'asc'} with type group
+          test({
+            code: `
+              import b from 'bar';
+              import c from 'Bar';
+              import a from 'foo';
+
+              import index from './';
+
+              import type { A } from 'foo';
+              import type { C } from 'Bar';
+            `,
+            output: `
+              import c from 'Bar';
+              import b from 'bar';
+              import a from 'foo';
+
+              import index from './';
+
+              import type { C } from 'Bar';
+              import type { A } from 'foo';
+            `,
+            ...parserConfig,
+            options: [
+              {
+                groups: ['external', 'index', 'type'],
+                alphabetize: { order: 'asc' },
+              },
+            ],
+            errors: semver.satisfies(eslintPkg.version, '< 3') ? [
+              { message: '`Bar` import should occur before import of `bar`' },
+              { message: '`Bar` type import should occur before type import of `foo`' },
+            ] : [
+              { message: /(`Bar` import should occur before import of `bar`)|(`bar` import should occur after import of `Bar`)/ },
+              { message: /(`Bar` type import should occur before type import of `foo`)|(`foo` type import should occur after type import of `Bar`)/ },
+            ],
+          }),
+          // Option alphabetize: {order: 'desc'} with type group
+          test({
+            code: `
+              import a from 'foo';
+              import c from 'Bar';
+              import b from 'bar';
+
+              import index from './';
+
+              import type { C } from 'Bar';
+              import type { A } from 'foo';
+            `,
+            output: `
+              import a from 'foo';
+              import b from 'bar';
+              import c from 'Bar';
+
+              import index from './';
+
+              import type { A } from 'foo';
+              import type { C } from 'Bar';
+            `,
+            ...parserConfig,
+            options: [
+              {
+                groups: ['external', 'index', 'type'],
+                alphabetize: { order: 'desc' },
+              },
+            ],
+            errors: semver.satisfies(eslintPkg.version, '< 3') ? [
+              { message: '`bar` import should occur before import of `Bar`' },
+              { message: '`foo` type import should occur before type import of `Bar`' },
+            ] : [
+              { message: /(`bar` import should occur before import of `Bar`)|(`Bar` import should occur after import of `bar`)/ },
+              { message: /(`foo` type import should occur before type import of `Bar`)|(`Bar` type import should occur after import of type `foo`)/ },
+            ],
+          }),
+          // warns for out of order unassigned imports (warnOnUnassignedImports enabled)
+          test(withoutAutofixOutput({
+            code: `
+              import './local1';
+              import global from 'global1';
+              import local from './local2';
+              import 'global2';
+            `,
+            errors: [
+              {
+                message: '`global1` import should occur before import of `./local1`',
+              },
+              {
+                message: '`global2` import should occur before import of `./local1`',
+              },
+            ],
+            options: [{ warnOnUnassignedImports: true }],
+          })),
+          // fix cannot move below unassigned import (warnOnUnassignedImports enabled)
+          test(withoutAutofixOutput({
+            code: `
+              import local from './local';
+
+              import 'global1';
+
+              import global2 from 'global2';
+              import global3 from 'global3';
+            `,
+            errors: [{
+              message: '`./local` import should occur after import of `global3`',
+            }],
+            options: [{ warnOnUnassignedImports: true }],
+          })),
+          // Imports inside module declaration
+          test({
+            code: `
+              import type { ParsedPath } from 'path';
+              import type { CopyOptions } from 'fs';
+
+              declare module 'my-module' {
+                import type { ParsedPath } from 'path';
+                import type { CopyOptions } from 'fs';
+              }
+            `,
+            output: `
+              import type { CopyOptions } from 'fs';
+              import type { ParsedPath } from 'path';
+
+              declare module 'my-module' {
+                import type { CopyOptions } from 'fs';
+                import type { ParsedPath } from 'path';
+              }
+            `,
+            errors: [
+              { message: '`fs` type import should occur before type import of `path`' },
+              { message: '`fs` type import should occur before type import of `path`' },
+            ],
+            ...parserConfig,
+            options: [
+              {
+                alphabetize: { order: 'asc' },
+              },
+            ],
+          }),
+          // named import order
+          test({
+            code: `
+              import { type Z, A } from "./Z";
+              import type N, { E, D } from "./Z";
+              import type { L, G } from "./Z";
+            `,
+            output: `
+              import { A, type Z } from "./Z";
+              import type N, { D, E } from "./Z";
+              import type { G, L } from "./Z";
+            `,
+            ...parserConfig,
+            options: [{
+              named: true,
+              alphabetize: { order: 'asc' },
+            }],
+            errors: [
+              { message: `\`A\` import should occur before${supportsImportTypeSpecifiers ? ' type' : ''} import of \`Z\`` },
+              { message: '`D` import should occur before import of `E`' },
+              { message: '`G` import should occur before import of `L`' },
+            ],
+          }),
+          test({
+            code: `
+              const { B, /* Hello World */ A } = require("./Z");
+              export { B, A } from "./Z";
+            `,
+            output: `
+              const { /* Hello World */ A, B } = require("./Z");
+              export { A, B } from "./Z";
+            `,
+            ...parserConfig,
+            options: [{
+              named: true,
+              alphabetize: { order: 'asc' },
+            }],
+            errors: [{
+              message: '`A` import should occur before import of `B`',
+            }, {
+              message: '`A` export should occur before export of `B`',
+            }],
+          }),
+          // Options: sortTypesGroup + newlines-between-types example #1 from the documentation (fail)
+          test({
+            code: `
+              import type A from "fs";
+              import type B from "path";
+              import type C from "../foo.js";
+              import type D from "./bar.js";
+              import type E from './';
+
+              import a from "fs";
+              import b from "path";
+
+              import c from "../foo.js";
+
+              import d from "./bar.js";
+
+              import e from "./";
+            `,
+            output: `
+              import type A from "fs";
+              import type B from "path";
+
+              import type C from "../foo.js";
+
+              import type D from "./bar.js";
+
+              import type E from './';
+
+              import a from "fs";
+              import b from "path";
+
+              import c from "../foo.js";
+
+              import d from "./bar.js";
+
+              import e from "./";
+            `,
+            ...parserConfig,
+            options: [
+              {
+                groups: ['type', 'builtin', 'parent', 'sibling', 'index'],
+                sortTypesGroup: true,
+                'newlines-between': 'always',
+              },
+            ],
+            errors: [
+              {
+                message: 'There should be at least one empty line between import groups',
+                line: 3,
+              },
+              {
+                message: 'There should be at least one empty line between import groups',
+                line: 4,
+              },
+              {
+                message: 'There should be at least one empty line between import groups',
+                line: 5,
+              },
+            ],
+          }),
+          // Options: sortTypesGroup + newlines-between-types example #2 from the documentation (fail)
+          test({
+            code: `
+              import type A from "fs";
+              import type B from "path";
+              import type C from "../foo.js";
+              import type D from "./bar.js";
+              import type E from './';
+
+              import a from "fs";
+              import b from "path";
+
+              import c from "../foo.js";
+
+              import d from "./bar.js";
+
+              import e from "./";
+            `,
+            output: `
+              import type A from "fs";
+              import type B from "path";
+              import type C from "../foo.js";
+              import type D from "./bar.js";
+              import type E from './';
+              import a from "fs";
+              import b from "path";
+
+              import c from "../foo.js";
+
+              import d from "./bar.js";
+
+              import e from "./";
+            `,
+            ...parserConfig,
+            options: [
+              {
+                groups: ['type', 'builtin', 'parent', 'sibling', 'index'],
+                sortTypesGroup: true,
+                'newlines-between': 'always',
+                'newlines-between-types': 'never',
+              },
+            ],
+            errors: [
+              {
+                message: 'There should be no empty line between import groups',
+                line: 6,
+              },
+            ],
+          }),
+          // Option: sortTypesGroup: true and newlines-between-types: 'always-and-inside-groups' and consolidateIslands: 'inside-groups' with all newlines
+          test({
+            code: `
+              import c from 'Bar';
+
+              import d from 'bar';
+
+              import {
+                aa,
+                bb,
+                cc,
+                dd,
+                ee,
+                ff,
+                gg
+              } from 'baz';
+
+              import {
+                hh,
+                ii,
+                jj,
+                kk,
+                ll,
+                mm,
+                nn
+              } from 'fizz';
+
+              import a from 'foo';
+
+              import b from 'dirA/bar';
+
+              import index from './';
+
+              import type { AA,
+                BB, CC } from 'abc';
+
+              import type { Z } from 'fizz';
+
+              import type {
+                A,
+                B
+              } from 'foo';
+
+              import type { C2 } from 'dirB/Bar';
+
+              import type {
+                D2,
+                X2,
+                Y2
+              } from 'dirB/bar';
+
+              import type { E2 } from 'dirB/baz';
+
+              import type { C3 } from 'dirC/Bar';
+
+              import type {
+                D3,
+                X3,
+                Y3
+              } from 'dirC/bar';
+
+              import type { E3 } from 'dirC/baz';
+
+              import type { F3 } from 'dirC/caz';
+
+              import type { C1 } from 'dirA/Bar';
+
+              import type {
+                D1,
+                X1,
+                Y1
+              } from 'dirA/bar';
+
+              import type { E1 } from 'dirA/baz';
+
+              import type { F } from './index.js';
+
+              import type { G } from './aaa.js';
+
+              import type { H } from './bbb';
+            `,
+            output: `
+              import c from 'Bar';
+              import d from 'bar';
+
+              import {
+                aa,
+                bb,
+                cc,
+                dd,
+                ee,
+                ff,
+                gg
+              } from 'baz';
+
+              import {
+                hh,
+                ii,
+                jj,
+                kk,
+                ll,
+                mm,
+                nn
+              } from 'fizz';
+
+              import a from 'foo';
+
+              import b from 'dirA/bar';
+
+              import index from './';
+
+              import type { AA,
+                BB, CC } from 'abc';
+
+              import type { Z } from 'fizz';
+
+              import type {
+                A,
+                B
+              } from 'foo';
+
+              import type { C2 } from 'dirB/Bar';
+
+              import type {
+                D2,
+                X2,
+                Y2
+              } from 'dirB/bar';
+
+              import type { E2 } from 'dirB/baz';
+
+              import type { C3 } from 'dirC/Bar';
+
+              import type {
+                D3,
+                X3,
+                Y3
+              } from 'dirC/bar';
+
+              import type { E3 } from 'dirC/baz';
+              import type { F3 } from 'dirC/caz';
+
+              import type { C1 } from 'dirA/Bar';
+
+              import type {
+                D1,
+                X1,
+                Y1
+              } from 'dirA/bar';
+
+              import type { E1 } from 'dirA/baz';
+
+              import type { F } from './index.js';
+
+              import type { G } from './aaa.js';
+              import type { H } from './bbb';
+            `,
+            ...parserConfig,
+            options: [
+              {
+                alphabetize: { order: 'asc' },
+                groups: ['external', 'internal', 'index', 'type'],
+                pathGroups: [
+                  {
+                    pattern: 'dirA/**',
+                    group: 'internal',
+                    position: 'after',
+                  },
+                  {
+                    pattern: 'dirB/**',
+                    group: 'internal',
+                    position: 'before',
+                  },
+                  {
+                    pattern: 'dirC/**',
+                    group: 'internal',
+                  },
+                ],
+                'newlines-between': 'always-and-inside-groups',
+                'newlines-between-types': 'always-and-inside-groups',
+                pathGroupsExcludedImportTypes: [],
+                sortTypesGroup: true,
+                consolidateIslands: 'inside-groups',
+              },
+            ],
+            errors: [
+              {
+                message:
+                  'There should be no empty lines between this single-line import and the single-line import that follows it',
+                line: 2,
+              },
+              {
+                message:
+                  'There should be no empty lines between this single-line import and the single-line import that follows it',
+                line: 60,
+              },
+              {
+                message:
+                  'There should be no empty lines between this single-line import and the single-line import that follows it',
+                line: 76,
+              },
+            ],
+          }),
+          // Option: sortTypesGroup: true and newlines-between-types: 'always-and-inside-groups' and consolidateIslands: 'inside-groups' with no newlines
+          test({
+            code: `
+              import c from 'Bar';
+              import d from 'bar';
+              import {
+                aa,
+                bb,
+                cc,
+                dd,
+                ee,
+                ff,
+                gg
+              } from 'baz';
+              import {
+                hh,
+                ii,
+                jj,
+                kk,
+                ll,
+                mm,
+                nn
+              } from 'fizz';
+              import a from 'foo';
+              import b from 'dirA/bar';
+              import index from './';
+              import type { AA,
+                BB, CC } from 'abc';
+              import type { Z } from 'fizz';
+              import type {
+                A,
+                B
+              } from 'foo';
+              import type { C2 } from 'dirB/Bar';
+              import type {
+                D2,
+                X2,
+                Y2
+              } from 'dirB/bar';
+              import type { E2 } from 'dirB/baz';
+              import type { C3 } from 'dirC/Bar';
+              import type {
+                D3,
+                X3,
+                Y3
+              } from 'dirC/bar';
+              import type { E3 } from 'dirC/baz';
+              import type { F3 } from 'dirC/caz';
+              import type { C1 } from 'dirA/Bar';
+              import type {
+                D1,
+                X1,
+                Y1
+              } from 'dirA/bar';
+              import type { E1 } from 'dirA/baz';
+              import type { F } from './index.js';
+              import type { G } from './aaa.js';
+              import type { H } from './bbb';
+            `,
+            output: `
+              import c from 'Bar';
+              import d from 'bar';
+
+              import {
+                aa,
+                bb,
+                cc,
+                dd,
+                ee,
+                ff,
+                gg
+              } from 'baz';
+
+              import {
+                hh,
+                ii,
+                jj,
+                kk,
+                ll,
+                mm,
+                nn
+              } from 'fizz';
+
+              import a from 'foo';
+
+              import b from 'dirA/bar';
+
+              import index from './';
+
+              import type { AA,
+                BB, CC } from 'abc';
+
+              import type { Z } from 'fizz';
+
+              import type {
+                A,
+                B
+              } from 'foo';
+
+              import type { C2 } from 'dirB/Bar';
+
+              import type {
+                D2,
+                X2,
+                Y2
+              } from 'dirB/bar';
+
+              import type { E2 } from 'dirB/baz';
+
+              import type { C3 } from 'dirC/Bar';
+
+              import type {
+                D3,
+                X3,
+                Y3
+              } from 'dirC/bar';
+
+              import type { E3 } from 'dirC/baz';
+              import type { F3 } from 'dirC/caz';
+
+              import type { C1 } from 'dirA/Bar';
+
+              import type {
+                D1,
+                X1,
+                Y1
+              } from 'dirA/bar';
+
+              import type { E1 } from 'dirA/baz';
+
+              import type { F } from './index.js';
+
+              import type { G } from './aaa.js';
+              import type { H } from './bbb';
+            `,
+            ...parserConfig,
+            options: [
+              {
+                alphabetize: { order: 'asc' },
+                groups: ['external', 'internal', 'index', 'type'],
+                pathGroups: [
+                  {
+                    pattern: 'dirA/**',
+                    group: 'internal',
+                    position: 'after',
+                  },
+                  {
+                    pattern: 'dirB/**',
+                    group: 'internal',
+                    position: 'before',
+                  },
+                  {
+                    pattern: 'dirC/**',
+                    group: 'internal',
+                  },
+                ],
+                'newlines-between': 'always-and-inside-groups',
+                'newlines-between-types': 'always-and-inside-groups',
+                pathGroupsExcludedImportTypes: [],
+                sortTypesGroup: true,
+                consolidateIslands: 'inside-groups',
+              },
+            ],
+            errors: [
+              {
+                message: 'There should be at least one empty line between this import and the multi-line import that follows it',
+                line: 3,
+              },
+              {
+                message: 'There should be at least one empty line between this import and the multi-line import that follows it',
+                line: 4,
+              },
+              {
+                message: 'There should be at least one empty line between this multi-line import and the import that follows it',
+                line: 13,
+              },
+              {
+                message: 'There should be at least one empty line between import groups',
+                line: 22,
+              },
+              {
+                message: 'There should be at least one empty line between import groups',
+                line: 23,
+              },
+              {
+                message: 'There should be at least one empty line between import groups',
+                line: 24,
+              },
+              {
+                message: 'There should be at least one empty line between this multi-line import and the import that follows it',
+                line: 25,
+              },
+              {
+                message: 'There should be at least one empty line between this import and the multi-line import that follows it',
+                line: 27,
+              },
+              {
+                message: 'There should be at least one empty line between import groups',
+                line: 28,
+              },
+              {
+                message: 'There should be at least one empty line between this import and the multi-line import that follows it',
+                line: 32,
+              },
+              {
+                message: 'There should be at least one empty line between this multi-line import and the import that follows it',
+                line: 33,
+              },
+              {
+                message: 'There should be at least one empty line between import groups',
+                line: 38,
+              },
+              {
+                message: 'There should be at least one empty line between this import and the multi-line import that follows it',
+                line: 39,
+              },
+              {
+                message: 'There should be at least one empty line between this multi-line import and the import that follows it',
+                line: 40,
+              },
+              {
+                message: 'There should be at least one empty line between import groups',
+                line: 46,
+              },
+              {
+                message: 'There should be at least one empty line between this import and the multi-line import that follows it',
+                line: 47,
+              },
+              {
+                message: 'There should be at least one empty line between this multi-line import and the import that follows it',
+                line: 48,
+              },
+              {
+                message: 'There should be at least one empty line between import groups',
+                line: 53,
+              },
+              {
+                message: 'There should be at least one empty line between import groups',
+                line: 54,
+              },
+            ],
+          }),
+          // Option: sortTypesGroup: true and newlines-between-types: 'always-and-inside-groups' and consolidateIslands: 'never' (default)
+          test({
+            code: `
+              import c from 'Bar';
+              import d from 'bar';
+              import {
+                aa,
+                bb,
+                cc,
+                dd,
+                ee,
+                ff,
+                gg
+              } from 'baz';
+              import {
+                hh,
+                ii,
+                jj,
+                kk,
+                ll,
+                mm,
+                nn
+              } from 'fizz';
+              import a from 'foo';
+              import b from 'dirA/bar';
+              import index from './';
+              import type { AA,
+                BB, CC } from 'abc';
+              import type { Z } from 'fizz';
+              import type {
+                A,
+                B
+              } from 'foo';
+              import type { C2 } from 'dirB/Bar';
+              import type {
+                D2,
+                X2,
+                Y2
+              } from 'dirB/bar';
+              import type { E2 } from 'dirB/baz';
+              import type { C3 } from 'dirC/Bar';
+              import type {
+                D3,
+                X3,
+                Y3
+              } from 'dirC/bar';
+              import type { E3 } from 'dirC/baz';
+              import type { F3 } from 'dirC/caz';
+              import type { C1 } from 'dirA/Bar';
+              import type {
+                D1,
+                X1,
+                Y1
+              } from 'dirA/bar';
+              import type { E1 } from 'dirA/baz';
+              import type { F } from './index.js';
+              import type { G } from './aaa.js';
+              import type { H } from './bbb';
+            `,
+            output: `
+              import c from 'Bar';
+              import d from 'bar';
+              import {
+                aa,
+                bb,
+                cc,
+                dd,
+                ee,
+                ff,
+                gg
+              } from 'baz';
+              import {
+                hh,
+                ii,
+                jj,
+                kk,
+                ll,
+                mm,
+                nn
+              } from 'fizz';
+              import a from 'foo';
+
+              import b from 'dirA/bar';
+
+              import index from './';
+
+              import type { AA,
+                BB, CC } from 'abc';
+              import type { Z } from 'fizz';
+              import type {
+                A,
+                B
+              } from 'foo';
+
+              import type { C2 } from 'dirB/Bar';
+              import type {
+                D2,
+                X2,
+                Y2
+              } from 'dirB/bar';
+              import type { E2 } from 'dirB/baz';
+
+              import type { C3 } from 'dirC/Bar';
+              import type {
+                D3,
+                X3,
+                Y3
+              } from 'dirC/bar';
+              import type { E3 } from 'dirC/baz';
+              import type { F3 } from 'dirC/caz';
+
+              import type { C1 } from 'dirA/Bar';
+              import type {
+                D1,
+                X1,
+                Y1
+              } from 'dirA/bar';
+              import type { E1 } from 'dirA/baz';
+
+              import type { F } from './index.js';
+
+              import type { G } from './aaa.js';
+              import type { H } from './bbb';
+            `,
+            ...parserConfig,
+            options: [
+              {
+                alphabetize: { order: 'asc' },
+                groups: ['external', 'internal', 'index', 'type'],
+                pathGroups: [
+                  {
+                    pattern: 'dirA/**',
+                    group: 'internal',
+                    position: 'after',
+                  },
+                  {
+                    pattern: 'dirB/**',
+                    group: 'internal',
+                    position: 'before',
+                  },
+                  {
+                    pattern: 'dirC/**',
+                    group: 'internal',
+                  },
+                ],
+                'newlines-between': 'always-and-inside-groups',
+                'newlines-between-types': 'always-and-inside-groups',
+                pathGroupsExcludedImportTypes: [],
+                sortTypesGroup: true,
+                consolidateIslands: 'never',
+              },
+            ],
+            errors: [
+              {
+                message: 'There should be at least one empty line between import groups',
+                line: 22,
+              },
+              {
+                message: 'There should be at least one empty line between import groups',
+                line: 23,
+              },
+              {
+                message: 'There should be at least one empty line between import groups',
+                line: 24,
+              },
+              {
+                message: 'There should be at least one empty line between import groups',
+                line: 28,
+              },
+              {
+                message: 'There should be at least one empty line between import groups',
+                line: 38,
+              },
+              {
+                message: 'There should be at least one empty line between import groups',
+                line: 46,
+              },
+              {
+                message: 'There should be at least one empty line between import groups',
+                line: 53,
+              },
+              {
+                message: 'There should be at least one empty line between import groups',
+                line: 54,
+              },
+
+            ],
+          }),
+
+          // Documentation failing example #1 for newlines-between
+          test({
+            code: `
+              import fs from 'fs';
+              import path from 'path';
+              import sibling from './foo';
+              import index from './';
+            `,
+            output: `
+              import fs from 'fs';
+              import path from 'path';
+
+              import sibling from './foo';
+
+              import index from './';
+            `,
+            ...parserConfig,
+            options: [
+              {
+                'newlines-between': 'always',
+              },
+            ],
+            errors: [
+              {
+                message: 'There should be at least one empty line between import groups',
+                line: 3,
+              },
+              {
+                message: 'There should be at least one empty line between import groups',
+                line: 4,
+              },
+            ],
+          }),
+          // Documentation failing example #2 for newlines-between
+          test({
+            code: `
+              import fs from 'fs';
+
+              import path from 'path';
+              import sibling from './foo';
+              import index from './';
+            `,
+            output: `
+              import fs from 'fs';
+
+              import path from 'path';
+
+              import sibling from './foo';
+
+              import index from './';
+            `,
+            ...parserConfig,
+            options: [
+              {
+                'newlines-between': 'always-and-inside-groups',
+              },
+            ],
+            errors: [
+              {
+                message: 'There should be at least one empty line between import groups',
+                line: 4,
+              },
+              {
+                message: 'There should be at least one empty line between import groups',
+                line: 5,
+              },
+            ],
+          }),
+          // Documentation failing example #3 for newlines-between
+          test({
+            code: `
+              import fs from 'fs';
+              import path from 'path';
+
+              import sibling from './foo';
+
+              import index from './';
+            `,
+            output: `
+              import fs from 'fs';
+              import path from 'path';
+              import sibling from './foo';
+              import index from './';
+            `,
+            ...parserConfig,
+            options: [
+              {
+                'newlines-between': 'never',
+              },
+            ],
+            errors: [
+              {
+                message: 'There should be no empty line between import groups',
+                line: 3,
+              },
+              {
+                message: 'There should be no empty line between import groups',
+                line: 5,
+              },
+            ],
+          }),
+          // Multiple errors
+          ...semver.satisfies(eslintPkg.version, '< 3.0.0') ? [] : [
+            // Documentation failing example #1 for alphabetize
+            test({
+              code: `
+                import React, { PureComponent } from 'react';
+                import aTypes from 'prop-types';
+                import { compose, apply } from 'xcompose';
+                import * as classnames from 'classnames';
+                import blist2 from 'blist';
+                import blist from 'BList';
+              `,
+              // The reason why this output does not match the success example after being fixed is because eslint will leave overlapping errors alone, so only one import gets reordered when fixes are applied
+              output: `
+                import aTypes from 'prop-types';
+                import React, { PureComponent } from 'react';
+                import { compose, apply } from 'xcompose';
+                import * as classnames from 'classnames';
+                import blist2 from 'blist';
+                import blist from 'BList';
+              `,
+              ...parserConfig,
+              options: [
+                {
+                  alphabetize: {
+                    order: 'asc',
+                    caseInsensitive: true,
+                  },
+                },
+              ],
+              errors: [
+                {
+                  message: '`prop-types` import should occur before import of `react`',
+                  line: 3,
+                },
+                {
+                  message: '`classnames` import should occur before import of `react`',
+                  line: 5,
+                },
+                {
+                  message: '`blist` import should occur before import of `react`',
+                  line: 6,
+                },
+                {
+                  message: '`BList` import should occur before import of `react`',
+                  line: 7,
+                },
+              ],
+            }),
+          ],
+          // Documentation failing example #1 for named
+          test({
+            code: `
+              import { compose, apply } from 'xcompose';
+            `,
+            output: `
+              import { apply, compose } from 'xcompose';
+            `,
+            ...parserConfig,
+            options: [
+              {
+                named: true,
+                alphabetize: {
+                  order: 'asc',
+                },
+              },
+            ],
+            errors: [
+              {
+                message: '`apply` import should occur before import of `compose`',
+                line: 2,
+              },
+            ],
+          }),
+          // Documentation failing example #1 for warnOnUnassignedImports
+          test({
+            code: `
+              import fs from 'fs';
+              import './styles.css';
+              import path from 'path';
+            `,
+            // Should not be fixed (eslint@>=9 expects null)
+            output: semver.satisfies(eslintPkg.version, '< 9.0.0') ? `
+              import fs from 'fs';
+              import './styles.css';
+              import path from 'path';
+            ` : null,
+            ...parserConfig,
+            options: [
+              {
+                warnOnUnassignedImports: true,
+              },
+            ],
+            errors: [
+              {
+                message: '`path` import should occur before import of `./styles.css`',
+                line: 4,
+              },
+            ],
+          }),
+          // Documentation failing example #1 for sortTypesGroup
+          test({
+            code: `
+              import type A from "fs";
+              import type B from "path";
+              import type C from "../foo.js";
+              import type D from "./bar.js";
+              import type E from './';
+
+              import a from "fs";
+              import b from "path";
+              import c from "../foo.js";
+              import d from "./bar.js";
+              import e from "./";
+            `,
+            // This is the "correct" behavior, but it's the wrong outcome (expectedly)
+            output: semver.satisfies(eslintPkg.version, '< 3.0.0')
+            // eslint@2 apparently attempts to fix multiple errors in one pass,
+            // which results in different erroneous output
+              ? `
+              import type E from './';
+              import type A from "fs";
+              import type B from "path";
+              import type C from "../foo.js";
+              import type D from "./bar.js";
+
+              import a from "fs";
+              import b from "path";
+              import c from "../foo.js";
+              import d from "./bar.js";
+              import e from "./";
+            ` : `
+              import type C from "../foo.js";
+              import type A from "fs";
+              import type B from "path";
+              import type D from "./bar.js";
+              import type E from './';
+
+              import a from "fs";
+              import b from "path";
+              import c from "../foo.js";
+              import d from "./bar.js";
+              import e from "./";
+            `,
+            ...parserConfig,
+            options: [
+              {
+                groups: ['type', 'builtin', 'parent', 'sibling', 'index'],
+                alphabetize: { order: 'asc' },
+              },
+            ],
+            errors: [
+              {
+                message: '`../foo.js` type import should occur before type import of `fs`',
+                line: 4,
+              },
+              {
+                message: '`./bar.js` type import should occur before type import of `fs`',
+                line: 5,
+              },
+              {
+                message: '`./` type import should occur before type import of `fs`',
+                line: 6,
+              },
+            ],
+          }),
+          // Documentation failing example #1 for newlines-between-types
+          test({
+            code: `
+              import type A from "fs";
+              import type B from "path";
+              import type C from "../foo.js";
+              import type D from "./bar.js";
+              import type E from './';
+
+              import a from "fs";
+              import b from "path";
+
+              import c from "../foo.js";
+
+              import d from "./bar.js";
+
+              import e from "./";
+            `,
+            output: `
+              import type A from "fs";
+              import type B from "path";
+
+              import type C from "../foo.js";
+
+              import type D from "./bar.js";
+
+              import type E from './';
+
+              import a from "fs";
+              import b from "path";
+
+              import c from "../foo.js";
+
+              import d from "./bar.js";
+
+              import e from "./";
+            `,
+            ...parserConfig,
+            options: [
+              {
+                groups: ['type', 'builtin', 'parent', 'sibling', 'index'],
+                sortTypesGroup: true,
+                'newlines-between': 'always',
+              },
+            ],
+            errors: [
+              {
+                message: 'There should be at least one empty line between import groups',
+                line: 3,
+              },
+              {
+                message: 'There should be at least one empty line between import groups',
+                line: 4,
+              },
+              {
+                message: 'There should be at least one empty line between import groups',
+                line: 5,
+              },
+            ],
+          }),
+          // Documentation failing example #2 for newlines-between-types
+          test({
+            code: `
+              import type A from "fs";
+              import type B from "path";
+              import type C from "../foo.js";
+              import type D from "./bar.js";
+              import type E from './';
+
+              import a from "fs";
+              import b from "path";
+
+              import c from "../foo.js";
+
+              import d from "./bar.js";
+
+              import e from "./";
+            `,
+            output: `
+              import type A from "fs";
+              import type B from "path";
+              import type C from "../foo.js";
+              import type D from "./bar.js";
+              import type E from './';
+              import a from "fs";
+              import b from "path";
+
+              import c from "../foo.js";
+
+              import d from "./bar.js";
+
+              import e from "./";
+            `,
+            ...parserConfig,
+            options: [
+              {
+                groups: ['type', 'builtin', 'parent', 'sibling', 'index'],
+                sortTypesGroup: true,
+                'newlines-between': 'always',
+                'newlines-between-types': 'never',
+              },
+            ],
+            errors: [
+              {
+                message: 'There should be no empty line between import groups',
+                line: 6,
+              },
+            ],
+          }),
+          // Documentation failing example #1 for consolidateIslands
+          test({
+            code: `
+              var fs = require('fs');
+              var path = require('path');
+              var { util1, util2, util3 } = require('util');
+              var async = require('async');
+              var relParent1 = require('../foo');
+              var {
+                relParent21,
+                relParent22,
+                relParent23,
+                relParent24,
+              } = require('../');
+              var relParent3 = require('../bar');
+              var { sibling1,
+                sibling2, sibling3 } = require('./foo');
+              var sibling2 = require('./bar');
+              var sibling3 = require('./foobar');
+            `,
+            output: `
+              var fs = require('fs');
+              var path = require('path');
+              var { util1, util2, util3 } = require('util');
+
+              var async = require('async');
+
+              var relParent1 = require('../foo');
+
+              var {
+                relParent21,
+                relParent22,
+                relParent23,
+                relParent24,
+              } = require('../');
+
+              var relParent3 = require('../bar');
+
+              var { sibling1,
+                sibling2, sibling3 } = require('./foo');
+
+              var sibling2 = require('./bar');
+              var sibling3 = require('./foobar');
+            `,
+            ...parserConfig,
+            options: [
+              {
+                'newlines-between': 'always-and-inside-groups',
+                consolidateIslands: 'inside-groups',
+              },
+            ],
+            errors: [
+              {
+                message: 'There should be at least one empty line between import groups',
+                line: 4,
+              },
+              {
+                message: 'There should be at least one empty line between import groups',
+                line: 5,
+              },
+              {
+                message:
+                  'There should be at least one empty line between this import and the multi-line import that follows it',
+                line: 6,
+              },
+              {
+                message:
+                  'There should be at least one empty line between this multi-line import and the import that follows it',
+                line: 12,
+              },
+              {
+                message: 'There should be at least one empty line between import groups',
+                line: 13,
+              },
+              {
+                message:
+                  'There should be at least one empty line between this multi-line import and the import that follows it',
+                line: 15,
+              },
+            ],
+          }),
+          // Documentation failing example #2 for consolidateIslands
+          test({
+            code: `
+              import c from 'Bar';
+              import d from 'bar';
+              import {
+                aa,
+                bb,
+                cc,
+                dd,
+                ee,
+                ff,
+                gg
+              } from 'baz';
+              import {
+                hh,
+                ii,
+                jj,
+                kk,
+                ll,
+                mm,
+                nn
+              } from 'fizz';
+              import a from 'foo';
+              import b from 'dirA/bar';
+              import index from './';
+              import type { AA,
+                BB, CC } from 'abc';
+              import type { Z } from 'fizz';
+              import type {
+                A,
+                B
+              } from 'foo';
+              import type { C2 } from 'dirB/Bar';
+              import type {
+                D2,
+                X2,
+                Y2
+              } from 'dirB/bar';
+              import type { E2 } from 'dirB/baz';
+              import type { C3 } from 'dirC/Bar';
+              import type {
+                D3,
+                X3,
+                Y3
+              } from 'dirC/bar';
+              import type { E3 } from 'dirC/baz';
+              import type { F3 } from 'dirC/caz';
+              import type { C1 } from 'dirA/Bar';
+              import type {
+                D1,
+                X1,
+                Y1
+              } from 'dirA/bar';
+              import type { E1 } from 'dirA/baz';
+              import type { F } from './index.js';
+              import type { G } from './aaa.js';
+              import type { H } from './bbb';
+            `,
+            output: `
+              import c from 'Bar';
+              import d from 'bar';
+
+              import {
+                aa,
+                bb,
+                cc,
+                dd,
+                ee,
+                ff,
+                gg
+              } from 'baz';
+
+              import {
+                hh,
+                ii,
+                jj,
+                kk,
+                ll,
+                mm,
+                nn
+              } from 'fizz';
+
+              import a from 'foo';
+
+              import b from 'dirA/bar';
+
+              import index from './';
+
+              import type { AA,
+                BB, CC } from 'abc';
+
+              import type { Z } from 'fizz';
+
+              import type {
+                A,
+                B
+              } from 'foo';
+
+              import type { C2 } from 'dirB/Bar';
+
+              import type {
+                D2,
+                X2,
+                Y2
+              } from 'dirB/bar';
+
+              import type { E2 } from 'dirB/baz';
+              import type { C3 } from 'dirC/Bar';
+
+              import type {
+                D3,
+                X3,
+                Y3
+              } from 'dirC/bar';
+
+              import type { E3 } from 'dirC/baz';
+              import type { F3 } from 'dirC/caz';
+              import type { C1 } from 'dirA/Bar';
+
+              import type {
+                D1,
+                X1,
+                Y1
+              } from 'dirA/bar';
+
+              import type { E1 } from 'dirA/baz';
+              import type { F } from './index.js';
+              import type { G } from './aaa.js';
+              import type { H } from './bbb';
+            `,
+            ...parserConfig,
+            options: [
+              {
+                alphabetize: { order: 'asc' },
+                groups: ['external', 'internal', 'index', 'type'],
+                pathGroups: [
+                  {
+                    pattern: 'dirA/**',
+                    group: 'internal',
+                    position: 'after',
+                  },
+                  {
+                    pattern: 'dirB/**',
+                    group: 'internal',
+                    position: 'before',
+                  },
+                  {
+                    pattern: 'dirC/**',
+                    group: 'internal',
+                  },
+                ],
+                'newlines-between': 'always-and-inside-groups',
+                'newlines-between-types': 'never',
+                pathGroupsExcludedImportTypes: [],
+                sortTypesGroup: true,
+                consolidateIslands: 'inside-groups',
+              },
+            ],
+            errors: [
+              {
+                message:
+                  'There should be at least one empty line between this import and the multi-line import that follows it',
+                line: 3,
+              },
+              {
+                message:
+                  'There should be at least one empty line between this import and the multi-line import that follows it',
+                line: 4,
+              },
+              {
+                message:
+                  'There should be at least one empty line between this multi-line import and the import that follows it',
+                line: 13,
+              },
+              {
+                message: 'There should be at least one empty line between import groups',
+                line: 22,
+              },
+              {
+                message: 'There should be at least one empty line between import groups',
+                line: 23,
+              },
+              {
+                message: 'There should be at least one empty line between import groups',
+                line: 24,
+              },
+              {
+                message:
+                  'There should be at least one empty line between this multi-line import and the import that follows it',
+                line: 25,
+              },
+              {
+                message:
+                  'There should be at least one empty line between this import and the multi-line import that follows it',
+                line: 27,
+              },
+              {
+                message: 'There should be at least one empty line between import groups',
+                line: 28,
+              },
+              {
+                message:
+                  'There should be at least one empty line between this import and the multi-line import that follows it',
+                line: 32,
+              },
+              {
+                message:
+                  'There should be at least one empty line between this multi-line import and the import that follows it',
+                line: 33,
+              },
+              {
+                message:
+                  'There should be at least one empty line between this import and the multi-line import that follows it',
+                line: 39,
+              },
+              {
+                message:
+                  'There should be at least one empty line between this multi-line import and the import that follows it',
+                line: 40,
+              },
+              {
+                message:
+                  'There should be at least one empty line between this import and the multi-line import that follows it',
+                line: 47,
+              },
+              {
+                message:
+                  'There should be at least one empty line between this multi-line import and the import that follows it',
+                line: 48,
+              },
+            ],
+          }),
+
+          supportsExportTypeSpecifiers ? [
+            test({
+              code: `
+                export { type B, A };
+              `,
+              output: `
+                export { A, type B };
+              `,
+              ...parserConfig,
+              options: [{
+                named: {
+                  enabled: true,
+                  types: 'mixed',
+                },
+                alphabetize: { order: 'asc' },
+              }],
+              errors: [{
+                message: '`A` export should occur before type export of `B`',
+              }],
+            }),
+            test({
+              code: `
+                import { type B, A, default as C } from "./Z";
+              `,
+              output: `
+                import { A, default as C, type B } from "./Z";
+              `,
+              ...parserConfig,
+              options: [{
+                named: {
+                  import: true,
+                  types: 'types-last',
+                },
+                alphabetize: { order: 'asc' },
+              }],
+              errors: [{
+                message: '`B` type import should occur after import of `default`',
+              }],
+            }),
+            test({
+              code: `
+                export { A, type Z } from "./Z";
+              `,
+              output: `
+                export { type Z, A } from "./Z";
+              `,
+              ...parserConfig,
+              options: [{
+                named: {
+                  enabled: true,
+                  types: 'types-first',
+                },
+              }],
+              errors: [
+                { message: '`Z` type export should occur before export of `A`' },
+              ],
+            }),
+          ] : [],
+
+          isCoreModule('node:child_process') && isCoreModule('node:fs/promises') ? [
+            test({
+              code: `
+                import express from 'express';
+                import log4js from 'log4js';
+                import chpro from 'node:child_process';
+                // import fsp from 'node:fs/promises';
+              `,
+              output: `
+                import chpro from 'node:child_process';
+                import express from 'express';
+                import log4js from 'log4js';
+                // import fsp from 'node:fs/promises';
+              `,
+              options: [{
+                groups: [
+                  'builtin',
+                  'external',
+                  'internal',
+                  'parent',
+                  'sibling',
+                  'index',
+                  'object',
+                  'type',
+                ],
+              }],
+              errors: [
+                { message: '`node:child_process` import should occur before import of `express`' },
+                // { message: '`node:fs/promises` import should occur before import of `express`' },
+              ],
+            }),
+          ] : [],
+        ),
+      });
+    });
+});
+
+flowRuleTester.run('order', rule, {
+  valid: [
+    test({
+      options: [
+        {
+          alphabetize: { order: 'asc', orderImportKind: 'asc' },
+        },
+      ],
+      code: `
+        import type {Bar} from 'common';
+        import typeof {foo} from 'common';
+        import {bar} from 'common';
+      `,
+    })],
+  invalid: [
+    test({
+      options: [
+        {
+          alphabetize: { order: 'asc', orderImportKind: 'asc' },
+        },
+      ],
+      code: `
+        import type {Bar} from 'common';
+        import {bar} from 'common';
+        import typeof {foo} from 'common';
+      `,
+      output: `
+        import type {Bar} from 'common';
+        import typeof {foo} from 'common';
+        import {bar} from 'common';
+      `,
+      errors: [{
+        message: '`common` typeof import should occur before import of `common`',
+      }],
+    }),
+    test({
+      options: [
+        {
+          alphabetize: { order: 'asc', orderImportKind: 'desc' },
+        },
+      ],
+      code: `
+        import type {Bar} from 'common';
+        import {bar} from 'common';
+        import typeof {foo} from 'common';
+      `,
+      output: `
+        import {bar} from 'common';
+        import typeof {foo} from 'common';
+        import type {Bar} from 'common';
+      `,
+      errors: [{
+        message: '`common` type import should occur after typeof import of `common`',
+      }],
+    }),
+    test({
+      options: [
+        {
+          alphabetize: { order: 'asc', orderImportKind: 'asc' },
+        },
+      ],
+      code: `
+        import type {Bar} from './local/sub';
+        import {bar} from './local/sub';
+        import {baz} from './local-sub';
+        import typeof {foo} from './local/sub';
+      `,
+      output: `
+        import type {Bar} from './local/sub';
+        import typeof {foo} from './local/sub';
+        import {bar} from './local/sub';
+        import {baz} from './local-sub';
+      `,
+      errors: [{
+        message: '`./local/sub` typeof import should occur before import of `./local/sub`',
+      }],
+    }),
+    test({
+      code: `
+        import { cfg } from 'path/path/path/src/Cfg';
+        import { l10n } from 'path/src/l10n';
+        import { helpers } from 'path/path/path/helpers';
+        import { tip } from 'path/path/tip';
+
+        import { controller } from '../../../../path/path/path/controller';
+        import { component } from '../../../../path/path/path/component';
+      `,
+      output: semver.satisfies(eslintPkg.version, '< 3') ? `
+        import { cfg } from 'path/path/path/src/Cfg';
+        import { tip } from 'path/path/tip';
+        import { l10n } from 'path/src/l10n';
+        import { helpers } from 'path/path/path/helpers';
+
+        import { component } from '../../../../path/path/path/component';
+        import { controller } from '../../../../path/path/path/controller';
+      ` : `
+        import { helpers } from 'path/path/path/helpers';
+        import { cfg } from 'path/path/path/src/Cfg';
+        import { l10n } from 'path/src/l10n';
+        import { tip } from 'path/path/tip';
+
+        import { component } from '../../../../path/path/path/component';
+        import { controller } from '../../../../path/path/path/controller';
+      `,
+      options: [
+        {
+          groups: [
+            ['builtin', 'external'],
+            'internal',
+            ['sibling', 'parent'],
+            'object',
+            'type',
+          ],
+          pathGroups: [
+            {
+              pattern: 'react',
+              group: 'builtin',
+              position: 'before',
+              patternOptions: {
+                matchBase: true,
+              },
+            },
+            {
+              pattern: '*.+(css|svg)',
+              group: 'type',
+              position: 'after',
+              patternOptions: {
+                matchBase: true,
+              },
+            },
+          ],
+          pathGroupsExcludedImportTypes: ['react'],
+          alphabetize: {
+            order: 'asc',
+          },
+          'newlines-between': 'always',
+        },
+      ],
+      errors: [
+        {
+          message: '`path/path/path/helpers` import should occur before import of `path/path/path/src/Cfg`',
+          line: 4,
+          column: 9,
+        },
+        {
+          message: '`path/path/tip` import should occur before import of `path/src/l10n`',
+          line: 5,
+          column: 9,
+        },
+        {
+          message: '`../../../../path/path/path/component` import should occur before import of `../../../../path/path/path/controller`',
+          line: 8,
+          column: 9,
+        },
+      ],
+    }),
+  ],
+});
diff --git a/tests/src/rules/prefer-default-export.js b/tests/src/rules/prefer-default-export.js
index ae630b447..8e459873f 100644
--- a/tests/src/rules/prefer-default-export.js
+++ b/tests/src/rules/prefer-default-export.js
@@ -1,105 +1,126 @@
-import { test, getNonDefaultParsers } from '../utils'
+import { test, testVersion, getNonDefaultParsers, parsers } from '../utils';
 
-import { RuleTester } from 'eslint'
+import { RuleTester } from '../rule-tester';
+import semver from 'semver';
+import { version as tsEslintVersion } from 'typescript-eslint-parser/package.json';
 
-const ruleTester = new RuleTester()
-    , rule = require('rules/prefer-default-export')
+const ruleTester = new RuleTester();
+const rule = require('../../../src/rules/prefer-default-export');
 
+const SINGLE_EXPORT_ERROR_MESSAGE = 'Prefer default export on a file with single export.';
+const ANY_EXPORT_ERROR_MESSAGE = 'Prefer default export to be present on every file that has export.';
+
+// test cases for default option { target: 'single' }
 ruleTester.run('prefer-default-export', rule, {
-  valid: [
+  valid: [].concat(
     test({
       code: `
         export const foo = 'foo';
         export const bar = 'bar';`,
-      }),
+    }),
     test({
       code: `
         export default function bar() {};`,
-      }),
+    }),
     test({
       code: `
         export const foo = 'foo';
         export function bar() {};`,
-      }),
+    }),
     test({
       code: `
         export const foo = 'foo';
         export default bar;`,
-      }),
+    }),
     test({
       code: `
         let foo, bar;
         export { foo, bar }`,
-      }),
+    }),
     test({
       code: `
         export const { foo, bar } = item;`,
-      }),
+    }),
     test({
       code: `
         export const { foo, bar: baz } = item;`,
-      }),
+    }),
     test({
       code: `
         export const { foo: { bar, baz } } = item;`,
-      }),
+    }),
+    test({
+      code: `
+        export const [a, b] = item;`,
+    }),
     test({
       code: `
         let item;
         export const foo = item;
         export { item };`,
-      }),
+    }),
     test({
       code: `
         let foo;
         export { foo as default }`,
-      }),
+    }),
     test({
       code: `
         export * from './foo';`,
-      }),
+    }),
     test({
       code: `export Memory, { MemoryValue } from './Memory'`,
-      parser: require.resolve('babel-eslint'),
-      }),
+      parser: parsers.BABEL_OLD,
+    }),
 
     // no exports at all
     test({
       code: `
         import * as foo from './foo';`,
-      }),
+    }),
 
     test({
       code: `export type UserId = number;`,
-      parser: require.resolve('babel-eslint'),
-      }),
+      parser: parsers.BABEL_OLD,
+    }),
 
     // issue #653
     test({
       code: 'export default from "foo.js"',
-      parser: require.resolve('babel-eslint'),
+      parser: parsers.BABEL_OLD,
     }),
     test({
       code: 'export { a, b } from "foo.js"',
-      parser: require.resolve('babel-eslint'),
+      parser: parsers.BABEL_OLD,
     }),
     // ...SYNTAX_CASES,
-  ],
+    test({
+      code: `
+        export const [CounterProvider,, withCounter] = func();;
+      `,
+      parser: parsers.BABEL_OLD,
+    }),
+    // es2022: Arbitrary module namespae identifier names
+    testVersion('>= 8.7', () => ({
+      code: 'let foo; export { foo as "default" };',
+      parserOptions: { ecmaVersion: 2022 },
+    })),
+  ),
   invalid: [
     test({
       code: `
         export function bar() {};`,
       errors: [{
-        ruleId: 'ExportNamedDeclaration',
-        message: 'Prefer default export.',
+        type: 'ExportNamedDeclaration',
+        message: SINGLE_EXPORT_ERROR_MESSAGE,
       }],
     }),
     test({
       code: `
         export const foo = 'foo';`,
       errors: [{
-        ruleId: 'ExportNamedDeclaration',
-        message: 'Prefer default export.',
+        type: 'ExportNamedDeclaration',
+        message: SINGLE_EXPORT_ERROR_MESSAGE,
       }],
     }),
     test({
@@ -107,33 +128,214 @@ ruleTester.run('prefer-default-export', rule, {
         const foo = 'foo';
         export { foo };`,
       errors: [{
-        ruleId: 'ExportNamedDeclaration',
-        message: 'Prefer default export.',
+        type: 'ExportSpecifier',
+        message: SINGLE_EXPORT_ERROR_MESSAGE,
       }],
     }),
     test({
       code: `
         export const { foo } = { foo: "bar" };`,
       errors: [{
-        ruleId: 'ExportNamedDeclaration',
-        message: 'Prefer default export.',
+        type: 'ExportNamedDeclaration',
+        message: SINGLE_EXPORT_ERROR_MESSAGE,
       }],
     }),
     test({
       code: `
         export const { foo: { bar } } = { foo: { bar: "baz" } };`,
       errors: [{
-        ruleId: 'ExportNamedDeclaration',
-        message: 'Prefer default export.',
+        type: 'ExportNamedDeclaration',
+        message: SINGLE_EXPORT_ERROR_MESSAGE,
+      }],
+    }),
+    test({
+      code: `
+        export const [a] = ["foo"]`,
+      errors: [{
+        type: 'ExportNamedDeclaration',
+        message: SINGLE_EXPORT_ERROR_MESSAGE,
       }],
     }),
   ],
 });
 
-context('Typescript', function() {
+// test cases for { target: 'any' }
+ruleTester.run('prefer-default-export', rule, {
+  // Any exporting file must contain default export
+  valid: [].concat(
+    test({
+      code: `
+          export default function bar() {};`,
+      options: [{
+        target: 'any',
+      }],
+    }),
+    test({
+      code: `
+              export const foo = 'foo';
+              export const bar = 'bar';
+              export default 42;`,
+      options: [{
+        target: 'any',
+      }],
+    }),
+    test({
+      code: `
+            export default a = 2;`,
+      options: [{
+        target: 'any',
+      }],
+    }),
+    test({
+      code: `
+            export const a = 2;
+            export default function foo() {};`,
+      options: [{
+        target: 'any',
+      }],
+    }),
+    test({
+      code: `
+          export const a = 5;
+          export function bar(){};
+          let foo;
+          export { foo as default }`,
+      options: [{
+        target: 'any',
+      }],
+    }),
+    test({
+      code: `
+          export * from './foo';`,
+      options: [{
+        target: 'any',
+      }],
+    }),
+    test({
+      code: `export Memory, { MemoryValue } from './Memory'`,
+      parser: parsers.BABEL_OLD,
+      options: [{
+        target: 'any',
+      }],
+    }),
+    // no exports at all
+    test({
+      code: `
+            import * as foo from './foo';`,
+      options: [{
+        target: 'any',
+      }],
+    }),
+    test({
+      code: `const a = 5;`,
+      options: [{
+        target: 'any',
+      }],
+    }),
+    // es2022: Arbitrary module namespae identifier names
+    testVersion('>= 8.7', () => ({
+      code: 'export const a = 4; let foo; export { foo as "default" };',
+      options: [{
+        target: 'any',
+      }],
+      parserOptions: { ecmaVersion: 2022 },
+    })),
+  ),
+  // { target: 'any' } invalid cases when any exporting file must contain default export but does not
+  invalid: [].concat(
+    test({
+      code: `
+        export const foo = 'foo';
+        export const bar = 'bar';`,
+      options: [{
+        target: 'any',
+      }],
+      errors: [{
+        message: ANY_EXPORT_ERROR_MESSAGE,
+      }],
+    }),
+    test({
+      code: `
+        export const foo = 'foo';
+        export function bar() {};`,
+      options: [{
+        target: 'any',
+      }],
+      errors: [{
+        message: ANY_EXPORT_ERROR_MESSAGE,
+      }],
+    }),
+    test({
+      code: `
+        let foo, bar;
+        export { foo, bar }`,
+      options: [{
+        target: 'any',
+      }],
+      errors: [{
+        message: ANY_EXPORT_ERROR_MESSAGE,
+      }],
+    }),
+    test({
+      code: `
+        let item;
+        export const foo = item;
+        export { item };`,
+      options: [{
+        target: 'any',
+      }],
+      errors: [{
+        message: ANY_EXPORT_ERROR_MESSAGE,
+      }],
+    }),
+    test({
+      code: 'export { a, b } from "foo.js"',
+      parser: parsers.BABEL_OLD,
+      options: [{
+        target: 'any',
+      }],
+      errors: [{
+        message: ANY_EXPORT_ERROR_MESSAGE,
+      }],
+    }),
+    test({
+      code: `
+        const foo = 'foo';
+        export { foo };`,
+      options: [{
+        target: 'any',
+      }],
+      errors: [{
+        message: ANY_EXPORT_ERROR_MESSAGE,
+      }],
+    }),
+    test({
+      code: `
+        export const { foo } = { foo: "bar" };`,
+      options: [{
+        target: 'any',
+      }],
+      errors: [{
+        message: ANY_EXPORT_ERROR_MESSAGE,
+      }],
+    }),
+    test({
+      code: `
+        export const { foo: { bar } } = { foo: { bar: "baz" } };`,
+      options: [{
+        target: 'any',
+      }],
+      errors: [{
+        message: ANY_EXPORT_ERROR_MESSAGE,
+      }],
+    }),
+  ),
+});
+
+context('TypeScript', function () {
   getNonDefaultParsers().forEach((parser) => {
     const parserConfig = {
-      parser: parser,
+      parser,
       settings: {
         'import/parsers': { [parser]: ['.ts'] },
         'import/resolver': { 'eslint-import-resolver-typescript': true },
@@ -141,48 +343,37 @@ context('Typescript', function() {
     };
 
     ruleTester.run('prefer-default-export', rule, {
-      valid: [
+      valid: [].concat(
         // Exporting types
-        test(
-          {
-            code: `
-        export type foo = string;
-        export type bar = number;`,
-            parser,
-          },
-          parserConfig,
-        ),
-        test(
-          {
-            code: `
-        export type foo = string;
-        export type bar = number;`,
-            parser,
-          },
-          parserConfig,
-        ),
-        test(
-          {
-            code: 'export type foo = string',
-            parser,
-          },
-          parserConfig,
-        ),
-        test(
-          {
-            code: 'export type foo = string',
-            parser,
-          },
-          parserConfig,
-        ),
-        test (
-          {
-            code: 'export interface foo { bar: string; }',
-            parser,
-          },
-          parserConfig,
-        ),
-      ],
+        semver.satisfies(tsEslintVersion, '>= 22') ? test({
+          code: `
+            export type foo = string;
+            export type bar = number;
+            /* ${parser.replace(process.cwd(), '$$PWD')} */
+          `,
+          ...parserConfig,
+        }) : [],
+        test({
+          code: `
+            export type foo = string;
+            export type bar = number;
+            /* ${parser.replace(process.cwd(), '$$PWD')} */
+          `,
+          ...parserConfig,
+        }),
+        semver.satisfies(tsEslintVersion, '>= 22') ? test({
+          code: `export type foo = string /* ${parser.replace(process.cwd(), '$$PWD')}*/`,
+          ...parserConfig,
+        }) : [],
+        semver.satisfies(tsEslintVersion, '> 20') ? test({
+          code: `export interface foo { bar: string; } /* ${parser.replace(process.cwd(), '$$PWD')}*/`,
+          ...parserConfig,
+        }) : [],
+        test({
+          code: `export interface foo { bar: string; }; export function goo() {} /* ${parser.replace(process.cwd(), '$$PWD')}*/`,
+          ...parserConfig,
+        }),
+      ),
       invalid: [],
     });
   });
diff --git a/tests/src/rules/unambiguous.js b/tests/src/rules/unambiguous.js
index 09ca57ef9..15c67470e 100644
--- a/tests/src/rules/unambiguous.js
+++ b/tests/src/rules/unambiguous.js
@@ -1,7 +1,8 @@
-import { RuleTester } from 'eslint'
+import { RuleTester, withoutAutofixOutput } from '../rule-tester';
+import { parsers } from '../utils';
 
-const ruleTester = new RuleTester()
-    , rule = require('rules/unambiguous')
+const ruleTester = new RuleTester();
+const rule = require('rules/unambiguous');
 
 ruleTester.run('unambiguous', rule, {
   valid: [
@@ -38,7 +39,7 @@ ruleTester.run('unambiguous', rule, {
     },
     {
       code: 'function x() {}; export * as y from "z"',
-      parser: require.resolve('babel-eslint'),
+      parser: parsers.BABEL_OLD,
       parserOptions: { ecmaVersion: 2015, sourceType: 'module' },
     },
     {
@@ -47,10 +48,10 @@ ruleTester.run('unambiguous', rule, {
     },
   ],
   invalid: [
-    {
+    withoutAutofixOutput({
       code: 'function x() {}',
       parserOptions: { ecmaVersion: 2015, sourceType: 'module' },
       errors: ['This module could be parsed as a valid script.'],
-    },
+    }),
   ],
-})
+});
diff --git a/tests/src/scc.js b/tests/src/scc.js
new file mode 100644
index 000000000..376b783ce
--- /dev/null
+++ b/tests/src/scc.js
@@ -0,0 +1,179 @@
+import sinon from 'sinon';
+import { expect } from 'chai';
+import StronglyConnectedComponentsBuilder from '../../src/scc';
+import ExportMapBuilder from '../../src/exportMap/builder';
+
+function exportMapFixtureBuilder(path, imports, isOnlyImportingTypes = false) {
+  return {
+    path,
+    imports: new Map(imports.map((imp) => [imp.path, { getter: () => imp, declarations: [{ isOnlyImportingTypes }] }])),
+  };
+}
+
+describe('Strongly Connected Components Builder', () => {
+  afterEach(() => ExportMapBuilder.for.restore());
+  afterEach(() => StronglyConnectedComponentsBuilder.clearCache());
+
+  describe('When getting an SCC', () => {
+    const source = '';
+    const context = {
+      settings: {},
+      parserOptions: {},
+      parserPath: '',
+    };
+
+    describe('Given two files', () => {
+      describe('When they don\'t value-cycle', () => {
+        it('Should return foreign SCCs', () => {
+          sinon.stub(ExportMapBuilder, 'for').returns(
+            exportMapFixtureBuilder('foo.js', [exportMapFixtureBuilder('bar.js', [])]),
+          );
+          const actual = StronglyConnectedComponentsBuilder.for(source, context);
+          expect(actual).to.deep.equal({ 'foo.js': 1, 'bar.js': 0 });
+        });
+      });
+
+      describe('When they do value-cycle', () => {
+        it('Should return same SCC', () => {
+          sinon.stub(ExportMapBuilder, 'for').returns(
+            exportMapFixtureBuilder('foo.js', [
+              exportMapFixtureBuilder('bar.js', [
+                exportMapFixtureBuilder('foo.js', [exportMapFixtureBuilder('bar.js', [])]),
+              ]),
+            ]),
+          );
+          const actual = StronglyConnectedComponentsBuilder.for(source, context);
+          expect(actual).to.deep.equal({ 'foo.js': 0, 'bar.js': 0 });
+        });
+      });
+
+      describe('When they type-cycle', () => {
+        it('Should return foreign SCCs', () => {
+          sinon.stub(ExportMapBuilder, 'for').returns(
+            exportMapFixtureBuilder('foo.js', [
+              exportMapFixtureBuilder('bar.js', [
+                exportMapFixtureBuilder('foo.js', []),
+              ], true),
+            ]),
+          );
+          const actual = StronglyConnectedComponentsBuilder.for(source, context);
+          expect(actual).to.deep.equal({ 'foo.js': 1, 'bar.js': 0 });
+        });
+      });
+    });
+
+    describe('Given three files', () => {
+      describe('When they form a line', () => {
+        describe('When A -> B -> C', () => {
+          it('Should return foreign SCCs', () => {
+            sinon.stub(ExportMapBuilder, 'for').returns(
+              exportMapFixtureBuilder('foo.js', [
+                exportMapFixtureBuilder('bar.js', [
+                  exportMapFixtureBuilder('buzz.js', []),
+                ]),
+              ]),
+            );
+            const actual = StronglyConnectedComponentsBuilder.for(source, context);
+            expect(actual).to.deep.equal({ 'foo.js': 2, 'bar.js': 1, 'buzz.js': 0 });
+          });
+        });
+
+        describe('When A -> B <-> C', () => {
+          it('Should return 2 SCCs, A on its own', () => {
+            sinon.stub(ExportMapBuilder, 'for').returns(
+              exportMapFixtureBuilder('foo.js', [
+                exportMapFixtureBuilder('bar.js', [
+                  exportMapFixtureBuilder('buzz.js', [
+                    exportMapFixtureBuilder('bar.js', []),
+                  ]),
+                ]),
+              ]),
+            );
+            const actual = StronglyConnectedComponentsBuilder.for(source, context);
+            expect(actual).to.deep.equal({ 'foo.js': 1, 'bar.js': 0, 'buzz.js': 0 });
+          });
+        });
+
+        describe('When A <-> B -> C', () => {
+          it('Should return 2 SCCs, C on its own', () => {
+            sinon.stub(ExportMapBuilder, 'for').returns(
+              exportMapFixtureBuilder('foo.js', [
+                exportMapFixtureBuilder('bar.js', [
+                  exportMapFixtureBuilder('buzz.js', []),
+                  exportMapFixtureBuilder('foo.js', []),
+                ]),
+              ]),
+            );
+            const actual = StronglyConnectedComponentsBuilder.for(source, context);
+            expect(actual).to.deep.equal({ 'foo.js': 1, 'bar.js': 1, 'buzz.js': 0 });
+          });
+        });
+
+        describe('When A <-> B <-> C', () => {
+          it('Should return same SCC', () => {
+            sinon.stub(ExportMapBuilder, 'for').returns(
+              exportMapFixtureBuilder('foo.js', [
+                exportMapFixtureBuilder('bar.js', [
+                  exportMapFixtureBuilder('foo.js', []),
+                  exportMapFixtureBuilder('buzz.js', [
+                    exportMapFixtureBuilder('bar.js', []),
+                  ]),
+                ]),
+              ]),
+            );
+            const actual = StronglyConnectedComponentsBuilder.for(source, context);
+            expect(actual).to.deep.equal({ 'foo.js': 0, 'bar.js': 0, 'buzz.js': 0 });
+          });
+        });
+      });
+
+      describe('When they form a loop', () => {
+        it('Should return same SCC', () => {
+          sinon.stub(ExportMapBuilder, 'for').returns(
+            exportMapFixtureBuilder('foo.js', [
+              exportMapFixtureBuilder('bar.js', [
+                exportMapFixtureBuilder('buzz.js', [
+                  exportMapFixtureBuilder('foo.js', []),
+                ]),
+              ]),
+            ]),
+          );
+          const actual = StronglyConnectedComponentsBuilder.for(source, context);
+          expect(actual).to.deep.equal({ 'foo.js': 0, 'bar.js': 0, 'buzz.js': 0 });
+        });
+      });
+
+      describe('When they form a Y', () => {
+        it('Should return 3 distinct SCCs', () => {
+          sinon.stub(ExportMapBuilder, 'for').returns(
+            exportMapFixtureBuilder('foo.js', [
+              exportMapFixtureBuilder('bar.js', []),
+              exportMapFixtureBuilder('buzz.js', []),
+            ]),
+          );
+          const actual = StronglyConnectedComponentsBuilder.for(source, context);
+          expect(actual).to.deep.equal({ 'foo.js': 2, 'bar.js': 0, 'buzz.js': 1 });
+        });
+      });
+
+      describe('When they form a Mercedes', () => {
+        it('Should return 1 SCC', () => {
+          sinon.stub(ExportMapBuilder, 'for').returns(
+            exportMapFixtureBuilder('foo.js', [
+              exportMapFixtureBuilder('bar.js', [
+                exportMapFixtureBuilder('foo.js', []),
+                exportMapFixtureBuilder('buzz.js', []),
+              ]),
+              exportMapFixtureBuilder('buzz.js', [
+                exportMapFixtureBuilder('foo.js', []),
+                exportMapFixtureBuilder('bar.js', []),
+              ]),
+            ]),
+          );
+          const actual = StronglyConnectedComponentsBuilder.for(source, context);
+          expect(actual).to.deep.equal({ 'foo.js': 0, 'bar.js': 0, 'buzz.js': 0 });
+        });
+      });
+    });
+  });
+});
diff --git a/tests/src/utils.js b/tests/src/utils.js
index 9e0c4a1e0..24d5504a7 100644
--- a/tests/src/utils.js
+++ b/tests/src/utils.js
@@ -1,54 +1,73 @@
-import path from 'path'
-import eslintPkg from 'eslint/package.json'
-import semver from 'semver'
+import path from 'path';
+import eslintPkg from 'eslint/package.json';
+import semver from 'semver';
+import typescriptPkg from 'typescript/package.json';
 
 // warms up the module cache. this import takes a while (>500ms)
-import 'babel-eslint'
+import 'babel-eslint';
+
+export const parsers = {
+  ESPREE: require.resolve('espree'),
+  TS_OLD: semver.satisfies(eslintPkg.version, '>=4.0.0 <6.0.0') && semver.satisfies(typescriptPkg.version, '<4') && require.resolve('typescript-eslint-parser'),
+  TS_NEW: semver.satisfies(eslintPkg.version, '> 5') && require.resolve('@typescript-eslint/parser'),
+  BABEL_OLD: require.resolve('babel-eslint'),
+};
+
+export function tsVersionSatisfies(specifier) {
+  return semver.satisfies(typescriptPkg.version, specifier);
+}
+
+export function typescriptEslintParserSatisfies(specifier) {
+  return parsers.TS_NEW && semver.satisfies(require('@typescript-eslint/parser/package.json').version, specifier);
+}
 
 export function testFilePath(relativePath) {
-  return path.join(process.cwd(), './tests/files', relativePath)
+  return path.join(process.cwd(), './tests/files', relativePath);
 }
 
 export function getTSParsers() {
-  const parsers = [];
-  if (semver.satisfies(eslintPkg.version, '>=4.0.0 <6.0.0')) {
-    parsers.push(require.resolve('typescript-eslint-parser'));
-  }
-
-  if (semver.satisfies(eslintPkg.version, '>5.0.0')) {
-    parsers.push(require.resolve('@typescript-eslint/parser'));
-  }
-  return parsers;
+  return [
+    parsers.TS_OLD,
+    parsers.TS_NEW,
+  ].filter(Boolean);
 }
 
 export function getNonDefaultParsers() {
-  return getTSParsers().concat(require.resolve('babel-eslint'));
+  return getTSParsers().concat(parsers.BABEL_OLD).filter(Boolean);
 }
 
-export const FILENAME = testFilePath('foo.js')
+export const FILENAME = testFilePath('foo.js');
 
-export function testVersion(specifier, t) {
-  return semver.satisfies(eslintPkg.version, specifier) && test(t())
+export function eslintVersionSatisfies(specifier) {
+  return semver.satisfies(eslintPkg.version, specifier);
 }
 
 export function test(t) {
-  return Object.assign({
+  if (arguments.length !== 1) {
+    throw new SyntaxError('`test` requires exactly one object argument');
+  }
+  return {
     filename: FILENAME,
-  }, t, {
-    parserOptions: Object.assign({
+    ...t,
+    parserOptions: {
       sourceType: 'module',
-      ecmaVersion: 6,
-    }, t.parserOptions),
-  })
+      ecmaVersion: 9,
+      ...t.parserOptions,
+    },
+  };
+}
+
+export function testVersion(specifier, t) {
+  return eslintVersionSatisfies(specifier) ? test(t()) : [];
 }
 
 export function testContext(settings) {
-  return { getFilename: function () { return FILENAME }
-         , settings: settings || {} }
+  return { getFilename() { return FILENAME; },
+    settings: settings || {} };
 }
 
 export function getFilename(file) {
-  return path.join(__dirname, '..', 'files', file || 'foo.js')
+  return path.join(__dirname, '..', 'files', file || 'foo.js');
 }
 
 /**
@@ -62,7 +81,7 @@ export const SYNTAX_CASES = [
   test({ code: 'for (let [ foo, bar ] of baz) {}' }),
 
   test({ code: 'const { x, y } = bar' }),
-  test({ code: 'const { x, y, ...z } = bar', parser: require.resolve('babel-eslint') }),
+  test({ code: 'const { x, y, ...z } = bar', parser: parsers.BABEL_OLD }),
 
   // all the exports
   test({ code: 'let x; export { x }' }),
@@ -70,7 +89,7 @@ export const SYNTAX_CASES = [
 
   // not sure about these since they reference a file
   // test({ code: 'export { x } from "./y.js"'}),
-  // test({ code: 'export * as y from "./y.js"', parser: require.resolve('babel-eslint')}),
+  // test({ code: 'export * as y from "./y.js"', parser: parsers.BABEL_OLD}),
 
   test({ code: 'export const x = null' }),
   test({ code: 'export var x = null' }),
@@ -115,5 +134,4 @@ export const SYNTAX_CASES = [
   test({
     code: 'import { foo } from "./ignore.invalid.extension"',
   }),
-
-]
+];
diff --git a/utils/.attw.json b/utils/.attw.json
new file mode 100644
index 000000000..45dd01e12
--- /dev/null
+++ b/utils/.attw.json
@@ -0,0 +1,5 @@
+{
+  "ignoreRules": [
+    "cjs-only-exports-default"
+  ]
+}
diff --git a/utils/.npmignore b/utils/.npmignore
new file mode 100644
index 000000000..366f3ebb6
--- /dev/null
+++ b/utils/.npmignore
@@ -0,0 +1 @@
+.attw.json
diff --git a/utils/.nycrc b/utils/.nycrc
new file mode 100644
index 000000000..108436087
--- /dev/null
+++ b/utils/.nycrc
@@ -0,0 +1,15 @@
+{
+	"all": true,
+	"check-coverage": false,
+	"reporter": ["text-summary", "lcov", "text", "html", "json"],
+	"instrument": false,
+	"exclude": [
+		"coverage",
+		"test",
+		"tests",
+		"resolvers/*/test",
+		"scripts",
+		"memo-parser",
+		"lib"
+	]
+}
diff --git a/utils/CHANGELOG.md b/utils/CHANGELOG.md
index d0b2128ed..fe7381a2d 100644
--- a/utils/CHANGELOG.md
+++ b/utils/CHANGELOG.md
@@ -1,15 +1,148 @@
 # 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
 
+### Fixed
+- `unambiguous`: detect modules exported from minified code ([#3124], thanks [@michaelfaith])
+
+### Changed
+- [refactor] `parse`: avoid using a regex here (thanks [@ljharb])
+
+## v2.12.0 - 2024-09-26
+
+### Added
+- `hash`: add support for hashing functions ([#3072], thanks [@michaelfaith])
+
+## v2.11.1 - 2024-09-23
+
+### Fixed
+- `parse`: remove unneeded extra backticks ([#3057], thanks [@G-Rath])
+- `parse`: espree parser isn't working with flat config ([#3061], thanks [@michaelfaith])
+- `parse`: add `ecmaVersion` and `sourceType` to `parserOptions` ([#3061], thanks [@michaelfaith])
+
+## v2.11.0 - 2024-09-05
+
+### New
+- `declaredScope`: take a `node` for modern eslint versions (thanks [@michaelfaith])
+
+## v2.10.0 - 2024-09-05
+
+### New
+- add context compatibility helpers ([#3049], thanks [@michaelfaith])
+
+## v2.9.0 - 2024-09-02
+
+### New
+- add support for Flat Config ([#3018], thanks [@michaelfaith])
+
+## v2.8.2 - 2024-08-25
+
+### Fixed
+- `parse`: also delete `parserOptions.projectService` ([#3039], thanks [@Mysak0CZ])
+
+### Changed
+- [types] use shared config (thanks [@ljharb])
+- [meta] add `exports`, `main`
+- [meta] add `repository.directory` field
+- [refactor] avoid hoisting
+
+## v2.8.1 - 2024-02-26
+
+### Fixed
+- `parse`: also delete `parserOptions.EXPERIMENTAL_useProjectService` ([#2963], thanks [@JoshuaKGoldberg])
+
+### Changed
+- add types (thanks [@ljharb])
+
+## v2.8.0 - 2023-04-14
+
+### New
+- `parse`: support flat config ([#2714], thanks [@DMartens])
+
+### Fixed
+- Improve performance of `fullResolve` for large projects ([#2755], thanks [@leipert])
+
+## v2.7.4 - 2022-08-11
+
+### Fixed
+- [Fix] Ignore hashbang and BOM while parsing ([#2431], thanks [@silverwind])
+
+### Changed
+- [patch] mark eslint as an optional peer dep ([#2523], thanks [@wmertens])
+
+## v2.7.3 - 2022-01-26
+
+### Fixed
+- `parse`: restore compatibility by making the return value `ast` again ([#2350], thanks [@ljharb])
+
+## v2.7.2 - 2022-01-01
+
+### Fixed
+- [patch] Fix `@babel/eslint-parser` 8 compatibility ([#2343], thanks [@nicolo-ribaudo])
+
+### Changed
+- [Refactor] inline `pkgDir` implementation; remove `pkg-dir`
+
+## v2.7.1 - 2021-10-13
+
+### Fixed
+- fixed SyntaxError in node <= 6: Unexpected token ) in parse.js ([#2261], thanks [@VitusFW])
+
+## v2.7.0 - 2021-10-11
+
+### Added
+- `fileExistsWithCaseSync`: add `strict` argument ([#1262], thanks [@sergei-startsev])
+- add `visit`, to support dynamic imports ([#1660], [#2212], thanks [@maxkomarychev], [@aladdin-add], [@Hypnosphi])
+- create internal replacement for `pkg-up` and `read-pkg-up` ([#2047], [@mgwalker])
+
+## v2.6.2 - 2021-08-08
+
+### Fixed
+- Use `context.getPhysicalFilename()` when available (ESLint 7.28+) ([#2160], thanks [@pmcelhaney])
+
+## v2.6.1 - 2021-05-13
+
+### Fixed
+- `no-unresolved`: check `import()` ([#2026], thanks [@aladdin-add])
+- Add fix for Windows Subsystem for Linux ([#1786], thanks [@manuth])
+
+### Changed
+- [deps] update `debug`
+- [Refactor] use `Array.isArray` instead of `instanceof Array`
+
+## v2.6.0 - 2020-03-28
+
+### Added
+- Print more helpful info if parsing fails ([#1671], thanks [@kaiyoma])
+
+## v2.5.2 - 2020-01-12
+
+### Fixed
+- Makes the loader resolution more tolerant ([#1606], thanks [@arcanis])
+- Use `createRequire` instead of `createRequireFromPath` if available ([#1602], thanks [@iamnapo])
+
+## v2.5.1 - 2020-01-11
+
+### Fixed
+- Uses createRequireFromPath to resolve loaders ([#1591], thanks [@arcanis])
+- report the error stack on a resolution error ([#599], thanks [@sompylasar])
+
+## v2.5.0 - 2019-12-07
+
+### Added
+- support `parseForESLint` from custom parser ([#1435], thanks [@JounQin])
+
+### Changed
+ - Avoid superfluous calls and code ([#1551], thanks [@brettz9])
+
 ## v2.4.1 - 2019-07-19
 
 ### Fixed
  - Improve parse perf when using `@typescript-eslint/parser` ([#1409], thanks [@bradzacher])
- - Improve support for Typescript declare structures ([#1356], thanks [@christophercurrie])
+ - Improve support for TypeScript declare structures ([#1356], thanks [@christophercurrie])
 
 ## v2.4.0 - 2019-04-13
 
@@ -50,18 +183,69 @@ Yanked due to critical issue with cache key resulting from #839.
 ### Fixed
 - `unambiguous.test()` regex is now properly in multiline mode
 
-
-
-[#1409]: https://github.com/benmosher/eslint-plugin-import/pull/1409
-[#1356]: https://github.com/benmosher/eslint-plugin-import/pull/1356
-[#1290]: https://github.com/benmosher/eslint-plugin-import/pull/1290
-[#1218]: https://github.com/benmosher/eslint-plugin-import/pull/1218
-[#1166]: https://github.com/benmosher/eslint-plugin-import/issues/1166
-[#1160]: https://github.com/benmosher/eslint-plugin-import/pull/1160
-[#1035]: https://github.com/benmosher/eslint-plugin-import/issues/1035
-
+[#3124]: https://github.com/import-js/eslint-plugin-import/pull/3124
+[#3072]: https://github.com/import-js/eslint-plugin-import/pull/3072
+[#3061]: https://github.com/import-js/eslint-plugin-import/pull/3061
+[#3057]: https://github.com/import-js/eslint-plugin-import/pull/3057
+[#3049]: https://github.com/import-js/eslint-plugin-import/pull/3049
+[#3039]: https://github.com/import-js/eslint-plugin-import/pull/3039
+[#3018]: https://github.com/import-js/eslint-plugin-import/pull/3018
+[#2963]: https://github.com/import-js/eslint-plugin-import/pull/2963
+[#2755]: https://github.com/import-js/eslint-plugin-import/pull/2755
+[#2714]: https://github.com/import-js/eslint-plugin-import/pull/2714
+[#2523]: https://github.com/import-js/eslint-plugin-import/pull/2523
+[#2431]: https://github.com/import-js/eslint-plugin-import/pull/2431
+[#2350]: https://github.com/import-js/eslint-plugin-import/issues/2350
+[#2343]: https://github.com/import-js/eslint-plugin-import/pull/2343
+[#2261]: https://github.com/import-js/eslint-plugin-import/pull/2261
+[#2212]: https://github.com/import-js/eslint-plugin-import/pull/2212
+[#2160]: https://github.com/import-js/eslint-plugin-import/pull/2160
+[#2047]: https://github.com/import-js/eslint-plugin-import/pull/2047
+[#2026]: https://github.com/import-js/eslint-plugin-import/pull/2026
+[#1786]: https://github.com/import-js/eslint-plugin-import/pull/1786
+[#1671]: https://github.com/import-js/eslint-plugin-import/pull/1671
+[#1660]: https://github.com/import-js/eslint-plugin-import/pull/1660
+[#1606]: https://github.com/import-js/eslint-plugin-import/pull/1606
+[#1602]: https://github.com/import-js/eslint-plugin-import/pull/1602
+[#1591]: https://github.com/import-js/eslint-plugin-import/pull/1591
+[#1551]: https://github.com/import-js/eslint-plugin-import/pull/1551
+[#1435]: https://github.com/import-js/eslint-plugin-import/pull/1435
+[#1409]: https://github.com/import-js/eslint-plugin-import/pull/1409
+[#1356]: https://github.com/import-js/eslint-plugin-import/pull/1356
+[#1290]: https://github.com/import-js/eslint-plugin-import/pull/1290
+[#1262]: https://github.com/import-js/eslint-plugin-import/pull/1262
+[#1218]: https://github.com/import-js/eslint-plugin-import/pull/1218
+[#1166]: https://github.com/import-js/eslint-plugin-import/issues/1166
+[#1160]: https://github.com/import-js/eslint-plugin-import/pull/1160
+[#1035]: https://github.com/import-js/eslint-plugin-import/issues/1035
+[#599]: https://github.com/import-js/eslint-plugin-import/pull/599
+
+[@aladdin-add]: https://github.com/aladdin-add
+[@arcanis]: https://github.com/arcanis
+[@bradzacher]: https://github.com/bradzacher
+[@brettz9]: https://github.com/brettz9
+[@christophercurrie]: https://github.com/christophercurrie
+[@DMartens]: https://github.com/DMartens
+[@G-Rath]: https://github.com/G-Rath
 [@hulkish]: https://github.com/hulkish
+[@Hypnosphi]: https://github.com/Hypnosphi
+[@iamnapo]: https://github.com/iamnapo
+[@JoshuaKGoldberg]: https://github.com/JoshuaKGoldberg
+[@JounQin]: https://github.com/JounQin
+[@kaiyoma]: https://github.com/kaiyoma
+[@leipert]: https://github.com/leipert
+[@ljharb]: https://github.com/ljharb
+[@manuth]: https://github.com/manuth
+[@maxkomarychev]: https://github.com/maxkomarychev
+[@mgwalker]: https://github.com/mgwalker
+[@michaelfaith]: https://github.com/michaelfaith
+[@Mysak0CZ]: https://github.com/Mysak0CZ
+[@nicolo-ribaudo]: https://github.com/nicolo-ribaudo
+[@pmcelhaney]: https://github.com/pmcelhaney
+[@sergei-startsev]: https://github.com/sergei-startsev
+[@silverwind]: https://github.com/silverwind
+[@sompylasar]: https://github.com/sompylasar
 [@timkraut]: https://github.com/timkraut
 [@vikr01]: https://github.com/vikr01
-[@bradzacher]: https://github.com/bradzacher
-[@christophercurrie]: https://github.com/christophercurrie
+[@VitusFW]: https://github.com/VitusFW
+[@wmertens]: https://github.com/wmertens
diff --git a/utils/ModuleCache.d.ts b/utils/ModuleCache.d.ts
new file mode 100644
index 000000000..72a72a069
--- /dev/null
+++ b/utils/ModuleCache.d.ts
@@ -0,0 +1,22 @@
+import type { ESLintSettings } from "./types";
+
+export type CacheKey = unknown;
+export type CacheObject = {
+    result: unknown;
+    lastSeen: ReturnType<typeof process.hrtime>;
+};
+
+declare class ModuleCache {
+    map: Map<CacheKey, CacheObject>;
+
+    constructor(map?: Map<CacheKey, CacheObject>);
+
+    get<T>(cacheKey: CacheKey, settings: ESLintSettings): T | undefined;
+
+    set<T>(cacheKey: CacheKey, result: T): T;
+
+    static getSettings(settings: ESLintSettings): { lifetime: number } & Omit<ESLintSettings['import/cache'], 'lifetime'>;
+}
+export default ModuleCache;
+
+export type { ModuleCache }
diff --git a/utils/ModuleCache.js b/utils/ModuleCache.js
index eba86d258..24c76849d 100644
--- a/utils/ModuleCache.js
+++ b/utils/ModuleCache.js
@@ -1,47 +1,52 @@
-"use strict"
-exports.__esModule = true
+'use strict';
 
-const log = require('debug')('eslint-module-utils:ModuleCache')
+exports.__esModule = true;
 
+const log = require('debug')('eslint-module-utils:ModuleCache');
+
+/** @type {import('./ModuleCache').ModuleCache} */
 class ModuleCache {
+  /** @param {typeof import('./ModuleCache').ModuleCache.prototype.map} map */
   constructor(map) {
-    this.map = map || new Map()
+    this.map = map || /** @type {{typeof import('./ModuleCache').ModuleCache.prototype.map} */ new Map();
   }
 
-  /**
-   * returns value for returning inline
-   * @param {[type]} cacheKey [description]
-   * @param {[type]} result   [description]
-   */
+  /** @type {typeof import('./ModuleCache').ModuleCache.prototype.set} */
   set(cacheKey, result) {
-    this.map.set(cacheKey, { result, lastSeen: process.hrtime() })
-    log('setting entry for', cacheKey)
-    return result
+    this.map.set(cacheKey, { result, lastSeen: process.hrtime() });
+    log('setting entry for', cacheKey);
+    return result;
   }
 
+  /** @type {typeof import('./ModuleCache').ModuleCache.prototype.get} */
   get(cacheKey, settings) {
     if (this.map.has(cacheKey)) {
-      const f = this.map.get(cacheKey)
-      // check fresness
-      if (process.hrtime(f.lastSeen)[0] < settings.lifetime) return f.result
-    } else log('cache miss for', cacheKey)
+      const f = this.map.get(cacheKey);
+      // check freshness
+      // @ts-expect-error TS can't narrow properly from `has` and `get`
+      if (process.hrtime(f.lastSeen)[0] < settings.lifetime) { return f.result; }
+    } else {
+      log('cache miss for', cacheKey);
+    }
     // cache miss
-    return undefined
+    return undefined;
   }
 
-}
+  /** @type {typeof import('./ModuleCache').ModuleCache.getSettings} */
+  static getSettings(settings) {
+    /** @type {ReturnType<typeof ModuleCache.getSettings>} */
+    const cacheSettings = Object.assign({
+      lifetime: 30,  // seconds
+    }, settings['import/cache']);
 
-ModuleCache.getSettings = function (settings) {
-  const cacheSettings = Object.assign({
-    lifetime: 30,  // seconds
-  }, settings['import/cache'])
+    // parse infinity
+    // @ts-expect-error the lack of type overlap is because we're abusing `cacheSettings` as a temporary object
+    if (cacheSettings.lifetime === '∞' || cacheSettings.lifetime === 'Infinity') {
+      cacheSettings.lifetime = Infinity;
+    }
 
-  // parse infinity
-  if (cacheSettings.lifetime === '∞' || cacheSettings.lifetime === 'Infinity') {
-    cacheSettings.lifetime = Infinity
+    return cacheSettings;
   }
-
-  return cacheSettings
 }
 
-exports.default = ModuleCache
+exports.default = ModuleCache;
diff --git a/utils/contextCompat.d.ts b/utils/contextCompat.d.ts
new file mode 100644
index 000000000..43fe0a91b
--- /dev/null
+++ b/utils/contextCompat.d.ts
@@ -0,0 +1,38 @@
+import { Scope, SourceCode, Rule } from 'eslint';
+import * as ESTree from 'estree';
+
+type LegacyContext = {
+  getFilename: () => string,
+  getPhysicalFilename: () => string,
+  getSourceCode: () => SourceCode,
+  getScope: never,
+  getAncestors: never,
+  getDeclaredVariables: never,
+};
+
+type NewContext = {
+  filename: string,
+  sourceCode: SourceCode,
+  getPhysicalFilename?: () => string,
+  getScope: () => Scope.Scope,
+  getAncestors: () => ESTree.Node[],
+  getDeclaredVariables: (node: ESTree.Node) => Scope.Variable[],
+};
+
+export type Context = LegacyContext | NewContext | Rule.RuleContext;
+
+declare function getAncestors(context: Context, node: ESTree.Node): ESTree.Node[];
+declare function getDeclaredVariables(context: Context, node: ESTree.Node): Scope.Variable[];
+declare function getFilename(context: Context): string;
+declare function getPhysicalFilename(context: Context): string;
+declare function getScope(context: Context, node: ESTree.Node): Scope.Scope;
+declare function getSourceCode(context: Context): SourceCode;
+
+export {
+  getAncestors,
+  getDeclaredVariables,
+  getFilename,
+  getPhysicalFilename,
+  getScope,
+  getSourceCode,
+};
diff --git a/utils/contextCompat.js b/utils/contextCompat.js
new file mode 100644
index 000000000..b1bdc598e
--- /dev/null
+++ b/utils/contextCompat.js
@@ -0,0 +1,72 @@
+'use strict';
+
+exports.__esModule = true;
+
+/** @type {import('./contextCompat').getAncestors} */
+function getAncestors(context, node) {
+  const sourceCode = getSourceCode(context);
+
+  if (sourceCode && sourceCode.getAncestors) {
+    return sourceCode.getAncestors(node);
+  }
+
+  return context.getAncestors();
+}
+
+/** @type {import('./contextCompat').getDeclaredVariables} */
+function getDeclaredVariables(context, node) {
+  const sourceCode = getSourceCode(context);
+
+  if (sourceCode && sourceCode.getDeclaredVariables) {
+    return sourceCode.getDeclaredVariables(node);
+  }
+
+  return context.getDeclaredVariables(node);
+}
+
+/** @type {import('./contextCompat').getFilename} */
+function getFilename(context) {
+  if ('filename' in context) {
+    return context.filename;
+  }
+
+  return context.getFilename();
+}
+
+/** @type {import('./contextCompat').getPhysicalFilename} */
+function getPhysicalFilename(context) {
+  if (context.getPhysicalFilename) {
+    return context.getPhysicalFilename();
+  }
+
+  return getFilename(context);
+}
+
+/** @type {import('./contextCompat').getScope} */
+function getScope(context, node) {
+  const sourceCode = getSourceCode(context);
+
+  if (sourceCode && sourceCode.getScope) {
+    return sourceCode.getScope(node);
+  }
+
+  return context.getScope();
+}
+
+/** @type {import('./contextCompat').getSourceCode} */
+function getSourceCode(context) {
+  if ('sourceCode' in context) {
+    return context.sourceCode;
+  }
+
+  return context.getSourceCode();
+}
+
+module.exports = {
+  getAncestors,
+  getDeclaredVariables,
+  getFilename,
+  getPhysicalFilename,
+  getScope,
+  getSourceCode,
+};
diff --git a/utils/declaredScope.d.ts b/utils/declaredScope.d.ts
new file mode 100644
index 000000000..90053e8e7
--- /dev/null
+++ b/utils/declaredScope.d.ts
@@ -0,0 +1,10 @@
+import { Rule, Scope } from 'eslint';
+import * as ESTree from 'estree';
+
+declare function declaredScope(
+    context: Rule.RuleContext,
+    name: string,
+    node?: ESTree.Node,
+): Scope.Scope['type'] | undefined;
+
+export default declaredScope;
diff --git a/utils/declaredScope.js b/utils/declaredScope.js
index 2ef3d19a9..aa3e38b47 100644
--- a/utils/declaredScope.js
+++ b/utils/declaredScope.js
@@ -1,14 +1,13 @@
-"use strict"
-exports.__esModule = true
+'use strict';
 
-exports.default = function declaredScope(context, name) {
-  let references = context.getScope().references
-    , i
-  for (i = 0; i < references.length; i++) {
-    if (references[i].identifier.name === name) {
-      break
-    }
-  }
-  if (!references[i]) return undefined
-  return references[i].resolved.scope.type
-}
+exports.__esModule = true;
+
+const { getScope } = require('./contextCompat');
+
+/** @type {import('./declaredScope').default} */
+exports.default = function declaredScope(context, name, node) {
+  const references = (node ? getScope(context, node) : context.getScope()).references;
+  const reference = references.find((x) => x.identifier.name === name);
+  if (!reference || !reference.resolved) { return undefined; }
+  return reference.resolved.scope.type;
+};
diff --git a/utils/hash.d.ts b/utils/hash.d.ts
new file mode 100644
index 000000000..5e4cf471b
--- /dev/null
+++ b/utils/hash.d.ts
@@ -0,0 +1,14 @@
+import type { Hash } from 'crypto';
+
+declare function hashArray(value: Array<unknown>, hash?: Hash): Hash;
+
+declare function hashObject<T extends object>(value: T, hash?: Hash): Hash;
+
+declare function hashify(
+    value: Array<unknown> | object | unknown,
+    hash?: Hash,
+): Hash;
+
+export default hashify;
+
+export { hashArray, hashObject };
diff --git a/utils/hash.js b/utils/hash.js
index 0b946a510..21ed524a9 100644
--- a/utils/hash.js
+++ b/utils/hash.js
@@ -2,58 +2,65 @@
  * utilities for hashing config objects.
  * basically iteratively updates hash with a JSON-like format
  */
-"use strict"
-exports.__esModule = true
 
-const createHash = require('crypto').createHash
+'use strict';
 
-const stringify = JSON.stringify
+exports.__esModule = true;
 
+const createHash = require('crypto').createHash;
+
+const stringify = JSON.stringify;
+
+/** @type {import('./hash').default} */
 function hashify(value, hash) {
-  if (!hash) hash = createHash('sha256')
+  if (!hash) { hash = createHash('sha256'); }
 
-  if (value instanceof Array) {
-    hashArray(value, hash)
+  if (Array.isArray(value)) {
+    hashArray(value, hash);
+  } else if (typeof value === 'function') {
+    hash.update(String(value));
   } else if (value instanceof Object) {
-    hashObject(value, hash)
+    hashObject(value, hash);
   } else {
-    hash.update(stringify(value) || 'undefined')
+    hash.update(stringify(value) || 'undefined');
   }
 
-  return hash
+  return hash;
 }
-exports.default = hashify
+exports.default = hashify;
 
+/** @type {import('./hash').hashArray} */
 function hashArray(array, hash) {
-  if (!hash) hash = createHash('sha256')
+  if (!hash) { hash = createHash('sha256'); }
 
-  hash.update('[')
+  hash.update('[');
   for (let i = 0; i < array.length; i++) {
-    hashify(array[i], hash)
-    hash.update(',')
+    hashify(array[i], hash);
+    hash.update(',');
   }
-  hash.update(']')
+  hash.update(']');
 
-  return hash
-}
-hashify.array = hashArray
-exports.hashArray = hashArray
-
-function hashObject(object, hash) {
-  if (!hash) hash = createHash('sha256')
-
-  hash.update("{")
-  Object.keys(object).sort().forEach(key => {
-    hash.update(stringify(key))
-    hash.update(':')
-    hashify(object[key], hash)
-    hash.update(",")
-  })
-  hash.update('}')
-
-  return hash
+  return hash;
 }
-hashify.object = hashObject
-exports.hashObject = hashObject
+hashify.array = hashArray;
+exports.hashArray = hashArray;
 
+/** @type {import('./hash').hashObject} */
+function hashObject(object, optionalHash) {
+  const hash = optionalHash || createHash('sha256');
+
+  hash.update('{');
+  Object.keys(object).sort().forEach((key) => {
+    hash.update(stringify(key));
+    hash.update(':');
+    // @ts-expect-error the key is guaranteed to exist on the object here
+    hashify(object[key], hash);
+    hash.update(',');
+  });
+  hash.update('}');
+
+  return hash;
+}
+hashify.object = hashObject;
+exports.hashObject = hashObject;
 
diff --git a/utils/ignore.d.ts b/utils/ignore.d.ts
new file mode 100644
index 000000000..53953b33e
--- /dev/null
+++ b/utils/ignore.d.ts
@@ -0,0 +1,12 @@
+import { Rule } from 'eslint';
+import type { ESLintSettings, Extension } from './types';
+
+declare function ignore(path: string, context: Rule.RuleContext): boolean;
+
+declare function getFileExtensions(settings: ESLintSettings): Set<Extension>;
+
+declare function hasValidExtension(path: string, context: Rule.RuleContext): path is `${string}${Extension}`;
+
+export default ignore;
+
+export { getFileExtensions, hasValidExtension }
diff --git a/utils/ignore.js b/utils/ignore.js
index 47af8122d..a42d4ceb1 100644
--- a/utils/ignore.js
+++ b/utils/ignore.js
@@ -1,60 +1,73 @@
-'use strict'
-exports.__esModule = true
+'use strict';
 
-const extname = require('path').extname
+exports.__esModule = true;
 
-const log = require('debug')('eslint-plugin-import:utils:ignore')
+const extname = require('path').extname;
 
-// one-shot memoized
-let cachedSet, lastSettings
-function validExtensions(context) {
-  if (cachedSet && context.settings === lastSettings) {
-    return cachedSet
-  }
+const log = require('debug')('eslint-plugin-import:utils:ignore');
 
-  lastSettings = context.settings
-  cachedSet = makeValidExtensionSet(context.settings)
-  return cachedSet
-}
+// one-shot memoized
+/** @type {Set<import('./types').Extension>} */ let cachedSet;
+/** @type {import('./types').ESLintSettings} */ let lastSettings;
 
+/** @type {import('./ignore').getFileExtensions} */
 function makeValidExtensionSet(settings) {
   // start with explicit JS-parsed extensions
-  const exts = new Set(settings['import/extensions'] || [ '.js' ])
+  /** @type {Set<import('./types').Extension>} */
+  const exts = new Set(settings['import/extensions'] || ['.js', '.mjs', '.cjs']);
 
   // all alternate parser extensions are also valid
   if ('import/parsers' in settings) {
-    for (let parser in settings['import/parsers']) {
-      const parserSettings = settings['import/parsers'][parser]
+    for (const parser in settings['import/parsers']) {
+      const parserSettings = settings['import/parsers'][parser];
       if (!Array.isArray(parserSettings)) {
-        throw new TypeError('"settings" for ' + parser + ' must be an array')
+        throw new TypeError('"settings" for ' + parser + ' must be an array');
       }
-      parserSettings.forEach(ext => exts.add(ext))
+      parserSettings.forEach((ext) => exts.add(ext));
     }
   }
 
-  return exts
+  return exts;
 }
-exports.getFileExtensions = makeValidExtensionSet
+exports.getFileExtensions = makeValidExtensionSet;
 
+/** @type {(context: import('eslint').Rule.RuleContext) => Set<import('./types').Extension>} */
+function validExtensions(context) {
+  if (cachedSet && context.settings === lastSettings) {
+    return cachedSet;
+  }
+
+  lastSettings = context.settings;
+  cachedSet = makeValidExtensionSet(context.settings);
+  return cachedSet;
+}
+
+/** @type {import('./ignore').hasValidExtension} */
+function hasValidExtension(path, context) {
+  // eslint-disable-next-line no-extra-parens
+  return validExtensions(context).has(/** @type {import('./types').Extension} */ (extname(path)));
+}
+exports.hasValidExtension = hasValidExtension;
+
+/** @type {import('./ignore').default} */
 exports.default = function ignore(path, context) {
   // check extension whitelist first (cheap)
-  if (!hasValidExtension(path, context)) return true
+  if (!hasValidExtension(path, context)) {
+    return true;
+  }
 
-  if (!('import/ignore' in context.settings)) return false
-  const ignoreStrings = context.settings['import/ignore']
+  if (!('import/ignore' in context.settings)) {
+    return false;
+  }
+  const ignoreStrings = context.settings['import/ignore'];
 
   for (let i = 0; i < ignoreStrings.length; i++) {
-    const regex = new RegExp(ignoreStrings[i])
+    const regex = new RegExp(ignoreStrings[i]);
     if (regex.test(path)) {
-      log(`ignoring ${path}, matched pattern /${ignoreStrings[i]}/`)
-      return true
+      log(`ignoring ${path}, matched pattern /${ignoreStrings[i]}/`);
+      return true;
     }
   }
 
-  return false
-}
-
-function hasValidExtension(path, context) {
-  return validExtensions(context).has(extname(path))
-}
-exports.hasValidExtension = hasValidExtension
+  return false;
+};
diff --git a/utils/module-require.d.ts b/utils/module-require.d.ts
new file mode 100644
index 000000000..91df90d61
--- /dev/null
+++ b/utils/module-require.d.ts
@@ -0,0 +1,3 @@
+declare function moduleRequire<T>(p: string): T;
+
+export default moduleRequire;
diff --git a/utils/module-require.js b/utils/module-require.js
index 9b387ad1a..14006c5dc 100644
--- a/utils/module-require.js
+++ b/utils/module-require.js
@@ -1,30 +1,36 @@
-"use strict"
-exports.__esModule = true
+'use strict';
 
-const Module = require('module')
-const path = require('path')
+exports.__esModule = true;
+
+const Module = require('module');
+const path = require('path');
 
 // borrowed from babel-eslint
+/** @type {(filename: string) => Module} */
 function createModule(filename) {
-  const mod = new Module(filename)
-  mod.filename = filename
-  mod.paths = Module._nodeModulePaths(path.dirname(filename))
-  return mod
+  const mod = new Module(filename);
+  mod.filename = filename;
+  // @ts-expect-error _nodeModulesPaths are undocumented
+  mod.paths = Module._nodeModulePaths(path.dirname(filename));
+  return mod;
 }
 
+/** @type {import('./module-require').default} */
 exports.default = function moduleRequire(p) {
   try {
     // attempt to get espree relative to eslint
-    const eslintPath = require.resolve('eslint')
-    const eslintModule = createModule(eslintPath)
-    return require(Module._resolveFilename(p, eslintModule))
-  } catch(err) { /* ignore */ }
+    const eslintPath = require.resolve('eslint');
+    const eslintModule = createModule(eslintPath);
+    // @ts-expect-error _resolveFilename is undocumented
+    return require(Module._resolveFilename(p, eslintModule));
+  } catch (err) { /* ignore */ }
 
   try {
     // try relative to entry point
-    return require.main.require(p)
-  } catch(err) { /* ignore */ }
+    // @ts-expect-error TODO: figure out what this is
+    return require.main.require(p);
+  } catch (err) { /* ignore */ }
 
   // finally, try from here
-  return require(p)
-}
+  return require(p);
+};
diff --git a/utils/moduleVisitor.d.ts b/utils/moduleVisitor.d.ts
new file mode 100644
index 000000000..6f30186d7
--- /dev/null
+++ b/utils/moduleVisitor.d.ts
@@ -0,0 +1,26 @@
+import type { Rule } from 'eslint';
+import type { Node } from 'estree';
+
+type Visitor = (source: Node, importer: unknown) => any;
+
+type Options = {
+    amd?: boolean;
+    commonjs?: boolean;
+    esmodule?: boolean;
+    ignore?: string[];
+};
+
+declare function moduleVisitor(
+    visitor: Visitor,
+    options?: Options,
+): object;
+
+export default moduleVisitor;
+
+export type Schema = NonNullable<Rule.RuleModule['schema']>;
+
+declare function makeOptionsSchema(additionalProperties?: Partial<Schema>): Schema
+
+declare const optionsSchema: Schema;
+
+export { makeOptionsSchema, optionsSchema };
diff --git a/utils/moduleVisitor.js b/utils/moduleVisitor.js
index bc8c91b0a..acdee6774 100644
--- a/utils/moduleVisitor.js
+++ b/utils/moduleVisitor.js
@@ -1,141 +1,164 @@
-'use strict'
-exports.__esModule = true
+'use strict';
+
+exports.__esModule = true;
+
+/** @typedef {import('estree').Node} Node */
+/** @typedef {{ arguments: import('estree').CallExpression['arguments'], callee: Node }} Call */
+/** @typedef {import('estree').ImportDeclaration | import('estree').ExportNamedDeclaration | import('estree').ExportAllDeclaration} Declaration */
 
 /**
  * Returns an object of node visitors that will call
  * 'visitor' with every discovered module path.
  *
- * todo: correct function prototype for visitor
- * @param  {Function(String)} visitor [description]
- * @param  {[type]} options [description]
- * @return {object}
+ * @type {(import('./moduleVisitor').default)}
  */
 exports.default = function visitModules(visitor, options) {
+  const ignore = options && options.ignore;
+  const amd = !!(options && options.amd);
+  const commonjs = !!(options && options.commonjs);
   // if esmodule is not explicitly disabled, it is assumed to be enabled
-  options = Object.assign({ esmodule: true }, options)
+  const esmodule = !!Object.assign({ esmodule: true }, options).esmodule;
 
-  let ignoreRegExps = []
-  if (options.ignore != null) {
-    ignoreRegExps = options.ignore.map(p => new RegExp(p))
-  }
+  const ignoreRegExps = ignore == null ? [] : ignore.map((p) => new RegExp(p));
 
+  /** @type {(source: undefined | null | import('estree').Literal, importer: Parameters<typeof visitor>[1]) => void} */
   function checkSourceValue(source, importer) {
-    if (source == null) return //?
+    if (source == null) { return; } //?
 
     // handle ignore
-    if (ignoreRegExps.some(re => re.test(source.value))) return
+    if (ignoreRegExps.some((re) => re.test(String(source.value)))) { return; }
 
     // fire visitor
-    visitor(source, importer)
+    visitor(source, importer);
   }
 
   // for import-y declarations
+  /** @type {(node: Declaration) => void} */
   function checkSource(node) {
-    checkSourceValue(node.source, node)
+    checkSourceValue(node.source, node);
   }
 
   // for esmodule dynamic `import()` calls
+  /** @type {(node: import('estree').ImportExpression | import('estree').CallExpression) => void} */
   function checkImportCall(node) {
-    if (node.callee.type !== 'Import') return
-    if (node.arguments.length !== 1) return
+    /** @type {import('estree').Expression | import('estree').Literal | import('estree').CallExpression['arguments'][0]} */
+    let modulePath;
+    // refs https://github.com/estree/estree/blob/HEAD/es2020.md#importexpression
+    if (node.type === 'ImportExpression') {
+      modulePath = node.source;
+    } else if (node.type === 'CallExpression') {
+      // @ts-expect-error this structure is from an older version of eslint
+      if (node.callee.type !== 'Import') { return; }
+      if (node.arguments.length !== 1) { return; }
+
+      modulePath = node.arguments[0];
+    } else {
+      throw new TypeError('this should be unreachable');
+    }
 
-    const modulePath = node.arguments[0]
-    if (modulePath.type !== 'Literal') return
-    if (typeof modulePath.value !== 'string') return
+    if (modulePath.type !== 'Literal') { return; }
+    if (typeof modulePath.value !== 'string') { return; }
 
-    checkSourceValue(modulePath, node)
+    checkSourceValue(modulePath, node);
   }
 
   // for CommonJS `require` calls
-  // adapted from @mctep: http://git.io/v4rAu
+  // adapted from @mctep: https://git.io/v4rAu
+  /** @type {(call: Call) => void} */
   function checkCommon(call) {
-    if (call.callee.type !== 'Identifier') return
-    if (call.callee.name !== 'require') return
-    if (call.arguments.length !== 1) return
+    if (call.callee.type !== 'Identifier') { return; }
+    if (call.callee.name !== 'require') { return; }
+    if (call.arguments.length !== 1) { return; }
 
-    const modulePath = call.arguments[0]
-    if (modulePath.type !== 'Literal') return
-    if (typeof modulePath.value !== 'string') return
+    const modulePath = call.arguments[0];
+    if (modulePath.type !== 'Literal') { return; }
+    if (typeof modulePath.value !== 'string') { return; }
 
-    checkSourceValue(modulePath, call)
+    checkSourceValue(modulePath, call);
   }
 
+  /** @type {(call: Call) => void} */
   function checkAMD(call) {
-    if (call.callee.type !== 'Identifier') return
-    if (call.callee.name !== 'require' &&
-        call.callee.name !== 'define') return
-    if (call.arguments.length !== 2) return
-
-    const modules = call.arguments[0]
-    if (modules.type !== 'ArrayExpression') return
-
-    for (let element of modules.elements) {
-      if (element.type !== 'Literal') continue
-      if (typeof element.value !== 'string') continue
-
-      if (element.value === 'require' ||
-          element.value === 'exports') continue // magic modules: http://git.io/vByan
-
-      checkSourceValue(element, element)
+    if (call.callee.type !== 'Identifier') { return; }
+    if (call.callee.name !== 'require' && call.callee.name !== 'define') { return; }
+    if (call.arguments.length !== 2) { return; }
+
+    const modules = call.arguments[0];
+    if (modules.type !== 'ArrayExpression') { return; }
+
+    for (const element of modules.elements) {
+      if (!element) { continue; }
+      if (element.type !== 'Literal') { continue; }
+      if (typeof element.value !== 'string') { continue; }
+
+      if (
+        element.value === 'require'
+        || element.value === 'exports'
+      ) {
+        continue; // magic modules: https://github.com/requirejs/requirejs/wiki/Differences-between-the-simplified-CommonJS-wrapper-and-standard-AMD-define#magic-modules
+      }
+
+      checkSourceValue(element, element);
     }
   }
 
-  const visitors = {}
-  if (options.esmodule) {
+  const visitors = {};
+  if (esmodule) {
     Object.assign(visitors, {
-      'ImportDeclaration': checkSource,
-      'ExportNamedDeclaration': checkSource,
-      'ExportAllDeclaration': checkSource,
-      'CallExpression': checkImportCall,
-    })
+      ImportDeclaration: checkSource,
+      ExportNamedDeclaration: checkSource,
+      ExportAllDeclaration: checkSource,
+      CallExpression: checkImportCall,
+      ImportExpression: checkImportCall,
+    });
   }
 
-  if (options.commonjs || options.amd) {
-    const currentCallExpression = visitors['CallExpression']
-    visitors['CallExpression'] = function (call) {
-      if (currentCallExpression) currentCallExpression(call)
-      if (options.commonjs) checkCommon(call)
-      if (options.amd) checkAMD(call)
-    }
+  if (commonjs || amd) {
+    const currentCallExpression = visitors.CallExpression;
+    visitors.CallExpression = /** @type {(call: Call) => void} */ function (call) {
+      if (currentCallExpression) { currentCallExpression(call); }
+      if (commonjs) { checkCommon(call); }
+      if (amd) { checkAMD(call); }
+    };
   }
 
-  return visitors
-}
+  return visitors;
+};
 
 /**
- * make an options schema for the module visitor, optionally
- * adding extra fields.
+ * make an options schema for the module visitor, optionally adding extra fields.
+ * @type {import('./moduleVisitor').makeOptionsSchema}
  */
 function makeOptionsSchema(additionalProperties) {
+  /** @type {import('./moduleVisitor').Schema} */
   const base =  {
-    'type': 'object',
-    'properties': {
-      'commonjs': { 'type': 'boolean' },
-      'amd': { 'type': 'boolean' },
-      'esmodule': { 'type': 'boolean' },
-      'ignore': {
-        'type': 'array',
-        'minItems': 1,
-        'items': { 'type': 'string' },
-        'uniqueItems': true,
+    type: 'object',
+    properties: {
+      commonjs: { type: 'boolean' },
+      amd: { type: 'boolean' },
+      esmodule: { type: 'boolean' },
+      ignore: {
+        type: 'array',
+        minItems: 1,
+        items: { type: 'string' },
+        uniqueItems: true,
       },
     },
-    'additionalProperties': false,
-  }
+    additionalProperties: false,
+  };
 
-  if (additionalProperties){
-    for (let key in additionalProperties) {
-      base.properties[key] = additionalProperties[key]
+  if (additionalProperties) {
+    for (const key in additionalProperties) {
+      // @ts-expect-error TS always has trouble with arbitrary object assignment/mutation
+      base.properties[key] = additionalProperties[key];
     }
   }
 
-  return base
+  return base;
 }
-exports.makeOptionsSchema = makeOptionsSchema
+exports.makeOptionsSchema = makeOptionsSchema;
 
 /**
- * json schema object for options parameter. can be used to build
- * rule options schema object.
- * @type {Object}
+ * json schema object for options parameter. can be used to build rule options schema object.
  */
-exports.optionsSchema = makeOptionsSchema()
+exports.optionsSchema = makeOptionsSchema();
diff --git a/utils/package.json b/utils/package.json
index eaad9b254..017eb7192 100644
--- a/utils/package.json
+++ b/utils/package.json
@@ -1,16 +1,51 @@
 {
   "name": "eslint-module-utils",
-  "version": "2.4.1",
+  "version": "2.12.0",
   "description": "Core utilities to support eslint-plugin-import and other module-related plugins.",
   "engines": {
     "node": ">=4"
   },
+  "main": false,
+  "exports": {
+    "./contextCompat": "./contextCompat.js",
+    "./ModuleCache": "./ModuleCache.js",
+    "./ModuleCache.js": "./ModuleCache.js",
+    "./declaredScope": "./declaredScope.js",
+    "./declaredScope.js": "./declaredScope.js",
+    "./hash": "./hash.js",
+    "./hash.js": "./hash.js",
+    "./ignore": "./ignore.js",
+    "./ignore.js": "./ignore.js",
+    "./module-require": "./module-require.js",
+    "./module-require.js": "./module-require.js",
+    "./moduleVisitor": "./moduleVisitor.js",
+    "./moduleVisitor.js": "./moduleVisitor.js",
+    "./parse": "./parse.js",
+    "./parse.js": "./parse.js",
+    "./pkgDir": "./pkgDir.js",
+    "./pkgDir.js": "./pkgDir.js",
+    "./pkgUp": "./pkgUp.js",
+    "./pkgUp.js": "./pkgUp.js",
+    "./readPkgUp": "./readPkgUp.js",
+    "./readPkgUp.js": "./readPkgUp.js",
+    "./resolve": "./resolve.js",
+    "./resolve.js": "./resolve.js",
+    "./unambiguous": "./unambiguous.js",
+    "./unambiguous.js": "./unambiguous.js",
+    "./visit": "./visit.js",
+    "./visit.js": "./visit.js",
+    "./package.json": "./package.json"
+  },
   "scripts": {
+    "prepublishOnly": "cp ../{LICENSE,.npmrc} ./",
+    "tsc": "tsc -p .",
+    "posttsc": "attw -P .",
     "test": "echo \"Error: no test specified\" && exit 1"
   },
   "repository": {
     "type": "git",
-    "url": "git+https://github.com/benmosher/eslint-plugin-import.git"
+    "url": "git+https://github.com/import-js/eslint-plugin-import.git",
+    "directory": "utils"
   },
   "keywords": [
     "eslint-plugin-import",
@@ -21,11 +56,28 @@
   "author": "Ben Mosher <me@benmosher.com>",
   "license": "MIT",
   "bugs": {
-    "url": "https://github.com/benmosher/eslint-plugin-import/issues"
+    "url": "https://github.com/import-js/eslint-plugin-import/issues"
   },
-  "homepage": "https://github.com/benmosher/eslint-plugin-import#readme",
+  "homepage": "https://github.com/import-js/eslint-plugin-import#readme",
   "dependencies": {
-    "debug": "^2.6.8",
-    "pkg-dir": "^2.0.0"
+    "debug": "^3.2.7"
+  },
+  "devDependencies": {
+    "@arethetypeswrong/cli": "^0.15.4",
+    "@ljharb/tsconfig": "^0.2.0",
+    "@types/debug": "^4.1.12",
+    "@types/eslint": "^8.56.3",
+    "@types/node": "^20.11.20",
+    "typescript": "next"
+  },
+  "peerDependenciesMeta": {
+    "eslint": {
+      "optional": true
+    }
+  },
+  "publishConfig": {
+    "ignore": [
+      ".attw.json"
+    ]
   }
 }
diff --git a/utils/parse.d.ts b/utils/parse.d.ts
new file mode 100644
index 000000000..f92ab3edc
--- /dev/null
+++ b/utils/parse.d.ts
@@ -0,0 +1,11 @@
+import { AST, Rule } from 'eslint';
+
+
+
+declare function parse(
+    path: string,
+    content: string,
+    context: Rule.RuleContext
+): AST.Program | null | undefined;
+
+export default parse;
diff --git a/utils/parse.js b/utils/parse.js
index 99e5a9334..793e37152 100644
--- a/utils/parse.js
+++ b/utils/parse.js
@@ -1,62 +1,193 @@
-"use strict"
-exports.__esModule = true
+'use strict';
 
-const moduleRequire = require('./module-require').default
-const extname = require('path').extname
+exports.__esModule = true;
 
-const log = require('debug')('eslint-plugin-import:parse')
+/** @typedef {`.${string}`} Extension  */
+/** @typedef {NonNullable<import('eslint').Rule.RuleContext['settings']> & { 'import/extensions'?: Extension[], 'import/parsers'?: { [k: string]: Extension[] }, 'import/cache'?: { lifetime: number | '∞' | 'Infinity' } }} ESLintSettings */
 
-exports.default = function parse(path, content, context) {
+const moduleRequire = require('./module-require').default;
+const extname = require('path').extname;
+const fs = require('fs');
 
-  if (context == null) throw new Error('need context to parse properly')
+const log = require('debug')('eslint-plugin-import:parse');
 
-  let parserOptions = context.parserOptions
-  const parserPath = getParserPath(path, context)
+/** @type {(parserPath: NonNullable<import('eslint').Rule.RuleContext['parserPath']>) => unknown} */
+function getBabelEslintVisitorKeys(parserPath) {
+  if (parserPath.endsWith('index.js')) {
+    const hypotheticalLocation = parserPath.replace('index.js', 'visitor-keys.js');
+    if (fs.existsSync(hypotheticalLocation)) {
+      const keys = moduleRequire(hypotheticalLocation);
+      return keys.default || keys;
+    }
+  }
+  return null;
+}
 
-  if (!parserPath) throw new Error('parserPath is required!')
+/** @type {(parserPath: import('eslint').Rule.RuleContext['parserPath'], parserInstance: { VisitorKeys: unknown }, parsedResult?: { visitorKeys?: unknown }) => unknown} */
+function keysFromParser(parserPath, parserInstance, parsedResult) {
+  // Exposed by @typescript-eslint/parser and @babel/eslint-parser
+  if (parsedResult && parsedResult.visitorKeys) {
+    return parsedResult.visitorKeys;
+  }
+  // The old babel parser doesn't have a `parseForESLint` eslint function, so we don't end
+  // up with a `parsedResult` here.  It also doesn't expose the visitor keys on the parser itself,
+  // so we have to try and infer the visitor-keys module from the parserPath.
+  // This is NOT supported in flat config!
+  if (typeof parserPath === 'string' && parserPath.indexOf('babel-eslint') > -1) {
+    return getBabelEslintVisitorKeys(parserPath);
+  }
+  // The espree parser doesn't have the `parseForESLint` function, so we don't end up with a
+  // `parsedResult` here, but it does expose the visitor keys on the parser instance that we can use.
+  if (parserInstance && parserInstance.VisitorKeys) {
+    return parserInstance.VisitorKeys;
+  }
+  return null;
+}
+
+// this exists to smooth over the unintentional breaking change in v2.7.
+// TODO, semver-major: avoid mutating `ast` and return a plain object instead.
+/** @type {<T extends import('eslint').AST.Program>(ast: T, visitorKeys: unknown) => T} */
+function makeParseReturn(ast, visitorKeys) {
+  if (ast) {
+    // @ts-expect-error see TODO
+    ast.visitorKeys = visitorKeys;
+    // @ts-expect-error see TODO
+    ast.ast = ast;
+  }
+  return ast;
+}
+
+/** @type {(text: string) => string} */
+function stripUnicodeBOM(text) {
+  return text.charCodeAt(0) === 0xFEFF ? text.slice(1) : text;
+}
+
+/** @type {(text: string) => string} */
+function transformHashbang(text) {
+  return text.replace(/^#!([^\r\n]+)/u, (_, captured) => `//${captured}`);
+}
+
+/** @type {(path: string, context: import('eslint').Rule.RuleContext & { settings?: ESLintSettings }) => import('eslint').Rule.RuleContext['parserPath']} */
+function getParserPath(path, context) {
+  const parsers = context.settings['import/parsers'];
+  if (parsers != null) {
+    // eslint-disable-next-line no-extra-parens
+    const extension = /** @type {Extension} */ (extname(path));
+    for (const parserPath in parsers) {
+      if (parsers[parserPath].indexOf(extension) > -1) {
+        // use this alternate parser
+        log('using alt parser:', parserPath);
+        return parserPath;
+      }
+    }
+  }
+  // default to use ESLint parser
+  return context.parserPath;
+}
+
+/** @type {(path: string, context: import('eslint').Rule.RuleContext) => string | null | (import('eslint').Linter.ParserModule)} */
+function getParser(path, context) {
+  const parserPath = getParserPath(path, context);
+  if (parserPath) {
+    return parserPath;
+  }
+  if (
+    !!context.languageOptions
+    && !!context.languageOptions.parser
+    && typeof context.languageOptions.parser !== 'string'
+    && (
+      // @ts-expect-error TODO: figure out a better type
+      typeof context.languageOptions.parser.parse === 'function'
+      // @ts-expect-error TODO: figure out a better type
+      || typeof context.languageOptions.parser.parseForESLint === 'function'
+    )
+  ) {
+    return context.languageOptions.parser;
+  }
+
+  return null;
+}
+
+/** @type {import('./parse').default} */
+exports.default = function parse(path, content, context) {
+  if (context == null) { throw new Error('need context to parse properly'); }
+
+  // ESLint in "flat" mode only sets context.languageOptions.parserOptions
+  const languageOptions = context.languageOptions;
+  let parserOptions = languageOptions && languageOptions.parserOptions || context.parserOptions;
+  const parserOrPath = getParser(path, context);
+
+  if (!parserOrPath) { throw new Error('parserPath or languageOptions.parser is required!'); }
 
   // hack: espree blows up with frozen options
-  parserOptions = Object.assign({}, parserOptions)
-  parserOptions.ecmaFeatures = Object.assign({}, parserOptions.ecmaFeatures)
+  parserOptions = Object.assign({}, parserOptions);
+  parserOptions.ecmaFeatures = Object.assign({}, parserOptions.ecmaFeatures);
 
   // always include comments and tokens (for doc parsing)
-  parserOptions.comment = true
-  parserOptions.attachComment = true  // keeping this for backward-compat with  older parsers
-  parserOptions.tokens = true
+  parserOptions.comment = true;
+  parserOptions.attachComment = true;  // keeping this for backward-compat with  older parsers
+  parserOptions.tokens = true;
 
   // attach node locations
-  parserOptions.loc = true
-  parserOptions.range = true
+  parserOptions.loc = true;
+  parserOptions.range = true;
 
   // provide the `filePath` like eslint itself does, in `parserOptions`
   // https://github.com/eslint/eslint/blob/3ec436ee/lib/linter.js#L637
-  parserOptions.filePath = path
-  
+  parserOptions.filePath = path;
+
   // @typescript-eslint/parser will parse the entire project with typechecking if you provide
   // "project" or "projects" in parserOptions. Removing these options means the parser will
   // only parse one file in isolate mode, which is much, much faster.
-  // https://github.com/benmosher/eslint-plugin-import/issues/1408#issuecomment-509298962
+  // https://github.com/import-js/eslint-plugin-import/issues/1408#issuecomment-509298962
+  delete parserOptions.EXPERIMENTAL_useProjectService;
+  delete parserOptions.projectService;
   delete parserOptions.project;
   delete parserOptions.projects;
-  
+
+  // If this is a flat config, we need to add ecmaVersion and sourceType (if present) from languageOptions
+  if (languageOptions && languageOptions.ecmaVersion) {
+    parserOptions.ecmaVersion = languageOptions.ecmaVersion;
+  }
+  if (languageOptions && languageOptions.sourceType) {
+    // @ts-expect-error languageOptions is from the flatConfig Linter type in 8.57 while parserOptions is not.
+    // Non-flat config parserOptions.sourceType doesn't have "commonjs" in the type.  Once upgraded to v9 types,
+    // they'll be the same and this expect-error should be removed.
+    parserOptions.sourceType = languageOptions.sourceType;
+  }
+
   // require the parser relative to the main module (i.e., ESLint)
-  const parser = moduleRequire(parserPath)
+  const parser = typeof parserOrPath === 'string' ? moduleRequire(parserOrPath) : parserOrPath;
 
-  return parser.parse(content, parserOptions)
-}
+  // replicate bom strip and hashbang transform of ESLint
+  // https://github.com/eslint/eslint/blob/b93af98b3c417225a027cabc964c38e779adb945/lib/linter/linter.js#L779
+  content = transformHashbang(stripUnicodeBOM(String(content)));
 
-function getParserPath(path, context) {
-  const parsers = context.settings['import/parsers']
-  if (parsers != null) {
-    const extension = extname(path)
-    for (let parserPath in parsers) {
-      if (parsers[parserPath].indexOf(extension) > -1) {
-        // use this alternate parser
-        log('using alt parser:', parserPath)
-        return parserPath
-      }
+  if (typeof parser.parseForESLint === 'function') {
+    let ast;
+    try {
+      const parserRaw = parser.parseForESLint(content, parserOptions);
+      ast = parserRaw.ast;
+      // @ts-expect-error TODO: FIXME
+      return makeParseReturn(ast, keysFromParser(parserOrPath, parser, parserRaw));
+    } catch (e) {
+      console.warn();
+      console.warn('Error while parsing ' + parserOptions.filePath);
+      // @ts-expect-error e is almost certainly an Error here
+      console.warn('Line ' + e.lineNumber + ', column ' + e.column + ': ' + e.message);
+    }
+    if (!ast || typeof ast !== 'object') {
+      console.warn(
+        // Can only be invalid for custom parser per imports/parser
+        '`parseForESLint` from parser `' + (typeof parserOrPath === 'string' ? parserOrPath : 'context.languageOptions.parser') + '` is invalid and will just be ignored'
+      );
+    } else {
+      // @ts-expect-error TODO: FIXME
+      return makeParseReturn(ast, keysFromParser(parserOrPath, parser, undefined));
     }
   }
-  // default to use ESLint parser
-  return context.parserPath
-}
+
+  const ast = parser.parse(content, parserOptions);
+  // @ts-expect-error TODO: FIXME
+  return makeParseReturn(ast, keysFromParser(parserOrPath, parser, undefined));
+};
diff --git a/utils/pkgDir.d.ts b/utils/pkgDir.d.ts
new file mode 100644
index 000000000..af01e2e9b
--- /dev/null
+++ b/utils/pkgDir.d.ts
@@ -0,0 +1,3 @@
+declare function pkgDir(cwd: string): string | null;
+
+export default pkgDir;
diff --git a/utils/pkgDir.js b/utils/pkgDir.js
new file mode 100644
index 000000000..84c334680
--- /dev/null
+++ b/utils/pkgDir.js
@@ -0,0 +1,12 @@
+'use strict';
+
+const path = require('path');
+const pkgUp = require('./pkgUp').default;
+
+exports.__esModule = true;
+
+/** @type {import('./pkgDir').default} */
+exports.default = function (cwd) {
+  const fp = pkgUp({ cwd });
+  return fp ? path.dirname(fp) : null;
+};
diff --git a/utils/pkgUp.d.ts b/utils/pkgUp.d.ts
new file mode 100644
index 000000000..6382457be
--- /dev/null
+++ b/utils/pkgUp.d.ts
@@ -0,0 +1,3 @@
+declare function pkgUp(opts?: { cwd?: string }): string | null;
+
+export default pkgUp;
diff --git a/utils/pkgUp.js b/utils/pkgUp.js
new file mode 100644
index 000000000..076e59fd7
--- /dev/null
+++ b/utils/pkgUp.js
@@ -0,0 +1,61 @@
+'use strict';
+
+exports.__esModule = true;
+
+const fs = require('fs');
+const path = require('path');
+
+/**
+ * Derived significantly from package find-up@2.0.0. See license below.
+ *
+ * @copyright Sindre Sorhus
+ * MIT License
+ *
+ * Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (https://sindresorhus.com)
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+/** @type {(filename: string | string[], cwd?: string) => string | null} */
+function findUp(filename, cwd) {
+  let dir = path.resolve(cwd || '');
+  const root = path.parse(dir).root;
+
+  /** @type {string[]} */ // @ts-expect-error TS sucks with concat
+  const filenames = [].concat(filename);
+
+  // eslint-disable-next-line no-constant-condition
+  while (true) {
+    const file = filenames.find((el) => fs.existsSync(path.resolve(dir, el)));
+
+    if (file) {
+      return path.join(dir, file);
+    }
+    if (dir === root) {
+      return null;
+    }
+
+    dir = path.dirname(dir);
+  }
+}
+
+/** @type {import('./pkgUp').default} */
+exports.default = function pkgUp(opts) {
+  return findUp('package.json', opts && opts.cwd);
+};
diff --git a/utils/readPkgUp.d.ts b/utils/readPkgUp.d.ts
new file mode 100644
index 000000000..5fc166887
--- /dev/null
+++ b/utils/readPkgUp.d.ts
@@ -0,0 +1,5 @@
+import pkgUp from './pkgUp';
+
+declare function readPkgUp(opts?: Parameters<typeof pkgUp>[0]): {} | { pkg: string, path: string };
+
+export default readPkgUp;
diff --git a/utils/readPkgUp.js b/utils/readPkgUp.js
new file mode 100644
index 000000000..08371931f
--- /dev/null
+++ b/utils/readPkgUp.js
@@ -0,0 +1,55 @@
+'use strict';
+
+exports.__esModule = true;
+
+const fs = require('fs');
+const pkgUp = require('./pkgUp').default;
+
+/** @type {(str: string) => string} */
+function stripBOM(str) {
+  return str.replace(/^\uFEFF/, '');
+}
+
+/**
+ * Derived significantly from read-pkg-up@2.0.0. See license below.
+ *
+ * @copyright Sindre Sorhus
+ * MIT License
+ *
+ * Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (https://sindresorhus.com)
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+/** @type {import('./readPkgUp').default} */
+exports.default = function readPkgUp(opts) {
+  const fp = pkgUp(opts);
+
+  if (!fp) {
+    return {};
+  }
+
+  try {
+    return {
+      pkg: JSON.parse(stripBOM(fs.readFileSync(fp, { encoding: 'utf-8' }))),
+      path: fp,
+    };
+  } catch (e) {
+    return {};
+  }
+};
diff --git a/utils/resolve.d.ts b/utils/resolve.d.ts
new file mode 100644
index 000000000..bb885bcfa
--- /dev/null
+++ b/utils/resolve.d.ts
@@ -0,0 +1,30 @@
+import type { Rule } from 'eslint';
+
+import type ModuleCache from './ModuleCache';
+import type { ESLintSettings } from './types';
+
+export type ResultNotFound = { found: false, path?: undefined };
+export type ResultFound = { found: true, path: string | null };
+export type ResolvedResult = ResultNotFound | ResultFound;
+
+export type ResolverResolve = (modulePath: string, sourceFile:string, config: unknown) => ResolvedResult;
+export type ResolverResolveImport = (modulePath: string, sourceFile:string, config: unknown) => string | undefined;
+export type Resolver = { interfaceVersion?: 1 | 2, resolve: ResolverResolve, resolveImport: ResolverResolveImport };
+
+declare function resolve(
+    p: string,
+    context: Rule.RuleContext,
+): ResolvedResult['path'];
+
+export default resolve;
+
+declare function fileExistsWithCaseSync(
+    filepath: string | null,
+    cacheSettings: ESLintSettings,
+    strict: boolean
+): boolean | ReturnType<typeof ModuleCache.prototype.get>;
+
+declare function relative(modulePath: string, sourceFile: string, settings: ESLintSettings): ResolvedResult['path'];
+
+
+export { fileExistsWithCaseSync, relative };
diff --git a/utils/resolve.js b/utils/resolve.js
index 87a1eaea8..b332d2ec2 100644
--- a/utils/resolve.js
+++ b/utils/resolve.js
@@ -1,25 +1,63 @@
-"use strict"
-exports.__esModule = true
+'use strict';
 
-const pkgDir = require('pkg-dir')
+exports.__esModule = true;
 
-const fs = require('fs')
-const path = require('path')
+const fs = require('fs');
+const Module = require('module');
+const path = require('path');
+const { getPhysicalFilename } = require('./contextCompat');
 
-const hashObject = require('./hash').hashObject
-    , ModuleCache = require('./ModuleCache').default
+const hashObject = require('./hash').hashObject;
+const ModuleCache = require('./ModuleCache').default;
+const pkgDir = require('./pkgDir').default;
 
-const CASE_SENSITIVE_FS = !fs.existsSync(path.join(__dirname, 'reSOLVE.js'))
-exports.CASE_SENSITIVE_FS = CASE_SENSITIVE_FS
+const CASE_SENSITIVE_FS = !fs.existsSync(path.join(__dirname.toUpperCase(), 'reSOLVE.js'));
+exports.CASE_SENSITIVE_FS = CASE_SENSITIVE_FS;
 
-const fileExistsCache = new ModuleCache()
+const ERROR_NAME = 'EslintPluginImportResolveError';
 
-function tryRequire(target) {
+const fileExistsCache = new ModuleCache();
+
+// Polyfill Node's `Module.createRequireFromPath` if not present (added in Node v10.12.0)
+// Use `Module.createRequire` if available (added in Node v12.2.0)
+const createRequire = Module.createRequire
+  // @ts-expect-error this only exists in older node
+  || Module.createRequireFromPath
+  || /** @type {(filename: string) => unknown} */ function (filename) {
+    const mod = new Module(filename, void null);
+    mod.filename = filename;
+    // @ts-expect-error _nodeModulePaths is undocumented
+    mod.paths = Module._nodeModulePaths(path.dirname(filename));
+
+    // @ts-expect-error _compile is undocumented
+    mod._compile(`module.exports = require;`, filename);
+
+    return mod.exports;
+  };
+
+/** @type {(resolver: object) => resolver is import('./resolve').Resolver} */
+function isResolverValid(resolver) {
+  if ('interfaceVersion' in resolver && resolver.interfaceVersion === 2) {
+    return 'resolve' in resolver && !!resolver.resolve && typeof resolver.resolve === 'function';
+  }
+  return 'resolveImport' in resolver && !!resolver.resolveImport && typeof resolver.resolveImport === 'function';
+}
+
+/** @type {<T extends string>(target: T, sourceFile?: string | null | undefined) => undefined | ReturnType<typeof require>} */
+function tryRequire(target, sourceFile) {
   let resolved;
   try {
     // Check if the target exists
-    resolved = require.resolve(target);
-  } catch(e) {
+    if (sourceFile != null) {
+      try {
+        resolved = createRequire(path.resolve(sourceFile)).resolve(target);
+      } catch (e) {
+        resolved = require.resolve(target);
+      }
+    } else {
+      resolved = require.resolve(target);
+    }
+  } catch (e) {
     // If the target does not exist then just return undefined
     return undefined;
   }
@@ -28,179 +66,189 @@ function tryRequire(target) {
   return require(resolved);
 }
 
-// http://stackoverflow.com/a/27382838
-exports.fileExistsWithCaseSync = function fileExistsWithCaseSync(filepath, cacheSettings) {
+/** @type {<T extends Map<string, unknown>>(resolvers: string[] | string | { [k: string]: string }, map: T) => T} */
+function resolverReducer(resolvers, map) {
+  if (Array.isArray(resolvers)) {
+    resolvers.forEach((r) => resolverReducer(r, map));
+    return map;
+  }
+
+  if (typeof resolvers === 'string') {
+    map.set(resolvers, null);
+    return map;
+  }
+
+  if (typeof resolvers === 'object') {
+    for (const key in resolvers) {
+      map.set(key, resolvers[key]);
+    }
+    return map;
+  }
+
+  const err = new Error('invalid resolver config');
+  err.name = ERROR_NAME;
+  throw err;
+}
+
+/** @type {(sourceFile: string) => string} */
+function getBaseDir(sourceFile) {
+  return pkgDir(sourceFile) || process.cwd();
+}
+
+/** @type {(name: string, sourceFile: string) => import('./resolve').Resolver} */
+function requireResolver(name, sourceFile) {
+  // Try to resolve package with conventional name
+  const resolver = tryRequire(`eslint-import-resolver-${name}`, sourceFile)
+    || tryRequire(name, sourceFile)
+    || tryRequire(path.resolve(getBaseDir(sourceFile), name));
+
+  if (!resolver) {
+    const err = new Error(`unable to load resolver "${name}".`);
+    err.name = ERROR_NAME;
+    throw err;
+  }
+  if (!isResolverValid(resolver)) {
+    const err = new Error(`${name} with invalid interface loaded as resolver`);
+    err.name = ERROR_NAME;
+    throw err;
+  }
+
+  return resolver;
+}
+
+// https://stackoverflow.com/a/27382838
+/** @type {import('./resolve').fileExistsWithCaseSync} */
+exports.fileExistsWithCaseSync = function fileExistsWithCaseSync(filepath, cacheSettings, strict) {
   // don't care if the FS is case-sensitive
-  if (CASE_SENSITIVE_FS) return true
+  if (CASE_SENSITIVE_FS) { return true; }
 
   // null means it resolved to a builtin
-  if (filepath === null) return true
-  if (filepath.toLowerCase() === process.cwd().toLowerCase()) return true
-  const parsedPath = path.parse(filepath)
-      , dir = parsedPath.dir
+  if (filepath === null) { return true; }
+  if (filepath.toLowerCase() === process.cwd().toLowerCase() && !strict) { return true; }
+  const parsedPath = path.parse(filepath);
+  const dir = parsedPath.dir;
 
-  let result = fileExistsCache.get(filepath, cacheSettings)
-  if (result != null) return result
+  let result = fileExistsCache.get(filepath, cacheSettings);
+  if (result != null) { return result; }
 
   // base case
   if (dir === '' || parsedPath.root === filepath) {
-    result = true
+    result = true;
   } else {
-    const filenames = fs.readdirSync(dir)
+    const filenames = fs.readdirSync(dir);
     if (filenames.indexOf(parsedPath.base) === -1) {
-      result = false
+      result = false;
     } else {
-      result = fileExistsWithCaseSync(dir, cacheSettings)
+      result = fileExistsWithCaseSync(dir, cacheSettings, strict);
     }
   }
-  fileExistsCache.set(filepath, result)
-  return result
-}
-
-function relative(modulePath, sourceFile, settings) {
-  return fullResolve(modulePath, sourceFile, settings).path
-}
-
+  fileExistsCache.set(filepath, result);
+  return result;
+};
+
+/** @type {import('./types').ESLintSettings | null} */
+let prevSettings = null;
+let memoizedHash = '';
+/** @type {(modulePath: string, sourceFile: string, settings: import('./types').ESLintSettings) => import('./resolve').ResolvedResult} */
 function fullResolve(modulePath, sourceFile, settings) {
   // check if this is a bonus core module
-  const coreSet = new Set(settings['import/core-modules'])
-  if (coreSet != null && coreSet.has(modulePath)) return { found: true, path: null }
+  const coreSet = new Set(settings['import/core-modules']);
+  if (coreSet.has(modulePath)) { return { found: true, path: null }; }
+
+  const sourceDir = path.dirname(sourceFile);
+
+  if (prevSettings !== settings) {
+    memoizedHash = hashObject(settings).digest('hex');
+    prevSettings = settings;
+  }
 
-  const sourceDir = path.dirname(sourceFile)
-      , cacheKey = sourceDir + hashObject(settings).digest('hex') + modulePath
+  const cacheKey = sourceDir + memoizedHash + modulePath;
 
-  const cacheSettings = ModuleCache.getSettings(settings)
+  const cacheSettings = ModuleCache.getSettings(settings);
 
-  const cachedPath = fileExistsCache.get(cacheKey, cacheSettings)
-  if (cachedPath !== undefined) return { found: true, path: cachedPath }
+  const cachedPath = fileExistsCache.get(cacheKey, cacheSettings);
+  if (cachedPath !== undefined) { return { found: true, path: cachedPath }; }
 
+  /** @type {(resolvedPath: string | null) => void} */
   function cache(resolvedPath) {
-    fileExistsCache.set(cacheKey, resolvedPath)
+    fileExistsCache.set(cacheKey, resolvedPath);
   }
 
+  /** @type {(resolver: import('./resolve').Resolver, config: unknown) => import('./resolve').ResolvedResult} */
   function withResolver(resolver, config) {
-
-    function v1() {
-      try {
-        const resolved = resolver.resolveImport(modulePath, sourceFile, config)
-        if (resolved === undefined) return { found: false }
-        return { found: true, path: resolved }
-      } catch (err) {
-        return { found: false }
-      }
-    }
-
-    function v2() {
-      return resolver.resolve(modulePath, sourceFile, config)
+    if (resolver.interfaceVersion === 2) {
+      return resolver.resolve(modulePath, sourceFile, config);
     }
 
-    switch (resolver.interfaceVersion) {
-      case 2:
-        return v2()
-
-      default:
-      case 1:
-        return v1()
+    try {
+      const resolved = resolver.resolveImport(modulePath, sourceFile, config);
+      if (resolved === undefined) { return { found: false }; }
+      return { found: true, path: resolved };
+    } catch (err) {
+      return { found: false };
     }
   }
 
-  const configResolvers = (settings['import/resolver']
-    || { 'node': settings['import/resolve'] }) // backward compatibility
+  const configResolvers = settings['import/resolver']
+    || { node: settings['import/resolve'] }; // backward compatibility
 
-  const resolvers = resolverReducer(configResolvers, new Map())
+  const resolvers = resolverReducer(configResolvers, new Map());
 
-  for (let pair of resolvers) {
-    let name = pair[0]
-      , config = pair[1]
-    const resolver = requireResolver(name, sourceFile)
-        , resolved = withResolver(resolver, config)
+  for (const pair of resolvers) {
+    const name = pair[0];
+    const config = pair[1];
+    const resolver = requireResolver(name, sourceFile);
+    const resolved = withResolver(resolver, config);
 
-    if (!resolved.found) continue
+    if (!resolved.found) { continue; }
 
     // else, counts
-    cache(resolved.path)
-    return resolved
+    cache(resolved.path);
+    return resolved;
   }
 
   // failed
   // cache(undefined)
-  return { found: false }
-}
-exports.relative = relative
-
-function resolverReducer(resolvers, map) {
-  if (resolvers instanceof Array) {
-    resolvers.forEach(r => resolverReducer(r, map))
-    return map
-  }
-
-  if (typeof resolvers === 'string') {
-    map.set(resolvers, null)
-    return map
-  }
-
-  if (typeof resolvers === 'object') {
-    for (let key in resolvers) {
-      map.set(key, resolvers[key])
-    }
-    return map
-  }
-
-  throw new Error('invalid resolver config')
-}
-
-function getBaseDir(sourceFile) {
-  return pkgDir.sync(sourceFile) || process.cwd()
-}
-function requireResolver(name, sourceFile) {
-  // Try to resolve package with conventional name
-  let resolver = tryRequire(`eslint-import-resolver-${name}`) ||
-    tryRequire(name) ||
-    tryRequire(path.resolve(getBaseDir(sourceFile), name))
-
-  if (!resolver) {
-    throw new Error(`unable to load resolver "${name}".`)
-  }
-  if (!isResolverValid(resolver)) {
-    throw new Error(`${name} with invalid interface loaded as resolver`)
-  }
-
-  return resolver
+  return { found: false };
 }
 
-function isResolverValid(resolver) {
-  if (resolver.interfaceVersion === 2) {
-    return resolver.resolve && typeof resolver.resolve === 'function'
-  } else {
-    return resolver.resolveImport && typeof resolver.resolveImport === 'function'
-  }
+/** @type {import('./resolve').relative} */
+function relative(modulePath, sourceFile, settings) {
+  return fullResolve(modulePath, sourceFile, settings).path;
 }
+exports.relative = relative;
 
-const erroredContexts = new Set()
+/** @type {Set<import('eslint').Rule.RuleContext>} */
+const erroredContexts = new Set();
 
 /**
  * Given
- * @param  {string} p - module path
- * @param  {object} context - ESLint context
- * @return {string} - the full module filesystem path;
- *                    null if package is core;
- *                    undefined if not found
+ * @param p - module path
+ * @param context - ESLint context
+ * @return - the full module filesystem path; null if package is core; undefined if not found
+ * @type {import('./resolve').default}
  */
 function resolve(p, context) {
   try {
-    return relative( p
-                   , context.getFilename()
-                   , context.settings
-                   )
+    return relative(p, getPhysicalFilename(context), context.settings);
   } catch (err) {
     if (!erroredContexts.has(context)) {
+      // The `err.stack` string starts with `err.name` followed by colon and `err.message`.
+      // We're filtering out the default `err.name` because it adds little value to the message.
+      // @ts-expect-error this might be an Error
+      let errMessage = err.message;
+      // @ts-expect-error this might be an Error
+      if (err.name !== ERROR_NAME && err.stack) {
+        // @ts-expect-error this might be an Error
+        errMessage = err.stack.replace(/^Error: /, '');
+      }
       context.report({
-        message: `Resolve error: ${err.message}`,
+        message: `Resolve error: ${errMessage}`,
         loc: { line: 1, column: 0 },
-      })
-      erroredContexts.add(context)
+      });
+      erroredContexts.add(context);
     }
   }
 }
-resolve.relative = relative
-exports.default = resolve
+resolve.relative = relative;
+exports.default = resolve;
diff --git a/utils/tsconfig.json b/utils/tsconfig.json
new file mode 100644
index 000000000..9e6fbc5cc
--- /dev/null
+++ b/utils/tsconfig.json
@@ -0,0 +1,11 @@
+{
+	"extends": "@ljharb/tsconfig",
+	"compilerOptions": {
+		"target": "ES2017",
+		"moduleResolution": "node",
+		"maxNodeModuleJsDepth": 0,
+	},
+	"exclude": [
+		"coverage",
+	],
+}
diff --git a/utils/types.d.ts b/utils/types.d.ts
new file mode 100644
index 000000000..e0c4f5749
--- /dev/null
+++ b/utils/types.d.ts
@@ -0,0 +1,9 @@
+import type { Rule } from 'eslint';
+
+export type Extension = `.${string}`;
+
+export type ESLintSettings = NonNullable<Rule.RuleContext['settings']> & {
+    'import/extensions'?: Extension[];
+    'import/parsers'?: { [k: string]: Extension[] };
+    'import/cache'?: { lifetime: number | '∞' | 'Infinity' };
+};
diff --git a/utils/unambiguous.d.ts b/utils/unambiguous.d.ts
new file mode 100644
index 000000000..167922418
--- /dev/null
+++ b/utils/unambiguous.d.ts
@@ -0,0 +1,7 @@
+import type { AST } from 'eslint';
+
+declare function isModule(ast: AST.Program): boolean;
+
+declare function test(content: string): boolean;
+
+export { isModule, test }
diff --git a/utils/unambiguous.js b/utils/unambiguous.js
index 1dae1d616..df7b9be7a 100644
--- a/utils/unambiguous.js
+++ b/utils/unambiguous.js
@@ -1,8 +1,8 @@
-'use strict'
-exports.__esModule = true
+'use strict';
 
+exports.__esModule = true;
 
-const pattern = /(^|;)\s*(export|import)((\s+\w)|(\s*[{*=]))/m
+const pattern = /(^|[;})])\s*(export|import)((\s+\w)|(\s*[{*=]))|import\(/m;
 /**
  * detect possible imports/exports without a full parse.
  *
@@ -11,20 +11,19 @@ const pattern = /(^|;)\s*(export|import)((\s+\w)|(\s*[{*=]))/m
  *
  * Not perfect, just a fast way to disqualify large non-ES6 modules and
  * avoid a parse.
- * @type {RegExp}
+ * @type {import('./unambiguous').test}
  */
 exports.test = function isMaybeUnambiguousModule(content) {
-  return pattern.test(content)
-}
+  return pattern.test(content);
+};
 
 // future-/Babel-proof at the expense of being a little loose
-const unambiguousNodeType = /^(?:(?:Exp|Imp)ort.*Declaration|TSExportAssignment)$/
+const unambiguousNodeType = /^(?:(?:Exp|Imp)ort.*Declaration|TSExportAssignment)$/;
 
 /**
  * Given an AST, return true if the AST unambiguously represents a module.
- * @param  {Program node}  ast
- * @return {Boolean}
+ * @type {import('./unambiguous').isModule}
  */
 exports.isModule = function isUnambiguousModule(ast) {
-  return ast.body.some(node => unambiguousNodeType.test(node.type))
-}
+  return ast.body && ast.body.some((node) => unambiguousNodeType.test(node.type));
+};
diff --git a/utils/visit.d.ts b/utils/visit.d.ts
new file mode 100644
index 000000000..50559aaab
--- /dev/null
+++ b/utils/visit.d.ts
@@ -0,0 +1,9 @@
+import type { Node } from 'estree';
+
+declare function visit(
+    node: Node,
+    keys: { [k in Node['type']]?: (keyof Node)[] },
+    visitorSpec: { [k in Node['type'] | `${Node['type']}:Exit`]?: Function }
+): void;
+
+export default visit;
diff --git a/utils/visit.js b/utils/visit.js
new file mode 100644
index 000000000..dd0c6248d
--- /dev/null
+++ b/utils/visit.js
@@ -0,0 +1,30 @@
+'use strict';
+
+exports.__esModule = true;
+
+/** @type {import('./visit').default} */
+exports.default = function visit(node, keys, visitorSpec) {
+  if (!node || !keys) {
+    return;
+  }
+  const type = node.type;
+  const visitor = visitorSpec[type];
+  if (typeof visitor === 'function') {
+    visitor(node);
+  }
+  const childFields = keys[type];
+  if (!childFields) {
+    return;
+  }
+  childFields.forEach((fieldName) => {
+    // @ts-expect-error TS sucks with concat
+    [].concat(node[fieldName]).forEach((item) => {
+      visit(item, keys, visitorSpec);
+    });
+  });
+
+  const exit = visitorSpec[`${type}:Exit`];
+  if (typeof exit === 'function') {
+    exit(node);
+  }
+};