diff options
Diffstat (limited to 'src/main/java/org/javacs/CompileBatch.java')
-rw-r--r-- | src/main/java/org/javacs/CompileBatch.java | 166 |
1 files changed, 162 insertions, 4 deletions
diff --git a/src/main/java/org/javacs/CompileBatch.java b/src/main/java/org/javacs/CompileBatch.java index ddfb505..f12c486 100644 --- a/src/main/java/org/javacs/CompileBatch.java +++ b/src/main/java/org/javacs/CompileBatch.java @@ -88,12 +88,10 @@ public class CompileBatch implements AutoCloseable { sources); } - public Optional<Element> element(URI uri, int line, int character) { + public CompilationUnitTree root(URI uri) { for (var root : roots) { if (root.getSourceFile().toUri().equals(uri)) { - var path = CompileFocus.findPath(borrow.task, root, line, character); - var el = trees.getElement(path); - return Optional.ofNullable(el); + return root; } } // Somehow, uri was not in batch @@ -104,6 +102,21 @@ public class CompileBatch implements AutoCloseable { throw new RuntimeException("File " + uri + " isn't in batch " + names); } + private String contents(CompilationUnitTree root) { + try { + return root.getSourceFile().getCharContent(true).toString(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public Optional<Element> element(URI uri, int line, int character) { + var root = root(uri); + var path = CompileFocus.findPath(borrow.task, root, line, character); + var el = trees.getElement(path); + return Optional.ofNullable(el); + } + public Optional<List<TreePath>> definitions(Element el) { LOG.info(String.format("Search for definitions of `%s` in %d files...", el, roots.size())); @@ -223,5 +236,150 @@ public class CompileBatch implements AutoCloseable { return ParseFile.range(borrow.task, contents, path); } + public SourcePositions sourcePositions() { + return trees.getSourcePositions(); + } + + public LineMap lineMap(URI uri) { + return root(uri).getLineMap(); + } + + public List<? extends ImportTree> imports(URI uri) { + return root(uri).getImports(); + } + + private List<Element> overrides(ExecutableElement method) { + var elements = borrow.task.getElements(); + var types = borrow.task.getTypes(); + var results = new ArrayList<Element>(); + var enclosingClass = (TypeElement) method.getEnclosingElement(); + var enclosingType = enclosingClass.asType(); + for (var superClass : types.directSupertypes(enclosingType)) { + var e = (TypeElement) types.asElement(superClass); + for (var other : e.getEnclosedElements()) { + if (!(other instanceof ExecutableElement)) continue; + if (elements.overrides(method, (ExecutableElement) other, enclosingClass)) { + results.add(other); + } + } + } + return results; + } + + private boolean hasOverrideAnnotation(ExecutableElement method) { + for (var ann : method.getAnnotationMirrors()) { + var type = ann.getAnnotationType(); + var el = type.asElement(); + var name = el.toString(); + if (name.equals("java.lang.Override")) { + return true; + } + } + return false; + } + + /** Find methods that override a method from a superclass but don't have an @Override annotation. */ + public List<TreePath> needsOverrideAnnotation(URI uri) { + LOG.info(String.format("Looking for methods that need an @Override annotation in %s ...", uri.getPath())); + + var root = root(uri); + var results = new ArrayList<TreePath>(); + class FindMissingOverride extends TreePathScanner<Void, Void> { + @Override + public Void visitMethod(MethodTree t, Void __) { + var method = (ExecutableElement) trees.getElement(getCurrentPath()); + var supers = overrides(method); + if (!supers.isEmpty() && !hasOverrideAnnotation(method)) { + var overridesMethod = supers.get(0); + var overridesClass = overridesMethod.getEnclosingElement(); + LOG.info( + String.format( + "...`%s` has no @Override annotation but overrides `%s.%s`", + method, overridesClass, overridesMethod)); + results.add(getCurrentPath()); + } + return super.visitMethod(t, null); + } + } + new FindMissingOverride().scan(root, null); + return results; + } + + /** + * Figure out what imports this file should have. Star-imports like `import java.util.*` are converted to individual + * class imports. Missing imports are inferred by looking at imports in other source files. + */ + public List<String> fixImports(URI uri) { + var root = root(uri); + var contents = contents(root); + // Check diagnostics for missing imports + var unresolved = new HashSet<String>(); + for (var d : parent.diags) { + if (d.getCode().equals("compiler.err.cant.resolve.location") && d.getSource().toUri().equals(uri)) { + long start = d.getStartPosition(), end = d.getEndPosition(); + var id = contents.substring((int) start, (int) end); + if (id.matches("[A-Z]\\w+")) { + unresolved.add(id); + } else LOG.warning(id + " doesn't look like a class"); + } else if (d.getMessage(null).contains("cannot find to")) { + var lines = d.getMessage(null).split("\n"); + var firstLine = lines.length > 0 ? lines[0] : ""; + LOG.warning(String.format("%s %s doesn't look like to-not-found", d.getCode(), firstLine)); + } + } + // Look at imports in other classes to help us guess how to fix imports + // TODO cache parsed imports on a per-file basis + var sourcePathImports = Parser.existingImports(FileStore.all()); + var classes = new HashSet<String>(); + classes.addAll(parent.jdkClasses); + classes.addAll(parent.classPathClasses); + var fixes = Parser.resolveSymbols(unresolved, sourcePathImports, classes); + // Figure out which existing imports are actually used + var trees = Trees.instance(borrow.task); + var references = new HashSet<String>(); + class FindUsedImports extends TreePathScanner<Void, Void> { + @Override + public Void visitIdentifier(IdentifierTree node, Void nothing) { + var e = trees.getElement(getCurrentPath()); + if (e instanceof TypeElement) { + var t = (TypeElement) e; + var qualifiedName = t.getQualifiedName().toString(); + var lastDot = qualifiedName.lastIndexOf('.'); + var packageName = lastDot == -1 ? "" : qualifiedName.substring(0, lastDot); + var thisPackage = Objects.toString(root.getPackageName(), ""); + // java.lang.* and current package are imported by default + if (!packageName.equals("java.lang") + && !packageName.equals(thisPackage) + && !packageName.equals("")) { + references.add(qualifiedName); + } + } + return null; + } + } + new FindUsedImports().scan(root, null); + // Take the intersection of existing imports ^ existing identifiers + var qualifiedNames = new HashSet<String>(); + for (var i : root.getImports()) { + var imported = i.getQualifiedIdentifier().toString(); + if (imported.endsWith(".*")) { + var packageName = Parser.mostName(imported); + var isUsed = references.stream().anyMatch(r -> r.startsWith(packageName)); + if (isUsed) qualifiedNames.add(imported); + else LOG.warning("There are no references to package " + imported); + } else { + if (references.contains(imported)) qualifiedNames.add(imported); + else LOG.warning("There are no references to class " + imported); + } + } + // Add qualified names from fixes + qualifiedNames.addAll(fixes.values()); + // Sort in alphabetical order + var sorted = new ArrayList<String>(); + sorted.addAll(qualifiedNames); + Collections.sort(sorted); + return sorted; + } + private static final Logger LOG = Logger.getLogger("main"); } |