From 7a86f1e752cde8a9364ba8cf6c9f81eff3e15cce Mon Sep 17 00:00:00 2001 From: Artemiy Sartakov Date: Fri, 8 Nov 2019 10:55:35 +0700 Subject: [PATCH] TextBlockMigrationInspection: preserve meaningful spaces at the beginning of each line (IDEA-226395) GitOrigin-RevId: a204f736a29271ebe83fd5f5222d0b1ebd28ddbf --- .../TextBlockMigrationInspection.java | 64 +++++++++++++------ .../com/intellij/psi/util/PsiLiteralUtil.java | 20 +++++- .../tree/java/PsiLiteralExpressionImpl.java | 16 +---- ...ConcatenationWithSpacesAtTheBeginning.java | 13 ++++ ...ConcatenationWithSpacesAtTheBeginning.java | 12 ++++ .../beforeConcatenationWithTextBlock.java | 11 ++++ 6 files changed, 102 insertions(+), 34 deletions(-) create mode 100644 java/java-tests/testData/inspection/textBlockMigration/afterConcatenationWithSpacesAtTheBeginning.java create mode 100644 java/java-tests/testData/inspection/textBlockMigration/beforeConcatenationWithSpacesAtTheBeginning.java create mode 100644 java/java-tests/testData/inspection/textBlockMigration/beforeConcatenationWithTextBlock.java diff --git a/java/java-impl/src/com/intellij/codeInspection/TextBlockMigrationInspection.java b/java/java-impl/src/com/intellij/codeInspection/TextBlockMigrationInspection.java index 89e0966ffb02..aa3351d63b6e 100644 --- a/java/java-impl/src/com/intellij/codeInspection/TextBlockMigrationInspection.java +++ b/java/java-impl/src/com/intellij/codeInspection/TextBlockMigrationInspection.java @@ -46,6 +46,7 @@ public class TextBlockMigrationInspection extends AbstractBaseJavaLocalInspectio for (PsiExpression operand : operands) { PsiLiteralExpressionImpl literal = getLiteralExpression(operand); if (literal == null) return; + if (nNewLines > 1) continue; String text = literal.getText(); int newLineIdx = getNewLineIndex(text, 0); if (newLineIdx == -1) continue; @@ -57,7 +58,6 @@ public class TextBlockMigrationInspection extends AbstractBaseJavaLocalInspectio nNewLines++; newLineIdx = getNewLineIndex(text, newLineIdx + 1); } - if (nNewLines > 1) break; } if (nNewLines <= 1) return; holder.registerProblem(expression, firstNewLineTextRange, @@ -106,22 +106,55 @@ public class TextBlockMigrationInspection extends AbstractBaseJavaLocalInspectio } private static void replaceWithTextBlock(@NotNull PsiExpression[] operands, @NotNull PsiExpression toReplace) { - StringBuilder textBlock = new StringBuilder(); - textBlock.append("\"\"\"\n"); + String[] lines = getContentLines(operands); + if (lines == null) return; + String textBlock = getTextBlock(lines); + PsiReplacementUtil.replaceExpression(toReplace, textBlock, new CommentTracker()); + } + + @NotNull + private static String getTextBlock(@NotNull String[] lines) { + int indent = PsiLiteralUtil.getTextBlockIndent(lines); + // we need additional indent call only when significant trailing line is missing + if (indent > 0 && lines.length > 0 && lines[lines.length - 1].endsWith("\n")) indent = 0; + String content = getTextBlockContent(lines, indent); + return "\"\"\"\n" + content + "\"\"\"" + (indent > 0 ? ".indent(" + indent + ")" : ""); + } + + @NotNull + private static String getTextBlockContent(@NotNull String[] lines, int indent) { + StringBuilder content = new StringBuilder(); boolean escapeStartQuote = false; + for (int i = 0; i < lines.length; i++) { + String line = lines[i]; + boolean isLastLine = i == lines.length - 1; + if (indent < line.length()) line = line.substring(indent); + line = PsiLiteralUtil.escapeTextBlockCharacters(line, escapeStartQuote, isLastLine, isLastLine); + escapeStartQuote = line.endsWith("\""); + content.append(line); + } + return content.toString(); + } + + @Nullable + private static String[] getContentLines(@NotNull PsiExpression[] operands) { + String[] lines = new String[operands.length]; for (int i = 0; i < operands.length; i++) { PsiExpression operand = operands[i]; 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); - escapeStartQuote = text.endsWith("\""); - textBlock.append(text); + if (literal == null) return null; + String line = getLiteralText(literal); + if (line == null) return null; + lines[i] = line; } - textBlock.append("\"\"\""); - PsiReplacementUtil.replaceExpression(toReplace, textBlock.toString(), new CommentTracker()); + return lines; + } + + @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(); } } @@ -151,11 +184,4 @@ public class TextBlockMigrationInspection extends AbstractBaseJavaLocalInspectio if (literal == null || literal.getLiteralElementType() == JavaTokenType.TEXT_BLOCK_LITERAL) return null; 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-psi-api/src/com/intellij/psi/util/PsiLiteralUtil.java b/java/java-psi-api/src/com/intellij/psi/util/PsiLiteralUtil.java index a3e01c077e67..33611a442732 100644 --- a/java/java-psi-api/src/com/intellij/psi/util/PsiLiteralUtil.java +++ b/java/java-psi-api/src/com/intellij/psi/util/PsiLiteralUtil.java @@ -170,7 +170,7 @@ public class PsiLiteralUtil { *
  • All escaped quotes are unescaped.
  • *
  • Every third quote is escaped. If escapeStartQuote / escapeEndQuote is set then start / end quote is also escaped.
  • *
  • All spaces before \n are converted to \040 escape sequence. - * This is required since spaces in the end of the line are trimmed by default (see JEP 355). + * This is required since spaces in the end of the line are trimmed by default (see JEP 368). * If escapeSpacesInTheEnd is set, then all spaces before the end of the line are converted even if new line in the end is missing.
  • *
  • All new line escape sequences are interpreted.
  • *
  • Rest of the content is processed as is.
  • @@ -372,4 +372,22 @@ public class PsiLiteralUtil { } return -1; } + + /** + * Determines how many whitespaces would be excluded at the beginning of each line of text block content. + * See JEP 368 for more details. + * + * @param lines text block content + */ + public static int getTextBlockIndent(@NotNull String[] lines) { + int prefix = Integer.MAX_VALUE; + for (int i = 0; i < lines.length; i++) { + String line = lines[i]; + int indent = 0; + while (indent < line.length() && Character.isWhitespace(line.charAt(indent))) indent++; + if (indent == line.length() && i < lines.length - 1) lines[i] = ""; + else if (indent < prefix) prefix = indent; + } + return prefix; + } } \ No newline at end of file diff --git a/java/java-psi-impl/src/com/intellij/psi/impl/source/tree/java/PsiLiteralExpressionImpl.java b/java/java-psi-impl/src/com/intellij/psi/impl/source/tree/java/PsiLiteralExpressionImpl.java index 6765f47a5c27..a0704209801f 100644 --- a/java/java-psi-impl/src/com/intellij/psi/impl/source/tree/java/PsiLiteralExpressionImpl.java +++ b/java/java-psi-impl/src/com/intellij/psi/impl/source/tree/java/PsiLiteralExpressionImpl.java @@ -158,7 +158,7 @@ public class PsiLiteralExpressionImpl String[] lines = getTextBlockLines(); if (lines == null) return null; - int prefix = getTextBlockIndent(lines); + int prefix = PsiLiteralUtil.getTextBlockIndent(lines); StringBuilder sb = new StringBuilder(); for (int i = 0; i < lines.length; i++) { @@ -176,19 +176,7 @@ public class PsiLiteralExpressionImpl public int getTextBlockIndent() { String[] lines = getTextBlockLines(); if (lines == null) return -1; - return getTextBlockIndent(lines); - } - - private static int getTextBlockIndent(String[] lines) { - int prefix = Integer.MAX_VALUE; - for (int i = 0; i < lines.length; i++) { - String line = lines[i]; - int indent = 0; - while (indent < line.length() && Character.isWhitespace(line.charAt(indent))) indent++; - if (indent == line.length() && i < lines.length - 1) lines[i] = ""; - else if (indent < prefix) prefix = indent; - } - return prefix; + return PsiLiteralUtil.getTextBlockIndent(lines); } @Nullable diff --git a/java/java-tests/testData/inspection/textBlockMigration/afterConcatenationWithSpacesAtTheBeginning.java b/java/java-tests/testData/inspection/textBlockMigration/afterConcatenationWithSpacesAtTheBeginning.java new file mode 100644 index 000000000000..101cbfd524a2 --- /dev/null +++ b/java/java-tests/testData/inspection/textBlockMigration/afterConcatenationWithSpacesAtTheBeginning.java @@ -0,0 +1,13 @@ +// "Replace with text block" "true" + +class TextBlockMigration { + + void concatenationWithSpacesAtTheBeginning() { + String body = """ + +

    +

    + """.indent(2); + } + +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/textBlockMigration/beforeConcatenationWithSpacesAtTheBeginning.java b/java/java-tests/testData/inspection/textBlockMigration/beforeConcatenationWithSpacesAtTheBeginning.java new file mode 100644 index 000000000000..7b08ce31ad32 --- /dev/null +++ b/java/java-tests/testData/inspection/textBlockMigration/beforeConcatenationWithSpacesAtTheBeginning.java @@ -0,0 +1,12 @@ +// "Replace with text block" "true" + +class TextBlockMigration { + + void concatenationWithSpacesAtTheBeginning() { + String body = " \n" + + "

    \n" + + "

    \n" + + " "; + } + +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/textBlockMigration/beforeConcatenationWithTextBlock.java b/java/java-tests/testData/inspection/textBlockMigration/beforeConcatenationWithTextBlock.java new file mode 100644 index 000000000000..eb440dff91b1 --- /dev/null +++ b/java/java-tests/testData/inspection/textBlockMigration/beforeConcatenationWithTextBlock.java @@ -0,0 +1,11 @@ +// "Fix all 'Text block can be used' problems in file" "false" + +class TextBlockMigration { + + void concatenationWithTextBlock() { + String concat = "foo\n" + "bar\n" + + """ + baz"""; + } + +} \ No newline at end of file