mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-01-07 05:09:37 +07:00
[codeInsight] IDEA-219640 Inspection to detect some pointless String.substring
This patch fixes the notes from the review, it includes: - restoring annotation.naming.convention.display.name - using com.intellij.openapi.util.Pair instead of custom private objects - using EquivalenceChecker.expressionsMatch in order to extract difference between two PsiBinaryExpression - using BoolUtils.findNegation to properly deduce the sign of the equals clause - restoring comments while replacing substring with charAt - properly handling special characters (e.g. '\\', '\n', '\"', etc.) in equals clause when it is converted to charAt + "==" - eliminating the imperative form of a message for the inspection of substring + equals Signed-off-by: Nikita Eshkeev <nikita.eshkeev@jetbrains.com> GitOrigin-RevId: be1dfd53a6af10979485cd172af39653faafb744
This commit is contained in:
committed by
intellij-monorepo-bot
parent
36a6a3ab65
commit
0aa2e160b0
@@ -142,6 +142,29 @@ public class PsiLiteralUtil {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a string that contains a character (e.g. ""\n"" or ""\\"", etc.) to a string (e.g. "'\n'" or "'\\'", etc.)
|
||||
*
|
||||
* @param text a string to convert
|
||||
* @return the converted string
|
||||
*/
|
||||
@NotNull
|
||||
public static String charLiteralForCharString(@NotNull final String text) {
|
||||
final int length = text.length();
|
||||
final String character = text.substring(1, length - 1);
|
||||
final String charLiteral;
|
||||
if ("'".equals(character)) {
|
||||
charLiteral = "'\\''";
|
||||
}
|
||||
else if ("\\\"".equals(character)) {
|
||||
charLiteral = "'\"'";
|
||||
}
|
||||
else {
|
||||
charLiteral = '\'' + character + '\'';
|
||||
}
|
||||
return charLiteral;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if given literal expression is invalid and reusing its text representation
|
||||
* in refactorings/quick-fixes may result in parse errors.
|
||||
|
||||
@@ -9,6 +9,16 @@ class Foo {
|
||||
sb.append(args[0].charAt(2));
|
||||
sb.append(args[0].charAt(i));
|
||||
|
||||
sb.append(args[0].substring(i - 3, i - 2));
|
||||
sb.append(args[0].substring(i - 3, 2 - i));
|
||||
sb.append(args[0].substring(3 - i, i - 2));
|
||||
sb.append(args[0].substring(3 - i, 2 - i));
|
||||
|
||||
sb.append(args[0].substring(2 - i, 4 - i));
|
||||
sb.append(args[0].substring(i - 2, i - 4));
|
||||
|
||||
sb.append(args[0].charAt(i + i));
|
||||
|
||||
sb.append(args[0].charAt(i + 2));
|
||||
sb.append(args[0].charAt(2 + i));
|
||||
sb.append(args[0].charAt(i + 2));
|
||||
@@ -17,17 +27,14 @@ class Foo {
|
||||
sb.append(args[0].substring(2 + i, 4 + i));
|
||||
sb.append(args[0].substring(i + 2, i + 4));
|
||||
|
||||
sb.append(new /*1
|
||||
|
||||
*/String(/* 2 */"foo")/*3 */./* 4*/charAt(i/*5\n*/+2 /*\n\r6 */))
|
||||
|
||||
String s1 = "xxx" + args[0].substring(3, 5);
|
||||
String s2 = "xxx" + args[0].charAt(3);
|
||||
String s3 = args[0].charAt(2) + "xxx";
|
||||
|
||||
boolean value = args[0].charAt(4) == '_';
|
||||
|
||||
if(args[0].charAt(4) == '_') { }
|
||||
if(args[0].charAt(4) != '_') { }
|
||||
if(args[0].charAt(4) == '_') { }
|
||||
if(args[0].charAt(4) != '_') { }
|
||||
|
||||
System.out.print(args[0].charAt(2));
|
||||
System.out.println(args[0].charAt(2));
|
||||
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
// "Fix all 'Redundant String operation' problems in file" "true"
|
||||
class Foo {
|
||||
public static void main(String[] args) {
|
||||
|
||||
boolean value = args[0].charAt(4) == '_';
|
||||
|
||||
if(args[0].charAt(4) == '_') { }
|
||||
if(args[0].charAt(4) != '_') { }
|
||||
if(args[0].charAt(4) == '_') { }
|
||||
if(args[0].charAt(4) != '_') { }
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,16 @@ class Foo {
|
||||
sb.append(args[0].substring(2, 3));
|
||||
sb.append(args[0].substring(i, i + 1));
|
||||
|
||||
sb.append(args[0].substring(i - 3, i - 2));
|
||||
sb.append(args[0].substring(i - 3, 2 - i));
|
||||
sb.append(args[0].substring(3 - i, i - 2));
|
||||
sb.append(args[0].substring(3 - i, 2 - i));
|
||||
|
||||
sb.append(args[0].substring(2 - i, 4 - i));
|
||||
sb.append(args[0].substring(i - 2, i - 4));
|
||||
|
||||
sb.append(args[0].substring(i + i, i + (i + 1)));
|
||||
|
||||
sb.append(args[0].substring(i + 2, i + 3));
|
||||
sb.append(args[0].substring(2 + i, i + 3));
|
||||
sb.append(args[0].substring(i + 2, 3 + i));
|
||||
@@ -17,17 +27,14 @@ class Foo {
|
||||
sb.append(args[0].substring(2 + i, 4 + i));
|
||||
sb.append(args[0].substring(i + 2, i + 4));
|
||||
|
||||
sb.append(new /*1
|
||||
|
||||
*/String(/* 2 */"foo")/*3 */./* 4*/substring(i/*5\n*/+2, i/*\n\r6 */+3))
|
||||
|
||||
String s1 = "xxx" + args[0].substring(3, 5);
|
||||
String s2 = "xxx" + args[0].substring(3, 4);
|
||||
String s3 = args[0].substring(2, 3) + "xxx";
|
||||
|
||||
boolean value = args[0].substring(4, 5).equals("_");
|
||||
|
||||
if(args[0].substring(4, 5).equals("_")) { }
|
||||
if(!args[0].substring(4, 5).equals("_")) { }
|
||||
if(!!args[0].substring(4, 5).equals("_")) { }
|
||||
if(!!!!!args[0].substring(4, 5).equals("_")) { }
|
||||
|
||||
System.out.print(args[0].substring(2, 3));
|
||||
System.out.println(args[0].substring(2, 3));
|
||||
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
// "Fix all 'Redundant String operation' problems in file" "true"
|
||||
class Foo {
|
||||
public static void main(String[] args) {
|
||||
|
||||
boolean value = args[0].substring(4, 5).equals("_");
|
||||
|
||||
if(args[0].sub<caret>string(4, 5).equals("_")) { }
|
||||
if(!args[0].substring(4, 5).equals("_")) { }
|
||||
if(!!args[0].substring(4, 5).equals("_")) { }
|
||||
if(!!!!!args[0].substring(4, 5).equals("_")) { }
|
||||
}
|
||||
}
|
||||
@@ -609,6 +609,7 @@ continue.statement.with.label.display.name='continue' statement with label
|
||||
class.loader.instantiation.display.name=ClassLoader instantiation
|
||||
return.from.finally.block.display.name='return' inside 'finally' block
|
||||
unnecessary.boxing.display.name=Unnecessary boxing
|
||||
annotation.naming.convention.display.name=Annotation naming convention
|
||||
annotation.naming.convention.element.description=Annotation
|
||||
checked.exception.class.display.name=Checked exception class
|
||||
switch.statement.with.confusing.declaration.display.name=Local variable used and declared in different 'switch' branches
|
||||
@@ -2209,6 +2210,7 @@ inspection.redundant.string.constructor.message=<code>#ref</code> is redundant #
|
||||
inspection.redundant.string.replace.with.arg.fix.name=Replace with argument
|
||||
inspection.redundant.string.replace.with.empty.fix.name=Replace with empty string
|
||||
inspection.redundant.string.option.do.not.report.string.constructors=Do not report String constructors
|
||||
inspection.x.call.can.be.replaced.with.y=The ''{0}'' call can be replaced with ''{1}''
|
||||
|
||||
inspection.type.may.be.weakened.display.name=Type may be weakened
|
||||
inspection.type.may.be.weakened.problem.descriptor=Type of variable <code>#ref</code> may be weakened to {0} #loc
|
||||
@@ -2367,7 +2369,6 @@ create.missing.switch.branches.fix.family.name=Create enum switch branches
|
||||
unnecessary.fully.qualified.name.fix.family.name=Replace fully qualified name
|
||||
return.of.collection.field.fix.family.name=Make return collection 'unmodifiable'
|
||||
remove.redundant.substring.fix.family.name=Remove redundant 'substring()' call
|
||||
remove.redundant.substring.to.char.at.fix.family.name=Replace 'substring()' to 'charAt()'
|
||||
remove.redundant.substring.fix.text=Use ''{0}'' and remove redundant ''substring()'' call
|
||||
make.class.final.fix.family.name=Make class final
|
||||
side.effects.method.ref.to.lambda.fix.family.name={0} (side effects)
|
||||
|
||||
@@ -7,6 +7,7 @@ import com.intellij.codeInsight.NullableNotNullManager;
|
||||
import com.intellij.codeInspection.dataFlow.ContractReturnValue;
|
||||
import com.intellij.codeInspection.dataFlow.JavaMethodContractUtil;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.util.Pair;
|
||||
import com.intellij.openapi.util.TextRange;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.codeStyle.CodeStyleManager;
|
||||
@@ -492,6 +493,17 @@ public class ExpressionUtils {
|
||||
return hasType(expression, CommonClassNames.JAVA_LANG_STRING);
|
||||
}
|
||||
|
||||
/**
|
||||
* The method checks if the passed expression does not need to be converted to string explicitly,
|
||||
* because the method it is passed to can do the string conversion itself.
|
||||
* This is the case for some StringBuilder/Buffer, PrintStream/Writer and some logging methods.
|
||||
* Otherwise it considers the conversion necessary and returns true
|
||||
*
|
||||
* @param expression an expression to examine
|
||||
* @param throwable is the first parameter a conversion to string on a throwable? either {@link Throwable#toString()} or {@link String#valueOf(Object)}
|
||||
*
|
||||
* @return true if the explicit conversion to string is not required, otherwise - false
|
||||
*/
|
||||
public static boolean isConversionToStringNecessary(PsiExpression expression, boolean throwable) {
|
||||
final PsiElement parent = ParenthesesUtils.getParentSkipParentheses(expression);
|
||||
if (parent instanceof PsiPolyadicExpression) {
|
||||
@@ -1149,6 +1161,12 @@ public class ExpressionUtils {
|
||||
EquivalenceChecker eq = EquivalenceChecker.getCanonicalPsiEquivalence();
|
||||
if (isZero(from) && eq.expressionsAreEquivalent(to, diff)) return true;
|
||||
if (isZero(diff) && eq.expressionsAreEquivalent(to, from)) return true;
|
||||
if (to instanceof PsiBinaryExpression && from instanceof PsiBinaryExpression) {
|
||||
final Pair<@NotNull PsiExpression, @NotNull PsiExpression> binaryExpressionsDiff = getBinaryExpressionsDiff((PsiBinaryExpression)from,
|
||||
(PsiBinaryExpression)to);
|
||||
from = binaryExpressionsDiff.first;
|
||||
to = binaryExpressionsDiff.second;
|
||||
}
|
||||
if (diff instanceof PsiBinaryExpression && ((PsiBinaryExpression)diff).getOperationTokenType().equals(JavaTokenType.MINUS)) {
|
||||
PsiExpression left = ((PsiBinaryExpression)diff).getLOperand();
|
||||
PsiExpression right = ((PsiBinaryExpression)diff).getROperand();
|
||||
@@ -1171,12 +1189,6 @@ public class ExpressionUtils {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (to instanceof PsiBinaryExpression && from instanceof PsiBinaryExpression) {
|
||||
final BinaryOpExtractor extractor = new BinaryOpExtractor((PsiBinaryExpression)from, (PsiBinaryExpression)to);
|
||||
final BinaryOpExtractor.Result result = extractor.extract();
|
||||
from = result.myFrom;
|
||||
to = result.myTo;
|
||||
}
|
||||
Integer fromConstant = tryCast(computeConstantExpression(from), Integer.class);
|
||||
if (fromConstant == null) return false;
|
||||
Integer toConstant = tryCast(computeConstantExpression(to), Integer.class);
|
||||
@@ -1186,54 +1198,46 @@ public class ExpressionUtils {
|
||||
return diffConstant == toConstant - fromConstant;
|
||||
}
|
||||
|
||||
private static final class BinaryOpExtractor {
|
||||
@NotNull static final EquivalenceChecker eq = EquivalenceChecker.getCanonicalPsiEquivalence();
|
||||
@NotNull private final PsiBinaryExpression myFrom;
|
||||
@NotNull private final PsiBinaryExpression myTo;
|
||||
/**
|
||||
* Eliminate the common part from two {@link PsiBinaryExpression} expressions.
|
||||
* It only handles {@link PsiBinaryExpression} with {@link JavaTokenType#PLUS}
|
||||
* If there are no common parts return the original expressions<br />
|
||||
* <ol>
|
||||
* <li>Example 1: "x + 1", "x + 2" will be converted to ("1", "2")</li>
|
||||
* <li>Example 2: "x + 1", "y + 2" will remain the same, the result is ("x + 1", "y + 2")</li>
|
||||
* </ol>
|
||||
*
|
||||
* @param from the first expression to examine
|
||||
* @param to the second expression to examine
|
||||
* @return a pair of expressions without common parts
|
||||
*/
|
||||
@NotNull
|
||||
private static Pair<@NotNull PsiExpression, @NotNull PsiExpression> getBinaryExpressionsDiff(@NotNull final PsiBinaryExpression from,
|
||||
@NotNull final PsiBinaryExpression to) {
|
||||
final IElementType fromOp = from.getOperationTokenType();
|
||||
final IElementType toOp = to.getOperationTokenType();
|
||||
if ((fromOp == JavaTokenType.PLUS) && fromOp == toOp) {
|
||||
@NotNull final EquivalenceChecker eq = EquivalenceChecker.getCanonicalPsiEquivalence();
|
||||
final EquivalenceChecker.Match match = eq.expressionsMatch(from, to);
|
||||
if (match.isPartialMatch()) {
|
||||
final PsiExpression leftDiff;
|
||||
final PsiExpression rightDiff;
|
||||
|
||||
private BinaryOpExtractor(@NotNull final PsiBinaryExpression from,
|
||||
@NotNull final PsiBinaryExpression to) {
|
||||
myFrom = from;
|
||||
myTo = to;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private Result extract() {
|
||||
final IElementType opTo = myTo.getOperationTokenType();
|
||||
final IElementType opFrom = myFrom.getOperationTokenType();
|
||||
|
||||
if (opTo == JavaTokenType.PLUS && opFrom == JavaTokenType.PLUS) {
|
||||
@NotNull final PsiExpression toLeft = myTo.getLOperand();
|
||||
@NotNull final PsiExpression toRight = myTo.getROperand() != null ? myTo.getROperand() : myTo;
|
||||
|
||||
@NotNull final PsiExpression fromLeft = myFrom.getLOperand();
|
||||
@NotNull final PsiExpression fromRight = myFrom.getROperand() != null ? myFrom.getROperand() : myFrom;
|
||||
|
||||
if (eq.expressionsAreEquivalent(fromLeft, toLeft)) {
|
||||
return new Result(fromRight, toRight);
|
||||
if (match.getLeftDiff().getParent() == from) {
|
||||
leftDiff = PsiUtil.skipParenthesizedExprDown((PsiExpression)match.getLeftDiff());
|
||||
rightDiff = PsiUtil.skipParenthesizedExprDown((PsiExpression)match.getRightDiff());
|
||||
}
|
||||
else if (eq.expressionsAreEquivalent(fromLeft, toRight)) {
|
||||
return new Result(fromRight, toLeft);
|
||||
else {
|
||||
rightDiff = PsiUtil.skipParenthesizedExprDown((PsiExpression)match.getLeftDiff());
|
||||
leftDiff = PsiUtil.skipParenthesizedExprDown((PsiExpression)match.getRightDiff());
|
||||
}
|
||||
else if (eq.expressionsAreEquivalent(fromRight, toLeft)) {
|
||||
return new Result(fromLeft, toRight);
|
||||
}
|
||||
else if (eq.expressionsAreEquivalent(fromRight, toRight)) {
|
||||
return new Result(fromLeft, toLeft);
|
||||
|
||||
if (leftDiff != null && rightDiff != null) {
|
||||
return Pair.create(leftDiff, rightDiff);
|
||||
}
|
||||
}
|
||||
return new Result(myFrom, myTo);
|
||||
}
|
||||
|
||||
private static final class Result {
|
||||
@NotNull private final PsiExpression myFrom;
|
||||
@NotNull private final PsiExpression myTo;
|
||||
|
||||
private Result(@NotNull final PsiExpression from, @NotNull final PsiExpression to) {
|
||||
myFrom = from;
|
||||
myTo = to;
|
||||
}
|
||||
}
|
||||
return Pair.create(from, to);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -10,6 +10,7 @@ import com.intellij.openapi.util.NlsContexts;
|
||||
import com.intellij.openapi.util.TextRange;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.util.PsiLiteralUtil;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.psi.util.PsiUtil;
|
||||
import com.siyeh.InspectionGadgetsBundle;
|
||||
@@ -18,7 +19,11 @@ import com.siyeh.ig.PsiReplacementUtil;
|
||||
import com.siyeh.ig.callMatcher.CallMapper;
|
||||
import com.siyeh.ig.callMatcher.CallMatcher;
|
||||
import com.siyeh.ig.psiutils.*;
|
||||
import org.jetbrains.annotations.*;
|
||||
import com.sun.tools.javac.util.Pair;
|
||||
import org.jetbrains.annotations.Nls;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.annotations.PropertyKey;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.util.Collections;
|
||||
@@ -164,25 +169,11 @@ public class RedundantStringOperationInspection extends AbstractBaseJavaLocalIns
|
||||
if (lengthMatches) {
|
||||
PsiElement anchor = qualifierCall.getMethodExpression().getReferenceNameElement();
|
||||
if (anchor != null) {
|
||||
final String equalToValue = tryCast(ExpressionUtils.computeConstantExpression(equalTo), String.class);
|
||||
if (StringUtil.length(equalToValue) == 1) {
|
||||
final PsiElement outermostEqualsExpr = getOutermostEqualsExpr(call);
|
||||
final String eqSign = getEqualsSing(outermostEqualsExpr);
|
||||
|
||||
final String converted = String.format("%s.charAt(%s) %s '%s'",
|
||||
receiver.getText(),
|
||||
args[0].getText(),
|
||||
eqSign,
|
||||
equalToValue
|
||||
);
|
||||
|
||||
final SubstringToCharAtQuickFix fix = new SubstringToCharAtQuickFix(outermostEqualsExpr.getText(),
|
||||
converted);
|
||||
return myManager.createProblemDescriptor(outermostEqualsExpr,
|
||||
CommonQuickFixBundle
|
||||
.message("fix.replace.x.with.y", outermostEqualsExpr.getText(), converted),
|
||||
fix,
|
||||
ProblemHighlightType.LIKE_UNUSED_SYMBOL, myIsOnTheFly);
|
||||
if (equalTo instanceof PsiLiteralExpression) {
|
||||
final Object equalsValue = ((PsiLiteralExpression)equalTo).getValue();
|
||||
if (equalsValue instanceof String && StringUtil.length((String)equalsValue) == 1) {
|
||||
return createSubstringToCharAtProblemDescriptor(call);
|
||||
}
|
||||
}
|
||||
return myManager.createProblemDescriptor(anchor, (TextRange)null,
|
||||
InspectionGadgetsBundle.message("inspection.redundant.string.call.message"),
|
||||
@@ -209,33 +200,69 @@ public class RedundantStringOperationInspection extends AbstractBaseJavaLocalIns
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is meant to generate a {@link ProblemDescriptor} for a call like
|
||||
* <pre>
|
||||
* stringValue.substring(i, i + 1).equals("_")
|
||||
* </pre>
|
||||
* which can be replaced with a more readable version
|
||||
* <pre>
|
||||
* stringValue.charAt(i) == '_'
|
||||
* </pre>
|
||||
* <hr />
|
||||
* <p>
|
||||
* <strong>IMPORTANT:</strong> this method is meant to be called only from
|
||||
* {@link RedundantStringOperationVisitor#getRedundantSubstringEqualsProblem(PsiMethodCallExpression)}
|
||||
* </p>
|
||||
*
|
||||
* @param call the original expression to generate a {@link ProblemDescriptor} for
|
||||
* @return generated instance of {@link ProblemDescriptor}
|
||||
*/
|
||||
@NotNull
|
||||
@Contract(value = "_ -> !null", pure = true)
|
||||
private static String getEqualsSing(@NotNull final PsiElement ancestor) {
|
||||
final int negations = StringUtil.countChars(ancestor.getText(), '!');
|
||||
final int i = negations & 1;
|
||||
private ProblemDescriptor createSubstringToCharAtProblemDescriptor(@NotNull final PsiMethodCallExpression call) {
|
||||
final PsiMethodCallExpression qualifierCall = MethodCallUtils.getQualifierMethodCall(call);
|
||||
assert qualifierCall != null : "The method is meant to be called only from getRedundantSubstringEqualsProblem";
|
||||
|
||||
return i == 0 ? "==" : "!=";
|
||||
final PsiExpression receiver = qualifierCall.getMethodExpression().getQualifierExpression();
|
||||
assert receiver != null : "The method is meant to be called only from getRedundantSubstringEqualsProblem";
|
||||
|
||||
final PsiExpression[] args = qualifierCall.getArgumentList().getExpressions();
|
||||
assert args.length == 2 : "The method is meant to be called only from getRedundantSubstringEqualsProblem";
|
||||
|
||||
final PsiExpression equalTo = call.getArgumentList().getExpressions()[0];
|
||||
|
||||
final String equalToValue = PsiLiteralUtil.charLiteralForCharString(equalTo.getText());
|
||||
final Pair<@NotNull PsiExpression, @NotNull Boolean> sign = getExpressionSign(call);
|
||||
|
||||
final PsiElement outermostEqualsExpr = sign.fst;
|
||||
final String eqSign = sign.snd ? "==" : "!=";
|
||||
|
||||
final String converted = String.format("%s.charAt(%s) %s %s",
|
||||
receiver.getText(),
|
||||
args[0].getText(),
|
||||
eqSign,
|
||||
equalToValue
|
||||
);
|
||||
|
||||
final SubstringEqualsToCharAtEqualsQuickFix fix = new SubstringEqualsToCharAtEqualsQuickFix(outermostEqualsExpr.getText(),
|
||||
converted);
|
||||
return myManager.createProblemDescriptor(outermostEqualsExpr,
|
||||
InspectionGadgetsBundle.message("inspection.x.call.can.be.replaced.with.y", "substring()", "charAt()"),
|
||||
fix,
|
||||
ProblemHighlightType.LIKE_UNUSED_SYMBOL, myIsOnTheFly);
|
||||
}
|
||||
|
||||
@Contract(value = "null -> null; !null -> !null", pure = true)
|
||||
@Nullable
|
||||
private static PsiElement getOutermostEqualsExpr(@Nullable PsiElement ancestor) {
|
||||
if (ancestor == null) return null;
|
||||
PsiElement parent;
|
||||
@NotNull
|
||||
private static Pair<@NotNull PsiExpression, @NotNull Boolean> getExpressionSign(@NotNull PsiExpression start) {
|
||||
boolean sign = true;
|
||||
PsiExpression expr = start;
|
||||
while (true) {
|
||||
parent = PsiUtil.skipParenthesizedExprUp(ancestor.getParent());
|
||||
if (parent instanceof PsiPrefixExpression) {
|
||||
final PsiPrefixExpression parentPrefixExpr = (PsiPrefixExpression)parent;
|
||||
if (JavaTokenType.EXCL != parentPrefixExpr.getOperationTokenType()) break;
|
||||
|
||||
ancestor = parent;
|
||||
}
|
||||
else {
|
||||
break;
|
||||
}
|
||||
final PsiPrefixExpression negation = (PsiPrefixExpression) BoolUtils.findNegation(expr);
|
||||
if (negation == null) break;
|
||||
sign = !sign;
|
||||
expr = negation;
|
||||
}
|
||||
return ancestor;
|
||||
return Pair.of(expr, sign);
|
||||
}
|
||||
|
||||
private boolean lengthMatches(PsiExpression equalTo, PsiExpression from, PsiExpression to) {
|
||||
@@ -373,20 +400,36 @@ public class RedundantStringOperationInspection extends AbstractBaseJavaLocalIns
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method examines the passed {@link PsiMethodCallExpression} that contains {@link String#substring(int, int)}
|
||||
* to decide if it should be altered with {@link String#charAt(int)}<br/>
|
||||
* <pre>
|
||||
* args[1] - args[0] == 1
|
||||
* yes no
|
||||
* | |
|
||||
* | |- return false
|
||||
* |
|
||||
* |-ExpressionUtils.isConversionToStringNecessary(call, false) == true
|
||||
* yes no
|
||||
* | |- return true
|
||||
* |- return false
|
||||
* </pre>
|
||||
* The method checks if the difference between the arguments of {@link String#substring(int, int)} is equal to "1"
|
||||
* and if so it checks if the expression should be converted to string according to its surroundings.
|
||||
* If the expression is not required to be converted to string explicitly then the expression is a good candidate
|
||||
* to be converted with {@link String#charAt(int)}
|
||||
* @param call an expression to examine
|
||||
* @return true if the expression is a good candidate to to be converted with {@link String#charAt(int)}, otherwise - false
|
||||
*/
|
||||
private static boolean isBetterWithCharAt(@NotNull final PsiMethodCallExpression call) {
|
||||
final PsiExpression[] args = call.getArgumentList().getExpressions();
|
||||
final PsiElementFactory factory = JavaPsiFacade.getElementFactory(call.getProject());
|
||||
|
||||
final boolean diffByOne = ExpressionUtils.isDifference(args[0],
|
||||
args[1],
|
||||
factory.createExpressionFromText("1", null));
|
||||
final PsiExpression one = factory.createExpressionFromText("1", null);
|
||||
final boolean diffByOne = ExpressionUtils.isDifference(args[0], args[1], one);
|
||||
if (!diffByOne) return false;
|
||||
|
||||
final PsiReferenceExpression methodExpression = call.getMethodExpression();
|
||||
final PsiExpression qualifier = ExpressionUtils.getEffectiveQualifier(methodExpression);
|
||||
final boolean throwable = TypeUtils.expressionHasTypeOrSubtype(qualifier, "java.lang.Throwable");
|
||||
|
||||
return !ExpressionUtils.isConversionToStringNecessary(call, throwable);
|
||||
return !ExpressionUtils.isConversionToStringNecessary(call, false);
|
||||
}
|
||||
|
||||
private static boolean isLengthOf(PsiExpression stringLengthCandidate, PsiExpression stringExpression) {
|
||||
@@ -415,6 +458,10 @@ public class RedundantStringOperationInspection extends AbstractBaseJavaLocalIns
|
||||
return call.getTextRange();
|
||||
}
|
||||
|
||||
/**
|
||||
* An instance of {@link LocalQuickFix} for problems that can be solved by replacing
|
||||
* {@link String#substring(int, int)} with {@link String#charAt(int)}
|
||||
*/
|
||||
private static class SubstringToCharAtQuickFix implements LocalQuickFix {
|
||||
@NotNull private final String myText;
|
||||
@NotNull private final String myConverted;
|
||||
@@ -432,7 +479,48 @@ public class RedundantStringOperationInspection extends AbstractBaseJavaLocalIns
|
||||
|
||||
@Override
|
||||
public @NlsContexts.ListItem @NotNull String getFamilyName() {
|
||||
return InspectionGadgetsBundle.message("remove.redundant.substring.to.char.at.fix.family.name");
|
||||
return CommonQuickFixBundle.message("fix.replace.x.with.y", "substring()", "charAt()");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyFix(@NotNull final Project project, @NotNull final ProblemDescriptor descriptor) {
|
||||
PsiMethodCallExpression call = tryCast(descriptor.getPsiElement(), PsiMethodCallExpression.class);
|
||||
if (call == null) return;
|
||||
PsiExpression[] args = call.getArgumentList().getExpressions();
|
||||
if (args.length != 2) return;
|
||||
ExpressionUtils.bindCallTo(call, "charAt");
|
||||
new CommentTracker().deleteAndRestoreComments(args[1]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An instance of {@link LocalQuickFix} for problems like
|
||||
* <pre>
|
||||
* stringValue.substring(i, i + 1).equals("_")
|
||||
* </pre>
|
||||
* that can be changed to
|
||||
* <pre>
|
||||
* stringValue.charAt(i) == '_'
|
||||
* </pre>
|
||||
*/
|
||||
private static class SubstringEqualsToCharAtEqualsQuickFix implements LocalQuickFix {
|
||||
@NotNull private final String myText;
|
||||
@NotNull private final String myConverted;
|
||||
|
||||
SubstringEqualsToCharAtEqualsQuickFix(@NotNull final String text,
|
||||
@NotNull final String converted) {
|
||||
myText = text;
|
||||
myConverted = converted;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NlsContexts.ListItem @NotNull String getName() {
|
||||
return CommonQuickFixBundle.message("fix.replace.x.with.y", myText, myConverted);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NlsContexts.ListItem @NotNull String getFamilyName() {
|
||||
return CommonQuickFixBundle.message("fix.replace.x.with.y", "substring()", "charAt()");
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -440,12 +528,11 @@ public class RedundantStringOperationInspection extends AbstractBaseJavaLocalIns
|
||||
final PsiElement element = descriptor.getPsiElement();
|
||||
if (element == null) return;
|
||||
|
||||
final PsiExpression text = JavaPsiFacade.getElementFactory(project).createExpressionFromText(myConverted, null);
|
||||
|
||||
final CommentTracker ct = new CommentTracker();
|
||||
ct.replaceAndRestoreComments(element, text);
|
||||
ct.replaceAndRestoreComments(element, myConverted);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class RemoveRedundantSubstringFix implements LocalQuickFix {
|
||||
|
||||
Reference in New Issue
Block a user