Infer Windows UNC paths with forward and mixed slashes (#49424)

## Why

`LegacyAppPathString` treated `//server/share/project` as POSIX, losing its UNC interpretation when inferring a `PathUri`.

## What changed

Infer Windows syntax whenever an API path starts with two separators, including forward and mixed slashes, independently of the host. For example, `//server/share/project` now converts to `file://server/share/project`. Double-slash POSIX paths require an explicit `PathConvention::Posix`; inferred paths still undergo validation.

## Testing

Add regression tests for UNC and namespace paths, mixed separators, URI serialization round trips, explicit POSIX conversion, convention mismatches, and malformed UNC paths.

GitOrigin-RevId: 720893de517addb7a938e6784d1ed0f05df3ec0d
This commit is contained in:
Dmitriy Kovalenko
2026-09-30 01:28:02 +00:00
committed by copyberry
parent ab84d71f57
commit d1d0e89558
2 changed files with 71 additions and 5 deletions
@@ -418,7 +418,7 @@ fn infers_absolute_path_conventions_from_api_text() {
(r"\\.\COM1", Some(PathConvention::Windows)),
("/workspace/file.rs", Some(PathConvention::Posix)),
("/C:/workspace/file.rs", Some(PathConvention::Posix)),
("//server/share/file.rs", Some(PathConvention::Posix)),
("//server/share/file.rs", Some(PathConvention::Windows)),
("", None),
(".", None),
("subdir/file.rs", None),
@@ -657,3 +657,62 @@ fn serializes_and_deserializes_as_a_string() {
rendered
);
}
#[test]
fn inferred_unc_paths_accept_forward_and_mixed_slashes_on_every_host() {
for raw in [
"//server/share/project",
r"//server/share\project",
r"/\server/share/project",
r"\/server/share/project",
r"\\server\share\project",
"//wsl.localhost/Ubuntu/home/user/project",
"//127.0.0.1/c$/project",
"//LOCALHOST/c$/project",
"//?/C:/project",
] {
let path = LegacyAppPathString::from_string(raw);
let expected = path.to_path_uri(PathConvention::Windows).expect(raw);
let inferred = path.to_inferred_path_uri().expect(raw);
assert_eq!(
(inferred.clone(), inferred.infer_path_convention()),
(expected, Some(PathConvention::Windows)),
"{raw}"
);
let serialized = serde_json::to_string(&inferred).expect("serialize URI");
assert_eq!(
serde_json::from_str::<PathUri>(&serialized).expect("deserialize URI"),
inferred,
"{raw}"
);
}
let slash = LegacyAppPathString::from_string("//server/share/project")
.to_inferred_path_uri()
.unwrap();
assert_eq!(
slash,
PathUri::parse("file://server/share/project").unwrap()
);
}
#[test]
fn double_slash_posix_paths_require_an_explicit_convention() {
let path = LegacyAppPathString::from_string("//server/share/project");
assert_eq!(
path.to_path_uri(PathConvention::Posix).unwrap(),
PathUri::parse("file:///server/share/project").unwrap()
);
let cwd = PathUri::parse("file:///workspace").unwrap();
assert!(matches!(
path.resolve_against(&cwd, /*user_home_dir*/ None),
Err(LegacyAppPathStringError::MismatchedConvention { .. })
));
for raw in ["//", "//server", "//server/", "///server/share"] {
assert!(
LegacyAppPathString::from_string(raw)
.to_inferred_path_uri()
.is_none(),
"{raw}"
);
}
}