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 }