diff --git a/src/__tests__/babel/helpers.test.ts b/src/__tests__/babel/helpers.test.ts new file mode 100644 index 00000000..d0520a14 --- /dev/null +++ b/src/__tests__/babel/helpers.test.ts @@ -0,0 +1,20 @@ +import { resolvePosix } from "../../babel/helpers"; + +describe("resolvePosix", () => { + test("resolves to an absolute path with POSIX separators on every platform", () => { + // The invariant the import handlers depend on: no backslashes leak through, + // so their forward-slash `split` / `startsWith` matching works on Windows + // (where path.resolve otherwise yields "\"-separated paths). + const result = resolvePosix(process.cwd(), "a", "b", "c"); + + expect(result).not.toContain("\\"); + expect(result.split("/").slice(-3)).toEqual(["a", "b", "c"]); + }); + + test("collapses '..' segments like path.resolve, keeping POSIX separators", () => { + const result = resolvePosix(process.cwd(), "a", "b", "..", "c"); + + expect(result).not.toContain("\\"); + expect(result.split("/").slice(-2)).toEqual(["a", "c"]); + }); +}); diff --git a/src/babel/helpers.ts b/src/babel/helpers.ts index c66f630c..9a08e5d0 100644 --- a/src/babel/helpers.ts +++ b/src/babel/helpers.ts @@ -1,3 +1,5 @@ +import { resolve } from "path"; + import tBabelTypes, { type CallExpression } from "@babel/types"; export type BabelTypes = typeof tBabelTypes; @@ -38,3 +40,17 @@ export function getInteropRequireDefaultSource( return requireArg.value; } + +/** + * `path.resolve`, normalized to POSIX separators. + * + * The relative-import handlers resolve a source against the file being + * transformed and then match the result against forward-slash literals + * (`react-native/Libraries/Components/`, `react-native-web/dist`, …). On Windows + * `path.resolve` yields backslash separators, so those `split` / `startsWith` + * matches silently miss and the import is left un-rewritten. Normalizing to `/` + * makes the matching platform-independent. + */ +export function resolvePosix(...segments: string[]): string { + return resolve(...segments).replace(/\\/g, "/"); +} diff --git a/src/babel/react-native-web.ts b/src/babel/react-native-web.ts index 1bacf5a0..db860a49 100644 --- a/src/babel/react-native-web.ts +++ b/src/babel/react-native-web.ts @@ -1,5 +1,3 @@ -import { resolve } from "path"; - import { type NodePath } from "@babel/traverse"; import tBabelTypes, { type ImportDeclaration, @@ -9,12 +7,13 @@ import tBabelTypes, { } from "@babel/types"; import { allowedModules } from "./allowedModules"; +import { resolvePosix } from "./helpers"; type BabelTypes = typeof tBabelTypes; function parseReactNativeWebSource(source: string, filename: string) { if (source.startsWith(".")) { - source = resolve(filename, source); + source = resolvePosix(filename, source); const internalPath = source.split("react-native-web/dist")[1]; if (!internalPath) { diff --git a/src/babel/react-native.ts b/src/babel/react-native.ts index 2522a848..ad65c941 100644 --- a/src/babel/react-native.ts +++ b/src/babel/react-native.ts @@ -1,4 +1,4 @@ -import { dirname, resolve } from "path"; +import { dirname } from "path"; import { type NodePath } from "@babel/traverse"; import tBabelTypes, { @@ -9,12 +9,13 @@ import tBabelTypes, { } from "@babel/types"; import { allowedModules } from "./allowedModules"; +import { resolvePosix } from "./helpers"; type BabelTypes = typeof tBabelTypes; function parseReactNativeSource(source: string, filename: string) { if (source.startsWith(".")) { - source = resolve(dirname(filename), source); + source = resolvePosix(dirname(filename), source); const internalPath = source.split("react-native/Libraries/Components/")[1]; if (!internalPath) {