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

moosetechnology / VerveineJ / 28802666890

06 Jul 2026 03:22PM UTC coverage: 52.722% (+0.06%) from 52.667%
28802666890

Pull #268

github

web-flow
Merge 88cf9c82a into 04fa886ba
Pull Request #268: Fix/267 class comment wrongly assigned to constructor

2027 of 4016 branches covered (50.47%)

Branch coverage included in aggregate %.

6 of 8 new or added lines in 1 file covered. (75.0%)

11 existing lines in 1 file now uncovered.

4480 of 8326 relevant lines covered (53.81%)

2.22 hits per line

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

89.63
/app/src/main/java/fr/inria/verveine/extractor/java/visitors/defvisitors/VisitorComments.java
1
package fr.inria.verveine.extractor.java.visitors.defvisitors;
2

3
import java.io.BufferedReader;
4
import java.io.FileInputStream;
5
import java.io.FileNotFoundException;
6
import java.io.IOException;
7
import java.io.InputStream;
8
import java.io.InputStreamReader;
9
import java.io.UnsupportedEncodingException;
10
import java.util.List;
11

12
import org.eclipse.jdt.core.dom.ASTNode;
13
import org.eclipse.jdt.core.dom.AnnotationTypeDeclaration;
14
import org.eclipse.jdt.core.dom.AnnotationTypeMemberDeclaration;
15
import org.eclipse.jdt.core.dom.AnonymousClassDeclaration;
16
import org.eclipse.jdt.core.dom.Comment;
17
import org.eclipse.jdt.core.dom.CompilationUnit;
18
import org.eclipse.jdt.core.dom.EnumDeclaration;
19
import org.eclipse.jdt.core.dom.FieldDeclaration;
20
import org.eclipse.jdt.core.dom.Initializer;
21
import org.eclipse.jdt.core.dom.Javadoc;
22
import org.eclipse.jdt.core.dom.MethodDeclaration;
23
import org.eclipse.jdt.core.dom.RecordDeclaration;
24
import org.eclipse.jdt.core.dom.TypeDeclaration;
25
import org.eclipse.jdt.core.dom.VariableDeclarationFragment;
26
import org.moosetechnology.model.famix.famixjavaentities.AnnotationTypeAttribute;
27
import org.moosetechnology.model.famix.famixjavaentities.Method;
28
import org.moosetechnology.model.famix.famixtraits.TCanBeStub;
29
import org.moosetechnology.model.famix.famixtraits.TWithAttributes;
30
import org.moosetechnology.model.famix.famixtraits.TWithComments;
31

32
import fr.inria.verveine.extractor.java.EntityDictionary;
33
import fr.inria.verveine.extractor.java.VerveineJOptions;
34
import fr.inria.verveine.extractor.java.visitors.GetVisitedEntityAbstractVisitor;
35
/**
36
 * A class to collect all comments.
37
 * Some important details on comments in JDT:
38
 * <ul>
39
 * <li>only JavaDoc comment are associated to any entity node in the AST (the entity immediately after the javadoc)</li>
40
 * <li>The <code>startPosition</code> of a node with a JavaDoc is actually the <code>startPosition</code> of the JavaDoc
41
 *   (ie. the JavaDoc is considered to be part of the entity declaration)</li> 
42
 * <li>line and block comment are recorded in a list, but we have to decide to what entity they will be associated.
43
 *   We put them in the method containing them or in the method or attribute immediately after them if they are outside a method</li>
44
 * <li>content (text) of block or line comments is not stored, only their position and length.
45
 *   We have to recover them from the source file</li> 
46
 * </ul>
47
 * 
48
 * This class assumes the AST is visited in the file position order of the nodes (nodes appearing first in the file
49
 * are visited first).
50
 * It visit the AST in parallel with "visiting" the list of comments and for each comment,
51
 * <ul>
52
 * <li>if it is a JavaDoc, gets its associated node</li>
53
 * <li>if not javadoc, finds out if it is in a method or not
54
 *         <ul>
55
 *  <li>if not, it find the class, method, or attribute that comes immediately after the comment</li>
56
 *  <li>if in a method, it associates the comment to this method</li>
57
 *  </ul>
58
 * </ul>
59
 * 
60
 * @author anquetil
61
 */
62
public class VisitorComments extends GetVisitedEntityAbstractVisitor {
63

64
        /**
65
         * list of all comments in the compilation unit
66
         */
67
        protected List<Comment> allComments;
68

69
        /**
70
         * Indice in {@link #allComments} of next comment to deal with (ie. associate with a Famix entity).
71
         */
72
        protected int nextComment;
73

74
        /**
75
         * Whether we are defining a class member or not<br>
76
         * Used to differentiate attribute declarations from other variable declarations 
77
         */
78
        protected boolean classMemberDeclarations;
79

80
        /**
81
         * To handle correctly AnonymousClassDeclaration, we need to keep the start position of methods
82
         * (that might contain these anonymous classes)
83
         */
84
        private int methodStartPosition;
85

86
        protected String currentFilename = null;
3✔
87
        protected BufferedReader openedFile = null;
3✔
88
        protected int lastPositionRead = 0;
3✔
89

90

91
        public VisitorComments(EntityDictionary dico, VerveineJOptions options) {
92
                super(dico, options);
4✔
93
                classMemberDeclarations = false;
3✔
94
        }
1✔
95

96
        // VISITOR METHODS
97

98
        @Override
99
        public boolean visit(CompilationUnit node) {
100
                initializeCommentsReader(node);
3✔
101
                if (allComments.size() == 0) {
4✔
102
                        // no comment, not visiting
103
                        return false;
2✔
104
                }
105
                else {
106
                        nextComment = 0;
3✔
107
                }
108
                return super.visit(node);
4✔
109
        }
110

111
        @Override
112
        public void endVisit(CompilationUnit node) {
113
                endVisitCompilationUnit(node);
3✔
114
                closeFile();
2✔
115
        }
1✔
116

117
        @Override
118
        public boolean visit(TypeDeclaration node) {
119
                TWithComments fmx = (TWithComments) visitTypeDeclaration(node);
5✔
120

121
                assignCommentsBefore(node, node.getJavadoc(), fmx);
6✔
122
                classMemberDeclarations = true;
3✔
123

124
                return super.visit(node);
4✔
125
        }
126

127
        @Override
128
        public void endVisit(TypeDeclaration node) {
129
                assignCommentsInside(node, (TWithComments) context.topType());
7✔
130
                classMemberDeclarations = false;
3✔
131
                endVisitTypeDeclaration(node);
3✔
132
        }
1✔
133

134
        @Override
135
        public boolean visit(RecordDeclaration node) {
136
                TWithComments fmx = (TWithComments) visitTypeDeclaration(node);
5✔
137

138
                assignCommentsBefore(node, node.getJavadoc(), fmx);
6✔
139
                classMemberDeclarations = true;
3✔
140

141
                return super.visit(node);
4✔
142
        }
143

144
        @Override
145
        public void endVisit(RecordDeclaration node) {
146
                assignCommentsInside(node, (TWithComments) context.topType());
7✔
147
                classMemberDeclarations = false;
3✔
NEW
148
                endVisitTypeDeclaration(node);
×
NEW
149
        }
×
150

151
        /**
152
         * AnonymousClassDeclaration appear within a method, we deal first with all "pending" comments
153
         * that appear before the AnonymousClassDeclaration and should be associated with the method and not anonymous class
154
         */
155
        @Override
156
        public boolean visit(AnonymousClassDeclaration node) {
157
                assignCommentsInInterval( methodStartPosition, node.getStartPosition(), (TWithComments) context.topMethod());
10✔
158
                classMemberDeclarations = true;
3✔
159

160
                return super.visit(node);
4✔
161
        }
162

163
        @Override
164
        public void endVisit(AnonymousClassDeclaration node) {
165
                assignCommentsInside(node, (TWithComments) context.topType());
7✔
166
                classMemberDeclarations = false;
3✔
167
                endVisitAnonymousClassDeclaration( node);
3✔
168
        }
1✔
169

170
        @Override
171
        public boolean visit(EnumDeclaration node) {
172
                TWithComments fmx = visitEnumDeclaration( node);
4✔
173

174
                assignCommentsBefore(node, node.getJavadoc(), fmx);
6✔
175
                classMemberDeclarations = true;
3✔
176

177
                return super.visit(node);
4✔
178
        }
179

180
        @Override
181
        public void endVisit(EnumDeclaration node) {
182
                assignCommentsInside(node, (TWithComments) context.topType());
7✔
183
                classMemberDeclarations = false;
3✔
184
                endVisitEnumDeclaration( node);
3✔
185
        }
1✔
186

187
        @Override
188
        public boolean visit(AnnotationTypeDeclaration node) {
189
                TWithComments fmx = visitAnnotationTypeDeclaration( node);
4✔
190

191
                assignCommentsBefore(node, node.getJavadoc(), fmx);
6✔
192
                classMemberDeclarations = true;
3✔
193

194
                return super.visit(node);
4✔
195
        }
196

197
        @Override
198
        public void endVisit(AnnotationTypeDeclaration node) {
199
                assignCommentsInside(node, (TWithComments) context.topType());
7✔
200
                classMemberDeclarations = false;
3✔
201
                endVisitAnnotationTypeDeclaration(node);
3✔
202
        }
1✔
203

204
        @Override
205
        public boolean visit(MethodDeclaration node) {
206
                Method fmx = visitMethodDeclaration( node);
4✔
207

208
                assignCommentsBefore(node, node.getJavadoc(), fmx);
6✔
209
                classMemberDeclarations = false;
3✔
210
                methodStartPosition = node.getStartPosition();
4✔
211

212
                return super.visit(node);
4✔
213
        }
214

215
        @Override
216
        public void endVisit(MethodDeclaration node) {
217
                assignCommentsInside(node, (TWithComments) context.topMethod());
7✔
218
                classMemberDeclarations = true;
3✔
219
                endVisitMethodDeclaration(node);
3✔
220
        }
1✔
221

222
        @Override
223
        public boolean visit(AnnotationTypeMemberDeclaration node) {
224
                AnnotationTypeAttribute fmx = visitAnnotationTypeMemberDeclaration( node);
4✔
225

226
                assignCommentsBefore(node, node.getJavadoc(), fmx);
6✔
227

228
                return super.visit(node);
4✔
229
        }
230

231
        @Override
232
        public void endVisit(AnnotationTypeMemberDeclaration node) {
233
                this.context.popAnnotationMember();
4✔
234
                super.endVisit(node);
3✔
235
        }
1✔
236

237
        @Override
238
        public boolean visit(Initializer node) {
239
                visitInitializer(node);
4✔
240
                classMemberDeclarations = false;
3✔
241
                return super.visit(node);
4✔
242
        }
243

244
        @Override
245
        public void endVisit(Initializer node) {
246
                assignCommentsInside(node, (TWithComments) context.topMethod());
7✔
247
                classMemberDeclarations = true;
3✔
248
                endVisitInitializer(node);
3✔
249
        }
1✔
250

251
        @Override
252
        public boolean visit(VariableDeclarationFragment node) {
253
                if (classMemberDeclarations && node.getParent() instanceof FieldDeclaration) {
7✔
254
                        TWithComments fmx = dico.getFamixAttribute(node.resolveBinding(), node.getName().getIdentifier(), (TWithAttributes) context.topType());
13✔
255
                        if ( ! ((TCanBeStub) fmx).getIsStub() ) {
5!
256
                                // if it is a stub, it might have been created by the getFamixAttribute just above
257
                                // Anyway we cannot have a comment on a stub
258
                                assignCommentsBefore(node, /*optionalJavadoc*/null, fmx);
5✔
259
                        }
260
                }
261

262
                return super.visit(node);
4✔
263
        }
264

265
        // UTILITY METHODS
266

267
        /**
268
         * Assigns all "pending" comments to the Famix entity.<br>
269
         * Pending comments are those not treated that appear before the <code>node</code> associated with the Famix entity.<br>
270
         * <code>optionalJavadoc</code> is an optional Javadoc comment (!) that could be associated to the <code>node</code>.
271
         * If this is the case, the real <code>startPosition</code> of the <code>node</code> must be incremented of the length of the javadoc
272
         * because this one is considered part of the <code>node</code>.
273
         */
274
        protected void assignCommentsBefore(ASTNode node, Javadoc optionalJavadoc, TWithComments fmx) {
275
                int start = node.getStartPosition();
3✔
276
                if (optionalJavadoc != null) {
2✔
277
                        start += optionalJavadoc.getLength();
5✔
278
                }
279
                assignCommentsInInterval( 0, start, fmx);
5✔
280
        }
1✔
281

282
        /**
283
         * Assigns all "pending" comments that appear with the Famix entity (actually within the <code>node</code> associated to it) 
284
         */
285
        protected void assignCommentsInside(ASTNode node, TWithComments fmx) {
286
                assignCommentsInInterval( node.getStartPosition(), node.getStartPosition() + node.getLength(), fmx);
10✔
287
        }
1✔
288
        
289
        /**
290
         * Assigns to the Famix entity all "pending" comments that appear within the interval [start ; stop] 
291
         */
292
        protected void assignCommentsInInterval(int start, int end, TWithComments fmx) {
293
                boolean searchComment = true;
2✔
294

295
                if (fmx == null) {
2!
296
                        return;
×
297
                }
298

299
                while ( searchComment && pendingComments() ) {
5✔
300
                        Comment cmt = allComments.get(nextComment);
7✔
301
                        if (commentIsInside(cmt, start, end)) {
6✔
302
                                commentCreation( cmt, fmx);
4✔
303
                                nextComment++;
7✔
304
                        }
305
                        else {
306
                                searchComment = false;
2✔
307
                        }
308
                }                
1✔
309
        }
1✔
310

311
        private void commentCreation(Comment cmt, TWithComments fmx) {
312
                if (options.commentsAsText()) {
4✔
313
                        
314
                        dico.createFamixComment(cmt, fmx, getFileContent(cmt.getStartPosition(), cmt.getLength()));
13✔
315
                }
316
                else {
317
                        dico.createFamixComment(cmt, fmx);
6✔
318
                }
319
        }
1✔
320

321
        /**
322
         * Whether there is still some "pending" comments
323
         */
324
        protected boolean pendingComments() {
325
                return nextComment < allComments.size();
10✔
326
        }
327

328
        /**
329
         * whether <code>cmt</code> appears inside interval [ start ; end ] 
330
         */
331
        protected boolean commentIsInside(Comment cmt, int start, int end) {
332
                return (start <= cmt.getStartPosition()) && (end >= cmt.getStartPosition() + cmt.getLength());
15✔
333
        }
334

335
        protected void initializeCommentsReader(CompilationUnit node) {
336
                allComments = node.getCommentList();
4✔
337

338
                currentFilename = (String) ((CompilationUnit)node).getProperty(EntityDictionary.SOURCE_FILENAME_PROPERTY);
6✔
339
                try {
340
                        InputStream is = new FileInputStream(currentFilename);
6✔
341
                        this.openedFile = new BufferedReader(new InputStreamReader(is, options.getFileEncoding()));
12✔
342

343
                        this.lastPositionRead = 0;
3✔
344
                } catch (FileNotFoundException|UnsupportedEncodingException e) {
×
345
                        System.err.println("Not able to read comments from "+currentFilename);
×
346
                }
1✔
347

348
        }
1✔
349

350
        protected String getFileContent( int start, int lenghtToRead) {
351
                char buffer[];
352
                
353
                if(openedFile == null) {
3!
354
                        return "";
×
355
                }
356

357
                buffer = new char[lenghtToRead];
3✔
358
                try {
359
                        
360
                        openedFile.skip(start - lastPositionRead);
9✔
361
                        int ret = openedFile.read( buffer, /*offset in buffer*/0, lenghtToRead);
7✔
362

363
                        if (ret < lenghtToRead) {
3!
364
                                System.err.println("missing bytes in "+ currentFilename + ", read " + ret + " instead of " + lenghtToRead);
×
365
                                return "";
×
366
                        }
367
                        lastPositionRead = start + ret;
5✔
368

369
                        return new String(buffer);
5✔
370
                        
371
                } catch (IOException e) {
×
372
                        e.printStackTrace();
×
373
                }
374
                return "";
×
375
        }
376

377
        protected void closeFile() {
378
                if (openedFile != null) {
3!
379
                        try {
380
                                openedFile.close();
3✔
381
                        } catch (IOException e) {
×
382
                                // nothing
383
                        }
1✔
384
                        openedFile= null;
3✔
385
                }
386
        }
1✔
387

388
}
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