Skip to content

Commit

Permalink
Merge pull request #28 from codecentric/issue-26
Browse files Browse the repository at this point in the history
Fix Issue 26
  • Loading branch information
ChristianSauer committed Jan 31, 2024
2 parents bb6eec4 + 23f0cc0 commit de3e4d5
Show file tree
Hide file tree
Showing 6 changed files with 294 additions and 10 deletions.
1 change: 1 addition & 0 deletions AutomaticInterface/AutomaticInterface.sln
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
ProjectSection(SolutionItems) = preProject
Directory.Build.targets = Directory.Build.targets
..\.github\workflows\action.yml = ..\.github\workflows\action.yml
..\README.md = ..\README.md
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestNuget", "TestNuget\TestNuget.csproj", "{E7CF6623-54D8-4A40-A1B1-CE078551EFE1}"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<EnableNETAnalyzers>True</EnableNETAnalyzers>
<AnalysisLevel>latest-Recommended</AnalysisLevel>
<Version>2.1.0</Version>
<Version>2.1.1</Version>
<PackageReadmeFile>README.md</PackageReadmeFile>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<NoWarn>1701;1702;NU5128</NoWarn>
Expand Down
64 changes: 55 additions & 9 deletions AutomaticInterface/AutomaticInterface/Builder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -84,22 +84,15 @@ private static void AddMethod(InterfaceBuilder codeGenerator, IMethodSymbol meth
var returnType = method.ReturnType;
var name = method.Name;

var hasNullableParameter = method.Parameters.Any(x =>
x.NullableAnnotation == NullableAnnotation.Annotated
);
var hasNullableReturn =
method.ReturnType.NullableAnnotation == NullableAnnotation.Annotated;
if (hasNullableParameter || hasNullableReturn)
{
codeGenerator.HasNullable = true;
}
ActivateNullableIfNeeded(codeGenerator, method);

var paramResult = new HashSet<string>();
method.Parameters.Select(GetMethodSignature).ToList().ForEach(x => paramResult.Add(x));

var typedArgs = method
.TypeParameters.Select(arg => (arg.ToDisplayString(), arg.GetWhereStatement()))
.ToList();

codeGenerator.AddMethodToInterface(
name,
returnType.ToDisplayString(),
Expand All @@ -109,6 +102,55 @@ private static void AddMethod(InterfaceBuilder codeGenerator, IMethodSymbol meth
);
}

private static void ActivateNullableIfNeeded(
InterfaceBuilder codeGenerator,
ITypeSymbol typeSymbol
)
{
if (IsNullable(typeSymbol))
{
codeGenerator.HasNullable = true;
}
}

private static void ActivateNullableIfNeeded(
InterfaceBuilder codeGenerator,
IMethodSymbol method
)
{
var hasNullableParameter = method.Parameters.Any(x => IsNullable(x.Type));

var hasNullableReturn = IsNullable(method.ReturnType);

if (hasNullableParameter || hasNullableReturn)
{
codeGenerator.HasNullable = true;
}
}

private static bool IsNullable(ITypeSymbol typeSymbol)
{
if (typeSymbol.NullableAnnotation == NullableAnnotation.Annotated)
{
return true;
}

if (typeSymbol is not INamedTypeSymbol named)
{
return false;
}

foreach (var param in named.TypeArguments)
{
if (IsNullable(param))
{
return true;
}
}

return false;
}

private static void AddEventsToInterface(
ITypeSymbol classSymbol,
InterfaceBuilder codeGenerator
Expand All @@ -126,6 +168,8 @@ InterfaceBuilder codeGenerator
var type = evt.Type;
var name = evt.Name;
ActivateNullableIfNeeded(codeGenerator, type);
codeGenerator.AddEventToInterface(name, type.ToDisplayString(), InheritDoc);
});
}
Expand Down Expand Up @@ -169,6 +213,8 @@ InterfaceBuilder interfaceGenerator
var hasGet = prop.GetMethod?.DeclaredAccessibility == Accessibility.Public;
var hasSet = prop.SetMethod?.DeclaredAccessibility == Accessibility.Public;
ActivateNullableIfNeeded(interfaceGenerator, type);
interfaceGenerator.AddPropertyToInterface(
name,
type.ToDisplayString(),
Expand Down
8 changes: 8 additions & 0 deletions AutomaticInterface/AutomaticInterfaceExample/DemoClass.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Threading.Tasks;
using AutomaticInterfaceAttribute;

namespace AutomaticInterfaceExample
Expand Down Expand Up @@ -45,6 +46,11 @@ public class DemoClass : IDemoClass // Generics, including constraints are allow
return "Ok";
}

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

