File: ScriptOptions.cs
Web Access
Project: src\src\Scripting\Core\Microsoft.CodeAnalysis.Scripting.csproj (Microsoft.CodeAnalysis.Scripting)
// 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.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Reflection.PortableExecutable;
using System.Text;
using System.Threading;
using Microsoft.CodeAnalysis.Scripting.Hosting;
 
namespace Microsoft.CodeAnalysis.Scripting
{
    using static ParameterValidationHelpers;
 
    /// <summary>
    /// Options for creating and running scripts.
    /// </summary>
    public sealed class ScriptOptions
    {
        public static ScriptOptions Default { get; } = new ScriptOptions(
            filePath: string.Empty,
            references: GetDefaultMetadataReferences(),
            namespaces: ImmutableArray<string>.Empty,
            metadataResolver: ScriptMetadataResolver.Default,
            sourceResolver: SourceFileResolver.Default,
            emitDebugInformation: false,
            fileEncoding: null,
            OptimizationLevel.Debug,
            checkOverflow: false,
            allowUnsafe: true,
            warningLevel: 4,
            parseOptions: null);
 
        private static ImmutableArray<MetadataReference> GetDefaultMetadataReferences()
        {
            if (GacFileResolver.IsAvailable)
            {
                return ImmutableArray<MetadataReference>.Empty;
            }
 
            // These references are resolved lazily. Keep in sync with list in core csi.rsp.
            var files = new[]
            {
                "System.Collections",
                "System.Collections.Concurrent",
                "System.Console",
                "System.Diagnostics.Debug",
                "System.Diagnostics.Process",
                "System.Diagnostics.StackTrace",
                "System.Globalization",
                "System.IO",
                "System.IO.FileSystem",
                "System.IO.FileSystem.Primitives",
                "System.Reflection",
                "System.Reflection.Extensions",
                "System.Reflection.Primitives",
                "System.Runtime",
                "System.Runtime.Extensions",
                "System.Runtime.InteropServices",
                "System.Text.Encoding",
                "System.Text.Encoding.CodePages",
                "System.Text.Encoding.Extensions",
                "System.Text.RegularExpressions",
                "System.Threading",
                "System.Threading.Tasks",
                "System.Threading.Tasks.Parallel",
                "System.Threading.Thread",
                "System.ValueTuple",
            };
 
            return ImmutableArray.CreateRange(files.Select(CreateUnresolvedReference));
        }
 
        /// <summary>
        /// An array of <see cref="MetadataReference"/>s to be added to the script.
        /// </summary>
        /// <remarks>
        /// The array may contain both resolved and unresolved references (<see cref="UnresolvedMetadataReference"/>).
        /// Unresolved references are resolved when the script is about to be executed 
        /// (<see cref="Script.RunAsync(object, CancellationToken)"/>.
        /// Any resolution errors are reported at that point through <see cref="CompilationErrorException"/>.
        /// </remarks>
        public ImmutableArray<MetadataReference> MetadataReferences { get; private set; }
 
        /// <summary>
        /// <see cref="MetadataReferenceResolver"/> to be used to resolve missing dependencies, unresolved metadata references and #r directives.
        /// </summary>
        public MetadataReferenceResolver MetadataResolver { get; private set; }
 
        /// <summary>
        /// <see cref="SourceReferenceResolver"/> to be used to resolve source of scripts referenced via #load directive.
        /// </summary>
        public SourceReferenceResolver SourceResolver { get; private set; }
 
        /// <summary>
        /// The namespaces, static classes and aliases imported by the script.
        /// </summary>
        public ImmutableArray<string> Imports { get; private set; }
 
        /// <summary>
        /// Specifies whether debugging symbols should be emitted.
        /// </summary>
        public bool EmitDebugInformation { get; private set; } = false;
 
        /// <summary>
        /// Specifies the encoding to be used when debugging scripts loaded from a file, or saved to a file for debugging purposes.
        /// If it's null, the compiler will attempt to detect the necessary encoding for debugging
        /// </summary>
        public Encoding? FileEncoding { get; private set; }
 
        /// <summary>
        /// The path to the script source if it originated from a file, empty otherwise.
        /// </summary>
        public string FilePath { get; private set; }
 
        /// <summary>
        /// Specifies whether or not optimizations should be performed on the output IL.
        /// </summary>
        public OptimizationLevel OptimizationLevel { get; private set; }
 
        /// <summary>
        /// Whether bounds checking on integer arithmetic is enforced by default or not.
        /// </summary>
        public bool CheckOverflow { get; private set; }
 
