summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGeorge Fraser <george@fivetran.com>2018-06-30 20:36:34 -0700
committerGeorge Fraser <george@fivetran.com>2018-06-30 20:36:34 -0700
commit9fab6a5321a4c6d886822e5229f6f912472db7a1 (patch)
tree61ab1de59152034d29f3abc06c6d74dea7389592
parent18d94f9e96bb91130992367ff40b90d8fd9e657d (diff)
downloadjava-language-server-9fab6a5321a4c6d886822e5229f6f912472db7a1.zip
Use parsing, not Doclet API
-rw-r--r--TODOS.md1
-rw-r--r--javaLsFlag.txt1
-rw-r--r--lib/extension.ts4
-rw-r--r--lib/src.zipbin0 -> 61243737 bytes
-rw-r--r--src/main/java/org/javacs/Classes.java2
-rw-r--r--src/main/java/org/javacs/Docs.java170
-rw-r--r--src/main/java/org/javacs/DocsIndexer.java109
-rw-r--r--src/main/java/org/javacs/JavaCompilerService.java36
-rw-r--r--src/main/java/org/javacs/JavaTextDocumentService.java106
-rw-r--r--src/main/java/org/javacs/Javadocs.java294
-rw-r--r--src/main/java/org/javacs/Lib.java15
-rw-r--r--src/main/java/org/javacs/Parser.java4
-rw-r--r--src/test/java/org/javacs/DocsTest.java13
-rw-r--r--src/test/java/org/javacs/JavaCompilerServiceTest.java6
-rw-r--r--src/test/java/org/javacs/JavadocsTest.java74
-rw-r--r--src/test/resources/LocalMethodDoc.java7
16 files changed, 288 insertions, 554 deletions
diff --git a/TODOS.md b/TODOS.md
index 7e2ab96..cf1832a 100644
--- a/TODOS.md
+++ b/TODOS.md
@@ -75,6 +75,7 @@
* Cast to type
* Import missing file
* Unused return value auto-add
+* Format-on-save should add missing method declarations
### Code lens
* "N references" on method, class
diff --git a/javaLsFlag.txt b/javaLsFlag.txt
new file mode 100644
index 0000000..4babb58
--- /dev/null
+++ b/javaLsFlag.txt
@@ -0,0 +1 @@
+This file is a flag to help Lib#installRoot find the root directory of the extension. \ No newline at end of file
diff --git a/lib/extension.ts b/lib/extension.ts
index 706e440..14a422f 100644
--- a/lib/extension.ts
+++ b/lib/extension.ts
@@ -51,11 +51,9 @@ export function activate(context: VSCode.ExtensionContext) {
let serverOptions: ServerOptions = {
command: javaExecutablePath,
args: args,
- options: { cwd: VSCode.workspace.rootPath }
+ options: { cwd: context.extensionPath }
}
- console.log(javaExecutablePath + ' ' + args.join(' '));
-
// Copied from typescript
VSCode.languages.setLanguageConfiguration('java', {
indentationRules: {
diff --git a/lib/src.zip b/lib/src.zip
new file mode 100644
index 0000000..d08e5b4
--- /dev/null
+++ b/lib/src.zip
Binary files differ
diff --git a/src/main/java/org/javacs/Classes.java b/src/main/java/org/javacs/Classes.java
index e3c2201..72a0267 100644
--- a/src/main/java/org/javacs/Classes.java
+++ b/src/main/java/org/javacs/Classes.java
@@ -20,7 +20,7 @@ import java.util.stream.Collectors;
class Classes {
/** All exported modules in the JDK */
- private static String[] JDK_MODULES = {
+ static String[] JDK_MODULES = {
"java.activation",
"java.base",
"java.compiler",
diff --git a/src/main/java/org/javacs/Docs.java b/src/main/java/org/javacs/Docs.java
index 3786349..659b785 100644
--- a/src/main/java/org/javacs/Docs.java
+++ b/src/main/java/org/javacs/Docs.java
@@ -1,10 +1,18 @@
package org.javacs;
+import com.overzealous.remark.Options;
+import com.overzealous.remark.Remark;
import com.sun.source.doctree.DocCommentTree;
+import com.sun.source.tree.*;
+import com.sun.source.util.*;
import java.io.*;
import java.nio.file.*;
import java.util.*;
+import java.util.function.*;
import java.util.logging.Logger;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.*;
import javax.tools.*;
class Docs {
@@ -12,36 +20,178 @@ class Docs {
/** File manager with source-path + platform sources, which we will use to look up individual source files */
private final StandardJavaFileManager fileManager;
+ private static Path srcZip() {
+ try {
+ var fs = FileSystems.newFileSystem(Lib.SRC_ZIP, Docs.class.getClassLoader());
+ return fs.getPath("/");
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
Docs(Set<Path> sourcePath) {
this.fileManager =
ServiceLoader.load(JavaCompiler.class).iterator().next().getStandardFileManager(__ -> {}, null, null);
// Compute the full source path, including src.zip for platform classes
- var allSourcePaths = new HashSet<File>();
- for (var p : sourcePath) allSourcePaths.add(p.toFile());
- // TODO src.zip
+ var sourcePathFiles = sourcePath.stream().map(Path::toFile).collect(Collectors.toSet());
+
+ try {
+ fileManager.setLocation(StandardLocation.SOURCE_PATH, sourcePathFiles);
+ fileManager.setLocationFromPaths(StandardLocation.MODULE_SOURCE_PATH, Set.of(srcZip()));
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ private Optional<JavaFileObject> file(String className) {
try {
- fileManager.setLocation(StandardLocation.SOURCE_PATH, allSourcePaths);
+ var fromSourcePath =
+ fileManager.getJavaFileForInput(
+ StandardLocation.SOURCE_PATH, className, JavaFileObject.Kind.SOURCE);
+ if (fromSourcePath != null) return Optional.of(fromSourcePath);
+ for (var module : Classes.JDK_MODULES) {
+ var moduleLocation = fileManager.getLocationForModule(StandardLocation.MODULE_SOURCE_PATH, module);
+ if (moduleLocation == null) continue;
+ var fromModuleSourcePath =
+ fileManager.getJavaFileForInput(moduleLocation, className, JavaFileObject.Kind.SOURCE);
+ if (fromModuleSourcePath != null) return Optional.of(fromModuleSourcePath);
+ }
+ return Optional.empty();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
- private JavaFileObject file(String className) {
+ private boolean memberNameEquals(Tree member, String name) {
+ if (member instanceof VariableTree) {
+ var variable = (VariableTree) member;
+ return variable.getName().contentEquals(name);
+ } else if (member instanceof MethodTree) {
+ var method = (MethodTree) member;
+ return method.getName().contentEquals(name);
+ } else return false;
+ }
+
+ private Optional<DocCommentTree> findDoc(String className, String memberName) {
+ var file = file(className);
+ if (!file.isPresent()) return Optional.empty();
+ var task = Parser.parseTask(file.get());
+ CompilationUnitTree root;
try {
- return fileManager.getJavaFileForInput(StandardLocation.SOURCE_PATH, className, JavaFileObject.Kind.SOURCE);
+ var it = task.parse().iterator();
+ if (!it.hasNext()) {
+ LOG.warning("Found no CompilationUnitTree in " + file);
+ return Optional.empty();
+ }
+ root = it.next();
} catch (IOException e) {
throw new RuntimeException(e);
}
+ var docs = DocTrees.instance(task);
+ var trees = Trees.instance(task);
+ class Find extends TreeScanner<Void, Void> {
+ Optional<DocCommentTree> result = Optional.empty();
+
+ @Override
+ public Void visitClass(ClassTree node, Void aVoid) {
+ // TODO this will be wrong when inner class has same name as top-level class
+ if (node.getSimpleName().contentEquals(Parser.lastName(className))) {
+ if (memberName == null) {
+ var path = trees.getPath(root, node);
+ result = Optional.ofNullable(docs.getDocCommentTree(path));
+ } else {
+ for (var member : node.getMembers()) {
+ if (memberNameEquals(member, memberName)) {
+ var path = trees.getPath(root, member);
+ result = Optional.ofNullable(docs.getDocCommentTree(path));
+ }
+ }
+ }
+ }
+ return null;
+ }
+ }
+ var find = new Find();
+ find.scan(root, null);
+ return find.result;
}
- DocCommentTree memberDoc(String className, String memberName) {
- return DocsIndexer.memberDoc(className, memberName, file(className));
+ Optional<DocCommentTree> memberDoc(String className, String memberName) {
+ Objects.requireNonNull(className);
+ Objects.requireNonNull(memberName);
+
+ return findDoc(className, memberName);
}
- DocCommentTree classDoc(String className) {
- return DocsIndexer.classDoc(className, file(className));
+ Optional<DocCommentTree> classDoc(String className) {
+ Objects.requireNonNull(className);
+
+ return findDoc(className, null);
+ }
+
+ Optional<MethodTree> findMethod(String className, String methodName) {
+ Objects.requireNonNull(className);
+ Objects.requireNonNull(methodName);
+
+ var file = file(className);
+ if (!file.isPresent()) return Optional.empty();
+ var task = Parser.parseTask(file.get());
+ CompilationUnitTree root;
+ try {
+ root = task.parse().iterator().next();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ class Find extends TreeScanner<Void, Void> {
+ Optional<MethodTree> result = Optional.empty();
+
+ @Override
+ public Void visitClass(ClassTree node, Void aVoid) {
+ // TODO this will be wrong when inner class has same name as top-level class
+ if (node.getSimpleName().contentEquals(Parser.lastName(className))) {
+ for (var member : node.getMembers()) {
+ if (member instanceof MethodTree) {
+ var method = (MethodTree) member;
+ if (method.getName().contentEquals(methodName)) result = Optional.of(method);
+ }
+ }
+ }
+ return null;
+ }
+ }
+ var find = new Find();
+ find.scan(root, null);
+ return find.result;
+ }
+
+ private static final Pattern HTML_TAG = Pattern.compile("<(\\w+)>");
+
+ private static boolean isHtml(String text) {
+ Matcher tags = HTML_TAG.matcher(text);
+ while (tags.find()) {
+ String tag = tags.group(1);
+ String close = String.format("</%s>", tag);
+ int findClose = text.indexOf(close, tags.end());
+ if (findClose != -1) return true;
+ }
+ return false;
+ }
+
+ // TODO is this still necessary?
+ /** If `commentText` looks like HTML, convert it to markdown */
+ static String htmlToMarkdown(String commentText) {
+ if (isHtml(commentText)) {
+ Options options = new Options();
+
+ options.tables = Options.Tables.CONVERT_TO_CODE_BLOCK;
+ options.hardwraps = true;
+ options.inlineLinks = true;
+ options.autoLinks = true;
+ options.reverseHtmlSmartPunctuation = true;
+
+ return new Remark(options).convertFragment(commentText);
+ } else return commentText;
}
private static final Logger LOG = Logger.getLogger("main");
diff --git a/src/main/java/org/javacs/DocsIndexer.java b/src/main/java/org/javacs/DocsIndexer.java
deleted file mode 100644
index 13f82ed..0000000
--- a/src/main/java/org/javacs/DocsIndexer.java
+++ /dev/null
@@ -1,109 +0,0 @@
-package org.javacs;
-
-import com.sun.source.doctree.DocCommentTree;
-import java.io.*;
-import java.nio.file.*;
-import java.util.*;
-import java.util.logging.Logger;
-import javax.lang.model.*;
-import javax.lang.model.element.*;
-import javax.tools.*;
-import jdk.javadoc.doclet.*;
-
-public class DocsIndexer implements Doclet {
-
- private static String targetClass, targetMember;
- private static DocCommentTree result;
-
- @Override
- public String getName() {
- return "Indexer";
- }
-
- @Override
- public Set<Doclet.Option> getSupportedOptions() {
- return Set.of();
- }
-
- @Override
- public SourceVersion getSupportedSourceVersion() {
- return SourceVersion.RELEASE_10;
- }
-
- @Override
- public void init(Locale locale, Reporter reporter) {
- // Nothing to do
- }
-
- @Override
- public boolean run(DocletEnvironment env) {
- Objects.requireNonNull(targetClass, "DocIndexer.targetClass has not been set");
-
- var els = env.getSpecifiedElements();
- if (els.isEmpty()) throw new RuntimeException("No specified elements");
- var docs = env.getDocTrees();
- var elements = env.getElementUtils();
- for (var e : els) {
- if (e instanceof TypeElement) {
- var t = (TypeElement) e;
- if (t.getQualifiedName().contentEquals(targetClass)) {
- if (targetMember == null) {
- result = docs.getDocCommentTree(t);
- } else {
- for (var member : elements.getAllMembers(t)) {
- if (member.getSimpleName().contentEquals(targetMember)) {
- result = docs.getDocCommentTree(member);
- }
- }
- }
- }
- }
- }
- return true;
- }
-
- /** Empty file manager we pass to javadoc to prevent it from roaming all over the place */
- private static final StandardJavaFileManager emptyFileManager =
- ServiceLoader.load(JavaCompiler.class).iterator().next().getStandardFileManager(__ -> {}, null, null);
-
- private static DocumentationTool.DocumentationTask task(JavaFileObject file, String className) {
- var tool = ToolProvider.getSystemDocumentationTool();
- return tool.getTask(
- null,
- emptyFileManager,
- err -> LOG.severe(err.getMessage(null)),
- DocsIndexer.class,
- List.of("--ignore-source-errors", "-Xclasses", className),
- List.of(file));
- }
-
- public static DocCommentTree classDoc(String className, JavaFileObject file) {
- Objects.requireNonNull(file, "file is null");
-
- try {
- targetClass = className;
- var task = task(file, className);
- if (!task.call()) throw new RuntimeException("Documentation task failed");
- Objects.requireNonNull(result, "Documentation task did not set result");
- return result;
- } finally {
- targetClass = null;
- }
- }
-
- public static DocCommentTree memberDoc(String className, String memberName, JavaFileObject file) {
- try {
- targetClass = className;
- targetMember = memberName;
- var task = task(file, className);
- if (!task.call()) throw new RuntimeException("Documentation task failed");
- Objects.requireNonNull(result, "Documentation task did not set result");
- return result;
- } finally {
- targetClass = null;
- targetMember = null;
- }
- }
-
- private static final Logger LOG = Logger.getLogger("main");
-}
diff --git a/src/main/java/org/javacs/JavaCompilerService.java b/src/main/java/org/javacs/JavaCompilerService.java
index 9b82940..e82ab26 100644
--- a/src/main/java/org/javacs/JavaCompilerService.java
+++ b/src/main/java/org/javacs/JavaCompilerService.java
@@ -1,7 +1,7 @@
package org.javacs;
-import com.sun.javadoc.ClassDoc;
-import com.sun.javadoc.MethodDoc;
+import com.google.common.collect.Sets;
+import com.sun.source.doctree.DocCommentTree;
import com.sun.source.tree.*;
import com.sun.source.util.JavacTask;
import com.sun.source.util.SourcePositions;
@@ -52,7 +52,7 @@ public class JavaCompilerService {
// Not modifiable! If you want to edit these, you need to create a new instance
private final Set<Path> sourcePath, classPath, docPath;
private final JavaCompiler compiler = ServiceLoader.load(JavaCompiler.class).iterator().next();
- private final Javadocs docs;
+ private final Docs docs;
private final ClassSource jdkClasses = Classes.jdkTopLevelClasses(), classPathClasses;
// Diagnostics from the last compilation task
private final List<Diagnostic<? extends JavaFileObject>> diags = new ArrayList<>();
@@ -84,7 +84,7 @@ public class JavaCompilerService {
this.sourcePath = Collections.unmodifiableSet(sourcePath);
this.classPath = Collections.unmodifiableSet(classPath);
this.docPath = docPath;
- this.docs = new Javadocs(sourcePath, docPath);
+ this.docs = new Docs(Sets.union(sourcePath, docPath));
this.classPathClasses = Classes.classPathTopLevelClasses(classPath);
}
@@ -426,6 +426,7 @@ public class JavaCompilerService {
// Add static members
for (Element member : t.getEnclosedElements()) {
+ // TODO if this is a member reference :: then include non-statics
if (member.getModifiers().contains(Modifier.STATIC)
&& trees.isAccessible(scope, member, (DeclaredType) t.asType())) {
result.add(Completion.ofElement(member));
@@ -777,14 +778,29 @@ public class JavaCompilerService {
return declaringFile.flatMap(f -> findIn(e, f, contents.apply(f.toUri())));
}
- /** Look up the javadoc associated with `e` */
- public Optional<MethodDoc> methodDoc(ExecutableElement e) {
- return docs.methodDoc(e);
+ /** Look up the javadoc associated with `method` */
+ public Optional<DocCommentTree> methodDoc(ExecutableElement method) {
+ var classElement = (TypeElement) method.getEnclosingElement();
+ var className = classElement.getQualifiedName().toString();
+ var methodName = method.getSimpleName().toString();
+ return docs.memberDoc(className, methodName);
}
- /** Look up the javadoc associated with `e` */
- public Optional<ClassDoc> classDoc(TypeElement e) {
- return docs.classDoc(e);
+ /** Find and parse the source code associated with `method` */
+ public Optional<MethodTree> methodTree(ExecutableElement method) {
+ var classElement = (TypeElement) method.getEnclosingElement();
+ var className = classElement.getQualifiedName().toString();
+ var methodName = method.getSimpleName().toString();
+ return docs.findMethod(className, methodName);
+ }
+
+ /** Look up the javadoc associated with `type` */
+ public Optional<DocCommentTree> classDoc(TypeElement type) {
+ return docs.classDoc(type.getQualifiedName().toString());
+ }
+
+ public Optional<DocCommentTree> classDoc(String qualifiedName) {
+ return docs.classDoc(qualifiedName);
}
private Stream<Path> javaSourcesInDir(Path dir) {
diff --git a/src/main/java/org/javacs/JavaTextDocumentService.java b/src/main/java/org/javacs/JavaTextDocumentService.java
index f74b4e2..5106afe 100644
--- a/src/main/java/org/javacs/JavaTextDocumentService.java
+++ b/src/main/java/org/javacs/JavaTextDocumentService.java
@@ -1,12 +1,13 @@
package org.javacs;
import com.google.gson.JsonPrimitive;
-import com.sun.javadoc.MethodDoc;
-import com.sun.javadoc.ParamTag;
-import com.sun.javadoc.Parameter;
+import com.sun.source.doctree.DocCommentTree;
+import com.sun.source.doctree.DocTree;
+import com.sun.source.doctree.ParamTree;
import com.sun.source.tree.CompilationUnitTree;
import com.sun.source.tree.ImportTree;
import com.sun.source.tree.LineMap;
+import com.sun.source.tree.MethodTree;
import com.sun.source.util.SourcePositions;
import com.sun.source.util.TreePath;
import com.sun.source.util.Trees;
@@ -22,6 +23,7 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.StringJoiner;
@@ -162,12 +164,12 @@ class JavaTextDocumentService implements TextDocumentService {
return CompletableFuture.completedFuture(Either.forRight(new CompletionList(completions.isIncomplete, result)));
}
- private String resolveDocDetail(MethodDoc doc) {
+ private String resolveDocDetail(MethodTree doc) {
StringJoiner args = new StringJoiner(", ");
- for (Parameter p : doc.parameters()) {
- args.add(p.name());
+ for (var p : doc.getParameters()) {
+ args.add(p.getName());
}
- return String.format("%s(%s)", doc.name(), args);
+ return String.format("%s(%s)", doc.getName(), args);
}
private String resolveDefaultDetail(ExecutableElement method) {
@@ -181,6 +183,26 @@ class JavaTextDocumentService implements TextDocumentService {
return String.format("%s(%s)", method.getSimpleName(), args);
}
+ private String asMarkdown(List<? extends DocTree> lines) {
+ var join = new StringJoiner("\n");
+ for (var l : lines) join.add(l.toString());
+ var html = join.toString();
+ return Docs.htmlToMarkdown(html);
+ }
+
+ private String asMarkdown(DocCommentTree comment) {
+ var lines = comment.getFirstSentence();
+ return asMarkdown(lines);
+ }
+
+ private MarkupContent asMarkupContent(DocCommentTree comment) {
+ var markdown = asMarkdown(comment);
+ var content = new MarkupContent();
+ content.setKind(MarkupKind.MARKDOWN);
+ content.setValue(markdown);
+ return content;
+ }
+
@Override
public CompletableFuture<CompletionItem> resolveCompletionItem(CompletionItem unresolved) {
JsonPrimitive idJson = (JsonPrimitive) unresolved.getData();
@@ -192,36 +214,27 @@ class JavaTextDocumentService implements TextDocumentService {
}
if (cached.element != null) {
if (cached.element instanceof ExecutableElement) {
- ExecutableElement method = (ExecutableElement) cached.element;
- Optional<MethodDoc> doc = server.compiler.methodDoc(method);
- String detail = doc.map(this::resolveDocDetail).orElse(resolveDefaultDetail(method));
+ var method = (ExecutableElement) cached.element;
+ var tree = server.compiler.methodTree(method);
+ var detail = tree.map(this::resolveDocDetail).orElse(resolveDefaultDetail(method));
unresolved.setDetail(detail);
- doc.flatMap(Javadocs::commentText)
- .ifPresent(
- html -> {
- String markdown = Javadocs.htmlToMarkdown(html);
- MarkupContent content = new MarkupContent();
- content.setKind(MarkupKind.MARKDOWN);
- content.setValue(markdown);
- unresolved.setDocumentation(content);
- });
+
+ var doc = server.compiler.methodDoc(method);
+ var markdown = doc.map(this::asMarkupContent);
+ markdown.ifPresent(unresolved::setDocumentation);
} else if (cached.element instanceof TypeElement) {
- TypeElement type = (TypeElement) cached.element;
- server.compiler
- .classDoc(type)
- .ifPresent(
- doc -> {
- String html = doc.commentText();
- String markdown = Javadocs.htmlToMarkdown(html);
- MarkupContent content = new MarkupContent();
- content.setKind(MarkupKind.MARKDOWN);
- content.setValue(markdown);
- unresolved.setDocumentation(content);
- });
+ var type = (TypeElement) cached.element;
+ var doc = server.compiler.classDoc(type);
+ var markdown = doc.map(this::asMarkupContent);
+ markdown.ifPresent(unresolved::setDocumentation);
} else {
LOG.info("Don't know how to look up docs for element " + cached.element);
}
// TODO constructors, fields
+ } else if (cached.notImportedClass != null) {
+ var doc = server.compiler.classDoc(cached.notImportedClass);
+ var markdown = doc.map(this::asMarkupContent);
+ markdown.ifPresent(unresolved::setDocumentation);
}
return CompletableFuture.completedFuture(unresolved); // TODO
}
@@ -281,10 +294,10 @@ class JavaTextDocumentService implements TextDocumentService {
private Optional<String> hoverDocs(Element e) {
if (e instanceof ExecutableElement) {
ExecutableElement m = (ExecutableElement) e;
- return server.compiler.methodDoc(m).flatMap(Javadocs::commentText).map(Javadocs::htmlToMarkdown);
+ return server.compiler.methodDoc(m).map(this::asMarkdown);
} else if (e instanceof TypeElement) {
TypeElement t = (TypeElement) e;
- return server.compiler.classDoc(t).map(doc -> doc.commentText()).map(Javadocs::htmlToMarkdown);
+ return server.compiler.classDoc(t).map(this::asMarkdown);
} else return Optional.empty();
}
@@ -303,17 +316,22 @@ class JavaTextDocumentService implements TextDocumentService {
} else return CompletableFuture.completedFuture(new Hover(Collections.emptyList()));
}
- private List<ParameterInformation> signatureParamsFromDocs(MethodDoc doc) {
+ private List<ParameterInformation> signatureParamsFromDocs(MethodTree method, DocCommentTree doc) {
List<ParameterInformation> ps = new ArrayList<>();
Map<String, String> paramComments = new HashMap<>();
- for (ParamTag t : doc.paramTags()) {
- paramComments.put(t.parameterName(), t.parameterComment());
+ for (var tag : doc.getBlockTags()) {
+ if (tag.getKind() == DocTree.Kind.PARAM) {
+ var param = (ParamTree) tag;
+ paramComments.put(param.getName().toString(), asMarkdown(param.getDescription()));
+ }
}
- for (Parameter d : doc.parameters()) {
- ParameterInformation p = new ParameterInformation();
- p.setLabel(d.name());
- p.setDocumentation(paramComments.get(d.name()));
- ps.add(p);
+ for (var param : method.getParameters()) {
+ var info = new ParameterInformation();
+ var name = param.getName().toString();
+ info.setLabel(name);
+ if (paramComments.containsKey(name)) info.setDocumentation(paramComments.get(name));
+ else info.setDocumentation(Objects.toString(param.getType(), ""));
+ ps.add(info);
}
return ps;
}
@@ -333,8 +351,10 @@ class JavaTextDocumentService implements TextDocumentService {
private SignatureInformation asSignatureInformation(ExecutableElement e) {
SignatureInformation i = new SignatureInformation();
- Optional<MethodDoc> doc = server.compiler.methodDoc(e);
- List<ParameterInformation> ps = doc.map(this::signatureParamsFromDocs).orElse(signatureParamsFromMethod(e));
+ var ps = signatureParamsFromMethod(e);
+ var doc = server.compiler.methodDoc(e);
+ var tree = server.compiler.methodTree(e);
+ if (doc.isPresent() && tree.isPresent()) ps = signatureParamsFromDocs(tree.get(), doc.get());
String args = ps.stream().map(p -> p.getLabel()).collect(Collectors.joining(", "));
String name = e.getSimpleName().toString();
if (name.equals("<init>")) name = e.getEnclosingElement().getSimpleName().toString();
diff --git a/src/main/java/org/javacs/Javadocs.java b/src/main/java/org/javacs/Javadocs.java
deleted file mode 100644
index f993283..0000000
--- a/src/main/java/org/javacs/Javadocs.java
+++ /dev/null
@@ -1,294 +0,0 @@
-package org.javacs;
-
-import com.google.common.base.Strings;
-import com.google.common.collect.ImmutableList;
-import com.overzealous.remark.Options;
-import com.overzealous.remark.Remark;
-import com.sun.javadoc.ClassDoc;
-import com.sun.javadoc.MethodDoc;
-import com.sun.javadoc.Parameter;
-import com.sun.javadoc.RootDoc;
-import com.sun.source.util.JavacTask;
-import java.io.File;
-import java.io.IOException;
-import java.nio.file.*;
-import java.text.BreakIterator;
-import java.time.Instant;
-import java.util.*;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.logging.Logger;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-import java.util.stream.Collectors;
-import javax.lang.model.element.ExecutableElement;
-import javax.lang.model.element.TypeElement;
-import javax.lang.model.element.VariableElement;
-import javax.tools.*;
-
-// This class must be public so DocletInvoker can call it
-public class Javadocs {
-
- /** Cache for performance reasons */
- private final StandardJavaFileManager fileManager;
-
- /** Empty file manager we pass to javadoc to prevent it from roaming all over the place */
- private final StandardJavaFileManager emptyFileManager =
- ServiceLoader.load(JavaCompiler.class).iterator().next().getStandardFileManager(__ -> {}, null, null);
-
- /** All the classes we have indexed so far */
- private final Map<String, IndexedDoc> topLevelClasses = new ConcurrentHashMap<>();
-
- private final JavacTask task;
-
- private static class IndexedDoc {
- final RootDoc doc;
- final Instant updated;
-
- IndexedDoc(RootDoc doc, Instant updated) {
- this.doc = doc;
- this.updated = updated;
- }
- }
-
- private static Path srcZip() {
- try {
- var fs = FileSystems.newFileSystem(Lib.SRC_ZIP, Docs.class.getClassLoader());
- return fs.getPath("/");
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- }
-
- Javadocs(Set<Path> sourcePath, Set<Path> docPath) {
- this.fileManager =
- ServiceLoader.load(JavaCompiler.class).iterator().next().getStandardFileManager(__ -> {}, null, null);
- this.task =
- (JavacTask)
- ServiceLoader.load(JavaCompiler.class)
- .iterator()
- .next()
- .getTask(null, emptyFileManager, __ -> {}, null, null, null);
- // Compute the full source path, including src.zip for platform classes
- var sourcePathFiles = sourcePath.stream().map(Path::toFile).collect(Collectors.toSet());
-
- try {
- fileManager.setLocation(StandardLocation.SOURCE_PATH, sourcePathFiles);
- fileManager.setLocationFromPaths(StandardLocation.MODULE_SOURCE_PATH, Set.of(srcZip()));
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- }
-
- private static final Pattern HTML_TAG = Pattern.compile("<(\\w+)>");
-
- private static boolean isHtml(String text) {
- Matcher tags = HTML_TAG.matcher(text);
- while (tags.find()) {
- String tag = tags.group(1);
- String close = String.format("</%s>", tag);
- int findClose = text.indexOf(close, tags.end());
- if (findClose != -1) return true;
- }
- return false;
- }
-
- /** If `commentText` looks like HTML, convert it to markdown */
- static String htmlToMarkdown(String commentText) {
- if (isHtml(commentText)) {
- Options options = new Options();
-
- options.tables = Options.Tables.CONVERT_TO_CODE_BLOCK;
- options.hardwraps = true;
- options.inlineLinks = true;
- options.autoLinks = true;
- options.reverseHtmlSmartPunctuation = true;
-
- return new Remark(options).convertFragment(commentText);
- } else return commentText;
- }
-
- /** Get docstring for method, using inherited method if necessary */
- static Optional<String> commentText(MethodDoc doc) {
- // TODO search interfaces as well
-
- while (doc != null && Strings.isNullOrEmpty(doc.commentText())) doc = doc.overriddenMethod();
-
- if (doc == null || Strings.isNullOrEmpty(doc.commentText())) return Optional.empty();
- else return Optional.of(doc.commentText());
- }
-
- // Figure out if elements and docs refer to the same thing, by computing String keys
-
- private String elementParamKey(VariableElement param) {
- return task.getTypes().erasure(param.asType()).toString();
- }
-
- private String elementMethodKey(ExecutableElement method) {
- TypeElement classElement = (TypeElement) method.getEnclosingElement();
- String params = method.getParameters().stream().map(this::elementParamKey).collect(Collectors.joining(","));
- return String.format("%s#%s(%s)", classElement.getQualifiedName(), method.getSimpleName(), params);
- }
-
- private String docParamKey(Parameter doc) {
- return doc.type().qualifiedTypeName() + doc.type().dimension();
- }
-
- private String docMethodKey(MethodDoc doc) {
- String params = Arrays.stream(doc.parameters()).map(this::docParamKey).collect(Collectors.joining(","));
- return String.format("%s#%s(%s)", doc.containingClass().qualifiedName(), doc.name(), params);
- }
-
- Optional<MethodDoc> methodDoc(ExecutableElement method) {
- String key = elementMethodKey(method);
- TypeElement classElement = (TypeElement) method.getEnclosingElement();
- String className = classElement.getQualifiedName().toString();
- RootDoc rootDoc = index(className);
- ClassDoc classDoc = rootDoc.classNamed(className);
- if (classDoc == null) {
- LOG.warning("No docs for class " + className);
- return Optional.empty();
- }
- for (MethodDoc each : classDoc.methods(false)) {
- if (docMethodKey(each).equals(key)) return Optional.of(each);
- }
- return Optional.empty();
- }
-
- Optional<ClassDoc> classDoc(TypeElement c) {
- String className = c.getQualifiedName().toString();
- RootDoc index = index(className);
- return Optional.ofNullable(index.classNamed(className));
- }
-
- /** Get or compute the javadoc for `className` */
- RootDoc index(String className) {
- if (needsUpdate(className)) topLevelClasses.put(className, doIndex(className));
-
- return topLevelClasses.get(className).doc;
- }
-
- private JavaFileObject file(String className) {
- try {
- var fromSourcePath =
- fileManager.getJavaFileForInput(
- StandardLocation.SOURCE_PATH, className, JavaFileObject.Kind.SOURCE);
- if (fromSourcePath != null) return fromSourcePath;
- var moduleLocation = fileManager.getLocationForModule(StandardLocation.MODULE_SOURCE_PATH, "java.base");
- var fromModuleSourcePath =
- fileManager.getJavaFileForInput(moduleLocation, className, JavaFileObject.Kind.SOURCE);
- if (fromModuleSourcePath != null) return fromModuleSourcePath;
- throw new RuntimeException("Couldn't find source file for " + className);
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- }
-
- private boolean needsUpdate(String className) {
- if (!topLevelClasses.containsKey(className)) return true;
-
- IndexedDoc indexed = topLevelClasses.get(className);
- JavaFileObject source = file(className);
-
- if (source == null) return true;
-
- Instant modified = Instant.ofEpochMilli(source.getLastModified());
-
- return indexed.updated.isBefore(modified);
- }
-
- /** Read all the Javadoc for `className` */
- private IndexedDoc doIndex(String className) {
- JavaFileObject source = file(className);
-
- if (source == null) {
- LOG.warning("No source file for " + className);
-
- return new IndexedDoc(EmptyRootDoc.INSTANCE, Instant.EPOCH);
- }
-
- LOG.info("Found " + source.toUri() + " for " + className);
-
- DocumentationTool.DocumentationTask task =
- ToolProvider.getSystemDocumentationTool()
- .getTask(null, emptyFileManager, __ -> {}, Javadocs.class, null, ImmutableList.of(source));
-
- task.call();
-
- return new IndexedDoc(
- getSneakyReturn().orElse(EmptyRootDoc.INSTANCE), Instant.ofEpochMilli(source.getLastModified()));
- }
-
- private Optional<RootDoc> getSneakyReturn() {
- RootDoc result = sneakyReturn.get();
- sneakyReturn.remove();
-
- if (result == null) {
- LOG.warning("index did not return a RootDoc");
-
- return Optional.empty();
- } else return Optional.of(result);
- }
-
- /** start(RootDoc) uses this to return its result to doIndex(...) */
- private static ThreadLocal<RootDoc> sneakyReturn = new ThreadLocal<>();
-
- /**
- * Called by the javadoc tool
- *
- * <p>{@link com.sun.javadoc.Doclet}
- */
- public static boolean start(RootDoc root) {
- sneakyReturn.set(root);
-
- return true;
- }
-
- /** Find the copy of src.zip that comes with the system-installed JDK */
- static Optional<File> findSrcZip() {
- Path javaHome = Paths.get(System.getProperty("java.home"));
- if (javaHome.endsWith("jre")) javaHome = javaHome.getParent();
-
- var java8 = tryFind(javaHome.resolve("src.zip"));
- if (java8.isPresent()) return java8;
-
- var java9 = tryFind(javaHome.resolve("lib").resolve("src.zip"));
- return java9;
- }
-
- private static Optional<File> tryFind(Path path) {
- while (Files.isSymbolicLink(path)) {
- try {
- path = path.getParent().resolve(Files.readSymbolicLink(path));
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- }
-
- if (Files.exists(path)) return Optional.of(path.toFile());
- else {
- LOG.warning(String.format("Could not find %s", path));
-
- return Optional.empty();
- }
- }
-
- /**
- * Get the first sentence of a doc-comment.
- *
- * <p>In general, VS Code does a good job of only displaying the beginning of a doc-comment where appropriate. But
- * if VS Code is displaying too much and you want to only show the first sentence, use this.
- */
- public static String firstSentence(String doc) {
- BreakIterator breaks = BreakIterator.getSentenceInstance();
-
- breaks.setText(doc.replace('\n', ' '));
-
- int start = breaks.first(), end = breaks.next();
-
- if (start == -1 || end == -1) return doc;
-
- return doc.substring(start, end).trim();
- }
-
- private static final Logger LOG = Logger.getLogger("main");
-}
diff --git a/src/main/java/org/javacs/Lib.java b/src/main/java/org/javacs/Lib.java
new file mode 100644
index 0000000..924f98c
--- /dev/null
+++ b/src/main/java/org/javacs/Lib.java
@@ -0,0 +1,15 @@
+package org.javacs;
+
+import java.nio.file.*;
+
+class Lib {
+ static Path installRoot() {
+ var root = Paths.get(".").toAbsolutePath();
+ var p = root;
+ while (p != null && !Files.exists(p.resolve("javaLsFlag.txt"))) p = p.getParent();
+ if (p == null) throw new RuntimeException("Couldn't find javaLsFlag.txt in any parent of " + root);
+ return p;
+ }
+
+ static final Path SRC_ZIP = installRoot().resolve("lib/src.zip");
+}
diff --git a/src/main/java/org/javacs/Parser.java b/src/main/java/org/javacs/Parser.java
index 128fbca..1aa6fe2 100644
--- a/src/main/java/org/javacs/Parser.java
+++ b/src/main/java/org/javacs/Parser.java
@@ -42,7 +42,7 @@ class Parser {
private static final StandardJavaFileManager fileManager =
compiler.getStandardFileManager(__ -> {}, null, Charset.defaultCharset());
- private static JavacTask parseTask(JavaFileObject file) {
+ static JavacTask parseTask(JavaFileObject file) {
return (JavacTask)
compiler.getTask(
null,
@@ -53,7 +53,7 @@ class Parser {
Collections.singletonList(file));
}
- private static JavacTask parseTask(Path source) {
+ static JavacTask parseTask(Path source) {
JavaFileObject file =
fileManager.getJavaFileObjectsFromFiles(Collections.singleton(source.toFile())).iterator().next();
return parseTask(file);
diff --git a/src/test/java/org/javacs/DocsTest.java b/src/test/java/org/javacs/DocsTest.java
index 6910079..23bad26 100644
--- a/src/test/java/org/javacs/DocsTest.java
+++ b/src/test/java/org/javacs/DocsTest.java
@@ -12,7 +12,8 @@ public class DocsTest {
var sourcePath = Set.of(JavaCompilerServiceTest.resourcesDir());
var docs = new Docs(sourcePath);
var tree = docs.classDoc("ClassDoc");
- assertThat(tree, hasToString("A great class"));
+ assertTrue(tree.isPresent());
+ assertThat(tree.get().getFirstSentence(), hasToString("A great class"));
}
@Test
@@ -20,6 +21,14 @@ public class DocsTest {
var sourcePath = Set.of(JavaCompilerServiceTest.resourcesDir());
var docs = new Docs(sourcePath);
var tree = docs.memberDoc("LocalMethodDoc", "targetMethod");
- assertThat(tree, hasToString("A great method"));
+ assertTrue(tree.isPresent());
+ assertThat(tree.get().getFirstSentence(), hasToString("A great method"));
+ }
+
+ @Test
+ public void platformDoc() {
+ var docs = new Docs(Set.of());
+ var tree = docs.classDoc("java.util.List");
+ assertTrue(tree.isPresent());
}
}
diff --git a/src/test/java/org/javacs/JavaCompilerServiceTest.java b/src/test/java/org/javacs/JavaCompilerServiceTest.java
index f912ec4..b8ffa5c 100644
--- a/src/test/java/org/javacs/JavaCompilerServiceTest.java
+++ b/src/test/java/org/javacs/JavaCompilerServiceTest.java
@@ -3,7 +3,6 @@ package org.javacs;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
-import com.sun.javadoc.MethodDoc;
import com.sun.source.tree.CompilationUnitTree;
import com.sun.source.tree.LineMap;
import com.sun.source.util.SourcePositions;
@@ -20,7 +19,6 @@ import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
-import java.util.Optional;
import java.util.Set;
import java.util.logging.Logger;
import java.util.stream.Collectors;
@@ -297,9 +295,9 @@ public class JavaCompilerServiceTest {
.get()
.activeMethod
.get();
- Optional<MethodDoc> doc = compiler.methodDoc(method);
+ var doc = compiler.methodDoc(method);
assertTrue(doc.isPresent());
- assertThat(Javadocs.commentText(doc.get()).orElse("<empty>"), containsString("A great method"));
+ assertThat(doc.toString(), containsString("A great method"));
}
@Test
diff --git a/src/test/java/org/javacs/JavadocsTest.java b/src/test/java/org/javacs/JavadocsTest.java
deleted file mode 100644
index 90e394c..0000000
--- a/src/test/java/org/javacs/JavadocsTest.java
+++ /dev/null
@@ -1,74 +0,0 @@
-package org.javacs;
-
-import static org.hamcrest.Matchers.*;
-import static org.junit.Assert.assertThat;
-import static org.junit.Assert.assertTrue;
-
-import com.sun.javadoc.RootDoc;
-import java.io.IOException;
-import java.nio.file.Paths;
-import java.util.Collections;
-import org.junit.Test;
-
-public class JavadocsTest {
-
- private final Javadocs docs =
- new Javadocs(
- Collections.singleton(Paths.get("src/test/test-project/workspace/src")), Collections.emptySet());
-
- @Test
- public void findSrcZip() {
- assertTrue("Can find src.zip", Javadocs.findSrcZip().isPresent());
- }
-
- @Test
- public void findSystemDoc() throws IOException {
- RootDoc root = docs.index("java.util.ArrayList");
-
- assertThat(root.classes(), not(emptyArray()));
- }
-
- /*
- @Test
- public void findMethodDoc() {
- assertTrue(
- "Found method",
- docs.methodDoc(
- "org.javacs.docs.TrickyDocstring#example(java.lang.String,java.lang.String[],java.util.List)")
- .isPresent());
- }
-
- @Test
- public void findParameterizedDoc() {
- assertTrue(
- "Found method",
- docs.methodDoc("org.javacs.docs.TrickyDocstring#parameterized(java.lang.Object)").isPresent());
- }
-
- @Test
- @Ignore // Blocked by emptyFileManager
- public void findInheritedDoc() {
- Optional<MethodDoc> found = docs.methodDoc("org.javacs.docs.SubDoc#method()");
-
- assertTrue("Found method", found.isPresent());
-
- Optional<String> docstring = found.flatMap(Javadocs::commentText);
-
- assertTrue("Has inherited doc", docstring.isPresent());
- assertThat("Inherited doc is not empty", docstring.get(), not(isEmptyOrNullString()));
- }
-
- @Test
- @Ignore // Doesn't work yet
- public void findInterfaceDoc() {
- Optional<MethodDoc> found = docs.methodDoc("org.javacs.docs.SubDoc#interfaceMethod()");
-
- assertTrue("Found method", found.isPresent());
-
- Optional<String> docstring = found.flatMap(Javadocs::commentText);
-
- assertTrue("Has inherited doc", docstring.isPresent());
- assertThat("Inherited doc is not empty", docstring.get(), not(isEmptyOrNullString()));
- }
- */
-}
diff --git a/src/test/resources/LocalMethodDoc.java b/src/test/resources/LocalMethodDoc.java
index 789f17c..701fa16 100644
--- a/src/test/resources/LocalMethodDoc.java
+++ b/src/test/resources/LocalMethodDoc.java
@@ -3,7 +3,10 @@ class LocalMethodDoc {
targetMethod();
}
- /** A great method */
- void targetMethod() {
+ /**
+ * A great method
+ * @param param A great param
+ */
+ void targetMethod(int param) {
}
} \ No newline at end of file