mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
cleanup IDEA-382376: move com.siyeh.ig.performance package from java-impl to java-impl-inspections
GitOrigin-RevId: 4c868558a579c35517733bb6ffdec476cfb212ba
This commit is contained in:
committed by
intellij-monorepo-bot
parent
cf426f0cfc
commit
5f8c225c21
+144
@@ -0,0 +1,144 @@
|
||||
/*
|
||||
* Copyright 2003-2018 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.performance;
|
||||
|
||||
import com.intellij.codeInspection.CleanupLocalInspectionTool;
|
||||
import com.intellij.codeInspection.LocalQuickFix;
|
||||
import com.intellij.codeInspection.options.OptPane;
|
||||
import com.intellij.psi.PsiClass;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiEnumConstant;
|
||||
import com.intellij.psi.PsiExpression;
|
||||
import com.intellij.psi.PsiExpressionStatement;
|
||||
import com.intellij.psi.PsiField;
|
||||
import com.intellij.psi.PsiMember;
|
||||
import com.intellij.psi.PsiMethod;
|
||||
import com.intellij.psi.PsiMethodCallExpression;
|
||||
import com.intellij.psi.PsiModifier;
|
||||
import com.intellij.psi.PsiReferenceExpression;
|
||||
import com.intellij.psi.PsiThisExpression;
|
||||
import com.intellij.psi.search.searches.OverridingMethodsSearch;
|
||||
import com.intellij.psi.util.PropertyUtil;
|
||||
import com.intellij.psi.util.PropertyUtilBase;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.psi.util.PsiUtil;
|
||||
import com.siyeh.InspectionGadgetsBundle;
|
||||
import com.siyeh.ig.BaseInspection;
|
||||
import com.siyeh.ig.BaseInspectionVisitor;
|
||||
import com.siyeh.ig.fixes.InlineGetterSetterCallFix;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import static com.intellij.codeInspection.options.OptPane.checkbox;
|
||||
import static com.intellij.codeInspection.options.OptPane.pane;
|
||||
|
||||
public final class CallToSimpleGetterInClassInspection extends BaseInspection implements CleanupLocalInspectionTool {
|
||||
@SuppressWarnings("UnusedDeclaration")
|
||||
public boolean ignoreGetterCallsOnOtherObjects = false;
|
||||
@SuppressWarnings("UnusedDeclaration")
|
||||
public boolean onlyReportPrivateGetter = false;
|
||||
|
||||
@Override
|
||||
public @NotNull OptPane getOptionsPane() {
|
||||
return pane(
|
||||
checkbox("ignoreGetterCallsOnOtherObjects", InspectionGadgetsBundle.message("call.to.simple.getter.in.class.ignore.option")),
|
||||
checkbox("onlyReportPrivateGetter", InspectionGadgetsBundle.message("call.to.private.simple.getter.in.class.option")));
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocalQuickFix buildFix(Object... infos) {
|
||||
return new InlineGetterSetterCallFix(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean runForWholeFile() {
|
||||
// Changes in another method (making getter more complicated) may affect
|
||||
// the inspection result at call sites
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String getID() {
|
||||
return "CallToSimpleGetterFromWithinClass";
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String buildErrorString(Object... infos) {
|
||||
return InspectionGadgetsBundle.message("call.to.simple.getter.in.class.problem.descriptor");
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull BaseInspectionVisitor buildVisitor() {
|
||||
return new CallToSimpleGetterInClassVisitor();
|
||||
}
|
||||
|
||||
private class CallToSimpleGetterInClassVisitor extends BaseInspectionVisitor {
|
||||
|
||||
@Override
|
||||
public void visitMethodCallExpression(@NotNull PsiMethodCallExpression call) {
|
||||
super.visitMethodCallExpression(call);
|
||||
|
||||
final PsiReferenceExpression methodExpression = call.getMethodExpression();
|
||||
final String referenceName = methodExpression.getReferenceName();
|
||||
if (referenceName == null ||
|
||||
PropertyUtilBase.getMethodNameGetterFlavour(referenceName) == PropertyUtilBase.GetterFlavour.NOT_A_GETTER) {
|
||||
return;
|
||||
}
|
||||
|
||||
final PsiElement parent = call.getParent();
|
||||
if (parent instanceof PsiExpressionStatement) {
|
||||
// inlining a top-level getter call would break code
|
||||
return;
|
||||
}
|
||||
final PsiClass containingClass = PsiUtil.getContainingClass(call);
|
||||
if (containingClass == null) {
|
||||
return;
|
||||
}
|
||||
final PsiMethod method = call.resolveMethod();
|
||||
if (method == null) {
|
||||
return;
|
||||
}
|
||||
if (!containingClass.equals(method.getContainingClass())) {
|
||||
return;
|
||||
}
|
||||
final PsiExpression qualifier = methodExpression.getQualifierExpression();
|
||||
if (qualifier != null && !(qualifier instanceof PsiThisExpression)) {
|
||||
if (ignoreGetterCallsOnOtherObjects) {
|
||||
return;
|
||||
}
|
||||
final PsiClass qualifierClass = PsiUtil.resolveClassInClassTypeOnly(qualifier.getType());
|
||||
if (!containingClass.equals(qualifierClass)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
final PsiField field = PropertyUtil.getFieldOfGetter(method);
|
||||
if (field == null) {
|
||||
return;
|
||||
}
|
||||
final PsiMember member = PsiTreeUtil.getParentOfType(call, PsiMember.class);
|
||||
if (member instanceof PsiField && !(member instanceof PsiEnumConstant) && member.getTextOffset() < field.getTextOffset()) {
|
||||
return;
|
||||
}
|
||||
if (onlyReportPrivateGetter && !method.hasModifierProperty(PsiModifier.PRIVATE)) {
|
||||
return;
|
||||
}
|
||||
final PsiMethod overridingMethod = OverridingMethodsSearch.search(method).findFirst();
|
||||
if (overridingMethod != null) {
|
||||
return;
|
||||
}
|
||||
registerMethodCallError(call);
|
||||
}
|
||||
}
|
||||
}
|
||||
+123
@@ -0,0 +1,123 @@
|
||||
/*
|
||||
* Copyright 2003-2018 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.performance;
|
||||
|
||||
import com.intellij.codeInspection.CleanupLocalInspectionTool;
|
||||
import com.intellij.codeInspection.LocalQuickFix;
|
||||
import com.intellij.codeInspection.options.OptPane;
|
||||
import com.intellij.psi.PsiClass;
|
||||
import com.intellij.psi.PsiExpression;
|
||||
import com.intellij.psi.PsiMethod;
|
||||
import com.intellij.psi.PsiMethodCallExpression;
|
||||
import com.intellij.psi.PsiModifier;
|
||||
import com.intellij.psi.PsiReferenceExpression;
|
||||
import com.intellij.psi.PsiThisExpression;
|
||||
import com.intellij.psi.search.searches.OverridingMethodsSearch;
|
||||
import com.intellij.psi.util.PropertyUtil;
|
||||
import com.intellij.psi.util.PsiUtil;
|
||||
import com.intellij.util.Query;
|
||||
import com.siyeh.InspectionGadgetsBundle;
|
||||
import com.siyeh.ig.BaseInspection;
|
||||
import com.siyeh.ig.BaseInspectionVisitor;
|
||||
import com.siyeh.ig.fixes.InlineGetterSetterCallFix;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import static com.intellij.codeInspection.options.OptPane.checkbox;
|
||||
import static com.intellij.codeInspection.options.OptPane.pane;
|
||||
|
||||
public final class CallToSimpleSetterInClassInspection extends BaseInspection implements CleanupLocalInspectionTool {
|
||||
|
||||
@SuppressWarnings("UnusedDeclaration")
|
||||
public boolean ignoreSetterCallsOnOtherObjects = false;
|
||||
@SuppressWarnings("UnusedDeclaration")
|
||||
public boolean onlyReportPrivateSetter = false;
|
||||
|
||||
@Override
|
||||
public @NotNull OptPane getOptionsPane() {
|
||||
return pane(
|
||||
checkbox("ignoreSetterCallsOnOtherObjects", InspectionGadgetsBundle.message("call.to.simple.setter.in.class.ignore.option")),
|
||||
checkbox("onlyReportPrivateSetter", InspectionGadgetsBundle.message("call.to.private.setter.in.class.option")));
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocalQuickFix buildFix(Object... infos) {
|
||||
return new InlineGetterSetterCallFix(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean runForWholeFile() {
|
||||
// Changes in another method (making setter more complicated) may affect
|
||||
// the inspection result at call sites
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String getID() {
|
||||
return "CallToSimpleSetterFromWithinClass";
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String buildErrorString(Object... infos) {
|
||||
return InspectionGadgetsBundle.message("call.to.simple.setter.in.class.problem.descriptor");
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull BaseInspectionVisitor buildVisitor() {
|
||||
return new CallToSimpleSetterInClassVisitor();
|
||||
}
|
||||
|
||||
private class CallToSimpleSetterInClassVisitor extends BaseInspectionVisitor {
|
||||
|
||||
@Override
|
||||
public void visitMethodCallExpression(@NotNull PsiMethodCallExpression call) {
|
||||
super.visitMethodCallExpression(call);
|
||||
final PsiClass containingClass = PsiUtil.getContainingClass(call);
|
||||
if (containingClass == null) {
|
||||
return;
|
||||
}
|
||||
final PsiMethod method = call.resolveMethod();
|
||||
if (method == null) {
|
||||
return;
|
||||
}
|
||||
if (!containingClass.equals(method.getContainingClass())) {
|
||||
return;
|
||||
}
|
||||
final PsiReferenceExpression methodExpression = call.getMethodExpression();
|
||||
final PsiExpression qualifier = methodExpression.getQualifierExpression();
|
||||
if (qualifier != null && !(qualifier instanceof PsiThisExpression)) {
|
||||
if (ignoreSetterCallsOnOtherObjects) {
|
||||
return;
|
||||
}
|
||||
final PsiClass qualifierClass = PsiUtil.resolveClassInClassTypeOnly(qualifier.getType());
|
||||
if (!containingClass.equals(qualifierClass)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (!PropertyUtil.isSimpleSetter(method)) {
|
||||
return;
|
||||
}
|
||||
if (onlyReportPrivateSetter && !method.hasModifierProperty(PsiModifier.PRIVATE)) {
|
||||
return;
|
||||
}
|
||||
final Query<PsiMethod> query = OverridingMethodsSearch.search(method);
|
||||
final PsiMethod overridingMethod = query.findFirst();
|
||||
if (overridingMethod != null) {
|
||||
return;
|
||||
}
|
||||
registerMethodCallError(call);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
// 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.performance;
|
||||
|
||||
import com.intellij.codeInsight.daemon.QuickFixBundle;
|
||||
import com.intellij.codeInsight.options.JavaClassValidator;
|
||||
import com.intellij.codeInspection.options.OptPane;
|
||||
import com.intellij.codeInspection.options.OptionContainer;
|
||||
import com.intellij.openapi.util.InvalidDataException;
|
||||
import com.intellij.openapi.util.WriteExternalException;
|
||||
import com.intellij.psi.CommonClassNames;
|
||||
import org.jdom.Element;
|
||||
import org.jetbrains.annotations.NonNls;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.SortedSet;
|
||||
import java.util.TreeSet;
|
||||
|
||||
import static com.intellij.codeInspection.options.OptPane.pane;
|
||||
import static com.intellij.codeInspection.options.OptPane.stringList;
|
||||
|
||||
/**
|
||||
* @author Dmitry Batkovich
|
||||
*/
|
||||
public abstract class CollectionsListSettings implements OptionContainer {
|
||||
public static final @NonNls SortedSet<String> DEFAULT_COLLECTION_LIST;
|
||||
|
||||
static {
|
||||
final SortedSet<String> set = new TreeSet<>();
|
||||
set.add("java.util.concurrent.ConcurrentHashMap");
|
||||
set.add("java.util.concurrent.PriorityBlockingQueue");
|
||||
set.add("java.util.ArrayDeque");
|
||||
set.add("java.util.ArrayList");
|
||||
set.add("java.util.HashMap");
|
||||
set.add("java.util.Hashtable");
|
||||
set.add("java.util.HashSet");
|
||||
set.add("java.util.IdentityHashMap");
|
||||
set.add("java.util.LinkedHashMap");
|
||||
set.add(CommonClassNames.JAVA_UTIL_LINKED_HASH_SET);
|
||||
set.add("java.util.PriorityQueue");
|
||||
set.add("java.util.Vector");
|
||||
set.add("java.util.WeakHashMap");
|
||||
DEFAULT_COLLECTION_LIST = Collections.unmodifiableSortedSet(set);
|
||||
}
|
||||
|
||||
private final List<String> myCollectionClassesRequiringCapacity;
|
||||
|
||||
public CollectionsListSettings() {
|
||||
myCollectionClassesRequiringCapacity = new ArrayList<>(getDefaultSettings());
|
||||
}
|
||||
|
||||
public void readSettings(@NotNull Element node) throws InvalidDataException {
|
||||
myCollectionClassesRequiringCapacity.clear();
|
||||
myCollectionClassesRequiringCapacity.addAll(getDefaultSettings());
|
||||
for (Element classElement : node.getChildren("cls")) {
|
||||
final String className = classElement.getText();
|
||||
if (classElement.getAttributeValue("remove", Boolean.FALSE.toString()).equals(Boolean.TRUE.toString())) {
|
||||
myCollectionClassesRequiringCapacity.remove(className);
|
||||
}
|
||||
else {
|
||||
myCollectionClassesRequiringCapacity.add(className);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void writeSettings(@NotNull Element node) throws WriteExternalException {
|
||||
final Collection<String> defaultToRemoveSettings = new HashSet<>(getDefaultSettings());
|
||||
defaultToRemoveSettings.removeAll(myCollectionClassesRequiringCapacity);
|
||||
|
||||
final Set<String> toAdd = new HashSet<>(myCollectionClassesRequiringCapacity);
|
||||
toAdd.removeAll(getDefaultSettings());
|
||||
|
||||
for (String className : defaultToRemoveSettings) {
|
||||
node.addContent(new Element("cls").setText(className).setAttribute("remove", Boolean.TRUE.toString()));
|
||||
}
|
||||
for (String className : toAdd) {
|
||||
node.addContent(new Element("cls").setText(className));
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract Collection<String> getDefaultSettings();
|
||||
|
||||
public Collection<String> getCollectionClassesRequiringCapacity() {
|
||||
return myCollectionClassesRequiringCapacity;
|
||||
}
|
||||
|
||||
public @NotNull OptPane getOptionPane() {
|
||||
return pane(stringList("myCollectionClassesRequiringCapacity",
|
||||
QuickFixBundle.message("collection.addall.can.be.replaced.with.constructor.fix.options.label"),
|
||||
new JavaClassValidator().withTitle(
|
||||
QuickFixBundle.message("collection.addall.can.be.replaced.with.constructor.fix.options.dialog.title"))));
|
||||
}
|
||||
}
|
||||
+111
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
* Copyright 2003-2017 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.performance;
|
||||
|
||||
import com.intellij.codeInspection.options.OptPane;
|
||||
import com.intellij.openapi.util.InvalidDataException;
|
||||
import com.intellij.openapi.util.JDOMExternalizer;
|
||||
import com.intellij.openapi.util.WriteExternalException;
|
||||
import com.intellij.psi.PsiExpressionList;
|
||||
import com.intellij.psi.PsiField;
|
||||
import com.intellij.psi.PsiNewExpression;
|
||||
import com.intellij.psi.PsiType;
|
||||
import com.intellij.util.JdomKt;
|
||||
import com.siyeh.InspectionGadgetsBundle;
|
||||
import com.siyeh.ig.BaseInspection;
|
||||
import com.siyeh.ig.BaseInspectionVisitor;
|
||||
import com.siyeh.ig.psiutils.TypeUtils;
|
||||
import org.intellij.lang.annotations.Pattern;
|
||||
import org.jdom.Element;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
|
||||
import static com.intellij.codeInspection.options.OptPane.checkbox;
|
||||
|
||||
public final class CollectionsMustHaveInitialCapacityInspection
|
||||
extends BaseInspection {
|
||||
|
||||
private final CollectionsListSettings mySettings = new CollectionsListSettings() {
|
||||
@Override
|
||||
protected Set<String> getDefaultSettings() {
|
||||
final Set<String> classes = new TreeSet<>(DEFAULT_COLLECTION_LIST);
|
||||
classes.add("java.util.BitSet");
|
||||
return classes;
|
||||
}
|
||||
};
|
||||
public boolean myIgnoreFields;
|
||||
|
||||
@Override
|
||||
public void readSettings(@NotNull Element node) throws InvalidDataException {
|
||||
mySettings.readSettings(node);
|
||||
myIgnoreFields = JDOMExternalizer.readBoolean(node, "ignoreFields");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeSettings(@NotNull Element node) throws WriteExternalException {
|
||||
mySettings.writeSettings(node);
|
||||
if (myIgnoreFields) {
|
||||
JdomKt.addOptionTag(node, "ignoreFields", Boolean.toString(true), "setting");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull OptPane getOptionsPane() {
|
||||
return mySettings.getOptionPane().prefix("mySettings")
|
||||
.append(checkbox("myIgnoreFields", InspectionGadgetsBundle.message(
|
||||
"inspection.collection.must.have.initial.capacity.initializers.option")));
|
||||
}
|
||||
|
||||
@Pattern(VALID_ID_PATTERN)
|
||||
@Override
|
||||
public @NotNull String getID() {
|
||||
return "CollectionWithoutInitialCapacity";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NotNull String buildErrorString(Object... infos) {
|
||||
return InspectionGadgetsBundle.message(
|
||||
"collections.must.have.initial.capacity.problem.descriptor");
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull BaseInspectionVisitor buildVisitor() {
|
||||
return new CollectionInitialCapacityVisitor();
|
||||
}
|
||||
|
||||
private class CollectionInitialCapacityVisitor extends BaseInspectionVisitor {
|
||||
|
||||
@Override
|
||||
public void visitNewExpression(@NotNull PsiNewExpression expression) {
|
||||
super.visitNewExpression(expression);
|
||||
if (myIgnoreFields && expression.getParent() instanceof PsiField) {
|
||||
return;
|
||||
}
|
||||
|
||||
final PsiType type = expression.getType();
|
||||
if (!mySettings.getCollectionClassesRequiringCapacity().contains(TypeUtils.resolvedClassName(type))) {
|
||||
return;
|
||||
}
|
||||
final PsiExpressionList argumentList = expression.getArgumentList();
|
||||
if (argumentList == null || !argumentList.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
registerNewExpressionError(expression);
|
||||
}
|
||||
}
|
||||
}
|
||||
+212
@@ -0,0 +1,212 @@
|
||||
// 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.performance;
|
||||
|
||||
import com.intellij.codeInspection.LocalQuickFix;
|
||||
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.PsiElement;
|
||||
import com.intellij.psi.PsiElementFactory;
|
||||
import com.intellij.psi.PsiExpression;
|
||||
import com.intellij.psi.PsiExpressionList;
|
||||
import com.intellij.psi.PsiField;
|
||||
import com.intellij.psi.PsiMethod;
|
||||
import com.intellij.psi.PsiMethodCallExpression;
|
||||
import com.intellij.psi.PsiModifier;
|
||||
import com.intellij.psi.PsiReferenceExpression;
|
||||
import com.intellij.psi.codeStyle.JavaCodeStyleManager;
|
||||
import com.intellij.psi.codeStyle.VariableKind;
|
||||
import com.intellij.psi.impl.AllowedApiFilterExtension;
|
||||
import com.intellij.psi.util.PsiUtil;
|
||||
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.CommentTracker;
|
||||
import com.siyeh.ig.psiutils.EquivalenceChecker;
|
||||
import com.siyeh.ig.psiutils.ExpressionUtils;
|
||||
import com.siyeh.ig.psiutils.VariableNameGenerator;
|
||||
import org.jetbrains.annotations.NonNls;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Matcher;
|
||||
|
||||
/**
|
||||
* @author Bas Leijdekkers
|
||||
*/
|
||||
public final class DynamicRegexReplaceableByCompiledPatternInspection extends BaseInspection {
|
||||
private static final @NonNls Collection<String> regexMethodNames = Set.of("matches", "replace", "replaceFirst", "replaceAll", "split");
|
||||
|
||||
@Override
|
||||
protected LocalQuickFix buildFix(Object... infos) {
|
||||
return new DynamicRegexReplaceableByCompiledPatternFix();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NotNull String buildErrorString(Object... infos) {
|
||||
return InspectionGadgetsBundle.message("dynamic.regex.replaceable.by.compiled.pattern.problem.descriptor");
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull BaseInspectionVisitor buildVisitor() {
|
||||
return new DynamicRegexReplaceableByCompiledPatternVisitor();
|
||||
}
|
||||
|
||||
private static class DynamicRegexReplaceableByCompiledPatternFix extends PsiUpdateModCommandQuickFix {
|
||||
|
||||
@Override
|
||||
public @NotNull String getFamilyName() {
|
||||
return InspectionGadgetsBundle.message("dynamic.regex.replaceable.by.compiled.pattern.quickfix");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void applyFix(@NotNull Project project, @NotNull PsiElement element, @NotNull ModPsiUpdater updater) {
|
||||
final PsiClass aClass = ClassUtils.getContainingStaticClass(element);
|
||||
if (aClass == null) {
|
||||
return;
|
||||
}
|
||||
final PsiElement parent = element.getParent();
|
||||
if (!(parent instanceof PsiReferenceExpression methodExpression)) {
|
||||
return;
|
||||
}
|
||||
final PsiElement grandParent = methodExpression.getParent();
|
||||
if (!(grandParent instanceof PsiMethodCallExpression methodCallExpression)) {
|
||||
return;
|
||||
}
|
||||
final PsiExpressionList list = methodCallExpression.getArgumentList();
|
||||
final PsiExpression[] expressions = list.getExpressions();
|
||||
if (expressions.length == 0) return;
|
||||
CommentTracker commentTracker = new CommentTracker();
|
||||
final String methodName = methodExpression.getReferenceName();
|
||||
final boolean literalReplacement = "replace".equals(methodName);
|
||||
String regexpText = commentTracker.text(expressions[0]);
|
||||
String initializer = "java.util.regex.Pattern.compile(" + regexpText +
|
||||
(literalReplacement ? ", java.util.regex.Pattern.LITERAL" : "") + ")";
|
||||
final PsiElementFactory factory = JavaPsiFacade.getElementFactory(project);
|
||||
List<String> names = new VariableNameGenerator(aClass, VariableKind.STATIC_FINAL_FIELD)
|
||||
.byExpression(factory.createExpressionFromText("__(" + regexpText + ")", null))
|
||||
.byName("PATTERN", "REGEX", "REGEXP").generateAll(true);
|
||||
String name = names.getFirst();
|
||||
String fieldText = "private static final java.util.regex.Pattern " + name + " = "+ initializer + ";";
|
||||
PsiField fieldTemplate = factory.createFieldFromText(fieldText, element);
|
||||
for (PsiField classField : aClass.getFields()) {
|
||||
if (classField.hasModifierProperty(PsiModifier.STATIC) && classField.hasModifierProperty(PsiModifier.FINAL) &&
|
||||
fieldTemplate.getType().equals(classField.getType()) &&
|
||||
EquivalenceChecker.getCanonicalPsiEquivalence()
|
||||
.expressionsAreEquivalent(fieldTemplate.getInitializer(), classField.getInitializer())) {
|
||||
name = classField.getName();
|
||||
fieldTemplate = null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
final @NonNls StringBuilder expressionText = new StringBuilder(name).append(".");
|
||||
final PsiExpression qualifier = methodExpression.getQualifierExpression();
|
||||
final @NonNls String qualifierText = (qualifier == null) ? "this" : commentTracker.text(qualifier);
|
||||
final int expressionsLength = expressions.length;
|
||||
if ("split".equals(methodName)) {
|
||||
expressionText.append(methodName).append('(').append(qualifierText);
|
||||
for (int i = 1; i < expressionsLength; i++) {
|
||||
expressionText.append(',').append(commentTracker.text(expressions[i]));
|
||||
}
|
||||
expressionText.append(')');
|
||||
}
|
||||
else {
|
||||
expressionText.append("matcher(").append(qualifierText).append(").");
|
||||
expressionText.append(literalReplacement ? "replaceAll" : methodName);
|
||||
expressionText.append('(');
|
||||
boolean quote = false;
|
||||
if (literalReplacement) {
|
||||
quote = (expressionsLength > 1 && needsQuote(expressions[1]));
|
||||
if (quote) {
|
||||
expressionText.append("java.util.regex.Matcher.quoteReplacement(");
|
||||
}
|
||||
}
|
||||
if (expressionsLength > 1) {
|
||||
expressionText.append(commentTracker.text(expressions[1]));
|
||||
for (int i = 2; i < expressionsLength; i++) {
|
||||
expressionText.append(',').append(commentTracker.text(expressions[i]));
|
||||
}
|
||||
}
|
||||
if (literalReplacement && quote) {
|
||||
expressionText.append(')');
|
||||
}
|
||||
expressionText.append(')');
|
||||
}
|
||||
|
||||
JavaCodeStyleManager javaCodeStyleManager = JavaCodeStyleManager.getInstance(project);
|
||||
if (fieldTemplate != null) {
|
||||
PsiField field = (PsiField)aClass.add(fieldTemplate);
|
||||
field = (PsiField)javaCodeStyleManager.shortenClassReferences(field);
|
||||
updater.rename(field, names);
|
||||
}
|
||||
javaCodeStyleManager.shortenClassReferences(
|
||||
commentTracker.replaceAndRestoreComments(methodCallExpression, expressionText.toString()));
|
||||
}
|
||||
|
||||
private static boolean needsQuote(PsiExpression expr) {
|
||||
Object constExprValue = ExpressionUtils.computeConstantExpression(expr);
|
||||
return !(constExprValue instanceof String value) || Matcher.quoteReplacement(value) != constExprValue;
|
||||
}
|
||||
}
|
||||
|
||||
private static class DynamicRegexReplaceableByCompiledPatternVisitor extends BaseInspectionVisitor {
|
||||
|
||||
@Override
|
||||
public void visitMethodCallExpression(@NotNull PsiMethodCallExpression expression) {
|
||||
super.visitMethodCallExpression(expression);
|
||||
if (!isCallToRegexMethod(expression)) {
|
||||
return;
|
||||
}
|
||||
registerMethodCallError(expression);
|
||||
}
|
||||
|
||||
private static boolean isCallToRegexMethod(PsiMethodCallExpression expression) {
|
||||
final @NonNls String name = expression.getMethodExpression().getReferenceName();
|
||||
if (!regexMethodNames.contains(name)) {
|
||||
return false;
|
||||
}
|
||||
final PsiExpression[] arguments = expression.getArgumentList().getExpressions();
|
||||
if (arguments.length == 0) {
|
||||
return false;
|
||||
}
|
||||
final Object value = ExpressionUtils.computeConstantExpression(arguments[0]);
|
||||
if (!(value instanceof String regex)) {
|
||||
return false;
|
||||
}
|
||||
if (PsiUtil.isLanguageLevel7OrHigher(expression) && "split".equals(name) && isOptimizedPattern(regex) ||
|
||||
PsiUtil.isLanguageLevel9OrHigher(expression) && "replace".equals(name)) {
|
||||
return false;
|
||||
}
|
||||
final PsiMethod method = expression.resolveMethod();
|
||||
if (method == null) {
|
||||
return false;
|
||||
}
|
||||
final PsiClass containingClass = method.getContainingClass();
|
||||
if (containingClass == null || !CommonClassNames.JAVA_LANG_STRING.equals(containingClass.getQualifiedName())) {
|
||||
return false;
|
||||
}
|
||||
return AllowedApiFilterExtension.isClassAllowed("java.util.regex.Pattern", expression);
|
||||
}
|
||||
|
||||
private static boolean isOptimizedPattern(String regex) {
|
||||
// from String.split()
|
||||
int ch;
|
||||
return ((regex.length() == 1 &&
|
||||
".$|()[{^?*+\\".indexOf(ch = regex.charAt(0)) == -1) ||
|
||||
(regex.length() == 2 &&
|
||||
regex.charAt(0) == '\\' &&
|
||||
(((ch = regex.charAt(1))-'0')|('9'-ch)) < 0 &&
|
||||
((ch-'a')|('z'-ch)) < 0 &&
|
||||
((ch-'A')|('Z'-ch)) < 0)) &&
|
||||
(ch < Character.MIN_HIGH_SURROGATE ||
|
||||
ch > Character.MAX_LOW_SURROGATE);
|
||||
}
|
||||
}
|
||||
}
|
||||
+144
@@ -0,0 +1,144 @@
|
||||
/*
|
||||
* Copyright 2003-2015 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.performance;
|
||||
|
||||
import com.intellij.codeInsight.daemon.impl.UnusedSymbolUtil;
|
||||
import com.intellij.codeInsight.options.JavaInspectionButtons;
|
||||
import com.intellij.codeInsight.options.JavaInspectionControls;
|
||||
import com.intellij.codeInspection.LocalQuickFix;
|
||||
import com.intellij.codeInspection.dataFlow.CommonDataflow;
|
||||
import com.intellij.codeInspection.dataFlow.Mutability;
|
||||
import com.intellij.codeInspection.options.OptPane;
|
||||
import com.intellij.pom.java.JavaFeature;
|
||||
import com.intellij.psi.JavaTokenType;
|
||||
import com.intellij.psi.PsiAnonymousClass;
|
||||
import com.intellij.psi.PsiBinaryExpression;
|
||||
import com.intellij.psi.PsiClass;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiExpression;
|
||||
import com.intellij.psi.PsiField;
|
||||
import com.intellij.psi.PsiModifier;
|
||||
import com.intellij.psi.PsiPrimitiveType;
|
||||
import com.intellij.psi.PsiReferenceExpression;
|
||||
import com.intellij.psi.PsiSynchronizedStatement;
|
||||
import com.intellij.psi.PsiType;
|
||||
import com.intellij.psi.util.PsiUtil;
|
||||
import com.siyeh.InspectionGadgetsBundle;
|
||||
import com.siyeh.ig.BaseInspection;
|
||||
import com.siyeh.ig.BaseInspectionVisitor;
|
||||
import com.siyeh.ig.fixes.ChangeModifierFix;
|
||||
import com.siyeh.ig.psiutils.ClassUtils;
|
||||
import com.siyeh.ig.psiutils.ExpressionUtils;
|
||||
import com.siyeh.ig.psiutils.SideEffectChecker;
|
||||
import com.siyeh.ig.psiutils.VariableAccessUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static com.intellij.codeInspection.options.OptPane.pane;
|
||||
|
||||
public final class FieldMayBeStaticInspection extends BaseInspection {
|
||||
|
||||
@Override
|
||||
public @NotNull BaseInspectionVisitor buildVisitor() {
|
||||
return new FieldMayBeStaticVisitor();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NotNull String buildErrorString(Object... infos) {
|
||||
return InspectionGadgetsBundle.message(
|
||||
"field.may.be.static.problem.descriptor");
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull OptPane getOptionsPane() {
|
||||
return pane(JavaInspectionControls.button(JavaInspectionButtons.ButtonKind.IMPLICIT_WRITE_ANNOTATIONS));
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocalQuickFix buildFix(Object... infos) {
|
||||
return new ChangeModifierFix(PsiModifier.STATIC);
|
||||
}
|
||||
|
||||
private static final class FieldMayBeStaticVisitor extends BaseInspectionVisitor {
|
||||
@Override
|
||||
public void visitField(@NotNull PsiField field) {
|
||||
if (field.hasModifierProperty(PsiModifier.STATIC)) {
|
||||
return;
|
||||
}
|
||||
if (!field.hasModifierProperty(PsiModifier.FINAL)) {
|
||||
return;
|
||||
}
|
||||
final PsiExpression initializer = field.getInitializer();
|
||||
if (initializer == null) {
|
||||
return;
|
||||
}
|
||||
if (SideEffectChecker.mayHaveSideEffects(initializer)) {
|
||||
return;
|
||||
}
|
||||
final PsiType type = field.getType();
|
||||
if (!ClassUtils.isImmutable(type) &&
|
||||
Mutability.fromDfType(CommonDataflow.getDfType(initializer)) != Mutability.UNMODIFIABLE) {
|
||||
return;
|
||||
}
|
||||
final PsiClass containingClass = field.getContainingClass();
|
||||
if (containingClass != null
|
||||
&& !containingClass.hasModifierProperty(PsiModifier.STATIC)
|
||||
&& containingClass.getContainingClass() != null
|
||||
&& !PsiUtil.isAvailable(JavaFeature.INNER_STATICS, containingClass)
|
||||
&& !PsiUtil.isCompileTimeConstant(field)) {
|
||||
// inner class cannot have static declarations in earlier Java versions
|
||||
return;
|
||||
}
|
||||
if (UnusedSymbolUtil.isImplicitWrite(field)) {
|
||||
return;
|
||||
}
|
||||
if (containingClass instanceof PsiAnonymousClass &&
|
||||
!PsiUtil.isAvailable(JavaFeature.INNER_STATICS, containingClass) &&
|
||||
!PsiUtil.isCompileTimeConstant(field)) {
|
||||
return;
|
||||
}
|
||||
if (!canBeStatic(initializer)) {
|
||||
return;
|
||||
}
|
||||
if (isIdentitySensitive(field)) {
|
||||
return;
|
||||
}
|
||||
registerFieldError(field);
|
||||
}
|
||||
|
||||
private static boolean isIdentitySensitive(@NotNull PsiField field) {
|
||||
if (field.getType() instanceof PsiPrimitiveType) return false;
|
||||
List<PsiReferenceExpression> refs = VariableAccessUtils.getVariableReferences(field);
|
||||
for (PsiReferenceExpression ref : refs) {
|
||||
PsiElement parent = PsiUtil.skipParenthesizedExprUp(ref.getParent());
|
||||
if (parent instanceof PsiSynchronizedStatement) return true;
|
||||
if (parent instanceof PsiBinaryExpression binOp &&
|
||||
(binOp.getOperationTokenType() == JavaTokenType.EQEQ || binOp.getOperationTokenType() == JavaTokenType.NE) &&
|
||||
ExpressionUtils.getValueComparedWithNull(binOp) == null) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean canBeStatic(PsiExpression initializer) {
|
||||
final CanBeStaticVisitor canBeStaticVisitor = new CanBeStaticVisitor();
|
||||
initializer.accept(canBeStaticVisitor);
|
||||
return canBeStaticVisitor.canBeStatic();
|
||||
}
|
||||
}
|
||||
}
|
||||
+230
@@ -0,0 +1,230 @@
|
||||
// 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.performance;
|
||||
|
||||
import com.intellij.codeInsight.PsiEquivalenceUtil;
|
||||
import com.intellij.codeInspection.AbstractBaseJavaLocalInspectionTool;
|
||||
import com.intellij.codeInspection.CommonQuickFixBundle;
|
||||
import com.intellij.codeInspection.ProblemHighlightType;
|
||||
import com.intellij.codeInspection.ProblemsHolder;
|
||||
import com.intellij.codeInspection.dataFlow.DfaPsiUtil;
|
||||
import com.intellij.codeInspection.dataFlow.DfaUtil;
|
||||
import com.intellij.codeInspection.dataFlow.value.RelationType;
|
||||
import com.intellij.modcommand.ModPsiUpdater;
|
||||
import com.intellij.modcommand.PsiUpdateModCommandQuickFix;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.util.Couple;
|
||||
import com.intellij.openapi.util.Pair;
|
||||
import com.intellij.psi.CommonClassNames;
|
||||
import com.intellij.psi.JavaElementVisitor;
|
||||
import com.intellij.psi.JavaTokenType;
|
||||
import com.intellij.psi.PsiBinaryExpression;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiElementVisitor;
|
||||
import com.intellij.psi.PsiExpression;
|
||||
import com.intellij.psi.PsiExpressionStatement;
|
||||
import com.intellij.psi.PsiForStatement;
|
||||
import com.intellij.psi.PsiIfStatement;
|
||||
import com.intellij.psi.PsiLoopStatement;
|
||||
import com.intellij.psi.PsiMember;
|
||||
import com.intellij.psi.PsiMethodCallExpression;
|
||||
import com.intellij.psi.PsiStatement;
|
||||
import com.intellij.psi.PsiWhileStatement;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.psi.util.PsiUtil;
|
||||
import com.siyeh.InspectionGadgetsBundle;
|
||||
import com.siyeh.ig.callMatcher.CallMatcher;
|
||||
import com.siyeh.ig.psiutils.CommentTracker;
|
||||
import com.siyeh.ig.psiutils.ControlFlowUtils;
|
||||
import com.siyeh.ig.psiutils.CountingLoop;
|
||||
import com.siyeh.ig.psiutils.ExpressionUtils;
|
||||
import com.siyeh.ig.psiutils.JavaPsiMathUtil;
|
||||
import org.jetbrains.annotations.Nls;
|
||||
import org.jetbrains.annotations.NonNls;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import static com.intellij.util.ObjectUtils.tryCast;
|
||||
import static com.siyeh.ig.callMatcher.CallMatcher.instanceCall;
|
||||
|
||||
public final class ListRemoveInLoopInspection extends AbstractBaseJavaLocalInspectionTool {
|
||||
private static final CallMatcher LIST_REMOVE = instanceCall(CommonClassNames.JAVA_UTIL_LIST, "remove").parameterTypes("int");
|
||||
private static final CallMatcher LIST_SIZE = instanceCall(CommonClassNames.JAVA_UTIL_LIST, "size").parameterCount(0);
|
||||
|
||||
@Override
|
||||
public @NotNull PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly) {
|
||||
return new JavaElementVisitor() {
|
||||
@Override
|
||||
public void visitMethodCallExpression(@NotNull PsiMethodCallExpression call) {
|
||||
if (!LIST_REMOVE.test(call)) return;
|
||||
PsiExpression listExpression = call.getMethodExpression().getQualifierExpression();
|
||||
if (listExpression == null) return;
|
||||
PsiElement parent = call.getParent();
|
||||
if (!(parent instanceof PsiExpressionStatement)) return;
|
||||
PsiLoopStatement loop = PsiTreeUtil.getParentOfType(parent, PsiLoopStatement.class, true, PsiMember.class);
|
||||
if (loop == null) return;
|
||||
if (ControlFlowUtils.stripBraces(loop.getBody()) != parent) return;
|
||||
PsiExpression arg = call.getArgumentList().getExpressions()[0];
|
||||
|
||||
ProblemHighlightType type;
|
||||
if (isRemoveInCountingLoop(loop, arg)) {
|
||||
type = ProblemHighlightType.GENERIC_ERROR_OR_WARNING;
|
||||
} else if (isRemoveInWhileLoop(loop, listExpression, arg)) {
|
||||
// while-loop scenario is not so bad (always the last element is removed which is usually fast),
|
||||
// the replacement is often longer and harder to read
|
||||
// and such loop is often used to remove at most one element which is faster than using subList().clear()
|
||||
if (!isOnTheFly) return;
|
||||
type = ProblemHighlightType.INFORMATION;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
holder.registerProblem(loop.getFirstChild(), InspectionGadgetsBundle.message("inspection.list.remove.in.loop.message"),
|
||||
type, new ListRemoveInLoopFix());
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks for pattern like
|
||||
* <pre>{@code
|
||||
* while (list.size() > smth) {
|
||||
* list.remove(list.size() - 1);
|
||||
* }}</pre>
|
||||
*/
|
||||
private static boolean isRemoveInWhileLoop(PsiLoopStatement loop, PsiExpression listExpression, PsiExpression arg) {
|
||||
if (!(loop instanceof PsiWhileStatement)) return false;
|
||||
PsiBinaryExpression condition =
|
||||
tryCast(PsiUtil.skipParenthesizedExprDown(((PsiWhileStatement)loop).getCondition()), PsiBinaryExpression.class);
|
||||
if (condition == null) return false;
|
||||
RelationType relationType = DfaPsiUtil.getRelationByToken(condition.getOperationTokenType());
|
||||
if (relationType == null) return false;
|
||||
PsiExpression sizeExpression;
|
||||
switch (relationType) {
|
||||
case GE, GT -> sizeExpression = condition.getLOperand();
|
||||
case LE, LT -> sizeExpression = condition.getROperand();
|
||||
default -> {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
PsiMethodCallExpression sizeCall = tryCast(PsiUtil.skipParenthesizedExprDown(sizeExpression), PsiMethodCallExpression.class);
|
||||
if (!LIST_SIZE.test(sizeCall)) return false;
|
||||
PsiExpression sizeQualifier = sizeCall.getMethodExpression().getQualifierExpression();
|
||||
if (sizeQualifier == null || !PsiEquivalenceUtil.areElementsEquivalent(sizeQualifier, listExpression)) return false;
|
||||
PsiBinaryExpression diff = tryCast(PsiUtil.skipParenthesizedExprDown(arg), PsiBinaryExpression.class);
|
||||
if (diff == null || !diff.getOperationTokenType().equals(JavaTokenType.MINUS)) return false;
|
||||
if (!ExpressionUtils.isLiteral(diff.getROperand(), 1)) return false;
|
||||
return PsiEquivalenceUtil.areElementsEquivalent(sizeCall, diff.getLOperand());
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks for pattern like
|
||||
* <pre>{@code
|
||||
* for (int i=from; i<to; i++) {
|
||||
* list.remove(from);
|
||||
* }}</pre> or
|
||||
* <pre>{@code
|
||||
* for (int i=to-1; i>=from; i--) {
|
||||
* list.remove(i);
|
||||
* }}</pre>
|
||||
*/
|
||||
private static boolean isRemoveInCountingLoop(PsiLoopStatement forLoop, PsiExpression arg) {
|
||||
if (!(forLoop instanceof PsiForStatement)) return false;
|
||||
CountingLoop loop = CountingLoop.from((PsiForStatement)forLoop);
|
||||
if (loop == null) return false;
|
||||
if (loop.isDescending()) {
|
||||
return ExpressionUtils.isReferenceTo(arg, loop.getCounter());
|
||||
}
|
||||
return PsiEquivalenceUtil.areElementsEquivalent(arg, loop.getInitializer());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static class ListRemoveInLoopFix extends PsiUpdateModCommandQuickFix {
|
||||
@Override
|
||||
public @Nls(capitalization = Nls.Capitalization.Sentence) @NotNull String getFamilyName() {
|
||||
return CommonQuickFixBundle.message("fix.replace.with.x", "List.subList().clear()");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void applyFix(@NotNull Project project, @NotNull PsiElement element, @NotNull ModPsiUpdater updater) {
|
||||
PsiLoopStatement loopStatement = PsiTreeUtil.getParentOfType(element, PsiLoopStatement.class);
|
||||
if (loopStatement == null) return;
|
||||
PsiExpressionStatement statement = tryCast(ControlFlowUtils.stripBraces(loopStatement.getBody()), PsiExpressionStatement.class);
|
||||
if (statement == null) return;
|
||||
PsiMethodCallExpression call = tryCast(statement.getExpression(), PsiMethodCallExpression.class);
|
||||
if (call == null) return;
|
||||
PsiExpression listExpression = call.getMethodExpression().getQualifierExpression();
|
||||
if (listExpression == null) return;
|
||||
CommentTracker ct = new CommentTracker();
|
||||
|
||||
Pair<String, String> startEnd = getStartEnd(loopStatement, ct);
|
||||
if (startEnd == null) return;
|
||||
|
||||
String start = startEnd.getFirst();
|
||||
String end = startEnd.getSecond();
|
||||
final @NonNls String statementText = ct.text(listExpression) + ".subList(" + start + "," + end + ").clear();";
|
||||
final String replacementText = "if(" + end + ">" + start + "){" + statementText + "}";
|
||||
PsiIfStatement ifStatement = (PsiIfStatement)ct.replaceAndRestoreComments(loopStatement, replacementText);
|
||||
ct = new CommentTracker();
|
||||
PsiExpression condition = ifStatement.getCondition();
|
||||
String simplified = JavaPsiMathUtil.simplifyComparison(condition, ct);
|
||||
if (simplified != null) {
|
||||
condition = (PsiExpression)ct.replaceAndRestoreComments(condition, simplified);
|
||||
ct = new CommentTracker();
|
||||
}
|
||||
if (Boolean.TRUE.equals(DfaUtil.evaluateCondition(condition))) {
|
||||
PsiStatement nakedSubListClear = ControlFlowUtils.stripBraces(ifStatement.getThenBranch());
|
||||
assert nakedSubListClear != null;
|
||||
ct.replaceAndRestoreComments(ifStatement, nakedSubListClear);
|
||||
}
|
||||
}
|
||||
|
||||
public Couple<String> getStartEnd(PsiLoopStatement loopStatement, CommentTracker ct) {
|
||||
if (loopStatement instanceof PsiForStatement) {
|
||||
CountingLoop loop = CountingLoop.from((PsiForStatement)loopStatement);
|
||||
if (loop == null) return null;
|
||||
|
||||
String start, end;
|
||||
if (loop.isDescending()) {
|
||||
start = loop.isIncluding() ? ct.text(loop.getBound()) : JavaPsiMathUtil.add(loop.getBound(), 1, ct);
|
||||
end = JavaPsiMathUtil.add(loop.getInitializer(), 1, ct);
|
||||
}
|
||||
else {
|
||||
start = ct.text(loop.getInitializer());
|
||||
end = loop.isIncluding() ? JavaPsiMathUtil.add(loop.getBound(), 1, ct) : ct.text(loop.getBound());
|
||||
}
|
||||
return Couple.of(start, end);
|
||||
}
|
||||
if (loopStatement instanceof PsiWhileStatement) {
|
||||
PsiBinaryExpression condition =
|
||||
tryCast(PsiUtil.skipParenthesizedExprDown(((PsiWhileStatement)loopStatement).getCondition()), PsiBinaryExpression.class);
|
||||
if (condition == null) return null;
|
||||
RelationType relationType = DfaPsiUtil.getRelationByToken(condition.getOperationTokenType());
|
||||
if (relationType == null) return null;
|
||||
PsiExpression left = condition.getLOperand();
|
||||
PsiExpression right = condition.getROperand();
|
||||
if (right == null) return null;
|
||||
String start, end;
|
||||
switch (relationType) {
|
||||
case GE -> {
|
||||
start = JavaPsiMathUtil.add(right, -1, ct);
|
||||
end = ct.text(left);
|
||||
}
|
||||
case GT -> {
|
||||
start = ct.text(right);
|
||||
end = ct.text(left);
|
||||
}
|
||||
case LE -> {
|
||||
start = JavaPsiMathUtil.add(left, -1, ct);
|
||||
end = ct.text(right);
|
||||
}
|
||||
case LT -> {
|
||||
start = ct.text(left);
|
||||
end = ct.text(right);
|
||||
}
|
||||
default -> {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return Couple.of(start, end);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
+187
@@ -0,0 +1,187 @@
|
||||
/*
|
||||
* Copyright 2003-2017 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.performance;
|
||||
|
||||
import com.intellij.codeInsight.intention.preview.IntentionPreviewInfo;
|
||||
import com.intellij.codeInspection.InspectionManager;
|
||||
import com.intellij.codeInspection.LocalQuickFix;
|
||||
import com.intellij.codeInspection.ProblemDescriptor;
|
||||
import com.intellij.codeInspection.options.OptPane;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.util.Condition;
|
||||
import com.intellij.openapi.util.WriteExternalException;
|
||||
import com.intellij.pom.java.JavaFeature;
|
||||
import com.intellij.psi.PsiClass;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiField;
|
||||
import com.intellij.psi.PsiJavaFile;
|
||||
import com.intellij.psi.PsiMethod;
|
||||
import com.intellij.psi.PsiModifier;
|
||||
import com.intellij.psi.impl.FindSuperElementsHelper;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.psi.util.PsiUtil;
|
||||
import com.intellij.refactoring.JavaRefactoringFactory;
|
||||
import com.siyeh.InspectionGadgetsBundle;
|
||||
import com.siyeh.ig.BaseInspection;
|
||||
import com.siyeh.ig.BaseInspectionVisitor;
|
||||
import com.siyeh.ig.psiutils.MethodUtils;
|
||||
import com.siyeh.ig.psiutils.SerializationUtils;
|
||||
import org.jdom.Element;
|
||||
import org.jetbrains.annotations.NonNls;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import static com.intellij.codeInspection.options.OptPane.checkbox;
|
||||
import static com.intellij.codeInspection.options.OptPane.pane;
|
||||
|
||||
public final class MethodMayBeStaticInspection extends BaseInspection {
|
||||
private static final @NonNls String IGNORE_DEFAULT_METHODS_ATTR_NAME = "m_ignoreDefaultMethods";
|
||||
private static final @NonNls String ONLY_PRIVATE_OR_FINAL_ATTR_NAME = "m_onlyPrivateOrFinal";
|
||||
private static final @NonNls String IGNORE_EMPTY_METHODS_ATTR_NAME = "m_ignoreEmptyMethods";
|
||||
private static final @NonNls String REPLACE_QUALIFIER_ATTR_NAME = "m_replaceQualifier";
|
||||
/**
|
||||
* @noinspection PublicField
|
||||
*/
|
||||
public boolean m_onlyPrivateOrFinal = false;
|
||||
/**
|
||||
* @noinspection PublicField
|
||||
*/
|
||||
public boolean m_ignoreEmptyMethods = true;
|
||||
public boolean m_ignoreDefaultMethods = true;
|
||||
public boolean m_replaceQualifier = true;
|
||||
|
||||
@Override
|
||||
protected LocalQuickFix buildFix(Object... infos) {
|
||||
return new LocalQuickFix() {
|
||||
@Override
|
||||
public @NotNull IntentionPreviewInfo generatePreview(@NotNull Project project, @NotNull ProblemDescriptor previewDescriptor) {
|
||||
final PsiMethod element = PsiTreeUtil.getParentOfType(previewDescriptor.getPsiElement(), PsiMethod.class);
|
||||
if (element != null) {
|
||||
element.getModifierList().setModifierProperty(PsiModifier.STATIC, true);
|
||||
}
|
||||
return IntentionPreviewInfo.DIFF;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean startInWriteAction() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String getFamilyName() {
|
||||
return InspectionGadgetsBundle.message("change.modifier.quickfix", PsiModifier.STATIC);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
|
||||
final PsiElement problemElement = descriptor.getPsiElement();
|
||||
if (problemElement == null || !problemElement.isValid()) return;
|
||||
|
||||
final PsiMethod element = PsiTreeUtil.getParentOfType(problemElement, PsiMethod.class);
|
||||
if (element != null) {
|
||||
JavaRefactoringFactory.getInstance(project).createMakeMethodStatic(element, m_replaceQualifier, null, PsiField.EMPTY_ARRAY, null).run();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull OptPane getOptionsPane() {
|
||||
return pane(
|
||||
checkbox(ONLY_PRIVATE_OR_FINAL_ATTR_NAME, InspectionGadgetsBundle.message("method.may.be.static.only.option")),
|
||||
checkbox(IGNORE_EMPTY_METHODS_ATTR_NAME, InspectionGadgetsBundle.message("method.may.be.static.empty.option")),
|
||||
checkbox(IGNORE_DEFAULT_METHODS_ATTR_NAME, InspectionGadgetsBundle.message("method.may.be.static.ignore.default.methods.option")),
|
||||
checkbox(REPLACE_QUALIFIER_ATTR_NAME,
|
||||
InspectionGadgetsBundle.message("method.may.be.static.replaces.qualifiers.with.class.references.option")));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NotNull String buildErrorString(Object... infos) {
|
||||
return InspectionGadgetsBundle.message("method.may.be.static.problem.descriptor");
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull BaseInspectionVisitor buildVisitor() {
|
||||
return new MethodCanBeStaticVisitor();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeSettings(@NotNull Element node) throws WriteExternalException {
|
||||
node.addContent(new Element("option").setAttribute("name", ONLY_PRIVATE_OR_FINAL_ATTR_NAME).setAttribute("value", String.valueOf(m_onlyPrivateOrFinal)));
|
||||
node.addContent(new Element("option").setAttribute("name", IGNORE_EMPTY_METHODS_ATTR_NAME).setAttribute("value", String.valueOf(
|
||||
m_ignoreEmptyMethods)));
|
||||
if (!m_ignoreDefaultMethods) {
|
||||
node.addContent(new Element("option").setAttribute("name", IGNORE_DEFAULT_METHODS_ATTR_NAME).setAttribute("value", "false"));
|
||||
}
|
||||
if (!m_replaceQualifier) {
|
||||
node.addContent(new Element("option").setAttribute("name", REPLACE_QUALIFIER_ATTR_NAME).setAttribute("value", "false"));
|
||||
}
|
||||
}
|
||||
|
||||
private class MethodCanBeStaticVisitor extends BaseInspectionVisitor {
|
||||
|
||||
@Override
|
||||
public void visitMethod(@NotNull PsiMethod method) {
|
||||
super.visitMethod(method);
|
||||
if (method.hasModifierProperty(PsiModifier.STATIC) ||
|
||||
method.hasModifierProperty(PsiModifier.ABSTRACT) ||
|
||||
method.hasModifierProperty(PsiModifier.SYNCHRONIZED) ||
|
||||
method.hasModifierProperty(PsiModifier.NATIVE)) {
|
||||
return;
|
||||
}
|
||||
if (method.isConstructor() || method.getNameIdentifier() == null) {
|
||||
return;
|
||||
}
|
||||
if (m_ignoreDefaultMethods && method.hasModifierProperty(PsiModifier.DEFAULT)) {
|
||||
return;
|
||||
}
|
||||
if (m_ignoreEmptyMethods && MethodUtils.isEmpty(method)) {
|
||||
return;
|
||||
}
|
||||
final PsiClass containingClass = PsiUtil.getContainingClass(method);
|
||||
if (containingClass == null) {
|
||||
return;
|
||||
}
|
||||
for (Condition<PsiElement> addin : InspectionManager.CANT_BE_STATIC_EXTENSION.getExtensionList()) {
|
||||
if (addin.value(method)) return;
|
||||
}
|
||||
final PsiElement scope = containingClass.getScope();
|
||||
if (!(scope instanceof PsiJavaFile) && !containingClass.hasModifierProperty(PsiModifier.STATIC) && !containingClass.isInterface() &&
|
||||
!PsiUtil.isAvailable(JavaFeature.INNER_STATICS, scope)) {
|
||||
return;
|
||||
}
|
||||
if (m_onlyPrivateOrFinal && !method.hasModifierProperty(PsiModifier.FINAL) && !method.hasModifierProperty(PsiModifier.PRIVATE)) {
|
||||
return;
|
||||
}
|
||||
if (isExcluded(method) || MethodUtils.hasSuper(method) || MethodUtils.isOverridden(method)) {
|
||||
return;
|
||||
}
|
||||
if (FindSuperElementsHelper.getSiblingInheritedViaSubClass(method) != null) {
|
||||
return;
|
||||
}
|
||||
final MethodReferenceVisitor visitor = new MethodReferenceVisitor(method);
|
||||
method.accept(visitor);
|
||||
if (!visitor.areReferencesStaticallyAccessible()) {
|
||||
return;
|
||||
}
|
||||
registerMethodError(method);
|
||||
}
|
||||
|
||||
private static boolean isExcluded(PsiMethod method) {
|
||||
return SerializationUtils.isWriteObject(method) || SerializationUtils.isReadObject(method) ||
|
||||
SerializationUtils.isWriteReplace(method) || SerializationUtils.isReadResolve(method);
|
||||
}
|
||||
}
|
||||
}
|
||||
+189
@@ -0,0 +1,189 @@
|
||||
// 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.performance;
|
||||
|
||||
import com.intellij.codeInspection.dataFlow.CommonDataflow;
|
||||
import com.intellij.codeInspection.dataFlow.ContractReturnValue;
|
||||
import com.intellij.codeInspection.dataFlow.JavaMethodContractUtil;
|
||||
import com.intellij.codeInspection.dataFlow.StandardMethodContract;
|
||||
import com.intellij.codeInspection.dataFlow.rangeSet.LongRangeSet;
|
||||
import com.intellij.psi.CommonClassNames;
|
||||
import com.intellij.psi.PsiArrayInitializerExpression;
|
||||
import com.intellij.psi.PsiArrayType;
|
||||
import com.intellij.psi.PsiAssertStatement;
|
||||
import com.intellij.psi.PsiClass;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiExpression;
|
||||
import com.intellij.psi.PsiForeachStatement;
|
||||
import com.intellij.psi.PsiMethod;
|
||||
import com.intellij.psi.PsiMethodCallExpression;
|
||||
import com.intellij.psi.PsiNewExpression;
|
||||
import com.intellij.psi.PsiPolyadicExpression;
|
||||
import com.intellij.psi.PsiThrowStatement;
|
||||
import com.intellij.psi.PsiType;
|
||||
import com.intellij.psi.PsiVariable;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.siyeh.InspectionGadgetsBundle;
|
||||
import com.siyeh.ig.BaseInspection;
|
||||
import com.siyeh.ig.BaseInspectionVisitor;
|
||||
import com.siyeh.ig.psiutils.ExpectedTypeUtils;
|
||||
import com.siyeh.ig.psiutils.ExpressionUtils;
|
||||
import com.siyeh.ig.psiutils.MethodUtils;
|
||||
import com.siyeh.ig.psiutils.TypeUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Bas Leijdekkers
|
||||
*/
|
||||
public final class ObjectInstantiationInEqualsHashCodeInspection extends BaseInspection {
|
||||
|
||||
@Override
|
||||
protected @NotNull String buildErrorString(Object... infos) {
|
||||
final PsiMethod method = PsiTreeUtil.getParentOfType((PsiElement)infos[0], PsiMethod.class);
|
||||
assert method != null;
|
||||
if (infos.length > 1) {
|
||||
return InspectionGadgetsBundle.message("object.instantiation.inside.equals.or.hashcode.problem.descriptor2", method.getName(), infos[1]);
|
||||
}
|
||||
return InspectionGadgetsBundle.message("object.instantiation.inside.equals.or.hashcode.problem.descriptor", method.getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull BaseInspectionVisitor buildVisitor() {
|
||||
return new ObjectInstantiationInEqualsHashCodeVisitor();
|
||||
}
|
||||
|
||||
private static class ObjectInstantiationInEqualsHashCodeVisitor extends BaseInspectionVisitor {
|
||||
|
||||
@Override
|
||||
public void visitExpression(@NotNull PsiExpression expression) {
|
||||
if (!ExpressionUtils.isAutoBoxed(expression) || isAutoBoxingFromCache(expression) || !isInsideEqualsOrHashCode(expression)) {
|
||||
return;
|
||||
}
|
||||
final PsiType expectedType = ExpectedTypeUtils.findExpectedType(expression, false, true);
|
||||
if (TypeUtils.getType(CommonClassNames.JAVA_LANG_BOOLEAN, expression).equals(expectedType) ||
|
||||
TypeUtils.getType(CommonClassNames.JAVA_LANG_BYTE, expression).equals(expectedType)) {
|
||||
return;
|
||||
}
|
||||
registerError(expression, expression, "autoboxing");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitForeachStatement(@NotNull PsiForeachStatement statement) {
|
||||
final PsiExpression iteratedValue = statement.getIteratedValue();
|
||||
if (iteratedValue == null || iteratedValue.getType() instanceof PsiArrayType || !isInsideEqualsOrHashCode(statement)) {
|
||||
return;
|
||||
}
|
||||
registerError(iteratedValue, iteratedValue, "iterator");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitMethodCallExpression(@NotNull PsiMethodCallExpression expression) {
|
||||
final PsiMethod method = expression.resolveMethod();
|
||||
if (method == null) {
|
||||
return;
|
||||
}
|
||||
List<StandardMethodContract> contracts = JavaMethodContractUtil.getMethodContracts(method);
|
||||
ContractReturnValue contractValue = JavaMethodContractUtil.getNonFailingReturnValue(contracts);
|
||||
if (ContractReturnValue.returnNew().equals(contractValue)) {
|
||||
if (!isInsideEqualsOrHashCode(expression)) {
|
||||
return;
|
||||
}
|
||||
registerMethodCallError(expression, expression);
|
||||
}
|
||||
else if (method.isVarArgs()) {
|
||||
if (!isInsideEqualsOrHashCode(expression)) {
|
||||
return;
|
||||
}
|
||||
registerMethodCallError(expression, expression, "varargs call");
|
||||
}
|
||||
else {
|
||||
if (!"valueOf".equals(method.getName())) {
|
||||
return;
|
||||
}
|
||||
final PsiExpression[] expressions = expression.getArgumentList().getExpressions();
|
||||
if (expressions.length != 1) {
|
||||
return;
|
||||
}
|
||||
final PsiClass aClass = method.getContainingClass();
|
||||
if (aClass == null) {
|
||||
return;
|
||||
}
|
||||
final String qualifiedName = aClass.getQualifiedName();
|
||||
if (CommonClassNames.JAVA_LANG_SHORT.equals(qualifiedName) ||
|
||||
CommonClassNames.JAVA_LANG_INTEGER.equals(qualifiedName) ||
|
||||
CommonClassNames.JAVA_LANG_LONG.equals(qualifiedName) ||
|
||||
CommonClassNames.JAVA_LANG_CHARACTER.equals(qualifiedName)) {
|
||||
if (isAutoBoxingFromCache(expressions[0]) || !isInsideEqualsOrHashCode(expression)) {
|
||||
return;
|
||||
}
|
||||
registerError(expression, expression);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isAutoBoxingFromCache(PsiExpression expression) {
|
||||
final LongRangeSet range = CommonDataflow.getExpressionRange(expression);
|
||||
if (range != null && !range.isEmpty() && range.min() >= -128 && range.max() <= 127) {
|
||||
return true;
|
||||
}
|
||||
final Object value = ExpressionUtils.computeConstantExpression(expression);
|
||||
if (value instanceof Number number) {
|
||||
final int l = number.intValue();
|
||||
if (l >= -128 && l <= 127) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if (value instanceof Character character) {
|
||||
final char c = character.charValue();
|
||||
if (c <= 127) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitArrayInitializerExpression(@NotNull PsiArrayInitializerExpression expression) {
|
||||
if (!(expression.getParent() instanceof PsiVariable)) {
|
||||
// new expressions are already reported.
|
||||
return;
|
||||
}
|
||||
if (!isInsideEqualsOrHashCode(expression)) {
|
||||
return;
|
||||
}
|
||||
registerError(expression, expression);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitPolyadicExpression(@NotNull PsiPolyadicExpression expression) {
|
||||
super.visitPolyadicExpression(expression);
|
||||
if (!ExpressionUtils.hasStringType(expression) || ExpressionUtils.isEvaluatedAtCompileTime(expression)) {
|
||||
return;
|
||||
}
|
||||
if (!isInsideEqualsOrHashCode(expression)) {
|
||||
return;
|
||||
}
|
||||
registerError(expression, expression);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitNewExpression(@NotNull PsiNewExpression expression) {
|
||||
super.visitNewExpression(expression);
|
||||
if (!isInsideEqualsOrHashCode(expression)) {
|
||||
return;
|
||||
}
|
||||
registerNewExpressionError(expression, expression);
|
||||
}
|
||||
|
||||
private static boolean isInsideEqualsOrHashCode(PsiElement element) {
|
||||
final PsiMethod method =
|
||||
PsiTreeUtil.getParentOfType(element, PsiMethod.class, true, PsiAssertStatement.class, PsiThrowStatement.class);
|
||||
if (method == null) {
|
||||
return false;
|
||||
}
|
||||
return MethodUtils.isEquals(method) || MethodUtils.isHashCode(method) ||
|
||||
MethodUtils.isCompareTo(method) || MethodUtils.isComparatorCompare(method);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,539 @@
|
||||
/*
|
||||
* Copyright 2003-2025 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.performance;
|
||||
|
||||
import com.intellij.codeInspection.CleanupLocalInspectionTool;
|
||||
import com.intellij.codeInspection.LocalQuickFix;
|
||||
import com.intellij.codeInspection.ProblemDescriptor;
|
||||
import com.intellij.modcommand.ModCommand;
|
||||
import com.intellij.modcommand.ModCommandQuickFix;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.psi.JavaPsiFacade;
|
||||
import com.intellij.psi.JavaRecursiveElementWalkingVisitor;
|
||||
import com.intellij.psi.JavaResolveResult;
|
||||
import com.intellij.psi.JavaTokenType;
|
||||
import com.intellij.psi.PsiClass;
|
||||
import com.intellij.psi.PsiCodeBlock;
|
||||
import com.intellij.psi.PsiComment;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiExpression;
|
||||
import com.intellij.psi.PsiExpressionStatement;
|
||||
import com.intellij.psi.PsiField;
|
||||
import com.intellij.psi.PsiIfStatement;
|
||||
import com.intellij.psi.PsiLambdaExpression;
|
||||
import com.intellij.psi.PsiMethod;
|
||||
import com.intellij.psi.PsiMethodCallExpression;
|
||||
import com.intellij.psi.PsiModifier;
|
||||
import com.intellij.psi.PsiParameter;
|
||||
import com.intellij.psi.PsiParserFacade;
|
||||
import com.intellij.psi.PsiPolyadicExpression;
|
||||
import com.intellij.psi.PsiQualifiedExpression;
|
||||
import com.intellij.psi.PsiReferenceExpression;
|
||||
import com.intellij.psi.PsiReturnStatement;
|
||||
import com.intellij.psi.PsiStatement;
|
||||
import com.intellij.psi.PsiThisExpression;
|
||||
import com.intellij.psi.PsiTypes;
|
||||
import com.intellij.psi.PsiWhiteSpace;
|
||||
import com.intellij.psi.codeStyle.CodeStyleManager;
|
||||
import com.intellij.psi.codeStyle.JavaCodeStyleManager;
|
||||
import com.intellij.psi.tree.IElementType;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.psi.util.PsiUtil;
|
||||
import com.intellij.util.ArrayUtil;
|
||||
import com.intellij.util.SmartList;
|
||||
import com.intellij.util.graph.CachingSemiGraph;
|
||||
import com.intellij.util.graph.DFSTBuilder;
|
||||
import com.intellij.util.graph.Graph;
|
||||
import com.intellij.util.graph.GraphGenerator;
|
||||
import com.intellij.util.graph.InboundSemiGraph;
|
||||
import com.siyeh.InspectionGadgetsBundle;
|
||||
import com.siyeh.ig.BaseInspection;
|
||||
import com.siyeh.ig.BaseInspectionVisitor;
|
||||
import com.siyeh.ig.psiutils.BoolUtils;
|
||||
import com.siyeh.ig.psiutils.ControlFlowUtils;
|
||||
import com.siyeh.ig.psiutils.MethodUtils;
|
||||
import com.siyeh.ig.psiutils.VariableAccessUtils;
|
||||
import org.jetbrains.annotations.NonNls;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
public final class TailRecursionInspection extends BaseInspection implements CleanupLocalInspectionTool {
|
||||
@Override
|
||||
protected @NotNull String buildErrorString(Object... infos) {
|
||||
return InspectionGadgetsBundle.message("tail.recursion.problem.descriptor");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable LocalQuickFix buildFix(Object... infos) {
|
||||
final PsiMethod containingMethod = (PsiMethod)infos[0];
|
||||
if (!mayBeReplacedByIterativeMethod(containingMethod)) {
|
||||
return null;
|
||||
}
|
||||
return new RemoveTailRecursionFix();
|
||||
}
|
||||
|
||||
private static boolean mayBeReplacedByIterativeMethod(PsiMethod containingMethod) {
|
||||
if (containingMethod.isVarArgs()) {
|
||||
return false;
|
||||
}
|
||||
for (PsiParameter parameter : containingMethod.getParameterList().getParameters()) {
|
||||
if (parameter.hasModifierProperty(PsiModifier.FINAL)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static final class RemoveTailRecursionFix extends ModCommandQuickFix {
|
||||
@Override
|
||||
public @NotNull String getFamilyName() {
|
||||
return InspectionGadgetsBundle.message("tail.recursion.replace.quickfix");
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull ModCommand perform(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
|
||||
PsiElement element = descriptor.getPsiElement();
|
||||
final PsiMethod method = PsiTreeUtil.getParentOfType(element, PsiMethod.class, true, PsiClass.class, PsiLambdaExpression.class);
|
||||
if (method == null) {
|
||||
return ModCommand.nop();
|
||||
}
|
||||
final PsiClass containingClass = method.getContainingClass();
|
||||
if (containingClass == null) {
|
||||
return ModCommand.nop();
|
||||
}
|
||||
final PsiCodeBlock body = method.getBody();
|
||||
if (body == null) {
|
||||
return ModCommand.nop();
|
||||
}
|
||||
final @NonNls StringBuilder builder = new StringBuilder("{");
|
||||
final String thisVariableName;
|
||||
final JavaCodeStyleManager styleManager = JavaCodeStyleManager.getInstance(project);
|
||||
if (methodReturnsContainingClassType(method, containingClass)) {
|
||||
builder.append(containingClass.getName());
|
||||
thisVariableName = styleManager.suggestUniqueVariableName("result", method, false);
|
||||
builder.append(' ').append(thisVariableName).append(" = this;");
|
||||
}
|
||||
else if (methodContainsCallOnOtherInstance(method)) {
|
||||
builder.append(containingClass.getName());
|
||||
thisVariableName = styleManager.suggestUniqueVariableName("other", method, false);
|
||||
builder.append(' ').append(thisVariableName).append(" = this;");
|
||||
}
|
||||
else {
|
||||
thisVariableName = null;
|
||||
}
|
||||
final boolean tailCallIsContainedInLoop;
|
||||
if (ControlFlowUtils.isInLoop(element)) {
|
||||
tailCallIsContainedInLoop = true;
|
||||
builder.append(method.getName()).append(':');
|
||||
}
|
||||
else {
|
||||
tailCallIsContainedInLoop = false;
|
||||
}
|
||||
builder.append("while(true)");
|
||||
final boolean methodMayCompleteNormally = ControlFlowUtils.methodMayCompleteNormally(method);
|
||||
replaceTailCalls(body, method, thisVariableName, tailCallIsContainedInLoop, methodMayCompleteNormally, builder);
|
||||
if (methodMayCompleteNormally) {
|
||||
builder.insert(builder.length() - 1, "return;");
|
||||
}
|
||||
builder.append('}');
|
||||
final PsiCodeBlock block = JavaPsiFacade.getElementFactory(project).createCodeBlockFromText(builder.toString(), method);
|
||||
removeEmptyElse(block);
|
||||
return ModCommand.psiUpdate(body, e -> CodeStyleManager.getInstance(project).reformat(e.replace(block)));
|
||||
}
|
||||
|
||||
private static void removeEmptyElse(PsiElement element) {
|
||||
final List<PsiStatement> emptyElseBranches = new SmartList<>();
|
||||
element.accept(new JavaRecursiveElementWalkingVisitor() {
|
||||
@Override
|
||||
public void visitIfStatement(@NotNull PsiIfStatement statement) {
|
||||
super.visitIfStatement(statement);
|
||||
final PsiStatement elseBranch = statement.getElseBranch();
|
||||
if (ControlFlowUtils.isEmpty(elseBranch, false, true)) {
|
||||
emptyElseBranches.add(elseBranch);
|
||||
}
|
||||
}
|
||||
});
|
||||
for (PsiStatement statement : emptyElseBranches) {
|
||||
final List<PsiComment> comments = new ArrayList<>(PsiTreeUtil.collectElementsOfType(statement, PsiComment.class));
|
||||
final PsiParserFacade parserFacade = PsiParserFacade.getInstance(statement.getProject());
|
||||
final PsiElement parent = statement.getParent();
|
||||
for (PsiComment comment : comments.reversed()) {
|
||||
parent.addAfter(comment, statement);
|
||||
// newline followed by space convinces formatter to indent line
|
||||
parent.addAfter(parserFacade.createWhiteSpaceFromText(isAtStartOfLine(comment) ? "\n" : "\n "), statement);
|
||||
}
|
||||
statement.delete();
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isAtStartOfLine(PsiElement element) {
|
||||
final PsiElement prev = element.getPrevSibling();
|
||||
if (!(prev instanceof PsiWhiteSpace)) {
|
||||
return false;
|
||||
}
|
||||
return prev.getText().endsWith("\n");
|
||||
}
|
||||
|
||||
private static boolean methodReturnsContainingClassType(PsiMethod method, PsiClass containingClass) {
|
||||
if (containingClass == null || method.hasModifierProperty(PsiModifier.STATIC)) {
|
||||
return false;
|
||||
}
|
||||
final PsiClass aClass = PsiUtil.resolveClassInClassTypeOnly(method.getReturnType());
|
||||
return containingClass.equals(aClass);
|
||||
}
|
||||
|
||||
private static boolean methodContainsCallOnOtherInstance(PsiMethod method) {
|
||||
if (method.hasModifierProperty(PsiModifier.STATIC)) {
|
||||
return false;
|
||||
}
|
||||
final PsiCodeBlock body = method.getBody();
|
||||
if (body == null) {
|
||||
return false;
|
||||
}
|
||||
final PsiClass aClass = method.getContainingClass();
|
||||
final MethodContainsCallOnOtherInstanceVisitor visitor = new MethodContainsCallOnOtherInstanceVisitor(aClass);
|
||||
body.accept(visitor);
|
||||
return visitor.containsCallOnOtherInstance();
|
||||
}
|
||||
|
||||
private static class MethodContainsCallOnOtherInstanceVisitor extends JavaRecursiveElementWalkingVisitor {
|
||||
|
||||
private boolean containsCallOnOtherInstance;
|
||||
private final PsiClass aClass;
|
||||
|
||||
MethodContainsCallOnOtherInstanceVisitor(PsiClass aClass) {
|
||||
this.aClass = aClass;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitMethodCallExpression(@NotNull PsiMethodCallExpression expression) {
|
||||
if (containsCallOnOtherInstance) {
|
||||
return;
|
||||
}
|
||||
super.visitMethodCallExpression(expression);
|
||||
final PsiReferenceExpression methodExpression = expression.getMethodExpression();
|
||||
final PsiExpression qualifier = methodExpression.getQualifierExpression();
|
||||
if (qualifier == null || qualifier instanceof PsiThisExpression) {
|
||||
return;
|
||||
}
|
||||
final PsiMethod method = expression.resolveMethod();
|
||||
if (method == null) {
|
||||
return;
|
||||
}
|
||||
final PsiClass containingClass = method.getContainingClass();
|
||||
if (aClass.equals(containingClass)) {
|
||||
containsCallOnOtherInstance = true;
|
||||
}
|
||||
}
|
||||
|
||||
boolean containsCallOnOtherInstance() {
|
||||
return containsCallOnOtherInstance;
|
||||
}
|
||||
}
|
||||
|
||||
private static void replaceTailCalls(PsiElement element,
|
||||
PsiMethod method,
|
||||
@Nullable String thisVariableName,
|
||||
boolean tailCallIsContainedInLoop,
|
||||
boolean isReturnAtTheEndOfWhileLoop,
|
||||
@NonNls StringBuilder out) {
|
||||
PsiMethodCallExpression tailCall;
|
||||
if (isImplicitCallOnThis(element, method)) {
|
||||
if (thisVariableName != null) {
|
||||
out.append(thisVariableName).append('.');
|
||||
}
|
||||
out.append(element.getText());
|
||||
}
|
||||
else if (element instanceof PsiQualifiedExpression) {
|
||||
out.append(thisVariableName == null ? element.getText() : thisVariableName);
|
||||
}
|
||||
else if ((tailCall = getTailCall(element, method)) != null) {
|
||||
assert element instanceof PsiStatement;
|
||||
final PsiExpression[] arguments = tailCall.getArgumentList().getExpressions();
|
||||
final PsiParameter[] parameters = method.getParameterList().getParameters();
|
||||
final boolean isInBlock = element.getParent() instanceof PsiCodeBlock;
|
||||
if (!isInBlock) {
|
||||
out.append('{');
|
||||
}
|
||||
else {
|
||||
// remove tabs and spaces at the end
|
||||
int index = out.length() - 1;
|
||||
char c = out.charAt(index);
|
||||
while (c == ' ' || c == '\t') c = out.charAt(--index);
|
||||
out.delete(index + 1, out.length());
|
||||
}
|
||||
for (PsiComment comment : PsiTreeUtil.findChildrenOfType(element, PsiComment.class)) {
|
||||
if (!isAtStartOfLine(comment)) out.append(' ');
|
||||
out.append(comment.getText()).append('\n');
|
||||
}
|
||||
PsiExpression current = tailCall;
|
||||
List<String> conditions = new ArrayList<>();
|
||||
while (true) {
|
||||
if (!(PsiUtil.skipParenthesizedExprUp(current.getParent()) instanceof PsiPolyadicExpression parent)) {
|
||||
break;
|
||||
}
|
||||
PsiExpression[] operands = parent.getOperands();
|
||||
if (operands.length < 2) break;
|
||||
String condition = parent.getText().substring(0, operands[operands.length - 2].getTextRangeInParent().getEndOffset());
|
||||
boolean returnTrue = parent.getOperationTokenType() == JavaTokenType.OROR;
|
||||
if (!returnTrue) {
|
||||
PsiExpression cond = JavaPsiFacade.getElementFactory(method.getProject()).createExpressionFromText(condition, parent);
|
||||
condition = BoolUtils.getNegatedExpressionText(cond);
|
||||
}
|
||||
String ifStatement = "if(" + condition + ") return " + returnTrue + ";\n";
|
||||
conditions.add(0, ifStatement);
|
||||
current = parent;
|
||||
}
|
||||
conditions.forEach(out::append);
|
||||
final Graph<Integer> graph = buildGraph(parameters, arguments);
|
||||
// When replacing recursion with iteration, new values are assigned to the parameters,
|
||||
// instead of calling the method with the new values. Care needs to be taken to not clobber
|
||||
// the value of a parameter which is used later (in some expression assigned to a different
|
||||
// parameter). To achieve this, a simple graph of the dependencies between the parameters is
|
||||
// built and sorted. If the graph is a directed acyclic graph, the assignments
|
||||
// are ordered in such a way that making a defensive copy is unnecessary (topological
|
||||
// ordering). If the graph has a cycle, a copy of at least one parameter needs
|
||||
// to be made before assigning a new value.
|
||||
final DFSTBuilder<Integer> builder = new DFSTBuilder<>(graph);
|
||||
final List<Integer> sortedNodes = builder.getSortedNodes();
|
||||
final Set<Integer> seen = new HashSet<>();
|
||||
final Map<PsiElement, String> replacements = new HashMap<>();
|
||||
for (Integer index : sortedNodes) {
|
||||
final PsiParameter parameter = parameters[index];
|
||||
final String parameterName = parameter.getName();
|
||||
final PsiExpression argument = PsiUtil.skipParenthesizedExprDown(arguments[index]);
|
||||
assert argument != null;
|
||||
if (argument instanceof PsiReferenceExpression referenceExpression) {
|
||||
if (parameter.equals(referenceExpression.resolve())) {
|
||||
// parameter keeps the same value
|
||||
continue;
|
||||
}
|
||||
}
|
||||
final Iterator<Integer> dependants = graph.getIn(index); // parameters which depend on parameter 'index'
|
||||
boolean copy = false;
|
||||
while (dependants.hasNext()) {
|
||||
if (!seen.contains(dependants.next())) {
|
||||
// the current parameter which depends on the value of parameter 'index' has not yet received its value (cycle)
|
||||
// if 'dependants' was some collection instead of an iterator this would have been a nice containsAll expression
|
||||
copy = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (copy) {
|
||||
final String variableName =
|
||||
JavaCodeStyleManager.getInstance(method.getProject()).suggestUniqueVariableName(parameterName, element, false);
|
||||
out.append(parameter.getType().getCanonicalText()).append(' ').append(variableName).append('=');
|
||||
out.append(parameterName).append(';');
|
||||
replacements.put(parameter, variableName);
|
||||
}
|
||||
out.append(parameterName).append('=');
|
||||
buildText(argument, replacements, out);
|
||||
out.append(';');
|
||||
seen.add(index);
|
||||
}
|
||||
if (thisVariableName != null) {
|
||||
final PsiReferenceExpression methodExpression = tailCall.getMethodExpression();
|
||||
final PsiExpression qualifier = methodExpression.getQualifierExpression();
|
||||
if (qualifier != null) {
|
||||
out.append(thisVariableName).append('=');
|
||||
replaceTailCalls(qualifier, method, thisVariableName, tailCallIsContainedInLoop, isReturnAtTheEndOfWhileLoop, out);
|
||||
out.append(';');
|
||||
}
|
||||
}
|
||||
final PsiCodeBlock body = method.getBody();
|
||||
assert body != null;
|
||||
if ((element instanceof PsiReturnStatement && ControlFlowUtils.blockCompletesWithStatement(body, (PsiStatement)element)) ||
|
||||
(element instanceof PsiExpressionStatement && (!isReturnAtTheEndOfWhileLoop || isBeforeVoidReturn(element, method)))) {
|
||||
//don't do anything, as the continue statement is unnecessary
|
||||
}
|
||||
else if (tailCallIsContainedInLoop) {
|
||||
out.append("continue ").append(method.getName()).append(';');
|
||||
}
|
||||
else {
|
||||
out.append("continue;");
|
||||
}
|
||||
if (!isInBlock) {
|
||||
out.append('}');
|
||||
}
|
||||
}
|
||||
else {
|
||||
final PsiCodeBlock body = method.getBody();
|
||||
assert body != null;
|
||||
if (isVoidReturn(element)) {
|
||||
final PsiExpressionStatement prevElement =
|
||||
PsiTreeUtil.skipWhitespacesAndCommentsBackward(element) instanceof PsiExpressionStatement s ? s : null;
|
||||
tailCall = getTailCall(prevElement, method);
|
||||
if (tailCall != null) {
|
||||
out.append("continue;");
|
||||
return;
|
||||
}
|
||||
}
|
||||
final PsiElement[] children = element.getChildren();
|
||||
if (children.length == 0) {
|
||||
out.append(element.getText());
|
||||
}
|
||||
else {
|
||||
for (PsiElement child : children) {
|
||||
replaceTailCalls(child, method, thisVariableName, tailCallIsContainedInLoop, isReturnAtTheEndOfWhileLoop, out);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void buildText(PsiElement element, Map<PsiElement, String> replacements, StringBuilder out) {
|
||||
if (element instanceof PsiReferenceExpression referenceExpression) {
|
||||
final PsiElement target = referenceExpression.resolve();
|
||||
final String replacement = replacements.get(target);
|
||||
out.append(replacement != null ? replacement : element.getText());
|
||||
return;
|
||||
}
|
||||
final PsiElement[] children = element.getChildren();
|
||||
if (children.length > 0) {
|
||||
for (PsiElement child : children) {
|
||||
buildText(child, replacements, out);
|
||||
}
|
||||
}
|
||||
else {
|
||||
out.append(element.getText());
|
||||
}
|
||||
}
|
||||
|
||||
private static Graph<Integer> buildGraph(PsiParameter[] parameters, PsiExpression[] arguments) {
|
||||
final InboundSemiGraph<Integer> graph = new InboundSemiGraph<>() {
|
||||
@Override
|
||||
public @NotNull Collection<Integer> getNodes() {
|
||||
final List<Integer> result = new ArrayList<>();
|
||||
for (int i = 0; i < parameters.length; i++) {
|
||||
result.add(i);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Iterator<Integer> getIn(Integer n) {
|
||||
final List<Integer> result = new ArrayList<>();
|
||||
final PsiParameter target = parameters[n];
|
||||
for (int i = 0, length = arguments.length; i < length; i++) {
|
||||
if (i == n) continue;
|
||||
if (VariableAccessUtils.variableIsUsed(target, arguments[i])) {
|
||||
result.add(i);
|
||||
}
|
||||
}
|
||||
return result.iterator();
|
||||
}
|
||||
};
|
||||
return GraphGenerator.generate(CachingSemiGraph.cache(graph));
|
||||
}
|
||||
|
||||
private static boolean isImplicitCallOnThis(PsiElement element, PsiMethod containingMethod) {
|
||||
if (containingMethod.hasModifierProperty(PsiModifier.STATIC)) {
|
||||
return false;
|
||||
}
|
||||
if (element instanceof PsiMethodCallExpression methodCallExpression) {
|
||||
final PsiReferenceExpression methodExpression = methodCallExpression.getMethodExpression();
|
||||
final PsiExpression qualifierExpression = methodExpression.getQualifierExpression();
|
||||
return qualifierExpression == null;
|
||||
}
|
||||
else if (element instanceof PsiReferenceExpression referenceExpression) {
|
||||
final PsiElement parent = referenceExpression.getParent();
|
||||
if (parent instanceof PsiMethodCallExpression) {
|
||||
return false;
|
||||
}
|
||||
final PsiExpression qualifier = referenceExpression.getQualifierExpression();
|
||||
if (qualifier != null) {
|
||||
return false;
|
||||
}
|
||||
final PsiElement target = referenceExpression.resolve();
|
||||
return target instanceof PsiField;
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull BaseInspectionVisitor buildVisitor() {
|
||||
return new TailRecursionVisitor();
|
||||
}
|
||||
|
||||
private static class TailRecursionVisitor extends BaseInspectionVisitor {
|
||||
@Override
|
||||
public void visitStatement(@NotNull PsiStatement statement) {
|
||||
super.visitStatement(statement);
|
||||
final PsiMethod containingMethod =
|
||||
PsiTreeUtil.getParentOfType(statement, PsiMethod.class, true, PsiClass.class, PsiLambdaExpression.class);
|
||||
if (containingMethod == null) {
|
||||
return;
|
||||
}
|
||||
final PsiMethodCallExpression tailCall = getTailCall(statement, containingMethod);
|
||||
if (tailCall == null) {
|
||||
return;
|
||||
}
|
||||
final PsiReferenceExpression methodExpression = tailCall.getMethodExpression();
|
||||
final PsiExpression qualifier = PsiUtil.skipParenthesizedExprDown(methodExpression.getQualifierExpression());
|
||||
if (qualifier != null && !(qualifier instanceof PsiThisExpression) && MethodUtils.isOverridden(containingMethod)) {
|
||||
return;
|
||||
}
|
||||
registerMethodCallError(tailCall, containingMethod);
|
||||
}
|
||||
}
|
||||
|
||||
private static @Nullable PsiMethodCallExpression getTailCall(@Nullable PsiElement element, @NotNull PsiMethod method) {
|
||||
PsiMethodCallExpression tailCall = null;
|
||||
if (element instanceof PsiReturnStatement returnStatement) {
|
||||
PsiExpression returnValue = PsiUtil.skipParenthesizedExprDown(returnStatement.getReturnValue());
|
||||
while (returnValue instanceof PsiPolyadicExpression polyadic) {
|
||||
returnValue = null;
|
||||
IElementType tokenType = polyadic.getOperationTokenType();
|
||||
if (tokenType == JavaTokenType.ANDAND || tokenType == JavaTokenType.OROR) {
|
||||
PsiExpression[] operands = polyadic.getOperands();
|
||||
if (operands.length >= 2) {
|
||||
returnValue = PsiUtil.skipParenthesizedExprDown(ArrayUtil.getLastElement(operands));
|
||||
}
|
||||
}
|
||||
}
|
||||
tailCall = returnValue instanceof PsiMethodCallExpression call ? call : null;
|
||||
}
|
||||
else if (element instanceof PsiExpressionStatement statement &&
|
||||
(ControlFlowUtils.blockCompletesWithStatement(Objects.requireNonNull(method.getBody()), statement) ||
|
||||
isBeforeVoidReturn(element, method))) {
|
||||
tailCall = statement.getExpression() instanceof PsiMethodCallExpression e ? e : null;
|
||||
}
|
||||
if (tailCall == null) return null;
|
||||
final JavaResolveResult resolveResult = tailCall.resolveMethodGenerics();
|
||||
return resolveResult.isValidResult() && method.equals(resolveResult.getElement()) ? tailCall : null;
|
||||
}
|
||||
|
||||
private static boolean isBeforeVoidReturn(PsiElement element, PsiMethod method) {
|
||||
return PsiTreeUtil.skipWhitespacesAndCommentsForward(element) instanceof PsiReturnStatement statement
|
||||
&& isVoidReturn(statement) && PsiTypes.voidType().equals(method.getReturnType());
|
||||
}
|
||||
|
||||
private static boolean isVoidReturn(PsiElement element) {
|
||||
return element instanceof PsiReturnStatement statement && statement.getReturnValue() == null;
|
||||
}
|
||||
}
|
||||
+211
@@ -0,0 +1,211 @@
|
||||
// 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.performance;
|
||||
|
||||
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.PsiArrayType;
|
||||
import com.intellij.psi.PsiDeclarationStatement;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiElementFactory;
|
||||
import com.intellij.psi.PsiExpression;
|
||||
import com.intellij.psi.PsiExpressionList;
|
||||
import com.intellij.psi.PsiField;
|
||||
import com.intellij.psi.PsiIfStatement;
|
||||
import com.intellij.psi.PsiLoopStatement;
|
||||
import com.intellij.psi.PsiMethodCallExpression;
|
||||
import com.intellij.psi.PsiNewExpression;
|
||||
import com.intellij.psi.PsiReferenceExpression;
|
||||
import com.intellij.psi.PsiStatement;
|
||||
import com.intellij.psi.PsiType;
|
||||
import com.intellij.psi.PsiVariable;
|
||||
import com.intellij.psi.codeStyle.VariableKind;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.psi.util.PsiUtil;
|
||||
import com.intellij.util.ObjectUtils;
|
||||
import com.siyeh.InspectionGadgetsBundle;
|
||||
import com.siyeh.ig.BaseInspection;
|
||||
import com.siyeh.ig.BaseInspectionVisitor;
|
||||
import com.siyeh.ig.callMatcher.CallMatcher;
|
||||
import com.siyeh.ig.psiutils.CollectionUtils;
|
||||
import com.siyeh.ig.psiutils.CommentTracker;
|
||||
import com.siyeh.ig.psiutils.ConstructionUtils;
|
||||
import com.siyeh.ig.psiutils.ExpressionUtils;
|
||||
import com.siyeh.ig.psiutils.VariableNameGenerator;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
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.dropdown;
|
||||
import static com.intellij.codeInspection.options.OptPane.pane;
|
||||
|
||||
public final class ToArrayCallWithZeroLengthArrayArgumentInspection extends BaseInspection {
|
||||
private static final CallMatcher COLLECTION_TO_ARRAY =
|
||||
CallMatcher.instanceCall(CommonClassNames.JAVA_UTIL_COLLECTION, "toArray").parameterCount(1);
|
||||
|
||||
private static final PreferEmptyArray DEFAULT_MODE = PreferEmptyArray.ALWAYS;
|
||||
|
||||
public enum PreferEmptyArray {
|
||||
ALWAYS {
|
||||
@Override @Nls String getMessage() { return InspectionGadgetsBundle.message("prefer.empty.array.options.mode.always"); }
|
||||
},
|
||||
BY_LEVEL {
|
||||
@Override @Nls String getMessage() { return InspectionGadgetsBundle.message("prefer.empty.array.options.mode.by.level"); }
|
||||
},
|
||||
NEVER {
|
||||
@Override @Nls String getMessage() { return InspectionGadgetsBundle.message("prefer.empty.array.options.mode.always.never"); }
|
||||
};
|
||||
|
||||
abstract @Nls String getMessage();
|
||||
|
||||
boolean isEmptyPreferred(PsiExpression expression) {
|
||||
return switch (this) {
|
||||
case ALWAYS -> true;
|
||||
case NEVER -> false;
|
||||
default -> PsiUtil.isLanguageLevel7OrHigher(expression);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("PublicField") public @NotNull PreferEmptyArray myMode = DEFAULT_MODE;
|
||||
|
||||
@Override
|
||||
public @NotNull OptPane getOptionsPane() {
|
||||
return pane(dropdown("myMode", InspectionGadgetsBundle.message("prefer.empty.array.options.title"),
|
||||
PreferEmptyArray.class, PreferEmptyArray::getMessage));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable LocalQuickFix buildFix(Object... infos) {
|
||||
final PsiExpression argument = (PsiExpression)infos[1];
|
||||
return new ToArrayCallWithZeroLengthArrayArgumentFix(myMode.isEmptyPreferred(argument));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NotNull String buildErrorString(Object... infos) {
|
||||
final PsiExpression argument = (PsiExpression)infos[1];
|
||||
return myMode.isEmptyPreferred(argument) ?
|
||||
InspectionGadgetsBundle.message("to.array.call.style.problem.descriptor.presized", argument.getText()) :
|
||||
InspectionGadgetsBundle.message("to.array.call.style.problem.descriptor.zero", argument.getText());
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull BaseInspectionVisitor buildVisitor() {
|
||||
return new BaseInspectionVisitor() {
|
||||
@Override
|
||||
public void visitMethodCallExpression(@NotNull PsiMethodCallExpression call) {
|
||||
if (!COLLECTION_TO_ARRAY.test(call)) return;
|
||||
final PsiExpression argument = call.getArgumentList().getExpressions()[0];
|
||||
final PsiType type = argument.getType();
|
||||
if (!(type instanceof PsiArrayType)) return;
|
||||
if (type.getArrayDimensions() != 1) return;
|
||||
|
||||
boolean wrongArray =
|
||||
myMode.isEmptyPreferred(argument)
|
||||
? isPresizedArray(argument, call.getMethodExpression().getQualifierExpression())
|
||||
: isEmptyArray(argument);
|
||||
if (wrongArray) {
|
||||
registerMethodCallError(call, call, argument);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static boolean isEmptyArray(@Nullable PsiExpression argument) {
|
||||
if (argument instanceof PsiReferenceExpression) {
|
||||
final PsiElement element = ((PsiReferenceExpression)argument).resolve();
|
||||
if (!(element instanceof PsiField)) return false;
|
||||
return CollectionUtils.isConstantEmptyArray((PsiField)element);
|
||||
}
|
||||
return ConstructionUtils.isEmptyArrayInitializer(argument);
|
||||
}
|
||||
|
||||
@Contract("_, null -> false")
|
||||
private static boolean isPresizedArray(@Nullable PsiExpression argument, @Nullable PsiExpression qualifier) {
|
||||
if (qualifier == null) return false;
|
||||
PsiNewExpression newExpression = ObjectUtils.tryCast(PsiUtil.skipParenthesizedExprDown(argument), PsiNewExpression.class);
|
||||
if (newExpression == null) return false;
|
||||
PsiExpression[] dimensions = newExpression.getArrayDimensions();
|
||||
if (dimensions.length != 1) return false;
|
||||
return CollectionUtils.isCollectionOrMapSize(dimensions[0], qualifier);
|
||||
}
|
||||
|
||||
private static class ToArrayCallWithZeroLengthArrayArgumentFix extends PsiUpdateModCommandQuickFix {
|
||||
private final boolean myEmptyPreferred;
|
||||
|
||||
ToArrayCallWithZeroLengthArrayArgumentFix(boolean emptyPreferred) {
|
||||
myEmptyPreferred = emptyPreferred;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nls @NotNull String getName() {
|
||||
return myEmptyPreferred ?
|
||||
InspectionGadgetsBundle.message("to.array.call.style.quickfix.make.zero") :
|
||||
InspectionGadgetsBundle.message("to.array.call.style.quickfix.make.presized");
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String getFamilyName() {
|
||||
return InspectionGadgetsBundle.message("to.array.call.style.quickfix.family.name");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void applyFix(@NotNull Project project, @NotNull PsiElement element, @NotNull ModPsiUpdater updater) {
|
||||
final PsiElement parent = element.getParent();
|
||||
final PsiElement grandParent = parent.getParent();
|
||||
if (!(grandParent instanceof PsiMethodCallExpression methodCallExpression)) return;
|
||||
final PsiReferenceExpression methodExpression = methodCallExpression.getMethodExpression();
|
||||
final PsiExpression qualifier = methodExpression.getQualifierExpression();
|
||||
final PsiElementFactory factory = JavaPsiFacade.getElementFactory(project);
|
||||
final PsiExpressionList argumentList = methodCallExpression.getArgumentList();
|
||||
final PsiExpression[] arguments = argumentList.getExpressions();
|
||||
if (arguments.length != 1) return;
|
||||
final PsiExpression argument = arguments[0];
|
||||
if (qualifier == null) return;
|
||||
|
||||
final String collectionText = qualifier.getText();
|
||||
final PsiType type = argument.getType();
|
||||
if (type == null) return;
|
||||
final PsiType componentType = type.getDeepComponentType();
|
||||
final String typeText = componentType.getCanonicalText();
|
||||
|
||||
|
||||
if (myEmptyPreferred || ExpressionUtils.isSafelyRecomputableExpression(qualifier)) {
|
||||
CommentTracker ct = new CommentTracker();
|
||||
String sizeClause = myEmptyPreferred ? "0" : collectionText + ".size()";
|
||||
final @NonNls String replacementText = "new " + typeText + '[' + sizeClause + "]";
|
||||
ct.replaceAndRestoreComments(argument, replacementText);
|
||||
return;
|
||||
}
|
||||
// need to introduce a variable to prevent calling a method twice
|
||||
PsiStatement statement = PsiTreeUtil.getParentOfType(methodCallExpression, PsiStatement.class);
|
||||
if (statement == null) return;
|
||||
final PsiType qualifierType = qualifier.getType();
|
||||
if (qualifierType == null) return;
|
||||
List<String> names = new VariableNameGenerator(statement, VariableKind.LOCAL_VARIABLE)
|
||||
.byExpression(qualifier).byType(qualifierType).generateAll(true);
|
||||
String name = names.get(0);
|
||||
PsiDeclarationStatement declarationStatement = factory.createVariableDeclarationStatement(name, qualifierType, qualifier);
|
||||
PsiElement statementParent = statement.getParent();
|
||||
while (statementParent instanceof PsiLoopStatement || statementParent instanceof PsiIfStatement) {
|
||||
statement = (PsiStatement)statementParent;
|
||||
statementParent = statement.getParent();
|
||||
}
|
||||
final String toArrayText = name + ".toArray(new " + typeText + "[" + name + ".size()])";
|
||||
PsiMethodCallExpression newMethodCallExpression =
|
||||
(PsiMethodCallExpression)factory.createExpressionFromText(toArrayText, methodCallExpression);
|
||||
declarationStatement = (PsiDeclarationStatement)statementParent.addBefore(declarationStatement, statement);
|
||||
methodCallExpression.replace(newMethodCallExpression);
|
||||
final PsiVariable variable = (PsiVariable)declarationStatement.getDeclaredElements()[0];
|
||||
updater.rename(variable, names);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user