88import javax .lang .model .util .Types ;
99
1010import javax .tools .JavaFileObject ;
11- import java .io .ByteArrayOutputStream ;
12- import java .io .IOException ;
13- import java .io .PrintWriter ;
11+ import java .io .*;
1412import java .net .URI ;
1513import java .nio .file .Files ;
1614import java .nio .file .Path ;
1715import java .nio .file .Paths ;
16+ import java .util .HashSet ;
17+ import java .util .Set ;
18+ import java .util .stream .Collectors ;
1819
1920/**
2021 * Callback hook that generates SemanticDB when the compiler has completed typechecking a Java
@@ -44,7 +45,22 @@ public SemanticdbTaskListener(
4445 }
4546
4647 @ Override
47- public void started (TaskEvent e ) {}
48+ public void started (TaskEvent e ) {
49+ // Upon first encounter with a file (before any other tasks are run)
50+ // we remove the semanticdb file for this source file to ensure
51+ // stale data doesn't cause problems
52+ if (e .getKind () == TaskEvent .Kind .ENTER ) {
53+ inferBazelSourceroot (e .getSourceFile ());
54+ Result <Path , String > semanticdbPath = semanticdbOutputPath (options , e );
55+ if (semanticdbPath .isOk ()) {
56+ try {
57+ Files .deleteIfExists (semanticdbPath .getOrThrow ());
58+ } catch (IOException ex ) {
59+ this .reportException (ex , e );
60+ }
61+ }
62+ }
63+ }
4864
4965 @ Override
5066 public void finished (TaskEvent e ) {
@@ -78,8 +94,10 @@ public void finished(TaskEvent e) {
7894 }
7995 }
8096
81- // Uses reporter.error with the full stack trace of the exception instead of reporter.exception
82- // because reporter.exception doesn't seem to print any meaningful information about the
97+ // Uses reporter.error with the full stack trace of the exception instead of
98+ // reporter.exception
99+ // because reporter.exception doesn't seem to print any meaningful information
100+ // about the
83101 // exception, it just prints the location with an empty message.
84102 private void reportException (Throwable exception , TaskEvent e ) {
85103 ByteArrayOutputStream baos = new ByteArrayOutputStream ();
@@ -96,7 +114,9 @@ private void onFinishedAnalyze(TaskEvent e) {
96114 Semanticdb .TextDocument textDocument =
97115 new SemanticdbVisitor (globals , e .getCompilationUnit (), options , types , trees , elements )
98116 .buildTextDocument (e .getCompilationUnit ());
99- writeSemanticdb (e , path .getOrThrow (), textDocument );
117+ Path output = path .getOrThrow ();
118+ if (Files .exists (output )) appendSemanticdb (e , output , textDocument );
119+ else writeSemanticdb (e , output , textDocument );
100120 } else {
101121 reporter .error (path .getErrorOrThrow (), e );
102122 }
@@ -114,6 +134,78 @@ private void writeSemanticdb(TaskEvent event, Path output, Semanticdb.TextDocume
114134 }
115135 }
116136
137+ private void appendSemanticdb (
138+ TaskEvent event , Path output , Semanticdb .TextDocument textDocument ) {
139+ /*
140+ * If there already is a semanticdb file at the given path,
141+ * we do the following:
142+ * - Read a documents collection
143+ * - Try to find the document with the matching relative path (matching the incoming textDocument)
144+ * - Then, depending on whether a matching document already exists in the collection:
145+ * - if YES, mutate it in place to only add entries from the incoming document
146+ * - if NO, simply add the incoming text document to the collection
147+ * - Write the collection back to disk
148+ * */
149+ Semanticdb .TextDocument document = null ;
150+ int documentIndex = -1 ;
151+ Semanticdb .TextDocuments documents = null ;
152+
153+ try (InputStream is = Files .newInputStream (output .toFile ().toPath ())) {
154+ documents = Semanticdb .TextDocuments .parseFrom (is );
155+
156+ for (int i = 0 ; i < documents .getDocumentsCount (); i ++) {
157+ Semanticdb .TextDocument candidate = documents .getDocuments (i );
158+ if (document == null && candidate .getUri ().equals (textDocument .getUri ())) {
159+ document = candidate ;
160+ documentIndex = i ;
161+ }
162+ }
163+
164+ } catch (IOException e ) {
165+ this .reportException (e , event );
166+ return ;
167+ }
168+
169+ if (document != null ) {
170+ // If there is a previous semanticdb document at this path, we need
171+ // to deduplicate symbols and occurrences and mutate the document in place
172+ Set <Semanticdb .SymbolInformation > symbols = new HashSet <>(textDocument .getSymbolsList ());
173+ Set <Semanticdb .SymbolOccurrence > occurrences =
174+ new HashSet <>(textDocument .getOccurrencesList ());
175+ Set <Semanticdb .Synthetic > synthetics = new HashSet <>(textDocument .getSyntheticsList ());
176+
177+ symbols .addAll (document .getSymbolsList ());
178+ occurrences .addAll (document .getOccurrencesList ());
179+ synthetics .addAll (document .getSyntheticsList ());
180+
181+ documents
182+ .toBuilder ()
183+ .addDocuments (
184+ documentIndex ,
185+ document
186+ .toBuilder ()
187+ .clearOccurrences ()
188+ .addAllOccurrences (occurrences )
189+ .clearSymbols ()
190+ .addAllSymbols (symbols )
191+ .clearSynthetics ()
192+ .addAllSynthetics (synthetics ));
193+
194+ } else {
195+ // If no prior document was found, we can just add the incoming one to the collection
196+ documents = documents .toBuilder ().addDocuments (textDocument ).build ();
197+ }
198+
199+ byte [] bytes = documents .toByteArray ();
200+
201+ try {
202+ Files .createDirectories (output .getParent ());
203+ Files .write (output , bytes );
204+ } catch (IOException e ) {
205+ this .reportException (e , event );
206+ }
207+ }
208+
117209 public static Path absolutePathFromUri (SemanticdbJavacOptions options , JavaFileObject file ) {
118210 URI uri = file .toUri ();
119211 if ((options .uriScheme == UriScheme .SBT || options .uriScheme == UriScheme .ZINC )
@@ -210,10 +302,13 @@ private Result<Path, String> semanticdbOutputPath(SemanticdbJavacOptions options
210302
211303 switch (options .noRelativePath ) {
212304 case INDEX_ANYWAY :
213- // Come up with a unique relative path for this file even if it's not under the sourceroot.
214- // By indexing auto-generated files, we collect SymbolInformation for auto-generated symbol,
305+ // Come up with a unique relative path for this file even if it's not under the
306+ // sourceroot.
307+ // By indexing auto-generated files, we collect SymbolInformation for
308+ // auto-generated symbol,
215309 // which results in more useful hover tooltips in the editor.
216- // In the future, we may want to additionally embed the full text contents of these files
310+ // In the future, we may want to additionally embed the full text contents of
311+ // these files
217312 // so that it's possible to browse generated files with precise code navigation.
218313 String uniqueFilename =
219314 String .format ("%d.%s.semanticdb" , ++noRelativePathCounter , absolutePath .getFileName ());
0 commit comments