diff --git a/java/java-impl/src/com/intellij/codeInspection/TextBlockMigrationInspection.java b/java/java-impl/src/com/intellij/codeInspection/TextBlockMigrationInspection.java index fb05705e5c3d..228bd074220d 100644 --- a/java/java-impl/src/com/intellij/codeInspection/TextBlockMigrationInspection.java +++ b/java/java-impl/src/com/intellij/codeInspection/TextBlockMigrationInspection.java @@ -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(); } diff --git a/java/java-tests/testData/inspection/textBlockMigration/beforeConcatenationNoNewLineInTheEnd.java b/java/java-tests/testData/inspection/textBlockMigration/beforeConcatenationNoNewLineInTheEnd.java index 60b0c429fcfb..191175be77a3 100644 --- a/java/java-tests/testData/inspection/textBlockMigration/beforeConcatenationNoNewLineInTheEnd.java +++ b/java/java-tests/testData/inspection/textBlockMigration/beforeConcatenationNoNewLineInTheEnd.java @@ -3,7 +3,7 @@ class TextBlockMigration { void concatenation() { - String foobarbaz = "foo\n" + + String foobarbaz = "foo\n" + "bar\n" + "baz"; } diff --git a/java/java-tests/testData/inspection/textBlockMigration/beforeConcatenationWithEscapedNewLine.java b/java/java-tests/testData/inspection/textBlockMigration/beforeConcatenationWithEscapedNewLine.java index e982eede02b6..e07b423a6fec 100644 --- a/java/java-tests/testData/inspection/textBlockMigration/beforeConcatenationWithEscapedNewLine.java +++ b/java/java-tests/testData/inspection/textBlockMigration/beforeConcatenationWithEscapedNewLine.java @@ -3,7 +3,7 @@ class TextBlockMigration { void concatenationWithMultipleNewLines() { - String text = "This 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\n" + "foo\n" + "bar\n"; } diff --git a/java/java-tests/testData/inspection/textBlockMigration/beforeConcatenationWithMultipleNewLines.java b/java/java-tests/testData/inspection/textBlockMigration/beforeConcatenationWithMultipleNewLines.java index ee8701e05e0d..dc87057ce4ed 100644 --- a/java/java-tests/testData/inspection/textBlockMigration/beforeConcatenationWithMultipleNewLines.java +++ b/java/java-tests/testData/inspection/textBlockMigration/beforeConcatenationWithMultipleNewLines.java @@ -3,7 +3,7 @@ class TextBlockMigration { void concatenationWithMultipleNewLines() { - String html = "\n" + + String html = "\n" + " \n" + "

Hello, world

\n" + " \n" + diff --git a/java/java-tests/testData/inspection/textBlockMigration/beforeConcatenationWithNonStrings.java b/java/java-tests/testData/inspection/textBlockMigration/beforeConcatenationWithNonStrings.java index e59c946c32f0..f2d456ba3b11 100644 --- a/java/java-tests/testData/inspection/textBlockMigration/beforeConcatenationWithNonStrings.java +++ b/java/java-tests/testData/inspection/textBlockMigration/beforeConcatenationWithNonStrings.java @@ -3,7 +3,7 @@ class TextBlockMigration { void concatenationWithNonStrings() { - String answer = "The answer to the meaning of life,\n" + + String answer = "The answer to the meaning of life,\n" + "the universe,\n" + "and everything\n" + "is " + 42 + '\n'; diff --git a/java/java-tests/testData/inspection/textBlockMigration/beforeConcatenationWithOneNewLine.java b/java/java-tests/testData/inspection/textBlockMigration/beforeConcatenationWithOneNewLine.java index ae29a0b2352e..dc84a04d36d2 100644 --- a/java/java-tests/testData/inspection/textBlockMigration/beforeConcatenationWithOneNewLine.java +++ b/java/java-tests/testData/inspection/textBlockMigration/beforeConcatenationWithOneNewLine.java @@ -3,7 +3,7 @@ class TextBlockMigration { void concatenationWithOneNewLine() { - String code = "<html>\n" + + String code = "\n" + ""; } diff --git a/java/java-tests/testData/inspection/textBlockMigration/beforeConcatenationWithSpacesInTheEnd.java b/java/java-tests/testData/inspection/textBlockMigration/beforeConcatenationWithSpacesInTheEnd.java index 94ff1c6e2e06..68e037c5ff87 100644 --- a/java/java-tests/testData/inspection/textBlockMigration/beforeConcatenationWithSpacesInTheEnd.java +++ b/java/java-tests/testData/inspection/textBlockMigration/beforeConcatenationWithSpacesInTheEnd.java @@ -3,7 +3,7 @@ class TextBlockMigration { void concatenationWithExtraSpaces() { - String code = "<html> \n" + + String code = " \n" + " \n" + " \n" + " "; diff --git a/java/java-tests/testData/inspection/textBlockMigration/beforeConcatenationWithThreeQuotesInText.java b/java/java-tests/testData/inspection/textBlockMigration/beforeConcatenationWithThreeQuotesInText.java index 2fe351f31b98..60c6dc7f2257 100644 --- a/java/java-tests/testData/inspection/textBlockMigration/beforeConcatenationWithThreeQuotesInText.java +++ b/java/java-tests/testData/inspection/textBlockMigration/beforeConcatenationWithThreeQuotesInText.java @@ -3,7 +3,7 @@ class TextBlockMigration { void concatenationWithThreeQuotes() { - String quotes = "this concatenation contains\n" + + String quotes = "this concatenation contains\n" + " three quotes\n" + "one after another\n" + "\"" + diff --git a/java/java-tests/testData/inspection/textBlockMigration/beforeLiteralWithMultipleNewLines.java b/java/java-tests/testData/inspection/textBlockMigration/beforeLiteralWithMultipleNewLines.java index 260c263c966b..d3ac9bfeeb8c 100644 --- a/java/java-tests/testData/inspection/textBlockMigration/beforeLiteralWithMultipleNewLines.java +++ b/java/java-tests/testData/inspection/textBlockMigration/beforeLiteralWithMultipleNewLines.java @@ -3,7 +3,7 @@ class TextBlockMigration { void literalWithNewLine() { - String foo = "foo\nbar\nbaz\n"; + String foo = "foo\nbar\nbaz\n"; } } \ No newline at end of file diff --git a/java/java-tests/testData/inspection/textBlockMigration/beforeLiteralWithNewLine.java b/java/java-tests/testData/inspection/textBlockMigration/beforeLiteralWithNewLine.java index 7cd362b6d609..81c249e4e39f 100644 --- a/java/java-tests/testData/inspection/textBlockMigration/beforeLiteralWithNewLine.java +++ b/java/java-tests/testData/inspection/textBlockMigration/beforeLiteralWithNewLine.java @@ -3,7 +3,7 @@ class TextBlockMigration { void literalWithNewLine() { - String foo = "foo\nbar"; + String foo = "foo\nbar"; } } \ No newline at end of file diff --git a/platform/platform-resources-en/src/messages/InspectionsBundle.properties b/platform/platform-resources-en/src/messages/InspectionsBundle.properties index 56516c07061c..f541c58355b4 100644 --- a/platform/platform-resources-en/src/messages/InspectionsBundle.properties +++ b/platform/platform-resources-en/src/messages/InspectionsBundle.properties @@ -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