From de0f7a6e9c40e73a07f4fa692430682c5381fa61 Mon Sep 17 00:00:00 2001 From: Stanislaw Szczepanowski <37585349+stan-sz@users.noreply.github.com> Date: Wed, 6 May 2026 15:15:04 +0200 Subject: [PATCH] Emit informational messages when NoWarn and Diagnostics enabled When EnableReferenceTrimmerDiagnostics NoWarn="RT000x" will still emit Informational reports about unused dependencies. The purpose is to allow gradual enforcement of ReferenceTrimmer on a mono-repo and still collect silent telemetry about not-yet-enforced projects. --- src/Analyzer/ReferenceTrimmerAnalyzer.cs | 39 +++++-- src/Package/build/ReferenceTrimmer.targets | 3 +- src/Shared/DeclaredReferences.cs | 6 +- src/Tasks/CollectDeclaredReferencesTask.cs | 31 +++-- src/Tests/E2ETests.cs | 128 ++++++++++----------- 5 files changed, 118 insertions(+), 89 deletions(-) diff --git a/src/Analyzer/ReferenceTrimmerAnalyzer.cs b/src/Analyzer/ReferenceTrimmerAnalyzer.cs index 5f2edab..f145c01 100644 --- a/src/Analyzer/ReferenceTrimmerAnalyzer.cs +++ b/src/Analyzer/ReferenceTrimmerAnalyzer.cs @@ -1,4 +1,4 @@ -using System.Collections.Concurrent; +using System.Collections.Concurrent; using System.Collections.Immutable; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -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 unusedReferences = new(PathComparer); foreach (MetadataReference metadataReference in compilation.References) @@ -949,6 +949,7 @@ private static void ReportUnusedReferences( } Dictionary> packageAssembliesDict = new(PathComparer); + Dictionary packageSeverities = new(PathComparer); foreach (DeclaredReference declaredReference in ReadDeclaredReferences(sourceText)) { switch (declaredReference.Kind) @@ -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; @@ -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; @@ -981,6 +984,7 @@ private static void ReportUnusedReferences( } packageAssemblies.Add(declaredReference.AssemblyPath); + packageSeverities.Add(declaredReference.Spec, ToDiagnosticSeverity(declaredReference.Severity)); break; } } @@ -993,7 +997,7 @@ private static void ReportUnusedReferences( List 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)); } } } @@ -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 ReadDeclaredReferences(SourceText sourceText) { @@ -1178,6 +1182,7 @@ private static IEnumerable ReadDeclaredReferences(SourceText int firstTab = -1; int secondTab = -1; + int thirdTab = -1; for (int i = start; i < end; i++) { if (sourceText[i] == '\t') @@ -1186,21 +1191,25 @@ private static IEnumerable 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(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). @@ -1223,7 +1232,15 @@ private static IEnumerable 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}") + }; } diff --git a/src/Package/build/ReferenceTrimmer.targets b/src/Package/build/ReferenceTrimmer.targets index 0cf944b..4b163aa 100644 --- a/src/Package/build/ReferenceTrimmer.targets +++ b/src/Package/build/ReferenceTrimmer.targets @@ -86,7 +86,8 @@ NuGetRestoreTargets="$(NuGetRestoreTargets)" TargetFrameworkDirectories="$(TargetFrameworkDirectory)" IgnorePackageBuildFiles="@(ReferenceTrimmerIgnorePackageBuildFiles)" - NuGetPackageRoot="$(NuGetPackageRoot)" /> + NuGetPackageRoot="$(NuGetPackageRoot)" + EnableReferenceTrimmerDiagnostics="$(EnableReferenceTrimmerDiagnostics)" />