net.sourceforge.pmd.lang.java.ast.JavaComment Maven / Gradle / Ivy
/**
* 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.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;
import net.sourceforge.pmd.util.IteratorUtil;
/**
* Wraps a comment token to provide some utilities.
* This is not a node, it's not part of the tree anywhere,
* just convenient.
*
* 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();
}
/** 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 || JavaAstUtils.isMarkdownComment(token);
}
/** 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 /*}, {@code /**} or {@code ///}
* and the end markers (*/
).
*
*
Empty lines are removed.
*
* @return List of lines of the comments
*/
public Iterable getFilteredLines() {
return getFilteredLines(false);
}
public Iterable 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("*/")
|| word.contentEquals("/**"));
}
/**
* Trim the start of the provided line to remove a comment
* markup opener ({@code //, ///, /*, /**, *}) or closer */
.
*/
public static Chars removeCommentMarkup(Chars line) {
line = line.trim().removeSuffix("*/");
int subseqFrom = 0;
if (line.startsWith('/', 0)) {
if (line.startsWith("**", 1)
|| 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 getSpecialTokensIn(JjtreeNode> node) {
return GenericToken.streamRange(node.getFirstToken(), node.getLastToken())
.flatMap(it -> IteratorUtil.toStream(GenericToken.previousSpecials(it).iterator()));
}
public static Stream getLeadingComments(JavaNode node) {
Stream specialTokens;
if (node instanceof ModifierOwner) {
node = ((ModifierOwner) node).getModifiers();
specialTokens = getSpecialTokensIn(node);
// if this was a non-implicit empty modifier node, we should also consider comments immediately after
if (!node.getFirstToken().isImplicit()) {
specialTokens = Stream.concat(specialTokens, getSpecialTokensIn(node.getNextSibling()));
}
} else {
specialTokens = getSpecialTokensIn(node);
}
return specialTokens.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();
}
}