File: ProjectSystemShim\CSharpProjectShim.OptionsProcessor.cs
Web Access
Project: src\src\VisualStudio\CSharp\Impl\Microsoft.VisualStudio.LanguageServices.CSharp_klvi2agp_wpftmp.csproj (Microsoft.VisualStudio.LanguageServices.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.Diagnostics.CodeAnalysis;
using System.IO;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Workspaces.ProjectSystem;
using Microsoft.VisualStudio.LanguageServices.CSharp.ProjectSystemShim.Interop;
using Roslyn.Utilities;
 
namespace Microsoft.VisualStudio.LanguageServices.CSharp.ProjectSystemShim;
 
internal partial class CSharpProjectShim
{
    private sealed class OptionsProcessor : ProjectSystemProjectOptionsProcessor
    {
        private readonly ProjectSystemProject _projectSystemProject;
 
        private readonly object[] _options = new object[(int)CompilerOptions.LARGEST_OPTION_ID];
        private string? _mainTypeName;
        private OutputKind _outputKind;
 
        public OptionsProcessor(ProjectSystemProject projectSystemProject, SolutionServices workspaceServices)
            : base(projectSystemProject, workspaceServices)
        {
            _projectSystemProject = projectSystemProject;
        }
 
        public object this[CompilerOptions compilerOption]
        {
            get
            {
                return _options[(int)compilerOption];
            }
 
            set
            {
                if (object.Equals(_options[(int)compilerOption], value))
                {
                    return;
                }
 
                _options[(int)compilerOption] = value;
                UpdateProjectForNewHostValues();
            }
        }
 
        protected override CompilationOptions ComputeCompilationOptionsWithHostValues(CompilationOptions compilationOptions, IRuleSetFile? ruleSetFile)
        {
            IDictionary<string, ReportDiagnostic>? ruleSetSpecificDiagnosticOptions;
 
            // Get options from the ruleset file, if any, first. That way project-specific
            // options can override them.
            ReportDiagnostic? ruleSetGeneralDiagnosticOption = null;
 
            // TODO: merge this core logic back down to the base of OptionsProcessor, since this should be the same for all languages. The CompilationOptions
            // would then already contain the right information, and could be updated accordingly by the language-specific logic.
            if (ruleSetFile != null)
            {
                ruleSetGeneralDiagnosticOption = ruleSetFile.GetGeneralDiagnosticOption();
                ruleSetSpecificDiagnosticOptions = new Dictionary<string, ReportDiagnostic>(ruleSetFile.GetSpecificDiagnosticOptions());
            }
            else
            {
                ruleSetSpecificDiagnosticOptions = new Dictionary<string, ReportDiagnostic>();
            }
 
            ReportDiagnostic generalDiagnosticOption;
            var warningsAreErrors = GetNullableBooleanOption(CompilerOptions.OPTID_WARNINGSAREERRORS);
            if (warningsAreErrors.HasValue)
            {
                generalDiagnosticOption = warningsAreErrors.Value ? ReportDiagnostic.Error : ReportDiagnostic.Default;
            }
            else if (ruleSetGeneralDiagnosticOption.HasValue)
            {
                generalDiagnosticOption = ruleSetGeneralDiagnosticOption.Value;
            }
            else
            {
                generalDiagnosticOption = ReportDiagnostic.Default;
            }
 
            // Start with the rule set options
            var diagnosticOptions = new Dictionary<string, ReportDiagnostic>(ruleSetSpecificDiagnosticOptions);
 
            // Update the specific options based on the general settings
            if (warningsAreErrors.HasValue && warningsAreErrors.Value == true)
            {
                foreach (var pair in ruleSetSpecificDiagnosticOptions)
                {
                    if (pair.Value == ReportDiagnostic.Warn)
                    {
                        diagnosticOptions[pair.Key] = ReportDiagnostic.Error;
                    }
                }
            }
 
            // Update the specific options based on the specific settings
            foreach (var diagnosticID in ParseWarningCodes(CompilerOptions.OPTID_WARNASERRORLIST))
            {
                diagnosticOptions[diagnosticID] = ReportDiagnostic.Error;
            }
 
            foreach (var diagnosticID in ParseWarningCodes(CompilerOptions.OPTID_WARNNOTASERRORLIST))
            {
                if (ruleSetSpecificDiagnosticOptions.TryGetValue(diagnosticID, out var ruleSetOption))
                {
                    diagnosticOptions[diagnosticID] = ruleSetOption;
                }
                else
                {
                    diagnosticOptions[diagnosticID] = ReportDiagnostic.Default;
                }
            }
 
            foreach (var diagnosticID in ParseWarningCodes(CompilerOptions.OPTID_NOWARNLIST))
            {
                diagnosticOptions[diagnosticID] = ReportDiagnostic.Suppress;
            }
 
            if (!Enum.TryParse(GetStringOption(CompilerOptions.OPTID_PLATFORM, ""), ignoreCase: true, result: out Platform platform))
            {
                platform = Platform.AnyCpu;
            }
 
            if (!int.TryParse(GetStringOption(CompilerOptions.OPTID_WARNINGLEVEL, defaultValue: ""), out var warningLevel))
            {
                warningLevel = 4;
            }
 
            // TODO: appConfigPath: GetFilePathOption(CompilerOptions.OPTID_FUSIONCONFIG), bug #869604
 
            return ((CSharpCompilationOptions)compilationOptions).WithAllowUnsafe(GetBooleanOption(CompilerOptions.OPTID_UNSAFE))
                .WithOverflowChecks(GetBooleanOption(CompilerOptions.OPTID_CHECKED))
                .WithCryptoKeyContainer(GetStringOption(CompilerOptions.OPTID_KEYNAME, defaultValue: null))
                .WithCryptoKeyFile(GetFilePathRelativeOption(CompilerOptions.OPTID_KEYFILE))
                .WithDelaySign(GetNullableBooleanOption(CompilerOptions.OPTID_DELAYSIGN))
                .WithGeneralDiagnosticOption(generalDiagnosticOption)
                .WithMainTypeName(_mainTypeName)
                .WithModuleName(GetStringOption(CompilerOptions.OPTID_MODULEASSEMBLY, defaultValue: null))
                .WithOptimizationLevel(GetBooleanOption(CompilerOptions.OPTID_OPTIMIZATIONS) ? OptimizationLevel.Release : OptimizationLevel.Debug)
                .WithOutputKind(_outputKind)
                .WithPlatform(platform)
                .WithSpecificDiagnosticOptions(diagnosticOptions)
                .WithWarningLevel(warningLevel);
        }
 
        private static string GetIdForErrorCode(int errorCode)
            => "CS" + errorCode.ToString("0000");
 
        private IEnumerable<string> ParseWarningCodes(CompilerOptions compilerOptions)
        {
            Contract.ThrowIfFalse(
                compilerOptions is CompilerOptions.OPTID_NOWARNLIST or
                CompilerOptions.OPTID_WARNASERRORLIST or
                CompilerOptions.OPTID_WARNNOTASERRORLIST);
 
            foreach (var warning in GetStringOption(compilerOptions, defaultValue: "").Split([' ', ',', ';'], StringSplitOptions.RemoveEmptyEntries))
            {
                var warningStringID = warning;
                if (int.TryParse(warning, out var warningId))
                {
                    warningStringID = GetIdForErrorCode(warningId);
                }
 
                yield return warningStringID;
            }
        }
 
        private bool? GetNullableBooleanOption(CompilerOptions optionID)
            => (bool?)_options[(int)optionID];
 
        private bool GetBooleanOption(CompilerOptions optionID)
            => GetNullableBooleanOption(optionID).GetValueOrDefault(defaultValue: false);
 
        private string? GetFilePathRelativeOption(CompilerOptions optionID)
        {
            var path = GetStringOption(optionID, defaultValue: null);
 
            if (string.IsNullOrEmpty(path))
            {
                return null;
            }
 
            var directory = Path.GetDirectoryName(_projectSystemProject.FilePath);
 
            if (!string.IsNullOrEmpty(directory))
            {
                return FileUtilities.ResolveRelativePath(path, directory);
            }
 
            return null;
        }
 
        [return: NotNullIfNotNull(nameof(defaultValue))]
        private string? GetStringOption(CompilerOptions optionID, string? defaultValue)
        {
            var value = (string)_options[(int)optionID];
 
            if (string.IsNullOrEmpty(value))
            {
                return defaultValue;
            }
            else
            {
                return value;
            }
        }
 
        protected override ParseOptions ComputeParseOptionsWithHostValues(ParseOptions parseOptions)
        {
            var symbols = GetStringOption(CompilerOptions.OPTID_CCSYMBOLS, defaultValue: "").Split([';'], StringSplitOptions.RemoveEmptyEntries);
 
            // The base implementation of OptionsProcessor already tried this, but it didn't have the real documentation
            // path so we have to do it a second time
            var documentationMode = DocumentationMode.Parse;
            if (GetStringOption(CompilerOptions.OPTID_XML_DOCFILE, defaultValue: null) != null)
            {
                documentationMode = DocumentationMode.Diagnose;
            }
 
            LanguageVersionFacts.TryParse(GetStringOption(CompilerOptions.OPTID_COMPATIBILITY, defaultValue: ""), out var languageVersion);
 
            return ((CSharpParseOptions)parseOptions).WithKind(SourceCodeKind.Regular)
                .WithLanguageVersion(languageVersion)
                .WithPreprocessorSymbols(symbols.AsImmutable())
                .WithDocumentationMode(documentationMode);
        }
 
        public void SetOutputFileType(OutputFileType fileType)
        {
            var newOutputKind = fileType switch
            {
                OutputFileType.Console => OutputKind.ConsoleApplication,
                OutputFileType.Windows => OutputKind.WindowsApplication,
                OutputFileType.Library => OutputKind.DynamicallyLinkedLibrary,
                OutputFileType.Module => OutputKind.NetModule,
                OutputFileType.AppContainer => OutputKind.WindowsRuntimeApplication,
                OutputFileType.WinMDObj => OutputKind.WindowsRuntimeMetadata,
                _ => throw new ArgumentException("fileType was not a valid OutputFileType", nameof(fileType)),
            };
 
            if (_outputKind != newOutputKind)
            {
                _outputKind = newOutputKind;
                UpdateProjectForNewHostValues();
            }
        }
 
        public void SetMainTypeName(string? mainTypeName)
        {
            if (_mainTypeName != mainTypeName)
            {
                _mainTypeName = mainTypeName;
                UpdateProjectForNewHostValues();
            }
        }
    }
}