Skip to content

Commit

Permalink
Fix Windows by handling Url paths correctly.
Browse files Browse the repository at this point in the history
Previous implementation was mixing Url paths with file paths.

It caused errors like:

"Error: `file:///C:/work/bazel_plain/sample.bzl` could not be converted back to a URL\n"

More: cameron-martin/bazel-lsp#62

The Url.path() is usually `"/" + path to file` which works fine as file
path under unix-like systems, but breaks under Windows, where it can be
e.g. "/c:/some/file". See
https://en.wikipedia.org/wiki/Uniform_Resource_Identifier#syntax

Switched to using Url.to_file_path() which
represents actual file path.

In the process:
  - Enabled back Windows tests that at least for my machine started
    passing;
  - In validate() function not converting from Url to LspUrl and back,
    just returning to client Url passed in the request.
  • Loading branch information
hauserx authored and Grzegorz Lukasik committed Dec 2, 2024
1 parent b85d972 commit 477d87e
Show file tree
Hide file tree
Showing 5 changed files with 46 additions and 41 deletions.
2 changes: 0 additions & 2 deletions starlark_lsp/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,4 @@ starlark_syntax = { version = "0.12.0", path = "../starlark_syntax" }
[dev-dependencies]
regex = "1.5.4"
textwrap = "0.11"

[target.'cfg(not(windows))'.dev-dependencies]
maplit = "1.0.2"
1 change: 0 additions & 1 deletion starlark_lsp/src/definition.rs
Original file line number Diff line number Diff line change
Expand Up @@ -736,7 +736,6 @@ pub(crate) mod helpers {
)?))
}

#[cfg(not(windows))]
pub(crate) fn program(&self) -> String {
self.program.clone()
}
Expand Down
2 changes: 1 addition & 1 deletion starlark_lsp/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,5 @@ pub(crate) mod inspect;
pub(crate) mod loaded;
pub mod server;
mod symbols;
#[cfg(all(test, not(windows)))]
#[cfg(test)]
mod test;
66 changes: 40 additions & 26 deletions starlark_lsp/src/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,8 @@ pub enum LspUrlError {
/// For some reason the PathBuf/Url in the LspUrl could not be converted back to a URL.
#[error("`{}` could not be converted back to a URL", .0)]
Unparsable(LspUrl),
#[error("invalid URL for file:// schema (possibly not absolute?): `{}`", .0)]
InvalidFileUrl(Url),
}

