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

pmd / pmd / 95

22 Jul 2025 05:33PM UTC coverage: 78.418% (-0.02%) from 78.436%
95

push

github

web-flow
chore: [scala] Fix javadoc config (#5920)

17758 of 23477 branches covered (75.64%)

Branch coverage included in aggregate %.

38997 of 48898 relevant lines covered (79.75%)

0.81 hits per line

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

95.36
/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
         * Compact Source Files 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
         * @see <a href="https://openjdk.org/jeps/512">JEP 512: Compact Source Files and Instance Main Methods</a> (Java 25)
138
         */
139
        COMPACT_SOURCE_FILES_AND_INSTANCE_MAIN_METHODS(22, 24, true),
1✔
140

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

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

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

166
        ;  // SUPPRESS CHECKSTYLE enum trailing semi is awesome
167

168

169
        private final int minPreviewVersion;
170
        private final int maxPreviewVersion;
171
        private final boolean wasStandardized;
172

173
        PreviewFeature(int minPreviewVersion, int maxPreviewVersion, boolean wasStandardized) {
1✔
174
            this.minPreviewVersion = minPreviewVersion;
1✔
175
            this.maxPreviewVersion = maxPreviewVersion;
1✔
176
            this.wasStandardized = wasStandardized;
1✔
177
        }
1✔
178

179

180
        @Override
181
        public String errorMessage(int jdk, boolean preview) {
182
            boolean isStandard = wasStandardized && jdk > maxPreviewVersion;
1✔
183
            boolean canBePreview = jdk >= minPreviewVersion && jdk <= maxPreviewVersion;
1✔
184
            boolean isPreview = preview && canBePreview;
1✔
185

186
            if (isStandard || isPreview) {
1✔
187
                return null;
1✔
188
            }
189

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

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

227
        /**
228
         * ContextualKeyword since Java 13 Preview.
229
         */
230
        YIELD_AS_A_TYPE_NAME(13, "yield"),
1✔
231

232
        /**
233
         * ContextualKeyword since Java 14 Preview.
234
         */
235
        RECORD_AS_A_TYPE_NAME(14, "record"),
1✔
236

237
        /**
238
         * ContextualKeyword since Java 15 Preview.
239
         */
240
        SEALED_AS_A_TYPE_NAME(15, "sealed"),
1✔
241

242
        /**
243
         * ContextualKeyword since Java 15 Preview.
244
         */
245
        PERMITS_AS_A_TYPE_NAME(15, "permits"),
1✔
246

247
        ;  // SUPPRESS CHECKSTYLE enum trailing semi is awesome
248

249
        private final int maxJdkVersion;
250
        private final String reserved;
251

252
        Keywords(int minJdkVersion, String reserved) {
1✔
253
            this.maxJdkVersion = minJdkVersion;
1✔
254
            this.reserved = reserved;
1✔
255
        }
1✔
256

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

269
    /** Those use a min valid version. */
270
    private enum RegularLanguageFeature implements LanguageFeature {
1✔
271

272
        ASSERT_STATEMENTS(4),
1✔
273

274
        STATIC_IMPORT(5),
1✔
275
        ENUMS(5),
1✔
276
        GENERICS(5),
1✔
277
        ANNOTATIONS(5),
1✔
278
        FOREACH_LOOPS(5),
1✔
279
        VARARGS_PARAMETERS(5),
1✔
280
        HEXADECIMAL_FLOATING_POINT_LITERALS(5),
1✔
281

282
        UNDERSCORES_IN_NUMERIC_LITERALS(7),
1✔
283
        BINARY_NUMERIC_LITERALS(7),
1✔
284
        TRY_WITH_RESOURCES(7),
1✔
285
        COMPOSITE_CATCH_CLAUSES(7),
1✔
286
        DIAMOND_TYPE_ARGUMENTS(7),
1✔
287

288
        DEFAULT_METHODS(8),
1✔
289
        RECEIVER_PARAMETERS(8),
1✔
290
        TYPE_ANNOTATIONS(8),
1✔
291
        INTERSECTION_TYPES_IN_CASTS(8),
1✔
292
        LAMBDA_EXPRESSIONS(8),
1✔
293
        METHOD_REFERENCES(8),
1✔
294

295
        MODULE_DECLARATIONS(9),
1✔
296
        DIAMOND_TYPE_ARGUMENTS_FOR_ANONYMOUS_CLASSES(9),
1✔
297
        PRIVATE_METHODS_IN_INTERFACES(9),
1✔
298
        CONCISE_RESOURCE_SYNTAX(9),
1✔
299

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

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

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

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

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

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

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

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

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

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

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

403
        ;  // SUPPRESS CHECKSTYLE enum trailing semi is awesome
404

405
        private final int minJdkLevel;
406

407
        RegularLanguageFeature(int minJdkLevel) {
1✔
408
            this.minJdkLevel = minJdkLevel;
1✔
409
        }
1✔
410

411

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

422
    }
423

424
    interface LanguageFeature {
425

426
        @Nullable
427
        String errorMessage(int jdk, boolean preview);
428
    }
429

430
    private final class CheckVisitor extends JavaVisitorBase<T, Void> {
1✔
431

432
        @Override
433
        protected Void visitChildren(Node node, T data) {
434
            throw new AssertionError("Shouldn't recurse");
×
435
        }
436

437
        @Override
438
        public Void visitNode(Node node, T param) {
439
            return null;
1✔
440
        }
441

442
        @Override
443
        public Void visit(ASTImplicitClassDeclaration node, T data) {
444
            check(node, PreviewFeature.COMPACT_SOURCE_FILES_AND_INSTANCE_MAIN_METHODS, data);
1✔
445
            return null;
1✔
446
        }
447

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

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

470
        @Override
471
        public Void visit(ASTYieldStatement node, T data) {
472
            check(node, RegularLanguageFeature.YIELD_STATEMENTS, data);
1✔
473
            return null;
1✔
474
        }
475

476
        @Override
477
        public Void visit(ASTSwitchExpression node, T data) {
478
            check(node, RegularLanguageFeature.SWITCH_EXPRESSIONS, data);
1✔
479
            return null;
1✔
480
        }
481

482
        @Override
483
        public Void visit(ASTRecordDeclaration node, T data) {
484
            check(node, RegularLanguageFeature.RECORD_DECLARATIONS, data);
1✔
485
            return null;
1✔
486
        }
487

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

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

504
        @Override
505
        public Void visit(ASTTypeParameters node, T data) {
506
            check(node, RegularLanguageFeature.GENERICS, data);
1✔
507
            return null;
1✔
508
        }
509

510
        @Override
511
        public Void visit(ASTFormalParameter node, T data) {
512
            if (node.isVarargs()) {
1✔
513
                check(node, RegularLanguageFeature.VARARGS_PARAMETERS, data);
1✔
514
            }
515
            return null;
1✔
516
        }
517

518

519
        @Override
520
        public Void visit(ASTReceiverParameter node, T data) {
521
            check(node, RegularLanguageFeature.RECEIVER_PARAMETERS, data);
1✔
522
            return null;
1✔
523
        }
524

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

535
        @Override
536
        public Void visit(ASTForeachStatement node, T data) {
537
            check(node, RegularLanguageFeature.FOREACH_LOOPS, data);
1✔
538
            return null;
1✔
539
        }
540

541
        @Override
542
        public Void visit(ASTEnumDeclaration node, T data) {
543
            check(node, RegularLanguageFeature.ENUMS, data);
1✔
544
            visitTypeDecl(node, data);
1✔
545
            return null;
1✔
546
        }
547

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

561
        @Override
562
        public Void visit(ASTMethodReference node, T data) {
563
            check(node, RegularLanguageFeature.METHOD_REFERENCES, data);
1✔
564
            return null;
1✔
565
        }
566

567
        @Override
568
        public Void visit(ASTLambdaExpression node, T data) {
569
            check(node, RegularLanguageFeature.LAMBDA_EXPRESSIONS, data);
1✔
570
            return null;
1✔
571
        }
572

573
        @Override
574
        public Void visit(ASTMethodDeclaration node, T data) {
575
            if (node.hasModifiers(JModifier.DEFAULT)) {
1✔
576
                check(node, RegularLanguageFeature.DEFAULT_METHODS, data);
1✔
577
            }
578

579
            if (node.hasVisibility(ModifierOwner.Visibility.V_PRIVATE) && node.getEnclosingType() != null && node.getEnclosingType().isInterface()) {
1!
580
                check(node, RegularLanguageFeature.PRIVATE_METHODS_IN_INTERFACES, data);
1✔
581
            }
582

583
            checkIdent(node, node.getName(), data);
1✔
584
            return null;
1✔
585
        }
586

587
        @Override
588
        public Void visit(ASTAssertStatement node, T data) {
589
            check(node, RegularLanguageFeature.ASSERT_STATEMENTS, data);
1✔
590
            return null;
1✔
591
        }
592

593
        @Override
594
        public Void visit(ASTTypePattern node, T data) {
595
            check(node, RegularLanguageFeature.TYPE_PATTERNS_IN_INSTANCEOF, data);
1✔
596
            return null;
1✔
597
        }
598

599
        @Override
600
        public Void visit(ASTRecordPattern node, T data) {
601
            check(node, RegularLanguageFeature.RECORD_PATTERNS, data);
1✔
602
            return null;
1✔
603
        }
604

605
        @Override
606
        public Void visit(ASTGuard node, T data) {
607
            check(node, RegularLanguageFeature.CASE_REFINEMENT, data);
1✔
608
            return null;
1✔
609
        }
610

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

626

627
        @Override
628
        public Void visit(ASTIntersectionType node, T data) {
629
            if (node.getParent() instanceof ASTCastExpression) {
1✔
630
                check(node, RegularLanguageFeature.INTERSECTION_TYPES_IN_CASTS, data);
1✔
631
            }
632
            return null;
1✔
633
        }
634

635

636
        @Override
637
        public Void visit(ASTCatchClause node, T data) {
638
            if (node.getParameter().isMulticatch()) {
1✔
639
                check(node, RegularLanguageFeature.COMPOSITE_CATCH_CLAUSES, data);
1✔
640
            }
641
            return null;
1✔
642
        }
643

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

665
        @Override
666
        public Void visit(ASTModuleDeclaration node, T data) {
667
            check(node, RegularLanguageFeature.MODULE_DECLARATIONS, data);
1✔
668
            return null;
1✔
669
        }
670

671
        @Override
672
        public Void visit(ASTSwitchArrowBranch node, T data) {
673
            check(node, RegularLanguageFeature.SWITCH_RULES, data);
1✔
674
            return null;
1✔
675
        }
676

677
        @Override
678
        public Void visit(ASTVariableId node, T data) {
679
            checkIdent(node, node.getName(), data);
1✔
680
            return null;
1✔
681
        }
682

683
        @Override
684
        public Void visit(ASTUnnamedPattern node, T data) {
685
            check(node, RegularLanguageFeature.UNNAMED_VARIABLES_AND_PATTERNS, data);
1✔
686
            return null;
1✔
687
        }
688

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

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

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

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

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

763
    }
764

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