diff --git a/crates/ide-host/src/plugins/api.rs b/crates/ide-host/src/plugins/api.rs index 350c4ddf..a09e61a1 100644 --- a/crates/ide-host/src/plugins/api.rs +++ b/crates/ide-host/src/plugins/api.rs @@ -807,6 +807,19 @@ mod tests { PluginApi { pkg: Package::new(manifest, dir.to_path_buf()), shared } } + #[test] + fn repository_and_search_paths_match_ipc_events() { + let tmp = tempfile::tempdir().unwrap(); + assert!(std::process::Command::new("git").args(["init", "-q"]).arg(tmp.path()).status().unwrap().success()); + let root = tmp.path().canonicalize().unwrap(); + std::fs::create_dir(root.join("src")).unwrap(); + let file = root.join("src/Example.java"); + std::fs::write(&file, "class Example {}\n").unwrap(); + let api = api(&root, Arc::default()); + assert_eq!(api.find_files(&root, "**/*.java", 8).unwrap(), [to_ipc(&file)]); + assert_eq!(api.vcs_status(&root).unwrap().unwrap().root, to_ipc(&root)); + } + #[test] fn programs_run_only_as_approved() { let tmp = tempfile::tempdir().unwrap(); diff --git a/crates/ide-plugin/src/runtime.rs b/crates/ide-plugin/src/runtime.rs index 5dd6830f..e7fe2555 100644 --- a/crates/ide-plugin/src/runtime.rs +++ b/crates/ide-plugin/src/runtime.rs @@ -1127,6 +1127,27 @@ fn event_to_wit_view(e: ViewEvent) -> wit_vt::ViewEvent { mod tests { use super::*; + #[cfg(windows)] + #[test] + fn provider_and_resolved_action_paths_match_ipc_events() { + for (path, expected) in [ + (r"C:\project\Example.java", "C:/project/Example.java"), + (r"\\?\C:\project\Example.java", "C:/project/Example.java"), + (r"\\server\share\Example.java", "//server/share/Example.java"), + ] { + let doc = ProviderDocument { path: path.into(), ..Default::default() }; + assert_eq!(doc_to_wit(&doc).path, expected); + let action = CodeAction { + title: "Fix".into(), + kind: None, + preferred: false, + edits: vec![(path.into(), Vec::new())], + data: None, + }; + assert_eq!(code_action_to_wit(&action).edits[0].path, expected); + } + } + fn ext(kind: &str) -> wit_types::Extension { wit_types::Extension { kind: kind.into(), data: "{}".into() } } diff --git a/plugins/official/checkstyle/Cargo.lock b/plugins/official/checkstyle/Cargo.lock index 5c3d4f38..bc79d791 100644 --- a/plugins/official/checkstyle/Cargo.lock +++ b/plugins/official/checkstyle/Cargo.lock @@ -103,7 +103,7 @@ dependencies = [ [[package]] name = "openidea-plugin-openidea-checkstyle" -version = "0.1.0" +version = "0.1.1" dependencies = [ "openidea-plugin", ] diff --git a/plugins/official/checkstyle/Cargo.toml b/plugins/official/checkstyle/Cargo.toml index 2a04ad51..0ffe0337 100644 --- a/plugins/official/checkstyle/Cargo.toml +++ b/plugins/official/checkstyle/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "openidea-plugin-openidea-checkstyle" -version = "0.1.0" +version = "0.1.1" edition = "2024" publish = false diff --git a/plugins/official/checkstyle/dist/plugin.wasm b/plugins/official/checkstyle/dist/plugin.wasm index e6c41ba8..0c5b399d 100644 Binary files a/plugins/official/checkstyle/dist/plugin.wasm and b/plugins/official/checkstyle/dist/plugin.wasm differ diff --git a/plugins/official/checkstyle/dist/plugin.wasm.sources b/plugins/official/checkstyle/dist/plugin.wasm.sources index 9d7525f7..d07b95aa 100644 --- a/plugins/official/checkstyle/dist/plugin.wasm.sources +++ b/plugins/official/checkstyle/dist/plugin.wasm.sources @@ -1 +1 @@ -2f4608630c5e41453458ecee5bc4e2f1994a680d4307a5f002f9dd5ccc04d457 +69273e1ddfd2fff9f49c11d4fbdc60a771f8e3befe440e1623c2ab97469aa250 diff --git a/plugins/official/checkstyle/plugin.toml b/plugins/official/checkstyle/plugin.toml index 1732ad30..9c83c2b1 100644 --- a/plugins/official/checkstyle/plugin.toml +++ b/plugins/official/checkstyle/plugin.toml @@ -1,6 +1,6 @@ id = "openidea.checkstyle" name = "Checkstyle" -version = "0.1.0" +version = "0.1.1" description = "Checks Java files with Checkstyle when you save them: problems in the editor and the Problems view, quick fixes for tabs, trailing spaces and missing final newlines." authors = ["OpenIDEA contributors"] license = "Apache-2.0" diff --git a/plugins/official/checkstyle/src/lib.rs b/plugins/official/checkstyle/src/lib.rs index aebf998e..6159e7ce 100644 --- a/plugins/official/checkstyle/src/lib.rs +++ b/plugins/official/checkstyle/src/lib.rs @@ -110,6 +110,26 @@ fn project_of(path: &str) -> Option { .max_by_key(|p| p.root().map(|r| r.len()).unwrap_or(0)) } +fn windows_path(path: &str) -> String { + let path = path.replace('\\', "/"); + if let Some(rest) = path.strip_prefix("//?/UNC/") { + format!("//{rest}") + } else { + path.strip_prefix("//?/").unwrap_or(&path).to_owned() + } +} + +fn report_matches_file(reported: &str, path: &str) -> bool { + // WASI plugins run on every host, so interpret the host's path rather than cfg(windows). + let drive = path.as_bytes().get(1) == Some(&b':') && path.as_bytes()[0].is_ascii_alphabetic(); + if drive || path.starts_with("//") || path.starts_with(r"\\") { + let (reported, path) = (windows_path(reported), windows_path(path)); + reported == path || path.ends_with(&reported) + } else { + reported == path || path.ends_with(reported) + } +} + /// The configuration for files of `project`. fn config_for(project: &Project) -> String { PROJECT_CONFIGS @@ -138,11 +158,8 @@ impl Checkstyle { return Err(Error::Failed(format!("checkstyle: {}", out.stderr.lines().next().unwrap_or("no report")))); } let report = parse_report(&out.stdout); - let problems = report - .into_iter() - .find(|(f, _)| f == path || path.ends_with(f.as_str())) - .map(|(_, p)| p) - .unwrap_or_default(); + let problems = + report.into_iter().find(|(f, _)| report_matches_file(f, path)).map(|(_, p)| p).unwrap_or_default(); let text = project.read_file(path).unwrap_or_default(); let diagnostics: Vec = problems.iter().map(|p| diagnostic(p, &text)).collect(); host::publish_diagnostics(path, &diagnostics)?; @@ -233,7 +250,7 @@ impl Plugin for Checkstyle { register_plugin!(Checkstyle, providers); -#[cfg(test)] +#[cfg(all(test, not(target_arch = "wasm32")))] mod tests { use super::*; use openidea_plugin::ProcessOutput; @@ -288,6 +305,45 @@ mod tests { assert_eq!(actions[1].edits[0].1[0].text, " void f() {}"); } + #[test] + fn unix_report_paths_preserve_backslashes() { + assert!(report_matches_file("/project/A\\B.java", "/project/A\\B.java")); + assert!(!report_matches_file("/project/A\\B.java", "/project/A/B.java")); + } + + #[test] + fn windows_reports_match_the_saved_file_and_keep_quick_fixes() { + for (root, reported) in [ + ("C:/project", r"C:\project\src\A.java"), + ("C:/project", r"\\?\C:\project\src\A.java"), + ("C:/project", r"src\A.java"), + ("//server/share/project", r"\\server\share\project\src\A.java"), + ("//server/share/project", r"\\?\UNC\server\share\project\src\A.java"), + ] { + let host = + MockHost::install().with_capabilities(&["project.read", "events", "storage", "process:checkstyle"]); + host.set_project_root(Some(root)); + host.add_file("src/A.java", "class A {\n\tint x;\n void f() {} \n}"); + let xml = REPORT.replace("/project/src/A.java", reported); + host.on_process("checkstyle", move |_, _| { + Ok(ProcessOutput { status: Some(1), stdout: xml.clone(), stderr: String::new() }) + }); + let path = format!("{root}/src/A.java"); + let mut plugin = Checkstyle::default(); + plugin.handle_events(&[Event::FileSaved(path.clone())], &host.invocation()); + assert_eq!(host.diagnostics()[&path].len(), 2, "{reported}"); + let doc = Document { + path: path.clone(), + language: "java".into(), + version: 1, + text: "class A {\n\tint x;\n void f() {} \n}".into(), + }; + let actions = plugin.code_actions(&doc, Range::new(Position::new(1, 0), Position::new(2, 0))); + assert_eq!(actions.len(), 2, "{reported}"); + assert_eq!(actions[0].edits[0].0, path); + } + } + #[test] fn the_projects_configuration_wins() { let host = MockHost::install().with_capabilities(&["project.read", "events", "storage", "process:checkstyle"]);