diff --git a/src/AutoConstructor.Generator/AutoConstructorGenerator.cs b/src/AutoConstructor.Generator/AutoConstructorGenerator.cs index 2e08d24..b974431 100644 --- a/src/AutoConstructor.Generator/AutoConstructorGenerator.cs +++ b/src/AutoConstructor.Generator/AutoConstructorGenerator.cs @@ -25,34 +25,36 @@ public void Initialize(IncrementalGeneratorInitializationContext context) i.AddSource(Source.InjectAttributeFullName, SourceText.From(Source.InjectAttributeText, Encoding.UTF8)); }); - IncrementalValuesProvider<(MainNamedTypeSymbolInfo? symbol, EquatableArray fields, Options options, EquatableArray diagnostics)> valueProvider = context.SyntaxProvider + IncrementalValuesProvider valueProvider = context.SyntaxProvider .ForAttributeWithMetadataName( Source.AttributeFullName, static (node, _) => IsSyntaxTargetForGeneration(node), static (context, _) => (ClassDeclarationSyntax)context.TargetNode) .Where(static m => m is not null) - .Collect() .Combine(context.AnalyzerConfigOptionsProvider.Select((c, _) => c.GlobalOptions)) .Combine(context.CompilationProvider) - .SelectMany((c, _) => + .Select((c, _) => { - (ImmutableArray classes, AnalyzerConfigOptions options, Compilation compilation) = (c.Left.Left, c.Left.Right, c.Right); - return Execute(compilation, classes, options); + (ClassDeclarationSyntax classSyntax, AnalyzerConfigOptions options, Compilation compilation) = (c.Left.Left, c.Left.Right, c.Right); + return Execute(compilation, classSyntax, options); }); context.RegisterSourceOutput(valueProvider, static (context, item) => { - if (!item.diagnostics.IsEmpty) + if (item is not null) { - foreach (Diagnostic diagnostic in item.diagnostics) + if (!item.Diagnostics.IsEmpty) { - context.ReportDiagnostic(diagnostic); + foreach (Diagnostic diagnostic in item.Diagnostics) + { + context.ReportDiagnostic(diagnostic); + } + } + else + { + CodeGenerator codeGenerator = GenerateAutoConstructor(item.Symbol!, item.Fields, item.Options); + context.AddSource($"{item.Symbol!.Filename}.g.cs", codeGenerator.ToString()); } - } - else - { - CodeGenerator codeGenerator = GenerateAutoConstructor(item.symbol!, item.fields, item.options); - context.AddSource($"{item.symbol!.Filename}.g.cs", codeGenerator.ToString()); } }); } @@ -62,23 +64,8 @@ private static bool IsSyntaxTargetForGeneration(SyntaxNode node) return node is ClassDeclarationSyntax classDeclarationSyntax && classDeclarationSyntax.Modifiers.Any(SyntaxKind.PartialKeyword); } - private static IEnumerable<(MainNamedTypeSymbolInfo? symbol, EquatableArray fields, Options options, EquatableArray diagnostics)> Execute(Compilation compilation, ImmutableArray classes, AnalyzerConfigOptions analyzerOptions) + private static GeneratorExectutionResult? Execute(Compilation compilation, ClassDeclarationSyntax classSyntax, AnalyzerConfigOptions analyzerOptions) { - if (classes.IsDefaultOrEmpty) - { - yield break; - } - - IEnumerable> classesBySymbol = Enumerable.Empty>(); - try - { - classesBySymbol = classes.GroupBy(c => compilation.GetSemanticModel(c.SyntaxTree).GetDeclaredSymbol(c), SymbolEqualityComparer.Default); - } - catch (ArgumentException) - { - yield break; - } - bool generateConstructorDocumentation = false; if (analyzerOptions.TryGetValue("build_property.AutoConstructor_GenerateConstructorDocumentation", out string? generateConstructorDocumentationSwitch)) { @@ -95,69 +82,61 @@ private static bool IsSyntaxTargetForGeneration(SyntaxNode node) Options options = new(generateConstructorDocumentation, constructorDocumentationComment, emitNullChecks); - foreach (IGrouping groupedClasses in classesBySymbol) + INamedTypeSymbol? symbol = compilation.GetSemanticModel(classSyntax.SyntaxTree).GetDeclaredSymbol(classSyntax); + if (symbol is null) { - INamedTypeSymbol? symbol = groupedClasses.Key as INamedTypeSymbol; - if (symbol is not null) - { - string filename = string.Empty; + return null; + } - if (symbol.ContainingType is not null) - { - filename = $"{string.Join(".", symbol.GetContainingTypes().Select(c => c.Name))}."; - } + string filename = string.Empty; + if (symbol.ContainingType is not null) + { + filename = $"{string.Join(".", symbol.GetContainingTypes().Select(c => c.Name))}."; + } - filename += $"{symbol.Name}"; + filename += $"{symbol.Name}"; - if (symbol.TypeArguments.Length > 0) - { - filename += string.Concat(symbol.TypeArguments.Select(tp => $".{tp.Name}")); - } + if (symbol.TypeArguments.Length > 0) + { + filename += string.Concat(symbol.TypeArguments.Select(tp => $".{tp.Name}")); + } - if (!symbol.ContainingNamespace.IsGlobalNamespace) - { - filename = $"{symbol.ContainingNamespace.ToDisplayString()}.{filename}"; - } + if (!symbol.ContainingNamespace.IsGlobalNamespace) + { + filename = $"{symbol.ContainingNamespace.ToDisplayString()}.{filename}"; + } - List concatenatedFields = GetFieldsFromSymbol(compilation, symbol, emitNullChecks); + List concatenatedFields = GetFieldsFromSymbol(compilation, symbol, emitNullChecks); - ExtractFieldsFromParent(compilation, symbol, emitNullChecks, concatenatedFields); + ExtractFieldsFromParent(compilation, symbol, emitNullChecks, concatenatedFields); - EquatableArray fields = concatenatedFields.ToImmutableArray(); + EquatableArray fields = concatenatedFields.ToImmutableArray(); - if (fields.IsEmpty) - { - // No need to report diagnostic, taken care by the analyzers. - continue; - } + if (fields.IsEmpty) + { + // No need to report diagnostic, taken care by the analyzers. + return null; + } - var diagnotics = new List(); + var diagnotics = new List(); - if (fields.GroupBy(x => x.ParameterName).Any(g => - g.Where(c => c.Type is not null).Select(c => c.Type).Count() > 1 - || (g.All(c => c.Type is null) && g.Select(c => c.FallbackType).Count() > 1) - )) - { - foreach (ClassDeclarationSyntax classDeclaration in groupedClasses) - { - diagnotics.Add(Diagnostic.Create(DiagnosticDescriptors.MistmatchTypesRule, classDeclaration.GetLocation())); - } + if (fields.GroupBy(x => x.ParameterName).Any(g => + g.Where(c => c.Type is not null).Select(c => c.Type).Count() > 1 + || (g.All(c => c.Type is null) && g.Select(c => c.FallbackType).Count() > 1) + )) + { + diagnotics.Add(Diagnostic.Create(DiagnosticDescriptors.MistmatchTypesRule, classSyntax.GetLocation())); - yield return (null, fields, options, diagnotics.ToImmutableArray()); - continue; - } + return new(null, fields, options, diagnotics.ToImmutableArray()); + } - bool hasParameterlessConstructor = - groupedClasses - .SelectMany(c => c - .ChildNodes() - .Where(n => n is ConstructorDeclarationSyntax constructor && !constructor.Modifiers.Any(m => m.IsKind(SyntaxKind.StaticKeyword)))) - .Count() == 1 - && symbol.Constructors.Any(d => !d.IsStatic && d.Parameters.Length == 0); + bool hasParameterlessConstructor = + classSyntax + .ChildNodes() + .Count(n => n is ConstructorDeclarationSyntax constructor && !constructor.Modifiers.Any(m => m.IsKind(SyntaxKind.StaticKeyword))) == 1 + && symbol.Constructors.Any(d => !d.IsStatic && d.Parameters.Length == 0); - yield return (new MainNamedTypeSymbolInfo(symbol, hasParameterlessConstructor, filename), fields, options, Array.Empty().ToImmutableArray()); - } - } + return new(new MainNamedTypeSymbolInfo(symbol, hasParameterlessConstructor, filename), fields, options, Array.Empty().ToImmutableArray()); } private static CodeGenerator GenerateAutoConstructor(MainNamedTypeSymbolInfo symbol, EquatableArray fields, Options options) @@ -166,7 +145,8 @@ private static CodeGenerator GenerateAutoConstructor(MainNamedTypeSymbolInfo sym string? constructorDocumentationComment = options.ConstructorDocumentationComment; if (string.IsNullOrWhiteSpace(constructorDocumentationComment)) { - constructorDocumentationComment = $"Initializes a new instance of the {{0}} class. // Counter: {Interlocked.Increment(ref _counter)}"; + constructorDocumentationComment = "Initializes a new instance of the {0} class."; + //constructorDocumentationComment = $"Initializes a new instance of the {{0}} class. // Counter: {Interlocked.Increment(ref _counter)}"; } var codeGenerator = new CodeGenerator(); @@ -336,7 +316,7 @@ private static void ExtractFieldsFromGeneratedParent(Compilation compilation, bo string.Empty, string.Empty, parameter.FallbackType, - false,//IsNullable(parameter.FallbackType), + parameter.Nullable, null, false, FieldType.PassedToBase)); diff --git a/src/AutoConstructor.Generator/GeneratorExectutionResult.cs b/src/AutoConstructor.Generator/GeneratorExectutionResult.cs new file mode 100644 index 0000000..ee20270 --- /dev/null +++ b/src/AutoConstructor.Generator/GeneratorExectutionResult.cs @@ -0,0 +1,9 @@ +using Microsoft.CodeAnalysis; + +namespace AutoConstructor.Generator; + +internal record GeneratorExectutionResult( + MainNamedTypeSymbolInfo? Symbol, + EquatableArray Fields, + Options Options, + EquatableArray Diagnostics); diff --git a/tests/AutoConstructor.Tests/AutoConstructor.Tests.csproj b/tests/AutoConstructor.Tests/AutoConstructor.Tests.csproj index f8e7e16..c8f11ee 100644 --- a/tests/AutoConstructor.Tests/AutoConstructor.Tests.csproj +++ b/tests/AutoConstructor.Tests/AutoConstructor.Tests.csproj @@ -3,6 +3,7 @@ net7.0 false + $(NoWarn);CS1591 diff --git a/tests/AutoConstructor.Tests/GeneratorTests.cs b/tests/AutoConstructor.Tests/GeneratorTests.cs index bc3b0b6..b2ff57e 100644 --- a/tests/AutoConstructor.Tests/GeneratorTests.cs +++ b/tests/AutoConstructor.Tests/GeneratorTests.cs @@ -1009,7 +1009,7 @@ public Test(int i1) } [Fact] - public async Task Run_WithMismatchingTypesWithTwoPartialParts_ShouldReportDiagnosticOnEachPart() + public async Task Run_WithMismatchingTypesWithTwoPartialParts_ShouldReportDiagnosticOnFirstPart() { const string code = @" namespace Test @@ -1028,8 +1028,7 @@ internal partial class Test }"; DiagnosticResult diagnosticResultFirstPart = new DiagnosticResult(DiagnosticDescriptors.MistmatchTypesDiagnosticId, DiagnosticSeverity.Error).WithSpan(4, 5, 9, 6); - DiagnosticResult diagnosticResultSecondPart = new DiagnosticResult(DiagnosticDescriptors.MistmatchTypesDiagnosticId, DiagnosticSeverity.Error).WithSpan(11, 5, 14, 6); - await VerifySourceGenerator.RunAsync(code, diagnostics: new[] { diagnosticResultFirstPart, diagnosticResultSecondPart }); + await VerifySourceGenerator.RunAsync(code, diagnostics: new[] { diagnosticResultFirstPart }); } [Theory]