View Javadoc
1   ///////////////////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3   // Copyright (C) 2001-2025 the original author or authors.
4   //
5   // This library is free software; you can redistribute it and/or
6   // modify it under the terms of the GNU Lesser General Public
7   // License as published by the Free Software Foundation; either
8   // version 2.1 of the License, or (at your option) any later version.
9   //
10  // This library is distributed in the hope that it will be useful,
11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  // Lesser General Public License for more details.
14  //
15  // You should have received a copy of the GNU Lesser General Public
16  // License along with this library; if not, write to the Free Software
17  // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18  ///////////////////////////////////////////////////////////////////////////////////////////////
19  
20  package com.puppycrawl.tools.checkstyle.checks.modifier;
21  
22  import java.util.ArrayList;
23  import java.util.List;
24  import java.util.Optional;
25  
26  import com.puppycrawl.tools.checkstyle.StatelessCheck;
27  import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
28  import com.puppycrawl.tools.checkstyle.api.DetailAST;
29  import com.puppycrawl.tools.checkstyle.api.TokenTypes;
30  import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
31  import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
32  
33  /**
34   * <div>
35   * Checks for redundant modifiers.
36   * </div>
37   *
38   * <p>
39   * Rationale: The Java Language Specification strongly discourages the usage
40   * of {@code public} and {@code abstract} for method declarations in interface
41   * definitions as a matter of style.
42   * </p>
43   *
44   * <p>The check validates:</p>
45   * <ol>
46   * <li>
47   * Interface and annotation definitions.
48   * </li>
49   * <li>
50   * Final modifier on methods of final and anonymous classes.
51   * </li>
52   * <li>
53   * Type declarations nested under interfaces that are declared as {@code public} or {@code static}.
54   * </li>
55   * <li>
56   * Class constructors.
57   * </li>
58   * <li>
59   * Nested {@code enum} definitions that are declared as {@code static}.
60   * </li>
61   * <li>
62   * {@code record} definitions that are declared as {@code final} and nested
63   * {@code record} definitions that are declared as {@code static}.
64   * </li>
65   * <li>
66   * {@code strictfp} modifier when using JDK 17 or later. See reason at
67   * <a href="https://openjdk.org/jeps/306">JEP 306</a>
68   * </li>
69   * <li>
70   * {@code final} modifier on unnamed variables when using JDK 22 or later.
71   * </li>
72   * </ol>
73   *
74   * <p>
75   * interfaces by definition are abstract so the {@code abstract} modifier is redundant on them.
76   * </p>
77   *
78   * <p>Type declarations nested under interfaces by definition are public and static,
79   * so the {@code public} and {@code static} modifiers on nested type declarations are redundant.
80   * On the other hand, classes inside of interfaces can be abstract or non abstract.
81   * So, {@code abstract} modifier is allowed.
82   * </p>
83   *
84   * <p>Fields in interfaces and annotations are automatically
85   * public, static and final, so these modifiers are redundant as
86   * well.</p>
87   *
88   * <p>As annotations are a form of interface, their fields are also
89   * automatically public, static and final just as their
90   * annotation fields are automatically public and abstract.</p>
91   *
92   * <p>A record class is implicitly final and cannot be abstract, these restrictions emphasize
93   * that the API of a record class is defined solely by its state description, and
94   * cannot be enhanced later by another class. Nested records are implicitly static. This avoids an
95   * immediately enclosing instance which would silently add state to the record class.
96   * See <a href="https://openjdk.org/jeps/395">JEP 395</a> for more info.</p>
97   *
98   * <p>Enums by definition are static implicit subclasses of java.lang.Enum&#60;E&#62;.
99   * So, the {@code static} modifier on the enums is redundant. In addition,
100  * if enum is inside of interface, {@code public} modifier is also redundant.</p>
101  *
102  * <p>Enums can also contain abstract methods and methods which can be overridden by the declared
103  * enumeration fields.
104  * See the following example:</p>
105  * <pre>
106  * public enum EnumClass {
107  *   FIELD_1,
108  *   FIELD_2 {
109  *     &#64;Override
110  *     public final void method1() {} // violation expected
111  *   };
112  *
113  *   public void method1() {}
114  *   public final void method2() {} // no violation expected
115  * }
116  * </pre>
117  *
118  * <p>Since these methods can be overridden in these situations, the final methods are not
119  * marked as redundant even though they can't be extended by other classes/enums.</p>
120  *
121  * <p>
122  * Nested {@code enum} types are always static by default.
123  * </p>
124  *
125  * <p>Final classes by definition cannot be extended so the {@code final}
126  * modifier on the method of a final class is redundant.
127  * </p>
128  *
129  * <p>Public modifier for constructors in non-public non-protected classes
130  * is always obsolete: </p>
131  *
132  * <pre>
133  * public class PublicClass {
134  *   public PublicClass() {} // OK
135  * }
136  *
137  * class PackagePrivateClass {
138  *   public PackagePrivateClass() {} // violation expected
139  * }
140  * </pre>
141  *
142  * <p>There is no violation in the following example,
143  * because removing public modifier from ProtectedInnerClass
144  * constructor will make this code not compiling: </p>
145  *
146  * <pre>
147  * package a;
148  * public class ClassExample {
149  *   protected class ProtectedInnerClass {
150  *     public ProtectedInnerClass () {}
151  *   }
152  * }
153  *
154  * package b;
155  * import a.ClassExample;
156  * public class ClassExtending extends ClassExample {
157  *   ProtectedInnerClass pc = new ProtectedInnerClass();
158  * }
159  * </pre>
160  * <ul>
161  * <li>
162  * Property {@code jdkVersion} - Set the JDK version that you are using.
163  * Old JDK version numbering is supported (e.g. 1.8 for Java 8)
164  * as well as just the major JDK version alone (e.g. 8) is supported.
165  * This property only considers features from officially released
166  * Java versions as supported. Features introduced in preview releases are not considered
167  * supported until they are included in a non-preview release.
168  * Type is {@code java.lang.String}.
169  * Default value is {@code "22"}.
170  * </li>
171  * <li>
172  * Property {@code tokens} - tokens to check
173  * Type is {@code java.lang.String[]}.
174  * Validation type is {@code tokenSet}.
175  * Default value is:
176  * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#METHOD_DEF">
177  * METHOD_DEF</a>,
178  * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#VARIABLE_DEF">
179  * VARIABLE_DEF</a>,
180  * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ANNOTATION_FIELD_DEF">
181  * ANNOTATION_FIELD_DEF</a>,
182  * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#INTERFACE_DEF">
183  * INTERFACE_DEF</a>,
184  * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#CTOR_DEF">
185  * CTOR_DEF</a>,
186  * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#CLASS_DEF">
187  * CLASS_DEF</a>,
188  * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ENUM_DEF">
189  * ENUM_DEF</a>,
190  * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#RESOURCE">
191  * RESOURCE</a>,
192  * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ANNOTATION_DEF">
193  * ANNOTATION_DEF</a>,
194  * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#RECORD_DEF">
195  * RECORD_DEF</a>,
196  * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#PATTERN_VARIABLE_DEF">
197  * PATTERN_VARIABLE_DEF</a>,
198  * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_CATCH">
199  * LITERAL_CATCH</a>,
200  * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LAMBDA">
201  * LAMBDA</a>.
202  * </li>
203  * </ul>
204  *
205  * <p>
206  * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
207  * </p>
208  *
209  * <p>
210  * Violation Message Keys:
211  * </p>
212  * <ul>
213  * <li>
214  * {@code redundantModifier}
215  * </li>
216  * </ul>
217  *
218  * @since 3.0
219  */
220 @StatelessCheck
221 public class RedundantModifierCheck
222     extends AbstractCheck {
223 
224     /**
225      * A key is pointing to the warning message text in "messages.properties"
226      * file.
227      */
228     public static final String MSG_KEY = "redundantModifier";
229 
230     /**
231      * An array of tokens for interface modifiers.
232      */
233     private static final int[] TOKENS_FOR_INTERFACE_MODIFIERS = {
234         TokenTypes.LITERAL_STATIC,
235         TokenTypes.ABSTRACT,
236     };
237 
238     /**
239      *  Constant for jdk 22 version number.
240      */
241     private static final int JDK_22 = 22;
242 
243     /**
244      *  Constant for jdk 17 version number.
245      *
246      */
247     private static final int JDK_17 = 17;
248 
249     /**
250      * Set the JDK version that you are using.
251      * Old JDK version numbering is supported (e.g. 1.8 for Java 8)
252      * as well as just the major JDK version alone (e.g. 8) is supported.
253      * This property only considers features from officially released
254      * Java versions as supported. Features introduced in preview releases are not considered
255      * supported until they are included in a non-preview release.
256      *
257      */
258     private int jdkVersion = JDK_22;
259 
260     /**
261      * Setter to set the JDK version that you are using.
262      * Old JDK version numbering is supported (e.g. 1.8 for Java 8)
263      * as well as just the major JDK version alone (e.g. 8) is supported.
264      * This property only considers features from officially released
265      * Java versions as supported. Features introduced in preview releases are not considered
266      * supported until they are included in a non-preview release.
267      *
268      * @param jdkVersion the Java version
269      * @since 10.18.0
270      */
271     public void setJdkVersion(String jdkVersion) {
272         final String singleVersionNumber;
273         if (jdkVersion.startsWith("1.")) {
274             singleVersionNumber = jdkVersion.substring(2);
275         }
276         else {
277             singleVersionNumber = jdkVersion;
278         }
279 
280         this.jdkVersion = Integer.parseInt(singleVersionNumber);
281     }
282 
283     @Override
284     public int[] getDefaultTokens() {
285         return getAcceptableTokens();
286     }
287 
288     @Override
289     public int[] getRequiredTokens() {
290         return CommonUtil.EMPTY_INT_ARRAY;
291     }
292 
293     @Override
294     public int[] getAcceptableTokens() {
295         return new int[] {
296             TokenTypes.METHOD_DEF,
297             TokenTypes.VARIABLE_DEF,
298             TokenTypes.ANNOTATION_FIELD_DEF,
299             TokenTypes.INTERFACE_DEF,
300             TokenTypes.CTOR_DEF,
301             TokenTypes.CLASS_DEF,
302             TokenTypes.ENUM_DEF,
303             TokenTypes.RESOURCE,
304             TokenTypes.ANNOTATION_DEF,
305             TokenTypes.RECORD_DEF,
306             TokenTypes.PATTERN_VARIABLE_DEF,
307             TokenTypes.LITERAL_CATCH,
308             TokenTypes.LAMBDA,
309         };
310     }
311 
312     @Override
313     public void visitToken(DetailAST ast) {
314         switch (ast.getType()) {
315             case TokenTypes.INTERFACE_DEF:
316             case TokenTypes.ANNOTATION_DEF:
317                 checkInterfaceModifiers(ast);
318                 break;
319             case TokenTypes.ENUM_DEF:
320                 checkForRedundantModifier(ast, TokenTypes.LITERAL_STATIC);
321                 break;
322             case TokenTypes.CTOR_DEF:
323                 checkConstructorModifiers(ast);
324                 break;
325             case TokenTypes.METHOD_DEF:
326                 processMethods(ast);
327                 break;
328             case TokenTypes.RESOURCE:
329                 processResources(ast);
330                 break;
331             case TokenTypes.RECORD_DEF:
332                 checkForRedundantModifier(ast, TokenTypes.FINAL, TokenTypes.LITERAL_STATIC);
333                 break;
334             case TokenTypes.VARIABLE_DEF:
335             case TokenTypes.PATTERN_VARIABLE_DEF:
336                 checkUnnamedVariables(ast);
337                 break;
338             case TokenTypes.LITERAL_CATCH:
339                 checkUnnamedVariables(ast.findFirstToken(TokenTypes.PARAMETER_DEF));
340                 break;
341             case TokenTypes.LAMBDA:
342                 processLambdaParameters(ast);
343                 break;
344             case TokenTypes.CLASS_DEF:
345             case TokenTypes.ANNOTATION_FIELD_DEF:
346                 break;
347             default:
348                 throw new IllegalStateException("Unexpected token type: " + ast.getType());
349         }
350 
351         if (isInterfaceOrAnnotationMember(ast)) {
352             processInterfaceOrAnnotation(ast);
353         }
354 
355         if (jdkVersion >= JDK_17) {
356             checkForRedundantModifier(ast, TokenTypes.STRICTFP);
357         }
358     }
359 
360     /**
361      * Process lambda parameters.
362      *
363      * @param lambdaAst node of type {@link TokenTypes#LAMBDA}
364      */
365     private void processLambdaParameters(DetailAST lambdaAst) {
366         final DetailAST lambdaParameters = lambdaAst.findFirstToken(TokenTypes.PARAMETERS);
367         if (lambdaParameters != null) {
368             TokenUtil.forEachChild(lambdaParameters, TokenTypes.PARAMETER_DEF,
369                     this::checkUnnamedVariables);
370         }
371     }
372 
373     /**
374      * Check if the variable is unnamed and has redundant final modifier.
375      *
376      * @param ast node of type {@link TokenTypes#VARIABLE_DEF}
377      *     or {@link TokenTypes#PATTERN_VARIABLE_DEF}
378      *     or {@link TokenTypes#PARAMETER_DEF}
379      */
380     private void checkUnnamedVariables(DetailAST ast) {
381         if (jdkVersion >= JDK_22 && isUnnamedVariable(ast)) {
382             checkForRedundantModifier(ast, TokenTypes.FINAL);
383         }
384     }
385 
386     /**
387      * Check if the variable is unnamed.
388      *
389      * @param ast node of type {@link TokenTypes#VARIABLE_DEF}
390      *     or {@link TokenTypes#PATTERN_VARIABLE_DEF}
391      *     or {@link TokenTypes#PARAMETER_DEF}
392      * @return true if the variable is unnamed
393      */
394     private static boolean isUnnamedVariable(DetailAST ast) {
395         return "_".equals(ast.findFirstToken(TokenTypes.IDENT).getText());
396     }
397 
398     /**
399      * Check modifiers of constructor.
400      *
401      * @param ctorDefAst ast node of type {@link TokenTypes#CTOR_DEF}
402      */
403     private void checkConstructorModifiers(DetailAST ctorDefAst) {
404         if (isEnumMember(ctorDefAst)) {
405             checkEnumConstructorModifiers(ctorDefAst);
406         }
407         else {
408             checkClassConstructorModifiers(ctorDefAst);
409         }
410     }
411 
412     /**
413      * Checks if interface has proper modifiers.
414      *
415      * @param ast interface to check
416      */
417     private void checkInterfaceModifiers(DetailAST ast) {
418         final DetailAST modifiers =
419             ast.findFirstToken(TokenTypes.MODIFIERS);
420 
421         for (final int tokenType : TOKENS_FOR_INTERFACE_MODIFIERS) {
422             final DetailAST modifier =
423                     modifiers.findFirstToken(tokenType);
424             if (modifier != null) {
425                 log(modifier, MSG_KEY, modifier.getText());
426             }
427         }
428     }
429 
430     /**
431      * Check if enum constructor has proper modifiers.
432      *
433      * @param ast constructor of enum
434      */
435     private void checkEnumConstructorModifiers(DetailAST ast) {
436         final DetailAST modifiers = ast.findFirstToken(TokenTypes.MODIFIERS);
437         TokenUtil.findFirstTokenByPredicate(
438             modifiers, mod -> mod.getType() != TokenTypes.ANNOTATION
439         ).ifPresent(modifier -> log(modifier, MSG_KEY, modifier.getText()));
440     }
441 
442     /**
443      * Do validation of interface of annotation.
444      *
445      * @param ast token AST
446      */
447     private void processInterfaceOrAnnotation(DetailAST ast) {
448         final DetailAST modifiers = ast.findFirstToken(TokenTypes.MODIFIERS);
449         DetailAST modifier = modifiers.getFirstChild();
450         while (modifier != null) {
451             // javac does not allow final or static in interface methods
452             // order annotation fields hence no need to check that this
453             // is not a method or annotation field
454 
455             final int type = modifier.getType();
456             if (type == TokenTypes.LITERAL_PUBLIC
457                 || type == TokenTypes.LITERAL_STATIC
458                         && ast.getType() != TokenTypes.METHOD_DEF
459                 || type == TokenTypes.ABSTRACT
460                         && ast.getType() != TokenTypes.CLASS_DEF
461                 || type == TokenTypes.FINAL
462                         && ast.getType() != TokenTypes.CLASS_DEF) {
463                 log(modifier, MSG_KEY, modifier.getText());
464             }
465 
466             modifier = modifier.getNextSibling();
467         }
468     }
469 
470     /**
471      * Process validation of Methods.
472      *
473      * @param ast method AST
474      */
475     private void processMethods(DetailAST ast) {
476         final DetailAST modifiers =
477                         ast.findFirstToken(TokenTypes.MODIFIERS);
478         // private method?
479         boolean checkFinal =
480             modifiers.findFirstToken(TokenTypes.LITERAL_PRIVATE) != null;
481         // declared in a final class?
482         DetailAST parent = ast;
483         while (parent != null && !checkFinal) {
484             if (parent.getType() == TokenTypes.CLASS_DEF) {
485                 final DetailAST classModifiers =
486                     parent.findFirstToken(TokenTypes.MODIFIERS);
487                 checkFinal = classModifiers.findFirstToken(TokenTypes.FINAL) != null;
488                 parent = null;
489             }
490             else if (parent.getType() == TokenTypes.LITERAL_NEW
491                     || parent.getType() == TokenTypes.ENUM_CONSTANT_DEF) {
492                 checkFinal = true;
493                 parent = null;
494             }
495             else if (parent.getType() == TokenTypes.ENUM_DEF) {
496                 checkFinal = modifiers.findFirstToken(TokenTypes.LITERAL_STATIC) != null;
497                 parent = null;
498             }
499             else {
500                 parent = parent.getParent();
501             }
502         }
503         if (checkFinal && !isAnnotatedWithSafeVarargs(ast)) {
504             checkForRedundantModifier(ast, TokenTypes.FINAL);
505         }
506 
507         if (ast.findFirstToken(TokenTypes.SLIST) == null) {
508             processAbstractMethodParameters(ast);
509         }
510     }
511 
512     /**
513      * Process validation of parameters for Methods with no definition.
514      *
515      * @param ast method AST
516      */
517     private void processAbstractMethodParameters(DetailAST ast) {
518         final DetailAST parameters = ast.findFirstToken(TokenTypes.PARAMETERS);
519         TokenUtil.forEachChild(parameters, TokenTypes.PARAMETER_DEF, paramDef -> {
520             checkForRedundantModifier(paramDef, TokenTypes.FINAL);
521         });
522     }
523 
524     /**
525      * Check if class constructor has proper modifiers.
526      *
527      * @param classCtorAst class constructor ast
528      */
529     private void checkClassConstructorModifiers(DetailAST classCtorAst) {
530         final DetailAST classDef = classCtorAst.getParent().getParent();
531         if (!isClassPublic(classDef) && !isClassProtected(classDef)) {
532             checkForRedundantModifier(classCtorAst, TokenTypes.LITERAL_PUBLIC);
533         }
534     }
535 
536     /**
537      * Checks if given resource has redundant modifiers.
538      *
539      * @param ast ast
540      */
541     private void processResources(DetailAST ast) {
542         checkForRedundantModifier(ast, TokenTypes.FINAL);
543     }
544 
545     /**
546      * Checks if given ast has a redundant modifier.
547      *
548      * @param ast ast
549      * @param modifierTypes The modifiers to check for.
550      */
551     private void checkForRedundantModifier(DetailAST ast, int... modifierTypes) {
552         Optional.ofNullable(ast.findFirstToken(TokenTypes.MODIFIERS))
553             .ifPresent(modifiers -> {
554                 for (DetailAST childAst = modifiers.getFirstChild();
555                      childAst != null; childAst = childAst.getNextSibling()) {
556                     if (TokenUtil.isOfType(childAst, modifierTypes)) {
557                         log(childAst, MSG_KEY, childAst.getText());
558                     }
559                 }
560             });
561     }
562 
563     /**
564      * Checks if given class ast has protected modifier.
565      *
566      * @param classDef class ast
567      * @return true if class is protected, false otherwise
568      */
569     private static boolean isClassProtected(DetailAST classDef) {
570         final DetailAST classModifiers =
571                 classDef.findFirstToken(TokenTypes.MODIFIERS);
572         return classModifiers.findFirstToken(TokenTypes.LITERAL_PROTECTED) != null;
573     }
574 
575     /**
576      * Checks if given class is accessible from "public" scope.
577      *
578      * @param ast class def to check
579      * @return true if class is accessible from public scope,false otherwise
580      */
581     private static boolean isClassPublic(DetailAST ast) {
582         boolean isAccessibleFromPublic = false;
583         final DetailAST modifiersAst = ast.findFirstToken(TokenTypes.MODIFIERS);
584         final boolean hasPublicModifier =
585                 modifiersAst.findFirstToken(TokenTypes.LITERAL_PUBLIC) != null;
586 
587         if (TokenUtil.isRootNode(ast.getParent())) {
588             isAccessibleFromPublic = hasPublicModifier;
589         }
590         else {
591             final DetailAST parentClassAst = ast.getParent().getParent();
592 
593             if (hasPublicModifier || parentClassAst.getType() == TokenTypes.INTERFACE_DEF) {
594                 isAccessibleFromPublic = isClassPublic(parentClassAst);
595             }
596         }
597 
598         return isAccessibleFromPublic;
599     }
600 
601     /**
602      * Checks if current AST node is member of Enum.
603      *
604      * @param ast AST node
605      * @return true if it is an enum member
606      */
607     private static boolean isEnumMember(DetailAST ast) {
608         final DetailAST parentTypeDef = ast.getParent().getParent();
609         return parentTypeDef.getType() == TokenTypes.ENUM_DEF;
610     }
611 
612     /**
613      * Checks if current AST node is member of Interface or Annotation, not of their subnodes.
614      *
615      * @param ast AST node
616      * @return true or false
617      */
618     private static boolean isInterfaceOrAnnotationMember(DetailAST ast) {
619         DetailAST parentTypeDef = ast.getParent();
620         parentTypeDef = parentTypeDef.getParent();
621         return parentTypeDef != null
622                 && (parentTypeDef.getType() == TokenTypes.INTERFACE_DEF
623                     || parentTypeDef.getType() == TokenTypes.ANNOTATION_DEF);
624     }
625 
626     /**
627      * Checks if method definition is annotated with.
628      * <a href="https://docs.oracle.com/javase/8/docs/api/java/lang/SafeVarargs.html">
629      * SafeVarargs</a> annotation
630      *
631      * @param methodDef method definition node
632      * @return true or false
633      */
634     private static boolean isAnnotatedWithSafeVarargs(DetailAST methodDef) {
635         boolean result = false;
636         final List<DetailAST> methodAnnotationsList = getMethodAnnotationsList(methodDef);
637         for (DetailAST annotationNode : methodAnnotationsList) {
638             if ("SafeVarargs".equals(annotationNode.getLastChild().getText())) {
639                 result = true;
640                 break;
641             }
642         }
643         return result;
644     }
645 
646     /**
647      * Gets the list of annotations on method definition.
648      *
649      * @param methodDef method definition node
650      * @return List of annotations
651      */
652     private static List<DetailAST> getMethodAnnotationsList(DetailAST methodDef) {
653         final List<DetailAST> annotationsList = new ArrayList<>();
654         final DetailAST modifiers = methodDef.findFirstToken(TokenTypes.MODIFIERS);
655         TokenUtil.forEachChild(modifiers, TokenTypes.ANNOTATION, annotationsList::add);
656         return annotationsList;
657     }
658 
659 }