Merge remote-tracking branch 'upstream/pmd/7.0.x' into clem.type-annots-in-infer
This commit is contained in:
@ -188,6 +188,9 @@ conversions that may be made implicit.
|
||||
* {% rule "java/design/UseUtilityClass" %}: The property `ignoredAnnotations` has been removed.
|
||||
* {% rule "java/design/LawOfDemeter" %}: the rule has a new property `trustRadius`. This defines the maximum degree
|
||||
of trusted data. The default of 1 is the most restrictive.
|
||||
* {% rule "java/documentation/CommentContent" %}: The properties `caseSensitive` and `disallowedTerms` are removed. The
|
||||
new property `fobiddenRegex` can be used now to define the disallowed terms with a single regular
|
||||
expression.
|
||||
|
||||
#### Deprecated Rules
|
||||
|
||||
|
@ -4,7 +4,7 @@
|
||||
|
||||
package net.sourceforge.pmd.lang.ast;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
@ -112,13 +112,19 @@ public interface GenericToken<T extends GenericToken<T>> extends Comparable<T>,
|
||||
*
|
||||
* @throws IllegalArgumentException If the first token does not come before the other token
|
||||
*/
|
||||
static <T extends GenericToken<T>> Iterator<T> range(T from, T to) {
|
||||
static <T extends GenericToken<T>> Iterable<T> range(T from, T to) {
|
||||
if (from.compareTo(to) > 0) {
|
||||
throw new IllegalArgumentException(from + " must come before " + to);
|
||||
}
|
||||
return IteratorUtil.generate(from, t -> t == to ? null : t.getNext());
|
||||
return () -> IteratorUtil.generate(from, t -> t == to ? null : t.getNext());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a stream corresponding to {@link #range(GenericToken, GenericToken)}.
|
||||
*/
|
||||
static <T extends GenericToken<T>> Stream<T> streamRange(T from, T to) {
|
||||
return IteratorUtil.toStream(range(from, to).iterator());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an iterable that enumerates all special tokens belonging
|
||||
|
@ -4,6 +4,7 @@
|
||||
|
||||
package net.sourceforge.pmd.lang.ast.impl.javacc;
|
||||
|
||||
import net.sourceforge.pmd.lang.ast.GenericToken;
|
||||
import net.sourceforge.pmd.lang.ast.TextAvailableNode;
|
||||
import net.sourceforge.pmd.lang.ast.impl.GenericNode;
|
||||
|
||||
@ -24,4 +25,11 @@ public interface JjtreeNode<N extends JjtreeNode<N>> extends GenericNode<N>, Tex
|
||||
|
||||
JavaccToken getLastToken();
|
||||
|
||||
/**
|
||||
* Returns a token range, that includes the first and last token.
|
||||
*/
|
||||
default Iterable<JavaccToken> tokens() {
|
||||
return GenericToken.range(getFirstToken(), getLastToken());
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ import java.nio.ByteBuffer;
|
||||
import java.nio.CharBuffer;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Iterator;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.stream.StreamSupport;
|
||||
@ -20,6 +21,8 @@ import java.util.stream.StreamSupport;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
|
||||
import net.sourceforge.pmd.internal.util.IteratorUtil.AbstractIterator;
|
||||
|
||||
/**
|
||||
* View on a string which doesn't copy the array for subsequence operations.
|
||||
* This view is immutable. Since it uses a string internally it benefits from
|
||||
@ -64,6 +67,12 @@ public final class Chars implements CharSequence {
|
||||
}
|
||||
|
||||
|
||||
/** Whether this slice is the empty string. */
|
||||
public boolean isEmpty() {
|
||||
return len == 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Wraps the given char sequence into a {@link Chars}. This may
|
||||
* call {@link CharSequence#toString()}. If the sequence is already
|
||||
@ -591,6 +600,40 @@ public final class Chars implements CharSequence {
|
||||
return sb;
|
||||
}
|
||||
|
||||
/**
|
||||
* Split this slice into subslices, like {@link String#split(String)},
|
||||
* except it's iterated lazily.
|
||||
*/
|
||||
public Iterable<Chars> splits(Pattern regex) {
|
||||
return () -> new AbstractIterator<Chars>() {
|
||||
final Matcher matcher = regex.matcher(Chars.this);
|
||||
int lastPos = 0;
|
||||
|
||||
private boolean shouldRetry() {
|
||||
if (matcher.find()) {
|
||||
if (matcher.start() == 0 && matcher.end() == 0 && lastPos != len) {
|
||||
return true; // zero length match at the start, we should retry once
|
||||
}
|
||||
setNext(subSequence(lastPos, matcher.start()));
|
||||
lastPos = matcher.end();
|
||||
} else if (lastPos != len) {
|
||||
setNext(subSequence(lastPos, len));
|
||||
} else {
|
||||
done();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void computeNext() {
|
||||
if (matcher.hitEnd()) {
|
||||
done();
|
||||
} else if (shouldRetry()) {
|
||||
shouldRetry();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new reader for the whole contents of this char sequence.
|
||||
|
@ -19,6 +19,10 @@ import net.sourceforge.pmd.lang.document.FileLocation;
|
||||
*/
|
||||
public interface Reportable {
|
||||
|
||||
// todo add optional method to get the nearest node, to implement
|
||||
// suppression that depends on tree structure (eg annotations) for
|
||||
// not just nodes, for example, for comments or individual tokens
|
||||
|
||||
/**
|
||||
* Returns the location at which this element should be reported.
|
||||
*
|
||||
|
@ -16,11 +16,15 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
import java.io.StringWriter;
|
||||
import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import net.sourceforge.pmd.internal.util.IteratorUtil;
|
||||
import net.sourceforge.pmd.util.CollectionUtil;
|
||||
|
||||
/**
|
||||
@ -307,6 +311,39 @@ class CharsTest {
|
||||
assertTrue(chars.contentEquals(Chars.wrap("A_B_C"), true));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSplits() {
|
||||
Chars chars = Chars.wrap("a_a_b_c_s").slice(2, 5);
|
||||
assertEquals("a_b_c", chars.toString());
|
||||
|
||||
testSplits(chars, "_");
|
||||
testSplits(chars, "a");
|
||||
testSplits(chars, "b");
|
||||
testSplits(chars, "c");
|
||||
assertEquals(listOf("", "_b_c"), listSplits(chars, "a"));
|
||||
|
||||
chars = chars.subSequence(1, 5);
|
||||
assertEquals("_b_c", chars.toString());
|
||||
|
||||
assertEquals(listOf("", "b", "c"), listSplits(chars, "_"));
|
||||
|
||||
|
||||
testSplits(Chars.wrap("abc"), "");
|
||||
testSplits(Chars.wrap(""), "");
|
||||
}
|
||||
|
||||
private List<String> listSplits(Chars chars, String regex) {
|
||||
Pattern pattern = Pattern.compile(regex);
|
||||
Iterator<Chars> splits = chars.splits(pattern).iterator();
|
||||
return IteratorUtil.toList(IteratorUtil.map(splits, Chars::toString));
|
||||
}
|
||||
|
||||
private void testSplits(Chars chars, String regex) {
|
||||
List<String> splitList = listSplits(chars, regex);
|
||||
List<String> expected = Arrays.asList(chars.toString().split(regex));
|
||||
assertEquals(expected, splitList, "Split should behave like String#split");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSlice() {
|
||||
// slice is offset + length
|
||||
|
@ -574,7 +574,7 @@ PARSER_END(JavaParserImpl)
|
||||
|
||||
TOKEN_MGR_DECLS :
|
||||
{
|
||||
protected List<Comment> comments = new ArrayList<Comment>();
|
||||
protected List<JavaComment> comments = new ArrayList<JavaComment>();
|
||||
}
|
||||
|
||||
/* WHITE SPACE */
|
||||
@ -600,7 +600,7 @@ SPECIAL_TOKEN :
|
||||
if (startOfNOPMD != -1) {
|
||||
suppressMap.put(matchedToken.getBeginLine(), matchedToken.getImage().substring(startOfNOPMD + suppressMarker.length()));
|
||||
}
|
||||
comments.add(new SingleLineComment(matchedToken));
|
||||
comments.add(new JavaComment(matchedToken));
|
||||
}
|
||||
}
|
||||
|
||||
@ -615,13 +615,13 @@ MORE :
|
||||
<IN_FORMAL_COMMENT>
|
||||
SPECIAL_TOKEN :
|
||||
{
|
||||
<FORMAL_COMMENT: "*/" > { comments.add(new FormalComment(matchedToken)); } : DEFAULT
|
||||
<FORMAL_COMMENT: "*/" > { comments.add(new JavadocComment(matchedToken)); } : DEFAULT
|
||||
}
|
||||
|
||||
<IN_MULTI_LINE_COMMENT>
|
||||
SPECIAL_TOKEN :
|
||||
{
|
||||
<MULTI_LINE_COMMENT: "*/" > { comments.add(new MultiLineComment(matchedToken)); } : DEFAULT
|
||||
<MULTI_LINE_COMMENT: "*/" > { comments.add(new JavaComment(matchedToken)); } : DEFAULT
|
||||
}
|
||||
|
||||
<IN_FORMAL_COMMENT,IN_MULTI_LINE_COMMENT>
|
||||
|
@ -7,7 +7,6 @@ package net.sourceforge.pmd.lang.java.ast;
|
||||
import net.sourceforge.pmd.lang.ast.impl.javacc.JavaccToken;
|
||||
import net.sourceforge.pmd.lang.java.ast.ASTList.ASTMaybeEmptyListOf;
|
||||
import net.sourceforge.pmd.lang.java.ast.InternalInterfaces.AllChildrenAreOfType;
|
||||
import net.sourceforge.pmd.lang.java.ast.internal.JavaAstUtils;
|
||||
|
||||
/**
|
||||
* A block of code. This is a {@linkplain ASTStatement statement} that
|
||||
@ -36,7 +35,7 @@ public final class ASTBlock extends ASTMaybeEmptyListOf<ASTStatement>
|
||||
public boolean containsComment() {
|
||||
JavaccToken t = getLastToken().getPreviousComment();
|
||||
while (t != null) {
|
||||
if (JavaAstUtils.isComment(t)) {
|
||||
if (JavaComment.isComment(t)) {
|
||||
return true;
|
||||
}
|
||||
t = t.getPreviousComment();
|
||||
|
@ -40,14 +40,14 @@ import net.sourceforge.pmd.lang.java.types.ast.LazyTypeResolver;
|
||||
public final class ASTCompilationUnit extends AbstractJavaNode implements JavaNode, GenericNode<JavaNode>, RootNode {
|
||||
|
||||
private LazyTypeResolver lazyTypeResolver;
|
||||
private List<Comment> comments;
|
||||
private List<JavaComment> comments;
|
||||
private AstInfo<ASTCompilationUnit> astInfo;
|
||||
|
||||
ASTCompilationUnit(int id) {
|
||||
super(id);
|
||||
}
|
||||
|
||||
public List<Comment> getComments() {
|
||||
public List<JavaComment> getComments() {
|
||||
return comments;
|
||||
}
|
||||
|
||||
@ -60,7 +60,7 @@ public final class ASTCompilationUnit extends AbstractJavaNode implements JavaNo
|
||||
return astInfo;
|
||||
}
|
||||
|
||||
void setComments(List<Comment> comments) {
|
||||
void setComments(List<JavaComment> comments) {
|
||||
this.comments = comments;
|
||||
}
|
||||
|
||||
|
@ -253,7 +253,7 @@ final class AstDisambiguationPass {
|
||||
* and in the worst case, the original {@link ASTAmbiguousName}.
|
||||
*/
|
||||
private static ASTExpression startResolve(ASTAmbiguousName name, ReferenceCtx ctx, boolean isPackageOrTypeOnly) {
|
||||
Iterator<JavaccToken> tokens = TokenUtils.tokenRange(name);
|
||||
Iterator<JavaccToken> tokens = name.tokens().iterator();
|
||||
JavaccToken firstIdent = tokens.next();
|
||||
TokenUtils.expectKind(firstIdent, JavaTokenKinds.IDENTIFIER);
|
||||
|
||||
|
@ -1,127 +0,0 @@
|
||||
/**
|
||||
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
|
||||
*/
|
||||
|
||||
package net.sourceforge.pmd.lang.java.ast;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import net.sourceforge.pmd.PMD;
|
||||
import net.sourceforge.pmd.lang.ast.impl.javacc.JavaccToken;
|
||||
import net.sourceforge.pmd.lang.document.FileLocation;
|
||||
import net.sourceforge.pmd.reporting.Reportable;
|
||||
|
||||
public abstract class Comment implements Reportable {
|
||||
|
||||
// single regex, that captures: the start of a multi-line comment (/**|/*), the start of a single line comment (//)
|
||||
// or the start of line within a multiline comment (*). It removes the end of the comment (*/) if existing.
|
||||
private static final Pattern COMMENT_LINE_COMBINED = Pattern.compile("^(?://|/\\*\\*?|\\*)?(.*?)(?:\\*/|/)?$");
|
||||
|
||||
// Same as "\\R" - but \\R is only available with java8+
|
||||
static final Pattern NEWLINES_PATTERN = Pattern.compile("\\u000D\\u000A|[\\u000A\\u000B\\u000C\\u000D\\u0085\\u2028\\u2029]");
|
||||
|
||||
private final JavaccToken token;
|
||||
|
||||
protected Comment(JavaccToken t) {
|
||||
this.token = t;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileLocation getReportLocation() {
|
||||
return token.getReportLocation();
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link #getText()}
|
||||
*/
|
||||
@Deprecated
|
||||
public String getImage() {
|
||||
return getToken().getImage();
|
||||
}
|
||||
|
||||
public final JavaccToken getToken() {
|
||||
return token;
|
||||
}
|
||||
|
||||
public final CharSequence getText() {
|
||||
return getToken().getImageCs();
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the comment by removing the leading comment marker (like {@code *}) of each line
|
||||
* as well as the start markers ({@code //}, {@code /*} or {@code /**}
|
||||
* and the end markers (<code>*/</code>).
|
||||
* Also leading and trailing empty lines are removed.
|
||||
*
|
||||
* @return the filtered comment
|
||||
*/
|
||||
public String getFilteredComment() {
|
||||
List<String> lines = multiLinesIn();
|
||||
lines = trim(lines);
|
||||
return StringUtils.join(lines, PMD.EOL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the leading comment marker (like {@code *}) of each line
|
||||
* of the comment as well as the start marker ({@code //}, {@code /*} or {@code /**}
|
||||
* and the end markers (<code>*/</code>).
|
||||
*
|
||||
* @return List of lines of the comments
|
||||
*/
|
||||
private List<String> multiLinesIn() {
|
||||
String[] lines = NEWLINES_PATTERN.split(token.getImageCs());
|
||||
List<String> filteredLines = new ArrayList<>(lines.length);
|
||||
|
||||
for (String rawLine : lines) {
|
||||
String line = rawLine.trim();
|
||||
|
||||
Matcher allMatcher = COMMENT_LINE_COMBINED.matcher(line);
|
||||
if (allMatcher.matches()) {
|
||||
filteredLines.add(allMatcher.group(1).trim());
|
||||
}
|
||||
}
|
||||
|
||||
return filteredLines;
|
||||
}
|
||||
|
||||
/**
|
||||
* Similar to the String.trim() function, this one removes the leading and
|
||||
* trailing empty/blank lines from the line list.
|
||||
*
|
||||
* @param lines the list of lines, which might contain empty lines
|
||||
* @return the lines without leading or trailing blank lines.
|
||||
*/
|
||||
// note: this is only package private, since it is used by CommentUtil. Once CommentUtil is gone, this
|
||||
// can be private
|
||||
static List<String> trim(List<String> lines) {
|
||||
if (lines == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
List<String> result = new ArrayList<>(lines.size());
|
||||
List<String> tempList = new ArrayList<>();
|
||||
boolean foundFirstNonEmptyLine = false;
|
||||
for (String line : lines) {
|
||||
if (StringUtils.isNotBlank(line)) {
|
||||
// new non-empty line: add all previous empty lines occurred before
|
||||
result.addAll(tempList);
|
||||
tempList.clear();
|
||||
result.add(line);
|
||||
|
||||
foundFirstNonEmptyLine = true;
|
||||
} else {
|
||||
if (foundFirstNonEmptyLine) {
|
||||
// add the empty line to a temporary list first
|
||||
tempList.add(line);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
@ -19,22 +19,23 @@ import net.sourceforge.pmd.util.DataMap.SimpleDataKey;
|
||||
|
||||
final class CommentAssignmentPass {
|
||||
|
||||
private static final SimpleDataKey<FormalComment> FORMAL_COMMENT_KEY = DataMap.simpleDataKey("java.comment");
|
||||
private static final SimpleDataKey<JavadocComment> FORMAL_COMMENT_KEY = DataMap.simpleDataKey("java.comment");
|
||||
|
||||
private CommentAssignmentPass() {
|
||||
// utility class
|
||||
}
|
||||
|
||||
static @Nullable FormalComment getComment(JavadocCommentOwner commentOwner) {
|
||||
static @Nullable JavadocComment getComment(JavadocCommentOwner commentOwner) {
|
||||
return commentOwner.getUserMap().get(CommentAssignmentPass.FORMAL_COMMENT_KEY);
|
||||
}
|
||||
|
||||
private static void setComment(JavadocCommentOwner commentableNode, FormalComment comment) {
|
||||
private static void setComment(JavadocCommentOwner commentableNode, JavadocComment comment) {
|
||||
commentableNode.getUserMap().set(FORMAL_COMMENT_KEY, comment);
|
||||
comment.setOwner(commentableNode);
|
||||
}
|
||||
|
||||
public static void assignCommentsToDeclarations(ASTCompilationUnit root) {
|
||||
final List<Comment> comments = root.getComments();
|
||||
final List<JavaComment> comments = root.getComments();
|
||||
if (comments.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
@ -45,11 +46,11 @@ final class CommentAssignmentPass {
|
||||
|
||||
for (JavaccToken maybeComment : GenericToken.previousSpecials(firstToken)) {
|
||||
if (maybeComment.kind == JavaTokenKinds.FORMAL_COMMENT) {
|
||||
FormalComment comment = new FormalComment(maybeComment);
|
||||
JavadocComment comment = new JavadocComment(maybeComment);
|
||||
// deduplicate the comment
|
||||
int idx = Collections.binarySearch(comments, comment, Comparator.comparing(Comment::getReportLocation, FileLocation.COORDS_COMPARATOR));
|
||||
int idx = Collections.binarySearch(comments, comment, Comparator.comparing(JavaComment::getReportLocation, FileLocation.COORDS_COMPARATOR));
|
||||
assert idx >= 0 : "Formal comment not found? " + comment;
|
||||
comment = (FormalComment) comments.get(idx);
|
||||
comment = (JavadocComment) comments.get(idx);
|
||||
|
||||
setComment(commentableNode, comment);
|
||||
continue outer;
|
||||
|
@ -1,47 +0,0 @@
|
||||
/**
|
||||
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
|
||||
*/
|
||||
|
||||
package net.sourceforge.pmd.lang.java.ast;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import net.sourceforge.pmd.lang.ast.impl.javacc.JavaccToken;
|
||||
import net.sourceforge.pmd.lang.java.javadoc.JavadocTag;
|
||||
|
||||
public class FormalComment extends Comment {
|
||||
|
||||
private static final Pattern JAVADOC_TAG = Pattern.compile("@([A-Za-z0-9]+)");
|
||||
|
||||
private final List<JavadocElement> children;
|
||||
|
||||
public FormalComment(JavaccToken t) {
|
||||
super(t);
|
||||
assert t.kind == JavaTokenKinds.FORMAL_COMMENT;
|
||||
this.children = findJavadocs();
|
||||
}
|
||||
|
||||
public List<JavadocElement> getChildren() {
|
||||
return children;
|
||||
}
|
||||
|
||||
private List<JavadocElement> findJavadocs() {
|
||||
List<JavadocElement> kids = new ArrayList<>();
|
||||
|
||||
Matcher javadocTagMatcher = JAVADOC_TAG.matcher(getFilteredComment());
|
||||
while (javadocTagMatcher.find()) {
|
||||
JavadocTag tag = JavadocTag.tagFor(javadocTagMatcher.group(1));
|
||||
int tagStartIndex = javadocTagMatcher.start(1);
|
||||
if (tag != null) {
|
||||
kids.add(new JavadocElement(getToken(), getBeginLine(), getBeginLine(),
|
||||
// TODO valid?
|
||||
tagStartIndex, tagStartIndex + tag.label.length() + 1, tag));
|
||||
}
|
||||
}
|
||||
|
||||
return kids;
|
||||
}
|
||||
}
|
@ -0,0 +1,176 @@
|
||||
/**
|
||||
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
|
||||
*/
|
||||
|
||||
package net.sourceforge.pmd.lang.java.ast;
|
||||
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import net.sourceforge.pmd.internal.util.IteratorUtil;
|
||||
import net.sourceforge.pmd.lang.ast.GenericToken;
|
||||
import net.sourceforge.pmd.lang.ast.impl.javacc.JavaccToken;
|
||||
import net.sourceforge.pmd.lang.ast.impl.javacc.JjtreeNode;
|
||||
import net.sourceforge.pmd.lang.document.Chars;
|
||||
import net.sourceforge.pmd.lang.document.FileLocation;
|
||||
import net.sourceforge.pmd.lang.java.ast.internal.JavaAstUtils;
|
||||
import net.sourceforge.pmd.reporting.Reportable;
|
||||
|
||||
/**
|
||||
* Wraps a comment token to provide some utilities.
|
||||
* This is not a node, it's not part of the tree anywhere,
|
||||
* just convenient.
|
||||
*
|
||||
* <p>This class represents any kind of comment. A specialized subclass
|
||||
* provides more API for Javadoc comments, see {@link JavadocComment}.
|
||||
*/
|
||||
public class JavaComment implements Reportable {
|
||||
//TODO maybe move part of this into pmd core
|
||||
|
||||
private final JavaccToken token;
|
||||
|
||||
JavaComment(JavaccToken t) {
|
||||
this.token = t;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileLocation getReportLocation() {
|
||||
return getToken().getReportLocation();
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link #getText()}
|
||||
*/
|
||||
@Deprecated
|
||||
public String getImage() {
|
||||
return getToken().getImage();
|
||||
}
|
||||
|
||||
/** The token underlying this comment. */
|
||||
public final JavaccToken getToken() {
|
||||
return token;
|
||||
}
|
||||
|
||||
public boolean isSingleLine() {
|
||||
return token.kind == JavaTokenKinds.SINGLE_LINE_COMMENT;
|
||||
}
|
||||
|
||||
public boolean hasJavadocContent() {
|
||||
return token.kind == JavaTokenKinds.FORMAL_COMMENT;
|
||||
}
|
||||
|
||||
/** Returns the full text of the comment. */
|
||||
public Chars getText() {
|
||||
return getToken().getImageCs();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the given token has the kind
|
||||
* of a comment token (there are three such kinds).
|
||||
*/
|
||||
public static boolean isComment(JavaccToken token) {
|
||||
return JavaAstUtils.isComment(token);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the leading comment marker (like {@code *}) of each line
|
||||
* of the comment as well as the start marker ({@code //}, {@code /*} or {@code /**}
|
||||
* and the end markers (<code>*/</code>).
|
||||
*
|
||||
* <p>Empty lines are removed.
|
||||
*
|
||||
* @return List of lines of the comments
|
||||
*/
|
||||
public Iterable<Chars> getFilteredLines() {
|
||||
return getFilteredLines(false);
|
||||
}
|
||||
|
||||
public Iterable<Chars> getFilteredLines(boolean preserveEmptyLines) {
|
||||
if (preserveEmptyLines) {
|
||||
return () -> IteratorUtil.map(getText().lines().iterator(), JavaComment::removeCommentMarkup);
|
||||
} else {
|
||||
return () -> IteratorUtil.mapNotNull(
|
||||
getText().lines().iterator(),
|
||||
line -> {
|
||||
line = removeCommentMarkup(line);
|
||||
return line.isEmpty() ? null : line;
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* True if this is a comment delimiter or an asterisk. This
|
||||
* tests the whole parameter and not a prefix/suffix.
|
||||
*/
|
||||
@SuppressWarnings("PMD.LiteralsFirstInComparisons") // a fp
|
||||
public static boolean isMarkupWord(Chars word) {
|
||||
return word.length() <= 3
|
||||
&& (word.contentEquals("*")
|
||||
|| word.contentEquals("//")
|
||||
|| word.contentEquals("/*")
|
||||
|| word.contentEquals("*/")
|
||||
|| word.contentEquals("/**"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Trim the start of the provided line to remove a comment
|
||||
* markup opener ({@code //, /*, /**, *}) or closer {@code * /}.
|
||||
*/
|
||||
private static Chars removeCommentMarkup(Chars line) {
|
||||
line = line.trim().removeSuffix("*/");
|
||||
int subseqFrom = 0;
|
||||
if (line.startsWith('/', 0)) {
|
||||
if (line.startsWith("**", 1)) {
|
||||
subseqFrom = 3;
|
||||
} else if (line.startsWith('/', 1)
|
||||
|| line.startsWith('*', 1)) {
|
||||
subseqFrom = 2;
|
||||
}
|
||||
} else if (line.startsWith('*', 0)) {
|
||||
subseqFrom = 1;
|
||||
}
|
||||
return line.subSequence(subseqFrom, line.length()).trim();
|
||||
}
|
||||
|
||||
private static Stream<JavaccToken> getSpecialCommentsIn(JjtreeNode<?> node) {
|
||||
return GenericToken.streamRange(node.getFirstToken(), node.getLastToken())
|
||||
.flatMap(it -> IteratorUtil.toStream(GenericToken.previousSpecials(it).iterator()));
|
||||
}
|
||||
|
||||
public static Stream<JavaComment> getLeadingComments(JavaNode node) {
|
||||
if (node instanceof AccessNode) {
|
||||
node = ((AccessNode) node).getModifiers();
|
||||
}
|
||||
return getSpecialCommentsIn(node).filter(JavaComment::isComment)
|
||||
.map(JavaComment::toComment);
|
||||
}
|
||||
|
||||
private static JavaComment toComment(JavaccToken tok) {
|
||||
switch (tok.kind) {
|
||||
case JavaTokenKinds.FORMAL_COMMENT:
|
||||
return new JavadocComment(tok);
|
||||
case JavaTokenKinds.MULTI_LINE_COMMENT:
|
||||
case JavaTokenKinds.SINGLE_LINE_COMMENT:
|
||||
return new JavaComment(tok);
|
||||
default:
|
||||
throw new IllegalArgumentException("Token is not a comment: " + tok);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (!(o instanceof JavaComment)) {
|
||||
return false;
|
||||
}
|
||||
JavaComment that = (JavaComment) o;
|
||||
return token.equals(that.token);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return token.hashCode();
|
||||
}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
/**
|
||||
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
|
||||
*/
|
||||
|
||||
package net.sourceforge.pmd.lang.java.ast;
|
||||
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
import net.sourceforge.pmd.lang.ast.impl.javacc.JavaccToken;
|
||||
|
||||
/**
|
||||
* A {@link JavaComment} that has Javadoc content.
|
||||
*/
|
||||
public final class JavadocComment extends JavaComment {
|
||||
|
||||
private JavadocCommentOwner owner;
|
||||
|
||||
JavadocComment(JavaccToken t) {
|
||||
super(t);
|
||||
assert t.kind == JavaTokenKinds.FORMAL_COMMENT;
|
||||
}
|
||||
|
||||
void setOwner(JavadocCommentOwner owner) {
|
||||
this.owner = owner;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the owner of this comment. Null if this comment is
|
||||
* misplaced.
|
||||
*/
|
||||
public @Nullable JavadocCommentOwner getOwner() {
|
||||
return owner;
|
||||
}
|
||||
|
||||
}
|
@ -16,7 +16,7 @@ public interface JavadocCommentOwner extends JavaNode {
|
||||
* Returns the javadoc comment that applies to this declaration. If
|
||||
* there is none, returns null.
|
||||
*/
|
||||
default @Nullable FormalComment getJavadocComment() {
|
||||
default @Nullable JavadocComment getJavadocComment() {
|
||||
return CommentAssignmentPass.getComment(this);
|
||||
}
|
||||
|
||||
|
@ -1,32 +0,0 @@
|
||||
/**
|
||||
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
|
||||
*/
|
||||
|
||||
package net.sourceforge.pmd.lang.java.ast;
|
||||
|
||||
import net.sourceforge.pmd.lang.ast.impl.javacc.JavaccToken;
|
||||
import net.sourceforge.pmd.lang.document.FileLocation;
|
||||
import net.sourceforge.pmd.lang.document.TextRange2d;
|
||||
import net.sourceforge.pmd.lang.java.javadoc.JavadocTag;
|
||||
|
||||
public class JavadocElement extends Comment {
|
||||
|
||||
private final JavadocTag tag;
|
||||
private final FileLocation reportLoc;
|
||||
|
||||
public JavadocElement(JavaccToken t, int theBeginLine, int theEndLine, int theBeginColumn, int theEndColumn, JavadocTag theTag) {
|
||||
super(t);
|
||||
this.tag = theTag;
|
||||
this.reportLoc = FileLocation.range("TODO", TextRange2d.range2d(theBeginLine, theBeginColumn, theEndLine, theEndColumn));
|
||||
}
|
||||
|
||||
public JavadocTag tag() {
|
||||
return tag;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileLocation getReportLocation() {
|
||||
return reportLoc;
|
||||
}
|
||||
|
||||
}
|
@ -1,15 +0,0 @@
|
||||
/**
|
||||
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
|
||||
*/
|
||||
|
||||
package net.sourceforge.pmd.lang.java.ast;
|
||||
|
||||
import net.sourceforge.pmd.lang.ast.impl.javacc.JavaccToken;
|
||||
|
||||
public class MultiLineComment extends Comment {
|
||||
|
||||
public MultiLineComment(JavaccToken t) {
|
||||
super(t);
|
||||
}
|
||||
|
||||
}
|
@ -1,15 +0,0 @@
|
||||
/**
|
||||
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
|
||||
*/
|
||||
|
||||
package net.sourceforge.pmd.lang.java.ast;
|
||||
|
||||
import net.sourceforge.pmd.lang.ast.impl.javacc.JavaccToken;
|
||||
|
||||
public class SingleLineComment extends Comment {
|
||||
|
||||
public SingleLineComment(JavaccToken t) {
|
||||
super(t);
|
||||
}
|
||||
|
||||
}
|
@ -4,7 +4,6 @@
|
||||
|
||||
package net.sourceforge.pmd.lang.java.ast;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Objects;
|
||||
|
||||
@ -25,19 +24,6 @@ final class TokenUtils {
|
||||
|
||||
}
|
||||
|
||||
public static <T extends GenericToken<T>> int compare(GenericToken<T> t1, GenericToken<T> t2) {
|
||||
return t1.getRegion().compareTo(t2.getRegion());
|
||||
}
|
||||
|
||||
public static <T extends GenericToken<T>> boolean isBefore(GenericToken<T> t1, GenericToken<T> t2) {
|
||||
return t1.getRegion().compareTo(t2.getRegion()) < 0;
|
||||
}
|
||||
|
||||
public static <T extends GenericToken<T>> boolean isAfter(GenericToken<T> t1, GenericToken<T> t2) {
|
||||
return t1.getRegion().compareTo(t2.getRegion()) > 0;
|
||||
}
|
||||
|
||||
|
||||
public static <T extends GenericToken<T>> T nthFollower(T token, int n) {
|
||||
if (n < 0) {
|
||||
throw new IllegalArgumentException("Negative index?");
|
||||
@ -103,7 +89,4 @@ final class TokenUtils {
|
||||
assert token.kind == kind : "Expected " + token.getDocument().describeKind(kind) + ", got " + token;
|
||||
}
|
||||
|
||||
public static Iterator<JavaccToken> tokenRange(JavaNode node) {
|
||||
return GenericToken.range(node.getFirstToken(), node.getLastToken());
|
||||
}
|
||||
}
|
||||
|
@ -348,8 +348,8 @@ public final class JavaAstUtils {
|
||||
// Since type and variable names obscure one another,
|
||||
// it's ok to use a single renaming function.
|
||||
|
||||
Iterator<JavaccToken> thisIt = GenericToken.range(node.getFirstToken(), node.getLastToken());
|
||||
Iterator<JavaccToken> thatIt = GenericToken.range(other.getFirstToken(), other.getLastToken());
|
||||
Iterator<JavaccToken> thisIt = GenericToken.range(node.getFirstToken(), node.getLastToken()).iterator();
|
||||
Iterator<JavaccToken> thatIt = GenericToken.range(other.getFirstToken(), other.getLastToken()).iterator();
|
||||
int lastKind = 0;
|
||||
while (thisIt.hasNext()) {
|
||||
if (!thatIt.hasNext()) {
|
||||
|
@ -4,16 +4,13 @@
|
||||
|
||||
package net.sourceforge.pmd.lang.java.rule.codestyle;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import net.sourceforge.pmd.RuleContext;
|
||||
import net.sourceforge.pmd.lang.java.ast.ASTAnnotationTypeDeclaration;
|
||||
import net.sourceforge.pmd.lang.java.ast.ASTAnyTypeDeclaration;
|
||||
import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceDeclaration;
|
||||
import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit;
|
||||
import net.sourceforge.pmd.lang.java.ast.ASTConstructorDeclaration;
|
||||
import net.sourceforge.pmd.lang.java.ast.ASTEnumDeclaration;
|
||||
import net.sourceforge.pmd.lang.java.ast.ASTFieldDeclaration;
|
||||
@ -21,7 +18,7 @@ import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration;
|
||||
import net.sourceforge.pmd.lang.java.ast.ASTRecordDeclaration;
|
||||
import net.sourceforge.pmd.lang.java.ast.AccessNode;
|
||||
import net.sourceforge.pmd.lang.java.ast.AccessNode.Visibility;
|
||||
import net.sourceforge.pmd.lang.java.ast.Comment;
|
||||
import net.sourceforge.pmd.lang.java.ast.JavaComment;
|
||||
import net.sourceforge.pmd.lang.java.ast.internal.PrettyPrintingUtil;
|
||||
import net.sourceforge.pmd.lang.java.rule.AbstractJavaRulechainRule;
|
||||
import net.sourceforge.pmd.lang.java.rule.internal.JavaPropertyUtil;
|
||||
@ -38,6 +35,18 @@ import net.sourceforge.pmd.properties.PropertyFactory;
|
||||
*/
|
||||
public class CommentDefaultAccessModifierRule extends AbstractJavaRulechainRule {
|
||||
|
||||
private static final PropertyDescriptor<Pattern> REGEX_DESCRIPTOR =
|
||||
PropertyFactory.regexProperty("regex")
|
||||
.desc("Regular expression")
|
||||
.defaultValue("\\/\\*\\s*(default|package)\\s*\\*\\/")
|
||||
.build();
|
||||
|
||||
private static final PropertyDescriptor<Boolean> TOP_LEVEL_TYPES =
|
||||
PropertyFactory.booleanProperty("checkTopLevelTypes")
|
||||
.desc("Check for default access modifier in top-level classes, annotations, and enums")
|
||||
.defaultValue(false)
|
||||
.build();
|
||||
|
||||
private static final PropertyDescriptor<List<String>> IGNORED_ANNOTS =
|
||||
JavaPropertyUtil.ignoredAnnotationsDescriptor(
|
||||
"com.google.common.annotations.VisibleForTesting",
|
||||
@ -53,35 +62,15 @@ public class CommentDefaultAccessModifierRule extends AbstractJavaRulechainRule
|
||||
"org.junit.jupiter.api.AfterAll"
|
||||
);
|
||||
|
||||
private static final PropertyDescriptor<Pattern> REGEX_DESCRIPTOR =
|
||||
PropertyFactory.regexProperty("regex")
|
||||
.desc("Regular expression")
|
||||
.defaultValue("\\/\\*\\s*(default|package)\\s*\\*\\/").build();
|
||||
private static final PropertyDescriptor<Boolean> TOP_LEVEL_TYPES =
|
||||
PropertyFactory.booleanProperty("checkTopLevelTypes")
|
||||
.desc("Check for default access modifier in top-level classes, annotations, and enums")
|
||||
.defaultValue(false).build();
|
||||
private static final String MESSAGE = "To avoid mistakes add a comment at the beginning of the {0} {1} if you want a default access modifier";
|
||||
private final Set<Integer> interestingLineNumberComments = new HashSet<>();
|
||||
|
||||
public CommentDefaultAccessModifierRule() {
|
||||
super(ASTCompilationUnit.class, ASTMethodDeclaration.class, ASTAnyTypeDeclaration.class,
|
||||
ASTConstructorDeclaration.class, ASTFieldDeclaration.class);
|
||||
super(ASTMethodDeclaration.class, ASTAnyTypeDeclaration.class,
|
||||
ASTConstructorDeclaration.class, ASTFieldDeclaration.class);
|
||||
definePropertyDescriptor(IGNORED_ANNOTS);
|
||||
definePropertyDescriptor(REGEX_DESCRIPTOR);
|
||||
definePropertyDescriptor(TOP_LEVEL_TYPES);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object visit(final ASTCompilationUnit node, final Object data) {
|
||||
interestingLineNumberComments.clear();
|
||||
for (final Comment comment : node.getComments()) {
|
||||
if (getProperty(REGEX_DESCRIPTOR).matcher(comment.getText()).matches()) {
|
||||
interestingLineNumberComments.add(comment.getBeginLine());
|
||||
}
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object visit(final ASTMethodDeclaration decl, final Object data) {
|
||||
@ -141,7 +130,7 @@ public class CommentDefaultAccessModifierRule extends AbstractJavaRulechainRule
|
||||
|
||||
|
||||
private void report(RuleContext ctx, AccessNode decl, String kind, String signature) {
|
||||
ctx.addViolationWithMessage(decl, MESSAGE, kind, signature);
|
||||
ctx.addViolation(decl, kind, signature);
|
||||
}
|
||||
|
||||
private boolean shouldReportNonTopLevel(final AccessNode decl) {
|
||||
@ -158,16 +147,22 @@ public class CommentDefaultAccessModifierRule extends AbstractJavaRulechainRule
|
||||
return decl.getVisibility() == Visibility.V_PACKAGE
|
||||
// if is a default access modifier check if there is a comment
|
||||
// in this line
|
||||
&& !interestingLineNumberComments.contains(decl.getBeginLine());
|
||||
&& !hasOkComment(decl);
|
||||
}
|
||||
|
||||
private boolean isNotIgnored(AccessNode decl) {
|
||||
return getProperty(IGNORED_ANNOTS).stream().noneMatch(decl::isAnnotationPresent);
|
||||
}
|
||||
|
||||
private boolean hasOkComment(AccessNode node) {
|
||||
Pattern regex = getProperty(REGEX_DESCRIPTOR);
|
||||
return JavaComment.getLeadingComments(node)
|
||||
.anyMatch(it -> regex.matcher(it.getText()).matches());
|
||||
}
|
||||
|
||||
private boolean shouldReportTypeDeclaration(ASTAnyTypeDeclaration decl) {
|
||||
// don't report on interfaces
|
||||
return !decl.isRegularInterface()
|
||||
return !(decl.isRegularInterface() && !decl.isAnnotation())
|
||||
&& isMissingComment(decl)
|
||||
&& isNotIgnored(decl)
|
||||
// either nested or top level and we should check it
|
||||
|
@ -20,8 +20,8 @@ import net.sourceforge.pmd.lang.java.ast.ASTMethodCall;
|
||||
import net.sourceforge.pmd.lang.java.ast.ASTSwitchLabel;
|
||||
import net.sourceforge.pmd.lang.java.ast.ASTSwitchLike;
|
||||
import net.sourceforge.pmd.lang.java.ast.ASTVariableAccess;
|
||||
import net.sourceforge.pmd.lang.java.ast.Comment;
|
||||
import net.sourceforge.pmd.lang.java.ast.FormalComment;
|
||||
import net.sourceforge.pmd.lang.java.ast.JavaComment;
|
||||
import net.sourceforge.pmd.lang.java.ast.JavadocComment;
|
||||
import net.sourceforge.pmd.lang.java.ast.internal.PrettyPrintingUtil;
|
||||
import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule;
|
||||
import net.sourceforge.pmd.lang.java.symbols.JAccessibleElementSymbol;
|
||||
@ -170,8 +170,8 @@ public class UnnecessaryImportRule extends AbstractJavaRule {
|
||||
|
||||
private void visitComments(ASTCompilationUnit node) {
|
||||
// todo improve that when we have a javadoc parser
|
||||
for (Comment comment : node.getComments()) {
|
||||
if (!(comment instanceof FormalComment)) {
|
||||
for (JavaComment comment : node.getComments()) {
|
||||
if (!(comment instanceof JavadocComment)) {
|
||||
continue;
|
||||
}
|
||||
for (Pattern p : PATTERNS) {
|
||||
@ -245,7 +245,7 @@ public class UnnecessaryImportRule extends AbstractJavaRule {
|
||||
if (node.getQualifier() == null) {
|
||||
OverloadSelectionResult overload = node.getOverloadSelectionInfo();
|
||||
if (overload.isFailed()) {
|
||||
return null; // todo we're erring towards FPs
|
||||
return null; // todo we're erring towards FPs
|
||||
}
|
||||
|
||||
ShadowChainIterator<JMethodSig, ScopeInfo> scopeIter =
|
||||
@ -307,7 +307,7 @@ public class UnnecessaryImportRule extends AbstractJavaRule {
|
||||
if (!it.isStatic() && onlyStatic) {
|
||||
return false;
|
||||
}
|
||||
// This is the class that contains the symbol
|
||||
// This is the class that contains the symbol
|
||||
// we're looking for.
|
||||
// We have to test whether this symbol is contained
|
||||
// by the imported type or package.
|
||||
|
@ -22,9 +22,11 @@ import java.util.stream.Stream;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
import net.sourceforge.pmd.RuleContext;
|
||||
import net.sourceforge.pmd.lang.ast.Node;
|
||||
import net.sourceforge.pmd.lang.java.ast.ASTArgumentList;
|
||||
import net.sourceforge.pmd.lang.java.ast.ASTArrayAccess;
|
||||
import net.sourceforge.pmd.lang.java.ast.ASTAssignableExpr.ASTNamedReferenceExpr;
|
||||
import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit;
|
||||
import net.sourceforge.pmd.lang.java.ast.ASTConstructorCall;
|
||||
import net.sourceforge.pmd.lang.java.ast.ASTExpression;
|
||||
import net.sourceforge.pmd.lang.java.ast.ASTExpressionStatement;
|
||||
@ -39,7 +41,7 @@ import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclarator;
|
||||
import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclaratorId;
|
||||
import net.sourceforge.pmd.lang.java.ast.QualifiableExpression;
|
||||
import net.sourceforge.pmd.lang.java.ast.internal.PrettyPrintingUtil;
|
||||
import net.sourceforge.pmd.lang.java.rule.AbstractJavaRulechainRule;
|
||||
import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule;
|
||||
import net.sourceforge.pmd.lang.java.rule.internal.DataflowPass;
|
||||
import net.sourceforge.pmd.lang.java.rule.internal.DataflowPass.AssignmentEntry;
|
||||
import net.sourceforge.pmd.lang.java.rule.internal.DataflowPass.DataflowResult;
|
||||
@ -73,7 +75,7 @@ import net.sourceforge.pmd.properties.PropertyFactory;
|
||||
* @since 5.0
|
||||
*
|
||||
*/
|
||||
public class LawOfDemeterRule extends AbstractJavaRulechainRule {
|
||||
public class LawOfDemeterRule extends AbstractJavaRule {
|
||||
|
||||
|
||||
private static final PropertyDescriptor<Integer> TRUST_RADIUS =
|
||||
@ -86,7 +88,6 @@ public class LawOfDemeterRule extends AbstractJavaRulechainRule {
|
||||
private static final String METHOD_CALL_ON_FOREIGN_VALUE = "Call to `{0}` on foreign value `{1}` (degree {2})";
|
||||
|
||||
public LawOfDemeterRule() {
|
||||
super(ASTMethodCall.class, ASTFieldAccess.class);
|
||||
definePropertyDescriptor(TRUST_RADIUS);
|
||||
}
|
||||
|
||||
@ -100,7 +101,19 @@ public class LawOfDemeterRule extends AbstractJavaRulechainRule {
|
||||
private final Map<ASTExpression, Integer> degreeCache = new LinkedHashMap<>();
|
||||
|
||||
@Override
|
||||
public void end(RuleContext ctx) {
|
||||
public void apply(Node target, RuleContext ctx) {
|
||||
degreeCache.clear();
|
||||
// reimplement our own traversal instead of using the rulechain,
|
||||
// so that we have a stable traversal order.
|
||||
((ASTCompilationUnit) target)
|
||||
.descendants().crossFindBoundaries()
|
||||
.forEach(it -> {
|
||||
if (it instanceof ASTMethodCall) {
|
||||
this.visit((ASTMethodCall) it, ctx);
|
||||
} else if (it instanceof ASTFieldAccess) {
|
||||
this.visit((ASTFieldAccess) it, ctx);
|
||||
}
|
||||
});
|
||||
degreeCache.clear(); // avoid memory leak
|
||||
}
|
||||
|
||||
|
@ -4,152 +4,74 @@
|
||||
|
||||
package net.sourceforge.pmd.lang.java.rule.documentation;
|
||||
|
||||
import static net.sourceforge.pmd.properties.PropertyFactory.booleanProperty;
|
||||
import static net.sourceforge.pmd.properties.PropertyFactory.stringListProperty;
|
||||
import static net.sourceforge.pmd.properties.PropertyFactory.regexProperty;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import net.sourceforge.pmd.RuleContext;
|
||||
import net.sourceforge.pmd.lang.document.Chars;
|
||||
import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit;
|
||||
import net.sourceforge.pmd.lang.java.ast.Comment;
|
||||
import net.sourceforge.pmd.lang.java.ast.JavaComment;
|
||||
import net.sourceforge.pmd.lang.java.rule.AbstractJavaRulechainRule;
|
||||
import net.sourceforge.pmd.properties.PropertyDescriptor;
|
||||
import net.sourceforge.pmd.properties.PropertySource;
|
||||
|
||||
/**
|
||||
* A rule that checks for illegal words in the comment text.
|
||||
*
|
||||
* TODO implement regex option
|
||||
*
|
||||
* @author Brian Remedios
|
||||
*/
|
||||
public class CommentContentRule extends AbstractJavaRulechainRule {
|
||||
|
||||
private boolean caseSensitive;
|
||||
private List<String> originalBadWords;
|
||||
private List<String> currentBadWords;
|
||||
|
||||
// ignored when property above == True
|
||||
public static final PropertyDescriptor<Boolean> CASE_SENSITIVE_DESCRIPTOR = booleanProperty("caseSensitive").defaultValue(false).desc("Case sensitive").build();
|
||||
|
||||
public static final PropertyDescriptor<List<String>> DISSALLOWED_TERMS_DESCRIPTOR =
|
||||
stringListProperty("disallowedTerms")
|
||||
.desc("Illegal terms or phrases")
|
||||
.defaultValues("idiot", "jerk").build(); // TODO make blank property? or add more defaults?
|
||||
|
||||
private static final Set<PropertyDescriptor<?>> NON_REGEX_PROPERTIES;
|
||||
|
||||
static {
|
||||
NON_REGEX_PROPERTIES = new HashSet<>(1);
|
||||
NON_REGEX_PROPERTIES.add(CASE_SENSITIVE_DESCRIPTOR);
|
||||
}
|
||||
private static final PropertyDescriptor<Pattern> DISSALLOWED_TERMS_DESCRIPTOR =
|
||||
regexProperty("forbiddenRegex")
|
||||
.desc("Illegal terms or phrases")
|
||||
.defaultValue("idiot|jerk").build();
|
||||
|
||||
public CommentContentRule() {
|
||||
super(ASTCompilationUnit.class);
|
||||
definePropertyDescriptor(CASE_SENSITIVE_DESCRIPTOR);
|
||||
definePropertyDescriptor(DISSALLOWED_TERMS_DESCRIPTOR);
|
||||
}
|
||||
|
||||
/**
|
||||
* Capture values and perform all the case-conversions once per run
|
||||
*/
|
||||
@Override
|
||||
public void start(RuleContext ctx) {
|
||||
originalBadWords = getProperty(DISSALLOWED_TERMS_DESCRIPTOR);
|
||||
caseSensitive = getProperty(CASE_SENSITIVE_DESCRIPTOR);
|
||||
if (caseSensitive) {
|
||||
currentBadWords = originalBadWords;
|
||||
} else {
|
||||
currentBadWords = new ArrayList<>();
|
||||
for (String badWord : originalBadWords) {
|
||||
currentBadWords.add(badWord.toUpperCase(Locale.ROOT));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private List<String> illegalTermsIn(Comment comment) {
|
||||
|
||||
if (currentBadWords.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
String commentText = comment.getFilteredComment();
|
||||
if (StringUtils.isBlank(commentText)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
if (!caseSensitive) {
|
||||
commentText = commentText.toUpperCase(Locale.ROOT);
|
||||
}
|
||||
|
||||
List<String> foundWords = new ArrayList<>();
|
||||
|
||||
for (int i = 0; i < currentBadWords.size(); i++) {
|
||||
if (commentText.contains(currentBadWords.get(i))) {
|
||||
foundWords.add(originalBadWords.get(i));
|
||||
}
|
||||
}
|
||||
|
||||
return foundWords;
|
||||
}
|
||||
|
||||
private String errorMsgFor(List<String> badWords) {
|
||||
StringBuilder msg = new StringBuilder(this.getMessage()).append(": ");
|
||||
if (badWords.size() == 1) {
|
||||
msg.append("Invalid term: '").append(badWords.get(0)).append('\'');
|
||||
} else {
|
||||
msg.append("Invalid terms: '");
|
||||
msg.append(badWords.get(0));
|
||||
for (int i = 1; i < badWords.size(); i++) {
|
||||
msg.append("', '").append(badWords.get(i));
|
||||
}
|
||||
msg.append('\'');
|
||||
}
|
||||
return msg.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object visit(ASTCompilationUnit cUnit, Object data) {
|
||||
|
||||
// NPE patch: Eclipse plugin doesn't call start() at onset?
|
||||
if (currentBadWords == null) {
|
||||
start(null);
|
||||
}
|
||||
Pattern pattern = getProperty(DISSALLOWED_TERMS_DESCRIPTOR);
|
||||
|
||||
for (Comment comment : cUnit.getComments()) {
|
||||
List<String> badWords = illegalTermsIn(comment);
|
||||
if (badWords.isEmpty()) {
|
||||
for (JavaComment comment : cUnit.getComments()) {
|
||||
List<Integer> lineNumbers = illegalTermsIn(comment, pattern);
|
||||
if (lineNumbers.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
addViolationWithMessage(data, cUnit, errorMsgFor(badWords), comment.getBeginLine(), comment.getEndLine());
|
||||
int offset = comment.getBeginLine();
|
||||
for (int lineNum : lineNumbers) {
|
||||
int lineNumWithOff = lineNum + offset;
|
||||
addViolationWithMessage(
|
||||
data,
|
||||
cUnit,
|
||||
"Line matches forbidden content regex (" + pattern.pattern() + ")",
|
||||
lineNumWithOff,
|
||||
lineNumWithOff
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private boolean hasDisallowedTerms() {
|
||||
List<String> terms = getProperty(DISSALLOWED_TERMS_DESCRIPTOR);
|
||||
return !terms.isEmpty();
|
||||
private List<Integer> illegalTermsIn(JavaComment comment, Pattern violationRegex) {
|
||||
|
||||
List<Integer> lines = new ArrayList<>();
|
||||
int i = 0;
|
||||
for (Chars line : comment.getFilteredLines(true)) {
|
||||
if (violationRegex.matcher(line).find()) {
|
||||
lines.add(i);
|
||||
}
|
||||
}
|
||||
|
||||
return lines;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public boolean hasDissallowedTerms() {
|
||||
return this.hasDisallowedTerms();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see PropertySource#dysfunctionReason()
|
||||
*/
|
||||
@Override
|
||||
public String dysfunctionReason() {
|
||||
return hasDissallowedTerms() ? null : "No disallowed terms specified";
|
||||
}
|
||||
}
|
||||
|
@ -13,12 +13,12 @@ import java.util.Set;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import net.sourceforge.pmd.lang.document.Chars;
|
||||
import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit;
|
||||
import net.sourceforge.pmd.lang.java.ast.Comment;
|
||||
import net.sourceforge.pmd.lang.java.ast.JavaComment;
|
||||
import net.sourceforge.pmd.lang.java.rule.AbstractJavaRulechainRule;
|
||||
import net.sourceforge.pmd.properties.PropertyDescriptor;
|
||||
import net.sourceforge.pmd.properties.PropertyFactory;
|
||||
import net.sourceforge.pmd.util.StringUtil;
|
||||
|
||||
/**
|
||||
* A rule to manage those who just can't shut up...
|
||||
@ -33,13 +33,15 @@ public class CommentSizeRule extends AbstractJavaRulechainRule {
|
||||
.require(positive()).defaultValue(6).build();
|
||||
|
||||
public static final PropertyDescriptor<Integer> MAX_LINE_LENGTH
|
||||
= PropertyFactory.intProperty("maxLineLength")
|
||||
.desc("Maximum line length")
|
||||
.require(positive()).defaultValue(80).build();
|
||||
= PropertyFactory.intProperty("maxLineLength")
|
||||
.desc("Maximum line length")
|
||||
.require(positive()).defaultValue(80).build();
|
||||
|
||||
private static final String CR = "\n";
|
||||
|
||||
static final Set<String> IGNORED_LINES = setOf("//", "/*", "/**", "*", "*/");
|
||||
static final Set<Chars> IGNORED_LINES = setOf(Chars.wrap("//"),
|
||||
Chars.wrap("/*"),
|
||||
Chars.wrap("/**"),
|
||||
Chars.wrap("*"),
|
||||
Chars.wrap("*/"));
|
||||
|
||||
public CommentSizeRule() {
|
||||
super(ASTCompilationUnit.class);
|
||||
@ -47,64 +49,14 @@ public class CommentSizeRule extends AbstractJavaRulechainRule {
|
||||
definePropertyDescriptor(MAX_LINE_LENGTH);
|
||||
}
|
||||
|
||||
private static boolean hasRealText(String line) {
|
||||
return !StringUtils.isBlank(line) && !IGNORED_LINES.contains(line.trim());
|
||||
}
|
||||
|
||||
private boolean hasTooManyLines(Comment comment) {
|
||||
|
||||
String[] lines = comment.getImage().split(CR);
|
||||
|
||||
int start = 0; // start from top
|
||||
for (; start < lines.length; start++) {
|
||||
if (hasRealText(lines[start])) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int end = lines.length - 1; // go up from bottom
|
||||
for (; end > 0; end--) {
|
||||
if (hasRealText(lines[end])) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int lineCount = end - start + 1;
|
||||
|
||||
return lineCount > getProperty(MAX_LINES);
|
||||
}
|
||||
|
||||
private String withoutCommentMarkup(String text) {
|
||||
|
||||
return StringUtil.withoutPrefixes(text.trim(), "//", "*", "/**");
|
||||
}
|
||||
|
||||
private List<Integer> overLengthLineIndicesIn(Comment comment) {
|
||||
|
||||
int maxLength = getProperty(MAX_LINE_LENGTH);
|
||||
|
||||
List<Integer> indices = new ArrayList<>();
|
||||
String[] lines = comment.getImage().split(CR);
|
||||
|
||||
int offset = comment.getBeginLine();
|
||||
|
||||
for (int i = 0; i < lines.length; i++) {
|
||||
String cleaned = withoutCommentMarkup(lines[i]);
|
||||
if (cleaned.length() > maxLength) {
|
||||
indices.add(i + offset);
|
||||
}
|
||||
}
|
||||
|
||||
return indices;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object visit(ASTCompilationUnit cUnit, Object data) {
|
||||
|
||||
for (Comment comment : cUnit.getComments()) {
|
||||
for (JavaComment comment : cUnit.getComments()) {
|
||||
if (hasTooManyLines(comment)) {
|
||||
addViolationWithMessage(data, cUnit, this.getMessage() + ": Too many lines", comment.getBeginLine(),
|
||||
comment.getEndLine());
|
||||
addViolationWithMessage(data, cUnit, this.getMessage()
|
||||
+ ": Too many lines", comment.getBeginLine(), comment.getEndLine());
|
||||
}
|
||||
|
||||
List<Integer> lineNumbers = overLengthLineIndicesIn(comment);
|
||||
@ -112,11 +64,60 @@ public class CommentSizeRule extends AbstractJavaRulechainRule {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (Integer lineNum : lineNumbers) {
|
||||
addViolationWithMessage(data, cUnit, this.getMessage() + ": Line too long", lineNum, lineNum);
|
||||
int offset = comment.getBeginLine();
|
||||
for (int lineNum : lineNumbers) {
|
||||
int lineNumWithOff = lineNum + offset;
|
||||
addViolationWithMessage(
|
||||
data,
|
||||
cUnit,
|
||||
this.getMessage() + ": Line too long",
|
||||
lineNumWithOff,
|
||||
lineNum
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static boolean hasRealText(Chars line) {
|
||||
return !StringUtils.isBlank(line) && !IGNORED_LINES.contains(line.trim());
|
||||
}
|
||||
|
||||
private boolean hasTooManyLines(JavaComment comment) {
|
||||
|
||||
int firstLineWithText = -1;
|
||||
int lastLineWithText;
|
||||
int i = 0;
|
||||
int maxLines = getProperty(MAX_LINES);
|
||||
for (Chars line : comment.getText().lines()) {
|
||||
boolean real = hasRealText(line);
|
||||
if (real) {
|
||||
lastLineWithText = i;
|
||||
if (firstLineWithText == -1) {
|
||||
firstLineWithText = i;
|
||||
}
|
||||
if (lastLineWithText - firstLineWithText + 1 > maxLines) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
i++;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private List<Integer> overLengthLineIndicesIn(JavaComment comment) {
|
||||
|
||||
int maxLength = getProperty(MAX_LINE_LENGTH);
|
||||
|
||||
List<Integer> indices = new ArrayList<>();
|
||||
int i = 0;
|
||||
for (Chars line : comment.getFilteredLines(true)) {
|
||||
if (line.length() > maxLength) {
|
||||
indices.add(i);
|
||||
}
|
||||
}
|
||||
return indices;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ import java.util.List;
|
||||
|
||||
import net.sourceforge.pmd.lang.ast.Node;
|
||||
import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit;
|
||||
import net.sourceforge.pmd.lang.java.ast.Comment;
|
||||
import net.sourceforge.pmd.lang.java.ast.JavaComment;
|
||||
import net.sourceforge.pmd.lang.rule.xpath.internal.AstElementNode;
|
||||
|
||||
import net.sf.saxon.expr.XPathContext;
|
||||
@ -63,10 +63,10 @@ public class GetCommentOnFunction extends BaseJavaXPathFunction {
|
||||
int codeBeginLine = contextNode.getBeginLine();
|
||||
int codeEndLine = contextNode.getEndLine();
|
||||
|
||||
List<Comment> commentList = contextNode.getFirstParentOfType(ASTCompilationUnit.class).getComments();
|
||||
for (Comment comment : commentList) {
|
||||
List<JavaComment> commentList = contextNode.getFirstParentOfType(ASTCompilationUnit.class).getComments();
|
||||
for (JavaComment comment : commentList) {
|
||||
if (comment.getBeginLine() == codeBeginLine || comment.getEndLine() == codeEndLine) {
|
||||
return new StringValue(comment.getImage());
|
||||
return new StringValue(comment.getText());
|
||||
}
|
||||
}
|
||||
return EmptyAtomicSequence.INSTANCE;
|
||||
|
@ -292,7 +292,7 @@ public class Éléphant {}
|
||||
language="java"
|
||||
since="5.4.0"
|
||||
class="net.sourceforge.pmd.lang.java.rule.codestyle.CommentDefaultAccessModifierRule"
|
||||
message="Missing commented default access modifier"
|
||||
message="Missing commented default access modifier on {0} ''{1}''"
|
||||
externalInfoUrl="${pmd.website.baseurl}/pmd_rules_java_codestyle.html#commentdefaultaccessmodifier">
|
||||
<description>
|
||||
To avoid mistakes if we want that an Annotation, Class, Enum, Method, Constructor or Field have a default access modifier
|
||||
|
@ -7,11 +7,14 @@ package net.sourceforge.pmd.lang.java.ast;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.instanceOf;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import net.sourceforge.pmd.lang.java.BaseParserTest;
|
||||
@ -28,14 +31,17 @@ class CommentAssignmentTest extends BaseParserTest {
|
||||
+ " /** a formal comment with blank lines\n\n\n */"
|
||||
+ "}");
|
||||
|
||||
Comment comment = node.getComments().get(0);
|
||||
JavaComment comment = node.getComments().get(0);
|
||||
|
||||
assertThat(comment, instanceOf(MultiLineComment.class));
|
||||
assertEquals("multi line comment with blank lines", comment.getFilteredComment());
|
||||
assertFalse(comment.isSingleLine());
|
||||
assertFalse(comment.hasJavadocContent());
|
||||
assertEquals("multi line comment with blank lines", StringUtils.join(comment.getFilteredLines(), ' '));
|
||||
|
||||
comment = node.getComments().get(1);
|
||||
assertThat(comment, instanceOf(FormalComment.class));
|
||||
assertEquals("a formal comment with blank lines", comment.getFilteredComment());
|
||||
assertFalse(comment.isSingleLine());
|
||||
assertTrue(comment.hasJavadocContent());
|
||||
assertThat(comment, instanceOf(JavadocComment.class));
|
||||
assertEquals("a formal comment with blank lines", StringUtils.join(comment.getFilteredLines(), ' '));
|
||||
}
|
||||
|
||||
|
||||
@ -51,7 +57,7 @@ class CommentAssignmentTest extends BaseParserTest {
|
||||
+ " /** Comment 3 */\n"
|
||||
+ " public void method2() {}" + "}");
|
||||
|
||||
List<ASTMethodDeclaration> methods = node.findDescendantsOfType(ASTMethodDeclaration.class);
|
||||
List<ASTMethodDeclaration> methods = node.descendants(ASTMethodDeclaration.class).toList();
|
||||
assertCommentEquals(methods.get(0), "/** Comment 1 */");
|
||||
assertCommentEquals(methods.get(1), "/** Comment 3 */");
|
||||
}
|
||||
|
@ -9,7 +9,6 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import net.sourceforge.pmd.PMD;
|
||||
import net.sourceforge.pmd.lang.java.BaseParserTest;
|
||||
|
||||
class CommentTest extends BaseParserTest {
|
||||
@ -46,7 +45,7 @@ class CommentTest extends BaseParserTest {
|
||||
+ " */\n";
|
||||
String filtered = filter(comment);
|
||||
assertEquals(2, lineCount(filtered));
|
||||
assertEquals("line 1" + PMD.EOL + "line 2", filtered);
|
||||
assertEquals("line 1\nline 2", filtered);
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -58,7 +57,7 @@ class CommentTest extends BaseParserTest {
|
||||
+ " */\r\n";
|
||||
String filtered = filter(comment);
|
||||
assertEquals(2, lineCount(filtered));
|
||||
assertEquals("line 1" + PMD.EOL + "line 2", filtered);
|
||||
assertEquals("line 1\nline 2", filtered);
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -70,7 +69,7 @@ class CommentTest extends BaseParserTest {
|
||||
+ " */\n";
|
||||
String filtered = filter(comment);
|
||||
assertEquals(2, lineCount(filtered));
|
||||
assertEquals("line 1" + PMD.EOL + "line 2", filtered);
|
||||
assertEquals("line 1\nline 2", filtered);
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -82,7 +81,7 @@ class CommentTest extends BaseParserTest {
|
||||
+ " */\r\n";
|
||||
String filtered = filter(comment);
|
||||
assertEquals(2, lineCount(filtered));
|
||||
assertEquals("line 1" + PMD.EOL + "line 2", filtered);
|
||||
assertEquals("line 1\nline 2", filtered);
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -95,14 +94,15 @@ class CommentTest extends BaseParserTest {
|
||||
+ " */\n";
|
||||
String filtered = filter(comment);
|
||||
assertEquals(2, lineCount(filtered));
|
||||
assertEquals("line 1" + PMD.EOL + "line 2", filtered);
|
||||
assertEquals("line 1\nline 2", filtered);
|
||||
}
|
||||
|
||||
private String filter(String comment) {
|
||||
return java.parse(comment).getComments().get(0).getFilteredComment();
|
||||
JavaComment firstComment = java.parse(comment).getComments().get(0);
|
||||
return StringUtils.join(firstComment.getFilteredLines(), '\n');
|
||||
}
|
||||
|
||||
private int lineCount(String filtered) {
|
||||
return StringUtils.countMatches(filtered, PMD.EOL) + 1;
|
||||
return StringUtils.countMatches(filtered, '\n') + 1;
|
||||
}
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user