Giter Club home page Giter Club logo

net_automatic_interface's Introduction

Automatic Interface

A C# Source Generator to automatically create Interface from classes.

NuGet version (sourcedepend)

What does it do?

Not all .NET Interfaces are created equal. Some Interfaces are lovingly handcrafted, e.g. the public interface of your .NET package which is used by your customers. Other interfaces are far from lovingly crafted, they are birthed because you need an interface for testing or for the DI container. They are often implemented only once or twice: The class itself and a mock for testing. They are noise at best and often create lots of friction. Adding a new method / field? You have to edit the interface, too!. Change parameters? Edit the interface. Add documentation? Hopefully you add it to the interface, too!

This Source Generator aims to eliminate this cost by generating an interface from the class, without you needing to do anything. This interface will be generated on each subsequent build, eliminating the the friction.

Example

using AutomaticInterfaceAttribute;
using System;

namespace AutomaticInterfaceExample
{
    /// <summary>
    /// Class Documentation will be copied
    /// </summary>
    [GenerateAutomaticInterface]  // you need to create an Attribute with exactly this name in your solution. You cannot reference Code from the Analyzer.
    class DemoClass: IDemoClass // You Interface will get the Name I+classname, here IDemoclass. 
    // Generics, including constraints are allowed, too. E.g. MyClass<T> where T: class
    {

        /// <summary>
        /// Property Documentation will be copied
        /// </summary>
        public string Hello { get; set; }  // included, get and set are copied to the interface when public

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

        /// <summary>
        /// Method Documentation will be copied
        /// </summary>
        public string AMethod(string x, string y) // included
        {
            return BMethod(x,y);
        }

        private string BMethod(string x, string y) // ignored because not public
        {
            return x + y;
        }
		
        public string CMethod<T, T1, T2, T3, T4>(string? x, string y) // included
            where T : class
            where T1 : struct
            where T3 : DemoClass
            where T4 : IDemoClass
        {
            return "Ok";
        }

        public static string StaticProperty => "abc"; // static property, ignored

        public static string StaticMethod()  // static method, ignored
        {
            return "static" + DateTime.Now;
        }

        /// <summary>
        /// event Documentation will be copied
        /// </summary>

        public event EventHandler ShapeChanged;  // included

        private event EventHandler ShapeChanged2; // ignored because not public

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

        public int this[int index] // currently ignored
        {
            get => arr[index];
            set => arr[index] = value;
        }
    }
}

This will create this interface:

#nullable enable
using System.CodeDom.Compiler;
using AutomaticInterfaceAttribute;
using System;

/// <summary>
/// Result of the generator
/// </summary>
namespace AutomaticInterfaceExample
{
    /// <summary>
    /// Bla bla
    /// </summary>
    [GeneratedCode("AutomaticInterface", "")]
    public partial interface IDemoClass
    {
        /// <summary>
        /// Property Documentation will be copied
        /// </summary>
        string Hello { get; set; }

        string OnlyGet { get; }

        /// <summary>
        /// Method Documentation will be copied
        /// </summary>
        string AMethod(string x, string y);

        string CMethod<T, T1, T2, T3, T4>(string? x, string y) where T : class where T1 : struct where T3 : DemoClass where T4 : IDemoClass;

        /// <summary>
        /// event Documentation will be copied
        /// </summary>
        event System.EventHandler ShapeChanged;

    }
}
#nullable restore

How to use it?

  1. Install the nuget: dotnet add package AutomaticInterface
  2. Create an Attribute with the Name [GenerateAutomaticInterface]. You can just copy the minimal code from this Repo (see the AutomaticInterfaceAttribute project). It's the easiest way to get that attribute because you cannot reference any code from the analyzer package.
  3. Let your class implement the interface, e.g. SomeClass: ISomeClass
  4. Build Solution, the Interface should now be available.

Any errors? Ping me at: [email protected]

Troubleshooting

How can I see the Source code?

Newer Visual Studio Versions (2019+) can see the source code directly:

alt text

Alternatively, the Source Generator generates a log file - look out for a "logs" folder somewhere in bin/debug/... OR your temp folder /logs. The exact location is also reported on Diagnosticlevel Info.

I have an error

Please create an issue and a minimally reproducible test for the problem.

PRs are welcome! Please make sure that you run CSharpier on the code for formatting.

Contributors

  • Thanks to dnf for creating some great extensions. I use them partially in this Generator. Unfortunately due to problems referencing packages I cannot depend on his packages directly.
  • skarllot for PRs
  • Frederik91 for PRs
  • definitelyokay for PRs
  • roflmuffin for PRs
  • mohummedibrahim for code and idea
  • simonmckenzie for PR

Run tests

Should be simply a build and run Tests

Changelog

2.3.0

  • Now supports methods with ref / in / out parameters. Thanks mohummedibrahim
  • Handles default values for true / false correctly. Thanks simonmckenzie!

2.2.1

  • Now supports Interfaces containing new, previously duplicates entries where created
  • Now supports ref parameters
  • Now supports properties with reserved names like @event

2.1.1

  • Fix bug where multiple automatic interfaces caused issues
  • Better support for nullable like Task<string?>, previously only top level generic where considered

2.0.0

  • Major rework to Incremental Source generator
  • Fixed some outstanding bugs
  • Removed logging, b/c not really used
  • Increased coverage

1.6.1

  • Minor bug fixes

1.5.0

  • Add support nullable context

1.4.0

  • Add support for overloaded methods.
  • Add support for optional parameters in method void test(string x = null) should now work.

net_automatic_interface's People

Contributors

christiansauer avatar roflmuffin avatar skarllot avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

net_automatic_interface's Issues

ref properties are not correctly generated as `ref` interface members

Example:

[GenerateAutomaticInterface]
public partial class Test : ITest
{
	private int _test = 5;
	public ref int Value => ref _test;
}

Generated code is

[GeneratedCode("AutomaticInterface", "")]
    public partial interface ITest
    {
        /// <inheritdoc />
        int Value { get; }
        
    }

You will then get an error on member Test.Value: cannot implement 'ITest.Value' because it does not return by value

Nullable types in interface generate a compilation warning.

When AutomaticInterface generates an interface for a class that has nullable types as parameter or return type the compilation warning is produced saying:

_Warning CS8669 : The annotation for nullable reference types should only be used in code within a '#nullable' annotations context. Auto-generated code requires an explicit '#nullable' directive in source.

I think the newest version removed explicit "#nullable enable" directive

Properties with the same name and `new` keyword generate duplicate interface members

Example code:

public abstract class FirstClass
{
	public IntPtr Handle { get; set; }
}

[GenerateAutomaticInterface]
public partial class SecondClass : FirstClass, ISecondClass
{
	public new IntPtr Handle { get; set; }
}

Generated code:

[GeneratedCode("AutomaticInterface", "")]
public partial interface ISecondClass
{
    /// <inheritdoc />
    nint Handle { get; set; }
    
    /// <inheritdoc />
    nint Handle { get; set; }
}

Implement interface inheritance

When two classes inherit from each other, it would be nice if the interfaces did also.

[GenerateAutomaticInterface]
public class Parent : IParent
{
    public void ParentMethod() { }
}
[GenerateAutomaticInterface]
public class Child : Parent, IChild
{
    public void ChildMethod() { }
}

Would generate

public interface IParent
{
    public void ParentMethod();
}
public interface IChild : IParent
{
    public void ChildMethod();
}

Split the Interface Building functionality into separate project/package.

Hi @ChristianSauer, thanks for building this amazing source generator library.

I'm wondering if it would be possible to split the generator project and separate the interface-building functionality on its own? So the other source generators can also benefit from this.

I see there're a couple of issues with having the source generator depend on another library/project (e.g. dotnet/roslyn#47517 (comment))

If that's something you're willing to accept, please let me know, and I can create an initial PR.

Thanks again!

does not handle boolean default values correctly

When annotating a class with a method that has a bool parameter with a default:

namespace test;

public class GenerateAutomaticInterfaceAttribute : Attribute
{
    public GenerateAutomaticInterfaceAttribute(string namespaceName = "") { }
}

[GenerateAutomaticInterface]
public class DemoClass : IDemoClass // Generics, including constraints are allowed, too.
{
    public bool BMethod(string x, bool y=true) // ignored because not public
    {
        return false;
    }
}

The following gets generated:

using System.CodeDom.Compiler;

namespace test
{
    [GeneratedCode("AutomaticInterface", "")]
    public partial interface IDemoClass
    {
        /// <inheritdoc />
        bool BMethod(string x, bool y = False);
        
    }
}

False /True are both capitalized.

Fails if class has any overloaded members

I was getting an error in my build output when trying to use this generator in my project:

CSC : warning CS8785: Generator 'AutomaticInterfaceGenerator' failed to generate source. It will not contribute to the output and compilation errors may occur as a result. Exception was of type 'InvalidOperationException' with message 'Sequence contains more than one matching element'

However I could not reproduce it in your own demo project.

I guessed that this exception method usually comes from Single() or SingleOrDefault(), so searched your code for these and found only three, in GetDocumentationFor(), GetDocumentationForProperty() and GetDocumentationForEvent(). I don't know anything about how source generators are written but it looks like you're trying to compare members to source code tokens based purely upon their names.

I had an idea it might be caused by multiple members of the same name, and modified your example code (DemoClass) to have an overload of AMethod():

        public string AMethod(string x, string y) // included
        {
            return BMethod(x,y);
        }

        public string AMethod(string x, string y, string crash) // included
        {
            return BMethod(x,y);
        }

Indeed the generator now fails with the same error I was getting.

I can see where a fix needs to be made, and had a go, but I've got no experience with source generators and got stuck. I replaced:

.FirstOrDefault(x => x.Identifier.ValueText == method.Name);

with

.SingleOrDefault(x => IsMatch(x, method));

Implemented as:

        private static bool IsMatch(MethodDeclarationSyntax x, IMethodSymbol method)
        {
            if (x.Identifier.ValueText != method.Name)
                return false;

            var LeftParams = x.ParameterList.Parameters.ToArray();
            var RightParams = method.Parameters.ToArray();

            if (LeftParams.Length != RightParams.Length)
                return false;

            for(var i = 0; i < LeftParams.Length; i++)
            {
                var Left = LeftParams[i];
                var Right = RightParams[i];

                log.LogMessage($"IsMatch: {Left.Type.ToString()}=={Right.Type.Name}?");

                if (Left.Type.ToString() != Right.Type.Name)
                    return false;
            }

            return true;
        }

which sort of almost works, except in the log I see "IsMatch: string==String?" which is clearly false. Seems like the right side is getting the type name as the compiler has eventually interpreted it (String?) but the left side is just whatever is literally written in the source code (string).

I have no idea how to reconcile these. For now I just changed SingleOrDefault() into FirstOrDefault() so at least I can use this without errors, but obviously that's not really a fix. I could strip both sides of any non-alphanumeric characters and do a case-insensitive compare which would pass for this particular case, but that seems very hacky. And anyway, would still fail if the overloads had the same number of parameters and the types differered only by namespace not by local name - really need to compare the fully-qualified type name.

I hope this information is of use to you in creating a proper fix.

out keyword in method parameter is missing in interface

This is my class:

[GenerateAutomaticInterface]
public class SomeClass : ISomeClass
{
    public void SomeMethod(out int someOutParameter)
    {
        someOutParameter = 1;
    }
}

This is the generated interface:

[GeneratedCode("AutomaticInterface", "")]
public partial interface ISomeClass
{
    /// <inheritdoc />
    void SomeMethod(int someOutParameter);
    
}

The out keyword is missing in the interface. Because of that, I'm getting a compilation error:
CS0535 'SomeClass' does not implement interface member 'ISomeClass.SomeMethod(int)'

Not playing nice with .razor files in Blazor

I am currently developing a Blazor-Server app where I inject interfaces, but when using them in the razor code it seems to not understand where they are, it just throws a compiler errors thinking that they are two different things, see this example:

@inject IMyThing myThing

<SomeComponent Thing="@myThing" />   <--- Throws error " Argument 1: Cannot convert from MyApp.BL.Provider.IMyThing to IMyThing"

Doesn't generate generic constraints in methods

Current version now works with namespaces. Tried it with my code and here we go again))

Class:

using AutomaticInterfaceAttribute;

namespace TestNuget;

[GenerateAutomaticInterface]
public class GenericTest : IGenericTest
{
public void Test<T>() where T : class { }
}

Result:

using System.CodeDom.Compiler;
using AutomaticInterfaceAttribute;

namespace TestNuget
{
[GeneratedCode("AutomaticInterface", "")]
public partial interface IGenericTest
{
void Test();
}
}


GenericTest.cs(6, 28): [CS0535] 'GenericTest' does not implement interface member 'IGenericTest.Test()'

Will try to find fix in couple days

Optional Parameters

Hi,
I love your work, this is an awesome time saver.
There seems to be an issue when generating a method with optional parameters, the generated code doesn't have the default values. I'm not sure what I'm doing, when I downloaded the code and ran the tests they all failed, so just going to post the code I got working here...
In GeneratorTests.cs I added....

[Fact]
        public async Task WorksWithOptionalParameters()
        {
            var code = @"
using AutomaticInterfaceAttribute;

namespace AutomaticInterfaceExample;

[GenerateAutomaticInterface]
public class DemoClass
{
        public bool TryStartTransaction(
            [CallerFilePath] string file = """",
            [CallerMemberName] string member = """",
            [CallerLineNumber] int line = 0)
        {
return true;
}
}

";

            var 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;

namespace AutomaticInterfaceExample
{
    [GeneratedCode(""AutomaticInterface"", """")]
    public partial interface IDemoClass
    {
    bool TryStartTransaction(string file = """", string member = """", int line = 0);        
    }
}
";
            await RunTestAsync(code, expected);
            Assert.True(true); // silence warnings, real test happens in the RunAsync() method
        }

And in AutomaticInterface.cs in te AddMethodsToInterface method I changed line 163 to

.Select(x => $"{x.ToDisplayString()} {x.Name}{(x.IsOptional ? $" = {(x.ExplicitDefaultValue is string ? $@"""{x.ExplicitDefaultValue ?? ""}""" : x.ExplicitDefaultValue?.ToString() ?? "")}" : "")}")

That worked but I have no idea if that's the right way or if it will break something else., hopefully it's helpful

Add support for `initial` keyword

I'm using the initial keyword in some of my properties to limit the setting of them.
This generator replaces initial with set, which is not the desired behaviour

Code:

public class X {
    public uint Id { get; init; }
}

Generated interface:

[GeneratedCode("AutomaticInterface", "")]
public partial interface IPlayer
{
    /// <inheritdoc />
    uint Id { get; set; }
}

Included in reference list

Hi,

I've included your repository in my personal C# Source Generators list to help gather these kind of projects in one place and hopefully under single umbrella, thus I propose to add the csharp-sourcegenerator topic to this repository to help with visibility.

I hope you're okay with this. If you happen to know other generator projects, I'll be happy to enlist those in my list as well.

Thanks!

Feature Request: Support "internal" as visibility

Generating interfaces is useful for internally used classes too, which are not part of the interface of a compilation unit.
I'd prefer using the same accessibility for the interface as used for the class. Is there any interest in that?

Can not generate interface for two classes in same directory

Thanks for the update! After latest change to 2.0.0 AutomaticInterface can not generate interface for two classes in the same directory with same namespace.
Generation produced error:

CSC: Warning CS8785 : Generator 'AutomaticInterfaceGenerator' failed to generate source. It will not contribute to the output and compilation errors may occur as a result. Exception was of type 'ArgumentException' with message 'The hintName 'ProjectName.cs' of the added source file must be unique within a generator. (Parameter 'hintName')'.

Interfaces generated for classes with `override` or `new` methods contain duplicates

The current code will insert multiple definitions of a method when that method has been overridden or shadowed via new.

For example, given these two classes:

public class BaseClass
{
    public virtual bool AMethod();
}

[GenerateAutomaticInterface]
public class DemoClass : BaseClass
{
    public override bool AMethod() => return true;
}

The generated interface will look like this:

[GeneratedCode("AutomaticInterface", "")]
public partial interface IDemoClass
{
    /// <inheritdoc />
    bool AMethod();
        
    /// <inheritdoc />
    bool AMethod();
}

Way to change namespace of generated class

Hello!

I believe it would be wonderful to have the ability to modify the namespace of the generated interfaces. For instance, this could be achieved through a parameter in attribute: [GenerateAutomaticInterface("New.Important.Namespace")] or at least through csproj configuration.

The reasoning behind implementing this feature is that we frequently desire to maintain abstractions in a distinct library, package, or namespace from the concrete implementation.

Boolean parameters with non-null defaults throw CS0103

Microsoft Visual Studio Enterprise 2022 (64-bit) - Current
Version 17.6.4
AutomaticInterface: 1.6.0

Severity Code Description Project
Error CS0103 The name 'False' does not exist in the current context AutomaticInterface-CS0103

Method with non-null defaults on Boolean parameters throw CS0103 error. Sample method call:

   public async Task<Stream?> GetFinalDocumentsByIDFails(
                         string agreementID, 
                         string docType, 
                         bool amt = false , 
                         bool? attachSupportingDocuments = true, 
                         CancellationToken cancellationToken = default)
        {
            await Task.Delay(100);
            return default(Stream?);

        }

Reason:
The case of the default value is converted to True or False instead of true or false in the GetMethodSignature method.
https://learn.microsoft.com/en-us/dotnet/api/system.boolean?view=net-7.0#converting-to-and-from-boolean-values

Fix:
Add an additional case to the swtich statement:
bool => $" = {Convert.ToString(x.ExplicitDefaultValue, CultureInfo.InvariantCulture).ToLowerInvariant()}",

AutomaticInterface.cs method "GetMethodSignature" with the fix applied:

 private static string GetMethodSignature(IParameterSymbol x)
    {
        if (!x.HasExplicitDefaultValue) return $"{x.Type.ToDisplayString()} {x.Name}";

        string optionalValue = x.ExplicitDefaultValue switch
        {
            string => $" = \"{x.ExplicitDefaultValue}\"",
            bool => $" = {Convert.ToString(x.ExplicitDefaultValue, CultureInfo.InvariantCulture).ToLowerInvariant()}",
            // struct
            null when x.Type.IsValueType => $" = default({x.Type})",
            null => " = null",
            _ => $" = {x.ExplicitDefaultValue}"
        };
        return $"{x.Type.ToDisplayString()} {x.Name}{optionalValue}";
    }

Methods with reserved keyword parameter names do not generate correctly.

The @ symbol is removed from parameter names, thereby causing reserved keywords in the generated interface.

Example code:

[GenerateAutomaticInterface]
public partial class Test : ITest
{
	public void TestMethod(int @event)
	{
		Console.WriteLine("Test");
	}
}

Generated code:

[GeneratedCode("AutomaticInterface", "")]
public partial interface ITest
{
    /// <inheritdoc />
    void TestMethod(int event); // invalid
    
}

Null reference exception with file-scoped namepace

If class with attribute has file-scoped namespace compilation will fail.

CSC: Warning CS8785 : Generator 'AutomaticInterfaceGenerator' failed to generate source. It will not contribute to the output and compilation errors may occur as a result. Exception was of type 'NullReferenceException' with message 'Object reference not set to an instance of an object.'

How to test:

  1. Open TestNuget project
  2. Change TargetFramework to net6.0
  3. Update AutomaticInterface to 1.1.1
  4. Change block-scoped namespace to file-scoped in Test.cs
  5. Compile

Will the repository be maintained?

@ChristianSauer will the repository be maintained anyhow?

I am not sure if I should invest time into preparing PR with fixes. I find this tool very useful, but it could use some improvements and one fix to be possible to be used.

Please leave a note. Thank you

Random crashes

The generator is randomly crashing with the error below:

Generator 'AutomaticInterfaceGenerator' failed to generate source. It will not contribute to the output and compilation errors may occur as a result.
Exception was of type 'ArgumentException' with message 'The hintName '<redacted>.IFileDiscovery.cs' of the added source file must be unique within a generator. (Parameter 'hintName')'.
System.ArgumentException: The hintName '<redacted>.IFileDiscovery.cs' of the added source file must be unique within a generator. (Parameter 'hintName')
at Microsoft.CodeAnalysis.AdditionalSourcesCollection.Add(String hintName, SourceText source) in Z:\BuildAgent\work\3b7ce003563d6f8f\src\Compilers\Core\Portable\SourceGeneration\AdditionalSourcesCollection.cs:line 78
at AutomaticInterface.AutomaticInterfaceGenerator.GenerateCode(SourceProductionContext context, ImmutableArray`1 enumerations)
at Microsoft.CodeAnalysis.UserFunctionExtensions.c__DisplayClass3_0`2. b__0(TInput1 input1, TInput2 input2, CancellationToken token) in Z:\BuildAgent\work\3b7ce003563d6f8f\src\Compilers\Core\Portable\SourceGeneration\UserFunction.cs:line 101

-----

System.ArgumentException: The hintName '<redacted>.IFileDiscovery.cs' of the added source file must be unique within a generator. (Parameter 'hintName')
at Microsoft.CodeAnalysis.AdditionalSourcesCollection.Add(String hintName, SourceText source) in Z:\BuildAgent\work\3b7ce003563d6f8f\src\Compilers\Core\Portable\SourceGeneration\AdditionalSourcesCollection.cs:line 78
at AutomaticInterface.AutomaticInterfaceGenerator.GenerateCode(SourceProductionContext context, ImmutableArray`1 enumerations)
at Microsoft.CodeAnalysis.UserFunctionExtensions.c__DisplayClass3_0`2. b__0(TInput1 input1, TInput2 input2, CancellationToken token) in Z:\BuildAgent\work\3b7ce003563d6f8f\src\Compilers\Core\Portable\SourceGeneration\UserFunction.cs:line 101

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.