View Javadoc
1   /*
2    * SPDX-FileCopyrightText: Copyright (c) 2011-2026 Yegor Bugayenko
3    * SPDX-License-Identifier: MIT
4    */
5   package com.qulice.pmd.rules;
6   
7   import java.util.Arrays;
8   import java.util.HashSet;
9   import java.util.Set;
10  import net.sourceforge.pmd.lang.java.ast.ASTArgumentList;
11  import net.sourceforge.pmd.lang.java.ast.ASTExpression;
12  import net.sourceforge.pmd.lang.java.ast.ASTMethodCall;
13  import net.sourceforge.pmd.lang.java.rule.AbstractJavaRulechainRule;
14  
15  /**
16   * Rule to flag redundant {@code String.format(...)} calls passed as
17   * arguments to {@code com.jcabi.log.Logger} methods. The Logger already
18   * supports printf-style format strings as the message argument, so
19   * pre-formatting with {@code String.format} is unnecessary and should be
20   * inlined into the Logger call.
21   *
22   * @since 0.26.0
23   */
24  public final class ProhibitFormatInLoggerRule
25      extends AbstractJavaRulechainRule {
26  
27      /**
28       * Logger method names that accept a format-string message.
29       */
30      private static final Set<String> METHODS = new HashSet<>(
31          Arrays.asList("trace", "debug", "info", "warn", "error")
32      );
33  
34      /**
35       * Default constructor.
36       */
37      public ProhibitFormatInLoggerRule() {
38          super(ASTMethodCall.class);
39      }
40  
41      @Override
42      public Object visit(final ASTMethodCall call, final Object data) {
43          if (ProhibitFormatInLoggerRule.isLoggerCall(call)
44              && ProhibitFormatInLoggerRule.hasFormatArgument(call)) {
45              this.asCtx(data).addViolation(call);
46          }
47          return data;
48      }
49  
50      private static boolean isLoggerCall(final ASTMethodCall call) {
51          boolean result = false;
52          if (ProhibitFormatInLoggerRule.METHODS.contains(call.getMethodName())) {
53              final ASTExpression qualifier = call.getQualifier();
54              result = qualifier != null
55                  && ProhibitFormatInLoggerRule.endsWith(
56                      qualifier.getText().toString(), "Logger"
57                  );
58          }
59          return result;
60      }
61  
62      private static boolean hasFormatArgument(final ASTMethodCall call) {
63          final ASTArgumentList args = call.getArguments();
64          return args != null
65              && args.toStream()
66                  .any(ProhibitFormatInLoggerRule::isStringFormatCall);
67      }
68  
69      private static boolean isStringFormatCall(final ASTExpression expr) {
70          boolean result = false;
71          if (expr instanceof ASTMethodCall method) {
72              final ASTExpression qualifier = method.getQualifier();
73              result = "format".equals(method.getMethodName())
74                  && qualifier != null
75                  && ProhibitFormatInLoggerRule.endsWith(
76                      qualifier.getText().toString(), "String"
77                  );
78          }
79          return result;
80      }
81  
82      private static boolean endsWith(final String text, final String name) {
83          return text.equals(name) || text.endsWith(".".concat(name));
84      }
85  }