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

pmd / pmd / 367

23 Jan 2026 03:29PM UTC coverage: 78.953% (-0.02%) from 78.97%
367

push

github

adangel
[ci] publish-snapshot: fix run-coveralls job

For some reason, we now need test-compile...

18523 of 24344 branches covered (76.09%)

Branch coverage included in aggregate %.

40374 of 50254 relevant lines covered (80.34%)

0.81 hits per line

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

93.4
/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.ASTLambdaParameter;
32
import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration;
33
import net.sourceforge.pmd.lang.java.ast.ASTMethodReference;
34
import net.sourceforge.pmd.lang.java.ast.ASTModuleDeclaration;
35
import net.sourceforge.pmd.lang.java.ast.ASTNullLiteral;
36
import net.sourceforge.pmd.lang.java.ast.ASTNumericLiteral;
37
import net.sourceforge.pmd.lang.java.ast.ASTPattern;
38
import net.sourceforge.pmd.lang.java.ast.ASTPatternExpression;
39
import net.sourceforge.pmd.lang.java.ast.ASTPrimitiveType;
40
import net.sourceforge.pmd.lang.java.ast.ASTReceiverParameter;
41
import net.sourceforge.pmd.lang.java.ast.ASTRecordDeclaration;
42
import net.sourceforge.pmd.lang.java.ast.ASTRecordPattern;
43
import net.sourceforge.pmd.lang.java.ast.ASTResource;
44
import net.sourceforge.pmd.lang.java.ast.ASTStringLiteral;
45
import net.sourceforge.pmd.lang.java.ast.ASTSwitchArrowBranch;
46
import net.sourceforge.pmd.lang.java.ast.ASTSwitchExpression;
47
import net.sourceforge.pmd.lang.java.ast.ASTSwitchLabel;
48
import net.sourceforge.pmd.lang.java.ast.ASTTryStatement;
49
import net.sourceforge.pmd.lang.java.ast.ASTType;
50
import net.sourceforge.pmd.lang.java.ast.ASTTypeArguments;
51
import net.sourceforge.pmd.lang.java.ast.ASTTypeDeclaration;
52
import net.sourceforge.pmd.lang.java.ast.ASTTypeExpression;
53
import net.sourceforge.pmd.lang.java.ast.ASTTypeParameters;
54
import net.sourceforge.pmd.lang.java.ast.ASTTypePattern;
55
import net.sourceforge.pmd.lang.java.ast.ASTUnnamedPattern;
56
import net.sourceforge.pmd.lang.java.ast.ASTVariableId;
57
import net.sourceforge.pmd.lang.java.ast.ASTYieldStatement;
58
import net.sourceforge.pmd.lang.java.ast.BinaryOp;
59
import net.sourceforge.pmd.lang.java.ast.JModifier;
60
import net.sourceforge.pmd.lang.java.ast.JavaNode;
61
import net.sourceforge.pmd.lang.java.ast.JavaTokenKinds;
62
import net.sourceforge.pmd.lang.java.ast.JavaVisitorBase;
63
import net.sourceforge.pmd.lang.java.ast.ModifierOwner;
64
import net.sourceforge.pmd.util.IteratorUtil;
65

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

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

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

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

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

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

95

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

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

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

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

125

126
    /**
127
     * Those are just for the preview features.
128
     * They are implemented in at least one preview language version.
129
     * They might be also be standardized.
130
     */
