duplicated string literal inspection: allow to navigate to duplicates (IDEA-167222)

This commit is contained in:
Dmitry Batkovich
2017-07-19 13:02:40 +03:00
parent c9bcfe4762
commit ba542676bc
2 changed files with 72 additions and 12 deletions
@@ -466,7 +466,9 @@ inspections.dead.code.entry.points.annotations.list.title=Additional entry point
inspection.duplicates.display.name=Duplicate String Literal
inspection.duplicates.replace.family.quickfix=Replace
inspection.duplicates.replace.quickfix=Replace with ''{0}''
inspection.duplicates.navigate.to.occurrences=Navigate to occurrences
inspection.duplicates.option=&Min string length:
inspection.duplicates.occurrences.view.title=Duplicates for ''{0}''
inspection.duplicates.message.in.this.file=(in this file)
inspection.duplicates.message.more=... ({0} more)
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2016 JetBrains s.r.o.
* Copyright 2000-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,6 +19,7 @@ import com.intellij.codeInsight.daemon.GroupNames;
import com.intellij.codeInspection.*;
import com.intellij.codeInspection.ex.BaseLocalInspectionTool;
import com.intellij.codeInspection.i18n.JavaI18nUtil;
import com.intellij.find.findUsages.PsiElement2UsageTargetAdapter;
import com.intellij.lang.java.JavaLanguage;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.progress.ProgressManager;
@@ -35,6 +36,8 @@ import com.intellij.refactoring.util.occurrences.BaseOccurrenceManager;
import com.intellij.refactoring.util.occurrences.OccurrenceFilter;
import com.intellij.refactoring.util.occurrences.OccurrenceManager;
import com.intellij.ui.DocumentAdapter;
import com.intellij.usageView.UsageInfo;
import com.intellij.usages.*;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.Processor;
import com.intellij.util.Processors;
@@ -45,6 +48,7 @@ import com.intellij.util.text.StringSearcher;
import com.siyeh.ig.style.UnnecessarilyQualifiedStaticUsageInspection;
import gnu.trove.THashMap;
import gnu.trove.THashSet;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -54,6 +58,7 @@ import javax.swing.event.DocumentEvent;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.*;
import java.util.stream.Stream;
public class DuplicateStringLiteralInspection extends BaseLocalInspectionTool {
@SuppressWarnings({"WeakerAccess"}) public int MIN_STRING_LENGTH = 5;
@@ -149,12 +154,7 @@ public class DuplicateStringLiteralInspection extends BaseLocalInspectionTool {
private void checkStringLiteralExpression(@NotNull final PsiLiteralExpression originalExpression,
@NotNull ProblemsHolder holder,
final boolean isOnTheFly) {
Object value = originalExpression.getValue();
if (!(value instanceof String)) return;
if (!shouldCheck(originalExpression)) return;
final String stringToFind = (String)value;
if (stringToFind.length() == 0) return;
List<PsiLiteralExpression> foundExpr = getDuplicateLiterals(stringToFind, originalExpression);
List<PsiLiteralExpression> foundExpr = getDuplicateLiterals(originalExpression);
if (foundExpr.isEmpty()) return;
Set<PsiClass> classes = new THashSet<>();
for (PsiElement aClass : foundExpr) {
@@ -199,17 +199,23 @@ public class DuplicateStringLiteralInspection extends BaseLocalInspectionTool {
fixes.add(introduceConstFix);
}
createReplaceFixes(foundExpr, originalExpression, fixes);
fixes.add(new NavigateToOccurrencesFix(originalExpression));
LocalQuickFix[] array = fixes.toArray(new LocalQuickFix[fixes.size()]);
holder.registerProblem(originalExpression, msg, ProblemHighlightType.GENERIC_ERROR_OR_WARNING, array);
}
@NotNull
private List<PsiLiteralExpression> getDuplicateLiterals(String stringToFind, PsiLiteralExpression place) {
private List<PsiLiteralExpression> getDuplicateLiterals(PsiLiteralExpression place) {
Object value = place.getValue();
if (!(value instanceof String)) return Collections.emptyList();
if (!shouldCheck(place)) return Collections.emptyList();
String stringToFind = (String)value;
if (stringToFind.length() == 0) return Collections.emptyList();
Project project = place.getProject();
Map<String, List<PsiLiteralExpression>> map = CachedValuesManager.getManager(project).getCachedValue(project, () -> {
Map<String, List<PsiLiteralExpression>> value = ConcurrentFactoryMap.createMap(
Map<String, List<PsiLiteralExpression>> duplicates = ConcurrentFactoryMap.createMap(
s -> Collections.unmodifiableList(findDuplicateLiterals(s, project)));
return CachedValueProvider.Result.create(value, PsiModificationTracker.MODIFICATION_COUNT);
return CachedValueProvider.Result.create(duplicates, PsiModificationTracker.MODIFICATION_COUNT);
});
return ContainerUtil.filter(map.get(stringToFind), literal -> literal != place);
}
@@ -247,7 +253,7 @@ public class DuplicateStringLiteralInspection extends BaseLocalInspectionTool {
}
private static LocalQuickFix createIntroduceConstFix(List<PsiLiteralExpression> foundExpr, PsiLiteralExpression originalExpression) {
final PsiExpression[] expressions = foundExpr.toArray(new PsiExpression[foundExpr.size() + 1]);
final PsiLiteralExpression[] expressions = foundExpr.toArray(new PsiLiteralExpression[foundExpr.size() + 1]);
expressions[foundExpr.size()] = originalExpression;
return new IntroduceLiteralConstantFix(expressions);
@@ -304,7 +310,7 @@ public class DuplicateStringLiteralInspection extends BaseLocalInspectionTool {
private static class IntroduceLiteralConstantFix implements LocalQuickFix {
private final SmartPsiElementPointer[] myExpressions;
public IntroduceLiteralConstantFix(final PsiExpression[] expressions) {
public IntroduceLiteralConstantFix(final PsiLiteralExpression[] expressions) {
myExpressions = new SmartPsiElementPointer[expressions.length];
for(int i=0; i<expressions.length; i++) {
PsiExpression expression = expressions[i];
@@ -325,6 +331,10 @@ public class DuplicateStringLiteralInspection extends BaseLocalInspectionTool {
@Override
public void applyFix(@NotNull final Project project, @NotNull ProblemDescriptor descriptor) {
applyFix(project);
}
private void applyFix(@NotNull Project project) {
final List<PsiExpression> expressions = new ArrayList<>();
for(SmartPsiElementPointer ptr: myExpressions) {
final PsiElement element = ptr.getElement();
@@ -413,4 +423,52 @@ public class DuplicateStringLiteralInspection extends BaseLocalInspectionTool {
return InspectionsBundle.message("inspection.duplicates.replace.family.quickfix");
}
}
private class NavigateToOccurrencesFix extends LocalQuickFixOnPsiElement {
public NavigateToOccurrencesFix(PsiLiteralExpression representative) {
super(representative);
}
@Override
public void invoke(@NotNull Project project, @NotNull PsiFile file, @NotNull PsiElement startElement, @NotNull PsiElement endElement) {
if (!(startElement instanceof PsiLiteralExpression)) return;
PsiLiteralExpression literal = (PsiLiteralExpression)startElement;
PsiLiteralExpression[] literalExpressions = Stream.concat(getDuplicateLiterals(literal).stream(), Stream.of(literal)).toArray(PsiLiteralExpression[]::new);
Usage[] usages = Stream.of(literalExpressions)
.map(UsageInfo::new)
.map(UsageInfo2UsageAdapter::new)
.toArray(Usage[]::new);
UsageViewPresentation presentation = new UsageViewPresentation();
String title = InspectionsBundle.message("inspection.duplicates.occurrences.view.title", literal.getValue());
presentation.setUsagesString(title);
presentation.setTabName(title);
presentation.setTabText(title);
presentation.setShowCancelButton(true);
UsageView view = UsageViewManager.getInstance(project).showUsages(new UsageTarget[]{new PsiElement2UsageTargetAdapter(literal) {
@Override
public String getPresentableText() {
return "String literal: \'" + literal.getValue() + "\'";
}
}}, usages, presentation);
view.addButtonToLowerPane(() -> {
new IntroduceLiteralConstantFix(literalExpressions).applyFix(project);
view.close();
}, InspectionsBundle.message("introduce.constant.across.the.project"));
}
@NotNull
@Override
public String getText() {
return getFamilyName();
}
@Nls
@NotNull
@Override
public String getFamilyName() {
return InspectionsBundle.message("inspection.duplicates.navigate.to.occurrences");
}
}
}