Repository: import-js/eslint-plugin-import
Pull request #2613 (2022-12-12 23:01:06)

[New] `no-absolute-path`: add fixer
Hello, this is my first contribution to this repo. Should I change anything?

adipascu: After reading https://github.com/nodejs/node/issues/6049 I think I should have used [path.posix](https://nodejs.org/api/path.html#pathposix).
Are any of these tests ran on MS Windows?
ljharb: Yes, the travis tests are on Mac and the appveyor ones on Windows.
adipascu: I've updated the PR to always do posix style paths.
- This seems to be [the way node.js works in general](https://github.com/nodejs/node/issues/6049)
 - [Browser URLs](https://developer.mozilla.org/en-US/docs/Web/API/URL/pathname) seem to only support `/` posix separators
 I hope this will pass windows CI.
adipascu: I've updated the PR to always do posix style paths.
- This seems to be [the way node.js works in general](https://github.com/nodejs/node/issues/6049)
 - [Browser URLs](https://developer.mozilla.org/en-US/docs/Web/API/URL/pathname) seem to only support `/` posix separators
 I hope this will pass windows CI.
 Edit: sorry for the force pushes, should I do separate commits with any feedback/follow up work?

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 58bfe0ab..5907c521 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -18,6 +18,7 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange
 - [`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])
 
 ### Fixed
 - [`order`]: move nested imports closer to main import entry ([#2396], thanks [@pri1311])
@@ -1026,6 +1027,7 @@ for info on changes for earlier releases.
 
 [`memo-parser`]: ./memo-parser/README.md
 
+[#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
diff --git a/README.md b/README.md
index 640929c0..5c6f1a32 100644
--- a/README.md
+++ b/README.md
@@ -56,7 +56,7 @@ This plugin intends to support linting of ES2015+ (ES6+) import/export syntax, a
 | [default](docs/rules/default.md)                                       | Ensure a default export is present, given a default import.                          | ❗ ☑️ |    |    |    |    |    |
 | [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-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.                                    |      |    |    |    |    |    |
diff --git a/docs/rules/no-absolute-path.md b/docs/rules/no-absolute-path.md
index 3d85b9eb..a796f9d5 100644
--- a/docs/rules/no-absolute-path.md
+++ b/docs/rules/no-absolute-path.md
@@ -1,5 +1,7 @@
 # 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.
diff --git a/src/rules/no-absolute-path.js b/src/rules/no-absolute-path.js
index 171419d8..19dae6b6 100644
--- a/src/rules/no-absolute-path.js
+++ b/src/rules/no-absolute-path.js
@@ -1,3 +1,4 @@
+import path from 'path';
 import moduleVisitor, { makeOptionsSchema } from 'eslint-module-utils/moduleVisitor';
 import { isAbsolute } from '../core/importType';
 import docsUrl from '../docsUrl';
@@ -10,13 +11,26 @@ module.exports = {
       description: 'Forbid import of modules using absolute paths.',
       url: docsUrl('no-absolute-path'),
     },
+    fixable: 'code',
     schema: [ makeOptionsSchema() ],
   },
 
   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 => {
+            const resolvedContext = context.getPhysicalFilename ? context.getPhysicalFilename() : context.getFilename();
+            // node.js and web imports work with posix style paths ("/")
+            let relativePath = path.posix.relative(path.dirname(resolvedContext), source.value);
+            if (!relativePath.startsWith('.')) {
+              relativePath = './' + relativePath;
+            }
+            return fixer.replaceText(source, JSON.stringify(relativePath));
+          },
+        });
       }
     }
 


Test patch:
diff --git a/tests/src/rules/no-absolute-path.js b/tests/src/rules/no-absolute-path.js
index 63fb8c0b..bfa08465 100644
--- a/tests/src/rules/no-absolute-path.js
+++ b/tests/src/rules/no-absolute-path.js
@@ -53,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) { /* ... */ })',
     }),
   ],
 });