Repository: benyap/resolve-tspaths
Pull request #332 (2023-12-14 11:59:28)

fix: resolve importPath with file extension
This PR fixes the issue where the importPath has the file extension (like `.js`), which in turn creates `possiblePaths` appending the current file extension as shown below in the screenshot.

**Issue:**
<img width="121" alt="Screenshot 2023-12-14 at 4 35 01 PM" src="https://github.com/benyap/resolve-tspaths/assets/15179597/cdda6433-17ca-412c-a025-19dcf7b5120f">


Note: This is for supporting `"moduleResolution": "nodenext"` of typescript.


Patch:
diff --git a/src/steps/generateChanges.ts b/src/steps/generateChanges.ts
index 7717c04..c5aa5af 100644
--- a/src/steps/generateChanges.ts
+++ b/src/steps/generateChanges.ts
@@ -24,6 +24,7 @@ const MODULE_EXTS = [
   ".mjs",
   ".mdx",
   ".d.ts",
+  ".json",
 ];
 
 /**
@@ -207,8 +208,14 @@ function resolveImportPath(importPath: string) {
   const importPathTs = importPath.replace(/\.[^/.]*js[^/.]*$/, (match) =>
     match.replace("js", "ts"),
   );
+
+  const importPathWithoutFileExtension = importPath.replace(
+    /\.[^/.]*(js|json)[^/.]*$/,
+    "",
+  );
+
   const importPathWithExtensions = MODULE_EXTS.map(
-    (ext) => `${importPath}${ext}`,
+    (ext) => `${importPathWithoutFileExtension}${ext}`,
   );
 
   const possiblePaths = [importPath, importPathTs, ...importPathWithExtensions];


Test patch:
diff --git a/test/fixtures/moduleResolutionNodeNext/out/components/App/index.d.ts b/test/fixtures/moduleResolutionNodeNext/out/components/App/index.d.ts
new file mode 100644
index 0000000..bd18910
--- /dev/null
+++ b/test/fixtures/moduleResolutionNodeNext/out/components/App/index.d.ts
@@ -0,0 +1,1 @@
+export declare const App: () => JSX.Element;
diff --git a/test/fixtures/moduleResolutionNodeNext/out/components/App/index.js b/test/fixtures/moduleResolutionNodeNext/out/components/App/index.js
new file mode 100644
index 0000000..0713713
--- /dev/null
+++ b/test/fixtures/moduleResolutionNodeNext/out/components/App/index.js
@@ -0,0 +1,2 @@
+import { jsx as _jsx } from "react/jsx-runtime";
+export const App = () => _jsx("div", { children: "Content" });
diff --git a/test/fixtures/moduleResolutionNodeNext/out/index.d.ts b/test/fixtures/moduleResolutionNodeNext/out/index.d.ts
new file mode 100644
index 0000000..b44f481
--- /dev/null
+++ b/test/fixtures/moduleResolutionNodeNext/out/index.d.ts
@@ -0,0 +1,1 @@
+export * from "~/components/App/index.js";
diff --git a/test/fixtures/moduleResolutionNodeNext/out/index.js b/test/fixtures/moduleResolutionNodeNext/out/index.js
new file mode 100644
index 0000000..b44f481
--- /dev/null
+++ b/test/fixtures/moduleResolutionNodeNext/out/index.js
@@ -0,0 +1,1 @@
+export * from "~/components/App/index.js";
diff --git a/test/fixtures/moduleResolutionNodeNext/package.json b/test/fixtures/moduleResolutionNodeNext/package.json
new file mode 100644
index 0000000..422ac53
--- /dev/null
+++ b/test/fixtures/moduleResolutionNodeNext/package.json
@@ -0,0 +1,8 @@
+{
+  "name": "test-module-resolution-node-next",
+  "version": "0.0.0",
+  "type": "module",
+  "dependencies": {
+    "typescript": "5.2.2"
+  }
+}
diff --git a/test/fixtures/moduleResolutionNodeNext/src/components/App/index.tsx b/test/fixtures/moduleResolutionNodeNext/src/components/App/index.tsx
new file mode 100644
index 0000000..ec4ad77
--- /dev/null
+++ b/test/fixtures/moduleResolutionNodeNext/src/components/App/index.tsx
@@ -0,0 +1,1 @@
+export const App = (): JSX.Element => <div>Content</div>;
diff --git a/test/fixtures/moduleResolutionNodeNext/src/index.ts b/test/fixtures/moduleResolutionNodeNext/src/index.ts
new file mode 100644
index 0000000..b44f481
--- /dev/null
+++ b/test/fixtures/moduleResolutionNodeNext/src/index.ts
@@ -0,0 +1,1 @@
+export * from "~/components/App/index.js";
diff --git a/test/fixtures/moduleResolutionNodeNext/tsconfig.json b/test/fixtures/moduleResolutionNodeNext/tsconfig.json
new file mode 100644
index 0000000..d8deb3a
--- /dev/null
+++ b/test/fixtures/moduleResolutionNodeNext/tsconfig.json
@@ -0,0 +1,16 @@
+{
+  "compilerOptions": {
+    "outDir": "./out",
+    "baseUrl": ".",
+    "declaration": true,
+    "module": "NodeNext",
+    "moduleResolution": "NodeNext",
+    "target": "ESNext",
+    "jsx": "react-jsx",
+    "paths": {
+      "~/*": ["./src/*"]
+    },
+    "resolveJsonModule": true,
+    "allowSyntheticDefaultImports": true
+  }
+}
diff --git a/test/steps/generateChanges.test.ts b/test/steps/generateChanges.test.ts
index 7d33a39..3df8d10 100644
--- a/test/steps/generateChanges.test.ts
+++ b/test/steps/generateChanges.test.ts
@@ -489,6 +489,37 @@ describe("steps/generateChanges", () => {
         }
       `);
     });
+
+    it("does replace paths for module resolution nodenext import", () => {
+      const root = `${cwd}/test/fixtures/moduleResolutionNodeNext`;
+      const aliases: Alias[] = [
+        {
+          alias: "~/*",
+          prefix: "~/",
+          aliasPaths: [`${root}/src`],
+        },
+      ];
+      const programPaths: Pick<ProgramPaths, "srcPath" | "outPath"> = {
+        srcPath: `${root}/src`,
+        outPath: `${root}/out`,
+      };
+
+      const result = aliasToRelativePath(
+        "~/components/App/index.js",
+        "test/fixtures/moduleResolutionNodeNext/out/index.js",
+        aliases,
+        programPaths,
+        true,
+      );
+
+      expect(result).toMatchInlineSnapshot(`
+        {
+          "file": "test/fixtures/moduleResolutionNodeNext/out/index.js",
+          "original": "~/components/App/index.js",
+          "replacement": "./components/App/index.js",
+        }
+      `);
+    });
   });
 
   describe(replaceAliasPathsInFile.name, () => {
@@ -1019,6 +1050,39 @@ describe("steps/generateChanges", () => {
           'export { sameName } from "./sameName";',
         );
       });
+
+      it("generates replacements for a file with imports of module resolution node next", () => {
+        const root = `${cwd}/test/fixtures/moduleResolutionNodeNext`;
+        const aliases: Alias[] = [
+          {
+            alias: "~/*",
+            prefix: "~/",
+            aliasPaths: [`${root}/src`],
+          },
+        ];
+        const programPaths: Pick<ProgramPaths, "srcPath" | "outPath"> = {
+          srcPath: `${root}/src`,
+          outPath: `${root}/out`,
+        };
+        const results = replaceAliasPathsInFile(
+          `${root}/out/index.js`,
+          aliases,
+          programPaths,
+        );
+        expect(results.changed).toBe(true);
+        expect(results.changes).toMatchInlineSnapshot(`
+          [
+            {
+              "modified": "./components/App/index.js",
+              "original": "~/components/App/index.js",
+            },
+          ]
+        `);
+        expect(results.text).toMatchInlineSnapshot(`
+          "export * from \\"./components/App/index.js\\";
+          "
+        `);
+      });
     });
   });