From ba542676bc0a2277561214db0fb2bd333ea0e7dd Mon Sep 17 00:00:00 2001 From: Dmitry Batkovich Date: Wed, 19 Jul 2017 13:01:43 +0300 Subject: [PATCH] duplicated string literal inspection: allow to navigate to duplicates (IDEA-167222) --- .../src/messages/InspectionsBundle.properties | 2 + .../DuplicateStringLiteralInspection.java | 82 ++++++++++++++++--- 2 files changed, 72 insertions(+), 12 deletions(-) diff --git a/platform/platform-resources-en/src/messages/InspectionsBundle.properties b/platform/platform-resources-en/src/messages/InspectionsBundle.properties index 6e65e3fda538..69fe9f651d4b 100644 --- a/platform/platform-resources-en/src/messages/InspectionsBundle.properties +++ b/platform/platform-resources-en/src/messages/InspectionsBundle.properties @@ -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) diff --git a/plugins/java-i18n/src/com/intellij/codeInspection/duplicateStringLiteral/DuplicateStringLiteralInspection.java b/plugins/java-i18n/src/com/intellij/codeInspection/duplicateStringLiteral/DuplicateStringLiteralInspection.java index ccce919efa9c..df9d2bd76364 100644 --- a/plugins/java-i18n/src/com/intellij/codeInspection/duplicateStringLiteral/DuplicateStringLiteralInspection.java +++ b/plugins/java-i18n/src/com/intellij/codeInspection/duplicateStringLiteral/DuplicateStringLiteralInspection.java @@ -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 foundExpr = getDuplicateLiterals(stringToFind, originalExpression); + List foundExpr = getDuplicateLiterals(originalExpression); if (foundExpr.isEmpty()) return; Set 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 getDuplicateLiterals(String stringToFind, PsiLiteralExpression place) { + private List 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> map = CachedValuesManager.getManager(project).getCachedValue(project, () -> { - Map> value = ConcurrentFactoryMap.createMap( + Map> 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 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 = 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"); + } + } }