From ff4ff6223e68c69e30a98ee535e83083b5e76228 Mon Sep 17 00:00:00 2001 From: Beakona Date: Sat, 16 Mar 2024 01:13:35 +0100 Subject: [PATCH] attribute generation bugs --- AutoInterface.sln | 12 ++ .../AutoInterfaceSample.csproj | 5 +- AutoInterfaceSample/Program.cs | 58 ++----- AutoInterfaceSample/SimpleLogger.cs | 11 -- .../AutoInterfaceSampleNetStandard.csproj | 15 ++ AutoInterfaceSampleNetStandard/TestRecord.cs | 25 +++ .../AliasRegistry.cs | 14 ++ .../AutoInterfaceResource.Designer.cs | 27 ++++ .../AutoInterfaceResource.resx | 9 ++ .../AutoInterfaceSourceGenerator.cs | 31 +++- .../BeaKona.AutoInterfaceGenerator.csproj | 2 +- .../CSharpCodeTextWriter.cs | 139 +++++++++------- .../CompilationExtensions.cs | 21 +++ BeaKona.AutoInterfaceGenerator/Helpers.cs | 17 ++ .../ICodeTextWriter.cs | 2 +- .../SourceBuilder.cs | 153 +++++++++++------- .../TypeRegistry.cs | 24 +++ TestInterfacesNetStandard/Interfaces.cs | 9 ++ .../NotNullWhenAttribute.cs | 11 ++ .../TestInterfacesNetStandard.csproj | 7 + 20 files changed, 409 insertions(+), 183 deletions(-) delete mode 100644 AutoInterfaceSample/SimpleLogger.cs create mode 100644 AutoInterfaceSampleNetStandard/AutoInterfaceSampleNetStandard.csproj create mode 100644 AutoInterfaceSampleNetStandard/TestRecord.cs create mode 100644 BeaKona.AutoInterfaceGenerator/AliasRegistry.cs create mode 100644 BeaKona.AutoInterfaceGenerator/TypeRegistry.cs create mode 100644 TestInterfacesNetStandard/Interfaces.cs create mode 100644 TestInterfacesNetStandard/NotNullWhenAttribute.cs create mode 100644 TestInterfacesNetStandard/TestInterfacesNetStandard.csproj diff --git a/AutoInterface.sln b/AutoInterface.sln index 90af706..75fd6c2 100644 --- a/AutoInterface.sln +++ b/AutoInterface.sln @@ -19,6 +19,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BeaKona.AutoInterfaceAttributes", "BeaKona.AutoInterfaceAttributes\BeaKona.AutoInterfaceAttributes.csproj", "{8EB5E4A3-F09E-42A8-9846-C000B2D5286C}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestInterfacesNetStandard", "TestInterfacesNetStandard\TestInterfacesNetStandard.csproj", "{FD5316C0-B9E2-48C9-8597-19D241D2CCE4}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AutoInterfaceSampleNetStandard", "AutoInterfaceSampleNetStandard\AutoInterfaceSampleNetStandard.csproj", "{FFDE2B7B-1B86-4FFD-800F-0682B5144560}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -39,6 +43,14 @@ Global {8EB5E4A3-F09E-42A8-9846-C000B2D5286C}.Debug|Any CPU.Build.0 = Debug|Any CPU {8EB5E4A3-F09E-42A8-9846-C000B2D5286C}.Release|Any CPU.ActiveCfg = Release|Any CPU {8EB5E4A3-F09E-42A8-9846-C000B2D5286C}.Release|Any CPU.Build.0 = Release|Any CPU + {FD5316C0-B9E2-48C9-8597-19D241D2CCE4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FD5316C0-B9E2-48C9-8597-19D241D2CCE4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FD5316C0-B9E2-48C9-8597-19D241D2CCE4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FD5316C0-B9E2-48C9-8597-19D241D2CCE4}.Release|Any CPU.Build.0 = Release|Any CPU + {FFDE2B7B-1B86-4FFD-800F-0682B5144560}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FFDE2B7B-1B86-4FFD-800F-0682B5144560}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FFDE2B7B-1B86-4FFD-800F-0682B5144560}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FFDE2B7B-1B86-4FFD-800F-0682B5144560}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/AutoInterfaceSample/AutoInterfaceSample.csproj b/AutoInterfaceSample/AutoInterfaceSample.csproj index 719528d..ff29ac1 100644 --- a/AutoInterfaceSample/AutoInterfaceSample.csproj +++ b/AutoInterfaceSample/AutoInterfaceSample.csproj @@ -20,10 +20,13 @@ + - + + + diff --git a/AutoInterfaceSample/Program.cs b/AutoInterfaceSample/Program.cs index 32ad0fa..cc7137d 100644 --- a/AutoInterfaceSample/Program.cs +++ b/AutoInterfaceSample/Program.cs @@ -1,62 +1,28 @@ -using System; -using System.Diagnostics.CodeAnalysis; +using TestInterfacesNetStandard; namespace AutoInterfaceSample.Test { - public partial record TestRecord( - [property: BeaKona.AutoInterface(IncludeBaseInterfaces = true)] NLog.ILogger nlog, - [property: BeaKona.AutoInterface(IncludeBaseInterfaces = true)] Serilog.ILogger slog, - [property: BeaKona.AutoInterface(IncludeBaseInterfaces = true)] Microsoft.Extensions.Logging.ILogger melog - - ) : NLog.ILogger; - public class Program { public static void Main() { //System.Diagnostics.Debug.WriteLine(BeaKona.Output.Debug_TestRecord.Info); - - ITestInterfaceObsolete r = new TestObsoleteRecord(null); - r.TestObsoleteMethod(""); - // ITestable p = new TestRecord(new SimpleLogger()); - - // p.Test(); - - //p.Log(LogLevel.Debug, default, 1, null, null); - //var result = p.BindProperty("test", 1, false, out var property, 1, 2, 3); } } - partial record TestObsoleteRecord( - [property: BeaKona.AutoInterface(IncludeBaseInterfaces = true)] - ITestInterfaceObsolete Testable) : ITestInterfaceObsolete; - interface ITestInterfaceObsolete + partial record TestRecord { - [Obsolete] - [DoesNotReturn] - [return: MaybeNull] - [return: NotNullIfNotNull(nameof(test))] - [return: TestReturn] - ResObject? TestObsoleteMethod(string? test); - - [Obsolete] - [DisallowNull] - ResObject? TestObsoleteProperty - { - get; - } - - [DisallowNull] - ResObject? TestExpandedProperty - { - [return: MaybeNull] - get; - } - + //[BeaKona.AutoInterface(IncludeBaseInterfaces = true)] + public ITestable2? Testable { get; set; } } +} - class ResObject; +namespace System.Diagnostics.CodeAnalysis +{ - [AttributeUsage(AttributeTargets.All)] - class TestReturnAttribute : Attribute; + [AttributeUsage(AttributeTargets.Parameter, Inherited = false)] + internal sealed class NotNullWhenAttribute(bool returnValue) : Attribute + { + public bool ReturnValue { get; } = returnValue; + } } diff --git a/AutoInterfaceSample/SimpleLogger.cs b/AutoInterfaceSample/SimpleLogger.cs deleted file mode 100644 index 804f4c8..0000000 --- a/AutoInterfaceSample/SimpleLogger.cs +++ /dev/null @@ -1,11 +0,0 @@ -using TestInterfaces.A.B; - -namespace AutoInterfaceSample.Test -{ - public sealed class SimpleLogger : ITestable - { - public void Test() - { - } - } -} diff --git a/AutoInterfaceSampleNetStandard/AutoInterfaceSampleNetStandard.csproj b/AutoInterfaceSampleNetStandard/AutoInterfaceSampleNetStandard.csproj new file mode 100644 index 0000000..acdd3b3 --- /dev/null +++ b/AutoInterfaceSampleNetStandard/AutoInterfaceSampleNetStandard.csproj @@ -0,0 +1,15 @@ + + + + netstandard2.0 + 8.0 + enable + + + + + + + + + diff --git a/AutoInterfaceSampleNetStandard/TestRecord.cs b/AutoInterfaceSampleNetStandard/TestRecord.cs new file mode 100644 index 0000000..832c83b --- /dev/null +++ b/AutoInterfaceSampleNetStandard/TestRecord.cs @@ -0,0 +1,25 @@ +using TestInterfacesNetStandard; + +namespace AutoInterfaceSampleNetStandard +{ + partial class TestRecord + { + [BeaKona.AutoInterface(IncludeBaseInterfaces = true)] + public ITestable2? Testable { get; set; } + } +} + +namespace System.Diagnostics.CodeAnalysis +{ + + [AttributeUsage(AttributeTargets.Parameter, Inherited = false)] + internal sealed class NotNullWhenAttribute : Attribute + { + public NotNullWhenAttribute(bool returnValue) + { + this.ReturnValue = returnValue; + } + + public bool ReturnValue { get; } + } +} diff --git a/BeaKona.AutoInterfaceGenerator/AliasRegistry.cs b/BeaKona.AutoInterfaceGenerator/AliasRegistry.cs new file mode 100644 index 0000000..e03d8d8 --- /dev/null +++ b/BeaKona.AutoInterfaceGenerator/AliasRegistry.cs @@ -0,0 +1,14 @@ +using System.Collections; + +namespace BeaKona.AutoInterfaceGenerator; + +public sealed class AliasRegistry : IEnumerable +{ + private readonly HashSet aliases = []; + + public void Add(string alias) => this.aliases.Add(alias); + + IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator(); + + public IEnumerator GetEnumerator() => this.aliases.GetEnumerator(); +} diff --git a/BeaKona.AutoInterfaceGenerator/AutoInterfaceResource.Designer.cs b/BeaKona.AutoInterfaceGenerator/AutoInterfaceResource.Designer.cs index 37b4e05..368ce9c 100644 --- a/BeaKona.AutoInterfaceGenerator/AutoInterfaceResource.Designer.cs +++ b/BeaKona.AutoInterfaceGenerator/AutoInterfaceResource.Designer.cs @@ -491,5 +491,32 @@ internal static string AG16_title { return ResourceManager.GetString("AG16_title", resourceCulture); } } + + /// + /// Looks up a localized string similar to AG17. + /// + internal static string AG17_description { + get { + return ResourceManager.GetString("AG17_description", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Following attributes are missing and should be implemented or referenced: {0}.. + /// + internal static string AG17_message { + get { + return ResourceManager.GetString("AG17_message", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Missing attributes.. + /// + internal static string AG17_title { + get { + return ResourceManager.GetString("AG17_title", resourceCulture); + } + } } } diff --git a/BeaKona.AutoInterfaceGenerator/AutoInterfaceResource.resx b/BeaKona.AutoInterfaceGenerator/AutoInterfaceResource.resx index 288e4f2..ca4ba0b 100644 --- a/BeaKona.AutoInterfaceGenerator/AutoInterfaceResource.resx +++ b/BeaKona.AutoInterfaceGenerator/AutoInterfaceResource.resx @@ -261,4 +261,13 @@ Multiple partial templates found. + + AG17 + + + Following attributes are missing and should be implemented or referenced: {0}. + + + Missing attributes. + \ No newline at end of file diff --git a/BeaKona.AutoInterfaceGenerator/AutoInterfaceSourceGenerator.cs b/BeaKona.AutoInterfaceGenerator/AutoInterfaceSourceGenerator.cs index 7d99cfe..7337639 100644 --- a/BeaKona.AutoInterfaceGenerator/AutoInterfaceSourceGenerator.cs +++ b/BeaKona.AutoInterfaceGenerator/AutoInterfaceSourceGenerator.cs @@ -88,6 +88,8 @@ public void Execute(GeneratorExecutionContext context) records.AddRange(GetRecords(context, property, property.Type, autoInterfaceAttributeSymbol, autoInterfaceTemplateAttributeSymbol)); } + var missingAttributes = new TypeRegistry(); + // group elements by the containing class, and generate the source foreach (IGrouping recordsByContainingType in records.GroupBy(i => i.Member.ContainingType, SymbolEqualityComparer.Default)) { @@ -115,7 +117,7 @@ public void Execute(GeneratorExecutionContext context) try { - string? code = AutoInterfaceSourceGenerator.ProcessClass(context, compilation, containingType, recordsByContainingType); + string? code = AutoInterfaceSourceGenerator.GetSourceCodeForType(context, compilation, containingType, recordsByContainingType, missingAttributes); if (code != null) { string name = containingType.Arity > 0 ? $"{containingType.Name}_{containingType.Arity}" : containingType.Name; @@ -132,6 +134,16 @@ public void Execute(GeneratorExecutionContext context) ex.ToString().Replace("\r", "").Replace("\n", "")); } } + + if (missingAttributes.Count() > 0) + { + var toEmit = missingAttributes.Where(i => compilation.IsVisible(i) == false).ToList(); + if (toEmit.Count > 0) + { + Helpers.ReportDiagnostic(context, "BKAG17", nameof(AutoInterfaceResource.AG17_title), nameof(AutoInterfaceResource.AG17_message), nameof(AutoInterfaceResource.AG17_description), DiagnosticSeverity.Error, (Location?)null, + string.Join(", ", toEmit.Select(i => i.ToDisplayString()))); + } + } } } } @@ -582,12 +594,14 @@ private static bool IsDuckImplementation(ITypeSymbol receiverType, ITypeSymbol i } } - private static string? ProcessClass(GeneratorExecutionContext context, Compilation compilation, INamedTypeSymbol type, IEnumerable members) + private static string? GetSourceCodeForType(GeneratorExecutionContext context, Compilation compilation, INamedTypeSymbol type, IEnumerable members, TypeRegistry missingAttributes) { + var attributeRegistry = new TypeRegistry(); + var scope = new ScopeInfo(type); var options = SourceBuilderOptions.Load(context, null); - var builder = new SourceBuilder(options); + var builder = new SourceBuilder([], attributeRegistry, options); ICodeTextWriter writer = new CSharpCodeTextWriter(context, compilation); bool anyReasonToEmitSourceFile = false; @@ -596,6 +610,7 @@ private static bool IsDuckImplementation(ITypeSymbol receiverType, ITypeSymbol i builder.AppendLine("// "); //bool isNullable = compilation.Options.NullableContextOptions == NullableContextOptions.Enable; builder.AppendLine("#nullable enable"); + builder.MarkPointForAliases(); builder.AppendLine(); bool namespaceGenerated = writer.WriteNamespaceBeginning(builder, type.ContainingNamespace); @@ -838,7 +853,15 @@ EventModel CreateEvent(IEventSymbol @event) builder.AppendLine(); } - return error == false && anyReasonToEmitSourceFile ? builder.ToString() : null; + if (error == false && anyReasonToEmitSourceFile) + { + missingAttributes.AddMany(attributeRegistry); + return builder.ToString(); + } + else + { + return null; + } } /// diff --git a/BeaKona.AutoInterfaceGenerator/BeaKona.AutoInterfaceGenerator.csproj b/BeaKona.AutoInterfaceGenerator/BeaKona.AutoInterfaceGenerator.csproj index c0678af..92e65cf 100644 --- a/BeaKona.AutoInterfaceGenerator/BeaKona.AutoInterfaceGenerator.csproj +++ b/BeaKona.AutoInterfaceGenerator/BeaKona.AutoInterfaceGenerator.csproj @@ -13,7 +13,7 @@ https://github.com/beakona/AutoInterface git $(TargetsForTfmSpecificContentInPackage);_AddAnalyzersToOutput - 1.0.38 + 1.0.39 true true diff --git a/BeaKona.AutoInterfaceGenerator/CSharpCodeTextWriter.cs b/BeaKona.AutoInterfaceGenerator/CSharpCodeTextWriter.cs index b112009..ec4b62a 100644 --- a/BeaKona.AutoInterfaceGenerator/CSharpCodeTextWriter.cs +++ b/BeaKona.AutoInterfaceGenerator/CSharpCodeTextWriter.cs @@ -123,7 +123,7 @@ static bool IsTupleWithAliases(INamedTypeSymbol tuple) symbols = SemanticFacts.GetContainingSymbols(type, false); builder.Append(alias); builder.Append("::"); - builder.RegisterAlias(alias); + builder.AliasRegistry.Add(alias); } foreach (ISymbol symbol in symbols) @@ -208,13 +208,13 @@ public void WriteTypeArgumentsDefinition(SourceBuilder builder, IEnumerable attributes) + private void WriteReturnAttributes(SourceBuilder builder, ScopeInfo scope, IEnumerable attributes) { if (attributes != null) { foreach (var attribute in attributes) { builder.AppendIndentation(); - this.WriteAttribute(builder, attribute, true); + this.WriteAttribute(builder, scope, attribute, true); builder.AppendLine(); } } } - private void WriteAttribute(SourceBuilder builder, AttributeData attribute, bool isReturn = false) + private void WriteAttributeReference(SourceBuilder builder, ScopeInfo scope, AttributeData attribute) + { + if (attribute.AttributeClass is INamedTypeSymbol attributeTypeSymbol) + { + if (Helpers.IsPublicAccess(attributeTypeSymbol) == false) + { + builder.MissingAttributesRegistry.Add(attributeTypeSymbol); + } + + this.WriteTypeReference(builder, attributeTypeSymbol, scope); + + if (attribute.ConstructorArguments.Any() || attribute.NamedArguments.Any()) + { + builder.Append('('); + + bool first = true; + + foreach (var constructorArgument in attribute.ConstructorArguments) + { + if (first) + { + first = false; + } + else + { + builder.Append(", "); + } + + builder.Append(constructorArgument.ToCSharpString()); + } + + foreach (var namedArgument in attribute.NamedArguments) + { + if (first) + { + first = false; + } + else + { + builder.Append(", "); + } + + builder.Append(namedArgument.Key); + builder.Append(" = "); + builder.Append(namedArgument.Value.ToCSharpString()); + } + + builder.Append(')'); + } + } + else + { + builder.Append(attribute.ToString()); + } + } + + private void WriteAttribute(SourceBuilder builder, ScopeInfo scope, AttributeData attribute, bool isReturn = false) { builder.Append('['); if (isReturn) { builder.Append("return: "); } - builder.Append(attribute.ToString()); + this.WriteAttributeReference(builder, scope, attribute); builder.Append(']'); } private IEnumerable GetParameterAttributes(IParameterSymbol parameter) { - foreach (var attribute in parameter.GetAttributes().Where(IsPublicAccess)) + foreach (var attribute in parameter.GetAttributes()) { if (attribute.AttributeClass is INamedTypeSymbol attributeClass) { @@ -333,7 +389,7 @@ private IEnumerable GetParameterAttributes(IParameterSymbol param private IEnumerable GetForwardAttributes(ISymbol symbol) { - foreach (var attribute in symbol.GetAttributes().Where(IsPublicAccess)) + foreach (var attribute in symbol.GetAttributes()) { if (attribute.AttributeClass is INamedTypeSymbol attributeClass) { @@ -351,11 +407,11 @@ private IEnumerable GetForwardAttributes(ISymbol symbol) private IEnumerable GetReturnAttributes(IMethodSymbol method) { - foreach (var attribute in method.GetReturnTypeAttributes().Where(IsPublicAccess)) + foreach (var attribute in method.GetReturnTypeAttributes()) { if (attribute.AttributeClass is INamedTypeSymbol attributeClass) { - if ((attributeClass.GetAttributeUsages() & AttributeTargets.ReturnValue) == AttributeTargets.ReturnValue) + //if ((attributeClass.GetAttributeUsages() & AttributeTargets.ReturnValue) == AttributeTargets.ReturnValue) { if (this.forwardAttributeNamespaces.Contains(attributeClass.ContainingNamespace.ToDisplayString())) { @@ -393,43 +449,12 @@ private bool HasAttributes(params ISymbol?[] members) return false; } - private static bool IsPublicAccess(AttributeData attribute) - { - if (attribute.AttributeClass is INamedTypeSymbol attributeClass) - { - return IsPublicAccess(attributeClass); - } - - return false; - } - - private static bool IsPublicAccess(ITypeSymbol type) - { - if (type.DeclaredAccessibility == Accessibility.Public) - { - if (type is INamedTypeSymbol namedType) - { - foreach (var typeArgument in namedType.TypeArguments) - { - if (IsPublicAccess(typeArgument) == false) - { - return false; - } - } - } - - return true; - } - - return false; - } - public void WriteMethodDefinition(SourceBuilder builder, IMethodSymbol method, ScopeInfo scope, INamedTypeSymbol @interface, IEnumerable references) { var methodScope = new ScopeInfo(scope); - this.WriteForwardAttributes(builder, method); - this.WriteReturnAttributes(builder, this.GetReturnAttributes(method)); + this.WriteForwardAttributes(builder, scope, method); + this.WriteReturnAttributes(builder, scope, this.GetReturnAttributes(method)); if (method.IsGenericMethod) { @@ -586,7 +611,7 @@ public void WritePropertyDefinition(SourceBuilder builder, IPropertySymbol prope PartialTemplate? getterTemplate = this.GetMatchedTemplates(references, getterTarget, property.IsIndexer ? "this" : property.Name); PartialTemplate? setterTemplate = this.GetMatchedTemplates(references, setterTarget, property.IsIndexer ? "this" : property.Name); - this.WriteForwardAttributes(builder, property); + this.WriteForwardAttributes(builder, scope, property); builder.AppendIndentation(); this.WriteTypeReference(builder, property.Type, scope); @@ -627,8 +652,8 @@ public void WritePropertyDefinition(SourceBuilder builder, IPropertySymbol prope if (property.GetMethod is not null) { - this.WriteForwardAttributes(builder, property.GetMethod); - this.WriteReturnAttributes(builder, this.GetReturnAttributes(property.GetMethod)); + this.WriteForwardAttributes(builder, scope, property.GetMethod); + this.WriteReturnAttributes(builder, scope, this.GetReturnAttributes(property.GetMethod)); builder.AppendIndentation(); builder.Append("get => "); @@ -637,7 +662,7 @@ public void WritePropertyDefinition(SourceBuilder builder, IPropertySymbol prope } if (property.SetMethod is not null) { - this.WriteForwardAttributes(builder, property.SetMethod); + this.WriteForwardAttributes(builder, scope, property.SetMethod); builder.AppendIndentation(); builder.Append("set => "); @@ -649,8 +674,8 @@ public void WritePropertyDefinition(SourceBuilder builder, IPropertySymbol prope { if (property.GetMethod is not null) { - this.WriteForwardAttributes(builder, property.GetMethod); - this.WriteReturnAttributes(builder, this.GetReturnAttributes(property.GetMethod)); + this.WriteForwardAttributes(builder, scope, property.GetMethod); + this.WriteReturnAttributes(builder, scope, this.GetReturnAttributes(property.GetMethod)); builder.AppendIndentation(); if (getterTemplate != null) @@ -693,7 +718,7 @@ public void WritePropertyDefinition(SourceBuilder builder, IPropertySymbol prope } if (property.SetMethod is not null) { - this.WriteForwardAttributes(builder, property.SetMethod); + this.WriteForwardAttributes(builder, scope, property.SetMethod); builder.AppendIndentation(); builder.AppendLine("set"); @@ -752,7 +777,7 @@ public void WriteEventDefinition(SourceBuilder builder, IEventSymbol @event, Sco PartialTemplate? adderTemplate = this.GetMatchedTemplates(references, AutoInterfaceTargets.EventAdder, @event.Name); PartialTemplate? removerTemplate = this.GetMatchedTemplates(references, AutoInterfaceTargets.EventRemover, @event.Name); - this.WriteForwardAttributes(builder, @event); + this.WriteForwardAttributes(builder, scope, @event); builder.AppendIndentation(); builder.Append("event"); @@ -790,7 +815,7 @@ public void WriteEventDefinition(SourceBuilder builder, IEventSymbol @event, Sco { if (@event.AddMethod != null) { - this.WriteForwardAttributes(builder, @event.AddMethod); + this.WriteForwardAttributes(builder, scope, @event.AddMethod); builder.AppendIndentation(); builder.AppendLine("add"); @@ -834,7 +859,7 @@ public void WriteEventDefinition(SourceBuilder builder, IEventSymbol @event, Sco } if (@event.RemoveMethod != null) { - this.WriteForwardAttributes(builder, @event.RemoveMethod); + this.WriteForwardAttributes(builder, scope, @event.RemoveMethod); builder.AppendIndentation(); builder.AppendLine("remove"); diff --git a/BeaKona.AutoInterfaceGenerator/CompilationExtensions.cs b/BeaKona.AutoInterfaceGenerator/CompilationExtensions.cs index f6c0381..ed409e0 100644 --- a/BeaKona.AutoInterfaceGenerator/CompilationExtensions.cs +++ b/BeaKona.AutoInterfaceGenerator/CompilationExtensions.cs @@ -16,6 +16,27 @@ internal static class CompilationExtensions } @namespace = next; } + return @namespace; } + + public static bool IsVisible(this Compilation @this, INamedTypeSymbol className) + { + foreach (var type in @this.GetTypesByMetadataName(className.ToDisplayString())) + { + if (type.DeclaredAccessibility == Accessibility.Public) + { + return true; + } + else if (type.DeclaredAccessibility == Accessibility.Internal) + { + if (@this.Assembly.Equals(type.ContainingAssembly, SymbolEqualityComparer.Default)) + { + return true; + } + } + } + + return false; + } } \ No newline at end of file diff --git a/BeaKona.AutoInterfaceGenerator/Helpers.cs b/BeaKona.AutoInterfaceGenerator/Helpers.cs index a08ee6d..f32482c 100644 --- a/BeaKona.AutoInterfaceGenerator/Helpers.cs +++ b/BeaKona.AutoInterfaceGenerator/Helpers.cs @@ -155,4 +155,21 @@ public static bool HasOutParameters(IEnumerable parameters) return false; } + + public static bool IsPublicAccess(ITypeSymbol type) + { + if (type.DeclaredAccessibility == Accessibility.Public) + { + if (type is INamedTypeSymbol namedType) + { + return namedType.TypeArguments.All(Helpers.IsPublicAccess); + } + else + { + return true; + } + } + + return false; + } } diff --git a/BeaKona.AutoInterfaceGenerator/ICodeTextWriter.cs b/BeaKona.AutoInterfaceGenerator/ICodeTextWriter.cs index b9274af..0f8775f 100644 --- a/BeaKona.AutoInterfaceGenerator/ICodeTextWriter.cs +++ b/BeaKona.AutoInterfaceGenerator/ICodeTextWriter.cs @@ -10,7 +10,7 @@ internal interface ICodeTextWriter void WriteTypeArgumentsDefinition(SourceBuilder builder, IEnumerable typeArguments, ScopeInfo scope); - void WriteParameterAttributes(SourceBuilder builder, IParameterSymbol parameter); + void WriteParameterAttributes(SourceBuilder builder, ScopeInfo scope, IParameterSymbol parameter); void WriteParameterDefinition(SourceBuilder builder, ScopeInfo scope, IEnumerable parameters); diff --git a/BeaKona.AutoInterfaceGenerator/SourceBuilder.cs b/BeaKona.AutoInterfaceGenerator/SourceBuilder.cs index 243a2cb..750d4ab 100644 --- a/BeaKona.AutoInterfaceGenerator/SourceBuilder.cs +++ b/BeaKona.AutoInterfaceGenerator/SourceBuilder.cs @@ -2,35 +2,39 @@ namespace BeaKona.AutoInterfaceGenerator; -internal sealed class SourceBuilder(SourceBuilderOptions options) +internal sealed class SourceBuilder { - private SourceBuilder(SourceBuilder owner, SourceBuilderOptions options) : this(options) + public SourceBuilder(AliasRegistry aliasRegistry, TypeRegistry missingAttributesRegistry, SourceBuilderOptions options) : this(aliasRegistry, missingAttributesRegistry, options, true) { - this.owner = owner; } - private readonly SourceBuilder? owner; + private SourceBuilder(AliasRegistry aliasRegistry, TypeRegistry missingAttributesRegistry, SourceBuilderOptions options, bool isRoot) + { + this.AliasRegistry = aliasRegistry; + this.MissingAttributesRegistry = missingAttributesRegistry; + this.Options = options; + this.IsRoot = isRoot; + } - public SourceBuilderOptions Options { get; } = options; + public SourceBuilderOptions Options { get; } + public AliasRegistry AliasRegistry { get; } + public TypeRegistry MissingAttributesRegistry { get; } + public bool IsRoot { get; } private readonly List elements = []; - private readonly HashSet aliases = []; public void Clear() { this.elements.Clear(); } - public void RegisterAlias(string alias) + private bool aliasMarkerAdded = false; + public void MarkPointForAliases() { - if (this.owner != null) - { - this.owner.RegisterAlias(alias); - } - else - { - this.aliases.Add(alias); - } + if (this.IsRoot == false) throw new InvalidOperationException(); + if (this.aliasMarkerAdded) throw new InvalidOperationException(); + this.elements.Add(new AliasesMarker()); + this.aliasMarkerAdded = true; } public void AppendLine() => this.elements.Add(new LineSeparatorMarker()); @@ -101,7 +105,7 @@ public void AppendIndentation() public SourceBuilder AppendNewBuilder(bool register = true) { - var builder = new SourceBuilder(this, this.Options); + var builder = new SourceBuilder(this.AliasRegistry, this.MissingAttributesRegistry, this.Options, false); if (register) { this.elements.Add(builder); @@ -122,19 +126,70 @@ public SourceBuilder AppendBuilder(Action builder) public override string ToString() { var text = new StringBuilder(); - foreach (string alias in this.aliases.OrderByDescending(i => i)) - { - text.Append("extern alias "); - text.Append(alias); - text.AppendLine(";"); - } this.Write(text); return text.ToString(); } + private void WriteAliases(StringBuilder builder) + { + foreach (string alias in this.AliasRegistry) + { + builder.Append("extern alias "); + builder.Append(alias); + builder.AppendLine(";"); + } + } + + private void WriteIndentation(StringBuilder builder, IndentationMarker indentation, Dictionary? cache = null) + { + int depth = indentation.Depth; + if (cache == null || cache.TryGetValue(depth, out string value) == false) + { + var sb = new StringBuilder(); + for (int i = 0; i < depth; i++) + { + sb.Append(this.Options.Indentation); + } + value = sb.ToString(); + if (cache != null) + { + cache[depth] = value; + } + } + builder.Append(value); + } + + private void WriteFlexibleSpace(StringBuilder builder, char previous) + { + switch (previous) + { + case ' ': + case '\t': + case '\r': + case '\n': + case '(': + case '[': + case '{': + case ')': + case ']': + case '}': + case ';': + break; + default: + builder.Append(' '); + break; + } + } + private void Write(StringBuilder builder) { - Dictionary? cache = null; + var cache = new Dictionary(); + + if (this.aliasMarkerAdded == false) + { + this.WriteAliases(builder); + } + foreach (object? element in this.elements) { if (element != null) @@ -148,52 +203,22 @@ private void Write(StringBuilder builder) case char ch: builder.Append(ch); break; + case AliasesMarker: + this.WriteAliases(builder); + break; case LineSeparatorMarker: builder.Append(this.Options.NewLine); break; case IndentationMarker indentation: - { - cache ??= []; - int depth = indentation.Depth; - if (cache.TryGetValue(depth, out string value) == false) - { - var sb = new StringBuilder(); - for (int i = 0; i < depth; i++) - { - sb.Append(this.Options.Indentation); - } - cache[depth] = value = sb.ToString(); - } - builder.Append(value); - break; - } + this.WriteIndentation(builder, indentation, cache); + break; case FlexibleSpaceMarker: + if (builder.Length > 0) { - if (builder.Length > 0) - { - char c = builder[builder.Length - 1]; - switch (c) - { - case ' ': - case '\t': - case '\r': - case '\n': - case '(': - case '[': - case '{': - case ')': - case ']': - case '}': - case ';': - break; - default: - builder.Append(' '); - break; - } - } - - break; + char previous = builder[builder.Length - 1]; + this.WriteFlexibleSpace(builder, previous); } + break; case SourceBuilder sb: sb.Append(builder); break; @@ -202,6 +227,10 @@ private void Write(StringBuilder builder) } } + private sealed class AliasesMarker + { + } + private sealed class LineSeparatorMarker { } diff --git a/BeaKona.AutoInterfaceGenerator/TypeRegistry.cs b/BeaKona.AutoInterfaceGenerator/TypeRegistry.cs new file mode 100644 index 0000000..a81a8c5 --- /dev/null +++ b/BeaKona.AutoInterfaceGenerator/TypeRegistry.cs @@ -0,0 +1,24 @@ +using System.Collections; + +namespace BeaKona.AutoInterfaceGenerator; + +public sealed class TypeRegistry : IEnumerable +{ + private readonly HashSet missingAttributeTypes = new(SymbolEqualityComparer.Default); + public void Add(INamedTypeSymbol attributeType) + { + this.missingAttributeTypes.Add(attributeType); + } + + public void AddMany(IEnumerable attributeTypes) + { + foreach (var attributeType in attributeTypes) + { + this.Add(attributeType); + } + } + + public IEnumerator GetEnumerator() => this.missingAttributeTypes.GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator(); +} diff --git a/TestInterfacesNetStandard/Interfaces.cs b/TestInterfacesNetStandard/Interfaces.cs new file mode 100644 index 0000000..b3ef086 --- /dev/null +++ b/TestInterfacesNetStandard/Interfaces.cs @@ -0,0 +1,9 @@ +using System.Diagnostics.CodeAnalysis; + +namespace TestInterfacesNetStandard +{ + public interface ITestable2 + { + bool TryParse(string s, [NotNullWhen(true)] out int? value); + } +} diff --git a/TestInterfacesNetStandard/NotNullWhenAttribute.cs b/TestInterfacesNetStandard/NotNullWhenAttribute.cs new file mode 100644 index 0000000..b67192b --- /dev/null +++ b/TestInterfacesNetStandard/NotNullWhenAttribute.cs @@ -0,0 +1,11 @@ +namespace System.Diagnostics.CodeAnalysis +{ + + [AttributeUsage(AttributeTargets.Parameter, Inherited = false)] + internal sealed class NotNullWhenAttribute : Attribute + { + public NotNullWhenAttribute(bool returnValue) => this.ReturnValue = returnValue; + + public bool ReturnValue { get; } + } +} diff --git a/TestInterfacesNetStandard/TestInterfacesNetStandard.csproj b/TestInterfacesNetStandard/TestInterfacesNetStandard.csproj new file mode 100644 index 0000000..dbdcea4 --- /dev/null +++ b/TestInterfacesNetStandard/TestInterfacesNetStandard.csproj @@ -0,0 +1,7 @@ + + + + netstandard2.0 + + +