File: Syntax\CSharpPragmaWarningStateMap.cs
Web Access
Project: src\src\Compilers\CSharp\Portable\Microsoft.CodeAnalysis.CSharp.csproj (Microsoft.CodeAnalysis.CSharp)
// 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.
 
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Syntax;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.CSharp.Syntax
{
    /// <summary>
    /// Describes how to report a warning diagnostic.
    /// </summary>
    internal enum PragmaWarningState : byte
    {
        /// <summary>
        /// Report a diagnostic by default.
        /// Either there is no corresponding #pragma, or the action is "restore".
        /// </summary>
        Default = 0,
 
        /// <summary>
        /// Diagnostic is enabled.
        /// NOTE: this may be removed as part of https://github.com/dotnet/roslyn/issues/36550
        /// </summary>
        Enabled = 1,
 
        /// <summary>
        /// Diagnostic is disabled.
        /// </summary>
        Disabled = 2,
    }
 
    internal class CSharpPragmaWarningStateMap : AbstractWarningStateMap<PragmaWarningState>
    {
        public CSharpPragmaWarningStateMap(SyntaxTree syntaxTree) :
            base(syntaxTree)
        {
        }
 
        protected override WarningStateMapEntry[] CreateWarningStateMapEntries(SyntaxTree syntaxTree)
        {
            // Accumulate all the pragma warning directives, in source code order
            var directives = ArrayBuilder<DirectiveTriviaSyntax>.GetInstance();
            GetAllPragmaWarningDirectives(syntaxTree, directives);
 
            // Create the pragma warning map.
            WarningStateMapEntry[] result = CreatePragmaWarningStateEntries(directives);
            directives.Free();
 
            return result;
        }
 
        // Add all active #pragma warn and #nullable directives under trivia into the list, in source code order.
        private static void GetAllPragmaWarningDirectives(SyntaxTree syntaxTree, ArrayBuilder<DirectiveTriviaSyntax> directiveList)
        {
            foreach (var d in syntaxTree.GetRoot().GetDirectives())
            {
                if (!d.IsActive || d.Kind() != SyntaxKind.PragmaWarningDirectiveTrivia)
                {
                    continue;
                }
 
                var w = (PragmaWarningDirectiveTriviaSyntax)d;
 
                // Ignore directives with errors (i.e., Unrecognized #pragma directive)
                if (!w.DisableOrRestoreKeyword.IsMissing && !w.WarningKeyword.IsMissing)
                {
                    directiveList.Add(w);
                }
            }
        }
 
        // Given the ordered list of all pragma warning and nullable directives in the syntax tree, return a list of mapping entries,
        // containing the cumulative set of warnings that are disabled for that point in the source.
        // This mapping also contains a global warning option, accumulated of all #pragma up to the current line position.
        private static WarningStateMapEntry[] CreatePragmaWarningStateEntries(ArrayBuilder<DirectiveTriviaSyntax> directiveList)
        {
            var entries = new WarningStateMapEntry[directiveList.Count + 1];
            var index = 0;
 
            // Captures the mapping of a warning number to the reporting option, accumulated of all #pragma up to the current directive.
            var accumulatedSpecificWarningState = ImmutableDictionary.Create<string, PragmaWarningState>();
 
            // Captures the general reporting option, accumulated of all #pragma up to the current directive.
            var accumulatedGeneralWarningState = PragmaWarningState.Default;
 
            var current = new WarningStateMapEntry(0, PragmaWarningState.Default, accumulatedSpecificWarningState);
            entries[index] = current;
 
            while (index < directiveList.Count)
            {
                var currentDirective = directiveList[index];
                var currentPragmaDirective = (PragmaWarningDirectiveTriviaSyntax)currentDirective;
 
                // Compute the directive state
                PragmaWarningState directiveState = currentPragmaDirective.DisableOrRestoreKeyword.Kind() switch
                {
                    SyntaxKind.DisableKeyword => PragmaWarningState.Disabled,
                    SyntaxKind.RestoreKeyword => PragmaWarningState.Default,
                    SyntaxKind.EnableKeyword => PragmaWarningState.Enabled,
                    var kind => throw ExceptionUtilities.UnexpectedValue(kind)
                };
 
                // Check if this directive applies for all (e.g., #pragma warning disable)
                if (currentPragmaDirective.ErrorCodes.Count == 0)
                {
                    // Update the warning state and reset the specific one
                    accumulatedGeneralWarningState = directiveState;
                    accumulatedSpecificWarningState = ImmutableDictionary.Create<string, PragmaWarningState>();
                }
                else
                {
                    // Compute warning numbers from the current directive's codes
                    for (int x = 0; x < currentPragmaDirective.ErrorCodes.Count; x++)
                    {
                        var currentErrorCode = currentPragmaDirective.ErrorCodes[x];
                        if (currentErrorCode.IsMissing || currentErrorCode.ContainsDiagnostics)
                            continue;
 
                        var errorId = string.Empty;
                        if (currentErrorCode.Kind() == SyntaxKind.NumericLiteralExpression)
                        {
                            var token = ((LiteralExpressionSyntax)currentErrorCode).Token;
                            errorId = MessageProvider.Instance.GetIdForErrorCode((int)token.Value!);
                        }
                        else if (currentErrorCode.Kind() == SyntaxKind.IdentifierName)
                        {
                            errorId = ((IdentifierNameSyntax)currentErrorCode).Identifier.ValueText;
                        }
 
                        if (!string.IsNullOrWhiteSpace(errorId))
                        {
                            // Update the state of this error code with the current directive state
                            accumulatedSpecificWarningState = accumulatedSpecificWarningState.SetItem(errorId, directiveState);
                        }
                    }
                }
 
                current = new WarningStateMapEntry(currentDirective.Location.SourceSpan.End, accumulatedGeneralWarningState, accumulatedSpecificWarningState);
                ++index;
                entries[index] = current;
            }
 
#if DEBUG
            // Make sure the entries array is correctly sorted.
            for (int i = 1; i < entries.Length - 1; ++i)
            {
                Debug.Assert(entries[i].CompareTo(entries[i + 1]) < 0);
            }
#endif
 
            return entries;
        }
    }
}