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

Merge release/dev16.11 to release/dev16.11-vs-deps #54312

Merged
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// 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.Collections.Immutable;
using System.IO;
Expand All @@ -15,16 +16,42 @@ namespace Microsoft.CodeAnalysis.Editor.UnitTests.UnusedReferences
{
public class UnusedReferencesRemoverTests
{
private const string UsedAssemblyPath = "/libs/Used.dll";
private const string UnusedAssemblyPath = "/libs/Unused.dll";
private static readonly string[] Empty = Array.Empty<string>();

private const string UsedAssemblyName = "Used.dll";
private const string UsedAssemblyPath = $"/libs/{UsedAssemblyName}";
private const string UnusedAssemblyName = "Unused.dll";
private const string UnusedAssemblyPath = $"/libs/{UnusedAssemblyName}";

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

var unusedReferences = GetUnusedReferences(usedAssemblies, usedReference);
var unusedReferences = GetUnusedReferences(usedAssemblies, usedProjectAssemblyNames: Empty, usedReference);

Assert.Empty(unusedReferences);
}

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

var unusedReferences = GetUnusedReferences(usedAssemblies, usedProjectAssemblyNames: Empty, usedReference);

Assert.Empty(unusedReferences);
}

[Fact, Trait(Traits.Feature, Traits.Features.UnusedReferences)]
public void GetUnusedReferences_DirectlyUsedProjectReferences_AreNotReturned()
{
var usedProjects = new[] { UsedAssemblyName };
var usedReference = ProjectReference(UsedAssemblyName);

var unusedReferences = GetUnusedReferences(usedCompilationAssemblies: Empty, usedProjects, usedReference);

Assert.Empty(unusedReferences);
}
Expand All @@ -35,19 +62,30 @@ public void GetUnusedReferences_UnusedReferences_AreReturned()
var usedAssemblies = new[] { UsedAssemblyPath };
var unusedReference = PackageReference(UnusedAssemblyPath);

var unusedReferences = GetUnusedReferences(usedAssemblies, unusedReference);
var unusedReferences = GetUnusedReferences(usedAssemblies, usedProjectAssemblyNames: Empty, unusedReference);

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

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

var unusedReferences = GetUnusedReferences(usedAssemblies, usedProjectAssemblyNames: Empty, transitivelyUsedReference);

Assert.Empty(unusedReferences);
}

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

var unusedReferences = GetUnusedReferences(usedAssemblies, transitivelyUsedReference);
var unusedReferences = GetUnusedReferences(usedAssemblies, usedProjectAssemblyNames: Empty, transitivelyUsedReference);

Assert.Empty(unusedReferences);
}
Expand All @@ -59,7 +97,7 @@ public void GetUnusedReferences_WhenUsedAssemblyIsAvilableDirectlyAndTransitivel
var transitivelyUsedReference = ProjectReference(UnusedAssemblyPath, PackageReference(UsedAssemblyPath));
var directlyUsedReference = PackageReference(UsedAssemblyPath);

var unusedReferences = GetUnusedReferences(usedAssemblies, transitivelyUsedReference, directlyUsedReference);
var unusedReferences = GetUnusedReferences(usedAssemblies, usedProjectAssemblyNames: Empty, transitivelyUsedReference, directlyUsedReference);

Assert.Contains(transitivelyUsedReference, unusedReferences);
Assert.Single(unusedReferences);
Expand All @@ -76,7 +114,7 @@ public void GetUnusedReferences_ReferencesThatDoNotContributeToCompilation_AreNo
compilationAssemblies: ImmutableArray<string>.Empty,
dependencies: ImmutableArray<ReferenceInfo>.Empty);

var unusedReferences = GetUnusedReferences(usedAssemblies, analyzerReference);
var unusedReferences = GetUnusedReferences(usedAssemblies, usedProjectAssemblyNames: Empty, analyzerReference);

Assert.Empty(unusedReferences);
}
Expand Down Expand Up @@ -122,8 +160,8 @@ public async Task ApplyReferenceUpdates_MixOfChangeAndNoChangeUpdates_ChangesAre
Assert.Single(appliedUpdates);
}

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

private static async Task<ImmutableArray<ReferenceUpdate>> ApplyReferenceUpdatesAsync(params ReferenceUpdate[] referenceUpdates)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

using System.Collections.Generic;
using System.Collections.Immutable;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
Expand Down Expand Up @@ -34,6 +35,7 @@ public static async Task<ImmutableArray<ReferenceInfo>> GetUnusedReferencesAsync
.Where(project => projectFilePath.Equals(project.FilePath, System.StringComparison.OrdinalIgnoreCase));

HashSet<string> usedAssemblyFilePaths = new();
HashSet<string> usedProjectFileNames = new();

foreach (var project in projects)
{
Expand All @@ -50,13 +52,21 @@ public static async Task<ImmutableArray<ReferenceInfo>> GetUnusedReferencesAsync
.OfType<PortableExecutableReference>()
.Select(reference => reference.FilePath)
.WhereNotNull());

// Compilation references do not contain the full path to the output assembly so we track them
// by file name.
usedProjectFileNames.AddRange(usedAssemblyReferences
.OfType<CompilationReference>()
.Select(reference => reference.Compilation.SourceModule.MetadataName)
.WhereNotNull());
}

return GetUnusedReferences(usedAssemblyFilePaths, references);
return GetUnusedReferences(usedAssemblyFilePaths, usedProjectFileNames, references);
}

