diff --git a/java/java-impl/src/META-INF/JavaPlugin.xml b/java/java-impl/src/META-INF/JavaPlugin.xml index e257efe714ac..c9cfbae08974 100644 --- a/java/java-impl/src/META-INF/JavaPlugin.xml +++ b/java/java-impl/src/META-INF/JavaPlugin.xml @@ -765,6 +765,11 @@ groupKey="group.names.language.level.specific.issues.and.migration.aids11" enabledByDefault="true" level="WARNING" implementationClass="com.intellij.codeInspection.ReadWriteStringCanBeUsedInspection" displayName="'Files.readString()' or 'Files.writeString()' can be used"/> + + +Reports loops which could be replaced with single String.repeat() method (available since Java 11). E.g.: +
+  for(int i=0; i<count; i++) {
+    sb.append('*');
+  }
+
+Such code could be replaced with sb.append("*".repeat(count)); + +

+ By default the inspection may wrap count expression with Math.max(0, count) if it cannot prove statically that count is + non-negative. This is done to prevent possible semantics change, as String.repeat() rejects negative numbers. + You may disable this behavior via option if you don't like it. Similarly a String to repeat could be wrapped with + String.valueOf to prevent possible NullPointerException if it's unknown whether it could be null. +

+

New in 2019.1

