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

Fix Issue 26 #28

Merged
merged 3 commits into from
Jan 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 All @@ -17,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 @@ -45,6 +46,11 @@
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 @@ -56,17 +62,19 @@
/// event Documentation will be copied
/// </summary>
#pragma warning disable S3264 // Events should be invoked
public event EventHandler ShapeChanged; // included

Check warning on line 65 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 65 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 70 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.

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

View workflow job for this annotation

GitHub Actions / test

The event 'DemoClass.ShapeChanged2' is never used
#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 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
Loading