diff --git a/src/Microsoft.VisualStudio.ProjectSystem.Managed.VS/ProjectSystem/VS/LanguageServices/Handlers/AbstractEvaluationCommandLineHandler.cs b/src/Microsoft.VisualStudio.ProjectSystem.Managed.VS/ProjectSystem/VS/LanguageServices/Handlers/AbstractEvaluationCommandLineHandler.cs index 8fe24dd4920..1bfc202f509 100644 --- a/src/Microsoft.VisualStudio.ProjectSystem.Managed.VS/ProjectSystem/VS/LanguageServices/Handlers/AbstractEvaluationCommandLineHandler.cs +++ b/src/Microsoft.VisualStudio.ProjectSystem.Managed.VS/ProjectSystem/VS/LanguageServices/Handlers/AbstractEvaluationCommandLineHandler.cs @@ -183,10 +183,7 @@ private void ApplyChangesToContext(IWorkspaceProjectContext context, IProjectCha foreach (string includePath in difference.ChangedItems) { - UpdateInContextIfPresent(context, includePath, previousMetadata, currentMetadata, isActiveContext, logger); - - // TODO: Check for changes in the metadata indicating if we should ignore the file - // in the current configuration. + HandleEvaluationMetadataChange(context, includePath, previousMetadata, currentMetadata, isActiveContext, logger); } } @@ -223,16 +220,38 @@ private void AddToContextIfNotPresent(IWorkspaceProjectContext context, string i } } - private void UpdateInContextIfPresent(IWorkspaceProjectContext context, string includePath, IImmutableDictionary> previousMetadata, IImmutableDictionary> currentMetadata, bool isActiveContext, IManagedProjectDiagnosticOutputService logger) + /// + /// This should only be called for evaluation changes. The items we get from design-time builds represent + /// command line arguments and won't have metadata. + /// + private void HandleEvaluationMetadataChange(IWorkspaceProjectContext context, string includePath, IImmutableDictionary> previousMetadata, IImmutableDictionary> currentMetadata, bool isActiveContext, IManagedProjectDiagnosticOutputService logger) { - string fullPath = _project.MakeRooted(includePath); - - if (_paths.Contains(fullPath)) + // A change in ExcludeFromCurrentConfiguration metadata needs to be processed as an add or remove rather + // than an update, so check for that first. + bool previouslyIncluded = IsItemInCurrentConfiguration(includePath, previousMetadata); + bool currentlyIncluded = IsItemInCurrentConfiguration(includePath, currentMetadata); + + if (previouslyIncluded && !currentlyIncluded) + { + RemoveFromContextIfPresent(context, includePath, logger); + } + else if (!previouslyIncluded && currentlyIncluded) { - IImmutableDictionary previousItemMetadata = previousMetadata.GetValueOrDefault(includePath, ImmutableStringDictionary.EmptyOrdinal); - IImmutableDictionary currentItemMetadata = currentMetadata.GetValueOrDefault(includePath, ImmutableStringDictionary.EmptyOrdinal); + AddToContextIfNotPresent(context, includePath, currentMetadata, isActiveContext, logger); + } + else + { + // No change to ExcludeFromCurrentConfiguration; handle as an update. + + string fullPath = _project.MakeRooted(includePath); - UpdateInContext(context, fullPath, previousItemMetadata, currentItemMetadata, isActiveContext, logger); + if (_paths.Contains(fullPath)) + { + IImmutableDictionary previousItemMetadata = previousMetadata.GetValueOrDefault(includePath, ImmutableStringDictionary.EmptyOrdinal); + IImmutableDictionary currentItemMetadata = currentMetadata.GetValueOrDefault(includePath, ImmutableStringDictionary.EmptyOrdinal); + + UpdateInContext(context, fullPath, previousItemMetadata, currentItemMetadata, isActiveContext, logger); + } } } diff --git a/tests/Microsoft.VisualStudio.ProjectSystem.Managed.VS.UnitTests/ProjectSystem/VS/LanguageServices/Handlers/AbstractEvaluationCommandLineHandlerTests.cs b/tests/Microsoft.VisualStudio.ProjectSystem.Managed.VS.UnitTests/ProjectSystem/VS/LanguageServices/Handlers/AbstractEvaluationCommandLineHandlerTests.cs index 148bc2e940c..b0eeaecbe43 100644 --- a/tests/Microsoft.VisualStudio.ProjectSystem.Managed.VS.UnitTests/ProjectSystem/VS/LanguageServices/Handlers/AbstractEvaluationCommandLineHandlerTests.cs +++ b/tests/Microsoft.VisualStudio.ProjectSystem.Managed.VS.UnitTests/ProjectSystem/VS/LanguageServices/Handlers/AbstractEvaluationCommandLineHandlerTests.cs @@ -99,7 +99,7 @@ public void AddEvaluationChanges_CanAddItemWithMetadata() var difference = IProjectChangeDiffFactory.WithAddedItems("A.cs"); var metadata = MetadataFactory.Create("A.cs", ("Name", "Value")); - ApplyProjectEvaluation(context, handler, 1, difference, metadata); + ApplyProjectEvaluation(context, handler, 1, difference, metadata: metadata); var result = handler.Files[@"C:\Project\A.cs"]; @@ -117,7 +117,7 @@ public void AddEvaluationChanges_ItemsWithExclusionMetadataAreIgnored() .Add("B.cs", ("ExcludeFromCurrentConfiguration", "false")); - ApplyProjectEvaluation(context, handler, 1, difference, metadata); + ApplyProjectEvaluation(context, handler, 1, difference, metadata: metadata); string[] expectedFiles = new[] { @"C:\Project\B.cs", @"C:\Project\C.cs" }; Assert.Equal(expectedFiles.OrderBy(f => f), handler.FileNames.OrderBy(f => f)); @@ -311,7 +311,7 @@ public void ApplyProjectEvaluationChanges_WithExistingEvaluationChanges_CanAddCh var difference = IProjectChangeDiffFactory.WithChangedItems(file); var metadata = MetadataFactory.Create(file, ("Name", "Value")); - ApplyProjectEvaluation(context, handler, 2, difference, metadata); + ApplyProjectEvaluation(context, handler, 2, difference, metadata: metadata); var result = handler.Files[@"C:\Project\A.cs"]; @@ -372,10 +372,56 @@ public void ApplyProjectBuild_WhenOlderEvaluationChangesWithRemovedConflict_Desi Assert.Single(handler.FileNames, @"C:\Project\Source.cs"); } - private static void ApplyProjectEvaluation(IWorkspaceProjectContext context, AbstractEvaluationCommandLineHandler handler, IComparable version, IProjectChangeDiff difference, IImmutableDictionary>? metadata = null) + [Fact] + public void ApplyProjectEvaluation_ChangingExclusionMetadata_IncludesFile() + { + var handler = CreateInstance(@"C:\Project\Project.csproj"); + var context = IWorkspaceProjectContextMockFactory.Create(); + + var metadata = MetadataFactory.Create("Source.cs", ("ExcludeFromCurrentConfiguration", "true")); + + ApplyProjectEvaluation(context, handler, version: 0, IProjectChangeDiffFactory.WithAddedItems("Source.cs"), metadata: metadata); + + Assert.Empty(handler.FileNames); + + var previousMetadata = metadata; + metadata = MetadataFactory.Create("Source.cs", ("ExcludeFromCurrentConfiguration", "false")); + + ApplyProjectEvaluation(context, handler, version: 1, IProjectChangeDiffFactory.WithChangedItems("Source.cs"), previousMetadata, metadata); + + Assert.Single(handler.FileNames, @"C:\Project\Source.cs"); + } + + [Fact] + public void ApplyProjectEvaluation_ChangingExclusionMetadata_ExcludesFile() + { + var handler = CreateInstance(@"C:\Project\Project.csproj"); + var context = IWorkspaceProjectContextMockFactory.Create(); + + var metadata = MetadataFactory.Create("Source.cs", ("ExcludeFromCurrentConfiguration", "false")); + + ApplyProjectEvaluation(context, handler, version: 0, IProjectChangeDiffFactory.WithAddedItems("Source.cs"), metadata: metadata); + + Assert.Single(handler.FileNames, @"C:\Project\Source.cs"); + + var previousMetadata = metadata; + metadata = MetadataFactory.Create("Source.cs", ("ExcludeFromCurrentConfiguration", "true")); + + ApplyProjectEvaluation(context, handler, version: 1, IProjectChangeDiffFactory.WithChangedItems("Source.cs"), previousMetadata, metadata); + + Assert.Empty(handler.FileNames); + } + + private static void ApplyProjectEvaluation( + IWorkspaceProjectContext context, + AbstractEvaluationCommandLineHandler handler, + IComparable version, + IProjectChangeDiff difference, + IImmutableDictionary>? previousMetadata = null, + IImmutableDictionary>? metadata = null) { metadata ??= ImmutableDictionary>.Empty; - var previousMetadata = ImmutableDictionary>.Empty; + previousMetadata ??= ImmutableDictionary>.Empty; bool isActiveContext = true; var logger = IManagedProjectDiagnosticOutputServiceFactory.Create();