131
    private enum PreviewFeature implements LanguageFeature {
1✔
132
        /**
133
         * Primitive types in patterns, instanceof, and switch
134
         * @see <a href="https://openjdk.org/jeps/455">JEP 455: Primitive Types in Patterns, instanceof, and switch (Preview)</a> (Java 23)
135
         * @see <a href="https://openjdk.org/jeps/488">JEP 488: Primitive Types in Patterns, instanceof, and switch (Second Preview)</a> (Java 24)
136
         * @see <a href="https://openjdk.org/jeps/507">JEP 507: Primitive Types in Patterns, instanceof, and switch (Third Preview)</a> (Java 25)
137
         * @see <a href="https://openjdk.org/jeps/530">JEP 530: Primitive Types in Patterns, instanceof, and switch (Fourth Preview)</a> (Java 26)
138
         */
139
        PRIMITIVE_TYPES_IN_PATTERNS_INSTANCEOF_AND_SWITCH(23, 26, false),
1✔
140

141
        ;  // SUPPRESS CHECKSTYLE enum trailing semi is awesome
142

143

144
        private final int minPreviewVersion;
145
        private final int maxPreviewVersion;
146
        private final boolean wasStandardized;
147

148
        PreviewFeature(int minPreviewVersion, int maxPreviewVersion, boolean wasStandardized) {
1✔
149
            this.minPreviewVersion = minPreviewVersion;
1✔
150
            this.maxPreviewVersion = maxPreviewVersion;
1✔
151
            this.wasStandardized = wasStandardized;
1✔
152
        }
1✔
153

154

155
        @Override
156
        public String errorMessage(int jdk, boolean preview) {
157
            boolean isStandard = wasStandardized && jdk > maxPreviewVersion;
1!
158
            boolean canBePreview = jdk >= minPreviewVersion && jdk <= maxPreviewVersion;
1!
159
            boolean isPreview = preview && canBePreview;
1!
160

161
            if (isStandard || isPreview) {
1!
162
                return null;
1✔
163
            }
164

165
            String message = StringUtils.capitalize(displayNameLower(name()));
1✔
166
            if (wasStandardized) {
1!
167
                message += " was only standardized in Java " + (maxPreviewVersion + 1);
×
168
            } else if (canBePreview) {
1✔
169
                message += " is a preview feature of JDK " + jdk;
1✔
170
            } else if (minPreviewVersion == maxPreviewVersion) {
1!
171
                message += " is a preview feature of JDK " + minPreviewVersion;
×
172
            } else {
173
                message += " is a preview feature of JDKs " + minPreviewVersion + " to " + maxPreviewVersion;
1✔
174
            }
175
            return message + ", you should select your language version accordingly";
1✔
176
        }
177
    }
178

179
    /**
180
     * Those use a max valid version.
181
     *
182
     * @see <a href="http://cr.openjdk.java.net/~gbierman/jep397/jep397-20201204/specs/contextual-keywords-jls.html">Contextual Keywords</a>
183
     */
184
    private enum Keywords implements LanguageFeature {
1✔
185
        /**
186
         * ReservedKeyword since Java 1.4.
187
         */
188
        ASSERT_AS_AN_IDENTIFIER(4, "assert"),
1✔
189
        /**
190
         * ReservedKeyword since Java 1.5.
191
         */
192
        ENUM_AS_AN_IDENTIFIER(5, "enum"),
1✔
193
        /**
194
         * ReservedKeyword since Java 9.
195
         */
196
        UNDERSCORE_AS_AN_IDENTIFIER(9, "_"),
1✔
197
        /**
198
         * ContextualKeyword since Java 10.
199
         */
200
        VAR_AS_A_TYPE_NAME(10, "var"),
1✔
201

202
        /**
203
         * ContextualKeyword since Java 13 Preview.
204
         */
205
        YIELD_AS_A_TYPE_NAME(13, "yield"),
1✔
206

207
        /**
208
         * ContextualKeyword since Java 14 Preview.
209
         */
210
        RECORD_AS_A_TYPE_NAME(14, "record"),
1✔
211

212
        /**
213
         * ContextualKeyword since Java 15 Preview.
214
         */
215
        SEALED_AS_A_TYPE_NAME(15, "sealed"),
1✔
216

217
        /**
218
         * ContextualKeyword since Java 15 Preview.
219
         */
220
        PERMITS_AS_A_TYPE_NAME(15, "permits"),
1✔
221

222
        ;  // SUPPRESS CHECKSTYLE enum trailing semi is awesome
223

224
        private final int maxJdkVersion;
225
        private final String reserved;
226

227
        Keywords(int minJdkVersion, String reserved) {
1✔
228
            this.maxJdkVersion = minJdkVersion;
1✔
229
            this.reserved = reserved;
1✔
230
        }
1✔
231

232
        @Override
233
        public String errorMessage(int jdk, boolean preview) {
234
            if (jdk < this.maxJdkVersion) {
1✔
235
                return null;
1✔
236
            }
237
            String s = displayNameLower(name());
1✔
238
            String usageType = s.substring(s.indexOf(' ') + 1); // eg "as an identifier"
1✔
239
            return "Since " + LanguageLevelChecker.versionDisplayName(maxJdkVersion) + ", '" + reserved + "'"
1✔
240
                + " is reserved and cannot be used " + usageType;
241
        }
242
    }
