mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-10 18:20:12 +07:00
cleanup IDEA-382376: move com.siyeh.ig.inheritance package from java-impl to java-impl-inspections
GitOrigin-RevId: 6ac8e5a9c13fcb5f26d8719020affcaf064bd429
This commit is contained in:
committed by
intellij-monorepo-bot
parent
2a72828cba
commit
c4f9ba47d3
+100
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
* Copyright 2003-2011 Dave Griffith, Bas Leijdekkers
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.siyeh.ig.inheritance;
|
||||
|
||||
import com.intellij.codeInspection.LocalQuickFix;
|
||||
import com.intellij.psi.PsiAnonymousClass;
|
||||
import com.intellij.psi.PsiClass;
|
||||
import com.intellij.psi.PsiClassType;
|
||||
import com.intellij.psi.PsiMethod;
|
||||
import com.intellij.psi.PsiParameter;
|
||||
import com.intellij.psi.PsiParameterList;
|
||||
import com.intellij.psi.PsiTypes;
|
||||
import com.siyeh.InspectionGadgetsBundle;
|
||||
import com.siyeh.ig.BaseInspection;
|
||||
import com.siyeh.ig.BaseInspectionVisitor;
|
||||
import com.siyeh.ig.fixes.ReplaceInheritanceWithDelegationFix;
|
||||
import com.siyeh.ig.psiutils.CollectionUtils;
|
||||
import com.siyeh.ig.psiutils.TypeUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public final class ExtendsConcreteCollectionInspection extends BaseInspection {
|
||||
|
||||
@Override
|
||||
protected LocalQuickFix buildFix(Object... infos) {
|
||||
final PsiClass aClass = (PsiClass)infos[1];
|
||||
// skip inheritance with delegation for anonymous classes
|
||||
// or better suggest to replace anonymous with inner and then replace with delegation
|
||||
if (aClass instanceof PsiAnonymousClass) {
|
||||
return null;
|
||||
}
|
||||
return new ReplaceInheritanceWithDelegationFix();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String getID() {
|
||||
return "ClassExtendsConcreteCollection";
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String buildErrorString(Object... infos) {
|
||||
final PsiClass superClass = (PsiClass)infos[0];
|
||||
final PsiClass aClass = (PsiClass)infos[1];
|
||||
if (aClass instanceof PsiAnonymousClass) {
|
||||
return InspectionGadgetsBundle.message("anonymous.extends.concrete.collection.problem.descriptor", superClass.getQualifiedName());
|
||||
} else {
|
||||
return InspectionGadgetsBundle.message("extends.concrete.collection.problem.descriptor", superClass.getQualifiedName());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull BaseInspectionVisitor buildVisitor() {
|
||||
return new ExtendsConcreteCollectionVisitor();
|
||||
}
|
||||
|
||||
private static class ExtendsConcreteCollectionVisitor extends BaseInspectionVisitor {
|
||||
|
||||
@Override
|
||||
public void visitClass(@NotNull PsiClass aClass) {
|
||||
if (aClass.isInterface() || aClass.isAnnotationType() || aClass.isEnum()) {
|
||||
return;
|
||||
}
|
||||
final PsiClass superClass = aClass.getSuperClass();
|
||||
if (!CollectionUtils.isConcreteCollectionClass(superClass)) {
|
||||
return;
|
||||
}
|
||||
final String qualifiedName = superClass.getQualifiedName();
|
||||
if ("java.util.LinkedHashMap".equals(qualifiedName)) {
|
||||
final PsiMethod[] methods = aClass.findMethodsByName("removeEldestEntry", false);
|
||||
final PsiClassType entryType = TypeUtils.getType("java.util.Map.Entry", aClass);
|
||||
for (PsiMethod method : methods) {
|
||||
if (!PsiTypes.booleanType().equals(method.getReturnType())) {
|
||||
continue;
|
||||
}
|
||||
final PsiParameterList parameterList = method.getParameterList();
|
||||
if ( parameterList.getParametersCount() != 1) {
|
||||
continue;
|
||||
}
|
||||
final PsiParameter parameter = parameterList.getParameters()[0];
|
||||
if (entryType.isAssignableFrom(parameter.getType())) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
registerClassError(aClass, superClass, aClass);
|
||||
}
|
||||
}
|
||||
}
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.siyeh.ig.inheritance;
|
||||
|
||||
import com.intellij.codeInsight.AnnotationUtil;
|
||||
import com.intellij.codeInsight.options.JavaClassValidator;
|
||||
import com.intellij.codeInspection.LocalQuickFix;
|
||||
import com.intellij.codeInspection.options.OptPane;
|
||||
import com.intellij.codeInspection.util.SpecialAnnotationsUtilBase;
|
||||
import com.intellij.psi.PsiClass;
|
||||
import com.siyeh.InspectionGadgetsBundle;
|
||||
import com.siyeh.ig.BaseInspection;
|
||||
import com.siyeh.ig.BaseInspectionVisitor;
|
||||
import com.siyeh.ig.psiutils.InheritanceUtil;
|
||||
import com.siyeh.ig.ui.ExternalizableStringSet;
|
||||
import org.jdom.Element;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import static com.intellij.codeInspection.options.OptPane.checkbox;
|
||||
import static com.intellij.codeInspection.options.OptPane.pane;
|
||||
import static com.intellij.codeInspection.options.OptPane.stringList;
|
||||
|
||||
public final class InterfaceNeverImplementedInspection extends BaseInspection {
|
||||
|
||||
@SuppressWarnings("PublicField")
|
||||
public boolean ignoreInterfacesThatOnlyDeclareConstants = false;
|
||||
|
||||
@SuppressWarnings("PublicField")
|
||||
public final ExternalizableStringSet ignorableAnnotations = new ExternalizableStringSet();
|
||||
|
||||
@Override
|
||||
public void writeSettings(@NotNull Element node) {
|
||||
defaultWriteSettings(node, "ignorableAnnotations");
|
||||
ignorableAnnotations.writeSettings(node, "ignorableAnnotations");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected LocalQuickFix @NotNull [] buildFixes(Object... infos) {
|
||||
final PsiClass aClass = (PsiClass)infos[0];
|
||||
return SpecialAnnotationsUtilBase.createAddAnnotationToListFixes(aClass, this, insp -> insp.ignorableAnnotations)
|
||||
.toArray(LocalQuickFix.EMPTY_ARRAY);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull OptPane getOptionsPane() {
|
||||
return pane(
|
||||
stringList("ignorableAnnotations", InspectionGadgetsBundle.message("ignore.if.annotated.by"),
|
||||
new JavaClassValidator().annotationsOnly()),
|
||||
checkbox("ignoreInterfacesThatOnlyDeclareConstants", InspectionGadgetsBundle.message("interface.never.implemented.option"))
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NotNull String buildErrorString(Object... infos) {
|
||||
return InspectionGadgetsBundle.message(
|
||||
"interface.never.implemented.problem.descriptor");
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull BaseInspectionVisitor buildVisitor() {
|
||||
return new InterfaceNeverImplementedVisitor();
|
||||
}
|
||||
|
||||
private class InterfaceNeverImplementedVisitor extends BaseInspectionVisitor {
|
||||
|
||||
@Override
|
||||
public void visitClass(@NotNull PsiClass aClass) {
|
||||
if (!aClass.isInterface() || aClass.isAnnotationType()) {
|
||||
return;
|
||||
}
|
||||
if (ignoreInterfacesThatOnlyDeclareConstants && aClass.getMethods().length == 0 && aClass.getFields().length != 0) {
|
||||
return;
|
||||
}
|
||||
if (AnnotationUtil.isAnnotated(aClass, ignorableAnnotations, 0) || InheritanceUtil.hasImplementation(aClass)) {
|
||||
return;
|
||||
}
|
||||
registerClassError(aClass, aClass);
|
||||
}
|
||||
}
|
||||
}
|
||||
+251
@@ -0,0 +1,251 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.siyeh.ig.inheritance;
|
||||
|
||||
import com.intellij.codeInsight.ExternalAnnotationsManager;
|
||||
import com.intellij.codeInspection.AnnotateMethodFix;
|
||||
import com.intellij.codeInspection.CleanupLocalInspectionTool;
|
||||
import com.intellij.codeInspection.LocalQuickFix;
|
||||
import com.intellij.codeInspection.options.OptPane;
|
||||
import com.intellij.openapi.module.JdkApiCompatibilityService;
|
||||
import com.intellij.openapi.module.LanguageLevelUtil;
|
||||
import com.intellij.openapi.module.Module;
|
||||
import com.intellij.openapi.module.ModuleManager;
|
||||
import com.intellij.openapi.module.impl.scopes.ModulesScope;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.roots.ProjectRootModificationTracker;
|
||||
import com.intellij.pom.java.JavaFeature;
|
||||
import com.intellij.pom.java.LanguageLevel;
|
||||
import com.intellij.psi.CommonClassNames;
|
||||
import com.intellij.psi.PsiAnnotation;
|
||||
import com.intellij.psi.PsiAnonymousClass;
|
||||
import com.intellij.psi.PsiClass;
|
||||
import com.intellij.psi.PsiMethod;
|
||||
import com.intellij.psi.PsiModifier;
|
||||
import com.intellij.psi.PsiModifierList;
|
||||
import com.intellij.psi.PsiModifierListOwner;
|
||||
import com.intellij.psi.search.GlobalSearchScope;
|
||||
import com.intellij.psi.util.CachedValueProvider;
|
||||
import com.intellij.psi.util.CachedValuesManager;
|
||||
import com.intellij.psi.util.InheritanceUtil;
|
||||
import com.intellij.psi.util.JavaPsiRecordUtil;
|
||||
import com.intellij.psi.util.PsiUtil;
|
||||
import com.intellij.util.containers.ConcurrentFactoryMap;
|
||||
import com.siyeh.InspectionGadgetsBundle;
|
||||
import com.siyeh.ig.BaseInspection;
|
||||
import com.siyeh.ig.BaseInspectionVisitor;
|
||||
import com.siyeh.ig.JavaOverridingMethodUtil;
|
||||
import com.siyeh.ig.psiutils.MethodUtils;
|
||||
import one.util.streamex.StreamEx;
|
||||
import org.jdom.Element;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static com.intellij.codeInspection.options.OptPane.checkbox;
|
||||
import static com.intellij.codeInspection.options.OptPane.pane;
|
||||
|
||||
public class MissingOverrideAnnotationInspection extends BaseInspection implements CleanupLocalInspectionTool{
|
||||
@SuppressWarnings("PublicField")
|
||||
public boolean ignoreObjectMethods = true;
|
||||
|
||||
@SuppressWarnings("PublicField")
|
||||
public boolean ignoreAnonymousClassMethods;
|
||||
|
||||
public boolean warnInSuper = true;
|
||||
|
||||
@Override
|
||||
public void writeSettings(@NotNull Element node) {
|
||||
defaultWriteSettings(node, "warnInSuper");
|
||||
writeBooleanOption(node, "warnInSuper", true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String getID() {
|
||||
return "override";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected LocalQuickFix buildFix(Object... infos) {
|
||||
final boolean annotateMethod = (boolean)infos[0];
|
||||
final boolean annotateHierarchy = (boolean)infos[1];
|
||||
return createAnnotateFix(annotateMethod, annotateHierarchy);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NotNull String buildErrorString(Object... infos) {
|
||||
final boolean annotateMethod = (boolean)infos[0];
|
||||
return InspectionGadgetsBundle.message(annotateMethod
|
||||
? "missing.override.annotation.problem.descriptor"
|
||||
: "missing.override.annotation.in.overriding.problem.descriptor");
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull OptPane getOptionsPane() {
|
||||
return pane(
|
||||
checkbox("ignoreObjectMethods", InspectionGadgetsBundle.message("ignore.equals.hashcode.and.tostring")),
|
||||
checkbox("ignoreAnonymousClassMethods", InspectionGadgetsBundle.message("ignore.methods.in.anonymous.classes")),
|
||||
checkbox("warnInSuper", InspectionGadgetsBundle.message("missing.override.warn.on.super.option")));
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Set<@NotNull JavaFeature> requiredFeatures() {
|
||||
return Set.of(JavaFeature.ANNOTATIONS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull BaseInspectionVisitor buildVisitor() {
|
||||
return new MissingOverrideAnnotationVisitor();
|
||||
}
|
||||
|
||||
private class MissingOverrideAnnotationVisitor extends BaseInspectionVisitor {
|
||||
|
||||
@Override
|
||||
public void visitMethod(@NotNull PsiMethod method) {
|
||||
if (method.getNameIdentifier() == null || method.isConstructor()) {
|
||||
return;
|
||||
}
|
||||
if (method.hasModifierProperty(PsiModifier.PRIVATE) || method.hasModifierProperty(PsiModifier.STATIC)) {
|
||||
return;
|
||||
}
|
||||
final PsiClass methodClass = method.getContainingClass();
|
||||
if (methodClass == null) {
|
||||
return;
|
||||
}
|
||||
if (ignoreObjectMethods &&
|
||||
(MethodUtils.isHashCode(method) || MethodUtils.isEquals(method) || MethodUtils.isToString(method))) {
|
||||
return;
|
||||
}
|
||||
|
||||
final boolean annotateMethod = isMissingOverride(method);
|
||||
final boolean annotateHierarchy = warnInSuper && isMissingOverrideInOverriders(method);
|
||||
if (annotateMethod || annotateHierarchy) {
|
||||
registerMethodError(method, annotateMethod, annotateHierarchy);
|
||||
}
|
||||
}
|
||||
|
||||
// we assume:
|
||||
// 1) method name is not frequently used
|
||||
// 2) most of overridden methods already have @Override annotation
|
||||
// 3) only one annotation with short name 'Override' exists: it's 'java.lang.Override'
|
||||
private static boolean isMissingOverrideInOverriders(@NotNull PsiMethod method) {
|
||||
if (!PsiUtil.canBeOverridden(method)) return false;
|
||||
|
||||
Project project = method.getProject();
|
||||
final boolean isInterface = Objects.requireNonNull(method.getContainingClass()).isInterface();
|
||||
JavaFeature requiredFeature = isInterface ? JavaFeature.OVERRIDE_INTERFACE : JavaFeature.ANNOTATIONS;
|
||||
|
||||
GlobalSearchScope scope = getLanguageLevelScope(requiredFeature.getMinimumLevel(), project);
|
||||
if (scope == null) return false;
|
||||
int paramCount = method.getParameterList().getParametersCount();
|
||||
Predicate<PsiMethod> preFilter = m -> m.getParameterList().getParametersCount() == paramCount &&
|
||||
!JavaOverridingMethodUtil.containsAnnotationWithName(m, "Override");
|
||||
Stream<PsiMethod> overridingMethods = JavaOverridingMethodUtil.getOverridingMethodsIfCheapEnough(method, scope, preFilter);
|
||||
return overridingMethods != null && overridingMethods.findAny().isPresent();
|
||||
}
|
||||
|
||||
private boolean isMissingOverride(@NotNull PsiMethod method) {
|
||||
PsiClass methodClass = method.getContainingClass();
|
||||
if (ignoreAnonymousClassMethods && methodClass instanceof PsiAnonymousClass) {
|
||||
return false;
|
||||
}
|
||||
if (hasOverrideAnnotation(method)) {
|
||||
return false;
|
||||
}
|
||||
LanguageLevel level = PsiUtil.getLanguageLevel(method);
|
||||
if (JavaPsiRecordUtil.getRecordComponentForAccessor(method) != null) {
|
||||
return true;
|
||||
}
|
||||
if (JavaFeature.OVERRIDE_INTERFACE.isSufficient(level)) {
|
||||
if (!isJdk6Override(method, methodClass)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (!isJdk5Override(method, methodClass)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean hasOverrideAnnotation(PsiModifierListOwner modifierListOwner) {
|
||||
final PsiModifierList modifierList = modifierListOwner.getModifierList();
|
||||
if (modifierList != null && modifierList.hasAnnotation(CommonClassNames.JAVA_LANG_OVERRIDE)) {
|
||||
return true;
|
||||
}
|
||||
final ExternalAnnotationsManager annotationsManager = ExternalAnnotationsManager.getInstance(modifierListOwner.getProject());
|
||||
final List<PsiAnnotation> annotations =
|
||||
annotationsManager.findExternalAnnotations(modifierListOwner, CommonClassNames.JAVA_LANG_OVERRIDE);
|
||||
return !annotations.isEmpty();
|
||||
}
|
||||
|
||||
private static boolean isJdk6Override(PsiMethod method, PsiClass methodClass) {
|
||||
final PsiMethod[] superMethods = method.findSuperMethods();
|
||||
boolean hasSupers = false;
|
||||
for (PsiMethod superMethod : superMethods) {
|
||||
final PsiClass superClass = superMethod.getContainingClass();
|
||||
if (ignoreSuperMethod(method, methodClass, superMethod, superClass)) {
|
||||
continue;
|
||||
}
|
||||
hasSupers = true;
|
||||
if (!superMethod.hasModifierProperty(PsiModifier.PROTECTED)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// is override except if this is an interface method
|
||||
// overriding a protected method in java.lang.Object
|
||||
// http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6501053
|
||||
return hasSupers && !methodClass.isInterface();
|
||||
}
|
||||
|
||||
private static boolean isJdk5Override(PsiMethod method, PsiClass methodClass) {
|
||||
final PsiMethod[] superMethods = method.findSuperMethods();
|
||||
for (PsiMethod superMethod : superMethods) {
|
||||
final PsiClass superClass = superMethod.getContainingClass();
|
||||
if (ignoreSuperMethod(method, methodClass, superMethod, superClass)) {
|
||||
continue;
|
||||
}
|
||||
if (superClass.isInterface()) {
|
||||
continue;
|
||||
}
|
||||
if (methodClass.isInterface() &&
|
||||
superMethod.hasModifierProperty(PsiModifier.PROTECTED)) {
|
||||
// only true for J2SE java.lang.Object.clone(), but might
|
||||
// be different on other/newer java platforms
|
||||
continue;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Contract("_, _, _,null -> true")
|
||||
private static boolean ignoreSuperMethod(PsiMethod method, PsiClass methodClass, PsiMethod superMethod, PsiClass superClass) {
|
||||
return !InheritanceUtil.isInheritorOrSelf(methodClass, superClass, true) ||
|
||||
JdkApiCompatibilityService.getInstance().firstCompatibleLanguageLevel(superMethod, PsiUtil.getLanguageLevel(method)) != null;
|
||||
}
|
||||
}
|
||||
|
||||
private static @NotNull LocalQuickFix createAnnotateFix(boolean annotateMethod, boolean annotateHierarchy) {
|
||||
return new AnnotateMethodFix(CommonClassNames.JAVA_LANG_OVERRIDE, annotateHierarchy, annotateMethod);
|
||||
}
|
||||
|
||||
private static @Nullable GlobalSearchScope getLanguageLevelScope(@NotNull LanguageLevel _minimal, @NotNull Project project) {
|
||||
Map<LanguageLevel, GlobalSearchScope> map = CachedValuesManager.getManager(project).getCachedValue(project, () -> {
|
||||
Map<LanguageLevel, GlobalSearchScope> result = ConcurrentFactoryMap.createMap(minimal -> {
|
||||
Set<Module> modules = StreamEx
|
||||
.of(ModuleManager.getInstance(project).getModules())
|
||||
.filter(m -> LanguageLevelUtil.getEffectiveLanguageLevel(m).isAtLeast(minimal))
|
||||
.toSet();
|
||||
return modules == null ? null : new ModulesScope(modules, project);
|
||||
});
|
||||
return CachedValueProvider.Result.create(result, ProjectRootModificationTracker.getInstance(project));
|
||||
});
|
||||
return map.get(_minimal);
|
||||
}
|
||||
}
|
||||
+254
@@ -0,0 +1,254 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.siyeh.ig.inheritance;
|
||||
|
||||
import com.intellij.codeInsight.AnnotationUtil;
|
||||
import com.intellij.codeInsight.options.JavaClassValidator;
|
||||
import com.intellij.codeInspection.LocalQuickFix;
|
||||
import com.intellij.codeInspection.options.OptPane;
|
||||
import com.intellij.modcommand.ModPsiUpdater;
|
||||
import com.intellij.modcommand.PsiUpdateModCommandQuickFix;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.psi.CommonClassNames;
|
||||
import com.intellij.psi.JavaPsiFacade;
|
||||
import com.intellij.psi.PsiClass;
|
||||
import com.intellij.psi.PsiCodeBlock;
|
||||
import com.intellij.psi.PsiDeclarationStatement;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiElementFactory;
|
||||
import com.intellij.psi.PsiJavaToken;
|
||||
import com.intellij.psi.PsiLocalVariable;
|
||||
import com.intellij.psi.PsiMethod;
|
||||
import com.intellij.psi.PsiModifier;
|
||||
import com.intellij.psi.PsiParameter;
|
||||
import com.intellij.psi.PsiStatement;
|
||||
import com.intellij.psi.PsiThrowStatement;
|
||||
import com.intellij.psi.PsiType;
|
||||
import com.intellij.psi.PsiTypes;
|
||||
import com.intellij.psi.codeStyle.CodeStyleManager;
|
||||
import com.intellij.psi.codeStyle.JavaCodeStyleManager;
|
||||
import com.intellij.psi.codeStyle.JavaCodeStyleSettings;
|
||||
import com.intellij.psi.codeStyle.SuggestedNameInfo;
|
||||
import com.intellij.psi.codeStyle.VariableKind;
|
||||
import com.intellij.psi.util.InheritanceUtil;
|
||||
import com.intellij.psi.util.MethodSignatureBackedByPsiMethod;
|
||||
import com.intellij.util.AstLoadingFilter;
|
||||
import com.siyeh.HardcodedMethodConstants;
|
||||
import com.siyeh.InspectionGadgetsBundle;
|
||||
import com.siyeh.ig.BaseInspection;
|
||||
import com.siyeh.ig.BaseInspectionVisitor;
|
||||
import com.siyeh.ig.psiutils.ClassUtils;
|
||||
import com.siyeh.ig.psiutils.CloneUtils;
|
||||
import com.siyeh.ig.psiutils.ControlFlowUtils;
|
||||
import com.siyeh.ig.psiutils.MethodCallUtils;
|
||||
import com.siyeh.ig.psiutils.MethodUtils;
|
||||
import com.siyeh.ig.psiutils.VariableNameGenerator;
|
||||
import com.siyeh.ig.ui.ExternalizableStringSet;
|
||||
import org.jdom.Element;
|
||||
import org.jetbrains.annotations.Nls;
|
||||
import org.jetbrains.annotations.NonNls;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static com.intellij.codeInspection.options.OptPane.checkbox;
|
||||
import static com.intellij.codeInspection.options.OptPane.pane;
|
||||
import static com.intellij.codeInspection.options.OptPane.stringList;
|
||||
|
||||
/**
|
||||
* @author Bas Leijdekkers
|
||||
*/
|
||||
public final class RefusedBequestInspection extends BaseInspection {
|
||||
|
||||
@SuppressWarnings("PublicField") public final ExternalizableStringSet annotations =
|
||||
new ExternalizableStringSet("javax.annotation.OverridingMethodsMustInvokeSuper",
|
||||
"org.jetbrains.annotations.MustBeInvokedByOverriders");
|
||||
@SuppressWarnings("PublicField") public boolean ignoreEmptySuperMethods;
|
||||
@SuppressWarnings("PublicField") public boolean ignoreDefaultSuperMethods;
|
||||
@SuppressWarnings("PublicField") public boolean onlyReportWhenAnnotated = true;
|
||||
|
||||
@Override
|
||||
public @NotNull OptPane getOptionsPane() {
|
||||
return pane(
|
||||
checkbox("onlyReportWhenAnnotated", InspectionGadgetsBundle.message("inspection.refused.bequest.super.annotated.option"),
|
||||
stringList("annotations", "", new JavaClassValidator().annotationsOnly())),
|
||||
checkbox("ignoreEmptySuperMethods", InspectionGadgetsBundle.message("refused.bequest.ignore.empty.super.methods.option")),
|
||||
checkbox("ignoreDefaultSuperMethods", InspectionGadgetsBundle.message("refused.bequest.ignore.default.super.methods.option"))
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable LocalQuickFix buildFix(Object... infos) {
|
||||
return new RefusedBequestFix();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String getID() {
|
||||
return "MethodDoesntCallSuperMethod";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeSettings(@NotNull Element node) {
|
||||
defaultWriteSettings(node, "onlyReportWhenAnnotated", "annotations", "ignoreDefaultSuperMethods");
|
||||
writeBooleanOption(node, "onlyReportWhenAnnotated", false);
|
||||
writeBooleanOption(node, "ignoreDefaultSuperMethods", false);
|
||||
annotations.writeSettings(node, "annotations");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readSettings(@NotNull Element node) {
|
||||
onlyReportWhenAnnotated = false; // should be false when not present, used to be false by default in the past.
|
||||
super.readSettings(node);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String buildErrorString(Object... infos) {
|
||||
return InspectionGadgetsBundle.message("refused.bequest.problem.descriptor");
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull BaseInspectionVisitor buildVisitor() {
|
||||
return new RefusedBequestVisitor();
|
||||
}
|
||||
|
||||
private static class RefusedBequestFix extends PsiUpdateModCommandQuickFix {
|
||||
|
||||
@Override
|
||||
public @Nls @NotNull String getFamilyName() {
|
||||
return InspectionGadgetsBundle.message("refused.bequest.fix.family.name");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void applyFix(@NotNull Project project, @NotNull PsiElement methodName, @NotNull ModPsiUpdater updater) {
|
||||
final PsiMethod method = (PsiMethod)methodName.getParent();
|
||||
assert method != null;
|
||||
final PsiCodeBlock body = method.getBody();
|
||||
if (body == null) {
|
||||
return;
|
||||
}
|
||||
final PsiType returnType = method.getReturnType();
|
||||
final @NonNls StringBuilder statementText = new StringBuilder();
|
||||
final PsiElementFactory factory = JavaPsiFacade.getElementFactory(project);
|
||||
if (returnType != null && !PsiTypes.voidType().equals(returnType)) {
|
||||
if (JavaCodeStyleSettings.getInstance(method.getContainingFile()).GENERATE_FINAL_LOCALS) {
|
||||
statementText.append("final ");
|
||||
}
|
||||
statementText.append(returnType.getCanonicalText()).append(' ');
|
||||
final JavaCodeStyleManager codeStyleManager = JavaCodeStyleManager.getInstance(project);
|
||||
final SuggestedNameInfo baseNameInfo = codeStyleManager.suggestVariableName(VariableKind.LOCAL_VARIABLE, null, null, returnType);
|
||||
final SuggestedNameInfo nameInfo = codeStyleManager.suggestUniqueVariableName(baseNameInfo, body, true);
|
||||
statementText.append(nameInfo.names.length > 0 ? nameInfo.names[0] : "result");
|
||||
statementText.append('=');
|
||||
final MethodSignatureBackedByPsiMethod superMethodSignature = MethodUtils.getSuperMethodSignature(method);
|
||||
if (superMethodSignature == null) {
|
||||
return;
|
||||
}
|
||||
final PsiMethod superMethod = superMethodSignature.getMethod();
|
||||
final PsiType superReturnType = superMethod.getReturnType();
|
||||
final PsiType substitutedType = superMethodSignature.getSubstitutor().substitute(superReturnType);
|
||||
if (superReturnType != null && !returnType.isAssignableFrom(substitutedType)) {
|
||||
statementText.append('(').append(returnType.getCanonicalText()).append(')');
|
||||
}
|
||||
}
|
||||
statementText.append("super.").append(methodName.getText()).append('(');
|
||||
boolean comma = false;
|
||||
for (PsiParameter parameter : method.getParameterList().getParameters()) {
|
||||
if (comma) statementText.append(',');
|
||||
else comma = true;
|
||||
statementText.append(parameter.getName());
|
||||
}
|
||||
statementText.append(");");
|
||||
final PsiStatement newStatement = factory.createStatementFromText(statementText.toString(), null);
|
||||
final CodeStyleManager styleManager = CodeStyleManager.getInstance(project);
|
||||
final PsiJavaToken brace = body.getLBrace();
|
||||
final PsiElement element = body.addAfter(newStatement, brace);
|
||||
final PsiElement element1 = styleManager.reformat(element);
|
||||
final PsiElement element2 = JavaCodeStyleManager.getInstance(project).shortenClassReferences(element1);
|
||||
updater.highlight(element2);
|
||||
if (element2 instanceof PsiDeclarationStatement declarationStatement) {
|
||||
final PsiLocalVariable variable = (PsiLocalVariable)declarationStatement.getDeclaredElements()[0];
|
||||
List<String> nameSuggestions = new VariableNameGenerator(variable, VariableKind.LOCAL_VARIABLE)
|
||||
.byExpression(variable.getInitializer())
|
||||
.byType(variable.getType())
|
||||
.byName("original", "superResult")
|
||||
.generateAll(true);
|
||||
updater.rename(variable, nameSuggestions);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class RefusedBequestVisitor extends BaseInspectionVisitor {
|
||||
|
||||
@Override
|
||||
public void visitMethod(@NotNull PsiMethod method) {
|
||||
super.visitMethod(method);
|
||||
final PsiCodeBlock body = method.getBody();
|
||||
if (body == null) {
|
||||
return;
|
||||
}
|
||||
if (method.getNameIdentifier() == null) {
|
||||
return;
|
||||
}
|
||||
final PsiMethod superMethod = getDirectSuperMethod(method);
|
||||
if (superMethod == null) {
|
||||
return;
|
||||
}
|
||||
final String methodName = method.getName();
|
||||
if (!HardcodedMethodConstants.CLONE.equals(methodName)) {
|
||||
final PsiClass superClass = superMethod.getContainingClass();
|
||||
if (superClass != null) {
|
||||
final String superClassName = superClass.getQualifiedName();
|
||||
if (CommonClassNames.JAVA_LANG_OBJECT.equals(superClassName)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (ignoreEmptySuperMethods && isTrivial(superMethod)) {
|
||||
return;
|
||||
}
|
||||
final boolean isClone = CloneUtils.isClone(method);
|
||||
if (onlyReportWhenAnnotated && !AnnotationUtil.isAnnotated(superMethod, annotations, 0)) {
|
||||
if (!isClone && !isJUnitSetUpOrTearDown(method) && !MethodUtils.isFinalize(method) || isTrivial(superMethod)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (isClone && ClassUtils.isSingleton(method.getContainingClass())) {
|
||||
return;
|
||||
}
|
||||
if (MethodCallUtils.containsSuperMethodCall(method) || ControlFlowUtils.methodAlwaysThrowsException(method)) {
|
||||
return;
|
||||
}
|
||||
registerMethodError(method);
|
||||
}
|
||||
|
||||
private static boolean isTrivial(PsiMethod method) {
|
||||
final PsiElement element = method.getNavigationElement();
|
||||
return AstLoadingFilter.forceAllowTreeLoading(method.getContainingFile(),
|
||||
() -> MethodUtils.isTrivial(element instanceof PsiMethod
|
||||
? (PsiMethod)element
|
||||
: method, s -> s instanceof PsiThrowStatement));
|
||||
}
|
||||
|
||||
private static boolean isJUnitSetUpOrTearDown(PsiMethod method) {
|
||||
final String name = method.getName();
|
||||
if (!"setUp".equals(name) && !"tearDown".equals(name)) {
|
||||
return false;
|
||||
}
|
||||
if (!method.getParameterList().isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
final PsiClass aClass = method.getContainingClass();
|
||||
return InheritanceUtil.isInheritor(aClass, "junit.framework.TestCase");
|
||||
}
|
||||
|
||||
private @Nullable PsiMethod getDirectSuperMethod(PsiMethod method) {
|
||||
final PsiMethod superMethod = MethodUtils.getSuper(method);
|
||||
if (superMethod == null ||
|
||||
superMethod.hasModifierProperty(PsiModifier.ABSTRACT) ||
|
||||
ignoreDefaultSuperMethods && superMethod.hasModifierProperty(PsiModifier.DEFAULT)) {
|
||||
return null;
|
||||
}
|
||||
return superMethod;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user