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 net.sourceforge.pmd.lang.java.ast.ASTClassDeclaration;
8   import net.sourceforge.pmd.lang.java.ast.JModifier;
9   import net.sourceforge.pmd.lang.java.rule.AbstractJavaRulechainRule;
10  import net.sourceforge.pmd.lang.java.types.JClassType;
11  import net.sourceforge.pmd.lang.java.types.TypeOps;
12  
13  /**
14   * Rule to flag an interface that is implicitly functional (declares exactly
15   * one abstract method) but is not annotated with {@code @FunctionalInterface}.
16   *
17   * <p>Unlike PMD's built-in {@code ImplicitFunctionalInterface} rule, this
18   * variant refuses to fire when the interface extends a super-interface whose
19   * type PMD cannot resolve. The built-in rule counts only the abstract methods
20   * it can see: when a super-interface is unresolved (for instance because
21   * Qulice runs PMD without an auxiliary classpath, so types declared in other
22   * files are invisible), the rule misses the abstract methods that
23   * super-interface contributes and wrongly reports the interface as functional.
24   * Adding the suggested {@code @FunctionalInterface} annotation to such an
25   * interface does not compile, because the interface really has more than one
26   * abstract method. An interface with a single visible method whose full set of
27   * inherited abstract methods cannot be computed is therefore left alone. Once
28   * the whole super-interface hierarchy resolves, the check is exactly the same
29   * as the built-in one: fire only when the interface has a single abstract
30   * method overall.</p>
31   *
32   * @since 1.0
33   */
34  public final class ImplicitFunctionalInterfaceRule
35      extends AbstractJavaRulechainRule {
36  
37      /**
38       * Default constructor.
39       */
40      public ImplicitFunctionalInterfaceRule() {
41          super(ASTClassDeclaration.class);
42      }
43  
44      @Override
45      public Object visit(final ASTClassDeclaration node, final Object data) {
46          if (ImplicitFunctionalInterfaceRule.implicit(node)
47              && ImplicitFunctionalInterfaceRule.resolved(node.getTypeMirror())
48              && TypeOps.findFunctionalInterfaceMethod(
49                  node.getTypeMirror()
50              ) != null) {
51              this.asCtx(data).addViolation(node);
52          }
53          return null;
54      }
55  
56      private static boolean implicit(final ASTClassDeclaration node) {
57          return node.isRegularInterface()
58              && !node.isAnnotationPresent(FunctionalInterface.class)
59              && !node.hasModifiers(JModifier.SEALED);
60      }
61  
62      private static boolean resolved(final JClassType type) {
63          boolean result = true;
64          for (final JClassType parent : type.getSuperInterfaces()) {
65              if (parent.getSymbol().isUnresolved()
66                  || !ImplicitFunctionalInterfaceRule.resolved(parent)) {
67                  result = false;
68                  break;
69              }
70          }
71          return result;
72      }
73  }