Skip to content
Open
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
f5c0f8c
add initial support for simplify hover information
aosen-xiong Sep 4, 2023
d3e8fc4
apply spotless
aosen-xiong Sep 4, 2023
b24905d
Merge branch 'master' into filterout-hover-info
aosen-xiong Oct 10, 2023
590f5de
add support for generics
aosen-xiong Oct 13, 2023
4c09a40
refactor code
aosen-xiong Oct 13, 2023
497c522
Merge branch 'master' into filterout-hover-info
aosen-xiong Oct 20, 2023
80e227e
temp for migrate to new laptop
aosen-xiong Nov 22, 2023
fcb8692
Merge branch 'master' into filterout-hover-info
wmdietl Nov 27, 2023
3c756c1
Fix formatting error
wmdietl Nov 27, 2023
6a618c3
Merge branch 'master' into filterout-hover-info
aosen-xiong Dec 5, 2023
81ba914
apply spotless
aosen-xiong Dec 5, 2023
45f7d64
Merge branch 'master' into filterout-hover-info
wmdietl Jan 4, 2024
f29a717
Merge branch 'master' into filterout-hover-info
aosen-xiong Feb 28, 2024
b184575
Undo unintentional change to build gradle
aosen-xiong Feb 28, 2024
451231e
Undo code action and leave to new PR
aosen-xiong Feb 28, 2024
5c9eeb3
Address naming comment
aosen-xiong Feb 28, 2024
d851dd7
Add javadoc
aosen-xiong Feb 28, 2024
c84507e
Refactor code and add comment
aosen-xiong Feb 28, 2024
6d52076
Applied suggestions from IDE
aosen-xiong Feb 28, 2024
7c33d4b
Remove no longer used method
aosen-xiong Feb 28, 2024
bfaa7de
Add space after :
aosen-xiong Mar 1, 2024
c4f0143
Merge branch 'master' into filterout-hover-info
wmdietl Mar 14, 2024
1a543dc
Merge branch 'master' into filterout-hover-info
wmdietl Jun 26, 2024
1dba908
Merge branch 'master' into filterout-hover-info
wmdietl Jan 2, 2025
ade6b64
Merge branch 'master' into filterout-hover-info
wmdietl May 16, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,13 @@ shadowJar {
}

tasks.compileJava {
options.compilerArgs += ['-Xlint', '-Werror']
// options.compilerArgs += ['-Xlint', '-Werror']
Comment thread
wmdietl marked this conversation as resolved.
Outdated
}

