Skip to content

Commit 86815e2

Browse files
Fixup: lsp: Override virtual file system
- VadlTextDocumentService: - Use Frontend.compileToAst() - Report if there are errors in imported files - Publish diagnostics for dependent files in parallel - Add DependencyMap Test
1 parent 504c6f9 commit 86815e2

File tree

4 files changed

+327
-87
lines changed

4 files changed

+327
-87
lines changed

vadl-lsp/build.gradle.kts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,13 @@
11
dependencies {
22
implementation(project(":vadl"))
33
implementation("org.eclipse.lsp4j:org.eclipse.lsp4j:0.24.0")
4-
}
4+
5+
testImplementation(platform("org.junit:junit-bom:5.11.4"))
6+
testImplementation("org.junit.jupiter:junit-jupiter")
7+
testImplementation("org.assertj:assertj-core:3.26.3")
8+
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
9+
}
10+
11+
tasks.withType<Test> {
12+
useJUnitPlatform {}
13+
}

vadl-lsp/main/vadl/lsp/DependencyMap.java

Lines changed: 15 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -48,35 +48,30 @@ public class DependencyMap<T> {
4848
* an item never depends on itself.
4949
*/
5050
public synchronized void setDependencies(T item, Set<T> newDependencies) {
51-
Set<T> itemSet = dependencies.get(item);
52-
if (itemSet == null) {
53-
itemSet = new HashSet<>();
54-
dependencies.put(item, itemSet);
51+
Set<T> itemSet = dependencies.computeIfAbsent(item, k -> new HashSet<>());
5552

56-
} else {
57-
// Remove all outdated dependencies
58-
var iter = itemSet.iterator();
59-
while (iter.hasNext()) {
60-
T d = iter.next();
61-
if (!newDependencies.contains(d)) {
62-
iter.remove();
63-
Set<T> ds = dependents.get(d);
64-
if (ds != null) {
65-
ds.remove(item);
66-
}
53+
// Remove all outdated dependencies
54+
var iter = itemSet.iterator();
55+
while (iter.hasNext()) {
56+
T dependency = iter.next();
57+
if (!newDependencies.contains(dependency)) {
58+
iter.remove();
59+
Set<T> dependencySet = dependents.get(dependency);
60+
if (dependencySet != null) {
61+
dependencySet.remove(item);
6762
}
6863
}
6964
}
7065

7166
// Add new dependencies
72-
for (T d : newDependencies) {
73-
if (item.equals(d)) {
67+
for (T dependency : newDependencies) {
68+
if (item.equals(dependency)) {
7469
// Ignore self-dependencies
7570
continue;
7671
}
77-
if (itemSet.add(d)) {
78-
Set<T> ds = dependents.computeIfAbsent(d, k -> new HashSet<>());
79-
ds.add(item);
72+
if (itemSet.add(dependency)) {
73+
Set<T> dependencySet = dependents.computeIfAbsent(dependency, k -> new HashSet<>());
74+
dependencySet.add(item);
8075
}
8176
}
8277
}

vadl-lsp/main/vadl/lsp/VadlTextDocumentService.java

Lines changed: 101 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package vadl.lsp;
1818

19+
import java.io.IOException;
1920
import java.nio.file.Path;
2021
import java.util.ArrayList;
2122
import java.util.HashMap;
@@ -31,6 +32,7 @@
3132
import org.eclipse.lsp4j.DidCloseTextDocumentParams;
3233
import org.eclipse.lsp4j.DidOpenTextDocumentParams;
3334
import org.eclipse.lsp4j.DidSaveTextDocumentParams;
35+
import org.eclipse.lsp4j.Position;
3436
import org.eclipse.lsp4j.PublishDiagnosticsParams;
3537
import org.eclipse.lsp4j.Range;
3638
import org.eclipse.lsp4j.SemanticTokens;
@@ -41,12 +43,8 @@
4143
import org.eclipse.lsp4j.services.TextDocumentService;
4244
import org.slf4j.Logger;
4345
import org.slf4j.LoggerFactory;
44-
import vadl.ast.Ast;
46+
import vadl.ast.Frontend;
4547
import vadl.ast.LspTokenizer;
46-
import vadl.ast.ModelRemover;
47-
import vadl.ast.TypeChecker;
48-
import vadl.ast.Ungrouper;
49-
import vadl.ast.VadlParser;
5048
import vadl.error.Diagnostic.MsgType;
5149
import vadl.error.DiagnosticList;
5250
import vadl.utils.DiskVirtualFileSystem;
@@ -190,71 +188,108 @@ private void publishDiagnostics(Document document) {
190188
}
191189

192190
private void publishDiagnosticsForOneDocumentSnapshot(Document.Snapshot snapshot) {
193-
List<Diagnostic> lspItems = new ArrayList<>();
194-
Path path = snapshot.getPath();
195-
try {
196-
Ast ast = VadlParser.parse(snapshot.text(), snapshot.virtualFileSystem(), Map.of(), path);
197-
new Ungrouper().ungroup(ast);
198-
new ModelRemover().removeModels(ast);
199-
new TypeChecker().verify(ast);
200-
201-
} catch (DiagnosticList dl) {
202-
log.debug("Raw diagnostics ({}): {}", snapshot.uri(), dl.getMessage());
203-
for (vadl.error.Diagnostic item : dl.items) {
204-
// TODO Look into secondary locations too? Maybe as relatedInformation? Or to put a
205-
// diagnostic message there as well?
206-
SourceLocation location = item.multiLocation.primaryLocation().location();
207-
if (!Objects.equals(location.path(), path)) {
208-
// Ignore errors for other files
209-
// TODO this means that errors in included files are not reported unless that file is
210-
// opened in the client, even though the Parser gives us diagnostics for them
211-
continue;
212-
}
191+
var unused = server.executor().submit(() -> {
192+
List<Diagnostic> lspItems = new ArrayList<>();
193+
Path path = snapshot.getPath();
194+
try {
195+
Frontend.compileToAst(path, snapshot.virtualFileSystem());
213196

214-
Diagnostic lspItem = new Diagnostic();
215-
lspItem.setRange(new Range(
216-
snapshot.calculateUtf16Position(location.begin(), false),
217-
snapshot.calculateUtf16Position(location.end(), true)
218-
));
219-
lspItem.setSeverity(
220-
switch (item.level) {
221-
case ERROR -> DiagnosticSeverity.Error;
222-
case WARNING -> DiagnosticSeverity.Warning;
197+
} catch (IOException e) {
198+
log.error("Unexpected Exception occurred when parsing {}", snapshot.uri(), e);
199+
200+
} catch (DiagnosticList dl) {
201+
log.debug("Raw diagnostics ({}): {}", snapshot.uri(), dl.getMessage());
202+
List<String> importedFileErrors = new ArrayList<>();
203+
for (vadl.error.Diagnostic item : dl.items) {
204+
Path itemPath = item.multiLocation.primaryLocation().location().path();
205+
if (!Objects.equals(itemPath, path)) {
206+
if (itemPath == null) {
207+
continue;
223208
}
209+
// Error in imported file
210+
importedFileErrors.add(relativePath(itemPath, snapshot.getPath()));
211+
continue;
212+
}
213+
214+
lspItems.add(buildLspDiagnostic(item, snapshot));
215+
}
216+
217+
if (!importedFileErrors.isEmpty()) {
218+
// Putting one diagnostic at the top of the file, which points out which imported files
219+
// have errors
220+
Diagnostic importedFilesDiagnostic = new Diagnostic();
221+
importedFilesDiagnostic.setRange(new Range(new Position(0, 0),
222+
new Position(0, 0)));
223+
// TODO Consider using different severity if all diagnostics represented by this are only
224+
// Warnings
225+
importedFilesDiagnostic.setSeverity(DiagnosticSeverity.Error);
226+
227+
String message = importedFileErrors.size() == 1
228+
? "Errors in imported file: \n" + importedFileErrors.getFirst()
229+
: "Errors in imported files:\n- " + String.join("\n- ", importedFileErrors);
230+
importedFilesDiagnostic.setMessage(message);
231+
lspItems.addFirst(importedFilesDiagnostic);
232+
}
233+
}
234+
// TODO There may be diagnostics in DeferredDiagnosticStore, but that is a static list and
235+
// has no clear() method (i.e. outdated diagnostics remain visible)
236+
237+
if (!documentVersionIsCurrent(snapshot)) {
238+
log.debug(
239+
"ABORT publishDiagnostics (after): outdated version {} of document {}",
240+
snapshot.version(),
241+
snapshot.uri()
224242
);
225-
// labels (aka messages) per location
226-
String labelsString = item.multiLocation.primaryLocation().labels().stream()
227-
.map(vadl.error.Diagnostic.Message::content)
228-
.collect(Collectors.joining("\n"));
229-
// messages per Diagnostic - they may offer help or give additional notes
230-
String messagesString = item.messages.stream()
231-
.filter(m -> !m.type().equals(MsgType.PLAIN)
232-
|| !m.content().contains("parser got confused at this point"))
233-
.map(vadl.error.Diagnostic.Message::content)
234-
.collect(Collectors.joining("\n"));
235-
236-
String fullMessage = item.reason + (!labelsString.isBlank() ? "\n" + labelsString : "")
237-
+ (!messagesString.isBlank() ? "\n" + messagesString : "");
238-
lspItem.setMessage(fullMessage);
239-
lspItems.add(lspItem);
243+
return;
240244
}
241-
}
242-
// TODO There may be diagnostics in DeferredDiagnosticStore, but that is a static list and
243-
// has no clear() method (i.e. outdated diagnostics remain visible)
244-
245-
if (!documentVersionIsCurrent(snapshot)) {
246-
log.debug(
247-
"ABORT publishDiagnostics (after): outdated version {} of document {}",
248-
snapshot.version(),
249-
snapshot.uri()
250-
);
251-
return;
252-
}
253-
documentDependencies.setDependencies(snapshot.uri(),
254-
snapshot.virtualFileSystem().getReadFiles());
255-
var data = new PublishDiagnosticsParams(snapshot.uri(), lspItems, snapshot.version());
256-
log.debug("<< publishDiagnostics ({}: {}", snapshot.uri(), data);
257-
server.client().publishDiagnostics(data);
245+
documentDependencies.setDependencies(snapshot.uri(),
246+
snapshot.virtualFileSystem().getReadFiles());
247+
var data = new PublishDiagnosticsParams(snapshot.uri(), lspItems, snapshot.version());
248+
log.debug("<< publishDiagnostics ({}: {}", snapshot.uri(), data);
249+
server.client().publishDiagnostics(data);
250+
});
251+
}
252+
253+
private Diagnostic buildLspDiagnostic(vadl.error.Diagnostic vadlDiagnostic,
254+
Document.Snapshot snapshot) {
255+
// TODO Look into secondary locations too? Maybe as relatedInformation? Or to put a
256+
// diagnostic message there as well?
257+
SourceLocation location = vadlDiagnostic.multiLocation.primaryLocation().location();
258+
259+
Diagnostic lspDiagnostic = new Diagnostic();
260+
lspDiagnostic.setRange(new Range(
261+
snapshot.calculateUtf16Position(location.begin(), false),
262+
snapshot.calculateUtf16Position(location.end(), true)
263+
));
264+
lspDiagnostic.setSeverity(
265+
switch (vadlDiagnostic.level) {
266+
case ERROR -> DiagnosticSeverity.Error;
267+
case WARNING -> DiagnosticSeverity.Warning;
268+
}
269+
);
270+
// labels (aka messages) per location
271+
String labelsString = vadlDiagnostic.multiLocation.primaryLocation().labels().stream()
272+
.map(vadl.error.Diagnostic.Message::content)
273+
.collect(Collectors.joining("\n"));
274+
// messages per Diagnostic - they may offer help or give additional notes
275+
String messagesString = vadlDiagnostic.messages.stream()
276+
.filter(m -> !m.type().equals(MsgType.PLAIN)
277+
|| !m.content().contains("parser got confused at this point"))
278+
.map(vadl.error.Diagnostic.Message::content)
279+
.collect(Collectors.joining("\n"));
280+
281+
String fullMessage = vadlDiagnostic.reason
282+
+ (!labelsString.isBlank() ? "\n" + labelsString : "")
283+
+ (!messagesString.isBlank() ? "\n" + messagesString : "");
284+
lspDiagnostic.setMessage(fullMessage);
285+
286+
return lspDiagnostic;
287+
}
288+
289+
private String relativePath(Path path, Path relativeTo) {
290+
// relativeTo is a file, but we need its directory as base
291+
relativeTo = relativeTo.getParent() != null ? relativeTo.getParent() : relativeTo;
292+
return relativeTo.relativize(path).toString();
258293
}
259294

260295
private boolean documentVersionIsCurrent(Document.Snapshot snapshot) {

0 commit comments

Comments
 (0)