@@ -80,7 +80,6 @@ public SearchResult FindMissingTypesForLonelyTypeMembers(HashSet<string> knownId
8080 /// All relationships crossing the element's boundary outwards: the source is the element
8181 /// or one of its descendants, the target lies outside. Relationships between two
8282 /// descendants are internal and not part of the result.
83- /// <see cref="FindAllRelationshipsDeep" /> shows those.
8483 /// The result contains the start element, the elements taking part in a found relationship
8584 /// and the containers connecting those to the start element. The reached targets come
8685 /// without their gaps filled. If the caller needs those, it runs
@@ -107,7 +106,6 @@ public SearchResult FindOutgoingRelationshipsDeep(string id)
107106 /// All relationships crossing the element's boundary inwards: the target is the element
108107 /// or one of its descendants, the source lies outside. Relationships between two
109108 /// descendants are internal and not part of the result.
110- /// <see cref="FindAllRelationshipsDeep" /> shows those.
111109 /// The result contains the start element, the elements taking part in a found relationship
112110 /// and the containers connecting those to the start element. The found sources come
113111 /// without their gaps filled. If the caller needs those, it runs
@@ -167,30 +165,64 @@ public IEnumerable<Relationship> FindAllRelationships(HashSet<string> ids)
167165 return GetRelationships ( d => ids . Contains ( d . SourceId ) && ids . Contains ( d . TargetId ) ) ;
168166 }
169167
170- public SearchResult FindAllRelationshipsDeep ( HashSet < string > ids )
168+ /// <summary>
169+ /// All relationships crossing the root element's boundary inwards or outwards.
170+ /// The result however is limited to be contained in the given roots
171+ /// The result contains the start element, the elements taking part in a found relationship
172+ /// and the gap filling elements.
173+ ///
174+ /// FindIncomingRelationshipsDeep / FindOutgoingRelationshipsDeep have an open result. Here,
175+ /// all new nodes are descendants of the root elements.
176+ /// </summary>
177+ public SearchResult FindAllRelationshipsDeep ( HashSet < string > rootIds )
171178 {
172179 if ( _codeGraph is null )
173180 {
174181 return new SearchResult ( [ ] , [ ] ) ;
175182 }
176183
177- var expandedIds = ids
178- . Where ( _codeGraph . Nodes . ContainsKey )
179- . SelectMany ( id => _codeGraph . Nodes [ id ] . GetChildrenIncludingSelf ( ) )
180- . ToHashSet ( ) ;
184+ // Expanded set per id
185+ // To which selected root does an element belong. This may be more than one since
186+ // the roots may be nested (e.g. class and namespace are selected. Both contain same method.)
187+ var rootsByElement = new Dictionary < string , HashSet < string > > ( ) ;
188+ foreach ( var rootId in rootIds . Where ( _codeGraph . Nodes . ContainsKey ) )
189+ {
190+ foreach ( var id in _codeGraph . Nodes [ rootId ] . GetChildrenIncludingSelf ( ) )
191+ {
192+ if ( ! rootsByElement . TryGetValue ( id , out var roots ) )
193+ {
194+ roots = [ ] ;
195+ rootsByElement [ id ] = roots ;
196+ }
181197
182- var relationships = FindAllRelationships ( expandedIds ) . ToList ( ) ;
198+ roots . Add ( rootId ) ;
199+ }
200+ }
201+
202+ // Are there a source root s and a target root t that are different?” That is exactly the definition of a crossing edge.
203+ bool CrossesRoots ( Relationship r )
204+ {
205+ return rootsByElement . TryGetValue ( r . SourceId , out var sourceRoots ) &&
206+ rootsByElement . TryGetValue ( r . TargetId , out var targetRoots ) &&
207+ sourceRoots . Any ( s => targetRoots . Any ( t => s != t ) ) ;
208+ }
209+
210+ var relationships = _codeGraph . GetAllRelationships ( ) . Where ( CrossesRoots ) . ToHashSet ( ) ;
211+
212+ // We collected all edges.
183213 var relationshipElementIds = relationships
184214 . SelectMany ( r => new [ ] { r . SourceId , r . TargetId } )
185215 . ToHashSet ( ) ;
186216
187217 // Found elements can be deep inside the given roots (e.g. two methods buried in two
188218 // selected assemblies). Fill in the namespaces/classes connecting them to a root that is
189219 // already known, so they don't end up added without their hierarchy.
190- var gapIds = FillGapsInHierarchy ( ids . Union ( relationshipElementIds ) . ToHashSet ( ) ) ;
220+ var gapIds = FillGapsInHierarchy ( rootIds . Union ( relationshipElementIds ) . ToHashSet ( ) ) ;
191221
192222 var elements = relationshipElementIds
193223 . Union ( gapIds )
224+ . Union ( rootIds ) // to be self-contained
225+ . Where ( _codeGraph . Nodes . ContainsKey )
194226 . Select ( id => _codeGraph . Nodes [ id ] )
195227 . ToHashSet ( ) ;
196228
@@ -261,7 +293,6 @@ public SearchResult FindIncomingCallsRecursive(string id)
261293 return new SearchResult ( foundMethods , foundCalls ) ;
262294 }
263295
264-
265296 /// <summary>
266297 /// This gives a heuristic only.
267298 /// The graph model does not contain the information for analyzing dynamic behavior.
@@ -646,6 +677,7 @@ private SearchResult CollectDeepResult(CodeElement startElement, List<Relationsh
646677 var elements = relationshipElementIds
647678 . Union ( gapIds )
648679 . Union ( [ startElement . Id ] )
680+ . Where ( _codeGraph ! . Nodes . ContainsKey )
649681 . Select ( i => _codeGraph ! . Nodes [ i ] )
650682 . ToHashSet ( ) ;
651683
0 commit comments