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