Skip to content
Merged
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
31 changes: 27 additions & 4 deletions src/Analyzer/ReferenceTrimmerAnalyzer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ public class ReferenceTrimmerAnalyzer : DiagnosticAnalyzer
{
private const string DeclaredReferencesFileName = "_ReferenceTrimmer_DeclaredReferences.tsv";
private const string UsedReferencesFileName = "_ReferenceTrimmer_UsedReferences.log";
private const string UnusedReferencesFileName = "_ReferenceTrimmer_UnusedReferences.log";

private static readonly DiagnosticDescriptor RT0000Descriptor = new(
"RT0000",
Expand Down Expand Up @@ -53,6 +54,7 @@ public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics
RT0002Descriptor,
RT0003Descriptor);

/// <inheritdoc/>
public override void Initialize(AnalysisContext context)
{
context.EnableConcurrentExecution();
Expand Down Expand Up @@ -90,7 +92,21 @@ private static void DumpUsedReferences(CompilationAnalysisContext context)
}
}

WriteUsedReferencesToFile(usedReferences, declaredReferencesPath);
var globalOptions = context.Options.AnalyzerConfigOptionsProvider.GlobalOptions;
if (globalOptions.TryGetValue("build_property.EnableReferenceTrimmerDiagnostics", out string? enableDiagnostics)
&& string.Equals(enableDiagnostics, "true", StringComparison.OrdinalIgnoreCase))
{
HashSet<string> unusedReferences = new(StringComparer.OrdinalIgnoreCase);
foreach (MetadataReference metadataReference in compilation.References)
{
if (metadataReference.Display != null && !usedReferences.Contains(metadataReference.Display))
{
unusedReferences.Add(metadataReference.Display);
}
}

DumpReferencesInfo(usedReferences, unusedReferences, declaredReferencesPath);
}

Dictionary<string, List<string>> packageAssembliesDict = new(StringComparer.OrdinalIgnoreCase);
foreach (DeclaredReference declaredReference in declaredReferences.References)
Expand Down Expand Up @@ -141,12 +157,19 @@ private static void DumpUsedReferences(CompilationAnalysisContext context)
}
}

private static void WriteUsedReferencesToFile(HashSet<string> usedReferences, string declaredReferencesPath)
private static void DumpReferencesInfo(HashSet<string> usedReferences, HashSet<string> unusedReferences, string declaredReferencesPath)
{
string filePath = Path.Combine(Path.GetDirectoryName(declaredReferencesPath), UsedReferencesFileName);

string dir = Path.GetDirectoryName(declaredReferencesPath);
string filePath = Path.Combine(dir, UsedReferencesFileName);
string text = string.Join(Environment.NewLine, usedReferences.OrderBy(s => s));
WriteFile(filePath, text);
filePath = Path.Combine(dir, UnusedReferencesFileName);
text = string.Join(Environment.NewLine, unusedReferences.OrderBy(s => s));
WriteFile(filePath, text);
}

