public static GetPkIdPlan Build(OutputModel outputModel, IMethodSymbol symbol, params Dictionary <string, PropertyPlan>[] propertyPlanByNames)
    {
        var plan = new GetPkIdPlan()
        {
            FullMethodName = $"{symbol.ContainingType.ToDisplayString()}.{symbol.Name}",
            Arguments      = symbol
                             .Parameters
                             .Select(x =>
            {
                var arg = new GetPkIdPlan.Argument
                {
                    ArgumentName = x.Name,
                    FullTypeName = x.Type.ToDisplayString()
                };
                var names = new List <string>(4)
                {
                    arg.ArgumentName
                };
                if (arg.ArgumentName.StartsWith("_"))
                {
                    names.Add(arg.ArgumentName.Substring(1));
                    names.Add(arg.ArgumentName.Substring(1).ToPascalCase());
                }
                else
                {
                    names.Add(arg.ArgumentName.ToPascalCase());
                }
                foreach (var name in names)
                {
                    foreach (var dict in propertyPlanByNames)
                    {
                        if (dict.TryGetValue(name, out var propertyPlan) && propertyPlan.FullTypeName == arg.FullTypeName)
                        {
                            arg.PropertyName = propertyPlan.PropertyName;
                            if (!propertyPlan.IsInitOnly)
                            {
                                outputModel.Report(Diagnostics.Warnings.InitOnlyKey, propertyPlan.PropertyModel.PropertySymbol, arg.PropertyName);
                            }
                            return(arg);
                        }
                    }
                }
                outputModel.Report(Diagnostics.Errors.PropertyResolvePkId, symbol, arg.ArgumentName);
                return(arg);
            })
                             .ToList()
        };

        plan.ArgumentByPropertyName = plan.Arguments.Where(x => x.PropertyName is not null).ToDictionary(x => x.PropertyName);
        return(plan);
    }
}
Exemple #2
0
    public static void Build(OutputModel outputModel, OutputPlan outputPlan)
    {
        outputModel.CancellationToken.ThrowIfCancellationRequested();
        if (!outputModel.CanGenerate)
        {
            return;
        }

        var databasePlansByClass = new Dictionary <ClassModel, List <DatabasePlan> >();

        foreach (var dbAttribute in outputModel.DbAttributes)
        {
            Initialize(outputModel, null, outputPlan, dbAttribute);
        }
        foreach (var classModel in outputModel.Classes)
        {
            outputPlan.DatabasePlansByClass[classModel] = new();
            foreach (var dbAttribute in classModel.DbAttributes)
            {
                Initialize(outputModel, classModel, outputPlan, dbAttribute);
            }
        }
        var defaultDatabase = outputPlan.DatabasePlansByName.Count == 1 ? outputPlan.DatabasePlansByName.Values.Single() : null;

        if (defaultDatabase is not null)
        {
            foreach (var byClass in outputPlan.DatabasePlansByClass.Values.Where(x => x.Count == 0))
            {
                byClass.Add(defaultDatabase);
            }
        }
        foreach (var classModel in outputPlan.DatabasePlansByName.Values.GroupBy(x => x.Namespace).Where(x => x.Count() > 1).Select(x => x.First().ClassModel))
        {
            outputModel.Report(Diagnostics.Errors.DatabaseNamespaces, classModel?.ClassSymbol as ISymbol ?? outputModel.Compilation.Assembly);
        }

        PartitionPlanBuilder.AddPartitions(outputModel, outputPlan);
        DocumentPlanBuilder.AddDocuments(outputModel, outputPlan);
        PartitionPlanBuilder.RemoveEmptyPartitions(outputModel, outputPlan);
        UnionPlanBuilder.AddUnions(outputModel, outputPlan);

        foreach (var databasePlan in outputPlan.DatabasePlansByName.Values)
        {
            foreach (var documentPlan in databasePlan.PartitionPlansByName.Values.SelectMany(x => x.Documents).GroupBy(x => x.DocType).Where(x => x.Count() > 1).Select(x => x.First()))
            {
                outputModel.Report(Diagnostics.Errors.DuplicateDocType, documentPlan.ClassModel.ClassSymbol, documentPlan.DocType);
            }
        }
    }
 public static void ValidateName(this string?name, OutputModel outputModel, ISymbol symbol)
 {
     if (ValidateName(name) is DiagnosticDescriptor diag)
     {
         outputModel.Report(diag, symbol, name);
     }
 }
    public static void AddProperties(OutputModel outputModel, ClassModel classModel, DocumentPlan documentPlan)
    {
        outputModel.CancellationToken.ThrowIfCancellationRequested();
        if (!outputModel.CanGenerate)
        {
            return;
        }

        foreach (var propertyModel in classModel.Properties)
        {
            var symbol = propertyModel.PropertySymbol;
            if (outputModel.CanIncludeProperty(symbol))
            {
                var propertyPlan = new PropertyPlan
                {
                    PropertyName  = symbol.Name,
                    ArgumentName  = symbol.Name.ToArgumentName(),
                    FullTypeName  = symbol.Type.ToDisplayString(),
                    UseDefault    = propertyModel.UseDefaultAttribute is not null,
                    IsInitOnly    = propertyModel.IsInitOnly,
                    PropertyModel = propertyModel
                };
                outputModel.ValidateIdentifiers(
                    symbol,
                    propertyPlan.PropertyName,
                    propertyPlan.ArgumentName);
                documentPlan.PropertiesByName[propertyPlan.PropertyName] = propertyPlan;
                if (documentPlan.PropertiesByArgumentName.ContainsKey(propertyPlan.ArgumentName))
                {
                    outputModel.Report(Diagnostics.Errors.PropertyArgumentCollision, symbol, propertyPlan.ArgumentName);
                }
                else
                {
                    documentPlan.PropertiesByArgumentName[propertyPlan.ArgumentName] = propertyPlan;
                    if (!propertyPlan.IsInitOnly &&
                        !documentPlan.IsMutable &&
                        SymbolEqualityComparer.Default.Equals(propertyPlan.PropertyModel.PropertySymbol.ContainingType, classModel.ClassSymbol))
                    {
                        outputModel.Report(Diagnostics.Warnings.InitOnlyNotMutable, symbol, propertyPlan.PropertyName);
                    }
                }
            }
        }
    }
}
 public static void ValidateDocType(this string?docType, OutputModel outputModel, ISymbol symbol)
 {
     if (string.IsNullOrWhiteSpace(docType) ||
         docType != docType !.Trim() ||
         docType.Contains('"') ||
         docType.Contains('\''))
     {
         outputModel.Report(Diagnostics.Errors.InvalidDocType, symbol, docType);
     }
 }
 public static void ValidateIdentifiers(this OutputModel outputModel, ISymbol symbol, params string[] names)
 {
     foreach (var name in names)
     {
         if (ValidateIdentifier(name) is DiagnosticDescriptor diag)
         {
             outputModel.Report(diag, symbol, name);
         }
     }
 }
