diff --git a/java/java-impl/src/com/intellij/find/findUsages/similarity/JavaSimilarityFeaturesExtractor.java b/java/java-impl/src/com/intellij/find/findUsages/similarity/JavaSimilarityFeaturesExtractor.java index 7bd6fdddc68c..bf560c09cc35 100644 --- a/java/java-impl/src/com/intellij/find/findUsages/similarity/JavaSimilarityFeaturesExtractor.java +++ b/java/java-impl/src/com/intellij/find/findUsages/similarity/JavaSimilarityFeaturesExtractor.java @@ -8,9 +8,13 @@ import com.intellij.psi.util.MethodSignature; import com.intellij.usages.similarity.bag.Bag; import com.intellij.usages.similarity.features.UsageSimilarityFeaturesRecorder; import com.intellij.util.ObjectUtils; +import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.Arrays; +import java.util.HashSet; + import java.util.stream.IntStream; import static com.intellij.psi.JavaTokenType.FINAL_KEYWORD; @@ -18,11 +22,13 @@ import static com.intellij.psi.JavaTokenType.FINAL_KEYWORD; public class JavaSimilarityFeaturesExtractor extends JavaRecursiveElementVisitor { private final @NotNull UsageSimilarityFeaturesRecorder myUsageSimilarityFeaturesRecorder; private final @NotNull PsiElement myContext; + private final @NotNull HashSet myVariableNames; private final @NotNull PsiElement myUsage; public JavaSimilarityFeaturesExtractor(@NotNull PsiElement usage, @NotNull PsiElement context) { myUsageSimilarityFeaturesRecorder = new UsageSimilarityFeaturesRecorder(context, usage); myContext = context; + myVariableNames = collectVariableNames(); myUsage = usage; } @@ -135,6 +141,9 @@ public class JavaSimilarityFeaturesExtractor extends JavaRecursiveElementVisitor @Override public void visitReferenceExpression(@NotNull PsiReferenceExpression expression) { + if (Registry.is("similarity.find.usages.add.features.for.fields") && isField(expression)) { + myUsageSimilarityFeaturesRecorder.addAllFeatures(expression, "FIELD: " + expression.getReferenceName()); + } if (!(expression instanceof PsiMethodReferenceExpression)) { if (!Registry.is("similarity.find.usages.fast.clustering")) { myUsageSimilarityFeaturesRecorder.addAllFeatures(expression, getTypeRepresentation(expression)); @@ -236,6 +245,96 @@ public class JavaSimilarityFeaturesExtractor extends JavaRecursiveElementVisitor } } + private boolean isField(@NotNull PsiReferenceExpression referenceExpression) { + return !isMethod(referenceExpression) && + (ContainerUtil.exists(referenceExpression.getChildren(), child -> child.getText().equals(".")) || + !myVariableNames.contains(referenceExpression.getReferenceName())); + } + + private @NotNull HashSet collectVariableNames() { + PsiMethod containingMethod = getWrappingMethodForUsage(myContext); + if (containingMethod == null) return new HashSet<>(); + PsiCodeBlock body = containingMethod.getBody(); + if (body == null) return new HashSet<>(); + HashSet allVariables = new HashSet<>(); + allVariables.addAll(processFunctionParameters(containingMethod)); + allVariables.addAll(processFunctionStatements(body)); + return allVariables; + } + + private static boolean isMethod(@NotNull PsiReferenceExpression expression) { + return expression.getParent() instanceof PsiMethodCallExpression && + expression.getNextSibling() instanceof PsiExpressionList; + } + + private static @Nullable PsiMethod getWrappingMethodForUsage(@NotNull PsiElement usage) { + while (!(usage instanceof PsiMethod)) { + usage = usage.getParent(); + if(usage instanceof PsiFile || usage == null) return null; + } + return (PsiMethod)usage; + } + + private static @NotNull HashSet processFunctionParameters(@NotNull PsiMethod containingMethod) { + HashSet variableNames = new HashSet<>(); + Arrays.stream(containingMethod.getParameterList().getParameters()) + .forEach(parameter -> variableNames.add(parameter.getName())); + return variableNames; + } + + private static @NotNull HashSet processFunctionStatements(@NotNull PsiCodeBlock body) { + HashSet variableNames = new HashSet<>(); + for (PsiStatement statement : body.getStatements()) { + if (statement instanceof PsiDeclarationStatement) { + variableNames.addAll(processDeclarationStatement((PsiDeclarationStatement)statement)); + continue; + } + + if (statement instanceof PsiLoopStatement) { + variableNames.addAll(processLoopStatement(statement)); + continue; + } + + if (statement instanceof PsiIfStatement) { + Arrays.stream(statement.getChildren()) + .filter(child -> child instanceof PsiBlockStatement) + .forEach(blockStatement -> variableNames + .addAll(processFunctionStatements(((PsiBlockStatement)blockStatement).getCodeBlock()))); + } + } + return variableNames; + } + + private static @NotNull HashSet processLoopStatement(@NotNull PsiStatement statement) { + HashSet variableNames = new HashSet<>(); + if (statement instanceof PsiForeachStatement) { + PsiParameter declaration = ((PsiForeachStatement)statement).getIterationParameter(); + variableNames.add((declaration).getName()); + } + + if (statement instanceof PsiForStatement) { + PsiStatement initStatement = ((PsiForStatement)statement).getInitialization(); + if (initStatement instanceof PsiDeclarationStatement) { + variableNames.addAll(processDeclarationStatement((PsiDeclarationStatement)initStatement)); + } + } + + PsiStatement statementBody = ((PsiLoopStatement)statement).getBody(); + if (!(statementBody instanceof PsiBlockStatement)) return variableNames; + variableNames.addAll(processFunctionStatements(((PsiBlockStatement)statementBody).getCodeBlock())); + return variableNames; + } + + private static @NotNull HashSet processDeclarationStatement(@NotNull PsiDeclarationStatement statement) { + HashSet variableNames = new HashSet<>(); + Arrays.stream(statement.getDeclaredElements()).forEach(element -> { + if (element instanceof PsiLocalVariable) { + variableNames.add(((PsiLocalVariable)element).getName()); + } + }); + return variableNames; + } + private static @Nullable String viaResolve(@NotNull PsiMethodReferenceExpression expression) { final PsiElement resolve = expression.resolve(); final PsiMethod method = ObjectUtils.tryCast(resolve, PsiMethod.class); diff --git a/java/java-tests/testData/findSimilar/Field.java b/java/java-tests/testData/findSimilar/Field.java new file mode 100644 index 000000000000..35b2502fb274 --- /dev/null +++ b/java/java-tests/testData/findSimilar/Field.java @@ -0,0 +1,41 @@ +public class Test { + int a; + + Test() { + this.field = 0; + } + + private int getInteger(int num) { + return num; + } + + private int runTest(){ + boolean localVar = true; + for(int forLoopVariable = 0; forLoopVariable < 0; ++forLoopVariable){ + localVar += 1; + + if (localVar) { + int nestedIfVar = 0; + } + } + + while(localVar){ + boolean whileVariable = true; + localVar = false; + } + + if(true){ + int ifVar = 5; + } + else{ + int elseVar = 7; + } + + ArrayList list = new ArrayList(); + for(String forEachVar: list){ + + } + + getInteger(2 + field); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/findSimilar/usageGroups/field/Field.java b/java/java-tests/testData/findSimilar/usageGroups/field/Field.java new file mode 100644 index 000000000000..a0819f0eb440 --- /dev/null +++ b/java/java-tests/testData/findSimilar/usageGroups/field/Field.java @@ -0,0 +1,38 @@ +import java.util.ArrayList; + +public class Test { + int field; + Test(){ + this.field = 0; + } + + private int test(int num){ + return num; + } + + private void testFields(){ + int a = 1; + test(a); + test(field); + } + + private void testIfWithFields(){ + int a = 1; + + if(tets(a) == 5){ + System.out.println("variable"); + } + + if(test(field) == 5){ + System.out.println("field"); + } + + if(test(a) == 5){ + System.out.println("field"); + } + + if(test(field) != 5){ + System.out.println("field"); + } + } +} diff --git a/java/java-tests/testData/findSimilar/usageGroups/field/results.txt b/java/java-tests/testData/findSimilar/usageGroups/field/results.txt new file mode 100644 index 000000000000..039d1f99afe2 --- /dev/null +++ b/java/java-tests/testData/findSimilar/usageGroups/field/results.txt @@ -0,0 +1,11 @@ +[{ +15|test|(a);} +, { +16|test|(field);} +, { +26|if|(|test|(field) == |5|){} +, { +30|if|(|test|(a) == |5|){} +, { +34|if|(|test|(field) != |5|){} +] \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/find/groupSimilar/JavaFindUsagesGroupsTest.java b/java/java-tests/testSrc/com/intellij/find/groupSimilar/JavaFindUsagesGroupsTest.java index 60b45863880a..145fca198395 100644 --- a/java/java-tests/testSrc/com/intellij/find/groupSimilar/JavaFindUsagesGroupsTest.java +++ b/java/java-tests/testSrc/com/intellij/find/groupSimilar/JavaFindUsagesGroupsTest.java @@ -79,7 +79,13 @@ public class JavaFindUsagesGroupsTest extends JavaPsiTestCase { public void testArrayAccess() { doTest(getMethod("Market", "getGoods")); } - public void testForEach() { - doTest(getMethod("java.lang.Iterable", "forEach")); + public void testField() { + try { + Registry.get("similarity.find.usages.add.features.for.fields").setValue(true); + doTest(getMethod("Test", "test")); + } + finally { + Registry.get("similarity.find.usages.add.features.for.fields").resetToDefault(); + } } } diff --git a/java/java-tests/testSrc/com/intellij/find/groupSimilar/JavaUsagesBySimilarityTest.java b/java/java-tests/testSrc/com/intellij/find/groupSimilar/JavaUsagesBySimilarityTest.java index c5a2408f941a..e123f1adaae3 100644 --- a/java/java-tests/testSrc/com/intellij/find/groupSimilar/JavaUsagesBySimilarityTest.java +++ b/java/java-tests/testSrc/com/intellij/find/groupSimilar/JavaUsagesBySimilarityTest.java @@ -152,6 +152,27 @@ public class JavaUsagesBySimilarityTest extends JavaCodeInsightFixtureTestCase { assertEquals(1, features.get("USAGE: FOR")); } + public void testFieldFeatures() throws ExecutionException, InterruptedException { + try { + Registry.get("similarity.find.usages.add.features.for.fields").setValue(true); + myFixture.configureByFile("Field.java"); + PsiElement elementAtCaret = myFixture.getReferenceAtCaretPosition().getElement(); + final Bag features = getFeatures(elementAtCaret); + assertEquals(1, features.get("CONTEXT: FIELD: field")); + assertEquals(0, features.get("CONTEXT: FIELD: localVariable")); + assertEquals(0, features.get("CONTEXT: FIELD: forLoopVariable")); + assertEquals(0, features.get("CONTEXT: FIELD: nestedIfVar")); + assertEquals(0, features.get("CONTEXT: FIELD: whileVariable")); + assertEquals(0, features.get("CONTEXT: FIELD: ifVar")); + assertEquals(0, features.get("CONTEXT: FIELD: elseVar")); + assertEquals(0, features.get("CONTEXT: FIELD: list")); + assertEquals(0, features.get("CONTEXT: FIELD: forEachVar")); + } + finally { + Registry.get("similarity.find.usages.add.features.for.fields").resetToDefault(); + } + } + public void testBag() { final Bag bag = new Bag("a", "b"); assertEquals(""" diff --git a/platform/usageView/resources/idea/UsageViewActions.xml b/platform/usageView/resources/idea/UsageViewActions.xml index 67f98bbcf6be..038f022939ed 100644 --- a/platform/usageView/resources/idea/UsageViewActions.xml +++ b/platform/usageView/resources/idea/UsageViewActions.xml @@ -15,6 +15,7 @@ +