22// The .NET Foundation licenses this file to you under the MIT license.
33// See the LICENSE file in the project root for more information.
44
5- #if NET6_0_OR_GREATER
5+ #if NET
66
77using System ;
88using System . Collections . Generic ;
99using System . Collections . Immutable ;
1010using System . Threading ;
11- using System . Threading . Channels ;
1211using System . Threading . Tasks ;
1312using Microsoft . CodeAnalysis . FindSymbols ;
14- using Microsoft . CodeAnalysis . FindUsages ;
13+ using Microsoft . CodeAnalysis . PooledObjects ;
1514using Microsoft . CodeAnalysis . Shared . Extensions ;
1615using Microsoft . CodeAnalysis . Shared . Utilities ;
17- using Microsoft . CodeAnalysis . Threading ;
1816
1917namespace Microsoft . CodeAnalysis . SemanticSearch ;
2018
@@ -34,85 +32,39 @@ public IEnumerable<SyntaxNode> Find(ISymbol symbol)
3432
3533 public async IAsyncEnumerable < SyntaxNode > FindAsync ( ISymbol symbol )
3634 {
37- var channel = Channel . CreateUnbounded < ReferenceLocation > ( new ( )
35+ using var _ = PooledHashSet < SyntaxNode > . GetInstance ( out var cachedRoots ) ;
36+
37+ // Kick off the SymbolFinder.FindReferencesAsync call on the provided symbol/solution. As it finds
38+ // ReferenceLocations, it will push those into the 'callback' delegate passed into it. ProducerConsumer will
39+ // then convert this to a simple IAsyncEnumerable<ReferenceLocation> that we can iterate over, converting those
40+ // locations to SyntaxNodes in the corresponding C# or VB document.
41+ await foreach ( var item in ProducerConsumer < ReferenceLocation > . RunAsync (
42+ FindReferencesAsync , args : ( solution , symbol ) , cancellationToken ) )
3843 {
39- SingleReader = true ,
40- SingleWriter = false ,
41- } ) ;
42-
43- using var _ = cancellationToken . Register (
44- static ( obj , cancellationToken ) => ( ( Channel < SourceReferenceItem > ) obj ! ) . Writer . TryComplete ( new OperationCanceledException ( cancellationToken ) ) ,
45- state : channel ) ;
46-
47- var progress = new Progress ( channel ) ;
48-
49- var writeTask = ProduceItemsAndWriteToChannelAsync ( ) ;
50-
51- await foreach ( var reference in channel . Reader . ReadAllAsync ( cancellationToken ) )
52- {
53- // TODO: consider grouping by document to avoid repeated syntax root lookup
54-
55- var root = await reference . Document . GetSyntaxRootAsync ( cancellationToken ) . ConfigureAwait ( false ) ;
44+ var root = await item . Document . GetSyntaxRootAsync ( cancellationToken ) . ConfigureAwait ( false ) ;
5645 if ( root == null )
57- {
5846 continue ;
59- }
60-
61- yield return root . FindNode ( reference . Location . SourceSpan , findInTrivia : true , getInnermostNodeForTie : true ) ;
62- }
63-
64- await writeTask . ConfigureAwait ( false ) ;
65-
66- async Task ProduceItemsAndWriteToChannelAsync ( )
67- {
68- await Task . Yield ( ) . ConfigureAwait ( false ) ;
6947
70- Exception ? exception = null ;
71- try
72- {
73- //await service.FindReferencesAsync(context, document, location.SourceSpan.Start, classificationOptions, cancellationToken).ConfigureAwait(false);
74- await SymbolFinder . FindReferencesAsync (
75- symbol ,
76- solution ,
77- progress ,
78- documents : null ,
79- s_options ,
80- cancellationToken ) . ConfigureAwait ( false ) ;
81- }
82- catch ( Exception ex ) when ( ( exception = ex ) == null )
83- {
84- throw ExceptionUtilities . Unreachable ( ) ;
85- }
86- finally
87- {
88- // No matter what path we take (exceptional or non-exceptional), always complete the channel so the
89- // writing task knows it's done.
90- channel . Writer . TryComplete ( exception ) ;
91- }
48+ // Hold onto the root so that if we find more references in the same document, we don't have to reparse it.
49+ cachedRoots . Add ( root ) ;
50+ yield return item . Location . FindNode ( findInsideTrivia : true , getInnermostNodeForTie : true , cancellationToken ) ;
9251 }
9352 }
9453
95- private sealed class Progress ( Channel < ReferenceLocation > channel ) : IStreamingFindReferencesProgress
96- {
97- public ValueTask OnStartedAsync ( CancellationToken cancellationToken )
98- => ValueTask . CompletedTask ;
99-
100- public ValueTask OnCompletedAsync ( CancellationToken cancellationToken )
101- => ValueTask . CompletedTask ;
54+ private static Task FindReferencesAsync ( Action < ReferenceLocation > callback , ( Solution solution , ISymbol symbol ) args , CancellationToken cancellationToken )
55+ => SymbolFinder . FindReferencesAsync (
56+ args . symbol , args . solution , new Progress ( callback ) , documents : null , s_options , cancellationToken ) ;
10257
103- public ValueTask OnDefinitionFoundAsync ( SymbolGroup group , CancellationToken cancellationToken )
104- => ValueTask . CompletedTask ;
58+ private sealed class Progress ( Action < ReferenceLocation > callback ) : IStreamingFindReferencesProgress
59+ {
60+ public ValueTask OnStartedAsync ( CancellationToken cancellationToken ) => ValueTask . CompletedTask ;
61+ public ValueTask OnCompletedAsync ( CancellationToken cancellationToken ) => ValueTask . CompletedTask ;
62+ public ValueTask OnDefinitionFoundAsync ( SymbolGroup group , CancellationToken cancellationToken ) => ValueTask . CompletedTask ;
10563
10664 public ValueTask OnReferencesFoundAsync ( ImmutableArray < ( SymbolGroup group , ISymbol symbol , ReferenceLocation location ) > references , CancellationToken cancellationToken )
10765 {
10866 foreach ( var ( _, _, location ) in references )
109- {
110- // It's ok to use TryWrite here. TryWrite always succeeds unless the channel is completed. And the
111- // channel is only ever completed by us (after produceItems completes or throws an exception) or if the
112- // cancellationToken is triggered above in RunAsync. In that latter case, it's ok for writing to the
113- // channel to do nothing as we no longer need to write out those assets to the pipe.
114- _ = channel . Writer . TryWrite ( location ) ;
115- }
67+ callback ( location ) ;
11668
11769 return ValueTask . CompletedTask ;
11870 }
0 commit comments