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

Proposal: represent of CompilerItems and CompilerProperties as dedicated types #245

Merged
merged 2 commits into from
Mar 7, 2024
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
14 changes: 4 additions & 10 deletions src/Buildalyzer/AnalyzerResult.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,6 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using Buildalyzer.Construction;
using Buildalyzer.Logging;
using Microsoft.Build.Framework;

namespace Buildalyzer;

Expand Down Expand Up @@ -162,15 +156,15 @@
internal void ProcessProject(PropertiesAndItems propertiesAndItems)
{
// Add properties
foreach (DictionaryEntry entry in propertiesAndItems.Properties.ToDictionaryEntries())
foreach (var entry in propertiesAndItems.Properties)

Check warning on line 159 in src/Buildalyzer/AnalyzerResult.cs

View workflow job for this annotation

GitHub Actions / Build (ubuntu-latest)

([deprecated] Use RCS1264 instead) Use explicit type instead of 'var' (https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1009)

Check warning on line 159 in src/Buildalyzer/AnalyzerResult.cs

View workflow job for this annotation

GitHub Actions / Build (macos-latest)

([deprecated] Use RCS1264 instead) Use explicit type instead of 'var' (https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1009)
{
_properties[entry.Key.ToString()] = entry.Value.ToString();
_properties[entry.Key] = entry.StringValue;
}

// Add items
foreach (IGrouping<string, DictionaryEntry> itemGroup in propertiesAndItems.Items.ToDictionaryEntries().GroupBy(x => x.Key.ToString()))
foreach (var items in propertiesAndItems.Items)

Check warning on line 165 in src/Buildalyzer/AnalyzerResult.cs

View workflow job for this annotation

GitHub Actions / Build (ubuntu-latest)

([deprecated] Use RCS1264 instead) Use explicit type instead of 'var' (https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1009)

Check warning on line 165 in src/Buildalyzer/AnalyzerResult.cs

View workflow job for this annotation

GitHub Actions / Build (macos-latest)

([deprecated] Use RCS1264 instead) Use explicit type instead of 'var' (https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1009)
{
_items[itemGroup.Key] = itemGroup.Select(x => new ProjectItem((ITaskItem)x.Value)).ToArray();
_items[items.Key] = items.Values.Select(task => new ProjectItem(task)).ToArray();
}
}

Expand Down
32 changes: 32 additions & 0 deletions src/Buildalyzer/Compiler/CompilerItems.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#nullable enable

using Microsoft.Build.Framework;

namespace Buildalyzer;

[DebuggerDisplay("{Key}, Count = {Count}")]
[DebuggerTypeProxy(typeof(Diagnostics.CollectionDebugView<ITaskItem>))]
#pragma warning disable CA1710 // Identifiers should have correct suffix

