Files
Ning YuanLin 6456f0e404 feat(java): add read/write filters for Java field usages (#569)
Find Usages mixed reads and writes of a Java field, including Lombok getter and setter calls, because jdtls has no read/write classification in its references. The usages popup and Find window now have Read and Write toggles. Read-write accesses stay visible in either mode and unclassified results are kept.

Classification comes from a jdtls extension in `plugins/java-usages`, embedded as a jar and installed beside the other bundles. The checked-in jar was rebuilt from source with ECJ and matches byte for byte. Any extension failure falls back to standard references, so a jdtls upgrade can lose the filter but not Find Usages; rebuilding the jar on an upgrade is manual and described in the plugin README. Older installed jars are removed. Other languages keep their plain result count.

Co-authored-by: NingYuanLin <57001533+NingYuanLin@users.noreply.github.com>
2026-11-01 09:09:15 +08:00

5.2 KiB

Reproduce Java usage access filtering

Small fixture

Use ReadWrite.java, a 24-line Java/Lombok fixture. It contains ordinary @Data, explicit fluent @Accessors, direct field reads/writes, compound updates, nested calls, inherited/interface calls and method references. Its line positions are assertions in read-write.py; keep them stable.

For manual testing, create a Java project with Lombok 1.18.48 on its classpath and copy this file into its source directory as Bean.java. For Maven, use JDK 21 or later and this minimal pom.xml:

<project xmlns="http://maven.apache.org/POM/4.0.0">
  <modelVersion>4.0.0</modelVersion>
  <groupId>example</groupId><artifactId>usage-access</artifactId><version>1.0</version>
  <dependencies>
    <dependency>
      <groupId>org.projectlombok</groupId><artifactId>lombok</artifactId>
      <version>1.18.48</version><scope>provided</scope>
    </dependency>
  </dependencies>
  <build><plugins><plugin>
    <groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId>
    <version>3.14.1</version>
    <configuration><release>21</release><annotationProcessorPaths><path>
      <groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.48</version>
    </path></annotationProcessorPaths></configuration>
  </plugin></plugins></build>
</project>

Open the project in OpenIDEA, allow JDT LS import/indexing to finish and verify there are no Java error diagnostics. Leave java.references.includeAccessors enabled. Invoke Show Usages on the declaration of Bean.count (Cmd-click on macOS, or the corresponding navigation action). Use the declaration to avoid navigating from a reference to its definition instead.

Before and after

On unmodified main, the references appear together and cannot be filtered by read/write access. Standard LSP references provide locations without this metadata. With this change, both toolbar buttons are initially selected:

Expression Access Read only Write only
b.count in an initializer Read Visible Hidden
b.count = 1 Write Hidden Visible
b.count += 2, b.count++, --b.count Read/write Visible Visible
b.getCount() Read Visible Hidden
b.setCount(...) Write Hidden Visible
b::getCount, b::setCount Unknown Visible Visible

The two references in b.setCount(b.getCount()) remain distinct even on one line. Inherited and interface calls are included. Selecting Fluent.score also includes score() and score(int) by method identity. b.items[0] = 1 reads items; it writes an element, not the field holding the array.

Turn off Show write accesses to inspect reads. Turn it back on and turn off Show read accesses to inspect writes. Turning off the last enabled switch enables the other. Move the results to Find: filters and original location metadata remain available. Reopen the popup to check persistence. Unknown results must stay visible in either mode. English tooltips are "Show read accesses" and "Show write accesses"; the icons are vendored JetBrains resources, not a claim of complete UI parity.

Automated server evidence and desktop checks

The harness creates an Eclipse project with the same fixture, loads the actual Lombok agent and extension, imports it in JDT LS, asserts semantic results and checks cancellation. No Maven download is needed for this harness.

export PATH=/path/to/jdk25/bin:$PATH
python3 plugins/java-usages/tests/read-write.py /path/to/jdtls /path/to/lombok.jar > /tmp/read-write.json

Then launch the desktop app with pnpm tauri dev and open the same Java/Lombok fixture. Check Read only, Write only, both toggles and Open in Find Window. The chosen mode and exact source ranges must survive transfer. Select a Java access mode, then run a non-Java search: it must show a plain count and no access toolbar. Repeat Java usages in a project with a marked source root. Confirm import and indexing have completed before comparing results.

For a desktop before/after demonstration use separate builds of the base and PR with this same fixture, settings and import completion state.

Scope and performance interpretation

This does not classify constructor initialization. Method references stay unknown, and lombok.config-only naming or unusual signatures may remain unmapped. See the parent README for internal API compatibility and conservative fallback.

A true no-feature timing baseline must omit this bundle entirely while retaining the same Lombok agent, then call textDocument/references directly. Compare the same query position, settings, server/compiler, fixture and import state. Measure cold first queries separately from warmed samples. RPC timings exclude UI and Rust orchestration. Fluent/prefixed fields may return fewer locations natively; those timings are not equal-workload comparisons and cannot establish a semantic speedup. Counter-instrumented runs must be separate from timing runs.