Skip to content

Commit ff24ac6

Browse files
committed
Add PackageResolver.relative_import_path/3
1 parent cd1cee3 commit ff24ac6

2 files changed

Lines changed: 84 additions & 0 deletions

File tree

lib/npm/package_resolver.ex

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,44 @@ defmodule NPM.PackageResolver do
203203
end
204204
end
205205

206+
# ---------------------------------------------------------------------------
207+
# Relative import paths
208+
# ---------------------------------------------------------------------------
209+
210+
@doc """
211+
Compute a relative import path from `importer` to `target` within `project_root`.
212+
213+
Both paths must be absolute. Returns a POSIX-style relative path with
214+
a guaranteed `./` or `../` prefix, suitable for use as an import specifier.
215+
216+
## Examples
217+
218+
iex> NPM.PackageResolver.relative_import_path(
219+
...> "/app/src/pages/home.js",
220+
...> "/app/src/utils/format.js",
221+
...> "/app"
222+
...> )
223+
"../utils/format.js"
224+
225+
iex> NPM.PackageResolver.relative_import_path(
226+
...> "/app/src/index.js",
227+
...> "/app/src/app.js",
228+
...> "/app"
229+
...> )
230+
"./app.js"
231+
"""
232+
@spec relative_import_path(String.t(), String.t(), String.t()) :: String.t()
233+
def relative_import_path(importer, target, project_root) do
234+
importer_dir = importer |> Path.relative_to(project_root) |> Path.dirname()
235+
target_label = Path.relative_to(target, project_root)
236+
relative = Path.relative_to(target_label, importer_dir)
237+
ensure_relative_prefix(relative)
238+
end
239+
240+
defp ensure_relative_prefix("./" <> _ = path), do: path
241+
defp ensure_relative_prefix("../" <> _ = path), do: path
242+
defp ensure_relative_prefix(path), do: "./" <> path
243+
206244
# ---------------------------------------------------------------------------
207245
# Private
208246
# ---------------------------------------------------------------------------

test/npm/package_resolver_test.exs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -463,6 +463,52 @@ defmodule NPM.PackageResolverTest do
463463
# Helpers
464464
# ---------------------------------------------------------------------------
465465

466+
# ---------------------------------------------------------------------------
467+
# relative_import_path/3
468+
# ---------------------------------------------------------------------------
469+
470+
describe "relative_import_path/3" do
471+
test "sibling file gets ./ prefix" do
472+
assert PackageResolver.relative_import_path(
473+
"/app/src/index.js",
474+
"/app/src/app.js",
475+
"/app"
476+
) == "./app.js"
477+
end
478+
479+
test "file in subdirectory" do
480+
assert PackageResolver.relative_import_path(
481+
"/app/src/index.js",
482+
"/app/src/utils/format.js",
483+
"/app"
484+
) == "./utils/format.js"
485+
end
486+
487+
test "file in parent directory" do
488+
assert PackageResolver.relative_import_path(
489+
"/app/src/pages/home.js",
490+
"/app/src/utils/format.js",
491+
"/app"
492+
) == "../utils/format.js"
493+
end
494+
495+
test "deeply nested upward traversal" do
496+
assert PackageResolver.relative_import_path(
497+
"/app/src/a/b/c/deep.js",
498+
"/app/src/lib/helper.js",
499+
"/app"
500+
) == "../../../lib/helper.js"
501+
end
502+
503+
test "same directory different extensions" do
504+
assert PackageResolver.relative_import_path(
505+
"/app/components/button.tsx",
506+
"/app/components/button.module.css",
507+
"/app"
508+
) == "./button.module.css"
509+
end
510+
end
511+
466512
defp write_pkg_json(dir, data) do
467513
File.write!(Path.join(dir, "package.json"), :json.encode(data))
468514
end

0 commit comments

Comments
 (0)