Exemple #7
0
    static void Validate(OutputModel outputModel, ClassModel model)
    {
        var symbol = model.ClassSymbol;

        foreach (var dbAttribute in model.DbAttributes)
        {
            dbAttribute.Name.ValidateName(outputModel, symbol);
        }
        if (model.PartitionAttribute is not null)
        {
            model.PartitionAttribute.Name.ValidateName(outputModel, symbol);
            if (!model.IsDbDoc)
            {
                outputModel.Report(Diagnostics.Errors.PartitionDbDoc, symbol);
            }
        }
        if (model.DocTypeAttribute is not null)
        {
            model.DocTypeAttribute.Name.ValidateName(outputModel, symbol);
            if (!model.IsDbDoc)
            {
                outputModel.Report(Diagnostics.Errors.DocTypeDbDoc, symbol);
            }
            if (symbol.IsAbstract)
            {
                outputModel.Report(Diagnostics.Errors.DocTypeAbstract, symbol);
            }
        }
        if (model.PartitionDefinitionAttribute is not null)
        {
            if (!symbol.IsStatic)
            {
                outputModel.Report(Diagnostics.Errors.PartitionDefinitionStatic, symbol);
            }
        }
        if (model.MutableAttribute is not null)
        {
            if (!model.IsDbDoc)
            {
                outputModel.Report(Diagnostics.Errors.MutableDbDoc, symbol);
            }
        }
        if (model.TransientAttribute is not null)
        {
            if (!model.IsDbDoc)
            {
                outputModel.Report(Diagnostics.Errors.TransientDbDoc, symbol);
            }
        }
        foreach (var methodModel in model.Methods)
        {
            Validate(outputModel, model, methodModel);
        }
        foreach (var propertyModel in model.Properties)
        {
            Validate(outputModel, model, propertyModel);
        }
    }
