Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
39 changes: 28 additions & 11 deletions src/Analyzer/ReferenceTrimmerAnalyzer.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System.Collections.Concurrent;
using System.Collections.Concurrent;
using System.Collections.Immutable;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
Expand Down Expand Up @@ -934,7 +934,7 @@ private static void ReportUnusedReferences(

if (context.Options.AnalyzerConfigOptionsProvider.GlobalOptions
.TryGetValue("build_property.EnableReferenceTrimmerDiagnostics", out string? enableDiagnostics)
&& string.Equals(enableDiagnostics, "true", StringComparison.OrdinalIgnoreCase))
&& bool.TrueString.Equals(enableDiagnostics, StringComparison.OrdinalIgnoreCase))
{
HashSet<string> unusedReferences = new(PathComparer);
foreach (MetadataReference metadataReference in compilation.References)
Expand All @@ -949,6 +949,7 @@ private static void ReportUnusedReferences(
}

Dictionary<string, List<string>> packageAssembliesDict = new(PathComparer);
Dictionary<string, DiagnosticSeverity> packageSeverities = new(PathComparer);
foreach (DeclaredReference declaredReference in ReadDeclaredReferences(sourceText))
{
switch (declaredReference.Kind)
Expand All @@ -958,7 +959,8 @@ private static void ReportUnusedReferences(
// Use the conservative transitively-used set for bare References
if (!transitivelyUsedReferences.Contains(declaredReference.AssemblyPath))
{
context.ReportDiagnostic(Diagnostic.Create(RT0001Descriptor, Location.None, declaredReference.Spec));
DiagnosticSeverity severity = ToDiagnosticSeverity(declaredReference.Severity);
context.ReportDiagnostic(Diagnostic.Create(RT0001Descriptor, Location.None, effectiveSeverity: severity, additionalLocations: null, properties: null, declaredReference.Spec));
}

break;
Expand All @@ -967,7 +969,8 @@ private static void ReportUnusedReferences(
{
if (!projectReferenceUsedSet.Contains(declaredReference.AssemblyPath))
{
context.ReportDiagnostic(Diagnostic.Create(RT0002Descriptor, Location.None, declaredReference.Spec));
DiagnosticSeverity severity = ToDiagnosticSeverity(declaredReference.Severity);
context.ReportDiagnostic(Diagnostic.Create(RT0002Descriptor, Location.None, effectiveSeverity: severity, additionalLocations: null, properties: null, declaredReference.Spec));
}

break;
Expand All @@ -981,6 +984,7 @@ private static void ReportUnusedReferences(
}

packageAssemblies.Add(declaredReference.AssemblyPath);
packageSeverities.Add(declaredReference.Spec, ToDiagnosticSeverity(declaredReference.Severity));
break;
}
}
Expand All @@ -993,7 +997,7 @@ private static void ReportUnusedReferences(
List<string> packageAssemblies = kvp.Value;
if (!usedReferences.Overlaps(packageAssemblies))
{
context.ReportDiagnostic(Diagnostic.Create(RT0003Descriptor, Location.None, packageName));
context.ReportDiagnostic(Diagnostic.Create(RT0003Descriptor, Location.None, effectiveSeverity: packageSeverities[kvp.Key], additionalLocations: null, properties: null, packageName));
}
}
}
Expand Down Expand Up @@ -1160,7 +1164,7 @@ private static void WriteFile(string filePath, string text)
}
}