private static void WriteFile(string filePath, string text)
{
try
{
if (File.Exists(filePath))
Expand Down
7 changes: 7 additions & 0 deletions src/Package/build/ReferenceTrimmer.targets
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,15 @@
<PropertyGroup>
<CoreCompileDependsOn Condition="'$(EnableReferenceTrimmer)' != 'false'">$(CoreCompileDependsOn);CollectDeclaredReferences</CoreCompileDependsOn>
</PropertyGroup>
<ItemGroup>
<CompilerVisibleProperty Include="EnableReferenceTrimmerDiagnostics"/>
</ItemGroup>

<Target Name="CollectDeclaredReferences" DependsOnTargets="ResolveAssemblyReferences;PrepareProjectReferences">
<PropertyGroup>
<_ReferenceTrimmerDeclaredReferencesFile>$(IntermediateOutputPath)\_ReferenceTrimmer_DeclaredReferences.tsv</_ReferenceTrimmerDeclaredReferencesFile>
<_ReferenceTrimmerUsedReferencesFile>$(IntermediateOutputPath)\_ReferenceTrimmer_UsedReferences.log</_ReferenceTrimmerUsedReferencesFile>
<_ReferenceTrimmerUnusedReferencesFile>$(IntermediateOutputPath)\_ReferenceTrimmer_UnusedReferences.log</_ReferenceTrimmerUnusedReferencesFile>
</PropertyGroup>
<ItemGroup>
<!--
Expand Down Expand Up @@ -42,6 +47,8 @@
<!-- https://github.com/dotnet/roslyn/blob/main/docs/analyzers/Using%20Additional%20Files.md#in-a-project-file -->
<AdditionalFiles Include="$(_ReferenceTrimmerDeclaredReferencesFile)" />
<FileWrites Include="$(_ReferenceTrimmerDeclaredReferencesFile)" />
<FileWrites Include="$(_ReferenceTrimmerUsedReferencesFile)" Condition="'$(EnableReferenceTrimmerDiagnostics)'=='true'" />
<FileWrites Include="$(_ReferenceTrimmerUnusedReferencesFile)" Condition="'$(EnableReferenceTrimmerDiagnostics)'=='true'" />
</ItemGroup>
</Target>
</Project>
19 changes: 15 additions & 4 deletions src/Tests/E2ETests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,14 +54,17 @@ public Task UsedProjectReferenceNoReferenceAssembly()
}

[TestMethod]
public Task UnusedProjectReference()
[DataRow(true)]
[DataRow(false)]
public Task UnusedProjectReference(bool enableReferenceTrimmerDiagnostics)
{
return RunMSBuildAsync(
projectFile: "Library/Library.csproj",
expectedWarnings: new[]
{
new Warning("RT0002: ProjectReference ../Dependency/Dependency.csproj can be removed", "Library/Library.csproj"),
});
},
enableReferenceTrimmerDiagnostics: enableReferenceTrimmerDiagnostics);
}

[TestMethod]
Expand Down Expand Up @@ -524,7 +527,8 @@ private static void DirectoryCopy(string sourceDirName, string destDirName)
}
}

private async Task RunMSBuildAsync(string projectFile, Warning[] expectedWarnings, string[]? expectedConsoleOutputs = null, bool expectUnusedMsvcLibrariesLog = false)
private async Task RunMSBuildAsync(string projectFile, Warning[] expectedWarnings, string[]? expectedConsoleOutputs = null, bool expectUnusedMsvcLibrariesLog = false,
bool enableReferenceTrimmerDiagnostics = false)
{
var testDataSourcePath = Path.GetFullPath(Path.Combine("TestData", TestContext?.TestName ?? string.Empty));

Expand All @@ -548,7 +552,8 @@ private async Task RunMSBuildAsync(string projectFile, Warning[] expectedWarning
$"-bl:\"{binlogFilePath}\" " +
$"-flp1:logfile=\"{errorsFilePath}\";errorsonly " +
$"-flp2:logfile=\"{warningsFilePath}\";warningsonly " +
$"-distributedlogger:CentralLogger,\"{loggersAssemblyPath}\"*ForwardingLogger,\"{loggersAssemblyPath}\"";
$"-distributedlogger:CentralLogger,\"{loggersAssemblyPath}\"*ForwardingLogger,\"{loggersAssemblyPath}\" " +
(enableReferenceTrimmerDiagnostics ? "-p:EnableReferenceTrimmerDiagnostics=true" : string.Empty);

Process? process = Process.Start(
new ProcessStartInfo
Expand Down Expand Up @@ -620,5 +625,11 @@ private async Task RunMSBuildAsync(string projectFile, Warning[] expectedWarning
Assert.IsTrue(stdOut.Contains(expectedLogOutput, StringComparison.OrdinalIgnoreCase), $"Expected log output '{expectedLogOutput}' was not found. Full console stdout: {stdOut}{Environment.NewLine}Console stderr: {stdErr}");
}
}

// local tests run debug, CI builds run release, thus the assertion needs to look for the file
var usedReferencesFiles = Directory.GetFiles(testDataSourcePath, "_ReferenceTrimmer_UsedReferences.log", SearchOption.AllDirectories);
var unusedReferencesFiles = Directory.GetFiles(testDataSourcePath, "_ReferenceTrimmer_UnusedReferences.log", SearchOption.AllDirectories);
Assert.AreEqual(enableReferenceTrimmerDiagnostics, usedReferencesFiles.Length > 0);
Assert.AreEqual(enableReferenceTrimmerDiagnostics, unusedReferencesFiles.Length > 0);
}
}