Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prune unused files #4288

Closed
wants to merge 8 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
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
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.FindSymbols;
using Microsoft.CodeAnalysis.Formatting;
using Microsoft.CodeAnalysis.Simplification;
using Microsoft.Generator.CSharp.Primitives;
Expand All @@ -20,6 +22,7 @@ namespace Microsoft.Generator.CSharp
internal class GeneratedCodeWorkspace
{
private const string GeneratedFolder = "Generated";
private const string InternalFolder = "Internal";
Copy link
Member

Choose a reason for hiding this comment

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

so this implementation looks very specific to our implementations, should we put it in a place so that others could customize or extend on?

private const string GeneratedCodeProjectName = "GeneratedCode";

private static readonly Lazy<IReadOnlyList<MetadataReference>> _assemblyMetadataReferences = new(() => new List<MetadataReference>()
Expand All @@ -30,6 +33,8 @@ 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 @@ -66,13 +71,19 @@ public async Task AddGeneratedFile(CodeFile codefile)
public async IAsyncEnumerable<(string Name, string Text)> GetGeneratedFilesAsync()
{
List<Task<Document>> documents = new List<Task<Document>>();
var used = new Dictionary<Document, bool>();
foreach (Document document in _project.Documents)
{
if (!IsGeneratedDocument(document))
{
continue;
}

if (IsInternalGeneratedDocument(document) && !(await IsUsedAsync(_project.Solution, document, used)))
{
continue;
}

documents.Add(ProcessDocument(document));
}
var docs = await Task.WhenAll(documents);
Expand All @@ -97,7 +108,9 @@ private async Task<Document> ProcessDocument(Document document)
return document;
}

public static bool IsGeneratedDocument(Document document) => document.Folders.Contains(GeneratedFolder);
private static bool IsGeneratedDocument(Document document) => document.Folders.Contains(GeneratedFolder);

private static bool IsInternalGeneratedDocument(Document document) => IsGeneratedDocument(document) && document.Name.Contains(InternalFolder);

/// <summary>
/// Create a new AdHoc workspace using the Roslyn SDK and add a project with all the necessary compilation options.
Expand Down Expand Up @@ -193,5 +206,79 @@ internal static Project AddDirectory(Project project, string directory, Func<str

return project;
}

private async Task<bool> IsUsedAsync(Solution solution, Document document, IDictionary<Document, bool> used)
{
if (used.TryGetValue(document, out var value))
{
return value;
}
var model = await document.GetSemanticModelAsync();
var declarations = (await document.GetSyntaxRootAsync())!.DescendantNodes().Where(n => n is TypeDeclarationSyntax or EnumDeclarationSyntax);
foreach (var declaration in declarations)
{
var type = (INamedTypeSymbol)model!.GetDeclaredSymbol(declaration)!;
if (await IsUsedAsync(solution, type, used))
{
used[document] = true;
return true;
}
}

used[document] = false;
return false;
}

private async Task<bool> IsUsedAsync(Solution solution, ISymbol type, IDictionary<Document, bool> used)
{
if (type.IsStatic)
{
var extMethods = ((INamedTypeSymbol)type).GetMembers().Where(s => s.Kind == SymbolKind.Method).Where(m => ((IMethodSymbol)m).IsExtensionMethod);
var enumerable = extMethods as ISymbol[] ?? extMethods.ToArray();
if (enumerable.Length != 0)
{
return true;
}
}
return await IsUsedCore(solution, type, used);
}
private async Task<bool> IsUsedCore(Solution solution, ISymbol type, IDictionary<Document, bool> used)
{
var typeRefs = await SymbolFinder.FindReferencesAsync(type, solution);
foreach (var typeRef in typeRefs)
{
foreach (var loc in typeRef.Locations)
{
// recursively search for non-internal generated code
if (IsInternalGeneratedDocument(loc.Document))
{
return await IsUsedAsync(solution, loc.Document, used);
}

var node = (await loc.Location.SourceTree?.GetRootAsync()!).FindNode(loc.Location.SourceSpan);
while (node != null && !node.IsKind(SyntaxKind.ClassDeclaration) && !node.IsKind(SyntaxKind.InterfaceDeclaration) && !node.IsKind(SyntaxKind.StructDeclaration))
{
node = node.Parent;
}
if (node == null)
{
continue;
}
Compilation compilation = await GetProjectCompilation();
ISymbol nodeSymbol = compilation.GetSemanticModel(loc.Location.SourceTree!).GetDeclaredSymbol(node)!;
if (!SymbolEqualityComparer.Default.Equals(nodeSymbol, type))
{
return true;
}
}
}
return false;
}

private async Task<Compilation> GetProjectCompilation()
{
_compilation ??= await _project.GetCompilationAsync();
return _compilation!;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,6 @@
<ProjectReference Include="..\src\Microsoft.Generator.CSharp.csproj" />
<ProjectReference Include="common\Microsoft.Generator.CSharp.Tests.Common.csproj" />
<Compile Include="../../TestProjects/Local/Unbranded-TypeSpec/src/Generated/Internal/**/*.cs" Link="Generated/Helpers/%(RecursiveDir)/%(Filename)%(Extension)" />
<Compile Remove="../../TestProjects/Local/Unbranded-TypeSpec/src/Generated/Internal/ModelSerializationExtensions.cs" />
<Compile Remove="../../TestProjects/Local/Unbranded-TypeSpec/src/Generated/Internal/ClientPipelineExtensions.cs" />
<Compile Remove="../../TestProjects/Local/Unbranded-TypeSpec/src/Generated/Internal/ErrorResult.cs" />
<Compile Remove="../../TestProjects/Local/Unbranded-TypeSpec/src/Generated/Internal/BinaryContentHelper.cs" />
<Compile Remove="../../TestProjects/Local/Unbranded-TypeSpec/src/Generated/Internal/Utf8JsonBinaryContent.cs" />
<Compile Remove="../../TestProjects/Local/Unbranded-TypeSpec/src/Generated/Internal/PipelineRequestHeadersExtensions.cs" />
</ItemGroup>

<ItemGroup>
Expand Down

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ protected virtual void JsonModelWriteCore(Utf8JsonWriter writer, ModelReaderWrit
writer.WritePropertyName("requiredString"u8);
writer.WriteStringValue(RequiredString);
writer.WritePropertyName("requiredInt"u8);
writer.WriteStringValue(RequiredInt.ToString());
writer.WriteNumberValue(RequiredInt);
writer.WritePropertyName("requiredCollection"u8);
writer.WriteStartArray();
foreach (var item in RequiredCollection)
Expand Down Expand Up @@ -316,7 +316,7 @@ internal static RoundTripModel DeserializeRoundTripModel(JsonElement element, Mo
}
if (prop.NameEquals("requiredInt"u8))
{
requiredInt = int.Parse(prop.Value.GetString());
requiredInt = prop.Value.GetInt32();
continue;
}
if (prop.NameEquals("requiredCollection"u8))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -962,7 +962,6 @@
"$id": "123",
"Kind": "int32",
"Name": "int32",
"Encode": "string",
"CrossLanguageDefinitionId": "TypeSpec.int32",
"Decorators": []
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,22 +39,16 @@ public ChangeTrackingDictionary(IReadOnlyDictionary<TKey, TValue> dictionary)
}
}

/// <summary> Gets the isundefined. </summary>
public bool IsUndefined => _innerDictionary == null;

/// <summary> Gets the count. </summary>
public int Count => IsUndefined ? 0 : EnsureDictionary().Count;

/// <summary> Gets the IsReadOnly. </summary>
public bool IsReadOnly => IsUndefined ? false : EnsureDictionary().IsReadOnly;

/// <summary> Gets the keys. </summary>
public ICollection<TKey> Keys => IsUndefined ? Array.Empty<TKey>() : EnsureDictionary().Keys;

/// <summary> Gets the values. </summary>
public ICollection<TValue> Values => IsUndefined ? Array.Empty<TValue>() : EnsureDictionary().Values;

/// <summary> Gets or sets the this. </summary>
public TValue this[TKey key]
{
get
Expand All @@ -71,10 +65,8 @@ public TValue this[TKey key]
}
}

/// <summary> Gets the keys. </summary>
IEnumerable<TKey> IReadOnlyDictionary<TKey, TValue>.Keys => Keys;

/// <summary> Gets the values. </summary>
IEnumerable<TValue> IReadOnlyDictionary<TKey, TValue>.Values => Values;

public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
Expand Down
Loading
Loading