        /// <summary>
        /// Allow unsafe regions (i.e. unsafe modifiers on members and unsafe blocks).
        /// </summary>
        public bool AllowUnsafe { get; private set; }
 
        /// <summary>
        /// Global warning level (from 0 to 4).
        /// </summary>
        public int WarningLevel { get; private set; }
 
        internal ParseOptions? ParseOptions { get; private set; }
 
        internal Func<string, PEStreamOptions, MetadataReferenceProperties, MetadataImageReference> CreateFromFileFunc { get; private set; }
 
        internal ScriptOptions(
            string filePath,
            ImmutableArray<MetadataReference> references,
            ImmutableArray<string> namespaces,
            MetadataReferenceResolver metadataResolver,
            SourceReferenceResolver sourceResolver,
            bool emitDebugInformation,
            Encoding? fileEncoding,
            OptimizationLevel optimizationLevel,
            bool checkOverflow,
            bool allowUnsafe,
            int warningLevel,
            ParseOptions? parseOptions,
            Func<string, PEStreamOptions, MetadataReferenceProperties, MetadataImageReference>? createFromFileFunc = null)
        {
            Debug.Assert(filePath != null);
            Debug.Assert(!references.IsDefault);
            Debug.Assert(!namespaces.IsDefault);
            Debug.Assert(metadataResolver != null);
            Debug.Assert(sourceResolver != null);
 
            FilePath = filePath;
            MetadataReferences = references;
            Imports = namespaces;
            MetadataResolver = metadataResolver;
            SourceResolver = sourceResolver;
            EmitDebugInformation = emitDebugInformation;
            FileEncoding = fileEncoding;
            OptimizationLevel = optimizationLevel;
            CheckOverflow = checkOverflow;
            AllowUnsafe = allowUnsafe;
            WarningLevel = warningLevel;
            ParseOptions = parseOptions;
            CreateFromFileFunc = createFromFileFunc ?? Script.CreateFromFile;
        }
 
        private ScriptOptions(ScriptOptions other)
            : this(filePath: other.FilePath,
                   references: other.MetadataReferences,
                   namespaces: other.Imports,
                   metadataResolver: other.MetadataResolver,
                   sourceResolver: other.SourceResolver,
                   emitDebugInformation: other.EmitDebugInformation,
                   fileEncoding: other.FileEncoding,
                   optimizationLevel: other.OptimizationLevel,
                   checkOverflow: other.CheckOverflow,
                   allowUnsafe: other.AllowUnsafe,
                   warningLevel: other.WarningLevel,
                   parseOptions: other.ParseOptions,
                   createFromFileFunc: other.CreateFromFileFunc)
        {
        }
 
        // a reference to an assembly should by default be equivalent to #r, which applies recursive global alias:
        private static readonly MetadataReferenceProperties s_assemblyReferenceProperties =
            MetadataReferenceProperties.Assembly.WithRecursiveAliases(true);
 
        /// <summary>
        /// Creates a new <see cref="ScriptOptions"/> with the <see cref="FilePath"/> changed.
        /// </summary>
        public ScriptOptions WithFilePath(string? filePath)
            => (FilePath == filePath) ? this : new ScriptOptions(this) { FilePath = filePath ?? "" };
 
        private static MetadataReference CreateUnresolvedReference(string reference)
            => new UnresolvedMetadataReference(reference, s_assemblyReferenceProperties);
 
        /// <summary>
        /// Creates a new <see cref="ScriptOptions"/> with the references changed.
        /// </summary>
        /// <exception cref="ArgumentNullException"><paramref name="references"/> is null or contains a null reference.</exception>
        private ScriptOptions WithReferences(ImmutableArray<MetadataReference> references)
            => MetadataReferences.Equals(references) ? this : new ScriptOptions(this) { MetadataReferences = CheckImmutableArray(references, nameof(references)) };
 
        /// <summary>
        /// Creates a new <see cref="ScriptOptions"/> with the references changed.
        /// </summary>
        /// <exception cref="ArgumentNullException"><paramref name="references"/> is null or contains a null reference.</exception>
        public ScriptOptions WithReferences(IEnumerable<MetadataReference> references)
            => WithReferences(ToImmutableArrayChecked(references, nameof(references)));
 
        /// <summary>
        /// Creates a new <see cref="ScriptOptions"/> with the references changed.
        /// </summary>
        /// <exception cref="ArgumentNullException"><paramref name="references"/> is null or contains a null reference.</exception>
        public ScriptOptions WithReferences(params MetadataReference[] references)
            => WithReferences((IEnumerable<MetadataReference>)references);
 
