Skip to content

Commit 1f8f4df

Browse files
committed
Add support for finding reference references. Closes #4654
1 parent 829944d commit 1f8f4df

File tree

3 files changed

+130
-23
lines changed

3 files changed

+130
-23
lines changed

Rubberduck.Core/UI/CodeExplorer/Commands/CodeExplorerFindAllReferencesCommand.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using System.Collections.Generic;
3+
using Rubberduck.AddRemoveReferences;
34
using Rubberduck.Navigation.CodeExplorer;
45
using Rubberduck.Parsing.VBA;
56
using Rubberduck.UI.Controls;
@@ -10,6 +11,7 @@ public class CodeExplorerFindAllReferencesCommand : CodeExplorerCommandBase
1011
{
1112
private static readonly Type[] ApplicableNodes =
1213
{
14+
typeof(CodeExplorerReferenceViewModel),
1315
typeof(CodeExplorerProjectViewModel),
1416
typeof(CodeExplorerComponentViewModel),
1517
typeof(CodeExplorerMemberViewModel)
@@ -33,6 +35,16 @@ protected override void OnExecute(object parameter)
3335
return;
3436
}
3537

38+
if (parameter is CodeExplorerReferenceViewModel reference)
39+
{
40+
if (!(reference.Reference is ReferenceModel model))
41+
{
42+
return;
43+
}
44+
_finder.FindAllReferences(node.Declaration, model.ToReferenceInfo());
45+
return;
46+
}
47+
3648
_finder.FindAllReferences(node.Declaration);
3749
}
3850

@@ -42,6 +54,7 @@ protected override bool EvaluateCanExecute(object parameter)
4254
{
4355
return base.EvaluateCanExecute(parameter) &&
4456
((CodeExplorerItemViewModel)parameter).Declaration != null &&
57+
(!(parameter is CodeExplorerReferenceViewModel reference) || !reference.IsDimmed) &&
4558
_state.Status == ParserState.Ready;
4659
}
4760
}

Rubberduck.Core/UI/Controls/FindAllReferencesService.cs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Collections.Generic;
23
using System.Linq;
34
using NLog;
45
using Rubberduck.Interaction;
@@ -78,6 +79,43 @@ public void FindAllReferences(Declaration declaration)
7879
}
7980
}
8081

82+
public void FindAllReferences(Declaration declaration, ReferenceInfo reference)
83+
{
84+
if (_state.Status != ParserState.Ready ||
85+
!(declaration is ProjectDeclaration project))
86+
{
87+
return;
88+
}
89+
90+
var usages = _state.DeclarationFinder.FindAllReferenceUsesInProject(project, reference, out var referenceProject);
91+
if (!usages.Any())
92+
{
93+
_messageBox.NotifyWarn(string.Format(RubberduckUI.AllReferences_NoneFoundReference, referenceProject.IdentifierName), RubberduckUI.Rubberduck);
94+
return;
95+
}
96+
97+
if (usages.Count > 1000 &&
98+
!_messageBox.ConfirmYesNo(string.Format(RubberduckUI.AllReferences_PerformanceWarning, referenceProject.IdentifierName, usages.Count),
99+
RubberduckUI.PerformanceWarningCaption))
100+
{
101+
return;
102+
}
103+
104+
var viewModel = CreateViewModel(project, referenceProject, usages);
105+
_viewModel.AddTab(viewModel);
106+
_viewModel.SelectedTab = viewModel;
107+
108+
try
109+
{
110+
var presenter = _presenterService.Presenter(_viewModel);
111+
presenter.Show();
112+
}
113+
catch (Exception e)
114+
{
115+
_logger.Error(e);
116+
}
117+
}
118+
81119
private string GetModuleLine(QualifiedModuleName module, int line)
82120
{
83121
var component = _state.ProjectsProvider.Component(module);
@@ -87,6 +125,19 @@ private string GetModuleLine(QualifiedModuleName module, int line)
87125
}
88126
}
89127