243

244
    /** Those use a min valid version. */
245
    private enum RegularLanguageFeature implements LanguageFeature {
1✔
246

247
        ASSERT_STATEMENTS(4),
1✔
248

249
        STATIC_IMPORT(5),
1✔
250
        ENUMS(5),
1✔
251
        GENERICS(5),
1✔
252
        ANNOTATIONS(5),
1✔
253
        FOREACH_LOOPS(5),
1✔
254
        VARARGS_PARAMETERS(5),
1✔
255
        HEXADECIMAL_FLOATING_POINT_LITERALS(5),
1✔
256

257
        UNDERSCORES_IN_NUMERIC_LITERALS(7),
1✔
258
        BINARY_NUMERIC_LITERALS(7),
1✔
259
        TRY_WITH_RESOURCES(7),
1✔
260
        COMPOSITE_CATCH_CLAUSES(7),
1✔
261
        DIAMOND_TYPE_ARGUMENTS(7),
1✔
262

263
        DEFAULT_METHODS(8),
1✔
264
        RECEIVER_PARAMETERS(8),
1✔
265
        TYPE_ANNOTATIONS(8),
1✔
266
        INTERSECTION_TYPES_IN_CASTS(8),
1✔
267
        LAMBDA_EXPRESSIONS(8),
1✔
268
        METHOD_REFERENCES(8),
1✔
269

270
        MODULE_DECLARATIONS(9),
1✔
271
        DIAMOND_TYPE_ARGUMENTS_FOR_ANONYMOUS_CLASSES(9),
1✔
272
        PRIVATE_METHODS_IN_INTERFACES(9),
1✔
273
        CONCISE_RESOURCE_SYNTAX(9),
1✔
274

275
        VAR_KEYWORD_IN_LAMBDA_PARAMETER(11),
1✔
276

277
        /**
278
         * @see <a href="https://openjdk.org/jeps/361">JEP 361: Switch Expressions</a>
279
         */
280
        COMPOSITE_CASE_LABEL(14),
1✔
281
        /**
282
         * @see <a href="https://openjdk.org/jeps/361">JEP 361: Switch Expressions</a>
283
         */
284
        SWITCH_EXPRESSIONS(14),
1✔
285
        /**
286
         * @see <a href="https://openjdk.org/jeps/361">JEP 361: Switch Expressions</a>
287
         */
288
        SWITCH_RULES(14),
1✔
289
        /**
290
         * @see #SWITCH_EXPRESSIONS
291
         * @see <a href="https://openjdk.org/jeps/361">JEP 361: Switch Expressions</a>
292
         */
293
        YIELD_STATEMENTS(14),
1✔
294

295
        /**
296
         * @see <a href="https://openjdk.org/jeps/378">JEP 378: Text Blocks</a>
297
         */
298
        TEXT_BLOCK_LITERALS(15),
1✔
299
        /**
300
         * The new escape sequence {@code \s} simply translates to a single space {@code \u0020}.
301
         *
302
         * @see #TEXT_BLOCK_LITERALS
303
         * @see <a href="https://openjdk.org/jeps/378">JEP 378: Text Blocks</a>
304
         */
305
        SPACE_STRING_ESCAPES(15),
1✔
306

307
        /**
308
         * @see <a href="https://openjdk.org/jeps/359">JEP 359: Records (Preview)</a> (Java 14)
309
         * @see <a href="https://openjdk.org/jeps/384">JEP 384: Records (Second Preview)</a> (Java 15)
310
         * @see <a href="https://openjdk.org/jeps/395">JEP 395: Records</a> (Java 16)
311
         */
312
        RECORD_DECLARATIONS(16),
1✔
313

314
        /**
315
         * @see <a href="https://openjdk.org/jeps/305">JEP 305: Pattern Matching for instanceof (Preview)</a> (Java 14)
316
         * @see <a href="https://openjdk.org/jeps/375">JEP 375: Pattern Matching for instanceof (Second Preview)</a> (Java 15)
317
         * @see <a href="https://openjdk.org/jeps/394">JEP 394: Pattern Matching for instanceof</a> (Java 16)
318
         */
319
        TYPE_PATTERNS_IN_INSTANCEOF(16),
1✔
320

321
        /**
322
         * Part of the records JEP 394.
323
         * @see #RECORD_DECLARATIONS
324
         * @see <a href="https://bugs.openjdk.org/browse/JDK-8253374">JLS changes for Static Members of Inner Classes</a> (Java 16)
325
         */
326
        STATIC_LOCAL_TYPE_DECLARATIONS(16),
1✔
327

328
        /**
329
         * @see <a href="https://openjdk.org/jeps/360">JEP 360: Sealed Classes (Preview)</a> (Java 15)
330
         * @see <a href="https://openjdk.org/jeps/397">JEP 397: Sealed Classes (Second Preview)</a> (Java 16)
331
         * @see <a href="https://openjdk.org/jeps/409">JEP 409: Sealed Classes</a> (Java 17)
332
         */
333
        SEALED_CLASSES(17),
1✔
334

335
        /**
336
         * Pattern matching for switch
337
         * @see <a href="https://openjdk.org/jeps/406">JEP 406: Pattern Matching for switch (Preview)</a> (Java 17)
338
         * @see <a href="https://openjdk.org/jeps/420">JEP 420: Pattern Matching for switch (Second Preview)</a> (Java 18)
339
         * @see <a href="https://openjdk.org/jeps/427">JEP 427: Pattern Matching for switch (Third Preview)</a> (Java 19)
340
         * @see <a href="https://openjdk.org/jeps/433">JEP 433: Pattern Matching for switch (Fourth Preview)</a> (Java 20)
341
         * @see <a href="https://openjdk.org/jeps/441">JEP 441: Pattern Matching for switch</a> (Java 21)
342
         */
343
        PATTERNS_IN_SWITCH_STATEMENTS(21),
1✔
344

345
        /**
346
         * Part of pattern matching for switch
347
         * @see #PATTERNS_IN_SWITCH_STATEMENTS
348
         * @see <a href="https://openjdk.org/jeps/406">JEP 406: Pattern Matching for switch (Preview)</a> (Java 17)
349
         * @see <a href="https://openjdk.org/jeps/420">JEP 420: Pattern Matching for switch (Second Preview)</a> (Java 18)
350
         * @see <a href="https://openjdk.org/jeps/427">JEP 427: Pattern Matching for switch (Third Preview)</a> (Java 19)
351
         * @see <a href="https://openjdk.org/jeps/433">JEP 433: Pattern Matching for switch (Fourth Preview)</a> (Java 20)
352
         * @see <a href="https://openjdk.org/jeps/441">JEP 441: Pattern Matching for switch</a> (Java 21)
353
         */
354
        NULL_IN_SWITCH_CASES(21),
1✔
355

356
        /**
357
         * Part of pattern matching for switch: Case refinement using "when"
358
         * @see #PATTERNS_IN_SWITCH_STATEMENTS
359
         * @see <a href="https://openjdk.org/jeps/427">JEP 427: Pattern Matching for switch (Third Preview)</a> (Java 19)
360
         * @see <a href="https://openjdk.org/jeps/433">JEP 433: Pattern Matching for switch (Fourth Preview)</a> (Java 20)
361
         * @see <a href="https://openjdk.org/jeps/441">JEP 441: Pattern Matching for switch</a> (Java 21)
362
         */
363
        CASE_REFINEMENT(21),
1✔
364

365
        /**
366
         * Record patterns
367
         * @see <a href="https://openjdk.org/jeps/405">JEP 405: Record Patterns (Preview)</a> (Java 19)
368
         * @see <a href="https://openjdk.org/jeps/432">JEP 432: Record Patterns (Second Preview)</a> (Java 20)
369
         * @see <a href="https://openjdk.org/jeps/440">JEP 440: Record Patterns</a> (Java 21)
370
         */
371
        RECORD_PATTERNS(21),
1✔
372

373
        /**
374
         * Unnamed variables and patterns.
375
         * @see <a href="https://openjdk.org/jeps/443">JEP 443: Unnamed patterns and variables (Preview)</a> (Java 21)
376
         * @see <a href="https://openjdk.org/jeps/456">JEP 456: Unnamed Variables & Patterns</a> (Java 22)
377
         */
378
        UNNAMED_VARIABLES_AND_PATTERNS(22),
1✔
379

380
        /**
381
         * Compact Source Files and Instance Main Methods
382
         * @see <a href="https://openjdk.org/jeps/445">JEP 445: Unnamed Classes and Instance Main Methods (Preview)</a> (Java 21)
383
         * @see <a href="https://openjdk.org/jeps/463">JEP 463: Implicitly Declared Classes and Instance Main Methods (Second Preview)</a> (Java 22)
384
         * @see <a href="https://openjdk.org/jeps/477">JEP 477: Implicitly Declared Classes and Instance Main Methods (Third Preview)</a> (Java 23)
385
         * @see <a href="https://openjdk.org/jeps/495">JEP 495: Simple Source Files and Instance Main Methods (Fourth Preview)</a> (Java 24)
386
         * @see <a href="https://openjdk.org/jeps/512">JEP 512: Compact Source Files and Instance Main Methods</a> (Java 25)
387
         */
388
        COMPACT_SOURCE_FILES_AND_INSTANCE_MAIN_METHODS(25),
1✔
389

390
        /**
391
         * Flexible Constructor Bodies
392
         * @see <a href="https://openjdk.org/jeps/447">JEP 447: Statements before super(...) (Preview)</a> (Java 22)
393
         * @see <a href="https://openjdk.org/jeps/482">JEP 482: Flexible Constructor Bodies (Second Preview)</a> (Java 23)
394
         * @see <a href="https://openjdk.org/jeps/492">JEP 492: Flexible Constructor Bodies (Third Preview)</a> (Java 24)
395
         * @see <a href="https://openjdk.org/jeps/513">JEP 513: Flexible Constructor Bodies</a> (Java 25)
396
         */
397
        FLEXIBLE_CONSTRUCTOR_BODIES(25),
1✔
398

399
        /**
400
         * Module import declarations
401
         * @see <a href="https://openjdk.org/jeps/476">JEP 476: Module Import Declarations (Preview)</a> (Java 23)
402
         * @see <a href="https://openjdk.org/jeps/494">JEP 494: Module Import Declarations (Second Preview)</a> (Java 24)
403
         * @see <a href="https://openjdk.org/jeps/511">JEP 511: Module Import Declarations</a> (Java 25)
404
         */
405
        MODULE_IMPORT_DECLARATIONS(25),
1✔
406

407
        ;  // SUPPRESS CHECKSTYLE enum trailing semi is awesome
408

409
        private final int minJdkLevel;
410

411
        RegularLanguageFeature(int minJdkLevel) {
1✔
412
            this.minJdkLevel = minJdkLevel;
1✔
413
        }
1✔
414

415

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

426
    }