internal static ImmutableArray<ReferenceInfo> GetUnusedReferences(
HashSet<string> usedAssemblyFilePaths,
HashSet<string> usedProjectFileNames,
ImmutableArray<ReferenceInfo> references)
{
var unusedReferencesBuilder = ImmutableArray.CreateBuilder<ReferenceInfo>();
Expand Down Expand Up @@ -85,7 +95,8 @@ internal static ImmutableArray<ReferenceInfo> GetUnusedReferences(

var unusedReferences = RemoveDirectlyUsedReferences(
referencesForReferenceType,
usedAssemblyFilePaths);
usedAssemblyFilePaths,
usedProjectFileNames);

// Update with the references that are remaining.
if (unusedReferences.IsEmpty)
Expand Down Expand Up @@ -119,7 +130,8 @@ internal static ImmutableArray<ReferenceInfo> GetUnusedReferences(

private static ImmutableArray<ReferenceInfo> RemoveDirectlyUsedReferences(
ImmutableArray<ReferenceInfo> references,
HashSet<string> usedAssemblyFilePaths)
HashSet<string> usedAssemblyFilePaths,
HashSet<string> usedProjectFileNames)
{
// In this method we will check if a reference directly brings in a used compilation assembly.
//
Expand All @@ -131,19 +143,42 @@ private static ImmutableArray<ReferenceInfo> RemoveDirectlyUsedReferences(

foreach (var reference in references)
{
// We will look at the compilation assemblies brought in directly by the
// references to see if they are used.
if (!reference.CompilationAssemblies.Any(usedAssemblyFilePaths.Contains))
if (reference.ReferenceType == ReferenceType.Project)
{
// None of the assemblies brought into this compilation are in the
// used assemblies list, so we will consider the reference unused.
unusedReferencesBuilder.Add(reference);
continue;
// Since we only know project references by their CompilationReference which
// does not include the full output path. We look only at the file name of the
// compilation assembly and compare it with our list of used project assembly names.
var projectAssemblyFileNames = reference.CompilationAssemblies
.SelectAsArray(assemblyPath => Path.GetFileName(assemblyPath));

// We will look at the project assemblies brought in directly by the
// references to see if they are used.
if (!projectAssemblyFileNames.Any(usedProjectFileNames.Contains))
{
// None of the project assemblies brought into this compilation are in the
// used assemblies list, so we will consider the reference unused.
unusedReferencesBuilder.Add(reference);
continue;
}

// Remove the project file name now that we've identified it.
usedProjectFileNames.ExceptWith(projectAssemblyFileNames);
}
else
{
// We will look at the compilation assemblies brought in directly by the
// references to see if they are used.
if (!reference.CompilationAssemblies.Any(usedAssemblyFilePaths.Contains))
{
// None of the assemblies brought into this compilation are in the
// used assemblies list, so we will consider the reference unused.
unusedReferencesBuilder.Add(reference);
continue;
}
}

// Remove all assemblies that are brought into this compilation by this reference.
usedAssemblyFilePaths.ExceptWith(GetAllCompilationAssemblies(reference));

}

return unusedReferencesBuilder.ToImmutable();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,8 +123,10 @@ public bool TryGetValue(string keyName, out object? content)
content = ReferenceUpdate.ReferenceInfo.ReferenceType;
break;
case UnusedReferencesTableKeyNames.ReferenceName:
// It is unnecessary to display the full path to project and assembly files.
content = Path.GetFileNameWithoutExtension(ReferenceUpdate.ReferenceInfo.ItemSpecification);
// For Project and Assembly references, use the file name instead of overwhelming the user with the full path.
content = ReferenceUpdate.ReferenceInfo.ReferenceType != ReferenceType.Package
? Path.GetFileName(ReferenceUpdate.ReferenceInfo.ItemSpecification)
: ReferenceUpdate.ReferenceInfo.ItemSpecification;
break;
case UnusedReferencesTableKeyNames.UpdateAction:
content = ReferenceUpdate.Action;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,8 @@ internal static ImmutableArray<ReferenceInfo> ReadReferences(
}

var autoReferences = projectAssets.Project?.Frameworks?.Values
.SelectMany(framework => framework.Dependencies?.Keys.Where(key => framework.Dependencies[key].AutoReferenced))
.Where(framework => framework.Dependencies != null)
.SelectMany(framework => framework.Dependencies!.Keys.Where(key => framework.Dependencies[key].AutoReferenced))
.Distinct()
.ToImmutableHashSet();
autoReferences ??= ImmutableHashSet<string>.Empty;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,13 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.UnusedReferences

Dim libraries = BuildLibraries(allReferences)
Dim targets = BuildTargets(targetFramework, allReferences)
Dim project = BuildProject(targetFramework)

Dim projectAssets As ProjectAssetsFile = New ProjectAssetsFile With {
.Version = version,
.Targets = targets,
.Libraries = libraries
.Libraries = libraries,
.Project = project
}

Return projectAssets
Expand Down Expand Up @@ -88,5 +90,15 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.UnusedReferences
Return String.Empty
End Function)
End Function

Private Function BuildProject(targetFramework As String) As ProjectAssetsProject
' Frameworks won't always specify a set of dependencies.
' This ensures the project asset reader does not error in these cases.
Return New ProjectAssetsProject With {
.Frameworks = New Dictionary(Of String, ProjectAssetsProjectFramework) From {
{targetFramework, New ProjectAssetsProjectFramework}
}
}
End Function
End Module
End Namespace