MLP-6 Similarity Groups in Find Usages initial support

Added support for java and kt usages KTIJ-21819 IDEA-294547

GitOrigin-RevId: 5bf947ecd97ba1ec106adbb9469307a51cc9508c
This commit is contained in:
Andrey Sokolov
2022-05-24 08:55:38 +00:00
committed by intellij-monorepo-bot
parent 4a8945601e
commit fcfc76cd46
60 changed files with 2356 additions and 7 deletions
@@ -2274,6 +2274,8 @@
<dependencyCollector kind="java" implementation="com.intellij.ide.JavaDependencyCollector"/>
<usageFeaturesProvider implementation="com.intellij.find.findUsages.similarity.JavaUsageSimilarityFeaturesProvider"/>
<registryKey key="java.formatter.chained.calls.pre212.compatibility"
defaultValue="false"
description="Format chained calls as in versions prior to 2021.2"/>
@@ -0,0 +1,237 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.find.findUsages.similarity;
import com.intellij.openapi.util.registry.Registry;
import com.intellij.psi.*;
import com.intellij.psi.tree.IElementType;
import com.intellij.usages.similarity.bag.Bag;
import com.intellij.usages.similarity.features.UsageSimilarityFeaturesRecorder;
import com.intellij.util.ObjectUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import static com.intellij.psi.JavaTokenType.FINAL_KEYWORD;
public class JavaSimilarityPropertiesExtractor extends JavaRecursiveElementVisitor {
private final @NotNull UsageSimilarityFeaturesRecorder myUsageSimilarityFeaturesRecorder;
private final @NotNull PsiElement myContext;
public JavaSimilarityPropertiesExtractor(@NotNull PsiElement context) {
myUsageSimilarityFeaturesRecorder = new UsageSimilarityFeaturesRecorder(context);
myContext = context;
}
public @NotNull Bag getFeatures() {
myContext.accept(this);
return myUsageSimilarityFeaturesRecorder.getFeatures();
}
@Override
public void visitKeyword(PsiKeyword keyword) {
final IElementType type = keyword.getTokenType();
if (!type.equals(FINAL_KEYWORD)) {
myUsageSimilarityFeaturesRecorder.addFeature(type.toString());
}
super.visitKeyword(keyword);
}
@Override
public void visitVariable(PsiVariable variable) {
myUsageSimilarityFeaturesRecorder.addAllFeatures(variable, "VAR: " + getTypeRepresentation(variable.getType()));
super.visitVariable(variable);
}
@Override
public void visitNewExpression(PsiNewExpression expression) {
myUsageSimilarityFeaturesRecorder.addAllFeatures(expression, "NEW: " + getTypeRepresentation(expression));
super.visitNewExpression(expression);
}
@Override
public void visitForStatement(PsiForStatement statement) {
myUsageSimilarityFeaturesRecorder.addAllFeatures(statement, "FOR");
super.visitForStatement(statement);
}
@Override
public void visitForeachStatement(PsiForeachStatement statement) {
myUsageSimilarityFeaturesRecorder.addAllFeatures(statement, "FOREACH");
super.visitForeachStatement(statement);
}
@Override
public void visitDoWhileStatement(PsiDoWhileStatement statement) {
myUsageSimilarityFeaturesRecorder.addAllFeatures(statement, "DO");
super.visitDoWhileStatement(statement);
}
@Override
public void visitIfStatement(PsiIfStatement statement) {
myUsageSimilarityFeaturesRecorder.addAllFeatures(statement, "IF");
super.visitIfStatement(statement);
}
@Override
public void visitAssignmentExpression(PsiAssignmentExpression expression) {
myUsageSimilarityFeaturesRecorder.addAllFeatures(expression, expression.getOperationSign().getText());
super.visitAssignmentExpression(expression);
}
@Override
public void visitInstanceOfExpression(PsiInstanceOfExpression expression) {
myUsageSimilarityFeaturesRecorder.addAllFeatures(expression, "instanceof");
super.visitInstanceOfExpression(expression);
}
@Override
public void visitPolyadicExpression(PsiPolyadicExpression expression) {
myUsageSimilarityFeaturesRecorder.addAllFeatures(expression, expression.getOperationTokenType().toString());
super.visitPolyadicExpression(expression);
}
@Override
public void visitSwitchStatement(PsiSwitchStatement statement) {
myUsageSimilarityFeaturesRecorder.addAllFeatures(statement, "SWITCH");
super.visitSwitchStatement(statement);
}
@Override
public void visitWhileStatement(PsiWhileStatement statement) {
myUsageSimilarityFeaturesRecorder.addAllFeatures(statement, "WHILE");
super.visitWhileStatement(statement);
}
@Override
public void visitReferenceExpression(PsiReferenceExpression expression) {
String tokenFeature = null;
if (!(expression instanceof PsiMethodReferenceExpression)) {
tokenFeature = "VAR: ";
if (!Registry.is("similarity.find.usages.fast.clustering")) {
tokenFeature += getTypeRepresentation(expression);
}
}
if (tokenFeature != null) {
myUsageSimilarityFeaturesRecorder.addAllFeatures(expression, tokenFeature);
}
super.visitReferenceExpression(expression);
}
@Override
public void visitMethodReferenceExpression(PsiMethodReferenceExpression expression) {
String tokenFeature;
if (Registry.is("similarity.find.usages.fast.clustering")) {
final PsiElement referenceNameElement = expression.getReferenceNameElement();
tokenFeature = "{CALL: " + (referenceNameElement != null ? referenceNameElement.getText() : null) + "}";
}
else {
tokenFeature = viaResolve(expression);
}
if (tokenFeature != null) {
myUsageSimilarityFeaturesRecorder.addAllFeatures(expression, tokenFeature);
}
super.visitMethodReferenceExpression(expression);
}
@Override
public void visitArrayAccessExpression(PsiArrayAccessExpression expression) {
PsiExpression arrayIndex = expression.getIndexExpression();
String tokenFeature = "{ARRAY: data: " + expression.getType() + " index:" + getTypeRepresentation(arrayIndex) + "}";
myUsageSimilarityFeaturesRecorder.addAllFeatures(expression, tokenFeature);
super.visitArrayAccessExpression(expression);
}
@Override
public void visitMethodCallExpression(PsiMethodCallExpression expression) {
String tokenFeature;
if (Registry.is("similarity.find.usages.fast.clustering")) {
tokenFeature = "{CALL: " + expression.getMethodExpression().getReferenceName() + "}";
}
else {
tokenFeature = getTokenFeatureViaResolve(expression);
}
if (tokenFeature != null) {
myUsageSimilarityFeaturesRecorder.addAllFeatures(expression, tokenFeature);
}
super.visitMethodCallExpression(expression);
}
@Override
public void visitLiteralExpression(PsiLiteralExpression expression) {
myUsageSimilarityFeaturesRecorder.addAllFeatures(expression, "literal:" + getTypeRepresentation(expression));
super.visitLiteralExpression(expression);
}
@Override
public void visitLambdaExpression(PsiLambdaExpression expression) {
myUsageSimilarityFeaturesRecorder.addAllFeatures(expression, "lambda");
super.visitLambdaExpression(expression);
}
@Override
public void visitAnonymousClass(PsiAnonymousClass clazz) {
myUsageSimilarityFeaturesRecorder.addAllFeatures(clazz, "anonymousClazz");
super.visitAnonymousClass(clazz);
}
private static @Nullable String viaResolve(@NotNull PsiMethodReferenceExpression expression) {
final PsiElement resolve = expression.resolve();
final PsiMethod method = ObjectUtils.tryCast(resolve, PsiMethod.class);
if (method != null) {
final PsiClass containingClass = method.getContainingClass();
if (containingClass != null) {
return "{CALL: " +
containingClass.getQualifiedName() +
"." +
method.getName() + "()" +
" ret:" +
getTypeRepresentation(expression) + "}";
}
}
return null;
}
public static @NotNull String getTypeRepresentation(@Nullable PsiExpression expression) {
if (expression == null) return "null";
return getTypeRepresentation(expression.getType());
}
private static @NotNull String getTypeRepresentation(@Nullable PsiType type) {
if (type != null) {
return type.getCanonicalText();
}
else {
return "null";
}
}
private static @Nullable String getTokenFeatureViaResolve(@NotNull PsiMethodCallExpression expression) {
String tokenFeature = null;
final PsiReferenceExpression referenceExpression = expression.getMethodExpression();
final PsiElement resolve = referenceExpression.resolve();
final PsiMethod method = ObjectUtils.tryCast(resolve, PsiMethod.class);
if (method != null) {
final PsiClass containingClass = method.getContainingClass();
if (containingClass != null) {
tokenFeature = "{CALL: " +
containingClass.getQualifiedName() +
"." +
method.getName() + "()" +
" ret:" +
getTypeRepresentation(referenceExpression) + " " +
getArgumentData(expression) + "}";
}
}
return tokenFeature;
}
public static @NotNull String getArgumentData(@NotNull PsiMethodCallExpression methodCallExpression) {
PsiExpression[] expressions = methodCallExpression.getArgumentList().getExpressions();
StringBuilder arguments = new StringBuilder();
for (int i = 0; i < expressions.length; i++) {
arguments.append("arg").append(i).append(" type: ").append(getTypeRepresentation(expressions[i]));
}
return arguments.toString();
}
}
@@ -0,0 +1,74 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.find.findUsages.similarity;
import com.intellij.openapi.util.registry.Registry;
import com.intellij.psi.*;
import com.intellij.psi.impl.source.PsiMethodImpl;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.usages.PsiElementUsageTarget;
import com.intellij.usages.similarity.bag.Bag;
import com.intellij.usages.similarity.features.UsageSimilarityFeaturesProvider;
import com.intellij.util.concurrency.annotations.RequiresBackgroundThread;
import com.intellij.util.concurrency.annotations.RequiresReadLock;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class JavaUsageSimilarityFeaturesProvider implements UsageSimilarityFeaturesProvider {
@Override
@RequiresReadLock
@RequiresBackgroundThread
public @NotNull Bag getFeatures(@NotNull PsiElement usage) {
PsiElement statement = getContainingStatement(usage);
if (statement != null) {
final Bag usageProperties = new JavaSimilarityPropertiesExtractor(statement).getFeatures();
if (Registry.is("similarity.find.usages.parent.statement.condition.feature")) {
usageProperties.addAll(getParentStatementFeatures(statement));
}
return usageProperties;
}
return Bag.EMPTY_BAG;
}
@Override
@RequiresReadLock
public boolean isAvailable(@NotNull PsiElementUsageTarget usageTarget) {
return usageTarget.getElement() instanceof PsiMethodImpl;
}
public @Nullable PsiElement getContainingStatement(@NotNull PsiElement element) {
return PsiTreeUtil.getParentOfType(element, PsiDeclarationStatement.class,
PsiExpressionStatement.class,
PsiIfStatement.class,
PsiWhileStatement.class,
PsiTryStatement.class,
PsiThrowStatement.class,
PsiSwitchStatement.class,
PsiReturnStatement.class,
PsiLoopStatement.class,
PsiImportStatement.class,
PsiForStatement.class,
PsiForeachStatement.class,
PsiConditionalLoopStatement.class,
PsiBlockStatement.class);
}
private static @NotNull Bag getParentStatementFeatures(@NotNull PsiElement context) {
final PsiElement parentControlStatement =
PsiTreeUtil.findFirstParent(context, true,
element -> element instanceof PsiConditionalLoopStatement || element instanceof PsiIfStatement);
if (parentControlStatement instanceof PsiConditionalLoopStatement) {
final PsiExpression conditionExpression = ((PsiConditionalLoopStatement)parentControlStatement).getCondition();
if (conditionExpression != null) {
return new JavaSimilarityPropertiesExtractor(conditionExpression).getFeatures();
}
}
if (parentControlStatement instanceof PsiIfStatement) {
final PsiExpression conditionExpression = ((PsiIfStatement)parentControlStatement).getCondition();
if (conditionExpression != null) {
return new JavaSimilarityPropertiesExtractor(conditionExpression).getFeatures();
}
}
return Bag.EMPTY_BAG;
}
}
@@ -0,0 +1,12 @@
class A {
public int foo(A a) {
return 0;
}
public static void main(String[] args) {
A a1 = new A();
int a = a1.f<caret>oo(new A() {
public int foo(A a) { return 1; }
});
}
}
@@ -0,0 +1,8 @@
class A {
public int foo(A a){
return 0;
}
public static void main(String[] args) {
A a1 = new A(); int a = a1.f<caret>oo(new A());
}
}
@@ -0,0 +1,10 @@
class A {
public int foo(A a){
return 0;
}
public static void main(String[] args) {
for (int a=0; i<f<caret>oo(this); i++) {
}
}
}
@@ -0,0 +1,9 @@
class A {
public int af() { return 1; }
public static void main(String[] args) {
int a = 0;
a += <caret>af();
}
}
@@ -0,0 +1,12 @@
package aPackage;
class A {
public int foo(Runnable run) {
return 0;
}
public static void main(String[] args) {
A a1 = new A();
int a = a1.fo<caret>o(()->{});
}
}
@@ -0,0 +1,14 @@
package aPackage;
class A {
public int foo(Runnable run) {
return 0;
}
public static void main(String[] args) {
A a1 = new A();
int a = a1.fo<caret>o(A::run);
}
private static void run() { }
}
@@ -0,0 +1,17 @@
import org.jetbrains.annotations.NotNull;
public class Market {
public void a() {
System.out.println(getGoods()[0]);
System.out.println(getGoods()[1]);
System.out.println(getGoods()[1].charAt(0));
System.out.println(getGoods()[0].charAt(0));
System.out.println(getGoods()[0]+ getGoods()[1]);
}
@NotNull
private static String[] getGoods() {
return new String[]{"orange", "apple", "cucumber", "tomato"};
}
}
@@ -0,0 +1,10 @@
[{id=0
, myUsages=6|System.out.println(|getGoods|()[|0|]);,
7|System.out.println(|getGoods|()[|1|]);}
, {id=1
, myUsages=8|System.out.println(|getGoods|()[|1|].charAt(|0|));,
9|System.out.println(|getGoods|()[|0|].charAt(|0|));}
, {id=2
, myUsages=10|System.out.println(|getGoods|()[|0|]+ getGoods()[|1|]);,
10|System.out.println(getGoods()[|0|]+ |getGoods|()[|1|]);}
]
@@ -0,0 +1,14 @@
class Foo {
public int f() {
return 1;
}
}
class D {
public void run() {
Foo foo = new Foo()
foo.f() + foo.f();
foo.f()+foo.f();
int ff1 = foo.f() +1;
int ff2 = foo.f() +2;
}
}
@@ -0,0 +1,9 @@
[{id=0
, myUsages=9|foo.|f|() + foo.f();,
9|foo.f() + foo.|f|();,
10|foo.|f|()+foo.f();,
10|foo.f()+foo.|f|();}
, {id=1
, myUsages=11|int| ff1 = foo.|f|() +|1|;,
12|int| ff2 = foo.|f|() +|2|;}
]
@@ -0,0 +1,22 @@
import java.util.HashMap;
import java.util.Map;
public class MapUsages {
public void a() {
Map<String, String> map =new HashMap<>();
//init map
map.put("1", "first");
map.put("2", "second");
//verify map
if (!map.containsKey("1")) {
map.put("1", "first");
}
if (!map.containsKey("2")) {
map.put("2", "second");
}
//clear data
map.put("1", null);
map.put("2", null);
}
}
@@ -0,0 +1,10 @@
[{id=0
, myUsages=9|map.|put|(|"1"|, |"first"|);,
10|map.|put|(|"2"|, |"second"|);}
, {id=1
, myUsages=13|map.|put|(|"1"|, |"first"|);,
16|map.|put|(|"2"|, |"second"|);}
, {id=2
, myUsages=19|map.|put|(|"1"|, |null|);,
20|map.|put|(|"2"|, |null|);}
]
@@ -0,0 +1,23 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
public class CollectionsPlayGround {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
for (int i = 0; i < 100; i++) {
list.add("entry:" + i);
}
final List<Integer> unmodifiableHashcodes = list.stream().map(String::hashCode).collect(Collectors.toUnmodifiableList());
final List<Integer> modifiableHashcodes = list.stream().map(String::hashCode).collect(Collectors.toList());
List<String> nonEmptyEntries = list.stream().filter(e -> e.length() > 0).collect(Collectors.toList());
List<String> moreThanOneCharEntry = list.stream().filter(e -> e.length() > 1).collect(Collectors.toList());
List<String> nonEmptyEntries2 = list.stream().filter(e -> e.isEmpty() ).collect(Collectors.toList());
Set<String> nonEmptySet = list.stream().filter(e -> e.length() > 0).collect(Collectors.toSet());
}
}
@@ -0,0 +1,12 @@
[{id=0
, myUsages=14|final| List<Integer> unmodifiableHashcodes = list.|stream|().map(String::hashCode).collect(Collectors.toUnmodifiableList());}
, {id=1
, myUsages=15|final| List<Integer> modifiableHashcodes = list.|stream|().map(String::hashCode).collect(Collectors.toList());}
, {id=2
, myUsages=17|List<String> nonEmptyEntries = list.|stream|().filter(e -> e.length() > |0|).collect(Collectors.toList());,
18|List<String> moreThanOneCharEntry = list.|stream|().filter(e -> e.length() > |1|).collect(Collectors.toList());}
, {id=3
, myUsages=19|List<String> nonEmptyEntries2 = list.|stream|().filter(e -> e.isEmpty() ).collect(Collectors.toList());}
, {id=4
, myUsages=21|Set<String> nonEmptySet = list.|stream|().filter(e -> e.length() > |0|).collect(Collectors.toSet());}
]
@@ -0,0 +1,69 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.find.groupSimilar;
import com.intellij.JavaTestUtil;
import com.intellij.find.findUsages.JavaFindUsagesHandler;
import com.intellij.find.findUsages.JavaFindUsagesHandlerFactory;
import com.intellij.openapi.util.registry.Registry;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiMethod;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.testFramework.IdeaTestUtil;
import com.intellij.testFramework.JavaPsiTestCase;
import com.intellij.testFramework.PsiTestUtil;
import com.intellij.usages.UsageInfoToUsageConverter;
import com.intellij.usages.similarity.clustering.ClusteringSearchSession;
import org.jetbrains.annotations.NotNull;
import java.io.File;
import static com.intellij.testFramework.EqualsToFile.assertEqualsToFile;
public class JavaFindUsagesGroupsTest extends JavaPsiTestCase {
@Override
protected void setUp() throws Exception {
super.setUp();
PsiTestUtil.removeAllRoots(myModule, IdeaTestUtil.getMockJdk18());
createTestProjectStructure(getTestRoot());
}
private @NotNull String getTestRoot() {
return JavaTestUtil.getJavaTestDataPath() + "/findSimilar/usageGroups/" + getTestName(true);
}
private void doTest(@NotNull PsiMethod streamMethod) {
final JavaFindUsagesHandlerFactory factory = JavaFindUsagesHandlerFactory.getInstance(getProject());
JavaFindUsagesHandler handler = new JavaFindUsagesHandler(streamMethod, factory);
ClusteringSearchSession session = new ClusteringSearchSession();
handler.processElementUsages(streamMethod, info -> {
UsageInfoToUsageConverter.convertToSimilarUsage(new PsiMethod[]{streamMethod}, info, session);
return true;
}, factory.getFindMethodOptions());
File file = new File(getTestRoot() + "/results.txt");
assertEqualsToFile("", file, session.getClusters().toString());
}
private @NotNull PsiMethod getMethod(String className, String methodName) {
PsiClass anInterface = myJavaFacade.findClass(className, GlobalSearchScope.allScope(myProject));
return anInterface.findMethodsByName(methodName, false)[0];
}
public void testStreamUsages() {
doTest(getMethod("java.util.Collection", "stream"));
}
public void testBinaryExpressions() {
doTest(getMethod("Foo", "f"));
}
public void testMapUsages() {
try {
Registry.get("similarity.find.usages.parent.statement.condition.feature").setValue(true);
doTest(getMethod("java.util.Map", "put"));
} finally {
Registry.get("similarity.find.usages.fast.clustering").resetToDefault();
}
}
public void testArrayAccess() { doTest(getMethod("Market", "getGoods")); }
}
@@ -0,0 +1,109 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.find.groupSimilar;
import com.intellij.JavaTestUtil;
import com.intellij.find.findUsages.similarity.JavaUsageSimilarityFeaturesProvider;
import com.intellij.openapi.util.registry.Registry;
import com.intellij.psi.PsiElement;
import com.intellij.testFramework.fixtures.JavaCodeInsightFixtureTestCase;
import com.intellij.usages.similarity.bag.Bag;
import com.intellij.usages.similarity.bag.BagsDistanceCalculator;
public class JavaUsagesBySimilarityTest extends JavaCodeInsightFixtureTestCase {
@Override
protected String getTestDataPath() {
return JavaTestUtil.getJavaTestDataPath() + "/findSimilar/";
}
public void testFeaturesProvider() {
myFixture.configureByFile("FeaturesProvider.java");
PsiElement elementAtCaret = myFixture.getReferenceAtCaretPosition().getElement();
final Bag features = new JavaUsageSimilarityFeaturesProvider().getFeatures(elementAtCaret);
assertEquals(1, features.get("{CALL: foo}"));
assertEquals(1, features.get("NEW: A"));
assertEquals(1, features.get("NEW_KEYWORD"));
}
public void testForFeatures() {
myFixture.configureByFile("ForFeatures.java");
PsiElement elementAtCaret = myFixture.getReferenceAtCaretPosition().getElement();
final Bag features = new JavaUsageSimilarityFeaturesProvider().getFeatures(elementAtCaret);
assertEquals(1, features.get("FOR"));
assertEquals(1, features.get("FOR_KEYWORD"));
assertEquals(1, features.get("GP:VAR: FOR_STATEMENT -1"));
}
public void testAnonymous() {
try {
Registry.get("similarity.find.usages.fast.clustering").setValue(false);
myFixture.configureByFile("Anonymous.java");
PsiElement elementAtCaret = myFixture.getReferenceAtCaretPosition().getElement();
final Bag features = new JavaUsageSimilarityFeaturesProvider().getFeatures(elementAtCaret);
assertEquals(1, features.get("P:anonymousClazz NEW_EXPRESSION -1"));
assertEquals(1, features.get("NEXT: {CALL: A.foo() ret:int arg0 type: A} PsiJavaToken:SEMICOLON"));
}
finally {
Registry.get("similarity.find.usages.fast.clustering").resetToDefault();
}
}
public void testIncrement() {
try {
Registry.get("similarity.find.usages.fast.clustering").setValue(false);
myFixture.configureByFile("Increment.java");
PsiElement elementAtCaret = myFixture.getReferenceAtCaretPosition().getElement();
final Bag features = new JavaUsageSimilarityFeaturesProvider().getFeatures(elementAtCaret);
assertEquals(1, features.get("+="));
assertEquals(1, features.get("{CALL: A.af() ret:int }"));
}
finally {
Registry.get("similarity.find.usages.fast.clustering").resetToDefault();
}
}
public void testLambda() {
try {
Registry.get("similarity.find.usages.fast.clustering").setValue(false);
myFixture.configureByFile("Lambda.java");
PsiElement elementAtCaret = myFixture.getReferenceAtCaretPosition().getElement();
final Bag features = new JavaUsageSimilarityFeaturesProvider().getFeatures(elementAtCaret);
assertEquals(1, features.get("{CALL: aPackage.A.foo() ret:int arg0 type: <lambda expression>}"));
assertEquals(1, features.get("GP:lambda METHOD_CALL_EXPRESSION -1"));
}
finally {
Registry.get("similarity.find.usages.fast.clustering").resetToDefault();
}
}
public void testMethodReference() {
try {
Registry.get("similarity.find.usages.fast.clustering").setValue(false);
myFixture.configureByFile("MethodReference.java");
PsiElement elementAtCaret = myFixture.getReferenceAtCaretPosition().getElement();
final Bag features = new JavaUsageSimilarityFeaturesProvider().getFeatures(elementAtCaret);
assertEquals(1, features.get("{CALL: aPackage.A.foo() ret:int arg0 type: <method reference>}"));
}
finally {
Registry.get("similarity.find.usages.fast.clustering").resetToDefault();
}
}
public void testBag() {
final Bag bag = new Bag("a", "b");
assertEquals("a : 1\n" +
"b : 1\n", bag.toString());
bag.add("a");
Bag toAdd = new Bag("a", "c");
bag.addAll(toAdd);
assertEquals("c : 1\n" +
"a : 3\n" +
"b : 1\n", bag.toString());
}
public void testDistance() {
assertEquals(0.6,
BagsDistanceCalculator.jaccardSimilarity(new Bag("a", "b", "c", "d", "e", "f", "g", "h"),
new Bag("a", "z", "c", "y", "e", "f", "g", "h")));
}
}
@@ -67,6 +67,7 @@ public abstract class ActionPlaces {
public static final String METHOD_HIERARCHY_VIEW_TOOLBAR = "MethodHierarchyViewToolbar";
public static final String CALL_HIERARCHY_VIEW_POPUP = "CallHierarchyViewPopup";
public static final String CALL_HIERARCHY_VIEW_TOOLBAR = "CallHierarchyViewToolbar";
public static final String SIMILAR_USAGES_PREVIEW_TOOLBAR = "SimilarUsagesPreviewToolbar";
public static final String J2EE_ATTRIBUTES_VIEW_POPUP = "J2EEAttributesViewPopup";
public static final String J2EE_VIEW_POPUP = "J2EEViewPopup";
public static final String RUNNER_TOOLBAR = "RunnerToolbar";
@@ -200,7 +201,7 @@ public abstract class ActionPlaces {
UNKNOWN, KEYBOARD_SHORTCUT, MOUSE_SHORTCUT, FORCE_TOUCH,
TOOLBAR, MAIN_MENU, MAIN_TOOLBAR, EDITOR_TOOLBAR, TABS_MORE_TOOLBAR, EDITOR_TAB, COMMANDER_TOOLBAR, CONTEXT_TOOLBAR, TOOLWINDOW_TITLE,
LEARN_TOOLWINDOW, PROJECT_VIEW_TOOLBAR, STATUS_BAR_PLACE, ACTION_SEARCH, TESTTREE_VIEW_TOOLBAR, TYPE_HIERARCHY_VIEW_TOOLBAR,
METHOD_HIERARCHY_VIEW_TOOLBAR, CALL_HIERARCHY_VIEW_TOOLBAR, RUNNER_TOOLBAR, DEBUGGER_TOOLBAR, USAGE_VIEW_TOOLBAR,
METHOD_HIERARCHY_VIEW_TOOLBAR, CALL_HIERARCHY_VIEW_TOOLBAR, SIMILAR_USAGES_PREVIEW_TOOLBAR, RUNNER_TOOLBAR, DEBUGGER_TOOLBAR, USAGE_VIEW_TOOLBAR,
SHOW_USAGES_POPUP_TOOLBAR,
STRUCTURE_VIEW_TOOLBAR, NAVIGATION_BAR_TOOLBAR, TODO_VIEW_TOOLBAR, COMPILER_MESSAGES_TOOLBAR,
ANT_MESSAGES_TOOLBAR, ANT_EXPLORER_TOOLBAR, CODE_INSPECTION, JAVADOC_TOOLBAR, JAVADOC_INPLACE_SETTINGS,
@@ -587,4 +587,4 @@ documentation.file.modification.datetime.label=Modified:
documentation.file.scope.label=Scope:
documentation.file.vcs.status.label=VCS Status:
notification.group.dependencies=Module dependencies analysis completed
notification.group.parameter.name.hints=Parameter name hints settings changed
notification.group.parameter.name.hints=Parameter name hints settings changed
@@ -47,6 +47,7 @@ import com.intellij.usageView.UsageViewContentManager;
import com.intellij.usageView.UsageViewUtil;
import com.intellij.usages.*;
import com.intellij.usages.impl.UsageViewStatisticsCollector;
import com.intellij.usages.similarity.clustering.ClusteringSearchSession;
import com.intellij.util.ArrayUtil;
import com.intellij.util.CommonProcessors;
import com.intellij.util.Processor;
@@ -357,6 +358,7 @@ public final class FindUsagesManager {
});
FindUsagesOptions optionsClone = options.clone();
ClusteringSearchSession clusteringSearchSession = ClusteringSearchSession.createClusteringSessionIfEnabled();
return processor -> {
Project project = ReadAction.compute(() -> scopeFile != null ? scopeFile.getProject() : primaryTargets[0].getProject());
ProgressIndicator indicator = ProgressManager.getInstance().getProgressIndicator();
@@ -375,7 +377,11 @@ public final class FindUsagesManager {
optionsClone.searchScope = new LocalSearchScope(scopeFile);
}
Processor<UsageInfo> usageInfoProcessor = new CommonProcessors.UniqueProcessor<>(usageInfo -> {
Usage usage = ReadAction.compute(() -> UsageInfoToUsageConverter.convert(primaryElements, usageInfo));
Usage usage = ReadAction.compute(
() -> clusteringSearchSession != null
? UsageInfoToUsageConverter.convertToSimilarUsage(primaryElements, usageInfo, clusteringSearchSession)
: UsageInfoToUsageConverter.convert(primaryElements, usageInfo)
);
return processor.process(usage);
});
PsiElement[] elements = ArrayUtil.mergeArrays(primaryElements, secondaryElements, PsiElement.ARRAY_FACTORY);
@@ -2465,6 +2465,7 @@ group.Internal.Java.text=Java
action.CollectFUStatisticsAction.text=Collect Statistics from Collector...
action.TestParseEventsSchemeAction.text=Test Parsing Events Scheme...
action.ValidateLocalHistory.text=Validate Local History Storage
action.ShowUsageFeatures.text=Show Usage Features
action.FixLineSeparators.text=Fix Line Separators
action.PruneEmptyDirectories.text=Prune Empty Directories
action.LoadAllVFSContent.text=Load All VFS Stored Files Content
@@ -399,6 +399,7 @@
<extensionPoint name="modelScopeItemPresenter" interface="com.intellij.analysis.dialog.ModelScopeItemPresenter" />
<extensionPoint name="usageFeaturesProvider" interface="com.intellij.usages.similarity.features.UsageSimilarityFeaturesProvider" dynamic="true"/>
<extensionPoint name="usageFilteringRuleProvider" interface="com.intellij.usages.rules.UsageFilteringRuleProvider" dynamic="true"/>
<extensionPoint name="importFilteringRule" interface="com.intellij.usages.rules.ImportFilteringRule" dynamic="true"/>
<extensionPoint name="usageGroupingRuleProvider" interface="com.intellij.usages.rules.UsageGroupingRuleProvider" dynamic="true"/>
@@ -1165,6 +1165,7 @@
<postStartupActivity implementation="com.intellij.codeInsight.highlighting.BackgroundHighlighter"/>
<usageContextPanelProvider implementation="com.intellij.usages.impl.UsagePreviewPanel$Provider"/>
<usageContextPanelProvider implementation="com.intellij.usageView.impl.UsageContextCallHierarchyPanel$Provider"/>
<usageContextPanelProvider implementation="com.intellij.find.findUsages.similarity.SimilarUsagesContextPanel$Provider"/>
<fileDocumentSynchronizationVetoer implementation="com.intellij.codeInsight.lookup.impl.LookupDocumentSavingVetoer"/>
<toolsProvider implementation="com.intellij.tools.LocalToolsProvider"/>
@@ -1707,5 +1707,6 @@
<separator/>
<add-to-group group-id="Internal" anchor="last"/>
</group>
<action internal="true" id="ShowUsageFeatures" class ="com.intellij.usages.similarity.internal.ShowUsageFeaturesInternalAction"/>
</actions>
</idea-plugin>
@@ -0,0 +1,39 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.find.findUsages.similarity;
import com.intellij.openapi.util.Iconable;
import com.intellij.psi.PsiFile;
import com.intellij.ui.components.ActionLink;
import com.intellij.usageView.UsageInfo;
import com.intellij.util.IconUtil;
import com.intellij.util.PsiNavigateUtil;
import com.intellij.util.ui.UIUtil;
import org.jetbrains.annotations.NotNull;
import javax.swing.*;
import java.awt.event.ActionEvent;
import java.util.Objects;
public class LocationLinkComponent {
@NotNull
private final ActionLink myLabel;
public LocationLinkComponent(@NotNull UsageInfo item) {
PsiFile file = item.getFile();
myLabel = new ActionLink(Objects.requireNonNull(file).getName(), new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
PsiNavigateUtil.navigate(item.getElement());
}
});
myLabel.setBackground(UIUtil.TRANSPARENT_COLOR);
myLabel.setOpaque(false);
myLabel.setIcon(IconUtil.getIcon(item.getFile().getVirtualFile(), Iconable.ICON_FLAG_READ_STATUS, item.getProject()));
}
public @NotNull JComponent getComponent() {
return myLabel;
}
}
@@ -0,0 +1,250 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.find.findUsages.similarity;
import com.intellij.icons.AllIcons;
import com.intellij.ide.ActivityTracker;
import com.intellij.ide.IdeBundle;
import com.intellij.ide.actions.RefreshAction;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.actionSystem.*;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.progress.Task;
import com.intellij.openapi.progress.impl.BackgroundableProcessIndicator;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.SimpleToolWindowPanel;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.Ref;
import com.intellij.ui.Gray;
import com.intellij.ui.JBColor;
import com.intellij.ui.ScrollPaneFactory;
import com.intellij.ui.components.ActionLink;
import com.intellij.ui.components.JBPanelWithEmptyText;
import com.intellij.ui.components.panels.VerticalLayout;
import com.intellij.usageView.UsageInfo;
import com.intellij.usageView.UsageViewBundle;
import com.intellij.usages.Usage;
import com.intellij.usages.UsageGroup;
import com.intellij.usages.UsageInfo2UsageAdapter;
import com.intellij.usages.UsageView;
import com.intellij.usages.impl.UsageViewImpl;
import com.intellij.usages.similarity.clustering.ClusteringSearchSession;
import com.intellij.usages.similarity.clustering.UsageCluster;
import com.intellij.usages.similarity.usageAdapter.SimilarityUsage;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.ui.JBUI;
import com.intellij.util.ui.scroll.BoundedRangeModelThresholdListener;
import kotlin.Unit;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.awt.*;
import java.util.List;
import java.util.*;
import static com.intellij.openapi.actionSystem.ActionPlaces.SIMILAR_USAGES_PREVIEW_TOOLBAR;
public class MostCommonUsagePatternsComponent extends SimpleToolWindowPanel implements Disposable {
private static final int CLUSTER_LIMIT = 20;
private final @NotNull Project myProject;
private final @NotNull Collection<Collection<? extends UsageGroup>> myGroups;
private final @NotNull UsageViewImpl myUsageView;
private final @NotNull JBPanelWithEmptyText myMainPanel;
private final @NotNull JScrollPane myScrollPane;
private @Nullable ClusteringSearchSession mySession;
private boolean myIsShowingSimilarUsages;
private boolean isDisposed = false;
private @Nullable BackgroundableProcessIndicator myProcessIndicator;
private int myAlreadyRenderedSnippets;
public MostCommonUsagePatternsComponent(@NotNull UsageViewImpl usageView,
@NotNull Collection<@NotNull Collection<? extends UsageGroup>> groups) {
super(false);
myUsageView = usageView;
myProject = usageView.getProject();
myGroups = groups;
setToolbar((JComponent)createToolbar(SIMILAR_USAGES_PREVIEW_TOOLBAR));
myMainPanel = new JBPanelWithEmptyText();
myMainPanel.setLayout(new VerticalLayout(0));
addMostCommonUsagesForSelectedGroups(myGroups);
myScrollPane = ScrollPaneFactory.createScrollPane(myMainPanel);
myScrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
revalidate();
setContent(myScrollPane);
}
@Nullable
public ClusteringSearchSession getSession() {
if (mySession == null) {
mySession = findClusteringSessionInUsageView(myUsageView);
}
return mySession;
}
public ActionToolbar createToolbar(@NotNull String place) {
DefaultActionGroup actionGroup = new DefaultActionGroup();
actionGroup.add(
new RefreshAction(IdeBundle.messagePointer("action.refresh"), IdeBundle.messagePointer("action.refresh"), AllIcons.Actions.Refresh) {
@Override
public void actionPerformed(@NotNull AnActionEvent e) {
myMainPanel.removeAll();
myMainPanel.revalidate();
myIsShowingSimilarUsages = false;
addMostCommonUsagesForSelectedGroups(myGroups);
}
@Override
public void update(@NotNull AnActionEvent event) {
Presentation presentation = event.getPresentation();
presentation.setEnabled(true);
}
});
actionGroup.add(
new AnAction(IdeBundle.messagePointer("action.Anonymous.text.back"), IdeBundle.messagePointer("action.Anonymous.text.back"),
AllIcons.Actions.Back) {
@Override
public void actionPerformed(@NotNull AnActionEvent e) {
if (myIsShowingSimilarUsages) {
myIsShowingSimilarUsages = false;
ActivityTracker.getInstance().inc();
setContent(myScrollPane);
revalidate();
}
}
@Override
public void update(@NotNull AnActionEvent event) {
Presentation presentation = event.getPresentation();
presentation.setEnabled(myIsShowingSimilarUsages);
}
});
ActionToolbar actionToolbar = ActionManager.getInstance().createActionToolbar(place, actionGroup, false);
actionToolbar.setTargetComponent(myMainPanel);
return actionToolbar;
}
private @NotNull ActionLink createOpenSimilarUsagesActionLink(UsageInfo info, @NotNull Set<SimilarityUsage> usagesToRender) {
final ActionLink actionLink = new ActionLink(UsageViewBundle.message("show.0.similar.usages", usagesToRender.size() - 1), e -> {
myIsShowingSimilarUsages = true;
ActivityTracker.getInstance().inc();
final SimilarUsagesComponent mySimilarComponent = new SimilarUsagesComponent(info, this);
JScrollPane scroll = ScrollPaneFactory.createScrollPane(mySimilarComponent);
final JScrollBar scrollBar = scroll.getVerticalScrollBar();
mySimilarComponent.renderOriginalUsage();
mySimilarComponent.renderSimilarUsages(usagesToRender);
BoundedRangeModelThresholdListener.Companion.install(scrollBar, () -> {
mySimilarComponent.renderSimilarUsages(usagesToRender);
return Unit.INSTANCE;
});
setContent(scroll);
revalidate();
});
actionLink.setLinkIcon();
return actionLink;
}
@Override
public void dispose() {
isDisposed = true;
if (myProcessIndicator != null && myProcessIndicator.isRunning()) {
myProcessIndicator.cancel();
}
}
private @NotNull JPanel createSummaryComponent(@Nullable ClusteringSearchSession session,
@NotNull List<UsageCluster> topClusters) {
JPanel summaryPanel = new JPanel(new VerticalLayout(0));
if (session == null) return summaryPanel;
myAlreadyRenderedSnippets = 0;
topClusters.stream().skip(myAlreadyRenderedSnippets).limit(CLUSTER_LIMIT).forEach(cluster -> {
renderCluster(summaryPanel, cluster);
});
final JScrollBar verticalScrollBar = myScrollPane.getVerticalScrollBar();
BoundedRangeModelThresholdListener.Companion.install(verticalScrollBar, () -> {
topClusters.stream().skip(myAlreadyRenderedSnippets).limit(CLUSTER_LIMIT).forEach(cluster -> {
renderCluster(summaryPanel, cluster);
});
return Unit.INSTANCE;
});
return summaryPanel;
}
private void renderCluster(@NotNull JPanel summaryPanel,
@NotNull UsageCluster cluster) {
final Set<SimilarityUsage> usageFilteredByGroup = new HashSet<>();
ApplicationManager.getApplication().runReadAction(() -> {
usageFilteredByGroup.addAll(cluster.getUsageFilteredByGroup(myGroups));
});
SimilarityUsage usage = ContainerUtil.getFirstItem(usageFilteredByGroup);
if (usage instanceof UsageInfo2UsageAdapter) {
final UsageInfo2UsageAdapter usageInfoAdapter = (UsageInfo2UsageAdapter)usage;
UsageCodeSnippetComponent summaryRendererComponent =
UsageCodeSnippetComponent.createUsageCodeSnippet(usageInfoAdapter.getUsageInfo());
if (!isDisposed) {
Disposer.register(this, summaryRendererComponent);
}
final JPanel headerPanel =
createHeaderPanel(usageInfoAdapter.getUsageInfo(), summaryRendererComponent.getEditor().getBackgroundColor());
if (usageFilteredByGroup.size() > 1) {
headerPanel.add(createOpenSimilarUsagesActionLink(usageInfoAdapter.getUsageInfo(), usageFilteredByGroup));
}
summaryPanel.add(headerPanel);
summaryPanel.add(summaryRendererComponent);
myAlreadyRenderedSnippets++;
}
}
private void addMostCommonUsagesForSelectedGroups(@NotNull Collection<Collection<? extends UsageGroup>> selectedGroups) {
Ref<List<UsageCluster>> sortedClusters = new Ref<>();
Task.Backgroundable loadMostCommonUsagePatternsTask =
new Task.Backgroundable(myProject, UsageViewBundle.message("loading.most.common.usage.patterns")) {
@Override
public void onSuccess() {
if (!sortedClusters.isNull()) {
myMainPanel.add(createSummaryComponent(getSession(), sortedClusters.get()));
myMainPanel.revalidate();
myIsShowingSimilarUsages = false;
}
}
@Override
public void run(@NotNull ProgressIndicator indicator) {
ClusteringSearchSession session = getSession();
if (session != null) {
final List<UsageCluster> groups = new ArrayList<>();
ApplicationManager.getApplication().runReadAction(() -> {
groups.addAll(session.getClustersFilteredByGroups(indicator, selectedGroups));
});
sortedClusters.set(groups);
}
}
};
myProcessIndicator = new BackgroundableProcessIndicator(loadMostCommonUsagePatternsTask);
ProgressManager.getInstance().runProcessWithProgressAsynchronously(loadMostCommonUsagePatternsTask,
myProcessIndicator);
}
public static @Nullable ClusteringSearchSession findClusteringSessionInUsageView(@NotNull UsageView usageView) {
Optional<Usage> usage = usageView.getUsages().stream().findFirst();
if (usage.isPresent()) {
Usage firstUsage = usage.get();
if (firstUsage instanceof SimilarityUsage) {
return ((SimilarityUsage)firstUsage).getClusteringSession();
}
}
return null;
}
public static @NotNull JPanel createHeaderPanel(@NotNull UsageInfo info, @NotNull Color backgroundColor) {
final LocationLinkComponent component = new LocationLinkComponent(info);
final JPanel header = new JPanel();
header.setBackground(backgroundColor);
header.setLayout(new FlowLayout(FlowLayout.LEFT));
header.add(component.getComponent());
header.setBorder(JBUI.Borders.customLineTop(new JBColor(Gray.xCD, Gray.x51)));
return header;
}
}
@@ -0,0 +1,81 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.find.findUsages.similarity;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.util.Disposer;
import com.intellij.psi.PsiElement;
import com.intellij.ui.*;
import com.intellij.ui.components.panels.VerticalLayout;
import com.intellij.usageView.UsageInfo;
import com.intellij.usageView.UsageViewBundle;
import com.intellij.usages.UsageInfo2UsageAdapter;
import com.intellij.usages.similarity.usageAdapter.SimilarityUsage;
import com.intellij.util.ui.JBUI;
import org.jetbrains.annotations.NotNull;
import javax.swing.*;
import java.awt.*;
import java.util.Collection;
public class SimilarUsagesComponent extends JPanel implements Disposable {
public static final int SNIPPET_LIMIT = 10;
private int alreadyShown = 0;
private final @NotNull UsageInfo myOriginalUsage;
public SimilarUsagesComponent(@NotNull UsageInfo originalUsage, @NotNull Disposable parent) {
myOriginalUsage = originalUsage;
setLayout(new VerticalLayout(0));
TitlePanel titlePanel = new TitlePanel();
titlePanel.getLabel().setHorizontalAlignment(SwingConstants.LEFT);
titlePanel.setText(UsageViewBundle.message("label.similar.usages"));
add(titlePanel);
Disposer.register(parent, this);
}
public void renderSimilarUsages(@NotNull Collection<SimilarityUsage> similarUsagesGroupUsages) {
if (alreadyShown < similarUsagesGroupUsages.size() - 1) {
similarUsagesGroupUsages.stream().skip(alreadyShown).limit(SNIPPET_LIMIT).forEach(usage -> {
final UsageInfo info = ((UsageInfo2UsageAdapter)usage).getUsageInfo();
if (myOriginalUsage != info) {
renderUsage(info);
alreadyShown++;
}
});
}
}
private void renderUsage(@NotNull UsageInfo info) {
PsiElement element = info.getElement();
if (element == null) return;
final UsageCodeSnippetComponent codeSnippet = UsageCodeSnippetComponent.createUsageCodeSnippet(info);
Disposer.register(this, codeSnippet);
Color color = codeSnippet.getEditor().getBackgroundColor();
add(getHeaderPanelForUsage(myOriginalUsage, info, color));
add(codeSnippet);
}
public void renderOriginalUsage() {
renderUsage(myOriginalUsage);
}
public static @NotNull JPanel getHeaderPanelForUsage(@NotNull UsageInfo originalUsage,
@NotNull UsageInfo usageInfo, @NotNull Color backGroundColor) {
final JPanel header = new JPanel(new FlowLayout(FlowLayout.LEFT));
header.setBackground(backGroundColor);
final JComponent link = new LocationLinkComponent(usageInfo).getComponent();
header.add(link);
if (usageInfo == originalUsage) {
final SimpleColoredComponent component = new SimpleColoredComponent();
component.append(UsageViewBundle.message("label.the.original.usage"), SimpleTextAttributes.REGULAR_BOLD_ATTRIBUTES);
header.add(component);
}
final Color color = new JBColor(Gray.xCD, Gray.x51);
header.setBorder(JBUI.Borders.customLineTop(color));
return header;
}
@Override
public void dispose() {
}
}
@@ -0,0 +1,131 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.find.findUsages.similarity;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.application.ReadAction;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.Ref;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.ui.ScrollPaneFactory;
import com.intellij.ui.components.JBPanelWithEmptyText;
import com.intellij.ui.components.panels.VerticalLayout;
import com.intellij.usageView.UsageInfo;
import com.intellij.usageView.UsageViewBundle;
import com.intellij.usages.PsiElementUsageTarget;
import com.intellij.usages.UsageContextPanel;
import com.intellij.usages.UsageTarget;
import com.intellij.usages.UsageView;
import com.intellij.usages.impl.UsageContextPanelBase;
import com.intellij.usages.impl.UsageViewImpl;
import com.intellij.usages.similarity.clustering.ClusteringSearchSession;
import com.intellij.usages.similarity.clustering.UsageCluster;
import com.intellij.usages.similarity.features.UsageSimilarityFeaturesProvider;
import com.intellij.util.RunnableCallable;
import com.intellij.util.concurrency.AppExecutorUtil;
import com.intellij.util.ui.scroll.BoundedRangeModelThresholdListener;
import kotlin.Unit;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.awt.*;
import java.util.List;
import static com.intellij.find.findUsages.similarity.MostCommonUsagePatternsComponent.findClusteringSessionInUsageView;
public class SimilarUsagesContextPanel extends UsageContextPanelBase {
private @NotNull final UsageViewImpl myUsageView;
private @Nullable JScrollPane myScrollPane;
private @Nullable ClusteringSearchSession mySession;
private @Nullable SimilarUsagesComponent mySimilarUsagesComponent;
public SimilarUsagesContextPanel(@NotNull Project project,
@NotNull UsageViewImpl usageView) {
super(project, usageView.getPresentation());
myUsageView = usageView;
}
@Override
public void dispose() {
super.dispose();
myScrollPane = null;
if (mySimilarUsagesComponent != null) {
Disposer.dispose(mySimilarUsagesComponent);
}
mySimilarUsagesComponent = null;
}
@Override
protected void updateLayoutLater(@Nullable List<? extends UsageInfo> infos) {
ApplicationManager.getApplication().assertIsDispatchThread();
removeAll();
if (mySimilarUsagesComponent != null) {
Disposer.dispose(mySimilarUsagesComponent);
}
JBPanelWithEmptyText mainPanel = new JBPanelWithEmptyText();
add(mainPanel);
if (infos == null || infos.size() == 0) return;
UsageInfo info = infos.get(0);
Ref<PsiElement> psiElementRef = new Ref<>();
ReadAction.nonBlocking(new RunnableCallable(() -> psiElementRef.set(info.getElement()))).finishOnUiThread(
ModalityState.NON_MODAL, context -> {
PsiElement psiElement = psiElementRef.get();
if (psiElement == null) return;
PsiFile psiFile = psiElement.getContainingFile();
if (psiFile == null) return;
ClusteringSearchSession session = findSessionInUsages();
if (session == null) return;
final UsageCluster cluster = session.findCluster(info);
if (cluster == null) return;
removeAll();
mainPanel.setLayout(new VerticalLayout(0));
mySimilarUsagesComponent = new SimilarUsagesComponent(info, this);
mainPanel.add(mySimilarUsagesComponent);
mySimilarUsagesComponent.renderOriginalUsage();
mySimilarUsagesComponent.renderSimilarUsages(cluster.getUsages());
myScrollPane = ScrollPaneFactory.createScrollPane(mainPanel);
myScrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
BoundedRangeModelThresholdListener.Companion.install(myScrollPane.getVerticalScrollBar(), () -> {
mySimilarUsagesComponent.renderSimilarUsages(cluster.getUsages());
return Unit.INSTANCE;
});
add(myScrollPane, BorderLayout.CENTER);
}).expireWith(this).submit(AppExecutorUtil.getAppExecutorService());
}
private @Nullable ClusteringSearchSession findSessionInUsages() {
if (mySession == null) {
mySession = findClusteringSessionInUsageView(myUsageView);
}
return mySession;
}
public static class Provider implements UsageContextPanel.Provider {
@Override
public @NotNull UsageContextPanel create(@NotNull UsageView usageView) {
return new SimilarUsagesContextPanel(((UsageViewImpl)usageView).getProject(), (UsageViewImpl)usageView);
}
@Override
public boolean isAvailableFor(@NotNull UsageView usageView) {
if (!ClusteringSearchSession.isSimilarUsagesClusteringEnabled()) return false;
UsageTarget[] targets = ((UsageViewImpl)usageView).getTargets();
if (targets.length == 0) return false;
UsageTarget target = targets[0];
if (!(target instanceof PsiElementUsageTarget)) return false;
return UsageSimilarityFeaturesProvider.EP_NAME.findFirstSafe(provider -> {
return provider.isAvailable((PsiElementUsageTarget)target);
}) != null;
}
@Override
public @NotNull String getTabTitle() {
return UsageViewBundle.message("similar.usages.tab");
}
}
}
@@ -0,0 +1,85 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.find.findUsages.similarity;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.EditorSettings;
import com.intellij.openapi.editor.LineNumberConverter;
import com.intellij.openapi.editor.colors.EditorColors;
import com.intellij.openapi.editor.ex.EditorEx;
import com.intellij.openapi.editor.ex.MarkupModelEx;
import com.intellij.openapi.editor.markup.HighlighterLayer;
import com.intellij.openapi.editor.markup.HighlighterTargetArea;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiElement;
import com.intellij.ui.EditorTextFieldCellRenderer;
import com.intellij.usageView.UsageInfo;
import com.intellij.util.ui.JBUI;
import org.jetbrains.annotations.NotNull;
import javax.swing.*;
public class UsageCodeSnippetComponent extends EditorTextFieldCellRenderer.SimpleWithGutterRendererComponent {
public static final int CONTEXT_LINE_NUMBER = 3;
public UsageCodeSnippetComponent(@NotNull PsiElement element) {
super(element.getProject(), element.getLanguage(), false);
setupEditor();
}
private void setupEditor() {
EditorEx editor = getEditor();
JScrollPane scrollPane = editor.getScrollPane();
editor.setBorder(JBUI.Borders.empty());
editor.getMarkupModel().removeAllHighlighters();
editor.getGutterComponentEx().setPaintBackground(false);
scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);
scrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED);
EditorSettings settings = editor.getSettings();
settings.setLineNumbersShown(true);
settings.setAdditionalLinesCount(0);
settings.setLineCursorWidth(1);
settings.setDndEnabled(false);
settings.setUseSoftWraps(false);
}
private void highlightRange(@NotNull TextRange rangeToHighlight) {
final MarkupModelEx markupModel = getEditor().getMarkupModel();
markupModel.addRangeHighlighter(EditorColors.SEARCH_RESULT_ATTRIBUTES, rangeToHighlight.getStartOffset(),
rangeToHighlight.getEndOffset(), HighlighterLayer.ADDITIONAL_SYNTAX, HighlighterTargetArea.EXACT_RANGE);
}
public void addUsagePreview(@NotNull UsageInfo usage) {
PsiElement element = usage.getElement();
if (element == null) return;
PsiDocumentManager docManager = PsiDocumentManager.getInstance(element.getProject());
Document doc = docManager.getDocument(element.getContainingFile());
if (doc == null) return;
int usageStartLineNumber = doc.getLineNumber(usage.getElement().getTextRange().getStartOffset());
int usageEndLineNumber = doc.getLineNumber(usage.getElement().getTextRange().getEndOffset());
final int contextStartLineNumber = Math.max(0, usageStartLineNumber - CONTEXT_LINE_NUMBER);
int startOffset = doc.getLineStartOffset(contextStartLineNumber);
int endOffset = doc.getLineEndOffset(Math.min(usageEndLineNumber + CONTEXT_LINE_NUMBER, doc.getLineCount() - 1));
getEditor().getGutterComponentEx().setLineNumberConverter(
contextStartLineNumber == 0 ? LineNumberConverter.DEFAULT : new LineNumberConverter.Increasing() {
@Override
public Integer convert(@NotNull Editor editor, int lineNumber) {
return lineNumber + contextStartLineNumber;
}
});
setText(doc.getText(new TextRange(startOffset, endOffset)));
getEditor().getGutterComponentEx().updateUI();
highlightRange(element.getTextRange().shiftLeft(startOffset));
}
public static @NotNull UsageCodeSnippetComponent createUsageCodeSnippet(@NotNull UsageInfo usage) {
PsiElement element = usage.getElement();
assert element != null;
UsageCodeSnippetComponent usageCodeSnippetComponent = new UsageCodeSnippetComponent(element);
usageCodeSnippetComponent.addUsagePreview(usage);
return usageCodeSnippetComponent;
}
}
@@ -8,6 +8,7 @@ import com.intellij.usages.UsageGroup;
import com.intellij.usages.UsageTarget;
import com.intellij.usages.rules.UsageFilteringRule;
import com.intellij.usages.rules.UsageGroupingRule;
import com.intellij.usages.similarity.usageAdapter.SimilarityUsage;
import com.intellij.util.Consumer;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
@@ -58,6 +59,9 @@ class UsageNodeTreeBuilder {
if (dumb && !DumbService.isDumbAware(rule)) continue;
List<UsageGroup> groups = rule.getParentGroupsFor(usage, myTargets);
if (usage instanceof SimilarityUsage) {
((SimilarityUsage)usage).addUsageGroupData(groups);
}
for (UsageGroup group : groups) {
groupNode = groupNode.addOrGetGroup(group, i, edtModelToSwingNodeChangesQueue);
}
@@ -3,6 +3,7 @@ package com.intellij.usages.impl;
import com.intellij.find.FindManager;
import com.intellij.find.FindModel;
import com.intellij.find.findUsages.similarity.MostCommonUsagePatternsComponent;
import com.intellij.ide.IdeTooltipManager;
import com.intellij.lang.injection.InjectedLanguageManager;
import com.intellij.openapi.actionSystem.CommonDataKeys;
@@ -33,8 +34,10 @@ import com.intellij.ui.awt.RelativePoint;
import com.intellij.usageView.UsageInfo;
import com.intellij.usageView.UsageViewBundle;
import com.intellij.usages.UsageContextPanel;
import com.intellij.usages.UsageGroup;
import com.intellij.usages.UsageView;
import com.intellij.usages.UsageViewPresentation;
import com.intellij.usages.similarity.clustering.ClusteringSearchSession;
import com.intellij.util.ui.PositionTracker;
import com.intellij.util.ui.StatusText;
import org.jetbrains.annotations.NonNls;
@@ -45,6 +48,7 @@ import javax.swing.*;
import java.awt.*;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.regex.Pattern;
@@ -60,6 +64,8 @@ public class UsagePreviewPanel extends UsageContextPanelBase implements DataProv
private Pattern myCachedSearchPattern;
private Pattern myCachedReplacePattern;
private final PropertyChangeSupport myPropertyChangeSupport = new PropertyChangeSupport(this);
private @Nullable UsageViewImpl myUsageView;
private @Nullable MostCommonUsagePatternsComponent myCommonUsagePatternsComponent;
public UsagePreviewPanel(@NotNull Project project, @NotNull UsageViewPresentation presentation) {
this(project, presentation, false);
@@ -72,6 +78,16 @@ public class UsagePreviewPanel extends UsageContextPanelBase implements DataProv
myIsEditor = isEditor;
}
public void setUsageView(@Nullable UsageViewImpl usageView) {
myUsageView = usageView;
}
@Nullable
public UsageViewImpl getUsageView() {
return myUsageView;
}
@Nullable
@Override
public Object getData(@NotNull @NonNls String dataId) {
@@ -88,11 +104,14 @@ public class UsagePreviewPanel extends UsageContextPanelBase implements DataProv
return null;
}
public static class Provider implements UsageContextPanel.Provider {
@NotNull
@Override
public UsageContextPanel create(@NotNull UsageView usageView) {
return new UsagePreviewPanel(((UsageViewImpl)usageView).getProject(), usageView.getPresentation(), true);
UsagePreviewPanel previewPanel = new UsagePreviewPanel(((UsageViewImpl)usageView).getProject(), usageView.getPresentation(), true);
previewPanel.setUsageView((UsageViewImpl)usageView);
return previewPanel;
}
@Override
@@ -326,6 +345,7 @@ public class UsagePreviewPanel extends UsageContextPanelBase implements DataProv
public void dispose() {
isDisposed = true;
releaseEditor();
disposeMostCommonUsageComponent();
for (Editor editor : EditorFactory.getInstance().getAllEditors()) {
if (editor.getProject() == myProject && editor.getUserData(PREVIEW_EDITOR_FLAG) == this) {
LOG.error("Editor was not released:" + editor);
@@ -333,6 +353,13 @@ public class UsagePreviewPanel extends UsageContextPanelBase implements DataProv
}
}
private void disposeMostCommonUsageComponent() {
if (myCommonUsagePatternsComponent != null) {
Disposer.dispose(myCommonUsagePatternsComponent);
}
myCommonUsagePatternsComponent = null;
}
private void releaseEditor() {
ApplicationManager.getApplication().assertIsDispatchThread();
if (myEditor != null) {
@@ -371,8 +398,30 @@ public class UsagePreviewPanel extends UsageContextPanelBase implements DataProv
}
@Override
public void updateLayoutLater(@Nullable final List<? extends UsageInfo> infos) {
public void updateLayoutLater(List<? extends UsageInfo> infos, @NotNull Collection<@NotNull Collection<? extends UsageGroup>> groups) {
ApplicationManager.getApplication().assertIsDispatchThread();
if (ClusteringSearchSession.isSimilarUsagesClusteringEnabled() && selectedGroupNodes(infos, groups) && myUsageView != null) {
releaseEditor();
disposeMostCommonUsageComponent();
removeAll();
myCommonUsagePatternsComponent = new MostCommonUsagePatternsComponent(myUsageView, groups);
add(myCommonUsagePatternsComponent);
} else {
previewUsages(infos);
}
}
private static boolean selectedGroupNodes(List<? extends UsageInfo> infos, Collection<Collection<? extends UsageGroup>> groups) {
return (infos == null || infos.isEmpty()) && !groups.isEmpty();
}
@Override
protected void updateLayoutLater(@Nullable List<? extends UsageInfo> infos) {
ApplicationManager.getApplication().assertIsDispatchThread();
previewUsages(infos);
}
private void previewUsages(@Nullable List<? extends UsageInfo> infos) {
String cannotPreviewMessage = cannotPreviewMessage(infos);
if (cannotPreviewMessage == null) {
resetEditor(infos);
@@ -2230,7 +2230,7 @@ public class UsageViewImpl implements UsageViewEx {
return USAGE_INFO_LIST_KEY.getData(DataManager.getInstance().getDataContext(myRootPanel));
}
private @NotNull Collection<Collection<? extends UsageGroup>> getSelectedGroups() {
private @NotNull Collection<@NotNull Collection<? extends UsageGroup>> getSelectedGroups() {
ApplicationManager.getApplication().assertIsDispatchThread();
Collection<Collection<? extends UsageGroup>> groupPaths = new ArrayList<>();
for (TreeNode node : selectedNodes()) {
@@ -9,6 +9,18 @@
<postStartupActivity implementation="com.intellij.usages.impl.rules.UsageFilteringRulesActivity"/>
<usages.usageReferenceClassProvider implementation="com.intellij.usages.impl.PsiElementUsageReferenceClassProvider"/>
<generatedSourceUsageFilter implementation="com.intellij.usages.impl.rules.DefaultGeneratedSourceUsageFilter"/>
<registryKey key="similarity.find.usages.enable" defaultValue="false" description="Enables grouping usages by similarity"/>
<registryKey key="similarity.find.usages.groups.threshold" defaultValue="0.9"
description="Similarity threshold for groups in find usages"/>
<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="Enable similarity clustering for Kotlin usages"
key="similarity.find.usages.kotlin.clustering.enable"/>
<registryKey defaultValue="true" description="Clustering without resolve" key="similarity.find.usages.fast.clustering"/>
<registryKey defaultValue="false" description="Parent statement condition feature"
key="similarity.find.usages.parent.statement.condition.feature"/>
<registryKey defaultValue="true" description="Use information from sibling nodes" key="similarity.find.usages.use.sibling.features"/>
</extensions>
<actions resource-bundle="messages.UsageViewBundle">
<group id="UsageGroupingActionGroup">
@@ -11,6 +11,8 @@ action.UsageFiltering.WriteAccess.text=Show Write Access
action.UsageFiltering.WriteAccess.description=Show usages that write the corresponding target
action.UsageFiltering.GeneratedCode.text=Show Results In Generated Code
action.UsageFiltering.Imports.text=Show Import Statements
0.similar.results.found.in.project={0, number} similar {0, choice, 1#result|2#results} found in project
similar.usages.tab=Similar Usages
group.UsageGroupingActionGroup.text=Usage View Grouping
action.group.by.title=Group By
action.group.by.file.structure=File Structure
@@ -127,4 +129,12 @@ usage.view.results.node.prefix=[{0}] {1}
usage.view.results.node.scope.in=in
references.to.be.changed=References to be changed {0}
occurrences.found.in.comments.strings.and.non.java.files=Occurrences found in comments, strings and non-code files
occurrences.found.in.comments.strings.non.java.files.and.generated.code=Occurrences found in comments, strings, non-code files and generated code
occurrences.found.in.comments.strings.non.java.files.and.generated.code=Occurrences found in comments, strings, non-code files and generated code
label.similar.usages=Similar usages
label.the.original.usage=(the original usage)
loading.most.common.usage.patterns=Loading most common usage patterns
show.0.similar.usages=Show {0, number} similar {0, choice, 1#usage|2#usages}
no.similar.usages.found=No Similar usages found
back=Back
refresh=Refresh
action.calculating.usage.features=Calculating Usage Features
@@ -7,6 +7,13 @@ import com.intellij.psi.PsiElement;
import com.intellij.psi.SmartPointerManager;
import com.intellij.psi.SmartPsiElementPointer;
import com.intellij.usageView.UsageInfo;
import com.intellij.usages.similarity.bag.Bag;
import com.intellij.usages.similarity.clustering.ClusteringSearchSession;
import com.intellij.usages.similarity.clustering.UsageCluster;
import com.intellij.usages.similarity.features.UsageSimilarityFeaturesProvider;
import com.intellij.usages.similarity.usageAdapter.SimilarityReadWriteUsageInfo2UsageAdapter;
import com.intellij.usages.similarity.usageAdapter.SimilarityUsage;
import com.intellij.usages.similarity.usageAdapter.SimilarityUsageInfo2UsageAdapter;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
@@ -109,6 +116,33 @@ public final class UsageInfoToUsageConverter {
return new UsageInfo2UsageAdapter(usageInfo);
}
public static @NotNull Usage convertToSimilarUsage(PsiElement @NotNull [] primaryElements,
@NotNull UsageInfo usageInfo,
@NotNull ClusteringSearchSession session) {
PsiElement usageElement = usageInfo.getElement();
if (usageElement != null && primaryElements.length != 0) {
Bag features = new Bag();
UsageSimilarityFeaturesProvider.EP_NAME.forEachExtensionSafe(provider -> {
features.addAll(provider.getFeatures(usageElement));
});
final UsageCluster cluster = session.findOrCreateCluster(features);
if (cluster != null) {
final ReadWriteAccessDetector.Access readWriteAccess = ReadWriteUtil.getReadWriteAccess(primaryElements, usageElement);
final Usage similarityUsageAdapter;
if (readWriteAccess != null) {
similarityUsageAdapter = new SimilarityReadWriteUsageInfo2UsageAdapter(usageInfo, readWriteAccess, features, session, cluster);
}
else {
similarityUsageAdapter = new SimilarityUsageInfo2UsageAdapter(usageInfo, features, session, cluster);
}
cluster.addUsage((SimilarityUsage)similarityUsageAdapter);
return similarityUsageAdapter;
}
}
return convert(primaryElements, usageInfo);
}
public static Usage @NotNull [] convert(@NotNull TargetElementsDescriptor descriptor, UsageInfo @NotNull [] usageInfos) {
Usage[] usages = new Usage[usageInfos.length];
for (int i = 0; i < usages.length; i++) {
@@ -0,0 +1,86 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.usages.similarity.bag;
import com.intellij.util.containers.Interner;
import it.unimi.dsi.fastutil.objects.Object2IntMap;
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
import org.jetbrains.annotations.NotNull;
public class Bag {
public static final Bag EMPTY_BAG = new Bag();
private static final Interner<String> ourInterner = Interner.createWeakInterner();
private final @NotNull Object2IntMap<String> myBag;
private int myCardinality;
public Bag() {
myBag = new Object2IntOpenHashMap<>();
myBag.defaultReturnValue(0);
}
public Bag(String @NotNull ... words) {
myBag = new Object2IntOpenHashMap<>();
myBag.defaultReturnValue(0);
for (String word : words) {
final String internedWord = ourInterner.intern(word);
myBag.put(internedWord, myBag.getInt(internedWord) + 1);
myCardinality += 1;
}
}
public @NotNull Object2IntMap<String> getBag() {
return myBag;
}
public void addAll(@NotNull Bag bag) {
for (Object2IntMap.Entry<String> entry : bag.myBag.object2IntEntrySet()) {
final String key = entry.getKey();
myBag.put(key, myBag.getInt(key) + entry.getIntValue());
myCardinality += entry.getIntValue();
}
}
public int getCardinality() {
return myCardinality;
}
public void add(@NotNull String key) {
final String internedKey = ourInterner.intern(key);
myBag.put(internedKey, myBag.getInt(internedKey) + 1);
}
public int get(@NotNull String word) {
return myBag.getInt(word);
}
public boolean isEmpty() {
return myBag.isEmpty();
}
@Override
public String toString() {
StringBuilder toReturn = new StringBuilder();
for (Object2IntMap.Entry<String> entry : getBag().object2IntEntrySet()) {
toReturn.append(entry.getKey()).append(" : ").append(entry.getIntValue()).append("\n");
}
return toReturn.toString();
}
public static int intersectionSize(@NotNull Bag bag1, @NotNull Bag bag2) {
Bag toIterate = (bag1.getBag().size() > bag2.getBag().size()) ? bag2 : bag1;
Bag toProcess = (bag1.getBag().size() > bag2.getBag().size()) ? bag1 : bag2;
int cardinality = 0;
for (Object2IntMap.Entry<String> entry : toIterate.getBag().object2IntEntrySet()) {
cardinality += Math.min(toProcess.getBag().getInt(entry.getKey()), toIterate.getBag().getInt(entry.getKey()));
}
return cardinality;
}
public static @NotNull Bag concat(Bag @NotNull ... bags) {
Bag bag = new Bag();
for (Bag it : bags) {
bag.addAll(it);
}
return bag;
}
}
@@ -0,0 +1,29 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.usages.similarity.bag;
import org.jetbrains.annotations.NotNull;
public class BagsDistanceCalculator {
private final double mySimilarityThreshold;
private final @NotNull Bag myExistingUsageFeatures;
public BagsDistanceCalculator(@NotNull Bag existingUsageFeatures, double similarityThreshold) {
this.myExistingUsageFeatures = existingUsageFeatures;
this.mySimilarityThreshold = similarityThreshold;
}
public double similarity(@NotNull Bag usageFeaturesToCheck) {
final double similarity = jaccardSimilarity(usageFeaturesToCheck, myExistingUsageFeatures);
if (similarity >= mySimilarityThreshold) {
return similarity;
}
return 0.0;
}
public static double jaccardSimilarity(@NotNull Bag bag1, @NotNull Bag bag2) {
final int cardinality1 = bag1.getCardinality();
final int cardinality2 = bag2.getCardinality();
int intersectionSize = Bag.intersectionSize(bag1, bag2);
return intersectionSize / (double)(cardinality1 + cardinality2 - intersectionSize);
}
}
@@ -0,0 +1,127 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.usages.similarity.clustering;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.util.registry.Registry;
import com.intellij.usageView.UsageInfo;
import com.intellij.usages.UsageGroup;
import com.intellij.usages.UsageInfo2UsageAdapter;
import com.intellij.usages.similarity.bag.Bag;
import com.intellij.usages.similarity.bag.BagsDistanceCalculator;
import com.intellij.usages.similarity.usageAdapter.SimilarityUsage;
import com.intellij.util.concurrency.annotations.RequiresBackgroundThread;
import com.intellij.util.concurrency.annotations.RequiresReadLock;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.stream.Collectors;
/**
* Does usage clustering during the find usage process. Clusters are used on find usages results presentation.
*/
public class ClusteringSearchSession {
private final @NotNull List<UsageCluster> myClusters;
private final double mySimilarityThreshold;
public ClusteringSearchSession() {
myClusters = new CopyOnWriteArrayList<>();
mySimilarityThreshold = Registry.doubleValue("similarity.find.usages.groups.threshold");
}
public @NotNull List<UsageCluster> getClusters() {
return myClusters;
}
@RequiresBackgroundThread
public synchronized @Nullable UsageCluster findOrCreateCluster(@NotNull Bag usageFeatures) {
if (usageFeatures.getBag().isEmpty()) {
return null;
}
UsageCluster cluster = getTheMostSimilarCluster(usageFeatures);
if (cluster == null) {
cluster = createNewCluster();
}
return cluster;
}
/**
* This method is designed to use from {@link com.intellij.usages.UsageContextPanel#updateLayout(List)}
*/
public @Nullable UsageCluster findCluster(@Nullable UsageInfo usageInfo) {
for (UsageCluster cluster : myClusters) {
for (SimilarityUsage usage : cluster.getUsages()) {
if (usage instanceof UsageInfo2UsageAdapter && ((UsageInfo2UsageAdapter)usage).getUsageInfo().equals(usageInfo)) {
return cluster;
}
}
}
return null;
}
@RequiresBackgroundThread
@RequiresReadLock
public @NotNull List<UsageCluster> getClustersFilteredByGroups(@NotNull ProgressIndicator indicator,
@NotNull Collection<Collection<? extends UsageGroup>> selectedGroups) {
//create new ArrayList from clusters to avoid a comparator contract violation during search
return new ArrayList<>(getClusters()).stream()
.sorted((o1, o2) -> {
indicator.checkCanceled();
return Integer.compare(o2.getUsageFilteredByGroup(selectedGroups).size(), o1.getUsageFilteredByGroup(selectedGroups).size());
}).collect(Collectors.toList());
}
private @NotNull UsageCluster createNewCluster() {
final UsageCluster newCluster = new UsageCluster(myClusters.size());
myClusters.add(newCluster);
return newCluster;
}
private @Nullable UsageCluster getTheMostSimilarCluster(@NotNull Bag features) {
if (myClusters.size() == 0) {
return null;
}
UsageCluster mostUsageCluster = null;
double maxSimilarity = Double.MIN_VALUE;
for (UsageCluster currentGroup : myClusters) {
double similarity = findMinimalSimilarity(currentGroup, features, mySimilarityThreshold);
if (maxSimilarity < similarity) {
if (similarity == 1.0) {
return currentGroup;
}
mostUsageCluster = currentGroup;
maxSimilarity = similarity;
}
}
return mostUsageCluster;
}
private static double findMinimalSimilarity(@NotNull UsageCluster usageCluster, Bag newUsageFeatures, double threshold) {
final BagsDistanceCalculator bagsDistanceCalculator = new BagsDistanceCalculator(newUsageFeatures, threshold);
double min = Double.MAX_VALUE;
for (SimilarityUsage usage : usageCluster.getUsages()) {
final double similarity = bagsDistanceCalculator.similarity(usage.getFeatures());
if (similarity < min) {
min = similarity;
}
if (min == 0.0) {
return min;
}
}
return min;
}
@Nullable
public static ClusteringSearchSession createClusteringSessionIfEnabled() {
return isSimilarUsagesClusteringEnabled() ? (new ClusteringSearchSession()) : null;
}
public static boolean isSimilarUsagesClusteringEnabled() {
return Registry.is("similarity.find.usages.enable") && ApplicationManager.getApplication().isInternal();
}
}
@@ -0,0 +1,64 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.usages.similarity.clustering;
import com.intellij.usages.UsageGroup;
import com.intellij.usages.similarity.usageAdapter.SimilarityUsage;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import java.util.Collection;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.stream.Collectors;
public class UsageCluster {
private final int myIndex;
private final Set<SimilarityUsage> myUsages = new CopyOnWriteArraySet<>();
public UsageCluster(int index) {
this.myIndex = index;
}
public int getIndex() {
return myIndex;
}
public synchronized void addUsage(@NotNull SimilarityUsage usage) {
myUsages.add(usage);
}
public @NotNull Set<SimilarityUsage> getUsages() {
return myUsages;
}
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof UsageCluster)) return false;
return myIndex == ((UsageCluster)o).myIndex;
}
@NotNull
public Set<SimilarityUsage> getUsageFilteredByGroup(@NotNull Collection<@NotNull Collection<? extends UsageGroup>> groups) {
return getUsages().stream().filter(e -> belongsToGroup(e, groups)).collect(Collectors.toSet());
}
public static boolean belongsToGroup(@NotNull SimilarityUsage info,
@NotNull Collection<@NotNull Collection<? extends UsageGroup>> selectedGroupPaths) {
for (Collection<? extends UsageGroup> selectedGroupPath : selectedGroupPaths) {
if (ContainerUtil.intersection(selectedGroupPath, info.getUsageGroupData()).size() == selectedGroupPath.size()) {
return true;
}
}
return false;
}
@Override
public String toString() {
return "{" +
"id=" + myIndex + "\n" +
", myUsages=" + myUsages.stream().map(usage -> usage.toString()).collect(Collectors.joining(",\n")) +
"}\n";
}
}
@@ -0,0 +1,24 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.usages.similarity.features;
import com.intellij.openapi.extensions.ExtensionPointName;
import com.intellij.psi.PsiElement;
import com.intellij.usages.PsiElementUsageTarget;
import com.intellij.usages.similarity.bag.Bag;
import com.intellij.util.concurrency.annotations.RequiresBackgroundThread;
import com.intellij.util.concurrency.annotations.RequiresReadLock;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
@ApiStatus.Experimental
public interface UsageSimilarityFeaturesProvider {
@ApiStatus.Experimental
ExtensionPointName<UsageSimilarityFeaturesProvider> EP_NAME = ExtensionPointName.create("com.intellij.usageFeaturesProvider");
@RequiresReadLock
@RequiresBackgroundThread
@NotNull Bag getFeatures(@NotNull PsiElement usage);
@RequiresReadLock
boolean isAvailable(@NotNull PsiElementUsageTarget usageTarget);
}
@@ -0,0 +1,104 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.usages.similarity.features;
import com.intellij.openapi.util.registry.Registry;
import com.intellij.psi.PsiComment;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiWhiteSpace;
import com.intellij.psi.util.PsiUtilCore;
import com.intellij.usages.similarity.bag.Bag;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Arrays;
public class UsageSimilarityFeaturesRecorder {
private final @NotNull Bag myFeatures;
private final @Nullable PsiElement myInitialElement;
public UsageSimilarityFeaturesRecorder(@Nullable PsiElement initialContext) {
myFeatures = new Bag();
myInitialElement = initialContext;
}
public @NotNull Bag getFeatures() {
return myFeatures;
}
public void addFeature(@NotNull String tokenFeature) {
myFeatures.add(tokenFeature);
}
public void addAllFeatures(@NotNull PsiElement element, @Nullable String tokenFeature) {
if (tokenFeature != null) {
myFeatures.add(tokenFeature);
if (element != myInitialElement) {
addStructuralFeatures(element, tokenFeature);
}
}
}
private void addParentFeatures(@NotNull PsiElement element, @Nullable String tokenFeature) {
if (!Registry.is("similarity.find.usages.use.parent.features")) {
return;
}
PsiElement parent = element.getParent();
PsiElement grandParent;
PsiElement grandGrandParent;
if (parent != null) {
grandParent = parent.getParent();
if (grandParent != null) {
myFeatures.add("P:" + tokenFeature + " " + PsiUtilCore.getElementType(parent) + " " + getChildNumber(parent, element));
grandGrandParent = grandParent.getParent();
if (grandGrandParent != null) {
myFeatures.add(
"GP:" + tokenFeature + " " + PsiUtilCore.getElementType(grandParent) + " " + getChildNumber(grandParent, parent));
}
}
}
}
private void addSiblingFeatures(@NotNull PsiElement element, @Nullable String tokenFeature) {
if (!Registry.is("similarity.find.usages.use.sibling.features")) {
return;
}
myFeatures.add("PREV: " + getPrevMeaningfulSibling(element) + " " + tokenFeature);
myFeatures.add("NEXT: " + tokenFeature + " " + getNextMeaningfulSibling(element));
}
public void addStructuralFeatures(@NotNull PsiElement element, @NotNull String tokenFeature) {
addParentFeatures(element, tokenFeature);
addSiblingFeatures(element, tokenFeature);
}
private static int getChildNumber(@NotNull PsiElement parent, @NotNull PsiElement child) {
if (!Registry.is("similarity.find.usages.use.parent.features.with.child.number")) return -1;
PsiElement[] children = Arrays.stream(parent.getChildren()).filter(e -> !(e instanceof PsiWhiteSpace)).toArray(PsiElement[]::new);
for (int i = 0; i < children.length; i++) {
if (children[i] == child) {
return i;
}
}
return -1;
}
private static @Nullable PsiElement getNextMeaningfulSibling(@NotNull PsiElement element) {
PsiElement nextSibling = element.getNextSibling();
if (nextSibling != null) {
while (nextSibling instanceof PsiWhiteSpace || nextSibling instanceof PsiComment) {
nextSibling = nextSibling.getNextSibling();
}
}
return nextSibling;
}
private static @Nullable PsiElement getPrevMeaningfulSibling(@NotNull PsiElement element) {
PsiElement prevSibling = element.getPrevSibling();
if (prevSibling != null) {
while (prevSibling instanceof PsiWhiteSpace || prevSibling instanceof PsiComment) {
prevSibling = prevSibling.getPrevSibling();
}
}
return prevSibling;
}
}
@@ -0,0 +1,80 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.usages.similarity.internal;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.CommonDataKeys;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.fileTypes.PlainTextFileType;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.progress.Task;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.project.ProjectUtil;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.*;
import com.intellij.usageView.UsageViewBundle;
import com.intellij.usages.similarity.bag.Bag;
import com.intellij.usages.similarity.features.UsageSimilarityFeaturesProvider;
import com.intellij.util.LocalTimeCounter;
import org.jetbrains.annotations.NotNull;
import static com.intellij.openapi.application.ActionsKt.runWriteAction;
public class ShowUsageFeaturesInternalAction extends AnAction {
@Override
public void actionPerformed(@NotNull AnActionEvent e) {
Editor editor = e.getData(CommonDataKeys.EDITOR);
if (editor == null) return;
final PsiFile file = e.getData(CommonDataKeys.PSI_FILE);
if (file == null) return;
final Project project = e.getProject();
assert project != null;
final VirtualFile projectDir = ProjectUtil.guessProjectDir(project);
assert projectDir != null;
PsiDirectory directory = PsiManager.getInstance(project).findDirectory(projectDir);
assert directory != null;
final Ref<PsiFile> featuresDump = new Ref<>();
calculateFeaturesForUsage(editor, file, project, featuresDump);
createFileWithFeatures(directory, featuresDump).navigate(true);
}
private static void calculateFeaturesForUsage(@NotNull Editor editor,
@NotNull PsiFile file,
@NotNull Project project,
@NotNull Ref<PsiFile> featuresDump) {
ProgressManager.getInstance().run(new Task.Modal(project, UsageViewBundle.message("action.calculating.usage.features"), true) {
@Override
public void run(@NotNull ProgressIndicator indicator) {
final Bag features = new Bag();
ApplicationManager.getApplication().runReadAction(() -> {
PsiElement element = file.findElementAt(editor.getCaretModel().getOffset());
if (element != null) {
UsageSimilarityFeaturesProvider.EP_NAME.forEachExtensionSafe(provider -> {
features.addAll(provider.getFeatures(element));
});
}
});
featuresDump.set(
PsiFileFactory.getInstance(project).createFileFromText("features" + System.currentTimeMillis(), PlainTextFileType.INSTANCE,
StringUtil.join(
features.getBag().object2IntEntrySet(), ",\n"),
LocalTimeCounter.currentTime(), false, false));
}
});
}
private static @NotNull PsiFile createFileWithFeatures(@NotNull PsiDirectory directory, @NotNull Ref<PsiFile> featuresDumpFile) {
return runWriteAction(() ->
{
final PsiElement featureDumpFile = directory.add(featuresDumpFile.get());
assert featureDumpFile instanceof PsiFile;
return (PsiFile)featureDumpFile;
}
);
}
}
@@ -0,0 +1,59 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.usages.similarity.usageAdapter;
import com.intellij.codeInsight.highlighting.ReadWriteAccessDetector;
import com.intellij.usageView.UsageInfo;
import com.intellij.usages.ReadWriteAccessUsageInfo2UsageAdapter;
import com.intellij.usages.UsageGroup;
import com.intellij.usages.similarity.bag.Bag;
import com.intellij.usages.similarity.clustering.ClusteringSearchSession;
import com.intellij.usages.similarity.clustering.UsageCluster;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
public class SimilarityReadWriteUsageInfo2UsageAdapter extends ReadWriteAccessUsageInfo2UsageAdapter implements SimilarityUsage {
private final @NotNull Bag myFeatures;
private final @NotNull ClusteringSearchSession mySession;
private final @Nullable UsageCluster myCluster;
private final @NotNull List<UsageGroup> myGroups;
public SimilarityReadWriteUsageInfo2UsageAdapter(@NotNull UsageInfo usageInfo,
@NotNull ReadWriteAccessDetector.Access rwAccess,
@NotNull Bag features,
@NotNull ClusteringSearchSession session, @Nullable UsageCluster cluster) {
super(usageInfo, rwAccess);
myFeatures = features;
mySession = session;
myGroups = new ArrayList<>();
myCluster = cluster;
}
@Override
public @NotNull Bag getFeatures() {
return myFeatures;
}
@Override
public @NotNull ClusteringSearchSession getClusteringSession() {
return mySession;
}
@Override
public @Nullable UsageCluster getCluster() {
return myCluster;
}
@Override
public @NotNull List<UsageGroup> getUsageGroupData() {
return myGroups;
}
@Override
public void addUsageGroupData(@NotNull List<UsageGroup> groups) {
myGroups.addAll(groups);
}
}
@@ -0,0 +1,44 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.usages.similarity.usageAdapter;
import com.intellij.usages.UsageGroup;
import com.intellij.usages.similarity.bag.Bag;
import com.intellij.usages.similarity.clustering.ClusteringSearchSession;
import com.intellij.usages.similarity.clustering.UsageCluster;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
/**
* This interface marks UsageInfo2UsageAdapter implementation as a subject of clustering
*/
@ApiStatus.Experimental
public interface SimilarityUsage {
/**
* Each usage provides a {@link Bag} of features (string tokens) collected by {@link com.intellij.usages.similarity.features.UsageSimilarityFeaturesProvider} implementation
*
* @return a Bag of words {@link Bag} with string features related to usage context usage
*/
@NotNull Bag getFeatures();
@NotNull ClusteringSearchSession getClusteringSession();
@Nullable UsageCluster getCluster();
/**
* Pre-cached {@link UsageGroup} data from {@link com.intellij.usages.rules.UsageGroupingRule} which is calculated on Usage tree building
*
* @return list of usage {@link UsageGroup} obtained from {@link com.intellij.usages.rules.UsageGroupingRule}
*/
@NotNull List<UsageGroup> getUsageGroupData();
/**
* This method is use for adding group data
*
* @param groups
*/
void addUsageGroupData(@NotNull List<UsageGroup> groups);
}
@@ -0,0 +1,56 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.usages.similarity.usageAdapter;
import com.intellij.usageView.UsageInfo;
import com.intellij.usages.UsageGroup;
import com.intellij.usages.UsageInfo2UsageAdapter;
import com.intellij.usages.similarity.bag.Bag;
import com.intellij.usages.similarity.clustering.ClusteringSearchSession;
import com.intellij.usages.similarity.clustering.UsageCluster;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
public class SimilarityUsageInfo2UsageAdapter extends UsageInfo2UsageAdapter implements SimilarityUsage {
private final @NotNull Bag myFeatures;
private final @NotNull ClusteringSearchSession mySession;
private final @NotNull List<UsageGroup> myGroups;
private final @Nullable UsageCluster myCluster;
public SimilarityUsageInfo2UsageAdapter(@NotNull UsageInfo usageInfo, @NotNull Bag features, @NotNull ClusteringSearchSession session,
@Nullable UsageCluster cluster) {
super(usageInfo);
myFeatures = features;
mySession = session;
myGroups = new ArrayList<>();
myCluster = cluster;
}
@Override
public @NotNull ClusteringSearchSession getClusteringSession() {
return mySession;
}
@Override
public @Nullable UsageCluster getCluster() {
return myCluster;
}
@Override
public @NotNull List<UsageGroup> getUsageGroupData() {
return myGroups;
}
@Override
public void addUsageGroupData(@NotNull List<UsageGroup> groups) {
myGroups.addAll(groups);
}
@Override
public @NotNull Bag getFeatures() {
return myFeatures;
}
}
@@ -0,0 +1,63 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.kotlin.idea.findUsages.similarity
import com.intellij.psi.PsiElement
import com.intellij.usages.similarity.bag.Bag
import com.intellij.usages.similarity.features.UsageSimilarityFeaturesRecorder
import org.jetbrains.kotlin.psi.*
class KotlinSimilarityPropertiesExtractor(context: PsiElement) : KtTreeVisitorVoid() {
private val myUsageSimilarityFeaturesRecorder =
UsageSimilarityFeaturesRecorder(context)
private val myContext = context
fun getFeatures(): Bag {
myContext.accept(this)
return myUsageSimilarityFeaturesRecorder.features
}
override fun visitStringTemplateExpression(expression: KtStringTemplateExpression) {
myUsageSimilarityFeaturesRecorder.addAllFeatures(expression, "literal: string")
super.visitStringTemplateExpression(expression)
}
override fun visitProperty(property: KtProperty) {
myUsageSimilarityFeaturesRecorder.addAllFeatures(property, "VAR: ")
super.visitProperty(property)
}
override fun visitBinaryExpression(expression: KtBinaryExpression) {
myUsageSimilarityFeaturesRecorder.addAllFeatures(expression, expression.operationToken.toString())
super.visitBinaryExpression(expression)
}
override fun visitIsExpression(expression: KtIsExpression) {
myUsageSimilarityFeaturesRecorder.addAllFeatures(expression, "instanceof")
super.visitIsExpression(expression)
}
override fun visitLambdaExpression(lambdaExpression: KtLambdaExpression) {
myUsageSimilarityFeaturesRecorder.addAllFeatures(lambdaExpression, "lambda")
super.visitLambdaExpression(lambdaExpression)
}
override fun visitUnaryExpression(expression: KtUnaryExpression) {
myUsageSimilarityFeaturesRecorder.addAllFeatures(expression, expression.operationToken.toString())
super.visitUnaryExpression(expression)
}
override fun visitReferenceExpression(expression: KtReferenceExpression) {
var feature = "VAR:"
if (!theFirstReferenceInQualifiedExpression(expression)) {
if (expression is KtNameReferenceExpression) {
feature = "{CALL: ${expression.getReferencedName()}}"
}
}
myUsageSimilarityFeaturesRecorder.addAllFeatures(expression, feature)
super.visitReferenceExpression(expression)
}
private fun theFirstReferenceInQualifiedExpression(expression: KtReferenceExpression) =
(expression.parent is KtQualifiedExpression && expression.parent.firstChild == expression)
}
@@ -0,0 +1,46 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.kotlin.idea.findUsages.similarity
import com.intellij.openapi.util.Condition
import com.intellij.openapi.util.registry.Registry
import com.intellij.psi.PsiElement
import com.intellij.psi.util.PsiTreeUtil
import com.intellij.usages.PsiElementUsageTarget
import com.intellij.usages.similarity.bag.Bag
import com.intellij.usages.similarity.features.UsageSimilarityFeaturesProvider
import com.intellij.util.concurrency.annotations.RequiresBackgroundThread
import com.intellij.util.concurrency.annotations.RequiresReadLock
import org.jetbrains.kotlin.psi.KtBlockExpression
import org.jetbrains.kotlin.psi.KtFunction
import org.jetbrains.kotlin.psi.KtStatementExpression
class KotlinUsageSimilarityFeaturesProvider : UsageSimilarityFeaturesProvider {
@RequiresReadLock
@RequiresBackgroundThread
override fun getFeatures(usage: PsiElement): Bag {
val properties = Bag()
if (!Registry.`is`("similarity.find.usages.kotlin.clustering.enable")) {
return properties
}
val statement = getContext(usage)
if (statement != null) {
properties.addAll(KotlinSimilarityPropertiesExtractor(statement).getFeatures())
}
return properties
}
@RequiresReadLock
override fun isAvailable(usageTarget: PsiElementUsageTarget): Boolean {
return usageTarget.element is KtFunction
}
fun getContext(element: PsiElement): PsiElement? {
return PsiTreeUtil.findFirstParent(
element,
false,
Condition { e: PsiElement? -> e is KtStatementExpression || e?.parent is KtBlockExpression },
)
}
}
@@ -0,0 +1,64 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.kotlin.findUsages
import com.intellij.testFramework.TestDataPath
import com.intellij.testFramework.assertEqualsToFile
import com.intellij.usages.UsageInfoToUsageConverter
import com.intellij.usages.similarity.clustering.ClusteringSearchSession
import org.jetbrains.kotlin.idea.findUsages.similarity.KotlinUsageSimilarityFeaturesProvider
import org.jetbrains.kotlin.idea.test.TestRoot
import org.jetbrains.kotlin.test.TestMetadata
import org.junit.internal.runners.JUnit38ClassRunner
import org.junit.runner.RunWith
import java.io.File
@TestRoot("idea/tests")
@TestDataPath("\$CONTENT_ROOT")
@TestMetadata("testData/findUsages/similarity")
@RunWith(
JUnit38ClassRunner::class
)
class KotlinGroupUsagesBySimilarityTest : AbstractFindUsagesTest() {
fun testSimpleFeatures() {
myFixture.configureByFile(getTestName(true)+".kt")
val elementAtCaret = myFixture.getReferenceAtCaretPosition()!!.element
val features = KotlinUsageSimilarityFeaturesProvider().getFeatures(elementAtCaret)
assertEquals(1, features["VAR: "])
assertEquals(1, features["{CALL: inc}"])
}
@TestMetadata("general.kt")
@Throws(Exception::class)
fun testGeneral() {
doTest()
}
@TestMetadata("chainCalls")
fun testChainCalls() {
myFixture.configureByFile(getTestName(true)+".kt")
val findUsages = findUsages(myFixture.elementAtCaret, null, false, myFixture.project)
val session = ClusteringSearchSession();
findUsages.forEach { usage -> UsageInfoToUsageConverter.convertToSimilarUsage(arrayOf(myFixture.elementAtCaret), usage, session) }
val file = File(testDataDirectory, getTestName(true)+".results.txt")
assertEqualsToFile("", file, session.clusters.toString())
}
fun testListAdd() {
doTest()
}
fun testFilter() {
doTest()
}
private fun doTest() {
myFixture.configureByFile(getTestName(true)+".kt")
val findUsages = findUsages(myFixture.elementAtCaret, null, false, myFixture.project)
val session = ClusteringSearchSession();
findUsages.forEach { usage -> UsageInfoToUsageConverter.convertToSimilarUsage(arrayOf(myFixture.elementAtCaret), usage, session) }
val file = File(testDataDirectory, getTestName(true) + ".results.txt")
assertEqualsToFile("", file, session.clusters.toString())
}
}
@@ -0,0 +1,39 @@
class PsiElement() {
val operationReference = PsiElement()
lateinit var myTextRange: TextRange
public constructor(textRange: TextRange) : this() {
myTextRange = textRange;
}
public fun getTex<caret>tRange(): TextRange {
return myTextRange;
}
}
class TextRange(val startOffset: Int, val endOffset: Int) {
fun containsOffset(caretOffset: Int): Boolean {
}
}
class ToTest {
fun f() {
val element = PsiElement(TextRange(1, 5))
val opRef = element.operationReference
if (!opRef.getTextRange().containsOffset(2)) return
System.out.println(element.getTextRange())
System.out.println(opRef.getTextRange().containsOffset(2))
System.out.println(opRef.getTextRange().containsOffset(5))
System.out.println(element.getTextRange().startOffset + 1)
System.out.println(element.getTextRange().startOffset + 2)
if (!opRef.getTextRange().containsOffset(3)) return
}
}
@@ -0,0 +1,12 @@
[{id=0
, myUsages=28|if| (!opRef.|getTextRange|().containsOffset(|2|)) |return,
35|if| (!opRef.|getTextRange|().containsOffset(|3|)) |return}
, {id=1
, myUsages=30|System.out.println(element.|getTextRange|())}
, {id=2
, myUsages=31|System.out.println(opRef.|getTextRange|().containsOffset(|2|)),
32|System.out.println(opRef.|getTextRange|().containsOffset(|5|))}
, {id=3
, myUsages=33|System.out.println(element.|getTextRange|().startOffset + |1|),
34|System.out.println(element.|getTextRange|().startOffset + |2|)}
]
@@ -0,0 +1,12 @@
fun f() {
val numbersMap = mapOf("key1" to 1, "key2" to 2, "key3" to 3, "key11" to 11)
var filteredMap = "";
filteredMap = numbersMap.<caret>filter { (key, value) -> value > 10 && key.endsWith("1")}
filteredMap = numbersMap.filter { (key, value) -> key.endsWith("5") && value > 1}
filteredMap = numbersMap.filter { (key, value) -> false }
filteredMap = numbersMap.filter { (key, value) -> true }
filteredMap = numbersMap.filter { (key, value) -> key.endsWith("1") }
filteredMap = numbersMap.filter { (key, value) -> key.endsWith("1") }
println(filteredMap)
}
@@ -0,0 +1,10 @@
[{id=0
, myUsages=5|filteredMap = numbersMap.|filter| { (key, value) -> value > |10| && key.endsWith(|"1"|)},
6|filteredMap = numbersMap.|filter| { (key, value) -> key.endsWith(|"5"|) && value > |1|}}
, {id=1
, myUsages=7|filteredMap = numbersMap.|filter| { (key, value) -> |false| },
8|filteredMap = numbersMap.|filter| { (key, value) -> |true| }}
, {id=2
, myUsages=9|filteredMap = numbersMap.|filter| { (key, value) -> key.endsWith(|"1"|) },
10|filteredMap = numbersMap.|filter| { (key, value) -> key.endsWith(|"1"|) }}
]
@@ -0,0 +1,22 @@
import java.lang.System
// PSI_ELEMENT: org.jetbrains.kotlin.psi.KtNamedFunction
// OPTIONS: usages
class Foo {
fun getFive(){
return 5
}
fun <caret>off(a){
}
fun m() {
java.lang.System.out.println(off(1))
off(1)
off(1)
if (off(getFive())) {
}
}
}
@@ -0,0 +1,8 @@
[{id=0
, myUsages=15|java.lang.System.out.println(|off|(|1|))}
, {id=1
, myUsages=16|off|(|1|),
17|off|(|1|)}
, {id=2
, myUsages=18|if| (|off|(getFive())) {}
]
@@ -0,0 +1,8 @@
fun f() {
val numbers = mutableListOf("one", "two", "three", "four")
numbers.a<caret>dd ("five")
numbers.add("zero")
numbers.add(getStr())
numbers.add(getStr() + "a")
numbers.add(getStr() + "b")
}
@@ -0,0 +1,9 @@
[{id=0
, myUsages=3|numbers.|add| (|"five"|),
4|numbers.|add|(|"zero"|)}
, {id=1
, myUsages=5|numbers.|add|(getStr())}
, {id=2
, myUsages=6|numbers.|add|(getStr() + |"a"|),
7|numbers.|add|(getStr() + |"b"|)}
]
@@ -0,0 +1,2 @@
var a = Char()
var length = a.<caret>inc()
@@ -381,6 +381,8 @@
<usageToPsiElementProvider implementation="org.jetbrains.kotlin.idea.codeInsight.KotlinUsageToPsiElementProvider"/>
<usageFeaturesProvider implementation="org.jetbrains.kotlin.idea.findUsages.similarity.KotlinUsageSimilarityFeaturesProvider"/>
<readWriteAccessDetector implementation="org.jetbrains.kotlin.idea.search.ideaExtensions.KotlinReadWriteAccessDetector" id="kotlin"/>
<dataflowIRProvider language="kotlin" implementationClass="org.jetbrains.kotlin.idea.inspections.dfa.KotlinDataFlowIRProvider"/>