summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/main/java/org/javacs/Classes.java26
-rw-r--r--src/main/java/org/javacs/JavaCompilerService.java47
-rw-r--r--src/main/java/org/javacs/JavaLanguageServer.java15
-rw-r--r--src/test/java/org/javacs/JavaCompilerServiceTest.java36
-rw-r--r--src/test/test-project/simple/UnusedVar.java5
5 files changed, 96 insertions, 33 deletions
diff --git a/src/main/java/org/javacs/Classes.java b/src/main/java/org/javacs/Classes.java
index 6215759..bc6b321 100644
--- a/src/main/java/org/javacs/Classes.java
+++ b/src/main/java/org/javacs/Classes.java
@@ -124,17 +124,17 @@ class Classes {
var fs = FileSystems.getFileSystem(URI.create("jrt:/"));
for (var m : JDK_MODULES) {
var moduleRoot = fs.getPath(String.format("/modules/%s/", m));
- try {
- Files.walk(moduleRoot)
- .forEach(
- classFile -> {
- var relative = moduleRoot.relativize(classFile).toString();
- if (relative.endsWith(".class") && !relative.contains("$")) {
- var trim = relative.substring(0, relative.length() - ".class".length());
- var qualifiedName = trim.replace(File.separatorChar, '.');
- classes.add(qualifiedName);
- }
- });
+ try (var stream = Files.walk(moduleRoot)) {
+ var it = stream.iterator();
+ while (it.hasNext()) {
+ var classFile = it.next();
+ var relative = moduleRoot.relativize(classFile).toString();
+ if (relative.endsWith(".class") && !relative.contains("$")) {
+ var trim = relative.substring(0, relative.length() - ".class".length());
+ var qualifiedName = trim.replace(File.separatorChar, '.');
+ classes.add(qualifiedName);
+ }
+ }
} catch (IOException e) {
LOG.log(Level.WARNING, "Failed indexing module " + m + "(" + e.getMessage() + ")");
}
@@ -143,10 +143,12 @@ class Classes {
LOG.info(String.format("Found %d classes in the java platform", classes.size()));
class PlatformClassSource implements ClassSource {
+ @Override
public Set<String> classes() {
return Collections.unmodifiableSet(classes);
}
+ @Override
public Optional<Class<?>> load(String className) {
if (loadError.contains(className)) return Optional.empty();
@@ -186,10 +188,12 @@ class Classes {
LOG.info(String.format("Found %d classes in classpath", classes.size()));
class ClassPathClassSource implements ClassSource {
+ @Override
public Set<String> classes() {
return Collections.unmodifiableSet(classes);
}
+ @Override
public Optional<Class<?>> load(String className) {
if (loadError.contains(className)) return Optional.empty();
diff --git a/src/main/java/org/javacs/JavaCompilerService.java b/src/main/java/org/javacs/JavaCompilerService.java
index 8691dc4..e3ded39 100644
--- a/src/main/java/org/javacs/JavaCompilerService.java
+++ b/src/main/java/org/javacs/JavaCompilerService.java
@@ -154,11 +154,11 @@ public class JavaCompilerService {
}
public List<Diagnostic<? extends JavaFileObject>> reportErrors(Collection<URI> uris) {
+ LOG.info(String.format("Report errors in %d files...", uris.size()));
+
var options = options(sourcePath, classPath);
// Add error-prone
- Collections.addAll(options, "-XDcompilePolicy=byfile");
- Collections.addAll(options, "-processorpath", Lib.ERROR_PRONE);
- Collections.addAll(options, "-Xplugin:ErrorProne -XepAllErrorsAsWarnings --illegal-access=warn");
+ options.addAll(errorProneOptions());
// Construct list of sources
var files = new ArrayList<File>();
for (var p : uris) files.add(new File(p));
@@ -170,6 +170,7 @@ public class JavaCompilerService {
var profiler = new Profiler();
task.addTaskListener(profiler);
// Run compilation
+ diags.clear();
try {
task.analyze();
} catch (IOException e) {
@@ -177,9 +178,49 @@ public class JavaCompilerService {
}
profiler.print();
+ LOG.info(String.format("...found %d errors", diags.size()));
+
return Collections.unmodifiableList(new ArrayList<>(diags));
}
+ // TODO make bug patterns configurable
+ // TODO error prone has a "suggest fix" option, make that available as a code action
+ // TODO benchmark speed effect of error-prone
+ private static List<String> errorProneOptions() {
+ var options = new ArrayList<String>();
+
+ // https://errorprone.info/docs/installation "Command Line"
+ Collections.addAll(options, "-XDcompilePolicy=simple");
+ Collections.addAll(options, "-processorpath", Lib.ERROR_PRONE);
+
+ // https://errorprone.info/bugpatterns
+ var bugPatterns = new StringJoiner(" ");
+ // Experimental errors
+ bugPatterns.add("-Xep:EmptyIf");
+ bugPatterns.add("-Xep:NumericEquality");
+ // Experimintal warnings
+ bugPatterns.add("-Xep:ConstructorLeaksThis");
+ bugPatterns.add("-Xep:EqualsBrokenForNull");
+ bugPatterns.add("-Xep:InvalidThrows");
+ bugPatterns.add("-Xep:RedundantThrows");
+ bugPatterns.add("-Xep:StaticQualifiedUsingExpression");
+ bugPatterns.add("-Xep:StringEquality");
+ bugPatterns.add("-Xep:Unused");
+ bugPatterns.add("-Xep:UnusedException");
+ // Experimental suggestions
+ bugPatterns.add("-Xep:FieldCanBeFinal");
+ bugPatterns.add("-Xep:FieldMissingNullable");
+ bugPatterns.add("-Xep:MethodCanBeStatic");
+ bugPatterns.add("-Xep:PackageLocation");
+ bugPatterns.add("-Xep:PrivateConstructorForUtilityClass");
+ bugPatterns.add("-Xep:ReturnMissingNullable");
+
+ Collections.addAll(
+ options, "-Xplugin:ErrorProne -XepAllErrorsAsWarnings " + bugPatterns + " --illegal-access=warn");
+
+ return options;
+ }
+
static boolean containsImport(String toPackage, String toClass, Path file) {
if (toPackage.isEmpty()) return true;
var samePackage = Pattern.compile("^package +" + toPackage + ";");
diff --git a/src/main/java/org/javacs/JavaLanguageServer.java b/src/main/java/org/javacs/JavaLanguageServer.java
index 647f8b4..c9ebb30 100644
--- a/src/main/java/org/javacs/JavaLanguageServer.java
+++ b/src/main/java/org/javacs/JavaLanguageServer.java
@@ -124,14 +124,7 @@ class JavaLanguageServer extends LanguageServer {
}
}
- void lint(Collection<URI> uris) {
- var message = new StringJoiner(", ");
- for (var uri : uris) {
- var path = uri.getPath();
- var name = Paths.get(path).getFileName();
- message.add(name.toString());
- }
- LOG.info("Lint " + message);
+ void reportErrors(Collection<URI> uris) {
var messages = compiler.reportErrors(uris);
publishDiagnostics(uris, messages);
}
@@ -1081,7 +1074,7 @@ class JavaLanguageServer extends LanguageServer {
if (needsUpdate.isEmpty()) return;
// If there's more than 1 file, report progress
- if (needsUpdate.size() > 1) { // TODO this could probably be tuned to be higher
+ if (needsUpdate.size() > 1) { // TODO this could probably be tuned to be higher and based on bytes of code
progress.start(String.format("Index %d files", needsUpdate.size()));
} else {
progress = ReportProgress.EMPTY;
@@ -1162,7 +1155,7 @@ class JavaLanguageServer extends LanguageServer {
public CodeLens resolveCodeLens(CodeLens unresolved) {
// TODO This is pretty klugey, should happen asynchronously after CodeLenses are shown
if (!recentlyOpened.isEmpty()) {
- lint(recentlyOpened);
+ reportErrors(recentlyOpened);
recentlyOpened.clear();
}
// Unpack data
@@ -1448,7 +1441,7 @@ class JavaLanguageServer extends LanguageServer {
var uri = params.textDocument.uri;
if (isJavaFile(uri)) {
// Re-lint all active documents
- lint(activeDocuments.keySet());
+ reportErrors(activeDocuments.keySet());
}
}
diff --git a/src/test/java/org/javacs/JavaCompilerServiceTest.java b/src/test/java/org/javacs/JavaCompilerServiceTest.java
index 82fe157..e06e72a 100644
--- a/src/test/java/org/javacs/JavaCompilerServiceTest.java
+++ b/src/test/java/org/javacs/JavaCompilerServiceTest.java
@@ -16,6 +16,8 @@ import java.util.StringJoiner;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import javax.lang.model.element.Element;
+import javax.tools.Diagnostic;
+import javax.tools.JavaFileObject;
import org.junit.Test;
public class JavaCompilerServiceTest {
@@ -285,19 +287,37 @@ public class JavaCompilerServiceTest {
assertThat(diags, not(empty()));
}
- @Test
- public void errorProne() {
- var uri = resourceUri("ErrorProne.java");
- var files = Collections.singleton(uri);
- var diags = compiler.reportErrors(files);
+ private static List<String> errorStrings(List<Diagnostic<? extends JavaFileObject>> diags) {
+ var strings = new ArrayList<String>();
for (var d : diags) {
- var file = d.getSource().toUri().getPath();
+ var file = Parser.fileName(d.getSource().toUri());
var line = d.getLineNumber();
var kind = d.getKind();
var msg = d.getMessage(null);
- System.out.println(String.format("%s(%d)\t%s\t%s", file, line, kind, msg));
+ var string = String.format("%s(%d): %s", file, line, msg);
+ strings.add(string);
}
- assertThat(diags, not(empty()));
+ return strings;
+ }
+
+ @Test
+ public void errorProne() {
+ // TODO verify that error-prone *only* runs when you call reportErrors(),
+ // by calling compileFile() and checking no diagnostic is reported
+ var uri = resourceUri("ErrorProne.java");
+ var files = Collections.singleton(uri);
+ var diags = compiler.reportErrors(files);
+ var strings = errorStrings(diags);
+ assertThat(strings, hasItem(containsString("ErrorProne.java(7): [CollectionIncompatibleType]")));
+ }
+
+ @Test
+ public void unusedVar() {
+ var uri = resourceUri("UnusedVar.java");
+ var files = Collections.singleton(uri);
+ var diags = compiler.reportErrors(files);
+ var strings = errorStrings(diags);
+ assertThat(strings, hasItem(containsString("UnusedVar.java(3): [Unused]")));
}
@Test
diff --git a/src/test/test-project/simple/UnusedVar.java b/src/test/test-project/simple/UnusedVar.java
new file mode 100644
index 0000000..e6f3315
--- /dev/null
+++ b/src/test/test-project/simple/UnusedVar.java
@@ -0,0 +1,5 @@
+class UnusedVar {
+ void main() {
+ var x = 1;
+ }
+} \ No newline at end of file