cleanup IDEA-382376: move com.siyeh.ig.performance package from java-impl to java-impl-inspections

GitOrigin-RevId: 4c868558a579c35517733bb6ffdec476cfb212ba
This commit is contained in:
Bartek Pacia
2026-02-12 17:26:40 +00:00
committed by intellij-monorepo-bot
parent cf426f0cfc
commit 5f8c225c21
11 changed files with 0 additions and 0 deletions
@@ -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);
}
}
}
@@ -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"))));
}
}
@@ -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);
}
}
}
@@ -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);
}
}
}
@@ -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();
}
}
}
@@ -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;
}
}
}
@@ -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);
}
}
}
@@ -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;
}
}
@@ -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);
}
}
}