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); } }
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); } } }
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); } }
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); } } } } } }
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); } } }
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); } } }
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); } } } } }
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); } }
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); } } }
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); } } } } } } } } } } }