Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Remove Unused References dialog #49862

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
b90c777
Cleanup ReferenceCleanupService
JoeRobich Dec 9, 2020
655c158
Add UnusedReferenceService to determine unused references
JoeRobich Dec 9, 2020
93e4bb1
Add ProjectAssets model and reader
JoeRobich Dec 9, 2020
98f10c7
Add string resources
JoeRobich Dec 9, 2020
0806d81
Add unused reference columns definitions and table provider
JoeRobich Dec 9, 2020
9e83175
Add remove unused references dialog
JoeRobich Dec 9, 2020
0a4255e
Add Remove Unused References command.
JoeRobich Dec 9, 2020
d0130fb
Add command handler for the remove unused references command
JoeRobich Dec 9, 2020
4cc3f12
Remove nullable pragmas
JoeRobich Dec 9, 2020
c818664
Handle case where compilation is null
JoeRobich Dec 15, 2020
1068618
Add no unused references found popup
JoeRobich Dec 15, 2020
6a1c0fb
Add comfirmation dialog when there are reference changes
JoeRobich Dec 16, 2020
cfac803
Add option to show Remove Unused Reference command
JoeRobich Dec 17, 2020
fe8b901
Add ellipses to command text
JoeRobich Dec 17, 2020
491c32e
Remove TFM argument from UpdateReferencesAsync
JoeRobich Dec 17, 2020
ca8179a
Fix build error
JoeRobich Dec 17, 2020
6e69823
Add tests for the UnusedReferencesService
JoeRobich Dec 18, 2020
18f2655
Remove use of IEnumerable from UnusedReferencesService
JoeRobich Dec 18, 2020
0d15843
Update ProjectAssetsReader comment with a tracking issue
JoeRobich Dec 18, 2020
eb4d85f
Add comment about ignoring empty files in ProjectAssetsReader
JoeRobich Dec 18, 2020
a3e0f83
Remove memoization of compilation assemblies from ReferenceInfo
JoeRobich Dec 18, 2020
2f0c6f7
Remove empty line from UnusedReferencesService
JoeRobich Dec 18, 2020
5e81897
Use GetRequiredProject extension method
JoeRobich Dec 18, 2020
2e2efdf
Update method name for clarity
JoeRobich Dec 18, 2020
76a4792
Move GetAllCompilationAssemblies out of ReferenceInfo
JoeRobich Dec 18, 2020
287d54a
Use mutable usedAssemblyLookup in UnusedReferencesService
JoeRobich Dec 18, 2020
7314957
Code cleanup
JoeRobich Dec 18, 2020
f816a38
Add clarifying comments
JoeRobich Dec 18, 2020
b501f74
Use TryGetValue instead of multiple dictionary accesses
JoeRobich Dec 18, 2020
a13c308
Extract out a CommandHelpers class of common code.
JoeRobich Dec 19, 2020
a0b7522
Fix accessibility of Actions ComboBox
JoeRobich Dec 19, 2020
cfd230b
Update string resources
JoeRobich Jan 6, 2021
30eebaf
Update parameter name for clarity
JoeRobich Jan 6, 2021
ab85d79
Updated command placement and cleaned up Commands.vsct
JoeRobich Jan 6, 2021
e8aa034
Update processing order comment for clarity
JoeRobich Jan 13, 2021
11be928
Rely on KnownUIContexts for whether a build is active
JoeRobich Jan 13, 2021
43b5ded
Add comment and remove default button from dialog
JoeRobich Jan 13, 2021
7ec935b
Simplify UnusedReferencesDataSource
JoeRobich Jan 13, 2021
ea0e2dc
Update tests to use Assert.Single
JoeRobich Jan 27, 2021
f603c6c
Make UnusedReferencesService a static class
JoeRobich Jan 27, 2021
42bd41b
Throw unexpected value exception from ColumnDefinitions
JoeRobich Jan 27, 2021
4e292c3
Add error handling to ProjectAssetsReader
JoeRobich Jan 27, 2021
05dad08
Don't show dialog if operation is cancelled by user
JoeRobich Jan 27, 2021
76ddc8e
Remove unused method
JoeRobich Jan 27, 2021
c82eb9c
Add nullability attribute
JoeRobich Jan 27, 2021
096736d
Check that feature is enabled and pass TFM when getting project
JoeRobich Jan 27, 2021
c9fa026
Refactor responsibilities between the Table and Dialog providers.
JoeRobich Jan 27, 2021
3c00635
Merge branch 'features/UsedAssemblyReferences' into add-unusedreferen…
JoeRobich Feb 6, 2021
1115ec9
Merge remote-tracking branch 'origin/features/UsedAssemblyReferences'…
JoeRobich Feb 10, 2021
03f3b23
Add an experiment to dogfood remove unused references
JoeRobich Feb 10, 2021
6b5efb6
Rename UnusedReferencesService to UnusedReferencesRemover
JoeRobich Feb 10, 2021
292bb6e
Use WhereNotNull() instread of OfType<>()
JoeRobich Feb 10, 2021
1e744b4
Remove argument to IsBuildActive
JoeRobich Feb 10, 2021
772a24a
Ensure we cleanup table data after showing dialog
JoeRobich Feb 10, 2021
63c782e
Don't remove table entries in the Dispose() call.
JoeRobich Feb 10, 2021
b4a71c3
Be defensive about potentially removed projects
JoeRobich Feb 10, 2021
a886836
Add missing paren
JoeRobich Feb 10, 2021
235c045
Fix test for new 2 pass behavior
JoeRobich Feb 10, 2021
473155a
Fix formatting
JoeRobich Feb 10, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/Compilers/Test/Core/Traits/Traits.cs
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,7 @@ public static class Features
public const string ToggleBlockComment = nameof(ToggleBlockComment);
public const string ToggleLineComment = nameof(ToggleLineComment);
public const string TypeInferenceService = nameof(TypeInferenceService);
public const string UnusedReferences = nameof(UnusedReferences);
public const string ValidateFormatString = nameof(ValidateFormatString);
public const string ValidateRegexString = nameof(ValidateRegexString);
public const string Venus = nameof(Venus);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,10 @@ internal static class FeatureOnOffOptions
public static readonly PerLanguageOption2<bool?> AddImportsOnPaste = new(
nameof(FeatureOnOffOptions), nameof(AddImportsOnPaste), defaultValue: null,
storageLocations: new RoamingProfileStorageLocation($"TextEditor.%LANGUAGE%.Specific.{nameof(AddImportsOnPaste)}"));

