Skip to content

Commit

Permalink
Prune unreferenced files (#4301)
Browse files Browse the repository at this point in the history
Fixes #4218

Replaces #4288
  • Loading branch information
JoshLove-msft committed Sep 4, 2024
1 parent 12ccdd1 commit 1dcd5c4
Show file tree
Hide file tree
Showing 70 changed files with 1,215 additions and 1,101 deletions.
16 changes: 14 additions & 2 deletions packages/http-client-csharp/eng/scripts/Test-CadlRanch.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,22 @@ foreach ($directory in $directories) {
if (-not (Compare-Paths $subPath $filter)) {
continue
}


$testPath = "$cadlRanchRoot.Tests"
$testFilter = "TestProjects.CadlRanch.Tests"
foreach ($folder in $folders) {
$testFilter += ".$(Get-Namespace $folder)"
$segment = "$(Get-Namespace $folder)"

# the test directory names match the test namespace names, but the source directory names will not have the leading underscore
# so check to see if the filter should contain a leading underscore by comparing with the test directory
if (-not (Test-Path (Join-Path $testPath $segment))) {
$testFilter += "._$segment"
$testPath = Join-Path $testPath "_$segment"
}
else{
$testFilter += ".$segment"
$testPath = Join-Path $testPath $segment
}
}

Write-Host "Regenerating $subPath" -ForegroundColor Cyan
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ public async Task ExecuteAsync()
DeleteDirectory(generatedTestOutputPath, _filesToKeep);
}

await workspace.PostProcessAsync();

// Write the generated files to the output directory
await foreach (var file in workspace.GetGeneratedFilesAsync())
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,13 @@ public class Configuration
"Enum",
];

internal enum UnreferencedTypesHandlingOption
{
RemoveOrInternalize = 0,
Internalize = 1,
KeepAll = 2
}

private const string GeneratedFolderName = "Generated";
private const string ConfigurationFileName = "Configuration.json";

Expand All @@ -43,7 +50,8 @@ private Configuration(
string libraryName,
bool useModelNamespace,
string libraryNamespace,
bool disableXmlDocs)
bool disableXmlDocs,
UnreferencedTypesHandlingOption unreferencedTypesHandling)
{
OutputDirectory = outputPath;
AdditionalConfigOptions = additionalConfigOptions;
Expand All @@ -56,6 +64,7 @@ private Configuration(
RootNamespace = GetCleanNameSpace(libraryNamespace);
ModelNamespace = useModelNamespace ? $"{RootNamespace}.Models" : RootNamespace;
DisableXmlDocs = disableXmlDocs;
UnreferencedTypesHandling = unreferencedTypesHandling;
}

private string GetCleanNameSpace(string libraryNamespace)
Expand Down Expand Up @@ -127,6 +136,7 @@ private static class Options
public const string Namespace = "namespace";
public const string UseModelNamespace = "use-model-namespace";
public const string DisableXmlDocs = "disable-xml-docs";
public const string UnreferencedTypesHandling = "unreferenced-types-handling";
}

/// <summary>
Expand All @@ -142,6 +152,8 @@ private static class Options

internal string OutputDirectory { get; }

internal static UnreferencedTypesHandlingOption UnreferencedTypesHandling { get; private set; } = UnreferencedTypesHandlingOption.RemoveOrInternalize;

private string? _projectDirectory;
internal string ProjectDirectory => _projectDirectory ??= Path.Combine(OutputDirectory, "src");

Expand Down Expand Up @@ -211,7 +223,8 @@ internal static Configuration Load(string outputPath, string? json = null)
ReadRequiredStringOption(root, Options.LibraryName),
ReadOption(root, Options.UseModelNamespace),
ReadRequiredStringOption(root, Options.Namespace),
ReadOption(root, Options.DisableXmlDocs));
ReadOption(root, Options.DisableXmlDocs),
ReadEnumOption<UnreferencedTypesHandlingOption>(root, Options.UnreferencedTypesHandling));
}

/// <summary>
Expand Down Expand Up @@ -240,6 +253,7 @@ internal static Configuration Load(string outputPath, string? json = null)
Options.UseModelNamespace,
Options.Namespace,
Options.DisableXmlDocs,
Options.UnreferencedTypesHandling,
};

private static bool ReadOption(JsonElement root, string option)
Expand Down Expand Up @@ -276,6 +290,22 @@ private static bool GetDefaultBoolOptionValue(string option)
return _defaultBoolOptionValues.TryGetValue(option, out bool defaultValue) && defaultValue;
}

private static T ReadEnumOption<T>(JsonElement root, string option) where T : struct, Enum
{
if (root.TryGetProperty(option, out JsonElement value) && Enum.TryParse<T>(value.ToString(), true, out var enumValue))
{
return enumValue;
}

return (T)GetDefaultEnumOptionValue(option)!;
}

