From 7d8f8b64dc98ea09734bf0c0b5735cb82a9203b3 Mon Sep 17 00:00:00 2001 From: Tagir Valeev Date: Thu, 2 Feb 2023 11:57:11 +0100 Subject: [PATCH] [java-refactoring] IDEA-215147 Detect common formats of string values when suggesting variable names GitOrigin-RevId: 68731a721306d012e37c01f71be4d66744b2ca9e --- .../codeStyle/JavaCodeStyleManagerImpl.java | 10 ++- .../after8.java | 2 +- .../preview8.java | 2 +- .../util/text/LiteralNameSuggester.java | 61 +++++++++++++++++++ .../util/text/LiteralNameSuggesterTest.java | 46 ++++++++++++++ .../fromJava/classMember.after.Dependency.kt | 2 +- .../companionMember.after.Dependency.kt | 2 +- .../fromJava/topLevel.after.Dependency.kt | 2 +- 8 files changed, 121 insertions(+), 6 deletions(-) create mode 100644 platform/util/base/src/com/intellij/openapi/util/text/LiteralNameSuggester.java create mode 100644 platform/util/testSrc/com/intellij/util/text/LiteralNameSuggesterTest.java 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 63dadfc40957..9d9da59009fd 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 @@ -7,6 +7,7 @@ import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Comparing; import com.intellij.openapi.util.registry.Registry; +import com.intellij.openapi.util.text.LiteralNameSuggester; import com.intellij.openapi.util.text.PastParticiple; import com.intellij.openapi.util.text.StringUtil; import com.intellij.pom.java.LanguageLevel; @@ -24,6 +25,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.siyeh.ig.psiutils.ExpressionUtils; import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; @@ -574,7 +576,13 @@ public class JavaCodeStyleManagerImpl extends JavaCodeStyleManager { @NotNull private NamesByExprInfo suggestVariableNameByExpression(@NotNull PsiExpression expr, @Nullable VariableKind variableKind) { - final LinkedHashSet names = new LinkedHashSet<>(); + List fromLiteral = ExpressionUtils.nonStructuralChildren(expr) + .map(e -> e instanceof PsiLiteralExpression literal && literal.getValue() instanceof String str ? str : null) + .filter(Objects::nonNull) + .flatMap(literal -> LiteralNameSuggester.literalNames(literal).stream()) + .distinct() + .toList(); + final LinkedHashSet names = new LinkedHashSet<>(fromLiteral); ContainerUtil.addIfNotNull(names, suggestVariableNameFromConstant(expr, variableKind)); ContainerUtil.addIfNotNull(names, suggestVariableNameFromLiterals(expr)); diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/changeMethodSignatureFromUsage/after8.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/changeMethodSignatureFromUsage/after8.java index d2f51d79ed8a..9dce6e1b5abc 100644 --- a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/changeMethodSignatureFromUsage/after8.java +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/changeMethodSignatureFromUsage/after8.java @@ -1,6 +1,6 @@ // " Change signature of A(int, int, String)" "true-preview" class A { - A(int i, int i1, String s) { + A(int i, int i1, String number) { new A(1,1,"4"); } } \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/changeMethodSignatureFromUsage/preview8.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/changeMethodSignatureFromUsage/preview8.java index 03ab912318e5..92f06b7f6b0a 100644 --- a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/changeMethodSignatureFromUsage/preview8.java +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/changeMethodSignatureFromUsage/preview8.java @@ -1,3 +1,3 @@ -A(int i, int i1, String s) { +A(int i, int i1, String number) { new A(1,1,"4"); } \ No newline at end of file diff --git a/platform/util/base/src/com/intellij/openapi/util/text/LiteralNameSuggester.java b/platform/util/base/src/com/intellij/openapi/util/text/LiteralNameSuggester.java new file mode 100644 index 000000000000..674e4e5ae8e5 --- /dev/null +++ b/platform/util/base/src/com/intellij/openapi/util/text/LiteralNameSuggester.java @@ -0,0 +1,61 @@ +// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package com.intellij.openapi.util.text; + +import org.intellij.lang.annotations.Language; +import org.jetbrains.annotations.NotNull; + +import java.util.Arrays; +import java.util.List; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +/** + * A utility class to suggest variable names for literals based on their content + */ +public class LiteralNameSuggester { + private static final class PatternBasedSuggestions { + private final @NotNull Pattern pattern; + private final @NotNull List<@NotNull String> names; + + PatternBasedSuggestions(@NotNull @Language("regexp") String pattern, @NotNull String @NotNull ... names) { + this.pattern = Pattern.compile(pattern); + this.names = Arrays.asList(names); + } + } + + private static final PatternBasedSuggestions[] ourPatternBasedSuggestions = { + new PatternBasedSuggestions("(?i)https?://|file:///", "protocol"), + new PatternBasedSuggestions("(?i)(https?://|file:///)\\w.*", "url"), + new PatternBasedSuggestions("(/\\w+){2,}", "path", "filePath", "fileName"), + new PatternBasedSuggestions("(?i)(\\w+)\\.(jpg|jpeg|gif|png|apng)", "image", "img"), + new PatternBasedSuggestions("(?i)(\\w+)\\.(mp4|avi|mov)", "video"), + new PatternBasedSuggestions("(?i)(\\w+)\\.txt", "file", "fileName", "text"), + new PatternBasedSuggestions("text/plain|text/html|text/css|text/javascript|" + + "image/png|image/jpeg|image/gif|image/apng|image/webp|image/svg+xml|" + + "audio/mpeg|audio/webm|video/webm|multipart/form-data|application/json|" + + "application/zip|application/pdf|application/graphql", "contentType"), + new PatternBasedSuggestions(" ", "space", "indent"), + new PatternBasedSuggestions("#\\w+", "hashtag"), + new PatternBasedSuggestions(" {2,}", "spaces", "indent"), + new PatternBasedSuggestions("\t", "tab", "indent"), + new PatternBasedSuggestions("\t{2,}", "tabs", "indent"), + new PatternBasedSuggestions("\n", "lineBreak", "lineFeed", "lf"), + new PatternBasedSuggestions("\r", "lineBreak", "cr"), + new PatternBasedSuggestions("\r\n", "crlf"), + new PatternBasedSuggestions("(?i)[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,4}", "mail", "email"), + new PatternBasedSuggestions("\\d+", "number", "id"), + new PatternBasedSuggestions("(?i)[0-9a-f]{8}\\b-[0-9a-f]{4}\\b-[0-9a-f]{4}\\b-[0-9a-f]{4}\\b-[0-9a-f]{12}", "uuid", "guid"), + new PatternBasedSuggestions("SELECT\\s.*FROM\\s.*", "query", "sql"), + new PatternBasedSuggestions("(19|20)[0-9][0-9]([-/])(0?[1-9]|1[0-2])\\2(0?[1-9]|[12][0-9]|30|31)", "date"), + }; + + /** + * @param literalValue string literal value + * @return suggested names + */ + public static List literalNames(String literalValue) { + return Arrays.stream(ourPatternBasedSuggestions) + .filter(suggestion -> suggestion.pattern.matcher(literalValue).matches()) + .flatMap(suggestion -> suggestion.names.stream()).collect(Collectors.toList()); + } +} diff --git a/platform/util/testSrc/com/intellij/util/text/LiteralNameSuggesterTest.java b/platform/util/testSrc/com/intellij/util/text/LiteralNameSuggesterTest.java new file mode 100644 index 000000000000..38f4f2aee3c9 --- /dev/null +++ b/platform/util/testSrc/com/intellij/util/text/LiteralNameSuggesterTest.java @@ -0,0 +1,46 @@ +// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package com.intellij.util.text; + +import com.intellij.openapi.util.text.LiteralNameSuggester; +import org.junit.Test; + +import java.util.Arrays; +import java.util.List; + +import static org.junit.Assert.assertEquals; + +public class LiteralNameSuggesterTest { + @Test + public void testLiteralNameSuggester() { + String[][] data = { + {"http://xyz", "url"}, + {"https://xyz", "url"}, + {"http://", "protocol"}, + {"file:///", "protocol"}, + {"/etc/passwd", "path", "filePath", "fileName"}, + {"image.jpg", "image", "img"}, + {"PICTURE.PNG", "image", "img"}, + {"movie.mp4", "video"}, + {"test.txt", "file", "fileName", "text"}, + {"application/json", "contentType"}, + {" ", "space", "indent"}, + {" ", "spaces", "indent"}, + {" ", "spaces", "indent"}, + {"\t", "tab", "indent"}, + {"\t\t", "tabs", "indent"}, + {"\n", "lineBreak", "lineFeed", "lf"}, + {"\r", "lineBreak", "cr"}, + {"\r\n", "crlf"}, + {"support@example.com", "mail", "email"}, + {"123", "number", "id"}, + {"123e4567-e89b-12d3-a456-426614174000", "uuid", "guid"}, + {"SELECT * FROM test", "query", "sql"}, + {"2012-12-20", "date"}, + {"2012/2/3", "date"} + }; + for (String[] test : data) { + List strings = LiteralNameSuggester.literalNames(test[0]); + assertEquals(test[0], strings, Arrays.asList(test).subList(1, test.length)); + } + } +} diff --git a/plugins/kotlin/idea/tests/testData/quickfix/createFromUsage/createFunction/fromJava/classMember.after.Dependency.kt b/plugins/kotlin/idea/tests/testData/quickfix/createFromUsage/createFunction/fromJava/classMember.after.Dependency.kt index df3ce83bea7c..0900c3c7530e 100644 --- a/plugins/kotlin/idea/tests/testData/quickfix/createFromUsage/createFunction/fromJava/classMember.after.Dependency.kt +++ b/plugins/kotlin/idea/tests/testData/quickfix/createFromUsage/createFunction/fromJava/classMember.after.Dependency.kt @@ -1,5 +1,5 @@ class K { - fun foo(i: Int, s: String): Boolean { + fun foo(i: Int, number: String): Boolean { TODO("Not yet implemented") } diff --git a/plugins/kotlin/idea/tests/testData/quickfix/createFromUsage/createFunction/fromJava/companionMember.after.Dependency.kt b/plugins/kotlin/idea/tests/testData/quickfix/createFromUsage/createFunction/fromJava/companionMember.after.Dependency.kt index 19dc23de0b58..a75096f4b19e 100644 --- a/plugins/kotlin/idea/tests/testData/quickfix/createFromUsage/createFunction/fromJava/companionMember.after.Dependency.kt +++ b/plugins/kotlin/idea/tests/testData/quickfix/createFromUsage/createFunction/fromJava/companionMember.after.Dependency.kt @@ -1,7 +1,7 @@ class K { companion object { @JvmStatic - fun foo(i: Int, s: String): Boolean { + fun foo(i: Int, number: String): Boolean { TODO("Not yet implemented") } } diff --git a/plugins/kotlin/idea/tests/testData/quickfix/createFromUsage/createFunction/fromJava/topLevel.after.Dependency.kt b/plugins/kotlin/idea/tests/testData/quickfix/createFromUsage/createFunction/fromJava/topLevel.after.Dependency.kt index 99f447658c7e..5457fc05ccd6 100644 --- a/plugins/kotlin/idea/tests/testData/quickfix/createFromUsage/createFunction/fromJava/topLevel.after.Dependency.kt +++ b/plugins/kotlin/idea/tests/testData/quickfix/createFromUsage/createFunction/fromJava/topLevel.after.Dependency.kt @@ -2,6 +2,6 @@ fun test() {} -fun foo(i: Int, s: String): Boolean { +fun foo(i: Int, number: String): Boolean { TODO("Not yet implemented") }