diff --git a/src/AssemblyMetadata.Generators/AssemblyConstant.cs b/src/AssemblyMetadata.Generators/AssemblyConstant.cs index 2eca479..16edc8f 100644 --- a/src/AssemblyMetadata.Generators/AssemblyConstant.cs +++ b/src/AssemblyMetadata.Generators/AssemblyConstant.cs @@ -1,39 +1,6 @@ -using System.Diagnostics.CodeAnalysis; - namespace AssemblyMetadata.Generators; -[ExcludeFromCodeCoverage] -public class AssemblyConstant : IEquatable -{ - public AssemblyConstant(string name, string value) - { - Name = name; - Value = value; - } - - public string Name { get; } - - public string Value { get; } - - public bool Equals(AssemblyConstant other) - { - if (ReferenceEquals(null, other)) - return false; - - if (ReferenceEquals(this, other)) - return true; - - return Name == other.Name - && Value == other.Value; - } - - public override bool Equals(object value) => value is AssemblyConstant assemblyContant && Equals(assemblyContant); - - public override int GetHashCode() => HashCode.Seed.Combine(Name).Combine(Value); - - public static bool operator ==(AssemblyConstant left, AssemblyConstant right) => Equals(left, right); - - public static bool operator !=(AssemblyConstant left, AssemblyConstant right) => !Equals(left, right); - - public override string ToString() => $"Name: {Name}; Value: {Value}"; -} +public record AssemblyConstant( + string Name, + string Value +); diff --git a/src/AssemblyMetadata.Generators/AssemblyMetadata.Generators.targets b/src/AssemblyMetadata.Generators/AssemblyMetadata.Generators.targets index c9ba8f7..58214d4 100644 --- a/src/AssemblyMetadata.Generators/AssemblyMetadata.Generators.targets +++ b/src/AssemblyMetadata.Generators/AssemblyMetadata.Generators.targets @@ -1,5 +1,8 @@ + + + diff --git a/src/AssemblyMetadata.Generators/AssemblyMetadataGenerator.cs b/src/AssemblyMetadata.Generators/AssemblyMetadataGenerator.cs index 0450661..052ba9c 100644 --- a/src/AssemblyMetadata.Generators/AssemblyMetadataGenerator.cs +++ b/src/AssemblyMetadata.Generators/AssemblyMetadataGenerator.cs @@ -59,14 +59,24 @@ public void Initialize(IncrementalGeneratorInitializationContext context) var assemblyName = context.CompilationProvider .Select(static (c, _) => c.AssemblyName); - var thisNamespace = context.AnalyzerConfigOptionsProvider + var globalOptions = context.AnalyzerConfigOptionsProvider .Select(static (c, _) => { - c.GlobalOptions.TryGetValue("build_property.ThisAssemblyNamespace", out var methodName); - return methodName; + c.GlobalOptions.TryGetValue("build_property.AssemblyName", out var assemblyName); + c.GlobalOptions.TryGetValue("build_property.DefineConstants", out var defineConstants); + c.GlobalOptions.TryGetValue("build_property.RootNamespace", out var rootNamespace); + c.GlobalOptions.TryGetValue("build_property.ThisAssemblyNamespace", out var thisNamespace); + + var globalOptions = new GlobalOptions( + AssemblyName: assemblyName, + DefineConstants: defineConstants, + RootNamespace: rootNamespace, + ThisAssemblyNamespace: thisNamespace); + + return globalOptions; }); - var options = assemblyName.Combine(thisNamespace); + var options = assemblyName.Combine(globalOptions); context.RegisterSourceOutput(constants.Combine(options), GenerateOutput); } @@ -127,7 +137,7 @@ private static GeneratorContext SemanticTransform(GeneratorAttributeSyntaxContex } } - return new GeneratorContext(Enumerable.Empty(), constants); + return new GeneratorContext([], constants); } private static void ReportDiagnostic(SourceProductionContext context, EquatableArray diagnostics) @@ -136,9 +146,32 @@ private static void ReportDiagnostic(SourceProductionContext context, EquatableA context.ReportDiagnostic(diagnostic); } - private void GenerateOutput(SourceProductionContext context, (EquatableArray constants, (string? assemblyName, string? thisNamespace) options) parameters) + private void GenerateOutput(SourceProductionContext context, (EquatableArray constants, (string? assemblyName, GlobalOptions globalOptions) options) parameters) { - var source = AssemblyMetadataWriter.Generate(parameters.constants, parameters.options.assemblyName, parameters.options.thisNamespace); + + var constants = new List(parameters.constants); + + if (!constants.Any(p => p.Name == nameof(GlobalOptions.AssemblyName))) + { + var assemblyName = parameters.options.globalOptions.AssemblyName ?? parameters.options.assemblyName ?? string.Empty; + constants.Add(new AssemblyConstant(nameof(GlobalOptions.AssemblyName), $"\"{assemblyName}\"")); + } + + if (!constants.Any(p => p.Name == nameof(GlobalOptions.DefineConstants))) + { + var defineConstants = parameters.options.globalOptions.DefineConstants; + if (!string.IsNullOrEmpty(defineConstants)) + constants.Add(new AssemblyConstant(nameof(GlobalOptions.DefineConstants), $"\"{defineConstants}\""!)); + } + + if (!constants.Any(p => p.Name == nameof(GlobalOptions.RootNamespace))) + { + var rootNamespace = parameters.options.globalOptions.RootNamespace; + if (!string.IsNullOrEmpty(rootNamespace)) + constants.Add(new AssemblyConstant(nameof(GlobalOptions.RootNamespace), $"\"{rootNamespace}\""!)); + } + + var source = AssemblyMetadataWriter.Generate(constants, parameters.options.globalOptions.ThisAssemblyNamespace); context.AddSource("AssemblyMetadata.g.cs", source); } diff --git a/src/AssemblyMetadata.Generators/AssemblyMetadataWriter.cs b/src/AssemblyMetadata.Generators/AssemblyMetadataWriter.cs index a9d485b..ca04d7e 100644 --- a/src/AssemblyMetadata.Generators/AssemblyMetadataWriter.cs +++ b/src/AssemblyMetadata.Generators/AssemblyMetadataWriter.cs @@ -11,9 +11,9 @@ public static class AssemblyMetadataWriter return attribute?.InformationalVersion ?? "1.0.0.0"; }); - public static string Generate(EquatableArray constants, string? assemblyName = null, string? thisNamespace = null) + public static string Generate(IEnumerable constants, string? thisNamespace = null) { - if (constants == null) + if (constants is null) throw new ArgumentNullException(nameof(constants)); var codeBuilder = new IndentedStringBuilder(); @@ -48,15 +48,6 @@ public static string Generate(EquatableArray constants, string .IncrementIndent() .AppendLine(); - if (!string.IsNullOrEmpty(assemblyName)) - { - codeBuilder - .Append("public const string AssemblyName = \"") - .Append(assemblyName) - .AppendLine("\";") - .AppendLine(); - } - foreach (var constant in constants) { var name = SafeName(constant.Name); diff --git a/src/AssemblyMetadata.Generators/EquatableArray.cs b/src/AssemblyMetadata.Generators/EquatableArray.cs index 34d31b1..34c55e6 100644 --- a/src/AssemblyMetadata.Generators/EquatableArray.cs +++ b/src/AssemblyMetadata.Generators/EquatableArray.cs @@ -4,68 +4,61 @@ namespace AssemblyMetadata.Generators; [ExcludeFromCodeCoverage] -public readonly struct EquatableArray : IReadOnlyCollection, IEquatable> +public readonly struct EquatableArray : IEquatable>, IEnumerable where T : IEquatable { - public static readonly EquatableArray Empty = new(Array.Empty()); + public static readonly EquatableArray Empty = new(); - private readonly T[] _array; - public EquatableArray(T[] array) - { - _array = array; - } + public EquatableArray() : this([]) { } - public EquatableArray(IEnumerable? array) - { - array ??= Enumerable.Empty(); - _array = array.ToArray(); - } + public EquatableArray(T[] array) => Array = array ?? []; + + public EquatableArray(IEnumerable items) => Array = items.ToArray() ?? []; - public bool Equals(EquatableArray array) - { - return AsSpan().SequenceEqual(array.AsSpan()); - } - public override bool Equals(object obj) + public T[] Array { get; } + + public int Count => Array.Length; + + + public ReadOnlySpan AsSpan() => Array.AsSpan(); + + public T[] AsArray() => Array; + + public T this[int i] { - return obj is EquatableArray array && Equals(this, array); + get => Array[i]; } + public static bool operator ==(EquatableArray left, EquatableArray right) => left.Equals(right); + + public static bool operator !=(EquatableArray left, EquatableArray right) => !left.Equals(right); + + public bool Equals(EquatableArray array) => Array.AsSpan().SequenceEqual(array.AsSpan()); + + public override bool Equals(object? obj) => obj is EquatableArray array && Equals(this, array); + public override int GetHashCode() { - if (_array is null) + if (Array is not T[] array) return 0; - return HashCode.Seed.CombineAll(_array); - } + var hashCode = 16777619; - public ReadOnlySpan AsSpan() - { - return _array.AsSpan(); - } + for (int i = 0; i < array.Length; i++) + hashCode = unchecked((hashCode * -1521134295) + EqualityComparer.Default.GetHashCode(array[i])); - IEnumerator IEnumerable.GetEnumerator() - { - IEnumerable array = _array ?? Array.Empty(); - return array.GetEnumerator(); + return hashCode; } - IEnumerator IEnumerable.GetEnumerator() - { - IEnumerable array = _array ?? Array.Empty(); - return array.GetEnumerator(); - } - public int Count => _array?.Length ?? 0; + IEnumerator IEnumerable.GetEnumerator() => (Array as IEnumerable).GetEnumerator(); - public static bool operator ==(EquatableArray left, EquatableArray right) - { - return left.Equals(right); - } + IEnumerator IEnumerable.GetEnumerator() => Array.GetEnumerator(); - public static bool operator !=(EquatableArray left, EquatableArray right) - { - return !left.Equals(right); - } + + public static implicit operator EquatableArray(T[] array) => new(array); + + public static implicit operator EquatableArray(List items) => new(items); } diff --git a/src/AssemblyMetadata.Generators/GeneratorContext.cs b/src/AssemblyMetadata.Generators/GeneratorContext.cs index 4699a79..cf57d61 100644 --- a/src/AssemblyMetadata.Generators/GeneratorContext.cs +++ b/src/AssemblyMetadata.Generators/GeneratorContext.cs @@ -1,39 +1,8 @@ -using System.Diagnostics.CodeAnalysis; - using Microsoft.CodeAnalysis; namespace AssemblyMetadata.Generators; -[ExcludeFromCodeCoverage] -public class GeneratorContext : IEquatable -{ - public GeneratorContext(IEnumerable diagnostics, IEnumerable constants) - { - Diagnostics = new EquatableArray(diagnostics); - Constants = new EquatableArray(constants); - } - - public EquatableArray Diagnostics { get; } - - public EquatableArray Constants { get; } - - public bool Equals(GeneratorContext other) - { - if (ReferenceEquals(null, other)) - return false; - - if (ReferenceEquals(this, other)) - return true; - - return Diagnostics.Equals(other.Diagnostics) - && Constants.Equals(other.Constants); - } - - public override bool Equals(object value) => value is GeneratorContext context && Equals(context); - - public override int GetHashCode() => HashCode.Seed.CombineAll(Diagnostics).CombineAll(Constants); - - public static bool operator ==(GeneratorContext left, GeneratorContext right) => Equals(left, right); - - public static bool operator !=(GeneratorContext left, GeneratorContext right) => !Equals(left, right); -} +public record GeneratorContext( + EquatableArray Diagnostics, + EquatableArray Constants +); diff --git a/src/AssemblyMetadata.Generators/GlobalOptions.cs b/src/AssemblyMetadata.Generators/GlobalOptions.cs new file mode 100644 index 0000000..475e658 --- /dev/null +++ b/src/AssemblyMetadata.Generators/GlobalOptions.cs @@ -0,0 +1,8 @@ +namespace AssemblyMetadata.Generators; + +public record GlobalOptions( + string? AssemblyName, + string? DefineConstants, + string? RootNamespace, + string? ThisAssemblyNamespace +); diff --git a/src/AssemblyMetadata.Generators/HashCode.cs b/src/AssemblyMetadata.Generators/HashCode.cs deleted file mode 100644 index 1a6fe84..0000000 --- a/src/AssemblyMetadata.Generators/HashCode.cs +++ /dev/null @@ -1,241 +0,0 @@ -using System.Diagnostics.CodeAnalysis; - -namespace AssemblyMetadata.Generators; - - -/// -/// An immutable hash code structure -/// -/// -/// Implements the Jon Skeet suggested implementation of GetHashCode(). -/// -[ExcludeFromCodeCoverage] -public readonly struct HashCode : IFormattable, IEquatable -{ - /// - /// The prime multiplier used to combine hash codes. - /// - public const int Multiplier = 31; - - private readonly int _hashCode; - - /// - /// Initializes a new instance of the struct. - /// - /// The hash code. - public HashCode(int hashCode) - { - _hashCode = hashCode; - } - - /// - /// Gets a hash code seed value for combine hash codes values. - /// - /// - /// The hash code seed value. - /// - public static HashCode Seed => new(17); - - /// - /// Combines this hash code with the hash code of specified . - /// - /// The type of the value. - /// The value to combine hash codes with. - /// A new hash code combined with this and the values hash codes. - public HashCode Combine(TValue? value) - { - var hashCode = value is null ? 0 : EqualityComparer.Default.GetHashCode(value); - unchecked - { - hashCode = (_hashCode * Multiplier) + hashCode; - } - - return new HashCode(hashCode); - } - - /// - /// Combines this hash code with the hash code of specified . - /// - /// The value to combine hash codes with. - /// A new hash code combined with this and the values hash codes. - public HashCode Combine(string? value) - { - // need to handle string values deterministically - var hashCode = HashString(value); - unchecked - { - hashCode = (_hashCode * Multiplier) + hashCode; - } - - return new HashCode(hashCode); - } - - /// - /// Combines this hash code with the hash code of specified . - /// - /// The value to combine hash codes with. - /// A new hash code combined with this and the values hash codes. - public HashCode Combine(object? value) - { - // need to handle string values deterministically - return value switch - { - string text => Combine(text), - _ => Combine(value?.GetHashCode() ?? 0), - }; - } - - /// - /// Combines this hash code with the hash code of each item specified . - /// - /// The type of the value. - /// The values to combine hash codes with. - /// A new hash code combined with this and the values hash codes. - public HashCode CombineAll(IEnumerable? values) - { - if (values == null) - return this; - - var comparer = EqualityComparer.Default; - var current = _hashCode; - - foreach (var value in values) - { - var hashCode = value switch - { - string text => HashString(text), - TValue instance => comparer.GetHashCode(instance), - _ => 0 - }; - - unchecked - { - hashCode = (current * Multiplier) + hashCode; - } - - current = hashCode; - } - - return new HashCode(current); - } - - - /// - /// Returns a hash code for this instance. - /// - /// - /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. - /// - public override int GetHashCode() => _hashCode; - - - /// - /// Converts the numeric value of this instance to its equivalent string representation. - /// - /// - /// The string representation of the value of this instance. - /// - public override string ToString() => _hashCode.ToString(); - - /// - /// Converts the numeric value of this instance to its equivalent string representation using the specified culture-specific format information. - /// - /// An object that supplies culture-specific formatting information. - /// - /// The string representation of the value of this instance as specified by provider. - /// - public string ToString(IFormatProvider provider) => _hashCode.ToString(provider); - - /// - /// Converts the numeric value of this instance to its equivalent string representation using the specified format. - /// - /// A standard or custom numeric format string. - /// - /// The string representation of the value of this instance as specified by format. - /// - public string ToString(string format) => _hashCode.ToString(format); - - /// - /// Converts the numeric value of this instance to its equivalent string representation using the specified format and culture-specific format information. - /// - /// A standard or custom numeric format string. - /// An object that supplies culture-specific formatting information. - /// - /// The string representation of the value of this instance as specified by format and provider. - /// - public string ToString(string format, IFormatProvider provider) => _hashCode.ToString(format, provider); - - - /// - /// Determines whether the specified , is equal to this instance. - /// - /// The to compare with this instance. - /// - /// true if the specified is equal to this instance; otherwise, false. - /// - public override bool Equals(object? other) => other is HashCode code && Equals(code); - - /// - /// Indicates whether the current object is equal to another object of the same type. - /// - /// An object to compare with this object. - /// - /// true if the current object is equal to the other parameter; otherwise, false. - /// - public bool Equals(HashCode other) => _hashCode == other._hashCode; - - - /// - /// Performs an implicit conversion from to . - /// - /// The hash code. - /// - /// The result of the conversion. - /// - public static implicit operator int(HashCode hashCode) => hashCode._hashCode; - - /// - /// Compares two values to determine equality. - /// - /// The value to compare with right. - /// The value to compare with left. - /// - /// true if left is equal to right; otherwise, false. - /// - public static bool operator ==(HashCode left, HashCode right) => left.Equals(right); - - /// - /// Compares two values to determine inequality. - /// - /// The value to compare with right. - /// The value to compare with left. - /// - /// true if left is not equal to right; otherwise, false. - /// - public static bool operator !=(HashCode left, HashCode right) => !(left == right); - - - /// - /// Deterministic string hash function - /// - /// The text to hash. - /// A 32-bit signed integer hash code. - public static int HashString(string? text) - { - if (string.IsNullOrEmpty(text)) - return 0; - - int hash = Seed; - - unchecked - { - // ReSharper disable once LoopCanBeConvertedToQuery - // ReSharper disable once ForCanBeConvertedToForeach - for (var index = 0; index < text!.Length; index++) - hash = (hash * Multiplier) + text[index]; - - } - - return hash; - } -} diff --git a/src/AssemblyMetadata.Generators/IsExternalInit.cs b/src/AssemblyMetadata.Generators/IsExternalInit.cs new file mode 100644 index 0000000..75cd67b --- /dev/null +++ b/src/AssemblyMetadata.Generators/IsExternalInit.cs @@ -0,0 +1,6 @@ +using System.ComponentModel; + +namespace System.Runtime.CompilerServices; + +[EditorBrowsable(EditorBrowsableState.Never)] +internal static class IsExternalInit; diff --git a/test/AssemblyMetadata.Generators.Tests/Snapshots/GeneratorTests.GenerateAssemblyMetadataGeneratorsTests.verified.txt b/test/AssemblyMetadata.Generators.Tests/Snapshots/GeneratorTests.GenerateAssemblyMetadataGeneratorsTests.verified.txt index 4d6e088..228e7c9 100644 --- a/test/AssemblyMetadata.Generators.Tests/Snapshots/GeneratorTests.GenerateAssemblyMetadataGeneratorsTests.verified.txt +++ b/test/AssemblyMetadata.Generators.Tests/Snapshots/GeneratorTests.GenerateAssemblyMetadataGeneratorsTests.verified.txt @@ -6,8 +6,6 @@ internal static partial class ThisAssembly { - public const string AssemblyName = "Test.Generator"; - public const string HardKey = "HardValue"; public const string VerifyTargetFrameworks = ""; @@ -40,4 +38,6 @@ internal static partial class ThisAssembly public const string Version = "1.0.0.0"; + public const string AssemblyName = "Test.Generator"; + } diff --git a/test/AssemblyMetadata.Generators.Tests/Snapshots/GeneratorTests.GenerateMicrosoftCSharp.verified.txt b/test/AssemblyMetadata.Generators.Tests/Snapshots/GeneratorTests.GenerateMicrosoftCSharp.verified.txt index 99510f1..ea317e2 100644 --- a/test/AssemblyMetadata.Generators.Tests/Snapshots/GeneratorTests.GenerateMicrosoftCSharp.verified.txt +++ b/test/AssemblyMetadata.Generators.Tests/Snapshots/GeneratorTests.GenerateMicrosoftCSharp.verified.txt @@ -6,8 +6,6 @@ internal static partial class ThisAssembly { - public const string AssemblyName = "Test.Generator"; - public const string TargetFramework = ".NETCoreApp,Version=v8.0"; public const string Serviceable = "True"; @@ -36,4 +34,6 @@ internal static partial class ThisAssembly public const string Version = "8.0.0.0"; + public const string AssemblyName = "Test.Generator"; + }