From ca7ecac1c7534865f0a0f9d2e5f3b29d6b3f69c1 Mon Sep 17 00:00:00 2001 From: David Federman <david.federman@outlook.com> Date: Mon, 30 Sep 2024 21:22:50 -0700 Subject: [PATCH] Add support for TreatAsUsed metadata (#104) --- src/Tasks/CollectDeclaredReferencesTask.cs | 40 ++++++++++++++++-- src/Tests/E2ETests.cs | 42 +++++++++++++++++++ .../Library/Library.csproj | 2 +- .../Library/Library.cs | 7 ++++ .../Library/Library.csproj | 12 ++++++ .../Library/Library.csproj | 2 +- .../Dependency/Dependency.cs | 7 ++++ .../Dependency/Dependency.csproj | 8 ++++ .../Library/Library.cs | 7 ++++ .../Library/Library.csproj | 12 ++++++ .../Library/Library.csproj | 2 +- .../Dependency/Dependency.cs | 7 ++++ .../Dependency/Dependency.csproj | 8 ++++ .../Library/Library.cs | 7 ++++ .../Library/Library.csproj | 15 +++++++ .../Library/Library.csproj | 2 +- .../Dependency/Dependency.cs | 7 ++++ .../Dependency/Dependency.csproj | 8 ++++ .../Library/Library.cs | 7 ++++ .../Library/Library.csproj | 12 ++++++ 20 files changed, 207 insertions(+), 7 deletions(-) create mode 100644 src/Tests/TestData/UnusedPackageReferenceTreatAsUsed/Library/Library.cs create mode 100644 src/Tests/TestData/UnusedPackageReferenceTreatAsUsed/Library/Library.csproj create mode 100644 src/Tests/TestData/UnusedProjectReferenceTreatAsUsed/Dependency/Dependency.cs create mode 100644 src/Tests/TestData/UnusedProjectReferenceTreatAsUsed/Dependency/Dependency.csproj create mode 100644 src/Tests/TestData/UnusedProjectReferenceTreatAsUsed/Library/Library.cs create mode 100644 src/Tests/TestData/UnusedProjectReferenceTreatAsUsed/Library/Library.csproj create mode 100644 src/Tests/TestData/UnusedReferenceHintPathTreatAsUsed/Dependency/Dependency.cs create mode 100644 src/Tests/TestData/UnusedReferenceHintPathTreatAsUsed/Dependency/Dependency.csproj create mode 100644 src/Tests/TestData/UnusedReferenceHintPathTreatAsUsed/Library/Library.cs create mode 100644 src/Tests/TestData/UnusedReferenceHintPathTreatAsUsed/Library/Library.csproj create mode 100644 src/Tests/TestData/UnusedReferenceItemSpecTreatAsUsed/Dependency/Dependency.cs create mode 100644 src/Tests/TestData/UnusedReferenceItemSpecTreatAsUsed/Dependency/Dependency.csproj create mode 100644 src/Tests/TestData/UnusedReferenceItemSpecTreatAsUsed/Library/Library.cs create mode 100644 src/Tests/TestData/UnusedReferenceItemSpecTreatAsUsed/Library/Library.csproj diff --git a/src/Tasks/CollectDeclaredReferencesTask.cs b/src/Tasks/CollectDeclaredReferencesTask.cs index 247acc5..0c38cf7 100644 --- a/src/Tasks/CollectDeclaredReferencesTask.cs +++ b/src/Tasks/CollectDeclaredReferencesTask.cs @@ -24,6 +24,7 @@ public sealed class CollectDeclaredReferencesTask : MSBuildTask }; private const string NoWarn = "NoWarn"; + private const string TreatAsUsed = "TreatAsUsed"; [Required] public string? OutputFile { get; set; } @@ -86,7 +87,7 @@ public override bool Execute() } // Ignore suppressions - if (reference.GetMetadata(NoWarn).Contains("RT0001")) + if (IsSuppressed(reference, "RT0001")) { continue; } @@ -130,7 +131,7 @@ public override bool Execute() foreach (ITaskItem projectReference in ProjectReferences) { // Ignore suppressions - if (projectReference.GetMetadata(NoWarn).Contains("RT0002")) + if (IsSuppressed(projectReference, "RT0002")) { continue; } @@ -158,7 +159,7 @@ public override bool Execute() foreach (ITaskItem packageReference in PackageReferences) { // Ignore suppressions - if (packageReference.GetMetadata(NoWarn).Contains("RT0003")) + if (IsSuppressed(packageReference, "RT0003")) { continue; } @@ -375,6 +376,39 @@ private HashSet<string> GetTargetFrameworkAssemblyNames() return null; } + private static bool IsSuppressed(ITaskItem item, string warningId) + { + ReadOnlySpan<char> warningIdSpan = warningId.AsSpan(); + ReadOnlySpan<char> remainingNoWarn = item.GetMetadata(NoWarn).AsSpan(); + while (!remainingNoWarn.IsEmpty) + { + ReadOnlySpan<char> currentNoWarn; + int idx = remainingNoWarn.IndexOf(';'); + if (idx == -1) + { + currentNoWarn = remainingNoWarn; + remainingNoWarn = ReadOnlySpan<char>.Empty; + } + else + { + currentNoWarn = remainingNoWarn.Slice(0, idx); + remainingNoWarn = remainingNoWarn.Slice(idx + 1); + } + + if (currentNoWarn.Trim().Equals(warningIdSpan, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + } + + if (item.GetMetadata(TreatAsUsed).Equals("True", StringComparison.OrdinalIgnoreCase)) + { + return true; + } + + return false; + } + private sealed class PackageInfoBuilder { private List<string>? _compileTimeAssemblies; diff --git a/src/Tests/E2ETests.cs b/src/Tests/E2ETests.cs index 7107775..a54e692 100644 --- a/src/Tests/E2ETests.cs +++ b/src/Tests/E2ETests.cs @@ -96,6 +96,14 @@ public Task UnusedProjectReferenceNoWarn() expectedWarnings: []); } + [TestMethod] + public Task UnusedProjectReferenceTreatAsUsed() + { + return RunMSBuildAsync( + projectFile: "Library/Library.csproj", + expectedWarnings: Array.Empty<Warning>()); + } + [TestMethod] public Task UnusedProjectReferenceSuppressed() { @@ -184,6 +192,19 @@ await RunMSBuildAsync( expectedWarnings: []); } + [TestMethod] + public async Task UnusedReferenceHintPathTreatAsUsed() + { + // For direct references, MSBuild can't determine build order so we need to ensure the dependency is already built + await RunMSBuildAsync( + projectFile: "Dependency/Dependency.csproj", + expectedWarnings: Array.Empty<Warning>()); + + await RunMSBuildAsync( + projectFile: "Library/Library.csproj", + expectedWarnings: Array.Empty<Warning>()); + } + [TestMethod] public async Task UnusedReferenceItemSpec() { @@ -222,6 +243,19 @@ await RunMSBuildAsync( expectedWarnings: []); } + [TestMethod] + public async Task UnusedReferenceItemSpecTreatAsUsed() + { + // For direct references, MSBuild can't determine build order so we need to ensure the dependency is already built + await RunMSBuildAsync( + projectFile: "Dependency/Dependency.csproj", + expectedWarnings: Array.Empty<Warning>()); + + await RunMSBuildAsync( + projectFile: "Library/Library.csproj", + expectedWarnings: Array.Empty<Warning>()); + } + [TestMethod] public async Task UnusedReferenceFromGac() { @@ -288,6 +322,14 @@ public Task UnusedPackageReferenceNoWarn() expectedWarnings: []); } + [TestMethod] + public Task UnusedPackageReferenceTreatAsUsed() + { + return RunMSBuildAsync( + projectFile: "Library/Library.csproj", + expectedWarnings: []); + } + [TestMethod] public Task UnusedPackageReferenceDocDisabled() { diff --git a/src/Tests/TestData/UnusedPackageReferenceNoWarn/Library/Library.csproj b/src/Tests/TestData/UnusedPackageReferenceNoWarn/Library/Library.csproj index 3261f14..d500a3a 100644 --- a/src/Tests/TestData/UnusedPackageReferenceNoWarn/Library/Library.csproj +++ b/src/Tests/TestData/UnusedPackageReferenceNoWarn/Library/Library.csproj @@ -6,7 +6,7 @@ </PropertyGroup> <ItemGroup> - <PackageReference Include="Newtonsoft.Json" Version="13.0.2" NoWarn="RT0003"/> + <PackageReference Include="Newtonsoft.Json" Version="13.0.2" NoWarn="Foo; rt0003 ; Bar"/> </ItemGroup> </Project> diff --git a/src/Tests/TestData/UnusedPackageReferenceTreatAsUsed/Library/Library.cs b/src/Tests/TestData/UnusedPackageReferenceTreatAsUsed/Library/Library.cs new file mode 100644 index 0000000..958c389 --- /dev/null +++ b/src/Tests/TestData/UnusedPackageReferenceTreatAsUsed/Library/Library.cs @@ -0,0 +1,7 @@ +namespace Library +{ + public static class Foo + { + public static string Bar() => "Baz"; + } +} diff --git a/src/Tests/TestData/UnusedPackageReferenceTreatAsUsed/Library/Library.csproj b/src/Tests/TestData/UnusedPackageReferenceTreatAsUsed/Library/Library.csproj new file mode 100644 index 0000000..8ce2a5f --- /dev/null +++ b/src/Tests/TestData/UnusedPackageReferenceTreatAsUsed/Library/Library.csproj @@ -0,0 +1,12 @@ +<Project Sdk="Microsoft.NET.Sdk"> + + <PropertyGroup> + <OutputType>Library</OutputType> + <TargetFramework>net472</TargetFramework> + </PropertyGroup> + + <ItemGroup> + <PackageReference Include="Newtonsoft.Json" Version="13.0.2" TreatAsUsed="true"/> + </ItemGroup> + +</Project> diff --git a/src/Tests/TestData/UnusedProjectReferenceNoWarn/Library/Library.csproj b/src/Tests/TestData/UnusedProjectReferenceNoWarn/Library/Library.csproj index 7787015..f9c6303 100644 --- a/src/Tests/TestData/UnusedProjectReferenceNoWarn/Library/Library.csproj +++ b/src/Tests/TestData/UnusedProjectReferenceNoWarn/Library/Library.csproj @@ -6,7 +6,7 @@ </PropertyGroup> <ItemGroup> - <ProjectReference Include="../Dependency/Dependency.csproj" NoWarn="RT0002" /> + <ProjectReference Include="../Dependency/Dependency.csproj" NoWarn="Foo; rt0002 ; Bar" /> </ItemGroup> </Project> diff --git a/src/Tests/TestData/UnusedProjectReferenceTreatAsUsed/Dependency/Dependency.cs b/src/Tests/TestData/UnusedProjectReferenceTreatAsUsed/Dependency/Dependency.cs new file mode 100644 index 0000000..ad07022 --- /dev/null +++ b/src/Tests/TestData/UnusedProjectReferenceTreatAsUsed/Dependency/Dependency.cs @@ -0,0 +1,7 @@ +namespace Dependency +{ + public static class Foo + { + public static string Bar() => "Baz"; + } +} diff --git a/src/Tests/TestData/UnusedProjectReferenceTreatAsUsed/Dependency/Dependency.csproj b/src/Tests/TestData/UnusedProjectReferenceTreatAsUsed/Dependency/Dependency.csproj new file mode 100644 index 0000000..33af96b --- /dev/null +++ b/src/Tests/TestData/UnusedProjectReferenceTreatAsUsed/Dependency/Dependency.csproj @@ -0,0 +1,8 @@ +<Project Sdk="Microsoft.NET.Sdk"> + + <PropertyGroup> + <OutputType>Library</OutputType> + <TargetFramework>net472</TargetFramework> + </PropertyGroup> + +</Project> diff --git a/src/Tests/TestData/UnusedProjectReferenceTreatAsUsed/Library/Library.cs b/src/Tests/TestData/UnusedProjectReferenceTreatAsUsed/Library/Library.cs new file mode 100644 index 0000000..958c389 --- /dev/null +++ b/src/Tests/TestData/UnusedProjectReferenceTreatAsUsed/Library/Library.cs @@ -0,0 +1,7 @@ +namespace Library +{ + public static class Foo + { + public static string Bar() => "Baz"; + } +} diff --git a/src/Tests/TestData/UnusedProjectReferenceTreatAsUsed/Library/Library.csproj b/src/Tests/TestData/UnusedProjectReferenceTreatAsUsed/Library/Library.csproj new file mode 100644 index 0000000..6d2174e --- /dev/null +++ b/src/Tests/TestData/UnusedProjectReferenceTreatAsUsed/Library/Library.csproj @@ -0,0 +1,12 @@ +<Project Sdk="Microsoft.NET.Sdk"> + + <PropertyGroup> + <OutputType>Library</OutputType> + <TargetFramework>net472</TargetFramework> + </PropertyGroup> + + <ItemGroup> + <ProjectReference Include="../Dependency/Dependency.csproj" TreatAsUsed="true" /> + </ItemGroup> + +</Project> diff --git a/src/Tests/TestData/UnusedReferenceHintPathNoWarn/Library/Library.csproj b/src/Tests/TestData/UnusedReferenceHintPathNoWarn/Library/Library.csproj index d7c55f5..f1c7e5f 100644 --- a/src/Tests/TestData/UnusedReferenceHintPathNoWarn/Library/Library.csproj +++ b/src/Tests/TestData/UnusedReferenceHintPathNoWarn/Library/Library.csproj @@ -8,7 +8,7 @@ <ItemGroup> <Reference Include="Dependency"> <HintPath>..\Dependency\$(OutputPath)\Dependency.dll</HintPath> - <NoWarn>RT0001</NoWarn> + <NoWarn>Foo; rt0001 ; Bar</NoWarn> </Reference> </ItemGroup> diff --git a/src/Tests/TestData/UnusedReferenceHintPathTreatAsUsed/Dependency/Dependency.cs b/src/Tests/TestData/UnusedReferenceHintPathTreatAsUsed/Dependency/Dependency.cs new file mode 100644 index 0000000..ad07022 --- /dev/null +++ b/src/Tests/TestData/UnusedReferenceHintPathTreatAsUsed/Dependency/Dependency.cs @@ -0,0 +1,7 @@ +namespace Dependency +{ + public static class Foo + { + public static string Bar() => "Baz"; + } +} diff --git a/src/Tests/TestData/UnusedReferenceHintPathTreatAsUsed/Dependency/Dependency.csproj b/src/Tests/TestData/UnusedReferenceHintPathTreatAsUsed/Dependency/Dependency.csproj new file mode 100644 index 0000000..33af96b --- /dev/null +++ b/src/Tests/TestData/UnusedReferenceHintPathTreatAsUsed/Dependency/Dependency.csproj @@ -0,0 +1,8 @@ +<Project Sdk="Microsoft.NET.Sdk"> + + <PropertyGroup> + <OutputType>Library</OutputType> + <TargetFramework>net472</TargetFramework> + </PropertyGroup> + +</Project> diff --git a/src/Tests/TestData/UnusedReferenceHintPathTreatAsUsed/Library/Library.cs b/src/Tests/TestData/UnusedReferenceHintPathTreatAsUsed/Library/Library.cs new file mode 100644 index 0000000..958c389 --- /dev/null +++ b/src/Tests/TestData/UnusedReferenceHintPathTreatAsUsed/Library/Library.cs @@ -0,0 +1,7 @@ +namespace Library +{ + public static class Foo + { + public static string Bar() => "Baz"; + } +} diff --git a/src/Tests/TestData/UnusedReferenceHintPathTreatAsUsed/Library/Library.csproj b/src/Tests/TestData/UnusedReferenceHintPathTreatAsUsed/Library/Library.csproj new file mode 100644 index 0000000..30c1c99 --- /dev/null +++ b/src/Tests/TestData/UnusedReferenceHintPathTreatAsUsed/Library/Library.csproj @@ -0,0 +1,15 @@ +<Project Sdk="Microsoft.NET.Sdk"> + + <PropertyGroup> + <OutputType>Library</OutputType> + <TargetFramework>net472</TargetFramework> + </PropertyGroup> + + <ItemGroup> + <Reference Include="Dependency"> + <HintPath>..\Dependency\$(OutputPath)\Dependency.dll</HintPath> + <TreatAsUsed>true</TreatAsUsed> + </Reference> + </ItemGroup> + +</Project> diff --git a/src/Tests/TestData/UnusedReferenceItemSpecNoWarn/Library/Library.csproj b/src/Tests/TestData/UnusedReferenceItemSpecNoWarn/Library/Library.csproj index 19146d3..0162fd3 100644 --- a/src/Tests/TestData/UnusedReferenceItemSpecNoWarn/Library/Library.csproj +++ b/src/Tests/TestData/UnusedReferenceItemSpecNoWarn/Library/Library.csproj @@ -6,7 +6,7 @@ </PropertyGroup> <ItemGroup> - <Reference Include="..\Dependency\$(OutputPath)\Dependency.dll" NoWarn="RT0001"/> + <Reference Include="..\Dependency\$(OutputPath)\Dependency.dll" NoWarn="Foo; rt0001 ; Bar"/> </ItemGroup> </Project> diff --git a/src/Tests/TestData/UnusedReferenceItemSpecTreatAsUsed/Dependency/Dependency.cs b/src/Tests/TestData/UnusedReferenceItemSpecTreatAsUsed/Dependency/Dependency.cs new file mode 100644 index 0000000..ad07022 --- /dev/null +++ b/src/Tests/TestData/UnusedReferenceItemSpecTreatAsUsed/Dependency/Dependency.cs @@ -0,0 +1,7 @@ +namespace Dependency +{ + public static class Foo + { + public static string Bar() => "Baz"; + } +} diff --git a/src/Tests/TestData/UnusedReferenceItemSpecTreatAsUsed/Dependency/Dependency.csproj b/src/Tests/TestData/UnusedReferenceItemSpecTreatAsUsed/Dependency/Dependency.csproj new file mode 100644 index 0000000..33af96b --- /dev/null +++ b/src/Tests/TestData/UnusedReferenceItemSpecTreatAsUsed/Dependency/Dependency.csproj @@ -0,0 +1,8 @@ +<Project Sdk="Microsoft.NET.Sdk"> + + <PropertyGroup> + <OutputType>Library</OutputType> + <TargetFramework>net472</TargetFramework> + </PropertyGroup> + +</Project> diff --git a/src/Tests/TestData/UnusedReferenceItemSpecTreatAsUsed/Library/Library.cs b/src/Tests/TestData/UnusedReferenceItemSpecTreatAsUsed/Library/Library.cs new file mode 100644 index 0000000..958c389 --- /dev/null +++ b/src/Tests/TestData/UnusedReferenceItemSpecTreatAsUsed/Library/Library.cs @@ -0,0 +1,7 @@ +namespace Library +{ + public static class Foo + { + public static string Bar() => "Baz"; + } +} diff --git a/src/Tests/TestData/UnusedReferenceItemSpecTreatAsUsed/Library/Library.csproj b/src/Tests/TestData/UnusedReferenceItemSpecTreatAsUsed/Library/Library.csproj new file mode 100644 index 0000000..660dedd --- /dev/null +++ b/src/Tests/TestData/UnusedReferenceItemSpecTreatAsUsed/Library/Library.csproj @@ -0,0 +1,12 @@ +<Project Sdk="Microsoft.NET.Sdk"> + + <PropertyGroup> + <OutputType>Library</OutputType> + <TargetFramework>net472</TargetFramework> + </PropertyGroup> + + <ItemGroup> + <Reference Include="..\Dependency\$(OutputPath)\Dependency.dll" TreatAsUsed="true" /> + </ItemGroup> + +</Project>