Similar usages: Feature collection for fields

(cherry picked from commit de9b492eb32220d016223baee65c3956502d9ef4)

IJ-CR-101246

GitOrigin-RevId: fa893906877a34d3242b8cc43bb10ac4d749819b
This commit is contained in:
Teodora Stojcheska
2023-02-08 20:54:12 +00:00
committed by intellij-monorepo-bot
parent b881c7cb31
commit ef63d90876
7 changed files with 219 additions and 2 deletions
@@ -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<String> 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<String> collectVariableNames() {
PsiMethod containingMethod = getWrappingMethodForUsage(myContext);
if (containingMethod == null) return new HashSet<>();
PsiCodeBlock body = containingMethod.getBody();
if (body == null) return new HashSet<>();
HashSet<String> 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<String> processFunctionParameters(@NotNull PsiMethod containingMethod) {
HashSet<String> variableNames = new HashSet<>();
Arrays.stream(containingMethod.getParameterList().getParameters())
.forEach(parameter -> variableNames.add(parameter.getName()));
return variableNames;
}
private static @NotNull HashSet<String> processFunctionStatements(@NotNull PsiCodeBlock body) {
HashSet<String> 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<String> processLoopStatement(@NotNull PsiStatement statement) {
HashSet<String> 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<String> processDeclarationStatement(@NotNull PsiDeclarationStatement statement) {
HashSet<String> 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);
@@ -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<String> list = new ArrayList();
for(String forEachVar: list){
}
get<caret>Integer(2 + field);
}
}
@@ -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");
}
}
}
@@ -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|){}
]
@@ -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();
}
}
}
@@ -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("""
@@ -15,6 +15,7 @@
<registryKey key="similarity.find.usages.use.parent.features.with.child.number" defaultValue="false"
description="Take in account what child number is in parent node"/>
<registryKey defaultValue="true" description="Use information from parent nodes" key="similarity.find.usages.use.parent.features"/>
<registryKey defaultValue="true" description="Add features for fields" key="similarity.find.usages.add.features.for.fields"/>
<registryKey defaultValue="true" description="Enable similarity clustering for Java usages" key="similarity.find.usages.java.clustering.enable"/>
<registryKey defaultValue="true" description="Enable similarity clustering for Kotlin usages"
key="similarity.find.usages.kotlin.clustering.enable"/>