spotless {
java {
googleJavaFormat().aosp()
importOrder('com', 'jdk', 'lib', 'lombok', 'org', 'java', 'javax')
importOrder('com', 'jdk', 'lib', 'lomlspbok', 'org', 'java', 'javax')
Comment thread
aosen-xiong marked this conversation as resolved.
Outdated
formatAnnotations()
}
groovyGradle {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ public CompletableFuture<InitializeResult> initialize(InitializeParams params) {
ServerCapabilities capabilities = new ServerCapabilities();
capabilities.setTextDocumentSync(TextDocumentSyncKind.Full);
capabilities.setHoverProvider(Boolean.TRUE);
capabilities.setCodeActionProvider(Boolean.TRUE);
return CompletableFuture.completedFuture(new InitializeResult(capabilities));
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
package org.checkerframework.languageserver;

import com.google.common.base.Splitter;
import com.google.common.collect.RangeMap;
import com.google.common.collect.TreeRangeMap;

import org.checkerframework.javacutil.BugInCF;
import org.eclipse.lsp4j.CodeAction;
import org.eclipse.lsp4j.CodeActionKind;
import org.eclipse.lsp4j.CodeActionParams;
import org.eclipse.lsp4j.Command;
import org.eclipse.lsp4j.Diagnostic;
import org.eclipse.lsp4j.DiagnosticSeverity;
import org.eclipse.lsp4j.DidChangeTextDocumentParams;
Expand All @@ -17,10 +22,18 @@
import org.eclipse.lsp4j.Position;
import org.eclipse.lsp4j.PublishDiagnosticsParams;
import org.eclipse.lsp4j.Range;
import org.eclipse.lsp4j.TextEdit;
import org.eclipse.lsp4j.WorkspaceEdit;
import org.eclipse.lsp4j.jsonrpc.messages.Either;
import org.eclipse.lsp4j.services.TextDocumentService;

import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
Expand Down Expand Up @@ -184,20 +197,177 @@ public void didSave(DidSaveTextDocumentParams params) {
public void publish(Map<String, List<javax.tools.Diagnostic<?>>> result) {
for (Map.Entry<String, List<javax.tools.Diagnostic<?>>> entry : result.entrySet()) {
List<Diagnostic> diagnostics = new ArrayList<>();
Map<String, List<CheckerTypeKind>> uniqueTypeInfo =
processDiagnosticString(entry, diagnostics);
publishTypeMessageWithFilter(entry, uniqueTypeInfo);
server.publishDiagnostics(new PublishDiagnosticsParams(entry.getKey(), diagnostics));
}
}

/**
* Process Diagnostic strings to separate type message and error message.
*
* @param entry The Diagnostic entry for getting diagnostic message
* @param diagnostics A list for storing checkerframework issued errors
* @return TypeMessage processed for non-verbose output
*/
private Map<String, List<CheckerTypeKind>> processDiagnosticString(
Map.Entry<String, List<javax.tools.Diagnostic<?>>> entry,
List<Diagnostic> diagnostics) {
Map<String, List<CheckerTypeKind>> TypeMessage = new HashMap<>();
for (javax.tools.Diagnostic<?> diagnostic : entry.getValue()) {
String message = diagnostic.getMessage(Locale.getDefault());
if (message != null && message.contains("lsp.type.information")) {
String checker = getChecker(message);
String kind = getKind(message);
String type = getType(message);
String positionInfo = getPosition(message);
if (positionInfo != null) {
if (!TypeMessage.containsKey(positionInfo)) {
List<CheckerTypeKind> checkerTypeKinds = new ArrayList<>();
Map<String, String> kindType = new HashMap<>();
kindType.put(type, kind);
checkerTypeKinds.add(new CheckerTypeKind(checker, kindType));
TypeMessage.put(positionInfo, checkerTypeKinds);
} else {
List<CheckerTypeKind> checkerTypeKinds = TypeMessage.get(positionInfo);
boolean foundChecker = false;
for (CheckerTypeKind checkerTypeKind : checkerTypeKinds) {
if (checkerTypeKind.getCheckername().equals(checker)) {
foundChecker = true;
if (!checkerTypeKind.getTypeKind().containsKey(type)) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These methods have a very deep nesting level. Can you try to split off smaller helper methods? Or at least add some comments to make the flow understandable?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

already refactored and added comment.

checkerTypeKind.getTypeKind().put(type, kind);
}
break;
}
}
if (!foundChecker) {
Map<String, String> kindType = new HashMap<>();
kindType.put(type, kind);
checkerTypeKinds.add(new CheckerTypeKind(checker, kindType));
}
}
}
} else {
diagnostics.add(convertToLSPDiagnostic(diagnostic));
}
}
return TypeMessage;
}

for (javax.tools.Diagnostic<?> diagnostic : entry.getValue()) {
String message = diagnostic.getMessage(Locale.getDefault());
if (message != null && message.contains("lsp.type.information")) {
// this message is for lsp support
File file = new File(URI.create(entry.getKey()));
publishTypeMessage(file, message);
/**
* Publish the given type message for the given file in non-verbose mode.
*
* @param entry The Diagnostic entry for getting file name string
* @param diagnosticString processed for simplicity output
*/
private void publishTypeMessageWithFilter(
Map.Entry<String, List<javax.tools.Diagnostic<?>>> entry,
Map<String, List<CheckerTypeKind>> diagnosticString) {
for (Map.Entry<String, List<CheckerTypeKind>> typeMessage : diagnosticString.entrySet()) {
String positionInfo = typeMessage.getKey();
for (CheckerTypeKind checkerTypeKind : typeMessage.getValue()) {
if (checkerTypeKind.getTypeKind().entrySet().size() == 1) {
for (Map.Entry<String, String> typeKind :
checkerTypeKind.getTypeKind().entrySet()) {
StringBuilder typeMessageBuilder =
new StringBuilder(checkerTypeKind.getCheckername())
.append(": ")
.append(typeKind.getKey())
.append(positionInfo);
publishTypeMessage(
new File(URI.create(entry.getKey())),
typeMessageBuilder.toString());
}
} else {
diagnostics.add(convertToLSPDiagnostic(diagnostic));
for (Map.Entry<String, String> typeKind :
checkerTypeKind.getTypeKind().entrySet()) {
StringBuilder typeMessageBuilder =
new StringBuilder(checkerTypeKind.getCheckername())
.append(" ")
.append(typeKind.getValue())
.append(":")
.append(typeKind.getKey())
.append(positionInfo);
publishTypeMessage(
new File(URI.create(entry.getKey())),
typeMessageBuilder.toString());
}
}
}
}
}

server.publishDiagnostics(new PublishDiagnosticsParams(entry.getKey(), diagnostics));
/**
* Get checker name from type message in checkerframework.
*
* @param typeMessage Type message string from checkerframework
* @return Checker name from checkerframework e.g. Nullness
*/
private static String getChecker(String typeMessage) {
String checkerPrefix = "checker=";
int checkerStart = typeMessage.indexOf(checkerPrefix);
if (checkerStart != -1) {
int checkerEnd = typeMessage.indexOf(";", checkerStart);
if (checkerEnd != -1) {
String checker =
typeMessage
.substring(checkerStart + checkerPrefix.length(), checkerEnd)
.trim();
checker = checker.replace("Subchecker", "").replace("Checker", "").trim();
return checker;
}
}
return null;
}

/**
* Get message kind from type message in checkerframework.
*
* @param typeMessage Type message string from checkerframework
* @return Lower case message kind from checkerframework e.g. use/declared
*/
private static String getKind(String typeMessage) {
String kindPrefix = "kind=";
int kindStart = typeMessage.indexOf(kindPrefix);
if (kindStart != -1) {
int kindEnd = typeMessage.indexOf(";", kindStart);
if (kindEnd != -1) {
String kind =
typeMessage.substring(kindStart + kindPrefix.length(), kindEnd).trim();
kind = kind.toLowerCase(Locale.ROOT).replace("_type", "");
return kind;
}
}
return null;
}

/**
* Get type information from type message in checkerframework.
*
* @param typeMessage Type Message String from checkerframework
* @return Type information from checkerframework e.g. @Nullable Object
*/
private static String getType(String typeMessage) {
String typePrefix = "type=";
int typeStart = typeMessage.indexOf(typePrefix);
if (typeStart != -1) {
int typeEnd = typeMessage.indexOf(";", typeStart);
return typeMessage.substring(typeStart + typePrefix.length(), typeEnd).trim() + "; ";
}
return null;
}

/**
* Get position range from type message in checkerframework.
*
* @param typeMessage Type Message String from checkerframework
* @return Position range from checkerframework e.g. range=(11, 8, 11, 9)
*/
private static String getPosition(String typeMessage) {
int lastDelimiter = typeMessage.lastIndexOf(';');
String positionInfo = typeMessage.substring(lastDelimiter + 1).trim();
return positionInfo;
}

/**
Expand Down Expand Up @@ -266,4 +436,97 @@ private void publishTypeMessage(File file, String msg) {
currentTypeInfo.put(
com.google.common.collect.Range.closed(start, end), typeInfoForPosition);
}

@Override
public CompletableFuture<List<Either<Command, CodeAction>>> codeAction(
Comment thread
aosen-xiong marked this conversation as resolved.
Outdated
CodeActionParams params) {
List<Either<Command, CodeAction>> actions = new ArrayList<>();
for (Diagnostic diagnostic : params.getContext().getDiagnostics()) {
if (!diagnostic.getMessage().contains("lsp.type.information")) {
if (diagnostic.getMessage().contains("dereference.of.nullable")) {
CodeAction codeAction = new CodeAction("Add nullcheck");
codeAction.setKind(CodeActionKind.QuickFix);
String uri = params.getTextDocument().getUri();
Range range = params.getRange();
String str = getContentInRange(uri, range);
List<TextEdit> editList = new ArrayList<>();
TextEdit edit1 =
new TextEdit(
new Range(
new Position(
params.getRange().getStart().getLine(),
params.getRange().getStart().getCharacter()),
new Position(
params.getRange().getEnd().getLine(),
params.getRange().getEnd().getCharacter())),
" if ("
+ str
+ " != null) {\n"
+ " "
+ str
+ ".toString();\n");
TextEdit edit2 =
new TextEdit(
new Range(
new Position(
params.getRange().getStart().getLine() + 1,
params.getRange().getStart().getCharacter()),
new Position(
params.getRange().getEnd().getLine(),
params.getRange().getEnd().getCharacter())),
" } else {\n"
+ " //TODO: Implement if the variable is null\n"
+ " }\n"
+ " }");
editList.add(edit1);
editList.add(edit2);
codeAction.setEdit(new WorkspaceEdit(Collections.singletonMap(uri, editList)));
codeAction.setDiagnostics(Collections.singletonList(diagnostic));
actions.add(Either.forRight(codeAction));
}
} else {
break;
}
}
return CompletableFuture.completedFuture(actions);
}

private static String getContentInRange(String uri, Range range) {
try {
URI uriObject = new URI(uri);
String filePath = Paths.get(uriObject).toFile().getAbsolutePath();
String fileContent = new String(Files.readAllBytes(Paths.get(filePath)), StandardCharsets.UTF_8);
int startLine = range.getStart().getLine();
int startCharacter = range.getStart().getCharacter();
int endLine = range.getEnd().getLine();
int endCharacter = range.getEnd().getCharacter();

List<String> lines = Splitter.onPattern(System.lineSeparator()).splitToList(fileContent);

StringBuilder textInRange = new StringBuilder();
for (int i = startLine; i <= endLine; i++) {
String line = lines.get(i);
if (i == startLine) {
if (i == endLine) {
textInRange.append(line.substring(startCharacter, endCharacter));
} else {
textInRange.append(line.substring(startCharacter));
textInRange.append(System.lineSeparator());
}
} else if (i == endLine) {
textInRange.append(line.substring(0, endCharacter));
} else {
textInRange.append(line);
textInRange.append(System.lineSeparator());
}
}

return textInRange.toString();
} catch (IOException e) {
e.printStackTrace();
return "";
} catch (URISyntaxException e) {
throw new RuntimeException(e);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package org.checkerframework.languageserver;

import java.util.Map;

/** This class is for processing type message. */
Comment thread
aosen-xiong marked this conversation as resolved.
Outdated
public class CheckerTypeKind {
private String checkername;

private Map<String, String> TypeKind;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is this mapping from/to?
Field names should start lower case.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The map stores type information in the key of the map and type kind information e.g. used/declared in the value of the map.


CheckerTypeKind(String checkername, Map<String, String> kindType) {
this.checkername = checkername;
this.TypeKind = kindType;
}

String getCheckername() {
Comment thread
aosen-xiong marked this conversation as resolved.
Outdated
return this.checkername;
}

Map<String, String> getTypeKind() {
return this.TypeKind;
}
}