// 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.ComponentModel; using System.Diagnostics.CodeAnalysis; using System.Linq; using Microsoft.CodeAnalysis.PooledObjects; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis { /// /// Represents parse options common to C# and VB. /// public abstract class ParseOptions { private readonly Lazy> _lazyErrors; /// /// Specifies whether to parse as regular code files, script files or interactive code. /// public SourceCodeKind Kind { get; protected set; } /// /// Gets the specified source code kind, which is the value that was specified in /// the call to the constructor, or modified using the method. /// public SourceCodeKind SpecifiedKind { get; protected set; } /// /// Gets a value indicating whether the documentation comments are parsed and analyzed. /// public DocumentationMode DocumentationMode { get; protected set; } internal ParseOptions(SourceCodeKind kind, DocumentationMode documentationMode) { this.SpecifiedKind = kind; this.Kind = kind.MapSpecifiedToEffectiveKind(); this.DocumentationMode = documentationMode; _lazyErrors = new Lazy>(() => { var builder = ArrayBuilder.GetInstance(); ValidateOptions(builder); return builder.ToImmutableAndFree(); }); } /// /// Gets the source language ("C#" or "Visual Basic"). /// public abstract string Language { get; } /// /// Errors collection related to an incompatible set of parse options /// public ImmutableArray Errors { get { return _lazyErrors.Value; } } /// /// Creates a new options instance with the specified source code kind. /// public ParseOptions WithKind(SourceCodeKind kind) { return CommonWithKind(kind); } /// /// Performs validation of options compatibilities and generates diagnostics if needed /// internal abstract void ValidateOptions(ArrayBuilder builder); internal void ValidateOptions(ArrayBuilder builder, CommonMessageProvider messageProvider) { // Validate SpecifiedKind not Kind, to catch deprecated specified kinds: if (!SpecifiedKind.IsValid()) { builder.Add(messageProvider.CreateDiagnostic(messageProvider.ERR_BadSourceCodeKind, Location.None, SpecifiedKind.ToString())); } if (!DocumentationMode.IsValid()) { builder.Add(messageProvider.CreateDiagnostic(messageProvider.ERR_BadDocumentationMode, Location.None, DocumentationMode.ToString())); } } // It was supposed to be a protected implementation detail. // The "pattern" we have for these is the public With* method is the only public callable one, // and that forwards to the protected Common* like all the other methods in the class. [EditorBrowsable(EditorBrowsableState.Never)] public abstract ParseOptions CommonWithKind(SourceCodeKind kind); /// /// Creates a new options instance with the specified documentation mode. /// public ParseOptions WithDocumentationMode(DocumentationMode documentationMode) { return CommonWithDocumentationMode(documentationMode); } protected abstract ParseOptions CommonWithDocumentationMode(DocumentationMode documentationMode); /// /// Enable some experimental language features for testing. /// // Note: use Feature entry for known feature flags. public ParseOptions WithFeatures(IEnumerable> features) { return CommonWithFeatures(features); } protected abstract ParseOptions CommonWithFeatures(IEnumerable> features); /// /// Returns the experimental features. /// // Note: use Feature entry for known feature flags. public abstract IReadOnlyDictionary Features { get; } internal bool HasFeature(string feature) { Feature.AssertValidFeature(feature); return Features.TryGetValue(feature, out var value) && value is not null; } /// /// Names of defined preprocessor symbols. /// public abstract IEnumerable PreprocessorSymbolNames { get; } public abstract override bool Equals(object? obj); protected bool EqualsHelper([NotNullWhen(true)] ParseOptions? other) { if (object.ReferenceEquals(other, null)) { return false; } return this.SpecifiedKind == other.SpecifiedKind && this.DocumentationMode == other.DocumentationMode && FeaturesEqual(Features, other.Features) && (this.PreprocessorSymbolNames == null ? other.PreprocessorSymbolNames == null : this.PreprocessorSymbolNames.SequenceEqual(other.PreprocessorSymbolNames, StringComparer.Ordinal)); } public abstract override int GetHashCode(); protected int GetHashCodeHelper() { return Hash.Combine((int)this.SpecifiedKind, Hash.Combine((int)this.DocumentationMode, Hash.Combine(HashFeatures(this.Features), Hash.Combine(Hash.CombineValues(this.PreprocessorSymbolNames, StringComparer.Ordinal), 0)))); } private static bool FeaturesEqual(IReadOnlyDictionary features, IReadOnlyDictionary other) { if (ReferenceEquals(features, other)) { return true; } if (features.Count != other.Count) { return false; } foreach (var (key, value) in features) { if (!other.TryGetValue(key, out var otherValue) || value != otherValue) { return false; } } return true; } private static int HashFeatures(IReadOnlyDictionary features) { int value = 0; foreach (var kv in features) { value = Hash.Combine(kv.Key.GetHashCode(), Hash.Combine(kv.Value.GetHashCode(), value)); } return value; } public static bool operator ==(ParseOptions? left, ParseOptions? right) { return object.Equals(left, right); } public static bool operator !=(ParseOptions? left, ParseOptions? right) { return !object.Equals(left, right); } } }