#develop (short for SharpDevelop) is a free IDE for .NET programming languages.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

274 lines
10 KiB

// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using ICSharpCode.Core;
using ICSharpCode.NRefactory.TypeSystem;
using ICSharpCode.NRefactory.TypeSystem.Implementation;
using ICSharpCode.SharpDevelop.Editor.Search;
using ICSharpCode.SharpDevelop.Gui;
using ICSharpCode.SharpDevelop.Parser;
using ICSharpCode.SharpDevelop.Project;
namespace ICSharpCode.SharpDevelop.Refactoring
{
/// <summary>
/// Language-independent finds references implementation.
/// This call will call into the individual language bindings to perform the job.
/// </summary>
public static class FindReferenceService
{
#region FindReferences
static IEnumerable<IProject> GetProjectsThatCouldReferenceEntity(IEntity entity)
{
Solution solution = ProjectService.OpenSolution;
if (solution == null)
yield break;
foreach (IProject project in solution.Projects) {
IProjectContent pc = project.ProjectContent;
if (pc == null)
continue;
yield return project;
}
}
static List<ISymbolSearch> PrepareSymbolSearch(IEntity entity, CancellationToken cancellationToken, out double totalWorkAmount)
{
totalWorkAmount = 0;
List<ISymbolSearch> symbolSearches = new List<ISymbolSearch>();
foreach (IProject project in GetProjectsThatCouldReferenceEntity(entity)) {
cancellationToken.ThrowIfCancellationRequested();
ISymbolSearch symbolSearch = project.PrepareSymbolSearch(entity);
if (symbolSearch != null) {
symbolSearches.Add(symbolSearch);
totalWorkAmount += symbolSearch.WorkAmount;
}
}
if (totalWorkAmount < 1)
totalWorkAmount = 1;
return symbolSearches;
}
/// <summary>
/// Finds all references to the specified entity.
/// The results are reported using the callback.
/// FindReferences may internally use parallelism, and may invoke the callback on multiple
/// threads in parallel.
/// </summary>
public static async Task FindReferencesAsync(IEntity entity, IProgressMonitor progressMonitor, Action<SearchedFile> callback)
{
if (entity == null)
throw new ArgumentNullException("entity");
if (progressMonitor == null)
throw new ArgumentNullException("progressMonitor");
if (callback == null)
throw new ArgumentNullException("callback");
SD.MainThread.VerifyAccess();
if (SD.ParserService.LoadSolutionProjectsThread.IsRunning) {
progressMonitor.ShowingDialog = true;
MessageService.ShowMessage("${res:SharpDevelop.Refactoring.LoadSolutionProjectsThreadRunning}");
progressMonitor.ShowingDialog = false;
return;
}
double totalWorkAmount;
List<ISymbolSearch> symbolSearches = PrepareSymbolSearch(entity, progressMonitor.CancellationToken, out totalWorkAmount);
double workDone = 0;
ParseableFileContentFinder parseableFileContentFinder = new ParseableFileContentFinder();
foreach (ISymbolSearch s in symbolSearches) {
progressMonitor.CancellationToken.ThrowIfCancellationRequested();
using (var childProgressMonitor = progressMonitor.CreateSubTask(s.WorkAmount / totalWorkAmount)) {
await s.FindReferencesAsync(new SymbolSearchArgs(childProgressMonitor, parseableFileContentFinder), callback);
}
workDone += s.WorkAmount;
progressMonitor.Progress = workDone / totalWorkAmount;
}
}
public static IObservable<SearchedFile> FindReferences(IEntity entity, IProgressMonitor progressMonitor)
{
return ReactiveExtensions.CreateObservable<SearchedFile>(
(monitor, callback) => FindReferencesAsync(entity, monitor, callback),
progressMonitor);
}
/// <summary>
/// Finds references to a local variable.
/// </summary>
public static async Task<SearchedFile> FindLocalReferencesAsync(IVariable variable, IProgressMonitor progressMonitor)
{
if (variable == null)
throw new ArgumentNullException("variable");
if (progressMonitor == null)
throw new ArgumentNullException("progressMonitor");
var fileName = FileName.Create(variable.Region.FileName);
List<Reference> references = new List<Reference>();
await SD.ParserService.FindLocalReferencesAsync(
fileName, variable,
r => { lock (references) references.Add(r); },
cancellationToken: progressMonitor.CancellationToken);
return new SearchedFile(fileName, references);
}
public static IObservable<SearchedFile> FindLocalReferences(IVariable variable, IProgressMonitor progressMonitor)
{
return ReactiveExtensions.CreateObservable<SearchedFile>(
(monitor, callback) => FindLocalReferencesAsync(variable, monitor).ContinueWith(t => callback(t.Result)),
progressMonitor);
}
#endregion
#region FindDerivedTypes
/// <summary>
/// Finds all types that are derived from the given base type.
/// </summary>
public static IList<ITypeDefinition> FindDerivedTypes(ITypeDefinition baseType, bool directDerivationOnly)
{
if (baseType == null)
throw new ArgumentNullException("baseType");
baseType = baseType.GetDefinition(); // ensure we use the compound class
List<ITypeDefinition> results = new List<ITypeDefinition>();
var solutionSnapshot = GetSolutionSnapshot(baseType.Compilation);
foreach (IProject project in GetProjectsThatCouldReferenceEntity(baseType)) {
var compilation = solutionSnapshot.GetCompilation(project);
var importedBaseType = compilation.Import(baseType);
if (importedBaseType == null)
continue;
foreach (ITypeDefinition typeDef in compilation.MainAssembly.GetAllTypeDefinitions()) {
bool isDerived;
if (directDerivationOnly) {
isDerived = typeDef.DirectBaseTypes.Select(t => t.GetDefinition()).Contains(importedBaseType);
} else {
isDerived = typeDef.IsDerivedFrom(importedBaseType);
}
if (isDerived)
results.Add(typeDef);
}
}
return results;
}
static ISolutionSnapshotWithProjectMapping GetSolutionSnapshot(ICompilation compilation)
{
var snapshot = compilation.SolutionSnapshot as ISolutionSnapshotWithProjectMapping;
return snapshot ?? SD.ParserService.GetCurrentSolutionSnapshot();
}
/// <summary>
/// Builds a graph of derived type definitions.
/// </summary>
public static TypeGraphNode BuildDerivedTypesGraph(ITypeDefinition baseType)
{
if (baseType == null)
throw new ArgumentNullException("baseType");
var solutionSnapshot = GetSolutionSnapshot(baseType.Compilation);
var compilations = GetProjectsThatCouldReferenceEntity(baseType).Select(p => solutionSnapshot.GetCompilation(p));
var graph = BuildTypeInheritanceGraph(compilations);
TypeGraphNode node;
if (graph.TryGetValue(new AssemblyQualifiedTypeName(baseType), out node)) {
node.BaseTypes.Clear(); // only derived types were requested, so don't return the base types
return node;
} else {
return new TypeGraphNode(baseType);
}
}
/// <summary>
/// Builds a graph of all type definitions in the specified set of project contents.
/// </summary>
/// <remarks>The resulting graph may be cyclic if there are cyclic type definitions.</remarks>
static Dictionary<AssemblyQualifiedTypeName, TypeGraphNode> BuildTypeInheritanceGraph(IEnumerable<ICompilation> compilations)
{
if (compilations == null)
throw new ArgumentNullException("projectContents");
Dictionary<AssemblyQualifiedTypeName, TypeGraphNode> dict = new Dictionary<AssemblyQualifiedTypeName, TypeGraphNode>();
foreach (ICompilation compilation in compilations) {
foreach (ITypeDefinition typeDef in compilation.MainAssembly.GetAllTypeDefinitions()) {
// Overwrite previous entry - duplicates can occur if there are multiple versions of the
// same project loaded in the solution (e.g. separate .csprojs for separate target frameworks)
dict[new AssemblyQualifiedTypeName(typeDef)] = new TypeGraphNode(typeDef);
}
}
foreach (ICompilation compilation in compilations) {
foreach (ITypeDefinition typeDef in compilation.MainAssembly.GetAllTypeDefinitions()) {
TypeGraphNode typeNode = dict[new AssemblyQualifiedTypeName(typeDef)];
foreach (IType baseType in typeDef.DirectBaseTypes) {
ITypeDefinition baseTypeDef = baseType.GetDefinition();
if (baseTypeDef != null) {
TypeGraphNode baseTypeNode;
if (dict.TryGetValue(new AssemblyQualifiedTypeName(baseTypeDef), out baseTypeNode)) {
typeNode.BaseTypes.Add(baseTypeNode);
baseTypeNode.DerivedTypes.Add(typeNode);
}
}
}
}
}
return dict;
}
#endregion
}
public class SymbolSearchArgs
{
public IProgressMonitor ProgressMonitor { get; private set; }
public CancellationToken CancellationToken {
get { return this.ProgressMonitor.CancellationToken; }
}
public ParseableFileContentFinder ParseableFileContentFinder { get; private set; }
public SymbolSearchArgs(IProgressMonitor progressMonitor, ParseableFileContentFinder parseableFileContentFinder)
{
if (progressMonitor == null)
throw new ArgumentNullException("progressMonitor");
if (parseableFileContentFinder == null)
throw new ArgumentNullException("parseableFileContentFinder");
this.ProgressMonitor = progressMonitor;
this.ParseableFileContentFinder = parseableFileContentFinder;
}
}
public interface ISymbolSearch
{
double WorkAmount { get; }
Task FindReferencesAsync(SymbolSearchArgs searchArguments, Action<SearchedFile> callback);
}
public sealed class CompositeSymbolSearch : ISymbolSearch
{
IEnumerable<ISymbolSearch> symbolSearches;
CompositeSymbolSearch(params ISymbolSearch[] symbolSearches)
{
this.symbolSearches = symbolSearches;
}
public static ISymbolSearch Create(ISymbolSearch symbolSearch1, ISymbolSearch symbolSearch2)
{
if (symbolSearch1 == null)
return symbolSearch2;
if (symbolSearch2 == null)
return symbolSearch1;
return new CompositeSymbolSearch(symbolSearch1, symbolSearch2);
}
public double WorkAmount {
get { return symbolSearches.Sum(s => s.WorkAmount); }
}
public Task FindReferencesAsync(SymbolSearchArgs searchArguments, Action<SearchedFile> callback)
{
return Task.WhenAll(symbolSearches.Select(s => s.FindReferencesAsync(searchArguments, callback)));
}
}
}