Skip to content

Commit

Permalink
Fix detection of base constructor if parent class defined in another …
Browse files Browse the repository at this point in the history
…assembly and is using AutoConstructorAttribute
  • Loading branch information
k94ll13nn3 committed Mar 4, 2024
1 parent 660fa92 commit 2d209c1
Show file tree
Hide file tree
Showing 6 changed files with 151 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ private static bool ParentHasFields(Compilation compilation, INamedTypeSymbol sy
if (baseType?.BaseType is not null && baseType.Constructors.Count(d => !d.IsStatic) == 1)
{
IMethodSymbol constructor = baseType.Constructors.Single(d => !d.IsStatic);
return baseType.HasAttribute(Source.AttributeFullName)
return (SymbolEqualityComparer.Default.Equals(baseType.ContainingAssembly, symbol.ContainingAssembly) && baseType.HasAttribute(Source.AttributeFullName))
? SymbolHasFields(baseType) || ParentHasFields(compilation, baseType)
: constructor.Parameters.Length > 0;
}
Expand Down
5 changes: 4 additions & 1 deletion src/AutoConstructor.Generator/AutoConstructorGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -345,7 +345,10 @@ private static void ExtractFieldsFromParent(INamedTypeSymbol symbol, List<FieldI
if (baseType?.BaseType is not null && baseType.Constructors.Count(d => !d.IsStatic) == 1)
{
IMethodSymbol constructor = baseType.Constructors.Single(d => !d.IsStatic);
if (baseType.HasAttribute(Source.AttributeFullName))

// If symbol is in same assembly, the generated constructor is not visible as it might not be yet generated.
// If not is the same assembly, is does not matter if the constructor was generated or not.
if (SymbolEqualityComparer.Default.Equals(baseType.ContainingAssembly, symbol.ContainingAssembly) && baseType.HasAttribute(Source.AttributeFullName))
{
ExtractFieldsFromGeneratedParent(concatenatedFields, baseType);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,8 +136,7 @@ public Test3(int myType) : base(myType)
}")]
public async Task Analyzer_ClassWithoutFieldsToInjectButFieldsOnParent_ShouldNotReportDiagnostic(string test)
{
DiagnosticResult[] expected = [];
await VerifyClassWithoutFieldsToInject.VerifyAnalyzerAsync(test, expected);
await VerifyClassWithoutFieldsToInject.VerifyAnalyzerAsync(test, []);
}

[Theory]
Expand All @@ -162,4 +161,41 @@ public async Task Analyzer_ClassWithoutFieldsToInject_ShouldFixCode(string test,
];
await VerifyClassWithoutFieldsToInject.VerifyCodeFixAsync(test, expected, fixtest);
}

[Fact]
public async Task Fix_Issue106_ShouldNotReportDiagnostic()
{
// ACONS02 is expected here but is not the tested case.
const string additionnalSource = $@"
namespace Test2
{{
{Source.AttributeText}
[{{|#0:AutoConstructor|}}]
public partial class ParentClass
{{
public ParentClass(int dependency)
{{
}}
}}
}}";

const string test = @"
using Test2;
namespace Test
{
[AutoConstructor]
public partial class Test : ParentClass
{
public Test(int dependency) : base(dependency)
{
}
}
}";
DiagnosticResult[] expected = [
VerifyClassWithoutFieldsToInject.Diagnostic(DiagnosticDescriptors.TypeWithoutFieldsToInjectDiagnosticId).WithLocation(0),
];

await VerifyClassWithoutFieldsToInject.VerifyAnalyzerAsync(test, expected, additionnalSource);
}
}
77 changes: 77 additions & 0 deletions tests/AutoConstructor.Tests/GeneratorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1965,4 +1965,81 @@ public Test(int t)
";
await VerifySourceGenerator.RunAsync(code, generated);
}

[Fact]
public async Task Fix_Issue106_WorkingCase()
{
const string additionnalSource = @"
namespace Test2
{
public partial class ParentClass
{
private int Dependency { get; }
public ParentClass(int dependency)
{
Dependency = dependency;
}
}
}";

const string code = @"
using Test2;
namespace Test
{
[AutoConstructor]
public partial class Test : ParentClass
{
}
}";
const string generated = @"namespace Test
{
partial class Test
{
public Test(int dependency) : base(dependency)
{
}
}
}
";
await VerifySourceGenerator.RunAsync(code, generated, additionalProjectsSource: additionnalSource);
}

[Fact]
public async Task Fix_Issue106_NotWorkingCase()
{
const string additionnalSource = $@"
namespace Test2
{{
{Source.AttributeText}
[AutoConstructor]
public partial class ParentClass
{{
public ParentClass(int dependency)
{{
}}
}}
}}";

const string code = @"
using Test2;
namespace Test
{
[AutoConstructor]
public partial class Test : ParentClass
{
}
}";
const string generated = @"namespace Test
{
partial class Test
{
public Test(int dependency) : base(dependency)
{
}
}
}
";
await VerifySourceGenerator.RunAsync(code, generated, additionalProjectsSource: additionnalSource);
}
}
17 changes: 15 additions & 2 deletions tests/AutoConstructor.Tests/Verifiers/CSharpCodeFixVerifier`2.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,26 @@ public static DiagnosticResult Diagnostic(DiagnosticDescriptor diagnostic)
return CSharpAnalyzerVerifier<TAnalyzer, DefaultVerifier>.Diagnostic(diagnostic);
}

public static async Task VerifyAnalyzerAsync(string source, params DiagnosticResult[] expected)
public static async Task VerifyAnalyzerAsync(string source, DiagnosticResult[] expected, string? additionalProjectsSource = null)
{
var test = new Test
{
TestCode = AppendBaseCode(source),
TestState =
{
Sources = { AppendBaseCode(source) },
AdditionalProjects =
{
["DependencyProject"] = { },
},
},
};

if (additionalProjectsSource is not null)
{
test.TestState.AdditionalProjectReferences.Add("DependencyProject");
test.TestState.AdditionalProjects["DependencyProject"].Sources.Add(additionalProjectsSource);
}

test.ExpectedDiagnostics.AddRange(expected);
await test.RunAsync(CancellationToken.None);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,19 @@ public static Task RunAsync(
string generatedName = "Test.Test.g.cs",
bool nullable = false,
IEnumerable<DiagnosticResult>? diagnostics = null,
string? configFileContent = null)
string? configFileContent = null,
string? additionalProjectsSource = null)
{
return RunAsync(code, new[] { (generated, generatedName) }, nullable, diagnostics, configFileContent);
return RunAsync(code, new[] { (generated, generatedName) }, nullable, diagnostics, configFileContent, additionalProjectsSource);
}

public static async Task RunAsync(
string code,
(string, string)[] generatedSources,
bool nullable = false,
IEnumerable<DiagnosticResult>? diagnostics = null,
string? configFileContent = null)
string? configFileContent = null,
string? additionalProjectsSource = null)
{
var test = new CSharpSourceGeneratorVerifier<TSourceGenerator>.Test()
{
Expand All @@ -41,12 +43,22 @@ public static async Task RunAsync(
(typeof(AutoConstructorGenerator), "AutoConstructorIgnoreAttribute.cs", SourceText.From(Source.IgnoreAttributeText, Encoding.UTF8)),
(typeof(AutoConstructorGenerator), "AutoConstructorInjectAttribute.cs", SourceText.From(Source.InjectAttributeText, Encoding.UTF8)),
(typeof(AutoConstructorGenerator), "AutoConstructorInitializerAttribute.cs", SourceText.From(Source.InitializerAttributeText, Encoding.UTF8)),
}
},
AdditionalProjects =
{
["DependencyProject"] = { },
},
},
EnableNullable = nullable,
LanguageVersion = LanguageVersion.Default,
};

if (additionalProjectsSource is not null)
{
test.TestState.AdditionalProjectReferences.Add("DependencyProject");
test.TestState.AdditionalProjects["DependencyProject"].Sources.Add(additionalProjectsSource);
}

string generatedCodeAttribute = @$"[global::System.CodeDom.Compiler.GeneratedCodeAttribute(""{nameof(AutoConstructor)}"", ""{AutoConstructorGenerator.GeneratorVersion}"")]";
foreach ((string? generated, string generatedName) in generatedSources)
{
Expand Down

0 comments on commit 2d209c1

Please sign in to comment.