• 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

81.82
/pmd-core/src/main/java/net/sourceforge/pmd/lang/document/TextDocument.java
1
/*
2
 * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
3
 */
4

5
package net.sourceforge.pmd.lang.document;
6

7
import java.io.Closeable;
8
import java.io.IOException;
9
import java.io.Reader;
10

11
import org.checkerframework.checker.nullness.qual.NonNull;
12

13
import net.sourceforge.pmd.lang.LanguageVersion;
14

15
/**
16
 * Represents a textual document, providing methods to edit it incrementally
17
 * and address regions of text. A text document delegates IO operations
18
 * to a {@link TextFile}. It reflects some in-memory snapshot of the file,
19
 * though the file may still be edited externally.
20
 *
21
 * <p>Note that the backing {@link TextFile} is purposefully not accessible
22
 * from a text document. Exposing it here could lead to files being written
23
 * to from within rules, while we want to eventually build an API that allows
24
 * file edition based on AST manipulation.
25
 *
26
 * <h2>Coordinates in TextDocument</h2>
27
 *
28
 * <p>This interface is an abstraction over a piece of text, which might not
29
 * correspond to the backing source file. This allows the document to
30
 * be a view on a piece of a larger document (eg, a Javadoc comment, or
31
 * a string in which a language is injected). Another use case is to perform
32
 * escape translation, while preserving the line breaks of the original source.
33
 *
34
 * <p>This complicates addressing within a text document. To explain it,
35
 * consider that there is always *one* text document that corresponds to
36
 * the backing text file, which we call the <i>root</i> text document.
37
 * Logical documents built on top of it are called <i>views</i>.
38
 *
39
 * <p>Text documents use <i>offsets</i> and {@link TextRegion} to address their
40
 * contents. These are always relative to the {@linkplain #getText() text} of
41
 * the document. Line and column information are provided by {@link FileLocation}
42
 * (see {@link #toLocation(TextRegion)}), and are always absolute (ie,
43
 * represent actual source lines in the file).
44
 *
45
 * <p>For instance, say you have the following file (and root text document):
46
 * <pre>{@code
47
 * l1
48
 * l2 (* comment *)
49
 * l3
50
 * }</pre>
51
 * and you create a view for just the section {@code (* comment *)}.
52
 * Then, that view's offset 0 (start of the document) will map
53
 * to the {@code (} character, while the root document's offset 0 maps
54
 * to the start of {@code l1}. When calling {@code toLocation(caretAt(0))},
55
 * the view will however return {@code line 2, column 4}, ie, a line/column
56
 * that can be found when inspecting the file.
57
 *
58
 * <p>To reduce the potential for mistakes, views do not provide access
59
 * to their underlying text document. That way, nodes only have access
60
 * to a single document, and their offsets can be assumed to be in the
61
 * coordinate system of that document.
62
 *
63
 * <p>This interface does not provide a way to obtain line/column
64
 * coordinates that are relative to a view's coordinate system. This
65
 * would complicate the construction of views significantly.
66
 */
