File: DirectiveSyntaxExtensions.DirectiveWalker.cs
Web Access
Project: ..\..\..\src\Workspaces\CSharp\Portable\Microsoft.CodeAnalysis.CSharp.Workspaces.csproj (Microsoft.CodeAnalysis.CSharp.Workspaces)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
#nullable disable
 
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.CSharp.Extensions
{
    internal partial class DirectiveSyntaxExtensions
    {
        private class DirectiveWalker : CSharpSyntaxWalker
        {
            private readonly IDictionary<DirectiveTriviaSyntax, DirectiveTriviaSyntax> _directiveMap;
            private readonly IDictionary<DirectiveTriviaSyntax, IReadOnlyList<DirectiveTriviaSyntax>> _conditionalMap;
            private readonly CancellationToken _cancellationToken;
 
            private readonly Stack<DirectiveTriviaSyntax> _regionStack = new();
            private readonly Stack<DirectiveTriviaSyntax> _ifStack = new();
 
            public DirectiveWalker(
                IDictionary<DirectiveTriviaSyntax, DirectiveTriviaSyntax> directiveMap,
                IDictionary<DirectiveTriviaSyntax, IReadOnlyList<DirectiveTriviaSyntax>> conditionalMap,
                CancellationToken cancellationToken)
                : base(SyntaxWalkerDepth.Token)
            {
                _directiveMap = directiveMap;
                _conditionalMap = conditionalMap;
                _cancellationToken = cancellationToken;
            }
 
            public override void DefaultVisit(SyntaxNode node)
            {
                _cancellationToken.ThrowIfCancellationRequested();
 
                if (!node.ContainsDirectives)
                {
                    return;
                }
 
                base.DefaultVisit(node);
            }
 
            public override void VisitToken(SyntaxToken token)
            {
                if (!token.ContainsDirectives)
                {
                    return;
                }
 
                foreach (var directive in token.LeadingTrivia)
                {
                    switch (directive.Kind())
                    {
                        case SyntaxKind.RegionDirectiveTrivia:
                            HandleRegionDirective((DirectiveTriviaSyntax)directive.GetStructure());
                            break;
                        case SyntaxKind.IfDirectiveTrivia:
                            HandleIfDirective((DirectiveTriviaSyntax)directive.GetStructure());
                            break;
                        case SyntaxKind.EndRegionDirectiveTrivia:
                            HandleEndRegionDirective((DirectiveTriviaSyntax)directive.GetStructure());
                            break;
                        case SyntaxKind.EndIfDirectiveTrivia:
                            HandleEndIfDirective((DirectiveTriviaSyntax)directive.GetStructure());
                            break;
                        case SyntaxKind.ElifDirectiveTrivia:
                            HandleElifDirective((DirectiveTriviaSyntax)directive.GetStructure());
                            break;
                        case SyntaxKind.ElseDirectiveTrivia:
                            HandleElseDirective((DirectiveTriviaSyntax)directive.GetStructure());
                            break;
                    }
                }
            }
 
            private void HandleIfDirective(DirectiveTriviaSyntax directive)
                => _ifStack.Push(directive);
 
            private void HandleRegionDirective(DirectiveTriviaSyntax directive)
                => _regionStack.Push(directive);
 
            private void HandleElifDirective(DirectiveTriviaSyntax directive)
                => _ifStack.Push(directive);
 
            private void HandleElseDirective(DirectiveTriviaSyntax directive)
                => _ifStack.Push(directive);
 
            private void HandleEndIfDirective(DirectiveTriviaSyntax directive)
            {
                if (_ifStack.IsEmpty())
                {
                    return;
                }
 
                FinishIf(directive);
            }
 
            private void FinishIf(DirectiveTriviaSyntax directiveOpt)
            {
                var condDirectives = new List<DirectiveTriviaSyntax>();
                if (directiveOpt != null)
                {
                    condDirectives.Add(directiveOpt);
                }
 
                while (!_ifStack.IsEmpty())
                {
                    var poppedDirective = _ifStack.Pop();
                    condDirectives.Add(poppedDirective);
                    if (poppedDirective.Kind() == SyntaxKind.IfDirectiveTrivia)
                    {
                        break;
                    }
                }
 
                condDirectives.Sort((n1, n2) => n1.SpanStart.CompareTo(n2.SpanStart));
 
                foreach (var cond in condDirectives)
                {
                    _conditionalMap.Add(cond, condDirectives);
                }
 
                // #If should be the first one in sorted order
                var ifDirective = condDirectives.First();
                Debug.Assert(
                    ifDirective.Kind() is SyntaxKind.IfDirectiveTrivia or
                    SyntaxKind.ElifDirectiveTrivia or
                    SyntaxKind.ElseDirectiveTrivia);
 
                if (directiveOpt != null)
                {
                    _directiveMap.Add(directiveOpt, ifDirective);
                    _directiveMap.Add(ifDirective, directiveOpt);
                }
            }
 
            private void HandleEndRegionDirective(DirectiveTriviaSyntax directive)
            {
                if (_regionStack.IsEmpty())
                {
                    return;
                }
 
                var previousDirective = _regionStack.Pop();
 
                _directiveMap.Add(directive, previousDirective);
                _directiveMap.Add(previousDirective, directive);
            }
 
            internal void Finish()
            {
                while (_regionStack.Count > 0)
                {
                    _directiveMap.Add(_regionStack.Pop(), null);
                }
 
                while (_ifStack.Count > 0)
                {
                    FinishIf(directiveOpt: null);
                }
            }
        }
    }
}