• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

pmd / pmd / 43

20 Jun 2025 06:39PM UTC coverage: 78.375% (-0.002%) from 78.377%
43

push

github

adangel
Fix #1639 #5832: Use filtered comment text for UnnecessaryImport (#5833)

Merged pull request #5833 from adangel:java/issue-5832-unnecessaryimport

17714 of 23438 branches covered (75.58%)

Branch coverage included in aggregate %.

3 of 3 new or added lines in 1 file covered. (100.0%)

109 existing lines in 17 files now uncovered.

38908 of 48807 relevant lines covered (79.72%)

0.81 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

71.43
/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/JavaComment.java
1
/**
2
 * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
3
 */
4

5
package net.sourceforge.pmd.lang.java.ast;
6

7
import java.util.stream.Stream;
8

9
import net.sourceforge.pmd.lang.ast.GenericToken;
10
import net.sourceforge.pmd.lang.ast.impl.javacc.JavaccToken;
11
import net.sourceforge.pmd.lang.ast.impl.javacc.JjtreeNode;
12
import net.sourceforge.pmd.lang.document.Chars;
13
import net.sourceforge.pmd.lang.document.FileLocation;
14
import net.sourceforge.pmd.lang.java.ast.internal.JavaAstUtils;
15
import net.sourceforge.pmd.reporting.Reportable;
16
import net.sourceforge.pmd.util.IteratorUtil;
17

18
/**
19
 * Wraps a comment token to provide some utilities.
20
 * This is not a node, it's not part of the tree anywhere,
21
 * just convenient.
22
 *
23
 * <p>This class represents any kind of comment. A specialized subclass
24
 * provides more API for Javadoc comments, see {@link JavadocComment}.
25
 */