Check warning on line 51 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

public static string StaticMethod() // static method, ignored
Expand All @@ -67,6 +73,8 @@ public class DemoClass : IDemoClass // Generics, including constraints are allow

public event EventHandler? ShapeChangedNullable; // included

Check warning on line 74 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 76 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 76 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];

public int this[int index] // currently ignored
Expand Down
220 changes: 220 additions & 0 deletions AutomaticInterface/Tests/GeneratorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1648,6 +1648,65 @@ public partial interface IDemoClass
GenerateCode(code).Should().Be(expected);
}

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

using AutomaticInterfaceAttribute;
using System;

namespace AutomaticInterfaceExample
{
/// <summary>
/// Bla bla
/// </summary>
[GenerateAutomaticInterface]
class DemoClass
{

/// <summary>
/// Bla bla
/// </summary>
public event EventHandler<string?> ShapeChangedNullable;
}
}

""";

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>
//--------------------------------------------------------------------------------------------------

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

namespace AutomaticInterfaceExample
{
/// <summary>
/// Bla bla
/// </summary>
[GeneratedCode("AutomaticInterface", "")]
public partial interface IDemoClass
{
/// <inheritdoc />
event System.EventHandler<string?> ShapeChangedNullable;

}
}
#nullable restore

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

[Fact]
public void NullableProperty()
{
Expand Down Expand Up @@ -1705,6 +1764,67 @@ public partial interface IDemoClass
GenerateCode(code).Should().Be(expected);
}

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

using AutomaticInterfaceAttribute;
using System;
using System.Threading.Tasks;

namespace AutomaticInterfaceExample
{
/// <summary>
/// Bla bla
/// </summary>
[GenerateAutomaticInterface]
class DemoClass
{

/// <summary>
/// Bla bla
/// </summary>
public Task<string?> NullableProperty { get; set; }
}
}

""";

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>
//--------------------------------------------------------------------------------------------------

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

namespace AutomaticInterfaceExample
{
/// <summary>
/// Bla bla
/// </summary>
[GeneratedCode("AutomaticInterface", "")]
public partial interface IDemoClass
{
/// <inheritdoc />
System.Threading.Tasks.Task<string?> NullableProperty { get; set; }

}
}
#nullable restore

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

[Fact]
public void BooleanWithNonNull()
{
Expand Down Expand Up @@ -1842,4 +1962,104 @@ public partial interface IDemoClass
""";
GenerateCode(code).Should().Be(expected);
}

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

using AutomaticInterfaceAttribute;
using System.Threading.Tasks;

namespace AutomaticInterfaceExample;
[GenerateAutomaticInterface]
public class DemoClass
{
public Task<string?> AMethodAsync(DemoClass x, string y)
{
return "Ok";
}
}

""";

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>
//--------------------------------------------------------------------------------------------------

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

namespace AutomaticInterfaceExample
{
[GeneratedCode("AutomaticInterface", "")]
public partial interface IDemoClass
{
/// <inheritdoc />
System.Threading.Tasks.Task<string?> AMethodAsync(AutomaticInterfaceExample.DemoClass x, string y);

}
}
#nullable restore

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

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

using AutomaticInterfaceAttribute;
using System.Threading.Tasks;

namespace AutomaticInterfaceExample;
[GenerateAutomaticInterface]
public class DemoClass
{
public string AMethod(Task<DemoClass?> x, string y)
{
return "Ok";
}
}

""";

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>
//--------------------------------------------------------------------------------------------------

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

namespace AutomaticInterfaceExample
{
[GeneratedCode("AutomaticInterface", "")]
public partial interface IDemoClass
{
/// <inheritdoc />
string AMethod(System.Threading.Tasks.Task<AutomaticInterfaceExample.DemoClass?> x, string y);

}
}
#nullable restore

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

0 comments on commit de3e4d5

Please sign in to comment.