diff --git a/crates/ide-lsp/tests/fixtures/LombokSymbols.java b/crates/ide-lsp/tests/fixtures/LombokSymbols.java new file mode 100644 index 000000000..b1d986cb1 --- /dev/null +++ b/crates/ide-lsp/tests/fixtures/LombokSymbols.java @@ -0,0 +1,28 @@ +package demo; + +@lombok.Data +@lombok.Builder +class User { + private String name; + private int age; +} + +@lombok.Getter +@lombok.Setter +@lombok.RequiredArgsConstructor +class Profile { + private final String id; + private String name; +} + +@lombok.extern.java.Log +class Logged { + void write() { log.info("hello"); } +} + +class PlainUser { + private String name; + PlainUser(String name) { this.name = name; } + String getName() { return name; } + void setName(String name) { this.name = name; } +} diff --git a/crates/ide-lsp/tests/real_servers.rs b/crates/ide-lsp/tests/real_servers.rs index a2ad393c4..f3b3e29cc 100644 --- a/crates/ide-lsp/tests/real_servers.rs +++ b/crates/ide-lsp/tests/real_servers.rs @@ -112,6 +112,72 @@ async fn kmp_lsp_java_end_to_end() { tokio::time::timeout(Duration::from_secs(10), mgr.shutdown_all()).await.unwrap(); } +#[tokio::test(flavor = "multi_thread")] +#[ignore = "requires jdtls on PATH and a compatible JDK"] +async fn lombok_generated_members_appear_in_document_symbols() { + let dir = tempfile::tempdir().unwrap(); + let root = dunce::canonicalize(dir.path()).unwrap(); + let source = include_str!("fixtures/LombokSymbols.java"); + let path = root.join("src/demo/LombokSymbols.java"); + std::fs::create_dir_all(path.parent().unwrap()).unwrap(); + std::fs::write(&path, source).unwrap(); + let agent = dunce::canonicalize( + Path::new(env!("CARGO_MANIFEST_DIR")).join("../../java/lombok/lombok-1.18.48-openidea.3.jar"), + ) + .unwrap(); + std::fs::write( + root.join(".project"), + "lombok-symbols\ + org.eclipse.jdt.core.javabuilder\ + org.eclipse.jdt.core.javanature", + ) + .unwrap(); + let library = agent.to_string_lossy().replace('&', "&").replace('"', """).replace('<', "<"); + std::fs::write( + root.join(".classpath"), + format!( + "\ + \ + \ + " + ), + ) + .unwrap(); + let mut java = LanguageRegistry::load(None).unwrap().get(&LanguageId::new("java")).unwrap(); + java.language_servers.retain(|s| s.name == "jdtls"); + let server = &mut java.language_servers[0]; + if let Ok(bin) = std::env::var("OPENIDEA_TEST_JDTLS") { + server.command = bin; + } + server.args.extend([ + "-data".into(), + root.join("workspace").to_string_lossy().into_owned(), + format!("--jvm-arg=-javaagent:{}", agent.display()), + ]); + let (mgr, _) = manager(&root, LanguageRegistry::from_configs(vec![java])); + let doc = DocId::next(); + mgr.did_open(doc, &path, &LanguageId::new("java"), 1, source); + let expected: &[(&str, &[&str])] = &[ + ("User", &["getName()", "setName(String)", "getAge()", "setAge(int)", "User(String, int)", "builder()"]), + ("UserBuilder", &["name(String)", "age(int)", "build()"]), + ("Profile", &["Profile(String)", "getId()", "getName()", "setName(String)"]), + ("Logged", &["log", "write()"]), + ("PlainUser", &["PlainUser(String)", "getName()", "setName(String)"]), + ]; + fn has_members(items: &[ide_core::OutlineItem], owner: &str, names: &[&str]) -> bool { + items.iter().any(|item| { + (item.name == owner && names.iter().all(|n| item.children.iter().any(|c| c.name == *n))) + || has_members(&item.children, owner, names) + }) + } + retry("Lombok document symbols", Duration::from_secs(120), || async { + let items = mgr.document_symbols(doc).await.ok().flatten()?; + expected.iter().all(|(owner, names)| has_members(&items, owner, names)).then_some(()) + }) + .await; + tokio::time::timeout(Duration::from_secs(10), mgr.shutdown_all()).await.unwrap(); +} + #[tokio::test(flavor = "multi_thread")] #[ignore = "requires jdtls on PATH and a compatible JDK"] async fn intellij_java_formatter_preserves_manual_breaks_and_wraps_long_lines() { diff --git a/languages/java.toml b/languages/java.toml index e7997db1a..e4d60f864 100644 --- a/languages/java.toml +++ b/languages/java.toml @@ -44,6 +44,7 @@ maven = { downloadSources = true, updateSnapshots = false } eclipse = { downloadSources = true } references = { includeDecompiledSources = true } signatureHelp = { enabled = true } +symbols = { includeGeneratedCode = true } configuration = { updateBuildConfiguration = "automatic" } project = { referencedLibraries = ["lib/**/*.jar"] } # Generated code (implement methods, getters, toString…) indents like the editor (`indent` above).