22
33using System . Collections . Generic ;
44using System . ComponentModel . Composition ;
5- using System . Linq ;
65using System . Threading ;
6+ using Microsoft . CodeAnalysis ;
77using Microsoft . CodeAnalysis . CSharp ;
88using Microsoft . CodeAnalysis . CSharp . Extensions ;
99using Microsoft . CodeAnalysis . CSharp . Syntax ;
1010using Microsoft . CodeAnalysis . Editor . Implementation . Highlighting ;
11+ using Microsoft . CodeAnalysis . PooledObjects ;
1112using Microsoft . CodeAnalysis . Text ;
1213
1314namespace Microsoft . CodeAnalysis . Editor . CSharp . KeywordHighlighting . KeywordHighlighters
1415{
1516 [ ExportHighlighter ( LanguageNames . CSharp ) ]
1617 internal class AsyncAwaitHighlighter : AbstractKeywordHighlighter
1718 {
19+ private static readonly ObjectPool < Stack < SyntaxNode > > s_stackPool
20+ = new ObjectPool < Stack < SyntaxNode > > ( ( ) => new Stack < SyntaxNode > ( ) ) ;
21+
1822 [ ImportingConstructor ]
1923 public AsyncAwaitHighlighter ( )
2024 {
@@ -26,93 +30,97 @@ protected override bool IsHighlightableNode(SyntaxNode node)
2630 protected override IEnumerable < TextSpan > GetHighlightsForNode ( SyntaxNode node , CancellationToken cancellationToken )
2731 {
2832 var spans = new List < TextSpan > ( ) ;
29- HighlightRelatedKeywords ( node , spans ) ;
33+
34+ foreach ( var current in WalkChildren ( node ) )
35+ {
36+ cancellationToken . ThrowIfCancellationRequested ( ) ;
37+ HighlightRelatedKeywords ( current , spans ) ;
38+ }
39+
3040 return spans ;
3141 }
3242
33- private static void HighlightRelatedKeywords ( SyntaxNode node , List < TextSpan > spans )
43+ private IEnumerable < SyntaxNode > WalkChildren ( SyntaxNode node )
3444 {
35- // Highlight async keyword
36- switch ( node )
45+ using var pooledObject = s_stackPool . GetPooledObject ( ) ;
46+
47+ var stack = pooledObject . Object ;
48+ stack . Push ( node ) ;
49+
50+ while ( stack . Count > 0 )
3751 {
38- case MethodDeclarationSyntax methodDeclaration :
39- {
40- var asyncModifier = methodDeclaration . Modifiers . FirstOrDefault ( m => m . Kind ( ) == SyntaxKind . AsyncKeyword ) ;
41- if ( asyncModifier . Kind ( ) != SyntaxKind . None )
42- {
43- spans . Add ( asyncModifier . Span ) ;
44- }
45- break ;
46- }
47- case LocalFunctionStatementSyntax localFunction :
48- {
49- var asyncModifier = localFunction . Modifiers . FirstOrDefault ( m => m . Kind ( ) == SyntaxKind . AsyncKeyword ) ;
50- if ( asyncModifier . Kind ( ) != SyntaxKind . None )
51- {
52- spans . Add ( asyncModifier . Span ) ;
53- }
54- break ;
55- }
56- case AnonymousFunctionExpressionSyntax anonymousFunction :
57- if ( anonymousFunction . AsyncKeyword . Kind ( ) == SyntaxKind . AsyncKeyword )
58- {
59- spans . Add ( anonymousFunction . AsyncKeyword . Span ) ;
60- }
61- break ;
62-
63- case AwaitExpressionSyntax awaitExpression :
64- // Note if there is already a highlight for the previous token, merge it
65- // with this span. That way, we highlight nested awaits with a single span.
66- var handled = false ;
67- var awaitToken = awaitExpression . AwaitKeyword ;
68- var previousToken = awaitToken . GetPreviousToken ( ) ;
69- if ( ! previousToken . Span . IsEmpty )
70- {
71- var index = spans . FindIndex ( s => s . Contains ( previousToken . Span ) ) ;
72- if ( index >= 0 )
73- {
74- var span = spans [ index ] ;
75- spans [ index ] = TextSpan . FromBounds ( span . Start , awaitToken . Span . End ) ;
76- handled = true ;
77- }
78- }
52+ var current = stack . Pop ( ) ;
53+ yield return current ;
7954
80- if ( ! handled )
55+ // 'Reverse' isn't really necessary, but it means we walk the nodes in document
56+ // order, which is nicer when debugging and understanding the results produced.
57+ foreach ( var child in current . ChildNodesAndTokens ( ) . Reverse ( ) )
58+ {
59+ if ( child . IsNode )
8160 {
82- spans . Add ( awaitToken . Span ) ;
83- }
84- break ;
61+ var childNode = child . AsNode ( ) ;
8562
86- case UsingStatementSyntax usingStatement :
87- if ( usingStatement . AwaitKeyword . Kind ( ) == SyntaxKind . AwaitKeyword )
88- {
89- spans . Add ( usingStatement . AwaitKeyword . Span ) ;
63+ // Only process children if they're not the start of another construct
64+ // that async/await would be related to.
65+ if ( ! childNode . IsReturnableConstruct ( ) )
66+ {
67+ stack . Push ( childNode ) ;
68+ }
9069 }
91- break ;
70+ }
71+ }
72+ }
9273
93- case LocalDeclarationStatementSyntax localDeclaration :
94- if ( localDeclaration . AwaitKeyword . Kind ( ) == SyntaxKind . AwaitKeyword && localDeclaration . UsingKeyword . Kind ( ) == SyntaxKind . UsingKeyword )
95- {
96- spans . Add ( localDeclaration . AwaitKeyword . Span ) ;
97- }
98- break ;
74+ private static bool HighlightRelatedKeywords ( SyntaxNode node , List < TextSpan > spans )
75+ => node switch
76+ {
77+ MethodDeclarationSyntax methodDeclaration => TryAddAsyncModifier ( methodDeclaration . Modifiers , spans ) ,
78+ LocalFunctionStatementSyntax localFunction => TryAddAsyncModifier ( localFunction . Modifiers , spans ) ,
79+ AnonymousFunctionExpressionSyntax anonymousFunction => TryAddAsyncOrAwaitKeyword ( anonymousFunction . AsyncKeyword , spans ) ,
80+ UsingStatementSyntax usingStatement => TryAddAsyncOrAwaitKeyword ( usingStatement . AwaitKeyword , spans ) ,
81+ LocalDeclarationStatementSyntax localDeclaration =>
82+ localDeclaration . UsingKeyword . Kind ( ) == SyntaxKind . UsingKeyword && TryAddAsyncOrAwaitKeyword ( localDeclaration . AwaitKeyword , spans ) ,
83+ CommonForEachStatementSyntax forEachStatement => TryAddAsyncOrAwaitKeyword ( forEachStatement . AwaitKeyword , spans ) ,
84+ AwaitExpressionSyntax awaitExpression => TryAddAsyncOrAwaitKeyword ( awaitExpression . AwaitKeyword , spans ) ,
85+ _ => false ,
86+ } ;
9987
100- case CommonForEachStatementSyntax forEachStatement :
101- if ( forEachStatement . AwaitKeyword . Kind ( ) == SyntaxKind . AwaitKeyword )
102- {
103- spans . Add ( forEachStatement . AwaitKeyword . Span ) ;
104- }
105- break ;
88+ private static bool TryAddAsyncModifier ( SyntaxTokenList modifiers , List < TextSpan > spans )
89+ {
90+ foreach ( var mod in modifiers )
91+ {
92+ if ( TryAddAsyncOrAwaitKeyword ( mod , spans ) )
93+ {
94+ return true ;
95+ }
10696 }
10797
108- foreach ( var child in node . ChildNodes ( ) )
98+ return false ;
99+ }
100+
101+ private static bool TryAddAsyncOrAwaitKeyword ( SyntaxToken mod , List < TextSpan > spans )
102+ {
103+ if ( mod . IsKind ( SyntaxKind . AsyncKeyword , SyntaxKind . AwaitKeyword ) )
109104 {
110- // Only recurse if we have anything to do
111- if ( ! child . IsReturnableConstruct ( ) )
105+ // Note if there is already a highlight for the previous token, merge it with this
106+ // span. That way, we highlight nested awaits with a single span.
107+
108+ if ( spans . Count > 0 )
112109 {
113- HighlightRelatedKeywords ( child , spans ) ;
110+ var previousToken = mod . GetPreviousToken ( ) ;
111+ var lastSpan = spans [ spans . Count - 1 ] ;
112+ if ( lastSpan == previousToken . Span )
113+ {
114+ spans [ spans . Count - 1 ] = TextSpan . FromBounds ( lastSpan . Start , mod . Span . End ) ;
115+ return true ;
116+ }
114117 }
118+
119+ spans . Add ( mod . Span ) ;
120+ return true ;
115121 }
122+
123+ return false ;
116124 }
117125 }
118126}
0 commit comments