private static bool TryAddBuildMethod(GeneratorExecutionContext context, BuilderToGenerate builder, ref ClassDeclarationSyntax builderClass) { var objectType = ParseTypeName(builder.Name); var buildMethodStatements = new List <StatementSyntax>(); var creationExpression = ObjectCreationExpression(objectType); var propertiesSetViaConstructor = new List <PropertyToGenerate>(); if (builder.ConstructorToUse is { } constructorToUse) { var arguments = new ArgumentSyntax[constructorToUse.Parameters.Length]; var blocked = false; for (var i = 0; i < constructorToUse.Parameters.Length; i++) { var parameterName = constructorToUse.Parameters[i].Name; var matchingProperty = builder.Properties.FirstOrDefault(p => p.PropertyName.Equals(parameterName, StringComparison.OrdinalIgnoreCase)); if (matchingProperty == null) { context.ReportDiagnostic(Diagnostic.Create(CannotInferBuilderPropertyFromArgumentDiagnostic, constructorToUse.Parameters[i].Locations[0])); blocked = true; } else { arguments[i] = Argument(PropertyAccessAndDefaultingExpression(matchingProperty)); propertiesSetViaConstructor.Add(matchingProperty); } foreach (var property in propertiesSetViaConstructor) { if (property.IsReferenceType && !property.IsNullable) { var throwStatement = ThrowStatement(ObjectCreationExpression(ParseTypeName("System.InvalidOperationException")) .AddArgumentListArguments(Argument(LiteralExpression(SyntaxKind.StringLiteralExpression, Literal($"No value present for required property '{property.PropertyName}'."))))); buildMethodStatements.Add(IfStatement(NullCheck(IdentifierName(property.FieldName)), throwStatement)); } } } if (blocked) { return(false); } creationExpression = creationExpression.AddArgumentListArguments(arguments); }
private static ClassDeclarationSyntax AddMutationMethods(BuilderToGenerate builder, ClassDeclarationSyntax builderClass) { foreach (var property in builder.Properties) { var lowerCamelParameterName = property.PropertyName.Substring(0, 1).ToLowerInvariant() + property.PropertyName.Substring(1); var upperCamelParameterName = property.PropertyName.Substring(0, 1).ToUpperInvariant() + property.PropertyName.Substring(1); var localBuilderIdentifier = Identifier("mutatedBuilder"); builderClass = builderClass.AddMembers( MethodDeclaration(builder.BuilderTypeSyntax, "With" + upperCamelParameterName) .AddModifiers(Token(SyntaxKind.PublicKeyword)) .AddParameterListParameters(Parameter(Identifier(lowerCamelParameterName)).WithType(property.TypeSyntax)) .AddBodyStatements( LocalDeclarationStatement(VariableDeclaration(IdentifierName("var")) .AddVariables(VariableDeclarator(localBuilderIdentifier) .WithInitializer(EqualsValueClause(ObjectCreationExpression(builder.BuilderTypeSyntax) .AddArgumentListArguments(Argument(ThisExpression())))))), ExpressionStatement(AssignmentExpression(SyntaxKind.SimpleAssignmentExpression, MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, IdentifierName(localBuilderIdentifier), IdentifierName(property.FieldName)), IdentifierName(lowerCamelParameterName))), ReturnStatement(IdentifierName(localBuilderIdentifier)) ) ); if (property.IsNullable || property.TypeSyntax is NullableTypeSyntax) { builderClass = builderClass.AddMembers( MethodDeclaration(builder.BuilderTypeSyntax, "Without" + upperCamelParameterName) .AddModifiers(Token(SyntaxKind.PublicKeyword)) .AddBodyStatements( LocalDeclarationStatement(VariableDeclaration(IdentifierName("var")) .AddVariables(VariableDeclarator(localBuilderIdentifier) .WithInitializer(EqualsValueClause(ObjectCreationExpression(builder.BuilderTypeSyntax) .AddArgumentListArguments(Argument(ThisExpression())))))), ExpressionStatement(AssignmentExpression(SyntaxKind.SimpleAssignmentExpression, MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, IdentifierName(localBuilderIdentifier), IdentifierName(property.FieldName)), LiteralExpression(SyntaxKind.NullLiteralExpression))), ReturnStatement(IdentifierName(localBuilderIdentifier)) ) ); } } return(builderClass); }
private static Dictionary <string, BuilderToGenerate> DetermineBuildersToGenerate(GeneratorExecutionContext context, INamedTypeSymbol generateDataBuilderAttributeType) { var builders = new Dictionary <string, BuilderToGenerate>(); foreach (var inputDocument in context.Compilation.SyntaxTrees) { context.CancellationToken.ThrowIfCancellationRequested(); var typeNodes = inputDocument.GetRoot() .DescendantNodesAndSelf(n => n is CompilationUnitSyntax || n is NamespaceDeclarationSyntax || n is TypeDeclarationSyntax) .OfType <TypeDeclarationSyntax>(); var semanticModel = context.Compilation.GetSemanticModel(inputDocument); foreach (var typeNode in typeNodes) { if (!typeNode.AttributeLists.ContainsAttributeType(semanticModel, generateDataBuilderAttributeType, exactMatch: true)) { continue; } var typeSymbol = semanticModel.GetDeclaredSymbol(typeNode); if (typeSymbol == null) { continue; } var generationBlocked = false; if (typeSymbol.IsAbstract) { context.ReportDiagnostic(Diagnostic.Create(GeneratorOnAbstractDiagnostic, typeSymbol.Locations.First(), typeSymbol.Name)); generationBlocked = true; } if (typeNode.Modifiers.Any(m => m.Kind() == SyntaxKind.PartialKeyword)) { context.ReportDiagnostic(Diagnostic.Create(GeneratorOnPartialDiagnostic, typeNode.GetLocation(), typeSymbol.Name)); generationBlocked = true; } if (generationBlocked) { continue; } string typeFullMetadataName = typeSymbol.GetFullMetadataName(); if (!builders.TryGetValue(typeFullMetadataName, out var builder)) { builder = new BuilderToGenerate( typeSymbol.ContainingNamespace?.GetFullMetadataName(), typeSymbol.Name, typeNode, typeSymbol.DeclaredAccessibility); IMethodSymbol?constructorToUse = null; foreach (var typeSymbolConstructor in typeSymbol.Constructors) { if (typeSymbolConstructor.IsStatic) { continue; } if (typeSymbolConstructor.Parameters.Length > (constructorToUse?.Parameters.Length ?? 0)) { constructorToUse = typeSymbolConstructor; } } builder.ConstructorToUse = constructorToUse; builders.Add(typeFullMetadataName, builder); } builder.Properties.AddRange( typeSymbol .GetMembers() .Concat(typeSymbol.GetBaseTypes().SelectMany(t => t.GetMembers())) .OfType <IPropertySymbol>() .Where(m => m.DeclaredAccessibility == Accessibility.Public && !m.IsReadOnly && !m.IsIndexer && !m.IsStatic) .Select(property => new PropertyToGenerate( $"_{property.Name.Substring(0, 1).ToLower()}{property.Name.Substring(1)}", property.Name, property.Type, DetermineNullability(semanticModel, property))) ); } } return(builders); }