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

pmd / pmd / 277

27 Nov 2025 01:37PM UTC coverage: 78.778% (+0.03%) from 78.749%
277

push

github

adangel
[java] UseArraysAsList: skip when if-statements (#6228)

18419 of 24233 branches covered (76.01%)

Branch coverage included in aggregate %.

40090 of 50038 relevant lines covered (80.12%)

0.81 hits per line

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

91.19
/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/ModifierOrderRule.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.rule.codestyle;
6

7
import java.util.List;
8

9
import org.checkerframework.checker.nullness.qual.Nullable;
10

11
import net.sourceforge.pmd.lang.ast.impl.javacc.JavaccToken;
12
import net.sourceforge.pmd.lang.java.ast.ASTAnnotation;
13
import net.sourceforge.pmd.lang.java.ast.ASTConstructorDeclaration;
14
import net.sourceforge.pmd.lang.java.ast.ASTLambdaParameter;
15
import net.sourceforge.pmd.lang.java.ast.ASTLocalVariableDeclaration;
16
import net.sourceforge.pmd.lang.java.ast.ASTModifierList;
17
import net.sourceforge.pmd.lang.java.ast.ASTType;
18
import net.sourceforge.pmd.lang.java.ast.ASTTypeParameters;
19
import net.sourceforge.pmd.lang.java.ast.ASTVoidType;
20
import net.sourceforge.pmd.lang.java.ast.JModifier;
21
import net.sourceforge.pmd.lang.java.ast.JavaNode;
22
import net.sourceforge.pmd.lang.java.ast.JavaTokenKinds;
23
import net.sourceforge.pmd.lang.java.ast.internal.PrettyPrintingUtil;
24
import net.sourceforge.pmd.lang.java.rule.AbstractJavaRulechainRule;
25
import net.sourceforge.pmd.lang.java.symbols.JClassSymbol;
26
import net.sourceforge.pmd.lang.java.symbols.JTypeDeclSymbol;
27
import net.sourceforge.pmd.properties.PropertyDescriptor;
28
import net.sourceforge.pmd.properties.PropertyFactory;
29
import net.sourceforge.pmd.reporting.RuleContext;
30
import net.sourceforge.pmd.util.AssertionUtil;
31
import net.sourceforge.pmd.util.OptionalBool;
32

33
/**
34
 * @since 7.17.0
35
 */
36
public class ModifierOrderRule extends AbstractJavaRulechainRule {
37

38
    private static final String MSG_KEYWORD_ORDER =
39
        "Missorted modifiers `{0} {1}`.";
40

41
    private static final String MSG_TYPE_ANNOTATIONS_SHOULD_BE_BEFORE_MODS =
42
        "Missorted modifiers `{0} {1}`. Type annotations should be placed before modifiers.";
43

44
    private static final String MSG_NON_TYPE_ANNOTATIONS_SHOULD_BE_BEFORE_MODS =
45
        "Missorted modifiers `{0} {1}`. Non-type annotations should be placed before modifiers.";
46

47
    private static final String MSG_TYPE_ANNOT_SHOULD_BE_BEFORE_TYPE =
48
        "Missorted modifiers `{0} {1}`. Type annotations should be placed before the type they qualify.";
49

50
    private static final PropertyDescriptor<TypeAnnotationPosition> TYPE_ANNOT_POLICY
1✔
51
        = PropertyFactory.enumProperty("typeAnnotations", TypeAnnotationPosition.class, TypeAnnotationPosition::label)
1✔
52
                         .desc("Whether type annotations should be placed next to the type they qualify and not before modifiers.")
1✔
53
                         .defaultValue(TypeAnnotationPosition.ANYWHERE)
1✔
54
                         .build();
1✔
55

56
    public enum TypeAnnotationPosition {
1✔
57
        ON_TYPE,
1✔
58
        ON_DECL,
1✔
59
        ANYWHERE;
1✔
60

61
        String label() {
62
            switch (this) {
1!
63
            case ON_TYPE:
64
                return "ontype";
1✔
65
            case ON_DECL:
66
                return "ondecl";
1✔
67
            case ANYWHERE:
68
                return "anywhere";
1✔
69
            }
70
            throw AssertionUtil.shouldNotReachHere("exhaustive switch");
×
71
        }
72
    }
73

74
    private TypeAnnotationPosition typeAnnotPosition;
75

76
    public ModifierOrderRule() {
77
        super(ASTModifierList.class);
1✔
78
        definePropertyDescriptor(TYPE_ANNOT_POLICY);
1✔
79
    }
1✔
80

81
    @Override
82
    public void start(RuleContext ctx) {
83
        this.typeAnnotPosition = getProperty(TYPE_ANNOT_POLICY);
1✔
84
    }
1✔
85

86
    /** Wrapper around a mod to do "double dispatch". */
87
    abstract static class LastModSeen {
1✔
88
        abstract boolean checkNextKeyword(KwMod next, RuleContext ctx);
89

90
        abstract boolean checkNextAnnot(AnnotMod next, RuleContext ctx);
91

92
        abstract boolean checkNextTypeParams(TypeParamsMod next, RuleContext ctx);
93

94
        @Override
95
        public abstract String toString();
96
    }
97

98
    class KwMod extends LastModSeen {
99
        private final JModifier mod;
100
        private final JavaccToken token;
101
        private final JavaNode reportNode;
102

103
        KwMod(JModifier mod, JavaccToken token, JavaNode reportNode) {
1✔
104
            this.mod = mod;
1✔
105
            this.token = token;
1✔
106
            this.reportNode = reportNode;
1✔
107
        }
1✔
108

109
        @Override
110
        boolean checkNextKeyword(KwMod next, RuleContext ctx) {
111
            if (mod.compareTo(next.mod) > 0) {
1✔
112
                ctx.addViolationWithPosition(reportNode, token, MSG_KEYWORD_ORDER, this, next);
1✔
113
                return true;
1✔
114
            }
115
            return false;
1✔
116
        }
117

118
        @Override
119
        boolean checkNextAnnot(AnnotMod next, RuleContext ctx) {
120
            // keyword before annot
121
            if (next.isTypeAnnot != OptionalBool.NO && typeAnnotPosition != TypeAnnotationPosition.ON_DECL) {
1✔
122
                return false;
1✔
123
            }
124

125
            if (next.isTypeAnnot.isTrue()) {
1✔
126
                ctx.addViolationWithPosition(reportNode, token, MSG_TYPE_ANNOTATIONS_SHOULD_BE_BEFORE_MODS, this, next);
1✔
127
            } else {
128
                ctx.addViolationWithPosition(reportNode, token, MSG_NON_TYPE_ANNOTATIONS_SHOULD_BE_BEFORE_MODS, this, next);
1✔
129
            }
130
            return true;
1✔
131

132
        }
133

134
        @Override
135
        boolean checkNextTypeParams(TypeParamsMod next, RuleContext ctx) {
136
            return false;
1✔
137
        }
138

139
        @Override
140
        public String toString() {
141
            return mod.getToken();
1✔
142
        }
143
    }
144

145
    class AnnotMod extends LastModSeen {
146
        private final @Nullable LastModSeen previous;
147
        private final ASTAnnotation annot;
148
        private final OptionalBool isTypeAnnot;
149

150
        AnnotMod(@Nullable LastModSeen previous, ASTAnnotation annot, boolean contextsAcceptsTypeAnnot) {
1✔
151
            this.previous = previous;
1✔
152
            this.annot = annot;
1✔
153
            this.isTypeAnnot = !contextsAcceptsTypeAnnot ? OptionalBool.NO : isTypeAnnotation(annot);
1✔
154
        }
1✔
155

156

157
        @Override
158
        boolean checkNextKeyword(KwMod next, RuleContext ctx) {
159
            if (isTypeAnnot.isTrue() && typeAnnotPosition == TypeAnnotationPosition.ON_TYPE) {
1✔
160
                ctx.addViolationWithMessage(annot, MSG_TYPE_ANNOT_SHOULD_BE_BEFORE_TYPE, this, next);
1✔
161
                return true;
1✔
162
            }
163

164
            if (previous instanceof KwMod) {
1✔
165
                // annotation sandwiched between keywords
166
                if (isTypeAnnot.isTrue() && typeAnnotPosition != TypeAnnotationPosition.ON_DECL) {
1!
167
                    ctx.addViolationWithMessage(annot, MSG_TYPE_ANNOT_SHOULD_BE_BEFORE_TYPE, this, next);
1✔
168
                } else if (isTypeAnnot.isTrue()) {
×
169
                    ctx.addViolationWithMessage(annot, MSG_TYPE_ANNOTATIONS_SHOULD_BE_BEFORE_MODS, previous, this);
×
170
                } else {
171
                    ctx.addViolationWithMessage(annot, MSG_NON_TYPE_ANNOTATIONS_SHOULD_BE_BEFORE_MODS, previous, this);
×
172
                }
173
                return true;
1✔
174
            }
175

176
            return false;
1✔
177
        }
178

179
        @Override
180
        boolean checkNextAnnot(AnnotMod next, RuleContext ctx) {
181
            // todo we could sort annotations (alphabetically or by length)
182
            return false;
1✔
183
        }
184

185
        @Override
186
        boolean checkNextTypeParams(TypeParamsMod next, RuleContext ctx) {
187
            if (isTypeAnnot.isTrue() && typeAnnotPosition == TypeAnnotationPosition.ON_TYPE) {
1✔
188
                ctx.addViolationWithMessage(annot, MSG_TYPE_ANNOT_SHOULD_BE_BEFORE_TYPE, this, next);
1✔
189
                return true;
1✔
190
            }
191
            return false;
1✔
192
        }
193

194
        @Override
195
        public String toString() {
196
            return PrettyPrintingUtil.prettyPrintAnnot(annot);
1✔
197
        }
198
    }
199

200
    class TypeParamsMod extends LastModSeen {
201
        private final ASTTypeParameters typeParameters;
202

203
        TypeParamsMod(ASTTypeParameters typeParameters) {
1✔
204
            this.typeParameters = typeParameters;
1✔
205
        }
1✔
206

207
        @Override
208
        boolean checkNextKeyword(KwMod next, RuleContext ctx) {
209
            return false;
×
210
        }
211

212
        @Override
213
        boolean checkNextAnnot(AnnotMod next, RuleContext ctx) {
214
            return false;
×
215
        }
216

217
        @Override
218
        boolean checkNextTypeParams(TypeParamsMod next, RuleContext ctx) {
219
            return false;
×
220
        }
221

222
        @Override
223
        public String toString() {
224
            return typeParameters.getText().toString();
1✔
225
        }
226
    }
227

228
    @Override
229
    public Object visit(ASTModifierList modList, Object data) {
230
        RuleContext ctx = asCtx(data);
1✔
231
        boolean acceptsTypeAnnot = contextCanHaveTypeAnnots(modList);
1✔
232
        ModifierOrderEvents eventHandler = new ModifierOrderEvents() {
1✔
233

234
            private @Nullable LastModSeen lastModSeen;
235

236

237
            @Override
238
            public boolean recordAnnotation(ASTAnnotation annot) {
239
                AnnotMod annotMod = new AnnotMod(lastModSeen, annot, acceptsTypeAnnot);
1✔
240
                if (lastModSeen != null) {
1✔
241
                    if (lastModSeen.checkNextAnnot(annotMod, ctx)) {
1✔
242
                        return true;
1✔
243
                    }
244
                }
245
                lastModSeen = annotMod;
1✔
246
                return false;
1✔
247
            }
248

249
            @Override
250
            public boolean recordModifier(JModifier mod, JavaccToken token) {
251
                KwMod kwMod = new KwMod(mod, token, modList);
1✔
252
                if (lastModSeen != null) {
1✔
253
                    if (lastModSeen.checkNextKeyword(kwMod, ctx)) {
1✔
254
                        return true;
1✔
255
                    }
256
                }
257
                lastModSeen = kwMod;
1✔
258
                return false;
1✔
259
            }
260

261
            @Override
262
            public boolean recordTypeParameters(ASTTypeParameters typeParameters) {
263
                TypeParamsMod typeParamsMod = new TypeParamsMod(typeParameters);
1✔
264
                if (lastModSeen != null) {
1✔
265
                    if (lastModSeen.checkNextTypeParams(typeParamsMod, ctx)) {
1✔
266
                        return true;
1✔
267
                    }
268
                }
269
                lastModSeen = typeParamsMod;
1✔
270
                return false;
1✔
271
            }
272
        };
273

274
        readModifierList(modList, eventHandler);
1✔
275
        return null;
1✔
276
    }
277

278
    private static boolean contextCanHaveTypeAnnots(ASTModifierList modList) {
279
        ASTType followingType = getFollowingType(modList);
1✔
280
        return followingType != null && !(followingType instanceof ASTVoidType)
1✔
281
            || isFollowedByVarKeyword(modList)
1✔
282
            || modList.getParent() instanceof ASTConstructorDeclaration;
1✔
283
    }
284

285
    private static OptionalBool isTypeAnnotation(ASTAnnotation node) {
286
        JTypeDeclSymbol sym = node.getTypeNode().getTypeMirror().getSymbol();
1✔
287
        if (sym instanceof JClassSymbol) {
1!
288
            return ((JClassSymbol) sym).mayBeTypeAnnotation(node.getLanguageVersion());
1✔
289
        }
290
        return OptionalBool.UNKNOWN;
×
291
    }
292

293
    private static @Nullable ASTType getFollowingType(ASTModifierList node) {
294
        JavaNode nextSibling = node.getNextSibling();
1✔
295
        if (nextSibling instanceof ASTTypeParameters) {
1✔
296
            nextSibling = nextSibling.getNextSibling();
1✔
297
        }
298
        if (nextSibling instanceof ASTType) {
1✔
299
            return (ASTType) nextSibling;
1✔
300
        }
301
        return null;
1✔
302
    }
303

304
    private static boolean isFollowedByVarKeyword(ASTModifierList node) {
305
        JavaNode parent = node.getParent();
1✔
306
        if (parent instanceof ASTLambdaParameter) {
1✔
307
            return ((ASTLambdaParameter) parent).hasVarKeyword();
1✔
308
        } else if (parent instanceof ASTLocalVariableDeclaration) {
1✔
309
            return ((ASTLocalVariableDeclaration) parent).isTypeInferred();
1✔
310
        }
311
        return false;
1✔
312
    }
313

314
    /**
315
     * Receives modifier events in order and checks their order. Methods return
316
     * true if we found a violation and need to stop.
317
     */
318
    interface ModifierOrderEvents {
319

320
        /** Record that the next modifier is the given annotation. */
321
        boolean recordAnnotation(ASTAnnotation annot);
322

323
        /** Record that the next modifier is the given one occurring at the given token. */
324
        boolean recordModifier(JModifier mod, JavaccToken token);
325

326
        /** Record that the next "modifier" is the given type parameters. */
327
        boolean recordTypeParameters(ASTTypeParameters typeParameters);
328
    }
329

330
    /**
331
     * Reads a modifier list in order, to recover the order of declared tokens.
332
     * Records annotations and modifiers in source order on the given callback interface.
333
     */
334
    private static void readModifierList(ASTModifierList modList, ModifierOrderEvents events) {
335

336
        JavaccToken tok = modList.getFirstToken();
1✔
337
        final JavaccToken lastTok = modList.getLastToken();
1✔
338

339
        int nextAnnotIndex = 0;
1✔
340
        List<ASTAnnotation> children = modList.children(ASTAnnotation.class).toList();
1✔
341

342
        while (tok != lastTok.getNext()) {
1✔
343
            if (tok.isImplicit()) {
1✔
344
                tok = tok.getNext();
1✔
345
                continue;
1✔
346
            }
347

348
            if (tok.kind == JavaTokenKinds.AT) {
1✔
349
                // this is an annotation
350
                assert nextAnnotIndex < children.size() : "annotation token was not parsed?";
1!
351
                ASTAnnotation annotation = children.get(nextAnnotIndex);
1✔
352
                assert annotation.getFirstToken() == tok : "next annot index didn't match token";
1!
353

354
                nextAnnotIndex++;
1✔
355
                if (events.recordAnnotation(annotation)) {
1✔
356
                    return;
1✔
357
                }
358
                tok = annotation.getLastToken();
1✔
359
            } else {
1✔
360
                JModifier mod = getModFromToken(tok);
1✔
361
                assert mod != null : "Token is not a modifier token? " + tok;
1!
362
                if (events.recordModifier(mod, tok)) {
1✔
363
                    return;
1✔
364
                }
365
                if (mod == JModifier.NON_SEALED) {
1✔
366
                    // advance until the sealed token
367
                    tok = tok.getNext();
1✔
368
                    assert tok.kind == JavaTokenKinds.MINUS;
1!
369
                    tok = tok.getNext();
1✔
370
                    assert tok.kind == JavaTokenKinds.IDENTIFIER && tok.getImageCs().contentEquals("sealed");
1!
371
                }
372
            }
373

374
            tok = tok.getNext();
1✔
375
        }
376

377
        if (modList.getNextSibling() instanceof ASTTypeParameters) {
1✔
378
            events.recordTypeParameters((ASTTypeParameters) modList.getNextSibling());
1✔
379
        }
380
    }
1✔
381

382
    private static JModifier getModFromToken(JavaccToken tok) {
383
        switch (tok.kind) {
1!
384
        case JavaTokenKinds.PUBLIC:
385
            return JModifier.PUBLIC;
1✔
386
        case JavaTokenKinds.PROTECTED:
387
            return JModifier.PROTECTED;
1✔
388
        case JavaTokenKinds.PRIVATE:
389
            return JModifier.PRIVATE;
1✔
390
        case JavaTokenKinds.STATIC:
391
            return JModifier.STATIC;
1✔
392
        case JavaTokenKinds.FINAL:
393
            return JModifier.FINAL;
1✔
394
        case JavaTokenKinds.ABSTRACT:
395
            return JModifier.ABSTRACT;
1✔
396
        case JavaTokenKinds.SYNCHRONIZED:
397
            return JModifier.SYNCHRONIZED;
1✔
398
        case JavaTokenKinds.NATIVE:
399
            return JModifier.NATIVE;
1✔
400
        case JavaTokenKinds.TRANSIENT:
401
            return JModifier.TRANSIENT;
1✔
402
        case JavaTokenKinds.VOLATILE:
403
            return JModifier.VOLATILE;
1✔
404
        case JavaTokenKinds.STRICTFP:
405
            return JModifier.STRICTFP;
1✔
406
        case JavaTokenKinds._DEFAULT:
407
            return JModifier.DEFAULT;
1✔
408
        case JavaTokenKinds.IDENTIFIER:
409
            if (tok.getImageCs().contentEquals("non")) {
1✔
410
                return JModifier.NON_SEALED;
1✔
411
            } else if (tok.getImageCs().contentEquals("sealed")) {
1!
412
                return JModifier.SEALED;
1✔
413
            }
414
        // fallthrough
415
        default:
416
            return null;
×
417
        }
418
    }
419

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