Do not redefine interfaces. (#67)
* Do not redefine interfaces. * Improve code style, readability and performance after initial code review * Implemented unit tests for the interface inheritance * Cleaned up unit tests. * Completely remove output helper.
This commit is contained in:
@@ -0,0 +1,164 @@
|
||||
using CSharp.SourceGenerators.Extensions;
|
||||
using CSharp.SourceGenerators.Extensions.Models;
|
||||
using FluentAssertions;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using ProxyInterfaceSourceGenerator;
|
||||
using ProxyInterfaceSourceGeneratorTests.Source.Disposable;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace ProxyInterfaceSourceGeneratorTests;
|
||||
|
||||
public class InheritedInterfaceTests
|
||||
{
|
||||
private const string Namespace = "ProxyInterfaceSourceGeneratorTests.Source.Disposable";
|
||||
private const string OutputPath = "../../../Destination/Disposable/";
|
||||
private readonly ProxyInterfaceCodeGenerator _sut;
|
||||
|
||||
public InheritedInterfaceTests()
|
||||
{
|
||||
if (!Directory.Exists(OutputPath))
|
||||
{
|
||||
Directory.CreateDirectory(OutputPath);
|
||||
}
|
||||
_sut = new ProxyInterfaceCodeGenerator();
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(false, false)]
|
||||
[InlineData(true, true)]
|
||||
public void GenerateFiles_InheritedInterface_InheritFromBaseClass(bool proxyBaseClass, bool inheritBaseInterface)
|
||||
{
|
||||
var name = "Child";
|
||||
var interfaceName = "I" + name;
|
||||
var proxyName = name + "Proxy";
|
||||
|
||||
// Arrange
|
||||
string[] fileNames = [
|
||||
$"{Namespace}.{interfaceName}.g.cs",
|
||||
$"{Namespace}.{proxyName}.g.cs"
|
||||
];
|
||||
var path = $"./Source/Disposable/{interfaceName}.cs";
|
||||
SourceFile sourceFile = CreateSourceFile(path, name, proxyBaseClass);
|
||||
|
||||
// Act
|
||||
var result = _sut.Execute([sourceFile]);
|
||||
|
||||
result.Valid.Should().BeTrue();
|
||||
result.Files.Should().HaveCount(fileNames.Length + 1);
|
||||
WriteFiles(fileNames, result);
|
||||
|
||||
var interfaceIndex = 1;
|
||||
var tree = result.Files[interfaceIndex].SyntaxTree;
|
||||
var root = tree.GetRoot();
|
||||
var interfaceDeclarations = root.DescendantNodes().OfType<InterfaceDeclarationSyntax>();
|
||||
|
||||
// Assert
|
||||
Assert.Single(interfaceDeclarations);
|
||||
var baseList = interfaceDeclarations.First().BaseList;
|
||||
bool didWeInherit = baseList is not null;
|
||||
Assert.Equal(didWeInherit, inheritBaseInterface);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("Parent")]
|
||||
[InlineData("Child")]
|
||||
public void GenerateFiles_InheritedInterface_Should_InheritTheInterface(string name)
|
||||
{
|
||||
var interfaceName = "I" + name;
|
||||
var proxyName = name + "Proxy";
|
||||
|
||||
// Arrange
|
||||
string[] fileNames = [
|
||||
$"{Namespace}.{interfaceName}.g.cs",
|
||||
$"{Namespace}.{proxyName}.g.cs"
|
||||
];
|
||||
|
||||
var path = $"./Source/Disposable/{interfaceName}.cs";
|
||||
SourceFile sourceFile = CreateSourceFile(path, name, true);
|
||||
|
||||
// Act
|
||||
var result = _sut.Execute([sourceFile]);
|
||||
|
||||
result.Valid.Should().BeTrue();
|
||||
result.Files.Should().HaveCount(fileNames.Length + 1);
|
||||
WriteFiles(fileNames, result);
|
||||
|
||||
var interfaceIndex = 1;
|
||||
var tree = result.Files[interfaceIndex].SyntaxTree;
|
||||
var root = tree.GetRoot();
|
||||
var interfaceDeclarations = root.DescendantNodes().OfType<InterfaceDeclarationSyntax>();
|
||||
|
||||
// Assert
|
||||
Assert.Single(interfaceDeclarations);
|
||||
var baseList = interfaceDeclarations.First().BaseList!;
|
||||
Assert.Equal(2, baseList.Types.Count);
|
||||
var type1 = (QualifiedNameSyntax)baseList.Types[0].Type;
|
||||
var type2 = (QualifiedNameSyntax)baseList.Types[1].Type;
|
||||
Assert.Equal(nameof(IDisposable), type1.Right.Identifier.Text);
|
||||
Assert.Equal(nameof(IUpdate<string>), type2.Right.Identifier.Text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GenerateFiles_InheritedInterface_Should_Not_InheritExplicitImplementedInterfaces()
|
||||
{
|
||||
var name = "Explicit";
|
||||
var interfaceName = "I" + name;
|
||||
var proxyName = name + "Proxy";
|
||||
|
||||
// Arrange
|
||||
string[] fileNames = [
|
||||
$"{Namespace}.{interfaceName}.g.cs",
|
||||
$"{Namespace}.{proxyName}.g.cs"
|
||||
];
|
||||
var interfaceIndex = 1;
|
||||
var path = $"./Source/Disposable/{interfaceName}.cs";
|
||||
SourceFile sourceFile = CreateSourceFile(path, name, true);
|
||||
|
||||
// Act
|
||||
var result = _sut.Execute([sourceFile]);
|
||||
|
||||
result.Valid.Should().BeTrue();
|
||||
result.Files.Should().HaveCount(fileNames.Length + 1);
|
||||
WriteFiles(fileNames, result);
|
||||
|
||||
var tree = result.Files[interfaceIndex].SyntaxTree;
|
||||
var root = tree.GetRoot();
|
||||
var interfaceDeclarations = root.DescendantNodes().OfType<InterfaceDeclarationSyntax>();
|
||||
|
||||
// Assert
|
||||
//This actually could work, we just need to implenent the logic inside the Proxy (and interface).
|
||||
//⚠ Dispose is not a public member of the 'Explicit' class and also not of the Proxy.
|
||||
//e.g. new Explicit().Dipose() is not possible.
|
||||
Assert.Single(interfaceDeclarations);
|
||||
var baseList = interfaceDeclarations.First().BaseList;
|
||||
bool noInterfaceImplementationFound = baseList is null;
|
||||
Assert.True(noInterfaceImplementationFound);
|
||||
}
|
||||
|
||||
private static SourceFile CreateSourceFile(string path, string name, bool extend)
|
||||
{
|
||||
var extendString = extend.ToString().ToLowerInvariant();
|
||||
return new SourceFile
|
||||
{
|
||||
Path = path,
|
||||
Text = File.ReadAllText(path),
|
||||
AttributeToAddToInterface = new ExtraAttribute
|
||||
{
|
||||
Name = "ProxyInterfaceGenerator.Proxy",
|
||||
ArgumentList = $"typeof({Namespace}.{name}), {extendString}"
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static void WriteFiles(string[] fileNames, ExecuteResult result)
|
||||
{
|
||||
foreach (var fileName in fileNames.Select((fileName, index) => new { fileName, index }))
|
||||
{
|
||||
var builder = result.Files[fileName.index + 1]; // +1 means skip the attribute
|
||||
builder.Path.Should().EndWith(fileName.fileName);
|
||||
File.WriteAllText($"{OutputPath}{fileName.fileName}", builder.Text);
|
||||
builder.Text.Should().Be(File.ReadAllText($"{OutputPath}{fileName.fileName}"));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<IsPackable>false</IsPackable>
|
||||
<LangVersion>10.0</LangVersion>
|
||||
<LangVersion>12.0</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<Configurations>Debug;Release;DebugAttach</Configurations>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
@@ -52,10 +52,9 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Destination\AkkaGenerated\" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Update="Source\Disposable\*.cs">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Compile>
|
||||
<Compile Update="Source\Generic.cs">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Compile>
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace ProxyInterfaceSourceGeneratorTests.Source.Disposable
|
||||
{
|
||||
public class Child : Parent
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
namespace ProxyInterfaceSourceGeneratorTests.Source.Disposable
|
||||
{
|
||||
public class Explicit : IDisposable, IUpdate<string>
|
||||
{
|
||||
string IUpdate<string>.Name => throw new NotSupportedException();
|
||||
|
||||
event EventHandler<string>? IUpdate<string>.Update
|
||||
{
|
||||
add
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
remove
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
|
||||
void IDisposable.Dispose()
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace ProxyInterfaceSourceGeneratorTests.Source.Disposable
|
||||
{
|
||||
public partial interface IChild
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace ProxyInterfaceSourceGeneratorTests.Source.Disposable
|
||||
{
|
||||
public partial interface IExplicit
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace ProxyInterfaceSourceGeneratorTests.Source.Disposable
|
||||
{
|
||||
public partial interface IParent
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace ProxyInterfaceSourceGeneratorTests.Source.Disposable
|
||||
{
|
||||
public interface IUpdate<T>
|
||||
{
|
||||
event EventHandler<T>? Update;
|
||||
|
||||
string Name { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
namespace ProxyInterfaceSourceGeneratorTests.Source.Disposable
|
||||
{
|
||||
public class Parent : IDisposable, IUpdate<string>
|
||||
{
|
||||
private bool disposedValue;
|
||||
|
||||
public event EventHandler<string>? Update;
|
||||
|
||||
public string Name => nameof(Parent);
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||
Dispose(disposing: true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
public bool Empty()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!disposedValue)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
// TODO: dispose managed state (managed objects)
|
||||
}
|
||||
|
||||
// TODO: free unmanaged resources (unmanaged objects) and override finalizer
|
||||
// TODO: set large fields to null
|
||||
disposedValue = true;
|
||||
}
|
||||
}
|
||||
|
||||
// // TODO: override finalizer only if 'Dispose(bool disposing)' has code to free unmanaged resources
|
||||
// ~TrashCan()
|
||||
// {
|
||||
// // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||
// Dispose(disposing: false);
|
||||
// }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user