|
// 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 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(new[] { ' ', ',', ';' }, 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(new[] { ';' }, 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();
}
}
}
}
}
|