diff --git a/java/java-analysis-impl/src/META-INF/JavaAnalysisPlugin.xml b/java/java-analysis-impl/src/META-INF/JavaAnalysisPlugin.xml index 3158c72445e6..74ed3fc32aac 100644 --- a/java/java-analysis-impl/src/META-INF/JavaAnalysisPlugin.xml +++ b/java/java-analysis-impl/src/META-INF/JavaAnalysisPlugin.xml @@ -241,5 +241,9 @@ + diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/RedundantBackticksAroundRawStringLiteralInspection.java b/java/java-analysis-impl/src/com/intellij/codeInspection/RedundantBackticksAroundRawStringLiteralInspection.java new file mode 100644 index 000000000000..3bfa73766afa --- /dev/null +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/RedundantBackticksAroundRawStringLiteralInspection.java @@ -0,0 +1,57 @@ +// Copyright 2000-2018 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; + +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.TextRange; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.pom.java.LanguageLevel; +import com.intellij.psi.*; +import com.intellij.psi.impl.source.tree.java.PsiLiteralExpressionImpl; +import com.intellij.psi.util.PsiUtil; +import org.jetbrains.annotations.Nls; +import org.jetbrains.annotations.NotNull; + +public class RedundantBackticksAroundRawStringLiteralInspection extends AbstractBaseJavaLocalInspectionTool implements CleanupLocalInspectionTool { + + @NotNull + @Override + public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, boolean isOnTheFly) { + if (PsiUtil.getLanguageLevel(holder.getFile()) != LanguageLevel.JDK_11_PREVIEW) { + return PsiElementVisitor.EMPTY_VISITOR; + } + return new JavaElementVisitor() { + @Override + public void visitLiteralExpression(PsiLiteralExpression expression) { + if (((PsiLiteralExpressionImpl)expression).getLiteralElementType() == JavaTokenType.RAW_STRING_LITERAL) { + String text = expression.getText(); + String rawString = ((PsiLiteralExpressionImpl)expression).getRawString(); + int reducedNumberOfBackTicks = PsiRawStringLiteralUtil.getReducedNumberOfBackticks(text); + if (reducedNumberOfBackTicks > 0) { + String newBackticksSequence = StringUtil.repeat("`", reducedNumberOfBackTicks); + int redundantTicksLength = (text.length() - rawString.length()) / 2 - reducedNumberOfBackTicks; + holder.registerProblem(expression, "Number of backticks may be reduced by " + redundantTicksLength, + ProblemHighlightType.LIKE_UNKNOWN_SYMBOL, + new TextRange(0, redundantTicksLength), + new LocalQuickFix() { + @Nls + @NotNull + @Override + public String getFamilyName() { + return "Reduce number of backticks"; + } + + @Override + public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) { + PsiElement element = descriptor.getPsiElement(); + PsiExpression newRawStringLiteral = JavaPsiFacade.getElementFactory(project) + .createExpressionFromText( + newBackticksSequence + rawString + newBackticksSequence, element); + element.replace(newRawStringLiteral); + } + }); + } + } + } + }; + } +} diff --git a/java/java-impl/src/inspectionDescriptions/RedundantBackticksAroundRawStringLiteral.html b/java/java-impl/src/inspectionDescriptions/RedundantBackticksAroundRawStringLiteral.html new file mode 100644 index 000000000000..6fb5209f1fb7 --- /dev/null +++ b/java/java-impl/src/inspectionDescriptions/RedundantBackticksAroundRawStringLiteral.html @@ -0,0 +1,5 @@ + + +This inspection reports raw string literals when the number of backticks around the value may be reduced. + + \ No newline at end of file diff --git a/java/java-psi-api/src/com/intellij/psi/PsiRawStringLiteralUtil.java b/java/java-psi-api/src/com/intellij/psi/PsiRawStringLiteralUtil.java index b460347ec55d..22c7837add9f 100644 --- a/java/java-psi-api/src/com/intellij/psi/PsiRawStringLiteralUtil.java +++ b/java/java-psi-api/src/com/intellij/psi/PsiRawStringLiteralUtil.java @@ -2,6 +2,7 @@ package com.intellij.psi; import com.intellij.openapi.util.text.StringUtil; +import gnu.trove.TIntHashSet; public class PsiRawStringLiteralUtil { /** @@ -48,4 +49,35 @@ public class PsiRawStringLiteralUtil { while (quotesLength < length && literalText.charAt(quotesLength) == '`') quotesLength++; return quotesLength - startIndex; } + + /** + * For given raw string literal text (with backticks) returns minimal number of backticks required for string content + * @return number less than current number of backticks, + * -1 otherwise + */ + public static int getReducedNumberOfBackticks(String text) { + int leadingTicsSequence = getLeadingTicsSequence(text); + int trailingTicsSequence = getTrailingTicsSequence(text); + if (leadingTicsSequence == trailingTicsSequence && leadingTicsSequence > 1) { + int length = text.length() - trailingTicsSequence; + int idx = leadingTicsSequence; + TIntHashSet usedTicSequences = new TIntHashSet(); + usedTicSequences.add(leadingTicsSequence); + while (idx < length) { + idx = text.indexOf("`", idx); + if (idx < 0) break; + int ticsSequence = getTicsSequence(text, length, idx); + usedTicSequences.add(ticsSequence); + idx += ticsSequence; + } + + for (int i = 1; i < leadingTicsSequence; i++) { + if (!usedTicSequences.contains(i)) { + return i; + } + } + } + return -1; + } + } diff --git a/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/LightAdvRawStringLiteralsTest.java b/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/LightAdvRawStringLiteralsTest.java index 5cad9f53f6c3..bafd7b302717 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/LightAdvRawStringLiteralsTest.java +++ b/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/LightAdvRawStringLiteralsTest.java @@ -4,6 +4,7 @@ package com.intellij.java.codeInsight.daemon; import com.intellij.JavaTestUtil; import com.intellij.codeInsight.daemon.QuickFixBundle; import com.intellij.codeInsight.intention.IntentionAction; +import com.intellij.codeInspection.RedundantBackticksAroundRawStringLiteralInspection; import com.intellij.openapi.actionSystem.IdeActions; import com.intellij.psi.JavaPsiFacade; import com.intellij.psi.PsiElementFactory; @@ -97,6 +98,23 @@ public class LightAdvRawStringLiteralsTest extends LightCodeInsightFixtureTestCa assertEquals("abc", ((PsiLiteralExpressionImpl)rawStringLiteral).getRawString()); } + public void testReduceNumberOfBackticks() { + doTestRedundantBackticks("class A {{String s = ```a`b```;}}", "class A {{String s = ``a`b``;}}"); + } + + public void testReduceNumberOfBackticksSimple() { + doTestRedundantBackticks("class A {{String s = ```a``b```;}}", "class A {{String s = `a``b`;}}"); + } + + private void doTestRedundantBackticks(String beforeText, String afterText) { + myFixture.configureByText("a.java", beforeText); + myFixture.enableInspections(new RedundantBackticksAroundRawStringLiteralInspection()); + IntentionAction reduceNumberOfBackticks = myFixture.getAvailableIntention("Reduce number of backticks"); + assertNotNull(reduceNumberOfBackticks); + myFixture.launchAction(reduceNumberOfBackticks); + myFixture.checkResult(afterText); + } + public void testTypingOpeningTic() { myFixture.configureByFile(getTestName(false ) + ".java"); myFixture.type('`'); diff --git a/platform/platform-resources-en/src/messages/InspectionsBundle.properties b/platform/platform-resources-en/src/messages/InspectionsBundle.properties index 9c62f3eefe7b..cdb0c8b22f9d 100644 --- a/platform/platform-resources-en/src/messages/InspectionsBundle.properties +++ b/platform/platform-resources-en/src/messages/InspectionsBundle.properties @@ -577,6 +577,7 @@ group.names.language.level.specific.issues.and.migration.aids8=Java 8 group.names.language.level.specific.issues.and.migration.aids9=Java 9 group.names.language.level.specific.issues.and.migration.aids5=Java 5 group.names.language.level.specific.issues.and.migration.aids10=Java 10 +group.names.language.level.specific.issues.and.migration.aids11=Java 11 group.names.javabeans.issues=JavaBeans issues group.names.inheritance.issues=Inheritance issues group.names.data.flow.issues=Data flow @@ -978,3 +979,4 @@ inspection.class.getclass.display.name=Class.getClass() call inspection.class.getclass.message='getClass()' is called on Class instance inspection.class.getclass.fix.remove.name=Remove 'getClass()' call inspection.class.getclass.fix.replace.name=Replace with 'Class.class' +redundant.backticks.around.raw.string.literal.display.name=Redundant backticks around raw string literal