// CompilerItems describes the type the best.
public readonly struct CompilerItems : IReadOnlyCollection<ITaskItem>
#pragma warning restore CA1710 // Identifiers should have correct suffix
{
private readonly IReadOnlyCollection<ITaskItem> _values;

public CompilerItems(string key, IReadOnlyCollection<ITaskItem> values)
{
Key = key;
_values = values;
}

public readonly string Key;

public IReadOnlyCollection<ITaskItem> Values => _values ?? Array.Empty<ITaskItem>();

public int Count => Values.Count;

public IEnumerator<ITaskItem> GetEnumerator() => Values.GetEnumerator();

IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
62 changes: 62 additions & 0 deletions src/Buildalyzer/Compiler/CompilerItemsCollection.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
#nullable enable

using Microsoft.Build.Framework;

namespace Buildalyzer;

[DebuggerDisplay("Count = {Count}")]
[DebuggerTypeProxy(typeof(Diagnostics.CollectionDebugView<CompilerItems>))]
public sealed class CompilerItemsCollection : IReadOnlyCollection<CompilerItems>
{
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private readonly Dictionary<string, IReadOnlyCollection<ITaskItem>> _values = new Dictionary<string, IReadOnlyCollection<ITaskItem>>(StringComparer.OrdinalIgnoreCase);

private CompilerItemsCollection()
{
}

public CompilerItemsCollection(IEnumerable<KeyValuePair<string, IReadOnlyCollection<ITaskItem>>> values)
{
_values = values.ToDictionary(kvp => kvp.Key, kvp => kvp.Value, StringComparer.OrdinalIgnoreCase);
}

public int Count => _values.Count;

[Pure]
public CompilerItems? TryGet(string key)
=> _values.TryGetValue(key, out IReadOnlyCollection<ITaskItem>? values)
? new CompilerItems(key, values)
: null;

[Pure]
public IEnumerator<CompilerItems> GetEnumerator()
{
return Select().GetEnumerator();

IEnumerable<CompilerItems> Select() => _values.Select(kvp => new CompilerItems(kvp.Key, kvp.Value));
}

[Pure]
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();

[Pure]
internal static CompilerItemsCollection FromDictionaryEntries(IEnumerable properties)
{
CompilerItemsCollection props = new CompilerItemsCollection();

foreach (DictionaryEntry entry in properties.ToDictionaryEntries())
{
if (entry.Key?.ToString() is { Length: > 0 } key && entry.Value is ITaskItem task)
{
if (!props._values.TryGetValue(key, out IReadOnlyCollection<ITaskItem>? values)
|| values is not List<ITaskItem> editable)
{
editable = new List<ITaskItem>();
props._values[key] = editable;
}
editable.Add(task);
}
}
return props;
}
}
58 changes: 58 additions & 0 deletions src/Buildalyzer/Compiler/CompilerProperties.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
#nullable enable

namespace Buildalyzer;

[DebuggerDisplay("Count = {Count}")]
[DebuggerTypeProxy(typeof(Diagnostics.CollectionDebugView<CompilerProperty>))]
#pragma warning disable CA1710 // Identifiers should have correct suffix

// CompilerProperties describes the type the best.
public sealed class CompilerProperties : IReadOnlyCollection<CompilerProperty>
#pragma warning restore CA1710 // Identifiers should have correct suffix
{
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private readonly Dictionary<string, object> _values = new(StringComparer.OrdinalIgnoreCase);

private CompilerProperties()
{
}

public CompilerProperties(IEnumerable<KeyValuePair<string, object>> values)
{
_values = values.ToDictionary(kvp => kvp.Key, kvp => kvp.Value, StringComparer.OrdinalIgnoreCase);
}

public int Count => _values.Count;

[Pure]
public CompilerProperty? TryGet(string key)
=> _values.TryGetValue(key, out object? value)
? new CompilerProperty(key, value)
: null;

[Pure]
public IEnumerator<CompilerProperty> GetEnumerator()
{
return Select().GetEnumerator();

IEnumerable<CompilerProperty> Select() => _values.Select(kvp => new CompilerProperty(kvp.Key, kvp.Value));
}

[Pure]
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();

[Pure]
internal static CompilerProperties FromDictionaryEntries(IEnumerable properties)
{
CompilerProperties props = new CompilerProperties();

foreach (DictionaryEntry entry in properties.ToDictionaryEntries())
{
if (entry.Key?.ToString() is { Length: > 0 } key && entry.Value is { })
{
props._values.Add(key, entry.Value);
}
}
return props;
}
}
22 changes: 22 additions & 0 deletions src/Buildalyzer/Compiler/CompilerProperty.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#nullable enable

namespace Buildalyzer;

public readonly struct CompilerProperty
{
public CompilerProperty(string key, object value)
{
Key = key;
Value = value;
}

public readonly string Key;

public readonly object Value;

public string StringValue => Value?.ToString() ?? string.Empty;

public Type? ValueType => Value?.GetType();

public override string ToString() => $"{Key}: {Value}";
}
21 changes: 21 additions & 0 deletions src/Buildalyzer/Diagnostics/CollectionDebugView.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#nullable enable

namespace Buildalyzer.Diagnostics;

/// <summary>Allows the debugger to display collections.</summary>
internal sealed class CollectionDebugView<T>
{
/// <summary>A reference to the enumeration to display.</summary>
private readonly IEnumerable<T> _enumeration;

/// <summary>Initializes a new instance of the <see cref="CollectionDebugView{T}"/> class..</summary>
public CollectionDebugView(IEnumerable<T> enumeration) => _enumeration = enumeration;

/// <summary>The array that is shown by the debugger.</summary>
/// <remarks>
/// Every time the enumeration is shown in the debugger, a new array is created.
/// By doing this, it is always in sync with the current state of the enumeration.
/// </remarks>
[DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
public T[] Items => _enumeration.ToArray();
}
37 changes: 15 additions & 22 deletions src/Buildalyzer/IEnumerableExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,29 +1,22 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
#nullable enable

namespace Buildalyzer;

internal static class IEnumerableExtensions
{
internal static IEnumerable<DictionaryEntry> ToDictionaryEntries(this IEnumerable enumerable) =>
enumerable
[Pure]
internal static IEnumerable<DictionaryEntry> ToDictionaryEntries(this IEnumerable? enumerable)
=> enumerable?
.Cast<object>()
.Select(x =>
{
switch (x)
{
case DictionaryEntry dictionaryEntry:
return dictionaryEntry;
case KeyValuePair<string, string> kvpStringString:
return new DictionaryEntry(kvpStringString.Key, kvpStringString.Value);
case KeyValuePair<string, object> kvpStringObject:
return new DictionaryEntry(kvpStringObject.Key, kvpStringObject.Value);
case KeyValuePair<object, object> kvpObjectObject:
return new DictionaryEntry(kvpObjectObject.Key, kvpObjectObject.Value);
default:
throw new InvalidOperationException("Could not determine enumerable dictionary entry type");
}
});
.Select(AsDictionaryEntry)
?? Array.Empty<DictionaryEntry>();

private static DictionaryEntry AsDictionaryEntry(object? obj) => obj switch
{
DictionaryEntry entry => entry,
KeyValuePair<string, object?> strObj => new DictionaryEntry(strObj.Key, strObj.Value),
KeyValuePair<string, string> strStr => new DictionaryEntry(strStr.Key, strStr.Value),
KeyValuePair<object, object?> objObj => new DictionaryEntry(objObj.Key, objObj.Value),
_ => throw new InvalidOperationException($"Could not determine enumerable dictionary entry type for {obj?.GetType()}."),
};
}
18 changes: 8 additions & 10 deletions src/Buildalyzer/Logging/EventProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,8 @@
{
_evalulationResults[slEv.BuildEventContext.EvaluationId] = new PropertiesAndItems
{
Properties = slEv.Properties,
Items = slEv.Items
Properties = CompilerProperties.FromDictionaryEntries(slEv.Properties),
Items = CompilerItemsCollection.FromDictionaryEntries(slEv.Items),
};
}
}
Expand All @@ -94,17 +94,15 @@
: null)
: new PropertiesAndItems
{
Properties = e.Properties,
Items = e.Items
Properties = CompilerProperties.FromDictionaryEntries(e.Properties),
Items = CompilerItemsCollection.FromDictionaryEntries(e.Items),
};