// File format: tab-separated fields (AssemblyPath, Kind, Spec), one reference per line.
// File format: tab-separated fields (AssemblyPath, Kind, Spec, Severity), one reference per line.
// Keep in sync with SaveDeclaredReferences in CollectDeclaredReferencesTask.cs.
private static IEnumerable<DeclaredReference> ReadDeclaredReferences(SourceText sourceText)
{
Expand All @@ -1178,6 +1182,7 @@ private static IEnumerable<DeclaredReference> ReadDeclaredReferences(SourceText

int firstTab = -1;
int secondTab = -1;
int thirdTab = -1;
for (int i = start; i < end; i++)
{
if (sourceText[i] == '\t')
Expand All @@ -1186,21 +1191,25 @@ private static IEnumerable<DeclaredReference> ReadDeclaredReferences(SourceText
{
firstTab = i;
}
else
else if (secondTab == -1)
{
secondTab = i;
break;
}
else
{
thirdTab = i;
}
}
}

if (firstTab == -1 || secondTab == -1)
if (firstTab == -1 || secondTab == -1 || thirdTab == -1)
{
yield break;
}

string assemblyPath = sourceText.ToString(TextSpan.FromBounds(start, firstTab));
string spec = sourceText.ToString(TextSpan.FromBounds(secondTab + 1, end));
string spec = sourceText.ToString(TextSpan.FromBounds(secondTab + 1, thirdTab));
Enum.TryParse<ReferenceTrimmerSeverity>(sourceText.ToString(TextSpan.FromBounds(thirdTab + 1, end)), out var severity);

// Determine kind without allocating a string. The three possible values are
// "Reference" (len 9), "ProjectReference" (len 16), "PackageReference" (len 16).
Expand All @@ -1223,7 +1232,15 @@ private static IEnumerable<DeclaredReference> ReadDeclaredReferences(SourceText
continue;
}

yield return new DeclaredReference(assemblyPath, kind, spec);
yield return new DeclaredReference(assemblyPath, kind, spec, severity);
}
}

private static DiagnosticSeverity ToDiagnosticSeverity(ReferenceTrimmerSeverity severity) => severity switch
{
ReferenceTrimmerSeverity.Hidden => DiagnosticSeverity.Hidden,
ReferenceTrimmerSeverity.Info => DiagnosticSeverity.Info,
ReferenceTrimmerSeverity.Warning => DiagnosticSeverity.Warning,
_ => throw new ArgumentOutOfRangeException(nameof(severity), $"Unexpected severity value: {severity}")
};
}
3 changes: 2 additions & 1 deletion src/Package/build/ReferenceTrimmer.targets
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,8 @@
NuGetRestoreTargets="$(NuGetRestoreTargets)"
TargetFrameworkDirectories="$(TargetFrameworkDirectory)"
IgnorePackageBuildFiles="@(ReferenceTrimmerIgnorePackageBuildFiles)"
NuGetPackageRoot="$(NuGetPackageRoot)" />
NuGetPackageRoot="$(NuGetPackageRoot)"
EnableReferenceTrimmerDiagnostics="$(EnableReferenceTrimmerDiagnostics)" />
</Target>

<!--
Expand Down
6 changes: 4 additions & 2 deletions src/Shared/DeclaredReferences.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
namespace ReferenceTrimmer.Shared;

internal readonly record struct DeclaredReference(string AssemblyPath, DeclaredReferenceKind Kind, string Spec);
internal readonly record struct DeclaredReference(string AssemblyPath, DeclaredReferenceKind Kind, string Spec, ReferenceTrimmerSeverity Severity);

internal enum DeclaredReferenceKind { Reference, ProjectReference, PackageReference }
internal enum DeclaredReferenceKind { Reference, ProjectReference, PackageReference }

internal enum ReferenceTrimmerSeverity { Hidden, Info, Warning }
31 changes: 20 additions & 11 deletions src/Tasks/CollectDeclaredReferencesTask.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ public sealed class CollectDeclaredReferencesTask : MSBuildTask

public string? NuGetPackageRoot { get; set; }

public string? EnableReferenceTrimmerDiagnostics { get; set; }

public override bool Execute()
{
AppDomain.CurrentDomain.AssemblyResolve += ResolveAssembly;
Expand Down Expand Up @@ -89,7 +91,8 @@ public override bool Execute()
}

