diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/main/java/org/javacs/Classes.java | 26 | ||||
-rw-r--r-- | src/main/java/org/javacs/JavaCompilerService.java | 47 | ||||
-rw-r--r-- | src/main/java/org/javacs/JavaLanguageServer.java | 15 | ||||
-rw-r--r-- | src/test/java/org/javacs/JavaCompilerServiceTest.java | 36 | ||||
-rw-r--r-- | src/test/test-project/simple/UnusedVar.java | 5 |
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 |