public static Enum? GetDefaultEnumOptionValue(string option) => option switch
{
Options.UnreferencedTypesHandling => UnreferencedTypesHandlingOption.RemoveOrInternalize,
_ => null
};

/// <summary>
/// Parses the additional configuration options from the given JSON element root and stores them in a dictionary.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ .. BuildModelFactory()

private static TypeProvider[] BuildModelFactory()
{
var modelFactory = new ModelFactoryProvider(CodeModelPlugin.Instance.InputLibrary.InputNamespace.Models);
var modelFactory = ModelFactoryProvider.FromInputLibrary();
return modelFactory.Methods.Count > 0 ? [modelFactory] : [];
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@

using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.IO;
using System.Linq;
Expand All @@ -14,13 +13,15 @@
using Microsoft.CodeAnalysis.Formatting;
using Microsoft.CodeAnalysis.Simplification;
using Microsoft.Generator.CSharp.Primitives;
using Microsoft.Generator.CSharp.Providers;

namespace Microsoft.Generator.CSharp
{
internal class GeneratedCodeWorkspace
{
private const string GeneratedFolder = "Generated";
private const string GeneratedCodeProjectName = "GeneratedCode";
private const string GeneratedTestFolder = "GeneratedTests";

private static readonly Lazy<IReadOnlyList<MetadataReference>> _assemblyMetadataReferences = new(() => new List<MetadataReference>()
{ MetadataReference.CreateFromFile(typeof(object).Assembly.Location) });
Expand All @@ -30,6 +31,7 @@ internal class GeneratedCodeWorkspace
private static readonly string _newLine = "\n";

private Project _project;
private Compilation? _compilation;
private Dictionary<string, string> PlainFiles { get; }

private GeneratedCodeWorkspace(Project generatedCodeProject)
Expand Down Expand Up @@ -101,11 +103,22 @@ public async Task AddGeneratedFile(CodeFile codefile)

private async Task<Document> ProcessDocument(Document document)
{
var syntaxTree = await document.GetSyntaxTreeAsync();
var compilation = await GetProjectCompilationAsync();
if (syntaxTree != null)
{
var semanticModel = compilation.GetSemanticModel(syntaxTree);
var modelRemoveRewriter = new MemberRemoverRewriter(_project, semanticModel);
document = document.WithSyntaxRoot(modelRemoveRewriter.Visit(await syntaxTree.GetRootAsync()));
}

document = await Simplifier.ReduceAsync(document);
return document;
}

public static bool IsGeneratedDocument(Document document) => document.Folders.Contains(GeneratedFolder);
public static bool IsCustomDocument(Document document) => !IsGeneratedDocument(document);
public static bool IsGeneratedTestDocument(Document document) => document.Folders.Contains(GeneratedTestFolder);

/// <summary>
/// Create a new AdHoc workspace using the Roslyn SDK and add a project with all the necessary compilation options.
Expand Down Expand Up @@ -183,7 +196,7 @@ internal static GeneratedCodeWorkspace CreateExistingCodeProject(string projectD
}

/// <summary>
/// Add the files in the directory to a project per a given predicate with the folders specified
/// Add the files in the directory to a project per a given predicate with the folders specified.
/// </summary>
/// <param name="project"></param>
/// <param name="directory"></param>
Expand All @@ -202,5 +215,33 @@ internal static Project AddDirectory(Project project, string directory, Func<str

return project;
}

/// <summary>
/// This method invokes the postProcessor to do some post processing work
/// Depending on the configuration, it will either remove + internalize, just internalize or do nothing
/// </summary>
public async Task PostProcessAsync()
{
var modelFactory = ModelFactoryProvider.FromInputLibrary();
var postProcessor = new PostProcessor(CodeModelPlugin.Instance.TypeFactory.UnionTypes, modelFactoryFullName: $"{modelFactory.Namespace}.{modelFactory.Name}");
switch (Configuration.UnreferencedTypesHandling)
{
case Configuration.UnreferencedTypesHandlingOption.KeepAll:
break;
case Configuration.UnreferencedTypesHandlingOption.Internalize:
_project = await postProcessor.InternalizeAsync(_project);
break;
case Configuration.UnreferencedTypesHandlingOption.RemoveOrInternalize:
_project = await postProcessor.InternalizeAsync(_project);
_project = await postProcessor.RemoveAsync(_project);
break;
}
}

private async Task<Compilation> GetProjectCompilationAsync()
{
_compilation ??= await _project.GetCompilationAsync();
return _compilation!;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;

namespace Microsoft.Generator.CSharp
{
internal class MemberRemoverRewriter : CSharpSyntaxRewriter
{
private readonly Project _project;
private readonly SemanticModel _semanticModel;

public MemberRemoverRewriter(Project project, SemanticModel semanticModel)
{
_project = project;
_semanticModel = semanticModel;
}

public override SyntaxNode? VisitCompilationUnit(CompilationUnitSyntax node)
{
var visitedNode = base.VisitCompilationUnit(node);
return visitedNode is CompilationUnitSyntax cu && !cu.Members.Any() ? SyntaxFactory.CompilationUnit() : visitedNode;
}

public override SyntaxNode? VisitNamespaceDeclaration(NamespaceDeclarationSyntax node)
{
var visitedNode = base.VisitNamespaceDeclaration(node);
return visitedNode is NamespaceDeclarationSyntax ns && !ns.Members.Any() ? null : visitedNode;
}

public override SyntaxNode? VisitConstructorDeclaration(ConstructorDeclarationSyntax node)
{
var symbol = _semanticModel.GetDeclaredSymbol(node);
return ShouldRemoveMember(symbol) ? null : base.VisitConstructorDeclaration(node);
}

public override SyntaxNode? VisitPropertyDeclaration(PropertyDeclarationSyntax node)
{
var symbol = _semanticModel.GetDeclaredSymbol(node);
return ShouldRemoveMember(symbol) ? null : base.VisitPropertyDeclaration(node);
}

public override SyntaxNode? VisitMethodDeclaration(MethodDeclarationSyntax node)
{
var symbol = _semanticModel.GetDeclaredSymbol(node);
return ShouldRemoveMember(symbol) ? null : base.VisitMethodDeclaration(node);
}

public override SyntaxNode? VisitOperatorDeclaration(OperatorDeclarationSyntax node)
{
var symbol = _semanticModel.GetDeclaredSymbol(node);
return ShouldRemoveMember(symbol) ? null : base.VisitOperatorDeclaration(node);
}

public override SyntaxNode? VisitConversionOperatorDeclaration(ConversionOperatorDeclarationSyntax node)
{
var symbol = _semanticModel.GetDeclaredSymbol(node);
return ShouldRemoveMember(symbol) ? null : base.VisitConversionOperatorDeclaration(node);
}

public override SyntaxNode? VisitFieldDeclaration(FieldDeclarationSyntax node)
{
var symbol = node.Declaration.Variables.Count == 1
? _semanticModel.GetDeclaredSymbol(node.Declaration.Variables[0])
: null;

return ShouldRemoveMember(symbol) ? null : base.VisitFieldDeclaration(node);
}

private bool ShouldRemoveMember(ISymbol? symbol)
{
if (symbol != null)
{
INamedTypeSymbol? containingType = symbol.ContainingType;
IMethodSymbol? methodSymbol = symbol as IMethodSymbol;

while (containingType != null)
{
var members = containingType.GetMembers(symbol.Name);
foreach (var member in members)
{
if (!SymbolEqualityComparer.Default.Equals(member, symbol) &&
IsDeclaredInNonGeneratedCode(member))
{
if (methodSymbol != null &&
member is IMethodSymbol memberMethodSymbol &&
!methodSymbol.Parameters.SequenceEqual(
memberMethodSymbol.Parameters, (s1, s2) => SymbolEqualityComparer.Default.Equals(s1, s2.Type)))
{
continue;
}

return true;
}
}

// Skip traversing parents for constructors and explicit interface implementations
if (methodSymbol != null &&
(methodSymbol.MethodKind == MethodKind.Constructor ||
!methodSymbol.ExplicitInterfaceImplementations.IsEmpty))
{
break;
}
containingType = containingType.BaseType;
}
}

return false;
}

private bool IsDeclaredInNonGeneratedCode(ISymbol member)
{
var references = member.DeclaringSyntaxReferences;

if (references.Length == 0)
{
return false;
}

foreach (var reference in references)
{
Document? document = _project.GetDocument(reference.SyntaxTree);

if (document != null && GeneratedCodeWorkspace.IsGeneratedDocument(document))
{
return false;
}
}

return true;
}

private readonly struct Supression
{
private readonly string? _name;
private readonly ISymbol?[] _types;

public Supression(string? name, ISymbol?[] types)
{
_name = name;
_types = types;
}

public bool Matches(ISymbol symbol)
{
if (symbol is IMethodSymbol methodSymbol)
{
string name = methodSymbol.Name;
// Use friendly name for ctors
if (methodSymbol.MethodKind == MethodKind.Constructor)
{
name = methodSymbol.ContainingType.Name;
}

return _name == name &&
_types.SequenceEqual(methodSymbol.Parameters.Select(p => p.Type), SymbolEqualityComparer.Default);
}
else
{
return symbol.Name == _name;
}
}
}
}
}
Loading

0 comments on commit 1dcd5c4

Please sign in to comment.