diff --git a/src/EditorFeatures/TestUtilities/Workspaces/TestHostProject.cs b/src/EditorFeatures/TestUtilities/Workspaces/TestHostProject.cs index 10f830ccf020486cf937e755387f34cc38f4a983..b50165fb7a4d8af67dea9b7596fbc6455424da20 100644 --- a/src/EditorFeatures/TestUtilities/Workspaces/TestHostProject.cs +++ b/src/EditorFeatures/TestUtilities/Workspaces/TestHostProject.cs @@ -361,7 +361,8 @@ public ProjectInfo ToProjectInfo() this.AnalyzerConfigDocuments.Select(d => d.ToDocumentInfo()), this.IsSubmission, this.HostObjectType, - hasAllInformation: true) + hasAllInformation: true, + runAnalyzers: true) .WithDefaultNamespace(this.DefaultNamespace); } diff --git a/src/Features/Core/Portable/Diagnostics/HostAnalyzerManager.cs b/src/Features/Core/Portable/Diagnostics/HostAnalyzerManager.cs index 6622d2c3ea92f570aac19ab223fbad657e3ccea1..a57cc77cb0f4afc898ad5963a29f9da4f377cde8 100644 --- a/src/Features/Core/Portable/Diagnostics/HostAnalyzerManager.cs +++ b/src/Features/Core/Portable/Diagnostics/HostAnalyzerManager.cs @@ -165,6 +165,14 @@ public bool IsAnalyzerSuppressed(DiagnosticAnalyzer analyzer, Project project) return false; } + // If user has disabled analyzer execution for this project, we only want to execute required analyzers + // that report diagnostics with category "Compiler". + if (!project.State.RunAnalyzers && + GetDiagnosticDescriptors(analyzer).All(d => d.Category != DiagnosticCategory.Compiler)) + { + return true; + } + // don't capture project var projectId = project.Id; diff --git a/src/Features/Core/Portable/RemoveUnnecessaryImports/AbstractRemoveUnnecessaryImportsDiagnosticAnalyzer.cs b/src/Features/Core/Portable/RemoveUnnecessaryImports/AbstractRemoveUnnecessaryImportsDiagnosticAnalyzer.cs index 4cafa92f1a1bdf0820db58042dac467bf47cdfc7..e2e1f84b73f474f8f1478da635fbba18cb0c55ef 100644 --- a/src/Features/Core/Portable/RemoveUnnecessaryImports/AbstractRemoveUnnecessaryImportsDiagnosticAnalyzer.cs +++ b/src/Features/Core/Portable/RemoveUnnecessaryImports/AbstractRemoveUnnecessaryImportsDiagnosticAnalyzer.cs @@ -23,9 +23,10 @@ internal abstract class AbstractRemoveUnnecessaryImportsDiagnosticAnalyzer // The NotConfigurable custom tag ensures that user can't turn this diagnostic into a warning / error via // ruleset editor or solution explorer. Setting messageFormat to empty string ensures that we won't display // this diagnostic in the preview pane header. + // Setting category to "Compiler" ensures we always run this analyzer even when user has turned off analyzer execution for the project. private static readonly DiagnosticDescriptor s_fixableIdDescriptor = new DiagnosticDescriptor(DiagnosticFixableId, - title: "", messageFormat: "", category: "", + title: "", messageFormat: "", category: DiagnosticCategory.Compiler, defaultSeverity: DiagnosticSeverity.Hidden, isEnabledByDefault: true, customTags: WellKnownDiagnosticTags.NotConfigurable); diff --git a/src/VisualStudio/CSharp/Test/ProjectSystemShim/CPS/AdditionalPropertiesTests.cs b/src/VisualStudio/CSharp/Test/ProjectSystemShim/CPS/AdditionalPropertiesTests.cs index 336189477825d52ad5ec82a5ccb1ee16a99c81dd..eeee59c023a31d585b7a5be3cc74bf3e78a70b6d 100644 --- a/src/VisualStudio/CSharp/Test/ProjectSystemShim/CPS/AdditionalPropertiesTests.cs +++ b/src/VisualStudio/CSharp/Test/ProjectSystemShim/CPS/AdditionalPropertiesTests.cs @@ -4,9 +4,11 @@ using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.Editor.UnitTests; using Microsoft.CodeAnalysis.Test.Utilities; +using Microsoft.VisualStudio; using Microsoft.VisualStudio.LanguageServices.CSharp.Utilities; using Microsoft.VisualStudio.LanguageServices.ProjectSystem; using Microsoft.VisualStudio.LanguageServices.UnitTests.ProjectSystemShim.Framework; +using Microsoft.VisualStudio.Shell.Interop; using Roslyn.Test.Utilities; using Xunit; @@ -66,5 +68,67 @@ public void SetProperty_MaxSupportedLangVersion_CPS(LanguageVersion maxSupported Assert.Equal(attemptedVersion <= maxSupportedLangVersion, canApply); } } + + [WpfTheory] + [Trait(Traits.Feature, Traits.Features.ProjectSystemShims)] + // RunAnalyzers: Not set, RunAnalyzersDuringLiveAnalysis: Not set, ExpectedRunAnalyzers = true + [InlineData("", "", true)] + // RunAnalyzers: true, RunAnalyzersDuringLiveAnalysis: Not set, ExpectedRunAnalyzers = true + [InlineData("true", "", true)] + // RunAnalyzers: false, RunAnalyzersDuringLiveAnalysis: Not set, ExpectedRunAnalyzers = false + [InlineData("false", "", false)] + // RunAnalyzers: Not set, RunAnalyzersDuringLiveAnalysis: true, ExpectedRunAnalyzers = true + [InlineData("", "true", true)] + // RunAnalyzers: Not set, RunAnalyzersDuringLiveAnalysis: false, ExpectedRunAnalyzers = false + [InlineData("", "false", false)] + // RunAnalyzers: true, RunAnalyzersDuringLiveAnalysis: true, ExpectedRunAnalyzers = true + [InlineData("true", "true", true)] + // RunAnalyzers: true, RunAnalyzersDuringLiveAnalysis: false, ExpectedRunAnalyzers = true + [InlineData("true", "false", true)] + // RunAnalyzers: false, RunAnalyzersDuringLiveAnalysis: true, ExpectedRunAnalyzers = false + [InlineData("false", "true", false)] + // RunAnalyzers: false, RunAnalyzersDuringLiveAnalysis: false, ExpectedRunAnalyzers = false + [InlineData("false", "false", false)] + // Case insensitive + [InlineData("FALSE", "", false)] + // Invalid values ignored + [InlineData("Invalid", "INVALID", true)] + public void SetProperty_RunAnalyzersAndRunAnalyzersDuringLiveAnalysis(string runAnalyzers, string runAnalyzersDuringLiveAnalysis, bool expectedRunAnalyzers) + { + TestCPSProject(); + TestLegacyProject(); + return; + + void TestCPSProject() + { + using var environment = new TestEnvironment(); + using var cpsProject = CSharpHelpers.CreateCSharpCPSProject(environment, "Test"); + + cpsProject.SetProperty(AdditionalPropertyNames.RunAnalyzers, runAnalyzers); + cpsProject.SetProperty(AdditionalPropertyNames.RunAnalyzersDuringLiveAnalysis, runAnalyzersDuringLiveAnalysis); + + Assert.Equal(expectedRunAnalyzers, environment.Workspace.CurrentSolution.Projects.Single().State.RunAnalyzers); + } + + void TestLegacyProject() + { + using var environment = new TestEnvironment(); + + var hierarchy = environment.CreateHierarchy("CSharpProject", "Bin", projectRefPath: null, projectCapabilities: "CSharp"); + var storage = Assert.IsAssignableFrom(hierarchy); + + Assert.True(ErrorHandler.Succeeded( + storage.SetPropertyValue( + AdditionalPropertyNames.RunAnalyzers, null, (uint)_PersistStorageType.PST_PROJECT_FILE, runAnalyzers))); + + Assert.True(ErrorHandler.Succeeded( + storage.SetPropertyValue( + AdditionalPropertyNames.RunAnalyzersDuringLiveAnalysis, null, (uint)_PersistStorageType.PST_PROJECT_FILE, runAnalyzersDuringLiveAnalysis))); + + _ = CSharpHelpers.CreateCSharpProject(environment, "Test", hierarchy); + + Assert.Equal(expectedRunAnalyzers, environment.Workspace.CurrentSolution.Projects.Single().State.RunAnalyzers); + } + } } } diff --git a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/CPS/AdditionalPropertyNames.cs b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/CPS/AdditionalPropertyNames.cs index 867caa0d7a9f50c517efb59fc5fd4e2559f65635..ad695f95ab0532746afba84cb05806619da56c9e 100644 --- a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/CPS/AdditionalPropertyNames.cs +++ b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/CPS/AdditionalPropertyNames.cs @@ -12,5 +12,7 @@ internal static class AdditionalPropertyNames public const string RootNamespace = nameof(RootNamespace); public const string MaxSupportedLangVersion = nameof(MaxSupportedLangVersion); + public const string RunAnalyzers = nameof(RunAnalyzers); + public const string RunAnalyzersDuringLiveAnalysis = nameof(RunAnalyzersDuringLiveAnalysis); } } diff --git a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/Legacy/AbstractLegacyProject.cs b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/Legacy/AbstractLegacyProject.cs index 6ac6374785da2ef9b3d5b66454104b7dd261feb6..f075e79e510ea21569e631963560eff227167189 100644 --- a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/Legacy/AbstractLegacyProject.cs +++ b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/Legacy/AbstractLegacyProject.cs @@ -12,6 +12,7 @@ using Microsoft.VisualStudio.LanguageServices.Implementation.CodeModel; using Microsoft.VisualStudio.LanguageServices.Implementation.EditAndContinue; using Microsoft.VisualStudio.LanguageServices.Implementation.TaskList; +using Microsoft.VisualStudio.LanguageServices.ProjectSystem; using Microsoft.VisualStudio.Shell; using Microsoft.VisualStudio.Shell.Interop; using Roslyn.Utilities; @@ -102,6 +103,16 @@ internal abstract partial class AbstractLegacyProject : ForegroundThreadAffiniti // (e.g. through a msbuild property) VisualStudioProject.DefaultNamespace = GetRootNamespacePropertyValue(hierarchy); + if (TryGetBoolPropertyValue(hierarchy, AdditionalPropertyNames.RunAnalyzers, out var runAnayzers)) + { + VisualStudioProject.RunAnalyzers = runAnayzers; + } + + if (TryGetBoolPropertyValue(hierarchy, AdditionalPropertyNames.RunAnalyzersDuringLiveAnalysis, out var runAnayzersDuringLiveAnalysis)) + { + VisualStudioProject.RunAnalyzersDuringLiveAnalysis = runAnayzersDuringLiveAnalysis; + } + Hierarchy = hierarchy; ConnectHierarchyEvents(); RefreshBinOutputPath(); @@ -392,5 +403,28 @@ private static string GetRootNamespacePropertyValue(IVsHierarchy hierarchy) return null; } + + private static bool TryGetPropertyValue(IVsHierarchy hierarchy, string propertyName, out string propertyValue) + { + if (!(hierarchy is IVsBuildPropertyStorage storage)) + { + propertyValue = null; + return false; + } + + return ErrorHandler.Succeeded(storage.GetPropertyValue(propertyName, null, (uint)_PersistStorageType.PST_PROJECT_FILE, out propertyValue)); + } + + private static bool TryGetBoolPropertyValue(IVsHierarchy hierarchy, string propertyName, out bool? propertyValue) + { + if (!TryGetPropertyValue(hierarchy, propertyName, out var stringPropertyValue)) + { + propertyValue = null; + return false; + } + + propertyValue = bool.TryParse(stringPropertyValue, out var parsedBoolValue) ? parsedBoolValue : (bool?)null; + return true; + } } } diff --git a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioProject.cs b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioProject.cs index 62466ca53a9943a31d11631e2d1c22dff47c5f55..60bc79a29f66c48c877ef121e9efed21cbe04031 100644 --- a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioProject.cs +++ b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioProject.cs @@ -61,6 +61,13 @@ internal sealed class VisualStudioProject private string _outputRefFilePath; private string _defaultNamespace; + // Actual property values for 'RunAnalyzers' and 'RunAnalyzersDuringLiveAnalysis' properties from the project file. + // Both these properties can be used to configure running analyzers, with RunAnalyzers overriding RunAnalyzersDuringLiveAnalysis. + private bool? _runAnalyzersPropertyValue, _runAnalyzersDuringLiveAnalysisPropertyValue; + + // Effective boolean value to determine if analyzers should be executed based on _runAnalyzersPropertyValue and _runAnalyzersDuringLiveAnalysisPropertyValue. + private bool _runAnalyzers = true; + private readonly Dictionary> _allMetadataReferences = new Dictionary>(); /// @@ -292,6 +299,36 @@ internal bool HasAllInformation w => w.OnHasAllInformationChanged(Id, value)); } + internal bool? RunAnalyzers + { + get => _runAnalyzersPropertyValue; + set + { + _runAnalyzersPropertyValue = value; + UpdateRunAnalyzers(); + } + } + + internal bool? RunAnalyzersDuringLiveAnalysis + { + get => _runAnalyzersDuringLiveAnalysisPropertyValue; + set + { + _runAnalyzersDuringLiveAnalysisPropertyValue = value; + UpdateRunAnalyzers(); + } + } + + void UpdateRunAnalyzers() + { + // Property RunAnalyzers overrides RunAnalyzersDuringLiveAnalysis, and default when both properties are not set is 'true'. + var runAnalyzers = _runAnalyzersPropertyValue ?? _runAnalyzersDuringLiveAnalysisPropertyValue ?? true; + ChangeProjectProperty(ref _runAnalyzers, + runAnalyzers, + s => s.WithRunAnalyzers(Id, runAnalyzers), + w => w.OnRunAnalyzersChanged(Id, runAnalyzers)); + } + /// /// The default namespace of the project. /// diff --git a/src/VisualStudio/Core/Impl/ProjectSystem/CPS/CPSProject_IWorkspaceProjectContext.cs b/src/VisualStudio/Core/Impl/ProjectSystem/CPS/CPSProject_IWorkspaceProjectContext.cs index d60cfc08883ec1784ef5a8874fbd99f5b5c31ef5..a9e7f522ccb9c71c9c30f43065bb87981ce99fe8 100644 --- a/src/VisualStudio/Core/Impl/ProjectSystem/CPS/CPSProject_IWorkspaceProjectContext.cs +++ b/src/VisualStudio/Core/Impl/ProjectSystem/CPS/CPSProject_IWorkspaceProjectContext.cs @@ -167,6 +167,16 @@ public void SetProperty(string name, string? value) { _visualStudioProject.MaxLangVersion = value; } + else if (name == AdditionalPropertyNames.RunAnalyzers) + { + bool? boolValue = bool.TryParse(value, out var parsedBoolValue) ? parsedBoolValue : (bool?)null; + _visualStudioProject.RunAnalyzers = boolValue; + } + else if (name == AdditionalPropertyNames.RunAnalyzersDuringLiveAnalysis) + { + bool? boolValue = bool.TryParse(value, out var parsedBoolValue) ? parsedBoolValue : (bool?)null; + _visualStudioProject.RunAnalyzersDuringLiveAnalysis = boolValue; + } } public void AddMetadataReference(string referencePath, MetadataReferenceProperties properties) diff --git a/src/VisualStudio/TestUtilities2/ProjectSystemShim/Framework/MockHierarchy.vb b/src/VisualStudio/TestUtilities2/ProjectSystemShim/Framework/MockHierarchy.vb index c78255a4ff477793fc611960d3208ff067295368..9eea4aaf86500bb269d1fc7c4bf886fc51b4f57f 100644 --- a/src/VisualStudio/TestUtilities2/ProjectSystemShim/Framework/MockHierarchy.vb +++ b/src/VisualStudio/TestUtilities2/ProjectSystemShim/Framework/MockHierarchy.vb @@ -7,6 +7,7 @@ Imports Microsoft.VisualStudio.Shell Imports Roslyn.Utilities Imports System.IO Imports Moq +Imports Microsoft.VisualStudio.LanguageServices.ProjectSystem Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.ProjectSystemShim.Framework Public NotInheritable Class MockHierarchy @@ -19,6 +20,8 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.ProjectSystemShim.Fr Private _projectName As String Private _projectBinPath As String + Private _runAnalyzers As String + Private _runAnalyzersDuringLiveAnalysis As String Private ReadOnly _projectRefPath As String Private ReadOnly _projectCapabilities As String Private ReadOnly _projectMock As Mock(Of EnvDTE.Project) = New Mock(Of EnvDTE.Project)(MockBehavior.Strict) @@ -330,6 +333,12 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.ProjectSystemShim.Fr ElseIf pszPropName = "TargetRefPath" Then pbstrPropValue = _projectRefPath Return VSConstants.S_OK + ElseIf pszPropName = AdditionalPropertyNames.RunAnalyzers Then + pbstrPropValue = _runAnalyzers + Return VSConstants.S_OK + ElseIf pszPropName = AdditionalPropertyNames.RunAnalyzersDuringLiveAnalysis Then + pbstrPropValue = _runAnalyzersDuringLiveAnalysis + Return VSConstants.S_OK End If Throw New NotSupportedException($"{NameOf(MockHierarchy)}.{NameOf(GetPropertyValue)} does not support reading {pszPropName}.") @@ -342,6 +351,12 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.ProjectSystemShim.Fr ElseIf pszPropName = "TargetFileName" Then _projectName = PathUtilities.GetFileName(pszPropValue, includeExtension:=False) Return VSConstants.S_OK + ElseIf pszPropName = AdditionalPropertyNames.RunAnalyzers Then + _runAnalyzers = pszPropValue + Return VSConstants.S_OK + ElseIf pszPropName = AdditionalPropertyNames.RunAnalyzersDuringLiveAnalysis Then + _runAnalyzersDuringLiveAnalysis = pszPropValue + Return VSConstants.S_OK End If Throw New NotImplementedException() diff --git a/src/Workspaces/Core/Portable/Execution/SolutionInfoCreator.cs b/src/Workspaces/Core/Portable/Execution/SolutionInfoCreator.cs index 8adfb9d4b893d83e3919f9ebd9f64191d6993c8b..22553186620559f43c451905706ed2c9ed8a53df 100644 --- a/src/Workspaces/Core/Portable/Execution/SolutionInfoCreator.cs +++ b/src/Workspaces/Core/Portable/Execution/SolutionInfoCreator.cs @@ -73,6 +73,7 @@ public static async Task CreateProjectInfoAsync(IAssetProvider asse documentInfos, p2p, metadata, analyzers, additionalDocumentInfos, projectInfo.IsSubmission) .WithOutputRefFilePath(projectInfo.OutputRefFilePath) .WithHasAllInformation(projectInfo.HasAllInformation) + .WithRunAnalyzers(projectInfo.RunAnalyzers) .WithDefaultNamespace(projectInfo.DefaultNamespace) .WithAnalyzerConfigDocuments(analyzerConfigDocumentInfos); } diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/ProjectInfo.cs b/src/Workspaces/Core/Portable/Workspace/Solution/ProjectInfo.cs index ded8d0199d795e0768b89d961e8fd4b621bd18f0..0c453e7c784e271c2cb422ccd223de93c505773b 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/ProjectInfo.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/ProjectInfo.cs @@ -85,6 +85,11 @@ public sealed class ProjectInfo /// internal bool HasAllInformation => Attributes.HasAllInformation; + /// + /// True if we should run analyzers for this project. + /// + internal bool RunAnalyzers => Attributes.RunAnalyzers; + /// /// The initial compilation options for the project, or null if the default options should be used. /// @@ -177,7 +182,8 @@ public sealed class ProjectInfo IEnumerable? analyzerConfigDocuments, bool isSubmission, Type? hostObjectType, - bool hasAllInformation) + bool hasAllInformation, + bool runAnalyzers) { return new ProjectInfo( new ProjectAttributes( @@ -191,7 +197,8 @@ public sealed class ProjectInfo outputRefFilePath, defaultNamespace, isSubmission, - hasAllInformation), + hasAllInformation, + runAnalyzers), compilationOptions, parseOptions, documents, @@ -229,7 +236,7 @@ public sealed class ProjectInfo id, version, name, assemblyName, language, filePath, outputFilePath, outputRefFilePath: null, defaultNamespace: null, compilationOptions, parseOptions, documents, projectReferences, metadataReferences, analyzerReferences, additionalDocuments, analyzerConfigDocuments: null, - isSubmission, hostObjectType, hasAllInformation: true); + isSubmission, hostObjectType, hasAllInformation: true, runAnalyzers: true); } /// @@ -258,7 +265,7 @@ public sealed class ProjectInfo id, version, name, assemblyName, language, filePath, outputFilePath, outputRefFilePath, defaultNamespace: null, compilationOptions, parseOptions, documents, projectReferences, metadataReferences, analyzerReferences, additionalDocuments, analyzerConfigDocuments: null, - isSubmission, hostObjectType, hasAllInformation: true); + isSubmission, hostObjectType, hasAllInformation: true, runAnalyzers: true); } private ProjectInfo With( @@ -393,6 +400,11 @@ internal ProjectInfo WithHasAllInformation(bool hasAllInformation) return With(attributes: Attributes.With(hasAllInformation: hasAllInformation)); } + internal ProjectInfo WithRunAnalyzers(bool runAnalyzers) + { + return With(attributes: Attributes.With(runAnalyzers: runAnalyzers)); + } + internal string GetDebuggerDisplay() { return nameof(ProjectInfo) + " " + Name + (!string.IsNullOrWhiteSpace(FilePath) ? " " + FilePath : ""); @@ -461,6 +473,11 @@ internal class ProjectAttributes : IChecksummedObject, IObjectWritable /// public bool HasAllInformation { get; } + /// + /// True if we should run analyzers for this project. + /// + public bool RunAnalyzers { get; } + public ProjectAttributes( ProjectId id, VersionStamp version, @@ -472,7 +489,8 @@ internal class ProjectAttributes : IChecksummedObject, IObjectWritable string? outputRefFilePath, string? defaultNamespace, bool isSubmission, - bool hasAllInformation) + bool hasAllInformation, + bool runAnalyzers) { Id = id ?? throw new ArgumentNullException(nameof(id)); Name = name ?? throw new ArgumentNullException(nameof(name)); @@ -486,6 +504,7 @@ internal class ProjectAttributes : IChecksummedObject, IObjectWritable DefaultNamespace = defaultNamespace; IsSubmission = isSubmission; HasAllInformation = hasAllInformation; + RunAnalyzers = runAnalyzers; } public ProjectAttributes With( @@ -498,7 +517,8 @@ internal class ProjectAttributes : IChecksummedObject, IObjectWritable Optional outputRefPath = default, Optional defaultNamespace = default, Optional isSubmission = default, - Optional hasAllInformation = default) + Optional hasAllInformation = default, + Optional runAnalyzers = default) { var newVersion = version.HasValue ? version.Value : Version; var newName = name ?? Name; @@ -510,6 +530,7 @@ internal class ProjectAttributes : IChecksummedObject, IObjectWritable var newDefaultNamespace = defaultNamespace.HasValue ? defaultNamespace.Value : DefaultNamespace; var newIsSubmission = isSubmission.HasValue ? isSubmission.Value : IsSubmission; var newHasAllInformation = hasAllInformation.HasValue ? hasAllInformation.Value : HasAllInformation; + var newRunAnalyzers = runAnalyzers.HasValue ? runAnalyzers.Value : RunAnalyzers; if (newVersion == Version && newName == Name && @@ -520,7 +541,8 @@ internal class ProjectAttributes : IChecksummedObject, IObjectWritable newOutputRefPath == OutputRefFilePath && newDefaultNamespace == DefaultNamespace && newIsSubmission == IsSubmission && - newHasAllInformation == HasAllInformation) + newHasAllInformation == HasAllInformation && + newRunAnalyzers == RunAnalyzers) { return this; } @@ -536,7 +558,8 @@ internal class ProjectAttributes : IChecksummedObject, IObjectWritable newOutputRefPath, newDefaultNamespace, newIsSubmission, - newHasAllInformation); + newHasAllInformation, + newRunAnalyzers); } bool IObjectWritable.ShouldReuseInSerialization => true; @@ -557,6 +580,7 @@ public void WriteTo(ObjectWriter writer) writer.WriteString(DefaultNamespace); writer.WriteBoolean(IsSubmission); writer.WriteBoolean(HasAllInformation); + writer.WriteBoolean(RunAnalyzers); // TODO: once CompilationOptions, ParseOptions, ProjectReference, MetadataReference, AnalyzerReference supports // serialization, we should include those here as well. @@ -576,8 +600,9 @@ public static ProjectAttributes ReadFrom(ObjectReader reader) var defaultNamespace = reader.ReadString(); var isSubmission = reader.ReadBoolean(); var hasAllInformation = reader.ReadBoolean(); + var runAnalyzers = reader.ReadBoolean(); - return new ProjectAttributes(projectId, VersionStamp.Create(), name, assemblyName, language, filePath, outputFilePath, outputRefFilePath, defaultNamespace, isSubmission, hasAllInformation); + return new ProjectAttributes(projectId, VersionStamp.Create(), name, assemblyName, language, filePath, outputFilePath, outputRefFilePath, defaultNamespace, isSubmission, hasAllInformation, runAnalyzers); } private Checksum? _lazyChecksum; diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/ProjectState.cs b/src/Workspaces/Core/Portable/Workspace/Solution/ProjectState.cs index 7683071634f7c5b6ebfdbbe5d423f9e3a8c22be3..bb1d03c1bf400a5ee41c59ffb5a9034cd8262812 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/ProjectState.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/ProjectState.cs @@ -425,6 +425,9 @@ public async Task GetSemanticVersionAsync(CancellationToken cancel [DebuggerBrowsable(DebuggerBrowsableState.Collapsed)] public bool HasAllInformation => this.ProjectInfo.HasAllInformation; + [DebuggerBrowsable(DebuggerBrowsableState.Collapsed)] + public bool RunAnalyzers => this.ProjectInfo.RunAnalyzers; + [DebuggerBrowsable(DebuggerBrowsableState.Collapsed)] public bool HasDocuments => _documentIds.Count > 0; @@ -610,6 +613,16 @@ public ProjectState UpdateHasAllInformation(bool hasAllInformation) return this.With(projectInfo: this.ProjectInfo.WithHasAllInformation(hasAllInformation).WithVersion(this.Version.GetNewerVersion())); } + public ProjectState UpdateRunAnalyzers(bool runAnalyzers) + { + if (runAnalyzers == this.RunAnalyzers) + { + return this; + } + + return this.With(projectInfo: this.ProjectInfo.WithRunAnalyzers(runAnalyzers).WithVersion(this.Version.GetNewerVersion())); + } + public static bool IsSameLanguage(ProjectState project1, ProjectState project2) { return project1.LanguageServices == project2.LanguageServices; diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/Solution.cs b/src/Workspaces/Core/Portable/Workspace/Solution/Solution.cs index 8ea77c8a087838ed64a3c2725a27c15225316a97..c87787818a63b5e1402f3e86cb866d367e31aefd 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/Solution.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/Solution.cs @@ -435,6 +435,21 @@ internal Solution WithHasAllInformation(ProjectId projectId, bool hasAllInformat return new Solution(newState); } + /// + /// Create a new solution instance with the project specified updated to have + /// the specified runAnalyzers. + /// + internal Solution WithRunAnalyzers(ProjectId projectId, bool runAnalyzers) + { + var newState = _state.WithRunAnalyzers(projectId, runAnalyzers); + if (newState == _state) + { + return this; + } + + return new Solution(newState); + } + /// /// Create a new solution instance with the project specified updated to include /// the specified project reference. diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.cs index 4f78420045e3b0d0d194d65553c1123066ab6fbc..6e17a8ba36d1339a5eb42a3b48f371825301b26c 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.cs @@ -854,6 +854,31 @@ public SolutionState WithHasAllInformation(ProjectId projectId, bool hasAllInfor return this.ForkProject(newProject); } + /// + /// Create a new solution instance with the project specified updated to have + /// the specified runAnalyzers. + /// + public SolutionState WithRunAnalyzers(ProjectId projectId, bool runAnalyzers) + { + if (projectId == null) + { + throw new ArgumentNullException(nameof(projectId)); + } + + Debug.Assert(this.ContainsProject(projectId)); + + var oldProject = this.GetProjectState(projectId)!; + var newProject = oldProject.UpdateRunAnalyzers(runAnalyzers); + + if (oldProject == newProject) + { + return this; + } + + // fork without any change on compilation. + return this.ForkProject(newProject); + } + /// /// Create a new solution instance with the project specified updated to include /// the specified project references. diff --git a/src/Workspaces/Core/Portable/Workspace/Workspace.cs b/src/Workspaces/Core/Portable/Workspace/Workspace.cs index 1d18084dae0a3350210a510170b8f69ca59c183b..b68e516b70dde97321ffa31e75c34bcc2c08d047 100644 --- a/src/Workspaces/Core/Portable/Workspace/Workspace.cs +++ b/src/Workspaces/Core/Portable/Workspace/Workspace.cs @@ -607,6 +607,14 @@ internal void OnHasAllInformationChanged(ProjectId projectId, bool hasAllInforma this.HandleProjectChange(projectId, oldSolution => oldSolution.WithHasAllInformation(projectId, hasAllInformation)); } + /// + /// Call this method when a project's RunAnalyzers property is changed in the host environment. + /// + internal void OnRunAnalyzersChanged(ProjectId projectId, bool runAnalyzers) + { + this.HandleProjectChange(projectId, oldSolution => oldSolution.WithRunAnalyzers(projectId, runAnalyzers)); + } + /// /// Call this method when a document is added to a project in the host environment. /// diff --git a/src/Workspaces/Remote/Core/Services/SolutionCreator.cs b/src/Workspaces/Remote/Core/Services/SolutionCreator.cs index 55dc603ab3f8e9c3b55d76ff245bb2fd0e0a34de..12eaec4bb521e5f1727c3d78ee13fa5b0da6270e 100644 --- a/src/Workspaces/Remote/Core/Services/SolutionCreator.cs +++ b/src/Workspaces/Remote/Core/Services/SolutionCreator.cs @@ -281,6 +281,11 @@ private async Task UpdateProjectInfoAsync(Project project, Checksum inf project = project.Solution.WithHasAllInformation(project.Id, newProjectInfo.HasAllInformation).GetProject(project.Id); } + if (project.State.ProjectInfo.Attributes.RunAnalyzers != newProjectInfo.RunAnalyzers) + { + project = project.Solution.WithRunAnalyzers(project.Id, newProjectInfo.RunAnalyzers).GetProject(project.Id); + } + return project; }