67
public interface TextDocument extends Closeable {
68
    // todo logical sub-documents, to support embedded languages
69
    //  ideally, just slice the text, and share the positioner
70
    //  a problem with document slices becomes reference counting for the close routine
71

72

73
    // todo text edition (there are some reverted commits in the branch
74
    //  with part of this, including a lot of tests)
75

76

77
    /**
78
     * Returns the language version that should be used to parse this file.
79
     */
80
    LanguageVersion getLanguageVersion();
81

82
    /**
83
     * Returns {@link TextFile#getFileId()} for the text file backing this document.
84
     */
85
    FileId getFileId();
86

87

88
    /**
89
     * Returns the current text of this document. Note that this doesn't take
90
     * external modifications to the {@link TextFile} into account.
91
     *
92
     * <p>Line endings are normalized to {@link TextFileContent#NORMALIZED_LINE_TERM}.
93
     *
94
     * @see TextFileContent#getNormalizedText()
95
     */
96
    Chars getText();
97

98
    /**
99
     * Returns a slice of the original text. Note that this is not the
100
     * same as {@code getText().subsequence}, as if this document has
101
     * translated escapes, the returned char slice will contain the
102
     * untranslated escapes, whereas {@link #getText()} would return
103
     * the translated characters.
104
     *
105
     * @param region A region, in the coordinate system of this document
106
     *
107
     * @return The slice of the original text that corresponds to the region
108
     *
109
     * @throws IndexOutOfBoundsException If the region is not a valid range
110
     */
111
    Chars sliceOriginalText(TextRegion region);
112

113
    /**
114
     * Returns a slice of the source text. This is always equal to
115
     * {@code getText().slice(region)}, as the text is the translated text.
116
     *
117
     * @param region A region, in the coordinate system of this document
118
     *
119
     * @return The slice of the original text that corresponds to the region
120
     *
121
     * @throws IndexOutOfBoundsException If the region is not a valid range
122
     */
123
    default Chars sliceTranslatedText(TextRegion region) {
124
        return getText().slice(region);
1✔
125
    }
126

127

128
    /**
129
     * Returns a checksum for the contents of the file.
130
     *
131
     * @see TextFileContent#getCheckSum()
132
     */
133
    long getCheckSum();
134

135

136
    /**
137
     * Returns a reader over the text of this document.
138
     */
139
    default Reader newReader() {
140
        return getText().newReader();
1✔
141
    }
142

143
    /**
144
     * Returns the length in characters of the {@linkplain #getText() text}.
145
     */
146
    default int getLength() {
147
        return getText().length();
1✔
148
    }
149

150
    /**
151
     * Returns a text region that corresponds to the entire document,
152
     * in the coordinate system of this document.
153
     */
154
    default TextRegion getEntireRegion() {
155
        return TextRegion.fromOffsetLength(0, getLength());
1✔
156
    }
157

158
    /**
159
     * Returns a region that spans the text of all the given lines.
160
     *
161
     * <p>Note that, as line numbers may only be obtained from {@link #toLocation(TextRegion)},
162
     * and hence are line numbers of the original source, both parameters
163
     * must be line numbers of the source text and not the translated text
164
     * that this represents.
165
     *
166
     * @param startLineInclusive Inclusive start line number (1-based)
167
     * @param endLineInclusive   Inclusive end line number (1-based)
168
     *
169
     * @throws IndexOutOfBoundsException If the arguments do not identify
170
     *                                   a valid region in the source document
171
     */
172
    TextRegion createLineRange(int startLineInclusive, int endLineInclusive);
173

174

175
    /**
176
     * Turn a text region into a {@link FileLocation}. This computes
177
     * the line/column information for both start and end offset of
178
     * the region.
179
     *
180
     * @param region A region, in the coordinate system of this document
181
     *
182
     * @return A new file position, with absolute coordinates
183
     *
184
     * @throws IndexOutOfBoundsException If the argument is not a valid region in this document
185
     */
186
    FileLocation toLocation(TextRegion region);
187

188

189
    /**
190
     * Returns the line and column at the given offset (inclusive).
191
     *
192
     * @param offset A source offset (0-based), can range in {@code [0, length]}.
193
     *
194
     * @throws IndexOutOfBoundsException if the offset is out of bounds
195
     * @see #lineColumnAtOffset(int, boolean)
196
     * @see #offsetAtLineColumn(TextPos2d)
197
     */
198
    default TextPos2d lineColumnAtOffset(int offset) {
199
        return lineColumnAtOffset(offset, true);
1✔
200
    }
201

202
    /**
203
     * Returns the line and column at the given offset.
204
     * Both the input offset and the output range are in the coordinates
205
     * of this document.
206
     *
207
     * @param offset    A source offset (0-based), can range in {@code [0, length]}.
208
     * @param inclusive If the offset falls right after a line terminator,
209
     *                  two behaviours are possible. If the parameter is true,
210
     *                  choose the position at the start of the next line,
211
     *                  otherwise choose the position at the end of the line.
212
     *
213
     * @return A position, in the coordinate system of the root document
214
     *
215
     * @throws IndexOutOfBoundsException if the offset is out of bounds
216
     * @see #offsetAtLineColumn(TextPos2d)
217
     */
218
    TextPos2d lineColumnAtOffset(int offset, boolean inclusive);
219

220
    /**
221
     * Calculates the offset from a given line/column.
222
     *
223
     * @param position the line/column
224
     *
225
     * @see #lineColumnAtOffset(int)
226
     * @see #lineColumnAtOffset(int, boolean)
227
     */
228
    int offsetAtLineColumn(TextPos2d position);
229

230
    /**
231
     * Closing a document closes the underlying {@link TextFile}.
232
     * New editors cannot be produced after that, and the document otherwise
233
     * remains in its current state.
234
     *
235
     * @throws IOException           If {@link TextFile#close()} throws
236
     * @throws IllegalStateException If an editor is currently open. In this case
237
     *                               the editor is rendered ineffective before the
238
     *                               exception is thrown. This indicates a programming
239
     *                               mistake.
240
     */
241
    @Override
242
    void close() throws IOException;
243

244
    /**
245
     * Create a new text document for the given text file. The document's
246
     * coordinate system is the same as the original text file.
247
     *
248
     * @param textFile A text file
249
     *
250
     * @return A new text document
251
     *
252
     * @throws IOException          If the file cannot be read ({@link TextFile#readContents()})
253
     * @throws NullPointerException If the parameter is null
254
     */
255
    static TextDocument create(TextFile textFile) throws IOException {
256
        return new RootTextDocument(textFile);
1✔
257
    }
258

259
    /**
260
     * Returns a read-only document for the given text.
261
     *
262
     * @see TextFile#forCharSeq(CharSequence, FileId, LanguageVersion)
263
     */
264
    static TextDocument readOnlyString(final CharSequence source, LanguageVersion lv) {
265
        return readOnlyString(source, FileId.UNKNOWN, lv);
1✔
266
    }
267

268
    /**
269
     * Returns a read-only document for the given text. This works as
270
     * if by calling {@link TextDocument#create(TextFile)} on a textfile
271
     * produced by {@link TextFile#forCharSeq(CharSequence, FileId, LanguageVersion) forString},
272
     * but doesn't throw {@link IOException}, as such text files will
273
     * not throw.
274
     *
275
     * @see TextFile#forCharSeq(CharSequence, FileId, LanguageVersion)
276
     */
277
    static TextDocument readOnlyString(@NonNull CharSequence source, @NonNull FileId filename, @NonNull LanguageVersion lv) {
278
        TextFile textFile = TextFile.forCharSeq(source, filename, lv);
1✔
279
        try {
280
            return create(textFile);
1✔
UNCOV
281
        } catch (IOException e) {
×
282
            throw new AssertionError("String text file should never throw IOException", e);
×
283
        }
284
    }
285
}
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