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   * @since 0.26.0
22   */
23  public final class ProhibitFormatInLoggerRule
24      extends AbstractJavaRulechainRule {
25  
26      /**
27       * Logger method names that accept a format-string message.
28       */
29      private static final Set<String> METHODS = new HashSet<>(
30          Arrays.asList("trace", "debug", "info", "warn", "error")
31      );
32  
33      public ProhibitFormatInLoggerRule() {
34          super(ASTMethodCall.class);
35      }
36  
37      @Override
38      public Object visit(final ASTMethodCall call, final Object data) {
39          if (ProhibitFormatInLoggerRule.isLoggerCall(call)
40              && ProhibitFormatInLoggerRule.hasFormatArgument(call)) {
41              this.asCtx(data).addViolation(call);
42          }
43          return data;
44      }
45  
46      private static boolean isLoggerCall(final ASTMethodCall call) {
47          boolean result = false;
48          if (ProhibitFormatInLoggerRule.METHODS.contains(call.getMethodName())) {
49              final ASTExpression qualifier = call.getQualifier();
50              result = qualifier != null
51                  && ProhibitFormatInLoggerRule.endsWith(
52                      qualifier.getText().toString(), "Logger"
53                  );
54          }
55          return result;
56      }
57  
58      private static boolean hasFormatArgument(final ASTMethodCall call) {
59          final ASTArgumentList args = call.getArguments();
60          return args != null
61              && args.toStream()
62                  .any(ProhibitFormatInLoggerRule::isStringFormatCall);
63      }
64  
65      private static boolean isStringFormatCall(final ASTExpression expr) {
66          boolean result = false;
67          if (expr instanceof ASTMethodCall) {
68              final ASTMethodCall method = (ASTMethodCall) expr;
69              final ASTExpression qualifier = method.getQualifier();
70              result = "format".equals(method.getMethodName())
71                  && qualifier != null
72                  && ProhibitFormatInLoggerRule.endsWith(
73                      qualifier.getText().toString(), "String"
74                  );
75          }
76          return result;
77      }
78  
79      private static boolean endsWith(final String text, final String name) {
80          return text.equals(name) || text.endsWith(".".concat(name));
81      }
82  }