[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:
Nikita Eshkeev
2020-04-17 17:10:45 +03:00
committed by intellij-monorepo-bot
parent 36a6a3ab65
commit 0aa2e160b0
8 changed files with 269 additions and 116 deletions

View File

@@ -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.

View File

@@ -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));

View File

@@ -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) != '_') { }
}
}

View File

@@ -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));

View File

@@ -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("_")) { }
}
}

View File

@@ -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)

View File

@@ -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);
}
/**

View File

@@ -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 {