public static readonly Option2<bool?> OfferRemoveUnusedReferences = new(
nameof(FeatureOnOffOptions), nameof(OfferRemoveUnusedReferences), defaultValue: null,
storageLocations: new RoamingProfileStorageLocation($"TextEditor.{nameof(OfferRemoveUnusedReferences)}"));
}

[ExportOptionProvider, Shared]
Expand Down Expand Up @@ -112,6 +116,7 @@ public FeatureOnOffOptionsProvider()
FeatureOnOffOptions.StreamingGoToImplementation,
FeatureOnOffOptions.NavigateToDecompiledSources,
FeatureOnOffOptions.UseEnhancedColors,
FeatureOnOffOptions.AddImportsOnPaste);
FeatureOnOffOptions.AddImportsOnPaste,
FeatureOnOffOptions.OfferRemoveUnusedReferences);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
// 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.Collections.Generic;
using System.Collections.Immutable;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Test.Utilities;
using Microsoft.CodeAnalysis.UnusedReferences;
using Xunit;

namespace Microsoft.CodeAnalysis.Editor.UnitTests.UnusedReferences
{
public class UnusedReferencesRemoverTests
{
private const string UsedAssemblyPath = "/libs/Used.dll";
private const string UnusedAssemblyPath = "/libs/Unused.dll";

[Fact, Trait(Traits.Feature, Traits.Features.UnusedReferences)]
public void GetUnusedReferences_UsedReferences_AreNotReturned()
{
var usedAssemblies = new[] { UsedAssemblyPath };
var usedReference = AssemblyReference(UsedAssemblyPath);

var unusedReferences = GetUnusedReferences(usedAssemblies, usedReference);

Assert.Empty(unusedReferences);
}

[Fact, Trait(Traits.Feature, Traits.Features.UnusedReferences)]
public void GetUnusedReferences_UnusedReferences_AreReturned()
{
var usedAssemblies = new[] { UsedAssemblyPath };
var unusedReference = PackageReference(UnusedAssemblyPath);

var unusedReferences = GetUnusedReferences(usedAssemblies, unusedReference);

Assert.Contains(unusedReference, unusedReferences);
Assert.Single(unusedReferences);
}

[Fact, Trait(Traits.Feature, Traits.Features.UnusedReferences)]
public void GetUnusedReferences_TransitivelyUsedReferences_AreNotReturned()
{
var usedAssemblies = new[] { UsedAssemblyPath };
var transitivelyUsedReference = ProjectReference(UnusedAssemblyPath, PackageReference(UsedAssemblyPath));

var unusedReferences = GetUnusedReferences(usedAssemblies, transitivelyUsedReference);

Assert.Empty(unusedReferences);
}

[Fact, Trait(Traits.Feature, Traits.Features.UnusedReferences)]
public void GetUnusedReferences_WhenUsedAssemblyIsAvilableDirectlyAndTransitively_DirectReferencesAreReturned()
{
var usedAssemblies = new[] { UsedAssemblyPath };
var transitivelyUsedReference = ProjectReference(UnusedAssemblyPath, PackageReference(UsedAssemblyPath));
var directlyUsedReference = PackageReference(UsedAssemblyPath);

var unusedReferences = GetUnusedReferences(usedAssemblies, transitivelyUsedReference, directlyUsedReference);

Assert.Contains(transitivelyUsedReference, unusedReferences);
Assert.Single(unusedReferences);
}

[Fact, Trait(Traits.Feature, Traits.Features.UnusedReferences)]
public void GetUnusedReferences_ReferencesThatDoNotContributeToCompilation_AreNotReturned()
{
var usedAssemblies = new[] { UsedAssemblyPath };
var analyzerReference = new ReferenceInfo(
ReferenceType.Package,
itemSpecification: "Analyzer",
treatAsUsed: false,
compilationAssemblies: ImmutableArray<string>.Empty,
dependencies: ImmutableArray<ReferenceInfo>.Empty);

var unusedReferences = GetUnusedReferences(usedAssemblies, analyzerReference);

Assert.Empty(unusedReferences);
}

[Theory, Trait(Traits.Feature, Traits.Features.UnusedReferences)]
[InlineData(UpdateAction.None, false)]
[InlineData(UpdateAction.None, true)]
[InlineData(UpdateAction.TreatAsUnused, false)]
[InlineData(UpdateAction.TreatAsUsed, true)]
internal async Task ApplyReferenceUpdates_NoChangeUpdates_AreNotApplied(UpdateAction action, bool treatAsUsed)
{
var noChangeUpdate = new ReferenceUpdate(action, PackageReference(UnusedAssemblyPath, treatAsUsed));

var appliedUpdates = await ApplyReferenceUpdatesAsync(noChangeUpdate);

Assert.Empty(appliedUpdates);
}

[Theory, Trait(Traits.Feature, Traits.Features.UnusedReferences)]
[InlineData(UpdateAction.Remove, false)]
[InlineData(UpdateAction.Remove, true)]
[InlineData(UpdateAction.TreatAsUnused, true)]
[InlineData(UpdateAction.TreatAsUsed, false)]
internal async Task ApplyReferenceUpdates_ChangeUpdates_AreApplied(UpdateAction action, bool treatAsUsed)
{
var changeUpdate = new ReferenceUpdate(action, PackageReference(UnusedAssemblyPath, treatAsUsed));

var appliedUpdates = await ApplyReferenceUpdatesAsync(changeUpdate);

Assert.Contains(changeUpdate, appliedUpdates);
Assert.Single(appliedUpdates);
}

[Fact, Trait(Traits.Feature, Traits.Features.UnusedReferences)]
public async Task ApplyReferenceUpdates_MixOfChangeAndNoChangeUpdates_ChangesAreApplied()
{
var noChangeUpdate = new ReferenceUpdate(UpdateAction.None, PackageReference(UsedAssemblyPath));
var changeUpdate = new ReferenceUpdate(UpdateAction.Remove, PackageReference(UnusedAssemblyPath));

var appliedUpdates = await ApplyReferenceUpdatesAsync(noChangeUpdate, changeUpdate);

Assert.Contains(changeUpdate, appliedUpdates);
Assert.Single(appliedUpdates);
}

private static ImmutableArray<ReferenceInfo> GetUnusedReferences(string[] usedCompilationAssemblies, params ReferenceInfo[] references)
=> UnusedReferencesRemover.GetUnusedReferences(new(usedCompilationAssemblies), references.ToImmutableArray());

private static async Task<ImmutableArray<ReferenceUpdate>> ApplyReferenceUpdatesAsync(params ReferenceUpdate[] referenceUpdates)
{
var referenceCleanupService = new TestReferenceCleanupService();

await UnusedReferencesRemover.ApplyReferenceUpdatesAsync(
referenceCleanupService,
string.Empty,
referenceUpdates.ToImmutableArray(),
CancellationToken.None).ConfigureAwait(false);

return referenceCleanupService.AppliedUpdates.ToImmutableArray();
}

private static ReferenceInfo ProjectReference(string assemblyPath, params ReferenceInfo[] dependencies)
=> ProjectReference(assemblyPath, treatAsUsed: false, dependencies);
private static ReferenceInfo ProjectReference(string assemblyPath, bool treatAsUsed, params ReferenceInfo[] dependencies)
=> new(ReferenceType.Project,
itemSpecification: Path.GetFileName(assemblyPath),
treatAsUsed,
compilationAssemblies: ImmutableArray.Create(assemblyPath),
dependencies.ToImmutableArray());

private static ReferenceInfo PackageReference(string assemblyPath, params ReferenceInfo[] dependencies)
=> PackageReference(assemblyPath, treatAsUsed: false, dependencies);
private static ReferenceInfo PackageReference(string assemblyPath, bool treatAsUsed, params ReferenceInfo[] dependencies)
=> new(ReferenceType.Package,
itemSpecification: Path.GetFileName(assemblyPath),
treatAsUsed,
compilationAssemblies: ImmutableArray.Create(assemblyPath),
dependencies.ToImmutableArray());

private static ReferenceInfo AssemblyReference(string assemblyPath)
=> AssemblyReference(assemblyPath, treatAsUsed: false);
private static ReferenceInfo AssemblyReference(string assemblyPath, bool treatAsUsed)
=> new(ReferenceType.Assembly,
itemSpecification: Path.GetFileName(assemblyPath),
treatAsUsed,
compilationAssemblies: ImmutableArray.Create(assemblyPath),
dependencies: ImmutableArray<ReferenceInfo>.Empty);

private class TestReferenceCleanupService : IReferenceCleanupService
{
private readonly List<ReferenceUpdate> _appliedUpdates = new();
public IReadOnlyList<ReferenceUpdate> AppliedUpdates => _appliedUpdates;

public Task<ImmutableArray<ReferenceInfo>> GetProjectReferencesAsync(string projectPath, CancellationToken cancellationToken)
{
throw new System.NotImplementedException();
}

public Task<bool> TryUpdateReferenceAsync(string projectPath, ReferenceUpdate referenceUpdate, CancellationToken cancellationToken)
{
_appliedUpdates.Add(referenceUpdate);
return Task.FromResult(true);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

#nullable enable

using System.Collections.Immutable;
using System.Threading;
using System.Threading.Tasks;
Expand All @@ -13,26 +11,12 @@ namespace Microsoft.CodeAnalysis.UnusedReferences
{
internal interface IReferenceCleanupService : IWorkspaceService
{
/// <summary>
/// Gets the current selected TargetFrameworkMoniker for the specified project.
/// </summary>
string GetTargetFrameworkMoniker(ProjectId projectId);

/// <summary>
/// For the given project, returns the full path to the project.assets.json file
/// generated in the intermediate output path by a NuGet restore.
/// </summary>
Task<string> GetProjectAssetsFilePathAsync(
string projectPath,
CancellationToken cancellationToken);

/// <summary>
/// Return the set of direct Project and Package references for the given project. This
/// is used to get the initial state of the TreatAsUsed attribute for each reference.
/// </summary>
Task<ImmutableArray<ReferenceInfo>> GetProjectReferencesAsync(
string projectPath,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this intentinoally not taking a Project object here, since it's returning it for all possible targets at once?

string targetFrameworkMoniker,
CancellationToken cancellationToken);

/// <summary>
Expand All @@ -42,7 +26,6 @@ Task<ImmutableArray<ReferenceInfo>> GetProjectReferencesAsync(
/// <returns>True, if the reference was updated.</returns>
Task<bool> TryUpdateReferenceAsync(
string projectPath,
string targetFrameworkMoniker,
ReferenceUpdate referenceUpdate,
CancellationToken cancellationToken);
}
Expand Down

This file was deleted.

16 changes: 15 additions & 1 deletion src/Features/Core/Portable/UnusedReferences/ReferenceInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
// 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.Collections.Immutable;

namespace Microsoft.CodeAnalysis.UnusedReferences
{
internal class ReferenceInfo
Expand All @@ -24,11 +26,23 @@ internal class ReferenceInfo
/// </summary>
public bool TreatAsUsed { get; }

public ReferenceInfo(ReferenceType referenceType, string itemSpecification, bool treatAsUsed)
/// <summary>
/// The full assembly paths that this reference directly adds to the compilation.
/// </summary>
public ImmutableArray<string> CompilationAssemblies { get; }

/// <summary>
/// The dependencies that this reference transitively brings in to the compilation.
/// </summary>
public ImmutableArray<ReferenceInfo> Dependencies { get; }

public ReferenceInfo(ReferenceType referenceType, string itemSpecification, bool treatAsUsed, ImmutableArray<string> compilationAssemblies, ImmutableArray<ReferenceInfo> dependencies)
{
ReferenceType = referenceType;
ItemSpecification = itemSpecification;
TreatAsUsed = treatAsUsed;
CompilationAssemblies = compilationAssemblies;
Dependencies = dependencies;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ internal sealed class ReferenceUpdate
/// <summary>
/// Indicates action to perform on the reference.
/// </summary>
public UpdateAction Action { get; }
public UpdateAction Action { get; set; }

/// <summary>
/// Gets the reference to be updated.
Expand Down
Loading