preserve text blocks when converting to (message)format string (IDEA-217253, IDEA-218831)

and use new text block escaping

GitOrigin-RevId: cc8eea8aa0c638b967e40b6a6b4b9dea1cfed61d
This commit is contained in:
Bas Leijdekkers
2019-08-02 00:04:00 +03:00
committed by intellij-monorepo-bot
parent f5bd40eb0c
commit a12eee9fbc
10 changed files with 107 additions and 110 deletions
@@ -1,18 +1,4 @@
/*
* Copyright 2000-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.codeInsight.intention.impl;
import com.intellij.codeInsight.AnnotationUtil;
@@ -20,10 +6,12 @@ import com.intellij.codeInsight.CodeInsightBundle;
import com.intellij.codeInsight.intention.IntentionAction;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.pom.java.LanguageLevel;
import com.intellij.psi.*;
import com.intellij.psi.codeStyle.CodeStyleManager;
import com.intellij.psi.codeStyle.JavaCodeStyleManager;
import com.intellij.psi.impl.source.tree.java.PsiLiteralExpressionImpl;
import com.intellij.psi.util.PsiConcatenationUtil;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
@@ -33,6 +21,7 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
@@ -56,15 +45,20 @@ public class ConcatenationToMessageFormatAction implements IntentionAction {
final PsiElement element = findElementAtCaret(editor, file);
PsiPolyadicExpression concatenation = getEnclosingLiteralConcatenation(element);
if (concatenation == null) return;
StringBuilder formatString = new StringBuilder();
List<PsiExpression> args = new ArrayList<>();
PsiConcatenationUtil.buildFormatString(concatenation, formatString, args, false);
final String formatString = PsiConcatenationUtil.buildFormatString(concatenation, false, args);
final PsiElementFactory factory = JavaPsiFacade.getElementFactory(project);
PsiMethodCallExpression call = (PsiMethodCallExpression)
factory.createExpressionFromText("java.text.MessageFormat.format()", concatenation);
PsiExpressionList argumentList = call.getArgumentList();
PsiExpression formatArgument = factory.createExpressionFromText("\"" + formatString.toString() + "\"", null);
boolean textBlocks = Arrays.stream(concatenation.getOperands())
.anyMatch(operand -> operand instanceof PsiLiteralExpressionImpl &&
((PsiLiteralExpressionImpl)operand).getLiteralElementType() == JavaTokenType.TEXT_BLOCK_LITERAL);
final String expressionText = textBlocks
? "\"\"\"\n" + StringUtil.escapeTextBlockCharacters(formatString) + "\"\"\""
: "\"" + StringUtil.escapeStringCharacters(formatString) + "\"";
PsiExpression formatArgument = factory.createExpressionFromText(expressionText, null);
argumentList.add(formatArgument);
if (PsiUtil.isLanguageLevel5OrHigher(file)) {
for (PsiExpression arg : args) {
@@ -1,21 +1,6 @@
/*
* Copyright 2000-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.psi.util;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.*;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.util.ObjectUtils;
@@ -28,17 +13,29 @@ import static com.intellij.psi.CommonClassNames.JAVA_LANG_STRING;
public class PsiConcatenationUtil {
/**
* @param formatParameters output parameter, will contain the format parameters found in the concatenation
* @return unescaped (!) format string produced from the concatenation
*/
public static String buildFormatString(PsiExpression concatenation, boolean printfFormat,
List<? super PsiExpression> formatParameters) {
final StringBuilder result = new StringBuilder();
buildFormatString(concatenation, result, formatParameters, printfFormat);
return result.toString();
}
// externally used
public static void buildFormatString(PsiExpression expression, StringBuilder formatString,
List<? super PsiExpression> formatParameters, boolean printfFormat) {
if (expression instanceof PsiLiteralExpression) {
final PsiLiteralExpression literalExpression = (PsiLiteralExpression) expression;
TextRange range = ElementManipulators.getValueTextRange(expression);
String formatText = range.substring(literalExpression.getText());
final String text = String.valueOf(literalExpression.getValue());
String formatText;
if (printfFormat) {
formatText = formatText.replace("%", "%%").replace("\\'", "'");
formatText = text.replace("%", "%%").replace("\\'", "'");
}
else {
formatText = formatText.replace("'", "''").replaceAll("((\\{|})+)", "'$1'");
formatText = text.replace("'", "''").replaceAll("([{}]+)", "'$1'");
}
formatString.append(formatText);
} else if (expression instanceof PsiPolyadicExpression) {
@@ -46,18 +43,21 @@ public class PsiConcatenationUtil {
if (type != null && type.equalsToText(JAVA_LANG_STRING)) {
final PsiPolyadicExpression binaryExpression = (PsiPolyadicExpression) expression;
PsiExpression[] operands = binaryExpression.getOperands();
PsiType left = operands[0].getType();
boolean stringStarted = left != null && left.equalsToText(JAVA_LANG_STRING);
PsiType first = operands[0].getType();
PsiType second = operands[1].getType();
boolean stringStarted = first != null && first.equalsToText(JAVA_LANG_STRING) ||
second != null && second.equalsToText(JAVA_LANG_STRING);
if (stringStarted) {
buildFormatString(operands[0], formatString, formatParameters, printfFormat);
}
for (int i = 1; i < operands.length; i++) {
PsiExpression op = operands[i];
PsiType optype = op.getType();
PsiType r = TypeConversionUtil.calcTypeForBinaryExpression(left, optype, binaryExpression.getOperationTokenType(), true);
PsiType r = TypeConversionUtil.calcTypeForBinaryExpression(first, optype, binaryExpression.getOperationTokenType(), true);
if (r != null && r.equalsToText(JAVA_LANG_STRING) && !stringStarted) {
stringStarted = true;
PsiElement element = binaryExpression.getTokenBeforeOperand(op);
assert element != null;
if (element.getPrevSibling() instanceof PsiWhiteSpace) element = element.getPrevSibling();
String text = binaryExpression.getText().substring(0, element.getStartOffsetInParent());
PsiExpression subExpression = JavaPsiFacade.getElementFactory(binaryExpression.getProject())
@@ -72,7 +72,7 @@ public class PsiConcatenationUtil {
addFormatParameter(op, formatString, formatParameters, printfFormat);
}
}
left = r;
first = r;
}
}
else {
@@ -0,0 +1,5 @@
class C {
private static String quote(String s) {
return java.text.MessageFormat.format("\"{0}\"", s);
}
}
@@ -0,0 +1,5 @@
class C {
private static String quote(String s) {
return '"' + <caret>s + '"';
}
}
@@ -0,0 +1,11 @@
class C {
void x(int a, int b) {
//keep me
String s = java.text.MessageFormat.format("""
the text
block
line2
{0}{1} "to" be""", a, b);
}
}
@@ -0,0 +1,11 @@
class C {
void x(int a, int b) {
String s = """
the text\n block
line2
""" +
a + b + <caret>//keep me
" \"to\" be";
}
}
@@ -1,18 +1,4 @@
/*
* Copyright 2000-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.java.codeInsight.intention;
import com.intellij.JavaTestUtil;
@@ -39,6 +25,8 @@ public class ConcatenationToMessageFormatTest extends JavaCodeInsightFixtureTest
}
public void testComments() { doTest(); }
public void testTextblock() { doTest(); }
public void testEscaping() { doTest(); }
private void doTest() {
final String name = getTestName(true);
@@ -53,14 +41,12 @@ public class ConcatenationToMessageFormatTest extends JavaCodeInsightFixtureTest
private void doTest(String expressionText, String messageFormatText, String... foundExpressionTexts) {
final PsiExpression expression = getElementFactory().createExpressionFromText(expressionText, null);
final StringBuilder result = new StringBuilder();
final ArrayList<PsiExpression> args = new ArrayList<>();
PsiConcatenationUtil.buildFormatString(expression, result, args, false);
assertEquals(messageFormatText, result.toString());
final String formatString = PsiConcatenationUtil.buildFormatString(expression, false, args);
assertEquals(messageFormatText, formatString);
assertEquals(foundExpressionTexts.length, args.size());
for (int i = 0; i < foundExpressionTexts.length; i++) {
final String foundExpressionText = foundExpressionTexts[i];
assertEquals(foundExpressionText, args.get(i).getText());
assertEquals(foundExpressionTexts[i], args.get(i).getText());
}
}
@@ -69,7 +55,7 @@ public class ConcatenationToMessageFormatTest extends JavaCodeInsightFixtureTest
}
public void test2() {
doTest("1 + 2 + 3 + \"{}'\" + '\\n' + ((java.lang.String)ccc)", "{0}'{}'''\\n{1}", "1 + 2 + 3", "ccc");
doTest("1 + 2 + 3 + \"{}'\" + '\\n' + ((java.lang.String)ccc)", "{0}'{}'''\n{1}", "1 + 2 + 3", "ccc");
}
public void test3() {
@@ -1,20 +1,7 @@
/*
* Copyright 2008-2018 Bas Leijdekkers
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.siyeh.ipp.concatenation;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
import com.intellij.psi.impl.source.tree.java.PsiLiteralExpressionImpl;
import com.intellij.psi.util.PsiConcatenationUtil;
@@ -45,9 +32,8 @@ public class ReplaceConcatenationWithFormatStringIntention extends Intention {
expression = (PsiPolyadicExpression)parent;
parent = expression.getParent();
}
final StringBuilder formatString = new StringBuilder();
final List<PsiExpression> formatParameters = new ArrayList<>();
PsiConcatenationUtil.buildFormatString(expression, formatString, formatParameters, true);
final String formatString = PsiConcatenationUtil.buildFormatString(expression, true, formatParameters);
if (replaceWithPrintfExpression(expression, formatString, formatParameters)) {
return;
}
@@ -96,7 +82,7 @@ public class ReplaceConcatenationWithFormatStringIntention extends Intention {
}
final String qualifiedName = containingClass.getQualifiedName();
if (!"java.io.PrintStream".equals(qualifiedName) &&
!"java.io.Printwriter".equals(qualifiedName)) {
!"java.io.PrintWriter".equals(qualifiedName)) {
return false;
}
CommentTracker commentTracker = new CommentTracker();
@@ -106,9 +92,9 @@ public class ReplaceConcatenationWithFormatStringIntention extends Intention {
newExpression.append(commentTracker.text(qualifier)).append('.');
}
newExpression.append("printf(");
appendFormatString(expression, formatString, insertNewline, newExpression);
appendFormatString(expression, formatString.toString(), insertNewline, newExpression);
for (PsiExpression formatParameter : formatParameters) {
newExpression.append(", ").append(commentTracker.text(formatParameter));
newExpression.append(",").append(commentTracker.text(formatParameter));
}
newExpression.append(')');
PsiReplacementUtil.replaceExpression(methodCallExpression, newExpression.toString(), commentTracker);
@@ -116,17 +102,26 @@ public class ReplaceConcatenationWithFormatStringIntention extends Intention {
}
private static void appendFormatString(PsiPolyadicExpression expression,
CharSequence formatString,
String formatString,
boolean insertNewline,
StringBuilder newExpression) {
boolean textBlocks = Arrays.stream(expression.getOperands())
.anyMatch(operand -> operand instanceof PsiLiteralExpressionImpl &&
((PsiLiteralExpressionImpl)operand).getLiteralElementType() == JavaTokenType.TEXT_BLOCK_LITERAL);
newExpression.append(textBlocks ? "\"\"\"\n" : '\"');
newExpression.append(formatString);
if (insertNewline) {
newExpression.append("%n");
if (textBlocks) {
newExpression.append("\"\"\"\n");
newExpression.append(StringUtil.escapeTextBlockCharacters(formatString));
if (insertNewline) {
newExpression.append('\n');
}
newExpression.append("\"\"\"");
} else {
newExpression.append('\"');
newExpression.append(StringUtil.escapeStringCharacters(formatString));
if (insertNewline) {
newExpression.append("%n");
}
newExpression.append('\"');
}
newExpression.append(textBlocks ? "\"\"\"" : '\"');
}
}
@@ -1,7 +1,8 @@
class C {
//keep me
String s = String.format("""
the text\n block
lin<caret>e2
%d%d to be""", 1, 2);
the text
block
line2
%d%d t<caret>o be""", 1, 2);
}
@@ -1,18 +1,4 @@
/*
* Copyright 2000-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.codeInspection.i18n;
import com.intellij.codeInsight.CodeInsightBundle;
@@ -21,6 +7,7 @@ import com.intellij.lang.properties.psi.PropertiesFile;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiConcatenationUtil;
import com.intellij.psi.util.PsiTreeUtil;
@@ -49,6 +36,7 @@ public class I18nizeConcatenationQuickFix extends I18nizeQuickFix{
@Override
public JavaI18nizeQuickFixDialog createDialog(Project project, Editor editor, PsiFile psiFile) {
PsiPolyadicExpression concatenation = getEnclosingLiteralConcatenation(psiFile, editor);
assert concatenation != null;
PsiLiteralExpression literalExpression = getContainingLiteral(concatenation);
if (literalExpression == null) return null;
return createDialog(project, psiFile, literalExpression);
@@ -66,6 +54,7 @@ public class I18nizeConcatenationQuickFix extends I18nizeQuickFix{
@Nullable PsiLiteralExpression literalExpression,
String i18nizedText) throws IncorrectOperationException {
PsiPolyadicExpression concatenation = getEnclosingLiteralConcatenation(psiFile, editor);
assert concatenation != null;
PsiExpression expression = JavaPsiFacade.getInstance(psiFile.getProject()).getElementFactory().createExpressionFromText(i18nizedText, concatenation);
return concatenation.replace(expression);
}
@@ -77,16 +66,16 @@ public class I18nizeConcatenationQuickFix extends I18nizeQuickFix{
@Override
protected JavaI18nizeQuickFixDialog createDialog(final Project project, final PsiFile context, final PsiLiteralExpression literalExpression) {
PsiPolyadicExpression concatenation = getEnclosingLiteralConcatenation(literalExpression);
StringBuilder formatString = new StringBuilder();
String formatString = "";
final List<PsiExpression> args = new ArrayList<>();
try {
PsiConcatenationUtil.buildFormatString(concatenation, formatString, args, false);
formatString = StringUtil.escapeStringCharacters(PsiConcatenationUtil.buildFormatString(concatenation, false, args));
}
catch (IncorrectOperationException e) {
LOG.error(e);
}
return new JavaI18nizeQuickFixDialog(project, context, literalExpression, formatString.toString(), null, true, true) {
return new JavaI18nizeQuickFixDialog(project, context, literalExpression, formatString, null, true, true) {
@Override
@Nullable
protected String getTemplateName() {