Exemple #8
0
 static void AddImplicitPartitions(OutputModel outputModel, OutputPlan outputPlan)
 {
     foreach (var kvp in outputPlan.DatabasePlansByClass)
     {
         var classModel    = kvp.Key;
         var databasePlans = kvp.Value;
         if (classModel.PartitionAttribute is not null)
         {
             var name = classModel.PartitionAttribute.Name;
             foreach (var databasePlan in databasePlans)
             {
                 if (!databasePlan.PartitionPlansByName.ContainsKey(name))
                 {
                     var getPk = classModel.Methods
                                 .Where(x => x.MethodSymbol.Name == "GetPk")
                                 .Where(x => x.MethodSymbol.IsStatic)
                                 .Where(x => x.MethodSymbol.ReturnType.SpecialType == SpecialType.System_String)
                                 .Where(x => x.MethodSymbol.DeclaredAccessibility.IsAccessible())
                                 .ToList();
                     if (getPk.Count == 0)
                     {
                         outputModel.Report(Diagnostics.Errors.NoGetPk, classModel.ClassSymbol);
                     }
                     else if (getPk.Count > 1)
                     {
                         outputModel.Report(Diagnostics.Errors.MultipleGetPk, classModel.ClassSymbol);
                     }
                     else
                     {
                         AddPartitionDefinition(outputModel, databasePlan, getPk[0], name);
                     }
                 }
             }
         }
     }
 }
Exemple #9
0
    static void Validate(OutputModel outputModel, ClassModel classModel, PropertyModel model)
    {
        var symbol = model.PropertySymbol;

        if (model.UseDefaultAttribute is not null)
        {
            if (symbol.IsStatic)
            {
                outputModel.Report(Diagnostics.Errors.UseDefaultStatic, symbol);
            }
            if (!classModel.IsDbDoc)
            {
                outputModel.Report(Diagnostics.Errors.UseDefaultDbDoc, symbol);
            }
            if (!outputModel.CanIncludeProperty(symbol))
            {
                outputModel.Report(Diagnostics.Warnings.UseDefaultIgnored, symbol);
            }
            if (symbol.Type.IsReferenceType && symbol.Type.NullableAnnotation != NullableAnnotation.Annotated)
            {
                outputModel.Report(Diagnostics.Errors.UseDefaultNullable, symbol);
            }
        }
    }
Exemple #10
0
    public static void RemoveEmptyPartitions(OutputModel outputModel, OutputPlan outputPlan)
    {
        outputModel.CancellationToken.ThrowIfCancellationRequested();
        if (!outputModel.CanGenerate)
        {
            return;
        }

        foreach (var databasePlan in outputPlan.DatabasePlansByName.Values)
        {
            foreach (var partitionPlan in databasePlan.PartitionPlansByName.Values.Where(x => x.Documents.Count == 0).ToList())
            {
                databasePlan.PartitionPlansByName.Remove(partitionPlan.Name);
                outputModel.Report(Diagnostics.Warnings.EmptyPartition, partitionPlan.GetPkModel.MethodSymbol, partitionPlan.Name);
            }
        }
    }
Exemple #11
0
 static void AddPartitionClasses(OutputModel outputModel, OutputPlan outputPlan)
 {
     foreach (var kvp in outputPlan.DatabasePlansByClass)
     {
         var classModel    = kvp.Key;
         var databasePlans = kvp.Value;
         if (classModel.PartitionDefinitionAttribute is not null)
         {
             if (databasePlans.Count == 0)
             {
                 outputModel.Report(Diagnostics.Errors.NoDatabase, classModel.ClassSymbol);
             }
             foreach (var methodModel in classModel.Methods)
             {
                 var name = methodModel.MethodSymbol.Name;
                 foreach (var databasePlan in databasePlans)
                 {
                     AddPartitionDefinition(outputModel, databasePlan, methodModel, name);
                 }
             }
         }
     }
 }