        /// <summary>
        /// Creates a new <see cref="ScriptOptions"/> with references added.
        /// </summary>
        /// <exception cref="ArgumentNullException"><paramref name="references"/> is null or contains a null reference.</exception>
        public ScriptOptions AddReferences(IEnumerable<MetadataReference> references)
            => WithReferences(ConcatChecked(MetadataReferences, references, nameof(references)));
 
        /// <summary>
        /// Creates a new <see cref="ScriptOptions"/> with references added.
        /// </summary>
        public ScriptOptions AddReferences(params MetadataReference[] references)
            => AddReferences((IEnumerable<MetadataReference>)references);
 
        /// <summary>
        /// Creates a new <see cref="ScriptOptions"/> with the references changed.
        /// </summary>
        /// <exception cref="ArgumentNullException"><paramref name="references"/> is null or contains a null reference.</exception>
        /// <exception cref="NotSupportedException">Specified assembly is not supported (e.g. it's a dynamic assembly).</exception>
        public ScriptOptions WithReferences(IEnumerable<Assembly> references)
            => WithReferences(SelectChecked(references, nameof(references), CreateFromAssembly));
 
        /// <summary>
        /// Creates a new <see cref="ScriptOptions"/> with the references changed.
        /// </summary>
        /// <exception cref="ArgumentNullException"><paramref name="references"/> is null or contains a null reference.</exception>
        /// <exception cref="NotSupportedException">Specified assembly is not supported (e.g. it's a dynamic assembly).</exception>
        public ScriptOptions WithReferences(params Assembly[] references)
            => WithReferences((IEnumerable<Assembly>)references);
 
        /// <summary>
        /// Creates a new <see cref="ScriptOptions"/> with references added.
        /// </summary>
        /// <exception cref="ArgumentNullException"><paramref name="references"/> is null or contains a null reference.</exception>
        /// <exception cref="NotSupportedException">Specified assembly is not supported (e.g. it's a dynamic assembly).</exception>
        public ScriptOptions AddReferences(IEnumerable<Assembly> references)
            => AddReferences(SelectChecked(references, nameof(references), CreateFromAssembly));
 
        private MetadataImageReference CreateFromAssembly(Assembly assembly) =>
            Script.CreateFromAssembly(assembly, s_assemblyReferenceProperties, CreateFromFileFunc);
 
        /// <summary>
        /// Creates a new <see cref="ScriptOptions"/> with references added.
        /// </summary>
        /// <exception cref="ArgumentNullException"><paramref name="references"/> is null or contains a null reference.</exception>
        /// <exception cref="NotSupportedException">Specified assembly is not supported (e.g. it's a dynamic assembly).</exception>
        public ScriptOptions AddReferences(params Assembly[] references)
            => AddReferences((IEnumerable<Assembly>)references);
 
        /// <summary>
        /// Creates a new <see cref="ScriptOptions"/> with the references changed.
        /// </summary>
        /// <exception cref="ArgumentNullException"><paramref name="references"/> is null or contains a null reference.</exception>
        public ScriptOptions WithReferences(IEnumerable<string> references)
            => WithReferences(SelectChecked(references, nameof(references), CreateUnresolvedReference));
 
        /// <summary>
        /// Creates a new <see cref="ScriptOptions"/> with the references changed.
        /// </summary>
        /// <exception cref="ArgumentNullException"><paramref name="references"/> is null or contains a null reference.</exception>
        public ScriptOptions WithReferences(params string[] references)
            => WithReferences((IEnumerable<string>)references);
 
        /// <summary>
        /// Creates a new <see cref="ScriptOptions"/> with references added.
        /// </summary>
        /// <exception cref="ArgumentNullException"><paramref name="references"/> is null or contains a null reference.</exception>
        public ScriptOptions AddReferences(IEnumerable<string> references)
            => AddReferences(SelectChecked(references, nameof(references), CreateUnresolvedReference));
 
        /// <summary>
        /// Creates a new <see cref="ScriptOptions"/> with references added.
        /// </summary>
        public ScriptOptions AddReferences(params string[] references)
            => AddReferences((IEnumerable<string>)references);
 
        /// <summary>
        /// Creates a new <see cref="ScriptOptions"/> with specified <see cref="MetadataResolver"/>.
        /// </summary>
        public ScriptOptions WithMetadataResolver(MetadataReferenceResolver resolver)
            => MetadataResolver == resolver ? this : new ScriptOptions(this) { MetadataResolver = resolver };
 
        /// <summary>
        /// Creates a new <see cref="ScriptOptions"/> with specified <see cref="SourceResolver"/>.
        /// </summary>
        public ScriptOptions WithSourceResolver(SourceReferenceResolver resolver)
            => SourceResolver == resolver ? this : new ScriptOptions(this) { SourceResolver = resolver };
 