// Ignore suppressions
if (IsSuppressed(reference, "RT0001"))
var severity = GetReferenceTrimmerSeverity(reference, "RT0001");
if (severity == ReferenceTrimmerSeverity.Hidden)
{
Log.LogMessage(MessageImportance.Low, "Skipping Reference '{0}' because it is suppressed via NoWarn=\"RT0001\" or <TreatAsUsed>", reference.ItemSpec);
continue;
Expand Down Expand Up @@ -125,7 +128,7 @@ public override bool Execute()

if (referencePath is not null)
{
declaredReferences.Add(new DeclaredReference(referencePath, DeclaredReferenceKind.Reference, referenceSpec));
declaredReferences.Add(new DeclaredReference(referencePath, DeclaredReferenceKind.Reference, referenceSpec, severity));
}
}
}
Expand All @@ -139,7 +142,8 @@ public override bool Execute()
foreach (ITaskItem projectReference in ProjectReferences)
{
// Ignore suppressions
if (IsSuppressed(projectReference, "RT0002"))
var severity = GetReferenceTrimmerSeverity(projectReference, "RT0002");
if (severity == ReferenceTrimmerSeverity.Hidden)
{
Log.LogMessage(MessageImportance.Low, "Skipping ProjectReference '{0}' because it is suppressed via NoWarn=\"RT0002\" or <TreatAsUsed>", projectReference.ItemSpec);
continue;
Expand All @@ -158,7 +162,7 @@ public override bool Execute()
string projectReferenceAssemblyPath = Path.GetFullPath(projectReference.ItemSpec);
string referenceProjectFile = projectReference.GetMetadata("OriginalProjectReferenceItemSpec");

declaredReferences.Add(new DeclaredReference(projectReferenceAssemblyPath, DeclaredReferenceKind.ProjectReference, referenceProjectFile));
declaredReferences.Add(new DeclaredReference(projectReferenceAssemblyPath, DeclaredReferenceKind.ProjectReference, referenceProjectFile, severity));
}
}
else
Expand All @@ -172,7 +176,8 @@ public override bool Execute()
foreach (ITaskItem packageReference in PackageReferences)
{
// Ignore suppressions
if (IsSuppressed(packageReference, "RT0003"))
var severity = GetReferenceTrimmerSeverity(packageReference, "RT0003");
if (severity == ReferenceTrimmerSeverity.Hidden)
{
Log.LogMessage(MessageImportance.Low, "Skipping PackageReference '{0}' because it is suppressed via NoWarn=\"RT0003\" or <TreatAsUsed>", packageReference.ItemSpec);
continue;
Expand All @@ -197,7 +202,7 @@ public override bool Execute()

foreach (string assemblyPath in packageInfo.CompileTimeAssemblies)
{
declaredReferences.Add(new DeclaredReference(assemblyPath, DeclaredReferenceKind.PackageReference, packageReference.ItemSpec));
declaredReferences.Add(new DeclaredReference(assemblyPath, DeclaredReferenceKind.PackageReference, packageReference.ItemSpec, severity));
}
}
}
Expand Down Expand Up @@ -450,7 +455,9 @@ private HashSet<string> GetTargetFrameworkAssemblyNames()
return null;
}

private static bool IsSuppressed(ITaskItem item, string warningId)
private bool IsReferenceTrimmerDiagnosticsEnabled() => bool.TrueString.Equals(EnableReferenceTrimmerDiagnostics, StringComparison.OrdinalIgnoreCase);

private ReferenceTrimmerSeverity GetReferenceTrimmerSeverity(ITaskItem item, string warningId)
{
ReadOnlySpan<char> warningIdSpan = warningId.AsSpan();
ReadOnlySpan<char> remainingNoWarn = item.GetMetadata(NoWarn).AsSpan();
Expand All @@ -471,19 +478,19 @@ private static bool IsSuppressed(ITaskItem item, string warningId)

if (currentNoWarn.Trim().Equals(warningIdSpan, StringComparison.OrdinalIgnoreCase))
{
return true;
return IsReferenceTrimmerDiagnosticsEnabled() ? ReferenceTrimmerSeverity.Info : ReferenceTrimmerSeverity.Hidden;
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This is the crux of the change. Other changes are plumbing.

}
}

if (item.GetMetadata(TreatAsUsed).Equals("True", StringComparison.OrdinalIgnoreCase))
{
return true;
return ReferenceTrimmerSeverity.Hidden;
}

return false;
return ReferenceTrimmerSeverity.Warning;
}

// File format: tab-separated fields (AssemblyPath, Kind, Spec), one reference per line.
// File format: tab-separated fields (AssemblyPath, Kind, Spec, Severity), one reference per line.
// Keep in sync with ReadDeclaredReferences in ReferenceTrimmerAnalyzer.cs.
private static void SaveDeclaredReferences(IReadOnlyList<DeclaredReference> declaredReferences, string filePath)
{
Expand All @@ -504,6 +511,8 @@ private static void SaveDeclaredReferences(IReadOnlyList<DeclaredReference> decl
writer.Write(kindString);
writer.Write(fieldDelimiter);
writer.WriteLine(reference.Spec);
writer.Write(fieldDelimiter);
writer.WriteLine(reference.Severity);
}
}

Expand Down
Loading
Loading