diff --git a/AutomaticInterface/AutomaticInterface/AutomaticInterface.csproj b/AutomaticInterface/AutomaticInterface/AutomaticInterface.csproj index 81eadf2..ef06b80 100644 --- a/AutomaticInterface/AutomaticInterface/AutomaticInterface.csproj +++ b/AutomaticInterface/AutomaticInterface/AutomaticInterface.csproj @@ -25,7 +25,7 @@ MIT True latest-Recommended - 4.0.0 + 4.1.0 README.md true 1701;1702;NU5128 diff --git a/AutomaticInterface/AutomaticInterface/Builder.cs b/AutomaticInterface/AutomaticInterface/Builder.cs index 8e6e1c0..b97e564 100644 --- a/AutomaticInterface/AutomaticInterface/Builder.cs +++ b/AutomaticInterface/AutomaticInterface/Builder.cs @@ -265,7 +265,7 @@ InterfaceBuilder interfaceGenerator var name = prop.Name; var hasGet = prop.GetMethod?.DeclaredAccessibility == Accessibility.Public; - var hasSet = prop.SetMethod?.DeclaredAccessibility == Accessibility.Public; + var hasSet = GetSetKind(prop.SetMethod); var isRef = prop.ReturnsByRef; ActivateNullableIfNeeded(interfaceGenerator, type); @@ -281,6 +281,20 @@ InterfaceBuilder interfaceGenerator }); } + private static PropertySetKind GetSetKind(IMethodSymbol? setMethodSymbol) + { + return setMethodSymbol switch + { + null => PropertySetKind.NoSet, + { IsInitOnly: true, DeclaredAccessibility: Accessibility.Public } + => PropertySetKind.Init, + _ + => setMethodSymbol is { DeclaredAccessibility: Accessibility.Public } + ? PropertySetKind.Always + : PropertySetKind.NoSet + }; + } + private static bool HasIgnoreAttribute(ISymbol x) { return x.GetAttributes() diff --git a/AutomaticInterface/AutomaticInterface/InterfaceBuilder.cs b/AutomaticInterface/AutomaticInterface/InterfaceBuilder.cs index c7d9660..26a3cb6 100644 --- a/AutomaticInterface/AutomaticInterface/InterfaceBuilder.cs +++ b/AutomaticInterface/AutomaticInterface/InterfaceBuilder.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; using System.Text; @@ -8,7 +9,7 @@ internal sealed record PropertyInfo( string Name, string Ttype, bool HasGet, - bool HasSet, + PropertySetKind SetKind, bool IsRef, string Documentation ); @@ -50,7 +51,7 @@ public void AddPropertyToInterface( string name, string ttype, bool hasGet, - bool hasSet, + PropertySetKind hasSet, bool isRef, string documentation ) @@ -126,7 +127,7 @@ public string Build() cb.AppendAndNormalizeMultipleLines(prop.Documentation); var @ref = prop.IsRef ? "ref " : string.Empty; var get = prop.HasGet ? "get; " : string.Empty; - var set = prop.HasSet ? "set; " : string.Empty; + var set = GetSet(prop.SetKind); cb.AppendLine($"{@ref}{prop.Ttype} {prop.Name} {{ {get}{set}}}"); cb.AppendLine(""); } @@ -159,6 +160,17 @@ public string Build() return cb.Build(); } + private static string GetSet(PropertySetKind propSetKind) + { + return propSetKind switch + { + PropertySetKind.NoSet => string.Empty, + PropertySetKind.Always => "set; ", + PropertySetKind.Init => "init; ", + _ => throw new ArgumentOutOfRangeException(nameof(propSetKind), propSetKind, null) + }; + } + private static void BuildMethod(CodeBuilder cb, MethodInfo method) { cb.AppendAndNormalizeMultipleLines(method.Documentation); @@ -191,6 +203,13 @@ public override string ToString() } } + public enum PropertySetKind + { + NoSet = 0, + Always = 1, + Init = 2, + } + public class CodeBuilder { private readonly StringBuilder sb = new(); diff --git a/AutomaticInterface/Tests/GeneratorTests.Properties.cs b/AutomaticInterface/Tests/GeneratorTests.Properties.cs new file mode 100644 index 0000000..45bfc92 --- /dev/null +++ b/AutomaticInterface/Tests/GeneratorTests.Properties.cs @@ -0,0 +1,559 @@ +using FluentAssertions; +using Xunit; + +namespace Tests; + +public partial class GeneratorTests +{ + [Fact] + public void OmitsPrivateSetPropertyInterface() + { + const string code = """ + + using AutomaticInterface; + + namespace AutomaticInterfaceExample + { + + [GenerateAutomaticInterface] + class DemoClass + { + /// + public string Hello { get; private set; } + } + } + + """; + + const string expected = """ + //-------------------------------------------------------------------------------------------------- + // + // This code was generated by a tool. + // + // Changes to this file may cause incorrect behavior and will be lost if the code is regenerated. + // + //-------------------------------------------------------------------------------------------------- + + using System.CodeDom.Compiler; + using AutomaticInterface; + + namespace AutomaticInterfaceExample + { + [GeneratedCode("AutomaticInterface", "")] + public partial interface IDemoClass + { + /// + string Hello { get; } + + } + } + + """; + GenerateCode(code).Should().Be(expected); + } + + [Fact] + public void CopiesDocumentationOfPropertyToInterface() + { + const string code = """ + + using AutomaticInterface; + + namespace AutomaticInterfaceExample + { + + [GenerateAutomaticInterface] + class DemoClass + { + /// + /// Bla bla + /// + public string Hello { get; private set; } + } + } + + """; + + const string expected = """ + //-------------------------------------------------------------------------------------------------- + // + // This code was generated by a tool. + // + // Changes to this file may cause incorrect behavior and will be lost if the code is regenerated. + // + //-------------------------------------------------------------------------------------------------- + + using System.CodeDom.Compiler; + using AutomaticInterface; + + namespace AutomaticInterfaceExample + { + [GeneratedCode("AutomaticInterface", "")] + public partial interface IDemoClass + { + /// + string Hello { get; } + + } + } + + """; + GenerateCode(code).Should().Be(expected); + } + + [Fact] + public void NullableProperty() + { + const string code = """ + + using AutomaticInterface; + using System; + + namespace AutomaticInterfaceExample + { + /// + /// Bla bla + /// + [GenerateAutomaticInterface] + class DemoClass + { + + /// + /// Bla bla + /// + public string? NullableProperty { get; set; } + } + } + + """; + + const string expected = """ + //-------------------------------------------------------------------------------------------------- + // + // This code was generated by a tool. + // + // Changes to this file may cause incorrect behavior and will be lost if the code is regenerated. + // + //-------------------------------------------------------------------------------------------------- + + #nullable enable + using System.CodeDom.Compiler; + using AutomaticInterface; + using System; + + namespace AutomaticInterfaceExample + { + /// + /// Bla bla + /// + [GeneratedCode("AutomaticInterface", "")] + public partial interface IDemoClass + { + /// + string? NullableProperty { get; set; } + + } + } + #nullable restore + + """; + GenerateCode(code).Should().Be(expected); + } + + [Fact] + public void NullableProperty2() + { + const string code = """ + + using AutomaticInterface; + using System; + using System.Threading.Tasks; + + namespace AutomaticInterfaceExample + { + /// + /// Bla bla + /// + [GenerateAutomaticInterface] + class DemoClass + { + + /// + /// Bla bla + /// + public Task NullableProperty { get; set; } + } + } + + """; + + const string expected = """ + //-------------------------------------------------------------------------------------------------- + // + // This code was generated by a tool. + // + // Changes to this file may cause incorrect behavior and will be lost if the code is regenerated. + // + //-------------------------------------------------------------------------------------------------- + + #nullable enable + using System.CodeDom.Compiler; + using AutomaticInterface; + using System; + using System.Threading.Tasks; + + namespace AutomaticInterfaceExample + { + /// + /// Bla bla + /// + [GeneratedCode("AutomaticInterface", "")] + public partial interface IDemoClass + { + /// + System.Threading.Tasks.Task NullableProperty { get; set; } + + } + } + #nullable restore + + """; + GenerateCode(code).Should().Be(expected); + } + + [Fact] + public void WorksWithNewKeyword() + { + const string code = """ + + using AutomaticInterface; + using System.Threading.Tasks; + + namespace AutomaticInterfaceExample; + + public abstract class FirstClass + { + public int AProperty { get; set; } + } + + [GenerateAutomaticInterface] + public partial class SecondClass : FirstClass + { + public new int AProperty { get; set; } + } + + """; + + const string expected = """ + //-------------------------------------------------------------------------------------------------- + // + // This code was generated by a tool. + // + // Changes to this file may cause incorrect behavior and will be lost if the code is regenerated. + // + //-------------------------------------------------------------------------------------------------- + + using System.CodeDom.Compiler; + using AutomaticInterface; + using System.Threading.Tasks; + + namespace AutomaticInterfaceExample + { + [GeneratedCode("AutomaticInterface", "")] + public partial interface ISecondClass + { + /// + int AProperty { get; set; } + + } + } + + """; + GenerateCode(code).Should().Be(expected); + } + + [Fact] + public void WorksWithPropertyShadowing() + { + const string code = """ + using AutomaticInterface; + using System; + + namespace AutomaticInterfaceExample; + + public class BaseClass + { + public string SomeProperty { get; set; } + } + + [GenerateAutomaticInterface] + public class DemoClass : BaseClass + { + public new string SomeProperty { get; set; } + } + """; + + const string expected = """ + //-------------------------------------------------------------------------------------------------- + // + // This code was generated by a tool. + // + // Changes to this file may cause incorrect behavior and will be lost if the code is regenerated. + // + //-------------------------------------------------------------------------------------------------- + + using System.CodeDom.Compiler; + using AutomaticInterface; + using System; + + namespace AutomaticInterfaceExample + { + [GeneratedCode("AutomaticInterface", "")] + public partial interface IDemoClass + { + /// + string SomeProperty { get; set; } + + } + } + + """; + GenerateCode(code).Should().Be(expected); + } + + [Fact] + public void WorksWithPropertyOverrides() + { + const string code = """ + using AutomaticInterface; + using System; + + namespace AutomaticInterfaceExample; + + public class BaseClass + { + public virtual string SomeProperty { get; set; } + } + + [GenerateAutomaticInterface] + public class DemoClass : BaseClass + { + public override string SomeProperty { get; set; } + } + + """; + + const string expected = """ + //-------------------------------------------------------------------------------------------------- + // + // This code was generated by a tool. + // + // Changes to this file may cause incorrect behavior and will be lost if the code is regenerated. + // + //-------------------------------------------------------------------------------------------------- + + using System.CodeDom.Compiler; + using AutomaticInterface; + using System; + + namespace AutomaticInterfaceExample + { + [GeneratedCode("AutomaticInterface", "")] + public partial interface IDemoClass + { + /// + string SomeProperty { get; set; } + + } + } + + """; + GenerateCode(code).Should().Be(expected); + } + + [Fact] + public void GeneratesStringPropertyInterface() + { + const string code = """ + + using AutomaticInterface; + + namespace AutomaticInterfaceExample + { + + [GenerateAutomaticInterface] + class DemoClass + { + public string Hello { get; set; } + } + } + + """; + + const string expected = """ + //-------------------------------------------------------------------------------------------------- + // + // This code was generated by a tool. + // + // Changes to this file may cause incorrect behavior and will be lost if the code is regenerated. + // + //-------------------------------------------------------------------------------------------------- + + using System.CodeDom.Compiler; + using AutomaticInterface; + + namespace AutomaticInterfaceExample + { + [GeneratedCode("AutomaticInterface", "")] + public partial interface IDemoClass + { + /// + string Hello { get; set; } + + } + } + + """; + GenerateCode(code).Should().Be(expected); + } + + [Fact] + public void GeneratesStringPropertySetOnlyInterface() + { + const string code = """ + + using AutomaticInterface; + + namespace AutomaticInterfaceExample + { + + [GenerateAutomaticInterface] + class DemoClass + { + private string x; + public string Hello { set => x = value; } + } + } + + """; + + const string expected = """ + //-------------------------------------------------------------------------------------------------- + // + // This code was generated by a tool. + // + // Changes to this file may cause incorrect behavior and will be lost if the code is regenerated. + // + //-------------------------------------------------------------------------------------------------- + + using System.CodeDom.Compiler; + using AutomaticInterface; + + namespace AutomaticInterfaceExample + { + [GeneratedCode("AutomaticInterface", "")] + public partial interface IDemoClass + { + /// + string Hello { set; } + + } + } + + """; + GenerateCode(code).Should().Be(expected); + } + + [Fact] + public void GeneratesStringPropertyGetOnlyInterface() + { + const string code = """ + + using AutomaticInterface; + + namespace AutomaticInterfaceExample + { + + [GenerateAutomaticInterface] + class DemoClass + { + private string x; + public string Hello { get; } + } + } + + """; + + const string expected = """ + //-------------------------------------------------------------------------------------------------- + // + // This code was generated by a tool. + // + // Changes to this file may cause incorrect behavior and will be lost if the code is regenerated. + // + //-------------------------------------------------------------------------------------------------- + + using System.CodeDom.Compiler; + using AutomaticInterface; + + namespace AutomaticInterfaceExample + { + [GeneratedCode("AutomaticInterface", "")] + public partial interface IDemoClass + { + /// + string Hello { get; } + + } + } + + """; + GenerateCode(code).Should().Be(expected); + } + + [Fact] + public void GeneratesInitPropertyInterface() + { + const string code = """ + + using AutomaticInterface; + + namespace AutomaticInterfaceExample + { + + [GenerateAutomaticInterface] + class DemoClass + { + public string Hello { get; init; } + } + } + + """; + + const string expected = """ + //-------------------------------------------------------------------------------------------------- + // + // This code was generated by a tool. + // + // Changes to this file may cause incorrect behavior and will be lost if the code is regenerated. + // + //-------------------------------------------------------------------------------------------------- + + using System.CodeDom.Compiler; + using AutomaticInterface; + + namespace AutomaticInterfaceExample + { + [GeneratedCode("AutomaticInterface", "")] + public partial interface IDemoClass + { + /// + string Hello { get; init; } + + } + } + + """; + GenerateCode(code).Should().Be(expected); + } +} diff --git a/AutomaticInterface/Tests/GeneratorTests.cs b/AutomaticInterface/Tests/GeneratorTests.cs index 07b0174..b548d97 100644 --- a/AutomaticInterface/Tests/GeneratorTests.cs +++ b/AutomaticInterface/Tests/GeneratorTests.cs @@ -257,146 +257,6 @@ public partial interface IDemoClass GenerateCode(code).Should().Be(expected); } - [Fact] - public void GeneratesStringPropertyInterface() - { - const string code = """ - - using AutomaticInterface; - - namespace AutomaticInterfaceExample - { - - [GenerateAutomaticInterface] - class DemoClass - { - public string Hello { get; set; } - } - } - - """; - - const string expected = """ - //-------------------------------------------------------------------------------------------------- - // - // This code was generated by a tool. - // - // Changes to this file may cause incorrect behavior and will be lost if the code is regenerated. - // - //-------------------------------------------------------------------------------------------------- - - using System.CodeDom.Compiler; - using AutomaticInterface; - - namespace AutomaticInterfaceExample - { - [GeneratedCode("AutomaticInterface", "")] - public partial interface IDemoClass - { - /// - string Hello { get; set; } - - } - } - - """; - GenerateCode(code).Should().Be(expected); - } - - [Fact] - public void GeneratesStringPropertySetOnlyInterface() - { - const string code = """ - - using AutomaticInterface; - - namespace AutomaticInterfaceExample - { - - [GenerateAutomaticInterface] - class DemoClass - { - private string x; - public string Hello { set => x = value; } - } - } - - """; - - const string expected = """ - //-------------------------------------------------------------------------------------------------- - // - // This code was generated by a tool. - // - // Changes to this file may cause incorrect behavior and will be lost if the code is regenerated. - // - //-------------------------------------------------------------------------------------------------- - - using System.CodeDom.Compiler; - using AutomaticInterface; - - namespace AutomaticInterfaceExample - { - [GeneratedCode("AutomaticInterface", "")] - public partial interface IDemoClass - { - /// - string Hello { set; } - - } - } - - """; - GenerateCode(code).Should().Be(expected); - } - - [Fact] - public void GeneratesStringPropertyGetOnlyInterface() - { - const string code = """ - - using AutomaticInterface; - - namespace AutomaticInterfaceExample - { - - [GenerateAutomaticInterface] - class DemoClass - { - private string x; - public string Hello { get; } - } - } - - """; - - const string expected = """ - //-------------------------------------------------------------------------------------------------- - // - // This code was generated by a tool. - // - // Changes to this file may cause incorrect behavior and will be lost if the code is regenerated. - // - //-------------------------------------------------------------------------------------------------- - - using System.CodeDom.Compiler; - using AutomaticInterface; - - namespace AutomaticInterfaceExample - { - [GeneratedCode("AutomaticInterface", "")] - public partial interface IDemoClass - { - /// - string Hello { get; } - - } - } - - """; - GenerateCode(code).Should().Be(expected); - } - [Fact] public void AddsUsingsToInterface() { @@ -891,102 +751,6 @@ public partial interface IDemoClass GenerateCode(code).Should().Be(expected); } - [Fact] - public void OmitsPrivateSetPropertyInterface() - { - const string code = """ - - using AutomaticInterface; - - namespace AutomaticInterfaceExample - { - - [GenerateAutomaticInterface] - class DemoClass - { - /// - public string Hello { get; private set; } - } - } - - """; - - const string expected = """ - //-------------------------------------------------------------------------------------------------- - // - // This code was generated by a tool. - // - // Changes to this file may cause incorrect behavior and will be lost if the code is regenerated. - // - //-------------------------------------------------------------------------------------------------- - - using System.CodeDom.Compiler; - using AutomaticInterface; - - namespace AutomaticInterfaceExample - { - [GeneratedCode("AutomaticInterface", "")] - public partial interface IDemoClass - { - /// - string Hello { get; } - - } - } - - """; - GenerateCode(code).Should().Be(expected); - } - - [Fact] - public void CopiesDocumentationOfPropertyToInterface() - { - const string code = """ - - using AutomaticInterface; - - namespace AutomaticInterfaceExample - { - - [GenerateAutomaticInterface] - class DemoClass - { - /// - /// Bla bla - /// - public string Hello { get; private set; } - } - } - - """; - - const string expected = """ - //-------------------------------------------------------------------------------------------------- - // - // This code was generated by a tool. - // - // Changes to this file may cause incorrect behavior and will be lost if the code is regenerated. - // - //-------------------------------------------------------------------------------------------------- - - using System.CodeDom.Compiler; - using AutomaticInterface; - - namespace AutomaticInterfaceExample - { - [GeneratedCode("AutomaticInterface", "")] - public partial interface IDemoClass - { - /// - string Hello { get; } - - } - } - - """; - GenerateCode(code).Should().Be(expected); - } - [Fact] public void CopiesDocumentationOfClassToInterface() { @@ -1783,126 +1547,6 @@ public partial interface IDemoClass GenerateCode(code).Should().Be(expected); } - [Fact] - public void NullableProperty() - { - const string code = """ - - using AutomaticInterface; - using System; - - namespace AutomaticInterfaceExample - { - /// - /// Bla bla - /// - [GenerateAutomaticInterface] - class DemoClass - { - - /// - /// Bla bla - /// - public string? NullableProperty { get; set; } - } - } - - """; - - const string expected = """ - //-------------------------------------------------------------------------------------------------- - // - // This code was generated by a tool. - // - // Changes to this file may cause incorrect behavior and will be lost if the code is regenerated. - // - //-------------------------------------------------------------------------------------------------- - - #nullable enable - using System.CodeDom.Compiler; - using AutomaticInterface; - using System; - - namespace AutomaticInterfaceExample - { - /// - /// Bla bla - /// - [GeneratedCode("AutomaticInterface", "")] - public partial interface IDemoClass - { - /// - string? NullableProperty { get; set; } - - } - } - #nullable restore - - """; - GenerateCode(code).Should().Be(expected); - } - - [Fact] - public void NullableProperty2() - { - const string code = """ - - using AutomaticInterface; - using System; - using System.Threading.Tasks; - - namespace AutomaticInterfaceExample - { - /// - /// Bla bla - /// - [GenerateAutomaticInterface] - class DemoClass - { - - /// - /// Bla bla - /// - public Task NullableProperty { get; set; } - } - } - - """; - - const string expected = """ - //-------------------------------------------------------------------------------------------------- - // - // This code was generated by a tool. - // - // Changes to this file may cause incorrect behavior and will be lost if the code is regenerated. - // - //-------------------------------------------------------------------------------------------------- - - #nullable enable - using System.CodeDom.Compiler; - using AutomaticInterface; - using System; - using System.Threading.Tasks; - - namespace AutomaticInterfaceExample - { - /// - /// Bla bla - /// - [GeneratedCode("AutomaticInterface", "")] - public partial interface IDemoClass - { - /// - System.Threading.Tasks.Task NullableProperty { get; set; } - - } - } - #nullable restore - - """; - GenerateCode(code).Should().Be(expected); - } - [Fact] public void BooleanWithNonNull() { @@ -2240,57 +1884,6 @@ public partial interface IDemoClass GenerateCode(code).Should().Be(expected); } - [Fact] - public void WorksWithNewKeyword() - { - const string code = """ - - using AutomaticInterface; - using System.Threading.Tasks; - - namespace AutomaticInterfaceExample; - - public abstract class FirstClass - { - public int AProperty { get; set; } - } - - [GenerateAutomaticInterface] - public partial class SecondClass : FirstClass - { - public new int AProperty { get; set; } - } - - """; - - const string expected = """ - //-------------------------------------------------------------------------------------------------- - // - // This code was generated by a tool. - // - // Changes to this file may cause incorrect behavior and will be lost if the code is regenerated. - // - //-------------------------------------------------------------------------------------------------- - - using System.CodeDom.Compiler; - using AutomaticInterface; - using System.Threading.Tasks; - - namespace AutomaticInterfaceExample - { - [GeneratedCode("AutomaticInterface", "")] - public partial interface ISecondClass - { - /// - int AProperty { get; set; } - - } - } - - """; - GenerateCode(code).Should().Be(expected); - } - [Fact] public void WorksWithMethodOverrides() { @@ -2489,105 +2082,6 @@ public partial interface IDemoClass GenerateCode(code).Should().Be(expected); } - [Fact] - public void WorksWithPropertyShadowing() - { - const string code = """ - using AutomaticInterface; - using System; - - namespace AutomaticInterfaceExample; - - public class BaseClass - { - public string SomeProperty { get; set; } - } - - [GenerateAutomaticInterface] - public class DemoClass : BaseClass - { - public new string SomeProperty { get; set; } - } - """; - - const string expected = """ - //-------------------------------------------------------------------------------------------------- - // - // This code was generated by a tool. - // - // Changes to this file may cause incorrect behavior and will be lost if the code is regenerated. - // - //-------------------------------------------------------------------------------------------------- - - using System.CodeDom.Compiler; - using AutomaticInterface; - using System; - - namespace AutomaticInterfaceExample - { - [GeneratedCode("AutomaticInterface", "")] - public partial interface IDemoClass - { - /// - string SomeProperty { get; set; } - - } - } - - """; - GenerateCode(code).Should().Be(expected); - } - - [Fact] - public void WorksWithPropertyOverrides() - { - const string code = """ - using AutomaticInterface; - using System; - - namespace AutomaticInterfaceExample; - - public class BaseClass - { - public virtual string SomeProperty { get; set; } - } - - [GenerateAutomaticInterface] - public class DemoClass : BaseClass - { - public override string SomeProperty { get; set; } - } - - """; - - const string expected = """ - //-------------------------------------------------------------------------------------------------- - // - // This code was generated by a tool. - // - // Changes to this file may cause incorrect behavior and will be lost if the code is regenerated. - // - //-------------------------------------------------------------------------------------------------- - - using System.CodeDom.Compiler; - using AutomaticInterface; - using System; - - namespace AutomaticInterfaceExample - { - [GeneratedCode("AutomaticInterface", "")] - public partial interface IDemoClass - { - /// - string SomeProperty { get; set; } - - } - } - - """; - GenerateCode(code).Should().Be(expected); - } - [Fact] public void WorksWithEventShadowing() { diff --git a/README.md b/README.md index 9892b31..c78106c 100644 --- a/README.md +++ b/README.md @@ -179,6 +179,10 @@ Should be simply a build and run Tests ## Changelog +### 4.1.0 + +- Adds ability to use `init` in property setters +- ### 4.0.0 - Breaking change in how generated code qualifies parameters, e.g. `async Task` should no longer generated as `System.Threading.Task`. I hope this does not break things