All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.plumelib.javadoc.RequireJavadoc Maven / Gradle / Ivy

The newest version!
package org.plumelib.javadoc;

import static com.github.javaparser.utils.PositionUtils.sortByBeginPosition;

import com.github.javaparser.ParseProblemException;
import com.github.javaparser.ParserConfiguration;
import com.github.javaparser.Position;
import com.github.javaparser.Range;
import com.github.javaparser.StaticJavaParser;
import com.github.javaparser.ast.CompilationUnit;
import com.github.javaparser.ast.Node;
import com.github.javaparser.ast.NodeList;
import com.github.javaparser.ast.PackageDeclaration;
import com.github.javaparser.ast.body.AnnotationDeclaration;
import com.github.javaparser.ast.body.AnnotationMemberDeclaration;
import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration;
import com.github.javaparser.ast.body.ConstructorDeclaration;
import com.github.javaparser.ast.body.EnumConstantDeclaration;
import com.github.javaparser.ast.body.EnumDeclaration;
import com.github.javaparser.ast.body.FieldDeclaration;
import com.github.javaparser.ast.body.MethodDeclaration;
import com.github.javaparser.ast.body.Parameter;
import com.github.javaparser.ast.body.RecordDeclaration;
import com.github.javaparser.ast.body.VariableDeclarator;
import com.github.javaparser.ast.comments.Comment;
import com.github.javaparser.ast.expr.AnnotationExpr;
import com.github.javaparser.ast.expr.AssignExpr;
import com.github.javaparser.ast.expr.Expression;
import com.github.javaparser.ast.expr.FieldAccessExpr;
import com.github.javaparser.ast.expr.NameExpr;
import com.github.javaparser.ast.expr.ThisExpr;
import com.github.javaparser.ast.expr.UnaryExpr;
import com.github.javaparser.ast.nodeTypes.NodeWithJavadoc;
import com.github.javaparser.ast.stmt.BlockStmt;
import com.github.javaparser.ast.stmt.ExpressionStmt;
import com.github.javaparser.ast.stmt.ReturnStmt;
import com.github.javaparser.ast.stmt.Statement;
import com.github.javaparser.ast.type.PrimitiveType;
import com.github.javaparser.ast.type.Type;
import com.github.javaparser.ast.visitor.VoidVisitorAdapter;
import java.io.File;
import java.io.IOException;
import java.nio.file.FileVisitResult;
import java.nio.file.FileVisitor;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.regex.Pattern;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.plumelib.options.Option;
import org.plumelib.options.Options;

/**
 * A program that issues an error for any class, constructor, method, or field that lacks a Javadoc
 * comment. Does not issue a warning for methods annotated with {@code @Override}. See documentation
 * at https://github.com/plume-lib/require-javadoc.
 */
public class RequireJavadoc {

  /** Matches name of file or directory where no problems should be reported. */
  @Option("Don't check files or directories whose pathname matches the regex")
  public @MonotonicNonNull Pattern exclude = null;

  // TODO: It would be nice to support matching fully-qualified class names, but matching
  // packages will have to do for now.
  /**
   * Matches simple name of class/constructor/method/field, or full package name, where no problems
   * should be reported.
   */
  @Option("Don't report problems in Java elements whose name matches the regex")
  public @MonotonicNonNull Pattern dont_require = null;

  /** If true, don't check elements with private access. */
  @Option("Don't report problems in elements with private access")
  public boolean dont_require_private;

  /**
   * If true, don't check constructors with zero formal parameters. These are sometimes called
   * "default constructors", though that term means a no-argument constructor that the compiler
   * synthesized when the programmer didn't write one.
   */
  @Option("Don't report problems in constructors with zero formal parameters")
  public boolean dont_require_noarg_constructor;

  /**
   * If true, don't check trivial getters and setters.
   *
   * 

Trivial getters and setters are of the form: * *

{@code
   * SomeType getFoo() {
   *   return foo;
   * }
   *
   * SomeType foo() {
   *   return foo;
   * }
   *
   * void setFoo(SomeType foo) {
   *   this.foo = foo;
   * }
   *
   * boolean hasFoo() {
   *   return foo;
   * }
   *
   * boolean isFoo() {
   *   return foo;
   * }
   *
   * boolean notFoo() {
   *   return !foo;
   * }
   * }
*/ @Option("Don't report problems in trivial getters and setters") public boolean dont_require_trivial_properties; /** If true, don't check type declarations: classes, interfaces, enums, annotations, records. */ @Option("Don't report problems in type declarations") public boolean dont_require_type; /** If true, don't check fields. */ @Option("Don't report problems in fields") public boolean dont_require_field; /** If true, don't check methods, constructors, and annotation members. */ @Option("Don't report problems in methods and constructors") public boolean dont_require_method; /** If true, warn if any package lacks a package-info.java file. */ @Option("Require package-info.java file to exist") public boolean require_package_info; /** * If true, print filenames relative to working directory. Setting this only has an effect if the * command-line arguments were absolute pathnames, or no command-line arguments were supplied. */ @Option("Report relative rather than absolute filenames") public boolean relative = false; /** If true, output debug information. */ @Option("Print diagnostic information") public boolean verbose = false; /** All the errors this program will report. */ private List errors = new ArrayList<>(); /** The Java files to be checked. */ private List javaFiles = new ArrayList(); /** The current working directory, for making relative pathnames. */ private Path workingDirRelative = Paths.get(""); /** The current working directory, for making relative pathnames. */ private Path workingDirAbsolute = Paths.get("").toAbsolutePath(); /** * The main entry point for the require-javadoc program. See documentation at https://github.com/plume-lib/require-javadoc. * * @param args the command-line arguments; see the README.md file */ public static void main(String[] args) { RequireJavadoc rj = new RequireJavadoc(); Options options = new Options( "java org.plumelib.javadoc.RequireJavadoc [options] [directory-or-file ...]", rj); String[] remainingArgs = options.parse(true, args); rj.setJavaFiles(remainingArgs); for (Path javaFile : rj.javaFiles) { if (rj.verbose) { System.out.println("Checking " + javaFile); } try { ParserConfiguration parserConfiguration = new ParserConfiguration(); parserConfiguration.setLanguageLevel(ParserConfiguration.LanguageLevel.JAVA_17); StaticJavaParser.setConfiguration(parserConfiguration); CompilationUnit cu = StaticJavaParser.parse(javaFile); RequireJavadocVisitor visitor = rj.new RequireJavadocVisitor(javaFile); visitor.visit(cu, null); } catch (IOException e) { System.out.println("Problem while reading " + javaFile + ": " + e.getMessage()); System.exit(2); } catch (ParseProblemException e) { System.out.println("Problem while parsing " + javaFile + ": " + e.getMessage()); System.exit(2); } } for (String error : rj.errors) { System.out.println(error); } System.exit(rj.errors.isEmpty() ? 0 : 1); } /** Creates a new RequireJavadoc instance. */ private RequireJavadoc() {} /** * Set the Java files to be processed from the command-line arguments. * * @param args the directories and files listed on the command line */ @SuppressWarnings({ "lock:unneeded.suppression", // TEMPORARY, until a CF release is made "lock:methodref.receiver", // Comparator.comparing "lock:type.arguments.not.inferred" // Comparator.comparing }) private void setJavaFiles(String[] args) { if (args.length == 0) { args = new String[] {workingDirAbsolute.toString()}; } FileVisitor walker = new JavaFilesVisitor(); for (String arg : args) { if (shouldExclude(arg)) { continue; } Path p = Paths.get(arg); File f = p.toFile(); if (!f.exists()) { System.out.println("File not found: " + f); System.exit(2); } if (f.isDirectory()) { try { Files.walkFileTree(p, walker); } catch (IOException e) { System.out.println("Problem while reading " + f + ": " + e.getMessage()); System.exit(2); } } else { javaFiles.add(Paths.get(arg)); } } javaFiles.sort(Comparator.comparing(Object::toString)); Set missingPackageInfoFiles = new LinkedHashSet<>(); if (require_package_info) { for (Path javaFile : javaFiles) { @SuppressWarnings("nullness:assignment") // the file is not "/", so getParent() is non-null @NonNull Path javaFileParent = javaFile.getParent(); // Java 11 has Path.of() instead of creating a new File. Path packageInfo = javaFileParent.resolve(new File("package-info.java").toPath()); if (!javaFiles.contains(packageInfo)) { missingPackageInfoFiles.add(packageInfo); } } for (Path packageInfo : missingPackageInfoFiles) { errors.add("missing package documentation: no file " + packageInfo); } } } /** Collects files into the {@link #javaFiles} variable. */ private class JavaFilesVisitor extends SimpleFileVisitor { /** Create a new JavaFilesVisitor. */ public JavaFilesVisitor() {} @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attr) { if (attr.isRegularFile() && file.toString().endsWith(".java")) { if (!shouldExclude(file)) { javaFiles.add(file); } } return FileVisitResult.CONTINUE; } @Override public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attr) { if (shouldExclude(dir)) { return FileVisitResult.SKIP_SUBTREE; } return FileVisitResult.CONTINUE; } @Override public FileVisitResult postVisitDirectory(Path dir, IOException exc) { if (exc != null) { System.out.println("Problem visiting " + dir + ": " + exc.getMessage()); System.exit(2); } return FileVisitResult.CONTINUE; } @Override public FileVisitResult visitFileFailed(Path file, IOException exc) { if (exc != null) { System.out.println("Problem visiting " + file + ": " + exc.getMessage()); System.exit(2); } return FileVisitResult.CONTINUE; } } /** * Return true if the given Java element should not be checked, based on the {@code * --dont-require} command-line argument. * * @param name the name of a Java element. It is a simple name, except for packages. * @return true if no warnings should be issued about the element */ private boolean shouldNotRequire(String name) { if (dont_require == null) { return false; } boolean result = dont_require.matcher(name).find(); if (verbose) { System.out.printf("shouldNotRequire(%s) => %s%n", name, result); } return result; } /** * Return true if the given file or directory should be skipped, based on the {@code --exclude} * command-line argument. * * @param fileName the name of a Java file or directory * @return true if the file or directory should be skipped */ private boolean shouldExclude(String fileName) { if (exclude == null) { return false; } boolean result = exclude.matcher(fileName).find(); if (verbose) { System.out.printf("shouldExclude(%s) => %s%n", fileName, result); } return result; } /** * Return true if the given file or directory should be skipped, based on the {@code --exclude} * command-line argument. * * @param path a Java file or directory * @return true if the file or directory should be skipped */ private boolean shouldExclude(Path path) { return shouldExclude(path.toString()); } /** A property method's return type. */ private enum ReturnType { /** The return type is void. */ VOID, /** The return type is boolean. */ BOOLEAN, /** The return type is non-void. */ NON_VOID; } /** The type of property method: a getter or setter. */ private enum PropertyKind { /** A method of the form {@code SomeType getFoo()}. */ GETTER("get", 0, ReturnType.NON_VOID), /** A method of the form {@code SomeType foo()}. */ GETTER_NO_PREFIX("", 0, ReturnType.NON_VOID), /** A method of the form {@code boolean hasFoo()}. */ GETTER_HAS("has", 0, ReturnType.BOOLEAN), /** A method of the form {@code boolean isFoo()}. */ GETTER_IS("is", 0, ReturnType.BOOLEAN), /** A method of the form {@code boolean notFoo()}. */ GETTER_NOT("not", 0, ReturnType.BOOLEAN), /** A method of the form {@code void setFoo(SomeType arg)}. */ SETTER("set", 1, ReturnType.VOID), /** Not a getter or setter. */ NOT_PROPERTY("", -1, ReturnType.VOID); /** The prefix for the method name: "get", "", "has", "is", "not", or "set". */ final String prefix; /** The number of required formal parameters: 0 or 1. */ final int requiredParams; /** The return type. */ final ReturnType returnType; /** * Create a new PropertyKind. * * @param prefix the prefix for the method name: "get", "has", "is", "not", or "set" * @param requiredParams the number of required formal parameters: 0 or 1 * @param returnType the return type */ PropertyKind(String prefix, int requiredParams, ReturnType returnType) { this.prefix = prefix; this.requiredParams = requiredParams; this.returnType = returnType; } /** * Returns true if this is a getter. * * @return true if this is a getter */ boolean isGetter() { return this != SETTER; } /** * Return the PropertyKind for the given method, or null if it isn't a property accessor method. * * @param md the method to check * @return the PropertyKind for the given method, or null */ static PropertyKind fromMethodDeclaration(MethodDeclaration md) { String methodName = md.getNameAsString(); if (methodName.startsWith("get")) { return GETTER; } else if (methodName.startsWith("has")) { return GETTER_HAS; } else if (methodName.startsWith("is")) { return GETTER_IS; } else if (methodName.startsWith("not")) { return GETTER_NOT; } else if (methodName.startsWith("set")) { return SETTER; } else { return GETTER_NO_PREFIX; } } } /** * Return true if this method declaration is a trivial getter or setter. * *
    *
  • A trivial getter is named {@code getFoo}, {@code foo}, {@code hasFoo}, {@code isFoo}, or * {@code notFoo}, has no formal parameters, and has a body of the form {@code return foo} * or {@code return this.foo} (except for {@code notFoo}, in which case the body is * negated). *
  • A trivial setter is named {@code setFoo}, has one formal parameter named {@code foo}, and * has a body of the form {@code this.foo = foo}. *
* * @param md the method to check * @return true if this method is a trivial getter or setter */ private boolean isTrivialGetterOrSetter(MethodDeclaration md) { PropertyKind kind = PropertyKind.fromMethodDeclaration(md); if (kind != PropertyKind.GETTER_NO_PREFIX) { if (isTrivialGetterOrSetter(md, kind)) { return true; } } return isTrivialGetterOrSetter(md, PropertyKind.GETTER_NO_PREFIX); } /** * Return true if this method declaration is a trivial getter or setter of the given kind. * * @see #isTrivialGetterOrSetter(MethodDeclaration) * @param md the method to check * @param propertyKind the kind of property * @return true if this method is a trivial getter or setter */ private boolean isTrivialGetterOrSetter(MethodDeclaration md, PropertyKind propertyKind) { String propertyName = propertyName(md, propertyKind); return propertyName != null && hasCorrectSignature(md, propertyKind, propertyName) && hasCorrectBody(md, propertyKind, propertyName); } /** * Returns the name of the property, if the method is a getter or setter of the given kind. * Otherwise returns null. * *

Examines the method's name, but not its signature or body. Also does not check that the * given property name corresponds to an existing field. * * @param md the method to test * @param propertyKind the type of property method * @return the name of the property, or null */ private @Nullable String propertyName(MethodDeclaration md, PropertyKind propertyKind) { String methodName = md.getNameAsString(); assert methodName.startsWith(propertyKind.prefix); @SuppressWarnings("index") // https://github.com/typetools/checker-framework/issues/5201 String upperCamelCaseProperty = methodName.substring(propertyKind.prefix.length()); if (upperCamelCaseProperty.length() == 0) { return null; } if (propertyKind == PropertyKind.GETTER_NO_PREFIX) { return upperCamelCaseProperty; } else if (!Character.isUpperCase(upperCamelCaseProperty.charAt(0))) { return null; } else { return "" + Character.toLowerCase(upperCamelCaseProperty.charAt(0)) + upperCamelCaseProperty.substring(1); } } /** * Returns true if the signature of the given method is a property accessor of the given kind. * * @param md the method * @param propertyKind the kind of property * @param propertyName the name of the property * @return true if the body of the given method is a property accessor */ private boolean hasCorrectSignature( MethodDeclaration md, PropertyKind propertyKind, String propertyName) { NodeList parameters = md.getParameters(); if (parameters.size() != propertyKind.requiredParams) { return false; } if (parameters.size() == 1) { Parameter parameter = parameters.get(0); if (!parameter.getNameAsString().equals(propertyName)) { return false; } } // Check presence/absence of return type. (The Java compiler will verify // that the type is consistent with the method body.) Type returnType = md.getType(); switch (propertyKind.returnType) { case VOID: if (!returnType.isVoidType()) { return false; } break; case BOOLEAN: if (!returnType.equals(PrimitiveType.booleanType())) { return false; } break; case NON_VOID: if (returnType.isVoidType()) { return false; } break; default: throw new Error("Unexpected enum value " + propertyKind.returnType); } return true; } /** * Returns true if the body of the given method is a property accessor of the given kind. * * @param md the method * @param propertyKind the kind of property * @param propertyName the name of the property * @return true if the body of the given method is a property accessor */ private boolean hasCorrectBody( MethodDeclaration md, PropertyKind propertyKind, String propertyName) { Statement statement = getOnlyStatement(md); if (propertyKind.isGetter()) { if (!(statement instanceof ReturnStmt)) { return false; } Optional optReturnExpr = ((ReturnStmt) statement).getExpression(); if (!optReturnExpr.isPresent()) { return false; } Expression returnExpr = optReturnExpr.get(); // Does not handle parentheses. if (propertyKind == PropertyKind.GETTER_NOT) { if (!(returnExpr instanceof UnaryExpr)) { return false; } UnaryExpr unary = (UnaryExpr) returnExpr; if (unary.getOperator() != UnaryExpr.Operator.LOGICAL_COMPLEMENT) { return false; } returnExpr = unary.getExpression(); } String returnName; // Does not handle parentheses. if (returnExpr instanceof NameExpr) { returnName = ((NameExpr) returnExpr).getNameAsString(); } else if (returnExpr instanceof FieldAccessExpr) { FieldAccessExpr fa = (FieldAccessExpr) returnExpr; Expression receiver = fa.getScope(); if (!(receiver instanceof ThisExpr)) { return false; } returnName = fa.getNameAsString(); } else { return false; } if (!returnName.equals(propertyName)) { return false; } return true; } else if (propertyKind == PropertyKind.SETTER) { if (!(statement instanceof ExpressionStmt)) { return false; } Expression expr = ((ExpressionStmt) statement).getExpression(); if (!(expr instanceof AssignExpr)) { return false; } AssignExpr assignExpr = (AssignExpr) expr; Expression target = assignExpr.getTarget(); if (!(target instanceof FieldAccessExpr)) { return false; } FieldAccessExpr fa = (FieldAccessExpr) target; Expression receiver = fa.getScope(); if (!(receiver instanceof ThisExpr)) { return false; } if (!fa.getNameAsString().equals(propertyName)) { return false; } if (assignExpr.getOperator() != AssignExpr.Operator.ASSIGN) { return false; } Expression value = assignExpr.getValue(); if (!(value instanceof NameExpr && ((NameExpr) value).getNameAsString().equals(propertyName))) { return false; } return true; } else { throw new Error("unexpected PropertyKind " + propertyKind); } } /** * If the body contains exactly one statement, returns it. Otherwise, returns null. * * @param md a method declaration * @return its sole statement, or null */ private @Nullable Statement getOnlyStatement(MethodDeclaration md) { Optional body = md.getBody(); if (!body.isPresent()) { return null; } NodeList statements = body.get().getStatements(); if (statements.size() != 1) { return null; } return statements.get(0); } /** Visits an AST and collects warnings about missing Javadoc. */ private class RequireJavadocVisitor extends VoidVisitorAdapter { /** The file being visited. Used for constructing error messages. */ private Path filename; /** * Create a new RequireJavadocVisitor. * * @param filename the file being visited; used for diagnostic messages */ public RequireJavadocVisitor(Path filename) { this.filename = filename; } /** * Return a string stating that documentation is missing on the given construct. * * @param node a Java language construct (class, constructor, method, field, etc.) * @param simpleName the construct's simple name, used in diagnostic messages * @return an error message for the given construct */ private String errorString(Node node, String simpleName) { Optional range = node.getRange(); if (range.isPresent()) { Position begin = range.get().begin; Path path = (relative ? (filename.isAbsolute() ? workingDirAbsolute : workingDirRelative) .relativize(filename) : filename); return String.format( "%s:%d:%d: missing documentation for %s", path, begin.line, begin.column, simpleName); } else { return "missing documentation for " + simpleName; } } @Override public void visit(CompilationUnit cu, Void ignore) { Optional opd = cu.getPackageDeclaration(); if (opd.isPresent()) { String packageName = opd.get().getName().asString(); if (shouldNotRequire(packageName)) { return; } Optional optTypeName = cu.getPrimaryTypeName(); if (optTypeName.isPresent() && optTypeName.get().equals("package-info") && !hasJavadocComment(opd.get()) && !hasJavadocComment(cu)) { errors.add(errorString(opd.get(), packageName)); } } if (verbose) { System.out.printf("Visiting compilation unit%n"); } super.visit(cu, ignore); } @Override public void visit(ClassOrInterfaceDeclaration cd, Void ignore) { if (dont_require_private && cd.isPrivate()) { return; } String name = cd.getNameAsString(); if (shouldNotRequire(name)) { return; } if (verbose) { System.out.printf("Visiting type %s%n", name); } if (!dont_require_type && !hasJavadocComment(cd)) { errors.add(errorString(cd, name)); } super.visit(cd, ignore); } @Override public void visit(ConstructorDeclaration cd, Void ignore) { if (dont_require_private && cd.isPrivate()) { return; } if (dont_require_noarg_constructor && cd.getParameters().size() == 0) { return; } String name = cd.getNameAsString(); if (shouldNotRequire(name)) { return; } if (verbose) { System.out.printf("Visiting constructor %s%n", name); } if (!dont_require_method && !hasJavadocComment(cd)) { errors.add(errorString(cd, name)); } super.visit(cd, ignore); } @Override public void visit(MethodDeclaration md, Void ignore) { if (dont_require_private && md.isPrivate()) { return; } if (dont_require_trivial_properties && isTrivialGetterOrSetter(md)) { if (verbose) { System.out.printf("skipping trivial property method %s%n", md.getNameAsString()); } return; } String name = md.getNameAsString(); if (shouldNotRequire(name)) { return; } if (verbose) { System.out.printf("Visiting method %s%n", md.getName()); } if (!dont_require_method && !isOverride(md) && !hasJavadocComment(md)) { errors.add(errorString(md, name)); } super.visit(md, ignore); } @Override public void visit(FieldDeclaration fd, Void ignore) { if (dont_require_private && fd.isPrivate()) { return; } // True if shouldNotRequire is false for at least one of the fields boolean shouldRequire = false; if (verbose) { System.out.printf("Visiting field %s%n", fd.getVariables().get(0).getName()); } boolean hasJavadocComment = hasJavadocComment(fd); for (VariableDeclarator vd : fd.getVariables()) { String name = vd.getNameAsString(); // TODO: Also check the type of the serialVersionUID variable. if (name.equals("serialVersionUID")) { continue; } if (shouldNotRequire(name)) { continue; } shouldRequire = true; if (!dont_require_field && !hasJavadocComment) { errors.add(errorString(vd, name)); } } if (shouldRequire) { super.visit(fd, ignore); } } @Override public void visit(EnumDeclaration ed, Void ignore) { if (dont_require_private && ed.isPrivate()) { return; } String name = ed.getNameAsString(); if (shouldNotRequire(name)) { return; } if (verbose) { System.out.printf("Visiting enum %s%n", name); } if (!dont_require_type && !hasJavadocComment(ed)) { errors.add(errorString(ed, name)); } super.visit(ed, ignore); } @Override public void visit(EnumConstantDeclaration ecd, Void ignore) { String name = ecd.getNameAsString(); if (shouldNotRequire(name)) { return; } if (verbose) { System.out.printf("Visiting enum constant %s%n", name); } if (!dont_require_field && !hasJavadocComment(ecd)) { errors.add(errorString(ecd, name)); } super.visit(ecd, ignore); } @Override public void visit(AnnotationDeclaration ad, Void ignore) { if (dont_require_private && ad.isPrivate()) { return; } String name = ad.getNameAsString(); if (shouldNotRequire(name)) { return; } if (verbose) { System.out.printf("Visiting annotation %s%n", name); } if (!dont_require_type && !hasJavadocComment(ad)) { errors.add(errorString(ad, name)); } super.visit(ad, ignore); } @Override public void visit(AnnotationMemberDeclaration amd, Void ignore) { String name = amd.getNameAsString(); if (shouldNotRequire(name)) { return; } if (verbose) { System.out.printf("Visiting annotation member %s%n", name); } if (!dont_require_method && !hasJavadocComment(amd)) { errors.add(errorString(amd, name)); } super.visit(amd, ignore); } @Override public void visit(RecordDeclaration rd, Void ignore) { if (dont_require_private && rd.isPrivate()) { return; } String name = rd.getNameAsString(); if (shouldNotRequire(name)) { return; } if (verbose) { System.out.printf("Visiting record %s%n", name); } if (!dont_require_type && !hasJavadocComment(rd)) { errors.add(errorString(rd, name)); } // Don't warn about record parameters, because Javadoc requires @param for them in the record // declaration itself. super.visit(rd, ignore); } /** * Return true if this method is annotated with {@code @Override}. * * @param md the method to check for an {@code @Override} annotation * @return true if this method is annotated with {@code @Override} */ private boolean isOverride(MethodDeclaration md) { for (AnnotationExpr anno : md.getAnnotations()) { String annoName = anno.getName().toString(); if (annoName.equals("Override") || annoName.equals("java.lang.Override")) { return true; } } return false; } } /** * Return true if this node has a Javadoc comment. * * @param n the node to check for a Javadoc comment * @return true if this node has a Javadoc comment */ private boolean hasJavadocComment(Node n) { if (n instanceof NodeWithJavadoc && ((NodeWithJavadoc) n).hasJavaDocComment()) { return true; } List orphans = new ArrayList<>(); getOrphanCommentsBeforeThisChildNode(n, orphans); for (Comment orphan : orphans) { if (orphan.isJavadocComment()) { return true; } } Optional oc = n.getComment(); if (oc.isPresent() && (oc.get().isJavadocComment() || oc.get().getContent().startsWith("/**"))) { return true; } return false; } /** * Get "orphan comments": comments before the comment before this node. For example, in * *

{@code
   * /** ... *}{@code /
   * // text 1
   * // text 2
   * void m() { ... }
   * }
* *

the Javadoc comment and {@code // text 1} are an orphan comment, and only {@code // text2} * is associated with the method. * * @param node the node whose orphan comments to collect * @param result the list to add orphan comments to. Is side-effected by this method. The * implementation uses this to minimize the diffs against upstream. */ @SuppressWarnings({ "JdkObsolete", // for LinkedList "interning:not.interned", // element of a list "ReferenceEquality", }) // This implementation is from Randoop's `Minimize.java` file, and before that from JavaParser's // PrettyPrintVisitor.printOrphanCommentsBeforeThisChildNode. The JavaParser maintainers refuse // to provide such functionality in JavaParser proper. private static void getOrphanCommentsBeforeThisChildNode(final Node node, List result) { if (node instanceof Comment) { return; } Node parent = node.getParentNode().orElse(null); if (parent == null) { return; } List everything = new LinkedList<>(parent.getChildNodes()); sortByBeginPosition(everything); int positionOfTheChild = -1; for (int i = 0; i < everything.size(); i++) { if (everything.get(i) == node) { positionOfTheChild = i; } } if (positionOfTheChild == -1) { throw new AssertionError("I am not a child of my parent."); } int positionOfPreviousChild = -1; for (int i = positionOfTheChild - 1; i >= 0 && positionOfPreviousChild == -1; i--) { if (!(everything.get(i) instanceof Comment)) { positionOfPreviousChild = i; } } for (int i = positionOfPreviousChild + 1; i < positionOfTheChild; i++) { Node nodeToPrint = everything.get(i); if (!(nodeToPrint instanceof Comment)) { throw new RuntimeException( "Expected comment, instead " + nodeToPrint.getClass() + ". Position of previous child: " + positionOfPreviousChild + ", position of child " + positionOfTheChild); } result.add((Comment) nodeToPrint); } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy