Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Use ForAttributeWithMetadataName() #54

Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,11 @@
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<EnableNETAnalyzers>True</EnableNETAnalyzers>
<AnalysisLevel>latest-Recommended</AnalysisLevel>
<Version>3.0.0</Version>
<Version>2.5.0</Version>
<Version>3.0.1</Version>
<PackageReadmeFile>README.md</PackageReadmeFile>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<NoWarn>1701;1702;NU5128</NoWarn>
<PackageReleaseNotes>Use ForAttributeWithMetadataName to improve performance</PackageReleaseNotes>
</PropertyGroup>

<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='DebugGenerator|AnyCPU'">
Expand All @@ -42,8 +42,7 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.CodeAnalysis.Common" Version="4.2.0" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.2.0" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.3.1" PrivateAssets="all" />
</ItemGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
using System;
using System.Collections.Immutable;
using System.Text;
using System.Threading;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;

namespace AutomaticInterface;

Expand All @@ -16,68 +13,23 @@ public class AutomaticInterfaceGenerator : IIncrementalGenerator

public void Initialize(IncrementalGeneratorInitializationContext context)
{
context.RegisterPostInitializationOutput(static postInitializationContext =>
{
postInitializationContext.AddSource(
$"{DefaultAttributeName}.Attribute.g.cs",
SourceText.From(
$$$"""
// <auto-generated />
using System;

namespace AutomaticInterface
{
/// <summary>
/// Use source generator to automatically create a Interface from this class
/// </summary>
[AttributeUsage(AttributeTargets.Class)]
internal sealed class {{{DefaultAttributeName}}}Attribute : Attribute
{
internal {{{DefaultAttributeName}}}Attribute(string namespaceName = "") { }
}
}
""",
Encoding.UTF8
)
);
});
context.RegisterDefaultAttribute();
context.RegisterIgnoreAttribute();

var classes = context
.SyntaxProvider.CreateSyntaxProvider(CouldBeClassWithInterfaceAttribute, Transform)
.Where(type => type is not null)
.SyntaxProvider.ForAttributeWithMetadataName(
$"AutomaticInterface.{DefaultAttributeName}Attribute",
(node, _) => node is ClassDeclarationSyntax,
(context, _) => (ITypeSymbol)context.TargetSymbol
)
.Collect();

context.RegisterSourceOutput(classes, GenerateCode);
}

private static bool CouldBeClassWithInterfaceAttribute(
SyntaxNode syntaxNode,
CancellationToken _
)
{
if (syntaxNode is not AttributeSyntax attribute)
{
return false;
}

var name = ExtractName(attribute.Name);

return name is DefaultAttributeName;
}

private static string? ExtractName(NameSyntax? name)
{
return name switch
{
SimpleNameSyntax ins => ins.Identifier.Text,
QualifiedNameSyntax qns => qns.Right.Identifier.Text,
_ => null
};
}

private static void GenerateCode(
SourceProductionContext context,
ImmutableArray<ITypeSymbol?> enumerations
ImmutableArray<ITypeSymbol> enumerations
)
{
if (enumerations.IsDefaultOrEmpty)
Expand All @@ -87,13 +39,8 @@ private static void GenerateCode(

foreach (var type in enumerations)
{
if (type is null)
{
continue;
}

var typeNamespace = type.ContainingNamespace.IsGlobalNamespace
? $"${Guid.NewGuid().ToString()}"
? $"${Guid.NewGuid()}"
: $"{type.ContainingNamespace}";

var code = Builder.BuildInterfaceFor(type);
Expand All @@ -102,23 +49,4 @@ private static void GenerateCode(
context.AddSource(hintName, code);
}
}

private static ITypeSymbol? Transform(
GeneratorSyntaxContext context,
CancellationToken cancellationToken
)
{
var attributeSyntax = (AttributeSyntax)context.Node;
if (attributeSyntax.Parent?.Parent is not ClassDeclarationSyntax classDeclaration)
{
return null;
}

var type =
context.SemanticModel.GetDeclaredSymbol(
classDeclaration,
cancellationToken: cancellationToken
) as ITypeSymbol;
return type;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Text;

namespace AutomaticInterface;

internal static class RegisterAttributesExtensions
{
public static IncrementalGeneratorInitializationContext RegisterDefaultAttribute(
this IncrementalGeneratorInitializationContext context
)
{
context.RegisterPostInitializationOutput(static postInitializationContext =>
{
postInitializationContext.AddSource(
$"{AutomaticInterfaceGenerator.DefaultAttributeName}.Attribute.g.cs",
SourceText.From(
$$$"""
// <auto-generated />
using System;

namespace AutomaticInterface
{
/// <summary>
/// Use source generator to automatically create a Interface from this class
/// </summary>
[AttributeUsage(AttributeTargets.Class)]
internal sealed class {{{AutomaticInterfaceGenerator.DefaultAttributeName}}}Attribute : Attribute
{
internal {{{AutomaticInterfaceGenerator.DefaultAttributeName}}}Attribute(string namespaceName = "") { }
}
}
""",
Encoding.UTF8
)
);
});
return context;
}

public static IncrementalGeneratorInitializationContext RegisterIgnoreAttribute(
this IncrementalGeneratorInitializationContext context
)
{
context.RegisterPostInitializationOutput(static postInitializationContext =>
{
postInitializationContext.AddSource(
$"{AutomaticInterfaceGenerator.IgnoreAutomaticInterfaceAttributeName}.Attribute.g.cs",
SourceText.From(
$$$"""
// <auto-generated />
using System;

namespace AutomaticInterface
{
/// <summary>
/// Ignore this member in a generated Interface from this class
/// </summary>
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Event)]
internal sealed class {{{AutomaticInterfaceGenerator.IgnoreAutomaticInterfaceAttributeName}}}Attribute : Attribute
{
internal {{{AutomaticInterfaceGenerator.IgnoreAutomaticInterfaceAttributeName}}}Attribute() { }
}
}
""",
Encoding.UTF8
)
);
});
return context;
}
}

This file was deleted.

6 changes: 6 additions & 0 deletions AutomaticInterface/AutomaticInterfaceExample/DemoClass.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@
/// <summary>
/// Property Documentation will be copied
/// </summary>
public string Hello { get; set; } // included, get and set are copied to the interface when public

Check warning on line 21 in AutomaticInterface/AutomaticInterfaceExample/DemoClass.cs

View workflow job for this annotation

GitHub Actions / test

Non-nullable property 'Hello' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

public string OnlyGet { get; } // included, get and set are copied to the interface when public

Check warning on line 23 in AutomaticInterface/AutomaticInterfaceExample/DemoClass.cs

View workflow job for this annotation

GitHub Actions / test

Non-nullable property 'OnlyGet' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

public string? NullableProperty { get; set; }

Expand All @@ -46,9 +46,15 @@
return "Ok";
}

[IgnoreAutomaticInterface]
public string IgnoreMethod(string x, string y) // // ignored because of attribute
{
return BMethod(x, y);
}

public Task<string?> ASync(string x, string y)
{
return Task.FromResult("");

Check warning on line 57 in AutomaticInterface/AutomaticInterfaceExample/DemoClass.cs

View workflow job for this annotation

GitHub Actions / test

Nullability of reference types in value of type 'Task<string>' doesn't match target type 'Task<string?>'.
}

public static string StaticProperty => "abc"; // static property, ignored
Expand All @@ -62,18 +68,18 @@
/// event Documentation will be copied
/// </summary>
#pragma warning disable S3264 // Events should be invoked
public event EventHandler ShapeChanged; // included

Check warning on line 71 in AutomaticInterface/AutomaticInterfaceExample/DemoClass.cs

View workflow job for this annotation

GitHub Actions / test

Non-nullable event 'ShapeChanged' must contain a non-null value when exiting constructor. Consider declaring the event as nullable.

Check warning on line 71 in AutomaticInterface/AutomaticInterfaceExample/DemoClass.cs

View workflow job for this annotation

GitHub Actions / test

The event 'DemoClass.ShapeChanged' is never used
#pragma warning restore S3264 // Events should be invoked

#pragma warning disable S3264 // Events should be invoked
#pragma warning disable IDE0051 // Remove unused private members
private event EventHandler ShapeChanged2; // ignored because not public

Check warning on line 76 in AutomaticInterface/AutomaticInterfaceExample/DemoClass.cs

View workflow job for this annotation

GitHub Actions / test

Non-nullable event 'ShapeChanged2' must contain a non-null value when exiting constructor. Consider declaring the event as nullable.
#pragma warning restore IDE0051 // Remove unused private members
#pragma warning restore S3264 // Events should be invoked

public event EventHandler? ShapeChangedNullable; // included

Check warning on line 80 in AutomaticInterface/AutomaticInterfaceExample/DemoClass.cs

View workflow job for this annotation

GitHub Actions / test

The event 'DemoClass.ShapeChangedNullable' is never used

public event EventHandler<string?> ShapeChangedNullable2; // included

Check warning on line 82 in AutomaticInterface/AutomaticInterfaceExample/DemoClass.cs

View workflow job for this annotation

GitHub Actions / test

Non-nullable event 'ShapeChangedNullable2' must contain a non-null value when exiting constructor. Consider declaring the event as nullable.

Check warning on line 82 in AutomaticInterface/AutomaticInterfaceExample/DemoClass.cs

View workflow job for this annotation

GitHub Actions / test

The event 'DemoClass.ShapeChangedNullable2' is never used

private readonly int[] arr = new int[100];

Expand Down
63 changes: 8 additions & 55 deletions AutomaticInterface/Tests/GeneratorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -718,54 +718,7 @@ public void IgnoresMembersAttributedWithSkip()
{
const string code = """

using AutomaticInterfaceAttribute;
using System.IO;

namespace AutomaticInterfaceExample
{

[GenerateAutomaticInterface]
class DemoClass
{
[IgnoreAutomaticInterface] public string Hello1(string x, int y, double z){return x;}
[IgnoreAutomaticInterface] public string Hello2 { get; set; }
[IgnoreAutomaticInterface] public event EventHandler Hello3;
}
}

""";

const string expected = """
//--------------------------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
//
// Changes to this file may cause incorrect behavior and will be lost if the code is regenerated.
// </auto-generated>
//--------------------------------------------------------------------------------------------------

using System.CodeDom.Compiler;
using AutomaticInterfaceAttribute;
using System.IO;

namespace AutomaticInterfaceExample
{
[GeneratedCode("AutomaticInterface", "")]
public partial interface IDemoClass
{
}
}

""";
GenerateCode(code).Should().Be(expected);
}

[Fact]
public void IgnoresMembersAttributedWithSkip()
{
const string code = """

using AutomaticInterfaceAttribute;
using AutomaticInterface;
using System.IO;

namespace AutomaticInterfaceExample
Expand All @@ -792,7 +745,7 @@ class DemoClass
//--------------------------------------------------------------------------------------------------

using System.CodeDom.Compiler;
using AutomaticInterfaceAttribute;
using AutomaticInterface;
using System.IO;

namespace AutomaticInterfaceExample
Expand Down Expand Up @@ -2305,7 +2258,7 @@ public void WorksWithMethodOverrides()
{
const string code = """

using AutomaticInterfaceAttribute;
using AutomaticInterface;

namespace AutomaticInterfaceExample;

Expand All @@ -2332,7 +2285,7 @@ public class DemoClass : BaseClass
//--------------------------------------------------------------------------------------------------

using System.CodeDom.Compiler;
using AutomaticInterfaceAttribute;
using AutomaticInterface;

namespace AutomaticInterfaceExample
{
Expand All @@ -2354,7 +2307,7 @@ public void WorksWithMethodShadowing()
{
const string code = """

using AutomaticInterfaceAttribute;
using AutomaticInterface;

namespace AutomaticInterfaceExample;

Expand All @@ -2381,7 +2334,7 @@ public class DemoClass : BaseClass
//--------------------------------------------------------------------------------------------------

using System.CodeDom.Compiler;
using AutomaticInterfaceAttribute;
using AutomaticInterface;

namespace AutomaticInterfaceExample
{
Expand All @@ -2403,7 +2356,7 @@ public void WorksWithParameterDirectionOverloads()
{
const string code = """

using AutomaticInterfaceAttribute;
using AutomaticInterface;

namespace AutomaticInterfaceExample;

Expand All @@ -2427,7 +2380,7 @@ public class DemoClass
//--------------------------------------------------------------------------------------------------

using System.CodeDom.Compiler;
using AutomaticInterfaceAttribute;
using AutomaticInterface;

namespace AutomaticInterfaceExample
{
Expand Down
Loading
Loading