Exemple #12
0
    static void AddPartitionDefinition(OutputModel outputModel, DatabasePlan databasePlan, MethodModel methodModel, string name)
    {
        if (databasePlan.PartitionPlansByName.TryGetValue(name, out var partitionPlan))
        {
            if (!SymbolEqualityComparer.Default.Equals(partitionPlan.GetPkModel.MethodSymbol, methodModel.MethodSymbol))
            {
                outputModel.Report(Diagnostics.Errors.PartitionAlreadyDefined, methodModel.MethodSymbol);
            }
        }
        else
        {
            partitionPlan = new PartitionPlan
            {
                Name                        = name,
                PluralName                  = name.Pluralize(),
                ClassName                   = name.WithSuffix(Suffixes.Partition),
                BatchHandlersClassName      = name.WithSuffix(Suffixes.BatchHandlers),
                CreateOrReplaceClassName    = name.WithSuffix(Suffixes.CreateOrReplace),
                ReadClassName               = name.WithSuffix(Suffixes.Read),
                ReadOrThrowClassName        = name.WithSuffix(Suffixes.ReadOrThrow),
                ReadManyClassName           = name.WithSuffix(Suffixes.ReadMany),
                ReadUnionsClassName         = name.WithSuffix(Suffixes.ReadUnions),
                ReadOrThrowUnionsClassName  = name.WithSuffix(Suffixes.ReadOrThrowUnions),
                ReadManyUnionsClassName     = name.WithSuffix(Suffixes.ReadManyUnions),
                QueryBuilderClassName       = name.WithSuffix(Suffixes.QueryBuilder),
                QueryBuilderUnionsClassName = name.WithSuffix(Suffixes.QueryBuilderUnions),
                QueryClassName              = name.WithSuffix(Suffixes.Query),
                QueryUnionsClassName        = name.WithSuffix(Suffixes.QueryUnions),
                ReadOrCreateClassName       = name.WithSuffix(Suffixes.ReadOrCreate),
                CreateClassName             = name.WithSuffix(Suffixes.Create),
                BatchClassName              = name.WithSuffix(Suffixes.Batch),
                GetPkModel                  = methodModel
            };
            partitionPlan.BatchHandlersClassNameArgument = partitionPlan.BatchHandlersClassName.ToArgumentName();
            partitionPlan.ClassNameArgument             = partitionPlan.ClassName.ToArgumentName();
            databasePlan.PartitionPlansByName[name]     = partitionPlan;
            partitionPlan.QueryBuilderClassNameArgument = partitionPlan.QueryBuilderClassName.ToArgumentName();

            outputModel.ValidateNames(
                methodModel.MethodSymbol,
                partitionPlan.Name,
                partitionPlan.PluralName,
                partitionPlan.ClassName,
                partitionPlan.BatchHandlersClassName,
                partitionPlan.CreateOrReplaceClassName,
                partitionPlan.ReadClassName,
                partitionPlan.ReadOrThrowClassName,
                partitionPlan.ReadManyClassName,
                partitionPlan.QueryBuilderClassName,
                partitionPlan.QueryClassName,
                partitionPlan.ReadOrCreateClassName,
                partitionPlan.CreateClassName,
                partitionPlan.BatchClassName,
                partitionPlan.QueryBuilderUnionsClassName,
                partitionPlan.QueryUnionsClassName,
                partitionPlan.ReadManyUnionsClassName,
                partitionPlan.ReadOrThrowUnionsClassName,
                partitionPlan.ReadUnionsClassName);

            outputModel.ValidateIdentifiers(
                methodModel.MethodSymbol,
                partitionPlan.ClassNameArgument,
                partitionPlan.BatchHandlersClassNameArgument,
                partitionPlan.QueryBuilderClassNameArgument);
        }
    }