/// A URL that represents the two types (plus an "Other") of URIs that are supported.
Expand Down Expand Up @@ -198,9 +200,12 @@ impl TryFrom<Url> for LspUrl {
fn try_from(url: Url) -> Result<Self, Self::Error> {
match url.scheme() {
"file" => {
let path = PathBuf::from(url.path());
if path.to_string_lossy().starts_with('/') {
Ok(Self::File(path))
let file_path = PathBuf::from(
url.to_file_path()
.map_err(|_| LspUrlError::InvalidFileUrl(url.clone()))?,
);
if file_path.is_absolute() {
Ok(Self::File(file_path))
} else {
Err(LspUrlError::NotAbsolute(url))
}
Expand Down Expand Up @@ -432,14 +437,14 @@ impl<T: LspContext> Backend<T> {
}

fn validate(&self, uri: Url, version: Option<i64>, text: String) -> anyhow::Result<()> {
let uri = uri.try_into()?;
let eval_result = self.context.parse_file_with_contents(&uri, text);
let lsp_url = uri.clone().try_into()?;
let eval_result = self.context.parse_file_with_contents(&lsp_url, text);
if let Some(ast) = eval_result.ast {
let module = Arc::new(LspModule::new(ast));
let mut last_valid_parse = self.last_valid_parse.write().unwrap();
last_valid_parse.insert(uri.clone(), module);
last_valid_parse.insert(lsp_url, module);
}
self.publish_diagnostics(uri.try_into()?, eval_result.diagnostics, version);
self.publish_diagnostics(uri, eval_result.diagnostics, version);
Ok(())
}

Expand Down Expand Up @@ -1361,11 +1366,8 @@ where
}
}

// TODO(nmj): Some of the windows tests get a bit flaky, especially around
// some paths. Revisit later.
#[cfg(all(test, not(windows)))]
#[cfg(test)]
mod tests {
use std::path::Path;
use std::path::PathBuf;

use anyhow::Context;
Expand Down Expand Up @@ -1469,6 +1471,16 @@ mod tests {
Url::from_file_path(PathBuf::from("/tmp").join(rel_path)).unwrap()
}

// Converts PathBuf to string that can be used in starlark load statements.
// Performs simple string quoting by replacing \ with \\ (for windows paths).
fn path_buf_to_load_string(p: &PathBuf) -> String {
p.to_str().unwrap().replace("\\", "\\\\")
}

fn uri_to_load_string(uri: &Url) -> String {
path_buf_to_load_string(&uri.to_file_path().unwrap())
}

#[test]
fn sends_empty_goto_definition_on_nonexistent_file() -> anyhow::Result<()> {
if is_wasm() {
Expand Down Expand Up @@ -1574,7 +1586,7 @@ mod tests {
<baz_click><baz>b</baz>az</baz_click>()
"#,
)
.replace("{load}", bar_uri.path())
.replace("{load}", &uri_to_load_string(&bar_uri))
.trim()
.to_owned();
let bar_contents = "def <baz>baz</baz>():\n pass";
Expand All @@ -1590,7 +1602,7 @@ mod tests {
let mut server = TestServer::new()?;
// Initialize with "junk" on disk so that we make sure we're using the contents from the
// client (potentially indicating an unsaved, modified file)
server.set_file_contents(PathBuf::from(bar_uri.path()), "some_symbol = 1".to_owned())?;
server.set_file_contents(&bar_uri, "some_symbol = 1".to_owned())?;
server.open_file(foo_uri.clone(), foo.program())?;
server.open_file(bar_uri, bar.program())?;

Expand Down Expand Up @@ -1623,9 +1635,10 @@ mod tests {
<baz_click><baz>b</baz>az</baz_click>()
"#,
)
.replace("{load}", bar_uri.path())
.replace("{load}", &uri_to_load_string(&bar_uri))
.trim()
.to_owned();
eprintln!("foo_contents: {}", foo_contents);
let bar_contents = "def <baz>baz</baz>():\n pass";
let foo = FixtureWithRanges::from_fixture(foo_uri.path(), &foo_contents)?;
let bar = FixtureWithRanges::from_fixture(bar_uri.path(), bar_contents)?;
Expand All @@ -1638,7 +1651,7 @@ mod tests {

let mut server = TestServer::new()?;
server.open_file(foo_uri.clone(), foo.program())?;
server.set_file_contents(PathBuf::from(bar_uri.path()), bar.program())?;
server.set_file_contents(&bar_uri, bar.program())?;

let goto_definition = goto_definition_request(
&mut server,
Expand Down Expand Up @@ -1683,7 +1696,7 @@ mod tests {

let mut server = TestServer::new()?;
server.open_file(foo_uri.clone(), foo.program())?;
server.set_file_contents(PathBuf::from(bar_uri.path()), bar.program())?;
server.set_file_contents(&bar_uri, bar.program())?;

let goto_definition = goto_definition_request(
&mut server,
Expand Down Expand Up @@ -1769,7 +1782,7 @@ mod tests {

let mut server = TestServer::new()?;
server.open_file(foo_uri.clone(), foo.program())?;
server.set_file_contents(PathBuf::from(bar_uri.path()), bar.program())?;
server.set_file_contents(&bar_uri, bar.program())?;

let goto_definition = goto_definition_request(
&mut server,
Expand Down Expand Up @@ -1800,7 +1813,7 @@ mod tests {
baz()
"#,
)
.replace("{load}", bar_uri.path())
.replace("{load}", &uri_to_load_string(&bar_uri))
.trim()
.to_owned();
let bar_contents = "def <baz>baz</baz>():\n pass";
Expand All @@ -1815,7 +1828,7 @@ mod tests {

let mut server = TestServer::new()?;
server.open_file(foo_uri.clone(), foo.program())?;
server.set_file_contents(PathBuf::from(bar_uri.path()), bar.program())?;
server.set_file_contents(&bar_uri, bar.program())?;

let goto_definition = goto_definition_request(
&mut server,
Expand Down Expand Up @@ -1860,7 +1873,7 @@ mod tests {

let mut server = TestServer::new()?;
server.open_file(foo_uri.clone(), foo.program())?;
server.set_file_contents(PathBuf::from(bar_uri.path()), bar.program())?;
server.set_file_contents(&bar_uri, bar.program())?;

let goto_definition = goto_definition_request(
&mut server,
Expand All @@ -1884,12 +1897,13 @@ mod tests {

let foo_uri = temp_file_uri("foo.star");
let bar_uri = temp_file_uri("bar.star");
let bar_load_string = Path::new(bar_uri.path())
let load_path = bar_uri
.to_file_path()
.unwrap()
.parent()
.unwrap()
.join("<bar>b</bar>ar<dot>.</dot>star")
.display()
.to_string();
.join("<bar>b</bar>ar<dot>.</dot>star");
let bar_load_string = path_buf_to_load_string(&load_path);

let foo_contents = dedent(
r#"
Expand Down Expand Up @@ -2141,7 +2155,7 @@ mod tests {

let mut server = TestServer::new()?;
server.open_file(foo_uri.clone(), foo.program())?;
server.set_file_contents(PathBuf::from(bar_uri.path()), bar.program())?;
server.set_file_contents(&bar_uri, bar.program())?;
server.mkdir(dir1_uri.clone());
server.mkdir(dir2_uri.clone());

Expand Down Expand Up @@ -2425,7 +2439,7 @@ mod tests {
loaded.<y><y_click>y</y_click></y>
"#,
)
.replace("{load}", bar_uri.path())
.replace("{load}", &uri_to_load_string(&bar_uri))
.trim()
.to_owned();

Expand Down
16 changes: 5 additions & 11 deletions starlark_lsp/src/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,14 +75,6 @@ use crate::server::LspServerSettings;
use crate::server::LspUrl;
use crate::server::StringLiteralResult;

/// Get the path from a URL, trimming off things like the leading slash that gets
/// appended in some windows test environments.
#[cfg(windows)]
fn get_path_from_uri(uri: &str) -> PathBuf {
PathBuf::from(uri.trim_start_match('/'))
}

#[cfg(not(windows))]
fn get_path_from_uri(uri: &str) -> PathBuf {
PathBuf::from(uri)
}
Expand Down Expand Up @@ -662,9 +654,11 @@ impl TestServer {
Ok(())
}

/// Set the file contents that `get_load_contents()` will return. The path must be absolute.
pub fn set_file_contents(&self, path: PathBuf, contents: String) -> anyhow::Result<()> {
let path = get_path_from_uri(&format!("{}", path.display()));
/// Set the file contents that `get_load_contents()` will return.
pub fn set_file_contents(&self, file_uri: &Url, contents: String) -> anyhow::Result<()> {
let path = file_uri
.to_file_path()
.map_err(|_| anyhow::anyhow!("Invalid file URI: {}", file_uri))?;
if !path.is_absolute() {
Err(TestServerError::SetFileNotAbsolute(path).into())
} else {
Expand Down

0 comments on commit 477d87e

Please sign in to comment.