File: CSharpParseOptions.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;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using Microsoft.CodeAnalysis.PooledObjects;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.CSharp
{
    /// <summary>
    /// This class stores several source parsing related options and offers access to their values.
    /// </summary>
    public sealed class CSharpParseOptions : ParseOptions, IEquatable<CSharpParseOptions>
    {
        /// <summary>
        /// The default parse options.
        /// </summary>
        public static CSharpParseOptions Default { get; } = new CSharpParseOptions();
 
        private ImmutableDictionary<string, string> _features;
        private ImmutableArray<ImmutableArray<string>> _interceptorsNamespaces;
 
        /// <summary>
        /// Gets the effective language version, which the compiler uses to select the
        /// language rules to apply to the program.
        /// </summary>
        public LanguageVersion LanguageVersion { get; private set; }
 
        /// <summary>
        /// Gets the specified language version, which is the value that was specified in
        /// the call to the constructor, or modified using the <see cref="WithLanguageVersion"/> method,
        /// or provided on the command line.
        /// </summary>
        public LanguageVersion SpecifiedLanguageVersion { get; private set; }
 
        internal ImmutableArray<string> PreprocessorSymbols { get; private set; }
 
        /// <summary>
        /// Gets the names of defined preprocessor symbols.
        /// </summary>
        public override IEnumerable<string> PreprocessorSymbolNames
        {
            get { return PreprocessorSymbols; }
        }
 
        public CSharpParseOptions(
            LanguageVersion languageVersion = LanguageVersion.Default,
            DocumentationMode documentationMode = DocumentationMode.Parse,
            SourceCodeKind kind = SourceCodeKind.Regular,
            IEnumerable<string>? preprocessorSymbols = null)
            : this(languageVersion,
                  documentationMode,
                  kind,
                  preprocessorSymbols.ToImmutableArrayOrEmpty(),
                  ImmutableDictionary<string, string>.Empty)
        {
        }
 
        internal CSharpParseOptions(
            LanguageVersion languageVersion,
            DocumentationMode documentationMode,
            SourceCodeKind kind,
            ImmutableArray<string> preprocessorSymbols,
            IReadOnlyDictionary<string, string>? features)
            : base(kind, documentationMode)
        {
            this.SpecifiedLanguageVersion = languageVersion;
            this.LanguageVersion = languageVersion.MapSpecifiedToEffectiveVersion();
            this.PreprocessorSymbols = preprocessorSymbols.ToImmutableArrayOrEmpty();
            _features = features?.ToImmutableDictionary() ?? ImmutableDictionary<string, string>.Empty;
        }
 
        private CSharpParseOptions(CSharpParseOptions other) : this(
            languageVersion: other.SpecifiedLanguageVersion,
            documentationMode: other.DocumentationMode,
            kind: other.Kind,
            preprocessorSymbols: other.PreprocessorSymbols,
            features: other.Features)
        {
        }
 
        public override string Language => LanguageNames.CSharp;
 
        public new CSharpParseOptions WithKind(SourceCodeKind kind)
        {
            if (kind == this.SpecifiedKind)
            {
                return this;
            }
 
            var effectiveKind = kind.MapSpecifiedToEffectiveKind();
            return new CSharpParseOptions(this) { SpecifiedKind = kind, Kind = effectiveKind };
        }
 
        public CSharpParseOptions WithLanguageVersion(LanguageVersion version)
        {
            if (version == this.SpecifiedLanguageVersion)
            {
                return this;
            }
 
            var effectiveLanguageVersion = version.MapSpecifiedToEffectiveVersion();
            return new CSharpParseOptions(this) { SpecifiedLanguageVersion = version, LanguageVersion = effectiveLanguageVersion };
        }
 
        public CSharpParseOptions WithPreprocessorSymbols(IEnumerable<string>? preprocessorSymbols)
        {
            return WithPreprocessorSymbols(preprocessorSymbols.AsImmutableOrNull());
        }
 
        public CSharpParseOptions WithPreprocessorSymbols(params string[]? preprocessorSymbols)
        {
            return WithPreprocessorSymbols(preprocessorSymbols.AsImmutableOrNull());
        }
 
        public CSharpParseOptions WithPreprocessorSymbols(ImmutableArray<string> symbols)
        {
            if (symbols.IsDefault)
            {
                symbols = ImmutableArray<string>.Empty;
            }
 
            if (symbols.Equals(this.PreprocessorSymbols))
            {
                return this;
            }
 
            return new CSharpParseOptions(this) { PreprocessorSymbols = symbols };
        }
 
        public new CSharpParseOptions WithDocumentationMode(DocumentationMode documentationMode)
        {
            if (documentationMode == this.DocumentationMode)
            {
                return this;
            }
 
            return new CSharpParseOptions(this) { DocumentationMode = documentationMode };
        }
 
        public override ParseOptions CommonWithKind(SourceCodeKind kind)
        {
            return WithKind(kind);
        }
 
        protected override ParseOptions CommonWithDocumentationMode(DocumentationMode documentationMode)
        {
            return WithDocumentationMode(documentationMode);
        }
 
        protected override ParseOptions CommonWithFeatures(IEnumerable<KeyValuePair<string, string>>? features)
        {
            return WithFeatures(features);
        }
 
        /// <summary>
        /// Enable some experimental language features for testing.
        /// </summary>
        public new CSharpParseOptions WithFeatures(IEnumerable<KeyValuePair<string, string>>? features)
        {
            ImmutableDictionary<string, string> dictionary =
                features?.ToImmutableDictionary(StringComparer.OrdinalIgnoreCase)
                ?? ImmutableDictionary<string, string>.Empty;
 
            return new CSharpParseOptions(this) { _features = dictionary };
        }
 
        public override IReadOnlyDictionary<string, string> Features
        {
            get
            {
                return _features;
            }
        }
 
        internal ImmutableArray<ImmutableArray<string>> InterceptorsNamespaces
        {
            get
            {
                if (!_interceptorsNamespaces.IsDefault)
                {
                    return _interceptorsNamespaces;
                }
 
                // e.g. [["System", "Threading"], ["System", "Collections"]]
                ImmutableArray<ImmutableArray<string>> previewNamespaces = Features.TryGetValue("InterceptorsNamespaces", out var namespaces) && namespaces.Length > 0
                    ? makeNamespaces(namespaces)
                    : ImmutableArray<ImmutableArray<string>>.Empty;
 
                ImmutableInterlocked.InterlockedInitialize(ref _interceptorsNamespaces, previewNamespaces);
                return previewNamespaces;
 
                static ImmutableArray<ImmutableArray<string>> makeNamespaces(string namespaces)
                {
                    var builder = ArrayBuilder<ImmutableArray<string>>.GetInstance();
                    var singleNamespaceBuilder = ArrayBuilder<string>.GetInstance();
                    int currentIndex = 0;
                    while (currentIndex < namespaces.Length && namespaces.IndexOf(';', currentIndex) is not -1 and var semicolonIndex)
                    {
                        addSingleNamespaceParts(builder, singleNamespaceBuilder, namespaces.AsSpan(currentIndex, semicolonIndex - currentIndex));
                        currentIndex = semicolonIndex + 1;
                    }
 
                    addSingleNamespaceParts(builder, singleNamespaceBuilder, namespaces.AsSpan(currentIndex));
                    singleNamespaceBuilder.Free();
                    return builder.ToImmutableAndFree();
                }
 
                static void addSingleNamespaceParts(ArrayBuilder<ImmutableArray<string>> namespacesBuilder, ArrayBuilder<string> singleNamespaceBuilder, ReadOnlySpan<char> @namespace)
                {
                    int currentIndex = 0;
                    while (currentIndex < @namespace.Length && @namespace.IndexOf('.', currentIndex) is not -1 and var dotIndex)
                    {
                        singleNamespaceBuilder.Add(@namespace.Slice(currentIndex, dotIndex - currentIndex).ToString());
                        currentIndex = dotIndex + 1;
                    }
                    singleNamespaceBuilder.Add(@namespace.Slice(currentIndex).ToString());
 
                    if (!namespacesBuilder.Any(ns => ns.SequenceEqual(singleNamespaceBuilder)))
                    {
                        namespacesBuilder.Add(singleNamespaceBuilder.ToImmutable());
                    }
 
                    singleNamespaceBuilder.Clear();
                }
            }
        }
 
        internal override void ValidateOptions(ArrayBuilder<Diagnostic> builder)
        {
            ValidateOptions(builder, MessageProvider.Instance);
 
            // Validate LanguageVersion not SpecifiedLanguageVersion, after Latest/Default has been converted:
            if (!LanguageVersion.IsValid())
            {
                builder.Add(Diagnostic.Create(MessageProvider.Instance, (int)ErrorCode.ERR_BadLanguageVersion, LanguageVersion.ToString()));
            }
 
            if (!PreprocessorSymbols.IsDefaultOrEmpty)
            {
                foreach (var symbol in PreprocessorSymbols)
                {
                    if (symbol == null)
                    {
                        builder.Add(Diagnostic.Create(MessageProvider.Instance, (int)ErrorCode.ERR_InvalidPreprocessingSymbol, "null"));
                    }
                    else if (!SyntaxFacts.IsValidIdentifier(symbol))
                    {
                        builder.Add(Diagnostic.Create(MessageProvider.Instance, (int)ErrorCode.ERR_InvalidPreprocessingSymbol, symbol));
                    }
                }
            }
        }
 
        internal bool IsFeatureEnabled(MessageID feature)
        {
            string? featureFlag = feature.RequiredFeature();
            if (featureFlag != null)
            {
                return Features.ContainsKey(featureFlag);
            }
            LanguageVersion availableVersion = LanguageVersion;
            LanguageVersion requiredVersion = feature.RequiredVersion();
            return availableVersion >= requiredVersion;
        }
 
        public override bool Equals(object? obj)
        {
            return this.Equals(obj as CSharpParseOptions);
        }
 
        public bool Equals(CSharpParseOptions? other)
        {
            if (object.ReferenceEquals(this, other))
            {
                return true;
            }
 
            if (!base.EqualsHelper(other))
            {
                return false;
            }
 
            return this.SpecifiedLanguageVersion == other.SpecifiedLanguageVersion;
        }
 
        public override int GetHashCode()
        {
            return
                Hash.Combine(base.GetHashCodeHelper(),
                Hash.Combine((int)this.SpecifiedLanguageVersion, 0));
        }
    }
}