diff --git a/java/java-impl-refactorings/src/com/intellij/refactoring/util/InlineUtil.java b/java/java-impl-refactorings/src/com/intellij/refactoring/util/InlineUtil.java index 111f60a3bb15..e37865d128a3 100644 --- a/java/java-impl-refactorings/src/com/intellij/refactoring/util/InlineUtil.java +++ b/java/java-impl-refactorings/src/com/intellij/refactoring/util/InlineUtil.java @@ -30,7 +30,7 @@ import com.intellij.util.CommonJavaRefactoringUtil; import com.intellij.util.IncorrectOperationException; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.MultiMap; -import com.intellij.util.text.NameUtilCore; +import com.intellij.util.text.UniqueNameGenerator; import com.siyeh.ig.psiutils.*; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -349,12 +349,12 @@ public final class InlineUtil implements CommonJavaInlineUtil { private static @NotNull String suggestClassName(@NotNull PsiElement place, @NotNull String name) { PsiResolveHelper helper = PsiResolveHelper.getInstance(place.getProject()); - return NameUtilCore.uniqName( + return UniqueNameGenerator.generateUniqueNameOneBased( name, - n -> helper.resolveReferencedClass(n, place) != null || - place instanceof PsiClass && place.getParent() instanceof PsiDeclarationStatement decl && - decl.getParent() instanceof PsiCodeBlock block && - SyntaxTraverser.psiTraverser(block).filter(PsiClass.class).find(cls -> n.equals(cls.getName())) != null); + n -> helper.resolveReferencedClass(n, place) == null && + !(place instanceof PsiClass && place.getParent() instanceof PsiDeclarationStatement decl && + decl.getParent() instanceof PsiCodeBlock block && + SyntaxTraverser.psiTraverser(block).filter(PsiClass.class).find(cls -> n.equals(cls.getName())) != null)); } public static boolean isChainingConstructor(PsiMethod constructor) { diff --git a/java/java-impl/src/com/intellij/psi/impl/source/codeStyle/JavaCodeStyleManagerImpl.java b/java/java-impl/src/com/intellij/psi/impl/source/codeStyle/JavaCodeStyleManagerImpl.java index 55c6b0b932b2..f1d620f8a5c3 100644 --- a/java/java-impl/src/com/intellij/psi/impl/source/codeStyle/JavaCodeStyleManagerImpl.java +++ b/java/java-impl/src/com/intellij/psi/impl/source/codeStyle/JavaCodeStyleManagerImpl.java @@ -28,6 +28,7 @@ import com.intellij.util.BitUtil; import com.intellij.util.IncorrectOperationException; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.text.NameUtilCore; +import com.intellij.util.text.UniqueNameGenerator; import com.siyeh.ig.psiutils.ExpressionUtils; import com.siyeh.ig.psiutils.MethodCallUtils; import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet; @@ -1089,8 +1090,9 @@ public class JavaCodeStyleManagerImpl extends JavaCodeStyleManager { boolean allowShadowing, Predicate canBeReused) { PsiElement scope = PsiTreeUtil.getNonStrictParentOfType(place, PsiStatement.class, PsiCodeBlock.class, PsiMethod.class); - return NameUtilCore.uniqName(baseName, name -> hasConflictingVariable(place, name, allowShadowing) || - lookForward && hasConflictingVariableAfterwards(scope, name, canBeReused)); + return UniqueNameGenerator.generateUniqueNameOneBased( + baseName, name -> !hasConflictingVariable(place, name, allowShadowing) && + (!lookForward || !hasConflictingVariableAfterwards(scope, name, canBeReused))); } private static boolean hasConflictingVariable(@Nullable PsiElement place, @NotNull String name, boolean allowShadowing) { diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/anonymous2lambda/afterNameConflictsWithNestedLambda.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/anonymous2lambda/afterNameConflictsWithNestedLambda.java index a45bcb73382d..17138de64b4b 100644 --- a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/anonymous2lambda/afterNameConflictsWithNestedLambda.java +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/anonymous2lambda/afterNameConflictsWithNestedLambda.java @@ -4,8 +4,8 @@ import java.util.function.Consumer; class A { void anonymousToLambda(String s) { - String s12 = ""; - Consumer consumer = s13 -> { + String s2 = ""; + Consumer consumer = s3 -> { String s1 = ""; }; } diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/anonymous2lambda/beforeNameConflictsWithNestedLambda.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/anonymous2lambda/beforeNameConflictsWithNestedLambda.java index 1258bec3139b..d0c21902db8a 100644 --- a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/anonymous2lambda/beforeNameConflictsWithNestedLambda.java +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/anonymous2lambda/beforeNameConflictsWithNestedLambda.java @@ -4,7 +4,7 @@ import java.util.function.Consumer; class A { void anonymousToLambda(String s) { - String s12 = ""; + String s2 = ""; Consumer consumer = new Consumer() { @Override public void accept(final String s) { diff --git a/platform/platform-tests/testSrc/com/intellij/psi/NameUtilTest.java b/platform/platform-tests/testSrc/com/intellij/psi/NameUtilTest.java index 61cfdd90c1b2..a408588d01bf 100644 --- a/platform/platform-tests/testSrc/com/intellij/psi/NameUtilTest.java +++ b/platform/platform-tests/testSrc/com/intellij/psi/NameUtilTest.java @@ -6,7 +6,6 @@ import com.intellij.util.text.NameUtilCore; import org.junit.Test; import java.util.Arrays; -import java.util.Set; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; @@ -73,14 +72,4 @@ public class NameUtilTest { final String[] result = NameUtil.splitNameIntoWords(name); assertEquals(Arrays.asList(expected).toString(), Arrays.asList(result).toString()); } - - @Test - public void testUniqName() { - assertEquals("best", NameUtilCore.uniqName("best", Set.of("test", "test1")::contains)); - assertEquals("test1", NameUtilCore.uniqName("test", Set.of("test")::contains)); - assertEquals("test2", NameUtilCore.uniqName("test", Set.of("test", "test1")::contains)); - assertEquals("test3", NameUtilCore.uniqName("test", Set.of("test", "test1", "test2")::contains)); - assertEquals("test3", NameUtilCore.uniqName("test1", Set.of("test", "test1", "test2")::contains)); - assertEquals("test3", NameUtilCore.uniqName("test2", Set.of("test", "test1", "test2")::contains)); - } } diff --git a/platform/util/api-dump-unreviewed.txt b/platform/util/api-dump-unreviewed.txt index 211810e1f9a7..751029a3f361 100644 --- a/platform/util/api-dump-unreviewed.txt +++ b/platform/util/api-dump-unreviewed.txt @@ -6898,6 +6898,7 @@ c:com.intellij.util.text.UniqueNameGenerator - s:generateUniqueName(java.lang.String,java.lang.String,java.lang.String,java.lang.String,java.lang.String,com.intellij.openapi.util.Condition):java.lang.String - s:generateUniqueName(java.lang.String,java.lang.String,java.lang.String,java.util.Collection):java.lang.String - s:generateUniqueName(java.lang.String,java.util.Collection):java.lang.String +- s:generateUniqueNameOneBased(java.lang.String,com.intellij.openapi.util.Condition):java.lang.String - f:isUnique(java.lang.String):Z - f:isUnique(java.lang.String,java.lang.String,java.lang.String):Z - f:value(java.lang.String):Z diff --git a/platform/util/base/api-dump.txt b/platform/util/base/api-dump.txt index 6f4693445d2f..03277c2f388b 100644 --- a/platform/util/base/api-dump.txt +++ b/platform/util/base/api-dump.txt @@ -673,7 +673,6 @@ f:com.intellij.util.text.NameUtilCore - s:nameToWords(java.lang.String):java.lang.String[] - s:nextWord(java.lang.String,I):I - s:splitNameIntoWords(java.lang.String):java.lang.String[] -- s:uniqName(java.lang.String,java.util.function.Predicate):java.lang.String f:com.intellij.util.text.SemVer - java.lang.Comparable - (java.lang.String,I,I,I):V diff --git a/platform/util/base/src/com/intellij/util/text/NameUtilCore.java b/platform/util/base/src/com/intellij/util/text/NameUtilCore.java index b3feccd48ee9..fc93a6de0d82 100644 --- a/platform/util/base/src/com/intellij/util/text/NameUtilCore.java +++ b/platform/util/base/src/com/intellij/util/text/NameUtilCore.java @@ -7,8 +7,6 @@ import org.jetbrains.annotations.NotNull; import java.util.ArrayList; import java.util.List; -import java.util.function.Predicate; -import java.util.regex.Pattern; public final class NameUtilCore { @@ -170,29 +168,4 @@ public final class NameUtilCore { } return ArrayUtilRt.toStringArray(array); } - - /** - * Generates a unique name - * - * @param origName original symbol name - * @param alreadyUsed a predicate that returns true if a supplied name is already used and we cannot use it - * @return the name based on the origName, which is definitely not used (typically by adding a numeric suffix) - */ - public static @NotNull String uniqName(@NotNull String origName, @NotNull Predicate<@NotNull String> alreadyUsed) { - if (!alreadyUsed.test(origName)) return origName; - String baseName = origName; - int index = 0; - Pattern pattern = Pattern.compile("(.+?)(\\d+)"); - java.util.regex.Matcher matcher = pattern.matcher(baseName); - if (matcher.matches()) { - baseName = matcher.group(1); - index = Integer.parseInt(matcher.group(2)); - } - while (true) { - String name = baseName + (++index); - if (!alreadyUsed.test(name)) { - return name; - } - } - } } diff --git a/platform/util/src/com/intellij/util/text/UniqueNameGenerator.java b/platform/util/src/com/intellij/util/text/UniqueNameGenerator.java index 3c435c4591dd..af5abb15083c 100644 --- a/platform/util/src/com/intellij/util/text/UniqueNameGenerator.java +++ b/platform/util/src/com/intellij/util/text/UniqueNameGenerator.java @@ -11,6 +11,7 @@ import org.jetbrains.annotations.Nullable; import java.util.Collection; import java.util.HashSet; import java.util.Set; +import java.util.regex.Pattern; public class UniqueNameGenerator implements Condition { private final Set myExistingNames = new HashSet<>(); @@ -45,26 +46,78 @@ public class UniqueNameGenerator implements Condition { return generateUniqueName(defaultName, prefix, suffix, s -> !existingNames.contains(s)); } + /** + * Generates a unique name. Derived names are numbered starting from 2. + * + * @param defaultName original symbol name + * @param validator a predicate that returns false if a supplied name is already used and we cannot use it + * @return the name based on the defaultName, which is definitely not used (typically by adding a numeric suffix) + */ public static @NlsSafe @NotNull String generateUniqueName(@NotNull String defaultName, @NotNull Condition validator) { return generateUniqueName(defaultName, "", "", validator); } + /** + * Generates a unique name. Derived names are numbered starting from 1. + * + * @param defaultName original symbol name + * @param validator a predicate that returns false if a supplied name is already used and we cannot use it + * @return the name based on the defaultName, which is definitely not used (typically by adding a numeric suffix) + */ + public static @NlsSafe @NotNull String generateUniqueNameOneBased(@NotNull String defaultName, @NotNull Condition validator) { + return generateUniqueName(defaultName, "", "", "", "", validator, 1); + } + + /** + * Generates a unique name + * + * @param defaultName original symbol name + * @param prefix prefix to add before defaultName + * @param suffix suffix to add after defaultName + * @param validator a predicate that returns false if a supplied name is already used and we cannot use it + * @return the name based on the defaultName, which is definitely not used (typically by adding a numeric suffix) + */ public static @NlsSafe @NotNull String generateUniqueName(@NotNull String defaultName, @NotNull String prefix, @NotNull String suffix, @NotNull Condition validator) { return generateUniqueName(defaultName, prefix, suffix, "", "", validator); } + /** + * Generates a unique name + * + * @param defaultName original symbol name + * @param prefix prefix to add before defaultName + * @param suffix suffix to add after defaultName + * @param beforeNumber infix to separate defaultName and number + * @param afterNumber infix to separate number and suffix + * @param validator a predicate that returns false if a supplied name is already used and we cannot use it + * @return the name based on the defaultName, which is definitely not used (typically by adding a numeric suffix) + */ public static @NlsSafe @NotNull String generateUniqueName(@NotNull String defaultName, @NotNull String prefix, @NotNull String suffix, @NotNull String beforeNumber, @NotNull String afterNumber, @NotNull Condition validator) { + return generateUniqueName(defaultName, prefix, suffix, beforeNumber, afterNumber, validator, 2); + } + + private static @NlsSafe @NotNull String generateUniqueName(@NotNull String defaultName, @NotNull String prefix, @NotNull String suffix, + @NotNull String beforeNumber, @NotNull String afterNumber, + @NotNull Condition validator, int startingNumber) { String defaultFullName = (prefix + defaultName + suffix).trim(); if (validator.value(defaultFullName)) { return defaultFullName; } - for (int i = 2; ; i++) { - String fullName = (prefix + defaultName + beforeNumber + i + afterNumber + suffix).trim(); - if (validator.value(fullName)) { - return fullName; + String baseName = defaultName; + Pattern pattern = Pattern.compile("(.+?)" + Pattern.quote(beforeNumber) + "(\\d{1,9})"); + java.util.regex.Matcher matcher = pattern.matcher(baseName); + int index = startingNumber; + if (matcher.matches()) { + baseName = matcher.group(1); + index = Integer.parseInt(matcher.group(2)) + 1; + } + while (true) { + String name = (prefix + baseName + beforeNumber + (index++) + afterNumber + suffix).trim(); + if (validator.value(name)) { + return name; } } } diff --git a/platform/util/testSrc/com/intellij/util/text/UniqueNameGeneratorTest.java b/platform/util/testSrc/com/intellij/util/text/UniqueNameGeneratorTest.java index 238a3f7e5f19..00399b724f75 100644 --- a/platform/util/testSrc/com/intellij/util/text/UniqueNameGeneratorTest.java +++ b/platform/util/testSrc/com/intellij/util/text/UniqueNameGeneratorTest.java @@ -3,6 +3,8 @@ package com.intellij.util.text; import org.junit.Test; +import java.util.Set; + import static org.junit.Assert.assertEquals; /** @@ -32,6 +34,18 @@ public class UniqueNameGeneratorTest { assertEquals("qwerty2", generator.generateUniqueName("qwerty")); assertEquals("qwerty3", generator.generateUniqueName("qwerty")); } + @Test + public void testOneBased() { + assertEquals("xyz1", UniqueNameGenerator.generateUniqueNameOneBased("xyz", x -> !"xyz".equals(x))); + } + + @Test + public void testStartingNumber() { + assertEquals("xyz129", UniqueNameGenerator.generateUniqueName("xyz128", Set.of("xyz128"))); + assertEquals("xyz130", UniqueNameGenerator.generateUniqueName("xyz128", Set.of("xyz128", "xyz129"))); + assertEquals("xyz123_2", UniqueNameGenerator.generateUniqueName("xyz123", "", "", "_", "", s -> !s.equals("xyz123"))); + assertEquals("xyz_124", UniqueNameGenerator.generateUniqueName("xyz_123", "", "", "_", "", s -> !s.equals("xyz_123"))); + } @Test public void testPrefixSuffix() {