Exemple #13
0
    static void Initialize(OutputModel outputModel, ClassModel?classModel, OutputPlan outputPlan, DbAttributeModel dbAttribute)
    {
        var name   = dbAttribute.Name ?? "";
        var symbol = classModel?.ClassSymbol as ISymbol ?? outputModel.Compilation.Assembly;

        if (outputPlan.DatabasePlansByName.TryGetValue(name, out var plan))
        {
            if (dbAttribute.Namespace.NullIfEmpty() is not null &&
                dbAttribute.Namespace != plan.Namespace)
            {
                if (plan.IsDefaultNamespace)
                {
                    plan.IsDefaultNamespace = false;
                    plan.Namespace          = dbAttribute.Namespace !;
                    plan.ClassModel         = classModel;
                }
                else
                {
                    outputModel.Report(Diagnostics.Errors.DbMultipleNamespace, symbol);
                }
            }
        }
        else
        {
            plan = new DatabasePlan
            {
                Namespace                    = dbAttribute.Namespace.NullIfEmpty() !,
                Name                         = name,
                ClassModel                   = classModel,
                DbClassName                  = name.WithSuffix(Suffixes.Database),
                PartitionsClassName          = name.WithSuffix(Suffixes.Partitions),
                QueryBuilderClassName        = name.WithSuffix(Suffixes.QueryBuilder),
                QueryBuilderUnionsClassName  = name.WithSuffix(Suffixes.QueryBuilderUnions),
                QueryClassName               = name.WithSuffix(Suffixes.Query),
                QueryUnionsClassName         = name.WithSuffix(Suffixes.QueryUnions),
                ReadClassName                = name.WithSuffix(Suffixes.Read),
                SerializerClassName          = name.WithSuffix(Suffixes.Serializer),
                ConverterClassName           = name.WithSuffix(Suffixes.Converter),
                TypesClassName               = name.WithSuffix(Suffixes.Types),
                BatchHandlersClassName       = name.WithSuffix(Suffixes.BatchHandlers),
                ChangeFeedProcessorClassName = name.WithSuffix(Suffixes.ChangeFeedProcessor)
            };
            plan.BatchHandlersArgumentName = plan.BatchHandlersClassName.ToArgumentName();
            plan.IsDefaultNamespace        = plan.Namespace is null;
            plan.Namespace ??= classModel?.ClassSymbol.ContainingNamespace?.ToDisplayString() ?? outputModel.Compilation.Assembly.Name.NullIfEmpty() ?? "Cosmogenesis.Generated";
            plan.DbClassNameArgument           = plan.DbClassName.ToArgumentName();
            plan.QueryBuilderClassNameArgument = plan.QueryBuilderClassName.ToArgumentName();
            outputModel.ValidateNames(
                symbol,
                plan.Name,
                plan.DbClassName,
                plan.PartitionsClassName,
                plan.QueryBuilderClassName,
                plan.QueryClassName,
                plan.ReadClassName,
                plan.SerializerClassName,
                plan.ConverterClassName,
                plan.TypesClassName,
                plan.BatchHandlersClassName,
                plan.ChangeFeedProcessorClassName,
                plan.QueryBuilderUnionsClassName,
                plan.QueryUnionsClassName);
            outputModel.ValidateIdentifiers(
                symbol,
                plan.BatchHandlersArgumentName,
                plan.DbClassNameArgument,
                plan.QueryBuilderClassNameArgument);
            plan.Namespace.ValidateNamespace(outputModel, symbol);
            outputPlan.DatabasePlansByName[name] = plan;
        }
        if (classModel is not null)
        {
            outputPlan.DatabasePlansByClass[classModel].Add(plan);
        }
    }
}
Exemple #14
0
    public static void AddDocuments(OutputModel outputModel, OutputPlan outputPlan)
    {
        outputModel.CancellationToken.ThrowIfCancellationRequested();
        if (!outputModel.CanGenerate)
        {
            return;
        }

        foreach (var kvp in outputPlan.DatabasePlansByClass)
        {
            var classModel    = kvp.Key;
            var databasePlans = kvp.Value;
            if (classModel.IsDbDoc && !classModel.ClassSymbol.IsAbstract)
            {
                if (databasePlans.Count == 0)
                {
                    outputModel.Report(Diagnostics.Errors.NoDatabase, classModel.ClassSymbol);
                }
                else if (classModel.PartitionAttribute is null)
                {
                    outputModel.Report(Diagnostics.Warnings.DbDocWithoutPartition, classModel.ClassSymbol, classModel.ClassSymbol.Name);
                }
                else
                {
                    if (!classModel.ClassSymbol.Constructors.Any(x => x.Parameters.IsDefaultOrEmpty && x.DeclaredAccessibility.IsAccessible()))
                    {
                        outputModel.Report(Diagnostics.Errors.ParameterlessConstructor, classModel.ClassSymbol);
                    }
                    var implicitGetIds = classModel.Methods
                                         .Where(x => x.MethodSymbol.Name == "GetId")
                                         .Where(x => x.MethodSymbol.IsStatic)
                                         .Where(x => x.MethodSymbol.ReturnType.SpecialType == SpecialType.System_String)
                                         .Where(x => x.MethodSymbol.DeclaredAccessibility.IsAccessible())
                                         .ToList();
                    if (implicitGetIds.Count == 0)
                    {
                        outputModel.Report(Diagnostics.Errors.NoGetId, classModel.ClassSymbol);
                    }
                    else if (implicitGetIds.Count > 1)
                    {
                        outputModel.Report(Diagnostics.Errors.MultipleGetId, classModel.ClassSymbol);
                    }
                    else
                    {
                        var getId         = implicitGetIds[0];
                        var partitionName = classModel.PartitionAttribute.Name;
                        var name          = classModel.ClassSymbol.Name.WithoutSuffix(Suffixes.Doc).WithoutSuffix(Suffixes.Document);
                        var type          = classModel.DocTypeAttribute?.Name.NullIfEmpty() ?? name;
                        type.ValidateDocType(outputModel, classModel.ClassSymbol);
                        foreach (var databasePlan in databasePlans)
                        {
                            if (!databasePlan.PartitionPlansByName.TryGetValue(partitionName, out var partitionPlan))
                            {
                                outputModel.Report(Diagnostics.Errors.NoGetPk, classModel.ClassSymbol);
                            }
                            else
                            {
                                var documentPlan = new DocumentPlan
                                {
                                    DocType           = type,
                                    FullTypeName      = classModel.ClassSymbol.ToDisplayString(),
                                    ClassModel        = classModel,
                                    IsMutable         = classModel.MutableAttribute is not null,
                                    IsTransient       = classModel.TransientAttribute is not null,
                                    ClassName         = name,
                                    ClassNameArgument = name.ToArgumentName(),
                                    PluralName        = name.Pluralize(),
                                    ConstDocType      = $"{databasePlan.Namespace}.{databasePlan.TypesClassName}.{partitionPlan.ClassName}.{name}",
                                    GetIdPlan         = new GetPkIdPlan
                                    {
                                        FullMethodName = $"{getId.MethodSymbol.ContainingType.ToDisplayString()}.{getId.MethodSymbol.Name}",
                                        Arguments      = getId
                                                         .MethodSymbol
                                                         .Parameters
                                                         .Select(x => new GetPkIdPlan.Argument
                                        {
                                            ArgumentName = x.Name.ToArgumentName(),
                                            FullTypeName = x.Type.ToDisplayString()
                                        })
                                                         .ToList()
                                    }
                                };

                                partitionPlan.Documents.Add(documentPlan);

                                outputModel.ValidateNames(
                                    classModel.ClassSymbol,
                                    documentPlan.ClassName,
                                    documentPlan.PluralName);
                                outputModel.ValidateIdentifiers(
                                    classModel.ClassSymbol,
                                    documentPlan.ClassNameArgument);

                                PropertyPlanBuilder.AddProperties(outputModel, classModel, documentPlan);

                                documentPlan.GetIdPlan = GetPkIdPlanBuilder.Build(outputModel, getId.MethodSymbol, documentPlan.PropertiesByName, documentPlan.PropertiesByArgumentName);
                                if (partitionPlan.GetPkPlan is null)
                                {
                                    partitionPlan.GetPkPlan = GetPkIdPlanBuilder.Build(outputModel, partitionPlan.GetPkModel.MethodSymbol, documentPlan.PropertiesByName, documentPlan.PropertiesByArgumentName);
                                }
                                else
                                {
                                    foreach (var arg in partitionPlan.GetPkPlan.Arguments)
                                    {
                                        if (arg.PropertyName is not null && !documentPlan.PropertiesByName.ContainsKey(arg.PropertyName))
                                        {
                                            outputModel.Report(Diagnostics.Errors.PropertyResolvePkIdConsistency, partitionPlan.GetPkModel.MethodSymbol, arg.ArgumentName, arg.PropertyName);
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}