Skip to content

Commit 16979b9

Browse files
SamChou19815facebook-github-bot
authored andcommitted
Support converting relative paths to module name
Summary: It will be used for autoimport with relative path. Reviewed By: yangdanny97 Differential Revision: D80853458 fbshipit-source-id: 4e3610f6b9ecba8ac70a00c63007ca546e0bc934
1 parent 90c808c commit 16979b9

File tree

3 files changed

+57
-11
lines changed

3 files changed

+57
-11
lines changed

Cargo.lock

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/pyrefly_python/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ equivalent = "1.0"
1515
itertools = "0.14.0"
1616
lsp-types = "0.94.1"
1717
parse-display = "0.8.2"
18+
pathdiff = "0.2"
1819
pyrefly_util = { path = "../pyrefly_util" }
1920
regex = "1.11.1"
2021
ruff_python_ast = { git = "https://github.com/astral-sh/ruff/", rev = "9bee8376a17401f9736b45fdefffb62edc2f1668" }

crates/pyrefly_python/src/module_name.rs

Lines changed: 49 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use std::fmt;
1010
use std::fmt::Debug;
1111
use std::fmt::Display;
1212
use std::hash::Hash;
13+
use std::path::Component;
1314
use std::path::Path;
1415

1516
use dupe::Dupe;
@@ -172,17 +173,7 @@ impl ModuleName {
172173
Self::from_string(itertools::join(parts, "."))
173174
}
174175

175-
pub fn from_relative_path(path: &Path) -> anyhow::Result<Self> {
176-
let mut components = Vec::new();
177-
for raw_component in path.components() {
178-
if let Some(component) = raw_component.as_os_str().to_str() {
179-
components.push(component)
180-
} else {
181-
return Err(anyhow::anyhow!(PathConversionError::ComponentNotUTF8 {
182-
component: raw_component.as_os_str().to_owned(),
183-
}));
184-
}
185-
}
176+
fn from_relative_path_components(mut components: Vec<&str>) -> anyhow::Result<Self> {
186177
let last_element = components.pop();
187178
match last_element {
188179
None => {}
@@ -201,6 +192,39 @@ impl ModuleName {
201192
Ok(ModuleName::from_parts(components))
202193
}
203194

195+
pub fn from_relative_path(path: &Path) -> anyhow::Result<Self> {
196+
let mut components = Vec::new();
197+
for raw_component in path.components() {
198+
if let Some(component) = raw_component.as_os_str().to_str() {
199+
components.push(component)
200+
} else {
201+
return Err(anyhow::anyhow!(PathConversionError::ComponentNotUTF8 {
202+
component: raw_component.as_os_str().to_owned(),
203+
}));
204+
}
205+
}
206+
Self::from_relative_path_components(components)
207+
}
208+
209+
pub fn relative_module_name_between(from: &Path, to: &Path) -> Option<ModuleName> {
210+
let relative_path = pathdiff::diff_paths(to, from.parent()?)?;
211+
// In the following loop, we aim to generate a list of components that can be joined by `.`
212+
// to form a correct relative import module name,
213+
let mut components = vec![""];
214+
for raw_component in relative_path.as_path().components() {
215+
match &raw_component {
216+
// For each parent, we should create a `.`.
217+
// The `.` is already provided during the join, so we only need an empty component.
218+
Component::ParentDir => components.push(""),
219+
Component::CurDir => {}
220+
Component::Prefix(_) | Component::RootDir | Component::Normal(_) => {
221+
components.push(raw_component.as_os_str().to_str()?);
222+
}
223+
};
224+
}
225+
Self::from_relative_path_components(components).ok()
226+
}
227+
204228
pub fn append(self, name: &Name) -> Self {
205229
Self::from_string(format!("{}.{}", self.as_str(), name))
206230
}
@@ -334,4 +358,18 @@ mod tests {
334358
assert_conversion_error("foo/bar/baz");
335359
assert_conversion_error("foo/bar/__init__.derp");
336360
}
361+
362+
#[test]
363+
fn test_relative_module_name_between() {
364+
fn assert_module_name(from: &str, to: &str, expected: &str) {
365+
let from = Path::new(from);
366+
let to = Path::new(to);
367+
let actual = ModuleName::relative_module_name_between(from, to);
368+
assert_eq!(Some(ModuleName::from_str(expected)), actual);
369+
}
370+
assert_module_name("foo/bar.py", "foo/baz.py", ".baz");
371+
assert_module_name("bar.py", "foo/baz.py", ".foo.baz");
372+
assert_module_name("foo/bar.py", "baz.py", "..baz");
373+
assert_module_name("foo/bar/boz.py", "baz.py", "...baz");
374+
}
337375
}

0 commit comments

Comments
 (0)