TextBlockMigrationInspection: option to replace concatenation only, less noisy highlighting (IDEA-224582)

GitOrigin-RevId: a420c84ac6daa81ab7eb69112161ac2adc3d30d5
This commit is contained in:
Artemiy Sartakov
2019-10-14 04:39:24 +00:00
committed by intellij-monorepo-bot
parent 4989493636
commit 3021c81a97
11 changed files with 78 additions and 23 deletions
@@ -2,23 +2,36 @@
package com.intellij.codeInspection;
import com.intellij.codeInsight.daemon.impl.analysis.HighlightUtil;
import com.intellij.codeInspection.ui.SingleCheckboxOptionsPanel;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.*;
import com.intellij.psi.impl.source.tree.java.PsiLiteralExpressionImpl;
import com.intellij.psi.util.PsiLiteralUtil;
import com.intellij.psi.util.PsiUtil;
import com.siyeh.ig.PsiReplacementUtil;
import com.siyeh.ig.psiutils.CommentTracker;
import org.apache.commons.lang.StringUtils;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import static com.intellij.util.ObjectUtils.tryCast;
public class TextBlockMigrationInspection extends AbstractBaseJavaLocalInspectionTool {
public boolean mySuggestLiteralReplacement = true;
@Nullable
@Override
public JComponent createOptionsPanel() {
return new SingleCheckboxOptionsPanel(InspectionsBundle.message("inspection.text.block.migration.suggest.literal.replacement"),
this,
"mySuggestLiteralReplacement");
}
@NotNull
@Override
public PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly) {
@@ -27,22 +40,41 @@ public class TextBlockMigrationInspection extends AbstractBaseJavaLocalInspectio
@Override
public void visitPolyadicExpression(PsiPolyadicExpression expression) {
if (!isConcatenation(expression)) return;
int newLinesCnt = 0;
for (PsiExpression operand : expression.getOperands()) {
String text = getExpressionText(operand, false);
if (text == null) return;
if (newLinesCnt <= 1) newLinesCnt += StringUtils.countMatches(text, "\n");
int nNewLines = 0;
PsiExpression[] operands = expression.getOperands();
TextRange firstNewLineTextRange = null;
for (PsiExpression operand : operands) {
PsiLiteralExpressionImpl literal = getLiteralExpression(operand);
if (literal == null) return;
String text = literal.getText();
int newLineIdx = getNewLineIndex(text, 0);
if (newLineIdx == -1) continue;
if (firstNewLineTextRange == null) {
int operandOffset = literal.getTextOffset() - expression.getTextOffset();
firstNewLineTextRange = new TextRange(operandOffset + newLineIdx, operandOffset + newLineIdx + 2);
}
while (nNewLines <= 1 && newLineIdx != -1) {
nNewLines++;
newLineIdx = getNewLineIndex(text, newLineIdx + 1);
}
if (nNewLines > 1) break;
}
if (newLinesCnt <= 1) return;
holder.registerProblem(expression, InspectionsBundle.message("inspection.text.block.migration.message", "Concatenation"),
if (nNewLines <= 1) return;
holder.registerProblem(expression, firstNewLineTextRange,
InspectionsBundle.message("inspection.text.block.migration.message", "Concatenation"),
new ReplaceWithTextBlockFix());
}
@Override
public void visitLiteralExpression(PsiLiteralExpression expression) {
String text = getExpressionText(expression, false);
if (text == null || StringUtils.countMatches(text, "\n") <= 1) return;
holder.registerProblem(expression, InspectionsBundle.message("inspection.text.block.migration.message", "String"),
if (!mySuggestLiteralReplacement) return;
PsiLiteralExpressionImpl literal = getLiteralExpression(expression);
if (literal == null) return;
String text = literal.getText();
int newLineIdx = getNewLineIndex(text, 0);
if (newLineIdx == -1 || getNewLineIndex(text, newLineIdx + 1) == -1) return;
holder.registerProblem(expression, new TextRange(newLineIdx, newLineIdx + 2),
InspectionsBundle.message("inspection.text.block.migration.message", "String"),
new ReplaceWithTextBlockFix());
}
};
@@ -79,7 +111,9 @@ public class TextBlockMigrationInspection extends AbstractBaseJavaLocalInspectio
boolean escapeStartQuote = false;
for (int i = 0; i < operands.length; i++) {
PsiExpression operand = operands[i];
String text = getExpressionText(operand, true);
PsiLiteralExpressionImpl literal = getLiteralExpression(operand);
if (literal == null) return;
String text = getLiteralText(literal);
if (text == null) return;
boolean isLastLine = i == operands.length - 1;
text = PsiLiteralUtil.escapeTextBlockCharacters(text, escapeStartQuote, isLastLine, isLastLine);
@@ -91,16 +125,36 @@ public class TextBlockMigrationInspection extends AbstractBaseJavaLocalInspectio
}
}
private static int getNewLineIndex(@NotNull String text, int start) {
int i = start;
while (i < text.length()) {
char c = text.charAt(i);
if (c == '\\') {
if (i + 1 < text.length() && text.charAt(i + 1) == 'n') return i;
i += 2;
}
else {
i++;
}
}
return -1;
}
private static boolean isConcatenation(@NotNull PsiPolyadicExpression expression) {
PsiType type = expression.getType();
return type != null && type.equalsToText(CommonClassNames.JAVA_LANG_STRING);
}
@Nullable
private static String getExpressionText(@NotNull PsiExpression expression, boolean isRawText) {
private static PsiLiteralExpressionImpl getLiteralExpression(@NotNull PsiExpression expression) {
PsiLiteralExpressionImpl literal = tryCast(PsiUtil.skipParenthesizedExprDown(expression), PsiLiteralExpressionImpl.class);
if (literal == null || literal.getLiteralElementType() == JavaTokenType.TEXT_BLOCK_LITERAL) return null;
if (literal.getLiteralElementType() == JavaTokenType.STRING_LITERAL && isRawText) return literal.getInnerText();
return literal;
}
@Nullable
private static String getLiteralText(@NotNull PsiLiteralExpressionImpl literal) {
if (literal.getLiteralElementType() == JavaTokenType.STRING_LITERAL) return literal.getInnerText();
Object value = literal.getValue();
return value == null ? null : value.toString();
}
@@ -3,7 +3,7 @@
class TextBlockMigration {
void concatenation() {
String foobarbaz = "foo\n" <caret>+
String foobarbaz = "foo<caret>\n" +
"bar\n" +
"baz";
}
@@ -3,7 +3,7 @@
class TextBlockMigration {
void concatenationWithMultipleNewLines() {
String text = "This<caret> text should be on the same line as \\n this one\n" +
String text = "This text should be on the same line as \\n this one<caret>\n" +
"foo\n" +
"bar\n";
}
@@ -3,7 +3,7 @@
class TextBlockMigration {
void concatenationWithMultipleNewLines() {
String html = "<html>\n" +<caret>
String html = "<html>\n<caret>" +
" <body>\n" +
" <p>Hello, world</p>\n" +
" </body>\n" +
@@ -3,7 +3,7 @@
class TextBlockMigration {
void concatenationWithNonStrings() {
String answer = "T<caret>he answer to the meaning of life,\n" +
String answer = "The answer to the meaning of life,<caret>\n" +
"the universe,\n" +
"and everything\n" +
"is " + 42 + '\n';
@@ -3,7 +3,7 @@
class TextBlockMigration {
void concatenationWithOneNewLine() {
String code = "<<caret>html>\n" +
String code = "<html><caret>\n" +
"</html>";
}
@@ -3,7 +3,7 @@
class TextBlockMigration {
void concatenationWithExtraSpaces() {
String code = "<<caret>html> \n" +
String code = "<html> <caret>\n" +
" <body>\n" +
" </body>\n" +
"</html> ";
@@ -3,7 +3,7 @@
class TextBlockMigration {
void concatenationWithThreeQuotes() {
String quotes = "<caret>this concatenation contains\n" +
String quotes = "this concatenation contains<caret>\n" +
" three quotes\n" +
"one after another\n" +
"\"" +
@@ -3,7 +3,7 @@
class TextBlockMigration {
void literalWithNewLine() {
String foo = "foo\nbar<caret>\nbaz\n";
String foo = "foo<caret>\nbar\nbaz\n";
}
}
@@ -3,7 +3,7 @@
class TextBlockMigration {
void literalWithNewLine() {
String foo = "foo\nbar<caret>";
String foo = "foo<caret>\nbar";
}
}
@@ -1011,6 +1011,7 @@ inspection.fold.expression.into.string.fix.name=Fold expression into 'String.joi
inspection.fold.expression.fix.family.name=Fold expression
inspection.text.block.migration.name=Text block can be used
inspection.text.block.migration.message={0} can be replaced with text block
inspection.text.block.migration.suggest.literal.replacement=Suggest to replace string with line breaks
inspection.replace.with.text.block.fix=Replace with text block
inspection.text.block.backward.migration.name=Text block can be replaced with regular string literal