128+
private SearchResultsViewModel CreateViewModel(ProjectDeclaration project, ProjectDeclaration reference, IEnumerable<IdentifierReference> usages)
129+
{
130+
var results = usages.Select(usage =>
131+
new SearchResultItem(usage.ParentNonScoping,
132+
new NavigateCodeEventArgs(usage.QualifiedModuleName, usage.Selection),
133+
GetModuleLine(usage.QualifiedModuleName, usage.Selection.StartLine)));
134+
135+
var viewModel = new SearchResultsViewModel(_navigateCommand,
136+
string.Format(RubberduckUI.SearchResults_AllReferencesTabFormat, reference.IdentifierName), project, results);
137+
138+
return viewModel;
139+
}
140+
90141
private SearchResultsViewModel CreateViewModel(Declaration declaration)
91142
{
92143
var results = declaration.References.Distinct().Select(reference =>

Rubberduck.Parsing/VBA/DeclarationCaching/DeclarationFinder.cs

Lines changed: 66 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,11 @@ public class DeclarationFinder
3232
private IDictionary<Declaration, List<ParameterDeclaration>> _parametersByParent;
3333
private IDictionary<DeclarationType, List<Declaration>> _userDeclarationsByType;
3434
private IDictionary<QualifiedSelection, List<Declaration>> _declarationsBySelection;
35+
36+
private IReadOnlyList<IdentifierReference> _identifierReferences;
3537
private IDictionary<QualifiedSelection, List<IdentifierReference>> _referencesBySelection;
3638
private IReadOnlyDictionary<QualifiedModuleName, IReadOnlyList<IdentifierReference>> _referencesByModule;
39+
private IReadOnlyDictionary<string, IReadOnlyList<IdentifierReference>> _referencesByProjectId;
3740
private IDictionary<QualifiedMemberName, List<IdentifierReference>> _referencesByMember;
3841

3942
private Lazy<IDictionary<DeclarationType, List<Declaration>>> _builtInDeclarationsByType;
@@ -107,12 +110,7 @@ private List<Action> CollectionConstructionActions(IReadOnlyList<Declaration> de
107110
.Where(declaration => declaration.IsUserDefined)
108111
.GroupBy(GetGroupingKey)
109112
.ToDictionary(),
110-
() =>
111-
_referencesBySelection = declarations
112-
.SelectMany(declaration => declaration.References)
113-
.GroupBy(
114-
reference => new QualifiedSelection(reference.QualifiedModuleName, reference.Selection))
115-
.ToDictionary(),
113+
116114
() =>
117115
_parametersByParent = declarations
118116
.Where(declaration => declaration.DeclarationType == DeclarationType.Parameter)
@@ -124,22 +122,34 @@ private List<Action> CollectionConstructionActions(IReadOnlyList<Declaration> de
124122
.Where(declaration => declaration.IsUserDefined)
125123
.GroupBy(declaration => declaration.DeclarationType)
126124
.ToDictionary(),
127-
() =>
128-
_referencesByModule = declarations
129-
.SelectMany(declaration => declaration.References)
130-
.GroupBy(reference =>
131-
Declaration.GetModuleParent(reference.ParentScoping).QualifiedName.QualifiedModuleName)
132-
.ToReadonlyDictionary(),
133-
() =>
134-
_referencesByMember = declarations
135-
.SelectMany(declaration => declaration.References)
136-
.GroupBy(reference => reference.ParentScoping.QualifiedName)
137-
.ToDictionary()
125+
126+
() => InitializeIdentifierDictionaries(declarations)
138127
};
139128

140129
return actions;
141130
}
142131

132+
private void InitializeIdentifierDictionaries(IReadOnlyList<Declaration> declarations)
133+
{
134+
_identifierReferences = declarations.SelectMany(declaration => declaration.References).ToList();
135+
136+
_referencesBySelection = _identifierReferences
137+
.GroupBy(reference => new QualifiedSelection(reference.QualifiedModuleName, reference.Selection))
138+
.ToDictionary();
139+
140+
_referencesByModule = _identifierReferences
141+
.GroupBy(reference => Declaration.GetModuleParent(reference.ParentScoping).QualifiedName.QualifiedModuleName)
142+
.ToReadonlyDictionary();
143+
144+
_referencesByMember = _identifierReferences
145+
.GroupBy(reference => reference.ParentScoping.QualifiedName)
146+
.ToDictionary();
147+
148+
_referencesByProjectId = _identifierReferences
149+
.GroupBy(reference => reference.Declaration.ProjectId)
150+
.ToReadonlyDictionary();
151+
}
152+
143153
private void InitializeLazyCollections()
144154
{
145155
_builtInDeclarationsByType = new Lazy<IDictionary<DeclarationType, List<Declaration>>>(() =>
@@ -1160,12 +1170,7 @@ public bool IsReferenceUsedInProject(ProjectDeclaration project, ReferenceInfo r
11601170
return false;
11611171
}
11621172

1163-
var referenceProject = reference.Guid.Equals(Guid.Empty)
1164-
? UserDeclarations(DeclarationType.Project).OfType<ProjectDeclaration>().FirstOrDefault(proj =>
1165-
proj.QualifiedModuleName.ProjectPath.Equals(reference.FullPath, StringComparison.OrdinalIgnoreCase))
1166-
: BuiltInDeclarations(DeclarationType.Project).OfType<ProjectDeclaration>().FirstOrDefault(proj =>
1167-
proj.Guid.Equals(reference.Guid) && proj.MajorVersion == reference.Major &&
1168-
proj.MinorVersion == reference.Minor);
1173+
var referenceProject = GetProjectDeclarationForReference(reference);
11691174

11701175
if (referenceProject == null || // Can't locate the project for the reference - assume it is used to avoid false negatives.
11711176
IdentifierReferences().Any(item =>
@@ -1186,6 +1191,44 @@ public bool IsReferenceUsedInProject(ProjectDeclaration project, ReferenceInfo r
11861191
declaration.AsTypeDeclaration.QualifiedModuleName.ProjectId.Equals(referenceProject.ProjectId));
11871192
}
11881193

1194+
public List<IdentifierReference> FindAllReferenceUsesInProject(ProjectDeclaration project, ReferenceInfo reference,
1195+
out ProjectDeclaration referenceProject)
1196+
{
1197+
var output = new List<IdentifierReference>();
1198+
if (project == null || string.IsNullOrEmpty(reference.FullPath))
1199+
{
1200+
referenceProject = null;
1201+
return output;
1202+
}
1203+
1204+
referenceProject = GetProjectDeclarationForReference(reference);
1205+
1206+
if (!_referencesByProjectId.TryGetValue(referenceProject.ProjectId, out var directReferences))
1207+
{
1208+
return output;
1209+
}
1210+
1211+
output.AddRange(directReferences);
1212+
1213+
var projectId = referenceProject.ProjectId;
1214+
1215+
output.AddRange(_identifierReferences.Where(identifier =>
1216+
identifier?.Declaration?.AsTypeDeclaration != null &&
1217+
identifier.Declaration.AsTypeDeclaration.QualifiedModuleName.ProjectId.Equals(projectId)));
1218+
1219+
return output;
1220+
}
1221+
1222+
private ProjectDeclaration GetProjectDeclarationForReference(ReferenceInfo reference)
1223+
{
1224+
return reference.Guid.Equals(Guid.Empty)
1225+
? UserDeclarations(DeclarationType.Project).OfType<ProjectDeclaration>().FirstOrDefault(proj =>
1226+
proj.QualifiedModuleName.ProjectPath.Equals(reference.FullPath, StringComparison.OrdinalIgnoreCase))
1227+
: BuiltInDeclarations(DeclarationType.Project).OfType<ProjectDeclaration>().FirstOrDefault(proj =>
1228+
proj.Guid.Equals(reference.Guid) && proj.MajorVersion == reference.Major &&
1229+
proj.MinorVersion == reference.Minor);
1230+
}
1231+
11891232
private bool UsesScopeResolution(RuleContext ruleContext)
11901233
{
11911234
return (ruleContext is VBAParser.WithMemberAccessExprContext)

0 commit comments

Comments
 (0)