// Get the TFM for this project
string tfm = propertiesAndItems
?.Properties
?.ToDictionaryEntries()
.FirstOrDefault(x => string.Equals(x.Key.ToString(), "TargetFrameworkMoniker", StringComparison.OrdinalIgnoreCase))
.Value
?.ToString() ?? string.Empty; // use an empty string if no target framework was found, for example in case of C++ projects with VS >= 2022
// use an empty string if no target framework was found, for example in case of C++ projects with VS >= 2022
var tfm = propertiesAndItems?.Properties.TryGet("TargetFrameworkMoniker")?.StringValue

Check warning on line 103 in src/Buildalyzer/Logging/EventProcessor.cs

View workflow job for this annotation

GitHub Actions / Build (windows-latest)

([deprecated] Use RCS1264 instead) Use explicit type instead of 'var' (https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1008)

Check warning on line 103 in src/Buildalyzer/Logging/EventProcessor.cs

View workflow job for this annotation

GitHub Actions / Build (ubuntu-latest)

([deprecated] Use RCS1264 instead) Use explicit type instead of 'var' (https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1008)

Check warning on line 103 in src/Buildalyzer/Logging/EventProcessor.cs

View workflow job for this annotation

GitHub Actions / Build (macos-latest)

([deprecated] Use RCS1264 instead) Use explicit type instead of 'var' (https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1008)
?? string.Empty;

if (propertiesAndItems != null && propertiesAndItems.Properties != null && propertiesAndItems.Items != null)
{
if (!_results.TryGetValue(tfm, out AnalyzerResult result))
Expand Down
6 changes: 2 additions & 4 deletions src/Buildalyzer/Logging/PropertiesAndItems.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
using System.Collections;

namespace Buildalyzer.Logging;

internal class PropertiesAndItems
{
public IEnumerable Properties { get; set; }
public IEnumerable Items { get; set; }
public CompilerProperties Properties { get; init; }
public CompilerItemsCollection Items { get; init; }
}
3 changes: 2 additions & 1 deletion src/Buildalyzer/Properties/GlobalUsings.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
global using System;
global using System;
global using System.Collections;
global using System.Collections.Generic;
global using System.Collections.Immutable;
global using System.Diagnostics;
Expand Down
Loading