        /// <summary>
        /// Creates a new <see cref="ScriptOptions"/> with the <see cref="Imports"/> changed.
        /// </summary>
        /// <exception cref="ArgumentNullException"><paramref name="imports"/> is null or contains a null reference.</exception>
        private ScriptOptions WithImports(ImmutableArray<string> imports)
            => Imports.Equals(imports) ? this : new ScriptOptions(this) { Imports = CheckImmutableArray(imports, nameof(imports)) };
 
        /// <summary>
        /// Creates a new <see cref="ScriptOptions"/> with the <see cref="Imports"/> changed.
        /// </summary>
        /// <exception cref="ArgumentNullException"><paramref name="imports"/> is null or contains a null reference.</exception>
        public ScriptOptions WithImports(IEnumerable<string> imports)
            => WithImports(ToImmutableArrayChecked(imports, nameof(imports)));
 
        /// <summary>
        /// Creates a new <see cref="ScriptOptions"/> with the <see cref="Imports"/> changed.
        /// </summary>
        /// <exception cref="ArgumentNullException"><paramref name="imports"/> is null or contains a null reference.</exception>
        public ScriptOptions WithImports(params string[] imports)
            => WithImports((IEnumerable<string>)imports);
 
        /// <summary>
        /// Creates a new <see cref="ScriptOptions"/> with <see cref="Imports"/> added.
        /// </summary>
        /// <exception cref="ArgumentNullException"><paramref name="imports"/> is null or contains a null reference.</exception>
        public ScriptOptions AddImports(IEnumerable<string> imports)
            => WithImports(ConcatChecked(Imports, imports, nameof(imports)));
 
        /// <summary>
        /// Creates a new <see cref="ScriptOptions"/> with <see cref="Imports"/> added.
        /// </summary>
        /// <exception cref="ArgumentNullException"><paramref name="imports"/> is null or contains a null reference.</exception>
        public ScriptOptions AddImports(params string[] imports)
            => AddImports((IEnumerable<string>)imports);
 
        /// <summary>
        /// Creates a new <see cref="ScriptOptions"/> with debugging information enabled.
        /// </summary>
        public ScriptOptions WithEmitDebugInformation(bool emitDebugInformation)
            => emitDebugInformation == EmitDebugInformation ? this : new ScriptOptions(this) { EmitDebugInformation = emitDebugInformation };
 
        /// <summary>
        /// Creates a new <see cref="ScriptOptions"/> with specified <see cref="FileEncoding"/>.
        /// </summary>
        public ScriptOptions WithFileEncoding(Encoding encoding)
            => encoding == FileEncoding ? this : new ScriptOptions(this) { FileEncoding = encoding };
 
        /// <summary>
        /// Create a new <see cref="ScriptOptions"/> with the specified <see cref="OptimizationLevel"/>.
        /// </summary>
        /// <returns></returns>
        public ScriptOptions WithOptimizationLevel(OptimizationLevel optimizationLevel)
            => optimizationLevel == OptimizationLevel ? this : new ScriptOptions(this) { OptimizationLevel = optimizationLevel };
 
        /// <summary>
        /// Create a new <see cref="ScriptOptions"/> with unsafe code regions allowed.
        /// </summary>
        public ScriptOptions WithAllowUnsafe(bool allowUnsafe)
            => allowUnsafe == AllowUnsafe ? this : new ScriptOptions(this) { AllowUnsafe = allowUnsafe };
 
        /// <summary>
        /// Create a new <see cref="ScriptOptions"/> with bounds checking on integer arithmetic enforced.
        /// </summary>
        public ScriptOptions WithCheckOverflow(bool checkOverflow)
            => checkOverflow == CheckOverflow ? this : new ScriptOptions(this) { CheckOverflow = checkOverflow };
 
        /// <summary>
        /// Create a new <see cref="ScriptOptions"/> with the specific <see cref="WarningLevel"/>.
        /// </summary>
        public ScriptOptions WithWarningLevel(int warningLevel)
            => warningLevel == WarningLevel ? this : new ScriptOptions(this) { WarningLevel = warningLevel };
 
        internal ScriptOptions WithParseOptions(ParseOptions parseOptions)
            => parseOptions == ParseOptions ? this : new ScriptOptions(this) { ParseOptions = parseOptions };
 
        internal ScriptOptions WithCreateFromFileFunc(Func<string, PEStreamOptions, MetadataReferenceProperties, MetadataImageReference> createFromFileFunc)
            => new ScriptOptions(this) { CreateFromFileFunc = createFromFileFunc };
    }
}