26
public class JavaComment implements Reportable {
27
    //TODO maybe move part of this into pmd core
28

29
    private final JavaccToken token;
30

31
    JavaComment(JavaccToken t) {
1✔
32
        this.token = t;
1✔
33
    }
1✔
34

35
    @Override
36
    public FileLocation getReportLocation() {
37
        return getToken().getReportLocation();
1✔
38
    }
39

40
    /** The token underlying this comment. */
41
    public final JavaccToken getToken() {
42
        return token;
1✔
43
    }
44

45
    public boolean isSingleLine() {
46
        return token.kind == JavaTokenKinds.SINGLE_LINE_COMMENT;
1!
47
    }
48

49
    public boolean hasJavadocContent() {
50
        return token.kind == JavaTokenKinds.FORMAL_COMMENT || JavaAstUtils.isMarkdownComment(token);
1✔
51
    }
52

53
    /** Returns the full text of the comment. */
54
    public Chars getText() {
55
        return getToken().getImageCs();
1✔
56
    }
57

58
    /**
59
     * Returns true if the given token has the kind
60
     * of a comment token (there are three such kinds).
61
     */
62
    public static boolean isComment(JavaccToken token) {
63
        return JavaAstUtils.isComment(token);
1✔
64
    }
65

66
    /**
67
     * Removes the leading comment marker (like {@code *}) of each line
68
     * of the comment as well as the start marker ({@code //}, {@code /*}, {@code /**} or {@code ///}
69
     * and the end markers (<code>&#x2a;/</code>).
70
     *
71
     * <p>Empty lines are removed.
72
     *
73
     * @return List of lines of the comments
74
     */
75
    public Iterable<Chars> getFilteredLines() {
76
        return getFilteredLines(false);
1✔
77
    }
78

79
    public Iterable<Chars> getFilteredLines(boolean preserveEmptyLines) {
80
        if (preserveEmptyLines) {
1✔
81
            return () -> IteratorUtil.map(getText().lines().iterator(), JavaComment::removeCommentMarkup);
1✔
82
        } else {
83
            return () -> IteratorUtil.mapNotNull(
1✔
84
                getText().lines().iterator(),
1✔
85
                line -> {
86
                    line = removeCommentMarkup(line);
1✔
87
                    return line.isEmpty() ? null : line;
1✔
88
                }
89
            );
90
        }
91
    }
92

93
    /**
94
     * True if this is a comment delimiter or an asterisk. This
95
     * tests the whole parameter and not a prefix/suffix.
96
     */
97
    public static boolean isMarkupWord(Chars word) {
UNCOV
98
        return word.length() <= 3
×
99
            && (word.contentEquals("*")
×
100
            || word.contentEquals("//")
×
101
            || word.contentEquals("///")
×
102
            || word.contentEquals("/*")
×
103
            || word.contentEquals("*/")
×
104
            || word.contentEquals("/**"));
×
105
    }
106

107
    /**
108
     * Trim the start of the provided line to remove a comment
109
     * markup opener ({@code //, ///, /*, /**, *}) or closer <code>&#x2a;/</code>.
110
     */
111
    public static Chars removeCommentMarkup(Chars line) {
112
        line = line.trim().removeSuffix("*/");
1✔
113
        int subseqFrom = 0;
1✔
114
        if (line.startsWith('/', 0)) {
1✔
115
            if (line.startsWith("**", 1)
1✔
116
                || line.startsWith("//", 1)) {
1✔
117
                subseqFrom = 3;
1✔
118
            } else if (line.startsWith('/', 1)
1✔
119
                || line.startsWith('*', 1)) {
1!
120
                subseqFrom = 2;
1✔
121
            }
122
        } else if (line.startsWith('*', 0)) {
1✔
123
            subseqFrom = 1;
1✔
124
        }
125
        return line.subSequence(subseqFrom, line.length()).trim();
1✔
126
    }
127

128
    private static Stream<JavaccToken> getSpecialTokensIn(JjtreeNode<?> node) {
129
        return GenericToken.streamRange(node.getFirstToken(), node.getLastToken())
1✔
130
                           .flatMap(it -> IteratorUtil.toStream(GenericToken.previousSpecials(it).iterator()));
1✔
131
    }
132

133
    public static Stream<JavaComment> getLeadingComments(JavaNode node) {
134
        Stream<JavaccToken> specialTokens = getSpecialTokensIn(node);
1✔
135
        
136
        if (node instanceof ModifierOwner && !(node instanceof ASTConstructorDeclaration)) {
1✔
137
            node = ((ModifierOwner) node).getModifiers();
1✔
138
            specialTokens = getSpecialTokensIn(node);
1✔
139
            
140
            // if this was a non-implicit empty modifier node, we should also consider comments immediately after
141
            if (!node.getFirstToken().isImplicit()) {
1✔
142
                specialTokens = Stream.concat(specialTokens, getSpecialTokensIn(node.getNextSibling()));
1✔
143
            }
144
        }
145
        
146
        return specialTokens.filter(JavaComment::isComment)
1✔
147
                                         .map(JavaComment::toComment);
1✔
148
    }
149

150
    private static JavaComment toComment(JavaccToken tok) {
151
        switch (tok.kind) {
1!
152
        case JavaTokenKinds.FORMAL_COMMENT:
153
            return new JavadocComment(tok);
1✔
154
        case JavaTokenKinds.MULTI_LINE_COMMENT:
155
        case JavaTokenKinds.SINGLE_LINE_COMMENT:
156
            return new JavaComment(tok);
1✔
157
        default:
UNCOV
158
            throw new IllegalArgumentException("Token is not a comment: " + tok);
×
159
        }
160
    }
161

162
    @Override
163
    public boolean equals(Object o) {
164
        if (this == o) {
1!
UNCOV
165
            return true;
×
166
        }
167
        if (!(o instanceof JavaComment)) {
1!
UNCOV
168
            return false;
×
169
        }
170
        JavaComment that = (JavaComment) o;
1✔
171
        return token.equals(that.token);
1✔
172
    }
173

174
    @Override
175
    public int hashCode() {
UNCOV
176
        return token.hashCode();
×
177
    }
178
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc