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

pmd / pmd / 4380

30 Jan 2025 09:37AM UTC coverage: 77.689% (+0.005%) from 77.684%
4380

push

github

adangel
[java] Fix tests

17353 of 23278 branches covered (74.55%)

Branch coverage included in aggregate %.

38134 of 48144 relevant lines covered (79.21%)

0.8 hits per line

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

93.3
/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/internal/LanguageLevelChecker.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.internal;
6

7

8
import java.util.Locale;
9
import java.util.regex.Pattern;
10

11
import org.apache.commons.lang3.StringUtils;
12
import org.checkerframework.checker.nullness.qual.Nullable;
13

14
import net.sourceforge.pmd.lang.ast.Node;
15
import net.sourceforge.pmd.lang.java.ast.ASTAnnotation;
16
import net.sourceforge.pmd.lang.java.ast.ASTAssertStatement;
17
import net.sourceforge.pmd.lang.java.ast.ASTCastExpression;
18
import net.sourceforge.pmd.lang.java.ast.ASTCatchClause;
19
import net.sourceforge.pmd.lang.java.ast.ASTConstructorCall;
20
import net.sourceforge.pmd.lang.java.ast.ASTConstructorDeclaration;
21
import net.sourceforge.pmd.lang.java.ast.ASTEnumDeclaration;
22
import net.sourceforge.pmd.lang.java.ast.ASTExplicitConstructorInvocation;
23
import net.sourceforge.pmd.lang.java.ast.ASTForeachStatement;
24
import net.sourceforge.pmd.lang.java.ast.ASTFormalParameter;
25
import net.sourceforge.pmd.lang.java.ast.ASTGuard;
26
import net.sourceforge.pmd.lang.java.ast.ASTImplicitClassDeclaration;
27
import net.sourceforge.pmd.lang.java.ast.ASTImportDeclaration;
28
import net.sourceforge.pmd.lang.java.ast.ASTInfixExpression;
29
import net.sourceforge.pmd.lang.java.ast.ASTIntersectionType;
30
import net.sourceforge.pmd.lang.java.ast.ASTLambdaExpression;
31
import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration;
32
import net.sourceforge.pmd.lang.java.ast.ASTMethodReference;
33
import net.sourceforge.pmd.lang.java.ast.ASTModuleDeclaration;
34
import net.sourceforge.pmd.lang.java.ast.ASTNullLiteral;
35
import net.sourceforge.pmd.lang.java.ast.ASTNumericLiteral;
36
import net.sourceforge.pmd.lang.java.ast.ASTPattern;
37
import net.sourceforge.pmd.lang.java.ast.ASTPatternExpression;
38
import net.sourceforge.pmd.lang.java.ast.ASTPrimitiveType;
39
import net.sourceforge.pmd.lang.java.ast.ASTReceiverParameter;
40
import net.sourceforge.pmd.lang.java.ast.ASTRecordDeclaration;
41
import net.sourceforge.pmd.lang.java.ast.ASTRecordPattern;
42
import net.sourceforge.pmd.lang.java.ast.ASTResource;
43
import net.sourceforge.pmd.lang.java.ast.ASTStringLiteral;
44
import net.sourceforge.pmd.lang.java.ast.ASTSwitchArrowBranch;
45
import net.sourceforge.pmd.lang.java.ast.ASTSwitchExpression;
46
import net.sourceforge.pmd.lang.java.ast.ASTSwitchLabel;
47
import net.sourceforge.pmd.lang.java.ast.ASTTryStatement;
48
import net.sourceforge.pmd.lang.java.ast.ASTType;
49
import net.sourceforge.pmd.lang.java.ast.ASTTypeArguments;
50
import net.sourceforge.pmd.lang.java.ast.ASTTypeDeclaration;
51
import net.sourceforge.pmd.lang.java.ast.ASTTypeExpression;
52
import net.sourceforge.pmd.lang.java.ast.ASTTypeParameters;
53
import net.sourceforge.pmd.lang.java.ast.ASTTypePattern;
54
import net.sourceforge.pmd.lang.java.ast.ASTUnnamedPattern;
55
import net.sourceforge.pmd.lang.java.ast.ASTVariableId;
56
import net.sourceforge.pmd.lang.java.ast.ASTYieldStatement;
57
import net.sourceforge.pmd.lang.java.ast.BinaryOp;
58
import net.sourceforge.pmd.lang.java.ast.JModifier;
59
import net.sourceforge.pmd.lang.java.ast.JavaNode;
60
import net.sourceforge.pmd.lang.java.ast.JavaTokenKinds;
61
import net.sourceforge.pmd.lang.java.ast.JavaVisitorBase;
62
import net.sourceforge.pmd.lang.java.ast.ModifierOwner;
63
import net.sourceforge.pmd.util.IteratorUtil;
64

65
/**
66
 * Checks that an AST conforms to some language level. The reporting
67
 * behaviour is parameterized with a {@link ReportingStrategy}.
68
 *
69
 * @param <T> Type of object accumulating violations
70
 */
71
public class LanguageLevelChecker<T> {
72

73
    private static final Pattern SPACE_ESCAPE_PATTERN = Pattern.compile("(?<!\\\\)\\\\s");
1✔
74

75
    private final int jdkVersion;
76
    private final boolean preview;
77
    private final CheckVisitor visitor = new CheckVisitor();
1✔
78
    private final ReportingStrategy<T> reportingStrategy;
79

80
    public LanguageLevelChecker(int jdkVersion, boolean preview, ReportingStrategy<T> reportingStrategy) {
1✔
81
        this.jdkVersion = jdkVersion;
1✔
82
        this.preview = preview;
1✔
83
        this.reportingStrategy = reportingStrategy;
1✔
84
    }
1✔
85

86
    public int getJdkVersion() {
87
        return jdkVersion;
×
88
    }
89

90
    public boolean isPreviewEnabled() {
91
        return preview;
×
92
    }
93

94

95
    public void check(JavaNode node) {
96
        T accumulator = reportingStrategy.createAccumulator();
1✔
97
        node.descendantsOrSelf().crossFindBoundaries().filterIs(JavaNode.class).forEach(n -> n.acceptVisitor(visitor, accumulator));
1✔
98
        reportingStrategy.done(accumulator);
1✔
99
    }
1✔
100

101
    private boolean check(Node node, LanguageFeature feature, T acc) {
102
        String message = feature.errorMessage(this.jdkVersion, this.preview);
1✔
103
        if (message != null) {
1✔
104
            reportingStrategy.report(node, message, acc);
×
105
            return false;
×
106
        }
107
        return true;
1✔
108
    }
109

110
    private static String displayNameLower(String name) {
111
        return name.replaceAll("__", "-")
1✔
112
                   .replace('_', ' ')
1✔
113
                   .toLowerCase(Locale.ROOT);
1✔
114
    }
115

116
    private static String versionDisplayName(int jdk) {
117
        if (jdk < 8) {
1✔
118
            return "Java 1." + jdk;
1✔
119
        } else {
120
            return "Java " + jdk;
1✔
121
        }
122
    }
123

124

125
    /**
126
     * Those are just for the preview features.
127
     * They are implemented in at least one preview language version.
128
     * They might be also be standardized.
129
     */
130
    private enum PreviewFeature implements LanguageFeature {
1✔
131
        /**
132
         * Unnamed Classes and Instance Main Methods
133
         * @see <a href="https://openjdk.org/jeps/445">JEP 445: Unnamed Classes and Instance Main Methods (Preview)</a> (Java 21)
134
         * @see <a href="https://openjdk.org/jeps/463">JEP 463: Implicitly Declared Classes and Instance Main Methods (Second Preview)</a> (Java 22)
135
         * @see <a href="https://openjdk.org/jeps/477">JEP 477: Implicitly Declared Classes and Instance Main Methods (Third Preview)</a> (Java 23)
136
         * @see <a href="https://openjdk.org/jeps/495">JEP 495: Simple Source Files and Instance Main Methods (Fourth Preview)</a> (Java 24)
137
         */
138
        SIMPLE_SOURCE_FILES_AND_INSTANCE_MAIN_METHODS(22, 24, false),
1✔
139

140
        /**
141
         * Statements before super
142
         * @see <a href="https://openjdk.org/jeps/447">JEP 447: Statements before super(...) (Preview)</a> (Java 22)
143
         * @see <a href="https://openjdk.org/jeps/482">JEP 482: Flexible Constructor Bodies (Second Preview)</a> (Java 23)
144
         * @see <a href="https://openjdk.org/jeps/492">JEP 492: Flexible Constructor Bodies (Third Preview)</a> (Java 24)
145
         */
146
        FLEXIBLE_CONSTRUCTOR_BODIES(22, 24, false),
1✔
147

148
        /**
149
         * Module import declarations
150
         * @see <a href="https://openjdk.org/jeps/476">JEP 476: Module Import Declarations (Preview)</a> (Java 23)
151
         * @see <a href="https://openjdk.org/jeps/494">JEP 494: Module Import Declarations (Second Preview)</a> (Java 24)
152
         */
153
        MODULE_IMPORT_DECLARATIONS(23, 24, false),
1✔
154

155
        /**
156
         * Primitive types in patterns, instanceof, and switch
157
         * @see <a href="https://openjdk.org/jeps/455">JEP 455: Primitive Types in Patterns, instanceof, and switch (Preview)</a> (Java 23)
158
         * @see <a href="https://openjdk.org/jeps/488">JEP 488: Primitive Types in Patterns, instanceof, and switch (Second Preview)</a> (Java 24)
159
         */
160
        PRIMITIVE_TYPES_IN_PATTERNS_INSTANCEOF_AND_SWITCH(23, 24, false),
1✔
161

162
        ;  // SUPPRESS CHECKSTYLE enum trailing semi is awesome
163

164

165
        private final int minPreviewVersion;
166
        private final int maxPreviewVersion;
167
        private final boolean wasStandardized;
168

169
        PreviewFeature(int minPreviewVersion, int maxPreviewVersion, boolean wasStandardized) {
1✔
170
            this.minPreviewVersion = minPreviewVersion;
1✔
171
            this.maxPreviewVersion = maxPreviewVersion;
1✔
172
            this.wasStandardized = wasStandardized;
1✔
173
        }
1✔
174

175

176
        @Override
177
        public String errorMessage(int jdk, boolean preview) {
178
            boolean isStandard = wasStandardized && jdk > maxPreviewVersion;
1!
179
            boolean canBePreview = jdk >= minPreviewVersion && jdk <= maxPreviewVersion;
1!
180
            boolean isPreview = preview && canBePreview;
1!
181

182
            if (isStandard || isPreview) {
1!
183
                return null;
1✔
184
            }
185

186
            String message = StringUtils.capitalize(displayNameLower(name()));
1✔
187
            if (wasStandardized) {
1!
188
                message += " was only standardized in Java " + (maxPreviewVersion + 1);
×
189
            } else if (canBePreview) {
1✔
190
                message += " is a preview feature of JDK " + jdk;
1✔
191
            } else if (minPreviewVersion == maxPreviewVersion) {
1!
192
                message += " is a preview feature of JDK " + minPreviewVersion;
×
193
            } else {
194
                message += " is a preview feature of JDKs " + minPreviewVersion + " to " + maxPreviewVersion;
1✔
195
            }
196
            return message + ", you should select your language version accordingly";
1✔
197
        }
198
    }
199

200
    /**
201
     * Those use a max valid version.
202
     *
203
     * @see <a href="http://cr.openjdk.java.net/~gbierman/jep397/jep397-20201204/specs/contextual-keywords-jls.html">Contextual Keywords</a>
204
     */
205
    private enum Keywords implements LanguageFeature {
1✔
206
        /**
207
         * ReservedKeyword since Java 1.4.
208
         */
209
        ASSERT_AS_AN_IDENTIFIER(4, "assert"),
1✔
210
        /**
211
         * ReservedKeyword since Java 1.5.
212
         */
213
        ENUM_AS_AN_IDENTIFIER(5, "enum"),
1✔
214
        /**
215
         * ReservedKeyword since Java 9.
216
         */
217
        UNDERSCORE_AS_AN_IDENTIFIER(9, "_"),
1✔
218
        /**
219
         * ContextualKeyword since Java 10.
220
         */
221
        VAR_AS_A_TYPE_NAME(10, "var"),
1✔
222

223
        /**
224
         * ContextualKeyword since Java 13 Preview.
225
         */
226
        YIELD_AS_A_TYPE_NAME(13, "yield"),
1✔
227

228
        /**
229
         * ContextualKeyword since Java 14 Preview.
230
         */
231
        RECORD_AS_A_TYPE_NAME(14, "record"),
1✔
232

233
        /**
234
         * ContextualKeyword since Java 15 Preview.
235
         */
236
        SEALED_AS_A_TYPE_NAME(15, "sealed"),
1✔
237

238
        /**
239
         * ContextualKeyword since Java 15 Preview.
240
         */
241
        PERMITS_AS_A_TYPE_NAME(15, "permits"),
1✔
242

243
        ;  // SUPPRESS CHECKSTYLE enum trailing semi is awesome
244

245
        private final int maxJdkVersion;
246
        private final String reserved;
247

248
        Keywords(int minJdkVersion, String reserved) {
1✔
249
            this.maxJdkVersion = minJdkVersion;
1✔
250
            this.reserved = reserved;
1✔
251
        }
1✔
252

253
        @Override
254
        public String errorMessage(int jdk, boolean preview) {
255
            if (jdk < this.maxJdkVersion) {
1✔
256
                return null;
1✔
257
            }
258
            String s = displayNameLower(name());
1✔
259
            String usageType = s.substring(s.indexOf(' ') + 1); // eg "as an identifier"
1✔
260
            return "Since " + LanguageLevelChecker.versionDisplayName(maxJdkVersion) + ", '" + reserved + "'"
1✔
261
                + " is reserved and cannot be used " + usageType;
262
        }
263
    }
264

265
    /** Those use a min valid version. */
266
    private enum RegularLanguageFeature implements LanguageFeature {
1✔
267

268
        ASSERT_STATEMENTS(4),
1✔
269

270
        STATIC_IMPORT(5),
1✔
271
        ENUMS(5),
1✔
272
        GENERICS(5),
1✔
273
        ANNOTATIONS(5),
1✔
274
        FOREACH_LOOPS(5),
1✔
275
        VARARGS_PARAMETERS(5),
1✔
276
        HEXADECIMAL_FLOATING_POINT_LITERALS(5),
1✔
277

278
        UNDERSCORES_IN_NUMERIC_LITERALS(7),
1✔
279
        BINARY_NUMERIC_LITERALS(7),
1✔
280
        TRY_WITH_RESOURCES(7),
1✔
281
        COMPOSITE_CATCH_CLAUSES(7),
1✔
282
        DIAMOND_TYPE_ARGUMENTS(7),
1✔
283

284
        DEFAULT_METHODS(8),
1✔
285
        RECEIVER_PARAMETERS(8),
1✔
286
        TYPE_ANNOTATIONS(8),
1✔
287
        INTERSECTION_TYPES_IN_CASTS(8),
1✔
288
        LAMBDA_EXPRESSIONS(8),
1✔
289
        METHOD_REFERENCES(8),
1✔
290

291
        MODULE_DECLARATIONS(9),
1✔
292
        DIAMOND_TYPE_ARGUMENTS_FOR_ANONYMOUS_CLASSES(9),
1✔
293
        PRIVATE_METHODS_IN_INTERFACES(9),
1✔
294
        CONCISE_RESOURCE_SYNTAX(9),
1✔
295

296
        /**
297
         * @see <a href="https://openjdk.org/jeps/361">JEP 361: Switch Expressions</a>
298
         */
299
        COMPOSITE_CASE_LABEL(14),
1✔
300
        /**
301
         * @see <a href="https://openjdk.org/jeps/361">JEP 361: Switch Expressions</a>
302
         */
303
        SWITCH_EXPRESSIONS(14),
1✔
304
        /**
305
         * @see <a href="https://openjdk.org/jeps/361">JEP 361: Switch Expressions</a>
306
         */
307
        SWITCH_RULES(14),
1✔
308
        /**
309
         * @see #SWITCH_EXPRESSIONS
310
         * @see <a href="https://openjdk.org/jeps/361">JEP 361: Switch Expressions</a>
311
         */
312
        YIELD_STATEMENTS(14),
1✔
313

314
        /**
315
         * @see <a href="https://openjdk.org/jeps/378">JEP 378: Text Blocks</a>
316
         */
317
        TEXT_BLOCK_LITERALS(15),
1✔
318
        /**
319
         * The new escape sequence {@code \s} simply translates to a single space {@code \u0020}.
320
         *
321
         * @see #TEXT_BLOCK_LITERALS
322
         * @see <a href="https://openjdk.org/jeps/378">JEP 378: Text Blocks</a>
323
         */
324
        SPACE_STRING_ESCAPES(15),
1✔
325

326
        /**
327
         * @see <a href="https://openjdk.org/jeps/359">JEP 359: Records (Preview)</a> (Java 14)
328
         * @see <a href="https://openjdk.org/jeps/384">JEP 384: Records (Second Preview)</a> (Java 15)
329
         * @see <a href="https://openjdk.org/jeps/395">JEP 395: Records</a> (Java 16)
330
         */
331
        RECORD_DECLARATIONS(16),
1✔
332

333
        /**
334
         * @see <a href="https://openjdk.org/jeps/305">JEP 305: Pattern Matching for instanceof (Preview)</a> (Java 14)
335
         * @see <a href="https://openjdk.org/jeps/375">JEP 375: Pattern Matching for instanceof (Second Preview)</a> (Java 15)
336
         * @see <a href="https://openjdk.org/jeps/394">JEP 394: Pattern Matching for instanceof</a> (Java 16)
337
         */
338
        TYPE_PATTERNS_IN_INSTANCEOF(16),
1✔
339

340
        /**
341
         * Part of the records JEP 394.
342
         * @see #RECORD_DECLARATIONS
343
         * @see <a href="https://bugs.openjdk.org/browse/JDK-8253374">JLS changes for Static Members of Inner Classes</a> (Java 16)
344
         */
345
        STATIC_LOCAL_TYPE_DECLARATIONS(16),
1✔
346

347
        /**
348
         * @see <a href="https://openjdk.org/jeps/360">JEP 360: Sealed Classes (Preview)</a> (Java 15)
349
         * @see <a href="https://openjdk.org/jeps/397">JEP 397: Sealed Classes (Second Preview)</a> (Java 16)
350
         * @see <a href="https://openjdk.org/jeps/409">JEP 409: Sealed Classes</a> (Java 17)
351
         */
352
        SEALED_CLASSES(17),
1✔
353

354
        /**
355
         * Pattern matching for switch
356
         * @see <a href="https://openjdk.org/jeps/406">JEP 406: Pattern Matching for switch (Preview)</a> (Java 17)
357
         * @see <a href="https://openjdk.org/jeps/420">JEP 420: Pattern Matching for switch (Second Preview)</a> (Java 18)
358
         * @see <a href="https://openjdk.org/jeps/427">JEP 427: Pattern Matching for switch (Third Preview)</a> (Java 19)
359
         * @see <a href="https://openjdk.org/jeps/433">JEP 433: Pattern Matching for switch (Fourth Preview)</a> (Java 20)
360
         * @see <a href="https://openjdk.org/jeps/441">JEP 441: Pattern Matching for switch</a> (Java 21)
361
         */
362
        PATTERNS_IN_SWITCH_STATEMENTS(21),
1✔
363

364
        /**
365
         * Part of pattern matching for switch
366
         * @see #PATTERNS_IN_SWITCH_STATEMENTS
367
         * @see <a href="https://openjdk.org/jeps/406">JEP 406: Pattern Matching for switch (Preview)</a> (Java 17)
368
         * @see <a href="https://openjdk.org/jeps/420">JEP 420: Pattern Matching for switch (Second Preview)</a> (Java 18)
369
         * @see <a href="https://openjdk.org/jeps/427">JEP 427: Pattern Matching for switch (Third Preview)</a> (Java 19)
370
         * @see <a href="https://openjdk.org/jeps/433">JEP 433: Pattern Matching for switch (Fourth Preview)</a> (Java 20)
371
         * @see <a href="https://openjdk.org/jeps/441">JEP 441: Pattern Matching for switch</a> (Java 21)
372
         */
373
        NULL_IN_SWITCH_CASES(21),
1✔
374

375
        /**
376
         * Part of pattern matching for switch: Case refinement using "when"
377
         * @see #PATTERNS_IN_SWITCH_STATEMENTS
378
         * @see <a href="https://openjdk.org/jeps/427">JEP 427: Pattern Matching for switch (Third Preview)</a> (Java 19)
379
         * @see <a href="https://openjdk.org/jeps/433">JEP 433: Pattern Matching for switch (Fourth Preview)</a> (Java 20)
380
         * @see <a href="https://openjdk.org/jeps/441">JEP 441: Pattern Matching for switch</a> (Java 21)
381
         */
382
        CASE_REFINEMENT(21),
1✔
383

384
        /**
385
         * Record patterns
386
         * @see <a href="https://openjdk.org/jeps/405">JEP 405: Record Patterns (Preview)</a> (Java 19)
387
         * @see <a href="https://openjdk.org/jeps/432">JEP 432: Record Patterns (Second Preview)</a> (Java 20)
388
         * @see <a href="https://openjdk.org/jeps/440">JEP 440: Record Patterns</a> (Java 21)
389
         */
390
        RECORD_PATTERNS(21),
1✔
391

392
        /**
393
         * Unnamed variables and patterns.
394
         * @see <a href="https://openjdk.org/jeps/443">JEP 443: Unnamed patterns and variables (Preview)</a> (Java 21)
395
         * @see <a href="https://openjdk.org/jeps/456">JEP 456: Unnamed Variables & Patterns</a> (Java 22)
396
         */
397
        UNNAMED_VARIABLES_AND_PATTERNS(22),
1✔
398

399
        ;  // SUPPRESS CHECKSTYLE enum trailing semi is awesome
400

401
        private final int minJdkLevel;
402

403
        RegularLanguageFeature(int minJdkLevel) {
1✔
404
            this.minJdkLevel = minJdkLevel;
1✔
405
        }
1✔
406

407

408
        @Override
409
        public String errorMessage(int jdk, boolean preview) {
410
            if (jdk >= this.minJdkLevel) {
1✔
411
                return null;
1✔
412
            }
413
            return StringUtils.capitalize(displayNameLower(name()))
1✔
414
                + " are a feature of " + versionDisplayName(minJdkLevel)
1✔
415
                + ", you should select your language version accordingly";
416
        }
417

418
    }
419

420
    interface LanguageFeature {
421

422
        @Nullable
423
        String errorMessage(int jdk, boolean preview);
424
    }
425

426
    private final class CheckVisitor extends JavaVisitorBase<T, Void> {
1✔
427

428
        @Override
429
        protected Void visitChildren(Node node, T data) {
430
            throw new AssertionError("Shouldn't recurse");
×
431
        }
432

433
        @Override
434
        public Void visitNode(Node node, T param) {
435
            return null;
1✔
436
        }
437

438
        @Override
439
        public Void visit(ASTImplicitClassDeclaration node, T data) {
440
            check(node, PreviewFeature.SIMPLE_SOURCE_FILES_AND_INSTANCE_MAIN_METHODS, data);
1✔
441
            return null;
1✔
442
        }
443

444
        @Override
445
        public Void visit(ASTStringLiteral node, T data) {
446
            if (!node.isTextBlock() && SPACE_ESCAPE_PATTERN.matcher(node.getLiteralText()).find()) {
1✔
447
                check(node, RegularLanguageFeature.SPACE_STRING_ESCAPES, data);
×
448
            }
449
            if (node.isTextBlock()) {
1✔
450
                check(node, RegularLanguageFeature.TEXT_BLOCK_LITERALS, data);
1✔
451
            }
452
            return null;
1✔
453
        }
454

455
        @Override
456
        public Void visit(ASTImportDeclaration node, T data) {
457
            if (node.isStatic()) {
1✔
458
                check(node, RegularLanguageFeature.STATIC_IMPORT, data);
1✔
459
            }
460
            if (node.isModuleImport()) {
1✔
461
                check(node, PreviewFeature.MODULE_IMPORT_DECLARATIONS, data);
1✔
462
            }
463
            return null;
1✔
464
        }
465

466
        @Override
467
        public Void visit(ASTYieldStatement node, T data) {
468
            check(node, RegularLanguageFeature.YIELD_STATEMENTS, data);
1✔
469
            return null;
1✔
470
        }
471

472
        @Override
473
        public Void visit(ASTSwitchExpression node, T data) {
474
            check(node, RegularLanguageFeature.SWITCH_EXPRESSIONS, data);
1✔
475
            return null;
1✔
476
        }
477

478
        @Override
479
        public Void visit(ASTRecordDeclaration node, T data) {
480
            check(node, RegularLanguageFeature.RECORD_DECLARATIONS, data);
1✔
481
            return null;
1✔
482
        }
483

484
        @Override
485
        public Void visit(ASTConstructorCall node, T data) {
486
            if (node.usesDiamondTypeArgs()) {
1✔
487
                if (check(node, RegularLanguageFeature.DIAMOND_TYPE_ARGUMENTS, data) && node.isAnonymousClass()) {
1!
488
                    check(node, RegularLanguageFeature.DIAMOND_TYPE_ARGUMENTS_FOR_ANONYMOUS_CLASSES, data);
1✔
489
                }
490
            }
491
            return null;
1✔
492
        }
493

494
        @Override
495
        public Void visit(ASTTypeArguments node, T data) {
496
            check(node, RegularLanguageFeature.GENERICS, data);
1✔
497
            return null;
1✔
498
        }
499

500
        @Override
501
        public Void visit(ASTTypeParameters node, T data) {
502
            check(node, RegularLanguageFeature.GENERICS, data);
1✔
503
            return null;
1✔
504
        }
505

506
        @Override
507
        public Void visit(ASTFormalParameter node, T data) {
508
            if (node.isVarargs()) {
1✔
509
                check(node, RegularLanguageFeature.VARARGS_PARAMETERS, data);
1✔
510
            }
511
            return null;
1✔
512
        }
513

514

515
        @Override
516
        public Void visit(ASTReceiverParameter node, T data) {
517
            check(node, RegularLanguageFeature.RECEIVER_PARAMETERS, data);
1✔
518
            return null;
1✔
519
        }
520

521
        @Override
522
        public Void visit(ASTAnnotation node, T data) {
523
            if (node.getParent() instanceof ASTType) {
1✔
524
                check(node, RegularLanguageFeature.TYPE_ANNOTATIONS, data);
1✔
525
            } else {
526
                check(node, RegularLanguageFeature.ANNOTATIONS, data);
1✔
527
            }
528
            return null;
1✔
529
        }
530

531
        @Override
532
        public Void visit(ASTForeachStatement node, T data) {
533
            check(node, RegularLanguageFeature.FOREACH_LOOPS, data);
1✔
534
            return null;
1✔
535
        }
536

537
        @Override
538
        public Void visit(ASTEnumDeclaration node, T data) {
539
            check(node, RegularLanguageFeature.ENUMS, data);
1✔
540
            visitTypeDecl(node, data);
1✔
541
            return null;
1✔
542
        }
543

544
        @Override
545
        public Void visit(ASTNumericLiteral node, T data) {
546
            int base = node.getBase();
1✔
547
            if (base == 16 && !node.isIntegral()) {
1✔
548
                check(node, RegularLanguageFeature.HEXADECIMAL_FLOATING_POINT_LITERALS, data);
1✔
549
            } else if (base == 2) {
1✔
550
                check(node, RegularLanguageFeature.BINARY_NUMERIC_LITERALS, data);
1✔
551
            } else if (node.getLiteralText().indexOf('_') >= 0) {
1✔
552
                check(node, RegularLanguageFeature.UNDERSCORES_IN_NUMERIC_LITERALS, data);
1✔
553
            }
554
            return null;
1✔
555
        }
556

557
        @Override
558
        public Void visit(ASTMethodReference node, T data) {
559
            check(node, RegularLanguageFeature.METHOD_REFERENCES, data);
1✔
560
            return null;
1✔
561
        }
562

563
        @Override
564
        public Void visit(ASTLambdaExpression node, T data) {
565
            check(node, RegularLanguageFeature.LAMBDA_EXPRESSIONS, data);
1✔
566
            return null;
1✔
567
        }
568

569
        @Override
570
        public Void visit(ASTMethodDeclaration node, T data) {
571
            if (node.hasModifiers(JModifier.DEFAULT)) {
1✔
572
                check(node, RegularLanguageFeature.DEFAULT_METHODS, data);
1✔
573
            }
574

575
            if (node.hasVisibility(ModifierOwner.Visibility.V_PRIVATE) && node.getEnclosingType() != null && node.getEnclosingType().isInterface()) {
1!
576
                check(node, RegularLanguageFeature.PRIVATE_METHODS_IN_INTERFACES, data);
1✔
577
            }
578

579
            checkIdent(node, node.getName(), data);
1✔
580
            return null;
1✔
581
        }
582

583
        @Override
584
        public Void visit(ASTAssertStatement node, T data) {
585
            check(node, RegularLanguageFeature.ASSERT_STATEMENTS, data);
1✔
586
            return null;
1✔
587
        }
588

589
        @Override
590
        public Void visit(ASTTypePattern node, T data) {
591
            check(node, RegularLanguageFeature.TYPE_PATTERNS_IN_INSTANCEOF, data);
1✔
592
            return null;
1✔
593
        }
594

595
        @Override
596
        public Void visit(ASTRecordPattern node, T data) {
597
            check(node, RegularLanguageFeature.RECORD_PATTERNS, data);
1✔
598
            return null;
1✔
599
        }
600

601
        @Override
602
        public Void visit(ASTGuard node, T data) {
603
            check(node, RegularLanguageFeature.CASE_REFINEMENT, data);
1✔
604
            return null;
1✔
605
        }
606

607
        @Override
608
        public Void visit(ASTTryStatement node, T data) {
609
            if (node.isTryWithResources()) {
1✔
610
                if (check(node, RegularLanguageFeature.TRY_WITH_RESOURCES, data)) {
1!
611
                    for (ASTResource resource : node.getResources()) {
1✔
612
                        if (resource.isConciseResource()) {
1✔
613
                            check(node, RegularLanguageFeature.CONCISE_RESOURCE_SYNTAX, data);
1✔
614
                            break;
1✔
615
                        }
616
                    }
1✔
617
                }
618
            }
619
            return null;
1✔
620
        }
621

622

623
        @Override
624
        public Void visit(ASTIntersectionType node, T data) {
625
            if (node.getParent() instanceof ASTCastExpression) {
1✔
626
                check(node, RegularLanguageFeature.INTERSECTION_TYPES_IN_CASTS, data);
1✔
627
            }
628
            return null;
1✔
629
        }
630

631

632
        @Override
633
        public Void visit(ASTCatchClause node, T data) {
634
            if (node.getParameter().isMulticatch()) {
1✔
635
                check(node, RegularLanguageFeature.COMPOSITE_CATCH_CLAUSES, data);
1✔
636
            }
637
            return null;
1✔
638
        }
639

640
        @Override
641
        public Void visit(ASTSwitchLabel node, T data) {
642
            if (IteratorUtil.count(node.iterator()) > 1) {
1✔
643
                check(node, RegularLanguageFeature.COMPOSITE_CASE_LABEL, data);
1✔
644
            }
645
            if (node.isDefault() && JavaTokenKinds.CASE == node.getFirstToken().getKind()) {
1✔
646
                check(node, RegularLanguageFeature.PATTERNS_IN_SWITCH_STATEMENTS, data);
1✔
647
            }
648
            if (node.getFirstChild() instanceof ASTNullLiteral) {
1✔
649
                check(node, RegularLanguageFeature.NULL_IN_SWITCH_CASES, data);
1✔
650
            }
651
            if (node.getFirstChild() instanceof ASTPattern) {
1✔
652
                check(node, RegularLanguageFeature.PATTERNS_IN_SWITCH_STATEMENTS, data);
1✔
653
            }
654
            if (node.getFirstChild() instanceof ASTTypePattern
1✔
655
                    && ((ASTTypePattern) node.getFirstChild()).getTypeNode() instanceof ASTPrimitiveType) {
1✔
656
                check(node, PreviewFeature.PRIMITIVE_TYPES_IN_PATTERNS_INSTANCEOF_AND_SWITCH, data);
1✔
657
            }
658
            return null;
1✔
659
        }
660

661
        @Override
662
        public Void visit(ASTModuleDeclaration node, T data) {
663
            check(node, RegularLanguageFeature.MODULE_DECLARATIONS, data);
1✔
664
            return null;
1✔
665
        }
666

667
        @Override
668
        public Void visit(ASTSwitchArrowBranch node, T data) {
669
            check(node, RegularLanguageFeature.SWITCH_RULES, data);
1✔
670
            return null;
1✔
671
        }
672

673
        @Override
674
        public Void visit(ASTVariableId node, T data) {
675
            checkIdent(node, node.getName(), data);
1✔
676
            return null;
1✔
677
        }
678

679
        @Override
680
        public Void visit(ASTUnnamedPattern node, T data) {
681
            check(node, RegularLanguageFeature.UNNAMED_VARIABLES_AND_PATTERNS, data);
1✔
682
            return null;
1✔
683
        }
684

685
        @Override
686
        public Void visitTypeDecl(ASTTypeDeclaration node, T data) {
687
            if (node.getModifiers().hasAnyExplicitly(JModifier.SEALED, JModifier.NON_SEALED)) {
1✔
688
                check(node, RegularLanguageFeature.SEALED_CLASSES, data);
1✔
689
            } else if (node.isLocal() && !node.isRegularClass()) {
1✔
690
                check(node, RegularLanguageFeature.STATIC_LOCAL_TYPE_DECLARATIONS, data);
1✔
691
            }
692
            String simpleName = node.getSimpleName();
1✔
693
            if ("var".equals(simpleName)) {
1✔
694
                check(node, Keywords.VAR_AS_A_TYPE_NAME, data);
1✔
695
            } else if ("yield".equals(simpleName)) {
1!
696
                check(node, Keywords.YIELD_AS_A_TYPE_NAME, data);
×
697
            } else if ("record".equals(simpleName)) {
1✔
698
                check(node, Keywords.RECORD_AS_A_TYPE_NAME, data);
×
699
            } else if ("sealed".equals(simpleName)) {
1!
700
                check(node, Keywords.SEALED_AS_A_TYPE_NAME, data);
×
701
            } else if ("permits".equals(simpleName)) {
1!
702
                check(node, Keywords.PERMITS_AS_A_TYPE_NAME, data);
×
703
            }
704
            checkIdent(node, simpleName, data);
1✔
705
            return null;
1✔
706
        }
707

708
        @Override
709
        public Void visit(ASTConstructorDeclaration node, T data) {
710
            super.visit(node, data);
1✔
711
            if (node.getBody().descendants(ASTExplicitConstructorInvocation.class).nonEmpty()) {
1✔
712
                if (!(node.getBody().getFirstChild() instanceof ASTExplicitConstructorInvocation)) {
1✔
713
                    check(node, PreviewFeature.FLEXIBLE_CONSTRUCTOR_BODIES, data);
1✔
714
                }
715
            }
716
            return null;
1✔
717
        }
718

719
        @Override
720
        public Void visit(ASTInfixExpression node, T data) {
721
            if (node.getOperator() == BinaryOp.INSTANCEOF) {
1✔
722
                if (node.getRightOperand() instanceof ASTPatternExpression && node.getRightOperand().getFirstChild() instanceof ASTTypePattern) {
1✔
723
                    ASTTypePattern typePattern = (ASTTypePattern) node.getRightOperand().getFirstChild();
1✔
724
                    if (typePattern.getTypeNode() instanceof ASTPrimitiveType) {
1✔
725
                        check(node, PreviewFeature.PRIMITIVE_TYPES_IN_PATTERNS_INSTANCEOF_AND_SWITCH, data);
1✔
726
                    }
727
                } else if (node.getRightOperand() instanceof ASTTypeExpression) {
1✔
728
                    ASTTypeExpression typeExpression = (ASTTypeExpression) node.getRightOperand();
1✔
729
                    if (typeExpression.getTypeNode() instanceof ASTPrimitiveType) {
1✔
730
                        check(node, PreviewFeature.PRIMITIVE_TYPES_IN_PATTERNS_INSTANCEOF_AND_SWITCH, data);
1✔
731
                    }
732
                }
733
            }
734
            return super.visit(node, data);
1✔
735
        }
736

737
        private void checkIdent(JavaNode node, String simpleName, T acc) {
738
            if ("enum".equals(simpleName)) {
1✔
739
                check(node, Keywords.ENUM_AS_AN_IDENTIFIER, acc);
1✔
740
            } else if ("assert".equals(simpleName)) {
1✔
741
                check(node, Keywords.ASSERT_AS_AN_IDENTIFIER, acc);
1✔
742
            } else if ("_".equals(simpleName)) {
1✔
743
                // see ASTVariableId#isUnnamed()
744
                // java 1-8: "_" is a valid name for an identifier
745
                // java 9-21: "_" is a restricted keyword and cannot be used anymore as an identifier
746
                // java 22+: "_" denotes an unnamed variable
747

748
                // in order to display a nicer message, we tell beginning with java 21,
749
                // (which brings record patterns, where unnamed patterns might be interesting)
750
                // that with java 22+ "_" can be used for unnamed variables
751
                if (LanguageLevelChecker.this.jdkVersion >= 21) {
1✔
752
                    check(node, RegularLanguageFeature.UNNAMED_VARIABLES_AND_PATTERNS, acc);
1✔
753
                } else {
754
                    check(node, Keywords.UNDERSCORE_AS_AN_IDENTIFIER, acc);
1✔
755
                }
756
            }
757
        }
1✔
758

759
    }
760

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