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).