diff --git a/.github/workflows/nuget.yml b/.github/workflows/nuget.yml index f18e6f2..b096c53 100644 --- a/.github/workflows/nuget.yml +++ b/.github/workflows/nuget.yml @@ -16,7 +16,7 @@ jobs: - name: Setup .NET uses: actions/setup-dotnet@v1 with: - dotnet-version: 6.0.x + dotnet-version: 8.0.x - name: Restore dependencies run: dotnet restore @@ -31,4 +31,4 @@ jobs: run: dotnet pack --no-build --configuration Release InterfaceGenerator/InterfaceGenerator.csproj --output . - name: Push to nuget.org - run: dotnet nuget push *.nupkg --source "https://api.nuget.org/v3/index.json" --api-key ${{secrets.NUGET_API_KEY}} --skip-duplicate \ No newline at end of file + run: dotnet nuget push *.nupkg --source "https://api.nuget.org/v3/index.json" --api-key ${{secrets.NUGET_KEY}} --skip-duplicate \ No newline at end of file diff --git a/InterfaceGenerator.Tests/AccessorsGenerationTests.cs b/InterfaceGenerator.Tests/AccessorsGenerationTests.cs index 7e61ee2..593016d 100644 --- a/InterfaceGenerator.Tests/AccessorsGenerationTests.cs +++ b/InterfaceGenerator.Tests/AccessorsGenerationTests.cs @@ -1,9 +1,10 @@ -using System.Runtime.CompilerServices; +using System; +using System.Runtime.CompilerServices; using FluentAssertions; using FluentAssertions.Common; using Xunit; -namespace InterfaceGenerator.Tests; +namespace Speckle.InterfaceGenerator.Tests; public class AccessorsGenerationTests { @@ -32,7 +33,7 @@ public class AccessorsGenerationTests public void PublicProperty_IsImplemented() { var prop = typeof(IAccessorsTestsService) - .GetProperty(nameof(IAccessorsTestsService.PublicProperty))!; + .GetProperty(nameof(IAccessorsTestsService.PublicProperty)) ?? throw new InvalidOperationException(); prop.Should().NotBeNull(); @@ -47,14 +48,14 @@ public class AccessorsGenerationTests public void InitProperty_IsImplemented() { var prop = typeof(IAccessorsTestsService) - .GetProperty(nameof(IAccessorsTestsService.InitOnlyProperty))!; + .GetProperty(nameof(IAccessorsTestsService.InitOnlyProperty)) ?? throw new InvalidOperationException(); prop.Should().NotBeNull(); prop.GetMethod.Should().NotBeNull(); prop.SetMethod.Should().NotBeNull(); - prop.SetMethod!.ReturnParameter!.GetRequiredCustomModifiers().Should().Contain(typeof(IsExternalInit)); + prop.SetMethod?.ReturnParameter?.GetRequiredCustomModifiers().Should().Contain(typeof(IsExternalInit)); var _ = _sut.InitOnlyProperty; } @@ -63,7 +64,7 @@ public class AccessorsGenerationTests public void PrivateSetter_IsOmitted() { var prop = typeof(IAccessorsTestsService) - .GetProperty(nameof(IAccessorsTestsService.PropertyWithPrivateSetter))!; + .GetProperty(nameof(IAccessorsTestsService.PropertyWithPrivateSetter)) ?? throw new InvalidOperationException(); prop.Should().NotBeNull(); @@ -77,7 +78,7 @@ public class AccessorsGenerationTests public void PrivateGetter_IsOmitted() { var prop = typeof(IAccessorsTestsService) - .GetProperty(nameof(IAccessorsTestsService.PropertyWithPrivateGetter))!; + .GetProperty(nameof(IAccessorsTestsService.PropertyWithPrivateGetter)) ?? throw new InvalidOperationException(); prop.Should().NotBeNull(); @@ -91,7 +92,7 @@ public class AccessorsGenerationTests public void ProtectedSetter_IsOmitted() { var prop = typeof(IAccessorsTestsService) - .GetProperty(nameof(IAccessorsTestsService.PropertyWithProtectedSetter))!; + .GetProperty(nameof(IAccessorsTestsService.PropertyWithProtectedSetter)) ?? throw new InvalidOperationException(); prop.Should().NotBeNull(); @@ -105,7 +106,7 @@ public class AccessorsGenerationTests public void ProtectedGetter_IsOmitted() { var prop = typeof(IAccessorsTestsService) - .GetProperty(nameof(IAccessorsTestsService.PropertyWithProtectedGetter))!; + .GetProperty(nameof(IAccessorsTestsService.PropertyWithProtectedGetter)) ?? throw new InvalidOperationException(); prop.Should().NotBeNull(); diff --git a/InterfaceGenerator.Tests/GenericInterfaceTests.cs b/InterfaceGenerator.Tests/GenericInterfaceTests.cs index 8642e3b..d58fab8 100644 --- a/InterfaceGenerator.Tests/GenericInterfaceTests.cs +++ b/InterfaceGenerator.Tests/GenericInterfaceTests.cs @@ -3,7 +3,7 @@ using System.Reflection; using FluentAssertions; using Xunit; -namespace InterfaceGenerator.Tests; +namespace Speckle.InterfaceGenerator.Tests; public class GenericInterfaceTests { diff --git a/InterfaceGenerator.Tests/MethodGenerationTests.cs b/InterfaceGenerator.Tests/MethodGenerationTests.cs index fb9a748..4c6ae1e 100644 --- a/InterfaceGenerator.Tests/MethodGenerationTests.cs +++ b/InterfaceGenerator.Tests/MethodGenerationTests.cs @@ -5,7 +5,7 @@ using System.Text; using FluentAssertions; using Xunit; -namespace InterfaceGenerator.Tests; +namespace Speckle.InterfaceGenerator.Tests; public class MethodGenerationTests { @@ -20,7 +20,7 @@ public class MethodGenerationTests public void VoidMethod_IsImplemented() { var method = typeof(IMethodsTestService).GetMethod( - nameof(MethodsTestService.VoidMethod))!; + nameof(MethodsTestService.VoidMethod)) ?? throw new InvalidOperationException(); method.Should().NotBeNull(); method.ReturnType.Should().Be(typeof(void)); @@ -35,7 +35,7 @@ public class MethodGenerationTests public void VoidMethodWithKeywordParam_IsImplemented() { var method = typeof(IMethodsTestService).GetMethod( - nameof(MethodsTestService.VoidMethodWithKeywordParam))!; + nameof(MethodsTestService.VoidMethodWithKeywordParam)) ?? throw new InvalidOperationException(); method.Should().NotBeNull(); method.ReturnType.Should().Be(typeof(void)); @@ -54,7 +54,7 @@ public class MethodGenerationTests { var method = typeof(IMethodsTestService).GetMethod( nameof(MethodsTestService.VoidMethodWithParams), - new[] { typeof(string), typeof(string) })!; + [typeof(string), typeof(string)]) ?? throw new InvalidOperationException(); method.Should().NotBeNull(); method.ReturnType.Should().Be(typeof(void)); @@ -71,7 +71,7 @@ public class MethodGenerationTests { var method = typeof(IMethodsTestService).GetMethod( nameof(MethodsTestService.VoidMethodWithOutParam), - new[] { typeof(string).MakeByRefType() })!; + [typeof(string).MakeByRefType()]) ?? throw new InvalidOperationException(); method.Should().NotBeNull(); method.ReturnType.Should().Be(typeof(void)); @@ -89,7 +89,7 @@ public class MethodGenerationTests { var method = typeof(IMethodsTestService).GetMethod( nameof(MethodsTestService.VoidMethodWithInParam), - new[] { typeof(string).MakeByRefType() })!; + [typeof(string).MakeByRefType()]) ?? throw new InvalidOperationException(); method.Should().NotBeNull(); method.ReturnType.Should().Be(typeof(void)); @@ -108,7 +108,7 @@ public class MethodGenerationTests { var method = typeof(IMethodsTestService).GetMethod( nameof(MethodsTestService.VoidMethodWithRefParam), - new[] { typeof(string).MakeByRefType() })!; + [typeof(string).MakeByRefType()]) ?? throw new InvalidOperationException(); method.Should().NotBeNull(); method.ReturnType.Should().Be(typeof(void)); @@ -127,7 +127,7 @@ public class MethodGenerationTests public void StringMethod_IsImplemented() { var method = typeof(IMethodsTestService).GetMethod( - nameof(MethodsTestService.StringMethod))!; + nameof(MethodsTestService.StringMethod)) ?? throw new InvalidOperationException(); method.Should().NotBeNull(); method.ReturnType.Should().Be(typeof(string)); diff --git a/InterfaceGenerator.Tests/Partial/PartialClass.1.cs b/InterfaceGenerator.Tests/Partial/PartialClass.1.cs index 02ff0de..bbb9788 100644 --- a/InterfaceGenerator.Tests/Partial/PartialClass.1.cs +++ b/InterfaceGenerator.Tests/Partial/PartialClass.1.cs @@ -1,4 +1,4 @@ -namespace InterfaceGenerator.Tests.Partial; +namespace Speckle.InterfaceGenerator.Tests.Partial; [GenerateAutoInterface] internal partial class PartialClass : IPartialClass diff --git a/InterfaceGenerator.Tests/Partial/PartialClass.2.cs b/InterfaceGenerator.Tests/Partial/PartialClass.2.cs index 9bcf3b9..fd52e37 100644 --- a/InterfaceGenerator.Tests/Partial/PartialClass.2.cs +++ b/InterfaceGenerator.Tests/Partial/PartialClass.2.cs @@ -1,4 +1,4 @@ -namespace InterfaceGenerator.Tests.Partial; +namespace Speckle.InterfaceGenerator.Tests.Partial; internal partial class PartialClass { diff --git a/InterfaceGenerator.Tests/PartialClassTests.cs b/InterfaceGenerator.Tests/PartialClassTests.cs index a290724..3befa71 100644 --- a/InterfaceGenerator.Tests/PartialClassTests.cs +++ b/InterfaceGenerator.Tests/PartialClassTests.cs @@ -1,9 +1,8 @@ -using System; using FluentAssertions; -using InterfaceGenerator.Tests.Partial; +using Speckle.InterfaceGenerator.Tests.Partial; using Xunit; -namespace InterfaceGenerator.Tests; +namespace Speckle.InterfaceGenerator.Tests; public class PartialClassTests { diff --git a/InterfaceGenerator.Tests/RecordInterfaceGenerationTests.cs b/InterfaceGenerator.Tests/RecordInterfaceGenerationTests.cs index 8274723..1cb38bc 100644 --- a/InterfaceGenerator.Tests/RecordInterfaceGenerationTests.cs +++ b/InterfaceGenerator.Tests/RecordInterfaceGenerationTests.cs @@ -1,8 +1,9 @@ +using System; using System.Runtime.CompilerServices; using FluentAssertions; using Xunit; -namespace InterfaceGenerator.Tests; +namespace Speckle.InterfaceGenerator.Tests; public class RecordInterfaceGenerationTests { @@ -17,13 +18,13 @@ public class RecordInterfaceGenerationTests public void RecordProperty_IsGenerated() { var prop = typeof(ITestRecord) - .GetProperty(nameof(TestRecord.RecordProperty))!; + .GetProperty(nameof(TestRecord.RecordProperty)) ?? throw new InvalidOperationException(); prop.Should().NotBeNull(); prop.GetMethod.Should().NotBeNull(); prop.SetMethod.Should().NotBeNull(); - prop.SetMethod!.ReturnParameter!.GetRequiredCustomModifiers().Should().Contain(typeof(IsExternalInit)); + prop.SetMethod?.ReturnParameter?.GetRequiredCustomModifiers().Should().Contain(typeof(IsExternalInit)); _sut.RecordProperty.Should().Be(420); } @@ -35,9 +36,9 @@ public class RecordInterfaceGenerationTests nameof(TestRecord.RecordMethod)); method.Should().NotBeNull(); - method!.ReturnType.Should().Be(typeof(void)); + method?.ReturnType.Should().Be(typeof(void)); - var parameters = method.GetParameters(); + var parameters = method?.GetParameters(); parameters.Should().BeEmpty(); _sut.RecordMethod(); @@ -50,9 +51,9 @@ public class RecordInterfaceGenerationTests nameof(TestRecord.Deconstruct)); method.Should().NotBeNull(); - method!.ReturnType.Should().Be(typeof(void)); + method?.ReturnType.Should().Be(typeof(void)); - var parameters = method.GetParameters(); + var parameters = method?.GetParameters() ?? throw new InvalidOperationException(); parameters.Length.Should().Be(1); var parameter = parameters[0]; diff --git a/InterfaceGenerator.Tests/SameName/SameNameClass.1.cs b/InterfaceGenerator.Tests/SameName/SameNameClass.1.cs index e3906f2..cb72fdf 100644 --- a/InterfaceGenerator.Tests/SameName/SameNameClass.1.cs +++ b/InterfaceGenerator.Tests/SameName/SameNameClass.1.cs @@ -1,4 +1,7 @@ // ReSharper disable CheckNamespace + +using Speckle.InterfaceGenerator; + namespace InterfaceGenerator.Tests.SameName_1; /// diff --git a/InterfaceGenerator.Tests/SameName/SameNameClass.2.cs b/InterfaceGenerator.Tests/SameName/SameNameClass.2.cs index 9147b4f..67a87c1 100644 --- a/InterfaceGenerator.Tests/SameName/SameNameClass.2.cs +++ b/InterfaceGenerator.Tests/SameName/SameNameClass.2.cs @@ -1,4 +1,7 @@ // ReSharper disable CheckNamespace + +using Speckle.InterfaceGenerator; + namespace InterfaceGenerator.Tests.SameName_2; [GenerateAutoInterface] diff --git a/InterfaceGenerator.Tests/InterfaceGenerator.Tests.csproj b/InterfaceGenerator.Tests/Speckle.InterfaceGenerator.Tests.csproj similarity index 64% rename from InterfaceGenerator.Tests/InterfaceGenerator.Tests.csproj rename to InterfaceGenerator.Tests/Speckle.InterfaceGenerator.Tests.csproj index db3e2fe..0c30220 100644 --- a/InterfaceGenerator.Tests/InterfaceGenerator.Tests.csproj +++ b/InterfaceGenerator.Tests/Speckle.InterfaceGenerator.Tests.csproj @@ -1,9 +1,9 @@ - net6.0 - + net8.0 false + Speckle.InterfaceGenerator.Tests @@ -14,7 +14,7 @@ - + diff --git a/InterfaceGenerator.Tests/VisibilityModifierTests.cs b/InterfaceGenerator.Tests/VisibilityModifierTests.cs index 33c9e08..951f472 100644 --- a/InterfaceGenerator.Tests/VisibilityModifierTests.cs +++ b/InterfaceGenerator.Tests/VisibilityModifierTests.cs @@ -2,7 +2,7 @@ using FluentAssertions; using Xunit; -namespace InterfaceGenerator.Tests; +namespace Speckle.InterfaceGenerator.Tests; public class VisibilityModifierTests { diff --git a/InterfaceGenerator/AttributeDataExtensions.cs b/InterfaceGenerator/AttributeDataExtensions.cs index 084842a..10671cb 100644 --- a/InterfaceGenerator/AttributeDataExtensions.cs +++ b/InterfaceGenerator/AttributeDataExtensions.cs @@ -1,14 +1,13 @@ using System.Linq; using Microsoft.CodeAnalysis; -namespace InterfaceGenerator +namespace Speckle.InterfaceGenerator; + +internal static class AttributeDataExtensions { - internal static class AttributeDataExtensions + public static string? GetNamedParamValue(this AttributeData attributeData, string paramName) { - public static string? GetNamedParamValue(this AttributeData attributeData, string paramName) - { - var pair = attributeData.NamedArguments.FirstOrDefault(x => x.Key == paramName); - return pair.Value.Value?.ToString(); - } + var pair = attributeData.NamedArguments.FirstOrDefault(x => x.Key == paramName); + return pair.Value.Value?.ToString(); } } \ No newline at end of file diff --git a/InterfaceGenerator/Attributes.cs b/InterfaceGenerator/Attributes.cs index 754b8ec..c600554 100644 --- a/InterfaceGenerator/Attributes.cs +++ b/InterfaceGenerator/Attributes.cs @@ -1,17 +1,16 @@ -namespace InterfaceGenerator +namespace Speckle.InterfaceGenerator; + +internal class Attributes { - - internal class Attributes - { - public const string AttributesNamespace = nameof(InterfaceGenerator); + public const string AttributesNamespace = "Speckle.InterfaceGenerator"; - public const string GenerateAutoInterfaceClassname = "GenerateAutoInterfaceAttribute"; - public const string AutoInterfaceIgnoreAttributeClassname = "AutoInterfaceIgnoreAttribute"; + public const string GenerateAutoInterfaceClassname = "GenerateAutoInterfaceAttribute"; + public const string AutoInterfaceIgnoreAttributeClassname = "AutoInterfaceIgnoreAttribute"; - public const string VisibilityModifierPropName = "VisibilityModifier"; - public const string InterfaceNamePropName = "Name"; + public const string VisibilityModifierPropName = "VisibilityModifier"; + public const string InterfaceNamePropName = "Name"; - public static readonly string AttributesSourceCode = $@" + public static readonly string AttributesSourceCode = $@" using System; using System.Diagnostics; @@ -39,5 +38,4 @@ namespace {AttributesNamespace} }} }} "; - } } \ No newline at end of file diff --git a/InterfaceGenerator/AutoInterfaceGenerator.cs b/InterfaceGenerator/AutoInterfaceGenerator.cs index e4ec264..818cb7c 100644 --- a/InterfaceGenerator/AutoInterfaceGenerator.cs +++ b/InterfaceGenerator/AutoInterfaceGenerator.cs @@ -11,451 +11,450 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.Text; -namespace InterfaceGenerator +namespace Speckle.InterfaceGenerator; + +[Generator] +public class AutoInterfaceGenerator : ISourceGenerator { - [Generator] - public class AutoInterfaceGenerator : ISourceGenerator + private INamedTypeSymbol? _generateAutoInterfaceAttribute; + private INamedTypeSymbol? _ignoreAttribute; + + public void Initialize(GeneratorInitializationContext context) { - private INamedTypeSymbol _generateAutoInterfaceAttribute = null!; - private INamedTypeSymbol _ignoreAttribute = null!; + context.RegisterForSyntaxNotifications(() => new SyntaxReceiver()); - public void Initialize(GeneratorInitializationContext context) +#if DEBUG + if (!Debugger.IsAttached) { - context.RegisterForSyntaxNotifications(() => new SyntaxReceiver()); - - #if DEBUG - if (!Debugger.IsAttached) - { - // sadly this is Windows only so as of now :( - Debugger.Launch(); - } - #endif + // sadly this is Windows only so as of now :( + Debugger.Launch(); } +#endif + } - public void Execute(GeneratorExecutionContext context) + public void Execute(GeneratorExecutionContext context) + { + try { - try - { - ExecuteCore(context); - } - catch (Exception exception) - { - RaiseExceptionDiagnostic(context, exception); - } + ExecuteCore(context); } - - private static void RaiseExceptionDiagnostic(GeneratorExecutionContext context, Exception exception) + catch (Exception exception) { - var descriptor = new DiagnosticDescriptor( - "InterfaceGenerator.CriticalError", - $"Exception thrown in InterfaceGenerator", - $"{exception.GetType().FullName} {exception.Message} {exception.StackTrace.Trim()}", - "InterfaceGenerator", - DiagnosticSeverity.Error, - true, - customTags: WellKnownDiagnosticTags.AnalyzerException); - - var diagnostic = Diagnostic.Create(descriptor, null); - - context.ReportDiagnostic(diagnostic); - } - - private void ExecuteCore(GeneratorExecutionContext context) - { - // setting the culture to invariant prevents errors such as emitting a decimal comma (0,1) instead of - // a decimal point (0.1) in certain cultures - var prevCulture = Thread.CurrentThread.CurrentCulture; - Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture; - - GenerateAttributes(context); - GenerateInterfaces(context); - - Thread.CurrentThread.CurrentCulture = prevCulture; - } - - private static void GenerateAttributes(GeneratorExecutionContext context) - { - context.AddSource( - Attributes.GenerateAutoInterfaceClassname, - SourceText.From(Attributes.AttributesSourceCode, Encoding.UTF8)); - } - - private void GenerateInterfaces(GeneratorExecutionContext context) - { - if (context.SyntaxReceiver is not SyntaxReceiver receiver) - { - return; - } - - var compilation = GetCompilation(context); - InitAttributes(compilation); - - var classSymbols = GetImplTypeSymbols(compilation, receiver); - - List classSymbolNames = new List(); - - foreach (var implTypeSymbol in classSymbols) - { - if (!implTypeSymbol.TryGetAttribute(_generateAutoInterfaceAttribute, out var attributes)) - { - continue; - } - - if(classSymbolNames.Contains(implTypeSymbol.GetFullMetadataName(useNameWhenNotFound: true))) - { - continue; // partial class, already added - } - - classSymbolNames.Add(implTypeSymbol.GetFullMetadataName(useNameWhenNotFound: true)); - - var attribute = attributes.Single(); - var source = SourceText.From(GenerateInterfaceCode(implTypeSymbol, attribute), Encoding.UTF8); - - context.AddSource($"{implTypeSymbol.GetFullMetadataName(useNameWhenNotFound: true)}_AutoInterface.g.cs", source); - } - } - - private static string InferVisibilityModifier(ISymbol implTypeSymbol, AttributeData attributeData) - { - string? result = attributeData.GetNamedParamValue(Attributes.VisibilityModifierPropName); - if (!string.IsNullOrEmpty(result)) - { - return result!; - } - - return implTypeSymbol.DeclaredAccessibility switch - { - Accessibility.Public => "public", - _ => "internal", - }; - } - - private static string InferInterfaceName(ISymbol implTypeSymbol, AttributeData attributeData) - { - return attributeData.GetNamedParamValue(Attributes.InterfaceNamePropName) ?? $"I{implTypeSymbol.Name}"; - } - - private string GenerateInterfaceCode(INamedTypeSymbol implTypeSymbol, AttributeData attributeData) - { - using var stream = new MemoryStream(); - var streamWriter = new StreamWriter(stream, Encoding.UTF8); - var codeWriter = new IndentedTextWriter(streamWriter, " "); - - var namespaceName = implTypeSymbol.ContainingNamespace.ToDisplayString(); - var interfaceName = InferInterfaceName(implTypeSymbol, attributeData); - var visibilityModifier = InferVisibilityModifier(implTypeSymbol, attributeData); - - codeWriter.WriteLine("namespace {0}", namespaceName); - codeWriter.WriteLine("{"); - - ++codeWriter.Indent; - WriteSymbolDocsIfPresent(codeWriter, implTypeSymbol); - codeWriter.Write("{0} partial interface {1}", visibilityModifier, interfaceName); - WriteTypeGenericsIfNeeded(codeWriter, implTypeSymbol); - codeWriter.WriteLine(); - codeWriter.WriteLine("{"); - - ++codeWriter.Indent; - GenerateInterfaceMemberDefinitions(codeWriter, implTypeSymbol); - --codeWriter.Indent; - - codeWriter.WriteLine("}"); - --codeWriter.Indent; - - codeWriter.WriteLine("}"); - - codeWriter.Flush(); - stream.Seek(0, SeekOrigin.Begin); - using var reader = new StreamReader(stream, Encoding.UTF8, true); - return reader.ReadToEnd(); - } - - private static void WriteTypeGenericsIfNeeded(TextWriter writer, INamedTypeSymbol implTypeSymbol) - { - if (!implTypeSymbol.IsGenericType) - { - return; - } - - writer.Write("<"); - writer.WriteJoin(", ", implTypeSymbol.TypeParameters.Select(x => x.Name)); - writer.Write(">"); - - WriteTypeParameterConstraints(writer, implTypeSymbol.TypeParameters); - } - - private void GenerateInterfaceMemberDefinitions(TextWriter writer, INamespaceOrTypeSymbol implTypeSymbol) - { - foreach (var member in implTypeSymbol.GetMembers()) - { - if (member.DeclaredAccessibility != Accessibility.Public || - member.HasAttribute(_ignoreAttribute)) - { - continue; - } - - GenerateInterfaceMemberDefinition(writer, member); - } - } - - private static void GenerateInterfaceMemberDefinition(TextWriter writer, ISymbol member) - { - switch (member) - { - case IPropertySymbol propertySymbol: - GeneratePropertyDefinition(writer, propertySymbol); - break; - case IMethodSymbol methodSymbol: - GenerateMethodDefinition(writer, methodSymbol); - break; - } - } - - private static void WriteSymbolDocsIfPresent(TextWriter writer, ISymbol symbol) - { - var xml = symbol.GetDocumentationCommentXml(); - if (string.IsNullOrWhiteSpace(xml)) - { - return; - } - - // omit the fist and last lines to skip the tag - - var reader = new StringReader(xml); - var lines = new List(); - - while (true) - { - var line = reader.ReadLine(); - if (line is null) - { - break; - } - - lines.Add(line); - } - - for (int i = 1; i < lines.Count - 1; i++) - { - var line = lines[i].TrimStart(); // for some reason, 4 spaces are inserted to the beginning of the line - writer.WriteLine("/// {0}", line); - } - } - - private static bool IsPublicOrInternal(ISymbol symbol) - { - return symbol.DeclaredAccessibility is Accessibility.Public or Accessibility.Internal; - } - - private static void GeneratePropertyDefinition(TextWriter writer, IPropertySymbol propertySymbol) - { - if (propertySymbol.IsStatic) - { - return; - } - - bool hasPublicGetter = propertySymbol.GetMethod is not null && - IsPublicOrInternal(propertySymbol.GetMethod); - - bool hasPublicSetter = propertySymbol.SetMethod is not null && - IsPublicOrInternal(propertySymbol.SetMethod); - - if (!hasPublicGetter && !hasPublicSetter) - { - return; - } - - WriteSymbolDocsIfPresent(writer, propertySymbol); - - if (propertySymbol.IsIndexer) - { - writer.Write("{0} this[", propertySymbol.Type); - writer.WriteJoin(", ", propertySymbol.Parameters, WriteMethodParam); - writer.Write("] "); - } - else - { - writer.Write("{0} {1} ", propertySymbol.Type, propertySymbol.Name); // ex. int Foo - } - - writer.Write("{ "); - - if (hasPublicGetter) - { - writer.Write("get; "); - } - - if (hasPublicSetter) - { - if (propertySymbol.SetMethod!.IsInitOnly) - { - writer.Write("init; "); - } - else - { - writer.Write("set; "); - } - } - - writer.WriteLine("}"); - } - - private static void GenerateMethodDefinition(TextWriter writer, IMethodSymbol methodSymbol) - { - if (methodSymbol.MethodKind != MethodKind.Ordinary || methodSymbol.IsStatic) - { - return; - } - - if (methodSymbol.IsImplicitlyDeclared && methodSymbol.Name != "Deconstruct") - { - // omit methods that are auto generated by the compiler (eg. record's methods), - // except for the record Deconstruct method - return; - } - - WriteSymbolDocsIfPresent(writer, methodSymbol); - - writer.Write("{0} {1}", methodSymbol.ReturnType, methodSymbol.Name); // ex. int Foo - - if (methodSymbol.IsGenericMethod) - { - writer.Write("<"); - writer.WriteJoin(", ", methodSymbol.TypeParameters.Select(x => x.Name)); - writer.Write(">"); - } - - writer.Write("("); - writer.WriteJoin(", ", methodSymbol.Parameters, WriteMethodParam); - - writer.Write(")"); - - if (methodSymbol.IsGenericMethod) - { - WriteTypeParameterConstraints(writer, methodSymbol.TypeParameters); - } - - writer.WriteLine(";"); - } - - private static void WriteMethodParam(TextWriter writer, IParameterSymbol param) - { - if (param.IsParams) - { - writer.Write("params "); - } - - switch (param.RefKind) - { - case RefKind.Ref: - writer.Write("ref "); - break; - case RefKind.Out: - writer.Write("out "); - break; - case RefKind.In: - writer.Write("in "); - break; - } - - writer.Write(param.Type); - writer.Write(" "); - - if (StringExtensions.IsCSharpKeyword(param.Name)) - { - writer.Write("@"); - } - - writer.Write(param.Name); - - if (param.HasExplicitDefaultValue) - { - WriteParamExplicitDefaultValue(writer, param); - } - } - - private static void WriteParamExplicitDefaultValue(TextWriter writer, IParameterSymbol param) - { - if (param.ExplicitDefaultValue is null) - { - writer.Write(" = default"); - } - else - { - switch (param.Type.Name) - { - case nameof(String): - writer.Write(" = \"{0}\"", param.ExplicitDefaultValue); - break; - case nameof(Single): - writer.Write(" = {0}f", param.ExplicitDefaultValue); - break; - case nameof(Double): - writer.Write(" = {0}d", param.ExplicitDefaultValue); - break; - case nameof(Decimal): - writer.Write(" = {0}m", param.ExplicitDefaultValue); - break; - case nameof(Boolean): - writer.Write(" = {0}", param.ExplicitDefaultValue.ToString().ToLower()); - break; - case nameof(Nullable): - writer.Write(" = {0}", param.ExplicitDefaultValue.ToString().ToLower()); - break; - default: - writer.Write(" = {0}", param.ExplicitDefaultValue); - break; - } - } - } - - private static void WriteTypeParameterConstraints( - TextWriter writer, - IEnumerable typeParameters) - { - foreach (var typeParameter in typeParameters) - { - var constraints = typeParameter.EnumGenericConstraints().ToList(); - if (constraints.Count == 0) - { - break; - } - - writer.Write(" where {0} : ", typeParameter.Name); - writer.WriteJoin(", ", constraints); - } - } - - private void InitAttributes(Compilation compilation) - { - _generateAutoInterfaceAttribute = compilation.GetTypeByMetadataName( - $"{Attributes.AttributesNamespace}.{Attributes.GenerateAutoInterfaceClassname}")!; - - _ignoreAttribute = compilation.GetTypeByMetadataName( - $"{Attributes.AttributesNamespace}.{Attributes.AutoInterfaceIgnoreAttributeClassname}")!; - } - - private static IEnumerable GetImplTypeSymbols(Compilation compilation, SyntaxReceiver receiver) - { - return receiver.CandidateTypes.Select(candidate => GetTypeSymbol(compilation, candidate)); - } - - private static INamedTypeSymbol GetTypeSymbol(Compilation compilation, SyntaxNode type) - { - var model = compilation.GetSemanticModel(type.SyntaxTree); - var typeSymbol = model.GetDeclaredSymbol(type)!; - return (INamedTypeSymbol)typeSymbol; - } - - private static Compilation GetCompilation(GeneratorExecutionContext context) - { - var options = context.Compilation.SyntaxTrees.First().Options as CSharpParseOptions; - - var compilation = context.Compilation.AddSyntaxTrees( - CSharpSyntaxTree.ParseText( - SourceText.From(Attributes.AttributesSourceCode, Encoding.UTF8), options)); - - return compilation; + RaiseExceptionDiagnostic(context, exception); } } + + private static void RaiseExceptionDiagnostic(GeneratorExecutionContext context, Exception exception) + { + var descriptor = new DiagnosticDescriptor( + "Speckle.InterfaceGenerator.CriticalError", + "Exception thrown in InterfaceGenerator", + $"{exception.GetType().FullName} {exception.Message} {exception.StackTrace.Trim()}", + "Speckle.InterfaceGenerator", + DiagnosticSeverity.Error, + true, + customTags: WellKnownDiagnosticTags.AnalyzerException); + + var diagnostic = Diagnostic.Create(descriptor, null); + + context.ReportDiagnostic(diagnostic); + } + + private void ExecuteCore(GeneratorExecutionContext context) + { + // setting the culture to invariant prevents errors such as emitting a decimal comma (0,1) instead of + // a decimal point (0.1) in certain cultures + var prevCulture = Thread.CurrentThread.CurrentCulture; + Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture; + + GenerateAttributes(context); + GenerateInterfaces(context); + + Thread.CurrentThread.CurrentCulture = prevCulture; + } + + private static void GenerateAttributes(GeneratorExecutionContext context) + { + context.AddSource( + Attributes.GenerateAutoInterfaceClassname, + SourceText.From(Attributes.AttributesSourceCode, Encoding.UTF8)); + } + + private void GenerateInterfaces(GeneratorExecutionContext context) + { + if (context.SyntaxReceiver is not SyntaxReceiver receiver) + { + return; + } + + var compilation = GetCompilation(context); + InitAttributes(compilation); + + var classSymbols = GetImplTypeSymbols(compilation, receiver); + + List classSymbolNames = []; + + foreach (var implTypeSymbol in classSymbols) + { + if (!implTypeSymbol.TryGetAttribute(_generateAutoInterfaceAttribute ?? throw new NullReferenceException("_generateAutoInterfaceAttribute is null"), out var attributes)) + { + continue; + } + + if(classSymbolNames.Contains(implTypeSymbol.GetFullMetadataName(useNameWhenNotFound: true))) + { + continue; // partial class, already added + } + + classSymbolNames.Add(implTypeSymbol.GetFullMetadataName(useNameWhenNotFound: true)); + + var attribute = attributes.Single(); + var source = SourceText.From(GenerateInterfaceCode(implTypeSymbol, attribute), Encoding.UTF8); + + context.AddSource($"{implTypeSymbol.GetFullMetadataName(useNameWhenNotFound: true)}_AutoInterface.g.cs", source); + } + } + + private static string InferVisibilityModifier(ISymbol implTypeSymbol, AttributeData attributeData) + { + string? result = attributeData.GetNamedParamValue(Attributes.VisibilityModifierPropName); + if (!string.IsNullOrEmpty(result)) + { + return result ?? throw new NullReferenceException("result is null"); + } + + return implTypeSymbol.DeclaredAccessibility switch + { + Accessibility.Public => "public", + _ => "internal", + }; + } + + private static string InferInterfaceName(ISymbol implTypeSymbol, AttributeData attributeData) + { + return attributeData.GetNamedParamValue(Attributes.InterfaceNamePropName) ?? $"I{implTypeSymbol.Name}"; + } + + private string GenerateInterfaceCode(INamedTypeSymbol implTypeSymbol, AttributeData attributeData) + { + using var stream = new MemoryStream(); + var streamWriter = new StreamWriter(stream, Encoding.UTF8); + var codeWriter = new IndentedTextWriter(streamWriter, " "); + + var namespaceName = implTypeSymbol.ContainingNamespace.ToDisplayString(); + var interfaceName = InferInterfaceName(implTypeSymbol, attributeData); + var visibilityModifier = InferVisibilityModifier(implTypeSymbol, attributeData); + + codeWriter.WriteLine("namespace {0}", namespaceName); + codeWriter.WriteLine("{"); + + ++codeWriter.Indent; + WriteSymbolDocsIfPresent(codeWriter, implTypeSymbol); + codeWriter.Write("{0} partial interface {1}", visibilityModifier, interfaceName); + WriteTypeGenericsIfNeeded(codeWriter, implTypeSymbol); + codeWriter.WriteLine(); + codeWriter.WriteLine("{"); + + ++codeWriter.Indent; + GenerateInterfaceMemberDefinitions(codeWriter, implTypeSymbol); + --codeWriter.Indent; + + codeWriter.WriteLine("}"); + --codeWriter.Indent; + + codeWriter.WriteLine("}"); + + codeWriter.Flush(); + stream.Seek(0, SeekOrigin.Begin); + using var reader = new StreamReader(stream, Encoding.UTF8, true); + return reader.ReadToEnd(); + } + + private static void WriteTypeGenericsIfNeeded(TextWriter writer, INamedTypeSymbol implTypeSymbol) + { + if (!implTypeSymbol.IsGenericType) + { + return; + } + + writer.Write("<"); + writer.WriteJoin(", ", implTypeSymbol.TypeParameters.Select(x => x.Name)); + writer.Write(">"); + + WriteTypeParameterConstraints(writer, implTypeSymbol.TypeParameters); + } + + private void GenerateInterfaceMemberDefinitions(TextWriter writer, INamespaceOrTypeSymbol implTypeSymbol) + { + foreach (var member in implTypeSymbol.GetMembers()) + { + if (member.DeclaredAccessibility != Accessibility.Public || + member.HasAttribute(_ignoreAttribute ?? throw new NullReferenceException("_ignoreAttribute is null"))) + { + continue; + } + + GenerateInterfaceMemberDefinition(writer, member); + } + } + + private static void GenerateInterfaceMemberDefinition(TextWriter writer, ISymbol member) + { + switch (member) + { + case IPropertySymbol propertySymbol: + GeneratePropertyDefinition(writer, propertySymbol); + break; + case IMethodSymbol methodSymbol: + GenerateMethodDefinition(writer, methodSymbol); + break; + } + } + + private static void WriteSymbolDocsIfPresent(TextWriter writer, ISymbol symbol) + { + var xml = symbol.GetDocumentationCommentXml(); + if (string.IsNullOrWhiteSpace(xml)) + { + return; + } + + // omit the fist and last lines to skip the tag + + var reader = new StringReader(xml); + var lines = new List(); + + while (true) + { + var line = reader.ReadLine(); + if (line is null) + { + break; + } + + lines.Add(line); + } + + for (int i = 1; i < lines.Count - 1; i++) + { + var line = lines[i].TrimStart(); // for some reason, 4 spaces are inserted to the beginning of the line + writer.WriteLine("/// {0}", line); + } + } + + private static bool IsPublicOrInternal(ISymbol symbol) + { + return symbol.DeclaredAccessibility is Accessibility.Public or Accessibility.Internal; + } + + private static void GeneratePropertyDefinition(TextWriter writer, IPropertySymbol propertySymbol) + { + if (propertySymbol.IsStatic) + { + return; + } + + bool hasPublicGetter = propertySymbol.GetMethod is not null && + IsPublicOrInternal(propertySymbol.GetMethod); + + bool hasPublicSetter = propertySymbol.SetMethod is not null && + IsPublicOrInternal(propertySymbol.SetMethod); + + if (!hasPublicGetter && !hasPublicSetter) + { + return; + } + + WriteSymbolDocsIfPresent(writer, propertySymbol); + + if (propertySymbol.IsIndexer) + { + writer.Write("{0} this[", propertySymbol.Type); + writer.WriteJoin(", ", propertySymbol.Parameters, WriteMethodParam); + writer.Write("] "); + } + else + { + writer.Write("{0} {1} ", propertySymbol.Type, propertySymbol.Name); // ex. int Foo + } + + writer.Write("{ "); + + if (hasPublicGetter) + { + writer.Write("get; "); + } + + if (hasPublicSetter) + { + if (propertySymbol.SetMethod?.IsInitOnly ?? false) + { + writer.Write("init; "); + } + else + { + writer.Write("set; "); + } + } + + writer.WriteLine("}"); + } + + private static void GenerateMethodDefinition(TextWriter writer, IMethodSymbol methodSymbol) + { + if (methodSymbol.MethodKind != MethodKind.Ordinary || methodSymbol.IsStatic) + { + return; + } + + if (methodSymbol.IsImplicitlyDeclared && methodSymbol.Name != "Deconstruct") + { + // omit methods that are auto generated by the compiler (eg. record's methods), + // except for the record Deconstruct method + return; + } + + WriteSymbolDocsIfPresent(writer, methodSymbol); + + writer.Write("{0} {1}", methodSymbol.ReturnType, methodSymbol.Name); // ex. int Foo + + if (methodSymbol.IsGenericMethod) + { + writer.Write("<"); + writer.WriteJoin(", ", methodSymbol.TypeParameters.Select(x => x.Name)); + writer.Write(">"); + } + + writer.Write("("); + writer.WriteJoin(", ", methodSymbol.Parameters, WriteMethodParam); + + writer.Write(")"); + + if (methodSymbol.IsGenericMethod) + { + WriteTypeParameterConstraints(writer, methodSymbol.TypeParameters); + } + + writer.WriteLine(";"); + } + + private static void WriteMethodParam(TextWriter writer, IParameterSymbol param) + { + if (param.IsParams) + { + writer.Write("params "); + } + + switch (param.RefKind) + { + case RefKind.Ref: + writer.Write("ref "); + break; + case RefKind.Out: + writer.Write("out "); + break; + case RefKind.In: + writer.Write("in "); + break; + } + + writer.Write(param.Type); + writer.Write(" "); + + if (StringExtensions.IsCSharpKeyword(param.Name)) + { + writer.Write("@"); + } + + writer.Write(param.Name); + + if (param.HasExplicitDefaultValue) + { + WriteParamExplicitDefaultValue(writer, param); + } + } + + private static void WriteParamExplicitDefaultValue(TextWriter writer, IParameterSymbol param) + { + if (param.ExplicitDefaultValue is null) + { + writer.Write(" = default"); + } + else + { + switch (param.Type.Name) + { + case nameof(String): + writer.Write(" = \"{0}\"", param.ExplicitDefaultValue); + break; + case nameof(Single): + writer.Write(" = {0}f", param.ExplicitDefaultValue); + break; + case nameof(Double): + writer.Write(" = {0}d", param.ExplicitDefaultValue); + break; + case nameof(Decimal): + writer.Write(" = {0}m", param.ExplicitDefaultValue); + break; + case nameof(Boolean): + writer.Write(" = {0}", param.ExplicitDefaultValue.ToString().ToLower()); + break; + case nameof(Nullable): + writer.Write(" = {0}", param.ExplicitDefaultValue.ToString().ToLower()); + break; + default: + writer.Write(" = {0}", param.ExplicitDefaultValue); + break; + } + } + } + + private static void WriteTypeParameterConstraints( + TextWriter writer, + IEnumerable typeParameters) + { + foreach (var typeParameter in typeParameters) + { + var constraints = typeParameter.EnumGenericConstraints().ToList(); + if (constraints.Count == 0) + { + break; + } + + writer.Write(" where {0} : ", typeParameter.Name); + writer.WriteJoin(", ", constraints); + } + } + + private void InitAttributes(Compilation compilation) + { + _generateAutoInterfaceAttribute = compilation.GetTypeByMetadataName( + $"{Attributes.AttributesNamespace}.{Attributes.GenerateAutoInterfaceClassname}"); + + _ignoreAttribute = compilation.GetTypeByMetadataName( + $"{Attributes.AttributesNamespace}.{Attributes.AutoInterfaceIgnoreAttributeClassname}"); + } + + private static IEnumerable GetImplTypeSymbols(Compilation compilation, SyntaxReceiver receiver) + { + return receiver.CandidateTypes.Select(candidate => GetTypeSymbol(compilation, candidate)).Where(x => x != null).Cast(); + } + + private static INamedTypeSymbol? GetTypeSymbol(Compilation compilation, SyntaxNode type) + { + var model = compilation.GetSemanticModel(type.SyntaxTree); + var typeSymbol = model.GetDeclaredSymbol(type); + return typeSymbol as INamedTypeSymbol; + } + + private static Compilation GetCompilation(GeneratorExecutionContext context) + { + var options = context.Compilation.SyntaxTrees.First().Options as CSharpParseOptions; + + var compilation = context.Compilation.AddSyntaxTrees( + CSharpSyntaxTree.ParseText( + SourceText.From(Attributes.AttributesSourceCode, Encoding.UTF8), options)); + + return compilation; + } } \ No newline at end of file diff --git a/InterfaceGenerator/InterfaceGenerator.csproj b/InterfaceGenerator/Speckle.InterfaceGenerator.csproj similarity index 70% rename from InterfaceGenerator/InterfaceGenerator.csproj rename to InterfaceGenerator/Speckle.InterfaceGenerator.csproj index 9da3776..5da3d0f 100644 --- a/InterfaceGenerator/InterfaceGenerator.csproj +++ b/InterfaceGenerator/Speckle.InterfaceGenerator.csproj @@ -1,23 +1,23 @@ netstandard2 - 9.0 + Latest enable - 1.0.14 + 0.9.0 true false false true + Speckle.InterfaceGenerator - R. David - InterfaceGenerator + Speckle.InterfaceGenerator A source generator that creates interfaces from implementations - https://github.com/daver32/InterfaceGenerator - https://github.com/daver32/InterfaceGenerator/blob/master/LICENSE - https://github.com/daver32/InterfaceGenerator + https://github.com/specklesystems/InterfaceGenerator + https://github.com/specklesystems/InterfaceGenerator/blob/master/LICENSE + https://github.com/specklesystems/InterfaceGenerator git diff --git a/InterfaceGenerator/StringExtensions.cs b/InterfaceGenerator/StringExtensions.cs index 8859a69..1240040 100644 --- a/InterfaceGenerator/StringExtensions.cs +++ b/InterfaceGenerator/StringExtensions.cs @@ -1,125 +1,124 @@ -namespace InterfaceGenerator +namespace Speckle.InterfaceGenerator; + +internal static class StringExtensions { - internal static class StringExtensions + public static bool IsCSharpKeyword(string? name) { - public static bool IsCSharpKeyword(string? name) + switch (name) { - switch (name) - { - case "abstract": - case "add": - case "alias": - case "as": - case "ascending": - case "async": - case "await": - case "base": - case "bool": - case "break": - case "by": - case "byte": - case "case": - case "catch": - case "char": - case "checked": - case "class": - case "const": - case "continue": - case "decimal": - case "default": - case "delegate": - case "descending": - case "do": - case "double": - case "dynamic": - case "else": - case "enum": - case "equals": - case "event": - case "explicit": - case "extern": - case "false": - case "finally": - case "fixed": - case "float": - case "for": - case "foreach": - case "from": - case "get": - case "global": - case "goto": - // `group` is a contextual to linq queries that we don't generate - //case "group": - case "if": - case "implicit": - case "in": - case "int": - case "interface": - case "internal": - case "into": - case "is": - case "join": - case "let": - case "lock": - case "long": - case "nameof": - case "namespace": - case "new": - case "null": - case "object": - case "on": - case "operator": - // `orderby` is a contextual to linq queries that we don't generate - //case "orderby": - case "out": - case "override": - case "params": - case "partial": - case "private": - case "protected": - case "public": - case "readonly": - case "ref": - case "remove": - case "return": - case "sbyte": - case "sealed": - // `select` is a contextual to linq queries that we don't generate - // case "select": - case "set": - case "short": - case "sizeof": - case "stackalloc": - case "static": - case "string": - case "struct": - case "switch": - case "this": - case "throw": - case "true": - case "try": - case "typeof": - case "uint": - case "ulong": - case "unchecked": - case "unmanaged": - case "unsafe": - case "ushort": - case "using": - // `value` is a contextual to getters that we don't generate - // case "value": - case "var": - case "virtual": - case "void": - case "volatile": - case "when": - case "where": - case "while": - case "yield": - return true; - default: - return false; - } + case "abstract": + case "add": + case "alias": + case "as": + case "ascending": + case "async": + case "await": + case "base": + case "bool": + case "break": + case "by": + case "byte": + case "case": + case "catch": + case "char": + case "checked": + case "class": + case "const": + case "continue": + case "decimal": + case "default": + case "delegate": + case "descending": + case "do": + case "double": + case "dynamic": + case "else": + case "enum": + case "equals": + case "event": + case "explicit": + case "extern": + case "false": + case "finally": + case "fixed": + case "float": + case "for": + case "foreach": + case "from": + case "get": + case "global": + case "goto": + // `group` is a contextual to linq queries that we don't generate + //case "group": + case "if": + case "implicit": + case "in": + case "int": + case "interface": + case "internal": + case "into": + case "is": + case "join": + case "let": + case "lock": + case "long": + case "nameof": + case "namespace": + case "new": + case "null": + case "object": + case "on": + case "operator": + // `orderby` is a contextual to linq queries that we don't generate + //case "orderby": + case "out": + case "override": + case "params": + case "partial": + case "private": + case "protected": + case "public": + case "readonly": + case "ref": + case "remove": + case "return": + case "sbyte": + case "sealed": + // `select` is a contextual to linq queries that we don't generate + // case "select": + case "set": + case "short": + case "sizeof": + case "stackalloc": + case "static": + case "string": + case "struct": + case "switch": + case "this": + case "throw": + case "true": + case "try": + case "typeof": + case "uint": + case "ulong": + case "unchecked": + case "unmanaged": + case "unsafe": + case "ushort": + case "using": + // `value` is a contextual to getters that we don't generate + // case "value": + case "var": + case "virtual": + case "void": + case "volatile": + case "when": + case "where": + case "while": + case "yield": + return true; + default: + return false; } } } \ No newline at end of file diff --git a/InterfaceGenerator/SymbolExtensions.cs b/InterfaceGenerator/SymbolExtensions.cs index 5f309ae..fc7adc8 100644 --- a/InterfaceGenerator/SymbolExtensions.cs +++ b/InterfaceGenerator/SymbolExtensions.cs @@ -3,66 +3,65 @@ using System.Linq; using System.Text; using Microsoft.CodeAnalysis; -namespace InterfaceGenerator +namespace Speckle.InterfaceGenerator; + +internal static class SymbolExtensions { - internal static class SymbolExtensions + public static bool TryGetAttribute( + this ISymbol symbol, + INamedTypeSymbol attributeType, + out IEnumerable attributes) { - public static bool TryGetAttribute( - this ISymbol symbol, - INamedTypeSymbol attributeType, - out IEnumerable attributes) + attributes = symbol.GetAttributes() + .Where(a => SymbolEqualityComparer.Default.Equals(a.AttributeClass, attributeType)); + return attributes.Any(); + } + + public static bool HasAttribute(this ISymbol symbol, INamedTypeSymbol attributeType) + { + return symbol.GetAttributes() + .Any(a => SymbolEqualityComparer.Default.Equals(a.AttributeClass, attributeType)); + } + + //Ref: https://stackoverflow.com/questions/27105909/get-fully-qualified-metadata-name-in-roslyn + public static string GetFullMetadataName(this ISymbol symbol, bool useNameWhenNotFound = false) + { + if (IsRootNamespace(symbol)) { - attributes = symbol.GetAttributes() - .Where(a => SymbolEqualityComparer.Default.Equals(a.AttributeClass, attributeType)); - return attributes.Any(); + return useNameWhenNotFound ? symbol.Name : string.Empty; } - public static bool HasAttribute(this ISymbol symbol, INamedTypeSymbol attributeType) - { - return symbol.GetAttributes() - .Any(a => SymbolEqualityComparer.Default.Equals(a.AttributeClass, attributeType)); - } + var stringBuilder = new StringBuilder(symbol.MetadataName); + var last = symbol; - //Ref: https://stackoverflow.com/questions/27105909/get-fully-qualified-metadata-name-in-roslyn - public static string GetFullMetadataName(this ISymbol symbol, bool useNameWhenNotFound = false) + symbol = symbol.ContainingSymbol; + + while (!IsRootNamespace(symbol)) { - if (IsRootNamespace(symbol)) + if (symbol is ITypeSymbol && last is ITypeSymbol) { - return useNameWhenNotFound ? symbol.Name : string.Empty; + stringBuilder.Insert(0, '+'); + } + else + { + stringBuilder.Insert(0, '.'); } - var stringBuilder = new StringBuilder(symbol.MetadataName); - var last = symbol; - + stringBuilder.Insert(0, symbol.OriginalDefinition.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat)); symbol = symbol.ContainingSymbol; - - while (!IsRootNamespace(symbol)) - { - if (symbol is ITypeSymbol && last is ITypeSymbol) - { - stringBuilder.Insert(0, '+'); - } - else - { - stringBuilder.Insert(0, '.'); - } - - stringBuilder.Insert(0, symbol.OriginalDefinition.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat)); - symbol = symbol.ContainingSymbol; - } - - var retVal = stringBuilder.ToString(); - if (string.IsNullOrWhiteSpace(retVal) && useNameWhenNotFound) - { - return symbol.Name; - } - - return retVal; } - private static bool IsRootNamespace(ISymbol symbol) + var retVal = stringBuilder.ToString(); + if (string.IsNullOrWhiteSpace(retVal) && useNameWhenNotFound) { - return symbol is INamespaceSymbol { IsGlobalNamespace: true }; + return symbol.Name; } + + return retVal; + } + + private static bool IsRootNamespace(ISymbol symbol) + { + return symbol is INamespaceSymbol { IsGlobalNamespace: true }; } } \ No newline at end of file diff --git a/InterfaceGenerator/SyntaxReceiver.cs b/InterfaceGenerator/SyntaxReceiver.cs index 570b42b..742630f 100644 --- a/InterfaceGenerator/SyntaxReceiver.cs +++ b/InterfaceGenerator/SyntaxReceiver.cs @@ -2,25 +2,24 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; -namespace InterfaceGenerator -{ - internal class SyntaxReceiver : ISyntaxReceiver - { - public IList CandidateTypes { get; } = new List(); - - public void OnVisitSyntaxNode(SyntaxNode syntaxNode) - { - if (syntaxNode is TypeDeclarationSyntax typeDeclarationSyntax && - IsClassOrRecord(typeDeclarationSyntax) && - typeDeclarationSyntax.AttributeLists.Count > 0) - { - CandidateTypes.Add(typeDeclarationSyntax); - } - } +namespace Speckle.InterfaceGenerator; - private static bool IsClassOrRecord(TypeDeclarationSyntax typeDeclarationSyntax) +internal class SyntaxReceiver : ISyntaxReceiver +{ + public IList CandidateTypes { get; } = new List(); + + public void OnVisitSyntaxNode(SyntaxNode syntaxNode) + { + if (syntaxNode is TypeDeclarationSyntax typeDeclarationSyntax && + IsClassOrRecord(typeDeclarationSyntax) && + typeDeclarationSyntax.AttributeLists.Count > 0) { - return typeDeclarationSyntax is ClassDeclarationSyntax || typeDeclarationSyntax is RecordDeclarationSyntax; + CandidateTypes.Add(typeDeclarationSyntax); } } + + private static bool IsClassOrRecord(TypeDeclarationSyntax typeDeclarationSyntax) + { + return typeDeclarationSyntax is ClassDeclarationSyntax || typeDeclarationSyntax is RecordDeclarationSyntax; + } } \ No newline at end of file diff --git a/InterfaceGenerator/TextWriterExtensions.cs b/InterfaceGenerator/TextWriterExtensions.cs index 2c25a61..4120d99 100644 --- a/InterfaceGenerator/TextWriterExtensions.cs +++ b/InterfaceGenerator/TextWriterExtensions.cs @@ -2,44 +2,43 @@ using System.Collections.Generic; using System.IO; -namespace InterfaceGenerator +namespace Speckle.InterfaceGenerator; + +internal static class TextWriterExtensions { - internal static class TextWriterExtensions + + public static void WriteJoin( + this TextWriter writer, + string separator, + IEnumerable values) { + writer.WriteJoin(separator, values, (w, x) => w.Write(x)); + } - public static void WriteJoin( - this TextWriter writer, - string separator, - IEnumerable values) + public static void WriteJoin( + this TextWriter writer, + string separator, + IEnumerable values, + Action writeAction) + { + using var enumerator = values.GetEnumerator(); + + if (!enumerator.MoveNext()) { - writer.WriteJoin(separator, values, (w, x) => w.Write(x)); + return; } - - public static void WriteJoin( - this TextWriter writer, - string separator, - IEnumerable values, - Action writeAction) + + writeAction(writer, enumerator.Current); + + if (!enumerator.MoveNext()) { - using var enumerator = values.GetEnumerator(); - - if (!enumerator.MoveNext()) - { - return; - } + return; + } + do + { + writer.Write(separator); writeAction(writer, enumerator.Current); - - if (!enumerator.MoveNext()) - { - return; - } - - do - { - writer.Write(separator); - writeAction(writer, enumerator.Current); - } while (enumerator.MoveNext()); - } + } while (enumerator.MoveNext()); } } \ No newline at end of file diff --git a/InterfaceGenerator/TypeParameterSymbolExtensions.cs b/InterfaceGenerator/TypeParameterSymbolExtensions.cs index bfaa883..ebc4388 100644 --- a/InterfaceGenerator/TypeParameterSymbolExtensions.cs +++ b/InterfaceGenerator/TypeParameterSymbolExtensions.cs @@ -1,48 +1,47 @@ using System.Collections.Generic; using Microsoft.CodeAnalysis; -namespace InterfaceGenerator +namespace Speckle.InterfaceGenerator; + +internal static class TypeParameterSymbolExtensions { - internal static class TypeParameterSymbolExtensions + public static IEnumerable EnumGenericConstraints(this ITypeParameterSymbol symbol) { - public static IEnumerable EnumGenericConstraints(this ITypeParameterSymbol symbol) + // the class/struct/unmanaged/notnull constraint has to be the last + if (symbol.HasNotNullConstraint) { - // the class/struct/unmanaged/notnull constraint has to be the last - if (symbol.HasNotNullConstraint) - { - yield return "notnull"; - } + yield return "notnull"; + } - if (symbol.HasValueTypeConstraint) - { - yield return "struct"; - } + if (symbol.HasValueTypeConstraint) + { + yield return "struct"; + } - if (symbol.HasUnmanagedTypeConstraint) - { - yield return "unmanaged"; - } + if (symbol.HasUnmanagedTypeConstraint) + { + yield return "unmanaged"; + } - if (symbol.HasReferenceTypeConstraint) - { - yield return symbol.ReferenceTypeConstraintNullableAnnotation == NullableAnnotation.Annotated - ? "class?" - : "class"; - } + if (symbol.HasReferenceTypeConstraint) + { + yield return symbol.ReferenceTypeConstraintNullableAnnotation == NullableAnnotation.Annotated + ? "class?" + : "class"; + } - // types go in the middle - foreach (var constraintType in symbol.ConstraintTypes) - { - yield return constraintType.ToDisplayString(); - } + // types go in the middle + foreach (var constraintType in symbol.ConstraintTypes) + { + yield return constraintType.ToDisplayString(); + } - // the new() constraint has to be the last - if (symbol.HasConstructorConstraint) - { - yield return "new()"; - } + // the new() constraint has to be the last + if (symbol.HasConstructorConstraint) + { + yield return "new()"; } } } \ No newline at end of file diff --git a/InterfaceGenerator.sln b/Speckle.InterfaceGenerator.sln similarity index 72% rename from InterfaceGenerator.sln rename to Speckle.InterfaceGenerator.sln index 3cae7ca..e52c24d 100644 --- a/InterfaceGenerator.sln +++ b/Speckle.InterfaceGenerator.sln @@ -1,8 +1,8 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InterfaceGenerator", "InterfaceGenerator\InterfaceGenerator.csproj", "{D40E2D60-5580-42D2-8316-F5AAA42CFBF6}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Speckle.InterfaceGenerator", "InterfaceGenerator\Speckle.InterfaceGenerator.csproj", "{D40E2D60-5580-42D2-8316-F5AAA42CFBF6}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InterfaceGenerator.Tests", "InterfaceGenerator.Tests\InterfaceGenerator.Tests.csproj", "{D39C541E-9EDC-41E9-BBD8-FAA9DA602CC0}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Speckle.InterfaceGenerator.Tests", "InterfaceGenerator.Tests\Speckle.InterfaceGenerator.Tests.csproj", "{D39C541E-9EDC-41E9-BBD8-FAA9DA602CC0}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution