diff --git a/CSharpCodeAnalyst/App.xaml.cs b/CSharpCodeAnalyst/App.xaml.cs index 7e85344..e90376b 100644 --- a/CSharpCodeAnalyst/App.xaml.cs +++ b/CSharpCodeAnalyst/App.xaml.cs @@ -35,7 +35,7 @@ protected override void OnStartup(StartupEventArgs e) var explorer = new CodeGraphExplorer(); var mainWindow = new MainWindow(); - var explorationGraphViewer = new DependencyGraphViewer(messaging); + var explorationGraphViewer = new GraphViewer(messaging); mainWindow.SetViewer(explorationGraphViewer); var viewModel = new MainViewModel(messaging, settings); diff --git a/CSharpCodeAnalyst/Common/AddMissingDependenciesRequest.cs b/CSharpCodeAnalyst/Common/AddMissingDependenciesRequest.cs deleted file mode 100644 index 29692f7..0000000 --- a/CSharpCodeAnalyst/Common/AddMissingDependenciesRequest.cs +++ /dev/null @@ -1,3 +0,0 @@ -namespace CSharpCodeAnalyst.Common; - -public class AddMissingDependenciesRequest; \ No newline at end of file diff --git a/CSharpCodeAnalyst/Exploration/CodeGraphExplorer.cs b/CSharpCodeAnalyst/Exploration/CodeGraphExplorer.cs index e318ad3..76670a4 100644 --- a/CSharpCodeAnalyst/Exploration/CodeGraphExplorer.cs +++ b/CSharpCodeAnalyst/Exploration/CodeGraphExplorer.cs @@ -5,7 +5,7 @@ namespace CSharpCodeAnalyst.Exploration; public class CodeGraphExplorer : ICodeGraphExplorer { - private List _allDependencies = []; + private List _allRelationships = []; private CodeGraph? _codeGraph; public void LoadCodeGraph(CodeGraph graph) @@ -13,7 +13,7 @@ public void LoadCodeGraph(CodeGraph graph) _codeGraph = graph; // Clear all cached data - _allDependencies = []; + _allRelationships = []; } public List GetElements(List ids) @@ -30,7 +30,7 @@ public List GetElements(List ids) { if (_codeGraph.Nodes.TryGetValue(id, out var element)) { - // The element is cloned internally and the dependencies discarded. + // The element is cloned internally and the relationships discarded. elements.Add(element); } } @@ -93,21 +93,21 @@ public SearchResult FindParents(List ids) } /// - /// Returns all dependencies that link the given nodes (ids). + /// Returns all relationships that link the given nodes (ids). /// - public IEnumerable FindAllDependencies(HashSet ids) + public IEnumerable FindAllRelationships(HashSet ids) { if (_codeGraph is null) { return []; } - var dependencies = _codeGraph.Nodes.Values - .SelectMany(n => n.Dependencies) + var relationships = _codeGraph.Nodes.Values + .SelectMany(n => n.Relationships) .Where(d => ids.Contains(d.SourceId) && ids.Contains(d.TargetId)) .ToList(); - return dependencies; + return relationships; } public Invocation FindIncomingCalls(string id) @@ -121,7 +121,7 @@ public Invocation FindIncomingCalls(string id) var method = _codeGraph.Nodes[id]; - var allCalls = GetDependencies(d => d.Type == DependencyType.Calls); + var allCalls = GetRelationships(d => d.Type == RelationshipType.Calls); var calls = allCalls.Where(call => call.TargetId == method.Id).ToArray(); var methods = calls.Select(d => _codeGraph.Nodes[d.SourceId]); @@ -143,10 +143,10 @@ public Invocation FindIncomingCallsRecursive(string id) var processingQueue = new Queue(); processingQueue.Enqueue(method); - var foundCalls = new HashSet(); + var foundCalls = new HashSet(); var foundMethods = new HashSet(); - var allCalls = GetDependencies(d => d.Type == DependencyType.Calls); + var allCalls = GetRelationships(d => d.Type == RelationshipType.Calls); var processed = new HashSet(); while (processingQueue.Any()) @@ -183,18 +183,18 @@ public SearchResult FollowIncomingCallsRecursive(string id) } var allImplementsAndOverrides = - GetDependencies(d => d.Type is DependencyType.Implements or DependencyType.Overrides); - var allCalls = GetDependencies(d => d.Type == DependencyType.Calls); + GetRelationships(d => d.Type is RelationshipType.Implements or RelationshipType.Overrides); + var allCalls = GetRelationships(d => d.Type == RelationshipType.Calls); - var allHandles = GetDependencies(d => d.Type == DependencyType.Handles); - var allInvokes = GetDependencies(d => d.Type == DependencyType.Invokes); + var allHandles = GetRelationships(d => d.Type == RelationshipType.Handles); + var allInvokes = GetRelationships(d => d.Type == RelationshipType.Invokes); var method = _codeGraph.Nodes[id]; var processingQueue = new Queue(); processingQueue.Enqueue(method); - var foundDependencies = new HashSet(); + var foundRelationships = new HashSet(); var foundElements = new HashSet(); @@ -209,31 +209,31 @@ public SearchResult FollowIncomingCallsRecursive(string id) // An event is raised by the specialization var specializations = allImplementsAndOverrides.Where(d => d.TargetId == element.Id).ToArray(); - foundDependencies.UnionWith(specializations); + foundRelationships.UnionWith(specializations); var specializedSources = specializations.Select(d => _codeGraph.Nodes[d.SourceId]).ToHashSet(); foundElements.UnionWith(specializedSources); // Add all methods that invoke the event var invokes = allInvokes.Where(call => call.TargetId == element.Id).ToArray(); - foundDependencies.UnionWith(invokes); + foundRelationships.UnionWith(invokes); var invokeSources = invokes.Select(d => _codeGraph.Nodes[d.SourceId]).ToHashSet(); foundElements.UnionWith(invokeSources); // Add Events that are handled by this method. var handles = allHandles.Where(h => h.SourceId == element.Id).ToArray(); - foundDependencies.UnionWith(handles); + foundRelationships.UnionWith(handles); var events = handles.Select(h => _codeGraph.Nodes[h.TargetId]).ToHashSet(); foundElements.UnionWith(events); // Calls var calls = allCalls.Where(call => call.TargetId == element.Id).ToArray(); - foundDependencies.UnionWith(calls); + foundRelationships.UnionWith(calls); var callSources = calls.Select(d => _codeGraph.Nodes[d.SourceId]).ToHashSet(); foundElements.UnionWith(callSources); // Abstractions. Sometimes the abstractions is called. var abstractions = allImplementsAndOverrides.Where(d => d.SourceId == element.Id).ToArray(); - foundDependencies.UnionWith(abstractions); + foundRelationships.UnionWith(abstractions); var abstractionTargets = abstractions.Select(d => _codeGraph.Nodes[d.TargetId]).ToHashSet(); foundElements.UnionWith(abstractionTargets); @@ -249,7 +249,7 @@ public SearchResult FollowIncomingCallsRecursive(string id) } } - return new SearchResult(foundElements, foundDependencies); + return new SearchResult(foundElements, foundRelationships); } /// @@ -267,7 +267,7 @@ public SearchResult FindFullInheritanceTree(string id) var type = _codeGraph.Nodes[id]; var types = new HashSet(); - var relationships = new HashSet(); + var relationships = new HashSet(); var processingQueue = new Queue(); var processed = new HashSet(); @@ -287,7 +287,8 @@ public SearchResult FindFullInheritanceTree(string id) // Case typeToAnalyze is subclass: typeToAnalyze implements X or inherits from Y var abstractionsOfAnalyzedType = - typeToAnalyze.Dependencies.Where(d => d.Type is DependencyType.Implements or DependencyType.Inherits); + typeToAnalyze.Relationships.Where(d => + d.Type is RelationshipType.Implements or RelationshipType.Inherits); foreach (var abstraction in abstractionsOfAnalyzedType) { var baseType = _codeGraph.Nodes[abstraction.TargetId]; @@ -339,12 +340,12 @@ public SearchResult FindSpecializations(string id) var element = _codeGraph.Nodes[id]; - var dependencies = _codeGraph.GetAllDependencies() - .Where(d => (d.Type == DependencyType.Overrides || - d.Type == DependencyType.Implements) && + var relationships = _codeGraph.GetAllRelationships() + .Where(d => (d.Type == RelationshipType.Overrides || + d.Type == RelationshipType.Implements) && d.TargetId == element.Id).ToList(); - var methods = dependencies.Select(m => _codeGraph.Nodes[m.SourceId]).ToList(); - return new SearchResult(methods, dependencies); + var methods = relationships.Select(m => _codeGraph.Nodes[m.SourceId]).ToList(); + return new SearchResult(methods, relationships); } /// @@ -361,12 +362,12 @@ public SearchResult FindAbstractions(string id) var element = _codeGraph.Nodes[id]; - var dependencies = element.Dependencies - .Where(d => (d.Type == DependencyType.Overrides || - d.Type == DependencyType.Implements) && + var relationships = element.Relationships + .Where(d => (d.Type == RelationshipType.Overrides || + d.Type == RelationshipType.Implements) && d.SourceId == element.Id).ToList(); - var methods = dependencies.Select(m => _codeGraph.Nodes[m.TargetId]).ToList(); - return new SearchResult(methods, dependencies); + var methods = relationships.Select(m => _codeGraph.Nodes[m.TargetId]).ToList(); + return new SearchResult(methods, relationships); } @@ -381,13 +382,13 @@ public Invocation FindOutgoingCalls(string id) var method = _codeGraph.Nodes[id]; - var calls = method.Dependencies - .Where(d => d.Type == DependencyType.Calls).ToList(); + var calls = method.Relationships + .Where(d => d.Type == RelationshipType.Calls).ToList(); var methods = calls.Select(m => _codeGraph.Nodes[m.TargetId]).ToList(); return new Invocation(methods, calls); } - public SearchResult FindOutgoingDependencies(string id) + public SearchResult FindOutgoingRelationships(string id) { ArgumentNullException.ThrowIfNull(id); @@ -397,12 +398,12 @@ public SearchResult FindOutgoingDependencies(string id) } var element = _codeGraph.Nodes[id]; - var dependencies = element.Dependencies; - var targets = dependencies.Select(m => _codeGraph.Nodes[m.TargetId]).ToList(); - return new SearchResult(targets, dependencies); + var relationships = element.Relationships; + var targets = relationships.Select(m => _codeGraph.Nodes[m.TargetId]).ToList(); + return new SearchResult(targets, relationships); } - public SearchResult FindIncomingDependencies(string id) + public SearchResult FindIncomingRelationships(string id) { ArgumentNullException.ThrowIfNull(id); if (_codeGraph is null) @@ -411,43 +412,43 @@ public SearchResult FindIncomingDependencies(string id) } var element = _codeGraph.Nodes[id]; - var dependencies = _codeGraph.Nodes.Values - .SelectMany(node => node.Dependencies) + var relationships = _codeGraph.Nodes.Values + .SelectMany(node => node.Relationships) .Where(d => d.TargetId == element.Id).ToList(); - var elements = dependencies.Select(d => _codeGraph.Nodes[d.SourceId]); + var elements = relationships.Select(d => _codeGraph.Nodes[d.SourceId]); - return new SearchResult(elements, dependencies); + return new SearchResult(elements, relationships); } - private List GetCachedDependencies() + private List GetCachedRelationships() { if (_codeGraph is null) { return []; } - if (_allDependencies.Count == 0) + if (_allRelationships.Count == 0) { - _allDependencies = _codeGraph.GetAllDependencies().ToList(); + _allRelationships = _codeGraph.GetAllRelationships().ToList(); } - return _allDependencies; + return _allRelationships; } - private List GetDependencies(Func filter) + private List GetRelationships(Func filter) { - return GetCachedDependencies().Where(filter).ToList(); + return GetCachedRelationships().Where(filter).ToList(); } - private HashSet FindInheritsAndImplementsRelationships() + private HashSet FindInheritsAndImplementsRelationships() { if (_codeGraph is null) { return []; } - var inheritsAndImplements = new HashSet(); + var inheritsAndImplements = new HashSet(); _codeGraph.DfsHierarchy(Collect); return inheritsAndImplements; @@ -458,17 +459,17 @@ void Collect(CodeElement c) return; } - foreach (var dependency in c.Dependencies) + foreach (var relationship in c.Relationships) { - if (dependency.Type is DependencyType.Inherits or DependencyType.Implements) + if (relationship.Type is RelationshipType.Inherits or RelationshipType.Implements) { - inheritsAndImplements.Add(dependency); + inheritsAndImplements.Add(relationship); } } } } } -public record struct SearchResult(IEnumerable Elements, IEnumerable Dependencies); +public record struct SearchResult(IEnumerable Elements, IEnumerable Relationships); -public record struct Invocation(IEnumerable Methods, IEnumerable Calls); \ No newline at end of file +public record struct Invocation(IEnumerable Methods, IEnumerable Calls); \ No newline at end of file diff --git a/CSharpCodeAnalyst/Exploration/ICodeGraphExplorer.cs b/CSharpCodeAnalyst/Exploration/ICodeGraphExplorer.cs index 39744ec..913cb11 100644 --- a/CSharpCodeAnalyst/Exploration/ICodeGraphExplorer.cs +++ b/CSharpCodeAnalyst/Exploration/ICodeGraphExplorer.cs @@ -20,9 +20,9 @@ public interface ICodeGraphExplorer SearchResult FindFullInheritanceTree(string id); /// - /// Finds all dependencies connect the given nodes. + /// Finds all relationships connect the given nodes. /// - IEnumerable FindAllDependencies(HashSet ids); + IEnumerable FindAllRelationships(HashSet ids); /// /// Methods that implement or overload the given method @@ -34,8 +34,8 @@ public interface ICodeGraphExplorer /// SearchResult FindAbstractions(string id); - SearchResult FindOutgoingDependencies(string id); - SearchResult FindIncomingDependencies(string id); + SearchResult FindOutgoingRelationships(string id); + SearchResult FindIncomingRelationships(string id); void LoadCodeGraph(CodeGraph graph); List GetElements(List ids); SearchResult FindParents(List ids); diff --git a/CSharpCodeAnalyst/Exports/DsiExport.cs b/CSharpCodeAnalyst/Exports/DsiExport.cs index 4b2a151..b5f6e57 100644 --- a/CSharpCodeAnalyst/Exports/DsiExport.cs +++ b/CSharpCodeAnalyst/Exports/DsiExport.cs @@ -36,15 +36,15 @@ public static string Convert(CodeGraph codeGraph) currentId++; } - // Add dependencies (relation) + // Add relationships (relation) foreach (var node in codeGraph.Nodes.Values) { - foreach (var dependency in node.Dependencies) + foreach (var relationship in node.Relationships) { relations.Add(new XElement(ns + "relation", new XAttribute("from", idMap[node.Id]), - new XAttribute("to", idMap[dependency.TargetId]), - new XAttribute("type", dependency.Type.ToString()), + new XAttribute("to", idMap[relationship.TargetId]), + new XAttribute("type", relationship.Type.ToString()), new XAttribute("weight", "1") )); } diff --git a/CSharpCodeAnalyst/GraphArea/CodeElementContextCommand.cs b/CSharpCodeAnalyst/GraphArea/CodeElementContextCommand.cs index b1e570e..817420a 100644 --- a/CSharpCodeAnalyst/GraphArea/CodeElementContextCommand.cs +++ b/CSharpCodeAnalyst/GraphArea/CodeElementContextCommand.cs @@ -18,7 +18,8 @@ public CodeElementContextCommand(string label, CodeElementType type, Action /// Generic for all code elements /// - public CodeElementContextCommand(string label, Action action, Func? canExecute = null) + public CodeElementContextCommand(string label, Action action, + Func? canExecute = null) { _type = null; _action = action; diff --git a/CSharpCodeAnalyst/GraphArea/DependencyContextCommand.cs b/CSharpCodeAnalyst/GraphArea/DependencyContextCommand.cs deleted file mode 100644 index 2a19b10..0000000 --- a/CSharpCodeAnalyst/GraphArea/DependencyContextCommand.cs +++ /dev/null @@ -1,63 +0,0 @@ -using Contracts.Graph; - -namespace CSharpCodeAnalyst.GraphArea; - -public class DependencyContextCommand : IDependencyContextCommand -{ - private readonly Action> _action; - private readonly Func, bool>? _canExecute; - private readonly DependencyType? _type; - - public DependencyContextCommand(string label, DependencyType type, Action> action) - { - _type = type; - _action = action; - Label = label; - } - - /// - /// Generic for all code elements - /// - public DependencyContextCommand(string label, Action> action, - Func, bool>? canExecute = null) - { - _type = null; - _action = action; - _canExecute = canExecute; - Label = label; - } - - public string Label { get; } - - public bool CanHandle(List dependencies) - { - // This is a dummy dependency to visualize hierarchical relationships in flat graph. - if (dependencies.Any(d => d.Type == DependencyType.Containment)) - { - return false; - } - - if (_type != null) - { - if (dependencies.All(d => d.Type == _type) is false) - { - return false; - } - } - - var canHandle = true; - if (_canExecute != null) - { - // Further restrict the handling - canHandle = _canExecute.Invoke(dependencies); - } - - return canHandle; - } - - - public void Invoke(List dependencies) - { - _action.Invoke(dependencies); - } -} \ No newline at end of file diff --git a/CSharpCodeAnalyst/GraphArea/GraphSession.cs b/CSharpCodeAnalyst/GraphArea/GraphSession.cs index 0f8d726..55ebc8d 100644 --- a/CSharpCodeAnalyst/GraphArea/GraphSession.cs +++ b/CSharpCodeAnalyst/GraphArea/GraphSession.cs @@ -12,21 +12,21 @@ public GraphSession() { Name = string.Empty; CodeElementIds = []; - Dependencies = []; + Relationships = []; PresentationState = new PresentationState(); } - private GraphSession(string name, List codeElementIds, List dependencies, + private GraphSession(string name, List codeElementIds, List relationships, PresentationState presentationState) { Name = name; CodeElementIds = codeElementIds; - Dependencies = dependencies; + Relationships = relationships; PresentationState = presentationState; } public List CodeElementIds { get; set; } - public List Dependencies { get; set; } + public List Relationships { get; set; } public string Name { get; set; } public PresentationState PresentationState { get; set; } @@ -34,9 +34,9 @@ public static GraphSession Create(string name, CodeGraph codeGraph, Presentation { // No references in this state should be shared with the original state var codeElementIds = codeGraph.Nodes.Keys.ToList(); - var dependencies = codeGraph.GetAllDependencies().ToList(); + var relationships = codeGraph.GetAllRelationships().ToList(); var clonedPresentationState = presentationState.Clone(); - var sessionState = new GraphSession(name, codeElementIds, dependencies, clonedPresentationState); + var sessionState = new GraphSession(name, codeElementIds, relationships, clonedPresentationState); return sessionState; } } \ No newline at end of file diff --git a/CSharpCodeAnalyst/GraphArea/GraphViewModel.cs b/CSharpCodeAnalyst/GraphArea/GraphViewModel.cs index 33aef1f..7d4cccf 100644 --- a/CSharpCodeAnalyst/GraphArea/GraphViewModel.cs +++ b/CSharpCodeAnalyst/GraphArea/GraphViewModel.cs @@ -22,13 +22,13 @@ internal class GraphViewModel : INotifyPropertyChanged private readonly ApplicationSettings? _settings; private readonly LinkedList _undoStack = new(); private readonly int _undoStackSize = 10; - private readonly IDependencyGraphViewer _viewer; + private readonly IGraphViewer _viewer; private HighlightOption _selectedHighlightOption; private RenderOption _selectedRenderOption; private bool _showFlatGraph; - internal GraphViewModel(IDependencyGraphViewer viewer, ICodeGraphExplorer explorer, IPublisher publisher, + internal GraphViewModel(IGraphViewer viewer, ICodeGraphExplorer explorer, IPublisher publisher, ApplicationSettings? settings) { _viewer = viewer; @@ -56,11 +56,11 @@ internal GraphViewModel(IDependencyGraphViewer viewer, ICodeGraphExplorer explor _selectedHighlightOption = HighlightOptions[0]; // Edge commands - _viewer.AddContextMenuCommand(new DependencyContextCommand(Strings.Delete, DeleteEdges)); + _viewer.AddContextMenuCommand(new RelationshipContextCommand(Strings.Delete, DeleteEdges)); // Global commands _viewer.AddGlobalContextMenuCommand( - new GlobalContextCommand(Strings.CompleteDependencies, CompleteDependencies)); + new GlobalContextCommand(Strings.CompleteRelationships, CompleteRelationships)); _viewer.AddGlobalContextMenuCommand(new GlobalContextCommand(Strings.CompleteToTypes, CompleteToTypes)); _viewer.AddGlobalContextMenuCommand(new GlobalContextCommand(Strings.MarkedFocus, FocusOnMarkedElements, CanHandleIfMarkedElements)); @@ -92,7 +92,8 @@ internal GraphViewModel(IDependencyGraphViewer viewer, ICodeGraphExplorer explor FollowIncomingCallsRecursive)); _viewer.AddContextMenuCommand(new CodeElementContextCommand(Strings.FindSpecializations, elementType, FindSpecializations)); - _viewer.AddContextMenuCommand(new CodeElementContextCommand(Strings.FindAbstractions, elementType, FindAbstractions)); + _viewer.AddContextMenuCommand(new CodeElementContextCommand(Strings.FindAbstractions, elementType, + FindAbstractions)); } // Classes, structs and interfaces @@ -103,7 +104,8 @@ internal GraphViewModel(IDependencyGraphViewer viewer, ICodeGraphExplorer explor FindInheritanceTree)); _viewer.AddContextMenuCommand(new CodeElementContextCommand(Strings.FindSpecializations, elementType, FindSpecializations)); - _viewer.AddContextMenuCommand(new CodeElementContextCommand(Strings.FindAbstractions, elementType, FindAbstractions)); + _viewer.AddContextMenuCommand(new CodeElementContextCommand(Strings.FindAbstractions, elementType, + FindAbstractions)); } // Events @@ -115,10 +117,12 @@ internal GraphViewModel(IDependencyGraphViewer viewer, ICodeGraphExplorer explor FollowIncomingCallsRecursive)); - // Everyone gets the in/out dependencies + // Everyone gets the in/out relationships _viewer.AddContextMenuCommand(new SeparatorCommand()); - _viewer.AddContextMenuCommand(new CodeElementContextCommand(Strings.AllIncomingDependencies, FindAllIncomingDependencies)); - _viewer.AddContextMenuCommand(new CodeElementContextCommand(Strings.AllOutgoingDependencies, FindAllOutgoingDependencies)); + _viewer.AddContextMenuCommand(new CodeElementContextCommand(Strings.AllIncomingRelationships, + FindAllIncomingRelationships)); + _viewer.AddContextMenuCommand(new CodeElementContextCommand(Strings.AllOutgoingRelationships, + FindAllOutgoingRelationships)); UndoCommand = new DelegateCommand(Undo); } @@ -222,10 +226,10 @@ private void FocusOnMarkedElements(List markedElements) _viewer.LoadSession(newGraph, presentationState); } - private void DeleteEdges(List dependencies) + private void DeleteEdges(List relationships) { PushUndo(); - _viewer.DeleteFromGraph(dependencies); + _viewer.DeleteFromGraph(relationships); } private void DeleteMarkedWithChildren(List markedElements) @@ -253,14 +257,14 @@ private void CompleteToTypes(List obj) AddToGraph(result.Elements, []); } - private void CompleteDependencies(List _) + private void CompleteRelationships(List _) { // Not interested in the marked elements! var viewerGraph = _viewer.GetGraph(); var ids = viewerGraph.Nodes.Keys.ToHashSet(); - var dependencies = _explorer.FindAllDependencies(ids); + var relationships = _explorer.FindAllRelationships(ids); - AddToGraph([], dependencies); + AddToGraph([], relationships); } private bool CanCollapse(CodeElement codeElement) @@ -347,7 +351,7 @@ private void Undo() var elements = _explorer.GetElements(state.CodeElementIds); // No undo stack operations while restoring the session. - _viewer.LoadSession(elements, state.Dependencies, state.PresentationState); + _viewer.LoadSession(elements, state.Relationships, state.PresentationState); } private void UpdateGraphRenderOption() @@ -355,34 +359,34 @@ private void UpdateGraphRenderOption() _viewer.UpdateRenderOption(SelectedRenderOption); } - private void FindAllOutgoingDependencies(CodeElement element) + private void FindAllOutgoingRelationships(CodeElement element) { - var result = _explorer.FindOutgoingDependencies(element.Id); - AddToGraph(result.Elements, result.Dependencies); + var result = _explorer.FindOutgoingRelationships(element.Id); + AddToGraph(result.Elements, result.Relationships); } - private void FindAllIncomingDependencies(CodeElement element) + private void FindAllIncomingRelationships(CodeElement element) { - var result = _explorer.FindIncomingDependencies(element.Id); - AddToGraph(result.Elements, result.Dependencies); + var result = _explorer.FindIncomingRelationships(element.Id); + AddToGraph(result.Elements, result.Relationships); } private void FindSpecializations(CodeElement method) { var result = _explorer.FindSpecializations(method.Id); - AddToGraph(result.Elements, result.Dependencies); + AddToGraph(result.Elements, result.Relationships); } private void FindAbstractions(CodeElement method) { var result = _explorer.FindAbstractions(method.Id); - AddToGraph(result.Elements, result.Dependencies); + AddToGraph(result.Elements, result.Relationships); } - private void AddToGraph(IEnumerable originalCodeElements, IEnumerable dependencies) + private void AddToGraph(IEnumerable originalCodeElements, IEnumerable relationships) { PushUndo(); - _viewer.AddToGraph(originalCodeElements, dependencies); + _viewer.AddToGraph(originalCodeElements, relationships); } public void LoadCodeGraph(CodeGraph codeGraph) @@ -447,7 +451,7 @@ internal void FollowIncomingCallsRecursive(CodeElement element) var result = _explorer.FollowIncomingCallsRecursive(element.Id); - AddToGraph(result.Elements, result.Dependencies); + AddToGraph(result.Elements, result.Relationships); } internal void FindInheritanceTree(CodeElement? type) @@ -459,7 +463,7 @@ internal void FindInheritanceTree(CodeElement? type) var relationships = _explorer.FindFullInheritanceTree(type.Id); - AddToGraph(relationships.Elements, relationships.Dependencies); + AddToGraph(relationships.Elements, relationships.Relationships); } internal void FindOutgoingCalls(CodeElement? method) @@ -484,7 +488,7 @@ protected virtual void OnPropertyChanged(string propertyName) } /// - /// Note that dependency type "Contains" is treated special + /// Note that relationship type "Contains" is treated special /// public CodeGraph ExportGraph() { @@ -553,6 +557,6 @@ public void LoadSession(GraphSession session, bool withUndo) } var elements = _explorer.GetElements(session.CodeElementIds); - _viewer.LoadSession(elements, session.Dependencies, session.PresentationState); + _viewer.LoadSession(elements, session.Relationships, session.PresentationState); } } \ No newline at end of file diff --git a/CSharpCodeAnalyst/GraphArea/DependencyGraphViewer.cs b/CSharpCodeAnalyst/GraphArea/GraphViewer.cs similarity index 83% rename from CSharpCodeAnalyst/GraphArea/DependencyGraphViewer.cs rename to CSharpCodeAnalyst/GraphArea/GraphViewer.cs index bebbef3..55dd01a 100644 --- a/CSharpCodeAnalyst/GraphArea/DependencyGraphViewer.cs +++ b/CSharpCodeAnalyst/GraphArea/GraphViewer.cs @@ -9,21 +9,20 @@ using CSharpCodeAnalyst.GraphArea.RenderOptions; using CSharpCodeAnalyst.Help; using Microsoft.Msagl.Drawing; -using Microsoft.Msagl.WpfGraphControl; using Node = Microsoft.Msagl.Drawing.Node; namespace CSharpCodeAnalyst.GraphArea; /// /// Note: -/// Between nodes we can have multiple dependencies if the dependency type is different. -/// Dependencies of the same type (i.e a method Calls another multiple times) are handled -/// in the parser. In this case the dependency holds all source references. +/// Between nodes we can have multiple relationships if the relationship type is different. +/// Relationships of the same type (i.e a method Calls another multiple times) are handled +/// in the parser. In this case the relationship holds all source references. /// If ever the MSAGL is replaced this is the adapter to re-write. /// -internal class DependencyGraphViewer : IDependencyGraphViewer, IDependencyGraphBinding, INotifyPropertyChanged +internal class GraphViewer : IGraphViewer, IGraphBinding, INotifyPropertyChanged { - private readonly List _edgeCommands = []; + private readonly List _edgeCommands = []; private readonly List _globalCommands = []; private readonly MsaglBuilder _msaglBuilder; private readonly List _nodeCommands = []; @@ -38,18 +37,18 @@ internal class DependencyGraphViewer : IDependencyGraphViewer, IDependencyGraphB private CodeGraph _clonedCodeGraph = new(); private IQuickInfoFactory? _factory; - private GraphViewer? _msaglViewer; + private Microsoft.Msagl.WpfGraphControl.GraphViewer? _msaglViewer; private PresentationState _presentationState = new(); private RenderOption _renderOption = new DefaultRenderOptions(); private bool _showFlatGraph; /// /// Note: - /// Between nodes we can have multiple dependencies if the dependency type is different. - /// Dependencies of the same type (i.e a method Calls another multiple times) are handled - /// in the parser. In this case the dependency holds all source references. + /// Between nodes we can have multiple relationships if the relationship type is different. + /// Relationships of the same type (i.e a method Calls another multiple times) are handled + /// in the parser. In this case the relationship holds all source references. /// - public DependencyGraphViewer(IPublisher publisher) + public GraphViewer(IPublisher publisher) { _publisher = publisher; _msaglBuilder = new MsaglBuilder(); @@ -58,7 +57,7 @@ public DependencyGraphViewer(IPublisher publisher) public void Bind(Panel graphPanel) { - _msaglViewer = new GraphViewer(); + _msaglViewer = new Microsoft.Msagl.WpfGraphControl.GraphViewer(); _msaglViewer.BindToPanel(graphPanel); _msaglViewer.ObjectUnderMouseCursorChanged += ObjectUnderMouseCursorChanged; @@ -72,18 +71,18 @@ public void ShowFlatGraph(bool value) } /// - /// Adding an existing element or dependency is prevented. + /// Adding an existing element or relationship is prevented. /// Note from the originalCodeElement we don't add parent or children. /// We just use this information to integrate the node into the existing canvas. /// - public void AddToGraph(IEnumerable originalCodeElements, IEnumerable newDependencies) + public void AddToGraph(IEnumerable originalCodeElements, IEnumerable newRelationships) { if (!IsBoundToPanel()) { return; } - AddToGraphInternal(originalCodeElements, newDependencies); + AddToGraphInternal(originalCodeElements, newRelationships); RefreshGraph(); } @@ -92,7 +91,7 @@ public void AddContextMenuCommand(ICodeElementContextCommand command) _nodeCommands.Add(command); } - public void AddContextMenuCommand(IDependencyContextCommand command) + public void AddContextMenuCommand(IRelationshipContextCommand command) { _edgeCommands.Add(command); } @@ -207,16 +206,16 @@ public void Clear() RefreshGraph(); } - public void DeleteFromGraph(List dependencies) + public void DeleteFromGraph(List relationships) { if (_msaglViewer is null) { return; } - foreach (var dependency in dependencies) + foreach (var relationship in relationships) { - _clonedCodeGraph.Nodes[dependency.SourceId].Dependencies.Remove(dependency); + _clonedCodeGraph.Nodes[relationship.SourceId].Relationships.Remove(relationship); } RefreshGraph(); @@ -257,7 +256,7 @@ public bool IsCollapsed(string id) return _presentationState.IsCollapsed(id); } - public void LoadSession(List codeElements, List dependencies, PresentationState state) + public void LoadSession(List codeElements, List relationships, PresentationState state) { if (_msaglViewer is null) { @@ -265,7 +264,7 @@ public void LoadSession(List codeElements, List depende } Clear(); - AddToGraphInternal(codeElements, dependencies); + AddToGraphInternal(codeElements, relationships); _presentationState = state; RefreshGraph(); @@ -291,7 +290,7 @@ private bool IsBoundToPanel() } private void AddToGraphInternal(IEnumerable originalCodeElements, - IEnumerable newDependencies) + IEnumerable newRelationships) { if (_msaglViewer is null) { @@ -300,11 +299,11 @@ private void AddToGraphInternal(IEnumerable originalCodeElements, IntegrateNewFromOriginal(originalCodeElements); - // Add dependencies we explicitly requested. - foreach (var newDependency in newDependencies) + // Add relationships we explicitly requested. + foreach (var newRelationship in newRelationships) { - var sourceElement = _clonedCodeGraph.Nodes[newDependency.SourceId]; - sourceElement.Dependencies.Add(newDependency); + var sourceElement = _clonedCodeGraph.Nodes[newRelationship.SourceId]; + sourceElement.Relationships.Add(newRelationship); } } @@ -428,8 +427,8 @@ bool IsCtrlPressed() // Click on specific edge var edge = viewerEdge.Edge; var contextMenu = new ContextMenu(); - var dependencies = GetDependenciesFromUserData(edge); - AddContextMenuEntries(dependencies, contextMenu); + var relationships = GetRelationshipsFromUserData(edge); + AddContextMenuEntries(relationships, contextMenu); if (contextMenu.Items.Count > 0) { contextMenu.IsOpen = true; @@ -447,9 +446,9 @@ bool IsCtrlPressed() } } - private void AddContextMenuEntries(List dependencies, ContextMenu contextMenu) + private void AddContextMenuEntries(List relationships, ContextMenu contextMenu) { - if (dependencies.Count == 0) + if (relationships.Count == 0) { return; } @@ -457,24 +456,24 @@ private void AddContextMenuEntries(List dependencies, ContextMenu co foreach (var cmd in _edgeCommands) { var menuItem = new MenuItem { Header = cmd.Label }; - if (cmd.CanHandle(dependencies)) + if (cmd.CanHandle(relationships)) { - menuItem.Click += (_, _) => cmd.Invoke(dependencies); + menuItem.Click += (_, _) => cmd.Invoke(relationships); contextMenu.Items.Add(menuItem); } } } - private static List GetDependenciesFromUserData(Edge edge) + private static List GetRelationshipsFromUserData(Edge edge) { - var result = new List(); + var result = new List(); switch (edge.UserData) { - case Dependency dependency: - result.Add(dependency); + case Relationship relationship: + result.Add(relationship); break; - case List dependencies: - result.AddRange(dependencies); + case List relationships: + result.AddRange(relationships); break; } diff --git a/CSharpCodeAnalyst/GraphArea/Highlighting/EdgeHoveredHighlighting.cs b/CSharpCodeAnalyst/GraphArea/Highlighting/EdgeHoveredHighlighting.cs index 8b141c7..f72d263 100644 --- a/CSharpCodeAnalyst/GraphArea/Highlighting/EdgeHoveredHighlighting.cs +++ b/CSharpCodeAnalyst/GraphArea/Highlighting/EdgeHoveredHighlighting.cs @@ -1,6 +1,5 @@ using Contracts.Graph; using Microsoft.Msagl.Drawing; -using Microsoft.Msagl.WpfGraphControl; namespace CSharpCodeAnalyst.GraphArea.Highlighting; @@ -8,13 +7,14 @@ internal class EdgeHoveredHighlighting : HighlightingBase { private IViewerEdge? _lastHighlightedEdge; - public override void Clear(GraphViewer? graphViewer) + public override void Clear(Microsoft.Msagl.WpfGraphControl.GraphViewer? graphViewer) { ClearAllEdges(graphViewer); _lastHighlightedEdge = null; } - public override void Highlight(GraphViewer? graphViewer, IViewerObject? viewerObject, CodeGraph? codeGraph) + public override void Highlight(Microsoft.Msagl.WpfGraphControl.GraphViewer? graphViewer, + IViewerObject? viewerObject, CodeGraph? codeGraph) { if (graphViewer is null || codeGraph is null) { diff --git a/CSharpCodeAnalyst/GraphArea/Highlighting/HighlightingBase.cs b/CSharpCodeAnalyst/GraphArea/Highlighting/HighlightingBase.cs index 4a80f7c..48fbe3b 100644 --- a/CSharpCodeAnalyst/GraphArea/Highlighting/HighlightingBase.cs +++ b/CSharpCodeAnalyst/GraphArea/Highlighting/HighlightingBase.cs @@ -1,22 +1,21 @@ using Contracts.Graph; using Microsoft.Msagl.Drawing; -using Microsoft.Msagl.WpfGraphControl; namespace CSharpCodeAnalyst.GraphArea.Highlighting; internal abstract class HighlightingBase : IHighlighting { + private readonly Color _grayColor = Color.LightGray; private readonly Color _highlightColor = Color.Red; private readonly int _highlightWeight = 3; private readonly Color _normalColor = Color.Black; private readonly int _normalWeight = 1; - private readonly Color _grayColor = Color.LightGray; - - public abstract void Highlight(GraphViewer? graphViewer, IViewerObject? viewerObject, CodeGraph? codeGraph); + public abstract void Highlight(Microsoft.Msagl.WpfGraphControl.GraphViewer? graphViewer, + IViewerObject? viewerObject, CodeGraph? codeGraph); - public abstract void Clear(GraphViewer? graphViewer); + public abstract void Clear(Microsoft.Msagl.WpfGraphControl.GraphViewer? graphViewer); protected void Highlight(IViewerEdge edge) { @@ -31,7 +30,7 @@ protected void ClearHighlight(IViewerEdge? edge) return; } - if (edge.Edge.UserData is Dependency { Type: DependencyType.Containment }) + if (edge.Edge.UserData is Relationship { Type: RelationshipType.Containment }) { edge.Edge.Attr.Color = _grayColor; } @@ -39,11 +38,11 @@ protected void ClearHighlight(IViewerEdge? edge) { edge.Edge.Attr.Color = _normalColor; } - + edge.Edge.Attr.LineWidth = _normalWeight; } - protected void ClearAllEdges(GraphViewer? graphViewer) + protected void ClearAllEdges(Microsoft.Msagl.WpfGraphControl.GraphViewer? graphViewer) { if (graphViewer is null) { diff --git a/CSharpCodeAnalyst/GraphArea/Highlighting/HighligtShortestNonSelfCircuit.cs b/CSharpCodeAnalyst/GraphArea/Highlighting/HighligtShortestNonSelfCircuit.cs index cb9da14..8b46424 100644 --- a/CSharpCodeAnalyst/GraphArea/Highlighting/HighligtShortestNonSelfCircuit.cs +++ b/CSharpCodeAnalyst/GraphArea/Highlighting/HighligtShortestNonSelfCircuit.cs @@ -1,6 +1,5 @@ using Contracts.Graph; using Microsoft.Msagl.Drawing; -using Microsoft.Msagl.WpfGraphControl; namespace CSharpCodeAnalyst.GraphArea.Highlighting; @@ -10,7 +9,7 @@ internal class HighligtShortestNonSelfCircuit : HighlightingBase private Graph? _lastGraph; - public override void Clear(GraphViewer? graphViewer) + public override void Clear(Microsoft.Msagl.WpfGraphControl.GraphViewer? graphViewer) { ClearAllEdges(graphViewer); _idToViewerNode.Clear(); @@ -18,7 +17,8 @@ public override void Clear(GraphViewer? graphViewer) } - public override void Highlight(GraphViewer? graphViewer, IViewerObject? viewerObject, CodeGraph? codeGraph) + public override void Highlight(Microsoft.Msagl.WpfGraphControl.GraphViewer? graphViewer, + IViewerObject? viewerObject, CodeGraph? codeGraph) { if (graphViewer is null || codeGraph is null) { diff --git a/CSharpCodeAnalyst/GraphArea/Highlighting/IHighlighting.cs b/CSharpCodeAnalyst/GraphArea/Highlighting/IHighlighting.cs index e0af16f..7855c9d 100644 --- a/CSharpCodeAnalyst/GraphArea/Highlighting/IHighlighting.cs +++ b/CSharpCodeAnalyst/GraphArea/Highlighting/IHighlighting.cs @@ -1,6 +1,5 @@ using Contracts.Graph; using Microsoft.Msagl.Drawing; -using Microsoft.Msagl.WpfGraphControl; namespace CSharpCodeAnalyst.GraphArea.Highlighting; @@ -10,7 +9,8 @@ internal interface IHighlighting /// Clear any internal state before another highlighting is selected- /// /// - void Clear(GraphViewer? graphViewer); + void Clear(Microsoft.Msagl.WpfGraphControl.GraphViewer? graphViewer); - void Highlight(GraphViewer? graphViewer, IViewerObject? viewerObject, CodeGraph? codeGraph); + void Highlight(Microsoft.Msagl.WpfGraphControl.GraphViewer? graphViewer, IViewerObject? viewerObject, + CodeGraph? codeGraph); } \ No newline at end of file diff --git a/CSharpCodeAnalyst/GraphArea/Highlighting/OutgointEdgesOfChildrenAndSelfHighlighting.cs b/CSharpCodeAnalyst/GraphArea/Highlighting/OutgointEdgesOfChildrenAndSelfHighlighting.cs index 8397ccc..8d88070 100644 --- a/CSharpCodeAnalyst/GraphArea/Highlighting/OutgointEdgesOfChildrenAndSelfHighlighting.cs +++ b/CSharpCodeAnalyst/GraphArea/Highlighting/OutgointEdgesOfChildrenAndSelfHighlighting.cs @@ -1,17 +1,17 @@ using Contracts.Graph; using Microsoft.Msagl.Drawing; -using Microsoft.Msagl.WpfGraphControl; namespace CSharpCodeAnalyst.GraphArea.Highlighting; internal class OutgointEdgesOfChildrenAndSelfHighlighting : HighlightingBase { - public override void Clear(GraphViewer? graphViewer) + public override void Clear(Microsoft.Msagl.WpfGraphControl.GraphViewer? graphViewer) { ClearAllEdges(graphViewer); } - public override void Highlight(GraphViewer? graphViewer, IViewerObject? viewerObject, CodeGraph? codeGraph) + public override void Highlight(Microsoft.Msagl.WpfGraphControl.GraphViewer? graphViewer, + IViewerObject? viewerObject, CodeGraph? codeGraph) { if (graphViewer is null || codeGraph is null) { @@ -27,7 +27,6 @@ public override void Highlight(GraphViewer? graphViewer, IViewerObject? viewerOb var ids = new HashSet(); if (node != null) { - // TODO atr How to iterate the graph? var id = node.Node.Id; var vertex = codeGraph.Nodes[id]; ids = vertex.GetChildrenIncludingSelf(); diff --git a/CSharpCodeAnalyst/GraphArea/IDependencyContextCommand.cs b/CSharpCodeAnalyst/GraphArea/IDependencyContextCommand.cs deleted file mode 100644 index c5cd033..0000000 --- a/CSharpCodeAnalyst/GraphArea/IDependencyContextCommand.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Contracts.Graph; - -namespace CSharpCodeAnalyst.GraphArea; - -public interface IDependencyContextCommand -{ - string Label { get; } - - bool CanHandle(List dependencies); - void Invoke(List dependencies); -} \ No newline at end of file diff --git a/CSharpCodeAnalyst/GraphArea/IDependencyGraphBinding.cs b/CSharpCodeAnalyst/GraphArea/IGraphBinding.cs similarity index 87% rename from CSharpCodeAnalyst/GraphArea/IDependencyGraphBinding.cs rename to CSharpCodeAnalyst/GraphArea/IGraphBinding.cs index 73c6474..d2fbda7 100644 --- a/CSharpCodeAnalyst/GraphArea/IDependencyGraphBinding.cs +++ b/CSharpCodeAnalyst/GraphArea/IGraphBinding.cs @@ -2,7 +2,7 @@ namespace CSharpCodeAnalyst.GraphArea; -public interface IDependencyGraphBinding +public interface IGraphBinding { /// /// Binds the given Panel to the viewer. diff --git a/CSharpCodeAnalyst/GraphArea/IDependencyGraphViewer.cs b/CSharpCodeAnalyst/GraphArea/IGraphViewer.cs similarity index 84% rename from CSharpCodeAnalyst/GraphArea/IDependencyGraphViewer.cs rename to CSharpCodeAnalyst/GraphArea/IGraphViewer.cs index 112cb9d..1bf2bcb 100644 --- a/CSharpCodeAnalyst/GraphArea/IDependencyGraphViewer.cs +++ b/CSharpCodeAnalyst/GraphArea/IGraphViewer.cs @@ -5,12 +5,12 @@ namespace CSharpCodeAnalyst.GraphArea; -internal interface IDependencyGraphViewer +internal interface IGraphViewer { void ShowFlatGraph(bool value); - void AddToGraph(IEnumerable originalCodeElements, IEnumerable dependencies); + void AddToGraph(IEnumerable originalCodeElements, IEnumerable newRelationships); void DeleteFromGraph(HashSet idsToRemove); - void DeleteFromGraph(List dependencies); + void DeleteFromGraph(List relationships); void AddContextMenuCommand(ICodeElementContextCommand command); void AddGlobalContextMenuCommand(IGlobalContextCommand command); @@ -52,12 +52,12 @@ internal interface IDependencyGraphViewer /// /// Undo and gallery. /// - void LoadSession(List codeElements, List dependencies, PresentationState state); + void LoadSession(List codeElements, List relationships, PresentationState state); /// /// Cycle groups, focus on marked elements /// void LoadSession(CodeGraph newGraph, PresentationState? presentationState); - void AddContextMenuCommand(IDependencyContextCommand command); + void AddContextMenuCommand(IRelationshipContextCommand command); } \ No newline at end of file diff --git a/CSharpCodeAnalyst/GraphArea/IRelationshipContextCommand.cs b/CSharpCodeAnalyst/GraphArea/IRelationshipContextCommand.cs new file mode 100644 index 0000000..bd84236 --- /dev/null +++ b/CSharpCodeAnalyst/GraphArea/IRelationshipContextCommand.cs @@ -0,0 +1,11 @@ +using Contracts.Graph; + +namespace CSharpCodeAnalyst.GraphArea; + +public interface IRelationshipContextCommand +{ + string Label { get; } + + bool CanHandle(List relationships); + void Invoke(List relationships); +} \ No newline at end of file diff --git a/CSharpCodeAnalyst/GraphArea/MsaglBuilder.cs b/CSharpCodeAnalyst/GraphArea/MsaglBuilder.cs index 07231ee..1158520 100644 --- a/CSharpCodeAnalyst/GraphArea/MsaglBuilder.cs +++ b/CSharpCodeAnalyst/GraphArea/MsaglBuilder.cs @@ -33,21 +33,22 @@ private Graph CreateFlatGraph(CodeGraph codeGraph) } // Add edges and hierarchy - codeGraph.DfsHierarchy(AddDependenciesFunc); + codeGraph.DfsHierarchy(AddRelationshipsFunc); return graph; - void AddDependenciesFunc(CodeElement element) + void AddRelationshipsFunc(CodeElement element) { - foreach (var dependency in element.Dependencies) + foreach (var relationship in element.Relationships) { - CreateEdgeForFlatStructure(graph, dependency); + CreateEdgeForFlatStructure(graph, relationship); } if (element.Parent != null) { - CreateContainmentEdge(graph, new Dependency(element.Parent.Id, element.Id, DependencyType.Containment)); + CreateContainmentEdge(graph, + new Relationship(element.Parent.Id, element.Id, RelationshipType.Containment)); } } } @@ -73,7 +74,7 @@ private CodeGraph GetVisibleGraph(CodeGraph codeGraph, PresentationState state) CollectVisibleNodes(root, state, visibleGraph); } - // Graph has no dependencies yet. + // Graph has no relationships yet. return visibleGraph; } @@ -140,35 +141,35 @@ private void AddNodeToParent(Graph graph, CodeElement node, Dictionary> GetCollapsedDependencies(CodeGraph codeGraph, + private Dictionary<(string, string), List> GetCollapsedRelationships(CodeGraph codeGraph, CodeGraph visibleGraph) { - var allDependencies = codeGraph.GetAllDependencies(); - var dependencies = new Dictionary<(string, string), List>(); + var allRelationships = codeGraph.GetAllRelationships(); + var relationships = new Dictionary<(string, string), List>(); - foreach (var dependency in allDependencies) + foreach (var relationship in allRelationships) { // Move edges to collapsed nodes. - var source = GetHighestVisibleParentOrSelf(dependency.SourceId, codeGraph, visibleGraph); - var target = GetHighestVisibleParentOrSelf(dependency.TargetId, codeGraph, visibleGraph); + var source = GetHighestVisibleParentOrSelf(relationship.SourceId, codeGraph, visibleGraph); + var target = GetHighestVisibleParentOrSelf(relationship.TargetId, codeGraph, visibleGraph); - if (!dependencies.TryGetValue((source, target), out var list)) + if (!relationships.TryGetValue((source, target), out var list)) { - list = new List(); - dependencies[(source, target)] = list; + list = new List(); + relationships[(source, target)] = list; } - list.Add(dependency); + list.Add(relationship); } - return dependencies; + return relationships; } /// @@ -204,34 +205,34 @@ private string GetHighestVisibleParentOrSelf(string id, CodeGraph codeGraph, Cod } private void CreateEdgeForHierarchicalStructure(Graph graph, - KeyValuePair<(string source, string target), List> mappedDependency) + KeyValuePair<(string source, string target), List> mappedRelationships) { // MSAGL does not allow two same edges with different labels to the same subgraph. // So I collapse them to a single one that carries all the user data. - var dependencies = mappedDependency.Value; - if (mappedDependency.Value.Count == 1 && mappedDependency.Key.source == dependencies[0].SourceId && - mappedDependency.Key.target == dependencies[0].TargetId) + var relationships = mappedRelationships.Value; + if (mappedRelationships.Value.Count == 1 && mappedRelationships.Key.source == relationships[0].SourceId && + mappedRelationships.Key.target == relationships[0].TargetId) { - // Single, unmapped dependency - var dependency = dependencies[0]; - var edge = graph.AddEdge(dependency.SourceId, dependency.TargetId); + // Single, unmapped relationship + var relationship = relationships[0]; + var edge = graph.AddEdge(relationship.SourceId, relationship.TargetId); - edge.LabelText = GetLabelText(dependency); - if (dependency.Type == DependencyType.Implements) + edge.LabelText = GetLabelText(relationship); + if (relationship.Type == RelationshipType.Implements) { edge.Attr.AddStyle(Style.Dotted); } - edge.UserData = new List { dependency }; + edge.UserData = new List { relationship }; } else { // More than one or mapped to collapsed container. - var edge = graph.AddEdge(mappedDependency.Key.source, mappedDependency.Key.target); + var edge = graph.AddEdge(mappedRelationships.Key.source, mappedRelationships.Key.target); - edge.UserData = dependencies; - edge.LabelText = dependencies.Count.ToString(); + edge.UserData = relationships; + edge.LabelText = relationships.Count.ToString(); // No unique styling possible when we collapse multiple edges // Mark the multi edges with a bold line @@ -239,55 +240,55 @@ private void CreateEdgeForHierarchicalStructure(Graph graph, } } - private static void CreateEdgeForFlatStructure(Graph graph, Dependency dependency) + private static void CreateEdgeForFlatStructure(Graph graph, Relationship relationship) { // MSAGL does not allow two same edges with different labels to the same subgraph. - var edge = graph.AddEdge(dependency.SourceId, dependency.TargetId); + var edge = graph.AddEdge(relationship.SourceId, relationship.TargetId); - edge.LabelText = GetLabelText(dependency); + edge.LabelText = GetLabelText(relationship); - if (dependency.Type == DependencyType.Implements) + if (relationship.Type == RelationshipType.Implements) { edge.Attr.AddStyle(Style.Dotted); } - edge.UserData = dependency; + edge.UserData = relationship; } - private static string GetLabelText(Dependency dependency) + private static string GetLabelText(Relationship relationship) { - // Omit the label text for now. The color makes it clear that it is a call dependency - if (dependency.Type == DependencyType.Calls || dependency.Type == DependencyType.Invokes) + // Omit the label text for now. The color makes it clear that it is a call relationship + if (relationship.Type == RelationshipType.Calls || relationship.Type == RelationshipType.Invokes) { return string.Empty; } // We can see this by the dotted line - if (dependency.Type == DependencyType.Implements || dependency.Type == DependencyType.Inherits) + if (relationship.Type == RelationshipType.Implements || relationship.Type == RelationshipType.Inherits) { return string.Empty; } - if (dependency.Type == DependencyType.Uses) + if (relationship.Type == RelationshipType.Uses) { return string.Empty; } - if (dependency.Type == DependencyType.UsesAttribute) + if (relationship.Type == RelationshipType.UsesAttribute) { return string.Empty; } - return dependency.Type.ToString(); + return relationship.Type.ToString(); } - private static void CreateContainmentEdge(Graph graph, Dependency dependency) + private static void CreateContainmentEdge(Graph graph, Relationship relationship) { - var edge = graph.AddEdge(dependency.SourceId, dependency.TargetId); + var edge = graph.AddEdge(relationship.SourceId, relationship.TargetId); edge.LabelText = ""; edge.Attr.Color = Color.LightGray; - edge.UserData = dependency; + edge.UserData = relationship; } private static Node CreateNode(Graph graph, CodeElement codeElement) diff --git a/CSharpCodeAnalyst/GraphArea/RelationshipContextCommand.cs b/CSharpCodeAnalyst/GraphArea/RelationshipContextCommand.cs new file mode 100644 index 0000000..39c7964 --- /dev/null +++ b/CSharpCodeAnalyst/GraphArea/RelationshipContextCommand.cs @@ -0,0 +1,63 @@ +using Contracts.Graph; + +namespace CSharpCodeAnalyst.GraphArea; + +public class RelationshipContextCommand : IRelationshipContextCommand +{ + private readonly Action> _action; + private readonly Func, bool>? _canExecute; + private readonly RelationshipType? _type; + + public RelationshipContextCommand(string label, RelationshipType type, Action> action) + { + _type = type; + _action = action; + Label = label; + } + + /// + /// Generic for all code elements + /// + public RelationshipContextCommand(string label, Action> action, + Func, bool>? canExecute = null) + { + _type = null; + _action = action; + _canExecute = canExecute; + Label = label; + } + + public string Label { get; } + + public bool CanHandle(List relationships) + { + // This is a dummy relationship to visualize hierarchical relationships in flat graph. + if (relationships.Any(d => d.Type == RelationshipType.Containment)) + { + return false; + } + + if (_type != null) + { + if (relationships.All(d => d.Type == _type) is false) + { + return false; + } + } + + var canHandle = true; + if (_canExecute != null) + { + // Further restrict the handling + canHandle = _canExecute.Invoke(relationships); + } + + return canHandle; + } + + + public void Invoke(List relationships) + { + _action.Invoke(relationships); + } +} \ No newline at end of file diff --git a/CSharpCodeAnalyst/Help/QuickInfoFactory.cs b/CSharpCodeAnalyst/Help/QuickInfoFactory.cs index 9fee447..65b9ea1 100644 --- a/CSharpCodeAnalyst/Help/QuickInfoFactory.cs +++ b/CSharpCodeAnalyst/Help/QuickInfoFactory.cs @@ -31,14 +31,14 @@ public List CrateQuickInfo(object? obj) else if (obj is IViewerEdge viewerEdge) { var edge = viewerEdge.Edge; - if (edge.UserData is Dependency dependency) + if (edge.UserData is Relationship relationship) { - return CreateEdgeQuickInfos([dependency]); + return CreateEdgeQuickInfos([relationship]); } - if (edge.UserData is List dependencies) + if (edge.UserData is List relationships) { - return CreateEdgeQuickInfos(dependencies); + return CreateEdgeQuickInfos(relationships); } } @@ -60,28 +60,28 @@ private QuickInfo CreateNodeQuickInfo(CodeElement codeElement) return contextInfo; } - private List CreateEdgeQuickInfos(List dependencies) + private List CreateEdgeQuickInfos(List relationships) { var quickInfos = new List(); - foreach (var d in dependencies) + foreach (var r in relationships) { - quickInfos.Add(CreateEdgeQuickInfo(d)); + quickInfos.Add(CreateEdgeQuickInfo(r)); } return quickInfos; } - private QuickInfo CreateEdgeQuickInfo(Dependency dependency) + private QuickInfo CreateEdgeQuickInfo(Relationship relationship) { var contextInfo = new QuickInfo { - Title = $"Dependency: {dependency.Type.ToString()}", + Title = $"Relationship: {relationship.Type.ToString()}", Lines = [ - new ContextInfoLine { Label = "Source:", Value = GetElementName(dependency.SourceId) }, - new ContextInfoLine { Label = "Target:", Value = GetElementName(dependency.TargetId) } + new ContextInfoLine { Label = "Source:", Value = GetElementName(relationship.SourceId) }, + new ContextInfoLine { Label = "Target:", Value = GetElementName(relationship.TargetId) } ], - SourceLocations = dependency.SourceLocations + SourceLocations = relationship.SourceLocations }; return contextInfo; } diff --git a/CSharpCodeAnalyst/MainViewModel.cs b/CSharpCodeAnalyst/MainViewModel.cs index 3f83f22..3924335 100644 --- a/CSharpCodeAnalyst/MainViewModel.cs +++ b/CSharpCodeAnalyst/MainViewModel.cs @@ -491,10 +491,6 @@ private async Task LoadAndAnalyzeSolution(string solutionPath) LoadCodeGraph(codeGraph); - // Debug output for the parser result. - //DgmlHierarchyExport.Export(@"d:\test_hierarchy.dgml", codeStructure); - //DgmlDependencyExport.Export(@"d:\test_dependency.dgml", codeStructure); - // Imported a new solution _isSaved = false; } @@ -543,11 +539,11 @@ private void LoadCodeGraph(CodeGraph codeGraph) } // Default output: summary of graph - var numberOfDependencies = codeGraph.GetAllDependencies().Count(); + var numberOfRelationships = codeGraph.GetAllRelationships().Count(); var outputs = new ObservableCollection(); outputs.Clear(); outputs.Add(new MetricOutput("# Code elements", codeGraph.Nodes.Count.ToString(CultureInfo.InvariantCulture))); - outputs.Add(new MetricOutput("# Dependencies", numberOfDependencies.ToString(CultureInfo.InvariantCulture))); + outputs.Add(new MetricOutput("# Relationships", numberOfRelationships.ToString(CultureInfo.InvariantCulture))); Metrics = outputs; } diff --git a/CSharpCodeAnalyst/MainWindow.xaml.cs b/CSharpCodeAnalyst/MainWindow.xaml.cs index 5fd4d0f..c236565 100644 --- a/CSharpCodeAnalyst/MainWindow.xaml.cs +++ b/CSharpCodeAnalyst/MainWindow.xaml.cs @@ -19,7 +19,7 @@ public MainWindow() InitializeComponent(); } - public void SetViewer(IDependencyGraphBinding explorationGraphViewer + public void SetViewer(IGraphBinding explorationGraphViewer ) { explorationGraphViewer.Bind(ExplorationGraphPanel); diff --git a/CSharpCodeAnalyst/Project/ProjectData.cs b/CSharpCodeAnalyst/Project/ProjectData.cs index 9ab26ea..eb12a7a 100644 --- a/CSharpCodeAnalyst/Project/ProjectData.cs +++ b/CSharpCodeAnalyst/Project/ProjectData.cs @@ -9,7 +9,7 @@ public class ProjectData public List CodeElements { get; set; } = []; - public List Dependencies { get; set; } = []; + public List Relationships { get; set; } = []; public Dictionary Settings { get; set; } = new(); @@ -20,8 +20,8 @@ public class ProjectData public void SetGallery(Gallery.Gallery gallery) { - // TODO Consider to introduce a serializable Gallery not storing the source locations for dependencies. - // This would save space. But we have to restore the dependencies. + // TODO Consider to introduce a serializable Gallery not storing the source locations for relationships. + // This would save space. But we have to restore the relationships. Gallery = gallery; } @@ -38,18 +38,19 @@ public void SetCodeGraph(CodeGraph codeGraph) { CodeElements = codeGraph.Nodes.Values .Select(n => - new SerializableCodeElement(n.Id, n.Name, n.FullName, n.ElementType, n.SourceLocations, n.Attributes) - { SourceLocations = n.SourceLocations }).ToList(); + new SerializableCodeElement(n.Id, n.Name, n.FullName, n.ElementType, n.SourceLocations, n.Attributes)) + .ToList(); // We iterate over children, so we expect to have a parent Children = codeGraph.Nodes.Values .SelectMany(element => element.Children) .Select(child => new SerializableChild(child.Id, child.Parent!.Id)).ToList(); - Dependencies = codeGraph.Nodes.Values - .SelectMany(element => element.Dependencies) - .Select(dependency => new SerializableDependency(dependency.SourceId, dependency.TargetId, dependency.Type, - dependency.SourceLocations)) + Relationships = codeGraph.Nodes.Values + .SelectMany(element => element.Relationships) + .Select(relationship => new SerializableRelationship(relationship.SourceId, relationship.TargetId, + relationship.Type, + relationship.SourceLocations)) .ToList(); } @@ -66,7 +67,7 @@ public CodeGraph GetCodeGraph() codeStructure.Nodes.Add(element.Id, element); } - // Pass two: Create dependencies and parent / child connections + // Pass two: Create relationships and parent / child connections foreach (var sc in Children) { var child = codeStructure.Nodes[sc.ChildId]; @@ -75,12 +76,12 @@ public CodeGraph GetCodeGraph() parent.Children.Add(child); } - foreach (var sd in Dependencies) + foreach (var sd in Relationships) { var source = codeStructure.Nodes[sd.SourceId]; - var dependency = new Dependency(sd.SourceId, sd.TargetId, sd.Type); - dependency.SourceLocations = sd.SourceLocations; - source.Dependencies.Add(dependency); + var relationship = new Relationship(sd.SourceId, sd.TargetId, sd.Type); + relationship.SourceLocations = sd.SourceLocations; + source.Relationships.Add(relationship); } diff --git a/CSharpCodeAnalyst/Project/SerializableDependency.cs b/CSharpCodeAnalyst/Project/SerializableRelationship.cs similarity index 74% rename from CSharpCodeAnalyst/Project/SerializableDependency.cs rename to CSharpCodeAnalyst/Project/SerializableRelationship.cs index d24d9a7..9f0ed0b 100644 --- a/CSharpCodeAnalyst/Project/SerializableDependency.cs +++ b/CSharpCodeAnalyst/Project/SerializableRelationship.cs @@ -3,14 +3,14 @@ namespace CSharpCodeAnalyst.Project; [Serializable] -public class SerializableDependency( +public class SerializableRelationship( string sourceId, string targetId, - DependencyType type, + RelationshipType type, List sourceLocations) { public string SourceId { get; set; } = sourceId; public string TargetId { get; set; } = targetId; - public DependencyType Type { get; set; } = type; + public RelationshipType Type { get; set; } = type; public List SourceLocations { get; set; } = sourceLocations; } \ No newline at end of file diff --git a/CSharpCodeAnalyst/Resources/Strings.Designer.cs b/CSharpCodeAnalyst/Resources/Strings.Designer.cs index 8fecf78..057f063 100644 --- a/CSharpCodeAnalyst/Resources/Strings.Designer.cs +++ b/CSharpCodeAnalyst/Resources/Strings.Designer.cs @@ -79,20 +79,20 @@ public static string AddToGraph_MenuItem { } /// - /// Looks up a localized string similar to All incoming dependencies. + /// Looks up a localized string similar to All incoming relationships. /// - public static string AllIncomingDependencies { + public static string AllIncomingRelationships { get { - return ResourceManager.GetString("AllIncomingDependencies", resourceCulture); + return ResourceManager.GetString("AllIncomingRelationships", resourceCulture); } } /// - /// Looks up a localized string similar to All outgoingdependencies. + /// Looks up a localized string similar to All outgoing relationships. /// - public static string AllOutgoingDependencies { + public static string AllOutgoingRelationships { get { - return ResourceManager.GetString("AllOutgoingDependencies", resourceCulture); + return ResourceManager.GetString("AllOutgoingRelationships", resourceCulture); } } @@ -187,11 +187,11 @@ public static string CollapseTree_Tooltip { } /// - /// Looks up a localized string similar to Complete dependencies. + /// Looks up a localized string similar to Complete relationships. /// - public static string CompleteDependencies { + public static string CompleteRelationships { get { - return ResourceManager.GetString("CompleteDependencies", resourceCulture); + return ResourceManager.GetString("CompleteRelationships", resourceCulture); } } diff --git a/CSharpCodeAnalyst/Resources/Strings.resx b/CSharpCodeAnalyst/Resources/Strings.resx index a92acff..b93aba5 100644 --- a/CSharpCodeAnalyst/Resources/Strings.resx +++ b/CSharpCodeAnalyst/Resources/Strings.resx @@ -120,11 +120,11 @@ Add Parent - - All incoming dependencies + + All incoming relationships - - All outgoingdependencies + + All outgoing relationships C# Code Analyst @@ -132,8 +132,8 @@ Collapse - - Complete dependencies + + Complete relationships Delete diff --git a/CSharpCodeAnalyst/board.txt b/CSharpCodeAnalyst/board.txt index bf8dec1..71592d8 100644 --- a/CSharpCodeAnalyst/board.txt +++ b/CSharpCodeAnalyst/board.txt @@ -3,11 +3,6 @@ - Anonymous methods like PdsOnElementCreated, lambdas -- SymbolEqualityComparer does not work for this application well. Remove it where it is used. -- Check if the map key -> Symbol is unique. If not use a list of symbols! - _elementIdToSymbolMap. Are all locations captured in this case? - - - Highlighting aggregated edges takes too long. If quick help is not visible then skip. - Performance in general @@ -19,7 +14,7 @@ UNHANDLED CASES --------------------- - Attributes are caught at class or method level. Not for the parameters like [CallerMemberName] - +- Two projects with same name BUGS --------------------- diff --git a/CodeParser/Analysis/Cycles/CodeGraphBuilder.cs b/CodeParser/Analysis/Cycles/CodeGraphBuilder.cs index 7f303b5..bae89db 100644 --- a/CodeParser/Analysis/Cycles/CodeGraphBuilder.cs +++ b/CodeParser/Analysis/Cycles/CodeGraphBuilder.cs @@ -74,7 +74,7 @@ public static CodeGraph GenerateDetailedCodeGraph(List searchGraph, originalGraph.Nodes[originalDependency.TargetId]); // Include dependency - source.Dependencies.Add(originalDependency); + source.Relationships.Add(originalDependency); } } } @@ -83,16 +83,16 @@ public static CodeGraph GenerateDetailedCodeGraph(List searchGraph, return detailedGraph; } - private static List GetOriginalDependencies(CodeGraph originalGraph, HashSet sources, + private static List GetOriginalDependencies(CodeGraph originalGraph, HashSet sources, HashSet targets) { // All original edges causing the same dependency as proxy used in search graph. var sourceElements = sources.Select(s => originalGraph.Nodes[s]); - var fromSource = sourceElements.SelectMany(s => s.Dependencies); + var fromSource = sourceElements.SelectMany(s => s.Relationships); var originalDependencies = fromSource .Where(d => targets.Contains(d.TargetId)) - .Where(d => DependencyClassifier.IsDependencyRelevantForCycle(originalGraph, d)) + .Where(d => RelationshipClassifier.IsRelationshipRelevantForCycle(originalGraph, d)) .ToList(); // Performance nightmare diff --git a/CodeParser/Analysis/Cycles/DependencyClassifier.cs b/CodeParser/Analysis/Cycles/DependencyClassifier.cs deleted file mode 100644 index 7d5a30f..0000000 --- a/CodeParser/Analysis/Cycles/DependencyClassifier.cs +++ /dev/null @@ -1,29 +0,0 @@ -using Contracts.Graph; - -namespace CodeParser.Analysis.Cycles; - -public static class DependencyClassifier -{ - /// - /// The fact that a method overrides another is only interesting when exploring a codebase. - /// For the dependency graph it is enough to see that the type inherits from an interface. - /// - public static bool IsDependencyRelevantForCycle(CodeGraph codeGraph, Dependency dependency) - { - var source = codeGraph.Nodes[dependency.SourceId]; - var target = codeGraph.Nodes[dependency.TargetId]; - - switch (source.ElementType) - { - case CodeElementType.Method when target.ElementType is CodeElementType.Method - && dependency.Type == DependencyType.Implements: - case CodeElementType.Method when target.ElementType is CodeElementType.Method - && dependency.Type == DependencyType.Overrides: - case CodeElementType.Property when target.ElementType is CodeElementType.Property - && dependency.Type == DependencyType.Implements: - return false; - default: - return true; - } - } -} \ No newline at end of file diff --git a/CodeParser/Analysis/Cycles/RelationshipClassifier.cs b/CodeParser/Analysis/Cycles/RelationshipClassifier.cs new file mode 100644 index 0000000..bbe813e --- /dev/null +++ b/CodeParser/Analysis/Cycles/RelationshipClassifier.cs @@ -0,0 +1,36 @@ +using Contracts.Graph; + +namespace CodeParser.Analysis.Cycles; + +public static class RelationshipClassifier +{ + /// + /// The fact that a method overrides another is only interesting when exploring a codebase. + /// For the relationship graph it is enough to see that the type inherits from an interface. + /// + public static bool IsRelationshipRelevantForCycle(CodeGraph codeGraph, Relationship relationship) + { + if (relationship.Type == RelationshipType.Handles) + { + // This is not a code dependency. It is actually the other direction. + // This would break the cycle detection. + return false; + } + + var source = codeGraph.Nodes[relationship.SourceId]; + var target = codeGraph.Nodes[relationship.TargetId]; + + switch (source.ElementType) + { + case CodeElementType.Method when target.ElementType is CodeElementType.Method + && relationship.Type == RelationshipType.Implements: + case CodeElementType.Method when target.ElementType is CodeElementType.Method + && relationship.Type == RelationshipType.Overrides: + case CodeElementType.Property when target.ElementType is CodeElementType.Property + && relationship.Type == RelationshipType.Implements: + return false; + default: + return true; + } + } +} \ No newline at end of file diff --git a/CodeParser/Analysis/Cycles/SearchGraphBuilder.cs b/CodeParser/Analysis/Cycles/SearchGraphBuilder.cs index 07029c6..9a83632 100644 --- a/CodeParser/Analysis/Cycles/SearchGraphBuilder.cs +++ b/CodeParser/Analysis/Cycles/SearchGraphBuilder.cs @@ -17,16 +17,12 @@ public static SearchGraph BuildSearchGraph(CodeGraph codeGraph) } // Second pass: Add dependencies for the search graph - var allDependencies = codeGraph.Nodes.Values.SelectMany(c => c.Dependencies); - + var allDependencies = codeGraph.Nodes.Values + .SelectMany(c => c.Relationships) + .Where(r => RelationshipClassifier.IsRelationshipRelevantForCycle(codeGraph, r)); foreach (var dependency in allDependencies) { - if (!DependencyClassifier.IsDependencyRelevantForCycle(codeGraph, dependency)) - { - continue; - } - var (source, target) = GetHighestElementsInvolvedInDependency(codeGraph, dependency); if (source.Id == target.Id && !IsMethod(codeGraph, source.Id)) { @@ -55,10 +51,10 @@ private static SearchNode GetSearchNode(CodeElement element, Dictionary /// Exports the given nodes and edges to a dgml file. - /// Note that the "Contains" dependency is treated as hierarchy information + /// Note that the "Contains" relationship is treated as hierarchy information /// to build sub-graphs in the output file. /// public void Export(string fileName, CodeGraph graph) @@ -34,9 +34,9 @@ public void Export(string fileName, CodeGraph graph) containsRelationships.AddRange(node.Children.Select(c => (node.Id, c.Id))); } - // Regular dependencies - var normal = new List(); - graph.DfsHierarchy(e => normal.AddRange(e.Dependencies)); + // Regular relationships + var normal = new List(); + graph.DfsHierarchy(e => normal.AddRange(e.Relationships)); foreach (var edge in normal) { // Omit the calls label for better readability. @@ -60,31 +60,31 @@ public void Export(string fileName, CodeGraph graph) builder.WriteOutput(fileName); } - private static string GetEdgeLabel(Dependency dependency) + private static string GetEdgeLabel(Relationship relationship) { - // Omit the label text for now. The color makes it clear that it is a call dependency - if (dependency.Type == DependencyType.Calls || dependency.Type == DependencyType.Invokes) + // Omit the label text for now. The color makes it clear that it is a call relationship + if (relationship.Type == RelationshipType.Calls || relationship.Type == RelationshipType.Invokes) { return string.Empty; } // We can see this by the dotted line - if (dependency.Type == DependencyType.Implements || dependency.Type == DependencyType.Inherits) + if (relationship.Type == RelationshipType.Implements || relationship.Type == RelationshipType.Inherits) { return string.Empty; } - if (dependency.Type == DependencyType.Uses) + if (relationship.Type == RelationshipType.Uses) { return string.Empty; } - if (dependency.Type == DependencyType.UsesAttribute) + if (relationship.Type == RelationshipType.UsesAttribute) { return string.Empty; } - return dependency.Type.ToString(); + return relationship.Type.ToString(); } private static void WriteCategories(DgmlFileBuilder writer) diff --git a/CodeParser/Export/DgmlHierarchyExport.cs b/CodeParser/Export/DgmlHierarchyExport.cs index b0509f3..4d5cf6e 100644 --- a/CodeParser/Export/DgmlHierarchyExport.cs +++ b/CodeParser/Export/DgmlHierarchyExport.cs @@ -4,7 +4,8 @@ namespace CodeParser.Export; /// -/// Exports a CodeGraph's hierarchy to a DGML file. +/// Debug class to export the hierarchy of a code graph to a dgml file. +/// See for hierarchy and relationships. /// public class DgmlHierarchyExport { diff --git a/CodeParser/Export/DgmlDependencyExport.cs b/CodeParser/Export/DgmlRelationshipExport.cs similarity index 77% rename from CodeParser/Export/DgmlDependencyExport.cs rename to CodeParser/Export/DgmlRelationshipExport.cs index 3c0298d..c247c4c 100644 --- a/CodeParser/Export/DgmlDependencyExport.cs +++ b/CodeParser/Export/DgmlRelationshipExport.cs @@ -3,7 +3,11 @@ namespace CodeParser.Export; -public class DgmlDependencyExport +/// +/// Debug class to export the relationship information of a code graph to a dgml file. +/// See for hierarchy and relationships. +/// +public class DgmlRelationshipExport { public static void Export(string fileName, CodeGraph codeGraph) { @@ -33,7 +37,7 @@ private static void WriteEdges(DgmlFileBuilder writer, IEnumerable { foreach (var node in nodes) { - foreach (var child in node.Dependencies) + foreach (var child in node.Relationships) { writer.AddEdgeById(node.Id, child.TargetId, child.Type.ToString()); } @@ -45,12 +49,12 @@ private static void WriteNodes(DgmlFileBuilder writer, IEnumerable { // Find all nodes we need for the graph. var allNodes = new HashSet(); - foreach (var node in nodes.Where(n => n.Dependencies.Count != 0)) + foreach (var node in nodes.Where(n => n.Relationships.Count != 0)) { allNodes.Add(node); - foreach (var dependency in node.Dependencies) + foreach (var relationship in node.Relationships) { - var targetElement = codeGraph.Nodes[dependency.TargetId]; + var targetElement = codeGraph.Nodes[relationship.TargetId]; allNodes.Add(targetElement); } } diff --git a/CodeParser/Extensions/CodeGraphExtensions.cs b/CodeParser/Extensions/CodeGraphExtensions.cs index 3d6590f..d654009 100644 --- a/CodeParser/Extensions/CodeGraphExtensions.cs +++ b/CodeParser/Extensions/CodeGraphExtensions.cs @@ -14,10 +14,10 @@ public static CodeGraph Clone(this CodeGraph originalCodeGraph) /// /// Clones the given code graph. - /// Dependencies and code element can be filtered to generate sub graphs. + /// Relationships and code element can be filtered to generate sub graphs. /// If no code element list is given (null) all code elements are returned. /// - public static CodeGraph Clone(this CodeGraph originalCodeGraph, Func? dependencyFilter, + public static CodeGraph Clone(this CodeGraph originalCodeGraph, Func? relationshipFilter, HashSet? codeElementIds) { List includedOriginalElements; @@ -42,7 +42,7 @@ public static CodeGraph Clone(this CodeGraph originalCodeGraph, Func codeElementIds) { - // Include only dependencies to code elements in the subgraph + // Include only relationships to code elements in the subgraph return graph.Clone(d => codeElementIds.Contains(d.TargetId), codeElementIds); } diff --git a/CodeParser/Parser/Parser.Phase1.cs b/CodeParser/Parser/Parser.Phase1.cs index cbc1444..20b5c2d 100644 --- a/CodeParser/Parser/Parser.Phase1.cs +++ b/CodeParser/Parser/Parser.Phase1.cs @@ -15,13 +15,9 @@ public partial class Parser private async Task BuildHierarchy(Solution solution) { - foreach (var project in solution.Projects) + var projects = await GetValidProjects(solution); + foreach (var project in projects) { - if (_config.IsProjectIncluded(project.Name) is false) - { - continue; - } - var compilation = await project.GetCompilationAsync(); if (compilation == null) { @@ -30,7 +26,7 @@ private async Task BuildHierarchy(Solution solution) } // Build also a list of all named types in the solution - // We need this in phase 2 to resolve dependencies + // We need this in phase 2 to resolve relationships // Constructed types are not contained in this list! var types = compilation.GetSymbolsWithName(_ => true, SymbolFilter.Type).OfType(); _allNamedTypesInSolution.AddRange(types); @@ -40,6 +36,46 @@ private async Task BuildHierarchy(Solution solution) } } + /// + /// Remove all projects that do not pass our include filter or cannot be parsed. + /// + private async Task> GetValidProjects(Solution solution) + { + // At the moment I cannot handle more than one project with the same assembly name + // So I remove them. + + var assemblyNameToProject = new Dictionary(); + var duplicates = new HashSet(); + foreach (var project in solution.Projects) + { + // Regular expression patterns. + if (_config.IsProjectIncluded(project.Name) is false) + { + continue; + } + + var compilation = await project.GetCompilationAsync(); + if (compilation != null) + { + var assemblyName = compilation.Assembly.Name; + if (assemblyNameToProject.ContainsKey(assemblyName)) + { + duplicates.Add(assemblyName); + } + + assemblyNameToProject[assemblyName] = project; + } + } + + foreach (var name in duplicates) + { + assemblyNameToProject.Remove(name); + Trace.WriteLine($"Removed assembly with duplicate name in solution: {name}"); + } + + return assemblyNameToProject.Values.ToList(); + } + private void BuildHierarchy(Compilation compilation) { // Assembly has no source location. diff --git a/CodeParser/Parser/Parser.Phase2.Properties.cs b/CodeParser/Parser/Parser.Phase2.Properties.cs index e67c758..14f22b8 100644 --- a/CodeParser/Parser/Parser.Phase2.Properties.cs +++ b/CodeParser/Parser/Parser.Phase2.Properties.cs @@ -11,18 +11,19 @@ public partial class Parser /// We treat the property like a method and do not distinguish between getter and setter. /// A property can have a getter, setter or an expression body. /// - private void AnalyzePropertyDependencies(Solution solution, CodeElement propertyElement, + private void AnalyzePropertyRelationships(Solution solution, CodeElement propertyElement, IPropertySymbol propertySymbol) { // Analyze the property type - AddTypeDependency(propertyElement, propertySymbol.Type, DependencyType.Uses); + AddTypeRelationship(propertyElement, propertySymbol.Type, RelationshipType.Uses); // Check for interface implementation var implementedInterfaceProperty = GetImplementedInterfaceProperty(propertySymbol); if (implementedInterfaceProperty != null) { var locations = GetLocations(propertySymbol); - AddPropertyDependency(propertyElement, implementedInterfaceProperty, DependencyType.Implements, locations); + AddPropertyRelationship(propertyElement, implementedInterfaceProperty, RelationshipType.Implements, + locations); } // Check for property override @@ -32,7 +33,7 @@ private void AnalyzePropertyDependencies(Solution solution, CodeElement property if (overriddenProperty != null) { var locations = GetLocations(propertySymbol); - AddPropertyDependency(propertyElement, overriddenProperty, DependencyType.Overrides, locations); + AddPropertyRelationship(propertyElement, overriddenProperty, RelationshipType.Overrides, locations); } } @@ -75,10 +76,10 @@ private void AnalyzePropertyBody(Solution solution, CodeElement propertyElement, } } - private void AddPropertyDependency(CodeElement sourceElement, IPropertySymbol propertySymbol, - DependencyType dependencyType, List locations) + private void AddPropertyRelationship(CodeElement sourceElement, IPropertySymbol propertySymbol, + RelationshipType relationshipType, List locations) { - AddDependencyWithFallbackToContainingType(sourceElement, propertySymbol, dependencyType, locations); + AddRelationshipWithFallbackToContainingType(sourceElement, propertySymbol, relationshipType, locations); } private IPropertySymbol? GetImplementedInterfaceProperty(IPropertySymbol propertySymbol) diff --git a/CodeParser/Parser/Parser.Phase2.cs b/CodeParser/Parser/Parser.Phase2.cs index 35b03ff..9f00c58 100644 --- a/CodeParser/Parser/Parser.Phase2.cs +++ b/CodeParser/Parser/Parser.Phase2.cs @@ -9,46 +9,49 @@ namespace CodeParser.Parser; public partial class Parser { /// - /// Entry for dependency analysis + /// Entry for relationship analysis /// - private void AnalyzeDependencies(Solution solution) + private void AnalyzeRelationships(Solution solution) { var numberOfCodeElements = _codeGraph.Nodes.Count; var loop = 0; foreach (var element in _codeGraph.Nodes.Values) { - // TODO atr Analyze if we can have more than one symbol! - var symbol = _elementIdToSymbolMap[element.Id]; + if (!_elementIdToSymbolMap.TryGetValue(element.Id, out var symbol)) + { + // INamespaceSymbol + continue; + } if (symbol is IEventSymbol eventSymbol) { - AnalyzeEventDependencies(solution, element, eventSymbol); + AnalyzeEventRelationships(solution, element, eventSymbol); } else if (symbol is INamedTypeSymbol { TypeKind: TypeKind.Delegate } delegateSymbol) { - // Handle before the type dependencies. - AnalyzeDelegateDependencies(element, delegateSymbol); + // Handle before the type relationships. + AnalyzeDelegateRelationships(element, delegateSymbol); } else if (symbol is INamedTypeSymbol typeSymbol) { - AnalyzeInheritanceDependencies(element, typeSymbol); + AnalyzeInheritanceRelationships(element, typeSymbol); } else if (symbol is IMethodSymbol methodSymbol) { - AnalyzeMethodDependencies(solution, element, methodSymbol); + AnalyzeMethodRelationships(solution, element, methodSymbol); } else if (symbol is IPropertySymbol propertySymbol) { - AnalyzePropertyDependencies(solution, element, propertySymbol); + AnalyzePropertyRelationships(solution, element, propertySymbol); } else if (symbol is IFieldSymbol fieldSymbol) { - AnalyzeFieldDependencies(element, fieldSymbol); + AnalyzeFieldRelationships(element, fieldSymbol); } // For all type of symbols check if decorated with an attribute. - AnalyzeAttributeDependencies(element, symbol); + AnalyzeAttributeRelationships(element, symbol); SendParserPhase2Progress(loop++, numberOfCodeElements); } @@ -62,7 +65,7 @@ private void SendParserPhase2Progress(int loop, int numberOfCodeElements) if (loop % 10 == 0) { var percent = Math.Floor(loop / (double)numberOfCodeElements * 100); - var msg = $"Phase 2/2: Analyzing dependencies. Finished {percent}%."; + var msg = $"Phase 2/2: Analyzing relationships. Finished {percent}%."; var args = new ParserProgressArg(msg); ParserProgress?.Invoke(this, args); @@ -115,7 +118,7 @@ private void AnalyzeGlobalStatementsForAssembly(Solution solution) } } - private void AnalyzeAttributeDependencies(CodeElement element, ISymbol symbol) + private void AnalyzeAttributeRelationships(CodeElement element, ISymbol symbol) { foreach (var attributeData in symbol.GetAttributes()) { @@ -126,12 +129,12 @@ private void AnalyzeAttributeDependencies(CodeElement element, ISymbol symbol) : null; element.Attributes.Add(attributeData.AttributeClass.Name); - AddTypeDependency(element, attributeData.AttributeClass, DependencyType.UsesAttribute, location); + AddTypeRelationship(element, attributeData.AttributeClass, RelationshipType.UsesAttribute, location); } } } - private void AnalyzeDelegateDependencies(CodeElement delegateElement, INamedTypeSymbol delegateSymbol) + private void AnalyzeDelegateRelationships(CodeElement delegateElement, INamedTypeSymbol delegateSymbol) { var methodSymbol = delegateSymbol.DelegateInvokeMethod; if (methodSymbol is null) @@ -141,37 +144,37 @@ private void AnalyzeDelegateDependencies(CodeElement delegateElement, INamedType } // Analyze return type - AddTypeDependency(delegateElement, methodSymbol.ReturnType, DependencyType.Uses); + AddTypeRelationship(delegateElement, methodSymbol.ReturnType, RelationshipType.Uses); // Analyze parameter types foreach (var parameter in methodSymbol.Parameters) { - AddTypeDependency(delegateElement, parameter.Type, DependencyType.Uses); + AddTypeRelationship(delegateElement, parameter.Type, RelationshipType.Uses); } } - private void AnalyzeEventDependencies(Solution solution, CodeElement eventElement, IEventSymbol eventSymbol) + private void AnalyzeEventRelationships(Solution solution, CodeElement eventElement, IEventSymbol eventSymbol) { // Analyze event type (usually a delegate type) - AddTypeDependency(eventElement, eventSymbol.Type, DependencyType.Uses); + AddTypeRelationship(eventElement, eventSymbol.Type, RelationshipType.Uses); // Check if this event implements an interface event var implementedInterfaceEvent = GetImplementedInterfaceEvent(eventSymbol); if (implementedInterfaceEvent != null) { var locations = GetLocations(eventSymbol); - AddEventDependency(eventElement, implementedInterfaceEvent, DependencyType.Implements, locations); + AddEventRelationship(eventElement, implementedInterfaceEvent, RelationshipType.Implements, locations); } // If the event has add/remove accessors, analyze them if (eventSymbol.AddMethod != null) { - AnalyzeMethodDependencies(solution, eventElement, eventSymbol.AddMethod); + AnalyzeMethodRelationships(solution, eventElement, eventSymbol.AddMethod); } if (eventSymbol.RemoveMethod != null) { - AnalyzeMethodDependencies(solution, eventElement, eventSymbol.RemoveMethod); + AnalyzeMethodRelationships(solution, eventElement, eventSymbol.RemoveMethod); } } @@ -194,35 +197,35 @@ private void AnalyzeEventDependencies(Solution solution, CodeElement eventElemen return null; } - private void AddEventDependency(CodeElement sourceElement, IEventSymbol eventSymbol, - DependencyType dependencyType, List locations) + private void AddEventRelationship(CodeElement sourceElement, IEventSymbol eventSymbol, + RelationshipType relationshipType, List locations) { - AddDependencyWithFallbackToContainingType(sourceElement, eventSymbol, dependencyType, locations); + AddRelationshipWithFallbackToContainingType(sourceElement, eventSymbol, relationshipType, locations); } /// /// Use solution, not the compilation. The syntax tree may not be found. /// - private void AnalyzeMethodDependencies(Solution solution, CodeElement methodElement, IMethodSymbol methodSymbol) + private void AnalyzeMethodRelationships(Solution solution, CodeElement methodElement, IMethodSymbol methodSymbol) { // Analyze parameter types foreach (var parameter in methodSymbol.Parameters) { - AddTypeDependency(methodElement, parameter.Type, DependencyType.Uses); + AddTypeRelationship(methodElement, parameter.Type, RelationshipType.Uses); } // Analyze return type if (!methodSymbol.ReturnsVoid) { - AddTypeDependency(methodElement, methodSymbol.ReturnType, DependencyType.Uses); + AddTypeRelationship(methodElement, methodSymbol.ReturnType, RelationshipType.Uses); } //if (methodSymbol.IsExtensionMethod) //{ // // The first parameter of an extension method is the extended type // var extendedType = methodSymbol.Parameters[0].Type; - // AddTypeDependency(methodElement, extendedType, DependencyType.Uses); + // AddTypeRelationship(methodElement, extendedType, RelationshipType.Uses); //} // If this method is an interface method or an abstract method, find its implementations @@ -238,7 +241,7 @@ private void AnalyzeMethodDependencies(Solution solution, CodeElement methodElem if (overriddenMethod != null) { var locations = GetLocations(methodSymbol); - AddMethodOverrideDependency(methodElement, overriddenMethod, locations); + AddMethodOverrideRelationship(methodElement, overriddenMethod, locations); } } @@ -284,7 +287,7 @@ private void FindImplementations(CodeElement methodElement, IMethodSymbol method { // Note: Implementations for external methods are not in our map var locations = GetLocations(implementingMethod); - AddDependency(implementingElement, DependencyType.Implements, methodElement, locations); + AddRelationship(implementingElement, RelationshipType.Implements, methodElement, locations); } } } @@ -321,18 +324,18 @@ private bool IsTypeDerivedFrom(INamedTypeSymbol type, INamedTypeSymbol baseType) /// /// Overrides /// - private void AddMethodOverrideDependency(CodeElement sourceElement, IMethodSymbol methodSymbol, + private void AddMethodOverrideRelationship(CodeElement sourceElement, IMethodSymbol methodSymbol, List locations) { - // If we don't have the method itself in our map, add a dependency to its containing type + // If we don't have the method itself in our map, add a relationship to its containing type // Maybe we override a framework method. Happens also if the base method is a generic one. // In this case the GetSymbolKey is different. One uses T, the overriding method uses the actual type. - AddDependencyWithFallbackToContainingType(sourceElement, methodSymbol, DependencyType.Overrides, locations); + AddRelationshipWithFallbackToContainingType(sourceElement, methodSymbol, RelationshipType.Overrides, locations); } - private void AnalyzeFieldDependencies(CodeElement fieldElement, IFieldSymbol fieldSymbol) + private void AnalyzeFieldRelationships(CodeElement fieldElement, IFieldSymbol fieldSymbol) { - AddTypeDependency(fieldElement, fieldSymbol.Type, DependencyType.Uses); + AddTypeRelationship(fieldElement, fieldSymbol.Type, RelationshipType.Uses); } /// @@ -349,7 +352,7 @@ private void AnalyzeMethodBody(CodeElement sourceElement, SyntaxNode node, Seman if (typeInfo.Type != null) { var location = GetLocation(objectCreationSyntax); - AddTypeDependency(sourceElement, typeInfo.Type, DependencyType.Creates, location); + AddTypeRelationship(sourceElement, typeInfo.Type, RelationshipType.Creates, location); } break; @@ -381,14 +384,14 @@ private void AnalyzeInvocation(CodeElement sourceElement, InvocationExpressionSy if (symbolInfo.Symbol is IMethodSymbol calledMethod) { var location = GetLocation(invocationSyntax); - AddCallsDependency(sourceElement, calledMethod, location); + AddCallsRelationship(sourceElement, calledMethod, location); // Handle generic method invocations if (calledMethod.IsGenericMethod) { foreach (var typeArg in calledMethod.TypeArguments) { - AddTypeDependency(sourceElement, typeArg, DependencyType.Uses, location); + AddTypeRelationship(sourceElement, typeArg, RelationshipType.Uses, location); } } @@ -419,7 +422,7 @@ private void AnalyzeInvocation(CodeElement sourceElement, InvocationExpressionSy if (eventSymbol != null) { - AddEventInvocationDependency(sourceElement, eventSymbol, location); + AddEventInvocationRelationship(sourceElement, eventSymbol, location); } } } @@ -429,19 +432,19 @@ private void AnalyzeInvocation(CodeElement sourceElement, InvocationExpressionSy //if (invokedSymbol is IMethodSymbol { AssociatedSymbol: IEventSymbol symbol }) if (invokedSymbol is IEventSymbol symbol) { - AddEventInvocationDependency(sourceElement, symbol, GetLocation(invocationSyntax)); + AddEventInvocationRelationship(sourceElement, symbol, GetLocation(invocationSyntax)); } } - private void AddEventInvocationDependency(CodeElement sourceElement, IEventSymbol eventSymbol, + private void AddEventInvocationRelationship(CodeElement sourceElement, IEventSymbol eventSymbol, SourceLocation location) { - AddDependencyWithFallbackToContainingType(sourceElement, eventSymbol, DependencyType.Invokes, [location]); + AddRelationshipWithFallbackToContainingType(sourceElement, eventSymbol, RelationshipType.Invokes, [location]); } - private void AddEventUsageDependency(CodeElement sourceElement, IEventSymbol eventSymbol, SourceLocation location) + private void AddEventUsageRelationship(CodeElement sourceElement, IEventSymbol eventSymbol, SourceLocation location) { - AddDependencyWithFallbackToContainingType(sourceElement, eventSymbol, DependencyType.Uses, [location]); + AddRelationshipWithFallbackToContainingType(sourceElement, eventSymbol, RelationshipType.Uses, [location]); } private void AnalyzeAssignment(CodeElement sourceElement, AssignmentExpressionSyntax assignmentExpression, @@ -462,18 +465,18 @@ private void AnalyzeAssignment(CodeElement sourceElement, AssignmentExpressionSy if (leftSymbol is IEventSymbol eventSymbol) { - AddEventUsageDependency(sourceElement, eventSymbol, GetLocation(assignmentExpression)); + AddEventUsageRelationship(sourceElement, eventSymbol, GetLocation(assignmentExpression)); - // If the right side is a method, add a Handles dependency + // If the right side is a method, add a Handles relationship if (rightSymbol is IMethodSymbol methodSymbol) { - AddEventHandlerDependency(methodSymbol, eventSymbol, GetLocation(assignmentExpression)); + AddEventHandlerRelationship(methodSymbol, eventSymbol, GetLocation(assignmentExpression)); } } } } - private void AddEventHandlerDependency(IMethodSymbol handlerMethod, IEventSymbol eventSymbol, + private void AddEventHandlerRelationship(IMethodSymbol handlerMethod, IEventSymbol eventSymbol, SourceLocation location) { var handlerElement = FindCodeElement(handlerMethod); @@ -481,15 +484,10 @@ private void AddEventHandlerDependency(IMethodSymbol handlerMethod, IEventSymbol if (handlerElement != null && eventElement != null) { - AddDependency(handlerElement, DependencyType.Handles, eventElement, [location]); - } - else - { - // If either the event or the handler method is not in our codebase, - // we might want to log this or handle it in some way - Debug.WriteLine( - $"Unable to add Handles dependency: Handler {handlerMethod.Name} or Event {eventSymbol.Name} not found in codebase."); + AddRelationship(handlerElement, RelationshipType.Handles, eventElement, [location]); } + //Trace.WriteLine( + // $"Unable to add 'Handles' relationship: Handler {handlerMethod.Name} or Event {eventSymbol.Name} not found in codebase."); } private void AnalyzeExpressionForPropertyAccess(CodeElement sourceElement, ExpressionSyntax expression, @@ -507,16 +505,10 @@ private void AnalyzeExpressionForPropertyAccess(CodeElement sourceElement, Expre } } - private void AddEventUsageDependency(CodeElement sourceElement, IEventSymbol eventSymbol) - { - // If we don't have the event itself in our map, add a dependency to its containing type - AddDependencyWithFallbackToContainingType(sourceElement, eventSymbol, DependencyType.Uses, []); - } - - private void AddCallsDependency(CodeElement sourceElement, IMethodSymbol methodSymbol, SourceLocation location) + private void AddCallsRelationship(CodeElement sourceElement, IMethodSymbol methodSymbol, SourceLocation location) { //Debug.Assert(FindCodeElement(methodSymbol)!= null); - //Trace.WriteLine($"Adding call dependency: {sourceElement.Name} -> {methodSymbol.Name}"); + //Trace.WriteLine($"Adding call relationship: {sourceElement.Name} -> {methodSymbol.Name}"); if (methodSymbol.IsExtensionMethod) { @@ -529,51 +521,52 @@ private void AddCallsDependency(CodeElement sourceElement, IMethodSymbol methodS methodSymbol = methodSymbol.OriginalDefinition; } - // If the method is not in our map, we might want to add a dependency to its containing type - AddDependencyWithFallbackToContainingType(sourceElement, methodSymbol, DependencyType.Calls, [location]); + // If the method is not in our map, we might want to add a relationship to its containing type + AddRelationshipWithFallbackToContainingType(sourceElement, methodSymbol, RelationshipType.Calls, [location]); } /// /// Handle also List_T. Where List is not a code element of our project /// - private void AnalyzeInheritanceDependencies(CodeElement element, INamedTypeSymbol typeSymbol) + private void AnalyzeInheritanceRelationships(CodeElement element, INamedTypeSymbol typeSymbol) { // Analyze base class if (typeSymbol.BaseType != null && typeSymbol.BaseType.SpecialType != SpecialType.System_Object) { - AddTypeDependency(element, typeSymbol.BaseType, DependencyType.Inherits); + AddTypeRelationship(element, typeSymbol.BaseType, RelationshipType.Inherits); } // Analyze implemented interfaces foreach (var @interface in typeSymbol.Interfaces) { - AddTypeDependency(element, @interface, DependencyType.Implements); + AddTypeRelationship(element, @interface, RelationshipType.Implements); } } - private void AddTypeDependency(CodeElement sourceElement, ITypeSymbol typeSymbol, DependencyType dependencyType, + private void AddTypeRelationship(CodeElement sourceElement, ITypeSymbol typeSymbol, + RelationshipType relationshipType, SourceLocation? location = null) { switch (typeSymbol) { case IArrayTypeSymbol arrayType: - // For arrays, we add an "Uses" dependency to the element type. Even if we create them. - AddTypeDependency(sourceElement, arrayType.ElementType, DependencyType.Uses, location); + // For arrays, we add an "Uses" relationship to the element type. Even if we create them. + AddTypeRelationship(sourceElement, arrayType.ElementType, RelationshipType.Uses, location); break; case INamedTypeSymbol namedTypeSymbol: - AddNamedTypeDependency(sourceElement, namedTypeSymbol, dependencyType, location); + AddNamedTypeRelationship(sourceElement, namedTypeSymbol, relationshipType, location); break; case IPointerTypeSymbol pointerTypeSymbol: - AddTypeDependency(sourceElement, pointerTypeSymbol.PointedAtType, DependencyType.Uses, location); + AddTypeRelationship(sourceElement, pointerTypeSymbol.PointedAtType, RelationshipType.Uses, location); break; case IFunctionPointerTypeSymbol functionPointerType: // The function pointer has a return type and parameters. - // we could add these dependencies here. + // we could add these relationships here. break; case IDynamicTypeSymbol: @@ -585,22 +578,22 @@ private void AddTypeDependency(CodeElement sourceElement, ITypeSymbol typeSymbol var symbolKey = typeSymbol.Key(); if (_symbolKeyToElementMap.TryGetValue(symbolKey, out var targetElement)) { - AddDependency(sourceElement, dependencyType, targetElement, location != null ? [location] : []); + AddRelationship(sourceElement, relationshipType, targetElement, location != null ? [location] : []); } break; } } - private void AddNamedTypeDependency(CodeElement sourceElement, INamedTypeSymbol namedTypeSymbol, - DependencyType dependencyType, + private void AddNamedTypeRelationship(CodeElement sourceElement, INamedTypeSymbol namedTypeSymbol, + RelationshipType relationshipType, SourceLocation? location) { var targetElement = FindCodeElement(namedTypeSymbol); if (targetElement != null) { // The type is internal (part of our codebase) - AddDependency(sourceElement, dependencyType, targetElement, location != null ? [location] : []); + AddRelationship(sourceElement, relationshipType, targetElement, location != null ? [location] : []); } else { @@ -612,19 +605,20 @@ private void AddNamedTypeDependency(CodeElement sourceElement, INamedTypeSymbol if (_symbolKeyToElementMap.TryGetValue(originalSymbolKey, out var originalTargetElement)) { - // We found the original definition, add dependency to it - AddDependency(sourceElement, dependencyType, originalTargetElement, location != null ? [location] : []); + // We found the original definition, add relationship to it + AddRelationship(sourceElement, relationshipType, originalTargetElement, + location != null ? [location] : []); } // The type is truly external, you might want to log this or handle it differently - // AddExternalDependency(sourceElement, namedTypeSymbol, dependencyType, location); + // AddExternalRelationship(sourceElement, namedTypeSymbol, relationshipType, location); } if (namedTypeSymbol.IsGenericType) { - // Add "Uses" dependencies to type arguments + // Add "Uses" relationships to type arguments foreach (var typeArg in namedTypeSymbol.TypeArguments) { - AddTypeDependency(sourceElement, typeArg, DependencyType.Uses, location); + AddTypeRelationship(sourceElement, typeArg, RelationshipType.Uses, location); } } } @@ -639,11 +633,11 @@ private void AnalyzeIdentifier(CodeElement sourceElement, IdentifierNameSyntax i if (symbol is IPropertySymbol propertySymbol) { var location = GetLocation(identifierSyntax); - AddPropertyCallDependency(sourceElement, propertySymbol, [location]); + AddPropertyCallRelationship(sourceElement, propertySymbol, [location]); } else if (symbol is IFieldSymbol fieldSymbol) { - AddDependencyWithFallbackToContainingType(sourceElement, fieldSymbol, DependencyType.Uses); + AddRelationshipWithFallbackToContainingType(sourceElement, fieldSymbol, RelationshipType.Uses); } } @@ -657,16 +651,16 @@ private void AnalyzeMemberAccess(CodeElement sourceElement, MemberAccessExpressi if (symbol is IPropertySymbol propertySymbol) { var location = GetLocation(memberAccessSyntax); - AddPropertyCallDependency(sourceElement, propertySymbol, [location]); + AddPropertyCallRelationship(sourceElement, propertySymbol, [location]); } else if (symbol is IFieldSymbol fieldSymbol) { - AddDependencyWithFallbackToContainingType(sourceElement, fieldSymbol, DependencyType.Uses); + AddRelationshipWithFallbackToContainingType(sourceElement, fieldSymbol, RelationshipType.Uses); } else if (symbol is IEventSymbol eventSymbol) { // This handles cases where the event is accessed but not necessarily invoked - AddEventUsageDependency(sourceElement, eventSymbol, GetLocation(memberAccessSyntax)); + AddEventUsageRelationship(sourceElement, eventSymbol, GetLocation(memberAccessSyntax)); } // Recursively analyze the expression in case of nested property access @@ -680,16 +674,16 @@ private void AnalyzeMemberAccess(CodeElement sourceElement, MemberAccessExpressi /// /// Calling a property is treated like calling a method. /// - private void AddPropertyCallDependency(CodeElement sourceElement, IPropertySymbol propertySymbol, + private void AddPropertyCallRelationship(CodeElement sourceElement, IPropertySymbol propertySymbol, List locations) { - AddDependencyWithFallbackToContainingType(sourceElement, propertySymbol, DependencyType.Calls, locations); + AddRelationshipWithFallbackToContainingType(sourceElement, propertySymbol, RelationshipType.Calls, locations); } - private void AddDependencyWithFallbackToContainingType(CodeElement sourceElement, ISymbol symbol, - DependencyType dependencyType, List? locations = null) + private void AddRelationshipWithFallbackToContainingType(CodeElement sourceElement, ISymbol symbol, + RelationshipType relationshipType, List? locations = null) { - // If we don't have the property itself in our map, add a dependency to its containing type + // If we don't have the property itself in our map, add a relationship to its containing type if (locations == null) { locations = []; @@ -698,14 +692,14 @@ private void AddDependencyWithFallbackToContainingType(CodeElement sourceElement var targetElement = FindCodeElement(symbol); if (targetElement != null) { - AddDependency(sourceElement, dependencyType, targetElement, locations); + AddRelationship(sourceElement, relationshipType, targetElement, locations); return; } var containingTypeElement = FindCodeElement(symbol.ContainingType); if (containingTypeElement != null) { - AddDependency(sourceElement, dependencyType, containingTypeElement, locations); + AddRelationship(sourceElement, relationshipType, containingTypeElement, locations); } } diff --git a/CodeParser/Parser/Parser.cs b/CodeParser/Parser/Parser.cs index be0a29c..7cb475c 100644 --- a/CodeParser/Parser/Parser.cs +++ b/CodeParser/Parser/Parser.cs @@ -36,18 +36,25 @@ public async Task ParseSolution(string solutionPath) // First Pass: Build Hierarchy await BuildHierarchy(solution); - // Second Pass: Build Dependencies + // Second Pass: Build Relationships // We don't need to iterate over the projects - AnalyzeDependencies(solution); + AnalyzeRelationships(solution); Clear(); // Makes the cycle detection easier because I never get to the assembly as shared ancestor - // for a nested dependency. + // for a nested relationships. InsertGlobalNamespaceIfUsed(); + + // Debug.Assert(_codeGraph.Nodes.Values.All(c => IsDistinct(c.SourceLocations))); return _codeGraph; } + private bool IsDistinct(List collection) + { + return collection.Count == collection.Distinct().Count(); + } + private void Clear() { _symbolKeyToElementMap.Clear(); @@ -157,7 +164,9 @@ private CodeElement GetOrCreateCodeElementWithNamespaceHierarchy(ISymbol symbol, return GetOrCreateCodeElement(symbol, elementType, initialParent, location); } - + /// + /// Note: We store the symbol used to build the hierarchy. + /// private CodeElement GetOrCreateCodeElement(ISymbol symbol, CodeElementType elementType, CodeElement? parent, SourceLocation? location) { @@ -167,6 +176,7 @@ private CodeElement GetOrCreateCodeElement(ISymbol symbol, CodeElementType eleme if (_symbolKeyToElementMap.TryGetValue(symbolKey, out var existingElement)) { UpdateCodeElementLocations(existingElement, location); + WarnIfCodeElementHasMultipleSymbols(symbol, existingElement); return existingElement; } @@ -182,14 +192,31 @@ private CodeElement GetOrCreateCodeElement(ISymbol symbol, CodeElementType eleme _codeGraph.Nodes[element.Id] = element; _symbolKeyToElementMap[symbolKey] = element; - // We need the symbol in phase2 when analyzing the dependencies. - _elementIdToSymbolMap[element.Id] = symbol; + // We need the symbol in phase2 when analyzing the relationships. + if (symbol is not INamespaceSymbol) + { + _elementIdToSymbolMap[element.Id] = symbol; + } SendParserPhase1Progress(_codeGraph.Nodes.Count); return element; } + private void WarnIfCodeElementHasMultipleSymbols(ISymbol symbol, CodeElement existingElement) + { + if (symbol is not INamespaceSymbol) + { + // Get warning if we have different symbols for the same element. + if (_elementIdToSymbolMap[existingElement.Id].Equals(symbol, SymbolEqualityComparer.Default) is false) + { + // Happens if two projects in the solution have the same name. + // You lose one of them. + Trace.WriteLine("(!) Found element with multiple symbols: " + symbol.ToDisplayString()); + } + } + } + private static void UpdateCodeElementLocations(CodeElement element, SourceLocation? location) { if (element.ElementType == CodeElementType.Namespace) @@ -244,25 +271,25 @@ private static List GetLocations(ISymbol symbol) } - private static void AddDependency(CodeElement source, DependencyType type, + private static void AddRelationship(CodeElement source, RelationshipType type, CodeElement target, List sourceLocations) { - var existingDependency = source.Dependencies.FirstOrDefault(d => + var existingRelationship = source.Relationships.FirstOrDefault(d => d.TargetId == target.Id && d.Type == type); - if (existingDependency != null) + if (existingRelationship != null) { - // Note we may read some dependencies more than once through different ways but that's fine. + // Note we may read some relationships more than once through different ways but that's fine. // For example identifier and member access of field. - var newLocations = sourceLocations.Except(existingDependency.SourceLocations); - existingDependency.SourceLocations.AddRange(newLocations); + var newLocations = sourceLocations.Except(existingRelationship.SourceLocations); + existingRelationship.SourceLocations.AddRange(newLocations); } else { - var newDependency = new Dependency(source.Id, target.Id, type); - newDependency.SourceLocations.AddRange(sourceLocations); - source.Dependencies.Add(newDependency); + var newRelationship = new Relationship(source.Id, target.Id, type); + newRelationship.SourceLocations.AddRange(sourceLocations); + source.Relationships.Add(newRelationship); } } } \ No newline at end of file diff --git a/CodeParser/Parser/SymbolExtensions.cs b/CodeParser/Parser/SymbolExtensions.cs index d9fb360..867a97a 100644 --- a/CodeParser/Parser/SymbolExtensions.cs +++ b/CodeParser/Parser/SymbolExtensions.cs @@ -3,69 +3,90 @@ namespace CodeParser.Parser; +/// +/// Symbol identification across compilations. +/// One of the main problems is that the symbols do not have a unique identifier. +/// For example a IMethodSymbol defined in one compilation may not the same as a IMethodSymbol +/// used in an invocation in another compilation. +/// public static class SymbolExtensions { + public static string BuildSymbolName(this ISymbol symbol) + { + var parts = GetParentChain(symbol); + parts.Reverse(); + var fullName = string.Join(".", parts.Where(p => !string.IsNullOrEmpty(p.Name)).Select(p => p.Name)); + return fullName; + } + + /// + /// Returns a unique key for the symbol + /// We may have for example multiple symbols for the same namespace (X.Y.Z vs X.Y{Z}) + /// See INamespaceSymbol.ConstituentNamespaces + /// Method overloads and generics are considered in the key. + /// This key more or less replaces the SymbolEqualityComparer not useful for this application. + /// + public static string Key(this ISymbol symbol) + { + // A generic method may be in a generic type. So we have to consider the generic part of the parent, too + + var parts = GetParentChain(symbol); + return string.Join(".", parts.Select(GetKeyInternal)); + } + /// /// Sometimes when walking up the parent chain: /// After the global namespace the containing symbol is not reliable. /// If we do not end up at an assembly it is added manually. + /// 0 = symbol itself + /// n = assembly /// - public static string BuildSymbolName(this ISymbol symbol) + private static List GetParentChain(this ISymbol symbol) { - var parts = new List(); - - var currentSymbol = symbol; - ISymbol? lastKnownSymbol = null; + var parts = new List(); - while (currentSymbol != null) + while (symbol != null) { - if (currentSymbol is IModuleSymbol) + if (symbol is IModuleSymbol) { - // Skip the module symbol - currentSymbol = currentSymbol.ContainingSymbol; + symbol = symbol.ContainingSymbol; + continue; // Skip the module symbol } - lastKnownSymbol = currentSymbol; - - var name = currentSymbol.Name; - parts.Add(name); - currentSymbol = currentSymbol.ContainingSymbol; + parts.Add(symbol); + symbol = symbol.ContainingSymbol; } - if (lastKnownSymbol is not IAssemblySymbol) + // Check if the last symbol is a global namespace and add the assembly + if (parts.LastOrDefault() is INamespaceSymbol { IsGlobalNamespace: true } globalNamespace) { - // global namespace has the ContainingCompilation set. - Debug.Assert(lastKnownSymbol is INamespaceSymbol { IsGlobalNamespace: true }); - var namespaceSymbol = lastKnownSymbol as INamespaceSymbol; - var assemblySymbol = namespaceSymbol.ContainingCompilation.Assembly; - parts.Add(assemblySymbol.Name); + parts.Add(globalNamespace.ContainingCompilation.Assembly); } - parts.Reverse(); - var fullName = string.Join(".", parts.Where(p => !string.IsNullOrEmpty(p))); - return fullName; + return parts; } - /// - /// Returns a unique key for the symbol - /// We may have for example multiple symbols for the same namespace (X.Y.Z vs X.Y{Z}) - /// See INamespaceSymbol.ConstituentNamespaces - /// Method overloads and generics are considered in the key. - /// This key more or less replaces the SymbolEqualityComparer not useful for this application. - /// - public static string Key(this ISymbol symbol) + + private static string GetKeyInternal(ISymbol symbol) { - var fullName = BuildSymbolName(symbol); + var name = symbol.Name; var genericPart = GetGenericPart(symbol); var kind = symbol.Kind.ToString(); + if (symbol is IAssemblySymbol assemblySymbol) + { + // Yes, people exist who add two projects with the same name in one solution + // Ignore this for the moment + return $"{name}"; + } + if (symbol is IMethodSymbol methodSymbol) { var parameters = string.Join("_", methodSymbol.Parameters.Select(p => p.Type.ToDisplayString())); - return $"{fullName}{genericPart}_{parameters}_{kind}"; + return $"{name}{genericPart}_{parameters}_{kind}"; } - return $"{fullName}{genericPart}_{kind}"; + return $"{name}{genericPart}_{kind}"; } private static string GetGenericPart(ISymbol symbol) @@ -85,7 +106,7 @@ private static string GetGenericPart(ISymbol symbol) else { // This is a generic type definition (e.g., List) - // When processing the solution hierarchy or dependency to original definition symbol + // When processing the solution hierarchy or relationship to original definition symbol result = $"<{string.Join(",", namedTypeSymbol.TypeParameters.Select(t => t.Name))}>"; } } diff --git a/CodeParserTests/AnalysisTests.cs b/CodeParserTests/AnalysisTests.cs index b6cfc38..633167a 100644 --- a/CodeParserTests/AnalysisTests.cs +++ b/CodeParserTests/AnalysisTests.cs @@ -35,12 +35,12 @@ private CodeGraph CreateTestCodeStructure() var nodeD = new CodeElement("D", CodeElementType.Class, "ClassD", "", null); // Create dependencies to form a cycle: A -> B -> C -> A - nodeA.Dependencies.Add(new Dependency("A", "B", DependencyType.Calls)); - nodeB.Dependencies.Add(new Dependency("B", "C", DependencyType.Calls)); - nodeC.Dependencies.Add(new Dependency("C", "A", DependencyType.Calls)); + nodeA.Relationships.Add(new Relationship("A", "B", RelationshipType.Calls)); + nodeB.Relationships.Add(new Relationship("B", "C", RelationshipType.Calls)); + nodeC.Relationships.Add(new Relationship("C", "A", RelationshipType.Calls)); // Additional dependency: D -> A (to ensure D is not part of the SCC) - nodeD.Dependencies.Add(new Dependency("D", "A", DependencyType.Calls)); + nodeD.Relationships.Add(new Relationship("D", "A", RelationshipType.Calls)); // Add nodes to the code graph codeStructure.Nodes["A"] = nodeA; diff --git a/CodeParserTests/CodeGraphBuilderTests.cs b/CodeParserTests/CodeGraphBuilderTests.cs index a50a925..da8d4de 100644 --- a/CodeParserTests/CodeGraphBuilderTests.cs +++ b/CodeParserTests/CodeGraphBuilderTests.cs @@ -15,7 +15,7 @@ public void GenerateDetailedCodeGraph_SimpleClassDependency_PreservesDependency( var originalGraph = new CodeGraph(); var classA = new CodeElement("A", CodeElementType.Class, "ClassA", "", null); var classB = new CodeElement("B", CodeElementType.Class, "ClassB", "", null); - classA.Dependencies.Add(new Dependency("A", "B", DependencyType.Uses)); + classA.Relationships.Add(new Relationship("A", "B", RelationshipType.Uses)); originalGraph.Nodes["A"] = classA; originalGraph.Nodes["B"] = classB; @@ -25,9 +25,9 @@ public void GenerateDetailedCodeGraph_SimpleClassDependency_PreservesDependency( Assert.AreEqual(2, detailedGraph.Nodes.Count); Assert.IsTrue(detailedGraph.Nodes.ContainsKey("A")); Assert.IsTrue(detailedGraph.Nodes.ContainsKey("B")); - Assert.AreEqual(1, detailedGraph.Nodes["A"].Dependencies.Count); - Assert.AreEqual(0, detailedGraph.Nodes["B"].Dependencies.Count); - Assert.AreEqual("B", detailedGraph.Nodes["A"].Dependencies.First().TargetId); + Assert.AreEqual(1, detailedGraph.Nodes["A"].Relationships.Count); + Assert.AreEqual(0, detailedGraph.Nodes["B"].Relationships.Count); + Assert.AreEqual("B", detailedGraph.Nodes["A"].Relationships.First().TargetId); } [Test] @@ -38,7 +38,7 @@ public void GenerateDetailedCodeGraph_MethodDependency_PreservesMethodLevelDepen var methodA = new CodeElement("A.M", CodeElementType.Method, "MethodA", "", classA); var classB = new CodeElement("B", CodeElementType.Class, "ClassB", "", null); var methodB = new CodeElement("B.M", CodeElementType.Method, "MethodB", "", classB); - methodA.Dependencies.Add(new Dependency("A.M", "B.M", DependencyType.Calls)); + methodA.Relationships.Add(new Relationship("A.M", "B.M", RelationshipType.Calls)); classA.Children.Add(methodA); classB.Children.Add(methodB); originalGraph.Nodes["A"] = classA; @@ -54,8 +54,8 @@ public void GenerateDetailedCodeGraph_MethodDependency_PreservesMethodLevelDepen Assert.IsTrue(detailedGraph.Nodes.ContainsKey("A.M")); Assert.IsTrue(detailedGraph.Nodes.ContainsKey("B")); Assert.IsTrue(detailedGraph.Nodes.ContainsKey("B.M")); - Assert.AreEqual(1, detailedGraph.Nodes["A.M"].Dependencies.Count); - Assert.AreEqual("B.M", detailedGraph.Nodes["A.M"].Dependencies.First().TargetId); + Assert.AreEqual(1, detailedGraph.Nodes["A.M"].Relationships.Count); + Assert.AreEqual("B.M", detailedGraph.Nodes["A.M"].Relationships.First().TargetId); } [Test] @@ -67,8 +67,8 @@ public void GenerateDetailedCodeGraph_MultipleDependencies_PreservesAllDependenc var methodA2 = new CodeElement("A.M2", CodeElementType.Method, "MethodA2", "", classA); var classB = new CodeElement("B", CodeElementType.Class, "ClassB", "", null); var methodB = new CodeElement("B.M", CodeElementType.Method, "MethodB", "", classB); - methodA1.Dependencies.Add(new Dependency("A.M1", "B", DependencyType.Calls)); - methodA2.Dependencies.Add(new Dependency("A.M2", "B.M", DependencyType.Calls)); + methodA1.Relationships.Add(new Relationship("A.M1", "B", RelationshipType.Calls)); + methodA2.Relationships.Add(new Relationship("A.M2", "B.M", RelationshipType.Calls)); classA.Children.Add(methodA1); classA.Children.Add(methodA2); classB.Children.Add(methodB); @@ -82,9 +82,9 @@ public void GenerateDetailedCodeGraph_MultipleDependencies_PreservesAllDependenc var detailedGraph = CodeGraphBuilder.GenerateDetailedCodeGraph(searchGraph.Vertices, originalGraph); Assert.AreEqual(5, detailedGraph.Nodes.Count); - Assert.AreEqual(1, detailedGraph.Nodes["A.M1"].Dependencies.Count); - Assert.AreEqual("B", detailedGraph.Nodes["A.M1"].Dependencies.First().TargetId); - Assert.AreEqual(1, detailedGraph.Nodes["A.M2"].Dependencies.Count); - Assert.AreEqual("B.M", detailedGraph.Nodes["A.M2"].Dependencies.First().TargetId); + Assert.AreEqual(1, detailedGraph.Nodes["A.M1"].Relationships.Count); + Assert.AreEqual("B", detailedGraph.Nodes["A.M1"].Relationships.First().TargetId); + Assert.AreEqual(1, detailedGraph.Nodes["A.M2"].Relationships.Count); + Assert.AreEqual("B.M", detailedGraph.Nodes["A.M2"].Relationships.First().TargetId); } } \ No newline at end of file diff --git a/CodeParserTests/CodeParserApprovalTests.cs b/CodeParserTests/CodeParserApprovalTests.cs index a2bc8a6..8b40ba9 100644 --- a/CodeParserTests/CodeParserApprovalTests.cs +++ b/CodeParserTests/CodeParserApprovalTests.cs @@ -98,12 +98,16 @@ public void FindsAllFunctions() "ModuleLevel1.ModuleLevel1.Model.ModelA.AccessToPropertiesSetter", // Show event registration - "CSharpLanguage.CSharpLanguage.EventInvocation.DoSomething", - "CSharpLanguage.CSharpLanguage.EventInvocation.Raise1", + "CSharpLanguage.CSharpLanguage.EventInvocation.DoSomething", + "CSharpLanguage.CSharpLanguage.EventInvocation.Raise1", "CSharpLanguage.CSharpLanguage.EventInvocation.Raise2", "CSharpLanguage.CSharpLanguage.EventInvocation.Raise3", - "CSharpLanguage.CSharpLanguage.EventSink..ctor", - "CSharpLanguage.CSharpLanguage.EventSink.Handler" + "CSharpLanguage.CSharpLanguage.EventSink..ctor", + "CSharpLanguage.CSharpLanguage.EventSink.Handler", + + "CSharpLanguage.CSharpLanguage.Partial.Client.CreateInstance", + "CSharpLanguage.CSharpLanguage.Partial.PartialClass.MethodInPartialClassPart1", + "CSharpLanguage.CSharpLanguage.Partial.PartialClass.MethodInPartialClassPart2" }; @@ -113,7 +117,7 @@ public void FindsAllFunctions() [Test] public void FindsAllCalls() { - var calls = _graph.GetAllDependencies().Where(d => d.Type == DependencyType.Calls); + var calls = _graph.GetAllRelationships().Where(d => d.Type == RelationshipType.Calls); var actual = calls.Select(d => $"{_graph.Nodes[d.SourceId].FullName} -> {_graph.Nodes[d.TargetId].FullName}") .ToHashSet(); @@ -151,10 +155,13 @@ public void FindsAllCalls() // Access to properties, setter and getter included. "ModuleLevel1.ModuleLevel1.Model.ModelA.AccessToPropertiesGetter -> ModuleLevel1.ModuleLevel1.Model.ModelA.ModelCPropertyOfModelA", "ModuleLevel1.ModuleLevel1.Model.ModelA.AccessToPropertiesSetter -> ModuleLevel1.ModuleLevel1.Model.ModelA.ModelCPropertyOfModelA", - + "CSharpLanguage.CSharpLanguage.EventInvocation.DoSomething -> CSharpLanguage.CSharpLanguage.EventInvocation.Raise1", "CSharpLanguage.CSharpLanguage.EventInvocation.DoSomething -> CSharpLanguage.CSharpLanguage.EventInvocation.Raise2", - "CSharpLanguage.CSharpLanguage.EventInvocation.DoSomething -> CSharpLanguage.CSharpLanguage.EventInvocation.Raise3" + "CSharpLanguage.CSharpLanguage.EventInvocation.DoSomething -> CSharpLanguage.CSharpLanguage.EventInvocation.Raise3", + + "CSharpLanguage.CSharpLanguage.Partial.Client.CreateInstance -> CSharpLanguage.CSharpLanguage.Partial.PartialClass.MethodInPartialClassPart1", + "CSharpLanguage.CSharpLanguage.Partial.Client.CreateInstance -> CSharpLanguage.CSharpLanguage.Partial.PartialClass.MethodInPartialClassPart2" }; CollectionAssert.AreEquivalent(expected, actual); @@ -193,8 +200,8 @@ public void FindsAllPropertyImplementations() { // Realize an interface var actual = _graph.Nodes.Values - .SelectMany(n => n.Dependencies) - .Where(d => d.Type == DependencyType.Implements) + .SelectMany(n => n.Relationships) + .Where(d => d.Type == RelationshipType.Implements) .Select(d => (_graph.Nodes[d.SourceId], _graph.Nodes[d.TargetId])) .Where(t => t.Item1.ElementType == CodeElementType.Property && t.Item2.ElementType == CodeElementType.Property) @@ -215,8 +222,8 @@ public void FindsAllPropertyImplementations() public void FindsAllPropertyOverrides() { var actual = _graph.Nodes.Values - .SelectMany(n => n.Dependencies) - .Where(d => d.Type == DependencyType.Overrides) + .SelectMany(n => n.Relationships) + .Where(d => d.Type == RelationshipType.Overrides) .Select(d => (_graph.Nodes[d.SourceId], _graph.Nodes[d.TargetId])) .Where(t => t.Item1.ElementType == CodeElementType.Property && t.Item2.ElementType == CodeElementType.Property) @@ -237,8 +244,8 @@ public void FindsAllPropertyOverrides() public void FindsAllMethodImplementations() { var actual = _graph.Nodes.Values - .SelectMany(n => n.Dependencies) - .Where(d => d.Type == DependencyType.Implements) + .SelectMany(n => n.Relationships) + .Where(d => d.Type == RelationshipType.Implements) .Select(d => (_graph.Nodes[d.SourceId], _graph.Nodes[d.TargetId])) .Where(t => t.Item1.ElementType == CodeElementType.Method && t.Item2.ElementType == CodeElementType.Method) @@ -262,8 +269,8 @@ public void FindsAllMethodImplementations() public void FindsAllMethodOverrides() { var actual = _graph.Nodes.Values - .SelectMany(n => n.Dependencies) - .Where(d => d.Type == DependencyType.Overrides) + .SelectMany(n => n.Relationships) + .Where(d => d.Type == RelationshipType.Overrides) .Select(d => (_graph.Nodes[d.SourceId], _graph.Nodes[d.TargetId])) .Where(t => t.Item1.ElementType == CodeElementType.Method && t.Item2.ElementType == CodeElementType.Method) @@ -286,8 +293,8 @@ public void FindsAllEventUsages() { // Registration and un-registration var actual = _graph.Nodes.Values - .SelectMany(n => n.Dependencies) - .Where(d => d.Type == DependencyType.Uses) + .SelectMany(n => n.Relationships) + .Where(d => d.Type == RelationshipType.Uses) .Select(d => (_graph.Nodes[d.SourceId], _graph.Nodes[d.TargetId])) .Where(t => t.Item2.ElementType == CodeElementType.Event) .Select(t => $"{t.Item1.FullName} -> {t.Item2.FullName}") @@ -297,7 +304,7 @@ public void FindsAllEventUsages() var expected = new HashSet { "CSharpLanguage.CSharpLanguage.ClassUsingAnEvent.Init -> CSharpLanguage.CSharpLanguage.ClassOfferingAnEvent.MyEvent1", - "CSharpLanguage.CSharpLanguage.ClassUsingAnEvent.Init -> CSharpLanguage.CSharpLanguage.ClassOfferingAnEvent.MyEvent2", + "CSharpLanguage.CSharpLanguage.ClassUsingAnEvent.Init -> CSharpLanguage.CSharpLanguage.ClassOfferingAnEvent.MyEvent2", "CSharpLanguage.CSharpLanguage.EventSink..ctor -> CSharpLanguage.CSharpLanguage.IInterfaceWithEvent.MyEvent" }; @@ -310,8 +317,8 @@ public void FindsAllEventUsages() public void FindsAllEventInvocation() { var actual = _graph.Nodes.Values - .SelectMany(n => n.Dependencies) - .Where(d => d.Type == DependencyType.Invokes) + .SelectMany(n => n.Relationships) + .Where(d => d.Type == RelationshipType.Invokes) .Select(d => (_graph.Nodes[d.SourceId], _graph.Nodes[d.TargetId])) .Select(t => $"{t.Item1.FullName} -> {t.Item2.FullName}") .ToList(); @@ -335,8 +342,8 @@ public void FindsAllEventImplementations() { // Registration and unregistration var actual = _graph.Nodes.Values - .SelectMany(n => n.Dependencies) - .Where(d => d.Type == DependencyType.Implements) + .SelectMany(n => n.Relationships) + .Where(d => d.Type == RelationshipType.Implements) .Select(d => (_graph.Nodes[d.SourceId], _graph.Nodes[d.TargetId])) .Where(t => t.Item1.ElementType == CodeElementType.Event && t.Item2.ElementType == CodeElementType.Event) @@ -358,17 +365,17 @@ public void FindsAllEventImplementations() public void FindsAllEventHandlers() { var actual = _graph.Nodes.Values - .SelectMany(n => n.Dependencies) - .Where(d => d.Type == DependencyType.Handles) - .Select(d => (_graph.Nodes[d.SourceId], _graph.Nodes[d.TargetId])) + .SelectMany(n => n.Relationships) + .Where(d => d.Type == RelationshipType.Handles) + .Select(d => (_graph.Nodes[d.SourceId], _graph.Nodes[d.TargetId])) .Select(t => $"{t.Item1.FullName} -> {t.Item2.FullName}") .ToList(); var expected = new HashSet { - "CSharpLanguage.CSharpLanguage.ClassUsingAnEvent.MyEventHandler -> CSharpLanguage.CSharpLanguage.ClassOfferingAnEvent.MyEvent1", - "CSharpLanguage.CSharpLanguage.ClassUsingAnEvent.MyEventHandler2 -> CSharpLanguage.CSharpLanguage.ClassOfferingAnEvent.MyEvent2", + "CSharpLanguage.CSharpLanguage.ClassUsingAnEvent.MyEventHandler -> CSharpLanguage.CSharpLanguage.ClassOfferingAnEvent.MyEvent1", + "CSharpLanguage.CSharpLanguage.ClassUsingAnEvent.MyEventHandler2 -> CSharpLanguage.CSharpLanguage.ClassOfferingAnEvent.MyEvent2", "CSharpLanguage.CSharpLanguage.EventSink.Handler -> CSharpLanguage.CSharpLanguage.IInterfaceWithEvent.MyEvent" }; @@ -381,8 +388,8 @@ public void FindsAllEventHandlers() public void FindsAllInterfaceImplementations() { var actual = _graph.Nodes.Values - .SelectMany(n => n.Dependencies) - .Where(d => d.Type == DependencyType.Implements) + .SelectMany(n => n.Relationships) + .Where(d => d.Type == RelationshipType.Implements) .Select(d => (_graph.Nodes[d.SourceId], _graph.Nodes[d.TargetId])) .Where(t => t.Item1.ElementType == CodeElementType.Class && t.Item2.ElementType == CodeElementType.Interface) @@ -405,8 +412,8 @@ public void FindsAllInterfaceImplementations() public void FindsAllInheritance() { var actual = _graph.Nodes.Values - .SelectMany(n => n.Dependencies) - .Where(d => d.Type == DependencyType.Inherits) + .SelectMany(n => n.Relationships) + .Where(d => d.Type == RelationshipType.Inherits) .Select(d => (_graph.Nodes[d.SourceId], _graph.Nodes[d.TargetId])) .Where(t => t.Item1.ElementType == CodeElementType.Class && t.Item2.ElementType == CodeElementType.Class) @@ -430,8 +437,8 @@ public void FindsAllInheritance() public void FindsAllUsingBetweenClasses() { var actual = _graph.Nodes.Values - .SelectMany(n => n.Dependencies) - .Where(d => d.Type == DependencyType.Uses) + .SelectMany(n => n.Relationships) + .Where(d => d.Type == RelationshipType.Uses) .Select(d => (_graph.Nodes[d.SourceId], _graph.Nodes[d.TargetId])) .Where(t => t.Item1.ElementType == CodeElementType.Class && t.Item2.ElementType == CodeElementType.Class) @@ -459,11 +466,13 @@ public void Find_Interface_Implementation_In_BaseClass() var iLoad = iStorage.Children.Single(); var load = baseStorage.Children.Single(); - Assert.IsTrue(storage.Dependencies.Any(d => d.TargetId == iStorage.Id && d.Type == DependencyType.Implements)); - Assert.IsTrue(storage.Dependencies.Any(d => d.TargetId == baseStorage.Id && d.Type == DependencyType.Inherits)); + Assert.IsTrue( + storage.Relationships.Any(d => d.TargetId == iStorage.Id && d.Type == RelationshipType.Implements)); + Assert.IsTrue( + storage.Relationships.Any(d => d.TargetId == baseStorage.Id && d.Type == RelationshipType.Inherits)); // Not detected! - Assert.IsTrue(load.Dependencies.Any(d => d.TargetId == iLoad.Id && d.Type == DependencyType.Implements)); + Assert.IsTrue(load.Relationships.Any(d => d.TargetId == iLoad.Id && d.Type == RelationshipType.Implements)); } [Test] diff --git a/CodeParserTests/CycleFinderTests.cs b/CodeParserTests/CycleFinderTests.cs index c0c69c0..6582324 100644 --- a/CodeParserTests/CycleFinderTests.cs +++ b/CodeParserTests/CycleFinderTests.cs @@ -12,8 +12,8 @@ public void FindClassCycle() var codeGraph = new TestCodeGraph(); var classA = codeGraph.CreateClass("ClassA"); var classB = codeGraph.CreateClass("ClassB"); - classA.Dependencies.Add(new Dependency(classA.Id, classB.Id, DependencyType.Uses)); - classB.Dependencies.Add(new Dependency(classB.Id, classA.Id, DependencyType.Uses)); + classA.Relationships.Add(new Relationship(classA.Id, classB.Id, RelationshipType.Uses)); + classB.Relationships.Add(new Relationship(classB.Id, classA.Id, RelationshipType.Uses)); var groups = CycleFinder.FindCycleGroups(codeGraph); @@ -32,9 +32,9 @@ public void PinSignalViewRegression() var owner = codeGraph.CreateField("AutomationPeer._owner", peer); var ctor = codeGraph.CreateMethod("AutomationPeer.ctor", peer); - createPeer.Dependencies.Add(new Dependency(createPeer.Id, peer.Id, DependencyType.Creates)); - ctor.Dependencies.Add(new Dependency(ctor.Id, view.Id, DependencyType.Uses)); - owner.Dependencies.Add(new Dependency(owner.Id, view.Id, DependencyType.Uses)); + createPeer.Relationships.Add(new Relationship(createPeer.Id, peer.Id, RelationshipType.Creates)); + ctor.Relationships.Add(new Relationship(ctor.Id, view.Id, RelationshipType.Uses)); + owner.Relationships.Add(new Relationship(owner.Id, view.Id, RelationshipType.Uses)); var groups = CycleFinder.FindCycleGroups(codeGraph); @@ -68,9 +68,9 @@ public void Regression_NestedClasses() var field2 = codeGraph.CreateField("_field2", classChild2); - field1.Dependencies.Add(new Dependency(field1.Id, enumInParent.Id, DependencyType.Uses)); - field2.Dependencies.Add(new Dependency(field2.Id, enumInParent.Id, DependencyType.Uses)); - methodInParent.Dependencies.Add(new Dependency(methodInParent.Id, classChild1.Id, DependencyType.Uses)); + field1.Relationships.Add(new Relationship(field1.Id, enumInParent.Id, RelationshipType.Uses)); + field2.Relationships.Add(new Relationship(field2.Id, enumInParent.Id, RelationshipType.Uses)); + methodInParent.Relationships.Add(new Relationship(methodInParent.Id, classChild1.Id, RelationshipType.Uses)); var groups = CycleFinder.FindCycleGroups(codeGraph); @@ -103,9 +103,9 @@ public void Regression_NestedNamespaces() var classNsIrrelevant = codeGraph.CreateClass("ClassNsIrrelevant", nsIrrelevant); var field2 = codeGraph.CreateField("_delegate2", classNsIrrelevant); - field1.Dependencies.Add(new Dependency(field1.Id, delegateNsChild.Id, DependencyType.Uses)); - field2.Dependencies.Add(new Dependency(field2.Id, delegateNsChild.Id, DependencyType.Uses)); - method.Dependencies.Add(new Dependency(method.Id, classNsParent.Id, DependencyType.Uses)); + field1.Relationships.Add(new Relationship(field1.Id, delegateNsChild.Id, RelationshipType.Uses)); + field2.Relationships.Add(new Relationship(field2.Id, delegateNsChild.Id, RelationshipType.Uses)); + method.Relationships.Add(new Relationship(method.Id, classNsParent.Id, RelationshipType.Uses)); var groups = CycleFinder.FindCycleGroups(codeGraph); @@ -125,8 +125,8 @@ public void FindClassCycleViaField() var fieldA = codeGraph.CreateField("ClassA.FieldA", classA); var fieldB = codeGraph.CreateField("ClassA.FieldB", classB); - classA.Dependencies.Add(new Dependency(fieldA.Id, classB.Id, DependencyType.Uses)); - classB.Dependencies.Add(new Dependency(fieldB.Id, classA.Id, DependencyType.Uses)); + classA.Relationships.Add(new Relationship(fieldA.Id, classB.Id, RelationshipType.Uses)); + classB.Relationships.Add(new Relationship(fieldB.Id, classA.Id, RelationshipType.Uses)); var groups = CycleFinder.FindCycleGroups(codeGraph); @@ -149,8 +149,8 @@ public void FindMethodCrossNamespaceCycle() var methodBA = codeGraph.CreateMethod("ClassB.MethodA", classB); var methodBB = codeGraph.CreateMethod("ClassB.MethodB", classB); - methodAA.Dependencies.Add(new Dependency(methodAA.Id, methodBA.Id, DependencyType.Calls)); - methodBB.Dependencies.Add(new Dependency(methodBB.Id, methodAB.Id, DependencyType.Calls)); + methodAA.Relationships.Add(new Relationship(methodAA.Id, methodBA.Id, RelationshipType.Calls)); + methodBB.Relationships.Add(new Relationship(methodBB.Id, methodAB.Id, RelationshipType.Calls)); var groups = CycleFinder.FindCycleGroups(codeGraph); @@ -167,8 +167,8 @@ public void FindMethodCycle() var classB = codeGraph.CreateClass("ClassB"); var methodA = codeGraph.CreateMethod("ClassA.MethodA", classA); var methodB = codeGraph.CreateMethod("ClassB.MethodB", classB); - methodA.Dependencies.Add(new Dependency(methodA.Id, methodB.Id, DependencyType.Calls)); - methodB.Dependencies.Add(new Dependency(methodB.Id, methodA.Id, DependencyType.Calls)); + methodA.Relationships.Add(new Relationship(methodA.Id, methodB.Id, RelationshipType.Calls)); + methodB.Relationships.Add(new Relationship(methodB.Id, methodA.Id, RelationshipType.Calls)); var groups = CycleFinder.FindCycleGroups(codeGraph); @@ -186,8 +186,8 @@ public void FindCycleInNestedNamespaces() var ns2 = codeGraph.CreateNamespace("NS1.NS2", ns1); var classA = codeGraph.CreateClass("ClassA", ns1); var classB = codeGraph.CreateClass("ClassB", ns2); - classA.Dependencies.Add(new Dependency(classA.Id, classB.Id, DependencyType.Uses)); - classB.Dependencies.Add(new Dependency(classB.Id, classA.Id, DependencyType.Uses)); + classA.Relationships.Add(new Relationship(classA.Id, classB.Id, RelationshipType.Uses)); + classB.Relationships.Add(new Relationship(classB.Id, classA.Id, RelationshipType.Uses)); var groups = CycleFinder.FindCycleGroups(codeGraph); @@ -204,8 +204,8 @@ public void FindCycleBetweenNestedClasses() var outerClass = codeGraph.CreateClass("OuterClass"); var innerClassA = codeGraph.CreateClass("InnerClassA", outerClass); var innerClassB = codeGraph.CreateClass("InnerClassB", outerClass); - innerClassA.Dependencies.Add(new Dependency(innerClassA.Id, innerClassB.Id, DependencyType.Uses)); - innerClassB.Dependencies.Add(new Dependency(innerClassB.Id, innerClassA.Id, DependencyType.Uses)); + innerClassA.Relationships.Add(new Relationship(innerClassA.Id, innerClassB.Id, RelationshipType.Uses)); + innerClassB.Relationships.Add(new Relationship(innerClassB.Id, innerClassA.Id, RelationshipType.Uses)); var groups = CycleFinder.FindCycleGroups(codeGraph); @@ -222,8 +222,8 @@ public void FindCycleBetweenMethodAndNestedClass() var outerClass = codeGraph.CreateClass("OuterClass"); var innerClass = codeGraph.CreateClass("InnerClass", outerClass); var method = codeGraph.CreateMethod("OuterClass.Method", outerClass); - method.Dependencies.Add(new Dependency(method.Id, innerClass.Id, DependencyType.Uses)); - innerClass.Dependencies.Add(new Dependency(innerClass.Id, method.Id, DependencyType.Calls)); + method.Relationships.Add(new Relationship(method.Id, innerClass.Id, RelationshipType.Uses)); + innerClass.Relationships.Add(new Relationship(innerClass.Id, method.Id, RelationshipType.Calls)); var groups = CycleFinder.FindCycleGroups(codeGraph); @@ -242,8 +242,8 @@ public void FindCycleBetweenNamespaceAndClass() var ns2 = codeGraph.CreateNamespace("NS2"); var classA = codeGraph.CreateClass("ClassA", ns1); var classB = codeGraph.CreateClass("ClassB", ns2); - ns1.Dependencies.Add(new Dependency(ns1.Id, classB.Id, DependencyType.Uses)); - classB.Dependencies.Add(new Dependency(classB.Id, ns1.Id, DependencyType.Uses)); + ns1.Relationships.Add(new Relationship(ns1.Id, classB.Id, RelationshipType.Uses)); + classB.Relationships.Add(new Relationship(classB.Id, ns1.Id, RelationshipType.Uses)); var groups = CycleFinder.FindCycleGroups(codeGraph); @@ -262,8 +262,8 @@ public void NoCycleBetweenContainedElements() var ns = codeGraph.CreateNamespace("NS"); var classA = codeGraph.CreateClass("ClassA", ns); var methodA = codeGraph.CreateMethod("ClassA.MethodA", classA); - ns.Dependencies.Add(new Dependency(ns.Id, classA.Id, DependencyType.Containment)); - classA.Dependencies.Add(new Dependency(classA.Id, methodA.Id, DependencyType.Containment)); + ns.Relationships.Add(new Relationship(ns.Id, classA.Id, RelationshipType.Containment)); + classA.Relationships.Add(new Relationship(classA.Id, methodA.Id, RelationshipType.Containment)); var groups = CycleFinder.FindCycleGroups(codeGraph); @@ -278,8 +278,8 @@ public void FindCycleBetweenInterfaceAndImplementingClass() var codeGraph = new TestCodeGraph(); var interfaceA = codeGraph.CreateInterface("InterfaceA"); var classA = codeGraph.CreateClass("ClassA"); - classA.Dependencies.Add(new Dependency(classA.Id, interfaceA.Id, DependencyType.Implements)); - interfaceA.Dependencies.Add(new Dependency(interfaceA.Id, classA.Id, DependencyType.Uses)); + classA.Relationships.Add(new Relationship(classA.Id, interfaceA.Id, RelationshipType.Implements)); + interfaceA.Relationships.Add(new Relationship(interfaceA.Id, classA.Id, RelationshipType.Uses)); var groups = CycleFinder.FindCycleGroups(codeGraph); @@ -295,8 +295,8 @@ public void FindCycleBetweenEnumAndClass() var codeGraph = new TestCodeGraph(); var enumA = codeGraph.CreateEnum("EnumA"); var classA = codeGraph.CreateClass("ClassA"); - classA.Dependencies.Add(new Dependency(classA.Id, enumA.Id, DependencyType.Uses)); - enumA.Dependencies.Add(new Dependency(enumA.Id, classA.Id, DependencyType.Uses)); + classA.Relationships.Add(new Relationship(classA.Id, enumA.Id, RelationshipType.Uses)); + enumA.Relationships.Add(new Relationship(enumA.Id, classA.Id, RelationshipType.Uses)); var groups = CycleFinder.FindCycleGroups(codeGraph); @@ -315,11 +315,11 @@ public void FindMultipleCyclesInGraph() var classC = codeGraph.CreateClass("ClassC"); var classD = codeGraph.CreateClass("ClassD"); - classA.Dependencies.Add(new Dependency(classA.Id, classB.Id, DependencyType.Uses)); - classB.Dependencies.Add(new Dependency(classB.Id, classA.Id, DependencyType.Uses)); + classA.Relationships.Add(new Relationship(classA.Id, classB.Id, RelationshipType.Uses)); + classB.Relationships.Add(new Relationship(classB.Id, classA.Id, RelationshipType.Uses)); - classC.Dependencies.Add(new Dependency(classC.Id, classD.Id, DependencyType.Uses)); - classD.Dependencies.Add(new Dependency(classD.Id, classC.Id, DependencyType.Uses)); + classC.Relationships.Add(new Relationship(classC.Id, classD.Id, RelationshipType.Uses)); + classD.Relationships.Add(new Relationship(classD.Id, classC.Id, RelationshipType.Uses)); var groups = CycleFinder.FindCycleGroups(codeGraph); diff --git a/Contracts/Colors/ColorDefinitions.cs b/Contracts/Colors/ColorDefinitions.cs index 77a6604..cb7908a 100644 --- a/Contracts/Colors/ColorDefinitions.cs +++ b/Contracts/Colors/ColorDefinitions.cs @@ -18,7 +18,7 @@ public static class ColorDefinitions /// Field: Dark Yellow (#D7BA7D) /// Event: Pink (#FF69B4) /// Delegate: Light Purple (#C586C0) - /// Dependencies: + /// Relationships: /// Inheritance: Dark Green (#008000) /// Implementation: Light Green (#90EE90) /// Calls: Blue (#0000FF) diff --git a/Contracts/Graph/CodeElement.cs b/Contracts/Graph/CodeElement.cs index 395e611..25d8354 100644 --- a/Contracts/Graph/CodeElement.cs +++ b/Contracts/Graph/CodeElement.cs @@ -8,14 +8,14 @@ public class CodeElement(string id, CodeElementType elementType, string name, st public List SourceLocations { get; set; } = []; /// - /// Unlike in the dependency graph where external dependencies are omitted + /// Unlike in the graph where external relationships are omitted /// I want to keep all attributes here. /// public HashSet Attributes { get; set; } = []; public HashSet Children { get; } = []; - public HashSet Dependencies { get; } = []; + public HashSet Relationships { get; } = []; public CodeElementType ElementType { get; set; } = elementType; diff --git a/Contracts/Graph/CodeGraph.cs b/Contracts/Graph/CodeGraph.cs index 9156fb8..c45b9ba 100644 --- a/Contracts/Graph/CodeGraph.cs +++ b/Contracts/Graph/CodeGraph.cs @@ -10,7 +10,7 @@ public class CodeGraph : IGraphRepresentation public IReadOnlyCollection GetNeighbors(CodeElement vertex) { - return vertex.Dependencies.Select(d => Nodes[d.TargetId]).ToList(); + return vertex.Relationships.Select(d => Nodes[d.TargetId]).ToList(); } public bool IsVertex(CodeElement vertex) @@ -20,7 +20,7 @@ public bool IsVertex(CodeElement vertex) public bool IsEdge(CodeElement source, CodeElement target) { - return Nodes[source.Id].Dependencies.Any(d => d.TargetId == target.Id); + return Nodes[source.Id].Relationships.Any(d => d.TargetId == target.Id); } public IReadOnlyCollection GetVertices() @@ -71,7 +71,7 @@ void CleanupFunc(CodeElement element) } element.Children.RemoveWhere(e => elementIds.Contains(e.Id)); - element.Dependencies.RemoveWhere(d => elementIds.Contains(d.SourceId) || elementIds.Contains(d.TargetId)); + element.Relationships.RemoveWhere(d => elementIds.Contains(d.SourceId) || elementIds.Contains(d.TargetId)); } } @@ -133,13 +133,23 @@ public CodeElement IntegrateCodeElementFromOriginal(CodeElement originalElement) return newElement; } - public IEnumerable GetAllDependencies() + public IEnumerable GetAllRelationships() { - return Nodes.Values.SelectMany(n => n.Dependencies).ToList(); + return Nodes.Values.SelectMany(n => n.Relationships).ToList(); } public List GetRoots() { return Nodes.Values.Where(n => n.Parent == null).ToList(); } + + public string ToDebug() + { + var relationships = GetAllRelationships().Select(d => (Nodes[d.SourceId].FullName, Nodes[d.TargetId].FullName)); + + var elementNames = Nodes.Values.OrderBy(v => v.FullName).Select(e => e.FullName); + var relationshipNames = relationships.Select(d => $"{d.Item1} -> {d.Item2}"); + return string.Join("\n", elementNames) + + string.Join("\n", relationshipNames); + } } \ No newline at end of file diff --git a/Contracts/Graph/DependencyType.cs b/Contracts/Graph/DependencyType.cs deleted file mode 100644 index 847e5c7..0000000 --- a/Contracts/Graph/DependencyType.cs +++ /dev/null @@ -1,27 +0,0 @@ -namespace Contracts.Graph; - -public enum DependencyType -{ - Calls, - Creates, - Uses, - Inherits, - - // Whole interface or a single method. - Implements, - - Overrides, - - // Special dependency to model the hierarchy. - // In the CodeElement this dependency is modeled via. - // Parent / Children - Containment, - - UsesAttribute, - - // Dependency type for event invocation - Invokes, - - // Dependency type for event handler registration - Handles -} \ No newline at end of file diff --git a/Contracts/Graph/Dependency.cs b/Contracts/Graph/Relationship.cs similarity index 65% rename from Contracts/Graph/Dependency.cs rename to Contracts/Graph/Relationship.cs index 682097a..861bd1b 100644 --- a/Contracts/Graph/Dependency.cs +++ b/Contracts/Graph/Relationship.cs @@ -3,17 +3,17 @@ namespace Contracts.Graph; [DebuggerDisplay("{Type}")] -public class Dependency(string sourceId, string targetId, DependencyType type) +public class Relationship(string sourceId, string targetId, RelationshipType type) { public string SourceId { get; } = sourceId; public string TargetId { get; } = targetId; - public DependencyType Type { get; } = type; + public RelationshipType Type { get; } = type; public List SourceLocations { get; set; } = []; public override bool Equals(object? obj) { - if (obj is not Dependency other) + if (obj is not Relationship other) { return false; } @@ -30,10 +30,10 @@ public override int GetHashCode() return HashCode.Combine(SourceId, TargetId, Type); } - public Dependency Clone() + public Relationship Clone() { - var newDependency = new Dependency(SourceId, TargetId, Type); - newDependency.SourceLocations.AddRange(SourceLocations); - return newDependency; + var newRelationship = new Relationship(SourceId, TargetId, Type); + newRelationship.SourceLocations.AddRange(SourceLocations); + return newRelationship; } } \ No newline at end of file diff --git a/Contracts/Graph/RelationshipType.cs b/Contracts/Graph/RelationshipType.cs new file mode 100644 index 0000000..7e04efc --- /dev/null +++ b/Contracts/Graph/RelationshipType.cs @@ -0,0 +1,28 @@ +namespace Contracts.Graph; + +public enum RelationshipType +{ + Calls, + Creates, + Uses, + Inherits, + + // Whole interface or a single method. + Implements, + + Overrides, + + // Special relationship to model the hierarchy. + // In the CodeElement this relationship is modeled via. + // Parent / Children + Containment, + + UsesAttribute, + + // Relationship type for event invocation + Invokes, + + // Relationship type for event handler registration + // This is not a code dependency. It is actually the other direction. + Handles +} \ No newline at end of file diff --git a/README.md b/README.md index 97fc440..abfb63b 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,5 @@ # C# Code Analyst - - -![](Images/traffic-barrier.png) **Under development** - This application helps you to explore, understand, and maintain C# code. ## Exploring your codebase @@ -63,7 +59,7 @@ Remember, the goal isn't to eliminate every cycle but to be aware of your code's A **DSM** (Dependency Structure Matrix) displays all the dependencies in your codebase in a condensed matrix. It may take some time to become familiar with it. For further explanation, you can visit https://dsmsuite.github.io/. -The **DSM Suite Viewer** is integrated into C# Code Analyst +C# Code Analyst uses the **DSM Suite Viewer** to visualize your code as a DSM. diff --git a/SampleProject/CSharpLanguage/Partial/Client.cs b/SampleProject/CSharpLanguage/Partial/Client.cs new file mode 100644 index 0000000..100da5c --- /dev/null +++ b/SampleProject/CSharpLanguage/Partial/Client.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CSharpLanguage.Partial +{ + internal class Client + { + public static Client CreateInstance() + { + var p = new PartialClass(); + p.MethodInPartialClassPart1(); + p.MethodInPartialClassPart2(); + return new Client(); + } + } +} diff --git a/SampleProject/CSharpLanguage/Partial/PartialClass.Part1.cs b/SampleProject/CSharpLanguage/Partial/PartialClass.Part1.cs new file mode 100644 index 0000000..ba9ea83 --- /dev/null +++ b/SampleProject/CSharpLanguage/Partial/PartialClass.Part1.cs @@ -0,0 +1,8 @@ +namespace CSharpLanguage.Partial; + +internal partial class PartialClass +{ + public void MethodInPartialClassPart1() + { + } +} \ No newline at end of file diff --git a/SampleProject/CSharpLanguage/Partial/PartialClass.Part2.cs b/SampleProject/CSharpLanguage/Partial/PartialClass.Part2.cs new file mode 100644 index 0000000..46079e8 --- /dev/null +++ b/SampleProject/CSharpLanguage/Partial/PartialClass.Part2.cs @@ -0,0 +1,8 @@ +namespace CSharpLanguage.Partial; + +internal partial class PartialClass +{ + public void MethodInPartialClassPart2() + { + } +} \ No newline at end of file