+ + \ No newline at end of file diff --git a/java/java-tests/testData/inspection/stringRepeat/afterRepeat.java b/java/java-tests/testData/inspection/stringRepeat/afterRepeat.java new file mode 100644 index 000000000000..53aaedde4b1b --- /dev/null +++ b/java/java-tests/testData/inspection/stringRepeat/afterRepeat.java @@ -0,0 +1,8 @@ +// "Replace with 'String.repeat()'" "true" +class Test { + String hundredSpaces() { + StringBuilder sb = new StringBuilder(); + sb.append(" ".repeat(100)); + return sb.toString(); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/stringRepeat/afterRepeatDiff.java b/java/java-tests/testData/inspection/stringRepeat/afterRepeatDiff.java new file mode 100644 index 000000000000..67c35631b1a5 --- /dev/null +++ b/java/java-tests/testData/inspection/stringRepeat/afterRepeatDiff.java @@ -0,0 +1,8 @@ +// "Replace with 'String.repeat()'" "true" +class Test { + String spaces(int a, int b, int c, int d) { + StringBuilder sb = new StringBuilder(); + sb.append(" ".repeat(Math.max(0, c - d - (a - b)))); + return sb.toString(); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/stringRepeat/afterRepeatInclusive.java b/java/java-tests/testData/inspection/stringRepeat/afterRepeatInclusive.java new file mode 100644 index 000000000000..f03f764533ee --- /dev/null +++ b/java/java-tests/testData/inspection/stringRepeat/afterRepeatInclusive.java @@ -0,0 +1,8 @@ +// "Replace with 'String.repeat()'" "true" +class Test { + String spaces(int c, int d) { + StringBuilder sb = new StringBuilder(); + sb.append("123456".repeat(Math.max(0, c - d))); + return sb.toString(); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/stringRepeat/afterRepeatNonConstant.java b/java/java-tests/testData/inspection/stringRepeat/afterRepeatNonConstant.java new file mode 100644 index 000000000000..afedc5f60175 --- /dev/null +++ b/java/java-tests/testData/inspection/stringRepeat/afterRepeatNonConstant.java @@ -0,0 +1,8 @@ +// "Replace with 'String.repeat()'" "true" +class Test { + String hundredTimes(String s) { + StringBuilder sb = new StringBuilder(); + sb.append(String.valueOf(s).repeat(100)); + return sb.toString(); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/stringRepeat/afterRepeatNonConstantNullChecked.java b/java/java-tests/testData/inspection/stringRepeat/afterRepeatNonConstantNullChecked.java new file mode 100644 index 000000000000..d2a164eb1e45 --- /dev/null +++ b/java/java-tests/testData/inspection/stringRepeat/afterRepeatNonConstantNullChecked.java @@ -0,0 +1,11 @@ +// "Replace with 'String.repeat()'" "true" +import java.util.Objects; + +class Test { + String hundredTimes(String s) { + Objects.requireNonNull(s); + StringBuilder sb = new StringBuilder(); + sb.append(s.repeat(100)); + return sb.toString(); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/stringRepeat/beforeRepeat.java b/java/java-tests/testData/inspection/stringRepeat/beforeRepeat.java new file mode 100644 index 000000000000..9531ed7432b4 --- /dev/null +++ b/java/java-tests/testData/inspection/stringRepeat/beforeRepeat.java @@ -0,0 +1,10 @@ +// "Replace with 'String.repeat()'" "true" +class Test { + String hundredSpaces() { + StringBuilder sb = new StringBuilder(); + for(int i=0; i<100; i++) { + sb.append(" "); + } + return sb.toString(); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/stringRepeat/beforeRepeatDiff.java b/java/java-tests/testData/inspection/stringRepeat/beforeRepeatDiff.java new file mode 100644 index 000000000000..1db4beb790d1 --- /dev/null +++ b/java/java-tests/testData/inspection/stringRepeat/beforeRepeatDiff.java @@ -0,0 +1,10 @@ +// "Replace with 'String.repeat()'" "true" +class Test { + String spaces(int a, int b, int c, int d) { + StringBuilder sb = new StringBuilder(); + for(int i=a-b; ior(int i=1; i<=c-d; i++) { + sb.append(123_456); + } + return sb.toString(); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/stringRepeat/beforeRepeatNonConstant.java b/java/java-tests/testData/inspection/stringRepeat/beforeRepeatNonConstant.java new file mode 100644 index 000000000000..30da59557c27 --- /dev/null +++ b/java/java-tests/testData/inspection/stringRepeat/beforeRepeatNonConstant.java @@ -0,0 +1,10 @@ +// "Replace with 'String.repeat()'" "true" +class Test { + String hundredTimes(String s) { + StringBuilder sb = new StringBuilder(); + for(int i=0; i<100; i++) { + sb.append(s); + } + return sb.toString(); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/stringRepeat/beforeRepeatNonConstantNullChecked.java b/java/java-tests/testData/inspection/stringRepeat/beforeRepeatNonConstantNullChecked.java new file mode 100644 index 000000000000..49b087ad83dc --- /dev/null +++ b/java/java-tests/testData/inspection/stringRepeat/beforeRepeatNonConstantNullChecked.java @@ -0,0 +1,13 @@ +// "Replace with 'String.repeat()'" "true" +import java.util.Objects; + +class Test { + String hundredTimes(String s) { + Objects.requireNonNull(s); + StringBuilder sb = new StringBuilder(); + for(int i=0; i<100; i++) { + sb.append(s); + } + return sb.toString(); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/stringRepeat/beforeRepeatSideEffect.java b/java/java-tests/testData/inspection/stringRepeat/beforeRepeatSideEffect.java new file mode 100644 index 000000000000..3a111fd4ee8c --- /dev/null +++ b/java/java-tests/testData/inspection/stringRepeat/beforeRepeatSideEffect.java @@ -0,0 +1,10 @@ +// "Replace with 'String.repeat()'" "false" +class Test { + String hundredNumbers() { + StringBuilder sb = new StringBuilder(); + for(int i=0; i<100; i++) { + sb.append(Math.random()); + } + return sb.toString(); + } +} \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/java/codeInspection/StringRepeatCanBeUsedInspectionTest.java b/java/java-tests/testSrc/com/intellij/java/codeInspection/StringRepeatCanBeUsedInspectionTest.java new file mode 100644 index 000000000000..24167ba5dede --- /dev/null +++ b/java/java-tests/testSrc/com/intellij/java/codeInspection/StringRepeatCanBeUsedInspectionTest.java @@ -0,0 +1,33 @@ +// 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.codeInspection; + +import com.intellij.codeInsight.daemon.quickFix.LightQuickFixParameterizedTestCase; +import com.intellij.codeInspection.LocalInspectionTool; +import com.intellij.codeInspection.StringRepeatCanBeUsedInspection; +import com.intellij.openapi.projectRoots.Sdk; +import com.intellij.pom.java.LanguageLevel; +import com.intellij.testFramework.IdeaTestUtil; +import org.jetbrains.annotations.NotNull; + +public class StringRepeatCanBeUsedInspectionTest extends LightQuickFixParameterizedTestCase { + @Override + protected LanguageLevel getDefaultLanguageLevel() { + return LanguageLevel.JDK_11; + } + + @NotNull + @Override + protected LocalInspectionTool[] configureLocalInspectionTools() { + return new LocalInspectionTool[]{new StringRepeatCanBeUsedInspection()}; + } + + @Override + protected Sdk getProjectJDK() { + return IdeaTestUtil.getMockJdk9(); + } + + @Override + protected String getBasePath() { + return "/inspection/stringRepeat"; + } +}