427

428
    interface LanguageFeature {
429

430
        @Nullable
431
        String errorMessage(int jdk, boolean preview);
432
    }
433

434
    private final class CheckVisitor extends JavaVisitorBase<T, Void> {
1✔
435

436
        @Override
437
        protected Void visitChildren(Node node, T data) {
438
            throw new AssertionError("Shouldn't recurse");
×
439
        }
440

441
        @Override
442
        public Void visitNode(Node node, T param) {
443
            return null;
1✔
444
        }
445

446
        @Override
447
        public Void visit(ASTImplicitClassDeclaration node, T data) {
448
            check(node, RegularLanguageFeature.COMPACT_SOURCE_FILES_AND_INSTANCE_MAIN_METHODS, data);
1✔
449
            return null;
1✔
450
        }
451

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

463
        @Override
464
        public Void visit(ASTImportDeclaration node, T data) {
465
            if (node.isStatic()) {
1✔
466
                check(node, RegularLanguageFeature.STATIC_IMPORT, data);
1✔
467
            }
468
            if (node.isModuleImport()) {
1✔
469
                check(node, RegularLanguageFeature.MODULE_IMPORT_DECLARATIONS, data);
1✔
470
            }
471
            return null;
1✔
472
        }
473

474
        @Override
475
        public Void visit(ASTYieldStatement node, T data) {
476
            check(node, RegularLanguageFeature.YIELD_STATEMENTS, data);
1✔
477
            return null;
1✔
478
        }
479

480
        @Override
481
        public Void visit(ASTSwitchExpression node, T data) {
482
            check(node, RegularLanguageFeature.SWITCH_EXPRESSIONS, data);
1✔
483
            return null;
1✔
484
        }
485

486
        @Override
487
        public Void visit(ASTRecordDeclaration node, T data) {
488
            check(node, RegularLanguageFeature.RECORD_DECLARATIONS, data);
1✔
489
            return null;
1✔
490
        }
491

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

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

508
        @Override
509
        public Void visit(ASTTypeParameters node, T data) {
510
            check(node, RegularLanguageFeature.GENERICS, data);
1✔
511
            return null;
1✔
512
        }
513

514
        @Override
515
        public Void visit(ASTFormalParameter node, T data) {
516
            if (node.isVarargs()) {
1✔
517
                check(node, RegularLanguageFeature.VARARGS_PARAMETERS, data);
1✔
518
            }
519
            return null;
1✔
520
        }
521

522

523
        @Override
524
        public Void visit(ASTReceiverParameter node, T data) {
525
            check(node, RegularLanguageFeature.RECEIVER_PARAMETERS, data);
1✔
526
            return null;
1✔
527
        }
528

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

539
        @Override
540
        public Void visit(ASTForeachStatement node, T data) {
541
            check(node, RegularLanguageFeature.FOREACH_LOOPS, data);
1✔
542
            return null;
1✔
543
        }
544

545
        @Override
546
        public Void visit(ASTEnumDeclaration node, T data) {
547
            check(node, RegularLanguageFeature.ENUMS, data);
1✔
548
            visitTypeDecl(node, data);
1✔
549
            return null;
1✔
550
        }
551

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

565
        @Override
566
        public Void visit(ASTMethodReference node, T data) {
567
            check(node, RegularLanguageFeature.METHOD_REFERENCES, data);
1✔
568
            return null;
1✔
569
        }
570

571
        @Override
572
        public Void visit(ASTLambdaExpression node, T data) {
573
            check(node, RegularLanguageFeature.LAMBDA_EXPRESSIONS, data);
1✔
574
            return null;
1✔
575
        }
576

577

578
        @Override
579
        public Void visit(ASTLambdaParameter node, T data) {
580
            if (node.hasVarKeyword()) {
1✔
581
                check(node, RegularLanguageFeature.VAR_KEYWORD_IN_LAMBDA_PARAMETER, data);
1✔
582
            }
583
            return null;
1✔
584
        }
585

586
        @Override
587
        public Void visit(ASTMethodDeclaration node, T data) {
588
            if (node.hasModifiers(JModifier.DEFAULT)) {
1✔
589
                check(node, RegularLanguageFeature.DEFAULT_METHODS, data);
1✔
590
            }
591

592
            if (node.hasVisibility(ModifierOwner.Visibility.V_PRIVATE) && node.getEnclosingType() != null && node.getEnclosingType().isInterface()) {
1!
593
                check(node, RegularLanguageFeature.PRIVATE_METHODS_IN_INTERFACES, data);
1✔
594
            }
595

596
            checkIdent(node, node.getName(), data);
1✔
597
            return null;
1✔
598
        }
599

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

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

612
        @Override
613
        public Void visit(ASTRecordPattern node, T data) {
614
            check(node, RegularLanguageFeature.RECORD_PATTERNS, data);
1✔
615
            return null;
1✔
616
        }
617

618
        @Override
619
        public Void visit(ASTGuard node, T data) {
620
            check(node, RegularLanguageFeature.CASE_REFINEMENT, data);
1✔
621
            return null;
1✔
622
        }
623

624
        @Override
625
        public Void visit(ASTTryStatement node, T data) {
626
            if (node.isTryWithResources()) {
1✔
627
                if (check(node, RegularLanguageFeature.TRY_WITH_RESOURCES, data)) {
1!
628
                    for (ASTResource resource : node.getResources()) {
1✔
629
                        if (resource.isConciseResource()) {
1✔
630
                            check(node, RegularLanguageFeature.CONCISE_RESOURCE_SYNTAX, data);
1✔
631
                            break;
1✔
632
                        }
633
                    }
1✔
634
                }
635
            }
636
            return null;
1✔
637
        }
638

639

640
        @Override
641
        public Void visit(ASTIntersectionType node, T data) {
642
            if (node.getParent() instanceof ASTCastExpression) {
1✔
643
                check(node, RegularLanguageFeature.INTERSECTION_TYPES_IN_CASTS, data);
1✔
644
            }
645
            return null;
1✔
646
        }
647

648

649
        @Override
650
        public Void visit(ASTCatchClause node, T data) {
651
            if (node.getParameter().isMulticatch()) {
1✔
652
                check(node, RegularLanguageFeature.COMPOSITE_CATCH_CLAUSES, data);
1✔
653
            }
654
            return null;
1✔
655
        }
656

657
        @Override
658
        public Void visit(ASTSwitchLabel node, T data) {
659
            if (IteratorUtil.count(node.iterator()) > 1) {
1✔
660
                check(node, RegularLanguageFeature.COMPOSITE_CASE_LABEL, data);
1✔
661
            }
662
            if (node.isDefault() && JavaTokenKinds.CASE == node.getFirstToken().getKind()) {
1✔
663
                check(node, RegularLanguageFeature.PATTERNS_IN_SWITCH_STATEMENTS, data);
1✔
664
            }
665
            if (node.getFirstChild() instanceof ASTNullLiteral) {
1✔
666
                check(node, RegularLanguageFeature.NULL_IN_SWITCH_CASES, data);
1✔
667
            }
668
            if (node.getFirstChild() instanceof ASTPattern) {
1✔
669
                check(node, RegularLanguageFeature.PATTERNS_IN_SWITCH_STATEMENTS, data);
1✔
670
            }
671
            if (node.getFirstChild() instanceof ASTTypePattern
1✔
672
                    && ((ASTTypePattern) node.getFirstChild()).getTypeNode() instanceof ASTPrimitiveType) {
1✔
673
                check(node, PreviewFeature.PRIMITIVE_TYPES_IN_PATTERNS_INSTANCEOF_AND_SWITCH, data);
1✔
674
            }
675
            return null;
1✔
676
        }
677

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

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

690
        @Override
691
        public Void visit(ASTVariableId node, T data) {
692
            checkIdent(node, node.getName(), data);
1✔
693
            return null;
1✔
694
        }
695

696
        @Override
697
        public Void visit(ASTUnnamedPattern node, T data) {
698
            check(node, RegularLanguageFeature.UNNAMED_VARIABLES_AND_PATTERNS, data);
1✔
699
            return null;
1✔
700
        }
701

702
        @Override
703
        public Void visitTypeDecl(ASTTypeDeclaration node, T data) {
704
            if (node.getModifiers().hasAnyExplicitly(JModifier.SEALED, JModifier.NON_SEALED)) {
1✔
705
                check(node, RegularLanguageFeature.SEALED_CLASSES, data);
1✔
706
            } else if (node.isLocal() && !node.isRegularClass()) {
1✔
707
                check(node, RegularLanguageFeature.STATIC_LOCAL_TYPE_DECLARATIONS, data);
1✔
708
            }
709
            String simpleName = node.getSimpleName();
1✔
710
            if ("var".equals(simpleName)) {
1✔
711
                check(node, Keywords.VAR_AS_A_TYPE_NAME, data);
1✔
712
            } else if ("yield".equals(simpleName)) {
1!
713
                check(node, Keywords.YIELD_AS_A_TYPE_NAME, data);
×
714
            } else if ("record".equals(simpleName)) {
1✔
715
                check(node, Keywords.RECORD_AS_A_TYPE_NAME, data);
×
716
            } else if ("sealed".equals(simpleName)) {
1!
717
                check(node, Keywords.SEALED_AS_A_TYPE_NAME, data);
×
718
            } else if ("permits".equals(simpleName)) {
1!
719
                check(node, Keywords.PERMITS_AS_A_TYPE_NAME, data);
×
720
            }
721
            checkIdent(node, simpleName, data);
1✔
722
            return null;
1✔
723
        }
724

725
        @Override
726
        public Void visit(ASTConstructorDeclaration node, T data) {
727
            super.visit(node, data);
1✔
728
            if (node.getBody().descendants(ASTExplicitConstructorInvocation.class).nonEmpty()) {
1✔
729
                if (!(node.getBody().getFirstChild() instanceof ASTExplicitConstructorInvocation)) {
1✔
730
                    check(node, RegularLanguageFeature.FLEXIBLE_CONSTRUCTOR_BODIES, data);
1✔
731
                }
732
            }
733
            return null;
1✔
734
        }
735

736
        @Override
737
        public Void visit(ASTInfixExpression node, T data) {
738
            if (node.getOperator() == BinaryOp.INSTANCEOF) {
1✔
739
                if (node.getRightOperand() instanceof ASTPatternExpression && node.getRightOperand().getFirstChild() instanceof ASTTypePattern) {
1✔
740
                    ASTTypePattern typePattern = (ASTTypePattern) node.getRightOperand().getFirstChild();
1✔
741
                    if (typePattern.getTypeNode() instanceof ASTPrimitiveType) {
1✔
742
                        check(node, PreviewFeature.PRIMITIVE_TYPES_IN_PATTERNS_INSTANCEOF_AND_SWITCH, data);
1✔
743
                    }
744
                } else if (node.getRightOperand() instanceof ASTTypeExpression) {
1✔
745
                    ASTTypeExpression typeExpression = (ASTTypeExpression) node.getRightOperand();
1✔
746
                    if (typeExpression.getTypeNode() instanceof ASTPrimitiveType) {
1✔
747
                        check(node, PreviewFeature.PRIMITIVE_TYPES_IN_PATTERNS_INSTANCEOF_AND_SWITCH, data);
1✔
748
                    }
749
                }
750
            }
751
            return super.visit(node, data);
1✔
752
        }
753

754
        private void checkIdent(JavaNode node, String simpleName, T acc) {
755
            if ("enum".equals(simpleName)) {
1✔
756
                check(node, Keywords.ENUM_AS_AN_IDENTIFIER, acc);
1✔
757
            } else if ("assert".equals(simpleName)) {
1✔
758
                check(node, Keywords.ASSERT_AS_AN_IDENTIFIER, acc);
1✔
759
            } else if ("_".equals(simpleName)) {
1✔
760
                // see ASTVariableId#isUnnamed()
761
                // java 1-8: "_" is a valid name for an identifier
762
                // java 9-21: "_" is a restricted keyword and cannot be used anymore as an identifier
763
                // java 22+: "_" denotes an unnamed variable
764

765
                // in order to display a nicer message, we tell beginning with java 21,
766
                // (which brings record patterns, where unnamed patterns might be interesting)
767
                // that with java 22+ "_" can be used for unnamed variables
768
                if (LanguageLevelChecker.this.jdkVersion >= 21) {
1✔
769
                    check(node, RegularLanguageFeature.UNNAMED_VARIABLES_AND_PATTERNS, acc);
1✔
770
                } else {
771
                    check(node, Keywords.UNDERSCORE_AS_AN_IDENTIFIER, acc);
1✔
772
                }
773
            }
774
        }
1✔
775

776
    }
777

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