diff --git a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioProjectOptionsProcessor.cs b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioProjectOptionsProcessor.cs index ea8a08b80278ea92e86904aebcf2144776b25f18..759af655e631f3bbb9487d368c29a8f7ca1564da 100644 --- a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioProjectOptionsProcessor.cs +++ b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioProjectOptionsProcessor.cs @@ -2,7 +2,9 @@ using System.Collections.Immutable; using System.IO; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Editor.Shared.Options; using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.Options; using Roslyn.Utilities; namespace Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem @@ -12,6 +14,7 @@ internal class VisualStudioProjectOptionsProcessor : IDisposable private readonly VisualStudioProject _project; private readonly HostWorkspaceServices _workspaceServices; private readonly ICommandLineParserService _commandLineParserService; + private IOptionService _optionService; /// /// Gate to guard all mutable fields in this class. @@ -28,11 +31,27 @@ public VisualStudioProjectOptionsProcessor(VisualStudioProject project, HostWork _project = project ?? throw new ArgumentNullException(nameof(project)); _workspaceServices = workspaceServices; _commandLineParserService = workspaceServices.GetLanguageServices(project.Language).GetRequiredService(); + _optionService = workspaceServices.GetRequiredService(); + + if (project.Language == LanguageNames.CSharp) + { + // For C#, we need to listen to the options for NRT analysis + // that can change in VS through tools > options + _optionService.OptionChanged += OptionService_OptionChanged; + } // Set up _commandLineArgumentsForCommandLine to a default. No lock taken since we're in the constructor so nothing can race. ReparseCommandLine_NoLock(); } + private void OptionService_OptionChanged(object sender, OptionChangedEventArgs e) + { + if (e.Option.Feature == FeatureOnOffOptions.UseNullableReferenceTypes.Feature) + { + UpdateProjectForNewHostValues(); + } + } + public string CommandLine { get @@ -159,6 +178,18 @@ private void UpdateProjectOptions_NoLock() compilationOptions = ComputeCompilationOptionsWithHostValues(compilationOptions, this._ruleSetFile?.Target.Value); parseOptions = ComputeParseOptionsWithHostValues(parseOptions); + if (_project.Language == LanguageNames.CSharp) + { + var useNullableReferenceTypesOption = _optionService.GetOption(FeatureOnOffOptions.UseNullableReferenceTypes); + + parseOptions = useNullableReferenceTypesOption switch + { + -1 => parseOptions.WithFeatures(new[] { KeyValuePairUtil.Create("run-nullable-analysis", "false") }), + 1 => parseOptions.WithFeatures(new[] { KeyValuePairUtil.Create("run-nullable-analysis", "true") }), + _ => parseOptions + }; + } + // For managed projects, AssemblyName has to be non-null, but the command line we get might be a partial command line // and not contain the existing value. Only update if we have one. _project.AssemblyName = _commandLineArgumentsForCommandLine.CompilationName ?? _project.AssemblyName; @@ -225,6 +256,12 @@ public void Dispose() lock (_gate) { DisposeOfRuleSetFile_NoLock(); + + if (_optionService != null) + { + _optionService.OptionChanged -= OptionService_OptionChanged; + _optionService = null; + } } } }